diff --git a/HISTORY.md b/HISTORY.md
index 7d26335..f3a1a34 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -5,6 +5,8 @@
### Breaking changes
- Dropped Python 2 support
+- Dependencies are now all optional. To install `tablib` as before with all
+ possible supported formats, run `pip install tablib[all]`
### Improvements
diff --git a/docs/formats.rst b/docs/formats.rst
index 6dac434..28ed5f9 100644
--- a/docs/formats.rst
+++ b/docs/formats.rst
@@ -51,7 +51,8 @@ Import/export using the dBASE_ format.
df (DataFrame)
==============
-Import/export using the pandas_ DataFrame format.
+Import/export using the pandas_ DataFrame format. This format is optional,
+install Tablib with ``pip install tablib[pandas]`` to make the format available.
.. _pandas: https://pandas.pydata.org/
@@ -62,6 +63,9 @@ The ``html`` format is currently export-only. The exports produce an HTML page
with the data in a ``
``. If headers have been set, they will be used as
table headers.
+This format is optional, install Tablib with ``pip install tablib[html]`` to
+make the format available.
+
jira
====
@@ -97,6 +101,9 @@ ods
Export data in OpenDocument Spreadsheet format. The ``ods`` format is currently
export-only.
+This format is optional, install Tablib with ``pip install tablib[ods]`` to
+make the format available.
+
.. admonition:: Binary Warning
:class:`Dataset.ods` contains binary data, so make sure to write in binary mode::
@@ -145,6 +152,9 @@ xls
Import/export data in Legacy Excel Spreadsheet representation.
+This format is optional, install Tablib with ``pip install tablib[xls]`` to
+make the format available.
+
.. note::
XLS files are limited to a maximum of 65,000 rows. Use xlsx_ to avoid this
@@ -162,6 +172,9 @@ xlsx
Import/export data in Excel 07+ Spreadsheet representation.
+This format is optional, install Tablib with ``pip install tablib[xlsx]`` to
+make the format available.
+
.. admonition:: Binary Warning
The `xlsx` file format is binary, so make sure to write in binary mode::
@@ -179,4 +192,7 @@ returned instead.
Import assumes (for now) that headers exist.
+This format is optional, install Tablib with ``pip install tablib[yaml]`` to
+make the format available.
+
.. _YAML: https://yaml.org
diff --git a/docs/install.rst b/docs/install.rst
index b42fe07..62e486d 100644
--- a/docs/install.rst
+++ b/docs/install.rst
@@ -19,8 +19,26 @@ Of course, the recommended way to install Tablib is with `pip =2.4.0',
- 'markuppy',
- 'xlrd',
- 'xlwt',
- 'pyyaml',
-]
-
-
setup(
name='tablib',
use_scm_version=True,
@@ -42,8 +32,13 @@ setup(
'Programming Language :: Python :: 3.8',
],
python_requires='>=3.5',
- install_requires=install,
extras_require={
+ 'all': ['markuppy', 'odfpy', 'openpyxl>=2.4.0', 'pandas', 'pyyaml', 'xlrd', 'xlwt'],
+ 'html': ['markuppy'],
+ 'ods': ['odfpy'],
'pandas': ['pandas'],
+ 'xls': ['xlrd', 'xlwt'],
+ 'xlsx': ['openpyxl>=2.4.0'],
+ 'yaml': ['pyyaml'],
},
)
diff --git a/src/tablib/core.py b/src/tablib/core.py
index d17e1e8..9fcbebd 100644
--- a/src/tablib/core.py
+++ b/src/tablib/core.py
@@ -13,6 +13,13 @@ from copy import copy
from operator import itemgetter
from tablib import formats
+from tablib.exceptions import (
+ HeadersNeeded,
+ InvalidDatasetIndex,
+ InvalidDatasetType,
+ InvalidDimensions,
+ UnsupportedFormat,
+)
from tablib.formats import registry
__title__ = 'tablib'
@@ -903,24 +910,4 @@ def import_book(stream, format=None, **kwargs):
return Databook().load(stream, format, **kwargs)
-class InvalidDatasetType(Exception):
- "Only Datasets can be added to a DataBook"
-
-
-class InvalidDimensions(Exception):
- "Invalid size"
-
-
-class InvalidDatasetIndex(Exception):
- "Outside of Dataset size"
-
-
-class HeadersNeeded(Exception):
- "Header parameter must be given when appending a column in this Dataset."
-
-
-class UnsupportedFormat(NotImplementedError):
- "Format is not supported"
-
-
registry.register_builtins()
diff --git a/src/tablib/exceptions.py b/src/tablib/exceptions.py
new file mode 100644
index 0000000..dee2b29
--- /dev/null
+++ b/src/tablib/exceptions.py
@@ -0,0 +1,18 @@
+class InvalidDatasetType(Exception):
+ "Only Datasets can be added to a DataBook"
+
+
+class InvalidDimensions(Exception):
+ "Invalid size"
+
+
+class InvalidDatasetIndex(Exception):
+ "Outside of Dataset size"
+
+
+class HeadersNeeded(Exception):
+ "Header parameter must be given when appending a column in this Dataset."
+
+
+class UnsupportedFormat(NotImplementedError):
+ "Format is not supported"
diff --git a/src/tablib/formats/__init__.py b/src/tablib/formats/__init__.py
index a462ce0..c324484 100644
--- a/src/tablib/formats/__init__.py
+++ b/src/tablib/formats/__init__.py
@@ -2,6 +2,9 @@
"""
from collections import OrderedDict
from functools import partialmethod
+from importlib.util import find_spec
+
+from tablib.exceptions import UnsupportedFormat
from ._csv import CSVFormat
from ._dbf import DBFFormat
@@ -17,6 +20,33 @@ from ._xls import XLSFormat
from ._xlsx import XLSXFormat
from ._yaml import YAMLFormat
+uninstalled_format_messages = {
+ 'df': (
+ "The 'df' format is not available. You may want to install the pandas "
+ "package (or `pip install tablib[pandas]`)."
+ ),
+ 'html': (
+ "The 'html' format is not available. You may want to install the MarkupPy "
+ "package (or `pip install tablib[html]`)."
+ ),
+ 'ods': (
+ "The 'ods' format is not available. You may want to install the odfpy "
+ "package (or `pip install tablib[ods]`)."
+ ),
+ 'xls': (
+ "The 'xls' format is not available. You may want to install the xlrd and "
+ "xlwt packages (or `pip install tablib[xls]`)."
+ ),
+ 'xlsx': (
+ "The 'xlsx' format is not available. You may want to install the openpyxl "
+ "package (or `pip install tablib[xlsx]`)."
+ ),
+ 'yaml': (
+ "The 'yaml' format is not available. You may want to install the pyyaml "
+ "package (or `pip install tablib[yaml]`)."
+ ),
+}
+
class Registry:
_formats = OrderedDict()
@@ -53,17 +83,23 @@ class Registry:
# Registration ordering matters for autodetection.
self.register('json', JSONFormat())
# xlsx before as xls (xlrd) can also read xlsx
- self.register('xlsx', XLSXFormat())
- self.register('xls', XLSFormat())
- self.register('yaml', YAMLFormat())
+ if find_spec('openpyxl'):
+ self.register('xlsx', XLSXFormat())
+ if find_spec('xlrd') and find_spec('xlwt'):
+ self.register('xls', XLSFormat())
+ if find_spec('yaml'):
+ self.register('yaml', YAMLFormat())
self.register('csv', CSVFormat())
self.register('tsv', TSVFormat())
- self.register('ods', ODSFormat())
+ if find_spec('odf'):
+ self.register('ods', ODSFormat())
self.register('dbf', DBFFormat())
- self.register('html', HTMLFormat())
+ if find_spec('MarkupPy'):
+ self.register('html', HTMLFormat())
self.register('jira', JIRAFormat())
self.register('latex', LATEXFormat())
- self.register('df', DataFrameFormat())
+ if find_spec('pandas'):
+ self.register('df', DataFrameFormat())
self.register('rst', ReSTFormat())
def formats(self):
@@ -71,6 +107,10 @@ class Registry:
yield frm
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)
return self._formats[key]
diff --git a/tests/test_tablib.py b/tests/test_tablib.py
index 0a01e12..fe793df 100755
--- a/tests/test_tablib.py
+++ b/tests/test_tablib.py
@@ -12,7 +12,8 @@ from uuid import uuid4
import tablib
from MarkupPy import markup
-from tablib.core import Row, UnsupportedFormat, detect_format
+from tablib.core import Row, detect_format
+from tablib.exceptions import UnsupportedFormat
from tablib.formats import registry
@@ -49,6 +50,16 @@ class TablibTestCase(BaseTestCase):
continue
dataset.export(format_)
+ def test_unknown_format(self):
+ with self.assertRaises(UnsupportedFormat):
+ data.export('??')
+ # A known format but uninstalled
+ del registry._formats['ods']
+ msg = (r"The 'ods' format is not available. You may want to install the "
+ "odfpy package \\(or `pip install tablib\\[ods\\]`\\).")
+ with self.assertRaisesRegex(UnsupportedFormat, msg):
+ data.export('ods')
+
def test_empty_append(self):
"""Verify append() correctly adds tuple with no headers."""
new_row = (1, 2, 3)