diff --git a/.travis.yml b/.travis.yml index b457dd1..9cb4e91 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,6 @@ python: stages: - lint - - moban - test @@ -23,16 +22,6 @@ stages: stage: lint script: make lint -.moban: &moban - python: 3.6 - env: - - MINREQ=0 - stage: moban - install: pip install moban gitfs2 pypifs moban-jinja2-github moban-ansible - script: - - moban - - git diff --exit-code - jobs: include: - *moban diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4051698..1c614f0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,13 @@ Change log ================================================================================ +0.6.0 - 8.10.2020 +-------------------------------------------------------------------------------- + +**added** + +#. new style reader and writer plugins. works with pyexcel-io v0.6.2 + 0.5.3 - 27.11.2018 -------------------------------------------------------------------------------- diff --git a/LICENSE b/LICENSE index 7b52f7f..0451a72 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2015-2019 by Onni Software Ltd. and its contributors +Copyright (c) 2015-2020 by Onni Software Ltd. and its contributors All rights reserved. Redistribution and use in source and binary forms of the software as well @@ -13,7 +13,7 @@ that the following conditions are met: and/or other materials provided with the distribution. * Neither the name of 'pyexcel-ods3' nor the names of the contributors - may be used to endorse or promote products derived from this software + may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND diff --git a/README.rst b/README.rst index f5064a2..b710b4a 100644 --- a/README.rst +++ b/README.rst @@ -29,6 +29,11 @@ pyexcel-ods3 - Let you focus on data, instead of ods format .. image:: https://img.shields.io/gitter/room/gitterHQ/gitter.svg :target: https://gitter.im/pyexcel/Lobby +.. image:: https://img.shields.io/static/v1?label=continuous%20templating&message=%E6%A8%A1%E7%89%88%E6%9B%B4%E6%96%B0&color=blue&style=flat-square + :target: https://moban.readthedocs.io/en/latest/#at-scale-continous-templating-for-open-source-projects + +.. image:: https://img.shields.io/static/v1?label=coding%20style&message=black&color=black&style=flat-square + :target: https://github.com/psf/black **pyexcel-ods3** is a tiny wrapper library to read, manipulate and write data in ods format. You are likely to use `pyexcel `__ together diff --git a/changelog.yml b/changelog.yml index fcecf33..3af250c 100644 --- a/changelog.yml +++ b/changelog.yml @@ -1,6 +1,12 @@ name: pyexcel-ods3 organisation: pyexcel releases: +- changes: + - action: added + details: + - 'new style reader and writer plugins. works with pyexcel-io v0.6.2' + date: 8.10.2020 + version: 0.6.0 - changes: - action: added details: diff --git a/docs/source/conf.py b/docs/source/conf.py index 24a1fe9..7f04ef6 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -22,12 +22,12 @@ # -- Project information ----------------------------------------------------- project = 'pyexcel-ods3' -copyright = '2015-2019 Onni Software Ltd.' +copyright = '2015-2020 Onni Software Ltd.' author = 'chfw' # The short X.Y version version = '0.6.0' # The full version, including alpha/beta/rc tags -release = '0.5.3' +release = '0.6.0' # -- General configuration --------------------------------------------------- diff --git a/pyexcel-ods3.yml b/pyexcel-ods3.yml index 9209539..c849847 100644 --- a/pyexcel-ods3.yml +++ b/pyexcel-ods3.yml @@ -3,16 +3,17 @@ name: "pyexcel-ods3" nick_name: ods3 version: 0.6.0 current_version: 0.6.0 -release: 0.5.3 +release: 0.6.0 file_type: ods is_on_conda: true dependencies: - - pyexcel-io>=0.5.10 + - pyexcel-io>=0.6.2 - lxml - pyexcel-ezodf>=0.3.3 test_dependencies: - pyexcel - psutil - pyexcel-xls -description: | - A wrapper library to read, manipulate and write data in ods format \ No newline at end of file +moban_command: false +description: |- + A wrapper library to read, manipulate and write data in ods format diff --git a/pyexcel_ods3/__init__.py b/pyexcel_ods3/__init__.py index 6929798..1c01207 100644 --- a/pyexcel_ods3/__init__.py +++ b/pyexcel_ods3/__init__.py @@ -15,15 +15,22 @@ # this line has to be place above all else # because of dynamic import -from pyexcel_io.plugins import IOPluginInfoChain +from pyexcel_io.plugins import IOPluginInfoChain, IOPluginInfoChainV2 __FILE_TYPE__ = "ods" -IOPluginInfoChain(__name__).add_a_reader( +IOPluginInfoChainV2(__name__).add_a_reader( relative_plugin_class_path="odsr.ODSBook", + locations=["file", "memory"], + file_types=[__FILE_TYPE__], + stream_type="binary", +).add_a_reader( + relative_plugin_class_path="odsr.ODSBookInContent", + locations=["content"], file_types=[__FILE_TYPE__], stream_type="binary", ).add_a_writer( relative_plugin_class_path="odsw.ODSWriter", + locations=["file", "memory"], file_types=[__FILE_TYPE__], stream_type="binary", ) diff --git a/pyexcel_ods3/odsr.py b/pyexcel_ods3/odsr.py index 2dc8d1a..3db0fd1 100644 --- a/pyexcel_ods3/odsr.py +++ b/pyexcel_ods3/odsr.py @@ -4,42 +4,38 @@ ods reader - :copyright: (c) 2015-2017 by Onni Software Ltd. & its contributors + :copyright: (c) 2015-2020 by Onni Software Ltd. & its contributors :license: New BSD License """ -import ezodf +from io import BytesIO +import ezodf import pyexcel_io.service as service -from pyexcel_io.book import BookReader -from pyexcel_io.sheet import SheetReader -from pyexcel_io._compact import OrderedDict +from pyexcel_io.plugin_api import ISheet, IReader, NamedContent -class ODSSheet(SheetReader): +class ODSSheet(ISheet): """ODS sheet representation""" - def __init__(self, sheet, auto_detect_int=True, **keywords): - SheetReader.__init__(self, sheet, **keywords) + def __init__(self, sheet, auto_detect_int=True): self.auto_detect_int = auto_detect_int + self.ods_sheet = sheet - @property - def name(self): - return self._native_sheet.name - - def number_of_rows(self): + def row_iterator(self): """ Number of rows in the xls sheet """ - return self._native_sheet.nrows() + return range(self.ods_sheet.nrows()) - def number_of_columns(self): + def column_iterator(self, row): """ Number of columns in the xls sheet """ - return self._native_sheet.ncols() + for column in range(self.ods_sheet.ncols()): + yield self.cell_value(row, column) def cell_value(self, row, column): - cell = self._native_sheet.get_cell((row, column)) + cell = self.ods_sheet.get_cell((row, column)) cell_type = cell.value_type ret = None if cell_type == "currency": @@ -63,63 +59,28 @@ def cell_value(self, row, column): return ret -class ODSBook(BookReader): - """read a ods book out""" - - def open(self, file_name, **keywords): - """load ods from file""" - BookReader.open(self, file_name, **keywords) - self._load_from_file() - - def open_stream(self, file_stream, **keywords): - """load ods from file stream""" - BookReader.open_stream(self, file_stream, **keywords) - self._load_from_memory() - - def read_sheet_by_name(self, sheet_name): - """read a named sheet""" - rets = [ - sheet - for sheet in self._native_book.sheets - if sheet.name == sheet_name +class ODSBook(IReader): + def __init__(self, file_alike_object, file_type, **keywords): + self.ods_book = ezodf.opendoc(file_alike_object) + self._keywords = keywords + self.content_array = [ + NamedContent(sheet.name, sheet) for sheet in self.ods_book.sheets ] - if len(rets) == 0: - raise ValueError("%s cannot be found" % sheet_name) - elif len(rets) == 1: - return self.read_sheet(rets[0]) - else: - raise ValueError( - "More than 1 sheet named as %s are found" % sheet_name - ) - - def read_sheet_by_index(self, sheet_index): - """read a sheet at an index""" - sheets = self._native_book.sheets - length = len(sheets) - if sheet_index < length: - return self.read_sheet(sheets[sheet_index]) - else: - raise IndexError( - "Index %d of out bound %d." % (sheet_index, length) - ) - - def read_all(self): - """read all available sheets""" - result = OrderedDict() - for sheet in self._native_book.sheets: - data_dict = self.read_sheet(sheet) - result.update(data_dict) - return result - - def read_sheet(self, native_sheet): + + def read_sheet(self, native_sheet_index): + native_sheet = self.content_array[native_sheet_index].payload sheet = ODSSheet(native_sheet, **self._keywords) - return {native_sheet.name: sheet.to_array()} + return sheet def close(self): - self._native_book = None + self.ods_book = None + - def _load_from_file(self): - self._native_book = ezodf.opendoc(self._file_name) +class ODSBookInContent(ODSBook): + """ + Open xlsx as read only mode + """ - def _load_from_memory(self): - self._native_book = ezodf.opendoc(self._file_stream) + def __init__(self, file_content, file_type, **keywords): + io = BytesIO(file_content) + super().__init__(io, file_type, **keywords) diff --git a/pyexcel_ods3/odsw.py b/pyexcel_ods3/odsw.py index 76a0ebe..474ca43 100644 --- a/pyexcel_ods3/odsw.py +++ b/pyexcel_ods3/odsw.py @@ -4,31 +4,30 @@ ods writer using ezodf - :copyright: (c) 2015-2017 by Onni Software Ltd. & its contributors + :copyright: (c) 2015-2020 by Onni Software Ltd. & its contributors :license: New BSD License """ import types import ezodf - import pyexcel_io.service as service -from pyexcel_io.book import BookWriter -from pyexcel_io.sheet import SheetWriter from pyexcel_io.constants import MAX_INTEGER from pyexcel_io.exceptions import IntegerAccuracyLossError +from pyexcel_io.plugin_api import IWriter, ISheetWriter -class ODSSheetWriter(SheetWriter): +class ODSSheetWriter(ISheetWriter): """ ODS sheet writer """ - def set_sheet_name(self, name): - self._native_sheet = ezodf.Sheet(name) + def __init__(self, ods_book, ods_sheet, sheet_name, **keywords): + self.ods_book = ods_book + self.ods_sheet = ezodf.Sheet(sheet_name) self.current_row = 0 - def set_size(self, size): - self._native_sheet.reset(size=size) + def _set_size(self, size): + self.ods_sheet.reset(size=size) def write_row(self, array): """ @@ -48,7 +47,7 @@ def write_row(self, array): elif value_type == "float": if cell > MAX_INTEGER: raise IntegerAccuracyLossError("%s is too big" % cell) - self._native_sheet[self.current_row, count].set_value( + self.ods_sheet[self.current_row, count].set_value( cell, value_type=value_type ) count += 1 @@ -62,7 +61,7 @@ def write_array(self, table): if rows < 1: return columns = max([len(row) for row in to_write_data]) - self.set_size((rows, columns)) + self._set_size((rows, columns)) for row in to_write_data: self.write_row(row) @@ -71,40 +70,36 @@ def close(self): This call writes file """ - self._native_book.sheets += self._native_sheet + self.ods_book.sheets += self.ods_sheet -class ODSWriter(BookWriter): +class ODSWriter(IWriter): """ open document spreadsheet writer """ - def __init__(self): - BookWriter.__init__(self) - self._native_book = None - - def open(self, file_name, **keywords): + def __init__( + self, file_alike_object, file_type, skip_backup=True, **keywords + ): """open a file for writing ods""" - BookWriter.open(self, file_name, **keywords) - self._native_book = ezodf.newdoc( - doctype="ods", filename=self._file_alike_object + self.ods_book = ezodf.newdoc( + doctype=file_type, filename=file_alike_object ) - skip_backup_flag = self._keywords.get("skip_backup", True) - if skip_backup_flag: - self._native_book.backup = False + if skip_backup: + self.ods_book.backup = False def create_sheet(self, name): """ write a row into the file """ - return ODSSheetWriter(self._native_book, None, name) + return ODSSheetWriter(self.ods_book, None, name) def close(self): """ This call writes file """ - self._native_book.save() - self._native_book = None + self.ods_book.save() + self.ods_book = None diff --git a/requirements.txt b/requirements.txt index 6c45e9a..6073d2f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -pyexcel-io>=0.5.10 +pyexcel-io>=0.6.2 lxml pyexcel-ezodf>=0.3.3 diff --git a/rnd_requirements.txt b/rnd_requirements.txt index 0a8701a..e69de29 100644 --- a/rnd_requirements.txt +++ b/rnd_requirements.txt @@ -1,2 +0,0 @@ -https://github.com/pyexcel/pyexcel-ezodf/archive/master.zip -https://github.com/pyexcel/pyexcel-io/archive/v0.5.10.zip diff --git a/setup.py b/setup.py index 70c2fc8..b9b3810 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ "A wrapper library to read, manipulate and write data in ods format" ) URL = "https://github.com/pyexcel/pyexcel-ods3" -DOWNLOAD_URL = "%s/archive/0.5.3.tar.gz" % URL +DOWNLOAD_URL = "%s/archive/0.6.0.tar.gz" % URL FILES = ["README.rst", "CHANGELOG.rst"] KEYWORDS = [ "python", @@ -63,7 +63,7 @@ INSTALL_REQUIRES = [ - "pyexcel-io>=0.5.10", + "pyexcel-io>=0.6.2", "lxml", "pyexcel-ezodf>=0.3.3", ] @@ -74,8 +74,8 @@ } # You do not need to read beyond this line PUBLISH_COMMAND = "{0} setup.py sdist bdist_wheel upload -r pypi".format(sys.executable) -GS_COMMAND = ("gs pyexcel-ods3 v0.5.3 " + - "Find 0.5.3 in changelog for more details") +GS_COMMAND = ("gs pyexcel-ods3 v0.6.0 " + + "Find 0.6.0 in changelog for more details") NO_GS_MESSAGE = ("Automatic github release is disabled. " + "Please install gease to enable it.") UPLOAD_FAILED_MSG = ( diff --git a/tests/base.py b/tests/base.py index 3d40097..55864a5 100644 --- a/tests/base.py +++ b/tests/base.py @@ -136,6 +136,6 @@ def test_formats(self): # Text eq_(self.data["Sheet1"][1][9], "abc") - @raises(IndexError) - def test_no_excessive_trailing_columns(self): - eq_(self.data["Sheet1"][2][6], "") + @raises(IndexError) + def test_no_excessive_trailing_columns(self): + eq_(self.data["Sheet1"][2][6], "") diff --git a/tests/test_bug_fixes.py b/tests/test_bug_fixes.py index 925fdc2..7d78acf 100644 --- a/tests/test_bug_fixes.py +++ b/tests/test_bug_fixes.py @@ -4,43 +4,14 @@ import psutil import pyexcel as pe +from pyexcel_io.exceptions import IntegerAccuracyLossError from nose import SkipTest from nose.tools import eq_, raises -from pyexcel_io.exceptions import IntegerAccuracyLossError IN_TRAVIS = "TRAVIS" in os.environ -@raises(Exception) -def test_invalid_date(): - from pyexcel_ods3.ods import date_value - - value = "2015-08-" - date_value(value) - - -@raises(Exception) -def test_fake_date_time_10(): - from pyexcel_ods3.ods import date_value - - date_value("1234567890") - - -@raises(Exception) -def test_fake_date_time_19(): - from pyexcel_ods3.ods import date_value - - date_value("1234567890123456789") - - -@raises(Exception) -def test_fake_date_time_20(): - from pyexcel_ods3.ods import date_value - - date_value("12345678901234567890") - - def test_issue_10(): test_file_name = "test_issue_10.ods" from pyexcel_ods3 import save_data diff --git a/tests/test_filter.py b/tests/test_filter.py index 1031451..376d6fc 100644 --- a/tests/test_filter.py +++ b/tests/test_filter.py @@ -1,8 +1,9 @@ import os -from nose.tools import eq_ from pyexcel_io import get_data, save_data +from nose.tools import eq_ + class TestFilter: def setUp(self): diff --git a/tests/test_multiple_sheets.py b/tests/test_multiple_sheets.py index f82b212..e2d52e2 100644 --- a/tests/test_multiple_sheets.py +++ b/tests/test_multiple_sheets.py @@ -1,16 +1,11 @@ import os -import sys +from collections import OrderedDict import pyexcel from base import PyexcelMultipleSheetBase from nose.tools import raises -if sys.version_info[0] == 2 and sys.version_info[1] < 7: - from ordereddict import OrderedDict -else: - from collections import OrderedDict - class TestOdsNxlsMultipleSheets(PyexcelMultipleSheetBase): def setUp(self): diff --git a/tests/test_ods_reader.py b/tests/test_ods_reader.py index e1dc898..d32cd01 100644 --- a/tests/test_ods_reader.py +++ b/tests/test_ods_reader.py @@ -1,13 +1,13 @@ import os from base import ODSCellTypes -from pyexcel_ods3.odsr import ODSBook +from pyexcel_io.reader import Reader from pyexcel_ods3.odsw import ODSWriter class TestODSReader(ODSCellTypes): def setUp(self): - r = ODSBook() + r = Reader("ods") r.open(os.path.join("tests", "fixtures", "ods_formats.ods")) self.data = r.read_all() for key in self.data.keys(): @@ -17,22 +17,23 @@ def setUp(self): class TestODSWriter(ODSCellTypes): def setUp(self): - r = ODSBook() + r = Reader("ods") r.open( os.path.join("tests", "fixtures", "ods_formats.ods"), skip_empty_rows=True, ) self.data1 = r.read_all() + r.close() self.testfile = "odswriter.ods" - w = ODSWriter() - w.open(self.testfile) + w = ODSWriter(self.testfile, "ods") w.write(self.data1) w.close() - r2 = ODSBook() - r2.open(self.testfile) - self.data = r2.read_all() + r.open(self.testfile) + self.data = r.read_all() + for key in self.data.keys(): self.data[key] = list(self.data[key]) + r.close() def tearDown(self): if os.path.exists(self.testfile): diff --git a/tests/test_writer.py b/tests/test_writer.py index 93d5efb..739515c 100644 --- a/tests/test_writer.py +++ b/tests/test_writer.py @@ -1,7 +1,7 @@ import os from base import PyexcelWriterBase, PyexcelHatWriterBase -from pyexcel_ods3.odsr import ODSBook as Reader +from pyexcel_ods3 import get_data from pyexcel_ods3.odsw import ODSWriter as Writer @@ -13,17 +13,13 @@ def test_write_book(self): "Sheet3": [[u"X", u"Y", u"Z"], [1, 4, 7], [2, 5, 8], [3, 6, 9]], } self.testfile = "writer.ods" - writer = Writer() - writer.open(self.testfile) + writer = Writer(self.testfile, "ods") writer.write(self.content) writer.close() - reader = Reader() - reader.open(self.testfile) - content = reader.read_all() + content = get_data(self.testfile) for key in content.keys(): content[key] = list(content[key]) assert content == self.content - reader.close() def tearDown(self): if os.path.exists(self.testfile):