Merge branch 'wip/github'

This commit is contained in:
Frédéric Péters 2022-03-29 19:01:59 +02:00
commit 5ec55c71dc
55 changed files with 2324 additions and 740 deletions

4
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,4 @@
# These are supported funding model platforms
github: chfw
patreon: chfw

8
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,8 @@
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)
- [ ] Has fair amount of documentation if your change is complex
- [ ] Agree on NEW BSD License for your contribution

20
.github/workflows/lint.yml vendored Normal file
View File

@ -0,0 +1,20 @@
name: lint
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
name: lint code
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: 3.8
- name: lint
run: |
pip --use-deprecated=legacy-resolver install flake8
pip --use-deprecated=legacy-resolver install -r tests/requirements.txt
flake8 --exclude=.moban.d,docs,setup.py --builtins=unicode,xrange,long .
python setup.py checkdocs

28
.github/workflows/moban-update.yml vendored Normal file
View File

@ -0,0 +1,28 @@
on: [push]
jobs:
run_moban:
runs-on: ubuntu-latest
name: synchronize templates via moban
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.head_ref }}
token: ${{ secrets.PAT }}
- 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: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: >-
This is an auto-commit, updating project meta data,
such as changelog.rst, contributors.rst

26
.github/workflows/pythonpublish.yml vendored Normal file
View File

@ -0,0 +1,26 @@
name: Upload Python Package
on:
release:
types: [created]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twine
- name: Build and publish
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
python setup.py sdist bdist_wheel
twine upload dist/*

35
.github/workflows/tests.yml vendored Normal file
View File

@ -0,0 +1,35 @@
name: run_tests
on: [push, pull_request]
jobs:
test:
strategy:
fail-fast: false
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
os: [macOs-latest, ubuntu-latest, windows-latest]
exclude:
- os: macOs-latest
python-version: 3.6
runs-on: ${{ matrix.os }}
name: run tests
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: install
run: |
pip --use-deprecated=legacy-resolver install -r requirements.txt
pip --use-deprecated=legacy-resolver install -r tests/requirements.txt
- name: test
run: |
pip freeze
nosetests --verbosity=3 --with-coverage --cover-package pyexcel_xls --cover-package tests tests --with-doctest --doctest-extension=.rst README.rst docs/source pyexcel_xls
- name: Upload coverage
uses: codecov/codecov-action@v1
with:
name: ${{ matrix.os }} Python ${{ matrix.python-version }}

547
.gitignore vendored
View File

@ -1,3 +1,546 @@
*.pyc
*~
# moban hashes
.moban.hashes
# Extra rules from https://github.com/github/gitignore/
# Python rules
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
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
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# 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 stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# VirtualEnv rules
# Virtualenv
# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
.Python
[Bb]in
[Ii]nclude
[Ll]ib
[Ll]ib64
[Ll]ocal
[Ss]cripts
pyvenv.cfg
.venv
pip-selfcheck.json
# Linux rules
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
# Windows rules
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# macOS rules
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Emacs rules
# -*- mode: gitignore; -*-
*~
\#*\#
/.emacs.desktop
/.emacs.desktop.lock
*.elc
auto-save-list
tramp
.\#*
# Org-mode
.org-id-locations
*_archive
# flymake-mode
*_flymake.*
# eshell files
/eshell/history
/eshell/lastdir
# elpa packages
/elpa/
# reftex files
*.rel
# AUCTeX auto folder
/auto/
# cask packages
.cask/
dist/
# Flycheck
flycheck_*.el
# server auth directory
/server/
# projectiles files
.projectile
# 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]
[._]sw[a-p]
# Session
Session.vim
Sessionx.vim
# Temporary
.netrwhist
*~
# Auto-generated tag files
tags
# Persistent undo
[._]*.un~
# JetBrains rules
# 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
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.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-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
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
*.tmPreferences.cache
*.stTheme.cache
# Workspace files are user-specific
*.sublime-workspace
# Project files should be checked into the repository, unless a significant
# proportion of contributors will probably not be using Sublime Text
# *.sublime-project
# SFTP configuration file
sftp-config.json
sftp-config-alt*.json
# Package control specific files
Package Control.last-run
Package Control.ca-list
Package Control.ca-bundle
Package Control.system-ca-bundle
Package Control.cache/
Package Control.ca-certs/
Package Control.merged-ca-bundle
Package Control.user-ca-bundle
oscrypto-ca-bundle.crt
bh_unicode_properties.cache
# Sublime-github package stores a github token in this file
# https://packagecontrol.io/packages/sublime-github
GitHub.sublime-settings
# KDevelop4 rules
*.kdev4
.kdev4/
# Kate rules
# Swap Files #
.*.kate-swp
.swp.*
# TextMate rules
*.tmproj
*.tmproject
tmtags
# VisualStudioCode rules
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Xcode rules
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## User settings
xcuserdata/
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
## Gcc Patch
/*.gcno
# Eclipse rules
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.settings/
.loadpath
.recommenders
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# PyDev specific (Python IDE for Eclipse)
*.pydevproject
# CDT-specific (C/C++ Development Tooling)
.cproject
# CDT- autotools
.autotools
# Java annotation processor (APT)
.factorypath
# PDT-specific (PHP Development Tools)
.buildpath
# sbteclipse plugin
.target
# Tern plugin
.tern-project
# TeXlipse plugin
.texlipse
# STS (Spring Tool Suite)
.springBeans
# Code Recommenders
.recommenders/
# 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
# Tags rules
# Ignore tags created by etags, ctags, gtags (GNU global) and cscope
TAGS
.TAGS
!TAGS/
tags
.tags
!tags/
gtags.files
GTAGS
GRTAGS
GPATH
GSYMS
cscope.files
cscope.out
cscope.in.out
cscope.po.out
# remove moban hash dictionary
.moban.hashes

10
.isort.cfg Normal file
View File

@ -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

View File

@ -1,13 +0,0 @@
{%extends 'README.rst.jj2' %}
{%block description%}
**pyexcel-{{file_type}}** is a tiny wrapper library to read, manipulate and write data in {{file_type}} format and it can read xlsx and xlsm fromat. You are likely to use it with `pyexcel <https://github.com/pyexcel/pyexcel>`_.
{%endblock%}
{%block extras %}
Known Issues
=============
* If a zero was typed in a DATE formatted field in xls, you will get "01/01/1900".
* If a zero was typed in a TIME formatted field in xls, you will get "00:00:00".
{%endblock%}

View File

@ -0,0 +1,50 @@
{%extends 'README.rst.jj2' %}
{% block documentation_link %}
{% endblock %}
{%block description%}
**pyexcel-{{file_type}}** is a tiny wrapper library to read, manipulate and
write data in {{file_type}} format and it can read xlsx and xlsm fromat.
You are likely to use it with `pyexcel <https://github.com/pyexcel/pyexcel>`_.
Oct 2021 - Update:
===================
1. v0.7.0 removed the pin on xlrd < 2. If you have xlrd >= 2, this
library will NOT read 'xlsx' format and you need to install pyexcel-xlsx. Othwise,
this library can use xlrd < 2 to read xlsx format for you. So 'xlsx' support
in this library will vary depending on the installed version of xlrd.
2. v0.7.0 can write datetime.timedelta. but when the value is read out,
you will get datetime.datetime. so you as the developer decides what to do with it.
Past news
===========
`detect_merged_cells` allows you to spread the same value among
all merged cells. But be aware that this may slow down its reading
performance.
`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.
Warning
================================================================================
**xls file cannot contain more than 65,000 rows**. You are risking the reputation
of yourself/your company/
`your country <https://www.bbc.co.uk/news/technology-54423988>`_ if you keep
using xls and are not aware of its row limit.
{%endblock%}
{%block extras %}
Known Issues
=============
* If a zero was typed in a DATE formatted field in xls, you will get "01/01/1900".
* If a zero was typed in a TIME formatted field in xls, you will get "00:00:00".
{%endblock%}

View File

@ -0,0 +1,16 @@
{% extends 'setup.py.jj2' %}
{%block platform_block%}
{%endblock%}
{%block additional_keywords %}
'xls',
'xlsx',
'xlsm'
{%endblock%}
{% block morefiles %}"CONTRIBUTORS.rst",{% endblock %}
{%block additional_classifiers%}
'Programming Language :: Python :: Implementation :: PyPy'
{%endblock%}

View File

@ -1,20 +0,0 @@
{% extends 'setup.py.jj2' %}
{%block platform_block%}
{%endblock%}
{%block additional_keywords %}
'xls',
'xlsx',
'xlsm'
{%endblock%}
{%block additional_classifiers%}
'Development Status :: 3 - Alpha',
'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 :: Implementation :: PyPy'
{%endblock%}

View File

@ -0,0 +1,4 @@
{% extends 'tests/base.py.jj2' %}
{%block ods_types%}
{%endblock%}

View File

@ -1,4 +1,8 @@
{% extends 'tests/requirements.txt.jj2' %}
{%block extras %}
pyexcel
xlrd==1.1.0
moban
black;python_version>="3.6"
isort;python_version>="3.6"
{%endblock%}

View File

@ -1,28 +0,0 @@
{% extends "travis.yml.jj2" %}
{%block test_other_python_versions%} - 2.6
- 3.3
- 3.4
- 3.5
- pypy
{%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%}

View File

@ -1,17 +1,10 @@
overrides: "git://github.com/pyexcel/pyexcel-mobans!/mobanfile.yaml"
configuration:
configuration_dir: "commons/config"
template_dir:
- "commons/templates"
- ".moban.d"
configuration: pyexcel_xls.yaml
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
- LICENSE: LICENSE.jj2
- .gitignore: gitignore.jj2
- MANIFEST.in: MANIFEST.in.jj2
- "tests/requirements.txt": "tests/requirements.txt"
- test.sh: test.sh.jj2
- test.bat: test.sh.jj2
- "tests/requirements.txt": "tests/custom_requirements.txt.jj2"

18
.readthedocs.yml Normal file
View File

@ -0,0 +1,18 @@
# .readthedocs.yml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/source/conf.py
# Optionally build your docs in additional formats such as PDF
formats:
- pdf
# Optionally set the version of Python and requirements required to build your docs
python:
version: 3.7

View File

@ -1,41 +0,0 @@
sudo: false
language: python
notifications:
email: false
python:
- pypy
- 2.6
- 2.7
- pypy3
- 3.3
- 3.4
- 3.5
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
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
- pip install --upgrade setuptools "pip==7.1"
- 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:
- make test
after_success:
codecov

View File

@ -1,11 +1,191 @@
Change log
================================================================================
0.7.0 - 07.10.2021
--------------------------------------------------------------------------------
**Removed**
#. `#46 <https://github.com/pyexcel/pyexcel-xls/issues/46>`_: remove the hard
pin on xlrd version < 2.0
**Added**
#. `#47 <https://github.com/pyexcel/pyexcel-xls/issues/47>`_: limit support to
persist datetime.timedelta. see more details in doc
0.6.2 - 12.12.2020
--------------------------------------------------------------------------------
**Updated**
#. lock down xlrd version less than version 2.0, because 2.0+ does not support
xlsx read
0.6.1 - 21.10.2020
--------------------------------------------------------------------------------
**Updated**
#. Restrict this library to get installed on python 3.6+, because pyexcel-io
0.6.0+ supports only python 3.6+.
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**
#. `#35 <https://github.com/pyexcel/pyexcel-xls/issues/35>`_, include tests
0.5.8 - 22.08.2018
--------------------------------------------------------------------------------
**Added**
#. `pyexcel#151 <https://github.com/pyexcel/pyexcel/issues/151>`_, read cell
error as #N/A.
0.5.7 - 15.03.2018
--------------------------------------------------------------------------------
**Added**
#. `pyexcel#54 <https://github.com/pyexcel/pyexcel/issues/54>`_, Book.datemode
attribute of that workbook should be passed always.
0.5.6 - 15.03.2018
--------------------------------------------------------------------------------
**Added**
#. `pyexcel#120 <https://github.com/pyexcel/pyexcel/issues/120>`_, xlwt cannot
save a book without any sheet. So, let's raise an exception in this case in
order to warn the developers.
0.5.5 - 8.11.2017
--------------------------------------------------------------------------------
**Added**
#. `#25 <https://github.com/pyexcel/pyexcel-xls/issues/25>`_, detect merged cell
in .xls
0.5.4 - 2.11.2017
--------------------------------------------------------------------------------
**Added**
#. `#24 <https://github.com/pyexcel/pyexcel-xls/issues/24>`_, xlsx format cannot
use skip_hidden_row_and_column. please use pyexcel-xlsx instead.
0.5.3 - 2.11.2017
--------------------------------------------------------------------------------
**Added**
#. `#21 <https://github.com/pyexcel/pyexcel-xls/issues/21>`_, skip hidden rows
and columns under 'skip_hidden_row_and_column' flag.
0.5.2 - 23.10.2017
--------------------------------------------------------------------------------
**updated**
#. pyexcel `pyexcel#105 <https://github.com/pyexcel/pyexcel/issues/105>`_,
remove gease from setup_requires, introduced by 0.5.1.
#. remove python2.6 test support
#. update its dependecy on pyexcel-io to 0.5.3
0.5.1 - 20.10.2017
--------------------------------------------------------------------------------
**added**
#. `pyexcel#103 <https://github.com/pyexcel/pyexcel/issues/103>`_, include
LICENSE file in MANIFEST.in, meaning LICENSE file will appear in the released
tar ball.
0.5.0 - 30.08.2017
--------------------------------------------------------------------------------
**Updated**
#. `#20 <https://github.com/pyexcel/pyexcel-xls/issues/20>`_, is handled in
pyexcel-io
#. put dependency on pyexcel-io 0.5.0, which uses cStringIO instead of StringIO.
Hence, there will be performance boost in handling files in memory.
0.4.1 - 25.08.2017
--------------------------------------------------------------------------------
**Updated**
#. `#20 <https://github.com/pyexcel/pyexcel-xls/issues/20>`_, handle unseekable
stream given by http response.
0.4.0 - 19.06.2017
--------------------------------------------------------------------------------
**Updated**
#. `pyexcel-xlsx#15 <https://github.com/pyexcel/pyexcel-xlsx/issues/15>`_, close
file handle
#. pyexcel-io plugin interface now updated to use `lml
<https://github.com/chfw/lml>`_.
0.3.3 - 30/05/2017
--------------------------------------------------------------------------------
**Updated**
#. `#18 <https://github.com/pyexcel/pyexcel-xls/issues/18>`_, pass on
encoding_override and others to xlrd.
0.3.2 - 18.05.2017
--------------------------------------------------------------------------------
**Updated**
#. `#16 <https://github.com/pyexcel/pyexcel-xls/issues/16>`_, allow mmap to be
passed as file content
0.3.1 - 16.01.2017
--------------------------------------------------------------------------------
**Updated**
#. `#14 <https://github.com/pyexcel/pyexcel-xls/issues/14>`_, Python 3.6 -
cannot use LOCALE flag with a str pattern
#. fix its dependency on pyexcel-io 0.3.0
0.3.0 - 22.12.2016
--------------------------------------------------------------------------------
**Updated**
#. `#13 <https://github.com/pyexcel/pyexcel-xls/issues/13>`_, alert on empyty
file content
#. Support pyexcel-io v0.3.0
0.2.3 - 20.09.2016
--------------------------------------------------------------------------------
**Updated**
#. `#10 <https://github.com/pyexcel/pyexcel-xls/issues/10>`_, To support
generator as member of the incoming two dimensional data
0.2.2 - 31.08.2016
--------------------------------------------------------------------------------
Added
********************************************************************************
**Added**
#. support pagination. two pairs: start_row, row_limit and start_column,
column_limit help you deal with large files.
@ -13,29 +193,24 @@ Added
0.2.1 - 13.07.2016
--------------------------------------------------------------------------------
Added
********************************************************************************
**Added**
#. `#9 <https://github.com/pyexcel/pyexcel-xls/issues/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
********************************************************************************
**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
other libraries, in the situation where there are more than one plugin for a
file type, e.g. xlsm
Updated
********************************************************************************
**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
@ -44,10 +219,8 @@ Updated
0.1.0 - 17.01.2016
--------------------------------------------------------------------------------
Added
********************************************************************************
**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.

10
CONTRIBUTORS.rst Normal file
View File

@ -0,0 +1,10 @@
3 contributors
================================================================================
In alphabetical order:
* `John Vandenberg <https://github.com/jayvdb>`_
* `Peter Carnesciali <https://github.com/pcarn>`_
* `vinraspa <https://github.com/vinraspa>`_

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2016 by Onni Software Ltd. and its contributors
Copyright (c) 2015-2021 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.
DAMAGE.

View File

@ -1,2 +1,6 @@
include README.rst
include LICENSE
include CHANGELOG.rst
include CONTRIBUTORS.rst
recursive-include tests *
recursive-include docs *

View File

@ -2,23 +2,105 @@
pyexcel-xls - Let you focus on data, instead of xls format
================================================================================
.. image:: https://api.travis-ci.org/pyexcel/pyexcel-xls.png
:target: http://travis-ci.org/pyexcel/pyexcel-xls
.. image:: https://raw.githubusercontent.com/pyexcel/pyexcel.github.io/master/images/patreon.png
:target: https://www.patreon.com/chfw
.. image:: https://codecov.io/github/pyexcel/pyexcel-xls/coverage.png
:target: https://codecov.io/github/pyexcel/pyexcel-xls
.. image:: https://raw.githubusercontent.com/pyexcel/pyexcel-mobans/master/images/awesome-badge.svg
:target: https://awesome-python.com/#specific-formats-processing
.. image:: https://github.com/pyexcel/pyexcel-xls/workflows/run_tests/badge.svg
:target: http://github.com/pyexcel/pyexcel-xls/actions
.. 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
.. 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 <https://github.com/pyexcel/pyexcel>`_.
Oct 2021 - Update:
===================
1. v0.7.0 removed the pin on xlrd < 2. If you have xlrd >= 2, this
library will NOT read 'xlsx' format and you need to install pyexcel-xlsx. Othwise,
this library can use xlrd < 2 to read xlsx format for you. So 'xlsx' support
in this library will vary depending on the installed version of xlrd.
2. v0.7.0 can write datetime.timedelta. but when the value is read out,
you will get datetime.datetime. so you as the developer decides what to do with it.
Past news
===========
`detect_merged_cells` allows you to spread the same value among
all merged cells. But be aware that this may slow down its reading
performance.
`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.
Warning
================================================================================
**xls file cannot contain more than 65,000 rows**. You are risking the reputation
of yourself/your company/
`your country <https://www.bbc.co.uk/news/technology-54423988>`_ if you keep
using xls and are not aware of its row limit.
Support the project
================================================================================
If your company has embedded pyexcel and its components into a revenue generating
product, please support me on github, `patreon <https://www.patreon.com/bePatron?u=5537627>`_
or `bounty source <https://salt.bountysource.com/teams/chfw-pyexcel>`_ 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 <https://www.patreon.com/pyexcel/posts>`_.
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.
**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 <https://github.com/pyexcel/pyexcel>`_.
Known constraints
==================
Fonts, colors and charts are not supported.
Nor to read password protected xls, xlsx and ods files.
Installation
================================================================================
You can install it via pip:
You can install pyexcel-xls via pip:
.. code-block:: bash
@ -29,7 +111,7 @@ or clone it and install it:
.. code-block:: bash
$ git clone http://github.com/pyexcel/pyexcel-xls.git
$ git clone https://github.com/pyexcel/pyexcel-xls.git
$ cd pyexcel-xls
$ python setup.py install
@ -39,9 +121,6 @@ Usage
As a standalone library
--------------------------------------------------------------------------------
Write to an xls file
********************************************************************************
.. testcode::
:hide:
@ -58,6 +137,11 @@ Write to an xls file
... from collections import OrderedDict
Write to an xls file
********************************************************************************
Here's the sample code to write a dictionary to an xls file:
.. code-block:: python
@ -68,6 +152,7 @@ Here's the sample code to write a dictionary to an xls file:
>>> data.update({"Sheet 2": [["row 1", "row 2", "row 3"]]})
>>> save_data("your_file.xls", data)
Read from an xls file
********************************************************************************
@ -101,6 +186,7 @@ Here's the sample code to write a dictionary to an xls file:
Read from an xls from memory
********************************************************************************
@ -119,6 +205,8 @@ Continue from previous example:
Pagination feature
********************************************************************************
Let's assume the following file is a huge xls file:
.. code-block:: python
@ -175,16 +263,6 @@ No longer, explicit import is needed since pyexcel version 0.2.2. Instead,
this library is auto-loaded. So if you want to read data in xls format,
installing it is enough.
Any version under pyexcel 0.2.2, you have to keep doing the following:
Import it in your file to enable this plugin:
.. code-block:: python
from pyexcel.ext import xls
Please note only pyexcel version 0.0.4+ support this.
Reading from an xls file
********************************************************************************
@ -194,7 +272,6 @@ Here is the sample code:
.. code-block:: python
>>> import pyexcel as pe
>>> # from pyexcel.ext import xls
>>> sheet = pe.get_book(file_name="your_file.xls")
>>> sheet
Sheet 1:
@ -265,6 +342,7 @@ You need to pass a StringIO instance to Writer:
>>> # In reality, you might give it to your http response
>>> # object for downloading
License
================================================================================
@ -280,7 +358,7 @@ Development steps for code changes
Upgrade your setup tools and pip. They are needed for development and testing only:
#. pip install --upgrade setuptools "pip==7.1"
#. pip install --upgrade setuptools pip
Then install relevant development requirements:
@ -288,28 +366,16 @@ Then install relevant development requirements:
#. pip install -r requirements.txt
#. pip install -r tests/requirements.txt
Once you have finished your changes, please provide test case(s), relevant documentation
and update changelog.yml
In order to update test environment, and documentation, additional setps are
required:
.. note::
#. pip install moban
#. git clone https://github.com/pyexcel/pyexcel-commons.git
#. make your changes in `.moban.d` directory, then issue command `moban`
As to rnd_requirements.txt, usually, it is created when a dependent
library is not released. Once the dependecy is installed
(will be released), the future
version of the dependency in the requirements.txt will be valid.
What is rnd_requirements.txt
-------------------------------
Usually, it is created when a dependent library is not released. Once the dependecy is installed(will be released), the future version of the dependency in the requirements.txt will be valid.
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.
What is .moban.d
---------------------------------
`.moban.d` stores the specific meta data for the library.
How to test your contribution
------------------------------
@ -318,12 +384,23 @@ Although `nose` and `doctest` are both used in code testing, it is adviable that
On Linux/Unix systems, please launch your tests like this::
$ make test
$ make
On Windows systems, please issue this command::
On Windows, please issue this command::
> test.bat
Before you commit
------------------------------
Please run::
$ make format
so as to beautify your code otherwise your build may fail your unit test.
Known Issues
=============

178
changelog.yml Normal file
View File

@ -0,0 +1,178 @@
name: pyexcel-xls
organisation: pyexcel
releases:
- changes:
- action: Removed
details:
- "`#46`: remove the hard pin on xlrd version < 2.0"
- action: Added
details:
- "`#47`: limit support to persist datetime.timedelta. see more details in doc"
date: 07.10.2021
version: 0.7.0
- changes:
- action: Updated
details:
- "lock down xlrd version less than version 2.0, because 2.0+ does not support xlsx read"
date: 12.12.2020
version: 0.6.2
- changes:
- action: Updated
details:
- "Restrict this library to get installed on python 3.6+, because pyexcel-io 0.6.0+ supports only python 3.6+."
date: 21.10.2020
version: 0.6.1
- 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:
- "`pyexcel#151`, read cell error as #N/A."
date: 22.08.2018
version: 0.5.8
- changes:
- action: Added
details:
- "`pyexcel#54`, Book.datemode attribute of that workbook should be passed always."
date: 15.03.2018
version: 0.5.7
- changes:
- action: Added
details:
- "`pyexcel#120`, xlwt cannot save a book without any sheet. So, let's raise an exception in this case in order to warn the developers."
date: 15.03.2018
version: 0.5.6
- changes:
- action: Added
details:
- '`#25`, detect merged cell in .xls'
date: 8.11.2017
version: 0.5.5
- changes:
- action: Added
details:
- '`#24`, xlsx format cannot use skip_hidden_row_and_column. please use pyexcel-xlsx
instead.'
date: 2.11.2017
version: 0.5.4
- changes:
- action: Added
details:
- '`#21`, skip hidden rows and columns under ''skip_hidden_row_and_column'' flag.'
date: 2.11.2017
version: 0.5.3
- changes:
- action: updated
details:
- pyexcel `pyexcel#105`, remove gease from setup_requires, introduced by 0.5.1.
- remove python2.6 test support
- update its dependecy on pyexcel-io to 0.5.3
date: 23.10.2017
version: 0.5.2
- changes:
- action: added
details:
- '`pyexcel#103`, include LICENSE file in MANIFEST.in, meaning LICENSE file will
appear in the released tar ball.'
date: 20.10.2017
version: 0.5.1
- changes:
- action: Updated
details:
- '`#20`, is handled in pyexcel-io'
- put dependency on pyexcel-io 0.5.0, which uses cStringIO instead of StringIO. Hence,
there will be performance boost in handling files in memory.
date: 30.08.2017
version: 0.5.0
- changes:
- action: Updated
details:
- '`#20`, handle unseekable stream given by http response.'
date: 25.08.2017
version: 0.4.1
- changes:
- action: Updated
details:
- '`pyexcel-xlsx#15`, close file handle'
- pyexcel-io plugin interface now updated to use `lml <https://github.com/chfw/lml>`_.
date: 19.06.2017
version: 0.4.0
- changes:
- action: Updated
details:
- '`#18`, pass on encoding_override and others to xlrd.'
date: 30/05/2017
version: 0.3.3
- changes:
- action: Updated
details:
- '`#16`, allow mmap to be passed as file content'
date: 18.05.2017
version: 0.3.2
- changes:
- action: Updated
details:
- '`#14`, Python 3.6 - cannot use LOCALE flag with a str pattern'
- fix its dependency on pyexcel-io 0.3.0
date: 16.01.2017
version: 0.3.1
- changes:
- action: Updated
details:
- '`#13`, alert on empyty file content'
- Support pyexcel-io v0.3.0
date: 22.12.2016
version: 0.3.0
- changes:
- action: Updated
details:
- '`#10`, To support generator as member of the incoming two dimensional data'
date: 20.09.2016
version: 0.2.3
- changes:
- action: Added
details:
- 'support pagination. two pairs: start_row, row_limit and start_column, column_limit
help you deal with large files.'
date: 31.08.2016
version: 0.2.2
- changes:
- action: Added
details:
- '`#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.'
date: 13.07.2016
version: 0.2.1
- changes:
- action: Added
details:
- 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'
- action: Updated
details:
- 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
date: 01.06.2016
version: 0.2.0
- changes:
- action: Added
details:
- 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.
date: 17.01.2016
version: 0.1.0

View File

@ -1,39 +1,87 @@
# -*- coding: utf-8 -*-
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.doctest',
'sphinx.ext.intersphinx',
'sphinx.ext.viewcode',
]
DESCRIPTION = (
'A wrapper library to read, manipulate and write data in xls format. It' +
' reads xlsx and xlsm format' +
''
)
# Configuration file for the Sphinx documentation builder.
#
# 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
intersphinx_mapping = {
'pyexcel': ('http://pyexcel.readthedocs.org/en/latest/', None)
}
spelling_word_list_filename = 'spelling_wordlist.txt'
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- Project information -----------------------------------------------------
project = 'pyexcel-xls'
copyright = '2015-2021 Onni Software Ltd.'
author = 'C.W.'
# The short X.Y version
version = '0.7.0'
# The full version, including alpha/beta/rc tags
release = '0.7.0'
# -- General configuration ---------------------------------------------------
# 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',]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
source_suffix = '.rst'
master_doc = 'index'
project = u'pyexcel-xls'
copyright = u'2015-2016 Onni Software Ltd.'
version = '0.2.1'
release = '0.2.2'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = 'en'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = []
pygments_style = 'sphinx'
html_theme = 'default'
# -- 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'
# 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']
htmlhelp_basename = 'pyexcel-xlsdoc'
latex_elements = {}
latex_documents = [
('index', 'pyexcel-xls.tex', u'pyexcel-xls Documentation',
'Onni Software Ltd.', 'manual'),
]
man_pages = [
('index', 'pyexcel-xls', u'pyexcel-xls Documentation',
[u'Onni Software Ltd.'], 1)
]
# -- Extension configuration -------------------------------------------------
# -- Options for intersphinx extension ---------------------------------------
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'https://docs.python.org/3/': None}
# TODO: html_theme not configurable upstream
html_theme = 'default'
# TODO: DESCRIPTION not configurable upstream
texinfo_documents = [
('index', 'pyexcel-xls', u'pyexcel-xls Documentation',
'Onni Software Ltd.', 'pyexcel-xls', 'One line description of project.',
('index', 'pyexcel-xls',
'pyexcel-xls Documentation',
'Onni Software Ltd.', 'pyexcel-xls',
DESCRIPTION,
'Miscellaneous'),
]
intersphinx_mapping.update({
'pyexcel': ('http://pyexcel.readthedocs.io/en/latest/', None),
})
master_doc = "index"

3
format.sh Normal file
View File

@ -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

2
lint.sh Normal file
View File

@ -0,0 +1,2 @@
pip install flake8
flake8 --exclude=.moban.d,docs,setup.py --builtins=unicode,xrange,long . && python setup.py checkdocs

View File

@ -1,12 +1,18 @@
overrides: "pyexcel.yaml"
name: "pyexcel-xls"
nick_name: xls
version: 0.2.2
release: 0.2.1
version: 0.7.0
current_version: 0.7.0
release: 0.7.0
file_type: xls
is_on_conda: true
dependencies:
- pyexcel-io>=0.2.2
- pyexcel-io>=0.6.2
- xlrd
- xlwt;python_version<"3"
- xlwt-future;python_version>="3"
- 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
python_requires: ">=3.6"
min_python_version: "3.6"

View File

@ -4,28 +4,71 @@
The lower level xls/xlsx/xlsm file format handler using xlrd/xlwt
:copyright: (c) 2015-2016 by Onni Software Ltd
:copyright: (c) 2016-2021 by Onni Software Ltd
:license: New BSD License
"""
import xlrd
# 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
_FILE_TYPE = 'xls'
__pyexcel_io_plugins__ = [_FILE_TYPE]
from pyexcel_io.plugins import IOPluginInfoChainV2
__FILE_TYPE__ = "xls"
from pyexcel_io.io import get_data as read_data, isstream, store_data as write_data
def xlrd_version_2_or_greater():
xlrd_version = getattr(xlrd, "__version__")
if xlrd_version:
major = int(xlrd_version.split(".")[0])
if major >= 2:
return True
return False
XLRD_VERSION_2_OR_ABOVE = xlrd_version_2_or_greater()
supported_file_formats = [__FILE_TYPE__, "xlsx", "xlsm"]
if XLRD_VERSION_2_OR_ABOVE:
supported_file_formats.remove("xlsx")
IOPluginInfoChainV2(__name__).add_a_reader(
relative_plugin_class_path="xlsr.XLSInFile",
locations=["file"],
file_types=supported_file_formats,
stream_type="binary",
).add_a_reader(
relative_plugin_class_path="xlsr.XLSInMemory",
locations=["memory"],
file_types=supported_file_formats,
stream_type="binary",
).add_a_reader(
relative_plugin_class_path="xlsr.XLSInContent",
locations=["content"],
file_types=supported_file_formats,
stream_type="binary",
).add_a_writer(
relative_plugin_class_path="xlsw.XLSWriter",
locations=["file", "memory"],
file_types=[__FILE_TYPE__],
stream_type="binary",
)
def get_data(afile, file_type=None, **keywords):
"""standalone module function for reading module supported file type"""
if isstream(afile) and file_type is None:
file_type = _FILE_TYPE
file_type = __FILE_TYPE__
return read_data(afile, file_type=file_type, **keywords)
def save_data(afile, data, file_type=None, **keywords):
"""standalone module function for writing module supported file type"""
if isstream(afile) and file_type is None:
file_type = _FILE_TYPE
file_type = __FILE_TYPE__
write_data(afile, data, file_type=file_type, **keywords)

View File

@ -1,277 +0,0 @@
"""
pyexcel_xls
~~~~~~~~~~~~~~~~~~~
The lower level xls/xlsm file format handler using xlrd/xlwt
:copyright: (c) 2015-2016 by Onni Software Ltd
:license: New BSD License
"""
import sys
import math
import datetime
import xlrd
from xlwt import Workbook, XFStyle
from pyexcel_io.book import BookReader, BookWriter
from pyexcel_io.sheet import SheetReader, SheetWriter
PY2 = sys.version_info[0] == 2
if PY2 and sys.version_info[1] < 7:
from ordereddict import OrderedDict
else:
from collections import OrderedDict
DEFAULT_DATE_FORMAT = "DD/MM/YY"
DEFAULT_TIME_FORMAT = "HH:MM:SS"
DEFAULT_DATETIME_FORMAT = "%s %s" % (DEFAULT_DATE_FORMAT, DEFAULT_TIME_FORMAT)
def is_integer_ok_for_xl_float(value):
"""check if a float value had zero value in digits"""
return value == math.floor(value)
def xldate_to_python_date(value):
"""
convert xl date to python date
"""
date_tuple = xlrd.xldate_as_tuple(value, 0)
ret = None
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])
elif date_tuple[3:6] == (0, 0, 0):
ret = datetime.date(date_tuple[0],
date_tuple[1],
date_tuple[2])
else:
ret = datetime.datetime(
date_tuple[0],
date_tuple[1],
date_tuple[2],
date_tuple[3],
date_tuple[4],
date_tuple[5]
)
return ret
class XLSheet(SheetReader):
"""
xls, xlsx, xlsm sheet reader
Currently only support first sheet in the file
"""
def __init__(self, sheet, auto_detect_int=True, **keywords):
SheetReader.__init__(self, sheet, **keywords)
self.auto_detect_int = auto_detect_int
@property
def name(self):
return self.native_sheet.name
def number_of_rows(self):
"""
Number of rows in the xls sheet
"""
return self.native_sheet.nrows
def number_of_columns(self):
"""
Number of columns in the xls sheet
"""
return self.native_sheet.ncols
def _cell_value(self, row, column):
"""
Random access to the xls cells
"""
cell_type = self.native_sheet.cell_type(row, column)
value = self.native_sheet.cell_value(row, column)
if cell_type == xlrd.XL_CELL_DATE:
value = xldate_to_python_date(value)
elif cell_type == xlrd.XL_CELL_NUMBER and self.auto_detect_int:
if is_integer_ok_for_xl_float(value):
value = int(value)
return value
class XLSBook(BookReader):
"""
XLSBook reader
It reads xls, xlsm, xlsx work book
"""
def __init__(self):
BookReader.__init__(self)
self.file_content = None
def open(self, file_name, **keywords):
BookReader.open(self, file_name, **keywords)
self._get_params()
def open_stream(self, file_stream, **keywords):
BookReader.open_stream(self, file_stream, **keywords)
self._get_params()
def open_content(self, file_content, **keywords):
self.keywords = keywords
self.file_content = file_content
self._get_params()
def close(self):
if self.native_book:
self.native_book.release_resources()
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():
if self.skip_hidden_sheets and sheet.visibility != 0:
continue
data_dict = self.read_sheet(sheet)
result.update(data_dict)
return result
def read_sheet(self, native_sheet):
sheet = XLSheet(native_sheet, **self.keywords)
return {sheet.name: sheet.to_array()}
def _get_book(self, on_demand=False):
if self.file_name:
xls_book = xlrd.open_workbook(self.file_name, on_demand=on_demand)
elif self.file_stream:
xls_book = xlrd.open_workbook(
None,
file_contents=self.file_stream.getvalue(),
on_demand=on_demand
)
elif self.file_content:
xls_book = xlrd.open_workbook(
None,
file_contents=self.file_content,
on_demand=on_demand
)
return xls_book
def _get_params(self):
self.skip_hidden_sheets = self.keywords.get('skip_hidden_sheets', True)
class XLSheetWriter(SheetWriter):
"""
xls sheet writer
"""
def set_sheet_name(self, name):
"""Create a sheet
"""
self.native_sheet = self.native_book.add_sheet(name)
self.current_row = 0
def write_row(self, array):
"""
write a row into the file
"""
for i in range(len(array)):
value = array[i]
style = None
tmp_array = []
if isinstance(value, datetime.datetime):
tmp_array = [
value.year, value.month, value.day,
value.hour, value.minute, value.second
]
value = xlrd.xldate.xldate_from_datetime_tuple(tmp_array, 0)
style = XFStyle()
style.num_format_str = DEFAULT_DATETIME_FORMAT
elif isinstance(value, datetime.date):
tmp_array = [value.year, value.month, value.day]
value = xlrd.xldate.xldate_from_date_tuple(tmp_array, 0)
style = XFStyle()
style.num_format_str = DEFAULT_DATE_FORMAT
elif isinstance(value, datetime.time):
tmp_array = [value.hour, value.minute, value.second]
value = xlrd.xldate.xldate_from_time_tuple(tmp_array)
style = XFStyle()
style.num_format_str = DEFAULT_TIME_FORMAT
if style:
self.native_sheet.write(self.current_row, i, value, style)
else:
self.native_sheet.write(self.current_row, i, value)
self.current_row += 1
class XLSWriter(BookWriter):
"""
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 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)
_xls_reader_registry = {
"file_type": "xls",
"reader": XLSBook,
"writer": XLSWriter,
"stream_type": "binary",
"mime_type": "application/vnd.ms-excel",
"library": "pyexcel-xls"
}
_XLSM_MIME = (
"application/" +
"vnd.openxmlformats-officedocument.spreadsheetml.sheet")
_xlsm_registry = {
"file_type": "xlsm",
"reader": XLSBook,
"stream_type": "binary",
"mime_type": _XLSM_MIME,
"library": "pyexcel-xls"
}
_xlsx_registry = {
"file_type": "xlsx",
"reader": XLSBook,
"stream_type": "binary",
"mime_type": "application/vnd.ms-excel.sheet.macroenabled.12",
"library": "pyexcel-xls"
}
exports = (_xls_reader_registry,
_xlsm_registry,
_xlsx_registry)

217
pyexcel_xls/xlsr.py Normal file
View File

@ -0,0 +1,217 @@
"""
pyexcel_xlsr
~~~~~~~~~~~~~~~~~~~
The lower level xls/xlsm file format handler using xlrd
:copyright: (c) 2016-2021 by Onni Software Ltd
:license: New BSD License
"""
import datetime
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",
]
DEFAULT_ERROR_VALUE = "#N/A"
class MergedCell(object):
def __init__(self, row_low, row_high, column_low, column_high):
self.__rl = row_low
self.__rh = row_high
self.__cl = column_low
self.__ch = column_high
self.value = None
def register_cells(self, registry):
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(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):
self.__auto_detect_int = auto_detect_int
self.__hidden_cols = []
self.__hidden_rows = []
self.__merged_cells = {}
self._book_date_mode = date_mode
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.xls_sheet.colinfo_map.items():
if info.hidden == 1:
self.__hidden_cols.append(col_index)
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.xls_sheet.name
def row_iterator(self):
number_of_rows = self.xls_sheet.nrows - len(self.__hidden_rows)
return range(number_of_rows)
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:
row, column = self._offset_hidden_indices(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)
elif cell_type == xlrd.XL_CELL_NUMBER and self.__auto_detect_int:
if has_no_digits_in_float(value):
value = int(value)
elif cell_type == xlrd.XL_CELL_ERROR:
value = DEFAULT_ERROR_VALUE
if self.__merged_cells:
merged_cell = self.__merged_cells.get("%s-%s" % (row, column))
if merged_cell:
if merged_cell.value:
value = merged_cell.value
else:
merged_cell.value = value
return value
def _offset_hidden_indices(self, row, column):
row = calculate_offsets(row, self.__hidden_rows)
column = calculate_offsets(column, self.__hidden_cols)
return row, column
def calculate_offsets(incoming_index, hidden_indices):
offset = 0
for index in hidden_indices:
if index <= (incoming_index + offset):
offset += 1
return incoming_index + offset
class XLSReader(IReader):
"""
XLSBook reader
It reads xls, xlsm, xlsx work book
"""
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)
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
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
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 close(self):
if self.xls_book:
self.xls_book.release_resources()
self.xls_book = None
def get_xls_book(self, **xlrd_params):
xls_book = xlrd.open_workbook(**xlrd_params)
return xls_book
def _extract_xlrd_params(self):
params = {}
if self._keywords is not None:
for key in list(self._keywords.keys()):
if key in XLS_KEYWORDS:
params[key] = self._keywords.pop(key)
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
"""
date_tuple = xlrd.xldate_as_tuple(value, date_mode)
ret = None
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])
elif date_tuple[3:6] == (0, 0, 0):
ret = datetime.date(date_tuple[0], date_tuple[1], date_tuple[2])
else:
ret = datetime.datetime(
date_tuple[0],
date_tuple[1],
date_tuple[2],
date_tuple[3],
date_tuple[4],
date_tuple[5],
)
return ret

111
pyexcel_xls/xlsw.py Normal file
View File

@ -0,0 +1,111 @@
"""
pyexcel_xlsw
~~~~~~~~~~~~~~~~~~~
The lower level xls file format handler using xlwt
:copyright: (c) 2016-2021 by Onni Software Ltd
:license: New BSD License
"""
import datetime
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"
DEFAULT_LONGTIME_FORMAT = "[HH]:MM:SS"
DEFAULT_DATETIME_FORMAT = "%s %s" % (DEFAULT_DATE_FORMAT, DEFAULT_TIME_FORMAT)
EMPTY_SHEET_NOT_ALLOWED = "xlwt does not support a book without any sheets"
class XLSheetWriter(ISheetWriter):
"""
xls sheet writer
"""
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):
"""
write a row into the file
"""
for i, value in enumerate(array):
style = None
tmp_array = []
if isinstance(value, datetime.datetime):
tmp_array = [
value.year,
value.month,
value.day,
value.hour,
value.minute,
value.second,
]
value = xlrd.xldate.xldate_from_datetime_tuple(tmp_array, 0)
style = XFStyle()
style.num_format_str = DEFAULT_DATETIME_FORMAT
elif isinstance(value, datetime.timedelta):
value = value.days + value.seconds / 86_400
style = XFStyle()
style.num_format_str = DEFAULT_LONGTIME_FORMAT
elif isinstance(value, datetime.date):
tmp_array = [value.year, value.month, value.day]
value = xlrd.xldate.xldate_from_date_tuple(tmp_array, 0)
style = XFStyle()
style.num_format_str = DEFAULT_DATE_FORMAT
elif isinstance(value, datetime.time):
tmp_array = [value.hour, value.minute, value.second]
value = xlrd.xldate.xldate_from_time_tuple(tmp_array)
style = XFStyle()
style.num_format_str = DEFAULT_TIME_FORMAT
if style:
self._xls_sheet.write(self.current_row, i, value, style)
else:
self._xls_sheet.write(self.current_row, i, value)
self.current_row += 1
def close(self):
pass
class XLSWriter(IWriter):
"""
xls writer
"""
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:
IWriter.write(self, incoming_dict)
else:
raise NotImplementedError(EMPTY_SHEET_NOT_ALLOWED)
def close(self):
"""
This call actually save the file
"""
self.work_book.save(self.file_alike_object)

View File

@ -1,4 +1,3 @@
pyexcel-io>=0.2.2
pyexcel-io>=0.6.2
xlrd
xlwt;python_version<"3"
xlwt-future;python_version>="3"
xlwt

View File

@ -1 +1,2 @@
https://github.com/pyexcel/pyexcel-io/archive/master.zip
https://github.com/pyexcel/pyexcel-io/archive/dev.zip

4
setup.cfg Normal file
View File

@ -0,0 +1,4 @@
[metadata]
description-file = README.rst
[bdist_wheel]
universal = 1

224
setup.py
View File

@ -1,92 +1,212 @@
try:
from setuptools import setup, find_packages
except ImportError:
from ez_setup import use_setuptools
use_setuptools()
from setuptools import setup, find_packages
#!/usr/bin/env python3
"""
Template by pypi-mobans
"""
import os
import sys
import codecs
import locale
import platform
from shutil import rmtree
from setuptools import Command, setup, find_packages
PY2 = sys.version_info[0] == 2
PY26 = PY2 and sys.version_info[1] < 7
PY33 = sys.version_info < (3, 4)
NAME = 'pyexcel-xls'
AUTHOR = 'C.W.'
VERSION = '0.2.2'
EMAIL = 'wangc_2011 (at) hotmail.com'
LICENSE = 'New BSD'
PACKAGES = find_packages(exclude=['ez_setup', 'examples', 'tests'])
# 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 = "C.W."
VERSION = "0.7.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.7.0.tar.gz" % URL
FILES = ["README.rst","CONTRIBUTORS.rst", "CHANGELOG.rst"]
KEYWORDS = [
'excel',
'python',
'pyexcel',
"python",
'xls',
'xlsx',
'xlsm'
]
INSTALL_REQUIRES = [
'pyexcel-io>=0.2.2',
'xlrd',
]
if PY2:
INSTALL_REQUIRES.append('xlwt')
if not PY2:
INSTALL_REQUIRES.append('xlwt-future')
EXTRAS_REQUIRE = {
}
CLASSIFIERS = [
'Topic :: Office/Business',
'Topic :: Utilities',
'Topic :: Software Development :: Libraries',
'Programming Language :: Python',
'License :: OSI Approved :: BSD License',
'Intended Audience :: Developers',
'Development Status :: 3 - Alpha',
'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',
"Topic :: Software Development :: Libraries",
"Programming Language :: Python",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
'Programming Language :: Python :: Implementation :: PyPy'
]
PYTHON_REQUIRES = ">=3.6"
INSTALL_REQUIRES = [
"pyexcel-io>=0.6.2",
"xlrd",
"xlwt",
]
SETUP_COMMANDS = {}
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)
HERE = os.path.abspath(os.path.dirname(__file__))
GS_COMMAND = ("gease pyexcel-xls v0.7.0 " +
"Find 0.7.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)
class PublishCommand(Command):
"""Support setup.py upload."""
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))
def initialize_options(self):
pass
def finalize_options(self):
pass
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"))
except OSError:
pass
self.status("Building Source and Wheel (universal) distribution...")
run_status = True
if has_gease():
run_status = os.system(GS_COMMAND) == 0
else:
self.status(NO_GS_MESSAGE)
if run_status:
if os.system(PUBLISH_COMMAND) != 0:
self.status(UPLOAD_FAILED_MSG)
sys.exit()
SETUP_COMMANDS.update({
"publish": PublishCommand
})
def has_gease():
"""
test if github release command is installed
visit http://github.com/moremoban/gease for more info
"""
try:
import gease # noqa
return True
except ImportError:
return False
def read_files(*files):
"""Read files into setup"""
text = ""
for single_file in files:
text = text + read(single_file) + "\n"
content = read(single_file)
text = text + content + "\n"
return text
def read(afile):
"""Read a file into setup"""
with open(afile, 'r') as opened_file:
return opened_file.read()
the_relative_file = os.path.join(HERE, afile)
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
if __name__ == '__main__':
def filter_out_test_code(file_handle):
found_test_code = False
for line in file_handle.readlines():
if line.startswith(".. testcode:"):
found_test_code = True
continue
if found_test_code is True:
if line.startswith(" "):
continue
else:
empty_line = line.strip()
if len(empty_line) == 0:
continue
else:
found_test_code = False
yield line
else:
for keyword in ["|version|", "|today|"]:
if keyword in line:
break
else:
yield line
if __name__ == "__main__":
setup(
test_suite="tests",
name=NAME,
author=AUTHOR,
version=VERSION,
author_email=EMAIL,
description=DESCRIPTION,
install_requires=INSTALL_REQUIRES,
url=URL,
download_url=DOWNLOAD_URL,
long_description=read_files(*FILES),
license=LICENSE,
keywords=KEYWORDS,
python_requires=PYTHON_REQUIRES,
extras_require=EXTRAS_REQUIRE,
tests_require=["nose"],
install_requires=INSTALL_REQUIRES,
packages=PACKAGES,
include_package_data=True,
long_description=read_files('README.rst', 'CHANGELOG.rst'),
zip_safe=False,
tests_require=['nose'],
license=LICENSE,
classifiers=CLASSIFIERS
classifiers=CLASSIFIERS,
cmdclass=SETUP_COMMANDS
)

View File

@ -1,3 +1,2 @@
pip freeze
nosetests --with-cov --cover-package pyexcel_xls --cover-package tests --with-doctest --doctest-extension=.rst tests README.rst pyexcel_xls && flake8 . --exclude=.moban.d --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

View File

@ -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:

View File

@ -1,31 +1,35 @@
import os
import os # noqa
import datetime # 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])
table.append(data[8:12])
pyexcel.save_as(dest_file_name=file, array=table)
pyexcel.save_as(array=table, dest_file_name=file)
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):
pyexcel.save_as(adict=self.content, dest_file_name=self.testfile)
r = pyexcel.get_sheet(file_name=self.testfile, name_columns_by_row=0)
actual = pyexcel.utils.to_dict(r)
assert actual == self.content
eq_(r.dict, self.content)
class PyexcelWriterBase:
@ -35,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):
@ -48,29 +53,13 @@ class PyexcelWriterBase:
def test_write_array(self):
self._create_a_file(self.testfile)
r = pyexcel.get_sheet(file_name=self.testfile)
actual = pyexcel.utils.to_array(r.rows())
assert actual == self.content
def test_write_reader(self):
"""
Use reader as data container
this test case shows the file written by pyexcel
can be read back by itself
"""
self._create_a_file(self.testfile)
r = pyexcel.Reader(self.testfile)
r.save_as(self.testfile2)
r2 = pyexcel.Reader(self.testfile2)
r2.format(int)
actual = pyexcel.utils.to_array(r2.rows())
actual = list(r.rows())
assert actual == self.content
class PyexcelMultipleSheetBase:
def _write_test_file(self, filename):
pyexcel.save_book_as(dest_file_name=filename, bookdict=self.content)
pyexcel.save_book_as(bookdict=self.content, dest_file_name=filename)
def _clean_up(self):
if os.path.exists(self.testfile2):
@ -87,36 +76,17 @@ class PyexcelMultipleSheetBase:
def test_reading_through_sheets(self):
b = pyexcel.BookReader(self.testfile)
data = pyexcel.utils.to_array(b["Sheet1"].rows())
data = list(b["Sheet1"].rows())
expected = [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]]
assert data == expected
data = pyexcel.utils.to_array(b["Sheet2"].rows())
data = list(b["Sheet2"].rows())
expected = [[4, 4, 4, 4], [5, 5, 5, 5], [6, 6, 6, 6]]
assert data == expected
data = pyexcel.utils.to_array(b["Sheet3"].rows())
expected = [[u'X', u'Y', u'Z'], [1, 4, 7], [2, 5, 8], [3, 6, 9]]
data = list(b["Sheet3"].rows())
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)
data = pyexcel.utils.to_array(b["Sheet3"].rows())
data = list(b["Sheet3"].rows())
expected = [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
assert data == expected
def test_iterate_through_sheets(self):
b = pyexcel.BookReader(self.testfile)
for s in b:
data = pyexcel.utils.to_array(s)
assert self.content[s.name] == data
si = pyexcel.iterators.SheetIterator(b)
for s in si:
data = pyexcel.utils.to_array(s)
assert self.content[s.name] == data
def test_random_access_operator(self):
r = pyexcel.BookReader(self.testfile)
value = r["Sheet1"][0, 1]
assert value == 1
value = r["Sheet3"][0, 1]
assert value == 'Y'
r["Sheet3"].name_columns_by_row(0)
assert r["Sheet3"][0, 1] == 4

BIN
tests/fixtures/complex-merged-cells-sheet.xls vendored Executable file

Binary file not shown.

BIN
tests/fixtures/complex_hidden_sheets.xls vendored Normal file

Binary file not shown.

BIN
tests/fixtures/hidden.xls vendored Executable file

Binary file not shown.

BIN
tests/fixtures/merged-cell-sheet.xls vendored Executable file

Binary file not shown.

BIN
tests/fixtures/merged-sheet-exploration.xls vendored Executable file

Binary file not shown.

BIN
tests/fixtures/pyexcel_issue_151.xlsx vendored Normal file

Binary file not shown.

View File

@ -1,5 +1,12 @@
nose
mock;python_version<"3"
codecov
coverage
flake8
black
isort
collective.checkdocs
pygments
moban
moban_jinja2_github
pyexcel

View File

@ -5,43 +5,122 @@
"""
import os
import pyexcel as pe
from pyexcel_xls import save_data
from _compact import OrderedDict
from nose.tools import eq_
import datetime
from unittest.mock import MagicMock, patch
import pyexcel as pe
from _compact import OrderedDict
from pyexcel_xls import XLRD_VERSION_2_OR_ABOVE, save_data
from pyexcel_xls.xlsr import xldate_to_python_date
from pyexcel_xls.xlsw import XLSWriter as Writer
from nose import SkipTest
from nose.tools import eq_, raises
IN_TRAVIS = "TRAVIS" in os.environ
class TestBugFix:
def test_pyexcel_issue_5(self):
"""pyexcel issue #5
def test_pyexcel_issue_5():
"""pyexcel issue #5
datetime is not properly parsed
"""
s = pe.load(os.path.join("tests",
"test-fixtures",
"test-date-format.xls"))
assert s[0, 0] == datetime.datetime(2015, 11, 11, 11, 12, 0)
datetime is not properly parsed
"""
s = pe.load(get_fixture("test-date-format.xls"))
assert s[0, 0] == datetime.datetime(2015, 11, 11, 11, 12, 0)
def test_pyexcel_xls_issue_2(self):
data = OrderedDict()
array = []
for i in range(4100):
array.append([datetime.datetime.now()])
data.update({"test": array})
save_data("test.xls", data)
os.unlink("test.xls")
def test_issue_9_hidden_sheet(self):
test_file = os.path.join("tests", "fixtures", "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']])
def test_pyexcel_xls_issue_2():
data = OrderedDict()
array = []
for i in range(4100):
array.append([datetime.datetime.now()])
data.update({"test": array})
save_data("test.xls", data)
os.unlink("test.xls")
def test_issue_9_hidden_sheet_2(self):
test_file = os.path.join("tests", "fixtures", "hidden_sheets.xls")
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']])
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"]])
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)
assert "hidden" in book_dict
eq_(book_dict["shown"], [["A", "B"]])
eq_(book_dict["hidden"], [["a", "b"]])
def test_issue_10_generator_as_content():
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")
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")
@patch("xlrd.open_workbook")
def test_issue_18_encoding_override_isnt_passed(fake_open):
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
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" # noqa: E501
)
def test_issue_151():
if XLRD_VERSION_2_OR_ABOVE:
raise SkipTest()
s = pe.get_sheet(
file_name=get_fixture("pyexcel_issue_151.xlsx"),
skip_hidden_row_and_column=False,
library="pyexcel-xls",
)
eq_("#N/A", s[0, 0])
@raises(NotImplementedError)
def test_empty_book_pyexcel_issue_120():
"""
https://github.com/pyexcel/pyexcel/issues/120
"""
writer = Writer("fake.xls", "xls")
writer.write({})
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)

View File

@ -1,6 +1,7 @@
import os
from pyexcel_io import get_data, save_data
from nose.tools import eq_
@ -13,49 +14,58 @@ class TestFilter:
[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)

View File

@ -1,86 +1,123 @@
import os
import datetime
from unittest import TestCase
from textwrap import dedent
import pyexcel as pe
from nose.tools import eq_
class TestDateFormat:
def test_reading_date_format(self):
"""
date time
25/12/14 11:11:11
25/12/14 12:11:11
25/12/14 12:12:12
01/01/15 13:13:13
0.0 0.0
"""
r = pe.get_sheet(file_name=os.path.join("tests", "fixtures",
"date_field.xls"))
assert isinstance(r[1, 0], datetime.date) is True
assert r[1, 0].strftime("%d/%m/%y") == "25/12/14"
import datetime
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
assert r[1, 1].strftime("%H:%M:%S") == "11:11:11"
assert r[4, 0].strftime("%d/%m/%Y") == "01/01/1900"
assert r[4, 1].strftime("%H:%M:%S") == "00:00:00"
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),
datetime.timedelta(
days=50,
seconds=27,
microseconds=10,
milliseconds=29000,
minutes=5,
hours=8,
weeks=2,
),
]
]
pe.save_as(dest_file_name=excel_filename, array=data)
r = pe.get_sheet(file_name=excel_filename)
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
assert r[0, 1].strftime("%H:%M:%S") == "11:11:11"
assert isinstance(r[0, 2], datetime.date) is True
assert r[0, 2].strftime("%d/%m/%y %H:%M:%S") == "25/12/14 11:11:11"
assert isinstance(r[0, 3], datetime.datetime)
assert r[0, 3].strftime("%D-%H:%M:%S") == "03/04/00-08:05:56"
os.unlink(excel_filename)
class TestAutoDetectInt(TestCase):
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)
def test_auto_detect_int(self):
sheet = pe.get_sheet(file_name=self.test_file)
expected = dedent("""
sheet = pe.get_sheet(file_name=self.test_file, library="pyexcel-xls")
expected = dedent(
"""
pyexcel_sheet1:
+---+---+-----+
| 1 | 2 | 3.1 |
+---+---+-----+""").strip()
self.assertEqual(str(sheet), expected)
+---+---+-----+"""
).strip()
eq_(str(sheet), expected)
def test_get_book_auto_detect_int(self):
book = pe.get_book(file_name=self.test_file)
expected = dedent("""
book = pe.get_book(file_name=self.test_file, library="pyexcel-xls")
expected = dedent(
"""
pyexcel_sheet1:
+---+---+-----+
| 1 | 2 | 3.1 |
+---+---+-----+""").strip()
self.assertEqual(str(book), expected)
+---+---+-----+"""
).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)
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()
self.assertEqual(str(sheet), expected)
+-----+-----+-----+"""
).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)
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()
self.assertEqual(str(book), expected)
+-----+-----+-----+"""
).strip()
eq_(str(book), expected)
def tearDown(self):
os.unlink(self.test_file)

23
tests/test_hidden.py Normal file
View File

@ -0,0 +1,23 @@
import os
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,
)
expected = [[1, 3], [7, 9]]
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,
)
expected = [[1, 3, 5, 7, 9], [31, 33, 35, 37, 39], [61, 63, 65, 67]]
eq_(data["Sheet1"], expected)

View File

@ -0,0 +1,84 @@
import os
from pyexcel_xls import get_data
from pyexcel_xls.xlsr import MergedCell
from nose.tools import eq_
def test_merged_cells():
data = get_data(
get_fixture("merged-cell-sheet.xls"),
detect_merged_cells=True,
library="pyexcel-xls",
)
expected = [[1, 2, 3], [1, 5, 6], [1, 8, 9], [10, 11, 11]]
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",
)
expected = [
[1, 1, 2, 3, 15, 16, 22, 22, 24, 24],
[1, 1, 4, 5, 15, 17, 22, 22, 24, 24],
[6, 7, 8, 9, 15, 18, 22, 22, 24, 24],
[10, 11, 11, 12, 19, 19, 23, 23, 24, 24],
[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],
]
eq_(data["Sheet1"], expected)
def test_exploration():
data = get_data(
get_fixture("merged-sheet-exploration.xls"),
detect_merged_cells=True,
library="pyexcel-xls",
)
expected_sheet1 = [
[1, 1, 1, 1, 1, 1],
[2],
[2],
[2],
[2],
[2],
[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)
expected_sheet3 = [
["", "", "", "", "", 2, 2, 2],
[],
[],
[],
["", "", "", 5],
["", "", "", 5],
["", "", "", 5],
["", "", "", 5],
["", "", "", 5],
]
eq_(data["Sheet3"], expected_sheet3)
def test_merged_cell_class():
test_dict = {}
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"]
eq_(keys, expected)
eq_(merged_cell, test_dict["3-1"])
def get_fixture(file_name):
return os.path.join("tests", "fixtures", file_name)

View File

@ -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 @@ class TestAddBooks:
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 @@ class TestAddBooks:
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):
@ -92,10 +93,10 @@ class TestAddBooks:
"""
test this scenario: book3 = book1 + book2
"""
b1 = pyexcel.BookReader(self.testfile)
b2 = pyexcel.BookReader(self.testfile2)
b1 = pyexcel.get_book(file_name=self.testfile)
b2 = pyexcel.get_book(file_name=self.testfile2)
b3 = b1 + b2
content = pyexcel.utils.to_dict(b3)
content = b3.dict
sheet_names = content.keys()
assert len(sheet_names) == 6
for name in sheet_names:
@ -108,12 +109,12 @@ class TestAddBooks:
def test_add_book1_in_place(self):
"""
test this scenario book1 += book2
test this scenario: book1 += book2
"""
b1 = pyexcel.BookReader(self.testfile)
b2 = pyexcel.BookReader(self.testfile2)
b1 += b2
content = pyexcel.utils.to_dict(b1)
content = b1.dict
sheet_names = content.keys()
assert len(sheet_names) == 6
for name in sheet_names:
@ -126,12 +127,12 @@ class TestAddBooks:
def test_add_book2(self):
"""
test this scenario book3 = book1 + sheet3
test this scenario: book3 = book1 + sheet3
"""
b1 = pyexcel.BookReader(self.testfile)
b2 = pyexcel.BookReader(self.testfile2)
b3 = b1 + b2["Sheet3"]
content = pyexcel.utils.to_dict(b3)
content = b3.dict
sheet_names = content.keys()
assert len(sheet_names) == 4
for name in sheet_names:
@ -144,12 +145,12 @@ class TestAddBooks:
def test_add_book2_in_place(self):
"""
test this scenario book3 = book1 + sheet3
test this scenario: book3 = book1 + sheet3
"""
b1 = pyexcel.BookReader(self.testfile)
b2 = pyexcel.BookReader(self.testfile2)
b1 += b2["Sheet3"]
content = pyexcel.utils.to_dict(b1)
content = b1.dict
sheet_names = content.keys()
assert len(sheet_names) == 4
for name in sheet_names:
@ -162,12 +163,12 @@ class TestAddBooks:
def test_add_book3(self):
"""
test this scenario book3 = sheet1 + sheet2
test this scenario: book3 = sheet1 + sheet2
"""
b1 = pyexcel.BookReader(self.testfile)
b2 = pyexcel.BookReader(self.testfile2)
b3 = b1["Sheet1"] + b2["Sheet3"]
content = pyexcel.utils.to_dict(b3)
content = b3.dict
sheet_names = content.keys()
assert len(sheet_names) == 2
assert content["Sheet3"] == self.content["Sheet3"]
@ -175,12 +176,12 @@ class TestAddBooks:
def test_add_book4(self):
"""
test this scenario book3 = sheet1 + book
test this scenario: book3 = sheet1 + book
"""
b1 = pyexcel.BookReader(self.testfile)
b2 = pyexcel.BookReader(self.testfile2)
b3 = b1["Sheet1"] + b2
content = pyexcel.utils.to_dict(b3)
content = b3.dict
sheet_names = content.keys()
assert len(sheet_names) == 4
for name in sheet_names:
@ -219,18 +220,17 @@ class TestMultiSheetReader:
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

View File

@ -1,30 +1,32 @@
import os
import pyexcel
from base import create_sample_file1
from nose.tools import eq_
class TestStringIO:
def test_xls_stringio(self):
xlsfile = "cute.xls"
create_sample_file1(xlsfile)
with open(xlsfile, "rb") as f:
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)
result = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 1.1, 1]
actual = pyexcel.utils.to_array(r.enumerate())
assert result == actual
if os.path.exists(xlsfile):
os.unlink(xlsfile)
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())
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 = pyexcel.utils.to_array(r.enumerate())
assert result == actual
actual = list(r.enumerate())
eq_(result, actual)

View File

@ -1,24 +1,22 @@
import os
from pyexcel_xls.xls import XLSWriter, XLSBook
from base import PyexcelWriterBase, PyexcelHatWriterBase
from pyexcel_xls import get_data
from pyexcel_xls.xlsw import XLSWriter as Writer
class TestNativeXLWriter:
class TestNativeXLSWriter:
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 = "xlwriter.xls"
writer = XLSWriter()
writer.open(self.testfile)
self.testfile = "writer.xls"
writer = Writer(self.testfile, "xls")
writer.write(self.content)
writer.close()
reader = XLSBook()
reader.open(self.testfile)
content = reader.read_all()
reader.close()
content = get_data(self.testfile)
for key in content.keys():
content[key] = list(content[key])
assert content == self.content
@ -28,7 +26,7 @@ class TestNativeXLWriter:
os.unlink(self.testfile)
class TestXLSnCSVWriter(PyexcelWriterBase):
class TestxlsnCSVWriter(PyexcelWriterBase):
def setUp(self):
self.testfile = "test.xls"
self.testfile2 = "test.csv"
@ -40,7 +38,7 @@ class TestXLSnCSVWriter(PyexcelWriterBase):
os.unlink(self.testfile2)
class TestXLSHatWriter(PyexcelHatWriterBase):
class TestxlsHatWriter(PyexcelHatWriterBase):
def setUp(self):
self.testfile = "test.xls"