|
| 1 | +# csv_importer.py |
| 2 | + |
| 3 | +import csv |
| 4 | +import pathlib |
| 5 | +import re |
| 6 | +import sys |
| 7 | +from importlib.machinery import ModuleSpec |
| 8 | + |
| 9 | + |
| 10 | +class CsvImporter: |
| 11 | + def __init__(self, csv_path): |
| 12 | + """Store path to CSV file""" |
| 13 | + self.csv_path = csv_path |
| 14 | + |
| 15 | + @classmethod |
| 16 | + def find_spec(cls, name, path, target=None): |
| 17 | + """Look for CSV file""" |
| 18 | + package, _, module_name = name.rpartition(".") |
| 19 | + csv_file_name = f"{module_name}.csv" |
| 20 | + directories = sys.path if path is None else path |
| 21 | + for directory in directories: |
| 22 | + csv_path = pathlib.Path(directory) / csv_file_name |
| 23 | + if csv_path.exists(): |
| 24 | + return ModuleSpec(name, cls(csv_path)) |
| 25 | + |
| 26 | + def create_module(self, spec): |
| 27 | + """Returning None uses the standard machinery for creating modules""" |
| 28 | + return None |
| 29 | + |
| 30 | + def exec_module(self, module): |
| 31 | + """Executing the module means reading the CSV file""" |
| 32 | + # Read CSV data and store as a list of rows |
| 33 | + with self.csv_path.open() as fid: |
| 34 | + rows = csv.DictReader(fid) |
| 35 | + data = list(rows) |
| 36 | + fieldnames = tuple(_identifier(f) for f in rows.fieldnames) |
| 37 | + |
| 38 | + # Create a dict with each field |
| 39 | + values = zip(*(row.values() for row in data)) |
| 40 | + fields = dict(zip(fieldnames, values)) |
| 41 | + |
| 42 | + # Add the data to the module |
| 43 | + module.__dict__.update(fields) |
| 44 | + module.__dict__["data"] = data |
| 45 | + module.__dict__["fieldnames"] = fieldnames |
| 46 | + module.__file__ = str(self.csv_path) |
| 47 | + |
| 48 | + def __repr__(self): |
| 49 | + """Nice representation of the class""" |
| 50 | + return f"{self.__class__.__name__}({str(self.csv_path)!r})" |
| 51 | + |
| 52 | + |
| 53 | +def _identifier(var_str): |
| 54 | + """Create a valid identifier from a string |
| 55 | +
|
| 56 | + See https://stackoverflow.com/a/3305731 |
| 57 | + """ |
| 58 | + return re.sub(r"\W|^(?=\d)", "_", var_str) |
| 59 | + |
| 60 | + |
| 61 | +# Add the CSV importer at the end of the list of finders |
| 62 | +sys.meta_path.append(CsvImporter) |
0 commit comments