diff options
| author | Claude Paroz <claude@2xlibre.net> | 2019-11-11 20:46:28 +0100 |
|---|---|---|
| committer | Hugo van Kemenade <hugovk@users.noreply.github.com> | 2019-11-11 21:46:28 +0200 |
| commit | f61b8d8926535953da95cdd61fd584c4f644f8e7 (patch) | |
| tree | 78a7151b2a66c3a75761e758496adf11e156eeea /src | |
| parent | 22a193dafb3f3fbcff16ddba0b1791ee2c270e9d (diff) | |
| download | tablib-f61b8d8926535953da95cdd61fd584c4f644f8e7.tar.gz | |
Fixes #422 - Allow ability to lazy-load external modules (#430)
Diffstat (limited to 'src')
| -rw-r--r-- | src/tablib/core.py | 8 | ||||
| -rw-r--r-- | src/tablib/formats/__init__.py | 106 | ||||
| -rw-r--r-- | src/tablib/formats/_csv.py | 1 |
3 files changed, 72 insertions, 43 deletions
diff --git a/src/tablib/core.py b/src/tablib/core.py index 24a3471..9eaeb61 100644 --- a/src/tablib/core.py +++ b/src/tablib/core.py @@ -236,11 +236,11 @@ class Dataset: # Internals # --------- - def _get_in_format(self, fmt, **kwargs): - return fmt.export_set(self, **kwargs) + def _get_in_format(self, fmt_key, **kwargs): + return registry.get_format(fmt_key).export_set(self, **kwargs) - def _set_in_format(self, fmt, *args, **kwargs): - return fmt.import_set(self, *args, **kwargs) + def _set_in_format(self, fmt_key, *args, **kwargs): + return registry.get_format(fmt_key).import_set(self, *args, **kwargs) def _validate(self, row=None, col=None, safety=False): """Assures size of every row in dataset is of proper proportions.""" diff --git a/src/tablib/formats/__init__.py b/src/tablib/formats/__init__.py index 9398607..5c46008 100644 --- a/src/tablib/formats/__init__.py +++ b/src/tablib/formats/__init__.py @@ -2,23 +2,14 @@ """ from collections import OrderedDict from functools import partialmethod +from importlib import import_module from importlib.util import find_spec from tablib.exceptions import UnsupportedFormat from ._csv import CSVFormat -from ._dbf import DBFFormat -from ._df import DataFrameFormat -from ._html import HTMLFormat -from ._jira import JIRAFormat from ._json import JSONFormat -from ._latex import LATEXFormat -from ._ods import ODSFormat -from ._rst import ReSTFormat from ._tsv import TSVFormat -from ._xls import XLSFormat -from ._xlsx import XLSXFormat -from ._yaml import YAMLFormat uninstalled_format_messages = { 'df': ( @@ -48,68 +39,105 @@ uninstalled_format_messages = { } +def load_format_class(dotted_path): + try: + module_path, class_name = dotted_path.rsplit('.', 1) + return getattr(import_module(module_path), class_name) + except (ValueError, AttributeError) as err: + raise ImportError("Unable to load format class '{}' ({})".format(dotted_path, err)) + + +class FormatDescriptorBase: + def __init__(self, key, format_or_path): + self.key = key + self._format_path = None + if isinstance(format_or_path, str): + self._format = None + self._format_path = format_or_path + else: + self._format = format_or_path + + def ensure_format_loaded(self): + if self._format is None: + self._format = load_format_class(self._format_path) + + +class ImportExportBookDescriptor(FormatDescriptorBase): + def __get__(self, obj, cls, **kwargs): + self.ensure_format_loaded() + return self._format.export_book(obj, **kwargs) + + def __set__(self, obj, val): + self.ensure_format_loaded() + return self._format.import_book(obj, val) + + +class ImportExportSetDescriptor(FormatDescriptorBase): + def __get__(self, obj, cls, **kwargs): + self.ensure_format_loaded() + return self._format.export_set(obj, **kwargs) + + def __set__(self, obj, val): + self.ensure_format_loaded() + return self._format.import_set(obj, val) + + class Registry: _formats = OrderedDict() - def register(self, key, format_): + def register(self, key, format_or_path): from tablib.core import Databook, Dataset # Create Databook.<format> read or read/write properties - try: - setattr(Databook, format_.title, property(format_.export_book, format_.import_book)) - except AttributeError: - try: - setattr(Databook, format_.title, property(format_.export_book)) - except AttributeError: - pass + setattr(Databook, key, ImportExportBookDescriptor(key, format_or_path)) # Create Dataset.<format> read or read/write properties, # and Dataset.get_<format>/set_<format> methods. + setattr(Dataset, key, ImportExportSetDescriptor(key, format_or_path)) try: - try: - setattr(Dataset, format_.title, property(format_.export_set, format_.import_set)) - setattr(Dataset, 'get_%s' % format_.title, partialmethod(Dataset._get_in_format, format_)) - setattr(Dataset, 'set_%s' % format_.title, partialmethod(Dataset._set_in_format, format_)) - except AttributeError: - setattr(Dataset, format_.title, property(format_.export_set)) - setattr(Dataset, 'get_%s' % format_.title, partialmethod(Dataset._get_in_format, format_)) - + setattr(Dataset, 'get_%s' % key, partialmethod(Dataset._get_in_format, key)) + setattr(Dataset, 'set_%s' % key, partialmethod(Dataset._set_in_format, key)) except AttributeError: - raise Exception("Your format class should minimally implement the export_set interface.") + setattr(Dataset, 'get_%s' % key, partialmethod(Dataset._get_in_format, key)) - self._formats[key] = format_ + self._formats[key] = format_or_path def register_builtins(self): # Registration ordering matters for autodetection. self.register('json', JSONFormat()) # xlsx before as xls (xlrd) can also read xlsx if find_spec('openpyxl'): - self.register('xlsx', XLSXFormat()) + self.register('xlsx', 'tablib.formats._xlsx.XLSXFormat') if find_spec('xlrd') and find_spec('xlwt'): - self.register('xls', XLSFormat()) + self.register('xls', 'tablib.formats._xls.XLSFormat') if find_spec('yaml'): - self.register('yaml', YAMLFormat()) + self.register('yaml', 'tablib.formats._yaml.YAMLFormat') self.register('csv', CSVFormat()) self.register('tsv', TSVFormat()) if find_spec('odf'): - self.register('ods', ODSFormat()) - self.register('dbf', DBFFormat()) + self.register('ods', 'tablib.formats._ods.ODSFormat') + self.register('dbf', 'tablib.formats._dbf.DBFFormat') if find_spec('MarkupPy'): - self.register('html', HTMLFormat()) - self.register('jira', JIRAFormat()) - self.register('latex', LATEXFormat()) + self.register('html', 'tablib.formats._html.HTMLFormat') + self.register('jira', 'tablib.formats._jira.JIRAFormat') + self.register('latex', 'tablib.formats._latex.LATEXFormat') if find_spec('pandas'): - self.register('df', DataFrameFormat()) - self.register('rst', ReSTFormat()) + self.register('df', 'tablib.formats._df.DataFrameFormat') + self.register('rst', 'tablib.formats._rst.ReSTFormat') def formats(self): - yield from self._formats.values() + for key, frm in self._formats.items(): + if isinstance(frm, str): + self._formats[key] = load_format_class(frm) + yield self._formats[key] def get_format(self, key): if key not in self._formats: if key in uninstalled_format_messages: raise UnsupportedFormat(uninstalled_format_messages[key]) raise UnsupportedFormat("Tablib has no format '%s' or it is not registered." % key) + if isinstance(self._formats[key], str): + self._formats[key] = load_format_class(self._formats[key]) return self._formats[key] diff --git a/src/tablib/formats/_csv.py b/src/tablib/formats/_csv.py index 5454bd9..cb209fc 100644 --- a/src/tablib/formats/_csv.py +++ b/src/tablib/formats/_csv.py @@ -48,6 +48,7 @@ class CSVFormat: elif row: dset.append(row) + @classmethod def detect(cls, stream, delimiter=None): """Returns True if given stream is valid CSV.""" try: |
