diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..0faea60 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +github: chfw +patreon: chfw diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..6017f21 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,9 @@ +With your PR, here is a check list: + +- [ ] Has test cases written? +- [ ] Has all code lines tested? +- [ ] Has `make format` been run? +- [ ] Please update CHANGELOG.yml(not CHANGELOG.rst) +- [ ] Passes all Travis CI builds +- [ ] Has fair amount of documentation if your change is complex +- [ ] Agree on NEW BSD License for your contribution diff --git a/.github/workflows/moban-update.yml b/.github/workflows/moban-update.yml new file mode 100644 index 0000000..706fd82 --- /dev/null +++ b/.github/workflows/moban-update.yml @@ -0,0 +1,29 @@ +on: [push] + +jobs: + run_moban: + runs-on: ubuntu-latest + name: synchronize templates via moban + steps: + - uses: actions/checkout@v2 + with: + ref: ${{ github.head_ref }} + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: '3.7' + - name: check changes + run: | + pip install moban gitfs2 pypifs moban-jinja2-github moban-ansible + moban + git status + git diff --exit-code + - name: Auto-commit + if: failure() + uses: docker://cdssnc/auto-commit-github-action + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + args: >- + This is an auto-commit, updating project meta data, + such as changelog.rst, contributors.rst diff --git a/.gitignore b/.gitignore index d09b336..e8b12f9 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ parts/ sdist/ var/ wheels/ +share/python-wheels/ *.egg-info/ .installed.cfg *.egg @@ -43,14 +44,17 @@ pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ +.nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover +*.py,cover .hypothesis/ .pytest_cache/ +cover/ # Translations *.mo @@ -60,6 +64,7 @@ coverage.xml *.log local_settings.py db.sqlite3 +db.sqlite3-journal # Flask stuff: instance/ @@ -72,16 +77,34 @@ instance/ docs/_build/ # PyBuilder +.pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints +# IPython +profile_default/ +ipython_config.py + # pyenv -.python-version +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ -# celery beat schedule file +# Celery stuff celerybeat-schedule +celerybeat.pid # SageMath parsed files *.sage.py @@ -107,6 +130,17 @@ venv.bak/ # mypy .mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ # VirtualEnv rules # Virtualenv @@ -140,6 +174,7 @@ pip-selfcheck.json # Windows rules # Windows thumbnail cache files Thumbs.db +Thumbs.db:encryptable ehthumbs.db ehthumbs_vista.db @@ -238,9 +273,14 @@ flycheck_*.el # directory configuration .dir-locals.el +# network security +/network-security.data + + # Vim rules # Swap [._]*.s[a-v][a-z] +!*.svg # comment out if you don't need vector files [._]*.sw[a-p] [._]s[a-rt-v][a-z] [._]ss[a-gi-z] @@ -248,6 +288,7 @@ flycheck_*.el # Session Session.vim +Sessionx.vim # Temporary .netrwhist @@ -258,7 +299,7 @@ tags [._]*.un~ # JetBrains rules -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # User-specific stuff @@ -268,6 +309,9 @@ tags .idea/**/dictionaries .idea/**/shelf +# Generated files +.idea/**/contentModel.xml + # Sensitive or high-churn files .idea/**/dataSources/ .idea/**/dataSources.ids @@ -281,6 +325,19 @@ tags .idea/**/gradle.xml .idea/**/libraries +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + # CMake cmake-build-*/ @@ -311,6 +368,9 @@ fabric.properties # Editor-based Rest Client .idea/httpRequests +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + # SublimeText rules # Cache files for Sublime Text *.tmlanguage.cache @@ -326,6 +386,7 @@ fabric.properties # SFTP configuration file sftp-config.json +sftp-config-alt*.json # Package control specific files Package Control.last-run @@ -363,6 +424,10 @@ tmtags !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ # Xcode rules # Xcode @@ -389,8 +454,10 @@ DerivedData/ *.perspectivev3 !default.perspectivev3 -# Eclipse rules +## Gcc Patch +/*.gcno +# Eclipse rules .metadata bin/ tmp/ @@ -441,12 +508,17 @@ local.properties # Annotation Processing .apt_generated/ +.apt_generated_test/ # Scala IDE specific (Scala & Java development for Eclipse) .cache-main .scala_dependencies .worksheet +# Uncomment this line if you wish to ignore the project description file. +# Typically, this file would be tracked if it contains build/dependency configurations: +#.project + # TortoiseGit rules # Project-level settings /.tgitconfig @@ -468,3 +540,7 @@ cscope.files cscope.out cscope.in.out cscope.po.out + + +# remove moban hash dictionary +.moban.hashes diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 0000000..8bdda20 --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,10 @@ +[settings] +line_length=79 +known_first_party=pyexcel,xlrd,xlwt +known_third_party=mock,nose +indent=' ' +multi_line_output=3 +length_sort=1 +default_section=FIRSTPARTY +no_lines_before=LOCALFOLDER +sections=FUTURE,STDLIB,FIRSTPARTY,THIRDPARTY,LOCALFOLDER diff --git a/.moban.d/README.rst b/.moban.d/custom_README.rst.jj2 similarity index 100% rename from .moban.d/README.rst rename to .moban.d/custom_README.rst.jj2 diff --git a/.moban.d/setup.py b/.moban.d/custom_setup.py.jj2 similarity index 82% rename from .moban.d/setup.py rename to .moban.d/custom_setup.py.jj2 index 8fc8de0..81aed01 100644 --- a/.moban.d/setup.py +++ b/.moban.d/custom_setup.py.jj2 @@ -9,6 +9,8 @@ 'xlsm' {%endblock%} +{% block morefiles %}"CONTRIBUTORS.rst",{% endblock %} + {%block additional_classifiers%} 'Programming Language :: Python :: Implementation :: PyPy' {%endblock%} diff --git a/.moban.d/tests/base.py b/.moban.d/tests/custom_base.py.jj2 similarity index 80% rename from .moban.d/tests/base.py rename to .moban.d/tests/custom_base.py.jj2 index b7510db..156f96c 100644 --- a/.moban.d/tests/base.py +++ b/.moban.d/tests/custom_base.py.jj2 @@ -1,4 +1,4 @@ {% extends 'tests/base.py.jj2' %} {%block ods_types%} -{%endblock%} \ No newline at end of file +{%endblock%} diff --git a/.moban.d/tests/requirements.txt b/.moban.d/tests/custom_requirements.txt.jj2 similarity index 60% rename from .moban.d/tests/requirements.txt rename to .moban.d/tests/custom_requirements.txt.jj2 index 414637d..6ca5326 100644 --- a/.moban.d/tests/requirements.txt +++ b/.moban.d/tests/custom_requirements.txt.jj2 @@ -2,4 +2,7 @@ {%block extras %} pyexcel xlrd==1.1.0 +moban +black;python_version>="3.6" +isort;python_version>="3.6" {%endblock%} diff --git a/.moban.d/travis.yml b/.moban.d/travis.yml deleted file mode 100644 index e6ce23b..0000000 --- a/.moban.d/travis.yml +++ /dev/null @@ -1,29 +0,0 @@ -{% extends "travis.yml.jj2" %} -{%block test_other_python_versions%} - 2.6 - - 3.3 - - 3.4 - - 3.5 - - pypy - - 3.6 -{%endblock%} - -{%block extra_matrix%} -matrix: - include: - - python: 2.7 - dist: trusty - sudo: required - virtualenv: - system_site_packages: true - addons: - apt: - packages: - - python-xlwt - - python-xlrd - - python-coverage - - python-nose - - python-mock -{%endblock%} - -{%block custom_install %} - if [[ $TRAVIS_PYTHON_VERSION == "2.6" ]]; then pip install ordereddict; fi -{%endblock%} diff --git a/.moban.d/xls_travis.yml.jj2 b/.moban.d/xls_travis.yml.jj2 new file mode 100644 index 0000000..a62287f --- /dev/null +++ b/.moban.d/xls_travis.yml.jj2 @@ -0,0 +1,8 @@ +{% extends "travis.yml.jj2" %} +{%block custom_python_versions%} +python: + - 3.8 + - 3.7 + - 3.6 +{%endblock%} + diff --git a/.moban.yml b/.moban.yml index 1089516..2af1b28 100644 --- a/.moban.yml +++ b/.moban.yml @@ -1,26 +1,11 @@ +overrides: "git://github.com/pyexcel/pyexcel-mobans!/mobanfile.yaml" configuration: - configuration_dir: "commons/config" - template_dir: - - "commons/templates" - - "setupmobans/templates" - - ".moban.d" configuration: pyexcel-xls.yml targets: - - README.rst: README.rst - - setup.py: setup.py + - README.rst: custom_README.rst.jj2 + - setup.py: custom_setup.py.jj2 - "docs/source/conf.py": "docs/source/conf.py.jj2" - - .travis.yml: travis.yml - - requirements.txt: requirements.txt + - .travis.yml: xls_travis.yml.jj2 - .gitignore: gitignore.jj2 - MANIFEST.in: MANIFEST.in.jj2 - - "tests/requirements.txt": "tests/requirements.txt" - - test.sh: test.script.jj2 - - test.bat: test.script.jj2 - - "tests/test_filter.py": "tests/test_filter.py.jj2" - - "tests/test_formatters.py": "tests/test_formatters.py.jj2" - - "tests/test_stringio.py": "tests/test_stringio.py.jj2" - - "tests/test_writer.py": "tests/test_writer.py.jj2" - - "tests/base.py": "tests/base.py" - - output: CHANGELOG.rst - configuration: changelog.yml - template: CHANGELOG.rst.jj2 + - "tests/requirements.txt": "tests/custom_requirements.txt.jj2" diff --git a/.travis.yml b/.travis.yml index 5817466..9cb4e91 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,36 +1,40 @@ sudo: false +dist: xenial language: python notifications: email: false python: - - pypy-5.3.1 - - 3.7-dev + - 3.8 + - 3.7 - 3.6 - - 3.5 - - 3.4 - - 2.7 -matrix: + +stages: + - lint + - test + + +.lint: &lint + git: + submodules: false + python: 3.6 + env: + - MINREQ=0 + stage: lint + script: make lint + +jobs: include: - - python: 2.7 - dist: trusty - sudo: required - virtualenv: - system_site_packages: true - addons: - apt: - packages: - - python-xlwt - - python-xlrd - - python-coverage - - python-nose - - python-mock + - *moban + - *lint + +stage: test + before_install: - - if [[ $TRAVIS_PYTHON_VERSION == "2.6" ]]; then pip install ordereddict; fi - - if [[ $TRAVIS_PYTHON_VERSION == "2.6" ]]; then pip install flake8==2.6.2; fi - if [[ -f min_requirements.txt && "$MINREQ" -eq 1 ]]; then mv min_requirements.txt requirements.txt ; fi - - test ! -f rnd_requirements.txt || pip install --no-deps -r rnd_requirements.txt + - test ! -f rnd_requirements.txt || + pip install --no-deps -r rnd_requirements.txt - test ! -f rnd_requirements.txt || pip install -r rnd_requirements.txt ; - pip install -r tests/requirements.txt script: diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 54af2ad..f876cbb 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,19 +1,24 @@ Change log ================================================================================ + +0.6.0 - 8.10.2020 +-------------------------------------------------------------------------------- + +**Updated** + +#. New style xlsx plugins, promoted by pyexcel-io v0.6.2. + 0.5.9 - 29.08.2020 -------------------------------------------------------------------------------- -Added +**Added** +#. `#35 `_, include tests -#. `pyexcel-xls#35 `_, include -tests - 0.5.8 - 22.08.2018 -------------------------------------------------------------------------------- -Added - +**Added** #. `pyexcel#151 `_, read cell error as #N/A. @@ -21,8 +26,7 @@ Added 0.5.7 - 15.03.2018 -------------------------------------------------------------------------------- -Added - +**Added** #. `pyexcel#54 `_, Book.datemode attribute of that workbook should be passed always. @@ -30,8 +34,7 @@ Added 0.5.6 - 15.03.2018 -------------------------------------------------------------------------------- -Added - +**Added** #. `pyexcel#120 `_, xlwt cannot save a book without any sheet. So, let's raise an exception in this case in @@ -40,8 +43,7 @@ Added 0.5.5 - 8.11.2017 -------------------------------------------------------------------------------- -Added - +**Added** #. `#25 `_, detect merged cell in .xls @@ -49,8 +51,7 @@ Added 0.5.4 - 2.11.2017 -------------------------------------------------------------------------------- -Added - +**Added** #. `#24 `_, xlsx format cannot use skip_hidden_row_and_column. please use pyexcel-xlsx instead. @@ -58,8 +59,7 @@ Added 0.5.3 - 2.11.2017 -------------------------------------------------------------------------------- -Added - +**Added** #. `#21 `_, skip hidden rows and columns under 'skip_hidden_row_and_column' flag. @@ -67,7 +67,7 @@ Added 0.5.2 - 23.10.2017 -------------------------------------------------------------------------------- -updated +**updated** #. pyexcel `pyexcel#105 `_, remove gease from setup_requires, introduced by 0.5.1. @@ -77,7 +77,7 @@ updated 0.5.1 - 20.10.2017 -------------------------------------------------------------------------------- -added +**added** #. `pyexcel#103 `_, include LICENSE file in MANIFEST.in, meaning LICENSE file will appear in the released @@ -86,7 +86,7 @@ added 0.5.0 - 30.08.2017 -------------------------------------------------------------------------------- -Updated +**Updated** #. `#20 `_, is handled in pyexcel-io @@ -96,7 +96,7 @@ Updated 0.4.1 - 25.08.2017 -------------------------------------------------------------------------------- -Updated +**Updated** #. `#20 `_, handle unseekable stream given by http response. @@ -104,7 +104,7 @@ Updated 0.4.0 - 19.06.2017 -------------------------------------------------------------------------------- -Updated +**Updated** #. `pyexcel-xlsx#15 `_, close file handle @@ -114,7 +114,7 @@ Updated 0.3.3 - 30/05/2017 -------------------------------------------------------------------------------- -Updated +**Updated** #. `#18 `_, pass on encoding_override and others to xlrd. @@ -122,7 +122,7 @@ Updated 0.3.2 - 18.05.2017 -------------------------------------------------------------------------------- -Updated +**Updated** #. `#16 `_, allow mmap to be passed as file content @@ -130,7 +130,7 @@ Updated 0.3.1 - 16.01.2017 -------------------------------------------------------------------------------- -Updated +**Updated** #. `#14 `_, Python 3.6 - cannot use LOCALE flag with a str pattern @@ -139,7 +139,7 @@ Updated 0.3.0 - 22.12.2016 -------------------------------------------------------------------------------- -Updated +**Updated** #. `#13 `_, alert on empyty file content @@ -148,8 +148,50 @@ Updated 0.2.3 - 20.09.2016 -------------------------------------------------------------------------------- -Updated +**Updated** #. `#10 `_, To support generator as member of the incoming two dimensional data +0.2.2 - 31.08.2016 +-------------------------------------------------------------------------------- + +**Added** + +#. support pagination. two pairs: start_row, row_limit and start_column, + column_limit help you deal with large files. + +0.2.1 - 13.07.2016 +-------------------------------------------------------------------------------- + +**Added** + +#. `#9 `_, `skip_hidden_sheets` + is added. By default, hidden sheets are skipped when reading all sheets. + Reading sheet by name or by index are not affected. + +0.2.0 - 01.06.2016 +-------------------------------------------------------------------------------- + +**Added** + +#. By default, `float` will be converted to `int` where fits. `auto_detect_int`, + a flag to switch off the autoatic conversion from `float` to `int`. +#. 'library=pyexcel-xls' was added so as to inform pyexcel to use it instead of + other libraries, in the situation where there are more than one plugin for a + file type, e.g. xlsm + +**Updated** + +#. support the auto-import feature of pyexcel-io 0.2.0 +#. xlwt is now used for python 2 implementation while xlwt-future is used for + python 3 + +0.1.0 - 17.01.2016 +-------------------------------------------------------------------------------- + +**Added** + +#. Passing "streaming=True" to get_data, you will get the two dimensional array + as a generator +#. Passing "data=your_generator" to save_data is acceptable too. diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst new file mode 100644 index 0000000..9265c0f --- /dev/null +++ b/CONTRIBUTORS.rst @@ -0,0 +1,8 @@ + + +1 contributors +================================================================================ + +In alphabetical order: + +* `John Vandenberg `_ diff --git a/LICENSE b/LICENSE index 4066e82..923df6b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2015-2017 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 @@ -27,4 +27,4 @@ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -DAMAGE. \ No newline at end of file +DAMAGE. diff --git a/MANIFEST.in b/MANIFEST.in index b1bf562..e86ae54 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,6 @@ include README.rst include LICENSE include CHANGELOG.rst +include CONTRIBUTORS.rst recursive-include tests * recursive-include docs * diff --git a/Makefile b/Makefile index 10b28ae..3e0ee51 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,16 @@ all: test -test: +test: lint bash test.sh +install_test: + pip install -r tests/requirements.txt + +lint: + bash lint.sh + +format: + bash format.sh + +git-diff-check: + git diff --exit-code diff --git a/README.rst b/README.rst index d2b5ea5..aea5b91 100644 --- a/README.rst +++ b/README.rst @@ -3,10 +3,10 @@ pyexcel-xls - Let you focus on data, instead of xls format ================================================================================ .. image:: https://raw.githubusercontent.com/pyexcel/pyexcel.github.io/master/images/patreon.png - :target: https://www.patreon.com/pyexcel + :target: https://www.patreon.com/chfw -.. image:: https://api.bountysource.com/badge/team?team_id=288537 - :target: https://salt.bountysource.com/teams/chfw-pyexcel +.. image:: https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg + :target: https://awesome-python.com/#specific-formats-processing .. image:: https://travis-ci.org/pyexcel/pyexcel-xls.svg?branch=master :target: http://travis-ci.org/pyexcel/pyexcel-xls @@ -14,9 +14,26 @@ pyexcel-xls - Let you focus on data, instead of xls format .. image:: https://codecov.io/gh/pyexcel/pyexcel-xls/branch/master/graph/badge.svg :target: https://codecov.io/gh/pyexcel/pyexcel-xls +.. image:: https://badge.fury.io/py/pyexcel-xls.svg + :target: https://pypi.org/project/pyexcel-xls + +.. image:: https://anaconda.org/conda-forge/pyexcel-xls/badges/version.svg + :target: https://anaconda.org/conda-forge/pyexcel-xls + +.. image:: https://pepy.tech/badge/pyexcel-xls/month + :target: https://pepy.tech/project/pyexcel-xls/month + +.. image:: https://anaconda.org/conda-forge/pyexcel-xls/badges/downloads.svg + :target: https://anaconda.org/conda-forge/pyexcel-xls + .. 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-xls** is a tiny wrapper library to read, manipulate and write data in xls format and it can read xlsx and xlsm fromat. You are likely to use it with `pyexcel `_. @@ -25,6 +42,24 @@ New flag: `detect_merged_cells` allows you to spread the same value among all me New flag: `skip_hidden_row_and_column` allows you to skip hidden rows and columns and is defaulted to **True**. It may slow down its reading performance. And it is only valid for 'xls' files. For 'xlsx' files, please use pyexcel-xlsx. +Support the project +================================================================================ + +If your company has embedded pyexcel and its components into a revenue generating +product, please support me on github, `patreon `_ +or `bounty source `_ to maintain +the project and develop it further. + +If you are an individual, you are welcome to support me too and for however long +you feel like. As my backer, you will receive +`early access to pyexcel related contents `_. + +And your issues will get prioritized if you would like to become my patreon as `pyexcel pro user`. + +With your financial support, I will be able to invest +a little bit more time in coding, documentation and writing interesting posts. + + Known constraints ================== @@ -49,24 +84,6 @@ or clone it and install it: $ cd pyexcel-xls $ python setup.py install -Support the project -================================================================================ - -If your company has embedded pyexcel and its components into a revenue generating -product, please support me on `patreon `_ -or `bounty source `_ to maintain -the project and develop it further. - -If you are an individual, you are welcome to support me too and for however long -you feel like. As my backer, you will receive -`early access to pyexcel related contents `_. - -And your issues will get prioritized if you would like to become my patreon as `pyexcel pro user`. - -With your financial support, I will be able to invest -a little bit more time in coding, documentation and writing interesting posts. - - Usage ================================================================================ @@ -342,36 +359,16 @@ On Windows systems, please issue this command:: > test.bat -How to update test environment and update documentation ---------------------------------------------------------- - -Additional steps are required: - -#. pip install moban -#. git clone https://github.com/moremoban/setupmobans.git # generic setup -#. git clone https://github.com/pyexcel/pyexcel-commons.git commons -#. make your changes in `.moban.d` directory, then issue command `moban` -What is pyexcel-commons ---------------------------------- - -Many information that are shared across pyexcel projects, such as: this developer guide, license info, etc. are stored in `pyexcel-commons` project. +Before you commit +------------------------------ -What is .moban.d ---------------------------------- +Please run:: -`.moban.d` stores the specific meta data for the library. + $ make format -Acceptance criteria -------------------- +so as to beautify your code otherwise travis-ci may fail your unit test. -#. Has Test cases written -#. Has all code lines tested -#. Passes all Travis CI builds -#. Has fair amount of documentation if your change is complex -#. Please update CHANGELOG.rst -#. Please add yourself to CONTRIBUTORS.rst -#. Agree on NEW BSD License for your contribution Known Issues ============= diff --git a/changelog.yml b/changelog.yml index f25f270..6717663 100644 --- a/changelog.yml +++ b/changelog.yml @@ -1,6 +1,18 @@ name: pyexcel-xls organisation: pyexcel releases: +- changes: + - action: Updated + details: + - 'New style xlsx plugins, promoted by pyexcel-io v0.6.2.' + date: 8.10.2020 + version: 0.6.0 +- changes: + - action: Added + details: + - "`#35`, include tests" + date: 29.08.2020 + version: 0.5.9 - changes: - action: Added details: diff --git a/docs/source/conf.py b/docs/source/conf.py index f3b62db..128c99c 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -4,13 +4,11 @@ ' reads xlsx and xlsm format' + '' ) -# -*- coding: utf-8 -*- -# # Configuration file for the Sphinx documentation builder. # -# This file does only contain a selection of the most common options. For a -# full list see the documentation: -# http://www.sphinx-doc.org/en/master/config +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- @@ -24,44 +22,24 @@ # -- Project information ----------------------------------------------------- -project = u'pyexcel-xls' -copyright = u'2015-2018 Onni Software Ltd.' -author = u'C.W.' - +project = 'pyexcel-xls' +copyright = '2015-2020 Onni Software Ltd.' +author = 'chfw' # The short X.Y version -version = u'0.5.8' +version = '0.6.0' # The full version, including alpha/beta/rc tags -release = u'0.5.8' - +release = '0.6.0' # -- General configuration --------------------------------------------------- -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - # 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.doctest', - 'sphinx.ext.intersphinx', - 'sphinx.ext.viewcode', -] +extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode',] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The master toctree document. -master_doc = 'index' - # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # @@ -74,9 +52,6 @@ # This pattern also affects html_static_path and html_extra_path. exclude_patterns = [] -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - # -- Options for HTML output ------------------------------------------------- @@ -85,107 +60,16 @@ # 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 = {} - # 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, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# The default sidebars (for documents that don't match any pattern) are -# defined by theme itself. Builtin themes are using these templates by -# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', -# 'searchbox.html']``. -# -# html_sidebars = {} - - -# -- Options for HTMLHelp output --------------------------------------------- - -# Output file base name for HTML help builder. -htmlhelp_basename = 'pyexcel-xlsdoc' - - -# -- Options for LaTeX output ------------------------------------------------ - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'pyexcel-xls.tex', u'pyexcel-xls Documentation', - u'Onni Software Ltd.', 'manual'), -] - - -# -- Options for manual page output ------------------------------------------ - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'pyexcel-xls', u'pyexcel-xls Documentation', - [author], 1) -] - - -# -- Options for Texinfo output ---------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'pyexcel-xls', u'pyexcel-xls Documentation', - author, 'pyexcel-xls', 'One line description of project.', - 'Miscellaneous'), -] - - -# -- Options for Epub output ------------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = project - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -# -# epub_identifier = '' - -# A unique identification for the text. -# -# epub_uid = '' - -# A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] - # -- Extension configuration ------------------------------------------------- # -- Options for intersphinx extension --------------------------------------- # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/': None} +intersphinx_mapping = {'https://docs.python.org/3/': None} # TODO: html_theme not configurable upstream html_theme = 'default' @@ -200,3 +84,4 @@ intersphinx_mapping.update({ 'pyexcel': ('http://pyexcel.readthedocs.io/en/latest/', None), }) +master_doc = "index" diff --git a/format.sh b/format.sh new file mode 100644 index 0000000..a8ae3f1 --- /dev/null +++ b/format.sh @@ -0,0 +1,3 @@ +isort $(find pyexcel_xls -name "*.py"|xargs echo) $(find tests -name "*.py"|xargs echo) +black -l 79 pyexcel_xls +black -l 79 tests diff --git a/lint.sh b/lint.sh new file mode 100644 index 0000000..891aa63 --- /dev/null +++ b/lint.sh @@ -0,0 +1,2 @@ +pip install flake8 +flake8 --exclude=.moban.d,docs,setup.py --builtins=unicode,xrange,long . && python setup.py checkdocs diff --git a/pyexcel-xls.yml b/pyexcel-xls.yml index c620ccb..2f7a0b3 100644 --- a/pyexcel-xls.yml +++ b/pyexcel-xls.yml @@ -1,12 +1,16 @@ overrides: "pyexcel.yaml" name: "pyexcel-xls" nick_name: xls -version: 0.5.8 -current_version: 0.5.8 -release: 0.5.8 +version: 0.6.0 +current_version: 0.6.0 +release: 0.6.0 file_type: xls +is_on_conda: true dependencies: - - pyexcel-io>=0.5.3 + - pyexcel-io>=0.6.2 - xlrd - xlwt +test_dependencies: + - pyexcel description: A wrapper library to read, manipulate and write data in xls format. It reads xlsx and xlsm format +moban_command: false diff --git a/pyexcel_xls/__init__.py b/pyexcel_xls/__init__.py index 839f600..a688b5b 100644 --- a/pyexcel_xls/__init__.py +++ b/pyexcel_xls/__init__.py @@ -4,24 +4,41 @@ The lower level xls/xlsx/xlsm file format handler using xlrd/xlwt - :copyright: (c) 2016-2017 by Onni Software Ltd + :copyright: (c) 2016-2020 by Onni Software Ltd :license: New BSD License """ + # flake8: noqa +from pyexcel_io.io import get_data as read_data +from pyexcel_io.io import isstream +from pyexcel_io.io import save_data as write_data + # this line has to be place above all else # because of dynamic import -from pyexcel_io.plugins import IOPluginInfoChain -from pyexcel_io.io import get_data as read_data, isstream, store_data as write_data - -__FILE_TYPE__ = 'xls' -IOPluginInfoChain(__name__).add_a_reader( - relative_plugin_class_path='xlsr.XLSBook', - file_types=[__FILE_TYPE__, 'xlsx', 'xlsm'], - stream_type='binary' +from pyexcel_io.plugins import IOPluginInfoChainV2 + +__FILE_TYPE__ = "xls" + +IOPluginInfoChainV2(__name__).add_a_reader( + relative_plugin_class_path="xlsr.XLSInFile", + locations=["file"], + file_types=[__FILE_TYPE__, "xlsx", "xlsm"], + stream_type="binary", +).add_a_reader( + relative_plugin_class_path="xlsr.XLSInMemory", + locations=["memory"], + file_types=[__FILE_TYPE__, "xlsx", "xlsm"], + stream_type="binary", +).add_a_reader( + relative_plugin_class_path="xlsr.XLSInContent", + locations=["content"], + file_types=[__FILE_TYPE__, "xlsx", "xlsm"], + stream_type="binary", ).add_a_writer( - relative_plugin_class_path='xlsw.XLSWriter', + relative_plugin_class_path="xlsw.XLSWriter", + locations=["file", "memory"], file_types=[__FILE_TYPE__], - stream_type='binary' + stream_type="binary", ) diff --git a/pyexcel_xls/xlsr.py b/pyexcel_xls/xlsr.py index 0476857..999c023 100644 --- a/pyexcel_xls/xlsr.py +++ b/pyexcel_xls/xlsr.py @@ -4,24 +4,27 @@ The lower level xls/xlsm file format handler using xlrd - :copyright: (c) 2016-2017 by Onni Software Ltd + :copyright: (c) 2016-2020 by Onni Software Ltd :license: New BSD License """ import datetime -import xlrd -from pyexcel_io.book import BookReader -from pyexcel_io.sheet import SheetReader -from pyexcel_io._compact import OrderedDict, irange +import xlrd from pyexcel_io.service import has_no_digits_in_float - +from pyexcel_io.plugin_api import ISheet, IReader XLS_KEYWORDS = [ - 'filename', 'logfile', 'verbosity', 'use_mmap', - 'file_contents', 'encoding_override', 'formatting_info', - 'on_demand', 'ragged_rows' + "filename", + "logfile", + "verbosity", + "use_mmap", + "file_contents", + "encoding_override", + "formatting_info", + "on_demand", + "ragged_rows", ] -DEFAULT_ERROR_VALUE = '#N/A' +DEFAULT_ERROR_VALUE = "#N/A" class MergedCell(object): @@ -33,61 +36,60 @@ def __init__(self, row_low, row_high, column_low, column_high): self.value = None def register_cells(self, registry): - for rowx in irange(self.__rl, self.__rh): - for colx in irange(self.__cl, self.__ch): + for rowx in range(self.__rl, self.__rh): + for colx in range(self.__cl, self.__ch): key = "%s-%s" % (rowx, colx) registry[key] = self -class XLSheet(SheetReader): +class XLSheet(ISheet): """ xls, xlsx, xlsm sheet reader Currently only support first sheet in the file """ + def __init__(self, sheet, auto_detect_int=True, date_mode=0, **keywords): - SheetReader.__init__(self, sheet, **keywords) self.__auto_detect_int = auto_detect_int self.__hidden_cols = [] self.__hidden_rows = [] self.__merged_cells = {} self._book_date_mode = date_mode - if keywords.get('detect_merged_cells') is True: + self.xls_sheet = sheet + self._keywords = keywords + if keywords.get("detect_merged_cells") is True: for merged_cell_ranges in sheet.merged_cells: merged_cells = MergedCell(*merged_cell_ranges) merged_cells.register_cells(self.__merged_cells) - if keywords.get('skip_hidden_row_and_column') is True: - for col_index, info in self._native_sheet.colinfo_map.items(): + if keywords.get("skip_hidden_row_and_column") is True: + for col_index, info in self.xls_sheet.colinfo_map.items(): if info.hidden == 1: self.__hidden_cols.append(col_index) - for row_index, info in self._native_sheet.rowinfo_map.items(): + for row_index, info in self.xls_sheet.rowinfo_map.items(): if info.hidden == 1: self.__hidden_rows.append(row_index) @property def name(self): - return self._native_sheet.name + return self.xls_sheet.name - def number_of_rows(self): - """ - Number of rows in the xls sheet - """ - return self._native_sheet.nrows - len(self.__hidden_rows) + def row_iterator(self): + number_of_rows = self.xls_sheet.nrows - len(self.__hidden_rows) + return range(number_of_rows) - def number_of_columns(self): - """ - Number of columns in the xls sheet - """ - return self._native_sheet.ncols - len(self.__hidden_cols) + def column_iterator(self, row): + number_of_columns = self.xls_sheet.ncols - len(self.__hidden_cols) + for column in range(number_of_columns): + yield self.cell_value(row, column) def cell_value(self, row, column): """ Random access to the xls cells """ - if self._keywords.get('skip_hidden_row_and_column') is True: + if self._keywords.get("skip_hidden_row_and_column") is True: row, column = self._offset_hidden_indices(row, column) - cell_type = self._native_sheet.cell_type(row, column) - value = self._native_sheet.cell_value(row, column) + cell_type = self.xls_sheet.cell_type(row, column) + value = self.xls_sheet.cell_value(row, column) if cell_type == xlrd.XL_CELL_DATE: value = xldate_to_python_date(value, self._book_date_mode) @@ -120,88 +122,48 @@ def calculate_offsets(incoming_index, hidden_indices): return incoming_index + offset -class XLSBook(BookReader): +class XLSReader(IReader): """ XLSBook reader It reads xls, xlsm, xlsx work book """ - def __init__(self): - BookReader.__init__(self) - self._file_content = None - self.__skip_hidden_sheets = True - self.__skip_hidden_row_column = True - self.__detect_merged_cells = False - - def open(self, file_name, **keywords): - self.__parse_keywords(**keywords) - BookReader.open(self, file_name, **keywords) - - def open_stream(self, file_stream, **keywords): - self.__parse_keywords(**keywords) - BookReader.open_stream(self, file_stream, **keywords) - - def open_content(self, file_content, **keywords): - self.__parse_keywords(**keywords) - self._keywords = keywords - self._file_content = file_content - def __parse_keywords(self, **keywords): - self.__skip_hidden_sheets = keywords.get('skip_hidden_sheets', True) + def __init__(self, file_type, **keywords): + self.__skip_hidden_sheets = keywords.get("skip_hidden_sheets", True) self.__skip_hidden_row_column = keywords.get( - 'skip_hidden_row_and_column', True) - self.__detect_merged_cells = keywords.get('detect_merged_cells', False) + "skip_hidden_row_and_column", True + ) + self.__detect_merged_cells = keywords.get("detect_merged_cells", False) + self._keywords = keywords + xlrd_params = self._extract_xlrd_params() + if self.__skip_hidden_row_column and file_type == "xls": + xlrd_params["formatting_info"] = True + if self.__detect_merged_cells: + xlrd_params["formatting_info"] = True - def close(self): - if self._native_book: - self._native_book.release_resources() - self._native_book = None - - def read_sheet_by_index(self, sheet_index): - self._native_book = self._get_book(on_demand=True) - sheet = self._native_book.sheet_by_index(sheet_index) - return self.read_sheet(sheet) - - def read_sheet_by_name(self, sheet_name): - self._native_book = self._get_book(on_demand=True) - try: - sheet = self._native_book.sheet_by_name(sheet_name) - except xlrd.XLRDError: - raise ValueError("%s cannot be found" % sheet_name) - return self.read_sheet(sheet) - - def read_all(self): - result = OrderedDict() - self._native_book = self._get_book() - for sheet in self._native_book.sheets(): + self.content_array = [] + self.xls_book = self.get_xls_book(**xlrd_params) + for sheet in self.xls_book.sheets(): if self.__skip_hidden_sheets and sheet.visibility != 0: continue - data_dict = self.read_sheet(sheet) - result.update(data_dict) - return result + self.content_array.append(sheet) + + def read_sheet(self, index): + native_sheet = self.content_array[index] + sheet = XLSheet( + native_sheet, + date_mode=self.xls_book.datemode, + **self._keywords + ) + return sheet - def read_sheet(self, native_sheet): - sheet = XLSheet(native_sheet, date_mode=self._native_book.datemode, - **self._keywords) - return {sheet.name: sheet.to_array()} + def close(self): + if self.xls_book: + self.xls_book.release_resources() + self.xls_book = None - def _get_book(self, on_demand=False): - xlrd_params = self._extract_xlrd_params() - xlrd_params['on_demand'] = on_demand - - if self._file_name: - xlrd_params['filename'] = self._file_name - elif self._file_stream: - file_content = self._file_stream.read() - xlrd_params['file_contents'] = file_content - elif self._file_content is not None: - xlrd_params['file_contents'] = self._file_content - else: - raise IOError("No valid file name or file content found.") - if self.__skip_hidden_row_column and self._file_type == 'xls': - xlrd_params['formatting_info'] = True - if self.__detect_merged_cells: - xlrd_params['formatting_info'] = True + def get_xls_book(self, **xlrd_params): xls_book = xlrd.open_workbook(**xlrd_params) return xls_book @@ -214,6 +176,24 @@ def _extract_xlrd_params(self): return params +class XLSInFile(XLSReader): + def __init__(self, file_name, file_type, **keywords): + super().__init__(file_type, filename=file_name, **keywords) + + +class XLSInContent(XLSReader): + def __init__(self, file_content, file_type, **keywords): + super().__init__(file_type, file_contents=file_content, **keywords) + + +class XLSInMemory(XLSReader): + def __init__(self, file_stream, file_type, **keywords): + file_stream.seek(0) + super().__init__( + file_type, file_contents=file_stream.read(), **keywords + ) + + def xldate_to_python_date(value, date_mode): """ convert xl date to python date @@ -224,13 +204,9 @@ def xldate_to_python_date(value, date_mode): if date_tuple == (0, 0, 0, 0, 0, 0): ret = datetime.datetime(1900, 1, 1, 0, 0, 0) elif date_tuple[0:3] == (0, 0, 0): - ret = datetime.time(date_tuple[3], - date_tuple[4], - date_tuple[5]) + ret = datetime.time(date_tuple[3], date_tuple[4], date_tuple[5]) elif date_tuple[3:6] == (0, 0, 0): - ret = datetime.date(date_tuple[0], - date_tuple[1], - date_tuple[2]) + ret = datetime.date(date_tuple[0], date_tuple[1], date_tuple[2]) else: ret = datetime.datetime( date_tuple[0], @@ -238,6 +214,6 @@ def xldate_to_python_date(value, date_mode): date_tuple[2], date_tuple[3], date_tuple[4], - date_tuple[5] + date_tuple[5], ) return ret diff --git a/pyexcel_xls/xlsw.py b/pyexcel_xls/xlsw.py index 28ab8e8..e348b9a 100644 --- a/pyexcel_xls/xlsw.py +++ b/pyexcel_xls/xlsw.py @@ -4,16 +4,15 @@ The lower level xls file format handler using xlwt - :copyright: (c) 2016-2017 by Onni Software Ltd + :copyright: (c) 2016-2020 by Onni Software Ltd :license: New BSD License """ import datetime -import xlrd -from xlwt import Workbook, XFStyle - -from pyexcel_io.book import BookWriter -from pyexcel_io.sheet import SheetWriter +import xlrd +from xlwt import XFStyle, Workbook +from pyexcel_io import constants +from pyexcel_io.plugin_api import IWriter, ISheetWriter DEFAULT_DATE_FORMAT = "DD/MM/YY" DEFAULT_TIME_FORMAT = "HH:MM:SS" @@ -21,14 +20,17 @@ EMPTY_SHEET_NOT_ALLOWED = "xlwt does not support a book without any sheets" -class XLSheetWriter(SheetWriter): +class XLSheetWriter(ISheetWriter): """ xls sheet writer """ - def set_sheet_name(self, name): - """Create a sheet - """ - self._native_sheet = self._native_book.add_sheet(name) + + def __init__(self, xls_book, xls_sheet, sheet_name): + if sheet_name is None: + sheet_name = constants.DEFAULT_SHEET_NAME + self._xls_book = xls_book + self._xls_sheet = xls_sheet + self._xls_sheet = self._xls_book.add_sheet(sheet_name) self.current_row = 0 def write_row(self, array): @@ -40,8 +42,12 @@ def write_row(self, array): tmp_array = [] if isinstance(value, datetime.datetime): tmp_array = [ - value.year, value.month, value.day, - value.hour, value.minute, value.second + value.year, + value.month, + value.day, + value.hour, + value.minute, + value.second, ] value = xlrd.xldate.xldate_from_datetime_tuple(tmp_array, 0) style = XFStyle() @@ -57,37 +63,44 @@ def write_row(self, array): style = XFStyle() style.num_format_str = DEFAULT_TIME_FORMAT if style: - self._native_sheet.write(self.current_row, i, value, style) + self._xls_sheet.write(self.current_row, i, value, style) else: - self._native_sheet.write(self.current_row, i, value) + self._xls_sheet.write(self.current_row, i, value) self.current_row += 1 + def close(self): + pass + -class XLSWriter(BookWriter): +class XLSWriter(IWriter): """ xls writer """ - def __init__(self): - BookWriter.__init__(self) - self.work_book = None - def open(self, file_name, - encoding='ascii', style_compression=2, **keywords): - BookWriter.open(self, file_name, **keywords) - self.work_book = Workbook(style_compression=style_compression, - encoding=encoding) + def __init__( + self, + file_alike_object, + _, # file_type not used + encoding="ascii", + style_compression=2, + **keywords + ): + self.file_alike_object = file_alike_object + self.work_book = Workbook( + style_compression=style_compression, encoding=encoding + ) + + def create_sheet(self, name): + return XLSheetWriter(self.work_book, None, name) def write(self, incoming_dict): if incoming_dict: - BookWriter.write(self, incoming_dict) + IWriter.write(self, incoming_dict) else: raise NotImplementedError(EMPTY_SHEET_NOT_ALLOWED) - def create_sheet(self, name): - return XLSheetWriter(self.work_book, None, name) - def close(self): """ This call actually save the file """ - self.work_book.save(self._file_alike_object) + self.work_book.save(self.file_alike_object) diff --git a/requirements.txt b/requirements.txt index cc6ba29..4c06b39 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -pyexcel-io>=0.5.3 +pyexcel-io>=0.6.2 xlrd xlwt diff --git a/rnd_requirements.txt b/rnd_requirements.txt index 6d9386d..dffaa2d 100644 --- a/rnd_requirements.txt +++ b/rnd_requirements.txt @@ -1,2 +1,2 @@ -https://github.com/pyexcel/pyexcel-io/archive/master.zip +https://github.com/pyexcel/pyexcel-io/archive/dev.zip diff --git a/setup.py b/setup.py index a10a4ba..8cfc14c 100644 --- a/setup.py +++ b/setup.py @@ -1,64 +1,87 @@ #!/usr/bin/env python3 -# Template by pypi-mobans +""" +Template by pypi-mobans +""" + import os import sys import codecs +import locale +import platform from shutil import rmtree -from setuptools import setup, find_packages, Command + +from setuptools import Command, setup, find_packages + PY2 = sys.version_info[0] == 2 PY26 = PY2 and sys.version_info[1] < 7 - -NAME = 'pyexcel-xls' -AUTHOR = 'C.W.' -VERSION = '0.5.9' -EMAIL = 'wangc_2011@hotmail.com' -LICENSE = 'New BSD' +PY33 = sys.version_info < (3, 4) + +# Work around mbcs bug in distutils. +# http://bugs.python.org/issue10945 +# This work around is only if a project supports Python < 3.4 + +# Work around for locale not being set +try: + lc = locale.getlocale() + pf = platform.system() + if pf != "Windows" and lc == (None, None): + locale.setlocale(locale.LC_ALL, "C.UTF-8") +except (ValueError, UnicodeError, locale.Error): + locale.setlocale(locale.LC_ALL, "en_US.UTF-8") + +NAME = "pyexcel-xls" +AUTHOR = "chfw" +VERSION = "0.6.0" +EMAIL = "info@pyexcel.org" +LICENSE = "New BSD" DESCRIPTION = ( - 'A wrapper library to read, manipulate and write data in xls format. It' + - 'reads xlsx and xlsm format' + "A wrapper library to read, manipulate and write data in xls format. It" + + "reads xlsx and xlsm format" ) -URL = 'https://github.com/pyexcel/pyexcel-xls' -DOWNLOAD_URL = '%s/archive/0.5.9.tar.gz' % URL -FILES = ['README.rst', 'CHANGELOG.rst'] +URL = "https://github.com/pyexcel/pyexcel-xls" +DOWNLOAD_URL = "%s/archive/0.6.0.tar.gz" % URL +FILES = ["README.rst","CONTRIBUTORS.rst", "CHANGELOG.rst"] KEYWORDS = [ + "python", 'xls', 'xlsx', - 'xlsm', - 'python', + 'xlsm' ] CLASSIFIERS = [ - 'Topic :: Software Development :: Libraries', - 'Programming Language :: Python', - 'Intended Audience :: Developers', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', + "Topic :: Software Development :: Libraries", + "Programming Language :: Python", + "Intended Audience :: Developers", + "Programming Language :: Python :: 2.6", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + 'Programming Language :: Python :: Implementation :: PyPy' ] + INSTALL_REQUIRES = [ - 'pyexcel-io>=0.5.3', - 'xlrd', - 'xlwt', + "pyexcel-io>=0.6.2", + "xlrd", + "xlwt", ] SETUP_COMMANDS = {} - -PACKAGES = find_packages(exclude=['ez_setup', 'examples', 'tests']) +PACKAGES = find_packages(exclude=["ez_setup", "examples", "tests", "tests.*"]) EXTRAS_REQUIRE = { } # 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-xls v0.5.9 ' + - "Find 0.5.9 in changelog for more details") -NO_GS_MESSAGE = ('Automatic github release is disabled. ' + - 'Please install gease to enable it.') +PUBLISH_COMMAND = "{0} setup.py sdist bdist_wheel upload -r pypi".format(sys.executable) +GS_COMMAND = ("gs pyexcel-xls 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 = ( 'Upload failed. please run "%s" yourself.' % PUBLISH_COMMAND) HERE = os.path.abspath(os.path.dirname(__file__)) @@ -67,13 +90,13 @@ class PublishCommand(Command): """Support setup.py upload.""" - description = 'Build and publish the package on github and pypi' + description = "Build and publish the package on github and pypi" user_options = [] @staticmethod def status(s): """Prints things in bold.""" - print('\033[1m{0}\033[0m'.format(s)) + print("\033[1m{0}\033[0m".format(s)) def initialize_options(self): pass @@ -83,14 +106,14 @@ def finalize_options(self): def run(self): try: - self.status('Removing previous builds...') - rmtree(os.path.join(HERE, 'dist')) - rmtree(os.path.join(HERE, 'build')) - rmtree(os.path.join(HERE, 'pyexcel_xls.egg-info')) + self.status("Removing previous builds...") + rmtree(os.path.join(HERE, "dist")) + rmtree(os.path.join(HERE, "build")) + rmtree(os.path.join(HERE, "pyexcel_xls.egg-info")) except OSError: pass - self.status('Building Source and Wheel (universal) distribution...') + self.status("Building Source and Wheel (universal) distribution...") run_status = True if has_gease(): run_status = os.system(GS_COMMAND) == 0 @@ -98,13 +121,13 @@ def run(self): self.status(NO_GS_MESSAGE) if run_status: if os.system(PUBLISH_COMMAND) != 0: - self.status(UPLOAD_FAILED_MSG % PUBLISH_COMMAND) + self.status(UPLOAD_FAILED_MSG) sys.exit() SETUP_COMMANDS.update({ - 'publish': PublishCommand + "publish": PublishCommand }) @@ -133,7 +156,7 @@ def read_files(*files): def read(afile): """Read a file into setup""" the_relative_file = os.path.join(HERE, afile) - with codecs.open(the_relative_file, 'r', 'utf-8') as opened_file: + with codecs.open(the_relative_file, "r", "utf-8") as opened_file: content = filter_out_test_code(opened_file) content = "".join(list(content)) return content @@ -142,11 +165,11 @@ def read(afile): def filter_out_test_code(file_handle): found_test_code = False for line in file_handle.readlines(): - if line.startswith('.. testcode:'): + if line.startswith(".. testcode:"): found_test_code = True continue if found_test_code is True: - if line.startswith(' '): + if line.startswith(" "): continue else: empty_line = line.strip() @@ -156,15 +179,16 @@ def filter_out_test_code(file_handle): found_test_code = False yield line else: - for keyword in ['|version|', '|today|']: + for keyword in ["|version|", "|today|"]: if keyword in line: break else: yield line -if __name__ == '__main__': +if __name__ == "__main__": setup( + test_suite="tests", name=NAME, author=AUTHOR, version=VERSION, @@ -176,7 +200,7 @@ def filter_out_test_code(file_handle): license=LICENSE, keywords=KEYWORDS, extras_require=EXTRAS_REQUIRE, - tests_require=['nose'], + tests_require=["nose"], install_requires=INSTALL_REQUIRES, packages=PACKAGES, include_package_data=True, diff --git a/test.bat b/test.bat index 351d266..823adb3 100644 --- a/test.bat +++ b/test.bat @@ -1,2 +1,2 @@ pip freeze -nosetests --with-coverage --cover-package pyexcel_xls --cover-package tests tests --with-doctest --doctest-extension=.rst README.rst docs/source pyexcel_xls && flake8 . --exclude=.moban.d,docs --builtins=unicode,xrange,long +nosetests --with-coverage --cover-package pyexcel_xls --cover-package tests tests --with-doctest --doctest-extension=.rst README.rst docs/source pyexcel_xls diff --git a/test.sh b/test.sh index 351d266..fda8353 100644 --- a/test.sh +++ b/test.sh @@ -1,2 +1,3 @@ +#/bin/bash pip freeze -nosetests --with-coverage --cover-package pyexcel_xls --cover-package tests tests --with-doctest --doctest-extension=.rst README.rst docs/source pyexcel_xls && flake8 . --exclude=.moban.d,docs --builtins=unicode,xrange,long +nosetests --with-coverage --cover-package pyexcel_xls --cover-package tests tests --with-doctest --doctest-extension=.rst README.rst docs/source pyexcel_xls diff --git a/tests/_compact.py b/tests/_compact.py index 08694b3..e47d675 100644 --- a/tests/_compact.py +++ b/tests/_compact.py @@ -1,7 +1,6 @@ # flake8: noqa import sys - if sys.version_info[0] == 2 and sys.version_info[1] < 7: from ordereddict import OrderedDict else: diff --git a/tests/base.py b/tests/base.py index 4339f70..71474a3 100644 --- a/tests/base.py +++ b/tests/base.py @@ -1,11 +1,13 @@ import os # noqa -import pyexcel import datetime # noqa -from nose.tools import raises, eq_ # noqa + +import pyexcel + +from nose.tools import eq_, raises # noqa def create_sample_file1(file): - data = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 1.1, 1] + data = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", 1.1, 1] table = [] table.append(data[:4]) table.append(data[4:8]) @@ -17,10 +19,11 @@ class PyexcelHatWriterBase: """ Abstract functional test for hat writers """ + content = { "X": [1, 2, 3, 4, 5], "Y": [6, 7, 8, 9, 10], - "Z": [11, 12, 13, 14, 15] + "Z": [11, 12, 13, 14, 15], } def test_series_table(self): @@ -36,11 +39,12 @@ class PyexcelWriterBase: testfile and testfile2 have to be initialized before it is used for testing """ + content = [ [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], - [1, 2, 3, 4, 5] + [1, 2, 3, 4, 5], ] def _create_a_file(self, file): @@ -54,7 +58,6 @@ def test_write_array(self): class PyexcelMultipleSheetBase: - def _write_test_file(self, filename): pyexcel.save_book_as(bookdict=self.content, dest_file_name=filename) @@ -80,7 +83,7 @@ def test_reading_through_sheets(self): expected = [[4, 4, 4, 4], [5, 5, 5, 5], [6, 6, 6, 6]] assert data == expected data = list(b["Sheet3"].rows()) - expected = [[u'X', u'Y', u'Z'], [1, 4, 7], [2, 5, 8], [3, 6, 9]] + expected = [[u"X", u"Y", u"Z"], [1, 4, 7], [2, 5, 8], [3, 6, 9]] assert data == expected sheet3 = b["Sheet3"] sheet3.name_columns_by_row(0) diff --git a/tests/requirements.txt b/tests/requirements.txt index 131361c..3e19c2a 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -3,5 +3,10 @@ mock;python_version<"3" codecov coverage flake8 +black +isort +collective.checkdocs +pygments +moban +moban_jinja2_github pyexcel -xlrd==1.1.0 diff --git a/tests/test_bug_fixes.py b/tests/test_bug_fixes.py index af8bf42..f38abfc 100644 --- a/tests/test_bug_fixes.py +++ b/tests/test_bug_fixes.py @@ -5,17 +5,19 @@ """ import os +import datetime +from unittest.mock import MagicMock, patch + import pyexcel as pe +from _compact import OrderedDict from pyexcel_xls import save_data from pyexcel_xls.xlsr import xldate_to_python_date from pyexcel_xls.xlsw import XLSWriter as Writer -from _compact import OrderedDict -from nose.tools import eq_, raises + from nose import SkipTest -import datetime -from mock import patch +from nose.tools import eq_, raises -IN_TRAVIS = 'TRAVIS' in os.environ +IN_TRAVIS = "TRAVIS" in os.environ def test_pyexcel_issue_5(): @@ -41,16 +43,15 @@ def test_issue_9_hidden_sheet(): test_file = get_fixture("hidden_sheets.xls") book_dict = pe.get_book_dict(file_name=test_file) assert "hidden" not in book_dict - eq_(book_dict['shown'], [['A', 'B']]) + eq_(book_dict["shown"], [["A", "B"]]) def test_issue_9_hidden_sheet_2(): test_file = get_fixture("hidden_sheets.xls") - book_dict = pe.get_book_dict(file_name=test_file, - skip_hidden_sheets=False) + book_dict = pe.get_book_dict(file_name=test_file, skip_hidden_sheets=False) assert "hidden" in book_dict - eq_(book_dict['shown'], [['A', 'B']]) - eq_(book_dict['hidden'], [['a', 'b']]) + eq_(book_dict["shown"], [["A", "B"]]) + eq_(book_dict["hidden"], [["a", "b"]]) def test_issue_10_generator_as_content(): @@ -58,46 +59,50 @@ def data_gen(): def custom_row_renderer(row): for e in row: yield e + for i in range(2): yield custom_row_renderer([1, 2]) + save_data("test.xls", {"sheet": data_gen()}) @raises(IOError) def test_issue_13_empty_file_content(): - pe.get_sheet(file_content='', file_type='xls') + pe.get_sheet(file_content="", file_type="xls") def test_issue_16_file_stream_has_no_getvalue(): test_file = get_fixture("hidden_sheets.xls") - with open(test_file, 'rb') as f: - pe.get_sheet(file_stream=f, file_type='xls') + with open(test_file, "rb") as f: + pe.get_sheet(file_stream=f, file_type="xls") -@patch('xlrd.open_workbook') +@patch("xlrd.open_workbook") def test_issue_18_encoding_override_isnt_passed(fake_open): - fake_open.return_value = None - test_encoding = 'utf-32' - from pyexcel_xls.xlsr import XLSBook - book = XLSBook() - book.open('fake_file.xls', encoding_override=test_encoding) - book._get_book() + fake_open.return_value = MagicMock(sheets=MagicMock(return_value=[])) + test_encoding = "utf-32" + from pyexcel_xls.xlsr import XLSInFile + + XLSInFile("fake_file.xls", "xls", encoding_override=test_encoding) keywords = fake_open.call_args[1] - assert keywords['encoding_override'] == test_encoding + assert keywords["encoding_override"] == test_encoding def test_issue_20(): if not IN_TRAVIS: raise SkipTest() - pe.get_book(url="https://github.com/pyexcel/pyexcel-xls/raw/master/tests/fixtures/file_with_an_empty_sheet.xls"); # flake8: noqa + pe.get_book( + url="https://github.com/pyexcel/pyexcel-xls/raw/master/tests/fixtures/file_with_an_empty_sheet.xls" # noqa: E501 + ) def test_issue_151(): s = pe.get_sheet( - file_name=get_fixture('pyexcel_issue_151.xlsx'), + file_name=get_fixture("pyexcel_issue_151.xlsx"), skip_hidden_row_and_column=False, - library='pyexcel-xls') - eq_('#N/A', s[0,0]) + library="pyexcel-xls", + ) + eq_("#N/A", s[0, 0]) @raises(NotImplementedError) @@ -105,7 +110,7 @@ def test_empty_book_pyexcel_issue_120(): """ https://github.com/pyexcel/pyexcel/issues/120 """ - writer = Writer() + writer = Writer("fake.xls", "xls") writer.write({}) @@ -113,7 +118,7 @@ def test_pyexcel_issue_54(): xlvalue = 41071.0 date = xldate_to_python_date(xlvalue, 1) eq_(date, datetime.date(2016, 6, 12)) - + def get_fixture(file_name): return os.path.join("tests", "fixtures", file_name) diff --git a/tests/test_filter.py b/tests/test_filter.py index 2d760b2..cd07d3c 100644 --- a/tests/test_filter.py +++ b/tests/test_filter.py @@ -1,6 +1,7 @@ import os from pyexcel_io import get_data, save_data + from nose.tools import eq_ @@ -13,49 +14,58 @@ def setUp(self): [3, 23, 33], [4, 24, 34], [5, 25, 35], - [6, 26, 36] + [6, 26, 36], ] save_data(self.test_file, sample) self.sheet_name = "pyexcel_sheet1" def test_filter_row(self): - filtered_data = get_data(self.test_file, start_row=3, - library="pyexcel-xls") + filtered_data = get_data( + self.test_file, start_row=3, library="pyexcel-xls" + ) expected = [[4, 24, 34], [5, 25, 35], [6, 26, 36]] eq_(filtered_data[self.sheet_name], expected) def test_filter_row_2(self): - filtered_data = get_data(self.test_file, start_row=3, row_limit=1, - library="pyexcel-xls") + filtered_data = get_data( + self.test_file, start_row=3, row_limit=1, library="pyexcel-xls" + ) expected = [[4, 24, 34]] eq_(filtered_data[self.sheet_name], expected) def test_filter_column(self): - filtered_data = get_data(self.test_file, start_column=1, - library="pyexcel-xls") - expected = [[21, 31], [22, 32], [23, 33], - [24, 34], [25, 35], [26, 36]] + filtered_data = get_data( + self.test_file, start_column=1, library="pyexcel-xls" + ) + expected = [[21, 31], [22, 32], [23, 33], [24, 34], [25, 35], [26, 36]] eq_(filtered_data[self.sheet_name], expected) def test_filter_column_2(self): - filtered_data = get_data(self.test_file, - start_column=1, column_limit=1, - library="pyexcel-xls") + filtered_data = get_data( + self.test_file, + start_column=1, + column_limit=1, + library="pyexcel-xls", + ) expected = [[21], [22], [23], [24], [25], [26]] eq_(filtered_data[self.sheet_name], expected) def test_filter_both_ways(self): - filtered_data = get_data(self.test_file, - start_column=1, start_row=3, - library="pyexcel-xls") + filtered_data = get_data( + self.test_file, start_column=1, start_row=3, library="pyexcel-xls" + ) expected = [[24, 34], [25, 35], [26, 36]] eq_(filtered_data[self.sheet_name], expected) def test_filter_both_ways_2(self): - filtered_data = get_data(self.test_file, - start_column=1, column_limit=1, - start_row=3, row_limit=1, - library="pyexcel-xls") + filtered_data = get_data( + self.test_file, + start_column=1, + column_limit=1, + start_row=3, + row_limit=1, + library="pyexcel-xls", + ) expected = [[24]] eq_(filtered_data[self.sheet_name], expected) diff --git a/tests/test_formatters.py b/tests/test_formatters.py index 76b8f87..0dcd74a 100644 --- a/tests/test_formatters.py +++ b/tests/test_formatters.py @@ -1,9 +1,10 @@ import os from textwrap import dedent -from nose.tools import eq_ import pyexcel as pe +from nose.tools import eq_ + class TestDateFormat: def test_reading_date_format(self): @@ -15,9 +16,11 @@ def test_reading_date_format(self): 0.0 0.0 """ import datetime - r = pe.get_sheet(file_name=os.path.join("tests", "fixtures", - "date_field.xls"), - library='pyexcel-xls') + + r = pe.get_sheet( + file_name=os.path.join("tests", "fixtures", "date_field.xls"), + library="pyexcel-xls", + ) assert isinstance(r[1, 0], datetime.date) eq_(r[1, 0].strftime("%d/%m/%y"), "25/12/14") assert isinstance(r[1, 1], datetime.time) is True @@ -27,12 +30,17 @@ def test_reading_date_format(self): def test_writing_date_format(self): import datetime + excel_filename = "testdateformat.xls" - data = [[datetime.date(2014, 12, 25), + data = [ + [ + datetime.date(2014, 12, 25), datetime.time(11, 11, 11), - datetime.datetime(2014, 12, 25, 11, 11, 11)]] + datetime.datetime(2014, 12, 25, 11, 11, 11), + ] + ] pe.save_as(dest_file_name=excel_filename, array=data) - r = pe.get_sheet(file_name=excel_filename, library='pyexcel-xls') + r = pe.get_sheet(file_name=excel_filename, library="pyexcel-xls") assert isinstance(r[0, 0], datetime.date) is True assert r[0, 0].strftime("%d/%m/%y") == "25/12/14" assert isinstance(r[0, 1], datetime.time) is True @@ -46,46 +54,58 @@ class TestAutoDetectInt: def setUp(self): self.content = [[1, 2, 3.1]] self.test_file = "test_auto_detect_init.xls" - pe.save_as( - array=self.content, dest_file_name=self.test_file - ) + pe.save_as(array=self.content, dest_file_name=self.test_file) def test_auto_detect_int(self): sheet = pe.get_sheet(file_name=self.test_file, library="pyexcel-xls") - expected = dedent(""" + expected = dedent( + """ pyexcel_sheet1: +---+---+-----+ | 1 | 2 | 3.1 | - +---+---+-----+""").strip() + +---+---+-----+""" + ).strip() eq_(str(sheet), expected) def test_get_book_auto_detect_int(self): book = pe.get_book(file_name=self.test_file, library="pyexcel-xls") - expected = dedent(""" + expected = dedent( + """ pyexcel_sheet1: +---+---+-----+ | 1 | 2 | 3.1 | - +---+---+-----+""").strip() + +---+---+-----+""" + ).strip() eq_(str(book), expected) def test_auto_detect_int_false(self): - sheet = pe.get_sheet(file_name=self.test_file, auto_detect_int=False, - library="pyexcel-xls") - expected = dedent(""" + sheet = pe.get_sheet( + file_name=self.test_file, + auto_detect_int=False, + library="pyexcel-xls", + ) + expected = dedent( + """ pyexcel_sheet1: +-----+-----+-----+ | 1.0 | 2.0 | 3.1 | - +-----+-----+-----+""").strip() + +-----+-----+-----+""" + ).strip() eq_(str(sheet), expected) def test_get_book_auto_detect_int_false(self): - book = pe.get_book(file_name=self.test_file, auto_detect_int=False, - library="pyexcel-xls") - expected = dedent(""" + book = pe.get_book( + file_name=self.test_file, + auto_detect_int=False, + library="pyexcel-xls", + ) + expected = dedent( + """ pyexcel_sheet1: +-----+-----+-----+ | 1.0 | 2.0 | 3.1 | - +-----+-----+-----+""").strip() + +-----+-----+-----+""" + ).strip() eq_(str(book), expected) def tearDown(self): diff --git a/tests/test_hidden.py b/tests/test_hidden.py index 77b1a71..62d6c29 100644 --- a/tests/test_hidden.py +++ b/tests/test_hidden.py @@ -1,18 +1,23 @@ import os -from nose.tools import eq_ + from pyexcel_xls import get_data +from nose.tools import eq_ + def test_simple_hidden_sheets(): - data = get_data(os.path.join("tests", "fixtures", "hidden.xls"), - skip_hidden_row_and_column=True) + data = get_data( + os.path.join("tests", "fixtures", "hidden.xls"), + skip_hidden_row_and_column=True, + ) expected = [[1, 3], [7, 9]] - eq_(data['Sheet1'], expected) + eq_(data["Sheet1"], expected) def test_complex_hidden_sheets(): data = get_data( os.path.join("tests", "fixtures", "complex_hidden_sheets.xls"), - skip_hidden_row_and_column=True) + skip_hidden_row_and_column=True, + ) expected = [[1, 3, 5, 7, 9], [31, 33, 35, 37, 39], [61, 63, 65, 67]] - eq_(data['Sheet1'], expected) + eq_(data["Sheet1"], expected) diff --git a/tests/test_merged_cells.py b/tests/test_merged_cells.py index 0247ccc..6f243ac 100644 --- a/tests/test_merged_cells.py +++ b/tests/test_merged_cells.py @@ -1,6 +1,8 @@ import os + from pyexcel_xls import get_data from pyexcel_xls.xlsr import MergedCell + from nose.tools import eq_ @@ -8,16 +10,18 @@ def test_merged_cells(): data = get_data( get_fixture("merged-cell-sheet.xls"), detect_merged_cells=True, - library="pyexcel-xls") + library="pyexcel-xls", + ) expected = [[1, 2, 3], [1, 5, 6], [1, 8, 9], [10, 11, 11]] - eq_(data['Sheet1'], expected) + eq_(data["Sheet1"], expected) def test_complex_merged_cells(): data = get_data( get_fixture("complex-merged-cells-sheet.xls"), detect_merged_cells=True, - library="pyexcel-xls") + library="pyexcel-xls", + ) expected = [ [1, 1, 2, 3, 15, 16, 22, 22, 24, 24], [1, 1, 4, 5, 15, 17, 22, 22, 24, 24], @@ -26,16 +30,17 @@ def test_complex_merged_cells(): [13, 11, 11, 14, 20, 20, 23, 23, 24, 24], [21, 21, 21, 21, 21, 21, 23, 23, 24, 24], [25, 25, 25, 25, 25, 25, 25, 25, 25, 25], - [25, 25, 25, 25, 25, 25, 25, 25, 25, 25] + [25, 25, 25, 25, 25, 25, 25, 25, 25, 25], ] - eq_(data['Sheet1'], expected) + eq_(data["Sheet1"], expected) def test_exploration(): data = get_data( get_fixture("merged-sheet-exploration.xls"), detect_merged_cells=True, - library="pyexcel-xls") + library="pyexcel-xls", + ) expected_sheet1 = [ [1, 1, 1, 1, 1, 1], [2], @@ -46,28 +51,23 @@ def test_exploration(): [2], [2], [2], - [2]] - eq_(data['Sheet1'], expected_sheet1) - expected_sheet2 = [ - [3], - [3], - [3], - [3, 4, 4, 4, 4, 4, 4], - [3], - [3], - [3]] - eq_(data['Sheet2'], expected_sheet2) + [2], + ] + eq_(data["Sheet1"], expected_sheet1) + expected_sheet2 = [[3], [3], [3], [3, 4, 4, 4, 4, 4, 4], [3], [3], [3]] + eq_(data["Sheet2"], expected_sheet2) expected_sheet3 = [ - ['', '', '', '', '', 2, 2, 2], + ["", "", "", "", "", 2, 2, 2], [], [], [], - ['', '', '', 5], - ['', '', '', 5], - ['', '', '', 5], - ['', '', '', 5], - ['', '', '', 5]] - eq_(data['Sheet3'], expected_sheet3) + ["", "", "", 5], + ["", "", "", 5], + ["", "", "", 5], + ["", "", "", 5], + ["", "", "", 5], + ] + eq_(data["Sheet3"], expected_sheet3) def test_merged_cell_class(): @@ -75,10 +75,9 @@ def test_merged_cell_class(): merged_cell = MergedCell(1, 4, 1, 4) merged_cell.register_cells(test_dict) keys = sorted(list(test_dict.keys())) - expected = ['1-1', '1-2', '1-3', '2-1', - '2-2', '2-3', '3-1', '3-2', '3-3'] + expected = ["1-1", "1-2", "1-3", "2-1", "2-2", "2-3", "3-1", "3-2", "3-3"] eq_(keys, expected) - eq_(merged_cell, test_dict['3-1']) + eq_(merged_cell, test_dict["3-1"]) def get_fixture(file_name): diff --git a/tests/test_multiple_sheets.py b/tests/test_multiple_sheets.py index d20dc61..3f526f7 100644 --- a/tests/test_multiple_sheets.py +++ b/tests/test_multiple_sheets.py @@ -1,9 +1,11 @@ import os import sys + import pyexcel -from nose.tools import raises 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: @@ -31,8 +33,7 @@ def _write_test_file(self, file): 3,3,3,3 """ self.rows = 3 - pyexcel.save_book_as(bookdict=self.content, - dest_file_name=file) + pyexcel.save_book_as(bookdict=self.content, dest_file_name=file) def setUp(self): self.testfile = "multiple1.xls" @@ -45,12 +46,12 @@ def setUp(self): def test_load_a_single_sheet(self): b1 = pyexcel.get_book(file_name=self.testfile, sheet_name="Sheet1") assert len(b1.sheet_names()) == 1 - assert b1['Sheet1'].to_array() == self.content['Sheet1'] + assert b1["Sheet1"].to_array() == self.content["Sheet1"] def test_load_a_single_sheet2(self): b1 = pyexcel.load_book(self.testfile, sheet_index=0) assert len(b1.sheet_names()) == 1 - assert b1['Sheet1'].to_array() == self.content['Sheet1'] + assert b1["Sheet1"].to_array() == self.content["Sheet1"] @raises(IndexError) def test_load_a_single_sheet3(self): @@ -219,18 +220,17 @@ def setUp(self): self.testfile = "file_with_an_empty_sheet.xls" def test_reader_with_correct_sheets(self): - r = pyexcel.BookReader(os.path.join("tests", - "fixtures", - self.testfile)) + r = pyexcel.BookReader( + os.path.join("tests", "fixtures", self.testfile) + ) assert r.number_of_sheets() == 3 def _produce_ordered_dict(): data_dict = OrderedDict() - data_dict.update({ - "Sheet1": [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]]}) - data_dict.update({ - "Sheet2": [[4, 4, 4, 4], [5, 5, 5, 5], [6, 6, 6, 6]]}) - data_dict.update({ - "Sheet3": [[u'X', u'Y', u'Z'], [1, 4, 7], [2, 5, 8], [3, 6, 9]]}) + data_dict.update({"Sheet1": [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]]}) + data_dict.update({"Sheet2": [[4, 4, 4, 4], [5, 5, 5, 5], [6, 6, 6, 6]]}) + data_dict.update( + {"Sheet3": [[u"X", u"Y", u"Z"], [1, 4, 7], [2, 5, 8], [3, 6, 9]]} + ) return data_dict diff --git a/tests/test_stringio.py b/tests/test_stringio.py index 8e9a1a1..e1aa055 100644 --- a/tests/test_stringio.py +++ b/tests/test_stringio.py @@ -1,33 +1,32 @@ import os + import pyexcel -from nose.tools import eq_ from base import create_sample_file1 +from nose.tools import eq_ -class TestStringIO: +class TestStringIO: def test_xls_stringio(self): testfile = "cute.xls" create_sample_file1(testfile) with open(testfile, "rb") as f: content = f.read() - r = pyexcel.get_sheet(file_type="xls", file_content=content, - library="pyexcel-xls") - result = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 1.1, 1] + r = pyexcel.get_sheet( + file_type="xls", file_content=content, library="pyexcel-xls" + ) + result = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", 1.1, 1] actual = list(r.enumerate()) eq_(result, actual) if os.path.exists(testfile): os.unlink(testfile) def test_xls_output_stringio(self): - data = [ - [1, 2, 3], - [4, 5, 6] - ] - io = pyexcel.save_as(dest_file_type="xls", - array=data) - r = pyexcel.get_sheet(file_type="xls", file_content=io.getvalue(), - library="pyexcel-xls") + data = [[1, 2, 3], [4, 5, 6]] + io = pyexcel.save_as(dest_file_type="xls", array=data) + r = pyexcel.get_sheet( + file_type="xls", file_content=io.getvalue(), library="pyexcel-xls" + ) result = [1, 2, 3, 4, 5, 6] actual = list(r.enumerate()) eq_(result, actual) diff --git a/tests/test_writer.py b/tests/test_writer.py index a045814..12004e8 100644 --- a/tests/test_writer.py +++ b/tests/test_writer.py @@ -1,7 +1,8 @@ import os -from pyexcel_xls.xlsw import XLSWriter as Writer -from pyexcel_xls.xlsr import XLSBook as Reader + from base import PyexcelWriterBase, PyexcelHatWriterBase +from pyexcel_xls import get_data +from pyexcel_xls.xlsw import XLSWriter as Writer class TestNativeXLSWriter: @@ -9,20 +10,16 @@ def test_write_book(self): self.content = { "Sheet1": [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]], "Sheet2": [[4, 4, 4, 4], [5, 5, 5, 5], [6, 6, 6, 6]], - "Sheet3": [[u'X', u'Y', u'Z'], [1, 4, 7], [2, 5, 8], [3, 6, 9]] + "Sheet3": [[u"X", u"Y", u"Z"], [1, 4, 7], [2, 5, 8], [3, 6, 9]], } self.testfile = "writer.xls" - writer = Writer() - writer.open(self.testfile) + writer = Writer(self.testfile, "xls") 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):