summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorClaude Paroz <claude@2xlibre.net>2019-11-11 20:46:28 +0100
committerHugo van Kemenade <hugovk@users.noreply.github.com>2019-11-11 21:46:28 +0200
commitf61b8d8926535953da95cdd61fd584c4f644f8e7 (patch)
tree78a7151b2a66c3a75761e758496adf11e156eeea /src
parent22a193dafb3f3fbcff16ddba0b1791ee2c270e9d (diff)
downloadtablib-f61b8d8926535953da95cdd61fd584c4f644f8e7.tar.gz
Fixes #422 - Allow ability to lazy-load external modules (#430)
Diffstat (limited to 'src')
-rw-r--r--src/tablib/core.py8
-rw-r--r--src/tablib/formats/__init__.py106
-rw-r--r--src/tablib/formats/_csv.py1
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: