Codebase list pyexcel / 9edb58e
New upstream release. Kali Janitor 1 year, 6 months ago
169 changed file(s) with 4217 addition(s) and 1789 deletion(s). Raw diff Collapse all Expand all
00 # These are supported funding model platforms
11
2 patreon: pyexcel
2 github: chfw
3 patreon: chfw
0 With your PR, here is a check list:
1
2 - [ ] Has test cases written?
3 - [ ] Has all code lines tested?
4 - [ ] Has `make format` been run?
5 - [ ] Please update CHANGELOG.yml(not CHANGELOG.rst)
6 - [ ] Has fair amount of documentation if your change is complex
7 - [ ] Agree on NEW BSD License for your contribution
0 name: lint
1
2 on: [push, pull_request]
3
4 jobs:
5 lint:
6 runs-on: ubuntu-latest
7 name: lint code
8 steps:
9 - uses: actions/checkout@v2
10 - name: Set up Python
11 uses: actions/setup-python@v1
12 with:
13 python-version: 3.8
14 - name: lint
15 run: |
16 pip --use-deprecated=legacy-resolver install flake8
17 pip --use-deprecated=legacy-resolver install -r tests/requirements.txt
18 flake8 --exclude=.moban.d,docs,setup.py --builtins=unicode,xrange,long .
19 python setup.py checkdocs
0 on: [push]
1
2 jobs:
3 run_moban:
4 runs-on: ubuntu-latest
5 name: synchronize templates via moban
6 steps:
7 - uses: actions/checkout@v2
8 with:
9 ref: ${{ github.head_ref }}
10 token: ${{ secrets.PAT }}
11 - name: Set up Python
12 uses: actions/setup-python@v1
13 with:
14 python-version: '3.7'
15 - name: check changes
16 run: |
17 pip install moban gitfs2 pypifs moban-jinja2-github moban-ansible
18 moban
19 git status
20 git diff --exit-code
21 - name: Auto-commit
22 if: failure()
23 uses: stefanzweifel/git-auto-commit-action@v4
24 with:
25 commit_message: >-
26 This is an auto-commit, updating project meta data,
27 such as changelog.rst, contributors.rst
0 name: Upload Python Package
1
2 on:
3 release:
4 types: [created]
5
6 jobs:
7 deploy:
8 runs-on: ubuntu-latest
9 steps:
10 - uses: actions/checkout@v1
11 - name: Set up Python
12 uses: actions/setup-python@v1
13 with:
14 python-version: '3.x'
15 - name: Install dependencies
16 run: |
17 python -m pip install --upgrade pip
18 pip install setuptools wheel twine
19 - name: Build and publish
20 env:
21 TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
22 TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
23 run: |
24 python setup.py sdist bdist_wheel
25 twine upload dist/*
0 name: Run unit tests on Windows, Ubuntu and Mac
1
2 on: [push, pull_request]
3
4 jobs:
5 test:
6 strategy:
7 fail-fast: false
8 matrix:
9 python-version: [3.7, 3.8, 3.9]
10 os: [macOs-latest, ubuntu-latest, windows-latest]
11 exclude:
12 - os: macOs-latest
13 python-version: 3.7
14
15 runs-on: ${{ matrix.os }}
16 name: run tests
17 steps:
18 - uses: actions/checkout@v2
19 - name: Set up Python
20 uses: actions/setup-python@v1
21 with:
22 python-version: ${{ matrix.python-version }}
23 - name: install
24 run: |
25 pip --use-deprecated=legacy-resolver install -r requirements.txt
26 pip --use-deprecated=legacy-resolver install -r tests/requirements.txt
27 - name: test
28 run: |
29 pip freeze
30 nosetests --verbosity=3 --with-coverage --cover-package pyexcel --cover-package tests tests --with-doctest --doctest-extension=.rst docs/source pyexcel
31 - name: Upload coverage
32 uses: codecov/codecov-action@v1
33 with:
34 name: ${{ matrix.os }} Python ${{ matrix.python-version }}
2424 sdist/
2525 var/
2626 wheels/
27 pip-wheel-metadata/
2827 share/python-wheels/
2928 *.egg-info/
3029 .installed.cfg
5150 nosetests.xml
5251 coverage.xml
5352 *.cover
53 *.py,cover
5454 .hypothesis/
5555 .pytest_cache/
56 cover/
5657
5758 # Translations
5859 *.mo
6263 *.log
6364 local_settings.py
6465 db.sqlite3
66 db.sqlite3-journal
6567
6668 # Flask stuff:
6769 instance/
7476 docs/_build/
7577
7678 # PyBuilder
79 .pybuilder/
7780 target/
7881
7982 # Jupyter Notebook
8487 ipython_config.py
8588
8689 # pyenv
87 .python-version
90 # For a library or package, you might want to ignore these files since the code is
91 # intended to run in multiple environments; otherwise, check them in:
92 # .python-version
8893
8994 # pipenv
9095 # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
9196 # However, in case of collaboration, if having platform-specific dependencies or dependencies
92 # having no cross-platform support, pipenv may install dependencies that don’t work, or not
97 # having no cross-platform support, pipenv may install dependencies that don't work, or not
9398 # install all needed dependencies.
9499 #Pipfile.lock
95100
96 # celery beat schedule file
101 # PEP 582; used by e.g. github.com/David-OConnor/pyflow
102 __pypackages__/
103
104 # Celery stuff
97105 celerybeat-schedule
106 celerybeat.pid
98107
99108 # SageMath parsed files
100109 *.sage.py
125134
126135 # Pyre type checker
127136 .pyre/
137
138 # pytype static type analyzer
139 .pytype/
140
141 # Cython debug symbols
142 cython_debug/
128143
129144 # VirtualEnv rules
130145 # Virtualenv
158173 # Windows rules
159174 # Windows thumbnail cache files
160175 Thumbs.db
176 Thumbs.db:encryptable
161177 ehthumbs.db
162178 ehthumbs_vista.db
163179
263279 # Vim rules
264280 # Swap
265281 [._]*.s[a-v][a-z]
282 !*.svg # comment out if you don't need vector files
266283 [._]*.sw[a-p]
267284 [._]s[a-rt-v][a-z]
268285 [._]ss[a-gi-z]
270287
271288 # Session
272289 Session.vim
290 Sessionx.vim
273291
274292 # Temporary
275293 .netrwhist
280298 [._]*.un~
281299
282300 # JetBrains rules
283 # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
301 # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
284302 # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
285303
286304 # User-specific stuff
310328 # When using Gradle or Maven with auto-import, you should exclude module files,
311329 # since they will be recreated, and may cause churn. Uncomment if using
312330 # auto-import.
331 # .idea/artifacts
332 # .idea/compiler.xml
333 # .idea/jarRepositories.xml
313334 # .idea/modules.xml
314335 # .idea/*.iml
315336 # .idea/modules
337 # *.iml
338 # *.ipr
316339
317340 # CMake
318341 cmake-build-*/
362385
363386 # SFTP configuration file
364387 sftp-config.json
388 sftp-config-alt*.json
365389
366390 # Package control specific files
367391 Package Control.last-run
399423 !.vscode/tasks.json
400424 !.vscode/launch.json
401425 !.vscode/extensions.json
426 *.code-workspace
427
428 # Local History for Visual Studio Code
429 .history/
402430
403431 # Xcode rules
404432 # Xcode
425453 *.perspectivev3
426454 !default.perspectivev3
427455
456 ## Gcc Patch
457 /*.gcno
458
428459 # Eclipse rules
429460 .metadata
430461 bin/
476507
477508 # Annotation Processing
478509 .apt_generated/
510 .apt_generated_test/
479511
480512 # Scala IDE specific (Scala & Java development for Eclipse)
481513 .cache-main
482514 .scala_dependencies
483515 .worksheet
516
517 # Uncomment this line if you wish to ignore the project description file.
518 # Typically, this file would be tracked if it contains build/dependency configurations:
519 #.project
484520
485521 # TortoiseGit rules
486522 # Project-level settings
505541 cscope.po.out
506542
507543
544 # remove moban hash dictionary
545 .moban.hashes
546
508547 docs/build/
509548 commons
0 [settings]
1 line_length=79
2 known_first_party=
3 known_third_party=mock,nose,pyexcel_io
4 indent=' '
5 multi_line_output=3
6 length_sort=1
7 default_section=FIRSTPARTY
8 no_lines_before=LOCALFOLDER
9 sections=FUTURE,STDLIB,FIRSTPARTY,THIRDPARTY,LOCALFOLDER
+0
-160
.moban.d/README.rst less more
0 {%extends "BASIC-README.rst.jj2"%}
1
2 {%block features%}
3
4 Feature Highlights
5 ===================
6
7 1. One application programming interface(API) to handle multiple data sources:
8
9 * physical file
10 * memory file
11 * SQLAlchemy table
12 * Django Model
13 * Python data structures: dictionary, records and array
14 2. One API to read and write data in various excel file formats.
15 3. For large data sets, data streaming are supported. A genenerator can be returned to you. Checkout iget_records, iget_array, isave_as and isave_book_as.
16
17 {% endblock %}
18
19 {%block usage%}
20
21 Usage
22 ===============
23
24 Please note that you will have to use '.sortable.html' in order to replicate the example.
25
26 .. image:: https://github.com/pyexcel/pyexcel-sortable/raw/master/sortable.gif
27
28 .. code-block:: python
29
30 >>> # pip install pyexcel-text==0.2.7.1
31 >>> import pyexcel as p
32 >>> ccs_insight2 = p.Sheet()
33 >>> ccs_insight2.name = "Worldwide Mobile Phone Shipments (Billions), 2017-2021"
34 >>> ccs_insight2.ndjson = """
35 ... {"year": ["2017", "2018", "2019", "2020", "2021"]}
36 ... {"smart phones": [1.53, 1.64, 1.74, 1.82, 1.90]}
37 ... {"feature phones": [0.46, 0.38, 0.30, 0.23, 0.17]}
38 ... """.strip()
39 >>> ccs_insight2
40 pyexcel sheet:
41 +----------------+------+------+------+------+------+
42 | year | 2017 | 2018 | 2019 | 2020 | 2021 |
43 +----------------+------+------+------+------+------+
44 | smart phones | 1.53 | 1.64 | 1.74 | 1.82 | 1.9 |
45 +----------------+------+------+------+------+------+
46 | feature phones | 0.46 | 0.38 | 0.3 | 0.23 | 0.17 |
47 +----------------+------+------+------+------+------+
48
49
50
51 Suppose you have the following data in a dictionary:
52
53 ========= ====
54 Name Age
55 ========= ====
56 Adam 28
57 Beatrice 29
58 Ceri 30
59 Dean 26
60 ========= ====
61
62 you can easily save it into an excel file using the following code:
63
64 .. code-block:: python
65
66 >>> import pyexcel
67 >>> # make sure you had pyexcel-xls installed
68 >>> a_list_of_dictionaries = [
69 ... {
70 ... "Name": 'Adam',
71 ... "Age": 28
72 ... },
73 ... {
74 ... "Name": 'Beatrice',
75 ... "Age": 29
76 ... },
77 ... {
78 ... "Name": 'Ceri',
79 ... "Age": 30
80 ... },
81 ... {
82 ... "Name": 'Dean',
83 ... "Age": 26
84 ... }
85 ... ]
86 >>> pyexcel.save_as(records=a_list_of_dictionaries, dest_file_name="your_file.xls")
87
88
89 And here's how to obtain the records:
90
91 .. code-block:: python
92
93 >>> import pyexcel as p
94 >>> records = p.iget_records(file_name="your_file.xls")
95 >>> for record in records:
96 ... print("%s is aged at %d" % (record['Name'], record['Age']))
97 Adam is aged at 28
98 Beatrice is aged at 29
99 Ceri is aged at 30
100 Dean is aged at 26
101 >>> p.free_resources()
102
103
104 Advanced usage :fire:
105 ----------------------
106
107 If you are dealing with big data, please consider these usages:
108
109 >>> def increase_everyones_age(generator):
110 ... for row in generator:
111 ... row['Age'] += 1
112 ... yield row
113 >>> def duplicate_each_record(generator):
114 ... for row in generator:
115 ... yield row
116 ... yield row
117 >>> records = p.iget_records(file_name="your_file.xls")
118 >>> io=p.isave_as(records=duplicate_each_record(increase_everyones_age(records)),
119 ... dest_file_type='csv', dest_lineterminator='\n')
120 >>> print(io.getvalue())
121 Age,Name
122 29,Adam
123 29,Adam
124 30,Beatrice
125 30,Beatrice
126 31,Ceri
127 31,Ceri
128 27,Dean
129 27,Dean
130 <BLANKLINE>
131
132 Two advantages of above method:
133
134 #. Add as many wrapping functions as you want.
135 #. Constant memory consumption
136
137 Available Plugins
138 =================
139
140 {% include "plugins-list.rst.jj2"%}
141
142
143 Acknowledgement
144 ===============
145
146 All great work have been done by odf, ezodf, xlrd, xlwt, tabulate and other
147 individual developers. This library unites only the data access code.
148
149
150 .. testcode::
151 :hide:
152
153 >>> import os
154 >>> os.unlink("your_file.xls")
155
156 {%endblock%}
157
158 {%block development_guide%}
159 {%endblock%}
0 {% extends 'setup.py.jj2' %}
1
2 {% block additional_keywords %}
3 'tsv',
4 'tsvz'
5 'csv',
6 'csvz',
7 'xls',
8 'xlsx',
9 'ods'
10 {% endblock %}
11
12 {% block additional_classifiers %}
13 'Development Status :: 3 - Alpha',
14 {% endblock %}}
15
16 {%- block morefiles %}
17 "CONTRIBUTORS.rst",
18 {%- endblock %}
0 ================================================================================
1 Read partial data
2 ================================================================================
3
4
5 {% include "partial-data.rst.jj2" %}
0 {% include 'docs/source/conf.py' %}
0 {% extends 'docs/source/conf.py.jj2'%}
11
2 master_doc = "index"
2 {%block SPHINX_EXTENSIONS%}
3 'sphinx.ext.autosummary',
4 'sphinx.ext.napoleon',
5 'sphinxcontrib.excel'
6 {%endblock%}
7
8 {%block custom_doc_theme%}
9 html_theme = 'default'
10 def setup(app):
11 app.add_stylesheet('theme_overrides.css')
12
13
14 {%endblock%}
15
16 {%block additional_mapping%}
17 'xlrd': ('http://xlrd.readthedocs.io/en/latest/', None)
18 {%endblock%}
+0
-6
.moban.d/docs/source/guide.rst less more
0 Developer's guide
1 =================
2
3 {%include "developer_guide.rst.jj2" %}
4
5
0 Developer's guide
1 =================
2
3 {%include "developer_guide.rst.jj2" %}
4
5
2222
2323 {%include "installation.rst.jj2" %}
2424
25 Suppose you have the following data in a dictionary:
26
27 ========= ====
28 Name Age
29 ========= ====
30 Adam 28
31 Beatrice 29
32 Ceri 30
33 Dean 26
34 ========= ====
35
36 you can easily save it into an excel file using the following code:
37
38 .. code-block:: python
39
40 >>> import pyexcel
41 >>> # make sure you had pyexcel-xls installed
42 >>> a_list_of_dictionaries = [
43 ... {
44 ... "Name": 'Adam',
45 ... "Age": 28
46 ... },
47 ... {
48 ... "Name": 'Beatrice',
49 ... "Age": 29
50 ... },
51 ... {
52 ... "Name": 'Ceri',
53 ... "Age": 30
54 ... },
55 ... {
56 ... "Name": 'Dean',
57 ... "Age": 26
58 ... }
59 ... ]
60 >>> pyexcel.save_as(records=a_list_of_dictionaries, dest_file_name="your_file.xls")
61
62 And here's how to obtain the records:
63
64 .. code-block:: python
65
66 >>> import pyexcel as p
67 >>> records = p.iget_records(file_name="your_file.xls")
68 >>> for record in records:
69 ... print("%s is aged at %d" % (record['Name'], record['Age']))
70 Adam is aged at 28
71 Beatrice is aged at 29
72 Ceri is aged at 30
73 Dean is aged at 26
74 >>> p.free_resources()
75
76
77 Custom data rendering:
78
79 .. code-block:: python
80
81 >>> # pip install pyexcel-text==0.2.7.1
82 >>> import pyexcel as p
83 >>> ccs_insight2 = p.Sheet()
84 >>> ccs_insight2.name = "Worldwide Mobile Phone Shipments (Billions), 2017-2021"
85 >>> ccs_insight2.ndjson = """
86 ... {"year": ["2017", "2018", "2019", "2020", "2021"]}
87 ... {"smart phones": [1.53, 1.64, 1.74, 1.82, 1.90]}
88 ... {"feature phones": [0.46, 0.38, 0.30, 0.23, 0.17]}
89 ... """.strip()
90 >>> ccs_insight2
91 pyexcel sheet:
92 +----------------+------+------+------+------+------+
93 | year | 2017 | 2018 | 2019 | 2020 | 2021 |
94 +----------------+------+------+------+------+------+
95 | smart phones | 1.53 | 1.64 | 1.74 | 1.82 | 1.9 |
96 +----------------+------+------+------+------+------+
97 | feature phones | 0.46 | 0.38 | 0.3 | 0.23 | 0.17 |
98 +----------------+------+------+------+------+------+
99
100
101 Advanced usage :fire:
102 ----------------------
103
104 If you are dealing with big data, please consider these usages:
105
106 .. code-block:: python
107
108 >>> def increase_everyones_age(generator):
109 ... for row in generator:
110 ... row['Age'] += 1
111 ... yield row
112 >>> def duplicate_each_record(generator):
113 ... for row in generator:
114 ... yield row
115 ... yield row
116 >>> records = p.iget_records(file_name="your_file.xls")
117 >>> io=p.isave_as(records=duplicate_each_record(increase_everyones_age(records)),
118 ... dest_file_type='csv', dest_lineterminator='\n')
119 >>> print(io.getvalue())
120 Age,Name
121 29,Adam
122 29,Adam
123 30,Beatrice
124 30,Beatrice
125 31,Ceri
126 31,Ceri
127 27,Dean
128 27,Dean
129 <BLANKLINE>
130
131
132 Two advantages of above method:
133
134 #. Add as many wrapping functions as you want.
135 #. Constant memory consumption
136
137
138 .. testcode::
139 :hide:
140
141 >>> import os
142 >>> os.unlink("your_file.xls")
143
144
145
25146 For individual excel file formats, please install them as you wish:
26147
27148 {%include "plugins-list.rst.jj2"%}
33154 ======== ========== ============= ==================== ============= =============
34155 pyexcel pyexcel-io pyexcel-text pyexcel-handsontable pyexcel-pygal pyexcel-gantt
35156 ======== ========== ============= ==================== ============= =============
36 0.5.14+ 0.5.18+ 0.2.6+ 0.0.1+ 0.0.1 0.0.1
157 0.6.5+ 0.6.2+ 0.2.6+ 0.0.1+ 0.0.1 0.0.1
158 0.5.15+ 0.5.19+ 0.2.6+ 0.0.1+ 0.0.1 0.0.1
159 0.5.14 0.5.18 0.2.6+ 0.0.1+ 0.0.1 0.0.1
37160 0.5.10+ 0.5.11+ 0.2.6+ 0.0.1+ 0.0.1 0.0.1
38161 0.5.9.1+ 0.5.9.1+ 0.2.6+ 0.0.1 0.0.1 0.0.1
39162 0.5.4+ 0.5.1+ 0.2.6+ 0.0.1 0.0.1 0.0.1
42165 ======== ========== ============= ==================== ============= =============
43166
44167
45 .. table:: a list of support file formats
46
47 ============ =======================================================
48 file format definition
49 ============ =======================================================
50 csv comma separated values
51 tsv tab separated values
52 csvz a zip file that contains one or many csv files
53 tsvz a zip file that contains one or many tsv files
54 xls a spreadsheet file format created by
55 MS-Excel 97-2003 [#f1]_
56 xlsx MS-Excel Extensions to the Office Open XML
57 SpreadsheetML File Format. [#f2]_
58 xlsm an MS-Excel Macro-Enabled Workbook file
59 ods open document spreadsheet
60 fods flat open document spreadsheet
61 json java script object notation
62 html html table of the data structure
63 simple simple presentation
64 rst rStructured Text presentation of the data
65 mediawiki media wiki table
66 ============ =======================================================
67
68
69 .. [f1] quoted from `whatis.com <http://whatis.techtarget.com/fileformat/XLS-Worksheet-file-Microsoft-Excel>`_. Technical details can be found at `MSDN XLS <https://msdn.microsoft.com/en-us/library/office/gg615597(v=office.14).aspx>`_
70 .. [f2] xlsx is used by MS-Excel 2007, more information can be found at `MSDN XLSX <https://msdn.microsoft.com/en-us/library/dd922181(v=office.12).aspx>`_
71
168 {% include "supported-file-list.rst.jj2" %}
72169
73170 Usage
74171 ------
140237 architecture
141238
142239 New tutorial
143 ----------
240 --------------
144241 .. toctree::
145242
146243 quickstart
153250 database
154251
155252 Old tutorial
156 ----------
253 --------------
157254 .. toctree::
158255
159256 tutorial_file
211308
212309 migration_guide
213310 changelog
214 note_on_pypy
215311
216312
217313 Indices and tables
0 {% set sphinx=True %}
1
2 {% include "one-liners.rst.jj2" %}
0 {% set sphinx=True %}
1
2 {% include "two-liners.rst.jj2" %}
+0
-3
.moban.d/minimum_requirements.txt less more
0 {%include "min_requirements.txt.jj2" %}
1 pyexcel-xlsx==0.4.1
2 pyexcel-xls==0.4.1
0 {%include "min_requirements.txt.jj2" %}
1 pyexcel-xlsx==0.4.1
2 pyexcel-xls==0.4.1
0 One liners
1 ================================================================================
2
3 This section shows you how to get data from your excel files and how to
4 export data to excel files in **one line**
5
6 Read from the excel files
7 --------------------------------------------------------------------------------
8
9 Get a list of dictionaries
10 ********************************************************************************
11
12 {% if sphinx %}
13 .. testcode::
14 :hide:
15
16 >>> import os
17 >>> import pyexcel as p
18 >>> content="""
19 ... Name,Period,Representative Composers
20 ... Medieval,c.1150-c.1400,"Machaut, Landini"
21 ... Renaissance,c.1400-c.1600,"Gibbons, Frescobaldi"
22 ... Baroque,c.1600-c.1750,"JS Bach, Vivaldi"
23 ... Classical,c.1750-c.1830,"Joseph Haydn, Wolfgan Amadeus Mozart"
24 ... Earley Romantic,c.1830-c.1860,"Chopin, Mendelssohn, Schumann, Liszt"
25 ... Late Romantic,c.1860-c.1920,"Wagner,Verdi"
26 ... """.strip()
27 >>> sheet = p.get_sheet(file_content=content, file_type='csv')
28 >>> sheet.save_as("your_file.xls")
29
30 {% endif %}
31
32 Suppose you want to process `History of Classical Music <https://www.naxos.com/education/brief_history.asp>`_:
33
34 {% if sphinx %}
35
36 .. pyexcel-table::
37
38 ---pyexcel:History of Classical Music---
39 Name,Period,Representative Composers
40 Medieval,c.1150-c.1400,"Machaut, Landini"
41 Renaissance,c.1400-c.1600,"Gibbons, Frescobaldi"
42 Baroque,c.1600-c.1750,"JS Bach, Vivaldi"
43 Classical,c.1750-c.1830,"Joseph Haydn, Wolfgan Amadeus Mozart"
44 Earley Romantic,c.1830-c.1860,"Chopin, Mendelssohn, Schumann, Liszt"
45 Late Romantic,c.1860-c.1920, "Wagner,Verdi"
46
47
48 {% else %}
49
50 History of Classical Music:
51
52 =============== ============= ====================================
53 Name Period Representative Composers
54 Medieval c.1150-c.1400 Machaut, Landini
55 Renaissance c.1400-c.1600 Gibbons, Frescobaldi
56 Baroque c.1600-c.1750 JS Bach, Vivaldi
57 Classical c.1750-c.1830 Joseph Haydn, Wolfgan Amadeus Mozart
58 Earley Romantic c.1830-c.1860 Chopin, Mendelssohn, Schumann, Liszt
59 Late Romantic c.1860-c.1920 Wagner,Verdi
60 =============== ============= ====================================
61
62 {% endif %}
63
64 Let's get a list of dictionary out from the xls file:
65
66 .. code-block:: python
67
68 >>> records = p.get_records(file_name="your_file.xls")
69
70 And let's check what do we have:
71
72 .. code-block:: python
73
74 >>> for row in records:
75 ... print(f"{row['Representative Composers']} are from {row['Name']} period ({row['Period']})")
76 Machaut, Landini are from Medieval period (c.1150-c.1400)
77 Gibbons, Frescobaldi are from Renaissance period (c.1400-c.1600)
78 JS Bach, Vivaldi are from Baroque period (c.1600-c.1750)
79 Joseph Haydn, Wolfgan Amadeus Mozart are from Classical period (c.1750-c.1830)
80 Chopin, Mendelssohn, Schumann, Liszt are from Earley Romantic period (c.1830-c.1860)
81 Wagner,Verdi are from Late Romantic period (c.1860-c.1920)
82
83
84 Get two dimensional array
85 ********************************************************************************
86
87 Instead, what if you have to use `pyexcel.get_array` to do the same:
88
89 .. code-block:: python
90
91 >>> for row in p.get_array(file_name="your_file.xls", start_row=1):
92 ... print(f"{row[2]} are from {row[0]} period ({row[1]})")
93 Machaut, Landini are from Medieval period (c.1150-c.1400)
94 Gibbons, Frescobaldi are from Renaissance period (c.1400-c.1600)
95 JS Bach, Vivaldi are from Baroque period (c.1600-c.1750)
96 Joseph Haydn, Wolfgan Amadeus Mozart are from Classical period (c.1750-c.1830)
97 Chopin, Mendelssohn, Schumann, Liszt are from Earley Romantic period (c.1830-c.1860)
98 Wagner,Verdi are from Late Romantic period (c.1860-c.1920)
99
100
101 where `start_row` skips the header row.
102
103
104 Get a dictionary
105 ********************************************************************************
106
107 You can get a dictionary too:
108
109 Now let's get a dictionary out from the spreadsheet:
110
111 .. code-block:: python
112
113 >>> my_dict = p.get_dict(file_name="your_file.xls", name_columns_by_row=0)
114
115 And check what do we have:
116
117 .. code-block:: python
118
119 >>> from pyexcel._compact import OrderedDict
120 >>> isinstance(my_dict, OrderedDict)
121 True
122 >>> for key, values in my_dict.items():
123 ... print(key + " : " + ','.join([str(item) for item in values]))
124 Name : Medieval,Renaissance,Baroque,Classical,Earley Romantic,Late Romantic
125 Period : c.1150-c.1400,c.1400-c.1600,c.1600-c.1750,c.1750-c.1830,c.1830-c.1860,c.1860-c.1920
126 Representative Composers : Machaut, Landini,Gibbons, Frescobaldi,JS Bach, Vivaldi,Joseph Haydn, Wolfgan Amadeus Mozart,Chopin, Mendelssohn, Schumann, Liszt,Wagner,Verdi
127
128
129 Please note that my_dict is an OrderedDict.
130
131 Get a dictionary of two dimensional array
132 ********************************************************************************
133
134 {% if sphinx %}
135 .. testcode::
136 :hide:
137
138 >>> a_dictionary_of_two_dimensional_arrays = {
139 ... 'Sheet 1':
140 ... [
141 ... [1.0, 2.0, 3.0],
142 ... [4.0, 5.0, 6.0],
143 ... [7.0, 8.0, 9.0]
144 ... ],
145 ... 'Sheet 2':
146 ... [
147 ... ['X', 'Y', 'Z'],
148 ... [1.0, 2.0, 3.0],
149 ... [4.0, 5.0, 6.0]
150 ... ],
151 ... 'Sheet 3':
152 ... [
153 ... ['O', 'P', 'Q'],
154 ... [3.0, 2.0, 1.0],
155 ... [4.0, 3.0, 2.0]
156 ... ]
157 ... }
158 >>> data = OrderedDict()
159 >>> data.update({"Sheet 1": a_dictionary_of_two_dimensional_arrays['Sheet 1']})
160 >>> data.update({"Sheet 2": a_dictionary_of_two_dimensional_arrays['Sheet 2']})
161 >>> data.update({"Sheet 3": a_dictionary_of_two_dimensional_arrays['Sheet 3']})
162 >>> p.save_book_as(bookdict=data, dest_file_name="book.xls")
163
164 {% endif %}
165
166 Suppose you have a multiple sheet book as the following:
167
168 {% if sphinx %}
169
170 .. pyexcel-table::
171
172 ---pyexcel:Sheet 1---
173 1,2,3
174 4,5,6
175 7,8,9
176 ---pyexcel---
177 ---pyexcel:Sheet 2---
178 X,Y,Z
179 1,2,3
180 4,5,6
181 ---pyexcel---
182 ---pyexcel:Sheet 3---
183 O,P,Q
184 3,2,1
185 4,3,2
186
187 {% else %}
188
189 pyexcel:Sheet 1:
190
191 ===================== = =
192 1 2 3
193 4 5 6
194 7 8 9
195 ===================== = =
196
197 pyexcel:Sheet 2:
198
199 ===================== = =
200 X Y Z
201 1 2 3
202 4 5 6
203 ===================== = =
204
205 pyexcel:Sheet 3:
206
207 ===================== = =
208 O P Q
209 3 2 1
210 4 3 2
211 ===================== = =
212
213 {% endif %}
214
215 Here is the code to obtain those sheets as a single dictionary:
216
217 .. code-block:: python
218
219 >>> book_dict = p.get_book_dict(file_name="book.xls")
220
221 And check:
222
223 .. code-block:: python
224
225 >>> isinstance(book_dict, OrderedDict)
226 True
227 >>> import json
228 >>> for key, item in book_dict.items():
229 ... print(json.dumps({key: item}))
230 {"Sheet 1": [[1, 2, 3], [4, 5, 6], [7, 8, 9]]}
231 {"Sheet 2": [["X", "Y", "Z"], [1, 2, 3], [4, 5, 6]]}
232 {"Sheet 3": [["O", "P", "Q"], [3, 2, 1], [4, 3, 2]]}
233
234 {% if sphinx %}
235
236 .. testcode::
237 :hide:
238
239 >>> import os
240 >>> os.unlink("book.xls")
241
242 {% endif %}
243
244 Write data
245 ---------------------------------------------
246
247 Export an array
248 **********************
249
250 Suppose you have the following array:
251
252 .. code-block:: python
253
254 >>> data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
255
256 And here is the code to save it as an excel file :
257
258 .. code-block:: python
259
260 >>> p.save_as(array=data, dest_file_name="example.xls")
261
262 Let's verify it:
263
264 .. code-block:: python
265
266 >>> p.get_sheet(file_name="example.xls")
267 pyexcel_sheet1:
268 +---+---+---+
269 | 1 | 2 | 3 |
270 +---+---+---+
271 | 4 | 5 | 6 |
272 +---+---+---+
273 | 7 | 8 | 9 |
274 +---+---+---+
275
276 {% if sphinx %}
277 .. testcode::
278 :hide:
279
280 >>> import os
281 >>> os.unlink("example.xls")
282 {% endif %}
283
284 And here is the code to save it as a csv file :
285
286 .. code-block:: python
287
288 >>> p.save_as(array=data,
289 ... dest_file_name="example.csv",
290 ... dest_delimiter=':')
291
292 Let's verify it:
293
294 .. code-block:: python
295
296 >>> with open("example.csv") as f:
297 ... for line in f.readlines():
298 ... print(line.rstrip())
299 ...
300 1:2:3
301 4:5:6
302 7:8:9
303
304 Export a list of dictionaries
305 **********************************
306
307 .. code-block:: python
308
309 >>> records = [
310 ... {"year": 1903, "country": "Germany", "speed": "206.7km/h"},
311 ... {"year": 1964, "country": "Japan", "speed": "210km/h"},
312 ... {"year": 2008, "country": "China", "speed": "350km/h"}
313 ... ]
314 >>> p.save_as(records=records, dest_file_name='high_speed_rail.xls')
315
316
317 Export a dictionary of single key value pair
318 ********************************************************************************
319
320 .. code-block:: python
321
322 >>> henley_on_thames_facts = {
323 ... "area": "5.58 square meters",
324 ... "population": "11,619",
325 ... "civial parish": "Henley-on-Thames",
326 ... "latitude": "51.536",
327 ... "longitude": "-0.898"
328 ... }
329 >>> p.save_as(adict=henley_on_thames_facts, dest_file_name='henley.xlsx')
330
331
332 Export a dictionary of single dimensonal array
333 ********************************************************************************
334
335 .. code-block:: python
336
337 >>> ccs_insights = {
338 ... "year": ["2017", "2018", "2019", "2020", "2021"],
339 ... "smart phones": [1.53, 1.64, 1.74, 1.82, 1.90],
340 ... "feature phones": [0.46, 0.38, 0.30, 0.23, 0.17]
341 ... }
342 >>> p.save_as(adict=ccs_insights, dest_file_name='ccs.csv')
343
344
345 Export a dictionary of two dimensional array as a book
346 ********************************************************************************
347
348 Suppose you want to save the below dictionary to an excel file :
349
350 .. code-block:: python
351
352 >>> a_dictionary_of_two_dimensional_arrays = {
353 ... 'Sheet 1':
354 ... [
355 ... [1.0, 2.0, 3.0],
356 ... [4.0, 5.0, 6.0],
357 ... [7.0, 8.0, 9.0]
358 ... ],
359 ... 'Sheet 2':
360 ... [
361 ... ['X', 'Y', 'Z'],
362 ... [1.0, 2.0, 3.0],
363 ... [4.0, 5.0, 6.0]
364 ... ],
365 ... 'Sheet 3':
366 ... [
367 ... ['O', 'P', 'Q'],
368 ... [3.0, 2.0, 1.0],
369 ... [4.0, 3.0, 2.0]
370 ... ]
371 ... }
372
373 Here is the code:
374
375 .. code-block:: python
376
377 >>> p.save_book_as(
378 ... bookdict=a_dictionary_of_two_dimensional_arrays,
379 ... dest_file_name="book.xls"
380 ... )
381
382 If you want to preserve the order of sheets in your dictionary, you have to
383 pass on an ordered dictionary to the function itself. For example:
384
385 .. code-block:: python
386
387 >>> data = OrderedDict()
388 >>> data.update({"Sheet 2": a_dictionary_of_two_dimensional_arrays['Sheet 2']})
389 >>> data.update({"Sheet 1": a_dictionary_of_two_dimensional_arrays['Sheet 1']})
390 >>> data.update({"Sheet 3": a_dictionary_of_two_dimensional_arrays['Sheet 3']})
391 >>> p.save_book_as(bookdict=data, dest_file_name="book.xls")
392
393 Let's verify its order:
394
395 .. code-block:: python
396
397 >>> book_dict = p.get_book_dict(file_name="book.xls")
398 >>> for key, item in book_dict.items():
399 ... print(json.dumps({key: item}))
400 {"Sheet 2": [["X", "Y", "Z"], [1, 2, 3], [4, 5, 6]]}
401 {"Sheet 1": [[1, 2, 3], [4, 5, 6], [7, 8, 9]]}
402 {"Sheet 3": [["O", "P", "Q"], [3, 2, 1], [4, 3, 2]]}
403
404 Please notice that "Sheet 2" is the first item in the *book_dict*, meaning the order of sheets are preserved.
405
406
407 Transcoding
408 -------------------------------------------
409
410 .. note::
411
412 Please note that `pyexcel-cli` can perform file transcoding at command line.
413 No need to open your editor, save the problem, then python run.
414
415 {% if sphinx %}
416 .. testcode::
417 :hide:
418
419 >>> import datetime
420 >>> data = [
421 ... ["name", "weight", "birth"],
422 ... ["Adam", 3.4, datetime.date(2015, 2, 3)],
423 ... ["Smith", 4.2, datetime.date(2014, 11, 12)]
424 ... ]
425 >>> p.save_as(array=data, dest_file_name="birth.xls")
426
427 {% endif %}
428
429 The following code does a simple file format transcoding from xls to csv:
430
431 .. code-block:: python
432
433 >>> p.save_as(file_name="birth.xls", dest_file_name="birth.csv")
434
435 Again it is really simple. Let's verify what we have gotten:
436
437 .. code-block:: python
438
439 >>> sheet = p.get_sheet(file_name="birth.csv")
440 >>> sheet
441 birth.csv:
442 +-------+--------+----------+
443 | name | weight | birth |
444 +-------+--------+----------+
445 | Adam | 3.4 | 03/02/15 |
446 +-------+--------+----------+
447 | Smith | 4.2 | 12/11/14 |
448 +-------+--------+----------+
449
450 .. NOTE::
451
452 Please note that csv(comma separate value) file is pure text file. Formula, charts, images and formatting in xls file will disappear no matter which transcoding tool you use. Hence, pyexcel is a quick alternative for this transcoding job.
453
454
455 Let use previous example and save it as xlsx instead
456
457 .. code-block:: python
458
459 >>> p.save_as(file_name="birth.xls",
460 ... dest_file_name="birth.xlsx") # change the file extension
461
462 Again let's verify what we have gotten:
463
464 .. code-block:: python
465
466 >>> sheet = p.get_sheet(file_name="birth.xlsx")
467 >>> sheet
468 pyexcel_sheet1:
469 +-------+--------+----------+
470 | name | weight | birth |
471 +-------+--------+----------+
472 | Adam | 3.4 | 03/02/15 |
473 +-------+--------+----------+
474 | Smith | 4.2 | 12/11/14 |
475 +-------+--------+----------+
476
477
478 Excel book merge and split operation in one line
479 --------------------------------------------------------------------------------
480
481 Merge all excel files in directory into a book where each file become a sheet
482 ********************************************************************************
483
484 The following code will merge every excel files into one file, say "output.xls":
485
486 .. code-block:: python
487
488 from pyexcel.cookbook import merge_all_to_a_book
489 import glob
490
491
492 merge_all_to_a_book(glob.glob("your_csv_directory\*.csv"), "output.xls")
493
494 You can mix and match with other excel formats: xls, xlsm and ods. For example, if you are sure you have only xls, xlsm, xlsx, ods and csv files in `your_excel_file_directory`, you can do the following:
495
496 .. code-block:: python
497
498 from pyexcel.cookbook import merge_all_to_a_book
499 import glob
500
501
502 merge_all_to_a_book(glob.glob("your_excel_file_directory\*.*"), "output.xls")
503
504 Split a book into single sheet files
505 ****************************************
506
507 {% if sphinx %}
508 .. testcode::
509 :hide:
510
511 >>> content = {
512 ... 'Sheet 1':
513 ... [
514 ... [1.0, 2.0, 3.0],
515 ... [4.0, 5.0, 6.0],
516 ... [7.0, 8.0, 9.0]
517 ... ],
518 ... 'Sheet 2':
519 ... [
520 ... ['X', 'Y', 'Z'],
521 ... [1.0, 2.0, 3.0],
522 ... [4.0, 5.0, 6.0]
523 ... ],
524 ... 'Sheet 3':
525 ... [
526 ... ['O', 'P', 'Q'],
527 ... [3.0, 2.0, 1.0],
528 ... [4.0, 3.0, 2.0]
529 ... ]
530 ... }
531 >>> book = p.Book(content)
532 >>> book.save_as("megabook.xls")
533
534 {% endif %}
535
536 Suppose you have many sheets in a work book and you would like to separate each into a single sheet excel file. You can easily do this:
537
538 .. code-block:: python
539
540 >>> from pyexcel.cookbook import split_a_book
541 >>> split_a_book("megabook.xls", "output.xls")
542 >>> import glob
543 >>> outputfiles = glob.glob("*_output.xls")
544 >>> for file in sorted(outputfiles):
545 ... print(file)
546 ...
547 Sheet 1_output.xls
548 Sheet 2_output.xls
549 Sheet 3_output.xls
550
551 for the output file, you can specify any of the supported formats
552
553 {% if sphinx %}
554 .. testcode::
555 :hide:
556
557 >>> os.unlink("Sheet 1_output.xls")
558 >>> os.unlink("Sheet 2_output.xls")
559 >>> os.unlink("Sheet 3_output.xls")
560 {% endif %}
561
562 Extract just one sheet from a book
563 *************************************
564
565
566 Suppose you just want to extract one sheet from many sheets that exists in a work book and you would like to separate it into a single sheet excel file. You can easily do this:
567
568 .. code-block:: python
569
570 >>> from pyexcel.cookbook import extract_a_sheet_from_a_book
571 >>> extract_a_sheet_from_a_book("megabook.xls", "Sheet 1", "output.xls")
572 >>> if os.path.exists("Sheet 1_output.xls"):
573 ... print("Sheet 1_output.xls exists")
574 ...
575 Sheet 1_output.xls exists
576
577 for the output file, you can specify any of the supported formats
578
579 {% if sphinx %}
580 .. testcode::
581 :hide:
582
583 >>> os.unlink("Sheet 1_output.xls")
584 >>> os.unlink("megabook.xls")
585 >>> os.unlink('birth.xls')
586 >>> os.unlink('birth.csv')
587 >>> os.unlink('birth.xlsx')
588 >>> os.unlink('high_speed_rail.xls')
589 >>> os.unlink('henley.xlsx')
590 >>> os.unlink('ccs.csv')
591 >>> os.unlink("book.xls")
592 >>> os.unlink("your_file.xls")
593 >>> os.unlink("example.csv")
594 {% endif %}
0
1 When you are dealing with huge amount of data, e.g. 64GB, obviously you would not
2 like to fill up your memory with those data. What you may want to do is, record
3 data from Nth line, take M records and stop. And you only want to use your memory
4 for the M records, not for beginning part nor for the tail part.
5
6 Hence partial read feature is developed to read partial data into memory for
7 processing.
8
9 You can paginate by row, by column and by both, hence you dictate what portion of the
10 data to read back. But remember only row limit features help you save memory. Let's
11 you use this feature to record data from Nth column, take M number of columns and skip
12 the rest. You are not going to reduce your memory footprint.
13
14 Why did not I see above benefit?
15 --------------------------------------------------------------------------------
16
17 This feature depends heavily on the implementation details.
18
19 `pyexcel-xls`_ (xlrd), `pyexcel-xlsx`_ (openpyxl), `pyexcel-ods`_ (odfpy) and
20 `pyexcel-ods3`_ (pyexcel-ezodf) will read all data into memory. Because xls,
21 xlsx and ods file are effective a zipped folder, all four will unzip the folder
22 and read the content in xml format in **full**, so as to make sense of all details.
23
24 Hence, during the partial data is been returned, the memory consumption won't
25 differ from reading the whole data back. Only after the partial
26 data is returned, the memory comsumption curve shall jump the cliff. So pagination
27 code here only limits the data returned to your program.
28
29 With that said, `pyexcel-xlsxr`_, `pyexcel-odsr`_ and `pyexcel-htmlr`_ DOES read
30 partial data into memory. Those three are implemented in such a way that they
31 consume the xml(html) when needed. When they have read designated portion of the
32 data, they stop, even if they are half way through.
33
34 In addition, pyexcel's csv readers can read partial data into memory too.
35
36 {% if sphinx %}
37
38 .. testcode::
39 :hide:
40
41 >>> import sys
42 >>> if sys.version_info[0] < 3:
43 ... from StringIO import StringIO
44 ... else:
45 ... from io import StringIO
46 >>> from pyexcel_io._compact import OrderedDict
47
48 {% endif %}
49
50 Let's assume the following file is a huge csv file:
51
52 .. code-block:: python
53
54 >>> import datetime
55 >>> import pyexcel as pe
56 >>> data = [
57 ... [1, 21, 31],
58 ... [2, 22, 32],
59 ... [3, 23, 33],
60 ... [4, 24, 34],
61 ... [5, 25, 35],
62 ... [6, 26, 36]
63 ... ]
64 >>> pe.save_as(array=data, dest_file_name="your_file.csv")
65
66
67 And let's pretend to read partial data:
68
69
70 .. code-block:: python
71
72 >>> pe.get_sheet(file_name="your_file.csv", start_row=2, row_limit=3)
73 your_file.csv:
74 +---+----+----+
75 | 3 | 23 | 33 |
76 +---+----+----+
77 | 4 | 24 | 34 |
78 +---+----+----+
79 | 5 | 25 | 35 |
80 +---+----+----+
81
82 And you could as well do the same for columns:
83
84 .. code-block:: python
85
86 >>> pe.get_sheet(file_name="your_file.csv", start_column=1, column_limit=2)
87 your_file.csv:
88 +----+----+
89 | 21 | 31 |
90 +----+----+
91 | 22 | 32 |
92 +----+----+
93 | 23 | 33 |
94 +----+----+
95 | 24 | 34 |
96 +----+----+
97 | 25 | 35 |
98 +----+----+
99 | 26 | 36 |
100 +----+----+
101
102 Obvious, you could do both at the same time:
103
104 .. code-block:: python
105
106 >>> pe.get_sheet(file_name="your_file.csv",
107 ... start_row=2, row_limit=3,
108 ... start_column=1, column_limit=2)
109 your_file.csv:
110 +----+----+
111 | 23 | 33 |
112 +----+----+
113 | 24 | 34 |
114 +----+----+
115 | 25 | 35 |
116 +----+----+
117
118
119 The pagination support is available across all pyexcel plugins.
120
121 .. note::
122
123 No column pagination support for query sets as data source.
124
125
126 Formatting while transcoding a big data file
127 --------------------------------------------------------------------------------
128
129 If you are transcoding a big data set, conventional formatting method would not
130 help unless a on-demand free RAM is available. However, there is a way to minimize
131 the memory footprint of pyexcel while the formatting is performed.
132
133 Let's continue from previous example. Suppose we want to transcode "your_file.csv"
134 to "your_file.xls" but increase each element by 1.
135
136 What we can do is to define a row renderer function as the following:
137
138 .. code-block:: python
139
140 >>> def increment_by_one(row):
141 ... for element in row:
142 ... yield element + 1
143
144 Then pass it onto save_as function using row_renderer:
145
146 .. code-block:: python
147
148 >>> pe.isave_as(file_name="your_file.csv",
149 ... row_renderer=increment_by_one,
150 ... dest_file_name="your_file.xlsx")
151
152
153 .. note::
154
155 If the data content is from a generator, isave_as has to be used.
156
157 We can verify if it was done correctly:
158
159 .. code-block:: python
160
161 >>> pe.get_sheet(file_name="your_file.xlsx")
162 your_file.csv:
163 +---+----+----+
164 | 2 | 22 | 32 |
165 +---+----+----+
166 | 3 | 23 | 33 |
167 +---+----+----+
168 | 4 | 24 | 34 |
169 +---+----+----+
170 | 5 | 25 | 35 |
171 +---+----+----+
172 | 6 | 26 | 36 |
173 +---+----+----+
174 | 7 | 27 | 37 |
175 +---+----+----+
176
177 {% if sphinx %}
178
179 .. testcode::
180 :hide:
181
182 >>> import os
183 >>> os.unlink("your_file.csv")
184 >>> os.unlink("your_file.xlsx")
185
186 {% endif %}
0 {%extends "BASIC-README.rst.jj2"%}
1
2 {%block features%}
3
4 Feature Highlights
5 ===================
6
7 {% include "supported-file-list.rst.jj2" %}
8
9 .. image:: https://github.com/pyexcel/pyexcel/raw/dev/docs/source/_static/images/architecture.svg
10
11
12 1. One application programming interface(API) to handle multiple data sources:
13
14 * physical file
15 * memory file
16 * SQLAlchemy table
17 * Django Model
18 * Python data structures: dictionary, records and array
19
20 2. One API to read and write data in various excel file formats.
21 3. For large data sets, data streaming are supported. A genenerator can be returned to you. Checkout iget_records, iget_array, isave_as and isave_book_as.
22
23 {% endblock %}
24
25 {%block usage%}
26
27 {%include "one-liners.rst.jj2" %}
28
29 Hidden feature: partial read
30 ===============================================
31
32 Most pyexcel users do not know, but other library users were requesting `partial read <https://github.com/jazzband/tablib/issues/467>`_
33
34 {%include "partial-data.rst.jj2" %}
35
36 {%include "two-liners.rst.jj2" %}
37
38 Available Plugins
39 =================
40
41 {% include "plugins-list.rst.jj2" %}
42
43
44 Acknowledgement
45 ===============
46
47 All great work have been done by odf, ezodf, xlrd, xlwt, tabulate and other
48 individual developers. This library unites only the data access code.
49
50
51 {%endblock%}
52
53 {%block development_guide%}
54 {%endblock%}
0 {% extends "travis.yml.jj2" %}
1 {%block extra_matrix %}
2 matrix:
3 include:
4 - python: 3.6
5 env: MINREQ=1
6 {%endblock%}
7 {%block custom_python_versions%}
8 python:
9 - 3.8
10 - 3.7
11 - 3.6
12 {%endblock%}
13 {%block pypi_deployment%}
14 {%endblock %}
+0
-16
.moban.d/setup.py less more
0 {% extends 'setup.py.jj2' %}
1
2 {% block additional_keywords %}
3 'tsv',
4 'tsvz'
5 'csv',
6 'csvz',
7 'xls',
8 'xlsx',
9 'ods'
10 {% endblock %}
11
12 {% block additional_classifiers %}
13 'Development Status :: 3 - Alpha',
14 'Programming Language :: Python :: Implementation :: PyPy'
15 {% endblock %}}
0 .. table:: A list of supported file formats
1
2 ============ =======================================================
3 file format definition
4 ============ =======================================================
5 csv comma separated values
6 tsv tab separated values
7 csvz a zip file that contains one or many csv files
8 tsvz a zip file that contains one or many tsv files
9 xls a spreadsheet file format created by
10 MS-Excel 97-2003
11 xlsx MS-Excel Extensions to the Office Open XML
12 SpreadsheetML File Format.
13 xlsm an MS-Excel Macro-Enabled Workbook file
14 ods open document spreadsheet
15 fods flat open document spreadsheet
16 json java script object notation
17 html html table of the data structure
18 simple simple presentation
19 rst rStructured Text presentation of the data
20 mediawiki media wiki table
21 ============ =======================================================
22
+0
-8
.moban.d/test.bat less more
0 {% extends "test.script.jj2" %}
1
2 {%block flake8_options%}
3 --builtins=unicode,xrange,long
4 {%endblock%}
5
6
7
+0
-9
.moban.d/test.sh less more
0 {% extends "test.script.jj2" %}
1
2 {%block pretest %}
3 #/bin/bash
4 {%endblock %}
5
6 {%block flake8_options%}
7 --builtins=unicode,xrange,long
8 {%endblock%}
0 {% extends 'tests/requirements.txt.jj2' %}
1 {%block extras %}
2 SQLAlchemy
3 flask
4 lxml==4.0.0;platform_python_implementation=="PyPy"
5 pyexcel-xlsx>=0.4.1
6 pyexcel-xls>=0.4.1
7 pyexcel-text>=0.2.0
8 psutil
9 mock
10 moban
11 black;python_version>="3.6"
12 isort;python_version>="3.6"
13 {%endblock%}
+0
-14
.moban.d/tests/requirements.txt less more
0 {% extends 'tests/requirements.txt.jj2' %}
1 {%block extras %}
2 SQLAlchemy
3 flask
4 lxml==4.0.0;platform_python_implementation=="PyPy"
5 pyexcel-xlsx>=0.4.1
6 pyexcel-xls>=0.4.1
7 pyexcel-text>=0.2.0
8 psutil
9 mock
10 moban
11 black;python_version>="3.6"
12 isort;python_version>="3.6"
13 {%endblock%}
+0
-13
.moban.d/travis.yml less more
0 {% extends "travis.yml.jj2" %}
1 {%block extra_matrix %}
2 matrix:
3 include:
4 - python: 2.7
5 env: MINREQ=1
6 {%endblock%}
7 {%block custom_install%}
8 - if [[ $TRAVIS_PYTHON_VERSION == "pypy" ]]; then rm tests/test_examples.py; fi
9 {%endblock%}
10
11 {%block pypi_deployment%}
12 {%endblock %}
0 Stream APIs for big file : A set of two liners
1 ================================================================================
2
3 When you are dealing with **BIG** excel files, you will want **pyexcel** to use
4 constant memory.
5
6 This section shows you how to get data from your **BIG** excel files and how to
7 export data to excel files in **two lines** at most, without eating all
8 your computer memory.
9
10
11 Two liners for get data from big excel files
12 --------------------------------------------------------------------------------
13
14 Get a list of dictionaries
15 ********************************************************************************
16
17 {% if sphinx %}
18 .. testcode::
19 :hide:
20
21 >>> import os
22 >>> import pyexcel as p
23 >>> content="""
24 ... Coffees,Serving Size,Caffeine (mg)
25 ... Starbucks Coffee Blonde Roast,venti(20 oz),475
26 ... Dunkin' Donuts Coffee with Turbo Shot,large(20 oz.),398
27 ... Starbucks Coffee Pike Place Roast,grande(16 oz.),310
28 ... Panera Coffee Light Roast,regular(16 oz.),300
29 ... """.strip()
30 >>> sheet = p.get_sheet(file_content=content, file_type='csv')
31 >>> sheet.save_as("your_file.xls")
32
33 {% endif %}
34
35
36 {% if sphinx %}
37 Suppose you want to process the following coffee data:
38
39 .. pyexcel-table::
40
41 ---pyexcel:Huge list of coffeine drinks---
42 Coffees,Serving Size,Caffeine (mg)
43 Starbucks Coffee Blonde Roast,venti(20 oz),475
44 Dunkin' Donuts Coffee with Turbo Shot,large(20 oz.),398
45 Starbucks Coffee Pike Place Roast,grande(16 oz.),310
46 Panera Coffee Light Roast,regular(16 oz.),300
47 {% else %}
48 Suppose you want to process the following coffee data again:
49
50 Top 5 coffeine drinks:
51
52 ===================================== =============== =============
53 Coffees Serving Size Caffeine (mg)
54 Starbucks Coffee Blonde Roast venti(20 oz) 475
55 Dunkin' Donuts Coffee with Turbo Shot large(20 oz.) 398
56 Starbucks Coffee Pike Place Roast grande(16 oz.) 310
57 Panera Coffee Light Roast regular(16 oz.) 300
58 ===================================== =============== =============
59
60 {% endif %}
61
62 Let's get a list of dictionary out from the xls file:
63
64 .. code-block:: python
65
66 >>> records = p.iget_records(file_name="your_file.xls")
67
68 And let's check what do we have:
69
70 .. code-block:: python
71
72 >>> for r in records:
73 ... print(f"{r['Serving Size']} of {r['Coffees']} has {r['Caffeine (mg)']} mg")
74 venti(20 oz) of Starbucks Coffee Blonde Roast has 475 mg
75 large(20 oz.) of Dunkin' Donuts Coffee with Turbo Shot has 398 mg
76 grande(16 oz.) of Starbucks Coffee Pike Place Roast has 310 mg
77 regular(16 oz.) of Panera Coffee Light Roast has 300 mg
78
79 Please do not forgot the second line to close the opened file handle:
80
81 .. code-block:: python
82
83 >>> p.free_resources()
84
85 Get two dimensional array
86 ********************************************************************************
87
88 Instead, what if you have to use `pyexcel.get_array` to do the same:
89
90 .. code-block:: python
91
92 >>> for row in p.iget_array(file_name="your_file.xls", start_row=1):
93 ... print(f"{row[1]} of {row[0]} has {row[2]} mg")
94 venti(20 oz) of Starbucks Coffee Blonde Roast has 475 mg
95 large(20 oz.) of Dunkin' Donuts Coffee with Turbo Shot has 398 mg
96 grande(16 oz.) of Starbucks Coffee Pike Place Roast has 310 mg
97 regular(16 oz.) of Panera Coffee Light Roast has 300 mg
98
99 Again, do not forgot the second line:
100
101 .. code-block:: python
102
103 >>> p.free_resources()
104
105 where `start_row` skips the header row.
106
107 Data export in one liners
108 ---------------------------------------------
109
110 Export an array
111 **********************
112
113 Suppose you have the following array:
114
115 .. code-block:: python
116
117 >>> data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
118
119 And here is the code to save it as an excel file :
120
121 .. code-block:: python
122
123 >>> p.isave_as(array=data, dest_file_name="example.xls")
124
125 But the following line is not required because the data source
126 are not file sources:
127
128 .. code-block:: python
129
130 >>> # p.free_resources()
131
132 Let's verify it:
133
134 .. code-block:: python
135
136 >>> p.get_sheet(file_name="example.xls")
137 pyexcel_sheet1:
138 +---+---+---+
139 | 1 | 2 | 3 |
140 +---+---+---+
141 | 4 | 5 | 6 |
142 +---+---+---+
143 | 7 | 8 | 9 |
144 +---+---+---+
145
146 {% if sphinx %}
147
148 .. testcode::
149 :hide:
150
151 >>> import os
152 >>> os.unlink("example.xls")
153
154 {% endif %}
155
156 And here is the code to save it as a csv file :
157
158 .. code-block:: python
159
160 >>> p.isave_as(array=data,
161 ... dest_file_name="example.csv",
162 ... dest_delimiter=':')
163
164 Let's verify it:
165
166 .. code-block:: python
167
168 >>> with open("example.csv") as f:
169 ... for line in f.readlines():
170 ... print(line.rstrip())
171 ...
172 1:2:3
173 4:5:6
174 7:8:9
175
176 Export a list of dictionaries
177 **********************************
178
179 .. code-block:: python
180
181 >>> records = [
182 ... {"year": 1903, "country": "Germany", "speed": "206.7km/h"},
183 ... {"year": 1964, "country": "Japan", "speed": "210km/h"},
184 ... {"year": 2008, "country": "China", "speed": "350km/h"}
185 ... ]
186 >>> p.isave_as(records=records, dest_file_name='high_speed_rail.xls')
187
188 Export a dictionary of single key value pair
189 ********************************************************************************
190
191 .. code-block:: python
192
193 >>> henley_on_thames_facts = {
194 ... "area": "5.58 square meters",
195 ... "population": "11,619",
196 ... "civial parish": "Henley-on-Thames",
197 ... "latitude": "51.536",
198 ... "longitude": "-0.898"
199 ... }
200 >>> p.isave_as(adict=henley_on_thames_facts, dest_file_name='henley.xlsx')
201
202 Export a dictionary of single dimensonal array
203 ********************************************************************************
204
205 .. code-block:: python
206
207 >>> ccs_insights = {
208 ... "year": ["2017", "2018", "2019", "2020", "2021"],
209 ... "smart phones": [1.53, 1.64, 1.74, 1.82, 1.90],
210 ... "feature phones": [0.46, 0.38, 0.30, 0.23, 0.17]
211 ... }
212 >>> p.isave_as(adict=ccs_insights, dest_file_name='ccs.csv')
213 >>> p.free_resources()
214
215 Export a dictionary of two dimensional array as a book
216 ********************************************************************************
217
218 Suppose you want to save the below dictionary to an excel file :
219
220 .. code-block:: python
221
222 >>> a_dictionary_of_two_dimensional_arrays = {
223 ... 'Sheet 1':
224 ... [
225 ... [1.0, 2.0, 3.0],
226 ... [4.0, 5.0, 6.0],
227 ... [7.0, 8.0, 9.0]
228 ... ],
229 ... 'Sheet 2':
230 ... [
231 ... ['X', 'Y', 'Z'],
232 ... [1.0, 2.0, 3.0],
233 ... [4.0, 5.0, 6.0]
234 ... ],
235 ... 'Sheet 3':
236 ... [
237 ... ['O', 'P', 'Q'],
238 ... [3.0, 2.0, 1.0],
239 ... [4.0, 3.0, 2.0]
240 ... ]
241 ... }
242
243 Here is the code:
244
245 .. code-block:: python
246
247 >>> p.isave_book_as(
248 ... bookdict=a_dictionary_of_two_dimensional_arrays,
249 ... dest_file_name="book.xls"
250 ... )
251
252 If you want to preserve the order of sheets in your dictionary, you have to
253 pass on an ordered dictionary to the function itself. For example:
254
255 .. code-block:: python
256
257 >>> from pyexcel._compact import OrderedDict
258 >>> data = OrderedDict()
259 >>> data.update({"Sheet 2": a_dictionary_of_two_dimensional_arrays['Sheet 2']})
260 >>> data.update({"Sheet 1": a_dictionary_of_two_dimensional_arrays['Sheet 1']})
261 >>> data.update({"Sheet 3": a_dictionary_of_two_dimensional_arrays['Sheet 3']})
262 >>> p.isave_book_as(bookdict=data, dest_file_name="book.xls")
263 >>> p.free_resources()
264
265 Let's verify its order:
266
267 .. code-block:: python
268
269 >>> import json
270 >>> book_dict = p.get_book_dict(file_name="book.xls")
271 >>> for key, item in book_dict.items():
272 ... print(json.dumps({key: item}))
273 {"Sheet 2": [["X", "Y", "Z"], [1, 2, 3], [4, 5, 6]]}
274 {"Sheet 1": [[1, 2, 3], [4, 5, 6], [7, 8, 9]]}
275 {"Sheet 3": [["O", "P", "Q"], [3, 2, 1], [4, 3, 2]]}
276
277 Please notice that "Sheet 2" is the first item in the *book_dict*, meaning the order of sheets are preserved.
278
279
280 File format transcoding on one line
281 -------------------------------------------
282
283 .. note::
284
285 Please note that the following file transcoding could be with zero line. Please
286 install pyexcel-cli and you will do the transcode in one command. No need to
287 open your editor, save the problem, then python run.
288
289 {% if sphinx %}
290
291 .. testcode::
292 :hide:
293
294 >>> import datetime
295 >>> data = [
296 ... ["name", "weight", "birth"],
297 ... ["Adam", 3.4, datetime.date(2015, 2, 3)],
298 ... ["Smith", 4.2, datetime.date(2014, 11, 12)]
299 ... ]
300 >>> p.isave_as(array=data, dest_file_name="birth.xls")
301
302 {% endif %}
303
304 The following code does a simple file format transcoding from xls to csv:
305
306 .. code-block:: python
307
308 >>> import pyexcel
309 >>> p.save_as(file_name="birth.xls", dest_file_name="birth.csv")
310
311 Again it is really simple. Let's verify what we have gotten:
312
313 .. code-block:: python
314
315 >>> sheet = p.get_sheet(file_name="birth.csv")
316 >>> sheet
317 birth.csv:
318 +-------+--------+----------+
319 | name | weight | birth |
320 +-------+--------+----------+
321 | Adam | 3.4 | 03/02/15 |
322 +-------+--------+----------+
323 | Smith | 4.2 | 12/11/14 |
324 +-------+--------+----------+
325
326 .. note::
327
328 Please note that csv(comma separate value) file is pure text file. Formula, charts, images and formatting in xls file will disappear no matter which transcoding tool you use. Hence, pyexcel is a quick alternative for this transcoding job.
329
330
331 Let use previous example and save it as xlsx instead
332
333 .. code-block:: python
334
335 >>> import pyexcel
336 >>> p.isave_as(file_name="birth.xls",
337 ... dest_file_name="birth.xlsx") # change the file extension
338
339 Again let's verify what we have gotten:
340
341 .. code-block:: python
342
343 >>> sheet = p.get_sheet(file_name="birth.xlsx")
344 >>> sheet
345 pyexcel_sheet1:
346 +-------+--------+----------+
347 | name | weight | birth |
348 +-------+--------+----------+
349 | Adam | 3.4 | 03/02/15 |
350 +-------+--------+----------+
351 | Smith | 4.2 | 12/11/14 |
352 +-------+--------+----------+
353
354 {% if sphinx %}
355 .. testcode::
356 :hide:
357
358 >>> import os
359 >>> os.unlink('ccs.csv')
360 >>> os.unlink('book.xls')
361
362 {% endif %}
0 requires:
1 - type: git
2 url: https://github.com/moremoban/pypi-mobans
3 submodule: true
4 - https://github.com/pyexcel/pyexcel-mobans
0 overrides: "git://github.com/pyexcel/pyexcel-mobans!/mobanfile.yaml"
51 configuration:
6 configuration_dir: "pyexcel-mobans:config"
7 template_dir:
8 - "pyexcel-mobans:templates"
9 - "pypi-mobans:templates"
10 - ".moban.d"
112 configuration: pyexcel.yml
123 targets:
13 - setup.py: setup.py
4 - setup.py: custom_setup.py.jj2
145 - "docs/source/conf.py": "docs/source/custom_conf.py.jj2"
15 - .travis.yml: travis.yml
16 - requirements.txt: requirements.txt.jj2
17 - min_requirements.txt: minimum_requirements.txt
18 - "tests/requirements.txt": "tests/requirements.txt"
19 - LICENSE: NEW_BSD_LICENSE.jj2
6 - "docs/source/quickstart.rst": "docs/source/quickstart.rst.jj2"
7 - "docs/source/two-liners.rst": "docs/source/two-liners.rst.jj2"
8 - "docs/source/bigdata.rst": "docs/source/bigdata.rst.jj2"
209 - MANIFEST.in: CUSTOM_MANIFEST.in.jj2
21 - README.rst: README.rst
22 - "docs/source/guide.rst": "docs/source/guide.rst"
23 - test.sh: test.sh
24 - test.bat: test.bat
10 - README.rst: pyexcel-README.rst.jj2
11 - "docs/source/guide.rst": "docs/source/guide.rst.jj2"
2512 - .gitignore: commons-gitignore.jj2
2613 - "pyexcel/__version__.py": version.txt
2714 - "docs/source/index.rst": "docs/source/pyexcel-index.rst.jj2"
28 - output: CHANGELOG.rst
29 configuration: changelog.yml
30 template: CHANGELOG.rst.jj2
31 - lint.sh: lint.script.jj2
15 - "tests/requirements.txt": "tests/custom_requirements.txt.jj2"
16 - "min_requirements.txt": "minimum_requirements.txt.jj2"
17
0 # .readthedocs.yml
1 # Read the Docs configuration file
2 # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
3
4 # Required
5 version: 2
6
7 # Build documentation in the docs/ directory with Sphinx
8 sphinx:
9 configuration: docs/source/conf.py
10
11 # Optionally build your docs in additional formats such as PDF
12 formats:
13 - pdf
14
15 # Optionally set the version of Python and requirements required to build your docs
16 python:
17 version: 3.7
18 install:
19 - requirements: docs/requirements.txt
+0
-56
.travis.yml less more
0
1 sudo: false
2 dist: xenial
3 language: python
4 notifications:
5 email: false
6 python:
7 - &pypy2 pypy2.7-6.0
8 - &pypy3 pypy3.5-6.0
9 - 3.7
10 - 3.6
11 - 3.5
12 - 2.7
13 matrix:
14 include:
15 - python: 2.7
16 env: MINREQ=1
17
18 stages:
19 - test
20 - lint
21
22 .disable_global: &disable_global
23 before_install: false
24 install: true
25 before_script: false
26 after_success: false
27 after_failure: false
28
29 .lint: &lint
30 <<: *disable_global
31 python: 3.6
32 stage: lint
33 install: pip install flake8
34 script: make lint
35
36 jobs:
37 include:
38 - *lint
39
40 stage: test
41
42 script: make test
43
44 before_install:
45 - if [[ $TRAVIS_PYTHON_VERSION == "pypy" ]]; then rm tests/test_examples.py; fi
46 - if [[ -f min_requirements.txt && "$MINREQ" -eq 1 ]]; then
47 mv min_requirements.txt requirements.txt ;
48 fi
49 - test ! -f rnd_requirements.txt || pip install --no-deps -r rnd_requirements.txt
50 - test ! -f rnd_requirements.txt || pip install -r rnd_requirements.txt ;
51 - pip install -r tests/requirements.txt
52 script:
53 - make test
54 after_success:
55 codecov
00 Change log
11 ================================================================================
22
3 0.7.0 - 12.2.2022
4 --------------------------------------------------------------------------------
5
6 **Fixed**
7
8 #. `#250 <https://github.com/pyexcel/pyexcel/issues/250>`_: RecursionError
9 raised on deepcopy of a sheet
10
11 **Updated**
12
13 #. `#255 <https://github.com/pyexcel/pyexcel/issues/255>`_: pyexcel.get_array
14 documentation page seems to be a copy of pyexcel.get_sheet
15
16 **Removed**
17
18 #. `#249 <https://github.com/pyexcel/pyexcel/issues/249>`_: drop the support for
19 dummy import statements pyexcel.ext.*
20
21 0.6.7 - 12.09.2021
22 --------------------------------------------------------------------------------
23
24 **Updated**
25
26 #. `#243 <https://github.com/pyexcel/pyexcel/issues/243>`_: fix small typo.
27 #. add chardet as explicit dependency
28
29 0.6.6 - 14.11.2020
30 --------------------------------------------------------------------------------
31
32 **Updated**
33
34 #. `#233 <https://github.com/pyexcel/pyexcel/issues/233>`_: dynamically resize
35 the table matrix on set_value. sheet['AA1'] = 'test' will work in this
36 release.
37
38 0.6.5 - 8.10.2020
39 --------------------------------------------------------------------------------
40
41 **Updated**
42
43 #. update queryset source to work with pyexcel-io 0.6.0
44
45 0.6.4 - 18.08.2020
46 --------------------------------------------------------------------------------
47
48 **Updated**
49
50 #. `#219 <https://github.com/pyexcel/pyexcel/issues/219>`_: book created from
51 dict no longer discards order.
52
53 0.6.3 - 01.08.2020
54 --------------------------------------------------------------------------------
55
56 **fixed**
57
58 #. `#214 <https://github.com/pyexcel/pyexcel/issues/214>`_: remove leading and
59 trailing whitespace for column names
60
61 **removed**
62
63 #. python 2 compatibility have been permanently removed.
64
65 0.6.2 - 8.06.2020
66 --------------------------------------------------------------------------------
67
68 **fixed**
69
70 #. `#109 <https://github.com/pyexcel/pyexcel/issues/109>`_: Control the column
71 order when write the data output
72
73 0.6.1 - 02.05.2020
74 --------------------------------------------------------------------------------
75
76 **fixed**
77
78 #. `#203 <https://github.com/pyexcel/pyexcel/issues/203>`_: texttable was
79 dropped out in 0.6.0 as compulsary dependency. end user may experience it
80 when a sheet/table is printed in a shell. otherwise, new user of pyexcel
81 won't see it. As of release date, no issues were created
82
83 0.6.0 - 21.04.2020
84 --------------------------------------------------------------------------------
85
86 **updated**
87
88 #. `#199 <https://github.com/pyexcel/pyexcel/issues/199>`_: += in place; = +
89 shall return new instance
90 #. `#195 <https://github.com/pyexcel/pyexcel/issues/195>`_: documentation
91 update. however small is welcome
92
93 **removed**
94
95 #. Dropping the test support for python version lower than 3.6. v0.6.0 should
96 work with python 2.7 but is not guaranteed to work. Please upgrade to python
97 3.6+.
98
99 0.5.15 - 07.07.2019
100 --------------------------------------------------------------------------------
101
102 **updated**
103
104 #. `#185 <https://github.com/pyexcel/pyexcel/issues/185>`_: fix a bug with http
105 data source. The real fix lies in pyexcel-io v0.5.19. this release just put
106 the version requirement in.
107
3108 0.5.14 - 12.06.2019
4109 --------------------------------------------------------------------------------
5110
6 updated
7 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
111 **updated**
8112
9113 #. `#182 <https://github.com/pyexcel/pyexcel/issues/182>`_: support
10114 dest_force_file_type on save_as and save_book_as
12116 0.5.13 - 12.03.2019
13117 --------------------------------------------------------------------------------
14118
15 updated
16 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
119 **updated**
17120
18121 #. `#176 <https://github.com/pyexcel/pyexcel/issues/176>`_: get_sheet
19122 {IndexError}list index out of range // XLSX can't be opened
21124 0.5.12 - 25.02.2019
22125 --------------------------------------------------------------------------------
23126
24 updated
25 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
127 **updated**
26128
27129 #. `#174 <https://github.com/pyexcel/pyexcel/issues/174>`_: include examples in
28130 tarbar
30132 0.5.11 - 22.02.2019
31133 --------------------------------------------------------------------------------
32134
33 updated
34 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
135 **updated**
35136
36137 #. `#169 <https://github.com/pyexcel/pyexcel/issues/169>`_: remove
37138 pyexcel-handsontalbe in test
40141 0.5.10 - 3.12.2018
41142 --------------------------------------------------------------------------------
42143
43 updated
44 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
144 **updated**
45145
46146 #. `#157 <https://github.com/pyexcel/pyexcel/issues/157>`_: Please use
47147 scan_plugins_regex, which lml 0.7 complains about
50150 0.5.9.1 - 30.08.2018
51151 --------------------------------------------------------------------------------
52152
53 updated
54 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
153 **updated**
55154
56155 #. to require pyexcel-io 0.5.9.1 and use lml at least version 0.0.2
57156
58157 0.5.9 - 30.08.2018
59158 --------------------------------------------------------------------------------
60159
61 added
62 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
160 **added**
63161
64162 #. support __len__. len(book) returns the number of sheets and len(sheet)
65163 returns the number of rows
71169 but with .blob file suffix.
72170 #. finally, pyexcel got import pyexcel.__version__
73171
74 updated
75 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
172 **updated**
76173
77174 #. Sheet.to_records() returns a generator now, saving memory
78175 #. `#115 <https://github.com/pyexcel/pyexcel/issues/115>`_, Fix set membership
80177 #. `#140 <https://github.com/pyexcel/pyexcel/issues/140>`_, Direct writes to
81178 cells yield weird results
82179
83 0.5.8 - unreleased
84 --------------------------------------------------------------------------------
85
86 added
87 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
180 0.5.8 - 26.03.2018
181 --------------------------------------------------------------------------------
182
183 **added**
88184
89185 #. `#125 <https://github.com/pyexcel/pyexcel/issues/125>`_, sort book sheets
90186
91 updated
92 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
187 **updated**
93188
94189 #. `#126 <https://github.com/pyexcel/pyexcel/issues/126>`_, dest_sheet_name in
95190 save_as will set the sheet name in the output
99194 0.5.7 - 11.01.2018
100195 --------------------------------------------------------------------------------
101196
102 added
103 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
197 **added**
104198
105199 #. `pyexcel-io#46 <https://github.com/pyexcel/pyexcel-io/issues/46>`_, expose
106200 `bulk_save` to developer.
108202 0.5.6 - 23.10.2017
109203 --------------------------------------------------------------------------------
110204
111 removed
112 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
205 **removed**
113206
114207 #. `#105 <https://github.com/pyexcel/pyexcel/issues/105>`_, remove gease from
115208 setup_requires, introduced by 0.5.5.
120213 0.5.5 - 20.10.2017
121214 --------------------------------------------------------------------------------
122215
123 removed
124 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
216 **removed**
125217
126218 #. `#105 <https://github.com/pyexcel/pyexcel/issues/105>`_, remove gease from
127219 setup_requires, introduced by 0.5.5.
132224 0.5.4 - 27.09.2017
133225 --------------------------------------------------------------------------------
134226
135 fixed
136 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
227 **fixed**
137228
138229 #. `#100 <https://github.com/pyexcel/pyexcel/issues/100>`_, Sheet.to_dict() gets
139230 out of range error because there is only one row.
140231
141 updated
142 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
232 **updated**
143233
144234 #. Updated the baseline of pyexcel-io to 0.5.1.
145235
146236 0.5.3 - 01-08-2017
147237 --------------------------------------------------------------------------------
148238
149 added
150 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
239 **added**
151240
152241 #. `#95 <https://github.com/pyexcel/pyexcel/issues/95>`_, respect the order of
153242 records in iget_records, isave_as and save_as.
157246 0.5.2 - 26-07-2017
158247 --------------------------------------------------------------------------------
159248
160 Updated
161 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
249 **Updated**
162250
163251 #. embeded the enabler for pyexcel-htmlr. http source does not support text/html
164252 as mime type.
166254 0.5.1 - 12.06.2017
167255 --------------------------------------------------------------------------------
168256
169 Updated
170 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
257 **Updated**
171258
172259 #. support saving SheetStream and BookStream to database targets. This is needed
173260 for pyexcel-webio and its downstream projects.
175262 0.5.0 - 19.06.2017
176263 --------------------------------------------------------------------------------
177264
178 Added
179 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
265 **Added**
180266
181267 #. Sheet.top() and Sheet.top_left() for data browsing
182268 #. add html as default rich display in Jupyter notebook when pyexcel-text and
194280 is enfored. free_resource is added and it should be called when iget_array,
195281 iget_records, isave_as and/or isave_book_as are used.
196282
197 Updated
198 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
283 **Updated**
199284
200285 #. array is passed to pyexcel.Sheet as reference. it means your array data will
201286 be modified.
202287
203 Removed
204 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
288 **Removed**
205289
206290 #. pyexcel.Writer and pyexcel.BookWriter were removed
207291 #. pyexcel.load_book_from_sql and pyexcel.load_from_sql were removed
213297 0.4.5 - 17.03.2017
214298 --------------------------------------------------------------------------------
215299
216 Updated
217 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
300 **Updated**
218301
219302 #. `#80 <https://github.com/pyexcel/pyexcel/issues/80>`_: remove pyexcel-chart
220303 import from v0.4.x
222305 0.4.4 - 06.02.2017
223306 --------------------------------------------------------------------------------
224307
225 Updated
226 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
308 **Updated**
227309
228310 #. `#68 <https://github.com/pyexcel/pyexcel/issues/68>`_: regression
229311 save_to_memory() should have returned a stream instance which has been reset
231313 #. `#74 <https://github.com/pyexcel/pyexcel/issues/74>`_: Not able to handle
232314 decimal.Decimal
233315
234 Removed
235 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
316 **Removed**
236317
237318 #. remove get_{{file_type}}_stream functions from pyexcel.Sheet and pyexcel.Book
238319 introduced since 0.4.3.
240321 0.4.3 - 26.01.2017
241322 --------------------------------------------------------------------------------
242323
243 Added
244 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
324 **Added**
245325
246326 #. '.stream' attribute are attached to `~pyexcel.Sheet` and `~pyexcel.Book` to
247327 get direct access the underneath stream in responding to file type
249329 world, for example, Sheet.stream.csv gives a text stream that contains csv
250330 formatted data. Book.stream.xls returns a xls format data in a byte stream.
251331
252 Updated
253 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
332 **Updated**
254333
255334 #. Better error reporting when an unknown parameters or unsupported file types
256335 were given to the signature functions.
258337 0.4.2 - 17.01.2017
259338 --------------------------------------------------------------------------------
260339
261 Updated
262 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
340 **Updated**
263341
264342 #. Raise exception if the incoming sheet does not have column names. In other
265343 words, only sheet with column names could be saved to database. sheet with
279357 0.4.1 - 23.12.2016
280358 --------------------------------------------------------------------------------
281359
282 Updated
283 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
360 **Updated**
284361
285362 #. `#68 <https://github.com/pyexcel/pyexcel/issues/68>`_: regression
286363 save_to_memory() should have returned a stream instance.
288365 0.4.0 - 22.12.2016
289366 --------------------------------------------------------------------------------
290367
291 Added
292 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
368 **Added**
293369
294370 #. `Flask-Excel#19 <https://github.com/pyexcel/Flask-Excel/issues/19>`_ allow
295371 sheet_name parameter
296372 #. `pyexcel-xls#11 <https://github.com/pyexcel/pyexcel-xls/issues/11>`_
297373 case-insensitive for file_type. `xls` and `XLS` are treated in the same way
298374
299 Updated
300 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
375 **Updated**
301376
302377 #. `#66 <https://github.com/pyexcel/pyexcel/issues/66>`_: `export_columns` is
303378 ignored
306381 0.3.3 - 07.11.2016
307382 --------------------------------------------------------------------------------
308383
309 Updated
310 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
384 **Updated**
311385
312386 #. `#63 <https://github.com/pyexcel/pyexcel/issues/63>`_: cannot display empty
313387 sheet(hence book with empty sheet) as texttable
315389 0.3.2 - 02.11.2016
316390 --------------------------------------------------------------------------------
317391
318 Updated
319 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
392 **Updated**
320393
321394 #. `#62 <https://github.com/pyexcel/pyexcel/issues/62>`_: optional module import
322395 error become visible.
324397 0.3.0 - 28.10.2016
325398 --------------------------------------------------------------------------------
326399
327 Added:
328 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
400 **Added:**
329401
330402 #. file type setters for Sheet and Book, and its documentation
331403 #. `iget_records` returns a generator for a list of records and should have
335407 files.
336408 #. Enable pagination support, and custom row renderer via pyexcel-io v0.2.3
337409
338 Updated
339 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
410 **Updated**
340411
341412 #. Take `isave_as` out from `save_as`. Hence two functions are there for save a
342413 sheet as
363434 actual content. No longer they will return a io object hence you cannot call
364435 getvalue() on them.
365436
366 Removed:
367 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
437 **Removed:**
368438
369439 #. `content` and `out_file` as function parameters to the signature functions
370440 are no longer supported.
414484 0.2.5 - 31.08.2016
415485 --------------------------------------------------------------------------------
416486
417 Updated:
418 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
487 **Updated:**
419488
420489 #. `#58 <https://github.com/pyexcel/pyexcel/issues/58>`_: texttable should have
421490 been made as compulsory requirement
423492 0.2.4 - 14.07.2016
424493 --------------------------------------------------------------------------------
425494
426 Updated:
427 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
495 **Updated:**
428496
429497 #. For python 2, writing to sys.stdout by pyexcel-cli raise IOError.
430498
431499 0.2.3 - 11.07.2016
432500 --------------------------------------------------------------------------------
433501
434 Updated:
435 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
502 **Updated:**
436503
437504 #. For python 3, do not seek 0 when saving to memory if sys.stdout is passed on.
438505 Hence, adding support for sys.stdin and sys.stdout.
440507 0.2.2 - 01.06.2016
441508 --------------------------------------------------------------------------------
442509
443 Updated:
444 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
510 **Updated:**
445511
446512 #. Explicit imports, no longer needed
447513 #. Depends on latest setuptools 18.0.1
452518 0.2.1 - 23.04.2016
453519 --------------------------------------------------------------------------------
454520
455 Added:
456 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
521 **Added:**
457522
458523 #. add pyexcel-text file types as attributes of pyexcel.Sheet and pyexcel.Book,
459524 related to `#31 <https://github.com/pyexcel/pyexcel/issues/31>`__
460525 #. auto import pyexcel-text if it is pip installed
461526
462 Updated:
463 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
527 **Updated:**
464528
465529 #. code refactoring done for easy addition of sources.
466530 #. bug fix `#29 <https://github.com/pyexcel/pyexcel/issues/29>`__, Even if the
468532 #. pyexcel-text is no longer a plugin to pyexcel-io but to pyexcel.sources, see
469533 `pyexcel-text#22 <https://github.com/pyexcel/pyexcel-text/issues/22>`__
470534
471 Removed:
472 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
535 **Removed:**
473536
474537 #. pyexcel.presentation is removed. No longer the internal decorate @outsource
475538 is used. related to `#31 <https://github.com/pyexcel/pyexcel/issues/31>`_
477540 0.2.0 - 17.01.2016
478541 --------------------------------------------------------------------------------
479542
480 Updated
481 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
543 **Updated**
482544
483545 #. adopt pyexcel-io yield key word to return generator as content
484546 #. pyexcel.save_as and pyexcel.save_book_as get performance improvements
486548 0.1.7 - 03.07.2015
487549 --------------------------------------------------------------------------------
488550
489 Added
490 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
551 **Added**
491552
492553 #. Support pyramid-excel which does the database commit on its own.
493554
494555 0.1.6 - 13.06.2015
495556 --------------------------------------------------------------------------------
496557
497 Added
498 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
558 **Added**
499559
500560 #. get excel data from a http url
501561
502562 0.0.13 - 07.02.2015
503563 --------------------------------------------------------------------------------
504564
505 Added
506 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
565 **Added**
507566
508567 #. Support django
509568 #. texttable as default renderer
511570 0.0.12 - 25.01.2015
512571 --------------------------------------------------------------------------------
513572
514 Added
515 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
573 **Added**
516574
517575 #. Added sqlalchemy support
518576
519577 0.0.10 - 15.12.2015
520578 --------------------------------------------------------------------------------
521579
522 Added
523 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
580 **Added**
524581
525582 #. added csvz and tsvz format
526583
527584 0.0.4 - 12.10.2014
528585 --------------------------------------------------------------------------------
529586
530 Updated
531 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
587 **Updated**
532588
533589 #. Support python 3
534590
535591 0.0.1 - 14.09.2014
536592 --------------------------------------------------------------------------------
537593
538 Features:
539 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
594 **Features:**
540595
541596 #. read and write csv, ods, xls, xlsx and xlsm files(which are referred later as
542597 excel files)
0
1
2 19 contributors
3 ================================================================================
4
5 In alphabetical order:
6
7 * `Akshaya Kumar Sharma <https://github.com/akshayakrsh>`_
8 * `Andre Almar <https://github.com/andrealmar>`_
9 * `Arunkumar Rajendran <https://github.com/arunkumar-ra>`_
10 * `Ayan Banerjee <https://github.com/ayan-b>`_
11 * `Chris Hill-Scott <https://github.com/quis>`_
12 * `Craig Anderson <https://github.com/craiga>`_
13 * `Daryl Yu <https://github.com/darylyu>`_
14 * `J Harley <https://github.com/julzhk>`_
15 * `Joel Nothman <https://github.com/jnothman>`_
16 * `John Vandenberg <https://github.com/jayvdb>`_
17 * `Linghui Zeng <https://github.com/mathsyouth>`_
18 * `Nik Nyby <https://github.com/nikolas>`_
19 * `Rintze M. Zelle, PhD <https://github.com/rmzelle>`_
20 * `Simeon Visser <https://github.com/svisser>`_
21 * `Simon Allen <https://github.com/garfunkel>`_
22 * `simon klemenc <https://github.com/hiaselhans>`_
23 * `Tim Gates <https://github.com/timgates42>`_
24 * `Wesley A. Cheng <https://github.com/wesleyacheng>`_
25 * `William Jamir Silva <https://github.com/williamjamir>`_
0 Copyright (c) 2014-2019 by Onni Software Ltd. and its contributors
0 Copyright (c) 2014-2022 by Onni Software Ltd. and its contributors
11 All rights reserved.
22
33 Redistribution and use in source and binary forms of the software as well
1212 and/or other materials provided with the distribution.
1313
1414 * Neither the name of 'pyexcel' nor the names of the contributors
15 may be used to endorse or promote products derived from this software
15 may not be used to endorse or promote products derived from this software
1616 without specific prior written permission.
1717
1818 THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND
00 include README.rst
11 include LICENSE
22 include CHANGELOG.rst
3 include CONTRIBUTORS.rst
34 recursive-include tests *
45 recursive-include docs *
56 recursive-include examples *
00 all: test
11
2 test:
2 test: lint
33 bash test.sh
44
5 doc:
6 bash document.sh
5 install_test:
6 pip install -r tests/requirements.txt
77
8 uml:
9 plantuml -tsvg -o ../_static/images/ docs/source/uml/*.uml
8 lint:
9 bash lint.sh
1010
1111 format:
12 isort -y $(find pyexcel -name "*.py"|xargs echo) $(find tests -name "*.py"|xargs echo)
13 black -l 79 pyexcel
14 black -l 79 tests
15 black -l 79 setup.py
16 lint:
17 bash lint.sh
12 bash format.sh
13
14 git-diff-check:
15 git diff --exit-code
22 ================================================================================
33
44 .. image:: https://raw.githubusercontent.com/pyexcel/pyexcel.github.io/master/images/patreon.png
5 :target: https://www.patreon.com/pyexcel
6
7 .. image:: https://api.bountysource.com/badge/team?team_id=288537
8 :target: https://salt.bountysource.com/teams/chfw-pyexcel
9
10 .. image:: https://travis-ci.org/pyexcel/pyexcel.svg?branch=master
11 :target: http://travis-ci.org/pyexcel/pyexcel
5 :target: https://www.patreon.com/chfw
6
7 .. image:: https://raw.githubusercontent.com/pyexcel/pyexcel-mobans/master/images/awesome-badge.svg
8 :target: https://awesome-python.com/#specific-formats-processing
9
10 .. image:: https://github.com/pyexcel/pyexcel/workflows/run_tests/badge.svg
11 :target: http://github.com/pyexcel/pyexcel/actions
1212
1313 .. image:: https://codecov.io/gh/pyexcel/pyexcel/branch/master/graph/badge.svg
1414 :target: https://codecov.io/gh/pyexcel/pyexcel
1515
16 .. image:: https://badge.fury.io/py/pyexcel.svg
17 :target: https://pypi.org/project/pyexcel
18
19 .. image:: https://anaconda.org/conda-forge/pyexcel/badges/version.svg
20 :target: https://anaconda.org/conda-forge/pyexcel
21
22 .. image:: https://pepy.tech/badge/pyexcel/month
23 :target: https://pepy.tech/project/pyexcel
24
25 .. image:: https://anaconda.org/conda-forge/pyexcel/badges/downloads.svg
26 :target: https://anaconda.org/conda-forge/pyexcel
27
1628 .. image:: https://img.shields.io/gitter/room/gitterHQ/gitter.svg
1729 :target: https://gitter.im/pyexcel/Lobby
1830
31 .. 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
32 :target: https://moban.readthedocs.io/en/latest/#at-scale-continous-templating-for-open-source-projects
33
34 .. image:: https://img.shields.io/static/v1?label=coding%20style&message=black&color=black&style=flat-square
35 :target: https://github.com/psf/black
1936 .. image:: https://readthedocs.org/projects/pyexcel/badge/?version=latest
2037 :target: http://pyexcel.readthedocs.org/en/latest/
2138
2340 ================================================================================
2441
2542 If your company has embedded pyexcel and its components into a revenue generating
26 product, please support me on `patreon <https://www.patreon.com/bePatron?u=5537627>`_
43 product, please support me on github, `patreon <https://www.patreon.com/bePatron?u=5537627>`_
2744 or `bounty source <https://salt.bountysource.com/teams/chfw-pyexcel>`_ to maintain
2845 the project and develop it further.
2946
4259
4360 Fonts, colors and charts are not supported.
4461
62 Nor to read password protected xls, xlsx and ods files.
63
4564 Introduction
4665 ================================================================================
4766
4867 Feature Highlights
4968 ===================
69
70 .. table:: A list of supported file formats
71
72 ============ =======================================================
73 file format definition
74 ============ =======================================================
75 csv comma separated values
76 tsv tab separated values
77 csvz a zip file that contains one or many csv files
78 tsvz a zip file that contains one or many tsv files
79 xls a spreadsheet file format created by
80 MS-Excel 97-2003
81 xlsx MS-Excel Extensions to the Office Open XML
82 SpreadsheetML File Format.
83 xlsm an MS-Excel Macro-Enabled Workbook file
84 ods open document spreadsheet
85 fods flat open document spreadsheet
86 json java script object notation
87 html html table of the data structure
88 simple simple presentation
89 rst rStructured Text presentation of the data
90 mediawiki media wiki table
91 ============ =======================================================
92
93
94 .. image:: https://github.com/pyexcel/pyexcel/raw/dev/docs/source/_static/images/architecture.svg
95
5096
5197 1. One application programming interface(API) to handle multiple data sources:
5298
55101 * SQLAlchemy table
56102 * Django Model
57103 * Python data structures: dictionary, records and array
104
58105 2. One API to read and write data in various excel file formats.
59 3. For large data sets, data streaming are supported. A genenerator can be returned to you. Checkout iget_records, iget_array, isave_as and isave_book_as.
106 3. For large data sets, data streaming are supported. A genenerator can be returned to you. Checkout iget_records, iget_array, isave_as and isave_book_as.
60107
61108
62109
81128
82129
83130
84 Usage
85 ===============
86
87 Please note that you will have to use '.sortable.html' in order to replicate the example.
88
89 .. image:: https://github.com/pyexcel/pyexcel-sortable/raw/master/sortable.gif
90
91 .. code-block:: python
92
93 >>> # pip install pyexcel-text==0.2.7.1
94 >>> import pyexcel as p
95 >>> ccs_insight2 = p.Sheet()
96 >>> ccs_insight2.name = "Worldwide Mobile Phone Shipments (Billions), 2017-2021"
97 >>> ccs_insight2.ndjson = """
98 ... {"year": ["2017", "2018", "2019", "2020", "2021"]}
99 ... {"smart phones": [1.53, 1.64, 1.74, 1.82, 1.90]}
100 ... {"feature phones": [0.46, 0.38, 0.30, 0.23, 0.17]}
101 ... """.strip()
102 >>> ccs_insight2
103 pyexcel sheet:
104 +----------------+------+------+------+------+------+
105 | year | 2017 | 2018 | 2019 | 2020 | 2021 |
106 +----------------+------+------+------+------+------+
107 | smart phones | 1.53 | 1.64 | 1.74 | 1.82 | 1.9 |
108 +----------------+------+------+------+------+------+
109 | feature phones | 0.46 | 0.38 | 0.3 | 0.23 | 0.17 |
110 +----------------+------+------+------+------+------+
111
112
113
114 Suppose you have the following data in a dictionary:
115
116 ========= ====
117 Name Age
118 ========= ====
119 Adam 28
120 Beatrice 29
121 Ceri 30
122 Dean 26
123 ========= ====
124
125 you can easily save it into an excel file using the following code:
131 One liners
132 ================================================================================
133
134 This section shows you how to get data from your excel files and how to
135 export data to excel files in **one line**
136
137 Read from the excel files
138 --------------------------------------------------------------------------------
139
140 Get a list of dictionaries
141 ********************************************************************************
142
143
144 Suppose you want to process `History of Classical Music <https://www.naxos.com/education/brief_history.asp>`_:
145
146
147 History of Classical Music:
148
149 =============== ============= ====================================
150 Name Period Representative Composers
151 Medieval c.1150-c.1400 Machaut, Landini
152 Renaissance c.1400-c.1600 Gibbons, Frescobaldi
153 Baroque c.1600-c.1750 JS Bach, Vivaldi
154 Classical c.1750-c.1830 Joseph Haydn, Wolfgan Amadeus Mozart
155 Earley Romantic c.1830-c.1860 Chopin, Mendelssohn, Schumann, Liszt
156 Late Romantic c.1860-c.1920 Wagner,Verdi
157 =============== ============= ====================================
158
159
160 Let's get a list of dictionary out from the xls file:
161
162 .. code-block:: python
163
164 >>> records = p.get_records(file_name="your_file.xls")
165
166 And let's check what do we have:
167
168 .. code-block:: python
169
170 >>> for row in records:
171 ... print(f"{row['Representative Composers']} are from {row['Name']} period ({row['Period']})")
172 Machaut, Landini are from Medieval period (c.1150-c.1400)
173 Gibbons, Frescobaldi are from Renaissance period (c.1400-c.1600)
174 JS Bach, Vivaldi are from Baroque period (c.1600-c.1750)
175 Joseph Haydn, Wolfgan Amadeus Mozart are from Classical period (c.1750-c.1830)
176 Chopin, Mendelssohn, Schumann, Liszt are from Earley Romantic period (c.1830-c.1860)
177 Wagner,Verdi are from Late Romantic period (c.1860-c.1920)
178
179
180 Get two dimensional array
181 ********************************************************************************
182
183 Instead, what if you have to use `pyexcel.get_array` to do the same:
184
185 .. code-block:: python
186
187 >>> for row in p.get_array(file_name="your_file.xls", start_row=1):
188 ... print(f"{row[2]} are from {row[0]} period ({row[1]})")
189 Machaut, Landini are from Medieval period (c.1150-c.1400)
190 Gibbons, Frescobaldi are from Renaissance period (c.1400-c.1600)
191 JS Bach, Vivaldi are from Baroque period (c.1600-c.1750)
192 Joseph Haydn, Wolfgan Amadeus Mozart are from Classical period (c.1750-c.1830)
193 Chopin, Mendelssohn, Schumann, Liszt are from Earley Romantic period (c.1830-c.1860)
194 Wagner,Verdi are from Late Romantic period (c.1860-c.1920)
195
196
197 where `start_row` skips the header row.
198
199
200 Get a dictionary
201 ********************************************************************************
202
203 You can get a dictionary too:
204
205 Now let's get a dictionary out from the spreadsheet:
206
207 .. code-block:: python
208
209 >>> my_dict = p.get_dict(file_name="your_file.xls", name_columns_by_row=0)
210
211 And check what do we have:
212
213 .. code-block:: python
214
215 >>> from pyexcel._compact import OrderedDict
216 >>> isinstance(my_dict, OrderedDict)
217 True
218 >>> for key, values in my_dict.items():
219 ... print(key + " : " + ','.join([str(item) for item in values]))
220 Name : Medieval,Renaissance,Baroque,Classical,Earley Romantic,Late Romantic
221 Period : c.1150-c.1400,c.1400-c.1600,c.1600-c.1750,c.1750-c.1830,c.1830-c.1860,c.1860-c.1920
222 Representative Composers : Machaut, Landini,Gibbons, Frescobaldi,JS Bach, Vivaldi,Joseph Haydn, Wolfgan Amadeus Mozart,Chopin, Mendelssohn, Schumann, Liszt,Wagner,Verdi
223
224
225 Please note that my_dict is an OrderedDict.
226
227 Get a dictionary of two dimensional array
228 ********************************************************************************
229
230
231 Suppose you have a multiple sheet book as the following:
232
233
234 pyexcel:Sheet 1:
235
236 ===================== = =
237 1 2 3
238 4 5 6
239 7 8 9
240 ===================== = =
241
242 pyexcel:Sheet 2:
243
244 ===================== = =
245 X Y Z
246 1 2 3
247 4 5 6
248 ===================== = =
249
250 pyexcel:Sheet 3:
251
252 ===================== = =
253 O P Q
254 3 2 1
255 4 3 2
256 ===================== = =
257
258
259 Here is the code to obtain those sheets as a single dictionary:
260
261 .. code-block:: python
262
263 >>> book_dict = p.get_book_dict(file_name="book.xls")
264
265 And check:
266
267 .. code-block:: python
268
269 >>> isinstance(book_dict, OrderedDict)
270 True
271 >>> import json
272 >>> for key, item in book_dict.items():
273 ... print(json.dumps({key: item}))
274 {"Sheet 1": [[1, 2, 3], [4, 5, 6], [7, 8, 9]]}
275 {"Sheet 2": [["X", "Y", "Z"], [1, 2, 3], [4, 5, 6]]}
276 {"Sheet 3": [["O", "P", "Q"], [3, 2, 1], [4, 3, 2]]}
277
278
279 Write data
280 ---------------------------------------------
281
282 Export an array
283 **********************
284
285 Suppose you have the following array:
286
287 .. code-block:: python
288
289 >>> data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
290
291 And here is the code to save it as an excel file :
292
293 .. code-block:: python
294
295 >>> p.save_as(array=data, dest_file_name="example.xls")
296
297 Let's verify it:
298
299 .. code-block:: python
300
301 >>> p.get_sheet(file_name="example.xls")
302 pyexcel_sheet1:
303 +---+---+---+
304 | 1 | 2 | 3 |
305 +---+---+---+
306 | 4 | 5 | 6 |
307 +---+---+---+
308 | 7 | 8 | 9 |
309 +---+---+---+
310
311
312 And here is the code to save it as a csv file :
313
314 .. code-block:: python
315
316 >>> p.save_as(array=data,
317 ... dest_file_name="example.csv",
318 ... dest_delimiter=':')
319
320 Let's verify it:
321
322 .. code-block:: python
323
324 >>> with open("example.csv") as f:
325 ... for line in f.readlines():
326 ... print(line.rstrip())
327 ...
328 1:2:3
329 4:5:6
330 7:8:9
331
332 Export a list of dictionaries
333 **********************************
334
335 .. code-block:: python
336
337 >>> records = [
338 ... {"year": 1903, "country": "Germany", "speed": "206.7km/h"},
339 ... {"year": 1964, "country": "Japan", "speed": "210km/h"},
340 ... {"year": 2008, "country": "China", "speed": "350km/h"}
341 ... ]
342 >>> p.save_as(records=records, dest_file_name='high_speed_rail.xls')
343
344
345 Export a dictionary of single key value pair
346 ********************************************************************************
347
348 .. code-block:: python
349
350 >>> henley_on_thames_facts = {
351 ... "area": "5.58 square meters",
352 ... "population": "11,619",
353 ... "civial parish": "Henley-on-Thames",
354 ... "latitude": "51.536",
355 ... "longitude": "-0.898"
356 ... }
357 >>> p.save_as(adict=henley_on_thames_facts, dest_file_name='henley.xlsx')
358
359
360 Export a dictionary of single dimensonal array
361 ********************************************************************************
362
363 .. code-block:: python
364
365 >>> ccs_insights = {
366 ... "year": ["2017", "2018", "2019", "2020", "2021"],
367 ... "smart phones": [1.53, 1.64, 1.74, 1.82, 1.90],
368 ... "feature phones": [0.46, 0.38, 0.30, 0.23, 0.17]
369 ... }
370 >>> p.save_as(adict=ccs_insights, dest_file_name='ccs.csv')
371
372
373 Export a dictionary of two dimensional array as a book
374 ********************************************************************************
375
376 Suppose you want to save the below dictionary to an excel file :
377
378 .. code-block:: python
379
380 >>> a_dictionary_of_two_dimensional_arrays = {
381 ... 'Sheet 1':
382 ... [
383 ... [1.0, 2.0, 3.0],
384 ... [4.0, 5.0, 6.0],
385 ... [7.0, 8.0, 9.0]
386 ... ],
387 ... 'Sheet 2':
388 ... [
389 ... ['X', 'Y', 'Z'],
390 ... [1.0, 2.0, 3.0],
391 ... [4.0, 5.0, 6.0]
392 ... ],
393 ... 'Sheet 3':
394 ... [
395 ... ['O', 'P', 'Q'],
396 ... [3.0, 2.0, 1.0],
397 ... [4.0, 3.0, 2.0]
398 ... ]
399 ... }
400
401 Here is the code:
402
403 .. code-block:: python
404
405 >>> p.save_book_as(
406 ... bookdict=a_dictionary_of_two_dimensional_arrays,
407 ... dest_file_name="book.xls"
408 ... )
409
410 If you want to preserve the order of sheets in your dictionary, you have to
411 pass on an ordered dictionary to the function itself. For example:
412
413 .. code-block:: python
414
415 >>> data = OrderedDict()
416 >>> data.update({"Sheet 2": a_dictionary_of_two_dimensional_arrays['Sheet 2']})
417 >>> data.update({"Sheet 1": a_dictionary_of_two_dimensional_arrays['Sheet 1']})
418 >>> data.update({"Sheet 3": a_dictionary_of_two_dimensional_arrays['Sheet 3']})
419 >>> p.save_book_as(bookdict=data, dest_file_name="book.xls")
420
421 Let's verify its order:
422
423 .. code-block:: python
424
425 >>> book_dict = p.get_book_dict(file_name="book.xls")
426 >>> for key, item in book_dict.items():
427 ... print(json.dumps({key: item}))
428 {"Sheet 2": [["X", "Y", "Z"], [1, 2, 3], [4, 5, 6]]}
429 {"Sheet 1": [[1, 2, 3], [4, 5, 6], [7, 8, 9]]}
430 {"Sheet 3": [["O", "P", "Q"], [3, 2, 1], [4, 3, 2]]}
431
432 Please notice that "Sheet 2" is the first item in the *book_dict*, meaning the order of sheets are preserved.
433
434
435 Transcoding
436 -------------------------------------------
437
438 .. note::
439
440 Please note that `pyexcel-cli` can perform file transcoding at command line.
441 No need to open your editor, save the problem, then python run.
442
443
444 The following code does a simple file format transcoding from xls to csv:
445
446 .. code-block:: python
447
448 >>> p.save_as(file_name="birth.xls", dest_file_name="birth.csv")
449
450 Again it is really simple. Let's verify what we have gotten:
451
452 .. code-block:: python
453
454 >>> sheet = p.get_sheet(file_name="birth.csv")
455 >>> sheet
456 birth.csv:
457 +-------+--------+----------+
458 | name | weight | birth |
459 +-------+--------+----------+
460 | Adam | 3.4 | 03/02/15 |
461 +-------+--------+----------+
462 | Smith | 4.2 | 12/11/14 |
463 +-------+--------+----------+
464
465 .. NOTE::
466
467 Please note that csv(comma separate value) file is pure text file. Formula, charts, images and formatting in xls file will disappear no matter which transcoding tool you use. Hence, pyexcel is a quick alternative for this transcoding job.
468
469
470 Let use previous example and save it as xlsx instead
471
472 .. code-block:: python
473
474 >>> p.save_as(file_name="birth.xls",
475 ... dest_file_name="birth.xlsx") # change the file extension
476
477 Again let's verify what we have gotten:
478
479 .. code-block:: python
480
481 >>> sheet = p.get_sheet(file_name="birth.xlsx")
482 >>> sheet
483 pyexcel_sheet1:
484 +-------+--------+----------+
485 | name | weight | birth |
486 +-------+--------+----------+
487 | Adam | 3.4 | 03/02/15 |
488 +-------+--------+----------+
489 | Smith | 4.2 | 12/11/14 |
490 +-------+--------+----------+
491
492
493 Excel book merge and split operation in one line
494 --------------------------------------------------------------------------------
495
496 Merge all excel files in directory into a book where each file become a sheet
497 ********************************************************************************
498
499 The following code will merge every excel files into one file, say "output.xls":
500
501 .. code-block:: python
502
503 from pyexcel.cookbook import merge_all_to_a_book
504 import glob
505
506
507 merge_all_to_a_book(glob.glob("your_csv_directory\*.csv"), "output.xls")
508
509 You can mix and match with other excel formats: xls, xlsm and ods. For example, if you are sure you have only xls, xlsm, xlsx, ods and csv files in `your_excel_file_directory`, you can do the following:
510
511 .. code-block:: python
512
513 from pyexcel.cookbook import merge_all_to_a_book
514 import glob
515
516
517 merge_all_to_a_book(glob.glob("your_excel_file_directory\*.*"), "output.xls")
518
519 Split a book into single sheet files
520 ****************************************
521
522
523 Suppose you have many sheets in a work book and you would like to separate each into a single sheet excel file. You can easily do this:
524
525 .. code-block:: python
526
527 >>> from pyexcel.cookbook import split_a_book
528 >>> split_a_book("megabook.xls", "output.xls")
529 >>> import glob
530 >>> outputfiles = glob.glob("*_output.xls")
531 >>> for file in sorted(outputfiles):
532 ... print(file)
533 ...
534 Sheet 1_output.xls
535 Sheet 2_output.xls
536 Sheet 3_output.xls
537
538 for the output file, you can specify any of the supported formats
539
540
541 Extract just one sheet from a book
542 *************************************
543
544
545 Suppose you just want to extract one sheet from many sheets that exists in a work book and you would like to separate it into a single sheet excel file. You can easily do this:
546
547 .. code-block:: python
548
549 >>> from pyexcel.cookbook import extract_a_sheet_from_a_book
550 >>> extract_a_sheet_from_a_book("megabook.xls", "Sheet 1", "output.xls")
551 >>> if os.path.exists("Sheet 1_output.xls"):
552 ... print("Sheet 1_output.xls exists")
553 ...
554 Sheet 1_output.xls exists
555
556 for the output file, you can specify any of the supported formats
557
558
559 Hidden feature: partial read
560 ===============================================
561
562 Most pyexcel users do not know, but other library users were requesting `partial read <https://github.com/jazzband/tablib/issues/467>`_
563
564
565 When you are dealing with huge amount of data, e.g. 64GB, obviously you would not
566 like to fill up your memory with those data. What you may want to do is, record
567 data from Nth line, take M records and stop. And you only want to use your memory
568 for the M records, not for beginning part nor for the tail part.
569
570 Hence partial read feature is developed to read partial data into memory for
571 processing.
572
573 You can paginate by row, by column and by both, hence you dictate what portion of the
574 data to read back. But remember only row limit features help you save memory. Let's
575 you use this feature to record data from Nth column, take M number of columns and skip
576 the rest. You are not going to reduce your memory footprint.
577
578 Why did not I see above benefit?
579 --------------------------------------------------------------------------------
580
581 This feature depends heavily on the implementation details.
582
583 `pyexcel-xls`_ (xlrd), `pyexcel-xlsx`_ (openpyxl), `pyexcel-ods`_ (odfpy) and
584 `pyexcel-ods3`_ (pyexcel-ezodf) will read all data into memory. Because xls,
585 xlsx and ods file are effective a zipped folder, all four will unzip the folder
586 and read the content in xml format in **full**, so as to make sense of all details.
587
588 Hence, during the partial data is been returned, the memory consumption won't
589 differ from reading the whole data back. Only after the partial
590 data is returned, the memory comsumption curve shall jump the cliff. So pagination
591 code here only limits the data returned to your program.
592
593 With that said, `pyexcel-xlsxr`_, `pyexcel-odsr`_ and `pyexcel-htmlr`_ DOES read
594 partial data into memory. Those three are implemented in such a way that they
595 consume the xml(html) when needed. When they have read designated portion of the
596 data, they stop, even if they are half way through.
597
598 In addition, pyexcel's csv readers can read partial data into memory too.
599
600
601 Let's assume the following file is a huge csv file:
602
603 .. code-block:: python
604
605 >>> import datetime
606 >>> import pyexcel as pe
607 >>> data = [
608 ... [1, 21, 31],
609 ... [2, 22, 32],
610 ... [3, 23, 33],
611 ... [4, 24, 34],
612 ... [5, 25, 35],
613 ... [6, 26, 36]
614 ... ]
615 >>> pe.save_as(array=data, dest_file_name="your_file.csv")
616
617
618 And let's pretend to read partial data:
619
620
621 .. code-block:: python
622
623 >>> pe.get_sheet(file_name="your_file.csv", start_row=2, row_limit=3)
624 your_file.csv:
625 +---+----+----+
626 | 3 | 23 | 33 |
627 +---+----+----+
628 | 4 | 24 | 34 |
629 +---+----+----+
630 | 5 | 25 | 35 |
631 +---+----+----+
632
633 And you could as well do the same for columns:
634
635 .. code-block:: python
636
637 >>> pe.get_sheet(file_name="your_file.csv", start_column=1, column_limit=2)
638 your_file.csv:
639 +----+----+
640 | 21 | 31 |
641 +----+----+
642 | 22 | 32 |
643 +----+----+
644 | 23 | 33 |
645 +----+----+
646 | 24 | 34 |
647 +----+----+
648 | 25 | 35 |
649 +----+----+
650 | 26 | 36 |
651 +----+----+
652
653 Obvious, you could do both at the same time:
654
655 .. code-block:: python
656
657 >>> pe.get_sheet(file_name="your_file.csv",
658 ... start_row=2, row_limit=3,
659 ... start_column=1, column_limit=2)
660 your_file.csv:
661 +----+----+
662 | 23 | 33 |
663 +----+----+
664 | 24 | 34 |
665 +----+----+
666 | 25 | 35 |
667 +----+----+
668
669
670 The pagination support is available across all pyexcel plugins.
671
672 .. note::
673
674 No column pagination support for query sets as data source.
675
676
677 Formatting while transcoding a big data file
678 --------------------------------------------------------------------------------
679
680 If you are transcoding a big data set, conventional formatting method would not
681 help unless a on-demand free RAM is available. However, there is a way to minimize
682 the memory footprint of pyexcel while the formatting is performed.
683
684 Let's continue from previous example. Suppose we want to transcode "your_file.csv"
685 to "your_file.xls" but increase each element by 1.
686
687 What we can do is to define a row renderer function as the following:
688
689 .. code-block:: python
690
691 >>> def increment_by_one(row):
692 ... for element in row:
693 ... yield element + 1
694
695 Then pass it onto save_as function using row_renderer:
696
697 .. code-block:: python
698
699 >>> pe.isave_as(file_name="your_file.csv",
700 ... row_renderer=increment_by_one,
701 ... dest_file_name="your_file.xlsx")
702
703
704 .. note::
705
706 If the data content is from a generator, isave_as has to be used.
707
708 We can verify if it was done correctly:
709
710 .. code-block:: python
711
712 >>> pe.get_sheet(file_name="your_file.xlsx")
713 your_file.csv:
714 +---+----+----+
715 | 2 | 22 | 32 |
716 +---+----+----+
717 | 3 | 23 | 33 |
718 +---+----+----+
719 | 4 | 24 | 34 |
720 +---+----+----+
721 | 5 | 25 | 35 |
722 +---+----+----+
723 | 6 | 26 | 36 |
724 +---+----+----+
725 | 7 | 27 | 37 |
726 +---+----+----+
727
728
729 Stream APIs for big file : A set of two liners
730 ================================================================================
731
732 When you are dealing with **BIG** excel files, you will want **pyexcel** to use
733 constant memory.
734
735 This section shows you how to get data from your **BIG** excel files and how to
736 export data to excel files in **two lines** at most, without eating all
737 your computer memory.
738
739
740 Two liners for get data from big excel files
741 --------------------------------------------------------------------------------
742
743 Get a list of dictionaries
744 ********************************************************************************
745
746
747
748 Suppose you want to process the following coffee data again:
749
750 Top 5 coffeine drinks:
751
752 ===================================== =============== =============
753 Coffees Serving Size Caffeine (mg)
754 Starbucks Coffee Blonde Roast venti(20 oz) 475
755 Dunkin' Donuts Coffee with Turbo Shot large(20 oz.) 398
756 Starbucks Coffee Pike Place Roast grande(16 oz.) 310
757 Panera Coffee Light Roast regular(16 oz.) 300
758 ===================================== =============== =============
759
760
761 Let's get a list of dictionary out from the xls file:
762
763 .. code-block:: python
764
765 >>> records = p.iget_records(file_name="your_file.xls")
766
767 And let's check what do we have:
768
769 .. code-block:: python
770
771 >>> for r in records:
772 ... print(f"{r['Serving Size']} of {r['Coffees']} has {r['Caffeine (mg)']} mg")
773 venti(20 oz) of Starbucks Coffee Blonde Roast has 475 mg
774 large(20 oz.) of Dunkin' Donuts Coffee with Turbo Shot has 398 mg
775 grande(16 oz.) of Starbucks Coffee Pike Place Roast has 310 mg
776 regular(16 oz.) of Panera Coffee Light Roast has 300 mg
777
778 Please do not forgot the second line to close the opened file handle:
779
780 .. code-block:: python
781
782 >>> p.free_resources()
783
784 Get two dimensional array
785 ********************************************************************************
786
787 Instead, what if you have to use `pyexcel.get_array` to do the same:
788
789 .. code-block:: python
790
791 >>> for row in p.iget_array(file_name="your_file.xls", start_row=1):
792 ... print(f"{row[1]} of {row[0]} has {row[2]} mg")
793 venti(20 oz) of Starbucks Coffee Blonde Roast has 475 mg
794 large(20 oz.) of Dunkin' Donuts Coffee with Turbo Shot has 398 mg
795 grande(16 oz.) of Starbucks Coffee Pike Place Roast has 310 mg
796 regular(16 oz.) of Panera Coffee Light Roast has 300 mg
797
798 Again, do not forgot the second line:
799
800 .. code-block:: python
801
802 >>> p.free_resources()
803
804 where `start_row` skips the header row.
805
806 Data export in one liners
807 ---------------------------------------------
808
809 Export an array
810 **********************
811
812 Suppose you have the following array:
813
814 .. code-block:: python
815
816 >>> data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
817
818 And here is the code to save it as an excel file :
819
820 .. code-block:: python
821
822 >>> p.isave_as(array=data, dest_file_name="example.xls")
823
824 But the following line is not required because the data source
825 are not file sources:
826
827 .. code-block:: python
828
829 >>> # p.free_resources()
830
831 Let's verify it:
832
833 .. code-block:: python
834
835 >>> p.get_sheet(file_name="example.xls")
836 pyexcel_sheet1:
837 +---+---+---+
838 | 1 | 2 | 3 |
839 +---+---+---+
840 | 4 | 5 | 6 |
841 +---+---+---+
842 | 7 | 8 | 9 |
843 +---+---+---+
844
845
846 And here is the code to save it as a csv file :
847
848 .. code-block:: python
849
850 >>> p.isave_as(array=data,
851 ... dest_file_name="example.csv",
852 ... dest_delimiter=':')
853
854 Let's verify it:
855
856 .. code-block:: python
857
858 >>> with open("example.csv") as f:
859 ... for line in f.readlines():
860 ... print(line.rstrip())
861 ...
862 1:2:3
863 4:5:6
864 7:8:9
865
866 Export a list of dictionaries
867 **********************************
868
869 .. code-block:: python
870
871 >>> records = [
872 ... {"year": 1903, "country": "Germany", "speed": "206.7km/h"},
873 ... {"year": 1964, "country": "Japan", "speed": "210km/h"},
874 ... {"year": 2008, "country": "China", "speed": "350km/h"}
875 ... ]
876 >>> p.isave_as(records=records, dest_file_name='high_speed_rail.xls')
877
878 Export a dictionary of single key value pair
879 ********************************************************************************
880
881 .. code-block:: python
882
883 >>> henley_on_thames_facts = {
884 ... "area": "5.58 square meters",
885 ... "population": "11,619",
886 ... "civial parish": "Henley-on-Thames",
887 ... "latitude": "51.536",
888 ... "longitude": "-0.898"
889 ... }
890 >>> p.isave_as(adict=henley_on_thames_facts, dest_file_name='henley.xlsx')
891
892 Export a dictionary of single dimensonal array
893 ********************************************************************************
894
895 .. code-block:: python
896
897 >>> ccs_insights = {
898 ... "year": ["2017", "2018", "2019", "2020", "2021"],
899 ... "smart phones": [1.53, 1.64, 1.74, 1.82, 1.90],
900 ... "feature phones": [0.46, 0.38, 0.30, 0.23, 0.17]
901 ... }
902 >>> p.isave_as(adict=ccs_insights, dest_file_name='ccs.csv')
903 >>> p.free_resources()
904
905 Export a dictionary of two dimensional array as a book
906 ********************************************************************************
907
908 Suppose you want to save the below dictionary to an excel file :
909
910 .. code-block:: python
911
912 >>> a_dictionary_of_two_dimensional_arrays = {
913 ... 'Sheet 1':
914 ... [
915 ... [1.0, 2.0, 3.0],
916 ... [4.0, 5.0, 6.0],
917 ... [7.0, 8.0, 9.0]
918 ... ],
919 ... 'Sheet 2':
920 ... [
921 ... ['X', 'Y', 'Z'],
922 ... [1.0, 2.0, 3.0],
923 ... [4.0, 5.0, 6.0]
924 ... ],
925 ... 'Sheet 3':
926 ... [
927 ... ['O', 'P', 'Q'],
928 ... [3.0, 2.0, 1.0],
929 ... [4.0, 3.0, 2.0]
930 ... ]
931 ... }
932
933 Here is the code:
934
935 .. code-block:: python
936
937 >>> p.isave_book_as(
938 ... bookdict=a_dictionary_of_two_dimensional_arrays,
939 ... dest_file_name="book.xls"
940 ... )
941
942 If you want to preserve the order of sheets in your dictionary, you have to
943 pass on an ordered dictionary to the function itself. For example:
944
945 .. code-block:: python
946
947 >>> from pyexcel._compact import OrderedDict
948 >>> data = OrderedDict()
949 >>> data.update({"Sheet 2": a_dictionary_of_two_dimensional_arrays['Sheet 2']})
950 >>> data.update({"Sheet 1": a_dictionary_of_two_dimensional_arrays['Sheet 1']})
951 >>> data.update({"Sheet 3": a_dictionary_of_two_dimensional_arrays['Sheet 3']})
952 >>> p.isave_book_as(bookdict=data, dest_file_name="book.xls")
953 >>> p.free_resources()
954
955 Let's verify its order:
956
957 .. code-block:: python
958
959 >>> import json
960 >>> book_dict = p.get_book_dict(file_name="book.xls")
961 >>> for key, item in book_dict.items():
962 ... print(json.dumps({key: item}))
963 {"Sheet 2": [["X", "Y", "Z"], [1, 2, 3], [4, 5, 6]]}
964 {"Sheet 1": [[1, 2, 3], [4, 5, 6], [7, 8, 9]]}
965 {"Sheet 3": [["O", "P", "Q"], [3, 2, 1], [4, 3, 2]]}
966
967 Please notice that "Sheet 2" is the first item in the *book_dict*, meaning the order of sheets are preserved.
968
969
970 File format transcoding on one line
971 -------------------------------------------
972
973 .. note::
974
975 Please note that the following file transcoding could be with zero line. Please
976 install pyexcel-cli and you will do the transcode in one command. No need to
977 open your editor, save the problem, then python run.
978
979
980 The following code does a simple file format transcoding from xls to csv:
126981
127982 .. code-block:: python
128983
129984 >>> import pyexcel
130 >>> # make sure you had pyexcel-xls installed
131 >>> a_list_of_dictionaries = [
132 ... {
133 ... "Name": 'Adam',
134 ... "Age": 28
135 ... },
136 ... {
137 ... "Name": 'Beatrice',
138 ... "Age": 29
139 ... },
140 ... {
141 ... "Name": 'Ceri',
142 ... "Age": 30
143 ... },
144 ... {
145 ... "Name": 'Dean',
146 ... "Age": 26
147 ... }
148 ... ]
149 >>> pyexcel.save_as(records=a_list_of_dictionaries, dest_file_name="your_file.xls")
150
151
152 And here's how to obtain the records:
153
154 .. code-block:: python
155
156 >>> import pyexcel as p
157 >>> records = p.iget_records(file_name="your_file.xls")
158 >>> for record in records:
159 ... print("%s is aged at %d" % (record['Name'], record['Age']))
160 Adam is aged at 28
161 Beatrice is aged at 29
162 Ceri is aged at 30
163 Dean is aged at 26
164 >>> p.free_resources()
165
166
167 Advanced usage :fire:
168 ----------------------
169
170 If you are dealing with big data, please consider these usages:
171
172 >>> def increase_everyones_age(generator):
173 ... for row in generator:
174 ... row['Age'] += 1
175 ... yield row
176 >>> def duplicate_each_record(generator):
177 ... for row in generator:
178 ... yield row
179 ... yield row
180 >>> records = p.iget_records(file_name="your_file.xls")
181 >>> io=p.isave_as(records=duplicate_each_record(increase_everyones_age(records)),
182 ... dest_file_type='csv', dest_lineterminator='\n')
183 >>> print(io.getvalue())
184 Age,Name
185 29,Adam
186 29,Adam
187 30,Beatrice
188 30,Beatrice
189 31,Ceri
190 31,Ceri
191 27,Dean
192 27,Dean
193 <BLANKLINE>
194
195 Two advantages of above method:
196
197 #. Add as many wrapping functions as you want.
198 #. Constant memory consumption
985 >>> p.save_as(file_name="birth.xls", dest_file_name="birth.csv")
986
987 Again it is really simple. Let's verify what we have gotten:
988
989 .. code-block:: python
990
991 >>> sheet = p.get_sheet(file_name="birth.csv")
992 >>> sheet
993 birth.csv:
994 +-------+--------+----------+
995 | name | weight | birth |
996 +-------+--------+----------+
997 | Adam | 3.4 | 03/02/15 |
998 +-------+--------+----------+
999 | Smith | 4.2 | 12/11/14 |
1000 +-------+--------+----------+
1001
1002 .. note::
1003
1004 Please note that csv(comma separate value) file is pure text file. Formula, charts, images and formatting in xls file will disappear no matter which transcoding tool you use. Hence, pyexcel is a quick alternative for this transcoding job.
1005
1006
1007 Let use previous example and save it as xlsx instead
1008
1009 .. code-block:: python
1010
1011 >>> import pyexcel
1012 >>> p.isave_as(file_name="birth.xls",
1013 ... dest_file_name="birth.xlsx") # change the file extension
1014
1015 Again let's verify what we have gotten:
1016
1017 .. code-block:: python
1018
1019 >>> sheet = p.get_sheet(file_name="birth.xlsx")
1020 >>> sheet
1021 pyexcel_sheet1:
1022 +-------+--------+----------+
1023 | name | weight | birth |
1024 +-------+--------+----------+
1025 | Adam | 3.4 | 03/02/15 |
1026 +-------+--------+----------+
1027 | Smith | 4.2 | 12/11/14 |
1028 +-------+--------+----------+
1029
1991030
2001031 Available Plugins
2011032 =================
2051036
2061037 .. table:: A list of file formats supported by external plugins
2071038
208 ======================== ======================= ================= ==================
209 Package name Supported file formats Dependencies Python versions
210 ======================== ======================= ================= ==================
211 `pyexcel-io`_ csv, csvz [#f1]_, tsv, 2.6, 2.7, 3.3,
212 tsvz [#f2]_ 3.4, 3.5, 3.6
213 pypy
214 `pyexcel-xls`_ xls, xlsx(read only), `xlrd`_, same as above
1039 ======================== ======================= =================
1040 Package name Supported file formats Dependencies
1041 ======================== ======================= =================
1042 `pyexcel-io`_ csv, csvz [#f1]_, tsv,
1043 tsvz [#f2]_
1044 `pyexcel-xls`_ xls, xlsx(read only), `xlrd`_,
2151045 xlsm(read only) `xlwt`_
216 `pyexcel-xlsx`_ xlsx `openpyxl`_ same as above
217 `pyexcel-ods3`_ ods `pyexcel-ezodf`_, 2.6, 2.7, 3.3, 3.4
218 lxml 3.5, 3.6
219 `pyexcel-ods`_ ods `odfpy`_ same as above
220 ======================== ======================= ================= ==================
1046 `pyexcel-xlsx`_ xlsx `openpyxl`_
1047 `pyexcel-ods3`_ ods `pyexcel-ezodf`_,
1048 lxml
1049 `pyexcel-ods`_ ods `odfpy`_
1050 ======================== ======================= =================
2211051
2221052 .. table:: Dedicated file reader and writers
2231053
224 ======================== ======================= ================= ==================
225 Package name Supported file formats Dependencies Python versions
226 ======================== ======================= ================= ==================
227 `pyexcel-xlsxw`_ xlsx(write only) `XlsxWriter`_ Python 2 and 3
228 `pyexcel-xlsxr`_ xlsx(read only) lxml same as above
229 `pyexcel-xlsbr`_ xlsx(read only) pyxlsb same as above
230 `pyexcel-odsr`_ read only for ods, fods lxml same as above
231 `pyexcel-odsw`_ write only for ods loxun same as above
232 `pyexcel-htmlr`_ html(read only) lxml,html5lib same as above
233 `pyexcel-pdfr`_ pdf(read only) pdftables Python 2 only.
234 ======================== ======================= ================= ==================
1054 ======================== ======================= =================
1055 Package name Supported file formats Dependencies
1056 ======================== ======================= =================
1057 `pyexcel-xlsxw`_ xlsx(write only) `XlsxWriter`_
1058 `pyexcel-libxlsxw`_ xlsx(write only) `libxlsxwriter`_
1059 `pyexcel-xlsxr`_ xlsx(read only) lxml
1060 `pyexcel-xlsbr`_ xlsb(read only) pyxlsb
1061 `pyexcel-odsr`_ read only for ods, fods lxml
1062 `pyexcel-odsw`_ write only for ods loxun
1063 `pyexcel-htmlr`_ html(read only) lxml,html5lib
1064 `pyexcel-pdfr`_ pdf(read only) camelot
1065 ======================== ======================= =================
1066
1067
1068 Plugin shopping guide
1069 ------------------------
1070
1071 Since 2020, all pyexcel-io plugins have dropped the support for python versions
1072 which are lower than 3.6. If you want to use any of those Python versions, please use pyexcel-io
1073 and its plugins versions that are lower than 0.6.0.
1074
1075
1076 Except csv files, xls, xlsx and ods files are a zip of a folder containing a lot of
1077 xml files
1078
1079 The dedicated readers for excel files can stream read
1080
1081
1082 In order to manage the list of plugins installed, you need to use pip to add or remove
1083 a plugin. When you use virtualenv, you can have different plugins per virtual
1084 environment. In the situation where you have multiple plugins that does the same thing
1085 in your environment, you need to tell pyexcel which plugin to use per function call.
1086 For example, pyexcel-ods and pyexcel-odsr, and you want to get_array to use pyexcel-odsr.
1087 You need to append get_array(..., library='pyexcel-odsr').
1088
2351089
2361090
2371091 .. _pyexcel-io: https://github.com/pyexcel/pyexcel-io
2441098 .. _pyexcel-pdfr: https://github.com/pyexcel/pyexcel-pdfr
2451099
2461100 .. _pyexcel-xlsxw: https://github.com/pyexcel/pyexcel-xlsxw
1101 .. _pyexcel-libxlsxw: https://github.com/pyexcel/pyexcel-libxlsxw
2471102 .. _pyexcel-xlsxr: https://github.com/pyexcel/pyexcel-xlsxr
2481103 .. _pyexcel-xlsbr: https://github.com/pyexcel/pyexcel-xlsbr
2491104 .. _pyexcel-htmlr: https://github.com/pyexcel/pyexcel-htmlr
2541109 .. _XlsxWriter: https://github.com/jmcnamara/XlsxWriter
2551110 .. _pyexcel-ezodf: https://github.com/pyexcel/pyexcel-ezodf
2561111 .. _odfpy: https://github.com/eea/odfpy
1112 .. _libxlsxwriter: http://libxlsxwriter.github.io/getting_started.html
2571113
2581114 .. table:: Other data renderers
2591115
2871143 .. _pyexcel-gantt: https://github.com/pyexcel/pyexcel-gantt
2881144 .. _frappe-gantt: https://github.com/frappe/gantt
2891145
290 In order to manage the list of plugins installed, you need to use pip to add or remove
291 a plugin. When you use virtualenv, you can have different plugins per virtual
292 environment. In the situation where you have multiple plugins that does the same thing
293 in your environment, you need to tell pyexcel which plugin to use per function call.
294 For example, pyexcel-ods and pyexcel-odsr, and you want to get_array to use pyexcel-odsr.
295 You need to append get_array(..., library='pyexcel-odsr').
296
2971146 .. rubric:: Footnotes
2981147
2991148 .. [#f1] zipped csv file
3071156 individual developers. This library unites only the data access code.
3081157
3091158
310 .. testcode::
311 :hide:
312
313 >>> import os
314 >>> os.unlink("your_file.xls")
315
3161159
3171160
3181161 License
00 name: pyexcel
11 organisation: pyexcel
22 releases:
3 - changes:
4 - action: Fixed
5 details:
6 - "`#250`: RecursionError raised on deepcopy of a sheet"
7 - action: Updated
8 details:
9 - "`#255`: pyexcel.get_array documentation page seems to be a copy of pyexcel.get_sheet"
10 - action: Removed
11 details:
12 - "`#249`: drop the support for dummy import statements pyexcel.ext.*"
13 version: 0.7.0
14 date: 12.2.2022
15 - changes:
16 - action: Updated
17 details:
18 - "`#243`: fix small typo."
19 - add chardet as explicit dependency
20 version: 0.6.7
21 date: 12.09.2021
22 - changes:
23 - action: Updated
24 details:
25 - "`#233`: dynamically resize the table matrix on set_value. sheet['AA1'] = 'test' will work in this release."
26 version: 0.6.6
27 date: 14.11.2020
28 - changes:
29 - action: Updated
30 details:
31 - "update queryset source to work with pyexcel-io 0.6.0"
32 version: 0.6.5
33 date: 8.10.2020
34 - changes:
35 - action: Updated
36 details:
37 - "`#219`: book created from dict no longer discards order."
38 version: 0.6.4
39 date: 18.08.2020
40 - changes:
41 - action: fixed
42 details:
43 - "`#214`: remove leading and trailing whitespace for column names"
44 - action: removed
45 details:
46 - python 2 compatibility have been permanently removed.
47 version: 0.6.3
48 date: 01.08.2020
49 - changes:
50 - action: fixed
51 details:
52 - "`#109`: Control the column order when write the data output"
53 version: 0.6.2
54 date: 8.06.2020
55 - changes:
56 - action: fixed
57 details:
58 - "`#203`: texttable was dropped out in 0.6.0 as compulsary dependency. end user may experience it when a sheet/table is printed in a shell. otherwise, new user of pyexcel won't see it. As of release date, no issues were created"
59 version: 0.6.1
60 date: 02.05.2020
61 - changes:
62 - action: updated
63 details:
64 - "`#199`: += in place; = + shall return new instance"
65 - "`#195`: documentation update. however small is welcome"
66 - action: removed
67 details:
68 - "Dropping the test support for python version lower than 3.6. v0.6.0 should work with python 2.7 but is not guaranteed to work. Please upgrade to python 3.6+."
69
70 version: 0.6.0
71 date: 21.04.2020
72 - changes:
73 - action: updated
74 details:
75 - "`#185`: fix a bug with http data source. The real fix lies in pyexcel-io v0.5.19. this release just put the version requirement in."
76 version: 0.5.15
77 date: 07.07.2019
378 - changes:
479 - action: updated
580 details:
60135 details:
61136 - '`#126`, dest_sheet_name in save_as will set the sheet name in the output'
62137 - '`#115`, Fix set membership test to run faster in python2'
63 date: unreleased
138 date: 26.03.2018
64139 version: 0.5.8
65140 - changes:
66141 - action: added
0 pyexcel (0.7.0-0kali1) UNRELEASED; urgency=low
1
2 * New upstream release.
3
4 -- Kali Janitor <[email protected]> Fri, 11 Nov 2022 02:08:08 -0000
5
06 pyexcel (0.5.14-0kali3) kali-dev; urgency=medium
17
28 [ Raphaël Hertzog ]
00 https://github.com/pyexcel/pyexcel-pygal/archive/master.zip
11 https://github.com/pyexcel/sphinxcontrib-excel/archive/master.zip
2 sphinx-copybutton
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentScriptType="application/ecmascript" contentStyleType="text/css" height="836px" preserveAspectRatio="none" style="width:982px;height:836px;" version="1.1" viewBox="0 0 982 836" width="982px" zoomAndPan="magnify"><defs><filter height="300%" id="f1qgfhkuhkkxbe" width="300%" x="-1" y="-1"><feGaussianBlur result="blurOut" stdDeviation="2.0"/><feColorMatrix in="blurOut" result="blurOut2" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .4 0"/><feOffset dx="4.0" dy="4.0" in="blurOut2" result="blurOut3"/><feBlend in="SourceGraphic" in2="blurOut3" mode="normal"/></filter></defs><g><!--cluster pyexcel--><polygon fill="#FFFFFF" filter="url(#f1qgfhkuhkkxbe)" points="88,190,147,190,154,212.4883,676,212.4883,676,365,88,365,88,190" style="stroke: #000000; stroke-width: 2.0;"/><line style="stroke: #000000; stroke-width: 2.0;" x1="88" x2="154" y1="212.4883" y2="212.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="53" x="92" y="205.5352">pyexcel</text><!--cluster plugins--><polygon fill="#FFFFFF" filter="url(#f1qgfhkuhkkxbe)" points="96,286,156,286,163,308.4883,668,308.4883,668,357,96,357,96,286" style="stroke: #000000; stroke-width: 2.0;"/><line style="stroke: #000000; stroke-width: 2.0;" x1="96" x2="163" y1="308.4883" y2="308.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="54" x="100" y="301.5352">plugins</text><!--cluster pyexcel-io--><polygon fill="#FFFFFF" filter="url(#f1qgfhkuhkkxbe)" points="14,382,96,382,103,404.4883,373,404.4883,373,670,14,670,14,382" style="stroke: #000000; stroke-width: 2.0;"/><line style="stroke: #000000; stroke-width: 2.0;" x1="14" x2="103" y1="404.4883" y2="404.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="76" x="18" y="397.5352">pyexcel-io</text><!--cluster io plugins--><polygon fill="#FFFFFF" filter="url(#f1qgfhkuhkkxbe)" points="22,591,101,591,108,613.4883,365,613.4883,365,662,22,662,22,591" style="stroke: #000000; stroke-width: 2.0;"/><line style="stroke: #000000; stroke-width: 2.0;" x1="22" x2="108" y1="613.4883" y2="613.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="73" x="26" y="606.5352">io plugins</text><!--cluster ods--><path d="M387,598 C387,595 389,593 392,593 C395,593 397,595 397,598 C397,595 399,593 402,593 C405,593 407,595 407,598 C407,595 409,593 412,593 C415,593 417,595 417,598 C417,595 419,593 422,593 C425,593 427,595 427,598 C427,595 429,593 432,593 C435,593 437,595 437,598 C437,595 439,593 442,593 C445,593 447,595 447,598 C447,595 449,593 452,593 C455,593 457,595 457,598 C457,595 459,593 462,593 C465,593 467,595 467,598 C467,595 469,593 472,593 C475,593 477,595 477,598 C477,595 479,593 482,593 C485,593 487,595 487,598 C487,595 489,593 492,593 C495,593 497,595 497,598 C497,595 499,593 502,593 C505,593 507,595 507,598 C507,595 509,593 512,593 C515,593 517,595 517,598 C517,595 519,593 522,593 C525,593 527,595 527,598 C527,595 529,593 532,593 C535,593 537,595 537,598 C537,595 539,593 542,593 C545,593 547,595 547,598 C547,595 549,593 552,593 C555,593 557,595 557,598 C557,595 559,593 562,593 C565,593 567,595 567,598 C567,595 569,593 572,593 C575,593 577,595 577,598 C577,595 579,593 582,593 C585,593 587,595 587,598 C587,595 589,593 592,593 C595,593 597,595 597,598 C597,595 599,593 602,593 C605,593 607,595 607,598 C607,595 609,593 612,593 C615,593 617,595 617,598 C617,595 619,593 622,593 C625,593 627,595 627,598 C627,595 629,593 632,593 C635,593 637,595 637,598 C637,595 639,593 642,593 C645,593 647,595 647,598 C647,595 649,593 652,593 C655,593 657,595 657,598 C657,595 659,593 662,593 C665,593 667,595 667,598 C667,595 669,593 672,593 C675,593 677,595 677,598 C677,595 679,593 682,593 C685,593 687,595 687,598 C687,595 689,593 692,593 C695,593 697,595 697,598 C697,595 699,593 702,593 C705,593 707,595 707,598 C707,595 709,593 712,593 C715,593 717,595 717,598 C717,595 719,593 722,593 C725,593 727,595 727,598 C727,595 729,593 732,593 C735,593 737,595 737,598 C737,595 739,593 742,593 C745,593 747,595 747,598 C747,595 749,593 752,593 C755,593 757,595 757,598 C757,595 759,593 762,593 C765,593 767,595 767,598 C767,595 769,593 772,593 C775,593 777,595 777,598 C777,595 779,593 782,593 C785,593 787,595 787,598 C787,595 789,593 792,593 C795,593 797,595 797,598 C800,598 802,600 802,603 C802,606 800,608 797,608 C800,608 802,610 802,613 C802,616 800,618 797,618 C800,618 802,620 802,623 C802,626 800,628 797,628 C800,628 802,630 802,633 C802,636 800,638 797,638 C800,638 802,640 802,643 C802,646 800,648 797,648 C800,648 802,650 802,653 C802,656 800,658 797,658 C797,661 794,663 792,663 C789,663 787,661 787,658 C787,661 784,663 782,663 C779,663 777,661 777,658 C777,661 774,663 772,663 C769,663 767,661 767,658 C767,661 764,663 762,663 C759,663 757,661 757,658 C757,661 754,663 752,663 C749,663 747,661 747,658 C747,661 744,663 742,663 C739,663 737,661 737,658 C737,661 734,663 732,663 C729,663 727,661 727,658 C727,661 724,663 722,663 C719,663 717,661 717,658 C717,661 714,663 712,663 C709,663 707,661 707,658 C707,661 704,663 702,663 C699,663 697,661 697,658 C697,661 694,663 692,663 C689,663 687,661 687,658 C687,661 684,663 682,663 C679,663 677,661 677,658 C677,661 674,663 672,663 C669,663 667,661 667,658 C667,661 664,663 662,663 C659,663 657,661 657,658 C657,661 654,663 652,663 C649,663 647,661 647,658 C647,661 644,663 642,663 C639,663 637,661 637,658 C637,661 634,663 632,663 C629,663 627,661 627,658 C627,661 624,663 622,663 C619,663 617,661 617,658 C617,661 614,663 612,663 C609,663 607,661 607,658 C607,661 604,663 602,663 C599,663 597,661 597,658 C597,661 594,663 592,663 C589,663 587,661 587,658 C587,661 584,663 582,663 C579,663 577,661 577,658 C577,661 574,663 572,663 C569,663 567,661 567,658 C567,661 564,663 562,663 C559,663 557,661 557,658 C557,661 554,663 552,663 C549,663 547,661 547,658 C547,661 544,663 542,663 C539,663 537,661 537,658 C537,661 534,663 532,663 C529,663 527,661 527,658 C527,661 524,663 522,663 C519,663 517,661 517,658 C517,661 514,663 512,663 C509,663 507,661 507,658 C507,661 504,663 502,663 C499,663 497,661 497,658 C497,661 494,663 492,663 C489,663 487,661 487,658 C487,661 484,663 482,663 C479,663 477,661 477,658 C477,661 474,663 472,663 C469,663 467,661 467,658 C467,661 464,663 462,663 C459,663 457,661 457,658 C457,661 454,663 452,663 C449,663 447,661 447,658 C447,661 444,663 442,663 C439,663 437,661 437,658 C437,661 434,663 432,663 C429,663 427,661 427,658 C427,661 424,663 422,663 C419,663 417,661 417,658 C417,661 414,663 412,663 C409,663 407,661 407,658 C407,661 404,663 402,663 C399,663 397,661 397,658 C397,661 394,663 392,663 C389,663 387,661 387,658 C384,658 382,656 382,653 C382,650 384,648 387,648 C384,648 382,646 382,643 C382,640 384,638 387,638 C384,638 382,636 382,633 C382,630 384,628 387,628 C384,628 382,626 382,623 C382,620 384,618 387,618 C384,618 382,616 382,613 C382,610 384,608 387,608 C384,608 382,606 382,603 C382,600 384,598 387,598 " fill="#FFFFFF" filter="url(#f1qgfhkuhkkxbe)" style="stroke: #000000; stroke-width: 2.0;"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="26" x="580" y="617.5352">ods</text><!--cluster xls, xlsx--><path d="M394,389 C394,386 396,384 399,384 C402,384 404,386 404,389 C404,386 406,384 409,384 C412,384 414,386 414,389 C414,386 416,384 419,384 C422,384 424,386 424,389 C424,386 426,384 429,384 C432,384 434,386 434,389 C434,386 436,384 439,384 C442,384 444,386 444,389 C444,386 446,384 449,384 C452,384 454,386 454,389 C454,386 456,384 459,384 C462,384 464,386 464,389 C464,386 466,384 469,384 C472,384 474,386 474,389 C474,386 476,384 479,384 C482,384 484,386 484,389 C484,386 486,384 489,384 C492,384 494,386 494,389 C494,386 496,384 499,384 C502,384 504,386 504,389 C504,386 506,384 509,384 C512,384 514,386 514,389 C514,386 516,384 519,384 C522,384 524,386 524,389 C524,386 526,384 529,384 C532,384 534,386 534,389 C534,386 536,384 539,384 C542,384 544,386 544,389 C544,386 546,384 549,384 C552,384 554,386 554,389 C554,386 556,384 559,384 C562,384 564,386 564,389 C564,386 566,384 569,384 C572,384 574,386 574,389 C574,386 576,384 579,384 C582,384 584,386 584,389 C584,386 586,384 589,384 C592,384 594,386 594,389 C594,386 596,384 599,384 C602,384 604,386 604,389 C604,386 606,384 609,384 C612,384 614,386 614,389 C614,386 616,384 619,384 C622,384 624,386 624,389 C624,386 626,384 629,384 C632,384 634,386 634,389 C634,386 636,384 639,384 C642,384 644,386 644,389 C644,386 646,384 649,384 C652,384 654,386 654,389 C654,386 656,384 659,384 C662,384 664,386 664,389 C664,386 666,384 669,384 C672,384 674,386 674,389 C674,386 676,384 679,384 C682,384 684,386 684,389 C684,386 686,384 689,384 C692,384 694,386 694,389 C694,386 696,384 699,384 C702,384 704,386 704,389 C704,386 706,384 709,384 C712,384 714,386 714,389 C714,386 716,384 719,384 C722,384 724,386 724,389 C724,386 726,384 729,384 C732,384 734,386 734,389 C734,386 736,384 739,384 C742,384 744,386 744,389 C744,386 746,384 749,384 C752,384 754,386 754,389 C754,386 756,384 759,384 C762,384 764,386 764,389 C764,386 766,384 769,384 C772,384 774,386 774,389 C774,386 776,384 779,384 C782,384 784,386 784,389 C784,386 786,384 789,384 C792,384 794,386 794,389 C794,386 796,384 799,384 C802,384 804,386 804,389 C807,389 809,391 809,394 C809,397 807,399 804,399 C807,399 809,401 809,404 C809,407 807,409 804,409 C807,409 809,411 809,414 C809,417 807,419 804,419 C807,419 809,421 809,424 C809,427 807,429 804,429 C807,429 809,431 809,434 C809,437 807,439 804,439 C807,439 809,441 809,444 C809,447 807,449 804,449 C804,452 801,454 799,454 C796,454 794,452 794,449 C794,452 791,454 789,454 C786,454 784,452 784,449 C784,452 781,454 779,454 C776,454 774,452 774,449 C774,452 771,454 769,454 C766,454 764,452 764,449 C764,452 761,454 759,454 C756,454 754,452 754,449 C754,452 751,454 749,454 C746,454 744,452 744,449 C744,452 741,454 739,454 C736,454 734,452 734,449 C734,452 731,454 729,454 C726,454 724,452 724,449 C724,452 721,454 719,454 C716,454 714,452 714,449 C714,452 711,454 709,454 C706,454 704,452 704,449 C704,452 701,454 699,454 C696,454 694,452 694,449 C694,452 691,454 689,454 C686,454 684,452 684,449 C684,452 681,454 679,454 C676,454 674,452 674,449 C674,452 671,454 669,454 C666,454 664,452 664,449 C664,452 661,454 659,454 C656,454 654,452 654,449 C654,452 651,454 649,454 C646,454 644,452 644,449 C644,452 641,454 639,454 C636,454 634,452 634,449 C634,452 631,454 629,454 C626,454 624,452 624,449 C624,452 621,454 619,454 C616,454 614,452 614,449 C614,452 611,454 609,454 C606,454 604,452 604,449 C604,452 601,454 599,454 C596,454 594,452 594,449 C594,452 591,454 589,454 C586,454 584,452 584,449 C584,452 581,454 579,454 C576,454 574,452 574,449 C574,452 571,454 569,454 C566,454 564,452 564,449 C564,452 561,454 559,454 C556,454 554,452 554,449 C554,452 551,454 549,454 C546,454 544,452 544,449 C544,452 541,454 539,454 C536,454 534,452 534,449 C534,452 531,454 529,454 C526,454 524,452 524,449 C524,452 521,454 519,454 C516,454 514,452 514,449 C514,452 511,454 509,454 C506,454 504,452 504,449 C504,452 501,454 499,454 C496,454 494,452 494,449 C494,452 491,454 489,454 C486,454 484,452 484,449 C484,452 481,454 479,454 C476,454 474,452 474,449 C474,452 471,454 469,454 C466,454 464,452 464,449 C464,452 461,454 459,454 C456,454 454,452 454,449 C454,452 451,454 449,454 C446,454 444,452 444,449 C444,452 441,454 439,454 C436,454 434,452 434,449 C434,452 431,454 429,454 C426,454 424,452 424,449 C424,452 421,454 419,454 C416,454 414,452 414,449 C414,452 411,454 409,454 C406,454 404,452 404,449 C404,452 401,454 399,454 C396,454 394,452 394,449 C391,449 389,447 389,444 C389,441 391,439 394,439 C391,439 389,437 389,434 C389,431 391,429 394,429 C391,429 389,427 389,424 C389,421 391,419 394,419 C391,419 389,417 389,414 C389,411 391,409 394,409 C391,409 389,407 389,404 C389,401 391,399 394,399 C391,399 389,397 389,394 C389,391 391,389 394,389 " fill="#FFFFFF" filter="url(#f1qgfhkuhkkxbe)" style="stroke: #000000; stroke-width: 2.0;"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="58" x="570" y="408.5352">xls, xlsx</text><!--entity pyexcel public api--><rect fill="#FEFECE" filter="url(#f1qgfhkuhkkxbe)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="143" x="96.5" y="217"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="91.5" y="222"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="91.5" y="243.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="123" x="106.5" y="240.5352">pyexcel public api</text><!--entity pyexcel internal api--><rect fill="#FEFECE" filter="url(#f1qgfhkuhkkxbe)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="154" x="275" y="217"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="270" y="222"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="270" y="243.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="134" x="285" y="240.5352">pyexcel internal api</text><!--entity pyexcel plugin interfaces--><rect fill="#FEFECE" filter="url(#f1qgfhkuhkkxbe)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="191" x="464.5" y="217"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="459.5" y="222"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="459.5" y="243.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="171" x="474.5" y="240.5352">pyexcel plugin interfaces</text><!--entity data source plugins--><rect fill="#FEFECE" filter="url(#f1qgfhkuhkkxbe)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="155" x="504.5" y="313"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="499.5" y="318"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="499.5" y="339.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="135" x="514.5" y="336.5352">data source plugins</text><!--entity data renderer plugins--><rect fill="#FEFECE" filter="url(#f1qgfhkuhkkxbe)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="169" x="300.5" y="313"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="295.5" y="318"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="295.5" y="339.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="149" x="310.5" y="336.5352">data renderer plugins</text><!--entity data parsing plugins--><rect fill="#FEFECE" filter="url(#f1qgfhkuhkkxbe)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="161" x="104.5" y="313"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="99.5" y="318"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="99.5" y="339.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="141" x="114.5" y="336.5352">data parsing plugins</text><!--entity pyexcel-io public api--><rect fill="#FEFECE" filter="url(#f1qgfhkuhkkxbe)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="164" x="177" y="409"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="172" y="414"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="172" y="435.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="144" x="187" y="432.5352">pyexcel-io public api</text><!--entity pyexcel-io plugin interfaces--><rect fill="#FEFECE" filter="url(#f1qgfhkuhkkxbe)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="212" x="153" y="513.5"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="148" y="518.5"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="148" y="539.9883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="192" x="163" y="537.0352">pyexcel-io plugin interfaces</text><!--entity csv, csvz, tsv, tsvz--><rect fill="#FEFECE" filter="url(#f1qgfhkuhkkxbe)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="140" x="217" y="618"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="212" y="623"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="212" y="644.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="120" x="227" y="641.5352">csv, csvz, tsv, tsvz</text><!--entity django, sqlalchemy--><rect fill="#FEFECE" filter="url(#f1qgfhkuhkkxbe)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="152" x="30" y="618"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="25" y="623"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="25" y="644.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="132" x="40" y="641.5352">django, sqlalchemy</text><!--entity pyexcel-ods--><rect fill="#FEFECE" filter="url(#f1qgfhkuhkkxbe)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="105" x="392.5" y="618"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="387.5" y="623"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="387.5" y="644.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="85" x="402.5" y="641.5352">pyexcel-ods</text><!--entity pyexcel-ods3--><rect fill="#FEFECE" filter="url(#f1qgfhkuhkkxbe)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="114" x="533" y="618"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="528" y="623"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="528" y="644.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="94" x="543" y="641.5352">pyexcel-ods3</text><!--entity pyexcel-odsr--><rect fill="#FEFECE" filter="url(#f1qgfhkuhkkxbe)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="111" x="682.5" y="618"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="677.5" y="623"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="677.5" y="644.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="91" x="692.5" y="641.5352">pyexcel-odsr</text><!--entity pyexcel-xls--><rect fill="#FEFECE" filter="url(#f1qgfhkuhkkxbe)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="100" x="554" y="409"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="549" y="414"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="549" y="435.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="80" x="564" y="432.5352">pyexcel-xls</text><!--entity pyexcel-xlsx--><rect fill="#FEFECE" filter="url(#f1qgfhkuhkkxbe)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="109" x="689.5" y="409"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="684.5" y="414"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="684.5" y="435.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="89" x="699.5" y="432.5352">pyexcel-xlsx</text><!--entity pyexcel-xlsxw--><rect fill="#FEFECE" filter="url(#f1qgfhkuhkkxbe)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="120" x="399" y="409"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="394" y="414"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="394" y="435.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="100" x="409" y="432.5352">pyexcel-xlsxw</text><!--entity tabulate--><rect fill="#FFFFFF" filter="url(#f1qgfhkuhkkxbe)" height="52.9766" style="stroke: #A80036; stroke-width: 1.5;" width="117" x="651.5" y="8"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="646.5" y="13"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="646.5" y="50.9766"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="97" x="661.5" y="31.5352">«dependency»</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="56" x="682" y="48.0234">tabulate</text><!--entity pygal--><rect fill="#FFFFFF" filter="url(#f1qgfhkuhkkxbe)" height="52.9766" style="stroke: #A80036; stroke-width: 1.5;" width="117" x="499.5" y="8"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="494.5" y="13"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="494.5" y="50.9766"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="97" x="509.5" y="31.5352">«dependency»</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="37" x="539.5" y="48.0234">pygal</text><!--entity handsontable--><rect fill="#FFFFFF" filter="url(#f1qgfhkuhkkxbe)" height="52.9766" style="stroke: #A80036; stroke-width: 1.5;" width="117" x="320.5" y="8"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="315.5" y="13"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="315.5" y="50.9766"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="97" x="330.5" y="31.5352">«dependency»</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="94" x="332" y="48.0234">handsontable</text><!--entity xlrd--><rect fill="#FFFFFF" filter="url(#f1qgfhkuhkkxbe)" height="52.9766" style="stroke: #A80036; stroke-width: 1.5;" width="117" x="552.5" y="505"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="547.5" y="510"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="547.5" y="547.9766"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="97" x="562.5" y="528.5352">«dependency»</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="28" x="597" y="545.0234">xlrd</text><!--entity xlwt--><rect fill="#FFFFFF" filter="url(#f1qgfhkuhkkxbe)" height="52.9766" style="stroke: #A80036; stroke-width: 1.5;" width="117" x="704.5" y="505"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="699.5" y="510"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="699.5" y="547.9766"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="97" x="714.5" y="528.5352">«dependency»</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="29" x="748.5" y="545.0234">xlwt</text><!--entity openpyxl--><rect fill="#FFFFFF" filter="url(#f1qgfhkuhkkxbe)" height="52.9766" style="stroke: #A80036; stroke-width: 1.5;" width="117" x="856.5" y="505"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="851.5" y="510"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="851.5" y="547.9766"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="97" x="866.5" y="528.5352">«dependency»</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="64" x="883" y="545.0234">openpyxl</text><!--entity xlsxwriter--><rect fill="#FFFFFF" filter="url(#f1qgfhkuhkkxbe)" height="52.9766" style="stroke: #A80036; stroke-width: 1.5;" width="117" x="400.5" y="505"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="395.5" y="510"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="395.5" y="547.9766"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="97" x="410.5" y="528.5352">«dependency»</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="69" x="424.5" y="545.0234">xlsxwriter</text><!--entity ezodf--><rect fill="#FFFFFF" filter="url(#f1qgfhkuhkkxbe)" height="52.9766" style="stroke: #A80036; stroke-width: 1.5;" width="117" x="535.5" y="775"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="530.5" y="780"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="530.5" y="817.9766"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="97" x="545.5" y="798.5352">«dependency»</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="39" x="574.5" y="815.0234">ezodf</text><!--entity odfpy--><rect fill="#FFFFFF" filter="url(#f1qgfhkuhkkxbe)" height="52.9766" style="stroke: #A80036; stroke-width: 1.5;" width="117" x="383.5" y="775"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="378.5" y="780"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="378.5" y="817.9766"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="97" x="393.5" y="798.5352">«dependency»</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="39" x="422.5" y="815.0234">odfpy</text><!--entity pyexcel-text--><rect fill="#FEFECE" filter="url(#f1qgfhkuhkkxbe)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="107" x="655.5" y="121"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="650.5" y="126"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="650.5" y="147.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="87" x="665.5" y="144.5352">pyexcel-text</text><!--entity pyexcel-pygal--><rect fill="#FEFECE" filter="url(#f1qgfhkuhkkxbe)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="117" x="501.5" y="121"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="496.5" y="126"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="496.5" y="147.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="97" x="511.5" y="144.5352">pyexcel-pygal</text><!--entity pyexcel-handsontable--><rect fill="#FEFECE" filter="url(#f1qgfhkuhkkxbe)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="174" x="292" y="121"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="287" y="126"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="287" y="147.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="154" x="302" y="144.5352">pyexcel-handsontable</text><!--link pyexcel public api to pyexcel internal api--><path d="M239.5156,235 C249.4935,235 259.4713,235 269.4492,235 " fill="none" id="pyexcel public api-pyexcel internal api" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="274.667,235,265.667,231,269.667,235,265.667,239,274.667,235" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel internal api to pyexcel plugin interfaces--><path d="M429.1875,235 C439.1956,235 449.2038,235 459.2119,235 " fill="none" id="pyexcel internal api-pyexcel plugin interfaces" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="464.4456,235,455.4456,231,459.4456,235,455.4456,239,464.4456,235" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-text to pyexcel plugin interfaces--><path d="M676.5042,159.9369 C650.1199,176.9362 613.2948,200.6624 588.153,216.8612 " fill="none" id="pyexcel-text-pyexcel plugin interfaces" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="680.9742,157.0569,671.2421,158.569,676.7711,159.765,675.5751,165.294,680.9742,157.0569" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-pygal to pyexcel plugin interfaces--><path d="M560,162.0611 C560,178.8106 560,201.1694 560,216.6977 " fill="none" id="pyexcel-pygal-pyexcel plugin interfaces" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="560,157.0569,556,166.0569,560,162.0569,564,166.0569,560,157.0569" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-handsontable to pyexcel plugin interfaces--><path d="M417.6827,159.5168 C449.7852,176.5435 495.0069,200.5285 525.8007,216.8612 " fill="none" id="pyexcel-handsontable-pyexcel plugin interfaces" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="413.0447,157.0569,419.1213,164.8077,417.4618,159.3998,422.8698,157.7403,413.0447,157.0569" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel plugin interfaces to data source plugins--><path d="M564.138,253.0569 C567.68,268.5126 572.8023,290.8647 576.6542,307.6731 " fill="none" id="pyexcel plugin interfaces-data source plugins" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="577.8057,312.6977,579.694,303.0316,576.6887,307.8241,571.8962,304.8187,577.8057,312.6977" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel plugin interfaces to data renderer plugins--><path d="M527.0838,253.0569 C497.3576,269.3638 453.6371,293.3476 422.5564,310.3976 " fill="none" id="pyexcel plugin interfaces-data renderer plugins" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="418.0656,312.8612,427.8801,312.0394,422.4493,310.4564,424.0324,305.0255,418.0656,312.8612" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel plugin interfaces to data parsing plugins--><path d="M464.174,250.0484 C397.9962,260.9344 316.1607,275.5139 283,286 C262.9306,292.3464 241.5873,301.7978 223.9599,310.403 " fill="none" id="pyexcel plugin interfaces-data parsing plugins" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="219.0908,312.8043,228.9318,312.4109,223.5751,310.5927,225.3933,305.236,219.0908,312.8043" style="stroke: #A80036; stroke-width: 1.0;"/><!--link data renderer plugins to pyexcel-io public api--><path d="M361.3004,349.0569 C340.2277,365.1122 309.3878,388.6093 287.083,405.6035 " fill="none" id="data renderer plugins-pyexcel-io public api" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="283.0218,408.6977,292.6049,406.4253,286.999,405.6676,287.7567,400.0617,283.0218,408.6977" style="stroke: #A80036; stroke-width: 1.0;"/><!--link data parsing plugins to pyexcel-io public api--><path d="M198.9188,349.0569 C211.0381,364.7791 228.6581,387.6375 241.6851,404.5375 " fill="none" id="data parsing plugins-pyexcel-io public api" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="244.892,408.6977,242.5654,399.1276,241.8394,404.7377,236.2293,404.0117,244.892,408.6977" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-io public api to pyexcel-io plugin interfaces--><path d="M259,445.2025 C259,462.6276 259,489.0009 259,508.0045 " fill="none" id="pyexcel-io public api-pyexcel-io plugin interfaces" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="259,513.1555,263,504.1555,259,508.1555,255,504.1555,259,513.1555" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-io plugin interfaces to csv, csvz, tsv, tsvz--><path d="M263.8772,549.7025 C268.5461,567.1276 275.6127,593.5009 280.7045,612.5045 " fill="none" id="pyexcel-io plugin interfaces-csv, csvz, tsv, tsvz" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="282.0847,617.6555,283.619,607.9269,280.7906,612.8259,275.8916,609.9975,282.0847,617.6555" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-io plugin interfaces to django, sqlalchemy--><path d="M232.3494,549.7025 C205.7746,567.8533 164.9845,595.7132 136.9817,614.8393 " fill="none" id="pyexcel-io plugin interfaces-django, sqlalchemy" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="132.6014,617.8311,142.2893,616.058,136.7302,615.011,137.7772,609.4519,132.6014,617.8311" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-xls to pyexcel-io plugin interfaces--><path d="M553.5505,446.8598 C547.6608,449.0087 541.7163,451.1014 536,453 C466.3876,476.1211 385.5607,498.5299 329.2517,513.4452 " fill="none" id="pyexcel-xls-pyexcel-io plugin interfaces" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="558.4787,445.0447,548.6508,444.4019,553.7868,446.7728,551.4159,451.9088,558.4787,445.0447" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-xlsx to pyexcel-io plugin interfaces--><path d="M692.7396,446.6976 C685.8249,449.0046 678.7788,451.1801 672,453 C545.9562,486.8386 511.0517,479.8037 383,505 C369.6082,507.6351 355.4298,510.5481 341.6314,513.45 " fill="none" id="pyexcel-xlsx-pyexcel-io plugin interfaces" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="697.5007,445.0822,687.6927,444.1859,692.7658,446.6886,690.263,451.7617,697.5007,445.0822" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-ods to pyexcel-io plugin interfaces--><path d="M412.8197,615.1442 C400.4927,607.3524 386.2173,598.5645 373,591 C347.4233,576.3619 317.9135,561.0002 295.3266,549.5528 " fill="none" id="pyexcel-ods-pyexcel-io plugin interfaces" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="417.2674,617.9657,411.8101,609.767,413.0452,615.2874,407.5249,616.5225,417.2674,617.9657" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-io plugin interfaces to pyexcel-ods3--><path d="M342.4549,549.5402 C410.8275,564.5457 498.8469,584.4658 515,591 C530.5558,597.2926 546.6572,606.5378 559.9343,615.014 " fill="none" id="pyexcel-io plugin interfaces-pyexcel-ods3" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="564.3239,617.8537,558.94,609.6067,560.1258,615.1379,554.5946,616.3237,564.3239,617.8537" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-io plugin interfaces to pyexcel-xlsxw--><path d="M293.7731,513.3311 C328.7742,495.043 382.6867,466.8737 419.3342,447.7254 " fill="none" id="pyexcel-io plugin interfaces-pyexcel-xlsxw" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="424.1627,445.2025,414.3335,445.825,419.7311,447.5179,418.0382,452.9155,424.1627,445.2025" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-io plugin interfaces to pyexcel-odsr--><path d="M337.8367,549.51 C352.7883,552.5954 368.3388,555.5811 383,558 C507.5053,578.5421 545.8036,549.578 665,591 C681.0463,596.5762 697.2905,606.1213 710.3686,614.9895 " fill="none" id="pyexcel-io plugin interfaces-pyexcel-odsr" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="714.6775,617.9643,709.5436,609.5593,710.5628,615.1236,704.9985,616.1428,714.6775,617.9643" style="stroke: #A80036; stroke-width: 1.0;"/><!--link tabulate to pyexcel-text--><path d="M709.7451,61.1401 C709.5657,79.8859 709.3304,104.4746 709.1726,120.9587 " fill="none" id="tabulate-pyexcel-text" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pygal to pyexcel-pygal--><path d="M558.5099,61.1401 C558.8686,79.8859 559.3392,104.4746 559.6547,120.9587 " fill="none" id="pygal-pyexcel-pygal" style="stroke: #A80036; stroke-width: 1.0;"/><!--link handsontable to pyexcel-handsontable--><path d="M379,61.1401 C379,79.8859 379,104.4746 379,120.9587 " fill="none" id="handsontable-pyexcel-handsontable" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-xls to xlrd--><path d="M605.2193,445.2025 C606.3239,461.6929 607.9654,486.1973 609.2172,504.8852 " fill="none" id="pyexcel-xls-xlrd" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-xls to xlwt--><path d="M631.6957,445.2025 C656.7863,461.6929 694.0705,486.1973 722.5048,504.8852 " fill="none" id="pyexcel-xls-xlwt" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-xlsx to openpyxl--><path d="M773.7859,445.2025 C800.7702,461.6929 840.8683,486.1973 871.4486,504.8852 " fill="none" id="pyexcel-xlsx-openpyxl" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-xlsxw to xlsxwriter--><path d="M459,445.2025 C459,461.6929 459,486.1973 459,504.8852 " fill="none" id="pyexcel-xlsxw-xlsxwriter" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-ods3 to ezodf--><path d="M590.4357,654.0265 C591.1403,683.1809 592.5337,740.8327 593.3541,774.7745 " fill="none" id="pyexcel-ods3-ezodf" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-ods to odfpy--><path d="M444.6732,654.0265 C444.1448,683.1809 443.0997,740.8327 442.4844,774.7745 " fill="none" id="pyexcel-ods-odfpy" style="stroke: #A80036; stroke-width: 1.0;"/><!--
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentScriptType="application/ecmascript" contentStyleType="text/css" height="853px" preserveAspectRatio="none" style="width:1340px;height:853px;" version="1.1" viewBox="0 0 1340 853" width="1340px" zoomAndPan="magnify"><defs><filter height="300%" id="fiw5od00ir7id" width="300%" x="-1" y="-1"><feGaussianBlur result="blurOut" stdDeviation="2.0"/><feColorMatrix in="blurOut" result="blurOut2" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .4 0"/><feOffset dx="4.0" dy="4.0" in="blurOut2" result="blurOut3"/><feBlend in="SourceGraphic" in2="blurOut3" mode="normal"/></filter></defs><g><!--cluster pyexcel--><polygon fill="#FFFFFF" filter="url(#fiw5od00ir7id)" points="14,190,73,190,80,212.4883,602,212.4883,602,373.5,14,373.5,14,190" style="stroke: #000000; stroke-width: 2.0;"/><line style="stroke: #000000; stroke-width: 2.0;" x1="14" x2="80" y1="212.4883" y2="212.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="53" x="18" y="205.5352">pyexcel</text><!--cluster plugins--><polygon fill="#FFFFFF" filter="url(#fiw5od00ir7id)" points="22,294.5,82,294.5,89,316.9883,594,316.9883,594,365.5,22,365.5,22,294.5" style="stroke: #000000; stroke-width: 2.0;"/><line style="stroke: #000000; stroke-width: 2.0;" x1="22" x2="89" y1="316.9883" y2="316.9883"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="54" x="26" y="310.0352">plugins</text><!--cluster pyexcel-io--><polygon fill="#FFFFFF" filter="url(#fiw5od00ir7id)" points="228,399,310,399,317,421.4883,587,421.4883,587,687,228,687,228,399" style="stroke: #000000; stroke-width: 2.0;"/><line style="stroke: #000000; stroke-width: 2.0;" x1="228" x2="317" y1="421.4883" y2="421.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="76" x="232" y="414.5352">pyexcel-io</text><!--cluster io plugins--><polygon fill="#FFFFFF" filter="url(#fiw5od00ir7id)" points="236,608,315,608,322,630.4883,579,630.4883,579,679,236,679,236,608" style="stroke: #000000; stroke-width: 2.0;"/><line style="stroke: #000000; stroke-width: 2.0;" x1="236" x2="322" y1="630.4883" y2="630.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="73" x="240" y="623.5352">io plugins</text><!--cluster ods--><path d="M601,615 C601,612 603,610 606,610 C609,610 611,612 611,615 C611,612 613,610 616,610 C619,610 621,612 621,615 C621,612 623,610 626,610 C629,610 631,612 631,615 C631,612 633,610 636,610 C639,610 641,612 641,615 C641,612 643,610 646,610 C649,610 651,612 651,615 C651,612 653,610 656,610 C659,610 661,612 661,615 C661,612 663,610 666,610 C669,610 671,612 671,615 C671,612 673,610 676,610 C679,610 681,612 681,615 C681,612 683,610 686,610 C689,610 691,612 691,615 C691,612 693,610 696,610 C699,610 701,612 701,615 C701,612 703,610 706,610 C709,610 711,612 711,615 C711,612 713,610 716,610 C719,610 721,612 721,615 C721,612 723,610 726,610 C729,610 731,612 731,615 C731,612 733,610 736,610 C739,610 741,612 741,615 C741,612 743,610 746,610 C749,610 751,612 751,615 C751,612 753,610 756,610 C759,610 761,612 761,615 C761,612 763,610 766,610 C769,610 771,612 771,615 C771,612 773,610 776,610 C779,610 781,612 781,615 C781,612 783,610 786,610 C789,610 791,612 791,615 C791,612 793,610 796,610 C799,610 801,612 801,615 C801,612 803,610 806,610 C809,610 811,612 811,615 C811,612 813,610 816,610 C819,610 821,612 821,615 C821,612 823,610 826,610 C829,610 831,612 831,615 C831,612 833,610 836,610 C839,610 841,612 841,615 C841,612 843,610 846,610 C849,610 851,612 851,615 C851,612 853,610 856,610 C859,610 861,612 861,615 C861,612 863,610 866,610 C869,610 871,612 871,615 C871,612 873,610 876,610 C879,610 881,612 881,615 C881,612 883,610 886,610 C889,610 891,612 891,615 C891,612 893,610 896,610 C899,610 901,612 901,615 C901,612 903,610 906,610 C909,610 911,612 911,615 C911,612 913,610 916,610 C919,610 921,612 921,615 C921,612 923,610 926,610 C929,610 931,612 931,615 C931,612 933,610 936,610 C939,610 941,612 941,615 C941,612 943,610 946,610 C949,610 951,612 951,615 C951,612 953,610 956,610 C959,610 961,612 961,615 C961,612 963,610 966,610 C969,610 971,612 971,615 C971,612 973,610 976,610 C979,610 981,612 981,615 C981,612 983,610 986,610 C989,610 991,612 991,615 C991,612 993,610 996,610 C999,610 1001,612 1001,615 C1001,612 1003,610 1006,610 C1009,610 1011,612 1011,615 C1014,615 1016,617 1016,620 C1016,623 1014,625 1011,625 C1014,625 1016,627 1016,630 C1016,633 1014,635 1011,635 C1014,635 1016,637 1016,640 C1016,643 1014,645 1011,645 C1014,645 1016,647 1016,650 C1016,653 1014,655 1011,655 C1014,655 1016,657 1016,660 C1016,663 1014,665 1011,665 C1014,665 1016,667 1016,670 C1016,673 1014,675 1011,675 C1011,678 1008,680 1006,680 C1003,680 1001,678 1001,675 C1001,678 998,680 996,680 C993,680 991,678 991,675 C991,678 988,680 986,680 C983,680 981,678 981,675 C981,678 978,680 976,680 C973,680 971,678 971,675 C971,678 968,680 966,680 C963,680 961,678 961,675 C961,678 958,680 956,680 C953,680 951,678 951,675 C951,678 948,680 946,680 C943,680 941,678 941,675 C941,678 938,680 936,680 C933,680 931,678 931,675 C931,678 928,680 926,680 C923,680 921,678 921,675 C921,678 918,680 916,680 C913,680 911,678 911,675 C911,678 908,680 906,680 C903,680 901,678 901,675 C901,678 898,680 896,680 C893,680 891,678 891,675 C891,678 888,680 886,680 C883,680 881,678 881,675 C881,678 878,680 876,680 C873,680 871,678 871,675 C871,678 868,680 866,680 C863,680 861,678 861,675 C861,678 858,680 856,680 C853,680 851,678 851,675 C851,678 848,680 846,680 C843,680 841,678 841,675 C841,678 838,680 836,680 C833,680 831,678 831,675 C831,678 828,680 826,680 C823,680 821,678 821,675 C821,678 818,680 816,680 C813,680 811,678 811,675 C811,678 808,680 806,680 C803,680 801,678 801,675 C801,678 798,680 796,680 C793,680 791,678 791,675 C791,678 788,680 786,680 C783,680 781,678 781,675 C781,678 778,680 776,680 C773,680 771,678 771,675 C771,678 768,680 766,680 C763,680 761,678 761,675 C761,678 758,680 756,680 C753,680 751,678 751,675 C751,678 748,680 746,680 C743,680 741,678 741,675 C741,678 738,680 736,680 C733,680 731,678 731,675 C731,678 728,680 726,680 C723,680 721,678 721,675 C721,678 718,680 716,680 C713,680 711,678 711,675 C711,678 708,680 706,680 C703,680 701,678 701,675 C701,678 698,680 696,680 C693,680 691,678 691,675 C691,678 688,680 686,680 C683,680 681,678 681,675 C681,678 678,680 676,680 C673,680 671,678 671,675 C671,678 668,680 666,680 C663,680 661,678 661,675 C661,678 658,680 656,680 C653,680 651,678 651,675 C651,678 648,680 646,680 C643,680 641,678 641,675 C641,678 638,680 636,680 C633,680 631,678 631,675 C631,678 628,680 626,680 C623,680 621,678 621,675 C621,678 618,680 616,680 C613,680 611,678 611,675 C611,678 608,680 606,680 C603,680 601,678 601,675 C598,675 596,673 596,670 C596,667 598,665 601,665 C598,665 596,663 596,660 C596,657 598,655 601,655 C598,655 596,653 596,650 C596,647 598,645 601,645 C598,645 596,643 596,640 C596,637 598,635 601,635 C598,635 596,633 596,630 C596,627 598,625 601,625 C598,625 596,623 596,620 C596,617 598,615 601,615 " fill="#FFFFFF" filter="url(#fiw5od00ir7id)" style="stroke: #000000; stroke-width: 2.0;"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="26" x="794" y="634.5352">ods</text><!--cluster xls, xlsx--><path d="M598,406 C598,403 600,401 603,401 C606,401 608,403 608,406 C608,403 610,401 613,401 C616,401 618,403 618,406 C618,403 620,401 623,401 C626,401 628,403 628,406 C628,403 630,401 633,401 C636,401 638,403 638,406 C638,403 640,401 643,401 C646,401 648,403 648,406 C648,403 650,401 653,401 C656,401 658,403 658,406 C658,403 660,401 663,401 C666,401 668,403 668,406 C668,403 670,401 673,401 C676,401 678,403 678,406 C678,403 680,401 683,401 C686,401 688,403 688,406 C688,403 690,401 693,401 C696,401 698,403 698,406 C698,403 700,401 703,401 C706,401 708,403 708,406 C708,403 710,401 713,401 C716,401 718,403 718,406 C718,403 720,401 723,401 C726,401 728,403 728,406 C728,403 730,401 733,401 C736,401 738,403 738,406 C738,403 740,401 743,401 C746,401 748,403 748,406 C748,403 750,401 753,401 C756,401 758,403 758,406 C758,403 760,401 763,401 C766,401 768,403 768,406 C768,403 770,401 773,401 C776,401 778,403 778,406 C778,403 780,401 783,401 C786,401 788,403 788,406 C788,403 790,401 793,401 C796,401 798,403 798,406 C798,403 800,401 803,401 C806,401 808,403 808,406 C808,403 810,401 813,401 C816,401 818,403 818,406 C818,403 820,401 823,401 C826,401 828,403 828,406 C828,403 830,401 833,401 C836,401 838,403 838,406 C838,403 840,401 843,401 C846,401 848,403 848,406 C848,403 850,401 853,401 C856,401 858,403 858,406 C858,403 860,401 863,401 C866,401 868,403 868,406 C868,403 870,401 873,401 C876,401 878,403 878,406 C878,403 880,401 883,401 C886,401 888,403 888,406 C888,403 890,401 893,401 C896,401 898,403 898,406 C898,403 900,401 903,401 C906,401 908,403 908,406 C908,403 910,401 913,401 C916,401 918,403 918,406 C918,403 920,401 923,401 C926,401 928,403 928,406 C928,403 930,401 933,401 C936,401 938,403 938,406 C938,403 940,401 943,401 C946,401 948,403 948,406 C948,403 950,401 953,401 C956,401 958,403 958,406 C958,403 960,401 963,401 C966,401 968,403 968,406 C968,403 970,401 973,401 C976,401 978,403 978,406 C978,403 980,401 983,401 C986,401 988,403 988,406 C988,403 990,401 993,401 C996,401 998,403 998,406 C998,403 1000,401 1003,401 C1006,401 1008,403 1008,406 C1008,403 1010,401 1013,401 C1016,401 1018,403 1018,406 C1018,403 1020,401 1023,401 C1026,401 1028,403 1028,406 C1028,403 1030,401 1033,401 C1036,401 1038,403 1038,406 C1038,403 1040,401 1043,401 C1046,401 1048,403 1048,406 C1048,403 1050,401 1053,401 C1056,401 1058,403 1058,406 C1058,403 1060,401 1063,401 C1066,401 1068,403 1068,406 C1068,403 1070,401 1073,401 C1076,401 1078,403 1078,406 C1078,403 1080,401 1083,401 C1086,401 1088,403 1088,406 C1088,403 1090,401 1093,401 C1096,401 1098,403 1098,406 C1098,403 1100,401 1103,401 C1106,401 1108,403 1108,406 C1108,403 1110,401 1113,401 C1116,401 1118,403 1118,406 C1118,403 1120,401 1123,401 C1126,401 1128,403 1128,406 C1128,403 1130,401 1133,401 C1136,401 1138,403 1138,406 C1138,403 1140,401 1143,401 C1146,401 1148,403 1148,406 C1148,403 1150,401 1153,401 C1156,401 1158,403 1158,406 C1158,403 1160,401 1163,401 C1166,401 1168,403 1168,406 C1168,403 1170,401 1173,401 C1176,401 1178,403 1178,406 C1178,403 1180,401 1183,401 C1186,401 1188,403 1188,406 C1188,403 1190,401 1193,401 C1196,401 1198,403 1198,406 C1198,403 1200,401 1203,401 C1206,401 1208,403 1208,406 C1208,403 1210,401 1213,401 C1216,401 1218,403 1218,406 C1218,403 1220,401 1223,401 C1226,401 1228,403 1228,406 C1228,403 1230,401 1233,401 C1236,401 1238,403 1238,406 C1238,403 1240,401 1243,401 C1246,401 1248,403 1248,406 C1248,403 1250,401 1253,401 C1256,401 1258,403 1258,406 C1258,403 1260,401 1263,401 C1266,401 1268,403 1268,406 C1268,403 1270,401 1273,401 C1276,401 1278,403 1278,406 C1278,403 1280,401 1283,401 C1286,401 1288,403 1288,406 C1288,403 1290,401 1293,401 C1296,401 1298,403 1298,406 C1298,403 1300,401 1303,401 C1306,401 1308,403 1308,406 C1308,403 1310,401 1313,401 C1316,401 1318,403 1318,406 C1318,403 1320,401 1323,401 C1326,401 1328,403 1328,406 C1331,406 1333,408 1333,411 C1333,414 1331,416 1328,416 C1331,416 1333,418 1333,421 C1333,424 1331,426 1328,426 C1331,426 1333,428 1333,431 C1333,434 1331,436 1328,436 C1331,436 1333,438 1333,441 C1333,444 1331,446 1328,446 C1331,446 1333,448 1333,451 C1333,454 1331,456 1328,456 C1331,456 1333,458 1333,461 C1333,464 1331,466 1328,466 C1328,469 1325,471 1323,471 C1320,471 1318,469 1318,466 C1318,469 1315,471 1313,471 C1310,471 1308,469 1308,466 C1308,469 1305,471 1303,471 C1300,471 1298,469 1298,466 C1298,469 1295,471 1293,471 C1290,471 1288,469 1288,466 C1288,469 1285,471 1283,471 C1280,471 1278,469 1278,466 C1278,469 1275,471 1273,471 C1270,471 1268,469 1268,466 C1268,469 1265,471 1263,471 C1260,471 1258,469 1258,466 C1258,469 1255,471 1253,471 C1250,471 1248,469 1248,466 C1248,469 1245,471 1243,471 C1240,471 1238,469 1238,466 C1238,469 1235,471 1233,471 C1230,471 1228,469 1228,466 C1228,469 1225,471 1223,471 C1220,471 1218,469 1218,466 C1218,469 1215,471 1213,471 C1210,471 1208,469 1208,466 C1208,469 1205,471 1203,471 C1200,471 1198,469 1198,466 C1198,469 1195,471 1193,471 C1190,471 1188,469 1188,466 C1188,469 1185,471 1183,471 C1180,471 1178,469 1178,466 C1178,469 1175,471 1173,471 C1170,471 1168,469 1168,466 C1168,469 1165,471 1163,471 C1160,471 1158,469 1158,466 C1158,469 1155,471 1153,471 C1150,471 1148,469 1148,466 C1148,469 1145,471 1143,471 C1140,471 1138,469 1138,466 C1138,469 1135,471 1133,471 C1130,471 1128,469 1128,466 C1128,469 1125,471 1123,471 C1120,471 1118,469 1118,466 C1118,469 1115,471 1113,471 C1110,471 1108,469 1108,466 C1108,469 1105,471 1103,471 C1100,471 1098,469 1098,466 C1098,469 1095,471 1093,471 C1090,471 1088,469 1088,466 C1088,469 1085,471 1083,471 C1080,471 1078,469 1078,466 C1078,469 1075,471 1073,471 C1070,471 1068,469 1068,466 C1068,469 1065,471 1063,471 C1060,471 1058,469 1058,466 C1058,469 1055,471 1053,471 C1050,471 1048,469 1048,466 C1048,469 1045,471 1043,471 C1040,471 1038,469 1038,466 C1038,469 1035,471 1033,471 C1030,471 1028,469 1028,466 C1028,469 1025,471 1023,471 C1020,471 1018,469 1018,466 C1018,469 1015,471 1013,471 C1010,471 1008,469 1008,466 C1008,469 1005,471 1003,471 C1000,471 998,469 998,466 C998,469 995,471 993,471 C990,471 988,469 988,466 C988,469 985,471 983,471 C980,471 978,469 978,466 C978,469 975,471 973,471 C970,471 968,469 968,466 C968,469 965,471 963,471 C960,471 958,469 958,466 C958,469 955,471 953,471 C950,471 948,469 948,466 C948,469 945,471 943,471 C940,471 938,469 938,466 C938,469 935,471 933,471 C930,471 928,469 928,466 C928,469 925,471 923,471 C920,471 918,469 918,466 C918,469 915,471 913,471 C910,471 908,469 908,466 C908,469 905,471 903,471 C900,471 898,469 898,466 C898,469 895,471 893,471 C890,471 888,469 888,466 C888,469 885,471 883,471 C880,471 878,469 878,466 C878,469 875,471 873,471 C870,471 868,469 868,466 C868,469 865,471 863,471 C860,471 858,469 858,466 C858,469 855,471 853,471 C850,471 848,469 848,466 C848,469 845,471 843,471 C840,471 838,469 838,466 C838,469 835,471 833,471 C830,471 828,469 828,466 C828,469 825,471 823,471 C820,471 818,469 818,466 C818,469 815,471 813,471 C810,471 808,469 808,466 C808,469 805,471 803,471 C800,471 798,469 798,466 C798,469 795,471 793,471 C790,471 788,469 788,466 C788,469 785,471 783,471 C780,471 778,469 778,466 C778,469 775,471 773,471 C770,471 768,469 768,466 C768,469 765,471 763,471 C760,471 758,469 758,466 C758,469 755,471 753,471 C750,471 748,469 748,466 C748,469 745,471 743,471 C740,471 738,469 738,466 C738,469 735,471 733,471 C730,471 728,469 728,466 C728,469 725,471 723,471 C720,471 718,469 718,466 C718,469 715,471 713,471 C710,471 708,469 708,466 C708,469 705,471 703,471 C700,471 698,469 698,466 C698,469 695,471 693,471 C690,471 688,469 688,466 C688,469 685,471 683,471 C680,471 678,469 678,466 C678,469 675,471 673,471 C670,471 668,469 668,466 C668,469 665,471 663,471 C660,471 658,469 658,466 C658,469 655,471 653,471 C650,471 648,469 648,466 C648,469 645,471 643,471 C640,471 638,469 638,466 C638,469 635,471 633,471 C630,471 628,469 628,466 C628,469 625,471 623,471 C620,471 618,469 618,466 C618,469 615,471 613,471 C610,471 608,469 608,466 C608,469 605,471 603,471 C600,471 598,469 598,466 C595,466 593,464 593,461 C593,458 595,456 598,456 C595,456 593,454 593,451 C593,448 595,446 598,446 C595,446 593,444 593,441 C593,438 595,436 598,436 C595,436 593,434 593,431 C593,428 595,426 598,426 C595,426 593,424 593,421 C593,418 595,416 598,416 C595,416 593,414 593,411 C593,408 595,406 598,406 " fill="#FFFFFF" filter="url(#fiw5od00ir7id)" style="stroke: #000000; stroke-width: 2.0;"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="58" x="935.5" y="425.5352">xls, xlsx</text><!--entity pyexcel public api--><rect fill="#FEFECE" filter="url(#fiw5od00ir7id)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="143" x="22.5" y="217"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="17.5" y="222"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="17.5" y="243.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="123" x="32.5" y="240.5352">pyexcel public api</text><!--entity pyexcel internal api--><rect fill="#FEFECE" filter="url(#fiw5od00ir7id)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="154" x="201" y="217"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="196" y="222"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="196" y="243.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="134" x="211" y="240.5352">pyexcel internal api</text><!--entity pyexcel plugin interfaces--><rect fill="#FEFECE" filter="url(#fiw5od00ir7id)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="191" x="390.5" y="217"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="385.5" y="222"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="385.5" y="243.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="171" x="400.5" y="240.5352">pyexcel plugin interfaces</text><!--entity data source plugins--><rect fill="#FEFECE" filter="url(#fiw5od00ir7id)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="155" x="430.5" y="321.5"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="425.5" y="326.5"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="425.5" y="347.9883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="135" x="440.5" y="345.0352">data source plugins</text><!--entity data renderer plugins--><rect fill="#FEFECE" filter="url(#fiw5od00ir7id)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="169" x="226.5" y="321.5"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="221.5" y="326.5"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="221.5" y="347.9883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="149" x="236.5" y="345.0352">data renderer plugins</text><!--entity data parsing plugins--><rect fill="#FEFECE" filter="url(#fiw5od00ir7id)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="161" x="30.5" y="321.5"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="25.5" y="326.5"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="25.5" y="347.9883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="141" x="40.5" y="345.0352">data parsing plugins</text><!--entity pyexcel-io public api--><rect fill="#FEFECE" filter="url(#fiw5od00ir7id)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="164" x="236" y="426"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="231" y="431"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="231" y="452.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="144" x="246" y="449.5352">pyexcel-io public api</text><!--entity pyexcel-io plugin interfaces--><rect fill="#FEFECE" filter="url(#fiw5od00ir7id)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="212" x="367" y="530.5"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="362" y="535.5"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="362" y="556.9883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="192" x="377" y="554.0352">pyexcel-io plugin interfaces</text><!--entity csv, csvz, tsv, tsvz--><rect fill="#FEFECE" filter="url(#fiw5od00ir7id)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="140" x="431" y="635"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="426" y="640"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="426" y="661.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="120" x="441" y="658.5352">csv, csvz, tsv, tsvz</text><!--entity django, sqlalchemy--><rect fill="#FEFECE" filter="url(#fiw5od00ir7id)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="152" x="244" y="635"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="239" y="640"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="239" y="661.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="132" x="254" y="658.5352">django, sqlalchemy</text><!--entity pyexcel-ods--><rect fill="#FEFECE" filter="url(#fiw5od00ir7id)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="105" x="606.5" y="635"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="601.5" y="640"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="601.5" y="661.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="85" x="616.5" y="658.5352">pyexcel-ods</text><!--entity pyexcel-ods3--><rect fill="#FEFECE" filter="url(#fiw5od00ir7id)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="114" x="747" y="635"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="742" y="640"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="742" y="661.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="94" x="757" y="658.5352">pyexcel-ods3</text><!--entity pyexcel-odsr--><rect fill="#FEFECE" filter="url(#fiw5od00ir7id)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="111" x="896.5" y="635"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="891.5" y="640"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="891.5" y="661.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="91" x="906.5" y="658.5352">pyexcel-odsr</text><!--entity pyexcel-xls--><rect fill="#FEFECE" filter="url(#fiw5od00ir7id)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="100" x="931" y="426"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="926" y="431"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="926" y="452.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="80" x="941" y="449.5352">pyexcel-xls</text><!--entity pyexcel-xlsx--><rect fill="#FEFECE" filter="url(#fiw5od00ir7id)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="109" x="1066.5" y="426"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="1061.5" y="431"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="1061.5" y="452.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="89" x="1076.5" y="449.5352">pyexcel-xlsx</text><!--entity pyexcel-xlsxw--><rect fill="#FEFECE" filter="url(#fiw5od00ir7id)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="120" x="776" y="426"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="771" y="431"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="771" y="452.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="100" x="786" y="449.5352">pyexcel-xlsxw</text><!--entity pyexcel-libxlsxw--><rect fill="#FEFECE" filter="url(#fiw5od00ir7id)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="137" x="603.5" y="426"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="598.5" y="431"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="598.5" y="452.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="117" x="613.5" y="449.5352">pyexcel-libxlsxw</text><!--entity pyexcel-xlsxr--><rect fill="#FEFECE" filter="url(#fiw5od00ir7id)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="115" x="1210.5" y="426"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="1205.5" y="431"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="1205.5" y="452.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="95" x="1220.5" y="449.5352">pyexcel-xlsxr</text><!--entity tabulate--><rect fill="#FFFFFF" filter="url(#fiw5od00ir7id)" height="52.9766" style="stroke: #A80036; stroke-width: 1.5;" width="117" x="251.5" y="8"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="246.5" y="13"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="246.5" y="50.9766"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="97" x="261.5" y="31.5352">«dependency»</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="56" x="282" y="48.0234">tabulate</text><!--entity pygal--><rect fill="#FFFFFF" filter="url(#fiw5od00ir7id)" height="52.9766" style="stroke: #A80036; stroke-width: 1.5;" width="117" x="608.5" y="8"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="603.5" y="13"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="603.5" y="50.9766"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="97" x="618.5" y="31.5352">«dependency»</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="37" x="648.5" y="48.0234">pygal</text><!--entity handsontable--><rect fill="#FFFFFF" filter="url(#fiw5od00ir7id)" height="52.9766" style="stroke: #A80036; stroke-width: 1.5;" width="117" x="427.5" y="8"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="422.5" y="13"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="422.5" y="50.9766"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="97" x="437.5" y="31.5352">«dependency»</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="94" x="439" y="48.0234">handsontable</text><!--entity xlrd--><rect fill="#FFFFFF" filter="url(#fiw5od00ir7id)" height="52.9766" style="stroke: #A80036; stroke-width: 1.5;" width="117" x="758.5" y="522"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="753.5" y="527"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="753.5" y="564.9766"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="97" x="768.5" y="545.5352">«dependency»</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="28" x="803" y="562.0234">xlrd</text><!--entity xlwt--><rect fill="#FFFFFF" filter="url(#fiw5od00ir7id)" height="52.9766" style="stroke: #A80036; stroke-width: 1.5;" width="117" x="910.5" y="522"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="905.5" y="527"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="905.5" y="564.9766"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="97" x="920.5" y="545.5352">«dependency»</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="29" x="954.5" y="562.0234">xlwt</text><!--entity openpyxl--><rect fill="#FFFFFF" filter="url(#fiw5od00ir7id)" height="52.9766" style="stroke: #A80036; stroke-width: 1.5;" width="117" x="1062.5" y="522"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="1057.5" y="527"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="1057.5" y="564.9766"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="97" x="1072.5" y="545.5352">«dependency»</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="64" x="1089" y="562.0234">openpyxl</text><!--entity xlsxwriter--><rect fill="#FFFFFF" filter="url(#fiw5od00ir7id)" height="52.9766" style="stroke: #A80036; stroke-width: 1.5;" width="117" x="924.5" y="313"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="919.5" y="318"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="919.5" y="355.9766"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="97" x="934.5" y="336.5352">«dependency»</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="69" x="948.5" y="353.0234">xlsxwriter</text><!--entity ezodf--><rect fill="#FFFFFF" filter="url(#fiw5od00ir7id)" height="52.9766" style="stroke: #A80036; stroke-width: 1.5;" width="117" x="749.5" y="792"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="744.5" y="797"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="744.5" y="834.9766"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="97" x="759.5" y="815.5352">«dependency»</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="39" x="788.5" y="832.0234">ezodf</text><!--entity odfpy--><rect fill="#FFFFFF" filter="url(#fiw5od00ir7id)" height="52.9766" style="stroke: #A80036; stroke-width: 1.5;" width="117" x="597.5" y="792"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="592.5" y="797"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="592.5" y="834.9766"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="97" x="607.5" y="815.5352">«dependency»</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="39" x="636.5" y="832.0234">odfpy</text><!--entity lxml--><rect fill="#FFFFFF" filter="url(#fiw5od00ir7id)" height="52.9766" style="stroke: #A80036; stroke-width: 1.5;" width="117" x="1214.5" y="522"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="1209.5" y="527"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="1209.5" y="564.9766"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="97" x="1224.5" y="545.5352">«dependency»</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="30" x="1258" y="562.0234">lxml</text><!--entity libxlsxwpy--><rect fill="#FFFFFF" filter="url(#fiw5od00ir7id)" height="52.9766" style="stroke: #A80036; stroke-width: 1.5;" width="117" x="620.5" y="313"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="615.5" y="318"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="615.5" y="355.9766"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="97" x="630.5" y="336.5352">«dependency»</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="73" x="642.5" y="353.0234">libxlsxwpy</text><!--entity libxlsx--><rect fill="#FFFFFF" filter="url(#fiw5od00ir7id)" height="52.9766" style="stroke: #A80036; stroke-width: 1.5;" width="117" x="772.5" y="313"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="767.5" y="318"/><rect fill="#FFFFFF" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="767.5" y="355.9766"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="97" x="782.5" y="336.5352">«dependency»</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="46" x="808" y="353.0234">libxlsx</text><!--entity pyexcel-text--><rect fill="#FEFECE" filter="url(#fiw5od00ir7id)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="107" x="256.5" y="121"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="251.5" y="126"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="251.5" y="147.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="87" x="266.5" y="144.5352">pyexcel-text</text><!--entity pyexcel-pygal--><rect fill="#FEFECE" filter="url(#fiw5od00ir7id)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="117" x="608.5" y="121"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="603.5" y="126"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="603.5" y="147.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="97" x="618.5" y="144.5352">pyexcel-pygal</text><!--entity pyexcel-handsontable--><rect fill="#FEFECE" filter="url(#fiw5od00ir7id)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="174" x="399" y="121"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="394" y="126"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="394" y="147.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="154" x="409" y="144.5352">pyexcel-handsontable</text><!--entity sphinxcontrib-excel--><rect fill="#FEFECE" filter="url(#fiw5od00ir7id)" height="36.4883" style="stroke: #A80036; stroke-width: 1.5;" width="160" x="61" y="121"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="56" y="126"/><rect fill="#FEFECE" height="5" style="stroke: #A80036; stroke-width: 1.5;" width="10" x="56" y="147.4883"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="140" x="71" y="144.5352">sphinxcontrib-excel</text><!--link pyexcel public api to pyexcel internal api--><path d="M165.5156,235 C175.4935,235 185.4713,235 195.4492,235 " fill="none" id="pyexcel public api-pyexcel internal api" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="200.667,235,191.667,231,195.667,235,191.667,239,200.667,235" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel internal api to pyexcel plugin interfaces--><path d="M355.1875,235 C365.1956,235 375.2038,235 385.2119,235 " fill="none" id="pyexcel internal api-pyexcel plugin interfaces" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="390.4456,235,381.4456,231,385.4456,235,381.4456,239,390.4456,235" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-text to pyexcel plugin interfaces--><path d="M347.6141,159.5168 C378.8298,176.5435 422.8023,200.5285 452.7455,216.8612 " fill="none" id="pyexcel-text-pyexcel plugin interfaces" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="343.1043,157.0569,349.0897,164.8784,347.4937,159.4513,352.9207,157.8553,343.1043,157.0569" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-pygal to pyexcel plugin interfaces--><path d="M628.3173,159.5168 C596.2148,176.5435 550.9931,200.5285 520.1993,216.8612 " fill="none" id="pyexcel-pygal-pyexcel plugin interfaces" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="632.9553,157.0569,623.1302,157.7403,628.5382,159.3998,626.8787,164.8077,632.9553,157.0569" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-handsontable to pyexcel plugin interfaces--><path d="M486,162.0611 C486,178.8106 486,201.1694 486,216.6977 " fill="none" id="pyexcel-handsontable-pyexcel plugin interfaces" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="486,157.0569,482,166.0569,486,162.0569,490,166.0569,486,157.0569" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel plugin interfaces to data source plugins--><path d="M489.8321,253.2025 C493.5005,270.6276 499.0528,297.0009 503.0536,316.0045 " fill="none" id="pyexcel plugin interfaces-data source plugins" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="504.138,321.1555,506.1982,311.5245,503.108,316.2627,498.3698,313.1725,504.138,321.1555" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel plugin interfaces to data renderer plugins--><path d="M455.5173,253.2025 C424.9962,271.428 378.0816,299.4427 346.042,318.5749 " fill="none" id="pyexcel plugin interfaces-data renderer plugins" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="341.4264,321.3311,351.2043,320.1512,345.7193,318.7677,347.1028,313.2826,341.4264,321.3311" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel plugin interfaces to data parsing plugins--><path d="M390.3918,250.2007 C336.3714,260.108 268.043,274.8665 209,294.5 C189.1888,301.0878 168.0415,310.4751 150.4814,318.9729 " fill="none" id="pyexcel plugin interfaces-data parsing plugins" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="145.6278,321.3431,155.4703,320.9883,150.1207,319.1491,151.9599,313.7996,145.6278,321.3431" style="stroke: #A80036; stroke-width: 1.0;"/><!--link data renderer plugins to pyexcel-io public api--><path d="M312.2193,357.7025 C313.3865,375.1276 315.1532,401.5009 316.4261,420.5045 " fill="none" id="data renderer plugins-pyexcel-io public api" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="316.7712,425.6555,320.1605,416.4082,316.4369,420.6667,312.1784,416.9431,316.7712,425.6555" style="stroke: #A80036; stroke-width: 1.0;"/><!--link data parsing plugins to pyexcel-io public api--><path d="M147.0566,357.7025 C183.4547,376.0774 239.563,404.4026 277.4787,423.5436 " fill="none" id="data parsing plugins-pyexcel-io public api" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="282.0099,425.8311,275.7785,418.2042,277.5465,423.5777,272.173,425.3457,282.0099,425.8311" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-io public api to pyexcel-io plugin interfaces--><path d="M344.9989,462.2025 C371.9211,480.3533 413.2445,508.2132 441.6133,527.3393 " fill="none" id="pyexcel-io public api-pyexcel-io plugin interfaces" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="446.0509,530.3311,440.8244,521.9834,441.9051,527.5361,436.3524,528.6168,446.0509,530.3311" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-io plugin interfaces to csv, csvz, tsv, tsvz--><path d="M477.8772,566.7025 C482.5461,584.1276 489.6127,610.5009 494.7045,629.5045 " fill="none" id="pyexcel-io plugin interfaces-csv, csvz, tsv, tsvz" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="496.0847,634.6555,497.619,624.9269,494.7906,629.8259,489.8916,626.9975,496.0847,634.6555" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-io plugin interfaces to django, sqlalchemy--><path d="M446.3494,566.7025 C419.7746,584.8533 378.9845,612.7132 350.9817,631.8393 " fill="none" id="pyexcel-io plugin interfaces-django, sqlalchemy" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="346.6014,634.8311,356.2893,633.058,350.7302,632.011,351.7772,626.4519,346.6014,634.8311" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-xls to pyexcel-io plugin interfaces--><path d="M933.2894,463.8058 C926.8692,466.0893 920.3188,468.2297 914,470 C801.6761,501.4696 669.5765,523.0116 579.4391,535.4557 " fill="none" id="pyexcel-xls-pyexcel-io plugin interfaces" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="938.1675,462.0354,928.3428,461.3459,933.4675,463.7413,931.0722,468.8659,938.1675,462.0354" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-xlsxw to pyexcel-io plugin interfaces--><path d="M778.4985,463.5491 C771.608,465.7785 764.6575,467.9751 758,470 C684.8397,492.2519 600.2959,515.1939 542.4034,530.4846 " fill="none" id="pyexcel-xlsxw-pyexcel-io plugin interfaces" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="783.2549,462.0022,773.459,460.9822,778.5001,463.5488,775.9335,468.5898,783.2549,462.0022" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-libxlsxw to pyexcel-io plugin interfaces--><path d="M632.5325,464.7254 C596.0682,483.8737 542.4253,512.043 507.5992,530.3311 " fill="none" id="pyexcel-libxlsxw-pyexcel-io plugin interfaces" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="637.3368,462.2025,627.509,462.8456,632.9101,464.5272,631.2285,469.9283,637.3368,462.2025" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-xlsx to pyexcel-io plugin interfaces--><path d="M1070.4488,463.7617 C1063.3191,466.1053 1056.0257,468.2733 1049,470 C962.3244,491.3024 720.4565,520.6417 579.2227,536.7432 " fill="none" id="pyexcel-xlsx-pyexcel-io plugin interfaces" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="1075.3532,462.1144,1065.548,461.1881,1070.6134,463.7064,1068.0952,468.7718,1075.3532,462.1144" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-xlsxr to pyexcel-io plugin interfaces--><path d="M1216.4492,463.5654 C1208.6725,466.0211 1200.6828,468.2727 1193,470 C1163.5188,476.6282 772.2616,517.5725 579.2254,537.5536 " fill="none" id="pyexcel-xlsxr-pyexcel-io plugin interfaces" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="1221.2592,462.0129,1211.4656,460.9708,1216.5009,463.5488,1213.923,468.5841,1221.2592,462.0129" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-ods to pyexcel-io plugin interfaces--><path d="M626.8197,632.1442 C614.4927,624.3524 600.2173,615.5645 587,608 C561.4233,593.3619 531.9135,578.0002 509.3266,566.5528 " fill="none" id="pyexcel-ods-pyexcel-io plugin interfaces" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="631.2674,634.9657,625.8101,626.767,627.0452,632.2874,621.5249,633.5225,631.2674,634.9657" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-io plugin interfaces to pyexcel-ods3--><path d="M579.2792,565.482 C626.2123,574.9288 681.4132,588.7504 729,608 C744.5558,614.2926 760.6572,623.5378 773.9343,632.014 " fill="none" id="pyexcel-io plugin interfaces-pyexcel-ods3" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="778.3239,634.8537,772.94,626.6067,774.1258,632.1379,768.5946,633.3237,778.3239,634.8537" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-io plugin interfaces to pyexcel-odsr--><path d="M579.3424,559.1569 C680.6927,570.2001 825.5275,588.5436 879,608 C894.838,613.7628 910.9433,623.2283 923.9826,631.995 " fill="none" id="pyexcel-io plugin interfaces-pyexcel-odsr" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 7.0,7.0;"/><polygon fill="#A80036" points="928.2822,634.9349,923.1108,626.553,924.1549,632.1127,918.5952,633.1568,928.2822,634.9349" style="stroke: #A80036; stroke-width: 1.0;"/><!--link tabulate to pyexcel-text--><path d="M310,61.1401 C310,79.8859 310,104.4746 310,120.9587 " fill="none" id="tabulate-pyexcel-text" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pygal to pyexcel-pygal--><path d="M667,61.1401 C667,79.8859 667,104.4746 667,120.9587 " fill="none" id="pygal-pyexcel-pygal" style="stroke: #A80036; stroke-width: 1.0;"/><!--link handsontable to pyexcel-handsontable--><path d="M486,61.1401 C486,79.8859 486,104.4746 486,120.9587 " fill="none" id="handsontable-pyexcel-handsontable" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-xls to xlrd--><path d="M952.4334,462.2025 C926.5538,478.6929 888.0971,503.1973 858.7686,521.8852 " fill="none" id="pyexcel-xls-xlrd" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-xls to xlwt--><path d="M978.9098,462.2025 C977.0161,478.6929 974.2022,503.1973 972.0562,521.8852 " fill="none" id="pyexcel-xls-xlwt" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-xlsx to openpyxl--><path d="M1121,462.2025 C1121,478.6929 1121,503.1973 1121,521.8852 " fill="none" id="pyexcel-xlsx-openpyxl" style="stroke: #A80036; stroke-width: 1.0;"/><!--link xlsxwriter to pyexcel-xlsxw--><path d="M945.5253,366.1401 C919.1557,384.8859 884.5669,409.4746 861.3787,425.9587 " fill="none" id="xlsxwriter-pyexcel-xlsxw" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-ods3 to ezodf--><path d="M804.4357,671.0265 C805.1403,700.1809 806.5337,757.8327 807.3541,791.7745 " fill="none" id="pyexcel-ods3-ezodf" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-ods to odfpy--><path d="M658.6732,671.0265 C658.1448,700.1809 657.0997,757.8327 656.4844,791.7745 " fill="none" id="pyexcel-ods-odfpy" style="stroke: #A80036; stroke-width: 1.0;"/><!--link lxml to pyexcel-odsr--><path d="M1214.1373,569.2022 C1208.3538,571.1828 1202.5675,573.1435 1197,575 C1132.2105,596.6045 1057.3524,620.2459 1007.6032,635.7725 " fill="none" id="lxml-pyexcel-odsr" style="stroke: #A80036; stroke-width: 1.0;"/><!--link pyexcel-xlsxr to lxml--><path d="M1268.8709,462.2025 C1269.6599,478.6929 1270.8324,503.1973 1271.7266,521.8852 " fill="none" id="pyexcel-xlsxr-lxml" style="stroke: #A80036; stroke-width: 1.0;"/><!--link sphinxcontrib-excel to pyexcel-handsontable--><path d="M174.5008,120.9336 C196.978,109.8672 227.6452,96.7571 256.5,91 C303.1364,81.6951 316.7711,82.1717 363.5,91 C392.0118,96.3866 422.3908,108.2464 445.7634,118.8289 " fill="none" id="sphinxcontrib-excel-pyexcel-handsontable" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="450.3419,120.926,443.8253,113.5413,445.7961,118.8437,440.4937,120.8146,450.3419,120.926" style="stroke: #A80036; stroke-width: 1.0;"/><!--link libxlsxwpy to pyexcel-libxlsxw--><path d="M677.2155,366.1401 C675.9598,384.8859 674.3127,409.4746 673.2085,425.9587 " fill="none" id="libxlsxwpy-pyexcel-libxlsxw" style="stroke: #A80036; stroke-width: 1.0;"/><!--link libxlsxwpy to libxlsx--><path d="M737.7813,339.5 C749.3122,339.5 760.8432,339.5 772.3741,339.5 " fill="none" id="libxlsxwpy-libxlsx" style="stroke: #A80036; stroke-width: 1.0;"/><!--
11 @startuml
22
33 package "pyexcel" {
3030 [pyexcel-xls]
3131 [pyexcel-xlsx]
3232 [pyexcel-xlsxw]
33 [pyexcel-libxlsxw]
34 [pyexcel-xlsxr]
3335 }
3436
3537 [tabulate] <<dependency>>
4143 [xlsxwriter] <<dependency>>
4244 [ezodf] <<dependency>>
4345 [odfpy] <<dependency>>
46 [lxml] <<dependency>>
47 [libxlsxwpy] <<dependency>>
48 [libxlsx] <<dependency>>
4449
4550 [pyexcel public api] -right-> [pyexcel internal api]
4651 [pyexcel internal api] -right-> [pyexcel plugin interfaces]
5863 [pyexcel-io plugin interfaces] ..> [csv, csvz, tsv, tsvz]
5964 [pyexcel-io plugin interfaces] ..> [django, sqlalchemy]
6065 [pyexcel-io plugin interfaces] .up.> [pyexcel-xls]
66 [pyexcel-io plugin interfaces] .up.> [pyexcel-xlsxw]
67 [pyexcel-io plugin interfaces] .up.> [pyexcel-libxlsxw]
6168 [pyexcel-io plugin interfaces] .up.> [pyexcel-xlsx]
69 [pyexcel-io plugin interfaces] .up.> [pyexcel-xlsxr]
6270 [pyexcel-io plugin interfaces] .left.> [pyexcel-ods]
6371 [pyexcel-io plugin interfaces] .right.> [pyexcel-ods3]
64 [pyexcel-io plugin interfaces] .right.> [pyexcel-xlsxw]
6572 [pyexcel-io plugin interfaces] .right.> [pyexcel-odsr]
73
6674
6775 [pyexcel-text] -up- [tabulate]
6876 [pyexcel-pygal] -up- [pygal]
7078 [pyexcel-xls] -right- [xlrd]
7179 [pyexcel-xls] -right- [xlwt]
7280 [pyexcel-xlsx] -right- [openpyxl]
73 [pyexcel-xlsxw] -down- [xlsxwriter]
81 [pyexcel-xlsxw] -up- [xlsxwriter]
7482 [pyexcel-ods3] - - - [ezodf]
7583 [pyexcel-ods] - - - [odfpy]
84 [pyexcel-odsr] -up- [lxml]
85 [pyexcel-xlsxr] -right- [lxml]
86 [sphinxcontrib-excel] -right-> [pyexcel-handsontable]
87 [pyexcel-libxlsxw] -up- [libxlsxwpy]
88 [libxlsxwpy] -right- [libxlsx]
7689
7790 skinparam component{
7891
88101 JVM: Java HotSpot(TM) 64-Bit Server VM
89102 Java Version: 1.8.0_131-b11
90103 Operating System: Mac OS X
91 OS Version: 10.11.6
104 OS Version: 10.14.6
92105 Default Encoding: UTF-8
93106 Language: en
94 Country: US
107 Country: GB
95108 --></g></svg>
177177 Sheet.number_of_columns
178178 Sheet.row_range
179179 Sheet.column_range
180
181 Iteration
182 -----------------
183
184 .. autosummary::
185 :toctree: generated/
186
187 Sheet.rows
188 Sheet.rrows
189 Sheet.columns
190 Sheet.rcolumns
191 Sheet.enumerate
192 Sheet.reverse
193 Sheet.vertical
194 Sheet.rvertical
195180
196181
197182 Cell access
300285 .. autosummary::
301286 :toctree: generated/
302287
288 Sheet.project
303289 Sheet.transpose
304290 Sheet.map
305291 Sheet.region
6767 .. testcode::
6868 :hide:
6969
70 >>> from mock import patch, MagicMock
70 >>> from unittest.mock import patch, MagicMock
7171 >>> import os
7272 >>> patcher = patch('pyexcel._compact.request.urlopen')
7373 >>> fake_url_open = patcher.start()
9595 .. testcode::
9696 :hide:
9797
98 >>> patcher.stop()
98 >>> patcher.stop() # doctest: +SKIP
9999
100100
101101 For book
253253 .. testcode::
254254 :hide:
255255
256 >>> from mock import patch, MagicMock
256 >>> from unittest.mock import patch, MagicMock
257257 >>> import os
258258 >>> patcher = patch('pyexcel._compact.request.urlopen')
259259 >>> fake_url_open = patcher.start()
298298 .. testcode::
299299 :hide:
300300
301 >>> patcher.stop()
301 >>> patcher.stop() # doctest: +SKIP
302302
303303
304304 Getters and Setters
11 Read partial data
22 ================================================================================
33
4 .. _get_an_array_from_an_excel_sheet:
5
6 Here the code to get an array of data from your excel file.
7
8 .. pyexcel-table::
9
10 ---pyexcel:data example---
11 1,2,3
12 4,5,6
13 7,8,9
14
15 .. testcode::
16 :hide:
17
18 >>> import pyexcel as pe
19 >>> data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
20 >>> s = pe.Sheet(data)
21 >>> s.save_as("example.xls")
22
23 The following code will give you the data in json::
24
25 >>> import pyexcel
26 >>> # "example.csv","example.xlsx","example.xlsm"
27 >>> my_array = pyexcel.get_array(file_name="example.xls")
28 >>> my_array
29 [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
30
31 .. testcode::
32 :hide:
33
34 >>> import os
35 >>> os.unlink("example.xls")
364
375
38 Pagination
6 When you are dealing with huge amount of data, e.g. 64GB, obviously you would not
7 like to fill up your memory with those data. What you may want to do is, record
8 data from Nth line, take M records and stop. And you only want to use your memory
9 for the M records, not for beginning part nor for the tail part.
10
11 Hence partial read feature is developed to read partial data into memory for
12 processing.
13
14 You can paginate by row, by column and by both, hence you dictate what portion of the
15 data to read back. But remember only row limit features help you save memory. Let's
16 you use this feature to record data from Nth column, take M number of columns and skip
17 the rest. You are not going to reduce your memory footprint.
18
19 Why did not I see above benefit?
3920 --------------------------------------------------------------------------------
4021
41 When you are dealing with huge amount of data, e.g. 64GB, obviously you would not
42 like to fill up your memory with those data. Hence pagination feature is developed
43 to read partial data into memory for processing. You can paginate by row, by
44 column and by both.
22 This feature depends heavily on the implementation details.
4523
46 Please note that `pyexcel-xls`, `pyexcel-xlsx`, `pyexcel-ods` and `pyexcel-ods3`
47 will read all data into memory. And pagination code here only limits the data
48 returned to your program. With that said, csv readers, `pyexcel-xlsxr` and `pyexcel-odsr`
49 DOES read partial data into memory.
24 `pyexcel-xls`_ (xlrd), `pyexcel-xlsx`_ (openpyxl), `pyexcel-ods`_ (odfpy) and
25 `pyexcel-ods3`_ (pyexcel-ezodf) will read all data into memory. Because xls,
26 xlsx and ods file are effective a zipped folder, all four will unzip the folder
27 and read the content in xml format in **full**, so as to make sense of all details.
5028
29 Hence, during the partial data is been returned, the memory consumption won't
30 differ from reading the whole data back. Only after the partial
31 data is returned, the memory comsumption curve shall jump the cliff. So pagination
32 code here only limits the data returned to your program.
5133
52 .. testcode::
53 :hide:
34 With that said, `pyexcel-xlsxr`_, `pyexcel-odsr`_ and `pyexcel-htmlr`_ DOES read
35 partial data into memory. Those three are implemented in such a way that they
36 consume the xml(html) when needed. When they have read designated portion of the
37 data, they stop, even if they are half way through.
5438
55 >>> import sys
56 >>> if sys.version_info[0] < 3:
57 ... from StringIO import StringIO
58 ... else:
59 ... from io import StringIO
60 >>> from pyexcel_io._compact import OrderedDict
39 In addition, pyexcel's csv readers can read partial data into memory too.
40
6141
6242 Let's assume the following file is a huge csv file:
6343
7555 ... ]
7656 >>> pe.save_as(array=data, dest_file_name="your_file.csv")
7757
58
7859 And let's pretend to read partial data:
60
7961
8062 .. code-block:: python
8163
145127
146128 What we can do is to define a row renderer function as the following:
147129
130 .. code-block:: python
131
148132 >>> def increment_by_one(row):
149133 ... for element in row:
150134 ... yield element + 1
151135
152136 Then pass it onto save_as function using row_renderer:
137
138 .. code-block:: python
153139
154140 >>> pe.isave_as(file_name="your_file.csv",
155141 ... row_renderer=increment_by_one,
161147 If the data content is from a generator, isave_as has to be used.
162148
163149 We can verify if it was done correctly:
150
151 .. code-block:: python
164152
165153 >>> pe.get_sheet(file_name="your_file.xlsx")
166154 your_file.csv:
177165 +---+----+----+
178166 | 7 | 27 | 37 |
179167 +---+----+----+
180
181 .. testcode::
182 :hide:
183
184 >>> import os
185 >>> os.unlink("your_file.csv")
186 >>> os.unlink("your_file.xlsx")
6363 1
6464
6565 .. TIP::
66 With pyexcel, you can regard single sheet reader as an
67 two dimensional array and multi-sheet excel book reader
68 as a ordered dictionary of two dimensional arrays.
69
70 **Write multiple sheet excel file**
66 With pyexcel, you can regard single sheet as an
67 two dimensional array and multi-sheet excel book
68 as an ordered dictionary of two dimensional arrays.
69
70 **Write multiple sheet excel book**
7171
7272 Suppose you have previous data as a dictionary and you want to
7373 save it as multiple sheet excel file::
77 #
88 # This file only contains a selection of the most common options. For a full
99 # list see the documentation:
10 # http://www.sphinx-doc.org/en/master/config
10 # https://www.sphinx-doc.org/en/master/usage/configuration.html
1111
1212 # -- Path setup --------------------------------------------------------------
1313
2222 # -- Project information -----------------------------------------------------
2323
2424 project = 'pyexcel'
25 copyright = '2014-2019 Onni Software Ltd.'
25 copyright = '2014-2022 Onni Software Ltd.'
2626 author = 'C.W.'
2727 # The short X.Y version
28 version = '0.5.14'
28 version = '0.7.0'
2929 # The full version, including alpha/beta/rc tags
30 release = '0.5.14'
30 release = '0.7.0'
3131
3232 # -- General configuration ---------------------------------------------------
3333
3434 # Add any Sphinx extension module names here, as strings. They can be
3535 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
3636 # ones.
37 extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode',]
37 extensions = [ 'sphinx.ext.autosummary', 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode', 'sphinxcontrib.excel', 'sphinx_copybutton',]
3838
3939 # Add any paths that contain templates here, relative to this directory.
4040 templates_path = ['_templates']
6868 # -- Options for intersphinx extension ---------------------------------------
6969
7070 # Example configuration for intersphinx: refer to the Python standard library.
71 intersphinx_mapping = {'https://docs.python.org/': None}
71 intersphinx_mapping = {'https://docs.python.org/3/': None}
7272 # TODO: html_theme not configurable upstream
7373 html_theme = 'default'
74
75
7674 def setup(app):
7775 app.add_stylesheet('theme_overrides.css')
7876
8987 intersphinx_mapping.update({
9088 'xlrd': ('http://xlrd.readthedocs.io/en/latest/', None)
9189 })
92
9390 master_doc = "index"
7676 :hide:
7777
7878 >>> session.close()
79 >>> os.unlink('birth.xls')
7980 >>> os.unlink('birth.db')
133133 Data visualization
134134 --------------------------------------------------------------------------------
135135
136 Via :class:`pyexel.renderer.AbstractRenderer` interface, data visualization
136 Via :class:`pyexcel.renderer.AbstractRenderer` interface, data visualization
137137 is made possible. **pyexcel-chart** is the interface plugin to formalize this
138138 effort. **pyexcel-pygal** is the first plugin to provide bar, pie, histogram
139139 charts and more.
+0
-6
docs/source/generated/pyexcel.Sheet.columns.rst less more
0 pyexcel.Sheet.columns
1 =====================
2
3 .. currentmodule:: pyexcel
4
5 .. automethod:: Sheet.columns
+0
-6
docs/source/generated/pyexcel.Sheet.enumerate.rst less more
0 pyexcel.Sheet.enumerate
1 =======================
2
3 .. currentmodule:: pyexcel
4
5 .. automethod:: Sheet.enumerate
0 pyexcel.Sheet.project
1 =====================
2
3 .. currentmodule:: pyexcel
4
5 .. automethod:: Sheet.project
+0
-6
docs/source/generated/pyexcel.Sheet.rcolumns.rst less more
0 pyexcel.Sheet.rcolumns
1 ======================
2
3 .. currentmodule:: pyexcel
4
5 .. automethod:: Sheet.rcolumns
+0
-6
docs/source/generated/pyexcel.Sheet.reverse.rst less more
0 pyexcel.Sheet.reverse
1 =====================
2
3 .. currentmodule:: pyexcel
4
5 .. automethod:: Sheet.reverse
+0
-6
docs/source/generated/pyexcel.Sheet.rows.rst less more
0 pyexcel.Sheet.rows
1 ==================
2
3 .. currentmodule:: pyexcel
4
5 .. automethod:: Sheet.rows
+0
-6
docs/source/generated/pyexcel.Sheet.rrows.rst less more
0 pyexcel.Sheet.rrows
1 ===================
2
3 .. currentmodule:: pyexcel
4
5 .. automethod:: Sheet.rrows
1414
1515 ~Sheet.__init__
1616 ~Sheet.cell_value
17 ~Sheet.clone
1718 ~Sheet.column_at
1819 ~Sheet.column_range
1920 ~Sheet.columns
7374 ~Sheet.number_of_rows
7475 ~Sheet.paste
7576 ~Sheet.plot
77 ~Sheet.project
7678 ~Sheet.rcolumns
7779 ~Sheet.region
7880 ~Sheet.register_input
+0
-6
docs/source/generated/pyexcel.Sheet.rvertical.rst less more
0 pyexcel.Sheet.rvertical
1 =======================
2
3 .. currentmodule:: pyexcel
4
5 .. automethod:: Sheet.rvertical
+0
-6
docs/source/generated/pyexcel.Sheet.vertical.rst less more
0 pyexcel.Sheet.vertical
1 ======================
2
3 .. currentmodule:: pyexcel
4
5 .. automethod:: Sheet.vertical
1616 #. pip install -r tests/requirements.txt
1717
1818 Once you have finished your changes, please provide test case(s), relevant documentation
19 and update CHANGELOG.rst.
19 and update changelog.yml
2020
2121 .. note::
2222
3535
3636 $ make
3737
38 On Windows systems, please issue this command::
38 On Windows, please issue this command::
3939
4040 > test.bat
4141
42 How to update test environment and update documentation
43 ---------------------------------------------------------
4442
45 Additional steps are required:
43 Before you commit
44 ------------------------------
4645
47 #. pip install moban
48 #. git clone https://github.com/moremoban/setupmobans.git # generic setup
49 #. git clone https://github.com/pyexcel/pyexcel-commons.git commons
50 #. make your changes in `.moban.d` directory, then issue command `moban`
46 Please run::
5147
52 What is pyexcel-commons
53 ---------------------------------
48 $ make format
5449
55 Many information that are shared across pyexcel projects, such as: this developer guide, license info, etc. are stored in `pyexcel-commons` project.
56
57 What is .moban.d
58 ---------------------------------
59
60 `.moban.d` stores the specific meta data for the library.
61
62 Acceptance criteria
63 -------------------
64
65 #. Has Test cases written
66 #. Has all code lines tested
67 #. Passes all Travis CI builds
68 #. Has fair amount of documentation if your change is complex
69 #. run 'make format' so as to confirm the pyexcel organisation's coding style
70 #. Please update CHANGELOG.rst
71 #. Please add yourself to CONTRIBUTORS.rst
72 #. Agree on NEW BSD License for your contribution
50 so as to beautify your code otherwise your build may fail your unit test.
1414
1515 ~Matrix.__init__
1616 ~Matrix.cell_value
17 ~Matrix.clone
1718 ~Matrix.column_at
1819 ~Matrix.column_range
1920 ~Matrix.columns
2020
2121 The idea originated from the common usability problem: when an excel file
2222 driven web application is delivered for non-developer users (ie: team assistant,
23 human resource administrator etc). The fact is that not everyone knows (or cares) about the
24 differences between various excel formats: csv, xls, xlsx are all the same to them. Instead of training those users
25 about file formats, this library helps web developers to handle most of the excel file
26 formats by providing a common programming interface. To add a specific excel file format type
27 to you application, all you need is to install an extra pyexcel plugin. Hence no code changes
28 to your application and no issues with excel file formats any more. Looking at the
29 community, this library and its associated ones try to become a small and easy to
30 install alternative to Pandas.
23 human resource administrator etc). The fact is that not everyone knows (or cares)
24 about the differences between various excel formats: csv, xls, xlsx are all
25 the same to them. Instead of training those users about file formats, this
26 library helps web developers to handle most of the excel file
27 formats by providing a common programming interface. To add a specific excel
28 file format type to you application, all you need is to install an extra pyexcel
29 plugin. Hence no code changes to your application and no issues with excel file
30 formats any more. Looking at the community, this library and its associated ones
31 try to become a small and easy to install alternative to Pandas.
3132
3233
3334 Support the project
3435 ================================================================================
3536
3637 If your company has embedded pyexcel and its components into a revenue generating
37 product, please support me on `patreon <https://www.patreon.com/bePatron?u=5537627>`_
38 product, please support me on github, `patreon <https://www.patreon.com/bePatron?u=5537627>`_
3839 or `bounty source <https://salt.bountysource.com/teams/chfw-pyexcel>`_ to maintain
3940 the project and develop it further.
4041
6768 $ cd pyexcel
6869 $ python setup.py install
6970
71 Suppose you have the following data in a dictionary:
72
73 ========= ====
74 Name Age
75 ========= ====
76 Adam 28
77 Beatrice 29
78 Ceri 30
79 Dean 26
80 ========= ====
81
82 you can easily save it into an excel file using the following code:
83
84 .. code-block:: python
85
86 >>> import pyexcel
87 >>> # make sure you had pyexcel-xls installed
88 >>> a_list_of_dictionaries = [
89 ... {
90 ... "Name": 'Adam',
91 ... "Age": 28
92 ... },
93 ... {
94 ... "Name": 'Beatrice',
95 ... "Age": 29
96 ... },
97 ... {
98 ... "Name": 'Ceri',
99 ... "Age": 30
100 ... },
101 ... {
102 ... "Name": 'Dean',
103 ... "Age": 26
104 ... }
105 ... ]
106 >>> pyexcel.save_as(records=a_list_of_dictionaries, dest_file_name="your_file.xls")
107
108 And here's how to obtain the records:
109
110 .. code-block:: python
111
112 >>> import pyexcel as p
113 >>> records = p.iget_records(file_name="your_file.xls")
114 >>> for record in records:
115 ... print("%s is aged at %d" % (record['Name'], record['Age']))
116 Adam is aged at 28
117 Beatrice is aged at 29
118 Ceri is aged at 30
119 Dean is aged at 26
120 >>> p.free_resources()
121
122
123 Custom data rendering:
124
125 .. code-block:: python
126
127 >>> # pip install pyexcel-text==0.2.7.1
128 >>> import pyexcel as p
129 >>> ccs_insight2 = p.Sheet()
130 >>> ccs_insight2.name = "Worldwide Mobile Phone Shipments (Billions), 2017-2021"
131 >>> ccs_insight2.ndjson = """
132 ... {"year": ["2017", "2018", "2019", "2020", "2021"]}
133 ... {"smart phones": [1.53, 1.64, 1.74, 1.82, 1.90]}
134 ... {"feature phones": [0.46, 0.38, 0.30, 0.23, 0.17]}
135 ... """.strip()
136 >>> ccs_insight2
137 pyexcel sheet:
138 +----------------+------+------+------+------+------+
139 | year | 2017 | 2018 | 2019 | 2020 | 2021 |
140 +----------------+------+------+------+------+------+
141 | smart phones | 1.53 | 1.64 | 1.74 | 1.82 | 1.9 |
142 +----------------+------+------+------+------+------+
143 | feature phones | 0.46 | 0.38 | 0.3 | 0.23 | 0.17 |
144 +----------------+------+------+------+------+------+
145
146
147 Advanced usage :fire:
148 ----------------------
149
150 If you are dealing with big data, please consider these usages:
151
152 .. code-block:: python
153
154 >>> def increase_everyones_age(generator):
155 ... for row in generator:
156 ... row['Age'] += 1
157 ... yield row
158 >>> def duplicate_each_record(generator):
159 ... for row in generator:
160 ... yield row
161 ... yield row
162 >>> records = p.iget_records(file_name="your_file.xls")
163 >>> io=p.isave_as(records=duplicate_each_record(increase_everyones_age(records)),
164 ... dest_file_type='csv', dest_lineterminator='\n')
165 >>> print(io.getvalue())
166 Age,Name
167 29,Adam
168 29,Adam
169 30,Beatrice
170 30,Beatrice
171 31,Ceri
172 31,Ceri
173 27,Dean
174 27,Dean
175 <BLANKLINE>
176
177
178 Two advantages of above method:
179
180 #. Add as many wrapping functions as you want.
181 #. Constant memory consumption
182
183
184 .. testcode::
185 :hide:
186
187 >>> import os
188 >>> os.unlink("your_file.xls")
189
190
191
70192 For individual excel file formats, please install them as you wish:
71193
72194 .. _file-format-list:
74196
75197 .. table:: A list of file formats supported by external plugins
76198
77 ======================== ======================= ================= ==================
78 Package name Supported file formats Dependencies Python versions
79 ======================== ======================= ================= ==================
80 `pyexcel-io`_ csv, csvz [#f1]_, tsv, 2.6, 2.7, 3.3,
81 tsvz [#f2]_ 3.4, 3.5, 3.6
82 pypy
83 `pyexcel-xls`_ xls, xlsx(read only), `xlrd`_, same as above
199 ======================== ======================= =================
200 Package name Supported file formats Dependencies
201 ======================== ======================= =================
202 `pyexcel-io`_ csv, csvz [#f1]_, tsv,
203 tsvz [#f2]_
204 `pyexcel-xls`_ xls, xlsx(read only), `xlrd`_,
84205 xlsm(read only) `xlwt`_
85 `pyexcel-xlsx`_ xlsx `openpyxl`_ same as above
86 `pyexcel-ods3`_ ods `pyexcel-ezodf`_, 2.6, 2.7, 3.3, 3.4
87 lxml 3.5, 3.6
88 `pyexcel-ods`_ ods `odfpy`_ same as above
89 ======================== ======================= ================= ==================
206 `pyexcel-xlsx`_ xlsx `openpyxl`_
207 `pyexcel-ods3`_ ods `pyexcel-ezodf`_,
208 lxml
209 `pyexcel-ods`_ ods `odfpy`_
210 ======================== ======================= =================
90211
91212 .. table:: Dedicated file reader and writers
92213
93 ======================== ======================= ================= ==================
94 Package name Supported file formats Dependencies Python versions
95 ======================== ======================= ================= ==================
96 `pyexcel-xlsxw`_ xlsx(write only) `XlsxWriter`_ Python 2 and 3
97 `pyexcel-xlsxr`_ xlsx(read only) lxml same as above
98 `pyexcel-xlsbr`_ xlsx(read only) pyxlsb same as above
99 `pyexcel-odsr`_ read only for ods, fods lxml same as above
100 `pyexcel-odsw`_ write only for ods loxun same as above
101 `pyexcel-htmlr`_ html(read only) lxml,html5lib same as above
102 `pyexcel-pdfr`_ pdf(read only) pdftables Python 2 only.
103 ======================== ======================= ================= ==================
214 ======================== ======================= =================
215 Package name Supported file formats Dependencies
216 ======================== ======================= =================
217 `pyexcel-xlsxw`_ xlsx(write only) `XlsxWriter`_
218 `pyexcel-libxlsxw`_ xlsx(write only) `libxlsxwriter`_
219 `pyexcel-xlsxr`_ xlsx(read only) lxml
220 `pyexcel-xlsbr`_ xlsb(read only) pyxlsb
221 `pyexcel-odsr`_ read only for ods, fods lxml
222 `pyexcel-odsw`_ write only for ods loxun
223 `pyexcel-htmlr`_ html(read only) lxml,html5lib
224 `pyexcel-pdfr`_ pdf(read only) camelot
225 ======================== ======================= =================
226
227
228 Plugin shopping guide
229 ------------------------
230
231 Since 2020, all pyexcel-io plugins have dropped the support for python versions
232 which are lower than 3.6. If you want to use any of those Python versions, please use pyexcel-io
233 and its plugins versions that are lower than 0.6.0.
234
235
236 Except csv files, xls, xlsx and ods files are a zip of a folder containing a lot of
237 xml files
238
239 The dedicated readers for excel files can stream read
240
241
242 In order to manage the list of plugins installed, you need to use pip to add or remove
243 a plugin. When you use virtualenv, you can have different plugins per virtual
244 environment. In the situation where you have multiple plugins that does the same thing
245 in your environment, you need to tell pyexcel which plugin to use per function call.
246 For example, pyexcel-ods and pyexcel-odsr, and you want to get_array to use pyexcel-odsr.
247 You need to append get_array(..., library='pyexcel-odsr').
248
104249
105250
106251 .. _pyexcel-io: https://github.com/pyexcel/pyexcel-io
113258 .. _pyexcel-pdfr: https://github.com/pyexcel/pyexcel-pdfr
114259
115260 .. _pyexcel-xlsxw: https://github.com/pyexcel/pyexcel-xlsxw
261 .. _pyexcel-libxlsxw: https://github.com/pyexcel/pyexcel-libxlsxw
116262 .. _pyexcel-xlsxr: https://github.com/pyexcel/pyexcel-xlsxr
117263 .. _pyexcel-xlsbr: https://github.com/pyexcel/pyexcel-xlsbr
118264 .. _pyexcel-htmlr: https://github.com/pyexcel/pyexcel-htmlr
123269 .. _XlsxWriter: https://github.com/jmcnamara/XlsxWriter
124270 .. _pyexcel-ezodf: https://github.com/pyexcel/pyexcel-ezodf
125271 .. _odfpy: https://github.com/eea/odfpy
272 .. _libxlsxwriter: http://libxlsxwriter.github.io/getting_started.html
126273
127274 .. table:: Other data renderers
128275
156303 .. _pyexcel-gantt: https://github.com/pyexcel/pyexcel-gantt
157304 .. _frappe-gantt: https://github.com/frappe/gantt
158305
159 In order to manage the list of plugins installed, you need to use pip to add or remove
160 a plugin. When you use virtualenv, you can have different plugins per virtual
161 environment. In the situation where you have multiple plugins that does the same thing
162 in your environment, you need to tell pyexcel which plugin to use per function call.
163 For example, pyexcel-ods and pyexcel-odsr, and you want to get_array to use pyexcel-odsr.
164 You need to append get_array(..., library='pyexcel-odsr').
165
166306 .. rubric:: Footnotes
167307
168308 .. [#f1] zipped csv file
175315 ======== ========== ============= ==================== ============= =============
176316 pyexcel pyexcel-io pyexcel-text pyexcel-handsontable pyexcel-pygal pyexcel-gantt
177317 ======== ========== ============= ==================== ============= =============
178 0.5.14+ 0.5.18+ 0.2.6+ 0.0.1+ 0.0.1 0.0.1
318 0.6.5+ 0.6.2+ 0.2.6+ 0.0.1+ 0.0.1 0.0.1
319 0.5.15+ 0.5.19+ 0.2.6+ 0.0.1+ 0.0.1 0.0.1
320 0.5.14 0.5.18 0.2.6+ 0.0.1+ 0.0.1 0.0.1
179321 0.5.10+ 0.5.11+ 0.2.6+ 0.0.1+ 0.0.1 0.0.1
180322 0.5.9.1+ 0.5.9.1+ 0.2.6+ 0.0.1 0.0.1 0.0.1
181323 0.5.4+ 0.5.1+ 0.2.6+ 0.0.1 0.0.1 0.0.1
184326 ======== ========== ============= ==================== ============= =============
185327
186328
187 .. table:: a list of support file formats
329 .. table:: A list of supported file formats
188330
189331 ============ =======================================================
190332 file format definition
194336 csvz a zip file that contains one or many csv files
195337 tsvz a zip file that contains one or many tsv files
196338 xls a spreadsheet file format created by
197 MS-Excel 97-2003 [#f1]_
339 MS-Excel 97-2003
198340 xlsx MS-Excel Extensions to the Office Open XML
199 SpreadsheetML File Format. [#f2]_
341 SpreadsheetML File Format.
200342 xlsm an MS-Excel Macro-Enabled Workbook file
201343 ods open document spreadsheet
202344 fods flat open document spreadsheet
208350 ============ =======================================================
209351
210352
211 .. [f1] quoted from `whatis.com <http://whatis.techtarget.com/fileformat/XLS-Worksheet-file-Microsoft-Excel>`_. Technical details can be found at `MSDN XLS <https://msdn.microsoft.com/en-us/library/office/gg615597(v=office.14).aspx>`_
212 .. [f2] xlsx is used by MS-Excel 2007, more information can be found at `MSDN XLSX <https://msdn.microsoft.com/en-us/library/dd922181(v=office.12).aspx>`_
213
214
215353 Usage
216354 ------
217355
282420 architecture
283421
284422 New tutorial
285 ----------
423 --------------
286424 .. toctree::
287425
288426 quickstart
295433 database
296434
297435 Old tutorial
298 ----------
436 --------------
299437 .. toctree::
300438
301439 tutorial_file
353491
354492 migration_guide
355493 changelog
356 note_on_pypy
357494
358495
359496 Indices and tables
0 What's breaking in 0.7.0
1 ================================================================================
2
3 The following statements will stop working::
4
5 import pyexcel.ext.ods
6 import pyexcel.ext.ods3
7 import pyexcel.ext.text
8 import pyexcel.ext.xls
9 import pyexcel.ext.xlsx
10
11 as they were deprecated since v0.2.2
12
13
14 What's breaking in 0.6.0
15 ================================================================================
16
17 In the following statements::
18
19 sheet_a = sheet.row + rows
20 sheet_b = sheet.column + columns
21 book = sheet_a + sheet_b
22
23 `sheet_a` and `sheet_b` will no longer have access to the data of `sheet`. `book`
24 will no longer have access to the data of `sheet_a` and `sheet_b`.
25
26 Under Hyrum's Law, this enhancement in 0.6.0 will cause breakage otherwise.
27
028 What's breaking in 0.5.9
129 ================================================================================
230
0
01 One liners
12 ================================================================================
23
34 This section shows you how to get data from your excel files and how to
45 export data to excel files in **one line**
56
6 One liner to get data from the excel files
7 Read from the excel files
78 --------------------------------------------------------------------------------
89
910 Get a list of dictionaries
1516 >>> import os
1617 >>> import pyexcel as p
1718 >>> content="""
18 ... Coffees,Serving Size,Caffeine (mg)
19 ... Starbucks Coffee Blonde Roast,venti(20 oz),475
20 ... Dunkin' Donuts Coffee with Turbo Shot,large(20 oz.),398
21 ... Starbucks Coffee Pike Place Roast,grande(16 oz.),310
22 ... Panera Coffee Light Roast,regular(16 oz.),300
19 ... Name,Period,Representative Composers
20 ... Medieval,c.1150-c.1400,"Machaut, Landini"
21 ... Renaissance,c.1400-c.1600,"Gibbons, Frescobaldi"
22 ... Baroque,c.1600-c.1750,"JS Bach, Vivaldi"
23 ... Classical,c.1750-c.1830,"Joseph Haydn, Wolfgan Amadeus Mozart"
24 ... Earley Romantic,c.1830-c.1860,"Chopin, Mendelssohn, Schumann, Liszt"
25 ... Late Romantic,c.1860-c.1920,"Wagner,Verdi"
2326 ... """.strip()
2427 >>> sheet = p.get_sheet(file_content=content, file_type='csv')
2528 >>> sheet.save_as("your_file.xls")
2629
27 Suppose you want to process the :download:`following coffee data <coffee.csv>` (data source `coffee chart <https://cspinet.org/eating-healthy/ingredients-of-concern/caffeine-chart>`_ on the center for science in the public interest):
30
31 Suppose you want to process `History of Classical Music <https://www.naxos.com/education/brief_history.asp>`_:
32
2833
2934 .. pyexcel-table::
3035
31 ---pyexcel:Top 5 coffeine drinks---
32 Coffees,Serving Size,Caffeine (mg)
33 Starbucks Coffee Blonde Roast,venti(20 oz),475
34 Dunkin' Donuts Coffee with Turbo Shot,large(20 oz.),398
35 Starbucks Coffee Pike Place Roast,grande(16 oz.),310
36 Panera Coffee Light Roast,regular(16 oz.),300
37
38 Let's get a list of dictionary out from the xls file::
39
36 ---pyexcel:History of Classical Music---
37 Name,Period,Representative Composers
38 Medieval,c.1150-c.1400,"Machaut, Landini"
39 Renaissance,c.1400-c.1600,"Gibbons, Frescobaldi"
40 Baroque,c.1600-c.1750,"JS Bach, Vivaldi"
41 Classical,c.1750-c.1830,"Joseph Haydn, Wolfgan Amadeus Mozart"
42 Earley Romantic,c.1830-c.1860,"Chopin, Mendelssohn, Schumann, Liszt"
43 Late Romantic,c.1860-c.1920, "Wagner,Verdi"
44
45
46
47 Let's get a list of dictionary out from the xls file:
48
49 .. code-block:: python
50
4051 >>> records = p.get_records(file_name="your_file.xls")
4152
42 And let's check what do we have::
43
44 >>> for record in records:
45 ... print("%s of %s has %s mg" % (
46 ... record['Serving Size'],
47 ... record['Coffees'],
48 ... record['Caffeine (mg)']))
49 venti(20 oz) of Starbucks Coffee Blonde Roast has 475 mg
50 large(20 oz.) of Dunkin' Donuts Coffee with Turbo Shot has 398 mg
51 grande(16 oz.) of Starbucks Coffee Pike Place Roast has 310 mg
52 regular(16 oz.) of Panera Coffee Light Roast has 300 mg
53 And let's check what do we have:
54
55 .. code-block:: python
56
57 >>> for row in records:
58 ... print(f"{row['Representative Composers']} are from {row['Name']} period ({row['Period']})")
59 Machaut, Landini are from Medieval period (c.1150-c.1400)
60 Gibbons, Frescobaldi are from Renaissance period (c.1400-c.1600)
61 JS Bach, Vivaldi are from Baroque period (c.1600-c.1750)
62 Joseph Haydn, Wolfgan Amadeus Mozart are from Classical period (c.1750-c.1830)
63 Chopin, Mendelssohn, Schumann, Liszt are from Earley Romantic period (c.1830-c.1860)
64 Wagner,Verdi are from Late Romantic period (c.1860-c.1920)
5365
5466
5567 Get two dimensional array
5668 ********************************************************************************
5769
58 Instead, what if you have to use :meth:`pyexcel.get_array` to do the same:
70 Instead, what if you have to use `pyexcel.get_array` to do the same:
71
72 .. code-block:: python
5973
6074 >>> for row in p.get_array(file_name="your_file.xls", start_row=1):
61 ... print("%s of %s has %s mg" % (
62 ... row[1],
63 ... row[0],
64 ... row[2]))
65 venti(20 oz) of Starbucks Coffee Blonde Roast has 475 mg
66 large(20 oz.) of Dunkin' Donuts Coffee with Turbo Shot has 398 mg
67 grande(16 oz.) of Starbucks Coffee Pike Place Roast has 310 mg
68 regular(16 oz.) of Panera Coffee Light Roast has 300 mg
75 ... print(f"{row[2]} are from {row[0]} period ({row[1]})")
76 Machaut, Landini are from Medieval period (c.1150-c.1400)
77 Gibbons, Frescobaldi are from Renaissance period (c.1400-c.1600)
78 JS Bach, Vivaldi are from Baroque period (c.1600-c.1750)
79 Joseph Haydn, Wolfgan Amadeus Mozart are from Classical period (c.1750-c.1830)
80 Chopin, Mendelssohn, Schumann, Liszt are from Earley Romantic period (c.1830-c.1860)
81 Wagner,Verdi are from Late Romantic period (c.1860-c.1920)
82
6983
7084 where `start_row` skips the header row.
7185
7892 Now let's get a dictionary out from the spreadsheet:
7993
8094 .. code-block:: python
81
95
8296 >>> my_dict = p.get_dict(file_name="your_file.xls", name_columns_by_row=0)
8397
84 And check what do we have::
98 And check what do we have:
99
100 .. code-block:: python
85101
86102 >>> from pyexcel._compact import OrderedDict
87103 >>> isinstance(my_dict, OrderedDict)
88104 True
89105 >>> for key, values in my_dict.items():
90106 ... print(key + " : " + ','.join([str(item) for item in values]))
91 Coffees : Starbucks Coffee Blonde Roast,Dunkin' Donuts Coffee with Turbo Shot,Starbucks Coffee Pike Place Roast,Panera Coffee Light Roast
92 Serving Size : venti(20 oz),large(20 oz.),grande(16 oz.),regular(16 oz.)
93 Caffeine (mg) : 475,398,310,300
107 Name : Medieval,Renaissance,Baroque,Classical,Earley Romantic,Late Romantic
108 Period : c.1150-c.1400,c.1400-c.1600,c.1600-c.1750,c.1750-c.1830,c.1830-c.1860,c.1860-c.1920
109 Representative Composers : Machaut, Landini,Gibbons, Frescobaldi,JS Bach, Vivaldi,Joseph Haydn, Wolfgan Amadeus Mozart,Chopin, Mendelssohn, Schumann, Liszt,Wagner,Verdi
110
94111
95112 Please note that my_dict is an OrderedDict.
96113
126143 >>> data.update({"Sheet 3": a_dictionary_of_two_dimensional_arrays['Sheet 3']})
127144 >>> p.save_book_as(bookdict=data, dest_file_name="book.xls")
128145
146
129147 Suppose you have a multiple sheet book as the following:
148
130149
131150 .. pyexcel-table::
132151
145164 3,2,1
146165 4,3,2
147166
148 Here is the code to obtain those sheets as a single dictionary::
167
168 Here is the code to obtain those sheets as a single dictionary:
169
170 .. code-block:: python
149171
150172 >>> book_dict = p.get_book_dict(file_name="book.xls")
151173
152 And check::
174 And check:
175
176 .. code-block:: python
177
153178 >>> isinstance(book_dict, OrderedDict)
154179 True
155180 >>> import json
159184 {"Sheet 2": [["X", "Y", "Z"], [1, 2, 3], [4, 5, 6]]}
160185 {"Sheet 3": [["O", "P", "Q"], [3, 2, 1], [4, 3, 2]]}
161186
187
162188 .. testcode::
163189 :hide:
164190
166192 >>> os.unlink("book.xls")
167193
168194
169 Data export in one line
195 Write data
170196 ---------------------------------------------
171197
172198 Export an array
173199 **********************
174200
175 Suppose you have the following array::
201 Suppose you have the following array:
202
203 .. code-block:: python
176204
177205 >>> data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
178206
179 And here is the code to save it as an excel file ::
207 And here is the code to save it as an excel file :
208
209 .. code-block:: python
180210
181211 >>> p.save_as(array=data, dest_file_name="example.xls")
182212
183 Let's verify it::
213 Let's verify it:
214
215 .. code-block:: python
184216
185217 >>> p.get_sheet(file_name="example.xls")
186218 pyexcel_sheet1:
198230 >>> import os
199231 >>> os.unlink("example.xls")
200232
201
202 And here is the code to save it as a csv file ::
233 And here is the code to save it as a csv file :
234
235 .. code-block:: python
203236
204237 >>> p.save_as(array=data,
205238 ... dest_file_name="example.csv",
206239 ... dest_delimiter=':')
207240
208 Let's verify it::
241 Let's verify it:
242
243 .. code-block:: python
209244
210245 >>> with open("example.csv") as f:
211246 ... for line in f.readlines():
218253 Export a list of dictionaries
219254 **********************************
220255
221 ::
256 .. code-block:: python
222257
223258 >>> records = [
224259 ... {"year": 1903, "country": "Germany", "speed": "206.7km/h"},
231266 Export a dictionary of single key value pair
232267 ********************************************************************************
233268
234 ::
269 .. code-block:: python
235270
236271 >>> henley_on_thames_facts = {
237272 ... "area": "5.58 square meters",
246281 Export a dictionary of single dimensonal array
247282 ********************************************************************************
248283
284 .. code-block:: python
285
249286 >>> ccs_insights = {
250287 ... "year": ["2017", "2018", "2019", "2020", "2021"],
251288 ... "smart phones": [1.53, 1.64, 1.74, 1.82, 1.90],
257294 Export a dictionary of two dimensional array as a book
258295 ********************************************************************************
259296
260 Suppose you want to save the below dictionary to an excel file ::
261
297 Suppose you want to save the below dictionary to an excel file :
298
299 .. code-block:: python
300
262301 >>> a_dictionary_of_two_dimensional_arrays = {
263302 ... 'Sheet 1':
264303 ... [
280319 ... ]
281320 ... }
282321
283 Here is the code::
322 Here is the code:
323
324 .. code-block:: python
284325
285326 >>> p.save_book_as(
286327 ... bookdict=a_dictionary_of_two_dimensional_arrays,
288329 ... )
289330
290331 If you want to preserve the order of sheets in your dictionary, you have to
291 pass on an ordered dictionary to the function itself. For example::
332 pass on an ordered dictionary to the function itself. For example:
333
334 .. code-block:: python
292335
293336 >>> data = OrderedDict()
294337 >>> data.update({"Sheet 2": a_dictionary_of_two_dimensional_arrays['Sheet 2']})
296339 >>> data.update({"Sheet 3": a_dictionary_of_two_dimensional_arrays['Sheet 3']})
297340 >>> p.save_book_as(bookdict=data, dest_file_name="book.xls")
298341
299 Let's verify its order::
342 Let's verify its order:
343
344 .. code-block:: python
300345
301346 >>> book_dict = p.get_book_dict(file_name="book.xls")
302347 >>> for key, item in book_dict.items():
308353 Please notice that "Sheet 2" is the first item in the *book_dict*, meaning the order of sheets are preserved.
309354
310355
311 File format transcoding on one line
356 Transcoding
312357 -------------------------------------------
313358
314359 .. note::
315360
316 Please note that the following file transcoding could be with zero line. Please
317 install pyexcel-cli and you will do the transcode in one command. No need to
318 open your editor, save the problem, then python run.
319
361 Please note that `pyexcel-cli` can perform file transcoding at command line.
362 No need to open your editor, save the problem, then python run.
320363
321364 .. testcode::
322365 :hide:
329372 ... ]
330373 >>> p.save_as(array=data, dest_file_name="birth.xls")
331374
332 The following code does a simple file format transcoding from xls to csv::
375
376 The following code does a simple file format transcoding from xls to csv:
377
378 .. code-block:: python
333379
334380 >>> p.save_as(file_name="birth.xls", dest_file_name="birth.csv")
335381
336382 Again it is really simple. Let's verify what we have gotten:
383
384 .. code-block:: python
337385
338386 >>> sheet = p.get_sheet(file_name="birth.csv")
339387 >>> sheet
353401
354402 Let use previous example and save it as xlsx instead
355403
404 .. code-block:: python
405
356406 >>> p.save_as(file_name="birth.xls",
357407 ... dest_file_name="birth.xlsx") # change the file extension
358408
359409 Again let's verify what we have gotten:
410
411 .. code-block:: python
360412
361413 >>> sheet = p.get_sheet(file_name="birth.xlsx")
362414 >>> sheet
376428 Merge all excel files in directory into a book where each file become a sheet
377429 ********************************************************************************
378430
379 The following code will merge every excel files into one file, say "output.xls"::
431 The following code will merge every excel files into one file, say "output.xls":
432
433 .. code-block:: python
380434
381435 from pyexcel.cookbook import merge_all_to_a_book
382436 import glob
384438
385439 merge_all_to_a_book(glob.glob("your_csv_directory\*.csv"), "output.xls")
386440
387 You can mix and match with other excel formats: xls, xlsm and ods. For example, if you are sure you have only xls, xlsm, xlsx, ods and csv files in `your_excel_file_directory`, you can do the following::
441 You can mix and match with other excel formats: xls, xlsm and ods. For example, if you are sure you have only xls, xlsm, xlsx, ods and csv files in `your_excel_file_directory`, you can do the following:
442
443 .. code-block:: python
388444
389445 from pyexcel.cookbook import merge_all_to_a_book
390446 import glob
399455 :hide:
400456
401457 >>> content = {
402 ... 'Sheet 1':
458 ... 'Sheet 1':
403459 ... [
404 ... [1.0, 2.0, 3.0],
405 ... [4.0, 5.0, 6.0],
460 ... [1.0, 2.0, 3.0],
461 ... [4.0, 5.0, 6.0],
406462 ... [7.0, 8.0, 9.0]
407463 ... ],
408 ... 'Sheet 2':
464 ... 'Sheet 2':
409465 ... [
410 ... ['X', 'Y', 'Z'],
411 ... [1.0, 2.0, 3.0],
466 ... ['X', 'Y', 'Z'],
467 ... [1.0, 2.0, 3.0],
412468 ... [4.0, 5.0, 6.0]
413 ... ],
414 ... 'Sheet 3':
469 ... ],
470 ... 'Sheet 3':
415471 ... [
416 ... ['O', 'P', 'Q'],
417 ... [3.0, 2.0, 1.0],
472 ... ['O', 'P', 'Q'],
473 ... [3.0, 2.0, 1.0],
418474 ... [4.0, 3.0, 2.0]
419 ... ]
475 ... ]
420476 ... }
421477 >>> book = p.Book(content)
422478 >>> book.save_as("megabook.xls")
423479
424 Suppose you have many sheets in a work book and you would like to separate each into a single sheet excel file. You can easily do this::
480
481 Suppose you have many sheets in a work book and you would like to separate each into a single sheet excel file. You can easily do this:
482
483 .. code-block:: python
425484
426485 >>> from pyexcel.cookbook import split_a_book
427486 >>> split_a_book("megabook.xls", "output.xls")
447506 *************************************
448507
449508
450 Suppose you just want to extract one sheet from many sheets that exists in a work book and you would like to separate it into a single sheet excel file. You can easily do this::
509 Suppose you just want to extract one sheet from many sheets that exists in a work book and you would like to separate it into a single sheet excel file. You can easily do this:
510
511 .. code-block:: python
451512
452513 >>> from pyexcel.cookbook import extract_a_sheet_from_a_book
453514 >>> extract_a_sheet_from_a_book("megabook.xls", "Sheet 1", "output.xls")
2828 `pyexcel-echarts`_ draws 2D, 3D, geo charts from pyexcel data and has awesome animations too, but
2929 it is under development.
3030
31 `pyexcel-matplotlib`_ helps you with scentific charts and is under developmement.
31 `pyexcel-matplotlib`_ helps you with scientific charts and is under developmement.
3232
3333 Gantt chart visualization for your excel data
3434 -------------------------------------------------
4242 .. _pyexcel-echarts: https://github.com/pyexcel-renderers/pyexcel-echarts
4343 .. _pyexcel-matplotlib: https://github.com/pyexcel-renderers/pyexcel-matplotlib
4444 .. _sphinxcontrib-excel: https://github.com/pyexcel-renderers/sphinxcontrib-excel
45 .. _pyexcel-gantt: https://github.com/pyexcel-renderers/pyexcel-gantt
4546
47
00 Sheet
11 ==========
2
3 The sheet api here is much less powerful than pandas DataFrame when the array is of
4 significant size. To be honesty, pandas DataFrame is much more powerful and
5 provide rich data manipulation apis. When would you consider the sheet api here?
6 if your data manipulation steps are basic and your data volume is not high, you
7 can use them.
8
29
310 Random access
411 -----------------
6875 In order to set a value to a cell, please use
6976 sheet[row_index, column_index] = new_value
7077
78 or sheet['A1'] = new_value
79
7180
7281 **Random access to rows and columns**
7382
185194
186195 >>> list(sheet.rows())
187196 [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
188
189 You can get data from the bottom to the top one by
190 calling :meth:`~pyexcel.Sheet.rrows()`::
191
192 >>> list(sheet.rrows())
193 [[7, 8, 9], [4, 5, 6], [1, 2, 3]]
194
195 You might want the data arranged vertically. You can call
196 :meth:`~pyexcel.Sheet.columns()`::
197
198 >>> list(sheet.columns())
199 [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
200
201 You can get columns in reverse sequence as well by calling
202 :meth:`~pyexcel.Sheet.rcolumns()`::
203
204 >>> list(sheet.rcolumns())
205 [[3, 6, 9], [2, 5, 8], [1, 4, 7]]
206
207 Do you want to flatten the data? You can get the content in one
208 dimensional array. If you are interested in playing with one
209 dimensional enumeration, you can check out these functions
210 :meth:`~pyexcel.Sheet.enumerate`, :meth:`~pyexcel.Sheet.reverse`,
211 :meth:`~pyexcel.Sheet.vertical`, and :meth:`~pyexcel.Sheet.rvertical()`::
212
213 >>> list(sheet.enumerate())
214 [1, 2, 3, 4, 5, 6, 7, 8, 9]
215 >>> list(sheet.reverse())
216 [9, 8, 7, 6, 5, 4, 3, 2, 1]
217 >>> list(sheet.vertical())
218 [1, 4, 7, 2, 5, 8, 3, 6, 9]
219 >>> list(sheet.rvertical())
220 [9, 6, 3, 8, 5, 2, 7, 4, 1]
221197
222198
223199 **attributes**
397373 ... [12, 15]
398374 ... ]
399375 >>> sheet2 = pyexcel.Sheet(extra_data)
400 >>> sheet.column += sheet2
401 >>> sheet.column["Column 4"]
376 >>> sheet3 = sheet.column + sheet2
377 >>> sheet3.column["Column 4"]
402378 [10, 11, 12]
403 >>> sheet.column["Column 5"]
379 >>> sheet3.column["Column 5"]
404380 [13, 14, 15]
381
382 Please note above column plus statement will not update original `sheet` instance, as
383 pyexcel user demanded:
384
385 .. code-block:: python
386
387 >>> sheet
388 pyexcel sheet:
389 +----------+----------+
390 | Column 1 | Column 3 |
391 +==========+==========+
392 | 1 | 7 |
393 +----------+----------+
394 | 2 | 8 |
395 +----------+----------+
396 | 3 | 9 |
397 +----------+----------+
398
399 So, to change orginal `sheet` instance, you can elect to do:
400
401 .. code-block:: python
402
403 >>> sheet.column += sheet2
405404
406405 Here is what you will get:
407406
160160 .. testcode::
161161 :hide:
162162
163 >>> from mock import patch, MagicMock
163 >>> from unittest.mock import patch, MagicMock
164164 >>> import pyexcel as pe
165 >>> from pyexcel._compact import StringIO
165 >>> from pyexcel._compact import StringIO, PY2, BytesIO
166166 >>> patcher = patch('pyexcel._compact.request.urlopen')
167167 >>> urlopen = patcher.start()
168 >>> io = StringIO("1,2,3")
168 >>> io = StringIO("1,2,3") if PY2 else BytesIO("1,2,3".encode('utf-8'))
169169 >>> x = MagicMock()
170170 >>> x.type.return_value = "text/csv"
171171 >>> io.info = x
188188 .. testcode::
189189 :hide:
190190
191 >>> patcher.stop()
191 >>> patcher.stop() # doctest: +SKIP
192192
193193
194194 For sheet
200200 .. testcode::
201201 :hide:
202202
203 >>> from mock import patch, MagicMock
203 >>> from unittest.mock import patch, MagicMock
204204 >>> import os
205205 >>> patcher = patch('pyexcel._compact.request.urlopen')
206206 >>> fake_url_open = patcher.start()
229229 .. testcode::
230230 :hide:
231231
232 >>> patcher.stop()
232 >>> patcher.stop() # doctest: +SKIP
233233
234234
235235 For book
240240 .. testcode::
241241 :hide:
242242
243 >>> from mock import patch, MagicMock
243 >>> from unittest.mock import patch, MagicMock
244244 >>> import os
245245 >>> patcher = patch('pyexcel._compact.request.urlopen')
246246 >>> fake_url_open = patcher.start()
286286 .. testcode::
287287 :hide:
288288
289 >>> patcher.stop()
289 >>> patcher.stop() # doctest: +SKIP
514514 >>> os.unlink("output.csv")
515515 >>> os.unlink("example.xls")
516516 >>> os.unlink("example_series.xls")
517 >>> os.unlink("tutorial.csv")
250250 :hide:
251251
252252 >>> import os
253 >>> import pyexcel
253 >>> import pyexcel as pe
254254 >>> data = [
255255 ... ["Row 1", 1, 2, 3],
256256 ... ["Row 2", 4, 5, 6],
257257 ... ["Row 3", 7, 8, 9],
258258 ... ]
259 >>> pyexcel.save_as(array=data, dest_file_name="row_example.xls")
259 >>> pe.save_as(array=data, dest_file_name="row_example.xls")
260260
261261 And then you want to update "Row 3" with for example::
262262
0
01 Stream APIs for big file : A set of two liners
12 ================================================================================
23
4 When you are dealing with **BIG** excel files, you will want **pyexcel** to use
5 constant memory.
6
37 This section shows you how to get data from your **BIG** excel files and how to
4 export data to excel files in **two lines** at most.
8 export data to excel files in **two lines** at most, without eating all
9 your computer memory.
510
611
712 Two liners for get data from big excel files
2530 >>> sheet = p.get_sheet(file_content=content, file_type='csv')
2631 >>> sheet.save_as("your_file.xls")
2732
33
34
2835 Suppose you want to process the following coffee data:
2936
3037 .. pyexcel-table::
3643 Starbucks Coffee Pike Place Roast,grande(16 oz.),310
3744 Panera Coffee Light Roast,regular(16 oz.),300
3845
39 Let's get a list of dictionary out from the xls file::
40
46 Let's get a list of dictionary out from the xls file:
47
48 .. code-block:: python
49
4150 >>> records = p.iget_records(file_name="your_file.xls")
4251
43 And let's check what do we have::
44
45 >>> for record in records:
46 ... print("%s of %s has %s mg" % (
47 ... record['Serving Size'],
48 ... record['Coffees'],
49 ... record['Caffeine (mg)']))
52 And let's check what do we have:
53
54 .. code-block:: python
55
56 >>> for r in records:
57 ... print(f"{r['Serving Size']} of {r['Coffees']} has {r['Caffeine (mg)']} mg")
5058 venti(20 oz) of Starbucks Coffee Blonde Roast has 475 mg
5159 large(20 oz.) of Dunkin' Donuts Coffee with Turbo Shot has 398 mg
5260 grande(16 oz.) of Starbucks Coffee Pike Place Roast has 310 mg
5361 regular(16 oz.) of Panera Coffee Light Roast has 300 mg
5462
55 Please do not forgot the second line::
63 Please do not forgot the second line to close the opened file handle:
64
65 .. code-block:: python
5666
5767 >>> p.free_resources()
5868
5969 Get two dimensional array
6070 ********************************************************************************
6171
62 Instead, what if you have to use :meth:`pyexcel.get_array` to do the same:
72 Instead, what if you have to use `pyexcel.get_array` to do the same:
73
74 .. code-block:: python
6375
6476 >>> for row in p.iget_array(file_name="your_file.xls", start_row=1):
65 ... print("%s of %s has %s mg" % (
66 ... row[1],
67 ... row[0],
68 ... row[2]))
77 ... print(f"{row[1]} of {row[0]} has {row[2]} mg")
6978 venti(20 oz) of Starbucks Coffee Blonde Roast has 475 mg
7079 large(20 oz.) of Dunkin' Donuts Coffee with Turbo Shot has 398 mg
7180 grande(16 oz.) of Starbucks Coffee Pike Place Roast has 310 mg
7281 regular(16 oz.) of Panera Coffee Light Roast has 300 mg
7382
74 Again, do not forgot the second line::
83 Again, do not forgot the second line:
84
85 .. code-block:: python
7586
7687 >>> p.free_resources()
77
88
7889 where `start_row` skips the header row.
7990
8091 Data export in one liners
8394 Export an array
8495 **********************
8596
86 Suppose you have the following array::
97 Suppose you have the following array:
98
99 .. code-block:: python
87100
88101 >>> data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
89102
90 And here is the code to save it as an excel file ::
103 And here is the code to save it as an excel file :
104
105 .. code-block:: python
91106
92107 >>> p.isave_as(array=data, dest_file_name="example.xls")
93108
94109 But the following line is not required because the data source
95 are not file sources::
110 are not file sources:
111
112 .. code-block:: python
96113
97114 >>> # p.free_resources()
98115
99 Let's verify it::
116 Let's verify it:
117
118 .. code-block:: python
100119
101120 >>> p.get_sheet(file_name="example.xls")
102121 pyexcel_sheet1:
108127 | 7 | 8 | 9 |
109128 +---+---+---+
110129
130
111131 .. testcode::
112132 :hide:
113133
115135 >>> os.unlink("example.xls")
116136
117137
118 And here is the code to save it as a csv file ::
138 And here is the code to save it as a csv file :
139
140 .. code-block:: python
119141
120142 >>> p.isave_as(array=data,
121143 ... dest_file_name="example.csv",
122144 ... dest_delimiter=':')
123145
124 Let's verify it::
146 Let's verify it:
147
148 .. code-block:: python
125149
126150 >>> with open("example.csv") as f:
127151 ... for line in f.readlines():
134158 Export a list of dictionaries
135159 **********************************
136160
137 ::
161 .. code-block:: python
138162
139163 >>> records = [
140164 ... {"year": 1903, "country": "Germany", "speed": "206.7km/h"},
146170 Export a dictionary of single key value pair
147171 ********************************************************************************
148172
149 ::
173 .. code-block:: python
150174
151175 >>> henley_on_thames_facts = {
152176 ... "area": "5.58 square meters",
160184 Export a dictionary of single dimensonal array
161185 ********************************************************************************
162186
187 .. code-block:: python
188
163189 >>> ccs_insights = {
164190 ... "year": ["2017", "2018", "2019", "2020", "2021"],
165191 ... "smart phones": [1.53, 1.64, 1.74, 1.82, 1.90],
171197 Export a dictionary of two dimensional array as a book
172198 ********************************************************************************
173199
174 Suppose you want to save the below dictionary to an excel file ::
175
200 Suppose you want to save the below dictionary to an excel file :
201
202 .. code-block:: python
203
176204 >>> a_dictionary_of_two_dimensional_arrays = {
177205 ... 'Sheet 1':
178206 ... [
194222 ... ]
195223 ... }
196224
197 Here is the code::
225 Here is the code:
226
227 .. code-block:: python
198228
199229 >>> p.isave_book_as(
200230 ... bookdict=a_dictionary_of_two_dimensional_arrays,
202232 ... )
203233
204234 If you want to preserve the order of sheets in your dictionary, you have to
205 pass on an ordered dictionary to the function itself. For example::
235 pass on an ordered dictionary to the function itself. For example:
236
237 .. code-block:: python
206238
207239 >>> from pyexcel._compact import OrderedDict
208240 >>> data = OrderedDict()
212244 >>> p.isave_book_as(bookdict=data, dest_file_name="book.xls")
213245 >>> p.free_resources()
214246
215 Let's verify its order::
247 Let's verify its order:
248
249 .. code-block:: python
216250
217251 >>> import json
218252 >>> book_dict = p.get_book_dict(file_name="book.xls")
246280 ... ]
247281 >>> p.isave_as(array=data, dest_file_name="birth.xls")
248282
249 The following code does a simple file format transcoding from xls to csv::
283
284 The following code does a simple file format transcoding from xls to csv:
285
286 .. code-block:: python
250287
251288 >>> import pyexcel
252289 >>> p.save_as(file_name="birth.xls", dest_file_name="birth.csv")
253290
254291 Again it is really simple. Let's verify what we have gotten:
292
293 .. code-block:: python
255294
256295 >>> sheet = p.get_sheet(file_name="birth.csv")
257296 >>> sheet
264303 | Smith | 4.2 | 12/11/14 |
265304 +-------+--------+----------+
266305
267 .. NOTE::
306 .. note::
268307
269308 Please note that csv(comma separate value) file is pure text file. Formula, charts, images and formatting in xls file will disappear no matter which transcoding tool you use. Hence, pyexcel is a quick alternative for this transcoding job.
270309
271310
272311 Let use previous example and save it as xlsx instead
312
313 .. code-block:: python
273314
274315 >>> import pyexcel
275316 >>> p.isave_as(file_name="birth.xls",
277318
278319 Again let's verify what we have gotten:
279320
321 .. code-block:: python
322
280323 >>> sheet = p.get_sheet(file_name="birth.xlsx")
281324 >>> sheet
282325 pyexcel_sheet1:
287330 +-------+--------+----------+
288331 | Smith | 4.2 | 12/11/14 |
289332 +-------+--------+----------+
333
334 .. testcode::
335 :hide:
336
337 >>> import os
338 >>> os.unlink('ccs.csv')
339 >>> os.unlink('book.xls')
2929 [pyexcel-xls]
3030 [pyexcel-xlsx]
3131 [pyexcel-xlsxw]
32 [pyexcel-libxlsxw]
33 [pyexcel-xlsxr]
3234 }
3335
3436 [tabulate] <<dependency>>
4042 [xlsxwriter] <<dependency>>
4143 [ezodf] <<dependency>>
4244 [odfpy] <<dependency>>
45 [lxml] <<dependency>>
46 [libxlsxwpy] <<dependency>>
47 [libxlsx] <<dependency>>
4348
4449 [pyexcel public api] -right-> [pyexcel internal api]
4550 [pyexcel internal api] -right-> [pyexcel plugin interfaces]
5762 [pyexcel-io plugin interfaces] ..> [csv, csvz, tsv, tsvz]
5863 [pyexcel-io plugin interfaces] ..> [django, sqlalchemy]
5964 [pyexcel-io plugin interfaces] .up.> [pyexcel-xls]
65 [pyexcel-io plugin interfaces] .up.> [pyexcel-xlsxw]
66 [pyexcel-io plugin interfaces] .up.> [pyexcel-libxlsxw]
6067 [pyexcel-io plugin interfaces] .up.> [pyexcel-xlsx]
68 [pyexcel-io plugin interfaces] .up.> [pyexcel-xlsxr]
6169 [pyexcel-io plugin interfaces] .left.> [pyexcel-ods]
6270 [pyexcel-io plugin interfaces] .right.> [pyexcel-ods3]
63 [pyexcel-io plugin interfaces] .right.> [pyexcel-xlsxw]
6471 [pyexcel-io plugin interfaces] .right.> [pyexcel-odsr]
72
6573
6674 [pyexcel-text] -up- [tabulate]
6775 [pyexcel-pygal] -up- [pygal]
6977 [pyexcel-xls] -right- [xlrd]
7078 [pyexcel-xls] -right- [xlwt]
7179 [pyexcel-xlsx] -right- [openpyxl]
72 [pyexcel-xlsxw] -down- [xlsxwriter]
80 [pyexcel-xlsxw] -up- [xlsxwriter]
7381 [pyexcel-ods3] --- [ezodf]
7482 [pyexcel-ods] --- [odfpy]
83 [pyexcel-odsr] -up- [lxml]
84 [pyexcel-xlsxr] -right- [lxml]
85 [sphinxcontrib-excel] -right-> [pyexcel-handsontable]
86 [pyexcel-libxlsxw] -up- [libxlsxwpy]
87 [libxlsxwpy] -right- [libxlsx]
7588
7689 skinparam component{
7790
0 isort $(find pyexcel -name "*.py"|xargs echo) $(find tests -name "*.py"|xargs echo)
1 black -l 79 pyexcel
2 black -l 79 tests
0 flake8 . --exclude=.moban.d,docs --builtins=unicode,xrange,long
0 pip install flake8
1 flake8 --exclude=.moban.d,docs,setup.py --builtins=unicode,xrange,long . && python setup.py checkdocs
0 chardet
01 lml==0.0.4
1 pyexcel-io==0.5.18
2 texttable==0.8.3;python_version<"3"
3 texttable==0.8.3;python_version>="3"
4 ordereddict;python_version<"2.7"
5 weakrefset;python_version<"2.7"
6 lxml==3.4.4;platform_python_implementation=="PyPy"
2 pyexcel-io==0.6.2
3 texttable==0.8.3
74 pyexcel-xlsx==0.4.1
85 pyexcel-xls==0.4.1
55 write data in different excel formats: csv, ods, xls, xlsx
66 and xlsm. It does not support formulas, styles and charts.
77
8 :copyright: (c) 2014-2019 by Onni Software Ltd.
8 :copyright: (c) 2014-2022 by Onni Software Ltd.
99 :license: New BSD License, see LICENSE for more details
1010 """
1111 from .book import Book
0 __version__ = "0.5.14"
1 __author__ = "C.W."
0 __version__ = '0.7.0'
1 __author__ = 'C.W.'
33
44 Compatibles
55
6 :copyright: (c) 2014-2019 by Onni Software Ltd.
6 :copyright: (c) 2014-2022 by Onni Software Ltd.
77 :license: New BSD License, see LICENSE for more details
88 """
99 # flake8: noqa
1616 # pylint: disable=ungrouped-imports
1717 import sys
1818 import warnings
19 from io import BytesIO, StringIO
20 from urllib import request as request
1921 from textwrap import dedent
22 from itertools import zip_longest
23 from collections import OrderedDict
2024
2125 PY2 = sys.version_info[0] == 2
2226 PY26 = PY2 and sys.version_info[1] < 7
2327 PY3_AND_ABOVE = sys.version_info[0] >= 3
2428
25 if PY26:
26 from ordereddict import OrderedDict
27 else:
28 from collections import OrderedDict
2929
30 if PY2:
31 from StringIO import StringIO
32 from StringIO import StringIO as BytesIO
33 from itertools import izip_longest as zip_longest
34 from itertools import izip as czip
35 import urllib2 as request
36
37 class Iterator(object):
38 """Python 2 iterator"""
39
40 def next(self):
41 """Iterator interface get next value"""
42 return type(self).__next__(self)
43
44 irange = xrange
45 else:
46 from io import StringIO, BytesIO
47 import urllib.request as request
48 from itertools import zip_longest
49
50 Iterator = object
51 irange = range
52 czip = zip
30 Iterator = object
31 irange = range
32 czip = zip
5333
5434
5535 def is_tuple_consists_of_strings(an_array):
33
44 Excel book
55
6 :copyright: (c) 2014-2019 by Onni Software Ltd.
6 :copyright: (c) 2014-2022 by Onni Software Ltd.
77 :license: New BSD License, see LICENSE for more details
88 """
9 import pyexcel._compact as compact
9 from pyexcel import _compact as compact
1010 from pyexcel.sheet import Sheet
1111 from pyexcel.internal.meta import BookMeta
1212 from pyexcel.internal.common import SheetIterator
3939 self.init(sheets=sheets, filename=filename, path=path)
4040
4141 def init(self, sheets=None, filename="memory", path=None):
42 """indpendent function so that it could be called multiple times
43 """
42 """indpendent function so that it could be called multiple times"""
4443 self.__path = path
4544 self.filename = filename
4645 self.load_from_sheets(sheets)
5554 if sheets is None:
5655 return
5756 keys = sheets.keys()
58 if not isinstance(sheets, compact.OrderedDict):
59 # if the end user does not care about the order
60 # we put alphatical order
61 keys = sorted(keys)
6257 for name in keys:
6358 value = sheets[name]
6459 if isinstance(value, Sheet):
8681 """
8782 Return the number of sheets
8883 """
89 return len(self.__name_array)
84 return len(self)
9085
9186 def sheet_names(self):
9287 """
221216
222217
223218 def to_book(bookstream):
224 """Convert a bookstream to Book
225 """
219 """Convert a bookstream to Book"""
226220 if isinstance(bookstream, Book):
227221 return bookstream
228222 else:
33
44 Constants appeared in pyexcel
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 # flake8: noqa
33
44 Cookbook for pyexcel
55
6 :copyright: (c) 2014-2019 by Onni Software Ltd.
6 :copyright: (c) 2014-2022 by Onni Software Ltd.
77 :license: New BSD License, see LICENSE for more details
88 """
99 import os
33
44 A list of pyexcel signature functions
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 import re
1010
11 import pyexcel.constants as constants
12 import pyexcel.docstrings as docs
13 import pyexcel.internal.core as sources
11 from pyexcel import constants as constants
12 from pyexcel import docstrings as docs
1413 from pyexcel.book import Book, to_book
1514 from pyexcel.sheet import Sheet
1615 from pyexcel._compact import OrderedDict, append_doc, zip_longest
17
18 import pyexcel_io.manager as manager
16 from pyexcel.internal import core as sources
17
18 from pyexcel_io import manager as manager
1919
2020 STARTS_WITH_DEST = "^dest_(.*)"
2121 SAVE_AS_EXCEPTION = (
33
44 List of apis that become deprecated but was kept for backward compatibility
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
9 import warnings
109 from functools import partial
1110
1211 from pyexcel.core import get_book, get_sheet
197196 message="Deprecated since v0.0.7! Please use class Book instead",
198197 )
199198 def BookReader(file_name, **keywords):
200 """For backward compatibility
201 """
199 """For backward compatibility"""
202200 return load_book(file_name, **keywords)
203
204
205 def deprecated_pyexcel_ext(version, module_name):
206 """Warn the deprecated usage"""
207 warnings.warn(
208 "Deprecated usage since v%s! Explicit import " % version
209 + "is no longer required. %s is auto imported." % module_name
210 )
33
44 Reusible docstrings
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 # flake8: noqa
33
44 Reusible docstrings for pyexcel.core
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 from . import keywords
1010
11 __GET_SHEET__ = (
12 keywords.EXAMPLE_NOTE_PAGINATION
13 + keywords.SOURCE_PARAMS_TABLE
11
12 __GET_ARRAY__ = (
13 keywords.SOURCE_PARAMS_TABLE
1414 + """
1515 **Parameters**
1616 """
1717 + keywords.SOURCE_PARAMS
18 )
19
20 __GET_SHEET__ = (
21 keywords.EXAMPLE_NOTE_PAGINATION
22 + __GET_ARRAY__
1823 )
1924
2025 __GET_BOOK__ = (
9196
9297 GET_SHEET = __GET_SHEET__
9398
94 GET_ARRAY = __GET_SHEET__
99 GET_ARRAY = __GET_ARRAY__
95100
96 IGET_ARRAY = __GET_SHEET__ + I_NOTE
101 IGET_ARRAY = __GET_ARRAY__ + I_NOTE
97102
98103 GET_DICT = __GET_SHEET__
99104
33
44 Reusible docstrings for keywords in signature functions
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 CSV_PARAMS = """
512512 :hide:
513513
514514 >>> import os
515 >>> pe.free_resources()
515516 >>> os.unlink("your_file.csv")
516517 >>> os.unlink("your_file.xlsx")
517518
33
44 Reusible docstrings for pyexcel.internal.meta
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 from .keywords import CSV_PARAMS
33
44 Exceptions appeared in pyexcel
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99
+0
-0
pyexcel/ext/__init__.py less more
(Empty file)
+0
-12
pyexcel/ext/ods.py less more
0 """
1 pyexcel.ext.ods
2 ~~~~~~~~~~~~~~~~~~~~~~~~
3
4 Deprecated module import
5
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
7 :license: New BSD License
8 """
9 from ..deprecated import deprecated_pyexcel_ext
10
11 deprecated_pyexcel_ext("0.2.2", __name__)
+0
-12
pyexcel/ext/ods3.py less more
0 """
1 pyexcel.ext.ods3
2 ~~~~~~~~~~~~~~~~~~~~~~~~
3
4 Deprecated module import
5
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
7 :license: New BSD License
8 """
9 from ..deprecated import deprecated_pyexcel_ext
10
11 deprecated_pyexcel_ext("0.2.2", __name__)
+0
-12
pyexcel/ext/text.py less more
0 """
1 pyexcel.ext.text
2 ~~~~~~~~~~~~~~~~~~~~~~~~
3
4 Deprecated module import
5
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
7 :license: New BSD License
8 """
9 from ..deprecated import deprecated_pyexcel_ext
10
11 deprecated_pyexcel_ext("0.2.1", __name__)
+0
-12
pyexcel/ext/xls.py less more
0 """
1 pyexcel.ext.xls
2 ~~~~~~~~~~~~~~~~~~~~~~~~
3
4 Deprecated module import
5
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
7 :license: New BSD License
8 """
9 from ..deprecated import deprecated_pyexcel_ext
10
11 deprecated_pyexcel_ext("0.2.2", __name__)
+0
-12
pyexcel/ext/xlsx.py less more
0 """
1 pyexcel.ext.xlsx
2 ~~~~~~~~~~~~~~~~~~~~~~~~
3
4 Deprecated module import
5
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
7 :license: New BSD License
8 """
9 from ..deprecated import deprecated_pyexcel_ext
10
11 deprecated_pyexcel_ext("0.2.2", __name__)
33
44 Pyexcel internals that subjected to change
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
9 from lml.loader import scan_plugins_regex
910 from pyexcel.internal.plugins import PARSER, RENDERER # noqa
1011 from pyexcel.internal.generators import BookStream, SheetStream # noqa
1112 from pyexcel.internal.source_plugin import SOURCE # noqa
12
13 from lml.loader import scan_plugins_regex
1413
1514 BLACK_LIST = [
1615 "pyexcel_io",
33
44 Book and sheet attributes
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
9 import pyexcel.constants as constants
9 from pyexcel import constants as constants
1010
1111 ATTRIBUTE_REGISTRY = {
1212 constants.SHEET: {
33
44 Defintion for the shared objects
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 NO_COLUMN_NAMES = "Only sheet with column names is accepted"
33
44 elementary functions to read and write generic excel content
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2021 by Onni Software Ltd.
77 :license: New BSD License
88 """
9 from pyexcel._compact import PY2
109 from pyexcel.internal import SOURCE
1110 from pyexcel.constants import DEFAULT_NO_DATA
1211 from pyexcel.internal.generators import BookStream, SheetStream
6665
6766
6867 def _seek_at_zero(a_stream):
69 if PY2:
70 try:
71 a_stream.seek(0)
72 except IOError:
73 pass
74 else:
75 import io
68 import io
7669
77 try:
78 a_stream.seek(0)
79 except io.UnsupportedOperation:
80 pass
70 try:
71 a_stream.seek(0)
72 except io.UnsupportedOperation:
73 pass
8174
8275
8376 def _one_sheet_tuple(items):
84 if not PY2:
85 items = list(items)
77 items = list(items)
8678 return items[0][0], items[0][1]
33
44 Simple garbage collector
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
9 import pyexcel.docstrings as docs
9 from pyexcel import docstrings as docs
1010 from pyexcel._compact import append_doc
1111
1212 GARBAGE = []
33
44 Defintion for the sheet and book generators.
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 from pyexcel._compact import OrderedDict
33
44 Annotate sheet and book class' attributes
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 import sys
1010 from functools import partial
1111
12 import pyexcel.constants as constants
13 import pyexcel.docstrings as docs
12 from pyexcel import constants as constants
13 from pyexcel import docstrings as docs
1414 from pyexcel._compact import PY2, append_doc
1515 from pyexcel.internal import SOURCE
1616 from pyexcel.internal.core import save_book, save_sheet, get_sheet_stream
1818
1919
2020 def make_presenter(source_getter, attribute=None):
21 """make a custom presentation method for each file types
22 """
21 """make a custom presentation method for each file types"""
2322
2423 def custom_presenter(self, **keywords):
2524 """docstring is assigned a few lines down the line"""
4140
4241
4342 def sheet_presenter(attribute=None):
44 """make a custom presentation method for sheet
45 """
43 """make a custom presentation method for sheet"""
4644 source_getter = SOURCE.get_writable_source
4745 return make_presenter(source_getter, attribute)
4846
4947
5048 def book_presenter(attribute=None):
51 """make a custom presentation method for book
52 """
49 """make a custom presentation method for book"""
5350 source_getter = SOURCE.get_writable_book_source
5451 return make_presenter(source_getter, attribute)
5552
5653
5754 def importer(attribute=None):
58 """make a custom input method for sheet
59 """
55 """make a custom input method for sheet"""
6056
6157 def custom_importer1(self, content, **keywords):
6258 """docstring is assigned a few lines down the line"""
7874
7975
8076 def book_importer(attribute=None):
81 """make a custom input method for book
82 """
77 """make a custom input method for book"""
8378
8479 def custom_book_importer(self, content, **keywords):
8580 """docstring is assigned a few lines down the line"""
260255
261256 @append_doc(docs.SAVE_AS_OPTIONS)
262257 def save_as(self, filename, **keywords):
263 """Save the content to a named file
264 """
258 """Save the content to a named file"""
265259 return save_sheet(self, file_name=filename, **keywords)
266260
267261 def save_to_memory(self, file_type, stream=None, **keywords):
33
44 Renderer and parser plugin manager
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 from lml.plugin import PluginManager
1010
1111
1212 class IOPluginManager(PluginManager):
13 """Generic plugin manager for renderer and parser
14 """
13 """Generic plugin manager for renderer and parser"""
1514
1615 def __init__(self, name):
1716 PluginManager.__init__(self, name)
1817
1918 def get_a_plugin(self, key, library=None):
20 """get a plugin to handle the file type
21 """
19 """get a plugin to handle the file type"""
2220 __file_type = None
2321 if key:
2422 __file_type = key.lower()
2725 return plugin_cls(__file_type)
2826
2927 def get_all_file_types(self):
30 """get all supported file types
31 """
28 """get all supported file types"""
3229 file_types = list(self.registry.keys())
3330 return file_types
3431
33
44 Core functionality of pyexcel, data model
55
6 :copyright: (c) 2014-2019 by Onni Software Ltd.
6 :copyright: (c) 2014-2022 by Onni Software Ltd.
77 :license: New BSD License, see LICENSE for more details
88 """
99 # flake8: noqa
33
44 Locally shared utility functions
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 import re
2727 raise NotImplementedError("Not implemented")
2828
2929 def __add__(self, other):
30 """Overload += sign
30 """Overload + sign
3131
3232 :return: self
3333 """
4646
4747
4848 def analyse_slice(aslice, upper_bound):
49 """An internal function to analyze a given slice
50 """
49 """An internal function to analyze a given slice"""
5150 if aslice.start is None:
5251 start = 0
5352 else:
33
44 Generic table column
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
9 import copy
910 import types
1011
11 import pyexcel._compact as compact
12 from pyexcel import _compact as compact
1213
1314 from . import _shared as utils
1415
249250 :return: self
250251 """
251252 if isinstance(other, compact.OrderedDict):
252 self._ref.extend_columns(other)
253 self._ref.extend_columns(copy.deepcopy(other))
253254 elif isinstance(other, list):
254 self._ref.extend_columns(other)
255 self._ref.extend_columns(copy.deepcopy(other))
255256 elif hasattr(other, "get_internal_array"):
256 self._ref.extend_columns_with_rows(other.get_internal_array())
257 self._ref.extend_columns_with_rows(
258 copy.deepcopy(other.get_internal_array())
259 )
257260 else:
258261 raise TypeError
259262
260263 return self
261264
262265 def __add__(self, other):
263 """Overload += sign
264
265 :return: self
266 """
267 self.__iadd__(other)
268 return self._ref
266 """Overload + sign
267 :return: new instance
268 """
269 new_instance = self._ref.clone()
270 if isinstance(other, compact.OrderedDict):
271 new_instance.extend_columns(copy.deepcopy(other))
272 elif isinstance(other, list):
273 new_instance.extend_columns(copy.deepcopy(other))
274 elif hasattr(other, "get_internal_array"):
275 new_instance.extend_columns_with_rows(
276 copy.deepcopy(other.get_internal_array())
277 )
278 else:
279 raise TypeError
280
281 return new_instance
269282
270283 def __getattr__(self, attr):
271284 """
280293 return self._ref.named_column_at(the_attr)
281294
282295 def format(self, column_index=None, formatter=None, format_specs=None):
283 """Format a column
284 """
296 """Format a column"""
285297 if column_index is not None:
286298 self._handle_one_formatter(column_index, formatter)
287299 elif format_specs:
33
44 These utilities help format the content
55
6 :copyright: (c) 2014-2019 by Onni Software Ltd.
6 :copyright: (c) 2014-2022 by Onni Software Ltd.
77 :license: New BSD License, see LICENSE for more details
88 """
99 import json
1010 import datetime
1111 from decimal import Decimal
1212
13 import pyexcel.constants as constants
14 from pyexcel._compact import PY2
13 from pyexcel import constants as constants
1514
1615
1716 def string_to_format(value, target_format):
107106 Decimal: float_to_format,
108107 }
109108
110 if PY2:
111 CONVERSION_FUNCTIONS[unicode] = string_to_format
112 CONVERSION_FUNCTIONS[long] = float_to_format
113
114109
115110 def default_formatter(value, to_type):
116111 return json.dumps(value)
44 Matrix, a data model that accepts any types, spread sheet style
55 of lookup.
66
7 :copyright: (c) 2014-2019 by Onni Software Ltd.
7 :copyright: (c) 2014-2022 by Onni Software Ltd.
88 :license: New BSD License, see LICENSE for more details
99 """
1010 import copy
1212 from functools import partial
1313 from itertools import chain
1414
15 import pyexcel._compact as compact
16 import pyexcel.constants as constants
15 from pyexcel import _compact as compact
16 from pyexcel import constants as constants
1717 from pyexcel.internal.meta import SheetMeta
1818 from pyexcel.internal.sheets.row import Row
1919 from pyexcel.internal.sheets.column import Column
8181 :param int column: column index which starts from 0
8282 :param any new_value: new value if this is to set the value
8383 """
84 if row < self.number_of_rows() and column < self.number_of_columns():
85 if new_value is None:
86 # get
84 fit = row < self.number_of_rows() and column < self.number_of_columns()
85 if new_value is None:
86 if fit:
8787 return self.__array[row][column]
8888 else:
89 # set
90 self.__array[row][column] = new_value
91 else:
92 if new_value is None:
9389 raise IndexError("Index out of range")
94 else:
95 self.paste((row, column), [[new_value]])
90 else:
91 if not fit:
92 width, array = uniform(self.__array, row + 1, column + 1)
93 self.__width, self.__array = width, array
94
95 self.__array[row][column] = new_value
9696
9797 def row_at(self, index):
9898 """
110110 raise IndexError(constants.MESSAGE_INDEX_OUT_OF_RANGE)
111111
112112 def set_row_at(self, row_index, data_array):
113 """Update a row data range
114 """
113 """Update a row data range"""
115114 nrows = self.number_of_rows()
116115 if row_index < nrows:
117116 self.__array[row_index] = data_array
448447 self.__width, self.__array = uniform(self.__array)
449448
450449 def delete_columns(self, column_indices):
451 """Delete columns by specified list of indices
452 """
450 """Delete columns by specified list of indices"""
453451 if isinstance(column_indices, list) is False:
454452 raise TypeError(constants.MESSAGE_DATA_ERROR_DATA_TYPE_MISMATCH)
455453 if len(column_indices) > 0:
501499 self.__width, self.__array = uniform(self.__array)
502500
503501 def to_array(self):
504 """Get an array out
505 """
502 """Get an array out"""
506503 return self.__array
507504
508505 def __iter__(self):
761758 value = custom_function(value)
762759 self.cell_value(row, column, value)
763760
761 def __iadd__(self, other):
762 return _add(self.name, self.__array, other)
763
764764 def __add__(self, other):
765765 """Overload the + sign
766766
767767 :returns: a new book
768768 """
769 from pyexcel.book import Book, local_uuid
770
771 content = {}
772 content[self.name] = self.__array
773 if isinstance(other, Book):
774 other_in_dict = other.to_dict()
775 for key in other_in_dict.keys():
776 new_key = key
777 if len(other_in_dict.keys()) == 1:
778 new_key = other.filename
779 if new_key in content:
780 uid = local_uuid()
781 new_key = "%s_%s" % (key, uid)
782 content[new_key] = other_in_dict[key]
783 elif isinstance(other, Matrix):
784 new_key = other.name
785 if new_key in content:
786 uid = local_uuid()
787 new_key = "%s_%s" % (other.name, uid)
788 content[new_key] = other.get_internal_array()
789 else:
790 raise TypeError
791 new_book = Book()
792 new_book.load_from_sheets(content)
793 return new_book
769 return _add(self.name, copy.deepcopy(self.__array), other)
770
771 def clone(self):
772 return Matrix(copy.deepcopy(self.__array))
794773
795774
796775 def _unique(seq):
817796 return 0
818797
819798
820 def uniform(array):
799 def uniform(array, min_rows=0, min_columns=0):
821800 """Fill-in empty strings to empty cells to make it MxN
822801
823802 :param list in_array: a list of arrays
803 :param int row_no: desired minimum row count
804 :param int column_no: desired minimum column length
824805 """
825 width = longest_row_number(array)
806 width = max(min_columns, longest_row_number(array))
807 array_length = len(array)
808 height = max(array_length, min_rows)
809
826810 if width == 0:
827811 return 0, array
828812 else:
833817 row[index] = constants.DEFAULT_NA
834818 if row_length < width:
835819 row += [constants.DEFAULT_NA] * (width - row_length)
820 for _ in range(array_length, height):
821 row = [constants.DEFAULT_NA] * width
822 array.append(row)
836823 return width, array
837824
838825
860847 row_data.append(constants.DEFAULT_NA)
861848 new_array.append(row_data)
862849 return new_array
850
851
852 def _add(name, left, right):
853 from pyexcel.book import Book, local_uuid
854
855 content = {}
856 content[name] = left
857 if isinstance(right, Book):
858 right_in_dict = copy.deepcopy(right.to_dict())
859 for key in right_in_dict.keys():
860 new_key = key
861 if len(right_in_dict.keys()) == 1:
862 new_key = right.filename
863 if new_key in content:
864 uid = local_uuid()
865 new_key = "%s_%s" % (key, uid)
866 content[new_key] = right_in_dict[key]
867 elif isinstance(right, Matrix):
868 new_key = right.name
869 if new_key in content:
870 uid = local_uuid()
871 new_key = "%s_%s" % (right.name, uid)
872 content[new_key] = copy.deepcopy(right.get_internal_array())
873 else:
874 raise TypeError
875 new_book = Book()
876 new_book.load_from_sheets(content)
877 return new_book
33
44 Generic table row
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
9 import copy
910 import types
1011
11 import pyexcel._compact as compact
12 from pyexcel import _compact as compact
1213
1314 from . import _shared as utils
1415
209210 :return: self
210211 """
211212 if isinstance(other, compact.OrderedDict):
212 self._ref.extend_rows(other)
213 self._ref.extend_rows(copy.deepcopy(other))
213214 elif isinstance(other, list):
214 self._ref.extend_rows(other)
215 self._ref.extend_rows(copy.deepcopy(other))
215216 elif hasattr(other, "get_internal_array"):
216 self._ref.extend_rows(other.get_internal_array())
217 self._ref.extend_rows(copy.deepcopy(other.get_internal_array()))
217218 else:
218219 raise TypeError
219220 return self
220221
222 def __add__(self, other):
223 """Overload + sign
224
225 :return: new instance
226 """
227 new_instance = self._ref.clone()
228 if isinstance(other, compact.OrderedDict):
229 new_instance.extend_rows(copy.deepcopy(other))
230 elif isinstance(other, list):
231 new_instance.extend_rows(copy.deepcopy(other))
232 elif hasattr(other, "get_internal_array"):
233 new_instance.extend_rows(copy.deepcopy(other.get_internal_array()))
234 else:
235 raise TypeError
236 return new_instance
237
221238 def format(self, row_index=None, formatter=None, format_specs=None):
222 """Format a row
223 """
239 """Format a row"""
224240 if row_index is not None:
225241 self._handle_one_formatter(row_index, formatter)
226242 elif format_specs:
33
44 Second level abstraction
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
9 import pyexcel.constants as constants
10 import pyexcel.exceptions as exceptions
9 from pyexcel import constants as constants
10 from pyexcel import exceptions as exceptions
11 from lml.plugin import PluginManager
1112 from pyexcel.internal.attributes import (
1213 register_book_attribute,
1314 register_sheet_attribute,
1415 )
1516
16 import pyexcel_io.constants as io_constants
17 from lml.plugin import PluginManager
17 from pyexcel_io import constants as io_constants
1818
1919 REGISTRY_KEY_FORMAT = "%s-%s"
2020 # ignore the following attributes
5959 def get_a_plugin(
6060 self, target=None, action=None, source_library=None, **keywords
6161 ):
62 """obtain a source plugin for pyexcel signature functions"""
62 """obtain a source plugin for signature functions"""
6363 key = REGISTRY_KEY_FORMAT % (target, action)
6464 io_library = None
6565 # backward support pyexcel-io library parameter
7474 return source_instance
7575
7676 def get_source(self, **keywords):
77 """obtain a sheet read source plugin for pyexcel signature functions"""
77 """obtain a sheet read source plugin for signature functions"""
7878 return self.get_a_plugin(
7979 target=constants.SHEET, action=constants.READ_ACTION, **keywords
8080 )
8181
8282 def get_book_source(self, **keywords):
83 """obtain a book read source plugin for pyexcel signature functions"""
83 """obtain a book read source plugin for signature functions"""
8484 return self.get_a_plugin(
8585 target=constants.BOOK, action=constants.READ_ACTION, **keywords
8686 )
8787
8888 def get_writable_source(self, **keywords):
89 """obtain a sheet write source plugin for pyexcel signature functions
90 """
89 """obtain a sheet write source plugin for signature functions"""
9190 return self.get_a_plugin(
9291 target=constants.SHEET, action=constants.WRITE_ACTION, **keywords
9392 )
9493
9594 def get_writable_book_source(self, **keywords):
96 """obtain a book write source plugin for pyexcel signature functions"""
95 """obtain a book write source plugin for signature functions"""
9796 return self.get_a_plugin(
9897 target=constants.BOOK, action=constants.WRITE_ACTION, **keywords
9998 )
33
44 Extract tabular data from external file, stream or content
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
9 import pyexcel.internal.garbagecollector as gc
9 from pyexcel.internal import garbagecollector as gc
1010
1111
1212 class AbstractParser(object):
33
44 Public interface for plugins
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 import types
1010 from itertools import product
1111
12 import pyexcel.constants as constants
12 from pyexcel import constants as constants
13 from lml.plugin import PluginInfo, PluginInfoChain
1314 from pyexcel._compact import is_string
1415 from pyexcel.exceptions import FileTypeNotSupported
1516 from pyexcel.internal.plugins import PARSER, RENDERER
16
17 from lml.plugin import PluginInfo, PluginInfoChain
1817
1918
2019 class SourceInfo(PluginInfo):
132131
133132
134133 class PyexcelPluginChain(PluginInfoChain):
135 """It is used by pyexcel plugins
136 """
134 """It is used by pyexcel plugins"""
137135
138136 def add_a_source(self, relative_plugin_class_path=None, **keywords):
139137 """
33
44 A list of built-in parsers
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 from pyexcel.plugins import PyexcelPluginChain
33
44 Export data into database datables
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 from pyexcel.parser import DbParser
1010
11 import pyexcel_io.database.common as django
1211 from pyexcel_io import get_data, iget_data
12 from pyexcel_io.database import common as django
1313
1414
1515 class DjangoExporter(DbParser):
33
44 Parsing excel sources
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 from pyexcel.parser import AbstractParser
33
44 Export data into database datables
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 from pyexcel.parser import DbParser
1010
11 import pyexcel_io.database.common as sql
1211 from pyexcel_io import get_data, iget_data
12 from pyexcel_io.database import common as sql
1313
1414
1515 class SQLAlchemyExporter(DbParser):
33
44 A list of built-in renderers
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 from pyexcel.plugins import PyexcelPluginChain
44 Export data into texttable format. It also serves the default
55 presentation of pyexcel sheet and book.
66
7 :copyright: (c) 2015-2017 by Onni Software Ltd.
7 :copyright: (c) 2015-2022 by Onni Software Ltd.
88 :license: New BSD License
99 """
1010 from types import GeneratorType
1111
12 import pyexcel.constants as constants
12 from pyexcel import constants as constants
13 from texttable import Texttable
1314 from pyexcel.renderer import Renderer
1415 from pyexcel.internal.sheets.formatters import to_format
15
16 from texttable import Texttable
1716
1817
1918 class TextTableRenderer(Renderer):
33
44 Export data into django models
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
9 import pyexcel.internal.common as common
109 from pyexcel._compact import OrderedDict
10 from pyexcel.internal import common as common
1111 from pyexcel.renderer import DbRenderer
1212
13 import pyexcel_io.database.common as django
1413 from pyexcel_io import save_data
14 from pyexcel_io.database import common as django
1515
1616 NO_COLUMN_NAMES = "Only sheet with column names is accepted"
1717
33
44 Export data into excel files
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 from pyexcel.renderer import AbstractRenderer
1010 from pyexcel.constants import DEFAULT_SHEET_NAME
1111
12 import pyexcel_io.manager as manager
12 from pyexcel_io import manager as manager
1313 from pyexcel_io import save_data
1414
1515
33
44 Export data into database datables
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
9 import pyexcel.internal.common as common
109 from pyexcel._compact import OrderedDict
10 from pyexcel.internal import common as common
1111 from pyexcel.renderer import DbRenderer
1212
13 import pyexcel_io.database.common as sql
1413 from pyexcel_io import save_data
14 from pyexcel_io.database import common as sql
1515
1616
1717 class SQLAlchemyRenderer(DbRenderer):
33
44 A list of built-in sources
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 from pyexcel.plugins import PyexcelPluginChain
33
44 Generic database sources
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 from pyexcel.source import AbstractSource
33
44 Representation of django sources
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 from pyexcel_io.constants import DB_DJANGO
33
44 Representation of input file sources
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 import os
1414
1515 # pylint: disable=W0223
1616 class ReadExcelFromFile(AbstractSource):
17 """Pick up 'file_name' field and do single sheet based read and write
18 """
17 """Pick up 'file_name' field and do single sheet based read and write"""
1918
2019 def __init__(self, file_name=None, parser_library=None, **keywords):
2120 self.__file_name = file_name
33
44 Representation of output file sources
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 from pyexcel.source import AbstractSource
1313
1414 # pylint: disable=W0223
1515 class WriteSheetToFile(AbstractSource):
16 """Pick up 'file_name' field and do single sheet based read and write
17 """
16 """Pick up 'file_name' field and do single sheet based read and write"""
1817
1918 def __init__(self, file_name=None, renderer_library=None, **keywords):
2019 AbstractSource.__init__(self, **keywords)
3534
3635 # pylint: disable=W0223
3736 class WriteBookToFile(WriteSheetToFile):
38 """Pick up 'file_name' field and do multiple sheet based read and write
39 """
37 """Pick up 'file_name' field and do multiple sheet based read and write"""
4038
4139 def write_data(self, book):
4240 self._renderer.render_book_to_file(
33
44 Representation of http sources
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
9 import pyexcel.constants as constants
9 from pyexcel import constants as constants
1010 from pyexcel.source import AbstractSource
1111 from pyexcel._compact import PY2, request
1212 from pyexcel.internal import PARSER
5454 file_type = _get_file_type_from_url(self.__url)
5555 parser_library = self._keywords.get("parser_library", None)
5656 aparser = PARSER.get_a_plugin(file_type, parser_library)
57 sheets = aparser.parse_file_stream(connection, **self._keywords)
57 content = connection.read()
58 sheets = aparser.parse_file_content(content, **self._keywords)
5859 return sheets
5960
6061 def get_source_info(self):
33
44 Representation of input file sources
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 from pyexcel.source import AbstractSource
33
44 Representation of output file sources
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 from pyexcel.source import AbstractSource, MemorySourceMixin
33
44 Local magic workds
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 # keywords
33
44 Representation of array source
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 from pyexcel.source import AbstractSource, MemorySourceMixin
33
44 Representation of book dict source
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 from pyexcel.source import AbstractSource, MemorySourceMixin
33
44 Representation of array, dict, records and book dict sources
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
9 import pyexcel.constants as constants
9 from pyexcel import constants as constants
1010 from pyexcel._compact import PY2, OrderedDict, zip_longest
1111
1212 from pyexcel_io.sheet import SheetReader
33
44 Representation of dict sources
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 from pyexcel.source import AbstractSource, MemorySourceMixin
33
44 Representation of records source
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 from pyexcel.source import AbstractSource, MemorySourceMixin
33
44 Representation of querysets
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
9 import pyexcel.constants as constants
9 from pyexcel import constants as constants
1010 from pyexcel.source import AbstractSource
1111
12 from pyexcel_io.database.querysets import QuerysetsReader
12 from pyexcel_io import get_data
13 from pyexcel_io.constants import DB_QUERYSET
1314 from . import params
1415
1516
6667 )
6768 if self.__skip_row_func is not None:
6869 local_params["skip_row_func"] = self.__skip_row_func
69 reader = QuerysetsReader(
70 self.__query_sets, self.__column_names, **local_params
70
71 data = get_data(
72 self.__query_sets,
73 file_type=DB_QUERYSET,
74 column_names=self.__column_names,
75 **local_params
7176 )
72 data = reader.to_array()
73 return {self.__sheet_name: data}
77 return data
33
44 Representation of array source
55
6 :copyright: (c) 2015-2018 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 from pyexcel.source import AbstractSource, MemorySourceMixin
33
44 Representation of sqlalchemy sources
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
99 from pyexcel_io.constants import DB_SQL
33
44 Renders pyexcel.Book and pyexcel.Sheet to any format
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
9 import pyexcel._compact as compact
9 from pyexcel import _compact as compact
1010
1111
1212 class AbstractRenderer(object):
143143 """
144144 Renderer pyexcel data into a binary object
145145 """
146
147 def __init__(self, file_type):
148 Renderer.__init__(self, file_type)
149 if compact.PY3_AND_ABOVE:
150 self.WRITE_FLAG = "wb"
146 WRITE_FLAG = "wb"
151147
152148 def get_io(self):
153149 io = compact.BytesIO()
33
44 Building on top of matrix, adding named columns and rows support
55
6 :copyright: (c) 2014-2019 by Onni Software Ltd.
6 :copyright: (c) 2014-2022 by Onni Software Ltd.
77 :license: New BSD License, see LICENSE for more details
88 """
9 import copy
910 from collections import defaultdict
1011
11 import pyexcel._compact as compact
12 import pyexcel.constants as constants
12 from pyexcel import _compact as compact
13 from pyexcel import constants as constants
14 from pyexcel._compact import OrderedDict
1315 from pyexcel.internal.sheets.row import Row as NamedRow
1416 from pyexcel.internal.sheets.column import Column as NamedColumn
1517 from pyexcel.internal.sheets.matrix import Matrix
8082 """
8183 self.__column_names = []
8284 self.__row_names = []
83 self.__row_index = 0
85 self.__row_index = -1
86 self.__column_index = -1
8487 self.init(
8588 sheet=sheet,
8689 name=name,
160163 if transpose_after:
161164 self.transpose()
162165
166 def clone(self):
167 new_sheet = Sheet(
168 copy.deepcopy(self.get_internal_array()),
169 name_columns_by_row=self.__row_index,
170 name_rows_by_column=self.__column_index,
171 )
172 return new_sheet
173
174 def __deepcopy__(self, memo):
175 return self.clone()
176
163177 def transpose(self):
164178 self.__column_names, self.__row_names = (
165179 self.__row_names,
183197 The specified column will be deleted from the data
184198 :param column_index: the index of the column that has the row names
185199 """
200 self.__column_index = column_index
186201 self.__row_names = make_names_unique(self.column_at(column_index))
187202 del self.column[column_index]
188203
463478 else:
464479 raise ValueError(constants.MESSAGE_DATA_ERROR_NO_SERIES)
465480
481 def project(self, new_ordered_columns, exclusion=False):
482 """
483 Rearrange the sheet.
484
485 :ivar new_ordered_columns: new columns
486 :ivar exclusion: to exlucde named column or not. defaults to False
487
488 Example::
489
490 >>> sheet = Sheet(
491 ... [["A", "B", "C"], [1, 2, 3], [11, 22, 33], [111, 222, 333]],
492 ... name_columns_by_row=0)
493 >>> sheet.project(["B", "A", "C"])
494 pyexcel sheet:
495 +-----+-----+-----+
496 | B | A | C |
497 +=====+=====+=====+
498 | 2 | 1 | 3 |
499 +-----+-----+-----+
500 | 22 | 11 | 33 |
501 +-----+-----+-----+
502 | 222 | 111 | 333 |
503 +-----+-----+-----+
504 >>> sheet.project(["B", "C"])
505 pyexcel sheet:
506 +-----+-----+
507 | B | C |
508 +=====+=====+
509 | 2 | 3 |
510 +-----+-----+
511 | 22 | 33 |
512 +-----+-----+
513 | 222 | 333 |
514 +-----+-----+
515 >>> sheet.project(["B", "C"], exclusion=True)
516 pyexcel sheet:
517 +-----+
518 | A |
519 +=====+
520 | 1 |
521 +-----+
522 | 11 |
523 +-----+
524 | 111 |
525 +-----+
526
527 """
528 from pyexcel import get_array
529
530 the_dict = self.to_dict()
531 new_dict = OrderedDict()
532 if exclusion:
533 for column in the_dict.keys():
534 if column not in new_ordered_columns:
535 new_dict[column] = the_dict[column]
536 else:
537 for column in new_ordered_columns:
538 new_dict[column] = the_dict[column]
539
540 array = get_array(adict=new_dict)
541 return Sheet(array, name=self.name, name_columns_by_row=0)
542
466543 def to_dict(self, row=False):
467544 """Returns a dictionary"""
468545 the_dict = compact.OrderedDict()
556633 for item in alist:
557634 if not compact.is_string(type(item)):
558635 item = str(item)
636 item = item.strip()
559637 if item in duplicates:
560638 duplicates[item] = duplicates[item] + 1
561639 new_names.append("%s-%d" % (item, duplicates[item]))
33
44 Generic data source definition
55
6 :copyright: (c) 2015-2017 by Onni Software Ltd.
6 :copyright: (c) 2015-2022 by Onni Software Ltd.
77 :license: New BSD License
88 """
9 import pyexcel.constants as constants
9 from pyexcel import constants as constants
1010
1111
1212 class AbstractSource(object):
00 overrides: "pyexcel.yaml"
11 name: "pyexcel"
22 nick_name: pyexcel
3 version: 0.5.14
4 current_version: 0.5.14
5 release: 0.5.14
6 copyright_year: 2014-2019
3 version: 0.7.0
4 current_version: 0.7.0
5 release: 0.7.0
6 copyright_year: 2014-2022
77 branch: master
8 is_on_conda: true
9 setup_use_markers: true
10 sphinx_extensions:
11 - sphinx.ext.autosummary
12 - sphinx.ext.autodoc
13 - sphinx.ext.doctest
14 - sphinx.ext.intersphinx
15 - sphinx.ext.viewcode
16 - sphinxcontrib.excel
17 - sphinx_copybutton
818 dependencies:
19 - chardet
920 - lml>=0.0.4
10 - pyexcel-io>=0.5.18
11 - texttable>=0.8.1;python_version<"3"
12 - texttable>=0.8.2;python_version>="3"
13 - ordereddict;python_version<"2.7"
14 - weakrefset;python_version<"2.7"
15 - lxml>=3.4.4;platform_python_implementation=="PyPy"
21 - pyexcel-io>=0.6.2
22 - texttable>=0.8.2
1623 extra_dependencies:
1724 - xls:
18 - pyexcel-xls>=0.5.0
25 - pyexcel-xls>=0.6.0
1926 - xlsx:
20 - pyexcel-xlsx>=0.5.0
27 - pyexcel-xlsx>=0.6.0
2128 - ods:
22 - pyexcel-ods3>=0.5.0
29 - pyexcel-ods3>=0.6.0
30 test_dependencies:
31 - flask
32 - SQLAlchemy
33 - pyexcel-xlsx>=0.4.1
34 - pyexcel-xls<=0.6.2
35 - pyexcel-text>=0.2.0
36 - psutil
37 - pyexcel-pygal
2338 description: A wrapper library that provides one API to read, manipulate and write data in different excel formats
39 python_requires: ">=3.6"
40 min_python_version: "3.6"
41 skip_readme: true
42 moban_command: false
0 chardet
01 lml>=0.0.4
1 pyexcel-io>=0.5.18
2 texttable>=0.8.1;python_version<"3"
3 texttable>=0.8.2;python_version>="3"
4 ordereddict;python_version<"2.7"
5 weakrefset;python_version<"2.7"
6 lxml>=3.4.4;platform_python_implementation=="PyPy"
2 pyexcel-io>=0.6.2
3 texttable>=0.8.2
0 https://github.com/pyexcel-renderers/pyexcel-pygal/archive/master.zip
1 https://github.com/pyexcel/pyexcel-io/archive/master.zip
00 #!/usr/bin/env python3
1
2 """
3 Template by pypi-mobans
4 """
15
26 import os
37 import sys
4
5 # Template by pypi-mobans
68 import codecs
79 import locale
810 import platform
2931
3032 NAME = "pyexcel"
3133 AUTHOR = "C.W."
32 VERSION = "0.5.14"
34 VERSION = "0.7.0"
3335 EMAIL = "[email protected]"
3436 LICENSE = "New BSD"
3537 DESCRIPTION = (
36 "A wrapper library that provides one API to read, manipulate and write"
37 + "data in different excel formats"
38 "A wrapper library that provides one API to read, manipulate and write" +
39 "data in different excel formats"
3840 )
3941 URL = "https://github.com/pyexcel/pyexcel"
40 DOWNLOAD_URL = "%s/archive/0.5.14.tar.gz" % URL
41 FILES = ["README.rst", "CHANGELOG.rst"]
42 KEYWORDS = ["python", "tsv", "tsvz" "csv", "csvz", "xls", "xlsx", "ods"]
42 DOWNLOAD_URL = "%s/archive/0.7.0.tar.gz" % URL
43 FILES = ["README.rst", "CONTRIBUTORS.rst", "CHANGELOG.rst"]
44 KEYWORDS = [
45 "python",
46 'tsv',
47 'tsvz'
48 'csv',
49 'csvz',
50 'xls',
51 'xlsx',
52 'ods'
53 ]
4354
4455 CLASSIFIERS = [
4556 "Topic :: Software Development :: Libraries",
4657 "Programming Language :: Python",
4758 "Intended Audience :: Developers",
48 "Programming Language :: Python :: 2.6",
49 "Programming Language :: Python :: 2.7",
50 "Programming Language :: Python :: 3.3",
51 "Programming Language :: Python :: 3.4",
52 "Programming Language :: Python :: 3.5",
59
60 "Programming Language :: Python :: 3 :: Only",
61
62
63
5364 "Programming Language :: Python :: 3.6",
5465 "Programming Language :: Python :: 3.7",
5566 "Programming Language :: Python :: 3.8",
56 "Development Status :: 3 - Alpha",
57 "Programming Language :: Python :: Implementation :: PyPy",
67
68 'Development Status :: 3 - Alpha',
5869 ]
5970
60 INSTALL_REQUIRES = ["lml>=0.0.4", "pyexcel-io>=0.5.18"]
71 PYTHON_REQUIRES = ">=3.6"
72
73 INSTALL_REQUIRES = [
74 "chardet",
75 "lml>=0.0.4",
76 "pyexcel-io>=0.6.2",
77 "texttable>=0.8.2",
78 ]
6179 SETUP_COMMANDS = {}
6280
63 if PY2:
64 INSTALL_REQUIRES.append("texttable>=0.8.1")
65 if not PY2:
66 INSTALL_REQUIRES.append("texttable>=0.8.2")
67 if PY26:
68 INSTALL_REQUIRES.append("ordereddict")
69 if PY26:
70 INSTALL_REQUIRES.append("weakrefset")
71 if platform.python_implementation == "PyPy":
72 INSTALL_REQUIRES.append("lxml>=3.4.4")
73
74 PACKAGES = find_packages(exclude=["ez_setup", "examples", "tests"])
81 PACKAGES = find_packages(exclude=["ez_setup", "examples", "tests", "tests.*"])
7582 EXTRAS_REQUIRE = {
76 "xls": ["pyexcel-xls>=0.5.0"],
77 "xlsx": ["pyexcel-xlsx>=0.5.0"],
78 "ods": ["pyexcel-ods3>=0.5.0"],
83 "xls": ['pyexcel-xls>=0.6.0'],
84 "xlsx": ['pyexcel-xlsx>=0.6.0'],
85 "ods": ['pyexcel-ods3>=0.6.0'],
7986 }
8087 # You do not need to read beyond this line
81 PUBLISH_COMMAND = "{0} setup.py sdist bdist_wheel upload -r pypi".format(
82 sys.executable
83 )
84 GS_COMMAND = (
85 "gs pyexcel v0.5.14 " + "Find 0.5.14 in changelog for more details"
86 )
87 NO_GS_MESSAGE = (
88 "Automatic github release is disabled. "
89 + "Please install gease to enable it."
90 )
88 PUBLISH_COMMAND = "{0} setup.py sdist bdist_wheel upload -r pypi".format(sys.executable)
89 HERE = os.path.abspath(os.path.dirname(__file__))
90
91 GS_COMMAND = ("gease pyexcel v0.7.0 " +
92 "Find 0.7.0 in changelog for more details")
93 NO_GS_MESSAGE = ("Automatic github release is disabled. " +
94 "Please install gease to enable it.")
9195 UPLOAD_FAILED_MSG = (
92 'Upload failed. please run "%s" yourself.' % PUBLISH_COMMAND
93 )
94 HERE = os.path.abspath(os.path.dirname(__file__))
96 'Upload failed. please run "%s" yourself.' % PUBLISH_COMMAND)
9597
9698
9799 class PublishCommand(Command):
128130 self.status(NO_GS_MESSAGE)
129131 if run_status:
130132 if os.system(PUBLISH_COMMAND) != 0:
131 self.status(UPLOAD_FAILED_MSG % PUBLISH_COMMAND)
133 self.status(UPLOAD_FAILED_MSG)
132134
133135 sys.exit()
134136
135137
136 SETUP_COMMANDS.update({"publish": PublishCommand})
137
138 SETUP_COMMANDS.update({
139 "publish": PublishCommand
140 })
138141
139142 def has_gease():
140143 """
144147 """
145148 try:
146149 import gease # noqa
147
148150 return True
149151 except ImportError:
150152 return False
205207 long_description=read_files(*FILES),
206208 license=LICENSE,
207209 keywords=KEYWORDS,
210 python_requires=PYTHON_REQUIRES,
208211 extras_require=EXTRAS_REQUIRE,
209212 tests_require=["nose"],
210213 install_requires=INSTALL_REQUIRES,
212215 include_package_data=True,
213216 zip_safe=False,
214217 classifiers=CLASSIFIERS,
215 cmdclass=SETUP_COMMANDS,
218 cmdclass=SETUP_COMMANDS
216219 )
00 pip freeze
1 nosetests --with-coverage --cover-package pyexcel --cover-package tests tests --with-doctest --doctest-extension=.rst README.rst docs/source pyexcel
1 nosetests --with-coverage --cover-package pyexcel --cover-package tests tests --with-doctest --doctest-extension=.rst docs/source pyexcel
00 #/bin/bash
11 pip freeze
2 nosetests --with-coverage --cover-package pyexcel --cover-package tests tests --with-doctest --doctest-extension=.rst README.rst docs/source pyexcel
2 nosetests --with-coverage --cover-package pyexcel --cover-package tests tests --with-doctest --doctest-extension=.rst docs/source pyexcel
00 import os
11 import json
2 import unittest
23
34 import pyexcel as pe
45
6061 table.append(array)
6162 io = pe.save_as(dest_file_type=file_type, array=table)
6263 return io
64
65
66 class PyexcelSheetBase(unittest.TestCase):
67 filename = "fixtures/non-uniform-rows.csv"
68
69 def setUp(self):
70 self.sheet = pe.Sheet(
71 [
72 [f"{row_no}_{col_no}" for col_no in range(10)]
73 for row_no in range(10)
74 ]
75 )
6376
6477
6578 class PyexcelBase:
142155 expected = [[4, 4, 4, 4], [5, 5, 5, 5], [6, 6, 6, 6]]
143156 assert data == expected
144157 data = list(b["Sheet3"].rows())
145 expected = [[u"X", u"Y", u"Z"], [1, 4, 7], [2, 5, 8], [3, 6, 9]]
158 expected = [["X", "Y", "Z"], [1, 4, 7], [2, 5, 8], [3, 6, 9]]
146159 assert data == expected
147160 sheet3 = b["Sheet3"]
148161 sheet3.name_columns_by_row(0)
0 header1,,header3,header4
1 1,2,3,4
0 headerA,headerB,,headerD
1 A,,C,D
2 F,G,M,T
22 codecov
33 coverage
44 flake8
5 black
6 isort
7 collective.checkdocs
8 pygments
9 moban
10 moban_jinja2_github
11 flask
512 SQLAlchemy
6 flask
7 lxml==4.0.0;platform_python_implementation=="PyPy"
813 pyexcel-xlsx>=0.4.1
9 pyexcel-xls>=0.4.1
14 pyexcel-xls<=0.6.2
1015 pyexcel-text>=0.2.0
1116 psutil
12 mock
13 moban
14 black;python_version>="3.6"
15 isort;python_version>="3.6"
17 pyexcel-pygal
1818
1919 book = p.Book(book_dict)
2020 eq_(len(book.test.array), 1)
21
22
23 def test_sheet_ordering():
24 test_data = [["a", "b"]]
25
26 book_dict = {"first": test_data, "middle": test_data, "last": test_data}
27
28 book = p.Book(book_dict)
29 eq_(book.sheet_names(), ["first", "middle", "last"])
44 from datetime import datetime
55 from textwrap import dedent
66
7 import psutil
78 import pyexcel as p
89 from _compact import StringIO, OrderedDict
910
10 import psutil
1111 from nose.tools import eq_
1212
1313
535535 file_name=os.path.join("tests", "fixtures", "bug_176.xlsx")
536536 )
537537 eq_({}, book.bookdict)
538
539
540 def test_issue_241():
541 import glob
542
543 from pyexcel import get_book, merge_all_to_a_book
544
545 merge_all_to_a_book(
546 glob.glob("tests/fixtures/issue_241/*.csv"), "issue_241.xlsx"
547 )
548 book = get_book(file_name="issue_241.xlsx")
549 book.sort_sheets()
550 expected = dedent(
551 """
552 1.csv:
553 +---------+---+---------+---------+
554 | header1 | | header3 | header4 |
555 +---------+---+---------+---------+
556 | 1 | 2 | 3 | 4 |
557 +---------+---+---------+---------+
558 2.csv:
559 +---------+---------+---+---------+
560 | headerA | headerB | | headerD |
561 +---------+---------+---+---------+
562 | A | | C | D |
563 +---------+---------+---+---------+
564 | F | G | M | T |
565 +---------+---------+---+---------+"""
566 ).lstrip()
567 eq_(str(book), expected)
568 os.unlink('issue_241.xlsx')
569
570
571 def test_issue_250():
572 from copy import deepcopy
573
574 s = p.Sheet()
575 deepcopy(s)
+0
-49
tests/test_deprecation_notes.py less more
0 import warnings
1
2 import pyexcel.ext.ods
3 import pyexcel.ext.xls
4 import pyexcel.ext.ods3
5 import pyexcel.ext.text
6 import pyexcel.ext.xlsx
7
8 from mock import patch
9
10 try:
11 reload
12 except NameError:
13 from imp import reload
14
15 EXPECTED_DEPRECATION_MESSAGE = (
16 "Deprecated usage since v%s! "
17 + "Explicit import is no longer required. pyexcel.ext.%s is auto imported."
18 )
19
20
21 @patch.object(warnings, "warn")
22 def test_ods_note(warn):
23 reload(pyexcel.ext.ods)
24 warn.assert_called_with(EXPECTED_DEPRECATION_MESSAGE % ("0.2.2", "ods"))
25
26
27 @patch.object(warnings, "warn")
28 def test_ods3_note(warn):
29 reload(pyexcel.ext.ods3)
30 warn.assert_called_with(EXPECTED_DEPRECATION_MESSAGE % ("0.2.2", "ods3"))
31
32
33 @patch.object(warnings, "warn")
34 def test_xls_note(warn):
35 reload(pyexcel.ext.xls)
36 warn.assert_called_with(EXPECTED_DEPRECATION_MESSAGE % ("0.2.2", "xls"))
37
38
39 @patch.object(warnings, "warn")
40 def test_xlsx_note(warn):
41 reload(pyexcel.ext.xlsx)
42 warn.assert_called_with(EXPECTED_DEPRECATION_MESSAGE % ("0.2.2", "xlsx"))
43
44
45 @patch.object(warnings, "warn")
46 def test_text_note(warn):
47 reload(pyexcel.ext.text)
48 warn.assert_called_with(EXPECTED_DEPRECATION_MESSAGE % ("0.2.1", "text"))
00 import os
11 from textwrap import dedent
22
3 import pyexcel.constants as constants
43 from pyexcel import Book, Sheet, get_book
4 from pyexcel import constants as constants
55 from _compact import StringIO, OrderedDict
66 from pyexcel.source import AbstractSource, MemorySourceMixin
77 from pyexcel.plugins import SourceInfo
221221 save_as(dest_file_name=self.testfile, adict=self.data)
222222
223223 def test_general_usage(self):
224 """format a row
225 """
224 """format a row"""
226225 r = get_sheet(file_name=self.testfile)
227226 r.row.format(1, str)
228227 c1 = r.row_at(1)
230229 self.assertEqual(c1, c2)
231230
232231 def test_general_usage2(self):
233 """format a row
234 """
232 """format a row"""
235233 r = get_sheet(file_name=self.testfile)
236234 r.row.format(1, str)
237235 c1 = r.row_at(1)
239237 self.assertEqual(c1, c2)
240238
241239 def test_one_formatter_for_two_rows(self):
242 """format more than one row
243 """
240 """format more than one row"""
244241 r = get_sheet(file_name=self.testfile)
245242 r.row.format([1, 2], str)
246243 c1 = r.row_at(2)
00 import os
11
2 import pyexcel.internal.garbagecollector as gc
32 from pyexcel import iget_array
3 from pyexcel.internal import garbagecollector as gc
44
55 from nose.tools import eq_
66
00 from textwrap import dedent
11 from unittest import TestCase
2 from unittest.mock import MagicMock, patch
23
34 import pyexcel as pe
4 from pyexcel._compact import PY2, StringIO
5
6 from mock import MagicMock, patch
5 from pyexcel._compact import PY2, BytesIO, StringIO
76
87
98 class TestHttpBookSource(TestCase):
1110 self.patcher = patch("pyexcel._compact.request.urlopen")
1211 mock_open = self.patcher.start()
1312 self.mocked_info = MagicMock()
14 io = StringIO("1,2,3")
13 if PY2:
14 io = StringIO("1,2,3")
15 else:
16 io = BytesIO("1,2,3".encode("utf-8"))
1517 io.info = self.mocked_info
1618 mock_open.return_value = io
1719
0 from unittest.mock import MagicMock
1
02 from pyexcel._compact import PY2
13 from pyexcel.internal.core import _seek_at_zero
2
3 from mock import MagicMock
44
55
66 def test_seek_at_zero():
8181 m.extend_columns(1.1)
8282
8383 def test_iadd_list(self):
84 """Test in place add a list
85 """
84 """Test in place add a list"""
8685 m2 = Matrix(self.data)
8786 m2.column += self.data3
8887 eq_(self.result, m2.get_internal_array())
8988
9089 def test_add(self):
91 """Test operator add overload
92 """
90 """Test operator add overload"""
9391 # +
9492 m3 = Matrix(self.data)
9593 m4 = m3.column + self.data3
33 from base import PyexcelMultipleSheetBase, clean_up_files, create_sample_file1
44 from _compact import OrderedDict
55
6 from nose.tools import raises
6 from nose.tools import eq_, raises
77
88
99 class TestXlsNXlsmMultipleSheets(PyexcelMultipleSheetBase):
445445 """
446446 b1 = pe.BookReader(self.testfile)
447447 b3 = b1["Sheet1"] + b1["Sheet1"]
448 content = b3.dict
449 sheet_names = content.keys()
450 assert len(sheet_names) == 2
451 for name in sheet_names:
452 if "Sheet1" in name:
453 assert content[name] == self.content["Sheet1"]
448 b3["Sheet1"].row[0] = [3, 3, 3, 3]
449 eq_(b1["Sheet1"].row[0], [1, 1, 1, 1])
454450
455451 @raises(TypeError)
456452 def test_add_book_error(self):
0 from textwrap import dedent
1
2 from pyexcel import Sheet
3
4 from nose.tools import eq_
5
6
7 def test_project():
8 sheet = Sheet(
9 [["A", "B", "C"], [1, 2, 3], [11, 22, 33], [111, 222, 333]],
10 name_columns_by_row=0,
11 )
12 expected = dedent(
13 """
14 pyexcel sheet:
15 +-----+-----+-----+
16 | B | A | C |
17 +=====+=====+=====+
18 | 2 | 1 | 3 |
19 +-----+-----+-----+
20 | 22 | 11 | 33 |
21 +-----+-----+-----+
22 | 222 | 111 | 333 |
23 +-----+-----+-----+"""
24 ).strip()
25 sheet = sheet.project(["B", "A", "C"])
26 eq_(expected, str(sheet))
27
28
29 def test_project_for_less():
30 sheet = Sheet(
31 [["A", "B", "C"], [1, 2, 3], [11, 22, 33], [111, 222, 333]],
32 name_columns_by_row=0,
33 )
34 expected = dedent(
35 """
36 pyexcel sheet:
37 +-----+-----+
38 | B | C |
39 +=====+=====+
40 | 2 | 3 |
41 +-----+-----+
42 | 22 | 33 |
43 +-----+-----+
44 | 222 | 333 |
45 +-----+-----+"""
46 ).strip()
47 sheet = sheet.project(["B", "C"])
48 eq_(expected, str(sheet))
49
50
51 def test_project_for_exclusion():
52 sheet = Sheet(
53 [["A", "B", "C"], [1, 2, 3], [11, 22, 33], [111, 222, 333]],
54 name_columns_by_row=0,
55 )
56 expected = dedent(
57 """
58 pyexcel sheet:
59 +-----+
60 | A |
61 +=====+
62 | 1 |
63 +-----+
64 | 11 |
65 +-----+
66 | 111 |
67 +-----+"""
68 ).strip()
69 sheet = sheet.project(["B", "C"], exclusion=True)
70 eq_(expected, str(sheet))
0 import random
1
2 from base import PyexcelSheetBase
3
4
5 class TestSheetAccess(PyexcelSheetBase):
6 @staticmethod
7 def get_random_char():
8 i = random.randint(97, 122)
9 return chr(i)
10
11 def test_out_of_bounds_write(self):
12 value = self.get_random_char()
13 column = self.get_random_char() + self.get_random_char()
14 cell = column + str(random.randint(9, 30))
15 self.sheet[cell] = value
16
17 self.assertEqual(value, self.sheet[cell])
18
19 def test_out_of_column_bound_write(self):
20 value = self.get_random_char()
21 column = self.get_random_char() + self.get_random_char()
22 cell = "A" + column + str(random.randint(9, 30))
23 self.sheet[cell] = value
24
25 self.assertEqual(value, self.sheet[cell])
26
27 def test_out_of_bounds_read(self):
28 with self.assertRaises(IndexError):
29 self.sheet[0, 20]
30
31 with self.assertRaises(IndexError):
32 self.sheet[20, 0]
33
34 def test_column_edge_case(self):
35 column = self.sheet.number_of_columns() - 1
36
37 self.assertEqual(self.sheet[0, column], f"0_{column}")
38 with self.assertRaises(IndexError):
39 self.sheet[0, column + 1]
40
41 def test_row_edge_case(self):
42 row = self.sheet.number_of_rows() - 1
43
44 self.assertEqual(self.sheet[row, 0], f"{row}_0")
45 with self.assertRaises(IndexError):
46 self.sheet[row + 1, 0]
11 import copy
22 from textwrap import dedent
33
4 from pyexcel.sheet import Sheet
4 from pyexcel.sheet import Sheet, make_names_unique
55 from pyexcel.internal.meta import PyexcelObject
66
77 from nose.tools import eq_, raises
129129 def test_pyexcel_object():
130130 obj = PyexcelObject()
131131 obj.save_to_memory("csv")
132
133
134 def test_make_names_unique():
135 alist = ["a", "a"]
136 new_names = make_names_unique(alist)
137 expected = ["a", "a-1"]
138 eq_(new_names, expected)
139
140
141 def test_make_names_unique_includes_leading_whitespace():
142 alist = ["a", " a"]
143 new_names = make_names_unique(alist)
144 expected = ["a", "a-1"]
145 eq_(new_names, expected)
146
147
148 def test_make_names_unique_includes_trailing_whitespace():
149 alist = ["a", "a "]
150 new_names = make_names_unique(alist)
151 expected = ["a", "a-1"]
152 eq_(new_names, expected)
153
154
155 def test_make_names_unique_includes_whitespace():
156 alist = ["a", " a ", " ", " "]
157 new_names = make_names_unique(alist)
158 expected = ["a", "a-1", "", "-1"]
159 eq_(new_names, expected)
00 from pyexcel import Sheet
11 from _compact import OrderedDict
22
3 from nose.tools import eq_, raises
3 from nose.tools import eq_, raises, assert_not_in
44
55
66 class TestSheetColumn:
3232 eq_(s.column["Column 1"], ["1", "4", "7"])
3333 eq_(s.column["Column 3"], ["3", "6", "9"])
3434
35 @raises(AttributeError)
3536 def test_add(self):
3637 s = Sheet(self.data, "test")
3738 s.name_columns_by_row(0)
3839 data = OrderedDict({"Column 4": [10, 11, 12]})
39 s = s.column + data
40 s1 = s.column + data
41 eq_(s1.column.Column_4, [10, 11, 12])
42 eq_(s.column.Column_4, [10, 11, 12])
43
44 def test_iadd(self):
45 s = Sheet(self.data, "test")
46 s.name_columns_by_row(0)
47 data = OrderedDict({"Column 4": [10, 11, 12]})
48 s.column += data
4049 eq_(s.column.Column_4, [10, 11, 12])
4150
4251 @raises(TypeError)
131140 s = Sheet(self.data, "test")
132141 s.name_columns_by_row(2)
133142 data = OrderedDict({"Column 4": [10, 11, 12]})
134 s = s.column + data
143 s1 = s.column + data
144 eq_(s1.column["Column 4"], [10, 11, 12])
145 assert_not_in("Column 4", s.column)
146
147 def test_iadd(self):
148 s = Sheet(self.data, "test")
149 s.name_columns_by_row(2)
150 data = OrderedDict({"Column 4": [10, 11, 12]})
151 s.column += data
135152 assert s.column["Column 4"] == [10, 11, 12]
136153
137154 def test_dot_notation(self):
00 from pyexcel import Sheet
11 from _compact import OrderedDict
22
3 from nose.tools import eq_, raises
3 from nose.tools import eq_, raises, assert_not_in
44
55
66 class TestSheetRow:
7171 s = Sheet(self.data, "test")
7272 s.name_rows_by_column(0)
7373 data = OrderedDict({"Row 5": [10, 11, 12]})
74 s = s.row + data
74 s1 = s.row + data
75 assert s1.row["Row 5"] == [10, 11, 12]
76 assert_not_in("Row 5", s.row)
77
78 def test_iadd(self):
79 s = Sheet(self.data, "test")
80 s.name_rows_by_column(0)
81 data = OrderedDict({"Row 5": [10, 11, 12]})
82 s.row += data
7583 assert s.row["Row 5"] == [10, 11, 12]
7684
7785 def test_dot_notation(self):
33 from pyexcel.internal.generators import SheetStream
44 from pyexcel.plugins.sources.output_to_memory import WriteSheetToMemory
55
6 import pyexcel_io.manager as manager
76 from nose.tools import eq_
7 from pyexcel_io import manager as manager
88
99
1010 def test_save_to():
00 import copy
11
22 from pyexcel import Sheet, load_from_dict, load_from_records
3 from _compact import OrderedDict
43
54 from nose.tools import eq_, raises
65
125124 s.paste((1, 2))
126125
127126
128 class TestSheetColumn:
129 def setUp(self):
130 self.data = [
131 ["Column 1", "Column 2", "Column 3"],
132 [1, 2, 3],
133 [4, 5, 6],
134 [7, 8, 9],
135 ]
136
137 def test_formatter_by_named_column(self):
138 """Test one named column"""
139 s = Sheet(self.data, "test")
140 s.name_columns_by_row(0)
141 s.column.format("Column 1", str)
142 assert s.column["Column 1"] == ["1", "4", "7"]
143
144 def test_formatter_by_named_columns(self):
145 """Test multiple named columns"""
146 s = Sheet(self.data, "test")
147 s.name_columns_by_row(0)
148 s.column.format(["Column 1", "Column 3"], str)
149 assert s.column["Column 1"] == ["1", "4", "7"]
150 assert s.column["Column 3"] == ["3", "6", "9"]
151
152 def test_add(self):
153 s = Sheet(self.data, "test")
154 s.name_columns_by_row(0)
155 data = OrderedDict({"Column 4": [10, 11, 12]})
156 s = s.column + data
157 assert s.column["Column 4"] == [10, 11, 12]
158
159 @raises(TypeError)
160 def test_add_wrong_type(self):
161 """Add string type"""
162 s = Sheet(self.data, "test")
163 s.name_columns_by_row(0)
164 s = s.column + "string type" # bang
165
166 @raises(ValueError)
167 def test_delete_named_column(self):
168 s = Sheet(self.data, "test")
169 s.name_columns_by_row(0)
170 del s.column["Column 2"]
171 assert s.number_of_columns() == 2
172 s.column["Column 2"] # bang
173
174 @raises(ValueError)
175 def test_delete_indexed_column1(self):
176 s = Sheet(self.data, "test")
177 s.name_columns_by_row(0)
178 del s.column[1]
179 assert s.number_of_columns() == 2
180 s.column["Column 2"] # access it after deletion, bang
181
182 @raises(ValueError)
183 def test_delete_indexed_column2(self):
184 s = Sheet(self.data, "test")
185 s.name_columns_by_row(0)
186 del s.column["Column 2"]
187 assert s.number_of_columns() == 2
188 s.column["Column 2"] # access it after deletion, bang
189
190 @raises(ValueError)
191 def test_delete_indexed_column(self):
192 s = Sheet(self.data, "test")
193 s.name_columns_by_row(0)
194 s.delete_named_column_at(1)
195 assert s.number_of_columns() == 2
196 s.column["Column 2"] # access it after deletion, bang
197
198 @raises(ValueError)
199 def test_delete_column(self):
200 s = Sheet(self.data, "test")
201 del s.column[1, 2]
202 assert s.number_of_columns() == 1
203 s.column["Column 2"] # access it after deletion, bang
204
205
206 class TestSheetColumn2:
207 def setUp(self):
208 self.data = [
209 [1, 2, 3],
210 [4, 5, 6],
211 ["Column 1", "Column 2", "Column 3"],
212 [7, 8, 9],
213 ]
214
215 def test_series(self):
216 s = Sheet(self.data, "test")
217 s.name_columns_by_row(2)
218 assert s.colnames == ["Column 1", "Column 2", "Column 3"]
219 custom_columns = ["C1", "C2", "C3"]
220 s.colnames = custom_columns
221 assert s.colnames == custom_columns
222
223 def test_series2(self):
224 custom_columns = ["C1", "C2", "C3"]
225 s = Sheet(self.data, "test", colnames=custom_columns)
226 assert s.colnames == custom_columns
227
228 @raises(NotImplementedError)
229 def test_series3(self):
230 custom_columns = ["C1", "C2", "C3"]
231 Sheet(
232 self.data, "test", colnames=custom_columns, name_columns_by_row=0
233 )
234
235 def test_formatter_by_named_column(self):
236 s = Sheet(self.data, "test")
237 s.name_columns_by_row(2)
238 s.column.format("Column 1", str)
239 assert s.column["Column 1"] == ["1", "4", "7"]
240
241 def test_formatter_by_named_column_2(self):
242 s = Sheet(self.data, "test")
243 s.name_columns_by_row(2)
244 s.column.format("Column 1", str)
245 assert s.column["Column 1"] == ["1", "4", "7"]
246
247 def test_add(self):
248 s = Sheet(self.data, "test")
249 s.name_columns_by_row(2)
250 data = OrderedDict({"Column 4": [10, 11, 12]})
251 s = s.column + data
252 assert s.column["Column 4"] == [10, 11, 12]
253
254 @raises(ValueError)
255 def test_delete_named_column(self):
256 s = Sheet(self.data, "test")
257 s.name_columns_by_row(2)
258 del s.column["Column 2"]
259 assert s.number_of_columns() == 2
260 s.column["Column 2"] # bang
261
262 def test_set_indexed_row(self):
263 s = Sheet(self.data, "test")
264 s.name_columns_by_row(2)
265 s.row[0] = [10000, 1, 11]
266 assert s.row[0] == [10000, 1, 11]
267
268
269 class TestSheetRow:
270 def setUp(self):
271 self.data = [
272 ["Row 0", -1, -2, -3],
273 ["Row 1", 1, 2, 3],
274 ["Row 2", 4, 5, 6],
275 ["Row 3", 7, 8, 9],
276 ]
277
278 def test_formatter_by_named_row(self):
279 s = Sheet(self.data, "test")
280 s.name_rows_by_column(0)
281 s.row.format("Row 1", str)
282 assert s.row["Row 1"] == ["1", "2", "3"]
283
284 def test_rownames(self):
285 s = Sheet(self.data, "test", name_rows_by_column=0)
286 assert s.rownames == ["Row 0", "Row 1", "Row 2", "Row 3"]
287 custom_rows = ["R0", "R1", "R2", "R3"]
288 s.rownames = custom_rows
289 assert s.rownames == custom_rows
290
291 def test_rownames2(self):
292 custom_rows = ["R0", "R1", "R2", "R3"]
293 s = Sheet(self.data, "test", rownames=custom_rows)
294 assert s.rownames == custom_rows
295
296 @raises(NotImplementedError)
297 def test_rownames3(self):
298 custom_rows = ["R0", "R1", "R2", "R3"]
299 Sheet(self.data, "test", name_rows_by_column=0, rownames=custom_rows)
300
301 def test_formatter_by_named_row_2(self):
302 s = Sheet(self.data, "test")
303 s.name_rows_by_column(0)
304 s.row.format("Row 1", str)
305 assert s.row["Row 1"] == ["1", "2", "3"]
306
307 def test_row_series_to_dict(self):
308 s = Sheet(self.data, "test")
309 s.name_rows_by_column(0)
310 content = s.to_dict(True)
311 keys = ["Row 0", "Row 1", "Row 2", "Row 3"]
312 assert keys == list(content.keys())
313
314 @raises(TypeError)
315 def test_extend_rows_using_wrong_data_type(self):
316 s = Sheet(self.data, "test")
317 s.name_rows_by_column(0)
318 s.extend_rows([1, 2])
319
320 def test_formatter_by_named_row2(self):
321 """Test a list of string as index"""
322 s = Sheet(self.data, "test")
323 s.name_rows_by_column(0)
324 s.row.format(["Row 1", "Row 2"], str)
325 assert s.row["Row 1"] == ["1", "2", "3"]
326 assert s.row["Row 2"] == ["4", "5", "6"]
327
328 def test_add(self):
329 s = Sheet(self.data, "test")
330 s.name_rows_by_column(0)
331 data = OrderedDict({"Row 5": [10, 11, 12]})
332 s = s.row + data
333 assert s.row["Row 5"] == [10, 11, 12]
334
335 @raises(TypeError)
336 def test_add_wrong_type(self):
337 s = Sheet(self.data, "test")
338 s.name_rows_by_column(0)
339 s = s.row + "string type" # bang
340
341 @raises(ValueError)
342 def test_delete_named_row(self):
343 s = Sheet(self.data, "test")
344 s.name_rows_by_column(0)
345 del s.row["Row 2"]
346 assert s.number_of_rows() == 3
347 s.row["Row 2"] # already deleted
348
349 @raises(ValueError)
350 def test_delete_indexed_row1(self):
351 s = Sheet(self.data, "test")
352 s.name_rows_by_column(0)
353 del s.row[2]
354 assert s.number_of_rows() == 3
355 s.row["Row 2"] # already deleted
356
357 @raises(ValueError)
358 def test_delete_indexed_row2(self):
359 s = Sheet(self.data, "test")
360 s.name_rows_by_column(0)
361 s.delete_named_row_at(2)
362 assert s.number_of_rows() == 3
363 s.row["Row 2"] # already deleted
364
365 @raises(ValueError)
366 def test_delete_indexed_row3(self):
367 s = Sheet(self.data, "test")
368 s.name_rows_by_column(0)
369 del s.row["Row 0", "Row 1"]
370 assert s.number_of_rows() == 2
371 s.row["Row 1"] # already deleted
372
373 @raises(ValueError)
374 def test_delete_row(self):
375 s = Sheet(self.data, "test")
376 del s.row[1, 2]
377 assert s.number_of_rows() == 2
378 s.row["Row 1"] # already deleted
379
380 def test_column_locator2(self):
381 """
382 Remove odd columns
383 """
384 sheet = Sheet(self.data)
385
386 def locator(index, _):
387 return index % 2 == 0
388
389 del sheet.row[locator]
390 assert sheet.number_of_rows() == 2
391
392 def test_set_named_row(self):
393 s = Sheet(self.data, "test")
394 s.name_rows_by_column(0)
395 s.row["Row 2"] = [11, 11, 11]
396 assert s.row["Row 2"] == [11, 11, 11]
397
398 def test_set_indexed_column(self):
399 s = Sheet(self.data, "test", name_rows_by_column=0)
400 s.column[0] = [12, 3, 4, 5]
401 assert s.column[0] == [12, 3, 4, 5]
402
403
404127 class TestUniquenessOfNames:
405128 def test_column_names(self):
406129 data = [
5252 eq_(result, list(actual))
5353
5454 def test_book_reader_to_records_custom(self):
55 """use custom header
56 """
55 """use custom header"""
5756 r = pe.SeriesReader(self.testfile)
5857 custom_headers = ["O", "P", "Q"]
5958 result = [