From 9abe3234bbe0653346e75ae7f1f5a07c95adb3e3 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 10 Nov 2019 21:04:11 +0200 Subject: [PATCH 1/8] Format with Black --- docs/conf.py | 110 +++--- setup.py | 67 ++-- src/tablib/core.py | 79 ++-- src/tablib/formats/__init__.py | 90 +++-- src/tablib/formats/_csv.py | 14 +- src/tablib/formats/_dbf.py | 16 +- src/tablib/formats/_df.py | 11 +- src/tablib/formats/_html.py | 22 +- src/tablib/formats/_jira.py | 18 +- src/tablib/formats/_json.py | 10 +- src/tablib/formats/_latex.py | 75 ++-- src/tablib/formats/_ods.py | 22 +- src/tablib/formats/_rst.py | 90 ++--- src/tablib/formats/_tsv.py | 6 +- src/tablib/formats/_xls.py | 27 +- src/tablib/formats/_xlsx.py | 24 +- src/tablib/formats/_yaml.py | 15 +- src/tablib/packages/dbfpy/dbf.py | 22 +- src/tablib/packages/dbfpy/dbfnew.py | 26 +- src/tablib/packages/dbfpy/fields.py | 79 ++-- src/tablib/packages/dbfpy/header.py | 55 ++- src/tablib/packages/dbfpy/record.py | 52 ++- src/tablib/packages/dbfpy/utils.py | 3 +- tests/test_tablib.py | 548 ++++++++++++++-------------- 24 files changed, 814 insertions(+), 667 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 8df446ed..b5da6854 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -22,50 +22,55 @@ # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode'] +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.todo", + "sphinx.ext.coverage", + "sphinx.ext.viewcode", +] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = 'Tablib' -copyright = '2019 Jazzband' +project = "Tablib" +copyright = "2019 Jazzband" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The full version, including alpha/beta/rc tags. -release = get_distribution('tablib').version +release = get_distribution("tablib").version # The short X.Y version. -version = '.'.join(release.split('.')[:2]) +version = ".".join(release.split(".")[:2]) # for example take major/minor # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True @@ -76,44 +81,44 @@ # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. # pygments_style = '' # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'alabaster' +html_theme = "alabaster" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -122,7 +127,7 @@ # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. @@ -130,23 +135,28 @@ # Custom sidebar templates, maps document names to template names. html_sidebars = { - 'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'], - '**': ['sidebarlogo.html', 'localtoc.html', 'relations.html', - 'sourcelink.html', 'searchbox.html'] + "index": ["sidebarintro.html", "sourcelink.html", "searchbox.html"], + "**": [ + "sidebarlogo.html", + "localtoc.html", + "relations.html", + "sourcelink.html", + "searchbox.html", + ], } # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. html_show_sourcelink = True @@ -155,72 +165,68 @@ html_show_sphinx = False # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'Tablibdoc' +htmlhelp_basename = "Tablibdoc" # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' +# latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' +# latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'Tablib.tex', 'Tablib Documentation', - 'Jazzband', 'manual'), + ("index", "Tablib.tex", "Tablib Documentation", "Jazzband", "manual"), ] latex_use_modindex = False latex_elements = { - 'papersize': 'a4paper', - 'pointsize': '12pt', + "papersize": "a4paper", + "pointsize": "12pt", } latex_use_parts = True # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Additional stuff for the LaTeX preamble. -#latex_preamble = '' +# latex_preamble = '' # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'tablib', 'Tablib Documentation', - ['Jazzband'], 1) -] +man_pages = [("index", "tablib", "Tablib Documentation", ["Jazzband"], 1)] diff --git a/setup.py b/setup.py index e22ba119..579085ba 100755 --- a/setup.py +++ b/setup.py @@ -3,42 +3,49 @@ from setuptools import find_packages, setup setup( - name='tablib', + name="tablib", use_scm_version=True, - setup_requires=['setuptools_scm'], - description='Format agnostic tabular data library (XLS, JSON, YAML, CSV)', - long_description=(open('README.md').read() + '\n\n' + - open('HISTORY.md').read()), + setup_requires=["setuptools_scm"], + description="Format agnostic tabular data library (XLS, JSON, YAML, CSV)", + long_description=(open("README.md").read() + "\n\n" + open("HISTORY.md").read()), long_description_content_type="text/markdown", - author='Kenneth Reitz', - author_email='me@kennethreitz.org', - maintainer='Jazzband', - maintainer_email='roadies@jazzband.co', - url='https://tablib.readthedocs.io', + author="Kenneth Reitz", + author_email="me@kennethreitz.org", + maintainer="Jazzband", + maintainer_email="roadies@jazzband.co", + url="https://tablib.readthedocs.io", packages=find_packages(where="src"), package_dir={"": "src"}, - license='MIT', + license="MIT", classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'Natural Language :: English', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Natural Language :: English", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", ], - python_requires='>=3.5', + python_requires=">=3.5", 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'], + "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 24a34717..28c9750b 100644 --- a/src/tablib/core.py +++ b/src/tablib/core.py @@ -22,17 +22,17 @@ ) from tablib.formats import registry -__title__ = 'tablib' -__author__ = 'Kenneth Reitz' -__license__ = 'MIT' -__copyright__ = 'Copyright 2017 Kenneth Reitz. 2019 Jazzband.' -__docformat__ = 'restructuredtext' +__title__ = "tablib" +__author__ = "Kenneth Reitz" +__license__ = "MIT" +__copyright__ = "Copyright 2017 Kenneth Reitz. 2019 Jazzband." +__docformat__ = "restructuredtext" class Row: """Internal Row object. Mainly used for filtering.""" - __slots__ = ['_row', 'tags'] + __slots__ = ["_row", "tags"] def __init__(self, row=list(), tags=list()): self._row = list(row) @@ -83,7 +83,7 @@ def insert(self, index, value): self._row.insert(index, value) def __contains__(self, item): - return (item in self._row) + return item in self._row @property def tuple(self): @@ -101,7 +101,7 @@ def has_tag(self, tag): if tag is None: return False elif isinstance(tag, str): - return (tag in self.tags) + return tag in self.tags else: return bool(len(set(tag) & set(self.tags))) @@ -163,9 +163,9 @@ def __init__(self, *args, **kwargs): # (column, callback) tuples self._formatters = [] - self.headers = kwargs.get('headers') + self.headers = kwargs.get("headers") - self.title = kwargs.get('title') + self.title = kwargs.get("title") def __len__(self): return self.height @@ -207,9 +207,9 @@ def __delitem__(self, key): def __repr__(self): try: - return '<%s dataset>' % (self.title.lower()) + return "<%s dataset>" % (self.title.lower()) except AttributeError: - return '' + return "" def __str__(self): result = [] @@ -226,11 +226,11 @@ def __str__(self): # delimiter between header and data if self.__headers: - result.insert(1, ['-' * length for length in field_lens]) + result.insert(1, ["-" * length for length in field_lens]) - format_string = '|'.join('{%s:%s}' % item for item in enumerate(field_lens)) + format_string = "|".join("{%s:%s}" % item for item in enumerate(field_lens)) - return '\n'.join(format_string.format(*row) for row in result) + return "\n".join(format_string.format(*row) for row in result) # --------- # Internals @@ -287,7 +287,9 @@ def _package(self, dicts=True, ordered=True): if self.headers: if dicts: - data = [dict_pack(list(zip(self.headers, data_row))) for data_row in _data] + data = [ + dict_pack(list(zip(self.headers, data_row))) for data_row in _data + ] else: data = [list(self.headers)] + list(_data) else: @@ -371,7 +373,7 @@ def _clean_col(self, col): else: header = [] - if len(col) == 1 and hasattr(col[0], '__call__'): + if len(col) == 1 and hasattr(col[0], "__call__"): col = list(map(col[0], self._data)) col = tuple(header + col) @@ -410,11 +412,11 @@ def load(self, in_stream, format=None, **kwargs): format = detect_format(in_stream) fmt = registry.get_format(format) - if not hasattr(fmt, 'import_set'): - raise UnsupportedFormat('Format {} cannot be imported.'.format(format)) - + if not hasattr(fmt, "import_set"): + raise UnsupportedFormat("Format {} cannot be imported.".format(format)) + if not import_set: - raise UnsupportedFormat('Format {} cannot be imported.'.format(format)) + raise UnsupportedFormat("Format {} cannot be imported.".format(format)) fmt.import_set(self, in_stream, **kwargs) return self @@ -426,8 +428,8 @@ def export(self, format, **kwargs): :param \\*\\*kwargs: (optional) custom configuration to the format `export_set`. """ fmt = registry.get_format(format) - if not hasattr(fmt, 'export_set'): - raise UnsupportedFormat('Format {} cannot be exported.'.format(format)) + if not hasattr(fmt, "export_set"): + raise UnsupportedFormat("Format {} cannot be exported.".format(format)) return fmt.export_set(self, **kwargs) @@ -534,7 +536,7 @@ def insert_col(self, index, col=None, header=None): col = [] # Callable Columns... - if hasattr(col, '__call__'): + if hasattr(col, "__call__"): col = list(map(col, self._data)) col = self._clean_col(col) @@ -574,13 +576,13 @@ def lpush_col(self, col, header=None): self.insert_col(0, col, header=header) - def insert_separator(self, index, text='-'): + def insert_separator(self, index, text="-"): """Adds a separator to :class:`Dataset` at given index.""" sep = (index, text) self._separators.append(sep) - def append_separator(self, text='-'): + def append_separator(self, text="-"): """Adds a :ref:`separator ` to the :class:`Dataset`.""" # change offsets if headers are or aren't defined @@ -763,7 +765,11 @@ def remove_duplicates(self): """Removes all duplicate rows from the :class:`Dataset` object while maintaining the original order.""" seen = set() - self._data[:] = [row for row in self._data if not (tuple(row) in seen or seen.add(tuple(row)))] + self._data[:] = [ + row + for row in self._data + if not (tuple(row) in seen or seen.add(tuple(row))) + ] def wipe(self): """Removes all content and headers from the :class:`Dataset` object.""" @@ -819,9 +825,9 @@ def __init__(self, sets=None): def __repr__(self): try: - return '<%s databook>' % (self.title.lower()) + return "<%s databook>" % (self.title.lower()) except AttributeError: - return '' + return "" def wipe(self): """Removes all :class:`Dataset` objects from the :class:`Databook`.""" @@ -847,10 +853,9 @@ def _package(self, ordered=True): dict_pack = dict for dset in self._datasets: - collector.append(dict_pack( - title=dset.title, - data=dset._package(ordered=ordered) - )) + collector.append( + dict_pack(title=dset.title, data=dset._package(ordered=ordered)) + ) return collector @property @@ -869,8 +874,8 @@ def load(self, in_stream, format, **kwargs): format = detect_format(in_stream) fmt = registry.get_format(format) - if not hasattr(fmt, 'import_book'): - raise UnsupportedFormat('Format {} cannot be loaded.'.format(format)) + if not hasattr(fmt, "import_book"): + raise UnsupportedFormat("Format {} cannot be loaded.".format(format)) fmt.import_book(self, in_stream, **kwargs) return self @@ -882,8 +887,8 @@ def export(self, format, **kwargs): :param \\*\\*kwargs: (optional) custom configuration to the format `export_book`. """ fmt = registry.get_format(format) - if not hasattr(fmt, 'export_book'): - raise UnsupportedFormat('Format {} cannot be exported.'.format(format)) + if not hasattr(fmt, "export_book"): + raise UnsupportedFormat("Format {} cannot be exported.".format(format)) return fmt.export_book(self, **kwargs) diff --git a/src/tablib/formats/__init__.py b/src/tablib/formats/__init__.py index 93986076..fdf42150 100644 --- a/src/tablib/formats/__init__.py +++ b/src/tablib/formats/__init__.py @@ -21,27 +21,27 @@ from ._yaml import YAMLFormat uninstalled_format_messages = { - 'df': ( + "df": ( "The 'df' format is not available. You may want to install the pandas " "package (or `pip install tablib[pandas]`)." ), - 'html': ( + "html": ( "The 'html' format is not available. You may want to install the MarkupPy " "package (or `pip install tablib[html]`)." ), - 'ods': ( + "ods": ( "The 'ods' format is not available. You may want to install the odfpy " "package (or `pip install tablib[ods]`)." ), - 'xls': ( + "xls": ( "The 'xls' format is not available. You may want to install the xlrd and " "xlwt packages (or `pip install tablib[xls]`)." ), - 'xlsx': ( + "xlsx": ( "The 'xlsx' format is not available. You may want to install the openpyxl " "package (or `pip install tablib[xlsx]`)." ), - 'yaml': ( + "yaml": ( "The 'yaml' format is not available. You may want to install the pyyaml " "package (or `pip install tablib[yaml]`)." ), @@ -54,9 +54,13 @@ class Registry: def register(self, key, format_): from tablib.core import Databook, Dataset - # Create Databook. read or read/write properties + # Create Databook. read or read/write properties try: - setattr(Databook, format_.title, property(format_.export_book, format_.import_book)) + setattr( + Databook, + format_.title, + property(format_.export_book, format_.import_book), + ) except AttributeError: try: setattr(Databook, format_.title, property(format_.export_book)) @@ -67,40 +71,58 @@ def register(self, key, format_): # and Dataset.get_/set_ methods. 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_)) + 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" % format_.title, + partialmethod(Dataset._get_in_format, format_), + ) except AttributeError: - raise Exception("Your format class should minimally implement the export_set interface.") + raise Exception( + "Your format class should minimally implement the export_set interface." + ) self._formats[key] = format_ def register_builtins(self): # Registration ordering matters for autodetection. - self.register('json', JSONFormat()) + self.register("json", JSONFormat()) # xlsx before as xls (xlrd) can also read xlsx - 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()) - if find_spec('odf'): - self.register('ods', ODSFormat()) - self.register('dbf', DBFFormat()) - if find_spec('MarkupPy'): - self.register('html', HTMLFormat()) - self.register('jira', JIRAFormat()) - self.register('latex', LATEXFormat()) - if find_spec('pandas'): - self.register('df', DataFrameFormat()) - self.register('rst', ReSTFormat()) + 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()) + if find_spec("odf"): + self.register("ods", ODSFormat()) + self.register("dbf", DBFFormat()) + if find_spec("MarkupPy"): + self.register("html", HTMLFormat()) + self.register("jira", JIRAFormat()) + self.register("latex", LATEXFormat()) + if find_spec("pandas"): + self.register("df", DataFrameFormat()) + self.register("rst", ReSTFormat()) def formats(self): yield from self._formats.values() @@ -109,7 +131,9 @@ 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) + raise UnsupportedFormat( + "Tablib has no format '%s' or it is not registered." % key + ) return self._formats[key] diff --git a/src/tablib/formats/_csv.py b/src/tablib/formats/_csv.py index 5454bd9f..b25701d1 100644 --- a/src/tablib/formats/_csv.py +++ b/src/tablib/formats/_csv.py @@ -6,17 +6,17 @@ class CSVFormat: - title = 'csv' - extensions = ('csv',) + title = "csv" + extensions = ("csv",) - DEFAULT_DELIMITER = ',' + DEFAULT_DELIMITER = "," @classmethod def export_stream_set(cls, dataset, **kwargs): """Returns CSV representation of Dataset as file-like.""" stream = StringIO() - kwargs.setdefault('delimiter', cls.DEFAULT_DELIMITER) + kwargs.setdefault("delimiter", cls.DEFAULT_DELIMITER) _csv = csv.writer(stream, **kwargs) @@ -38,7 +38,7 @@ def import_set(cls, dset, in_stream, headers=True, **kwargs): dset.wipe() - kwargs.setdefault('delimiter', cls.DEFAULT_DELIMITER) + kwargs.setdefault("delimiter", cls.DEFAULT_DELIMITER) rows = csv.reader(StringIO(in_stream), **kwargs) for i, row in enumerate(rows): @@ -51,7 +51,9 @@ def import_set(cls, dset, in_stream, headers=True, **kwargs): def detect(cls, stream, delimiter=None): """Returns True if given stream is valid CSV.""" try: - csv.Sniffer().sniff(stream[:1024], delimiters=delimiter or cls.DEFAULT_DELIMITER) + csv.Sniffer().sniff( + stream[:1024], delimiters=delimiter or cls.DEFAULT_DELIMITER + ) return True except Exception: return False diff --git a/src/tablib/formats/_dbf.py b/src/tablib/formats/_dbf.py index 4ff16048..a241e9e1 100644 --- a/src/tablib/formats/_dbf.py +++ b/src/tablib/formats/_dbf.py @@ -9,10 +9,10 @@ class DBFFormat: - title = 'dbf' - extensions = ('csv',) + title = "dbf" + extensions = ("csv",) - DEFAULT_ENCODING = 'utf-8' + DEFAULT_ENCODING = "utf-8" @classmethod def export_set(cls, dataset): @@ -24,9 +24,9 @@ def export_set(cls, dataset): first_row = dataset[0] for fieldname, field_value in zip(dataset.headers, first_row): if type(field_value) in [int, float]: - new_dbf.add_field(fieldname, 'N', 10, 8) + new_dbf.add_field(fieldname, "N", 10, 8) else: - new_dbf.add_field(fieldname, 'C', 80) + new_dbf.add_field(fieldname, "C", 80) new_dbf.write(temp_uri) @@ -38,7 +38,7 @@ def export_set(cls, dataset): record.store() dbf_file.close() - dbf_stream = open(temp_uri, 'rb') + dbf_stream = open(temp_uri, "rb") stream = io.BytesIO(dbf_stream.read()) dbf_stream.close() os.close(temp_file) @@ -59,10 +59,10 @@ def import_set(cls, dset, in_stream, headers=True): @classmethod def detect(cls, stream): """Returns True if the given stream is valid DBF""" - #_dbf = dbf.Table(StringIO(stream)) + # _dbf = dbf.Table(StringIO(stream)) try: if type(stream) is not bytes: - stream = bytes(stream, 'utf-8') + stream = bytes(stream, "utf-8") _dbf = dbf.Dbf(io.BytesIO(stream), readOnly=True) return True except Exception: diff --git a/src/tablib/formats/_df.py b/src/tablib/formats/_df.py index b4cfa118..c188f875 100644 --- a/src/tablib/formats/_df.py +++ b/src/tablib/formats/_df.py @@ -8,8 +8,8 @@ class DataFrameFormat: - title = 'df' - extensions = ('df',) + title = "df" + extensions = ("df",) @classmethod def detect(cls, stream): @@ -27,8 +27,9 @@ def export_set(cls, dset, index=None): """Returns DataFrame representation of DataBook.""" if DataFrame is None: raise NotImplementedError( - 'DataFrame Format requires `pandas` to be installed.' - ' Try `pip install tablib[pandas]`.') + "DataFrame Format requires `pandas` to be installed." + " Try `pip install tablib[pandas]`." + ) dataframe = DataFrame(dset.dict, columns=dset.headers) return dataframe @@ -36,4 +37,4 @@ def export_set(cls, dset, index=None): def import_set(cls, dset, in_stream): """Returns dataset from DataFrame.""" dset.wipe() - dset.dict = in_stream.to_dict(orient='records') + dset.dict = in_stream.to_dict(orient="records") diff --git a/src/tablib/formats/_html.py b/src/tablib/formats/_html.py index bfb096ed..5b486712 100644 --- a/src/tablib/formats/_html.py +++ b/src/tablib/formats/_html.py @@ -8,10 +8,10 @@ class HTMLFormat: - BOOK_ENDINGS = 'h3' + BOOK_ENDINGS = "h3" - title = 'html' - extensions = ('html', ) + title = "html" + extensions = ("html",) @classmethod def export_set(cls, dataset): @@ -23,7 +23,7 @@ def export_set(cls, dataset): page.table.open() if dataset.headers is not None: - new_header = [item if item is not None else '' for item in dataset.headers] + new_header = [item if item is not None else "" for item in dataset.headers] page.thead.open() headers = markup.oneliner.th(new_header) @@ -31,7 +31,7 @@ def export_set(cls, dataset): page.thead.close() for row in dataset: - new_row = [item if item is not None else '' for item in row] + new_row = [item if item is not None else "" for item in row] html_row = markup.oneliner.td(new_row) page.tr(html_row) @@ -42,7 +42,7 @@ def export_set(cls, dataset): wrapper = codecs.getwriter("utf8")(stream) wrapper.writelines(str(page)) - return stream.getvalue().decode('utf-8') + return stream.getvalue().decode("utf-8") @classmethod def export_book(cls, databook): @@ -54,9 +54,11 @@ def export_book(cls, databook): wrapper = codecs.getwriter("utf8")(stream) for i, dset in enumerate(databook._datasets): - title = (dset.title if dset.title else 'Set %s' % (i)) - wrapper.write('<{}>{}\n'.format(cls.BOOK_ENDINGS, title, cls.BOOK_ENDINGS)) + title = dset.title if dset.title else "Set %s" % (i) + wrapper.write( + "<{}>{}\n".format(cls.BOOK_ENDINGS, title, cls.BOOK_ENDINGS) + ) wrapper.write(dset.html) - wrapper.write('\n') + wrapper.write("\n") - return stream.getvalue().decode('utf-8') + return stream.getvalue().decode("utf-8") diff --git a/src/tablib/formats/_jira.py b/src/tablib/formats/_jira.py index a4efc437..ac004674 100644 --- a/src/tablib/formats/_jira.py +++ b/src/tablib/formats/_jira.py @@ -5,7 +5,7 @@ class JIRAFormat: - title = 'jira' + title = "jira" @classmethod def export_set(cls, dataset): @@ -19,22 +19,22 @@ def export_set(cls, dataset): :type dataset: tablib.core.Dataset """ - header = cls._get_header(dataset.headers) if dataset.headers else '' + header = cls._get_header(dataset.headers) if dataset.headers else "" body = cls._get_body(dataset) - return '{}\n{}'.format(header, body) if header else body + return "{}\n{}".format(header, body) if header else body @classmethod def _get_body(cls, dataset): - return '\n'.join([cls._serialize_row(row) for row in dataset]) + return "\n".join([cls._serialize_row(row) for row in dataset]) @classmethod def _get_header(cls, headers): - return cls._serialize_row(headers, delimiter='||') + return cls._serialize_row(headers, delimiter="||") @classmethod - def _serialize_row(cls, row, delimiter='|'): - return '{}{}{}'.format( + def _serialize_row(cls, row, delimiter="|"): + return "{}{}{}".format( + delimiter, + delimiter.join([str(item) if item else " " for item in row]), delimiter, - delimiter.join([str(item) if item else ' ' for item in row]), - delimiter ) diff --git a/src/tablib/formats/_json.py b/src/tablib/formats/_json.py index 99e2aafe..b4198475 100644 --- a/src/tablib/formats/_json.py +++ b/src/tablib/formats/_json.py @@ -10,15 +10,15 @@ def serialize_objects_handler(obj): if isinstance(obj, (decimal.Decimal, UUID)): return str(obj) - elif hasattr(obj, 'isoformat'): + elif hasattr(obj, "isoformat"): return obj.isoformat() else: return obj class JSONFormat: - title = 'json' - extensions = ('json', 'jsn') + title = "json" + extensions = ("json", "jsn") @classmethod def export_set(cls, dataset): @@ -44,8 +44,8 @@ def import_book(cls, dbook, in_stream): dbook.wipe() for sheet in json.loads(in_stream): data = tablib.Dataset() - data.title = sheet['title'] - data.dict = sheet['data'] + data.title = sheet["title"] + data.dict = sheet["data"] dbook.add_sheet(data) @classmethod diff --git a/src/tablib/formats/_latex.py b/src/tablib/formats/_latex.py index f4161426..de72d2ce 100644 --- a/src/tablib/formats/_latex.py +++ b/src/tablib/formats/_latex.py @@ -6,8 +6,8 @@ class LATEXFormat: - title = 'latex' - extensions = ('tex',) + title = "latex" + extensions = ("tex",) TABLE_TEMPLATE = """\ %% Note: add \\usepackage{booktabs} to your preamble @@ -25,21 +25,24 @@ class LATEXFormat: \\end{table} """ - TEX_RESERVED_SYMBOLS_MAP = dict([ - ('\\', '\\textbackslash{}'), - ('{', '\\{'), - ('}', '\\}'), - ('$', '\\$'), - ('&', '\\&'), - ('#', '\\#'), - ('^', '\\textasciicircum{}'), - ('_', '\\_'), - ('~', '\\textasciitilde{}'), - ('%', '\\%'), - ]) + TEX_RESERVED_SYMBOLS_MAP = dict( + [ + ("\\", "\\textbackslash{}"), + ("{", "\\{"), + ("}", "\\}"), + ("$", "\\$"), + ("&", "\\&"), + ("#", "\\#"), + ("^", "\\textasciicircum{}"), + ("_", "\\_"), + ("~", "\\textasciitilde{}"), + ("%", "\\%"), + ] + ) TEX_RESERVED_SYMBOLS_RE = re.compile( - '(%s)' % '|'.join(map(re.escape, TEX_RESERVED_SYMBOLS_MAP.keys()))) + "(%s)" % "|".join(map(re.escape, TEX_RESERVED_SYMBOLS_MAP.keys())) + ) @classmethod def export_set(cls, dataset): @@ -49,13 +52,14 @@ def export_set(cls, dataset): :type dataset: tablib.core.Dataset """ - caption = '\\caption{%s}' % dataset.title if dataset.title else '%' + caption = "\\caption{%s}" % dataset.title if dataset.title else "%" colspec = cls._colspec(dataset.width) - header = cls._serialize_row(dataset.headers) if dataset.headers else '' + header = cls._serialize_row(dataset.headers) if dataset.headers else "" midrule = cls._midrule(dataset.width) - body = '\n'.join([cls._serialize_row(row) for row in dataset]) - return cls.TABLE_TEMPLATE % dict(CAPTION=caption, COLSPEC=colspec, - HEADER=header, MIDRULE=midrule, BODY=body) + body = "\n".join([cls._serialize_row(row) for row in dataset]) + return cls.TABLE_TEMPLATE % dict( + CAPTION=caption, COLSPEC=colspec, HEADER=header, MIDRULE=midrule, BODY=body + ) @classmethod def _colspec(cls, dataset_width): @@ -73,9 +77,9 @@ def _colspec(cls, dataset_width): :param dataset_width: width of the dataset """ - spec = 'l' + spec = "l" for _ in range(1, dataset_width): - spec += 'r' + spec += "r" return spec @classmethod @@ -87,9 +91,13 @@ def _midrule(cls, dataset_width): """ if not dataset_width or dataset_width == 1: - return '\\midrule' - return ' '.join([cls._cmidrule(colindex, dataset_width) for colindex in - range(1, dataset_width + 1)]) + return "\\midrule" + return " ".join( + [ + cls._cmidrule(colindex, dataset_width) + for colindex in range(1, dataset_width + 1) + ] + ) @classmethod def _cmidrule(cls, colindex, dataset_width): @@ -100,15 +108,15 @@ def _cmidrule(cls, colindex, dataset_width): :param dataset_width: width of the dataset """ - rule = '\\cmidrule(%s){%d-%d}' + rule = "\\cmidrule(%s){%d-%d}" if colindex == 1: # Rule of first column is trimmed on the right - return rule % ('r', colindex, colindex) + return rule % ("r", colindex, colindex) if colindex == dataset_width: # Rule of last column is trimmed on the left - return rule % ('l', colindex, colindex) + return rule % ("l", colindex, colindex) # Inner columns are trimmed on the left and right - return rule % ('lr', colindex, colindex) + return rule % ("lr", colindex, colindex) @classmethod def _serialize_row(cls, row): @@ -117,9 +125,10 @@ def _serialize_row(cls, row): :param row: single dataset row """ - new_row = [cls._escape_tex_reserved_symbols(str(item)) if item else '' - for item in row] - return 6 * ' ' + ' & '.join(new_row) + ' \\\\' + new_row = [ + cls._escape_tex_reserved_symbols(str(item)) if item else "" for item in row + ] + return 6 * " " + " & ".join(new_row) + " \\\\" @classmethod def _escape_tex_reserved_symbols(cls, input): @@ -127,6 +136,8 @@ def _escape_tex_reserved_symbols(cls, input): :param input: String to escape """ + def replace(match): return cls.TEX_RESERVED_SYMBOLS_MAP[match.group()] + return cls.TEX_RESERVED_SYMBOLS_RE.sub(replace, input) diff --git a/src/tablib/formats/_ods.py b/src/tablib/formats/_ods.py index e1bf1042..3e77fca5 100644 --- a/src/tablib/formats/_ods.py +++ b/src/tablib/formats/_ods.py @@ -6,12 +6,16 @@ from odf import opendocument, style, table, text bold = style.Style(name="bold", family="paragraph") -bold.addElement(style.TextProperties(fontweight="bold", fontweightasian="bold", fontweightcomplex="bold")) +bold.addElement( + style.TextProperties( + fontweight="bold", fontweightasian="bold", fontweightcomplex="bold" + ) +) class ODSFormat: - title = 'ods' - extensions = ('ods',) + title = "ods" + extensions = ("ods",) @classmethod def export_set(cls, dataset): @@ -20,7 +24,7 @@ def export_set(cls, dataset): wb = opendocument.OpenDocumentSpreadsheet() wb.automaticstyles.addElement(bold) - ws = table.Table(name=dataset.title if dataset.title else 'Tablib Dataset') + ws = table.Table(name=dataset.title if dataset.title else "Tablib Dataset") wb.spreadsheet.addElement(ws) cls.dset_sheet(dataset, ws) @@ -36,7 +40,7 @@ def export_book(cls, databook): wb.automaticstyles.addElement(bold) for i, dset in enumerate(databook._datasets): - ws = table.Table(name=dset.title if dset.title else 'Sheet%s' % (i)) + ws = table.Table(name=dset.title if dset.title else "Sheet%s" % (i)) wb.spreadsheet.addElement(ws) cls.dset_sheet(dset, ws) @@ -55,10 +59,10 @@ def dset_sheet(cls, dataset, ws): for i, row in enumerate(_package): row_number = i + 1 - odf_row = table.TableRow(stylename=bold, defaultcellstylename='bold') + odf_row = table.TableRow(stylename=bold, defaultcellstylename="bold") for j, col in enumerate(row): try: - col = str(col, errors='ignore') + col = str(col, errors="ignore") except TypeError: ## col is already str pass @@ -66,7 +70,7 @@ def dset_sheet(cls, dataset, ws): # bold headers if (row_number == 1) and dataset.headers: - odf_row.setAttribute('stylename', bold) + odf_row.setAttribute("stylename", bold) ws.addElement(odf_row) cell = table.TableCell() p = text.P() @@ -77,7 +81,7 @@ def dset_sheet(cls, dataset, ws): # wrap the rest else: try: - if '\n' in col: + if "\n" in col: ws.addElement(odf_row) cell = table.TableCell() cell.addElement(text.P(text=col)) diff --git a/src/tablib/formats/_rst.py b/src/tablib/formats/_rst.py index 9e2cc73c..5d7ec33a 100644 --- a/src/tablib/formats/_rst.py +++ b/src/tablib/formats/_rst.py @@ -5,15 +5,15 @@ from statistics import median from textwrap import TextWrapper -JUSTIFY_LEFT = 'left' -JUSTIFY_CENTER = 'center' -JUSTIFY_RIGHT = 'right' +JUSTIFY_LEFT = "left" +JUSTIFY_CENTER = "center" +JUSTIFY_RIGHT = "right" JUSTIFY_VALUES = (JUSTIFY_LEFT, JUSTIFY_CENTER, JUSTIFY_RIGHT) def to_str(value): if isinstance(value, bytes): - return value.decode('utf-8') + return value.decode("utf-8") return str(value) @@ -28,8 +28,8 @@ def _max_word_len(text): class ReSTFormat: - title = 'rst' - extensions = ('rst',) + title = "rst" + extensions = ("rst",) MAX_TABLE_WIDTH = 80 # Roughly. It may be wider to avoid breaking words. @@ -46,7 +46,7 @@ def _get_column_string_lengths(cls, dataset): column_lengths = [[] for _ in range(dataset.width)] word_lens = [0 for _ in range(dataset.width)] for row in dataset.dict: - values = iter(row.values() if hasattr(row, 'values') else row) + values = iter(row.values() if hasattr(row, "values") else row) for i, val in enumerate(values): text = to_str(val) column_lengths[i].append(len(text)) @@ -54,38 +54,39 @@ def _get_column_string_lengths(cls, dataset): return column_lengths, word_lens @classmethod - def _row_to_lines(cls, values, widths, wrapper, sep='|', justify=JUSTIFY_LEFT): + def _row_to_lines(cls, values, widths, wrapper, sep="|", justify=JUSTIFY_LEFT): """ Returns a table row of wrapped values as a list of lines """ if justify not in JUSTIFY_VALUES: - raise ValueError('Value of "justify" must be one of "{}"'.format( - '", "'.join(JUSTIFY_VALUES) - )) + raise ValueError( + 'Value of "justify" must be one of "{}"'.format( + '", "'.join(JUSTIFY_VALUES) + ) + ) if justify == JUSTIFY_LEFT: just = lambda text, width: text.ljust(width) elif justify == JUSTIFY_CENTER: just = lambda text, width: text.center(width) else: just = lambda text, width: text.rjust(width) - lpad = sep + ' ' if sep else '' - rpad = ' ' + sep if sep else '' - pad = ' ' + sep + ' ' + lpad = sep + " " if sep else "" + rpad = " " + sep if sep else "" + pad = " " + sep + " " cells = [] for value, width in zip(values, widths): wrapper.width = width text = to_str(value) cell = wrapper.wrap(text) cells.append(cell) - lines = zip_longest(*cells, fillvalue='') + lines = zip_longest(*cells, fillvalue="") lines = ( (just(cell_line, widths[i]) for i, cell_line in enumerate(line)) for line in lines ) - lines = [''.join((lpad, pad.join(line), rpad)) for line in lines] + lines = ["".join((lpad, pad.join(line), rpad)) for line in lines] return lines - @classmethod def _get_column_widths(cls, dataset, max_table_width=MAX_TABLE_WIDTH, pad_len=3): """ @@ -114,23 +115,25 @@ def export_set_as_simple_table(cls, dataset, column_widths=None): wrapper = TextWrapper() if column_widths is None: column_widths = _get_column_widths(dataset, pad_len=2) - border = ' '.join(['=' * w for w in column_widths]) + border = " ".join(["=" * w for w in column_widths]) lines.append(border) if dataset.headers: - lines.extend(cls._row_to_lines( - dataset.headers, - column_widths, - wrapper, - sep='', - justify=JUSTIFY_CENTER, - )) + lines.extend( + cls._row_to_lines( + dataset.headers, + column_widths, + wrapper, + sep="", + justify=JUSTIFY_CENTER, + ) + ) lines.append(border) for row in dataset.dict: - values = iter(row.values() if hasattr(row, 'values') else row) - lines.extend(cls._row_to_lines(values, column_widths, wrapper, '')) + values = iter(row.values() if hasattr(row, "values") else row) + lines.extend(cls._row_to_lines(values, column_widths, wrapper, "")) lines.append(border) - return '\n'.join(lines) + return "\n".join(lines) @classmethod def export_set_as_grid_table(cls, dataset, column_widths=None): @@ -165,25 +168,23 @@ def export_set_as_grid_table(cls, dataset, column_widths=None): wrapper = TextWrapper() if column_widths is None: column_widths = cls._get_column_widths(dataset) - header_sep = '+=' + '=+='.join(['=' * w for w in column_widths]) + '=+' - row_sep = '+-' + '-+-'.join(['-' * w for w in column_widths]) + '-+' + header_sep = "+=" + "=+=".join(["=" * w for w in column_widths]) + "=+" + row_sep = "+-" + "-+-".join(["-" * w for w in column_widths]) + "-+" lines.append(row_sep) if dataset.headers: - lines.extend(cls._row_to_lines( - dataset.headers, - column_widths, - wrapper, - justify=JUSTIFY_CENTER, - )) + lines.extend( + cls._row_to_lines( + dataset.headers, column_widths, wrapper, justify=JUSTIFY_CENTER, + ) + ) lines.append(header_sep) for row in dataset.dict: - values = iter(row.values() if hasattr(row, 'values') else row) + values = iter(row.values() if hasattr(row, "values") else row) lines.extend(cls._row_to_lines(values, column_widths, wrapper)) lines.append(row_sep) - return '\n'.join(lines) - + return "\n".join(lines) @classmethod def _use_simple_table(cls, head0, col0, width0): @@ -240,9 +241,9 @@ def export_set(cls, dataset, **kwargs): """ if not dataset.dict: - return '' - force_grid = kwargs.get('force_grid', False) - max_table_width = kwargs.get('max_table_width', cls.MAX_TABLE_WIDTH) + return "" + force_grid = kwargs.get("force_grid", False) + max_table_width = kwargs.get("max_table_width", cls.MAX_TABLE_WIDTH) column_widths = cls._get_column_widths(dataset, max_table_width) use_simple_table = cls._use_simple_table( @@ -263,5 +264,6 @@ def export_book(cls, databook): Tables are separated by a blank line. All tables use the grid format. """ - return '\n\n'.join(cls.export_set(dataset, force_grid=True) - for dataset in databook._datasets) + return "\n\n".join( + cls.export_set(dataset, force_grid=True) for dataset in databook._datasets + ) diff --git a/src/tablib/formats/_tsv.py b/src/tablib/formats/_tsv.py index 928246c8..00e958ef 100644 --- a/src/tablib/formats/_tsv.py +++ b/src/tablib/formats/_tsv.py @@ -5,7 +5,7 @@ class TSVFormat(CSVFormat): - title = 'tsv' - extensions = ('tsv',) + title = "tsv" + extensions = ("tsv",) - DEFAULT_DELIMITER = '\t' + DEFAULT_DELIMITER = "\t" diff --git a/src/tablib/formats/_xls.py b/src/tablib/formats/_xls.py index fd39b46b..cfb11889 100644 --- a/src/tablib/formats/_xls.py +++ b/src/tablib/formats/_xls.py @@ -13,8 +13,8 @@ class XLSFormat: - title = 'xls' - extensions = ('xls',) + title = "xls" + extensions = ("xls",) @classmethod def detect(cls, stream): @@ -39,8 +39,8 @@ def detect(cls, stream): def export_set(cls, dataset): """Returns XLS representation of Dataset.""" - wb = xlwt.Workbook(encoding='utf8') - ws = wb.add_sheet(dataset.title if dataset.title else 'Tablib Dataset') + wb = xlwt.Workbook(encoding="utf8") + ws = wb.add_sheet(dataset.title if dataset.title else "Tablib Dataset") cls.dset_sheet(dataset, ws) @@ -52,10 +52,10 @@ def export_set(cls, dataset): def export_book(cls, databook): """Returns XLS representation of DataBook.""" - wb = xlwt.Workbook(encoding='utf8') + wb = xlwt.Workbook(encoding="utf8") for i, dset in enumerate(databook._datasets): - ws = wb.add_sheet(dset.title if dset.title else 'Sheet%s' % (i)) + ws = wb.add_sheet(dset.title if dset.title else "Sheet%s" % (i)) cls.dset_sheet(dset, ws) @@ -63,7 +63,6 @@ def export_book(cls, databook): wb.save(stream) return stream.getvalue() - @classmethod def import_set(cls, dset, in_stream, headers=True): """Returns databook from XLS stream.""" @@ -79,10 +78,14 @@ def import_set(cls, dset, in_stream, headers=True): if i == 0 and headers: dset.headers = sheet.row_values(0) else: - dset.append([ - val if typ != xlrd.XL_CELL_ERROR else xlrd.error_text_from_code[val] - for val, typ in zip(sheet.row_values(i), sheet.row_types(i)) - ]) + dset.append( + [ + val + if typ != xlrd.XL_CELL_ERROR + else xlrd.error_text_from_code[val] + for val, typ in zip(sheet.row_values(i), sheet.row_types(i)) + ] + ) @classmethod def import_book(cls, dbook, in_stream, headers=True): @@ -131,7 +134,7 @@ def dset_sheet(cls, dataset, ws): # wrap the rest else: try: - if '\n' in col: + if "\n" in col: ws.write(i, j, col, wrap) else: ws.write(i, j, col) diff --git a/src/tablib/formats/_xlsx.py b/src/tablib/formats/_xlsx.py index cc0a6106..98bf5c6b 100644 --- a/src/tablib/formats/_xlsx.py +++ b/src/tablib/formats/_xlsx.py @@ -12,8 +12,8 @@ class XLSXFormat: - title = 'xlsx' - extensions = ('xlsx',) + title = "xlsx" + extensions = ("xlsx",) @classmethod def detect(cls, stream): @@ -32,7 +32,7 @@ def export_set(cls, dataset, freeze_panes=True): """Returns XLSX representation of Dataset.""" wb = Workbook() ws = wb.worksheets[0] - ws.title = dataset.title if dataset.title else 'Tablib Dataset' + ws.title = dataset.title if dataset.title else "Tablib Dataset" cls.dset_sheet(dataset, ws, freeze_panes=freeze_panes) @@ -49,7 +49,7 @@ def export_book(cls, databook, freeze_panes=True): wb.remove(sheet) for i, dset in enumerate(databook._datasets): ws = wb.create_sheet() - ws.title = dset.title if dset.title else 'Sheet%s' % (i) + ws.title = dset.title if dset.title else "Sheet%s" % (i) cls.dset_sheet(dset, ws, freeze_panes=freeze_panes) @@ -63,7 +63,9 @@ def import_set(cls, dset, in_stream, headers=True): dset.wipe() - xls_book = openpyxl.reader.excel.load_workbook(BytesIO(in_stream), read_only=True) + xls_book = openpyxl.reader.excel.load_workbook( + BytesIO(in_stream), read_only=True + ) sheet = xls_book.active dset.title = sheet.title @@ -81,7 +83,9 @@ def import_book(cls, dbook, in_stream, headers=True): dbook.wipe() - xls_book = openpyxl.reader.excel.load_workbook(BytesIO(in_stream), read_only=True) + xls_book = openpyxl.reader.excel.load_workbook( + BytesIO(in_stream), read_only=True + ) for sheet in xls_book.worksheets: data = tablib.Dataset() @@ -112,14 +116,14 @@ def dset_sheet(cls, dataset, ws, freeze_panes=True): row_number = i + 1 for j, col in enumerate(row): col_idx = get_column_letter(j + 1) - cell = ws['{}{}'.format(col_idx, row_number)] + cell = ws["{}{}".format(col_idx, row_number)] # bold headers if (row_number == 1) and dataset.headers: cell.font = bold if freeze_panes: # Export Freeze only after first Line - ws.freeze_panes = 'A2' + ws.freeze_panes = "A2" # bold separators elif len(row) < dataset.width: @@ -130,8 +134,8 @@ def dset_sheet(cls, dataset, ws, freeze_panes=True): try: str_col_value = str(col) except TypeError: - str_col_value = '' - if '\n' in str_col_value: + str_col_value = "" + if "\n" in str_col_value: cell.alignment = wrap_text try: diff --git a/src/tablib/formats/_yaml.py b/src/tablib/formats/_yaml.py index 408400b9..3452d595 100644 --- a/src/tablib/formats/_yaml.py +++ b/src/tablib/formats/_yaml.py @@ -6,8 +6,8 @@ class YAMLFormat: - title = 'yaml' - extensions = ('yaml', 'yml') + title = "yaml" + extensions = ("yaml", "yml") @classmethod def export_set(cls, dataset): @@ -35,8 +35,8 @@ def import_book(cls, dbook, in_stream): for sheet in yaml.safe_load(in_stream): data = tablib.Dataset() - data.title = sheet['title'] - data.dict = sheet['data'] + data.title = sheet["title"] + data.dict = sheet["data"] dbook.add_sheet(data) @classmethod @@ -48,6 +48,9 @@ def detect(cls, stream): return True else: return False - except (yaml.parser.ParserError, yaml.reader.ReaderError, - yaml.scanner.ScannerError): + except ( + yaml.parser.ParserError, + yaml.reader.ReaderError, + yaml.scanner.ScannerError, + ): return False diff --git a/src/tablib/packages/dbfpy/dbf.py b/src/tablib/packages/dbfpy/dbf.py index dd38c9c3..7d3fe1bc 100644 --- a/src/tablib/packages/dbfpy/dbf.py +++ b/src/tablib/packages/dbfpy/dbf.py @@ -81,8 +81,7 @@ class Dbf: """ - __slots__ = ("name", "header", "stream", - "_changed", "_new", "_ignore_errors") + __slots__ = ("name", "header", "stream", "_changed", "_new", "_ignore_errors") HeaderClass = header.DbfHeader RecordClass = record.DbfRecord @@ -141,8 +140,7 @@ def __init__(self, f, readOnly=False, new=False, ignoreErrors=False): closed = property(lambda self: self.stream.closed) recordCount = property(lambda self: self.header.recordCount) - fieldNames = property( - lambda self: [_fld.name for _fld in self.header.fields]) + fieldNames = property(lambda self: [_fld.name for _fld in self.header.fields]) fieldDefs = property(lambda self: self.header.fields) changed = property(lambda self: self._changed or self.header.changed) @@ -158,7 +156,8 @@ def ignoreErrors(self, value): if set, failing field value conversion will return ``INVALID_VALUE`` instead of raising conversion error. - """) + """, + ) # protected methods @@ -227,8 +226,7 @@ def addField(self, *defs): if self._new: self.header.addField(*defs) else: - raise TypeError("At least one record was added, " - "structure can't be changed") + raise TypeError("At least one record was added, structure can't be changed") # 'magic' methods (representation and sequence interface) @@ -272,10 +270,10 @@ def demo_create(filename): ("BIRTHDATE", "D"), ) for (_n, _s, _i, _b) in ( - ("John", "Miller", "YC", (1981, 1, 2)), - ("Andy", "Larkin", "AL", (1982, 3, 4)), - ("Bill", "Clinth", "", (1983, 5, 6)), - ("Bobb", "McNail", "", (1984, 7, 8)), + ("John", "Miller", "YC", (1981, 1, 2)), + ("Andy", "Larkin", "AL", (1982, 3, 4)), + ("Bill", "Clinth", "", (1983, 5, 6)), + ("Bobb", "McNail", "", (1984, 7, 8)), ): _rec = _dbf.newRecord() _rec["NAME"] = _n @@ -287,7 +285,7 @@ def demo_create(filename): _dbf.close() -if __name__ == '__main__': +if __name__ == "__main__": import sys _name = len(sys.argv) > 1 and sys.argv[1] or "county.dbf" diff --git a/src/tablib/packages/dbfpy/dbfnew.py b/src/tablib/packages/dbfpy/dbfnew.py index 15620211..97875447 100644 --- a/src/tablib/packages/dbfpy/dbfnew.py +++ b/src/tablib/packages/dbfpy/dbfnew.py @@ -47,7 +47,7 @@ class _FieldDefinition: # WARNING: be attentive - dictionaries are mutable! FLD_TYPES = { - # type: (cls, len) + # field type: (cls, len) "C": (DbfCharacterFieldDef, None), "N": (DbfNumericFieldDef, None), "L": (DbfLogicalFieldDef, 1), @@ -146,28 +146,28 @@ def write(self, filename): _dbfStream.close() -if __name__ == '__main__': +if __name__ == "__main__": # create a new DBF-File dbfn = dbf_new() - dbfn.add_field("name", 'C', 80) - dbfn.add_field("price", 'N', 10, 2) - dbfn.add_field("date", 'D', 8) + dbfn.add_field("name", "C", 80) + dbfn.add_field("price", "N", 10, 2) + dbfn.add_field("date", "D", 8) dbfn.write("tst.dbf") # test new dbf print("*** created tst.dbf: ***") - dbft = Dbf('tst.dbf', readOnly=0) + dbft = Dbf("tst.dbf", readOnly=0) print(repr(dbft)) # add a record rec = DbfRecord(dbft) - rec['name'] = 'something' - rec['price'] = 10.5 - rec['date'] = (2000, 1, 12) + rec["name"] = "something" + rec["price"] = 10.5 + rec["date"] = (2000, 1, 12) rec.store() # add another record rec = DbfRecord(dbft) - rec['name'] = 'foo and bar' - rec['price'] = 12234 - rec['date'] = (1992, 7, 15) + rec["name"] = "foo and bar" + rec["price"] = 12234 + rec["date"] = (1992, 7, 15) rec.store() # show the records @@ -176,7 +176,7 @@ def write(self, filename): for i1 in range(len(dbft)): rec = dbft[i1] for fldName in dbft.fieldNames: - print('{}:\t {}'.format(fldName, rec[fldName])) + print("{}:\t {}".format(fldName, rec[fldName])) print() dbft.close() diff --git a/src/tablib/packages/dbfpy/fields.py b/src/tablib/packages/dbfpy/fields.py index d6ee84ef..f8bf8874 100644 --- a/src/tablib/packages/dbfpy/fields.py +++ b/src/tablib/packages/dbfpy/fields.py @@ -72,14 +72,21 @@ class DbfFieldDef: # overridden in child classes defaultValue = None - def __init__(self, name, length=None, decimalCount=None, - start=None, stop=None, ignoreErrors=False): + def __init__( + self, + name, + length=None, + decimalCount=None, + start=None, + stop=None, + ignoreErrors=False, + ): """Initialize instance.""" assert self.typeCode is not None, "Type code must be overridden" assert self.defaultValue is not None, "Default value must be overridden" # fix arguments if len(name) > 10: - raise ValueError("Field name \"%s\" is too long" % name) + raise ValueError('Field name "%s" is too long' % name) name = str(name).upper() if self.__class__.length is None: if length is None: @@ -122,8 +129,15 @@ def fromString(cls, string, start, ignoreErrors=False): """ assert len(string) == 32 _length = string[16] - return cls(utils.unzfill(string)[:11].decode('utf-8'), _length, - string[17], start, start + _length, ignoreErrors=ignoreErrors) + return cls( + utils.unzfill(string)[:11].decode("utf-8"), + _length, + string[17], + start, + start + _length, + ignoreErrors=ignoreErrors, + ) + fromString = classmethod(fromString) def toString(self): @@ -134,15 +148,16 @@ def toString(self): definition of this field. """ - _name = self.name.ljust(11, '\0') + _name = self.name.ljust(11, "\0") return ( - _name + - self.typeCode + + _name + + self.typeCode + + # data address - chr(0) * 4 + - chr(self.length) + - chr(self.decimalCount) + - chr(0) * 14 + chr(0) * 4 + + chr(self.length) + + chr(self.decimalCount) + + chr(0) * 14 ) def __repr__(self): @@ -159,7 +174,7 @@ def fieldInfo(self): def rawFromRecord(self, record): """Return a "raw" field value from the record string.""" - return record[self.start:self.end] + return record[self.start : self.end] def decodeFromRecord(self, record): """Return decoded field value from the record string.""" @@ -188,6 +203,7 @@ def encodeValue(self, value): """ raise NotImplementedError + # real classes @@ -195,7 +211,7 @@ class DbfCharacterFieldDef(DbfFieldDef): """Definition of the character field.""" typeCode = "C" - defaultValue = b'' + defaultValue = b"" def decodeValue(self, value): """Return string object. @@ -203,11 +219,11 @@ def decodeValue(self, value): Return value is a ``value`` argument with stripped right spaces. """ - return value.rstrip(b' ').decode('utf-8') + return value.rstrip(b" ").decode("utf-8") def encodeValue(self, value): """Return raw data string encoded from a ``value``.""" - return str(value)[:self.length].ljust(self.length) + return str(value)[: self.length].ljust(self.length) class DbfNumericFieldDef(DbfFieldDef): @@ -230,8 +246,8 @@ def decodeValue(self, value): Return value is a int (long) or float instance. """ - value = value.strip(b' \0') - if b'.' in value: + value = value.strip(b" \0") + if b"." in value: # a float (has decimal separator) return float(value) elif value: @@ -242,14 +258,16 @@ def decodeValue(self, value): def encodeValue(self, value): """Return string containing encoded ``value``.""" - _rv = ("%*.*f" % (self.length, self.decimalCount, value)) + _rv = "%*.*f" % (self.length, self.decimalCount, value) if len(_rv) > self.length: _ppos = _rv.find(".") if 0 <= _ppos <= self.length: - _rv = _rv[:self.length] + _rv = _rv[: self.length] else: - raise ValueError("[%s] Numeric overflow: %s (field width: %i)" - % (self.name, _rv, self.length)) + raise ValueError( + "[%s] Numeric overflow: %s (field width: %i)" + % (self.name, _rv, self.length) + ) return _rv @@ -284,7 +302,7 @@ class DbfCurrencyFieldDef(DbfFieldDef): def decodeValue(self, value): """Return float number decoded from ``value``.""" - return struct.unpack(" Date: Sun, 10 Nov 2019 21:12:21 +0200 Subject: [PATCH 2/8] Run Black on pre-commit and therefore tox and CI --- .pre-commit-config.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 026796a9..cf6a8fad 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,6 +5,12 @@ repos: - id: pyupgrade args: ["--py3-plus"] + - repo: https://github.com/psf/black + rev: 19.10b0 + hooks: + - id: black + args: ["--target-version", "py35"] + - repo: https://github.com/pre-commit/mirrors-isort rev: v4.3.21 hooks: From 8c2364f7bb5dc2fb3c39f07ab1dbbea473dfdd59 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 10 Nov 2019 21:38:46 +0200 Subject: [PATCH 3/8] .tox is already a default. Don't overwrite other default excludes --- tox.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/tox.ini b/tox.ini index 059f3351..634f75ff 100644 --- a/tox.ini +++ b/tox.ini @@ -31,6 +31,4 @@ commands = skip_install = true [flake8] -exclude = - .tox ignore=E501,E127,E128,E124 From fd1922f39b3f150cc0324e632c1ab236745ad8db Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 10 Nov 2019 21:58:43 +0200 Subject: [PATCH 4/8] Add Black-compatible config to Flake8 --- .flake8 | 3 +++ MANIFEST.in | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..4f38d06f --- /dev/null +++ b/.flake8 @@ -0,0 +1,3 @@ +[flake8] +ignore = E203, W503 +max_line_length = 88 diff --git a/MANIFEST.in b/MANIFEST.in index ed238e97..b1c0c62d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,6 @@ recursive-include docs * recursive-include tests * -include pyproject.toml pytest.ini tox.ini .coveragerc .pre-commit-config.yaml *.md LICENSE AUTHORS +include pyproject.toml pytest.ini tox.ini .coveragerc .flake8 .pre-commit-config.yaml *.md LICENSE AUTHORS prune docs/_build prune *.pyc prune __pycache__ From 77c54885878dbbcbef1fb2aa68ed116f4f266ff1 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 10 Nov 2019 22:01:27 +0200 Subject: [PATCH 5/8] No __cmp__ or cmp in Python 3 --- src/tablib/packages/dbfpy/fields.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/tablib/packages/dbfpy/fields.py b/src/tablib/packages/dbfpy/fields.py index f8bf8874..1facac51 100644 --- a/src/tablib/packages/dbfpy/fields.py +++ b/src/tablib/packages/dbfpy/fields.py @@ -108,9 +108,6 @@ def __init__( self.start = start self.end = stop - def __cmp__(self, other): - return cmp(self.name, str(other).upper()) - def __hash__(self): return hash(self.name) From 09873837986245a7c80f794029de0767effed7fd Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 10 Nov 2019 22:22:34 +0200 Subject: [PATCH 6/8] Fix Flake8 --- src/tablib/__init__.py | 2 +- src/tablib/core.py | 28 +++++++++++--------- src/tablib/formats/_dbf.py | 2 +- src/tablib/formats/_ods.py | 2 +- src/tablib/formats/_rst.py | 15 ++++++++--- src/tablib/packages/dbfpy/dbf.py | 6 ++--- src/tablib/packages/dbfpy/dbfnew.py | 16 ++++++++---- src/tablib/packages/dbfpy/fields.py | 15 +++++------ src/tablib/packages/dbfpy/header.py | 16 ++++++------ src/tablib/packages/dbfpy/record.py | 8 +++--- src/tablib/packages/dbfpy/utils.py | 6 ++--- tests/test_tablib.py | 32 +++++++++++++++++------ tests/test_tablib_dbfpy_packages_utils.py | 2 +- 13 files changed, 91 insertions(+), 59 deletions(-) diff --git a/src/tablib/__init__.py b/src/tablib/__init__.py index 1d9d5056..6e10d783 100644 --- a/src/tablib/__init__.py +++ b/src/tablib/__init__.py @@ -1,6 +1,6 @@ """ Tablib. """ from pkg_resources import DistributionNotFound, get_distribution -from tablib.core import ( +from tablib.core import ( # noqa: F401 Databook, Dataset, InvalidDatasetType, diff --git a/src/tablib/core.py b/src/tablib/core.py index 28c9750b..93bfdc70 100644 --- a/src/tablib/core.py +++ b/src/tablib/core.py @@ -12,7 +12,6 @@ from copy import copy from operator import itemgetter -from tablib import formats from tablib.exceptions import ( HeadersNeeded, InvalidDatasetIndex, @@ -300,7 +299,8 @@ def _package(self, dicts=True, ordered=True): def _get_headers(self): """An *optional* list of strings to be used for header rows and attribute names. - This must be set manually. The given list length must equal :class:`Dataset.width`. + This must be set manually. The given list length must equal + :class:`Dataset.width`. """ return self.__headers @@ -319,11 +319,12 @@ def _set_headers(self, collection): headers = property(_get_headers, _set_headers) def _get_dict(self): - """A native Python representation of the :class:`Dataset` object. If headers have - been set, a list of Python dictionaries will be returned. If no headers have been set, - a list of tuples (rows) will be returned instead. + """A native Python representation of the :class:`Dataset` object. If headers + have been set, a list of Python dictionaries will be returned. If no headers + have been set, a list of tuples (rows) will be returned instead. - A dataset object can also be imported by setting the `Dataset.dict` attribute: :: + A dataset object can also be imported by setting the `Dataset.dict` + attribute: :: data = tablib.Dataset() data.dict = [{'age': 90, 'first_name': 'Kenneth', 'last_name': 'Reitz'}] @@ -336,7 +337,8 @@ def _set_dict(self, pickle): set, a list of Python dictionaries will be returned. If no headers have been set, a list of tuples (rows) will be returned instead. - A dataset object can also be imported by setting the :class:`Dataset.dict` attribute. :: + A dataset object can also be imported by setting the :class:`Dataset.dict` + attribute. :: data = tablib.Dataset() data.dict = [{'age': 90, 'first_name': 'Kenneth', 'last_name': 'Reitz'}] @@ -526,9 +528,9 @@ def insert_col(self, index, col=None, header=None): that row. .. versionadded:: 0.9.0 - If inserting a row, you can add :ref:`tags ` to the row you are inserting. - This gives you the ability to :class:`filter ` your - :class:`Dataset` later. + If inserting a row, you can add :ref:`tags ` to the row you are + inserting. This gives you the ability to :class:`filter ` + your :class:`Dataset` later. """ @@ -867,7 +869,8 @@ def load(self, in_stream, format, **kwargs): """ Import `in_stream` to the :class:`Databook` object using the `format`. - :param \\*\\*kwargs: (optional) custom configuration to the format `import_book`. + :param \\*\\*kwargs: (optional) custom configuration to the format + `import_book`. """ if not format: @@ -884,7 +887,8 @@ def export(self, format, **kwargs): """ Export :class:`Databook` object to `format`. - :param \\*\\*kwargs: (optional) custom configuration to the format `export_book`. + :param \\*\\*kwargs: (optional) custom configuration to the format + `export_book`. """ fmt = registry.get_format(format) if not hasattr(fmt, "export_book"): diff --git a/src/tablib/formats/_dbf.py b/src/tablib/formats/_dbf.py index a241e9e1..a65a773b 100644 --- a/src/tablib/formats/_dbf.py +++ b/src/tablib/formats/_dbf.py @@ -63,7 +63,7 @@ def detect(cls, stream): try: if type(stream) is not bytes: stream = bytes(stream, "utf-8") - _dbf = dbf.Dbf(io.BytesIO(stream), readOnly=True) + dbf.Dbf(io.BytesIO(stream), readOnly=True) return True except Exception: return False diff --git a/src/tablib/formats/_ods.py b/src/tablib/formats/_ods.py index 3e77fca5..d64d8624 100644 --- a/src/tablib/formats/_ods.py +++ b/src/tablib/formats/_ods.py @@ -64,7 +64,7 @@ def dset_sheet(cls, dataset, ws): try: col = str(col, errors="ignore") except TypeError: - ## col is already str + # col is already str pass ws.addElement(table.TableColumn()) diff --git a/src/tablib/formats/_rst.py b/src/tablib/formats/_rst.py index 5d7ec33a..9be22e24 100644 --- a/src/tablib/formats/_rst.py +++ b/src/tablib/formats/_rst.py @@ -65,11 +65,20 @@ def _row_to_lines(cls, values, widths, wrapper, sep="|", justify=JUSTIFY_LEFT): ) ) if justify == JUSTIFY_LEFT: - just = lambda text, width: text.ljust(width) + + def just(text, width): + return text.ljust(width) + elif justify == JUSTIFY_CENTER: - just = lambda text, width: text.center(width) + + def just(text, width): + return text.center(width) + else: - just = lambda text, width: text.rjust(width) + + def just(text, width): + return text.rjust(width) + lpad = sep + " " if sep else "" rpad = " " + sep if sep else "" pad = " " + sep + " " diff --git a/src/tablib/packages/dbfpy/dbf.py b/src/tablib/packages/dbfpy/dbf.py index 7d3fe1bc..923c38cb 100644 --- a/src/tablib/packages/dbfpy/dbf.py +++ b/src/tablib/packages/dbfpy/dbf.py @@ -37,6 +37,9 @@ dbf.close() """ +from . import header, record +from .utils import INVALID_VALUE + """History (most recent first): 11-feb-2007 [als] export INVALID_VALUE; Dbf: added .ignoreErrors, .INVALID_VALUE @@ -62,9 +65,6 @@ __all__ = ["Dbf"] -from . import header, record -from .utils import INVALID_VALUE - class Dbf: """DBF accessor. diff --git a/src/tablib/packages/dbfpy/dbfnew.py b/src/tablib/packages/dbfpy/dbfnew.py index 97875447..7e910642 100644 --- a/src/tablib/packages/dbfpy/dbfnew.py +++ b/src/tablib/packages/dbfpy/dbfnew.py @@ -10,6 +10,17 @@ `http://www.clicketyclick.dk/databases/xbase/format/data_types.html` """ +from .dbf import Dbf +from .fields import ( + DbfCharacterFieldDef, + DbfDateFieldDef, + DbfDateTimeFieldDef, + DbfLogicalFieldDef, + DbfNumericFieldDef, +) +from .header import DbfHeader +from .record import DbfRecord + """History (most recent first) 04-jul-2006 [als] added export declaration; updated for dbfpy 2.0 @@ -24,11 +35,6 @@ __all__ = ["dbf_new"] -from .dbf import * -from .fields import * -from .header import * -from .record import * - class _FieldDefinition: """Field definition. diff --git a/src/tablib/packages/dbfpy/fields.py b/src/tablib/packages/dbfpy/fields.py index 1facac51..12917faa 100644 --- a/src/tablib/packages/dbfpy/fields.py +++ b/src/tablib/packages/dbfpy/fields.py @@ -3,6 +3,11 @@ TODO: - make memos work """ +import datetime +import struct + +from . import utils + """History (most recent first): 26-may-2009 [als] DbfNumericFieldDef.decodeValue: strip zero bytes 05-feb-2009 [als] DbfDateFieldDef.encodeValue: empty arg produces empty date @@ -30,12 +35,6 @@ __all__ = ["lookupFor"] # field classes added at the end of the module -import datetime -import struct -import sys - -from . import utils - # abstract definitions @@ -149,9 +148,7 @@ def toString(self): return ( _name + self.typeCode - + - # data address - chr(0) * 4 + + chr(0) * 4 # data address + chr(self.length) + chr(self.decimalCount) + chr(0) * 14 diff --git a/src/tablib/packages/dbfpy/header.py b/src/tablib/packages/dbfpy/header.py index 4afb7692..1823640a 100644 --- a/src/tablib/packages/dbfpy/header.py +++ b/src/tablib/packages/dbfpy/header.py @@ -5,6 +5,14 @@ (encoding information stored in the DBF header) """ +import datetime +import io +import struct +import sys + +from . import fields +from .utils import getDate + """History (most recent first): 16-sep-2010 [als] fromStream: fix century of the last update field 11-feb-2007 [als] added .ignoreErrors @@ -19,14 +27,6 @@ __all__ = ["DbfHeader"] -import datetime -import io -import struct -import sys - -from . import fields -from .utils import getDate - class DbfHeader: """Dbf header definition. diff --git a/src/tablib/packages/dbfpy/record.py b/src/tablib/packages/dbfpy/record.py index 11b5019a..778d8e5c 100644 --- a/src/tablib/packages/dbfpy/record.py +++ b/src/tablib/packages/dbfpy/record.py @@ -1,6 +1,10 @@ """DBF record definition. """ +import sys + +from . import utils + """History (most recent first): 11-feb-2007 [als] __repr__: added special case for invalid field values 10-feb-2007 [als] added .rawFromStream() @@ -16,10 +20,6 @@ __all__ = ["DbfRecord"] -import sys - -from . import utils - class DbfRecord: """DBF record. diff --git a/src/tablib/packages/dbfpy/utils.py b/src/tablib/packages/dbfpy/utils.py index 9e7b45f4..d13002df 100644 --- a/src/tablib/packages/dbfpy/utils.py +++ b/src/tablib/packages/dbfpy/utils.py @@ -3,6 +3,9 @@ TODO: - allow strings in getDateTime routine; """ +import datetime +import time + """History (most recent first): 11-feb-2007 [als] added INVALID_VALUE 10-feb-2007 [als] allow date strings padded with spaces instead of zeroes @@ -13,9 +16,6 @@ __version__ = "$Revision: 1.4 $"[11:-2] __date__ = "$Date: 2007/02/11 08:57:17 $"[7:-2] -import datetime -import time - def unzfill(str): """Return a string without ASCII NULs. diff --git a/tests/test_tablib.py b/tests/test_tablib.py index 19c5736e..341023df 100755 --- a/tests/test_tablib.py +++ b/tests/test_tablib.py @@ -180,7 +180,8 @@ def test_add_column_with_header_and_data_exists(self): def test_add_callable_column(self): """Verify adding column with values specified as callable.""" - new_col = lambda x: x[0] + def new_col(x): + return x[0] self.founders.append_col(new_col, header="first_again") @@ -346,7 +347,10 @@ def test_auto_format_detect(self): _tsv = "1\t2\t3\n4\t5\t6\n7\t8\t9\n" self.assertEqual(tablib.detect_format(_tsv), "tsv") - _bunk = "¡¡¡¡¡¡---///\n\n\n¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†•§º¶•†¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶" + _bunk = ( + "¡¡¡¡¡¡---///\n\n\n¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†" + "•§º¶•†¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶" + ) self.assertEqual(tablib.detect_format(_bunk), None) def test_transpose(self): @@ -684,7 +688,10 @@ def test_csv_format_detect(self): """Test CSV format detection.""" _csv = "1,2,3\n4,5,6\n7,8,9\n" - _bunk = "¡¡¡¡¡¡¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†•§º¶•†¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶" + _bunk = ( + "¡¡¡¡¡¡¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†•§º¶•†" + "¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶" + ) fmt = registry.get_format("csv") self.assertTrue(fmt.detect(_csv)) @@ -895,7 +902,10 @@ def test_tsv_format_detect(self): """Test TSV format detection.""" _tsv = "1\t2\t3\n4\t5\t6\n7\t8\t9\n" - _bunk = "¡¡¡¡¡¡¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†•§º¶•†¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶" + _bunk = ( + "¡¡¡¡¡¡¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†•§º¶•†" + "¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶" + ) fmt = registry.get_format("tsv") self.assertTrue(fmt.detect(_tsv)) @@ -975,7 +985,10 @@ def test_json_format_detect(self): """Test JSON format detection.""" _json = '[{"last_name": "Adams","age": 90,"first_name": "John"}]' - _bunk = "¡¡¡¡¡¡¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†•§º¶•†¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶" + _bunk = ( + "¡¡¡¡¡¡¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†•§º¶•†" + "¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶" + ) fmt = registry.get_format("json") self.assertTrue(fmt.detect(_json)) @@ -1033,8 +1046,8 @@ def test_yaml_format_detect(self): _yaml = "- {age: 90, first_name: John, last_name: Adams}" _tsv = "foo\tbar" _bunk = ( - "¡¡¡¡¡¡---///\n\n\n¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†•§º¶•†¥ª–º•§ƒø¥¨©πƒø†" - "ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶" + "¡¡¡¡¡¡---///\n\n\n¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†" + "•§º¶•†¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶" ) fmt = registry.get_format("yaml") @@ -1226,7 +1239,10 @@ def test_dbf_format_detect(self): _csv = "1,2,3\n4,5,6\n7,8,9\n" _json = '[{"last_name": "Adams","age": 90,"first_name": "John"}]' - _bunk = "¡¡¡¡¡¡¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†•§º¶•†¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶" + _bunk = ( + "¡¡¡¡¡¡¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†•§º¶•†" + "¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶" + ) fmt = registry.get_format("dbf") self.assertTrue(fmt.detect(_dbf)) self.assertFalse(fmt.detect(_yaml)) diff --git a/tests/test_tablib_dbfpy_packages_utils.py b/tests/test_tablib_dbfpy_packages_utils.py index 9288916f..ce40c4e7 100644 --- a/tests/test_tablib_dbfpy_packages_utils.py +++ b/tests/test_tablib_dbfpy_packages_utils.py @@ -151,7 +151,7 @@ def test_getDateTime_datetime_string(self): # Act / Assert with self.assertRaises(NotImplementedError): - output = utils.getDateTime(value) + utils.getDateTime(value) class InvalidValueTestCase(unittest.TestCase): From e3b445cec3ed25fc3c972fd3982efd81629f997a Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 10 Nov 2019 22:26:17 +0200 Subject: [PATCH 7/8] Fix undefined name --- src/tablib/formats/_rst.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tablib/formats/_rst.py b/src/tablib/formats/_rst.py index 9be22e24..ce985fe9 100644 --- a/src/tablib/formats/_rst.py +++ b/src/tablib/formats/_rst.py @@ -123,7 +123,7 @@ def export_set_as_simple_table(cls, dataset, column_widths=None): lines = [] wrapper = TextWrapper() if column_widths is None: - column_widths = _get_column_widths(dataset, pad_len=2) + column_widths = cls._get_column_widths(dataset, pad_len=2) border = " ".join(["=" * w for w in column_widths]) lines.append(border) From 6bab35b11f6c30c29abc2d3c5c02f26100ee4c44 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 10 Nov 2019 22:34:35 +0200 Subject: [PATCH 8/8] Run Flake8 on pre-commit and therefore tox and CI --- .pre-commit-config.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cf6a8fad..54a9b0c0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,6 +11,12 @@ repos: - id: black args: ["--target-version", "py35"] + - repo: https://gitlab.com/pycqa/flake8 + rev: 3.7.9 + hooks: + - id: flake8 + additional_dependencies: [flake8-2020] + - repo: https://github.com/pre-commit/mirrors-isort rev: v4.3.21 hooks: