Import upstream version 0.6.6
Kali Janitor
3 years ago
0 | 0 | # These are supported funding model platforms |
1 | 1 | |
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 | - [ ] Passes all Travis CI builds | |
7 | - [ ] Has fair amount of documentation if your change is complex | |
8 | - [ ] Agree on NEW BSD License for your contribution |
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 | - name: Set up Python | |
11 | uses: actions/setup-python@v1 | |
12 | with: | |
13 | python-version: '3.7' | |
14 | - name: check changes | |
15 | run: | | |
16 | pip install moban gitfs2 pypifs moban-jinja2-github moban-ansible | |
17 | moban | |
18 | git status | |
19 | git diff --exit-code | |
20 | - name: Auto-commit | |
21 | if: failure() | |
22 | uses: docker://cdssnc/auto-commit-github-action | |
23 | env: | |
24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
25 | with: | |
26 | args: >- | |
27 | This is an auto-commit, updating project meta data, | |
28 | 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/* |
24 | 24 | sdist/ |
25 | 25 | var/ |
26 | 26 | wheels/ |
27 | pip-wheel-metadata/ | |
28 | 27 | share/python-wheels/ |
29 | 28 | *.egg-info/ |
30 | 29 | .installed.cfg |
51 | 50 | nosetests.xml |
52 | 51 | coverage.xml |
53 | 52 | *.cover |
53 | *.py,cover | |
54 | 54 | .hypothesis/ |
55 | 55 | .pytest_cache/ |
56 | cover/ | |
56 | 57 | |
57 | 58 | # Translations |
58 | 59 | *.mo |
62 | 63 | *.log |
63 | 64 | local_settings.py |
64 | 65 | db.sqlite3 |
66 | db.sqlite3-journal | |
65 | 67 | |
66 | 68 | # Flask stuff: |
67 | 69 | instance/ |
74 | 76 | docs/_build/ |
75 | 77 | |
76 | 78 | # PyBuilder |
79 | .pybuilder/ | |
77 | 80 | target/ |
78 | 81 | |
79 | 82 | # Jupyter Notebook |
84 | 87 | ipython_config.py |
85 | 88 | |
86 | 89 | # 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 | |
88 | 93 | |
89 | 94 | # pipenv |
90 | 95 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. |
91 | 96 | # 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 | |
93 | 98 | # install all needed dependencies. |
94 | 99 | #Pipfile.lock |
95 | 100 | |
96 | # celery beat schedule file | |
101 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow | |
102 | __pypackages__/ | |
103 | ||
104 | # Celery stuff | |
97 | 105 | celerybeat-schedule |
106 | celerybeat.pid | |
98 | 107 | |
99 | 108 | # SageMath parsed files |
100 | 109 | *.sage.py |
125 | 134 | |
126 | 135 | # Pyre type checker |
127 | 136 | .pyre/ |
137 | ||
138 | # pytype static type analyzer | |
139 | .pytype/ | |
140 | ||
141 | # Cython debug symbols | |
142 | cython_debug/ | |
128 | 143 | |
129 | 144 | # VirtualEnv rules |
130 | 145 | # Virtualenv |
158 | 173 | # Windows rules |
159 | 174 | # Windows thumbnail cache files |
160 | 175 | Thumbs.db |
176 | Thumbs.db:encryptable | |
161 | 177 | ehthumbs.db |
162 | 178 | ehthumbs_vista.db |
163 | 179 | |
263 | 279 | # Vim rules |
264 | 280 | # Swap |
265 | 281 | [._]*.s[a-v][a-z] |
282 | !*.svg # comment out if you don't need vector files | |
266 | 283 | [._]*.sw[a-p] |
267 | 284 | [._]s[a-rt-v][a-z] |
268 | 285 | [._]ss[a-gi-z] |
270 | 287 | |
271 | 288 | # Session |
272 | 289 | Session.vim |
290 | Sessionx.vim | |
273 | 291 | |
274 | 292 | # Temporary |
275 | 293 | .netrwhist |
280 | 298 | [._]*.un~ |
281 | 299 | |
282 | 300 | # 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 | |
284 | 302 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 |
285 | 303 | |
286 | 304 | # User-specific stuff |
310 | 328 | # When using Gradle or Maven with auto-import, you should exclude module files, |
311 | 329 | # since they will be recreated, and may cause churn. Uncomment if using |
312 | 330 | # auto-import. |
331 | # .idea/artifacts | |
332 | # .idea/compiler.xml | |
333 | # .idea/jarRepositories.xml | |
313 | 334 | # .idea/modules.xml |
314 | 335 | # .idea/*.iml |
315 | 336 | # .idea/modules |
337 | # *.iml | |
338 | # *.ipr | |
316 | 339 | |
317 | 340 | # CMake |
318 | 341 | cmake-build-*/ |
362 | 385 | |
363 | 386 | # SFTP configuration file |
364 | 387 | sftp-config.json |
388 | sftp-config-alt*.json | |
365 | 389 | |
366 | 390 | # Package control specific files |
367 | 391 | Package Control.last-run |
399 | 423 | !.vscode/tasks.json |
400 | 424 | !.vscode/launch.json |
401 | 425 | !.vscode/extensions.json |
426 | *.code-workspace | |
427 | ||
428 | # Local History for Visual Studio Code | |
429 | .history/ | |
402 | 430 | |
403 | 431 | # Xcode rules |
404 | 432 | # Xcode |
425 | 453 | *.perspectivev3 |
426 | 454 | !default.perspectivev3 |
427 | 455 | |
456 | ## Gcc Patch | |
457 | /*.gcno | |
458 | ||
428 | 459 | # Eclipse rules |
429 | 460 | .metadata |
430 | 461 | bin/ |
476 | 507 | |
477 | 508 | # Annotation Processing |
478 | 509 | .apt_generated/ |
510 | .apt_generated_test/ | |
479 | 511 | |
480 | 512 | # Scala IDE specific (Scala & Java development for Eclipse) |
481 | 513 | .cache-main |
482 | 514 | .scala_dependencies |
483 | 515 | .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 | |
484 | 520 | |
485 | 521 | # TortoiseGit rules |
486 | 522 | # Project-level settings |
505 | 541 | cscope.po.out |
506 | 542 | |
507 | 543 | |
544 | # remove moban hash dictionary | |
545 | .moban.hashes | |
546 | ||
508 | 547 | docs/build/ |
509 | 548 | 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 | {%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'%} | |
1 | 1 | |
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%} |
22 | 22 | |
23 | 23 | {%include "installation.rst.jj2" %} |
24 | 24 | |
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 | ||
25 | 146 | For individual excel file formats, please install them as you wish: |
26 | 147 | |
27 | 148 | {%include "plugins-list.rst.jj2"%} |
33 | 154 | ======== ========== ============= ==================== ============= ============= |
34 | 155 | pyexcel pyexcel-io pyexcel-text pyexcel-handsontable pyexcel-pygal pyexcel-gantt |
35 | 156 | ======== ========== ============= ==================== ============= ============= |
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 | |
37 | 160 | 0.5.10+ 0.5.11+ 0.2.6+ 0.0.1+ 0.0.1 0.0.1 |
38 | 161 | 0.5.9.1+ 0.5.9.1+ 0.2.6+ 0.0.1 0.0.1 0.0.1 |
39 | 162 | 0.5.4+ 0.5.1+ 0.2.6+ 0.0.1 0.0.1 0.0.1 |
140 | 263 | architecture |
141 | 264 | |
142 | 265 | New tutorial |
143 | ---------- | |
266 | -------------- | |
144 | 267 | .. toctree:: |
145 | 268 | |
146 | 269 | quickstart |
153 | 276 | database |
154 | 277 | |
155 | 278 | Old tutorial |
156 | ---------- | |
279 | -------------- | |
157 | 280 | .. toctree:: |
158 | 281 | |
159 | 282 | tutorial_file |
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 | ... Coffees,Serving Size,Caffeine (mg) | |
20 | ... Starbucks Coffee Blonde Roast,venti(20 oz),475 | |
21 | ... Dunkin' Donuts Coffee with Turbo Shot,large(20 oz.),398 | |
22 | ... Starbucks Coffee Pike Place Roast,grande(16 oz.),310 | |
23 | ... Panera Coffee Light Roast,regular(16 oz.),300 | |
24 | ... """.strip() | |
25 | >>> sheet = p.get_sheet(file_content=content, file_type='csv') | |
26 | >>> sheet.save_as("your_file.xls") | |
27 | ||
28 | {% endif %} | |
29 | ||
30 | Suppose you want to process the following coffee data (data source `coffee chart <https://cspinet.org/eating-healthy/ingredients-of-concern/caffeine-chart>`_ on the center for science in the public interest): | |
31 | ||
32 | {% if sphinx %} | |
33 | ||
34 | .. pyexcel-table:: | |
35 | ||
36 | ---pyexcel:Top 5 coffeine drinks--- | |
37 | Coffees,Serving Size,Caffeine (mg) | |
38 | Starbucks Coffee Blonde Roast,venti(20 oz),475 | |
39 | Dunkin' Donuts Coffee with Turbo Shot,large(20 oz.),398 | |
40 | Starbucks Coffee Pike Place Roast,grande(16 oz.),310 | |
41 | Panera Coffee Light Roast,regular(16 oz.),300 | |
42 | ||
43 | {% else %} | |
44 | ||
45 | Top 5 coffeine drinks: | |
46 | ||
47 | ===================================== =============== ============= | |
48 | Coffees Serving Size Caffeine (mg) | |
49 | Starbucks Coffee Blonde Roast venti(20 oz) 475 | |
50 | Dunkin' Donuts Coffee with Turbo Shot large(20 oz.) 398 | |
51 | Starbucks Coffee Pike Place Roast grande(16 oz.) 310 | |
52 | Panera Coffee Light Roast regular(16 oz.) 300 | |
53 | ===================================== =============== ============= | |
54 | ||
55 | {% endif %} | |
56 | ||
57 | Let's get a list of dictionary out from the xls file: | |
58 | ||
59 | .. code-block:: python | |
60 | ||
61 | >>> records = p.get_records(file_name="your_file.xls") | |
62 | ||
63 | And let's check what do we have: | |
64 | ||
65 | .. code-block:: python | |
66 | ||
67 | >>> for r in records: | |
68 | ... print(f"{r['Serving Size']} of {r['Coffees']} has {r['Caffeine (mg)']} mg") | |
69 | venti(20 oz) of Starbucks Coffee Blonde Roast has 475 mg | |
70 | large(20 oz.) of Dunkin' Donuts Coffee with Turbo Shot has 398 mg | |
71 | grande(16 oz.) of Starbucks Coffee Pike Place Roast has 310 mg | |
72 | regular(16 oz.) of Panera Coffee Light Roast has 300 mg | |
73 | ||
74 | ||
75 | Get two dimensional array | |
76 | ******************************************************************************** | |
77 | ||
78 | Instead, what if you have to use `pyexcel.get_array` to do the same: | |
79 | ||
80 | .. code-block:: python | |
81 | ||
82 | >>> for row in p.get_array(file_name="your_file.xls", start_row=1): | |
83 | ... print(f"{row[1]} of {row[0]} has {row[2]} mg") | |
84 | venti(20 oz) of Starbucks Coffee Blonde Roast has 475 mg | |
85 | large(20 oz.) of Dunkin' Donuts Coffee with Turbo Shot has 398 mg | |
86 | grande(16 oz.) of Starbucks Coffee Pike Place Roast has 310 mg | |
87 | regular(16 oz.) of Panera Coffee Light Roast has 300 mg | |
88 | ||
89 | ||
90 | where `start_row` skips the header row. | |
91 | ||
92 | ||
93 | Get a dictionary | |
94 | ******************************************************************************** | |
95 | ||
96 | You can get a dictionary too: | |
97 | ||
98 | Now let's get a dictionary out from the spreadsheet: | |
99 | ||
100 | .. code-block:: python | |
101 | ||
102 | >>> my_dict = p.get_dict(file_name="your_file.xls", name_columns_by_row=0) | |
103 | ||
104 | And check what do we have: | |
105 | ||
106 | .. code-block:: python | |
107 | ||
108 | >>> from pyexcel._compact import OrderedDict | |
109 | >>> isinstance(my_dict, OrderedDict) | |
110 | True | |
111 | >>> for key, values in my_dict.items(): | |
112 | ... print(key + " : " + ','.join([str(item) for item in values])) | |
113 | Coffees : Starbucks Coffee Blonde Roast,Dunkin' Donuts Coffee with Turbo Shot,Starbucks Coffee Pike Place Roast,Panera Coffee Light Roast | |
114 | Serving Size : venti(20 oz),large(20 oz.),grande(16 oz.),regular(16 oz.) | |
115 | Caffeine (mg) : 475,398,310,300 | |
116 | ||
117 | Please note that my_dict is an OrderedDict. | |
118 | ||
119 | Get a dictionary of two dimensional array | |
120 | ******************************************************************************** | |
121 | ||
122 | {% if sphinx %} | |
123 | .. testcode:: | |
124 | :hide: | |
125 | ||
126 | >>> a_dictionary_of_two_dimensional_arrays = { | |
127 | ... 'Sheet 1': | |
128 | ... [ | |
129 | ... [1.0, 2.0, 3.0], | |
130 | ... [4.0, 5.0, 6.0], | |
131 | ... [7.0, 8.0, 9.0] | |
132 | ... ], | |
133 | ... 'Sheet 2': | |
134 | ... [ | |
135 | ... ['X', 'Y', 'Z'], | |
136 | ... [1.0, 2.0, 3.0], | |
137 | ... [4.0, 5.0, 6.0] | |
138 | ... ], | |
139 | ... 'Sheet 3': | |
140 | ... [ | |
141 | ... ['O', 'P', 'Q'], | |
142 | ... [3.0, 2.0, 1.0], | |
143 | ... [4.0, 3.0, 2.0] | |
144 | ... ] | |
145 | ... } | |
146 | >>> data = OrderedDict() | |
147 | >>> data.update({"Sheet 1": a_dictionary_of_two_dimensional_arrays['Sheet 1']}) | |
148 | >>> data.update({"Sheet 2": a_dictionary_of_two_dimensional_arrays['Sheet 2']}) | |
149 | >>> data.update({"Sheet 3": a_dictionary_of_two_dimensional_arrays['Sheet 3']}) | |
150 | >>> p.save_book_as(bookdict=data, dest_file_name="book.xls") | |
151 | ||
152 | {% endif %} | |
153 | ||
154 | Suppose you have a multiple sheet book as the following: | |
155 | ||
156 | {% if sphinx %} | |
157 | ||
158 | .. pyexcel-table:: | |
159 | ||
160 | ---pyexcel:Sheet 1--- | |
161 | 1,2,3 | |
162 | 4,5,6 | |
163 | 7,8,9 | |
164 | ---pyexcel--- | |
165 | ---pyexcel:Sheet 2--- | |
166 | X,Y,Z | |
167 | 1,2,3 | |
168 | 4,5,6 | |
169 | ---pyexcel--- | |
170 | ---pyexcel:Sheet 3--- | |
171 | O,P,Q | |
172 | 3,2,1 | |
173 | 4,3,2 | |
174 | ||
175 | {% else %} | |
176 | ||
177 | pyexcel:Sheet 1: | |
178 | ||
179 | ===================== = = | |
180 | 1 2 3 | |
181 | 4 5 6 | |
182 | 7 8 9 | |
183 | ===================== = = | |
184 | ||
185 | pyexcel:Sheet 2: | |
186 | ||
187 | ===================== = = | |
188 | X Y Z | |
189 | 1 2 3 | |
190 | 4 5 6 | |
191 | ===================== = = | |
192 | ||
193 | pyexcel:Sheet 3: | |
194 | ||
195 | ===================== = = | |
196 | O P Q | |
197 | 3 2 1 | |
198 | 4 3 2 | |
199 | ===================== = = | |
200 | ||
201 | {% endif %} | |
202 | ||
203 | Here is the code to obtain those sheets as a single dictionary: | |
204 | ||
205 | .. code-block:: python | |
206 | ||
207 | >>> book_dict = p.get_book_dict(file_name="book.xls") | |
208 | ||
209 | And check: | |
210 | ||
211 | .. code-block:: python | |
212 | ||
213 | >>> isinstance(book_dict, OrderedDict) | |
214 | True | |
215 | >>> import json | |
216 | >>> for key, item in book_dict.items(): | |
217 | ... print(json.dumps({key: item})) | |
218 | {"Sheet 1": [[1, 2, 3], [4, 5, 6], [7, 8, 9]]} | |
219 | {"Sheet 2": [["X", "Y", "Z"], [1, 2, 3], [4, 5, 6]]} | |
220 | {"Sheet 3": [["O", "P", "Q"], [3, 2, 1], [4, 3, 2]]} | |
221 | ||
222 | {% if sphinx %} | |
223 | ||
224 | .. testcode:: | |
225 | :hide: | |
226 | ||
227 | >>> import os | |
228 | >>> os.unlink("book.xls") | |
229 | ||
230 | {% endif %} | |
231 | ||
232 | Write data | |
233 | --------------------------------------------- | |
234 | ||
235 | Export an array | |
236 | ********************** | |
237 | ||
238 | Suppose you have the following array: | |
239 | ||
240 | .. code-block:: python | |
241 | ||
242 | >>> data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] | |
243 | ||
244 | And here is the code to save it as an excel file : | |
245 | ||
246 | .. code-block:: python | |
247 | ||
248 | >>> p.save_as(array=data, dest_file_name="example.xls") | |
249 | ||
250 | Let's verify it: | |
251 | ||
252 | .. code-block:: python | |
253 | ||
254 | >>> p.get_sheet(file_name="example.xls") | |
255 | pyexcel_sheet1: | |
256 | +---+---+---+ | |
257 | | 1 | 2 | 3 | | |
258 | +---+---+---+ | |
259 | | 4 | 5 | 6 | | |
260 | +---+---+---+ | |
261 | | 7 | 8 | 9 | | |
262 | +---+---+---+ | |
263 | ||
264 | {% if sphinx %} | |
265 | .. testcode:: | |
266 | :hide: | |
267 | ||
268 | >>> import os | |
269 | >>> os.unlink("example.xls") | |
270 | {% endif %} | |
271 | ||
272 | And here is the code to save it as a csv file : | |
273 | ||
274 | .. code-block:: python | |
275 | ||
276 | >>> p.save_as(array=data, | |
277 | ... dest_file_name="example.csv", | |
278 | ... dest_delimiter=':') | |
279 | ||
280 | Let's verify it: | |
281 | ||
282 | .. code-block:: python | |
283 | ||
284 | >>> with open("example.csv") as f: | |
285 | ... for line in f.readlines(): | |
286 | ... print(line.rstrip()) | |
287 | ... | |
288 | 1:2:3 | |
289 | 4:5:6 | |
290 | 7:8:9 | |
291 | ||
292 | Export a list of dictionaries | |
293 | ********************************** | |
294 | ||
295 | .. code-block:: python | |
296 | ||
297 | >>> records = [ | |
298 | ... {"year": 1903, "country": "Germany", "speed": "206.7km/h"}, | |
299 | ... {"year": 1964, "country": "Japan", "speed": "210km/h"}, | |
300 | ... {"year": 2008, "country": "China", "speed": "350km/h"} | |
301 | ... ] | |
302 | >>> p.save_as(records=records, dest_file_name='high_speed_rail.xls') | |
303 | ||
304 | ||
305 | Export a dictionary of single key value pair | |
306 | ******************************************************************************** | |
307 | ||
308 | .. code-block:: python | |
309 | ||
310 | >>> henley_on_thames_facts = { | |
311 | ... "area": "5.58 square meters", | |
312 | ... "population": "11,619", | |
313 | ... "civial parish": "Henley-on-Thames", | |
314 | ... "latitude": "51.536", | |
315 | ... "longitude": "-0.898" | |
316 | ... } | |
317 | >>> p.save_as(adict=henley_on_thames_facts, dest_file_name='henley.xlsx') | |
318 | ||
319 | ||
320 | Export a dictionary of single dimensonal array | |
321 | ******************************************************************************** | |
322 | ||
323 | .. code-block:: python | |
324 | ||
325 | >>> ccs_insights = { | |
326 | ... "year": ["2017", "2018", "2019", "2020", "2021"], | |
327 | ... "smart phones": [1.53, 1.64, 1.74, 1.82, 1.90], | |
328 | ... "feature phones": [0.46, 0.38, 0.30, 0.23, 0.17] | |
329 | ... } | |
330 | >>> p.save_as(adict=ccs_insights, dest_file_name='ccs.csv') | |
331 | ||
332 | ||
333 | Export a dictionary of two dimensional array as a book | |
334 | ******************************************************************************** | |
335 | ||
336 | Suppose you want to save the below dictionary to an excel file : | |
337 | ||
338 | .. code-block:: python | |
339 | ||
340 | >>> a_dictionary_of_two_dimensional_arrays = { | |
341 | ... 'Sheet 1': | |
342 | ... [ | |
343 | ... [1.0, 2.0, 3.0], | |
344 | ... [4.0, 5.0, 6.0], | |
345 | ... [7.0, 8.0, 9.0] | |
346 | ... ], | |
347 | ... 'Sheet 2': | |
348 | ... [ | |
349 | ... ['X', 'Y', 'Z'], | |
350 | ... [1.0, 2.0, 3.0], | |
351 | ... [4.0, 5.0, 6.0] | |
352 | ... ], | |
353 | ... 'Sheet 3': | |
354 | ... [ | |
355 | ... ['O', 'P', 'Q'], | |
356 | ... [3.0, 2.0, 1.0], | |
357 | ... [4.0, 3.0, 2.0] | |
358 | ... ] | |
359 | ... } | |
360 | ||
361 | Here is the code: | |
362 | ||
363 | .. code-block:: python | |
364 | ||
365 | >>> p.save_book_as( | |
366 | ... bookdict=a_dictionary_of_two_dimensional_arrays, | |
367 | ... dest_file_name="book.xls" | |
368 | ... ) | |
369 | ||
370 | If you want to preserve the order of sheets in your dictionary, you have to | |
371 | pass on an ordered dictionary to the function itself. For example: | |
372 | ||
373 | .. code-block:: python | |
374 | ||
375 | >>> data = OrderedDict() | |
376 | >>> data.update({"Sheet 2": a_dictionary_of_two_dimensional_arrays['Sheet 2']}) | |
377 | >>> data.update({"Sheet 1": a_dictionary_of_two_dimensional_arrays['Sheet 1']}) | |
378 | >>> data.update({"Sheet 3": a_dictionary_of_two_dimensional_arrays['Sheet 3']}) | |
379 | >>> p.save_book_as(bookdict=data, dest_file_name="book.xls") | |
380 | ||
381 | Let's verify its order: | |
382 | ||
383 | .. code-block:: python | |
384 | ||
385 | >>> book_dict = p.get_book_dict(file_name="book.xls") | |
386 | >>> for key, item in book_dict.items(): | |
387 | ... print(json.dumps({key: item})) | |
388 | {"Sheet 2": [["X", "Y", "Z"], [1, 2, 3], [4, 5, 6]]} | |
389 | {"Sheet 1": [[1, 2, 3], [4, 5, 6], [7, 8, 9]]} | |
390 | {"Sheet 3": [["O", "P", "Q"], [3, 2, 1], [4, 3, 2]]} | |
391 | ||
392 | Please notice that "Sheet 2" is the first item in the *book_dict*, meaning the order of sheets are preserved. | |
393 | ||
394 | ||
395 | Transcoding | |
396 | ------------------------------------------- | |
397 | ||
398 | .. note:: | |
399 | ||
400 | Please note that `pyexcel-cli` can perform file transcoding at command line. | |
401 | No need to open your editor, save the problem, then python run. | |
402 | ||
403 | {% if sphinx %} | |
404 | .. testcode:: | |
405 | :hide: | |
406 | ||
407 | >>> import datetime | |
408 | >>> data = [ | |
409 | ... ["name", "weight", "birth"], | |
410 | ... ["Adam", 3.4, datetime.date(2015, 2, 3)], | |
411 | ... ["Smith", 4.2, datetime.date(2014, 11, 12)] | |
412 | ... ] | |
413 | >>> p.save_as(array=data, dest_file_name="birth.xls") | |
414 | ||
415 | {% endif %} | |
416 | ||
417 | The following code does a simple file format transcoding from xls to csv: | |
418 | ||
419 | .. code-block:: python | |
420 | ||
421 | >>> p.save_as(file_name="birth.xls", dest_file_name="birth.csv") | |
422 | ||
423 | Again it is really simple. Let's verify what we have gotten: | |
424 | ||
425 | .. code-block:: python | |
426 | ||
427 | >>> sheet = p.get_sheet(file_name="birth.csv") | |
428 | >>> sheet | |
429 | birth.csv: | |
430 | +-------+--------+----------+ | |
431 | | name | weight | birth | | |
432 | +-------+--------+----------+ | |
433 | | Adam | 3.4 | 03/02/15 | | |
434 | +-------+--------+----------+ | |
435 | | Smith | 4.2 | 12/11/14 | | |
436 | +-------+--------+----------+ | |
437 | ||
438 | .. NOTE:: | |
439 | ||
440 | 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. | |
441 | ||
442 | ||
443 | Let use previous example and save it as xlsx instead | |
444 | ||
445 | .. code-block:: python | |
446 | ||
447 | >>> p.save_as(file_name="birth.xls", | |
448 | ... dest_file_name="birth.xlsx") # change the file extension | |
449 | ||
450 | Again let's verify what we have gotten: | |
451 | ||
452 | .. code-block:: python | |
453 | ||
454 | >>> sheet = p.get_sheet(file_name="birth.xlsx") | |
455 | >>> sheet | |
456 | pyexcel_sheet1: | |
457 | +-------+--------+----------+ | |
458 | | name | weight | birth | | |
459 | +-------+--------+----------+ | |
460 | | Adam | 3.4 | 03/02/15 | | |
461 | +-------+--------+----------+ | |
462 | | Smith | 4.2 | 12/11/14 | | |
463 | +-------+--------+----------+ | |
464 | ||
465 | ||
466 | Excel book merge and split operation in one line | |
467 | -------------------------------------------------------------------------------- | |
468 | ||
469 | Merge all excel files in directory into a book where each file become a sheet | |
470 | ******************************************************************************** | |
471 | ||
472 | The following code will merge every excel files into one file, say "output.xls": | |
473 | ||
474 | .. code-block:: python | |
475 | ||
476 | from pyexcel.cookbook import merge_all_to_a_book | |
477 | import glob | |
478 | ||
479 | ||
480 | merge_all_to_a_book(glob.glob("your_csv_directory\*.csv"), "output.xls") | |
481 | ||
482 | 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: | |
483 | ||
484 | .. code-block:: python | |
485 | ||
486 | from pyexcel.cookbook import merge_all_to_a_book | |
487 | import glob | |
488 | ||
489 | ||
490 | merge_all_to_a_book(glob.glob("your_excel_file_directory\*.*"), "output.xls") | |
491 | ||
492 | Split a book into single sheet files | |
493 | **************************************** | |
494 | ||
495 | {% if sphinx %} | |
496 | .. testcode:: | |
497 | :hide: | |
498 | ||
499 | >>> content = { | |
500 | ... 'Sheet 1': | |
501 | ... [ | |
502 | ... [1.0, 2.0, 3.0], | |
503 | ... [4.0, 5.0, 6.0], | |
504 | ... [7.0, 8.0, 9.0] | |
505 | ... ], | |
506 | ... 'Sheet 2': | |
507 | ... [ | |
508 | ... ['X', 'Y', 'Z'], | |
509 | ... [1.0, 2.0, 3.0], | |
510 | ... [4.0, 5.0, 6.0] | |
511 | ... ], | |
512 | ... 'Sheet 3': | |
513 | ... [ | |
514 | ... ['O', 'P', 'Q'], | |
515 | ... [3.0, 2.0, 1.0], | |
516 | ... [4.0, 3.0, 2.0] | |
517 | ... ] | |
518 | ... } | |
519 | >>> book = p.Book(content) | |
520 | >>> book.save_as("megabook.xls") | |
521 | ||
522 | {% endif %} | |
523 | ||
524 | 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: | |
525 | ||
526 | .. code-block:: python | |
527 | ||
528 | >>> from pyexcel.cookbook import split_a_book | |
529 | >>> split_a_book("megabook.xls", "output.xls") | |
530 | >>> import glob | |
531 | >>> outputfiles = glob.glob("*_output.xls") | |
532 | >>> for file in sorted(outputfiles): | |
533 | ... print(file) | |
534 | ... | |
535 | Sheet 1_output.xls | |
536 | Sheet 2_output.xls | |
537 | Sheet 3_output.xls | |
538 | ||
539 | for the output file, you can specify any of the supported formats | |
540 | ||
541 | {% if sphinx %} | |
542 | .. testcode:: | |
543 | :hide: | |
544 | ||
545 | >>> os.unlink("Sheet 1_output.xls") | |
546 | >>> os.unlink("Sheet 2_output.xls") | |
547 | >>> os.unlink("Sheet 3_output.xls") | |
548 | {% endif %} | |
549 | ||
550 | Extract just one sheet from a book | |
551 | ************************************* | |
552 | ||
553 | ||
554 | 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: | |
555 | ||
556 | .. code-block:: python | |
557 | ||
558 | >>> from pyexcel.cookbook import extract_a_sheet_from_a_book | |
559 | >>> extract_a_sheet_from_a_book("megabook.xls", "Sheet 1", "output.xls") | |
560 | >>> if os.path.exists("Sheet 1_output.xls"): | |
561 | ... print("Sheet 1_output.xls exists") | |
562 | ... | |
563 | Sheet 1_output.xls exists | |
564 | ||
565 | for the output file, you can specify any of the supported formats | |
566 | ||
567 | {% if sphinx %} | |
568 | .. testcode:: | |
569 | :hide: | |
570 | ||
571 | >>> os.unlink("Sheet 1_output.xls") | |
572 | >>> os.unlink("megabook.xls") | |
573 | >>> os.unlink('birth.xls') | |
574 | >>> os.unlink('birth.csv') | |
575 | >>> os.unlink('birth.xlsx') | |
576 | >>> os.unlink('high_speed_rail.xls') | |
577 | >>> os.unlink('henley.xlsx') | |
578 | >>> os.unlink('ccs.csv') | |
579 | >>> os.unlink("book.xls") | |
580 | >>> os.unlink("your_file.xls") | |
581 | >>> os.unlink("example.csv") | |
582 | {% 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 | .. image:: https://github.com/pyexcel/pyexcel/raw/dev/docs/source/_static/images/architecture.svg | |
8 | ||
9 | ||
10 | 1. One application programming interface(API) to handle multiple data sources: | |
11 | ||
12 | * physical file | |
13 | * memory file | |
14 | * SQLAlchemy table | |
15 | * Django Model | |
16 | * Python data structures: dictionary, records and array | |
17 | ||
18 | 2. One API to read and write data in various excel file formats. | |
19 | 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. | |
20 | ||
21 | {% endblock %} | |
22 | ||
23 | {%block usage%} | |
24 | ||
25 | {%include "one-liners.rst.jj2" %} | |
26 | ||
27 | Hidden feature: partial read | |
28 | =============================================== | |
29 | ||
30 | Most pyexcel users do not know, but other library users were requesting `the similar features <https://github.com/jazzband/tablib/issues/467>`_ | |
31 | ||
32 | {%include "partial-data.rst.jj2" %} | |
33 | ||
34 | {%include "two-liners.rst.jj2" %} | |
35 | ||
36 | Available Plugins | |
37 | ================= | |
38 | ||
39 | {% include "plugins-list.rst.jj2" %} | |
40 | ||
41 | ||
42 | Acknowledgement | |
43 | =============== | |
44 | ||
45 | All great work have been done by odf, ezodf, xlrd, xlwt, tabulate and other | |
46 | individual developers. This library unites only the data access code. | |
47 | ||
48 | ||
49 | {%endblock%} | |
50 | ||
51 | {%block development_guide%} | |
52 | {%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 | {% 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 | {% extends "test.script.jj2" %} | |
1 | ||
2 | {%block flake8_options%} | |
3 | --builtins=unicode,xrange,long | |
4 | {%endblock%} | |
5 | ||
6 | ||
7 |
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 | {% 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 | {% 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" | |
5 | 1 | configuration: |
6 | configuration_dir: "pyexcel-mobans:config" | |
7 | template_dir: | |
8 | - "pyexcel-mobans:templates" | |
9 | - "pypi-mobans:templates" | |
10 | - ".moban.d" | |
11 | 2 | configuration: pyexcel.yml |
12 | 3 | targets: |
13 | - setup.py: setup.py | |
4 | - setup.py: custom_setup.py.jj2 | |
14 | 5 | - "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" | |
9 | - .travis.yml: pyexcel-travis.yml.jj2 | |
20 | 10 | - 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 | |
11 | - README.rst: pyexcel-README.rst.jj2 | |
12 | - "docs/source/guide.rst": "docs/source/guide.rst.jj2" | |
25 | 13 | - .gitignore: commons-gitignore.jj2 |
26 | 14 | - "pyexcel/__version__.py": version.txt |
27 | 15 | - "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 | |
16 | - "tests/requirements.txt": "tests/custom_requirements.txt.jj2" | |
17 | - "min_requirements.txt": "minimum_requirements.txt.jj2" | |
18 |
0 | ||
1 | 0 | sudo: false |
2 | 1 | dist: xenial |
3 | 2 | language: python |
4 | 3 | notifications: |
5 | 4 | email: false |
6 | 5 | python: |
7 | - &pypy2 pypy2.7-6.0 | |
8 | - &pypy3 pypy3.5-6.0 | |
6 | - 3.8 | |
9 | 7 | - 3.7 |
10 | 8 | - 3.6 |
11 | - 3.5 | |
12 | - 2.7 | |
13 | 9 | matrix: |
14 | 10 | include: |
15 | - python: 2.7 | |
11 | - python: 3.6 | |
16 | 12 | env: MINREQ=1 |
17 | 13 | |
18 | 14 | stages: |
15 | - lint | |
19 | 16 | - test |
20 | - lint | |
21 | 17 | |
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 | 18 | |
29 | 19 | .lint: &lint |
30 | <<: *disable_global | |
20 | git: | |
21 | submodules: false | |
31 | 22 | python: 3.6 |
23 | env: | |
24 | - MINREQ=0 | |
32 | 25 | stage: lint |
33 | install: pip install flake8 | |
34 | 26 | script: make lint |
35 | 27 | |
36 | 28 | jobs: |
37 | 29 | include: |
30 | - *moban | |
38 | 31 | - *lint |
39 | 32 | |
40 | 33 | stage: test |
41 | 34 | |
42 | script: make test | |
43 | ||
44 | 35 | before_install: |
45 | - if [[ $TRAVIS_PYTHON_VERSION == "pypy" ]]; then rm tests/test_examples.py; fi | |
46 | 36 | - if [[ -f min_requirements.txt && "$MINREQ" -eq 1 ]]; then |
47 | 37 | mv min_requirements.txt requirements.txt ; |
48 | 38 | fi |
49 | - test ! -f rnd_requirements.txt || pip install --no-deps -r rnd_requirements.txt | |
39 | - test ! -f rnd_requirements.txt || | |
40 | pip install --no-deps -r rnd_requirements.txt | |
50 | 41 | - test ! -f rnd_requirements.txt || pip install -r rnd_requirements.txt ; |
51 | 42 | - pip install -r tests/requirements.txt |
52 | 43 | script: |
0 | 0 | Change log |
1 | 1 | ================================================================================ |
2 | 2 | |
3 | 0.6.6 - 14.11.2020 | |
4 | -------------------------------------------------------------------------------- | |
5 | ||
6 | **Updated** | |
7 | ||
8 | #. `#233 <https://github.com/pyexcel/pyexcel/issues/233>`_: dynamically resize | |
9 | the table matrix on set_value. sheet['AA1'] = 'test' will work in this | |
10 | release. | |
11 | ||
12 | 0.6.5 - 8.10.2020 | |
13 | -------------------------------------------------------------------------------- | |
14 | ||
15 | **Updated** | |
16 | ||
17 | #. update queryset source to work with pyexcel-io 0.6.0 | |
18 | ||
19 | 0.6.4 - 18.08.2020 | |
20 | -------------------------------------------------------------------------------- | |
21 | ||
22 | **Updated** | |
23 | ||
24 | #. `#219 <https://github.com/pyexcel/pyexcel/issues/219>`_: book created from | |
25 | dict no longer discards order. | |
26 | ||
27 | 0.6.3 - 01.08.2020 | |
28 | -------------------------------------------------------------------------------- | |
29 | ||
30 | **fixed** | |
31 | ||
32 | #. `#214 <https://github.com/pyexcel/pyexcel/issues/214>`_: remove leading and | |
33 | trailing whitespace for column names | |
34 | ||
35 | **removed** | |
36 | ||
37 | #. python 2 compatibility have been permanently removed. | |
38 | ||
39 | 0.6.2 - 8.06.2020 | |
40 | -------------------------------------------------------------------------------- | |
41 | ||
42 | **fixed** | |
43 | ||
44 | #. `#109 <https://github.com/pyexcel/pyexcel/issues/109>`_: Control the column | |
45 | order when write the data output | |
46 | ||
47 | 0.6.1 - 02.05.2020 | |
48 | -------------------------------------------------------------------------------- | |
49 | ||
50 | **fixed** | |
51 | ||
52 | #. `#203 <https://github.com/pyexcel/pyexcel/issues/203>`_: texttable was | |
53 | dropped out in 0.6.0 as compulsary dependency. end user may experience it | |
54 | when a sheet/table is printed in a shell. otherwise, new user of pyexcel | |
55 | won't see it. As of release date, no issues were created | |
56 | ||
57 | 0.6.0 - 21.04.2020 | |
58 | -------------------------------------------------------------------------------- | |
59 | ||
60 | **updated** | |
61 | ||
62 | #. `#199 <https://github.com/pyexcel/pyexcel/issues/199>`_: += in place; = + | |
63 | shall return new instance | |
64 | #. `#195 <https://github.com/pyexcel/pyexcel/issues/195>`_: documentation | |
65 | update. however small is welcome | |
66 | ||
67 | **removed** | |
68 | ||
69 | #. Dropping the test support for python version lower than 3.6. v0.6.0 should | |
70 | work with python 2.7 but is not guaranteed to work. Please upgrade to python | |
71 | 3.6+. | |
72 | ||
73 | 0.5.15 - 07.07.2019 | |
74 | -------------------------------------------------------------------------------- | |
75 | ||
76 | **updated** | |
77 | ||
78 | #. `#185 <https://github.com/pyexcel/pyexcel/issues/185>`_: fix a bug with http | |
79 | data source. The real fix lies in pyexcel-io v0.5.19. this release just put | |
80 | the version requirement in. | |
81 | ||
3 | 82 | 0.5.14 - 12.06.2019 |
4 | 83 | -------------------------------------------------------------------------------- |
5 | 84 | |
6 | updated | |
7 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
85 | **updated** | |
8 | 86 | |
9 | 87 | #. `#182 <https://github.com/pyexcel/pyexcel/issues/182>`_: support |
10 | 88 | dest_force_file_type on save_as and save_book_as |
12 | 90 | 0.5.13 - 12.03.2019 |
13 | 91 | -------------------------------------------------------------------------------- |
14 | 92 | |
15 | updated | |
16 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
93 | **updated** | |
17 | 94 | |
18 | 95 | #. `#176 <https://github.com/pyexcel/pyexcel/issues/176>`_: get_sheet |
19 | 96 | {IndexError}list index out of range // XLSX can't be opened |
21 | 98 | 0.5.12 - 25.02.2019 |
22 | 99 | -------------------------------------------------------------------------------- |
23 | 100 | |
24 | updated | |
25 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
101 | **updated** | |
26 | 102 | |
27 | 103 | #. `#174 <https://github.com/pyexcel/pyexcel/issues/174>`_: include examples in |
28 | 104 | tarbar |
30 | 106 | 0.5.11 - 22.02.2019 |
31 | 107 | -------------------------------------------------------------------------------- |
32 | 108 | |
33 | updated | |
34 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
109 | **updated** | |
35 | 110 | |
36 | 111 | #. `#169 <https://github.com/pyexcel/pyexcel/issues/169>`_: remove |
37 | 112 | pyexcel-handsontalbe in test |
40 | 115 | 0.5.10 - 3.12.2018 |
41 | 116 | -------------------------------------------------------------------------------- |
42 | 117 | |
43 | updated | |
44 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
118 | **updated** | |
45 | 119 | |
46 | 120 | #. `#157 <https://github.com/pyexcel/pyexcel/issues/157>`_: Please use |
47 | 121 | scan_plugins_regex, which lml 0.7 complains about |
50 | 124 | 0.5.9.1 - 30.08.2018 |
51 | 125 | -------------------------------------------------------------------------------- |
52 | 126 | |
53 | updated | |
54 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
127 | **updated** | |
55 | 128 | |
56 | 129 | #. to require pyexcel-io 0.5.9.1 and use lml at least version 0.0.2 |
57 | 130 | |
58 | 131 | 0.5.9 - 30.08.2018 |
59 | 132 | -------------------------------------------------------------------------------- |
60 | 133 | |
61 | added | |
62 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
134 | **added** | |
63 | 135 | |
64 | 136 | #. support __len__. len(book) returns the number of sheets and len(sheet) |
65 | 137 | returns the number of rows |
71 | 143 | but with .blob file suffix. |
72 | 144 | #. finally, pyexcel got import pyexcel.__version__ |
73 | 145 | |
74 | updated | |
75 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
146 | **updated** | |
76 | 147 | |
77 | 148 | #. Sheet.to_records() returns a generator now, saving memory |
78 | 149 | #. `#115 <https://github.com/pyexcel/pyexcel/issues/115>`_, Fix set membership |
80 | 151 | #. `#140 <https://github.com/pyexcel/pyexcel/issues/140>`_, Direct writes to |
81 | 152 | cells yield weird results |
82 | 153 | |
83 | 0.5.8 - unreleased | |
84 | -------------------------------------------------------------------------------- | |
85 | ||
86 | added | |
87 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
154 | 0.5.8 - 26.03.2018 | |
155 | -------------------------------------------------------------------------------- | |
156 | ||
157 | **added** | |
88 | 158 | |
89 | 159 | #. `#125 <https://github.com/pyexcel/pyexcel/issues/125>`_, sort book sheets |
90 | 160 | |
91 | updated | |
92 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
161 | **updated** | |
93 | 162 | |
94 | 163 | #. `#126 <https://github.com/pyexcel/pyexcel/issues/126>`_, dest_sheet_name in |
95 | 164 | save_as will set the sheet name in the output |
99 | 168 | 0.5.7 - 11.01.2018 |
100 | 169 | -------------------------------------------------------------------------------- |
101 | 170 | |
102 | added | |
103 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
171 | **added** | |
104 | 172 | |
105 | 173 | #. `pyexcel-io#46 <https://github.com/pyexcel/pyexcel-io/issues/46>`_, expose |
106 | 174 | `bulk_save` to developer. |
108 | 176 | 0.5.6 - 23.10.2017 |
109 | 177 | -------------------------------------------------------------------------------- |
110 | 178 | |
111 | removed | |
112 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
179 | **removed** | |
113 | 180 | |
114 | 181 | #. `#105 <https://github.com/pyexcel/pyexcel/issues/105>`_, remove gease from |
115 | 182 | setup_requires, introduced by 0.5.5. |
120 | 187 | 0.5.5 - 20.10.2017 |
121 | 188 | -------------------------------------------------------------------------------- |
122 | 189 | |
123 | removed | |
124 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
190 | **removed** | |
125 | 191 | |
126 | 192 | #. `#105 <https://github.com/pyexcel/pyexcel/issues/105>`_, remove gease from |
127 | 193 | setup_requires, introduced by 0.5.5. |
132 | 198 | 0.5.4 - 27.09.2017 |
133 | 199 | -------------------------------------------------------------------------------- |
134 | 200 | |
135 | fixed | |
136 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
201 | **fixed** | |
137 | 202 | |
138 | 203 | #. `#100 <https://github.com/pyexcel/pyexcel/issues/100>`_, Sheet.to_dict() gets |
139 | 204 | out of range error because there is only one row. |
140 | 205 | |
141 | updated | |
142 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
206 | **updated** | |
143 | 207 | |
144 | 208 | #. Updated the baseline of pyexcel-io to 0.5.1. |
145 | 209 | |
146 | 210 | 0.5.3 - 01-08-2017 |
147 | 211 | -------------------------------------------------------------------------------- |
148 | 212 | |
149 | added | |
150 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
213 | **added** | |
151 | 214 | |
152 | 215 | #. `#95 <https://github.com/pyexcel/pyexcel/issues/95>`_, respect the order of |
153 | 216 | records in iget_records, isave_as and save_as. |
157 | 220 | 0.5.2 - 26-07-2017 |
158 | 221 | -------------------------------------------------------------------------------- |
159 | 222 | |
160 | Updated | |
161 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
223 | **Updated** | |
162 | 224 | |
163 | 225 | #. embeded the enabler for pyexcel-htmlr. http source does not support text/html |
164 | 226 | as mime type. |
166 | 228 | 0.5.1 - 12.06.2017 |
167 | 229 | -------------------------------------------------------------------------------- |
168 | 230 | |
169 | Updated | |
170 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
231 | **Updated** | |
171 | 232 | |
172 | 233 | #. support saving SheetStream and BookStream to database targets. This is needed |
173 | 234 | for pyexcel-webio and its downstream projects. |
175 | 236 | 0.5.0 - 19.06.2017 |
176 | 237 | -------------------------------------------------------------------------------- |
177 | 238 | |
178 | Added | |
179 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
239 | **Added** | |
180 | 240 | |
181 | 241 | #. Sheet.top() and Sheet.top_left() for data browsing |
182 | 242 | #. add html as default rich display in Jupyter notebook when pyexcel-text and |
194 | 254 | is enfored. free_resource is added and it should be called when iget_array, |
195 | 255 | iget_records, isave_as and/or isave_book_as are used. |
196 | 256 | |
197 | Updated | |
198 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
257 | **Updated** | |
199 | 258 | |
200 | 259 | #. array is passed to pyexcel.Sheet as reference. it means your array data will |
201 | 260 | be modified. |
202 | 261 | |
203 | Removed | |
204 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
262 | **Removed** | |
205 | 263 | |
206 | 264 | #. pyexcel.Writer and pyexcel.BookWriter were removed |
207 | 265 | #. pyexcel.load_book_from_sql and pyexcel.load_from_sql were removed |
213 | 271 | 0.4.5 - 17.03.2017 |
214 | 272 | -------------------------------------------------------------------------------- |
215 | 273 | |
216 | Updated | |
217 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
274 | **Updated** | |
218 | 275 | |
219 | 276 | #. `#80 <https://github.com/pyexcel/pyexcel/issues/80>`_: remove pyexcel-chart |
220 | 277 | import from v0.4.x |
222 | 279 | 0.4.4 - 06.02.2017 |
223 | 280 | -------------------------------------------------------------------------------- |
224 | 281 | |
225 | Updated | |
226 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
282 | **Updated** | |
227 | 283 | |
228 | 284 | #. `#68 <https://github.com/pyexcel/pyexcel/issues/68>`_: regression |
229 | 285 | save_to_memory() should have returned a stream instance which has been reset |
231 | 287 | #. `#74 <https://github.com/pyexcel/pyexcel/issues/74>`_: Not able to handle |
232 | 288 | decimal.Decimal |
233 | 289 | |
234 | Removed | |
235 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
290 | **Removed** | |
236 | 291 | |
237 | 292 | #. remove get_{{file_type}}_stream functions from pyexcel.Sheet and pyexcel.Book |
238 | 293 | introduced since 0.4.3. |
240 | 295 | 0.4.3 - 26.01.2017 |
241 | 296 | -------------------------------------------------------------------------------- |
242 | 297 | |
243 | Added | |
244 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
298 | **Added** | |
245 | 299 | |
246 | 300 | #. '.stream' attribute are attached to `~pyexcel.Sheet` and `~pyexcel.Book` to |
247 | 301 | get direct access the underneath stream in responding to file type |
249 | 303 | world, for example, Sheet.stream.csv gives a text stream that contains csv |
250 | 304 | formatted data. Book.stream.xls returns a xls format data in a byte stream. |
251 | 305 | |
252 | Updated | |
253 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
306 | **Updated** | |
254 | 307 | |
255 | 308 | #. Better error reporting when an unknown parameters or unsupported file types |
256 | 309 | were given to the signature functions. |
258 | 311 | 0.4.2 - 17.01.2017 |
259 | 312 | -------------------------------------------------------------------------------- |
260 | 313 | |
261 | Updated | |
262 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
314 | **Updated** | |
263 | 315 | |
264 | 316 | #. Raise exception if the incoming sheet does not have column names. In other |
265 | 317 | words, only sheet with column names could be saved to database. sheet with |
279 | 331 | 0.4.1 - 23.12.2016 |
280 | 332 | -------------------------------------------------------------------------------- |
281 | 333 | |
282 | Updated | |
283 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
334 | **Updated** | |
284 | 335 | |
285 | 336 | #. `#68 <https://github.com/pyexcel/pyexcel/issues/68>`_: regression |
286 | 337 | save_to_memory() should have returned a stream instance. |
288 | 339 | 0.4.0 - 22.12.2016 |
289 | 340 | -------------------------------------------------------------------------------- |
290 | 341 | |
291 | Added | |
292 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
342 | **Added** | |
293 | 343 | |
294 | 344 | #. `Flask-Excel#19 <https://github.com/pyexcel/Flask-Excel/issues/19>`_ allow |
295 | 345 | sheet_name parameter |
296 | 346 | #. `pyexcel-xls#11 <https://github.com/pyexcel/pyexcel-xls/issues/11>`_ |
297 | 347 | case-insensitive for file_type. `xls` and `XLS` are treated in the same way |
298 | 348 | |
299 | Updated | |
300 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
349 | **Updated** | |
301 | 350 | |
302 | 351 | #. `#66 <https://github.com/pyexcel/pyexcel/issues/66>`_: `export_columns` is |
303 | 352 | ignored |
306 | 355 | 0.3.3 - 07.11.2016 |
307 | 356 | -------------------------------------------------------------------------------- |
308 | 357 | |
309 | Updated | |
310 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
358 | **Updated** | |
311 | 359 | |
312 | 360 | #. `#63 <https://github.com/pyexcel/pyexcel/issues/63>`_: cannot display empty |
313 | 361 | sheet(hence book with empty sheet) as texttable |
315 | 363 | 0.3.2 - 02.11.2016 |
316 | 364 | -------------------------------------------------------------------------------- |
317 | 365 | |
318 | Updated | |
319 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
366 | **Updated** | |
320 | 367 | |
321 | 368 | #. `#62 <https://github.com/pyexcel/pyexcel/issues/62>`_: optional module import |
322 | 369 | error become visible. |
324 | 371 | 0.3.0 - 28.10.2016 |
325 | 372 | -------------------------------------------------------------------------------- |
326 | 373 | |
327 | Added: | |
328 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
374 | **Added:** | |
329 | 375 | |
330 | 376 | #. file type setters for Sheet and Book, and its documentation |
331 | 377 | #. `iget_records` returns a generator for a list of records and should have |
335 | 381 | files. |
336 | 382 | #. Enable pagination support, and custom row renderer via pyexcel-io v0.2.3 |
337 | 383 | |
338 | Updated | |
339 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
384 | **Updated** | |
340 | 385 | |
341 | 386 | #. Take `isave_as` out from `save_as`. Hence two functions are there for save a |
342 | 387 | sheet as |
363 | 408 | actual content. No longer they will return a io object hence you cannot call |
364 | 409 | getvalue() on them. |
365 | 410 | |
366 | Removed: | |
367 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
411 | **Removed:** | |
368 | 412 | |
369 | 413 | #. `content` and `out_file` as function parameters to the signature functions |
370 | 414 | are no longer supported. |
414 | 458 | 0.2.5 - 31.08.2016 |
415 | 459 | -------------------------------------------------------------------------------- |
416 | 460 | |
417 | Updated: | |
418 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
461 | **Updated:** | |
419 | 462 | |
420 | 463 | #. `#58 <https://github.com/pyexcel/pyexcel/issues/58>`_: texttable should have |
421 | 464 | been made as compulsory requirement |
423 | 466 | 0.2.4 - 14.07.2016 |
424 | 467 | -------------------------------------------------------------------------------- |
425 | 468 | |
426 | Updated: | |
427 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
469 | **Updated:** | |
428 | 470 | |
429 | 471 | #. For python 2, writing to sys.stdout by pyexcel-cli raise IOError. |
430 | 472 | |
431 | 473 | 0.2.3 - 11.07.2016 |
432 | 474 | -------------------------------------------------------------------------------- |
433 | 475 | |
434 | Updated: | |
435 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
476 | **Updated:** | |
436 | 477 | |
437 | 478 | #. For python 3, do not seek 0 when saving to memory if sys.stdout is passed on. |
438 | 479 | Hence, adding support for sys.stdin and sys.stdout. |
440 | 481 | 0.2.2 - 01.06.2016 |
441 | 482 | -------------------------------------------------------------------------------- |
442 | 483 | |
443 | Updated: | |
444 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
484 | **Updated:** | |
445 | 485 | |
446 | 486 | #. Explicit imports, no longer needed |
447 | 487 | #. Depends on latest setuptools 18.0.1 |
452 | 492 | 0.2.1 - 23.04.2016 |
453 | 493 | -------------------------------------------------------------------------------- |
454 | 494 | |
455 | Added: | |
456 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
495 | **Added:** | |
457 | 496 | |
458 | 497 | #. add pyexcel-text file types as attributes of pyexcel.Sheet and pyexcel.Book, |
459 | 498 | related to `#31 <https://github.com/pyexcel/pyexcel/issues/31>`__ |
460 | 499 | #. auto import pyexcel-text if it is pip installed |
461 | 500 | |
462 | Updated: | |
463 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
501 | **Updated:** | |
464 | 502 | |
465 | 503 | #. code refactoring done for easy addition of sources. |
466 | 504 | #. bug fix `#29 <https://github.com/pyexcel/pyexcel/issues/29>`__, Even if the |
468 | 506 | #. pyexcel-text is no longer a plugin to pyexcel-io but to pyexcel.sources, see |
469 | 507 | `pyexcel-text#22 <https://github.com/pyexcel/pyexcel-text/issues/22>`__ |
470 | 508 | |
471 | Removed: | |
472 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
509 | **Removed:** | |
473 | 510 | |
474 | 511 | #. pyexcel.presentation is removed. No longer the internal decorate @outsource |
475 | 512 | is used. related to `#31 <https://github.com/pyexcel/pyexcel/issues/31>`_ |
477 | 514 | 0.2.0 - 17.01.2016 |
478 | 515 | -------------------------------------------------------------------------------- |
479 | 516 | |
480 | Updated | |
481 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
517 | **Updated** | |
482 | 518 | |
483 | 519 | #. adopt pyexcel-io yield key word to return generator as content |
484 | 520 | #. pyexcel.save_as and pyexcel.save_book_as get performance improvements |
486 | 522 | 0.1.7 - 03.07.2015 |
487 | 523 | -------------------------------------------------------------------------------- |
488 | 524 | |
489 | Added | |
490 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
525 | **Added** | |
491 | 526 | |
492 | 527 | #. Support pyramid-excel which does the database commit on its own. |
493 | 528 | |
494 | 529 | 0.1.6 - 13.06.2015 |
495 | 530 | -------------------------------------------------------------------------------- |
496 | 531 | |
497 | Added | |
498 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
532 | **Added** | |
499 | 533 | |
500 | 534 | #. get excel data from a http url |
501 | 535 | |
502 | 536 | 0.0.13 - 07.02.2015 |
503 | 537 | -------------------------------------------------------------------------------- |
504 | 538 | |
505 | Added | |
506 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
539 | **Added** | |
507 | 540 | |
508 | 541 | #. Support django |
509 | 542 | #. texttable as default renderer |
511 | 544 | 0.0.12 - 25.01.2015 |
512 | 545 | -------------------------------------------------------------------------------- |
513 | 546 | |
514 | Added | |
515 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
547 | **Added** | |
516 | 548 | |
517 | 549 | #. Added sqlalchemy support |
518 | 550 | |
519 | 551 | 0.0.10 - 15.12.2015 |
520 | 552 | -------------------------------------------------------------------------------- |
521 | 553 | |
522 | Added | |
523 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
554 | **Added** | |
524 | 555 | |
525 | 556 | #. added csvz and tsvz format |
526 | 557 | |
527 | 558 | 0.0.4 - 12.10.2014 |
528 | 559 | -------------------------------------------------------------------------------- |
529 | 560 | |
530 | Updated | |
531 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
561 | **Updated** | |
532 | 562 | |
533 | 563 | #. Support python 3 |
534 | 564 | |
535 | 565 | 0.0.1 - 14.09.2014 |
536 | 566 | -------------------------------------------------------------------------------- |
537 | 567 | |
538 | Features: | |
539 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
568 | **Features:** | |
540 | 569 | |
541 | 570 | #. read and write csv, ods, xls, xlsx and xlsm files(which are referred later as |
542 | 571 | excel files) |
0 | ||
1 | ||
2 | 18 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 | * `nikolas <https://github.com/nikolas>`_ | |
19 | * `Rintze M. Zelle <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 | * `William Jamir Silva <https://github.com/williamjamir>`_ |
0 | Copyright (c) 2014-2019 by Onni Software Ltd. and its contributors | |
0 | Copyright (c) 2014-2020 by Onni Software Ltd. and its contributors | |
1 | 1 | All rights reserved. |
2 | 2 | |
3 | 3 | Redistribution and use in source and binary forms of the software as well |
12 | 12 | and/or other materials provided with the distribution. |
13 | 13 | |
14 | 14 | * 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 | |
16 | 16 | without specific prior written permission. |
17 | 17 | |
18 | 18 | THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
0 | 0 | include README.rst |
1 | 1 | include LICENSE |
2 | 2 | include CHANGELOG.rst |
3 | include CONTRIBUTORS.rst | |
3 | 4 | recursive-include tests * |
4 | 5 | recursive-include docs * |
5 | 6 | recursive-include examples * |
0 | 0 | all: test |
1 | 1 | |
2 | test: | |
2 | test: lint | |
3 | 3 | bash test.sh |
4 | 4 | |
5 | doc: | |
6 | bash document.sh | |
5 | install_test: | |
6 | pip install -r tests/requirements.txt | |
7 | 7 | |
8 | uml: | |
9 | plantuml -tsvg -o ../_static/images/ docs/source/uml/*.uml | |
8 | lint: | |
9 | bash lint.sh | |
10 | 10 | |
11 | 11 | 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 |
2 | 2 | ================================================================================ |
3 | 3 | |
4 | 4 | .. 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 | |
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 | 9 | |
10 | 10 | .. image:: https://travis-ci.org/pyexcel/pyexcel.svg?branch=master |
11 | 11 | :target: http://travis-ci.org/pyexcel/pyexcel |
13 | 13 | .. image:: https://codecov.io/gh/pyexcel/pyexcel/branch/master/graph/badge.svg |
14 | 14 | :target: https://codecov.io/gh/pyexcel/pyexcel |
15 | 15 | |
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 | ||
16 | 28 | .. image:: https://img.shields.io/gitter/room/gitterHQ/gitter.svg |
17 | 29 | :target: https://gitter.im/pyexcel/Lobby |
18 | 30 | |
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 | |
19 | 36 | .. image:: https://readthedocs.org/projects/pyexcel/badge/?version=latest |
20 | 37 | :target: http://pyexcel.readthedocs.org/en/latest/ |
21 | 38 | |
23 | 40 | ================================================================================ |
24 | 41 | |
25 | 42 | 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>`_ | |
27 | 44 | or `bounty source <https://salt.bountysource.com/teams/chfw-pyexcel>`_ to maintain |
28 | 45 | the project and develop it further. |
29 | 46 | |
42 | 59 | |
43 | 60 | Fonts, colors and charts are not supported. |
44 | 61 | |
62 | Nor to read password protected xls, xlsx and ods files. | |
63 | ||
45 | 64 | Introduction |
46 | 65 | ================================================================================ |
47 | 66 | |
48 | 67 | Feature Highlights |
49 | 68 | =================== |
69 | ||
70 | .. image:: https://github.com/pyexcel/pyexcel/raw/dev/docs/source/_static/images/architecture.svg | |
71 | ||
50 | 72 | |
51 | 73 | 1. One application programming interface(API) to handle multiple data sources: |
52 | 74 | |
55 | 77 | * SQLAlchemy table |
56 | 78 | * Django Model |
57 | 79 | * Python data structures: dictionary, records and array |
80 | ||
58 | 81 | 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. | |
82 | 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. | |
60 | 83 | |
61 | 84 | |
62 | 85 | |
81 | 104 | |
82 | 105 | |
83 | 106 | |
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: | |
107 | One liners | |
108 | ================================================================================ | |
109 | ||
110 | This section shows you how to get data from your excel files and how to | |
111 | export data to excel files in **one line** | |
112 | ||
113 | Read from the excel files | |
114 | -------------------------------------------------------------------------------- | |
115 | ||
116 | Get a list of dictionaries | |
117 | ******************************************************************************** | |
118 | ||
119 | ||
120 | Suppose you want to process the following coffee data (data source `coffee chart <https://cspinet.org/eating-healthy/ingredients-of-concern/caffeine-chart>`_ on the center for science in the public interest): | |
121 | ||
122 | ||
123 | Top 5 coffeine drinks: | |
124 | ||
125 | ===================================== =============== ============= | |
126 | Coffees Serving Size Caffeine (mg) | |
127 | Starbucks Coffee Blonde Roast venti(20 oz) 475 | |
128 | Dunkin' Donuts Coffee with Turbo Shot large(20 oz.) 398 | |
129 | Starbucks Coffee Pike Place Roast grande(16 oz.) 310 | |
130 | Panera Coffee Light Roast regular(16 oz.) 300 | |
131 | ===================================== =============== ============= | |
132 | ||
133 | ||
134 | Let's get a list of dictionary out from the xls file: | |
135 | ||
136 | .. code-block:: python | |
137 | ||
138 | >>> records = p.get_records(file_name="your_file.xls") | |
139 | ||
140 | And let's check what do we have: | |
141 | ||
142 | .. code-block:: python | |
143 | ||
144 | >>> for r in records: | |
145 | ... print(f"{r['Serving Size']} of {r['Coffees']} has {r['Caffeine (mg)']} mg") | |
146 | venti(20 oz) of Starbucks Coffee Blonde Roast has 475 mg | |
147 | large(20 oz.) of Dunkin' Donuts Coffee with Turbo Shot has 398 mg | |
148 | grande(16 oz.) of Starbucks Coffee Pike Place Roast has 310 mg | |
149 | regular(16 oz.) of Panera Coffee Light Roast has 300 mg | |
150 | ||
151 | ||
152 | Get two dimensional array | |
153 | ******************************************************************************** | |
154 | ||
155 | Instead, what if you have to use `pyexcel.get_array` to do the same: | |
156 | ||
157 | .. code-block:: python | |
158 | ||
159 | >>> for row in p.get_array(file_name="your_file.xls", start_row=1): | |
160 | ... print(f"{row[1]} of {row[0]} has {row[2]} mg") | |
161 | venti(20 oz) of Starbucks Coffee Blonde Roast has 475 mg | |
162 | large(20 oz.) of Dunkin' Donuts Coffee with Turbo Shot has 398 mg | |
163 | grande(16 oz.) of Starbucks Coffee Pike Place Roast has 310 mg | |
164 | regular(16 oz.) of Panera Coffee Light Roast has 300 mg | |
165 | ||
166 | ||
167 | where `start_row` skips the header row. | |
168 | ||
169 | ||
170 | Get a dictionary | |
171 | ******************************************************************************** | |
172 | ||
173 | You can get a dictionary too: | |
174 | ||
175 | Now let's get a dictionary out from the spreadsheet: | |
176 | ||
177 | .. code-block:: python | |
178 | ||
179 | >>> my_dict = p.get_dict(file_name="your_file.xls", name_columns_by_row=0) | |
180 | ||
181 | And check what do we have: | |
182 | ||
183 | .. code-block:: python | |
184 | ||
185 | >>> from pyexcel._compact import OrderedDict | |
186 | >>> isinstance(my_dict, OrderedDict) | |
187 | True | |
188 | >>> for key, values in my_dict.items(): | |
189 | ... print(key + " : " + ','.join([str(item) for item in values])) | |
190 | Coffees : Starbucks Coffee Blonde Roast,Dunkin' Donuts Coffee with Turbo Shot,Starbucks Coffee Pike Place Roast,Panera Coffee Light Roast | |
191 | Serving Size : venti(20 oz),large(20 oz.),grande(16 oz.),regular(16 oz.) | |
192 | Caffeine (mg) : 475,398,310,300 | |
193 | ||
194 | Please note that my_dict is an OrderedDict. | |
195 | ||
196 | Get a dictionary of two dimensional array | |
197 | ******************************************************************************** | |
198 | ||
199 | ||
200 | Suppose you have a multiple sheet book as the following: | |
201 | ||
202 | ||
203 | pyexcel:Sheet 1: | |
204 | ||
205 | ===================== = = | |
206 | 1 2 3 | |
207 | 4 5 6 | |
208 | 7 8 9 | |
209 | ===================== = = | |
210 | ||
211 | pyexcel:Sheet 2: | |
212 | ||
213 | ===================== = = | |
214 | X Y Z | |
215 | 1 2 3 | |
216 | 4 5 6 | |
217 | ===================== = = | |
218 | ||
219 | pyexcel:Sheet 3: | |
220 | ||
221 | ===================== = = | |
222 | O P Q | |
223 | 3 2 1 | |
224 | 4 3 2 | |
225 | ===================== = = | |
226 | ||
227 | ||
228 | Here is the code to obtain those sheets as a single dictionary: | |
229 | ||
230 | .. code-block:: python | |
231 | ||
232 | >>> book_dict = p.get_book_dict(file_name="book.xls") | |
233 | ||
234 | And check: | |
235 | ||
236 | .. code-block:: python | |
237 | ||
238 | >>> isinstance(book_dict, OrderedDict) | |
239 | True | |
240 | >>> import json | |
241 | >>> for key, item in book_dict.items(): | |
242 | ... print(json.dumps({key: item})) | |
243 | {"Sheet 1": [[1, 2, 3], [4, 5, 6], [7, 8, 9]]} | |
244 | {"Sheet 2": [["X", "Y", "Z"], [1, 2, 3], [4, 5, 6]]} | |
245 | {"Sheet 3": [["O", "P", "Q"], [3, 2, 1], [4, 3, 2]]} | |
246 | ||
247 | ||
248 | Write data | |
249 | --------------------------------------------- | |
250 | ||
251 | Export an array | |
252 | ********************** | |
253 | ||
254 | Suppose you have the following array: | |
255 | ||
256 | .. code-block:: python | |
257 | ||
258 | >>> data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] | |
259 | ||
260 | And here is the code to save it as an excel file : | |
261 | ||
262 | .. code-block:: python | |
263 | ||
264 | >>> p.save_as(array=data, dest_file_name="example.xls") | |
265 | ||
266 | Let's verify it: | |
267 | ||
268 | .. code-block:: python | |
269 | ||
270 | >>> p.get_sheet(file_name="example.xls") | |
271 | pyexcel_sheet1: | |
272 | +---+---+---+ | |
273 | | 1 | 2 | 3 | | |
274 | +---+---+---+ | |
275 | | 4 | 5 | 6 | | |
276 | +---+---+---+ | |
277 | | 7 | 8 | 9 | | |
278 | +---+---+---+ | |
279 | ||
280 | ||
281 | And here is the code to save it as a csv file : | |
282 | ||
283 | .. code-block:: python | |
284 | ||
285 | >>> p.save_as(array=data, | |
286 | ... dest_file_name="example.csv", | |
287 | ... dest_delimiter=':') | |
288 | ||
289 | Let's verify it: | |
290 | ||
291 | .. code-block:: python | |
292 | ||
293 | >>> with open("example.csv") as f: | |
294 | ... for line in f.readlines(): | |
295 | ... print(line.rstrip()) | |
296 | ... | |
297 | 1:2:3 | |
298 | 4:5:6 | |
299 | 7:8:9 | |
300 | ||
301 | Export a list of dictionaries | |
302 | ********************************** | |
303 | ||
304 | .. code-block:: python | |
305 | ||
306 | >>> records = [ | |
307 | ... {"year": 1903, "country": "Germany", "speed": "206.7km/h"}, | |
308 | ... {"year": 1964, "country": "Japan", "speed": "210km/h"}, | |
309 | ... {"year": 2008, "country": "China", "speed": "350km/h"} | |
310 | ... ] | |
311 | >>> p.save_as(records=records, dest_file_name='high_speed_rail.xls') | |
312 | ||
313 | ||
314 | Export a dictionary of single key value pair | |
315 | ******************************************************************************** | |
316 | ||
317 | .. code-block:: python | |
318 | ||
319 | >>> henley_on_thames_facts = { | |
320 | ... "area": "5.58 square meters", | |
321 | ... "population": "11,619", | |
322 | ... "civial parish": "Henley-on-Thames", | |
323 | ... "latitude": "51.536", | |
324 | ... "longitude": "-0.898" | |
325 | ... } | |
326 | >>> p.save_as(adict=henley_on_thames_facts, dest_file_name='henley.xlsx') | |
327 | ||
328 | ||
329 | Export a dictionary of single dimensonal array | |
330 | ******************************************************************************** | |
331 | ||
332 | .. code-block:: python | |
333 | ||
334 | >>> ccs_insights = { | |
335 | ... "year": ["2017", "2018", "2019", "2020", "2021"], | |
336 | ... "smart phones": [1.53, 1.64, 1.74, 1.82, 1.90], | |
337 | ... "feature phones": [0.46, 0.38, 0.30, 0.23, 0.17] | |
338 | ... } | |
339 | >>> p.save_as(adict=ccs_insights, dest_file_name='ccs.csv') | |
340 | ||
341 | ||
342 | Export a dictionary of two dimensional array as a book | |
343 | ******************************************************************************** | |
344 | ||
345 | Suppose you want to save the below dictionary to an excel file : | |
346 | ||
347 | .. code-block:: python | |
348 | ||
349 | >>> a_dictionary_of_two_dimensional_arrays = { | |
350 | ... 'Sheet 1': | |
351 | ... [ | |
352 | ... [1.0, 2.0, 3.0], | |
353 | ... [4.0, 5.0, 6.0], | |
354 | ... [7.0, 8.0, 9.0] | |
355 | ... ], | |
356 | ... 'Sheet 2': | |
357 | ... [ | |
358 | ... ['X', 'Y', 'Z'], | |
359 | ... [1.0, 2.0, 3.0], | |
360 | ... [4.0, 5.0, 6.0] | |
361 | ... ], | |
362 | ... 'Sheet 3': | |
363 | ... [ | |
364 | ... ['O', 'P', 'Q'], | |
365 | ... [3.0, 2.0, 1.0], | |
366 | ... [4.0, 3.0, 2.0] | |
367 | ... ] | |
368 | ... } | |
369 | ||
370 | Here is the code: | |
371 | ||
372 | .. code-block:: python | |
373 | ||
374 | >>> p.save_book_as( | |
375 | ... bookdict=a_dictionary_of_two_dimensional_arrays, | |
376 | ... dest_file_name="book.xls" | |
377 | ... ) | |
378 | ||
379 | If you want to preserve the order of sheets in your dictionary, you have to | |
380 | pass on an ordered dictionary to the function itself. For example: | |
381 | ||
382 | .. code-block:: python | |
383 | ||
384 | >>> data = OrderedDict() | |
385 | >>> data.update({"Sheet 2": a_dictionary_of_two_dimensional_arrays['Sheet 2']}) | |
386 | >>> data.update({"Sheet 1": a_dictionary_of_two_dimensional_arrays['Sheet 1']}) | |
387 | >>> data.update({"Sheet 3": a_dictionary_of_two_dimensional_arrays['Sheet 3']}) | |
388 | >>> p.save_book_as(bookdict=data, dest_file_name="book.xls") | |
389 | ||
390 | Let's verify its order: | |
391 | ||
392 | .. code-block:: python | |
393 | ||
394 | >>> book_dict = p.get_book_dict(file_name="book.xls") | |
395 | >>> for key, item in book_dict.items(): | |
396 | ... print(json.dumps({key: item})) | |
397 | {"Sheet 2": [["X", "Y", "Z"], [1, 2, 3], [4, 5, 6]]} | |
398 | {"Sheet 1": [[1, 2, 3], [4, 5, 6], [7, 8, 9]]} | |
399 | {"Sheet 3": [["O", "P", "Q"], [3, 2, 1], [4, 3, 2]]} | |
400 | ||
401 | Please notice that "Sheet 2" is the first item in the *book_dict*, meaning the order of sheets are preserved. | |
402 | ||
403 | ||
404 | Transcoding | |
405 | ------------------------------------------- | |
406 | ||
407 | .. note:: | |
408 | ||
409 | Please note that `pyexcel-cli` can perform file transcoding at command line. | |
410 | No need to open your editor, save the problem, then python run. | |
411 | ||
412 | ||
413 | The following code does a simple file format transcoding from xls to csv: | |
414 | ||
415 | .. code-block:: python | |
416 | ||
417 | >>> p.save_as(file_name="birth.xls", dest_file_name="birth.csv") | |
418 | ||
419 | Again it is really simple. Let's verify what we have gotten: | |
420 | ||
421 | .. code-block:: python | |
422 | ||
423 | >>> sheet = p.get_sheet(file_name="birth.csv") | |
424 | >>> sheet | |
425 | birth.csv: | |
426 | +-------+--------+----------+ | |
427 | | name | weight | birth | | |
428 | +-------+--------+----------+ | |
429 | | Adam | 3.4 | 03/02/15 | | |
430 | +-------+--------+----------+ | |
431 | | Smith | 4.2 | 12/11/14 | | |
432 | +-------+--------+----------+ | |
433 | ||
434 | .. NOTE:: | |
435 | ||
436 | 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. | |
437 | ||
438 | ||
439 | Let use previous example and save it as xlsx instead | |
440 | ||
441 | .. code-block:: python | |
442 | ||
443 | >>> p.save_as(file_name="birth.xls", | |
444 | ... dest_file_name="birth.xlsx") # change the file extension | |
445 | ||
446 | Again let's verify what we have gotten: | |
447 | ||
448 | .. code-block:: python | |
449 | ||
450 | >>> sheet = p.get_sheet(file_name="birth.xlsx") | |
451 | >>> sheet | |
452 | pyexcel_sheet1: | |
453 | +-------+--------+----------+ | |
454 | | name | weight | birth | | |
455 | +-------+--------+----------+ | |
456 | | Adam | 3.4 | 03/02/15 | | |
457 | +-------+--------+----------+ | |
458 | | Smith | 4.2 | 12/11/14 | | |
459 | +-------+--------+----------+ | |
460 | ||
461 | ||
462 | Excel book merge and split operation in one line | |
463 | -------------------------------------------------------------------------------- | |
464 | ||
465 | Merge all excel files in directory into a book where each file become a sheet | |
466 | ******************************************************************************** | |
467 | ||
468 | The following code will merge every excel files into one file, say "output.xls": | |
469 | ||
470 | .. code-block:: python | |
471 | ||
472 | from pyexcel.cookbook import merge_all_to_a_book | |
473 | import glob | |
474 | ||
475 | ||
476 | merge_all_to_a_book(glob.glob("your_csv_directory\*.csv"), "output.xls") | |
477 | ||
478 | 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: | |
479 | ||
480 | .. code-block:: python | |
481 | ||
482 | from pyexcel.cookbook import merge_all_to_a_book | |
483 | import glob | |
484 | ||
485 | ||
486 | merge_all_to_a_book(glob.glob("your_excel_file_directory\*.*"), "output.xls") | |
487 | ||
488 | Split a book into single sheet files | |
489 | **************************************** | |
490 | ||
491 | ||
492 | 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: | |
493 | ||
494 | .. code-block:: python | |
495 | ||
496 | >>> from pyexcel.cookbook import split_a_book | |
497 | >>> split_a_book("megabook.xls", "output.xls") | |
498 | >>> import glob | |
499 | >>> outputfiles = glob.glob("*_output.xls") | |
500 | >>> for file in sorted(outputfiles): | |
501 | ... print(file) | |
502 | ... | |
503 | Sheet 1_output.xls | |
504 | Sheet 2_output.xls | |
505 | Sheet 3_output.xls | |
506 | ||
507 | for the output file, you can specify any of the supported formats | |
508 | ||
509 | ||
510 | Extract just one sheet from a book | |
511 | ************************************* | |
512 | ||
513 | ||
514 | 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: | |
515 | ||
516 | .. code-block:: python | |
517 | ||
518 | >>> from pyexcel.cookbook import extract_a_sheet_from_a_book | |
519 | >>> extract_a_sheet_from_a_book("megabook.xls", "Sheet 1", "output.xls") | |
520 | >>> if os.path.exists("Sheet 1_output.xls"): | |
521 | ... print("Sheet 1_output.xls exists") | |
522 | ... | |
523 | Sheet 1_output.xls exists | |
524 | ||
525 | for the output file, you can specify any of the supported formats | |
526 | ||
527 | ||
528 | Hidden feature: partial read | |
529 | =============================================== | |
530 | ||
531 | Most pyexcel users do not know, but other library users were requesting `the similar features <https://github.com/jazzband/tablib/issues/467>`_ | |
532 | ||
533 | ||
534 | When you are dealing with huge amount of data, e.g. 64GB, obviously you would not | |
535 | like to fill up your memory with those data. What you may want to do is, record | |
536 | data from Nth line, take M records and stop. And you only want to use your memory | |
537 | for the M records, not for beginning part nor for the tail part. | |
538 | ||
539 | Hence partial read feature is developed to read partial data into memory for | |
540 | processing. | |
541 | ||
542 | You can paginate by row, by column and by both, hence you dictate what portion of the | |
543 | data to read back. But remember only row limit features help you save memory. Let's | |
544 | you use this feature to record data from Nth column, take M number of columns and skip | |
545 | the rest. You are not going to reduce your memory footprint. | |
546 | ||
547 | Why did not I see above benefit? | |
548 | -------------------------------------------------------------------------------- | |
549 | ||
550 | This feature depends heavily on the implementation details. | |
551 | ||
552 | `pyexcel-xls`_ (xlrd), `pyexcel-xlsx`_ (openpyxl), `pyexcel-ods`_ (odfpy) and | |
553 | `pyexcel-ods3`_ (pyexcel-ezodf) will read all data into memory. Because xls, | |
554 | xlsx and ods file are effective a zipped folder, all four will unzip the folder | |
555 | and read the content in xml format in **full**, so as to make sense of all details. | |
556 | ||
557 | Hence, during the partial data is been returned, the memory consumption won't | |
558 | differ from reading the whole data back. Only after the partial | |
559 | data is returned, the memory comsumption curve shall jump the cliff. So pagination | |
560 | code here only limits the data returned to your program. | |
561 | ||
562 | With that said, `pyexcel-xlsxr`_, `pyexcel-odsr`_ and `pyexcel-htmlr`_ DOES read | |
563 | partial data into memory. Those three are implemented in such a way that they | |
564 | consume the xml(html) when needed. When they have read designated portion of the | |
565 | data, they stop, even if they are half way through. | |
566 | ||
567 | In addition, pyexcel's csv readers can read partial data into memory too. | |
568 | ||
569 | ||
570 | Let's assume the following file is a huge csv file: | |
571 | ||
572 | .. code-block:: python | |
573 | ||
574 | >>> import datetime | |
575 | >>> import pyexcel as pe | |
576 | >>> data = [ | |
577 | ... [1, 21, 31], | |
578 | ... [2, 22, 32], | |
579 | ... [3, 23, 33], | |
580 | ... [4, 24, 34], | |
581 | ... [5, 25, 35], | |
582 | ... [6, 26, 36] | |
583 | ... ] | |
584 | >>> pe.save_as(array=data, dest_file_name="your_file.csv") | |
585 | ||
586 | ||
587 | And let's pretend to read partial data: | |
588 | ||
589 | ||
590 | .. code-block:: python | |
591 | ||
592 | >>> pe.get_sheet(file_name="your_file.csv", start_row=2, row_limit=3) | |
593 | your_file.csv: | |
594 | +---+----+----+ | |
595 | | 3 | 23 | 33 | | |
596 | +---+----+----+ | |
597 | | 4 | 24 | 34 | | |
598 | +---+----+----+ | |
599 | | 5 | 25 | 35 | | |
600 | +---+----+----+ | |
601 | ||
602 | And you could as well do the same for columns: | |
603 | ||
604 | .. code-block:: python | |
605 | ||
606 | >>> pe.get_sheet(file_name="your_file.csv", start_column=1, column_limit=2) | |
607 | your_file.csv: | |
608 | +----+----+ | |
609 | | 21 | 31 | | |
610 | +----+----+ | |
611 | | 22 | 32 | | |
612 | +----+----+ | |
613 | | 23 | 33 | | |
614 | +----+----+ | |
615 | | 24 | 34 | | |
616 | +----+----+ | |
617 | | 25 | 35 | | |
618 | +----+----+ | |
619 | | 26 | 36 | | |
620 | +----+----+ | |
621 | ||
622 | Obvious, you could do both at the same time: | |
623 | ||
624 | .. code-block:: python | |
625 | ||
626 | >>> pe.get_sheet(file_name="your_file.csv", | |
627 | ... start_row=2, row_limit=3, | |
628 | ... start_column=1, column_limit=2) | |
629 | your_file.csv: | |
630 | +----+----+ | |
631 | | 23 | 33 | | |
632 | +----+----+ | |
633 | | 24 | 34 | | |
634 | +----+----+ | |
635 | | 25 | 35 | | |
636 | +----+----+ | |
637 | ||
638 | ||
639 | The pagination support is available across all pyexcel plugins. | |
640 | ||
641 | .. note:: | |
642 | ||
643 | No column pagination support for query sets as data source. | |
644 | ||
645 | ||
646 | Formatting while transcoding a big data file | |
647 | -------------------------------------------------------------------------------- | |
648 | ||
649 | If you are transcoding a big data set, conventional formatting method would not | |
650 | help unless a on-demand free RAM is available. However, there is a way to minimize | |
651 | the memory footprint of pyexcel while the formatting is performed. | |
652 | ||
653 | Let's continue from previous example. Suppose we want to transcode "your_file.csv" | |
654 | to "your_file.xls" but increase each element by 1. | |
655 | ||
656 | What we can do is to define a row renderer function as the following: | |
657 | ||
658 | .. code-block:: python | |
659 | ||
660 | >>> def increment_by_one(row): | |
661 | ... for element in row: | |
662 | ... yield element + 1 | |
663 | ||
664 | Then pass it onto save_as function using row_renderer: | |
665 | ||
666 | .. code-block:: python | |
667 | ||
668 | >>> pe.isave_as(file_name="your_file.csv", | |
669 | ... row_renderer=increment_by_one, | |
670 | ... dest_file_name="your_file.xlsx") | |
671 | ||
672 | ||
673 | .. note:: | |
674 | ||
675 | If the data content is from a generator, isave_as has to be used. | |
676 | ||
677 | We can verify if it was done correctly: | |
678 | ||
679 | .. code-block:: python | |
680 | ||
681 | >>> pe.get_sheet(file_name="your_file.xlsx") | |
682 | your_file.csv: | |
683 | +---+----+----+ | |
684 | | 2 | 22 | 32 | | |
685 | +---+----+----+ | |
686 | | 3 | 23 | 33 | | |
687 | +---+----+----+ | |
688 | | 4 | 24 | 34 | | |
689 | +---+----+----+ | |
690 | | 5 | 25 | 35 | | |
691 | +---+----+----+ | |
692 | | 6 | 26 | 36 | | |
693 | +---+----+----+ | |
694 | | 7 | 27 | 37 | | |
695 | +---+----+----+ | |
696 | ||
697 | ||
698 | Stream APIs for big file : A set of two liners | |
699 | ================================================================================ | |
700 | ||
701 | When you are dealing with **BIG** excel files, you will want **pyexcel** to use | |
702 | constant memory. | |
703 | ||
704 | This section shows you how to get data from your **BIG** excel files and how to | |
705 | export data to excel files in **two lines** at most, without eating all | |
706 | your computer memory. | |
707 | ||
708 | ||
709 | Two liners for get data from big excel files | |
710 | -------------------------------------------------------------------------------- | |
711 | ||
712 | Get a list of dictionaries | |
713 | ******************************************************************************** | |
714 | ||
715 | ||
716 | ||
717 | Suppose you want to process the following coffee data again: | |
718 | ||
719 | Top 5 coffeine drinks: | |
720 | ||
721 | ===================================== =============== ============= | |
722 | Coffees Serving Size Caffeine (mg) | |
723 | Starbucks Coffee Blonde Roast venti(20 oz) 475 | |
724 | Dunkin' Donuts Coffee with Turbo Shot large(20 oz.) 398 | |
725 | Starbucks Coffee Pike Place Roast grande(16 oz.) 310 | |
726 | Panera Coffee Light Roast regular(16 oz.) 300 | |
727 | ===================================== =============== ============= | |
728 | ||
729 | ||
730 | Let's get a list of dictionary out from the xls file: | |
731 | ||
732 | .. code-block:: python | |
733 | ||
734 | >>> records = p.iget_records(file_name="your_file.xls") | |
735 | ||
736 | And let's check what do we have: | |
737 | ||
738 | .. code-block:: python | |
739 | ||
740 | >>> for r in records: | |
741 | ... print(f"{r['Serving Size']} of {r['Coffees']} has {r['Caffeine (mg)']} mg") | |
742 | venti(20 oz) of Starbucks Coffee Blonde Roast has 475 mg | |
743 | large(20 oz.) of Dunkin' Donuts Coffee with Turbo Shot has 398 mg | |
744 | grande(16 oz.) of Starbucks Coffee Pike Place Roast has 310 mg | |
745 | regular(16 oz.) of Panera Coffee Light Roast has 300 mg | |
746 | ||
747 | Please do not forgot the second line to close the opened file handle: | |
748 | ||
749 | .. code-block:: python | |
750 | ||
751 | >>> p.free_resources() | |
752 | ||
753 | Get two dimensional array | |
754 | ******************************************************************************** | |
755 | ||
756 | Instead, what if you have to use `pyexcel.get_array` to do the same: | |
757 | ||
758 | .. code-block:: python | |
759 | ||
760 | >>> for row in p.iget_array(file_name="your_file.xls", start_row=1): | |
761 | ... print(f"{row[1]} of {row[0]} has {row[2]} mg") | |
762 | venti(20 oz) of Starbucks Coffee Blonde Roast has 475 mg | |
763 | large(20 oz.) of Dunkin' Donuts Coffee with Turbo Shot has 398 mg | |
764 | grande(16 oz.) of Starbucks Coffee Pike Place Roast has 310 mg | |
765 | regular(16 oz.) of Panera Coffee Light Roast has 300 mg | |
766 | ||
767 | Again, do not forgot the second line: | |
768 | ||
769 | .. code-block:: python | |
770 | ||
771 | >>> p.free_resources() | |
772 | ||
773 | where `start_row` skips the header row. | |
774 | ||
775 | Data export in one liners | |
776 | --------------------------------------------- | |
777 | ||
778 | Export an array | |
779 | ********************** | |
780 | ||
781 | Suppose you have the following array: | |
782 | ||
783 | .. code-block:: python | |
784 | ||
785 | >>> data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] | |
786 | ||
787 | And here is the code to save it as an excel file : | |
788 | ||
789 | .. code-block:: python | |
790 | ||
791 | >>> p.isave_as(array=data, dest_file_name="example.xls") | |
792 | ||
793 | But the following line is not required because the data source | |
794 | are not file sources: | |
795 | ||
796 | .. code-block:: python | |
797 | ||
798 | >>> # p.free_resources() | |
799 | ||
800 | Let's verify it: | |
801 | ||
802 | .. code-block:: python | |
803 | ||
804 | >>> p.get_sheet(file_name="example.xls") | |
805 | pyexcel_sheet1: | |
806 | +---+---+---+ | |
807 | | 1 | 2 | 3 | | |
808 | +---+---+---+ | |
809 | | 4 | 5 | 6 | | |
810 | +---+---+---+ | |
811 | | 7 | 8 | 9 | | |
812 | +---+---+---+ | |
813 | ||
814 | ||
815 | And here is the code to save it as a csv file : | |
816 | ||
817 | .. code-block:: python | |
818 | ||
819 | >>> p.isave_as(array=data, | |
820 | ... dest_file_name="example.csv", | |
821 | ... dest_delimiter=':') | |
822 | ||
823 | Let's verify it: | |
824 | ||
825 | .. code-block:: python | |
826 | ||
827 | >>> with open("example.csv") as f: | |
828 | ... for line in f.readlines(): | |
829 | ... print(line.rstrip()) | |
830 | ... | |
831 | 1:2:3 | |
832 | 4:5:6 | |
833 | 7:8:9 | |
834 | ||
835 | Export a list of dictionaries | |
836 | ********************************** | |
837 | ||
838 | .. code-block:: python | |
839 | ||
840 | >>> records = [ | |
841 | ... {"year": 1903, "country": "Germany", "speed": "206.7km/h"}, | |
842 | ... {"year": 1964, "country": "Japan", "speed": "210km/h"}, | |
843 | ... {"year": 2008, "country": "China", "speed": "350km/h"} | |
844 | ... ] | |
845 | >>> p.isave_as(records=records, dest_file_name='high_speed_rail.xls') | |
846 | ||
847 | Export a dictionary of single key value pair | |
848 | ******************************************************************************** | |
849 | ||
850 | .. code-block:: python | |
851 | ||
852 | >>> henley_on_thames_facts = { | |
853 | ... "area": "5.58 square meters", | |
854 | ... "population": "11,619", | |
855 | ... "civial parish": "Henley-on-Thames", | |
856 | ... "latitude": "51.536", | |
857 | ... "longitude": "-0.898" | |
858 | ... } | |
859 | >>> p.isave_as(adict=henley_on_thames_facts, dest_file_name='henley.xlsx') | |
860 | ||
861 | Export a dictionary of single dimensonal array | |
862 | ******************************************************************************** | |
863 | ||
864 | .. code-block:: python | |
865 | ||
866 | >>> ccs_insights = { | |
867 | ... "year": ["2017", "2018", "2019", "2020", "2021"], | |
868 | ... "smart phones": [1.53, 1.64, 1.74, 1.82, 1.90], | |
869 | ... "feature phones": [0.46, 0.38, 0.30, 0.23, 0.17] | |
870 | ... } | |
871 | >>> p.isave_as(adict=ccs_insights, dest_file_name='ccs.csv') | |
872 | >>> p.free_resources() | |
873 | ||
874 | Export a dictionary of two dimensional array as a book | |
875 | ******************************************************************************** | |
876 | ||
877 | Suppose you want to save the below dictionary to an excel file : | |
878 | ||
879 | .. code-block:: python | |
880 | ||
881 | >>> a_dictionary_of_two_dimensional_arrays = { | |
882 | ... 'Sheet 1': | |
883 | ... [ | |
884 | ... [1.0, 2.0, 3.0], | |
885 | ... [4.0, 5.0, 6.0], | |
886 | ... [7.0, 8.0, 9.0] | |
887 | ... ], | |
888 | ... 'Sheet 2': | |
889 | ... [ | |
890 | ... ['X', 'Y', 'Z'], | |
891 | ... [1.0, 2.0, 3.0], | |
892 | ... [4.0, 5.0, 6.0] | |
893 | ... ], | |
894 | ... 'Sheet 3': | |
895 | ... [ | |
896 | ... ['O', 'P', 'Q'], | |
897 | ... [3.0, 2.0, 1.0], | |
898 | ... [4.0, 3.0, 2.0] | |
899 | ... ] | |
900 | ... } | |
901 | ||
902 | Here is the code: | |
903 | ||
904 | .. code-block:: python | |
905 | ||
906 | >>> p.isave_book_as( | |
907 | ... bookdict=a_dictionary_of_two_dimensional_arrays, | |
908 | ... dest_file_name="book.xls" | |
909 | ... ) | |
910 | ||
911 | If you want to preserve the order of sheets in your dictionary, you have to | |
912 | pass on an ordered dictionary to the function itself. For example: | |
913 | ||
914 | .. code-block:: python | |
915 | ||
916 | >>> from pyexcel._compact import OrderedDict | |
917 | >>> data = OrderedDict() | |
918 | >>> data.update({"Sheet 2": a_dictionary_of_two_dimensional_arrays['Sheet 2']}) | |
919 | >>> data.update({"Sheet 1": a_dictionary_of_two_dimensional_arrays['Sheet 1']}) | |
920 | >>> data.update({"Sheet 3": a_dictionary_of_two_dimensional_arrays['Sheet 3']}) | |
921 | >>> p.isave_book_as(bookdict=data, dest_file_name="book.xls") | |
922 | >>> p.free_resources() | |
923 | ||
924 | Let's verify its order: | |
925 | ||
926 | .. code-block:: python | |
927 | ||
928 | >>> import json | |
929 | >>> book_dict = p.get_book_dict(file_name="book.xls") | |
930 | >>> for key, item in book_dict.items(): | |
931 | ... print(json.dumps({key: item})) | |
932 | {"Sheet 2": [["X", "Y", "Z"], [1, 2, 3], [4, 5, 6]]} | |
933 | {"Sheet 1": [[1, 2, 3], [4, 5, 6], [7, 8, 9]]} | |
934 | {"Sheet 3": [["O", "P", "Q"], [3, 2, 1], [4, 3, 2]]} | |
935 | ||
936 | Please notice that "Sheet 2" is the first item in the *book_dict*, meaning the order of sheets are preserved. | |
937 | ||
938 | ||
939 | File format transcoding on one line | |
940 | ------------------------------------------- | |
941 | ||
942 | .. note:: | |
943 | ||
944 | Please note that the following file transcoding could be with zero line. Please | |
945 | install pyexcel-cli and you will do the transcode in one command. No need to | |
946 | open your editor, save the problem, then python run. | |
947 | ||
948 | ||
949 | The following code does a simple file format transcoding from xls to csv: | |
126 | 950 | |
127 | 951 | .. code-block:: python |
128 | 952 | |
129 | 953 | >>> 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 | |
954 | >>> p.save_as(file_name="birth.xls", dest_file_name="birth.csv") | |
955 | ||
956 | Again it is really simple. Let's verify what we have gotten: | |
957 | ||
958 | .. code-block:: python | |
959 | ||
960 | >>> sheet = p.get_sheet(file_name="birth.csv") | |
961 | >>> sheet | |
962 | birth.csv: | |
963 | +-------+--------+----------+ | |
964 | | name | weight | birth | | |
965 | +-------+--------+----------+ | |
966 | | Adam | 3.4 | 03/02/15 | | |
967 | +-------+--------+----------+ | |
968 | | Smith | 4.2 | 12/11/14 | | |
969 | +-------+--------+----------+ | |
970 | ||
971 | .. note:: | |
972 | ||
973 | 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. | |
974 | ||
975 | ||
976 | Let use previous example and save it as xlsx instead | |
977 | ||
978 | .. code-block:: python | |
979 | ||
980 | >>> import pyexcel | |
981 | >>> p.isave_as(file_name="birth.xls", | |
982 | ... dest_file_name="birth.xlsx") # change the file extension | |
983 | ||
984 | Again let's verify what we have gotten: | |
985 | ||
986 | .. code-block:: python | |
987 | ||
988 | >>> sheet = p.get_sheet(file_name="birth.xlsx") | |
989 | >>> sheet | |
990 | pyexcel_sheet1: | |
991 | +-------+--------+----------+ | |
992 | | name | weight | birth | | |
993 | +-------+--------+----------+ | |
994 | | Adam | 3.4 | 03/02/15 | | |
995 | +-------+--------+----------+ | |
996 | | Smith | 4.2 | 12/11/14 | | |
997 | +-------+--------+----------+ | |
998 | ||
199 | 999 | |
200 | 1000 | Available Plugins |
201 | 1001 | ================= |
205 | 1005 | |
206 | 1006 | .. table:: A list of file formats supported by external plugins |
207 | 1007 | |
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 | |
1008 | ======================== ======================= ================= | |
1009 | Package name Supported file formats Dependencies | |
1010 | ======================== ======================= ================= | |
1011 | `pyexcel-io`_ csv, csvz [#f1]_, tsv, | |
1012 | tsvz [#f2]_ | |
1013 | `pyexcel-xls`_ xls, xlsx(read only), `xlrd`_, | |
215 | 1014 | 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 | ======================== ======================= ================= ================== | |
1015 | `pyexcel-xlsx`_ xlsx `openpyxl`_ | |
1016 | `pyexcel-ods3`_ ods `pyexcel-ezodf`_, | |
1017 | lxml | |
1018 | `pyexcel-ods`_ ods `odfpy`_ | |
1019 | ======================== ======================= ================= | |
221 | 1020 | |
222 | 1021 | .. table:: Dedicated file reader and writers |
223 | 1022 | |
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 | ======================== ======================= ================= ================== | |
1023 | ======================== ======================= ================= | |
1024 | Package name Supported file formats Dependencies | |
1025 | ======================== ======================= ================= | |
1026 | `pyexcel-xlsxw`_ xlsx(write only) `XlsxWriter`_ | |
1027 | `pyexcel-libxlsxw`_ xlsx(write only) `libxlsxwriter`_ | |
1028 | `pyexcel-xlsxr`_ xlsx(read only) lxml | |
1029 | `pyexcel-xlsbr`_ xlsb(read only) pyxlsb | |
1030 | `pyexcel-odsr`_ read only for ods, fods lxml | |
1031 | `pyexcel-odsw`_ write only for ods loxun | |
1032 | `pyexcel-htmlr`_ html(read only) lxml,html5lib | |
1033 | `pyexcel-pdfr`_ pdf(read only) camelot | |
1034 | ======================== ======================= ================= | |
1035 | ||
1036 | ||
1037 | Plugin shopping guide | |
1038 | ------------------------ | |
1039 | ||
1040 | Since 2020, all pyexcel-io plugins have dropped the support for python version | |
1041 | lower than 3.6. If you want to use any python verions, please use pyexcel-io | |
1042 | and its plugins version lower than 0.6.0. | |
1043 | ||
1044 | ||
1045 | Except csv files, xls, xlsx and ods files are a zip of a folder containing a lot of | |
1046 | xml files | |
1047 | ||
1048 | The dedicated readers for excel files can stream read | |
1049 | ||
1050 | ||
1051 | In order to manage the list of plugins installed, you need to use pip to add or remove | |
1052 | a plugin. When you use virtualenv, you can have different plugins per virtual | |
1053 | environment. In the situation where you have multiple plugins that does the same thing | |
1054 | in your environment, you need to tell pyexcel which plugin to use per function call. | |
1055 | For example, pyexcel-ods and pyexcel-odsr, and you want to get_array to use pyexcel-odsr. | |
1056 | You need to append get_array(..., library='pyexcel-odsr'). | |
1057 | ||
235 | 1058 | |
236 | 1059 | |
237 | 1060 | .. _pyexcel-io: https://github.com/pyexcel/pyexcel-io |
244 | 1067 | .. _pyexcel-pdfr: https://github.com/pyexcel/pyexcel-pdfr |
245 | 1068 | |
246 | 1069 | .. _pyexcel-xlsxw: https://github.com/pyexcel/pyexcel-xlsxw |
1070 | .. _pyexcel-libxlsxw: https://github.com/pyexcel/pyexcel-libxlsxw | |
247 | 1071 | .. _pyexcel-xlsxr: https://github.com/pyexcel/pyexcel-xlsxr |
248 | 1072 | .. _pyexcel-xlsbr: https://github.com/pyexcel/pyexcel-xlsbr |
249 | 1073 | .. _pyexcel-htmlr: https://github.com/pyexcel/pyexcel-htmlr |
254 | 1078 | .. _XlsxWriter: https://github.com/jmcnamara/XlsxWriter |
255 | 1079 | .. _pyexcel-ezodf: https://github.com/pyexcel/pyexcel-ezodf |
256 | 1080 | .. _odfpy: https://github.com/eea/odfpy |
1081 | .. _libxlsxwriter: http://libxlsxwriter.github.io/getting_started.html | |
257 | 1082 | |
258 | 1083 | .. table:: Other data renderers |
259 | 1084 | |
287 | 1112 | .. _pyexcel-gantt: https://github.com/pyexcel/pyexcel-gantt |
288 | 1113 | .. _frappe-gantt: https://github.com/frappe/gantt |
289 | 1114 | |
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 | ||
297 | 1115 | .. rubric:: Footnotes |
298 | 1116 | |
299 | 1117 | .. [#f1] zipped csv file |
307 | 1125 | individual developers. This library unites only the data access code. |
308 | 1126 | |
309 | 1127 | |
310 | .. testcode:: | |
311 | :hide: | |
312 | ||
313 | >>> import os | |
314 | >>> os.unlink("your_file.xls") | |
315 | ||
316 | 1128 | |
317 | 1129 | |
318 | 1130 | License |
0 | 0 | name: pyexcel |
1 | 1 | organisation: pyexcel |
2 | 2 | releases: |
3 | - changes: | |
4 | - action: Updated | |
5 | details: | |
6 | - "`#233`: dynamically resize the table matrix on set_value. sheet['AA1'] = 'test' will work in this release." | |
7 | version: 0.6.6 | |
8 | date: 14.11.2020 | |
9 | - changes: | |
10 | - action: Updated | |
11 | details: | |
12 | - "update queryset source to work with pyexcel-io 0.6.0" | |
13 | version: 0.6.5 | |
14 | date: 8.10.2020 | |
15 | - changes: | |
16 | - action: Updated | |
17 | details: | |
18 | - "`#219`: book created from dict no longer discards order." | |
19 | version: 0.6.4 | |
20 | date: 18.08.2020 | |
21 | - changes: | |
22 | - action: fixed | |
23 | details: | |
24 | - "`#214`: remove leading and trailing whitespace for column names" | |
25 | - action: removed | |
26 | details: | |
27 | - python 2 compatibility have been permanently removed. | |
28 | version: 0.6.3 | |
29 | date: 01.08.2020 | |
30 | - changes: | |
31 | - action: fixed | |
32 | details: | |
33 | - "`#109`: Control the column order when write the data output" | |
34 | version: 0.6.2 | |
35 | date: 8.06.2020 | |
36 | - changes: | |
37 | - action: fixed | |
38 | details: | |
39 | - "`#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" | |
40 | version: 0.6.1 | |
41 | date: 02.05.2020 | |
42 | - changes: | |
43 | - action: updated | |
44 | details: | |
45 | - "`#199`: += in place; = + shall return new instance" | |
46 | - "`#195`: documentation update. however small is welcome" | |
47 | - action: removed | |
48 | details: | |
49 | - "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+." | |
50 | ||
51 | version: 0.6.0 | |
52 | date: 21.04.2020 | |
53 | - changes: | |
54 | - action: updated | |
55 | details: | |
56 | - "`#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." | |
57 | version: 0.5.15 | |
58 | date: 07.07.2019 | |
3 | 59 | - changes: |
4 | 60 | - action: updated |
5 | 61 | details: |
60 | 116 | details: |
61 | 117 | - '`#126`, dest_sheet_name in save_as will set the sheet name in the output' |
62 | 118 | - '`#115`, Fix set membership test to run faster in python2' |
63 | date: unreleased | |
119 | date: 26.03.2018 | |
64 | 120 | version: 0.5.8 |
65 | 121 | - changes: |
66 | 122 | - action: added |
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;"/><!-- | |
1 | 1 | @startuml |
2 | 2 | |
3 | 3 | package "pyexcel" { |
30 | 30 | [pyexcel-xls] |
31 | 31 | [pyexcel-xlsx] |
32 | 32 | [pyexcel-xlsxw] |
33 | [pyexcel-libxlsxw] | |
34 | [pyexcel-xlsxr] | |
33 | 35 | } |
34 | 36 | |
35 | 37 | [tabulate] <<dependency>> |
41 | 43 | [xlsxwriter] <<dependency>> |
42 | 44 | [ezodf] <<dependency>> |
43 | 45 | [odfpy] <<dependency>> |
46 | [lxml] <<dependency>> | |
47 | [libxlsxwpy] <<dependency>> | |
48 | [libxlsx] <<dependency>> | |
44 | 49 | |
45 | 50 | [pyexcel public api] -right-> [pyexcel internal api] |
46 | 51 | [pyexcel internal api] -right-> [pyexcel plugin interfaces] |
58 | 63 | [pyexcel-io plugin interfaces] ..> [csv, csvz, tsv, tsvz] |
59 | 64 | [pyexcel-io plugin interfaces] ..> [django, sqlalchemy] |
60 | 65 | [pyexcel-io plugin interfaces] .up.> [pyexcel-xls] |
66 | [pyexcel-io plugin interfaces] .up.> [pyexcel-xlsxw] | |
67 | [pyexcel-io plugin interfaces] .up.> [pyexcel-libxlsxw] | |
61 | 68 | [pyexcel-io plugin interfaces] .up.> [pyexcel-xlsx] |
69 | [pyexcel-io plugin interfaces] .up.> [pyexcel-xlsxr] | |
62 | 70 | [pyexcel-io plugin interfaces] .left.> [pyexcel-ods] |
63 | 71 | [pyexcel-io plugin interfaces] .right.> [pyexcel-ods3] |
64 | [pyexcel-io plugin interfaces] .right.> [pyexcel-xlsxw] | |
65 | 72 | [pyexcel-io plugin interfaces] .right.> [pyexcel-odsr] |
73 | ||
66 | 74 | |
67 | 75 | [pyexcel-text] -up- [tabulate] |
68 | 76 | [pyexcel-pygal] -up- [pygal] |
70 | 78 | [pyexcel-xls] -right- [xlrd] |
71 | 79 | [pyexcel-xls] -right- [xlwt] |
72 | 80 | [pyexcel-xlsx] -right- [openpyxl] |
73 | [pyexcel-xlsxw] -down- [xlsxwriter] | |
81 | [pyexcel-xlsxw] -up- [xlsxwriter] | |
74 | 82 | [pyexcel-ods3] - - - [ezodf] |
75 | 83 | [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] | |
76 | 89 | |
77 | 90 | skinparam component{ |
78 | 91 | |
88 | 101 | JVM: Java HotSpot(TM) 64-Bit Server VM |
89 | 102 | Java Version: 1.8.0_131-b11 |
90 | 103 | Operating System: Mac OS X |
91 | OS Version: 10.11.6 | |
104 | OS Version: 10.14.6 | |
92 | 105 | Default Encoding: UTF-8 |
93 | 106 | Language: en |
94 | Country: US | |
107 | Country: GB | |
95 | 108 | --></g></svg>⏎ |
177 | 177 | Sheet.number_of_columns |
178 | 178 | Sheet.row_range |
179 | 179 | 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 | |
195 | 180 | |
196 | 181 | |
197 | 182 | Cell access |
300 | 285 | .. autosummary:: |
301 | 286 | :toctree: generated/ |
302 | 287 | |
288 | Sheet.project | |
303 | 289 | Sheet.transpose |
304 | 290 | Sheet.map |
305 | 291 | Sheet.region |
67 | 67 | .. testcode:: |
68 | 68 | :hide: |
69 | 69 | |
70 | >>> from mock import patch, MagicMock | |
70 | >>> from unittest.mock import patch, MagicMock | |
71 | 71 | >>> import os |
72 | 72 | >>> patcher = patch('pyexcel._compact.request.urlopen') |
73 | 73 | >>> fake_url_open = patcher.start() |
253 | 253 | .. testcode:: |
254 | 254 | :hide: |
255 | 255 | |
256 | >>> from mock import patch, MagicMock | |
256 | >>> from unittest.mock import patch, MagicMock | |
257 | 257 | >>> import os |
258 | 258 | >>> patcher = patch('pyexcel._compact.request.urlopen') |
259 | 259 | >>> fake_url_open = patcher.start() |
1 | 1 | Read partial data |
2 | 2 | ================================================================================ |
3 | 3 | |
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") | |
36 | 4 | |
37 | 5 | |
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? | |
39 | 20 | -------------------------------------------------------------------------------- |
40 | 21 | |
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. | |
45 | 23 | |
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. | |
50 | 28 | |
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. | |
51 | 33 | |
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. | |
54 | 38 | |
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 | ||
61 | 41 | |
62 | 42 | Let's assume the following file is a huge csv file: |
63 | 43 | |
75 | 55 | ... ] |
76 | 56 | >>> pe.save_as(array=data, dest_file_name="your_file.csv") |
77 | 57 | |
58 | ||
78 | 59 | And let's pretend to read partial data: |
60 | ||
79 | 61 | |
80 | 62 | .. code-block:: python |
81 | 63 | |
145 | 127 | |
146 | 128 | What we can do is to define a row renderer function as the following: |
147 | 129 | |
130 | .. code-block:: python | |
131 | ||
148 | 132 | >>> def increment_by_one(row): |
149 | 133 | ... for element in row: |
150 | 134 | ... yield element + 1 |
151 | 135 | |
152 | 136 | Then pass it onto save_as function using row_renderer: |
137 | ||
138 | .. code-block:: python | |
153 | 139 | |
154 | 140 | >>> pe.isave_as(file_name="your_file.csv", |
155 | 141 | ... row_renderer=increment_by_one, |
161 | 147 | If the data content is from a generator, isave_as has to be used. |
162 | 148 | |
163 | 149 | We can verify if it was done correctly: |
150 | ||
151 | .. code-block:: python | |
164 | 152 | |
165 | 153 | >>> pe.get_sheet(file_name="your_file.xlsx") |
166 | 154 | your_file.csv: |
177 | 165 | +---+----+----+ |
178 | 166 | | 7 | 27 | 37 | |
179 | 167 | +---+----+----+ |
180 | ||
181 | .. testcode:: | |
182 | :hide: | |
183 | ||
184 | >>> import os | |
185 | >>> os.unlink("your_file.csv") | |
186 | >>> os.unlink("your_file.xlsx") |
63 | 63 | 1 |
64 | 64 | |
65 | 65 | .. 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** | |
71 | 71 | |
72 | 72 | Suppose you have previous data as a dictionary and you want to |
73 | 73 | save it as multiple sheet excel file:: |
7 | 7 | # |
8 | 8 | # This file only contains a selection of the most common options. For a full |
9 | 9 | # list see the documentation: |
10 | # http://www.sphinx-doc.org/en/master/config | |
10 | # https://www.sphinx-doc.org/en/master/usage/configuration.html | |
11 | 11 | |
12 | 12 | # -- Path setup -------------------------------------------------------------- |
13 | 13 | |
22 | 22 | # -- Project information ----------------------------------------------------- |
23 | 23 | |
24 | 24 | project = 'pyexcel' |
25 | copyright = '2014-2019 Onni Software Ltd.' | |
26 | author = 'C.W.' | |
25 | copyright = '2014-2020 Onni Software Ltd.' | |
26 | author = 'chfw' | |
27 | 27 | # The short X.Y version |
28 | version = '0.5.14' | |
28 | version = '0.6.6' | |
29 | 29 | # The full version, including alpha/beta/rc tags |
30 | release = '0.5.14' | |
30 | release = '0.6.6' | |
31 | 31 | |
32 | 32 | # -- General configuration --------------------------------------------------- |
33 | 33 | |
34 | 34 | # Add any Sphinx extension module names here, as strings. They can be |
35 | 35 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom |
36 | 36 | # 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',] | |
38 | 38 | |
39 | 39 | # Add any paths that contain templates here, relative to this directory. |
40 | 40 | templates_path = ['_templates'] |
68 | 68 | # -- Options for intersphinx extension --------------------------------------- |
69 | 69 | |
70 | 70 | # 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} | |
72 | 72 | # TODO: html_theme not configurable upstream |
73 | 73 | html_theme = 'default' |
74 | ||
75 | ||
76 | 74 | def setup(app): |
77 | 75 | app.add_stylesheet('theme_overrides.css') |
78 | 76 | |
89 | 87 | intersphinx_mapping.update({ |
90 | 88 | 'xlrd': ('http://xlrd.readthedocs.io/en/latest/', None) |
91 | 89 | }) |
92 | ||
93 | 90 | master_doc = "index" |
76 | 76 | :hide: |
77 | 77 | |
78 | 78 | >>> session.close() |
79 | >>> os.unlink('birth.xls') | |
79 | 80 | >>> os.unlink('birth.db') |
0 | pyexcel.Sheet.columns | |
1 | ===================== | |
2 | ||
3 | .. currentmodule:: pyexcel | |
4 | ||
5 | .. automethod:: Sheet.columns⏎ |
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 | pyexcel.Sheet.rcolumns | |
1 | ====================== | |
2 | ||
3 | .. currentmodule:: pyexcel | |
4 | ||
5 | .. automethod:: Sheet.rcolumns⏎ |
0 | pyexcel.Sheet.reverse | |
1 | ===================== | |
2 | ||
3 | .. currentmodule:: pyexcel | |
4 | ||
5 | .. automethod:: Sheet.reverse⏎ |
0 | pyexcel.Sheet.rows | |
1 | ================== | |
2 | ||
3 | .. currentmodule:: pyexcel | |
4 | ||
5 | .. automethod:: Sheet.rows⏎ |
0 | pyexcel.Sheet.rrows | |
1 | =================== | |
2 | ||
3 | .. currentmodule:: pyexcel | |
4 | ||
5 | .. automethod:: Sheet.rrows⏎ |
14 | 14 | |
15 | 15 | ~Sheet.__init__ |
16 | 16 | ~Sheet.cell_value |
17 | ~Sheet.clone | |
17 | 18 | ~Sheet.column_at |
18 | 19 | ~Sheet.column_range |
19 | 20 | ~Sheet.columns |
73 | 74 | ~Sheet.number_of_rows |
74 | 75 | ~Sheet.paste |
75 | 76 | ~Sheet.plot |
77 | ~Sheet.project | |
76 | 78 | ~Sheet.rcolumns |
77 | 79 | ~Sheet.region |
78 | 80 | ~Sheet.register_input |
0 | pyexcel.Sheet.rvertical | |
1 | ======================= | |
2 | ||
3 | .. currentmodule:: pyexcel | |
4 | ||
5 | .. automethod:: Sheet.rvertical⏎ |
0 | pyexcel.Sheet.vertical | |
1 | ====================== | |
2 | ||
3 | .. currentmodule:: pyexcel | |
4 | ||
5 | .. automethod:: Sheet.vertical⏎ |
39 | 39 | |
40 | 40 | > test.bat |
41 | 41 | |
42 | How to update test environment and update documentation | |
43 | --------------------------------------------------------- | |
44 | 42 | |
45 | Additional steps are required: | |
43 | Before you commit | |
44 | ------------------------------ | |
46 | 45 | |
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:: | |
51 | 47 | |
52 | What is pyexcel-commons | |
53 | --------------------------------- | |
48 | $ make format | |
54 | 49 | |
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 travis-ci may fail your unit test. |
14 | 14 | |
15 | 15 | ~Matrix.__init__ |
16 | 16 | ~Matrix.cell_value |
17 | ~Matrix.clone | |
17 | 18 | ~Matrix.column_at |
18 | 19 | ~Matrix.column_range |
19 | 20 | ~Matrix.columns |
0 | 0 | `pyexcel` - Let you focus on data, instead of file formats |
1 | 1 | ================================================================================ |
2 | 2 | |
3 | :Author: C.W. | |
3 | :Author: chfw | |
4 | 4 | :Source code: http://github.com/pyexcel/pyexcel.git |
5 | 5 | :Issues: http://github.com/pyexcel/pyexcel/issues |
6 | 6 | :License: New BSD License |
20 | 20 | |
21 | 21 | The idea originated from the common usability problem: when an excel file |
22 | 22 | 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. | |
31 | 32 | |
32 | 33 | |
33 | 34 | Support the project |
34 | 35 | ================================================================================ |
35 | 36 | |
36 | 37 | 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>`_ | |
38 | 39 | or `bounty source <https://salt.bountysource.com/teams/chfw-pyexcel>`_ to maintain |
39 | 40 | the project and develop it further. |
40 | 41 | |
67 | 68 | $ cd pyexcel |
68 | 69 | $ python setup.py install |
69 | 70 | |
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 | ||
70 | 192 | For individual excel file formats, please install them as you wish: |
71 | 193 | |
72 | 194 | .. _file-format-list: |
74 | 196 | |
75 | 197 | .. table:: A list of file formats supported by external plugins |
76 | 198 | |
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`_, | |
84 | 205 | 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 | ======================== ======================= ================= | |
90 | 211 | |
91 | 212 | .. table:: Dedicated file reader and writers |
92 | 213 | |
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 version | |
232 | lower than 3.6. If you want to use any python verions, please use pyexcel-io | |
233 | and its plugins version 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 | ||
104 | 249 | |
105 | 250 | |
106 | 251 | .. _pyexcel-io: https://github.com/pyexcel/pyexcel-io |
113 | 258 | .. _pyexcel-pdfr: https://github.com/pyexcel/pyexcel-pdfr |
114 | 259 | |
115 | 260 | .. _pyexcel-xlsxw: https://github.com/pyexcel/pyexcel-xlsxw |
261 | .. _pyexcel-libxlsxw: https://github.com/pyexcel/pyexcel-libxlsxw | |
116 | 262 | .. _pyexcel-xlsxr: https://github.com/pyexcel/pyexcel-xlsxr |
117 | 263 | .. _pyexcel-xlsbr: https://github.com/pyexcel/pyexcel-xlsbr |
118 | 264 | .. _pyexcel-htmlr: https://github.com/pyexcel/pyexcel-htmlr |
123 | 269 | .. _XlsxWriter: https://github.com/jmcnamara/XlsxWriter |
124 | 270 | .. _pyexcel-ezodf: https://github.com/pyexcel/pyexcel-ezodf |
125 | 271 | .. _odfpy: https://github.com/eea/odfpy |
272 | .. _libxlsxwriter: http://libxlsxwriter.github.io/getting_started.html | |
126 | 273 | |
127 | 274 | .. table:: Other data renderers |
128 | 275 | |
156 | 303 | .. _pyexcel-gantt: https://github.com/pyexcel/pyexcel-gantt |
157 | 304 | .. _frappe-gantt: https://github.com/frappe/gantt |
158 | 305 | |
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 | ||
166 | 306 | .. rubric:: Footnotes |
167 | 307 | |
168 | 308 | .. [#f1] zipped csv file |
175 | 315 | ======== ========== ============= ==================== ============= ============= |
176 | 316 | pyexcel pyexcel-io pyexcel-text pyexcel-handsontable pyexcel-pygal pyexcel-gantt |
177 | 317 | ======== ========== ============= ==================== ============= ============= |
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 | |
179 | 321 | 0.5.10+ 0.5.11+ 0.2.6+ 0.0.1+ 0.0.1 0.0.1 |
180 | 322 | 0.5.9.1+ 0.5.9.1+ 0.2.6+ 0.0.1 0.0.1 0.0.1 |
181 | 323 | 0.5.4+ 0.5.1+ 0.2.6+ 0.0.1 0.0.1 0.0.1 |
282 | 424 | architecture |
283 | 425 | |
284 | 426 | New tutorial |
285 | ---------- | |
427 | -------------- | |
286 | 428 | .. toctree:: |
287 | 429 | |
288 | 430 | quickstart |
295 | 437 | database |
296 | 438 | |
297 | 439 | Old tutorial |
298 | ---------- | |
440 | -------------- | |
299 | 441 | .. toctree:: |
300 | 442 | |
301 | 443 | tutorial_file |
0 | What's breaking in 0.6.0 | |
1 | ================================================================================ | |
2 | ||
3 | In the following statements:: | |
4 | ||
5 | sheet_a = sheet.row + rows | |
6 | sheet_b = sheet.column + columns | |
7 | book = sheet_a + sheet_b | |
8 | ||
9 | `sheet_a` and `sheet_b` will no longer have access to the data of `sheet`. `book` | |
10 | will no longer have access to the data of `sheet_a` and `sheet_b`. | |
11 | ||
12 | Under Hyrum's Law, this enhancement in 0.6.0 will cause breakage otherwise. | |
13 | ||
0 | 14 | What's breaking in 0.5.9 |
1 | 15 | ================================================================================ |
2 | 16 |
0 | ||
0 | 1 | One liners |
1 | 2 | ================================================================================ |
2 | 3 | |
3 | 4 | This section shows you how to get data from your excel files and how to |
4 | 5 | export data to excel files in **one line** |
5 | 6 | |
6 | One liner to get data from the excel files | |
7 | Read from the excel files | |
7 | 8 | -------------------------------------------------------------------------------- |
8 | 9 | |
9 | 10 | Get a list of dictionaries |
24 | 25 | >>> sheet = p.get_sheet(file_content=content, file_type='csv') |
25 | 26 | >>> sheet.save_as("your_file.xls") |
26 | 27 | |
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): | |
28 | ||
29 | Suppose you want to process the following coffee data (data source `coffee chart <https://cspinet.org/eating-healthy/ingredients-of-concern/caffeine-chart>`_ on the center for science in the public interest): | |
30 | ||
28 | 31 | |
29 | 32 | .. pyexcel-table:: |
30 | 33 | |
35 | 38 | Starbucks Coffee Pike Place Roast,grande(16 oz.),310 |
36 | 39 | Panera Coffee Light Roast,regular(16 oz.),300 |
37 | 40 | |
38 | Let's get a list of dictionary out from the xls file:: | |
39 | ||
41 | ||
42 | Let's get a list of dictionary out from the xls file: | |
43 | ||
44 | .. code-block:: python | |
45 | ||
40 | 46 | >>> records = p.get_records(file_name="your_file.xls") |
41 | 47 | |
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)'])) | |
48 | And let's check what do we have: | |
49 | ||
50 | .. code-block:: python | |
51 | ||
52 | >>> for r in records: | |
53 | ... print(f"{r['Serving Size']} of {r['Coffees']} has {r['Caffeine (mg)']} mg") | |
49 | 54 | venti(20 oz) of Starbucks Coffee Blonde Roast has 475 mg |
50 | 55 | large(20 oz.) of Dunkin' Donuts Coffee with Turbo Shot has 398 mg |
51 | 56 | grande(16 oz.) of Starbucks Coffee Pike Place Roast has 310 mg |
55 | 60 | Get two dimensional array |
56 | 61 | ******************************************************************************** |
57 | 62 | |
58 | Instead, what if you have to use :meth:`pyexcel.get_array` to do the same: | |
63 | Instead, what if you have to use `pyexcel.get_array` to do the same: | |
64 | ||
65 | .. code-block:: python | |
59 | 66 | |
60 | 67 | >>> 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])) | |
68 | ... print(f"{row[1]} of {row[0]} has {row[2]} mg") | |
65 | 69 | venti(20 oz) of Starbucks Coffee Blonde Roast has 475 mg |
66 | 70 | large(20 oz.) of Dunkin' Donuts Coffee with Turbo Shot has 398 mg |
67 | 71 | grande(16 oz.) of Starbucks Coffee Pike Place Roast has 310 mg |
68 | 72 | regular(16 oz.) of Panera Coffee Light Roast has 300 mg |
69 | 73 | |
74 | ||
70 | 75 | where `start_row` skips the header row. |
71 | 76 | |
72 | 77 | |
78 | 83 | Now let's get a dictionary out from the spreadsheet: |
79 | 84 | |
80 | 85 | .. code-block:: python |
81 | ||
86 | ||
82 | 87 | >>> my_dict = p.get_dict(file_name="your_file.xls", name_columns_by_row=0) |
83 | 88 | |
84 | And check what do we have:: | |
89 | And check what do we have: | |
90 | ||
91 | .. code-block:: python | |
85 | 92 | |
86 | 93 | >>> from pyexcel._compact import OrderedDict |
87 | 94 | >>> isinstance(my_dict, OrderedDict) |
126 | 133 | >>> data.update({"Sheet 3": a_dictionary_of_two_dimensional_arrays['Sheet 3']}) |
127 | 134 | >>> p.save_book_as(bookdict=data, dest_file_name="book.xls") |
128 | 135 | |
136 | ||
129 | 137 | Suppose you have a multiple sheet book as the following: |
138 | ||
130 | 139 | |
131 | 140 | .. pyexcel-table:: |
132 | 141 | |
145 | 154 | 3,2,1 |
146 | 155 | 4,3,2 |
147 | 156 | |
148 | Here is the code to obtain those sheets as a single dictionary:: | |
157 | ||
158 | Here is the code to obtain those sheets as a single dictionary: | |
159 | ||
160 | .. code-block:: python | |
149 | 161 | |
150 | 162 | >>> book_dict = p.get_book_dict(file_name="book.xls") |
151 | 163 | |
152 | And check:: | |
164 | And check: | |
165 | ||
166 | .. code-block:: python | |
167 | ||
153 | 168 | >>> isinstance(book_dict, OrderedDict) |
154 | 169 | True |
155 | 170 | >>> import json |
159 | 174 | {"Sheet 2": [["X", "Y", "Z"], [1, 2, 3], [4, 5, 6]]} |
160 | 175 | {"Sheet 3": [["O", "P", "Q"], [3, 2, 1], [4, 3, 2]]} |
161 | 176 | |
177 | ||
162 | 178 | .. testcode:: |
163 | 179 | :hide: |
164 | 180 | |
166 | 182 | >>> os.unlink("book.xls") |
167 | 183 | |
168 | 184 | |
169 | Data export in one line | |
185 | Write data | |
170 | 186 | --------------------------------------------- |
171 | 187 | |
172 | 188 | Export an array |
173 | 189 | ********************** |
174 | 190 | |
175 | Suppose you have the following array:: | |
191 | Suppose you have the following array: | |
192 | ||
193 | .. code-block:: python | |
176 | 194 | |
177 | 195 | >>> data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] |
178 | 196 | |
179 | And here is the code to save it as an excel file :: | |
197 | And here is the code to save it as an excel file : | |
198 | ||
199 | .. code-block:: python | |
180 | 200 | |
181 | 201 | >>> p.save_as(array=data, dest_file_name="example.xls") |
182 | 202 | |
183 | Let's verify it:: | |
203 | Let's verify it: | |
204 | ||
205 | .. code-block:: python | |
184 | 206 | |
185 | 207 | >>> p.get_sheet(file_name="example.xls") |
186 | 208 | pyexcel_sheet1: |
198 | 220 | >>> import os |
199 | 221 | >>> os.unlink("example.xls") |
200 | 222 | |
201 | ||
202 | And here is the code to save it as a csv file :: | |
223 | And here is the code to save it as a csv file : | |
224 | ||
225 | .. code-block:: python | |
203 | 226 | |
204 | 227 | >>> p.save_as(array=data, |
205 | 228 | ... dest_file_name="example.csv", |
206 | 229 | ... dest_delimiter=':') |
207 | 230 | |
208 | Let's verify it:: | |
231 | Let's verify it: | |
232 | ||
233 | .. code-block:: python | |
209 | 234 | |
210 | 235 | >>> with open("example.csv") as f: |
211 | 236 | ... for line in f.readlines(): |
218 | 243 | Export a list of dictionaries |
219 | 244 | ********************************** |
220 | 245 | |
221 | :: | |
246 | .. code-block:: python | |
222 | 247 | |
223 | 248 | >>> records = [ |
224 | 249 | ... {"year": 1903, "country": "Germany", "speed": "206.7km/h"}, |
231 | 256 | Export a dictionary of single key value pair |
232 | 257 | ******************************************************************************** |
233 | 258 | |
234 | :: | |
259 | .. code-block:: python | |
235 | 260 | |
236 | 261 | >>> henley_on_thames_facts = { |
237 | 262 | ... "area": "5.58 square meters", |
246 | 271 | Export a dictionary of single dimensonal array |
247 | 272 | ******************************************************************************** |
248 | 273 | |
274 | .. code-block:: python | |
275 | ||
249 | 276 | >>> ccs_insights = { |
250 | 277 | ... "year": ["2017", "2018", "2019", "2020", "2021"], |
251 | 278 | ... "smart phones": [1.53, 1.64, 1.74, 1.82, 1.90], |
257 | 284 | Export a dictionary of two dimensional array as a book |
258 | 285 | ******************************************************************************** |
259 | 286 | |
260 | Suppose you want to save the below dictionary to an excel file :: | |
261 | ||
287 | Suppose you want to save the below dictionary to an excel file : | |
288 | ||
289 | .. code-block:: python | |
290 | ||
262 | 291 | >>> a_dictionary_of_two_dimensional_arrays = { |
263 | 292 | ... 'Sheet 1': |
264 | 293 | ... [ |
280 | 309 | ... ] |
281 | 310 | ... } |
282 | 311 | |
283 | Here is the code:: | |
312 | Here is the code: | |
313 | ||
314 | .. code-block:: python | |
284 | 315 | |
285 | 316 | >>> p.save_book_as( |
286 | 317 | ... bookdict=a_dictionary_of_two_dimensional_arrays, |
288 | 319 | ... ) |
289 | 320 | |
290 | 321 | 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:: | |
322 | pass on an ordered dictionary to the function itself. For example: | |
323 | ||
324 | .. code-block:: python | |
292 | 325 | |
293 | 326 | >>> data = OrderedDict() |
294 | 327 | >>> data.update({"Sheet 2": a_dictionary_of_two_dimensional_arrays['Sheet 2']}) |
296 | 329 | >>> data.update({"Sheet 3": a_dictionary_of_two_dimensional_arrays['Sheet 3']}) |
297 | 330 | >>> p.save_book_as(bookdict=data, dest_file_name="book.xls") |
298 | 331 | |
299 | Let's verify its order:: | |
332 | Let's verify its order: | |
333 | ||
334 | .. code-block:: python | |
300 | 335 | |
301 | 336 | >>> book_dict = p.get_book_dict(file_name="book.xls") |
302 | 337 | >>> for key, item in book_dict.items(): |
308 | 343 | Please notice that "Sheet 2" is the first item in the *book_dict*, meaning the order of sheets are preserved. |
309 | 344 | |
310 | 345 | |
311 | File format transcoding on one line | |
346 | Transcoding | |
312 | 347 | ------------------------------------------- |
313 | 348 | |
314 | 349 | .. note:: |
315 | 350 | |
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 | ||
351 | Please note that `pyexcel-cli` can perform file transcoding at command line. | |
352 | No need to open your editor, save the problem, then python run. | |
320 | 353 | |
321 | 354 | .. testcode:: |
322 | 355 | :hide: |
329 | 362 | ... ] |
330 | 363 | >>> p.save_as(array=data, dest_file_name="birth.xls") |
331 | 364 | |
332 | The following code does a simple file format transcoding from xls to csv:: | |
365 | ||
366 | The following code does a simple file format transcoding from xls to csv: | |
367 | ||
368 | .. code-block:: python | |
333 | 369 | |
334 | 370 | >>> p.save_as(file_name="birth.xls", dest_file_name="birth.csv") |
335 | 371 | |
336 | 372 | Again it is really simple. Let's verify what we have gotten: |
373 | ||
374 | .. code-block:: python | |
337 | 375 | |
338 | 376 | >>> sheet = p.get_sheet(file_name="birth.csv") |
339 | 377 | >>> sheet |
353 | 391 | |
354 | 392 | Let use previous example and save it as xlsx instead |
355 | 393 | |
394 | .. code-block:: python | |
395 | ||
356 | 396 | >>> p.save_as(file_name="birth.xls", |
357 | 397 | ... dest_file_name="birth.xlsx") # change the file extension |
358 | 398 | |
359 | 399 | Again let's verify what we have gotten: |
400 | ||
401 | .. code-block:: python | |
360 | 402 | |
361 | 403 | >>> sheet = p.get_sheet(file_name="birth.xlsx") |
362 | 404 | >>> sheet |
376 | 418 | Merge all excel files in directory into a book where each file become a sheet |
377 | 419 | ******************************************************************************** |
378 | 420 | |
379 | The following code will merge every excel files into one file, say "output.xls":: | |
421 | The following code will merge every excel files into one file, say "output.xls": | |
422 | ||
423 | .. code-block:: python | |
380 | 424 | |
381 | 425 | from pyexcel.cookbook import merge_all_to_a_book |
382 | 426 | import glob |
384 | 428 | |
385 | 429 | merge_all_to_a_book(glob.glob("your_csv_directory\*.csv"), "output.xls") |
386 | 430 | |
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:: | |
431 | 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: | |
432 | ||
433 | .. code-block:: python | |
388 | 434 | |
389 | 435 | from pyexcel.cookbook import merge_all_to_a_book |
390 | 436 | import glob |
399 | 445 | :hide: |
400 | 446 | |
401 | 447 | >>> content = { |
402 | ... 'Sheet 1': | |
448 | ... 'Sheet 1': | |
403 | 449 | ... [ |
404 | ... [1.0, 2.0, 3.0], | |
405 | ... [4.0, 5.0, 6.0], | |
450 | ... [1.0, 2.0, 3.0], | |
451 | ... [4.0, 5.0, 6.0], | |
406 | 452 | ... [7.0, 8.0, 9.0] |
407 | 453 | ... ], |
408 | ... 'Sheet 2': | |
454 | ... 'Sheet 2': | |
409 | 455 | ... [ |
410 | ... ['X', 'Y', 'Z'], | |
411 | ... [1.0, 2.0, 3.0], | |
456 | ... ['X', 'Y', 'Z'], | |
457 | ... [1.0, 2.0, 3.0], | |
412 | 458 | ... [4.0, 5.0, 6.0] |
413 | ... ], | |
414 | ... 'Sheet 3': | |
459 | ... ], | |
460 | ... 'Sheet 3': | |
415 | 461 | ... [ |
416 | ... ['O', 'P', 'Q'], | |
417 | ... [3.0, 2.0, 1.0], | |
462 | ... ['O', 'P', 'Q'], | |
463 | ... [3.0, 2.0, 1.0], | |
418 | 464 | ... [4.0, 3.0, 2.0] |
419 | ... ] | |
465 | ... ] | |
420 | 466 | ... } |
421 | 467 | >>> book = p.Book(content) |
422 | 468 | >>> book.save_as("megabook.xls") |
423 | 469 | |
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:: | |
470 | ||
471 | 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: | |
472 | ||
473 | .. code-block:: python | |
425 | 474 | |
426 | 475 | >>> from pyexcel.cookbook import split_a_book |
427 | 476 | >>> split_a_book("megabook.xls", "output.xls") |
447 | 496 | ************************************* |
448 | 497 | |
449 | 498 | |
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:: | |
499 | 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: | |
500 | ||
501 | .. code-block:: python | |
451 | 502 | |
452 | 503 | >>> from pyexcel.cookbook import extract_a_sheet_from_a_book |
453 | 504 | >>> extract_a_sheet_from_a_book("megabook.xls", "Sheet 1", "output.xls") |
28 | 28 | `pyexcel-echarts`_ draws 2D, 3D, geo charts from pyexcel data and has awesome animations too, but |
29 | 29 | it is under development. |
30 | 30 | |
31 | `pyexcel-matplotlib`_ helps you with scentific charts and is under developmement. | |
31 | `pyexcel-matplotlib`_ helps you with scientific charts and is under developmement. | |
32 | 32 | |
33 | 33 | Gantt chart visualization for your excel data |
34 | 34 | ------------------------------------------------- |
42 | 42 | .. _pyexcel-echarts: https://github.com/pyexcel-renderers/pyexcel-echarts |
43 | 43 | .. _pyexcel-matplotlib: https://github.com/pyexcel-renderers/pyexcel-matplotlib |
44 | 44 | .. _sphinxcontrib-excel: https://github.com/pyexcel-renderers/sphinxcontrib-excel |
45 | .. _pyexcel-gantt: https://github.com/pyexcel-renderers/pyexcel-gantt | |
45 | 46 | |
47 |
0 | 0 | Sheet |
1 | 1 | ========== |
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 | ||
2 | 9 | |
3 | 10 | Random access |
4 | 11 | ----------------- |
68 | 75 | In order to set a value to a cell, please use |
69 | 76 | sheet[row_index, column_index] = new_value |
70 | 77 | |
78 | or sheet['A1'] = new_value | |
79 | ||
71 | 80 | |
72 | 81 | **Random access to rows and columns** |
73 | 82 | |
185 | 194 | |
186 | 195 | >>> list(sheet.rows()) |
187 | 196 | [[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] | |
221 | 197 | |
222 | 198 | |
223 | 199 | **attributes** |
397 | 373 | ... [12, 15] |
398 | 374 | ... ] |
399 | 375 | >>> sheet2 = pyexcel.Sheet(extra_data) |
400 | >>> sheet.column += sheet2 | |
401 | >>> sheet.column["Column 4"] | |
376 | >>> sheet3 = sheet.column + sheet2 | |
377 | >>> sheet3.column["Column 4"] | |
402 | 378 | [10, 11, 12] |
403 | >>> sheet.column["Column 5"] | |
379 | >>> sheet3.column["Column 5"] | |
404 | 380 | [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 | |
405 | 404 | |
406 | 405 | Here is what you will get: |
407 | 406 |
160 | 160 | .. testcode:: |
161 | 161 | :hide: |
162 | 162 | |
163 | >>> from mock import patch, MagicMock | |
163 | >>> from unittest.mock import patch, MagicMock | |
164 | 164 | >>> import pyexcel as pe |
165 | >>> from pyexcel._compact import StringIO | |
165 | >>> from pyexcel._compact import StringIO, PY2, BytesIO | |
166 | 166 | >>> patcher = patch('pyexcel._compact.request.urlopen') |
167 | 167 | >>> 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')) | |
169 | 169 | >>> x = MagicMock() |
170 | 170 | >>> x.type.return_value = "text/csv" |
171 | 171 | >>> io.info = x |
200 | 200 | .. testcode:: |
201 | 201 | :hide: |
202 | 202 | |
203 | >>> from mock import patch, MagicMock | |
203 | >>> from unittest.mock import patch, MagicMock | |
204 | 204 | >>> import os |
205 | 205 | >>> patcher = patch('pyexcel._compact.request.urlopen') |
206 | 206 | >>> fake_url_open = patcher.start() |
240 | 240 | .. testcode:: |
241 | 241 | :hide: |
242 | 242 | |
243 | >>> from mock import patch, MagicMock | |
243 | >>> from unittest.mock import patch, MagicMock | |
244 | 244 | >>> import os |
245 | 245 | >>> patcher = patch('pyexcel._compact.request.urlopen') |
246 | 246 | >>> fake_url_open = patcher.start() |
514 | 514 | >>> os.unlink("output.csv") |
515 | 515 | >>> os.unlink("example.xls") |
516 | 516 | >>> os.unlink("example_series.xls") |
517 | >>> os.unlink("tutorial.csv") |
250 | 250 | :hide: |
251 | 251 | |
252 | 252 | >>> import os |
253 | >>> import pyexcel | |
253 | >>> import pyexcel as pe | |
254 | 254 | >>> data = [ |
255 | 255 | ... ["Row 1", 1, 2, 3], |
256 | 256 | ... ["Row 2", 4, 5, 6], |
257 | 257 | ... ["Row 3", 7, 8, 9], |
258 | 258 | ... ] |
259 | >>> pyexcel.save_as(array=data, dest_file_name="row_example.xls") | |
259 | >>> pe.save_as(array=data, dest_file_name="row_example.xls") | |
260 | 260 | |
261 | 261 | And then you want to update "Row 3" with for example:: |
262 | 262 |
0 | ||
0 | 1 | Stream APIs for big file : A set of two liners |
1 | 2 | ================================================================================ |
2 | 3 | |
4 | When you are dealing with **BIG** excel files, you will want **pyexcel** to use | |
5 | constant memory. | |
6 | ||
3 | 7 | 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. | |
5 | 10 | |
6 | 11 | |
7 | 12 | Two liners for get data from big excel files |
25 | 30 | >>> sheet = p.get_sheet(file_content=content, file_type='csv') |
26 | 31 | >>> sheet.save_as("your_file.xls") |
27 | 32 | |
33 | ||
34 | ||
28 | 35 | Suppose you want to process the following coffee data: |
29 | 36 | |
30 | 37 | .. pyexcel-table:: |
36 | 43 | Starbucks Coffee Pike Place Roast,grande(16 oz.),310 |
37 | 44 | Panera Coffee Light Roast,regular(16 oz.),300 |
38 | 45 | |
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 | ||
41 | 50 | >>> records = p.iget_records(file_name="your_file.xls") |
42 | 51 | |
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") | |
50 | 58 | venti(20 oz) of Starbucks Coffee Blonde Roast has 475 mg |
51 | 59 | large(20 oz.) of Dunkin' Donuts Coffee with Turbo Shot has 398 mg |
52 | 60 | grande(16 oz.) of Starbucks Coffee Pike Place Roast has 310 mg |
53 | 61 | regular(16 oz.) of Panera Coffee Light Roast has 300 mg |
54 | 62 | |
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 | |
56 | 66 | |
57 | 67 | >>> p.free_resources() |
58 | 68 | |
59 | 69 | Get two dimensional array |
60 | 70 | ******************************************************************************** |
61 | 71 | |
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 | |
63 | 75 | |
64 | 76 | >>> 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") | |
69 | 78 | venti(20 oz) of Starbucks Coffee Blonde Roast has 475 mg |
70 | 79 | large(20 oz.) of Dunkin' Donuts Coffee with Turbo Shot has 398 mg |
71 | 80 | grande(16 oz.) of Starbucks Coffee Pike Place Roast has 310 mg |
72 | 81 | regular(16 oz.) of Panera Coffee Light Roast has 300 mg |
73 | 82 | |
74 | Again, do not forgot the second line:: | |
83 | Again, do not forgot the second line: | |
84 | ||
85 | .. code-block:: python | |
75 | 86 | |
76 | 87 | >>> p.free_resources() |
77 | ||
88 | ||
78 | 89 | where `start_row` skips the header row. |
79 | 90 | |
80 | 91 | Data export in one liners |
83 | 94 | Export an array |
84 | 95 | ********************** |
85 | 96 | |
86 | Suppose you have the following array:: | |
97 | Suppose you have the following array: | |
98 | ||
99 | .. code-block:: python | |
87 | 100 | |
88 | 101 | >>> data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] |
89 | 102 | |
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 | |
91 | 106 | |
92 | 107 | >>> p.isave_as(array=data, dest_file_name="example.xls") |
93 | 108 | |
94 | 109 | 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 | |
96 | 113 | |
97 | 114 | >>> # p.free_resources() |
98 | 115 | |
99 | Let's verify it:: | |
116 | Let's verify it: | |
117 | ||
118 | .. code-block:: python | |
100 | 119 | |
101 | 120 | >>> p.get_sheet(file_name="example.xls") |
102 | 121 | pyexcel_sheet1: |
108 | 127 | | 7 | 8 | 9 | |
109 | 128 | +---+---+---+ |
110 | 129 | |
130 | ||
111 | 131 | .. testcode:: |
112 | 132 | :hide: |
113 | 133 | |
115 | 135 | >>> os.unlink("example.xls") |
116 | 136 | |
117 | 137 | |
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 | |
119 | 141 | |
120 | 142 | >>> p.isave_as(array=data, |
121 | 143 | ... dest_file_name="example.csv", |
122 | 144 | ... dest_delimiter=':') |
123 | 145 | |
124 | Let's verify it:: | |
146 | Let's verify it: | |
147 | ||
148 | .. code-block:: python | |
125 | 149 | |
126 | 150 | >>> with open("example.csv") as f: |
127 | 151 | ... for line in f.readlines(): |
134 | 158 | Export a list of dictionaries |
135 | 159 | ********************************** |
136 | 160 | |
137 | :: | |
161 | .. code-block:: python | |
138 | 162 | |
139 | 163 | >>> records = [ |
140 | 164 | ... {"year": 1903, "country": "Germany", "speed": "206.7km/h"}, |
146 | 170 | Export a dictionary of single key value pair |
147 | 171 | ******************************************************************************** |
148 | 172 | |
149 | :: | |
173 | .. code-block:: python | |
150 | 174 | |
151 | 175 | >>> henley_on_thames_facts = { |
152 | 176 | ... "area": "5.58 square meters", |
160 | 184 | Export a dictionary of single dimensonal array |
161 | 185 | ******************************************************************************** |
162 | 186 | |
187 | .. code-block:: python | |
188 | ||
163 | 189 | >>> ccs_insights = { |
164 | 190 | ... "year": ["2017", "2018", "2019", "2020", "2021"], |
165 | 191 | ... "smart phones": [1.53, 1.64, 1.74, 1.82, 1.90], |
171 | 197 | Export a dictionary of two dimensional array as a book |
172 | 198 | ******************************************************************************** |
173 | 199 | |
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 | ||
176 | 204 | >>> a_dictionary_of_two_dimensional_arrays = { |
177 | 205 | ... 'Sheet 1': |
178 | 206 | ... [ |
194 | 222 | ... ] |
195 | 223 | ... } |
196 | 224 | |
197 | Here is the code:: | |
225 | Here is the code: | |
226 | ||
227 | .. code-block:: python | |
198 | 228 | |
199 | 229 | >>> p.isave_book_as( |
200 | 230 | ... bookdict=a_dictionary_of_two_dimensional_arrays, |
202 | 232 | ... ) |
203 | 233 | |
204 | 234 | 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 | |
206 | 238 | |
207 | 239 | >>> from pyexcel._compact import OrderedDict |
208 | 240 | >>> data = OrderedDict() |
212 | 244 | >>> p.isave_book_as(bookdict=data, dest_file_name="book.xls") |
213 | 245 | >>> p.free_resources() |
214 | 246 | |
215 | Let's verify its order:: | |
247 | Let's verify its order: | |
248 | ||
249 | .. code-block:: python | |
216 | 250 | |
217 | 251 | >>> import json |
218 | 252 | >>> book_dict = p.get_book_dict(file_name="book.xls") |
246 | 280 | ... ] |
247 | 281 | >>> p.isave_as(array=data, dest_file_name="birth.xls") |
248 | 282 | |
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 | |
250 | 287 | |
251 | 288 | >>> import pyexcel |
252 | 289 | >>> p.save_as(file_name="birth.xls", dest_file_name="birth.csv") |
253 | 290 | |
254 | 291 | Again it is really simple. Let's verify what we have gotten: |
292 | ||
293 | .. code-block:: python | |
255 | 294 | |
256 | 295 | >>> sheet = p.get_sheet(file_name="birth.csv") |
257 | 296 | >>> sheet |
264 | 303 | | Smith | 4.2 | 12/11/14 | |
265 | 304 | +-------+--------+----------+ |
266 | 305 | |
267 | .. NOTE:: | |
306 | .. note:: | |
268 | 307 | |
269 | 308 | 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. |
270 | 309 | |
271 | 310 | |
272 | 311 | Let use previous example and save it as xlsx instead |
312 | ||
313 | .. code-block:: python | |
273 | 314 | |
274 | 315 | >>> import pyexcel |
275 | 316 | >>> p.isave_as(file_name="birth.xls", |
277 | 318 | |
278 | 319 | Again let's verify what we have gotten: |
279 | 320 | |
321 | .. code-block:: python | |
322 | ||
280 | 323 | >>> sheet = p.get_sheet(file_name="birth.xlsx") |
281 | 324 | >>> sheet |
282 | 325 | pyexcel_sheet1: |
287 | 330 | +-------+--------+----------+ |
288 | 331 | | Smith | 4.2 | 12/11/14 | |
289 | 332 | +-------+--------+----------+ |
333 | ||
334 | .. testcode:: | |
335 | :hide: | |
336 | ||
337 | >>> import os | |
338 | >>> os.unlink('ccs.csv') | |
339 | >>> os.unlink('book.xls') |
29 | 29 | [pyexcel-xls] |
30 | 30 | [pyexcel-xlsx] |
31 | 31 | [pyexcel-xlsxw] |
32 | [pyexcel-libxlsxw] | |
33 | [pyexcel-xlsxr] | |
32 | 34 | } |
33 | 35 | |
34 | 36 | [tabulate] <<dependency>> |
40 | 42 | [xlsxwriter] <<dependency>> |
41 | 43 | [ezodf] <<dependency>> |
42 | 44 | [odfpy] <<dependency>> |
45 | [lxml] <<dependency>> | |
46 | [libxlsxwpy] <<dependency>> | |
47 | [libxlsx] <<dependency>> | |
43 | 48 | |
44 | 49 | [pyexcel public api] -right-> [pyexcel internal api] |
45 | 50 | [pyexcel internal api] -right-> [pyexcel plugin interfaces] |
57 | 62 | [pyexcel-io plugin interfaces] ..> [csv, csvz, tsv, tsvz] |
58 | 63 | [pyexcel-io plugin interfaces] ..> [django, sqlalchemy] |
59 | 64 | [pyexcel-io plugin interfaces] .up.> [pyexcel-xls] |
65 | [pyexcel-io plugin interfaces] .up.> [pyexcel-xlsxw] | |
66 | [pyexcel-io plugin interfaces] .up.> [pyexcel-libxlsxw] | |
60 | 67 | [pyexcel-io plugin interfaces] .up.> [pyexcel-xlsx] |
68 | [pyexcel-io plugin interfaces] .up.> [pyexcel-xlsxr] | |
61 | 69 | [pyexcel-io plugin interfaces] .left.> [pyexcel-ods] |
62 | 70 | [pyexcel-io plugin interfaces] .right.> [pyexcel-ods3] |
63 | [pyexcel-io plugin interfaces] .right.> [pyexcel-xlsxw] | |
64 | 71 | [pyexcel-io plugin interfaces] .right.> [pyexcel-odsr] |
72 | ||
65 | 73 | |
66 | 74 | [pyexcel-text] -up- [tabulate] |
67 | 75 | [pyexcel-pygal] -up- [pygal] |
69 | 77 | [pyexcel-xls] -right- [xlrd] |
70 | 78 | [pyexcel-xls] -right- [xlwt] |
71 | 79 | [pyexcel-xlsx] -right- [openpyxl] |
72 | [pyexcel-xlsxw] -down- [xlsxwriter] | |
80 | [pyexcel-xlsxw] -up- [xlsxwriter] | |
73 | 81 | [pyexcel-ods3] --- [ezodf] |
74 | 82 | [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] | |
75 | 88 | |
76 | 89 | skinparam component{ |
77 | 90 |
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 | 0 | 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" | |
1 | pyexcel-io==0.6.2 | |
2 | texttable==0.8.3 | |
7 | 3 | pyexcel-xlsx==0.4.1 |
8 | 4 | pyexcel-xls==0.4.1 |
16 | 16 | # pylint: disable=ungrouped-imports |
17 | 17 | import sys |
18 | 18 | import warnings |
19 | from io import BytesIO, StringIO | |
20 | from urllib import request as request | |
19 | 21 | from textwrap import dedent |
22 | from itertools import zip_longest | |
23 | from collections import OrderedDict | |
20 | 24 | |
21 | 25 | PY2 = sys.version_info[0] == 2 |
22 | 26 | PY26 = PY2 and sys.version_info[1] < 7 |
23 | 27 | PY3_AND_ABOVE = sys.version_info[0] >= 3 |
24 | 28 | |
25 | if PY26: | |
26 | from ordereddict import OrderedDict | |
27 | else: | |
28 | from collections import OrderedDict | |
29 | 29 | |
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 | |
53 | 33 | |
54 | 34 | |
55 | 35 | def is_tuple_consists_of_strings(an_array): |
6 | 6 | :copyright: (c) 2014-2019 by Onni Software Ltd. |
7 | 7 | :license: New BSD License, see LICENSE for more details |
8 | 8 | """ |
9 | import pyexcel._compact as compact | |
9 | from pyexcel import _compact as compact | |
10 | 10 | from pyexcel.sheet import Sheet |
11 | 11 | from pyexcel.internal.meta import BookMeta |
12 | 12 | from pyexcel.internal.common import SheetIterator |
39 | 39 | self.init(sheets=sheets, filename=filename, path=path) |
40 | 40 | |
41 | 41 | 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""" | |
44 | 43 | self.__path = path |
45 | 44 | self.filename = filename |
46 | 45 | self.load_from_sheets(sheets) |
55 | 54 | if sheets is None: |
56 | 55 | return |
57 | 56 | 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) | |
62 | 57 | for name in keys: |
63 | 58 | value = sheets[name] |
64 | 59 | if isinstance(value, Sheet): |
221 | 216 | |
222 | 217 | |
223 | 218 | def to_book(bookstream): |
224 | """Convert a bookstream to Book | |
225 | """ | |
219 | """Convert a bookstream to Book""" | |
226 | 220 | if isinstance(bookstream, Book): |
227 | 221 | return bookstream |
228 | 222 | else: |
3 | 3 | |
4 | 4 | Constants appeared in pyexcel |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | # flake8: noqa |
3 | 3 | |
4 | 4 | A list of pyexcel signature functions |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | import re |
10 | 10 | |
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 | |
14 | 13 | from pyexcel.book import Book, to_book |
15 | 14 | from pyexcel.sheet import Sheet |
16 | 15 | 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 | |
19 | 19 | |
20 | 20 | STARTS_WITH_DEST = "^dest_(.*)" |
21 | 21 | SAVE_AS_EXCEPTION = ( |
3 | 3 | |
4 | 4 | List of apis that become deprecated but was kept for backward compatibility |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | import warnings |
197 | 197 | message="Deprecated since v0.0.7! Please use class Book instead", |
198 | 198 | ) |
199 | 199 | def BookReader(file_name, **keywords): |
200 | """For backward compatibility | |
201 | """ | |
200 | """For backward compatibility""" | |
202 | 201 | return load_book(file_name, **keywords) |
203 | 202 | |
204 | 203 |
3 | 3 | |
4 | 4 | Reusible docstrings |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | # flake8: noqa |
3 | 3 | |
4 | 4 | Reusible docstrings for pyexcel.core |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from . import keywords |
3 | 3 | |
4 | 4 | Reusible docstrings for keywords in signature functions |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | CSV_PARAMS = """ |
3 | 3 | |
4 | 4 | Reusible docstrings for pyexcel.internal.meta |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from .keywords import CSV_PARAMS |
3 | 3 | |
4 | 4 | Exceptions appeared in pyexcel |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 |
3 | 3 | |
4 | 4 | Deprecated module import |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from ..deprecated import deprecated_pyexcel_ext |
3 | 3 | |
4 | 4 | Deprecated module import |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from ..deprecated import deprecated_pyexcel_ext |
3 | 3 | |
4 | 4 | Deprecated module import |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from ..deprecated import deprecated_pyexcel_ext |
3 | 3 | |
4 | 4 | Deprecated module import |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from ..deprecated import deprecated_pyexcel_ext |
3 | 3 | |
4 | 4 | Deprecated module import |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from ..deprecated import deprecated_pyexcel_ext |
3 | 3 | |
4 | 4 | Pyexcel internals that subjected to change |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | from lml.loader import scan_plugins_regex | |
9 | 10 | from pyexcel.internal.plugins import PARSER, RENDERER # noqa |
10 | 11 | from pyexcel.internal.generators import BookStream, SheetStream # noqa |
11 | 12 | from pyexcel.internal.source_plugin import SOURCE # noqa |
12 | ||
13 | from lml.loader import scan_plugins_regex | |
14 | 13 | |
15 | 14 | BLACK_LIST = [ |
16 | 15 | "pyexcel_io", |
3 | 3 | |
4 | 4 | Book and sheet attributes |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | import pyexcel.constants as constants | |
9 | from pyexcel import constants as constants | |
10 | 10 | |
11 | 11 | ATTRIBUTE_REGISTRY = { |
12 | 12 | constants.SHEET: { |
3 | 3 | |
4 | 4 | Defintion for the shared objects |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | NO_COLUMN_NAMES = "Only sheet with column names is accepted" |
3 | 3 | |
4 | 4 | elementary functions to read and write generic excel content |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from pyexcel._compact import PY2 |
3 | 3 | |
4 | 4 | Simple garbage collector |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | import pyexcel.docstrings as docs | |
9 | from pyexcel import docstrings as docs | |
10 | 10 | from pyexcel._compact import append_doc |
11 | 11 | |
12 | 12 | GARBAGE = [] |
3 | 3 | |
4 | 4 | Defintion for the sheet and book generators. |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from pyexcel._compact import OrderedDict |
3 | 3 | |
4 | 4 | Annotate sheet and book class' attributes |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | import sys |
10 | 10 | from functools import partial |
11 | 11 | |
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 | |
14 | 14 | from pyexcel._compact import PY2, append_doc |
15 | 15 | from pyexcel.internal import SOURCE |
16 | 16 | from pyexcel.internal.core import save_book, save_sheet, get_sheet_stream |
18 | 18 | |
19 | 19 | |
20 | 20 | 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""" | |
23 | 22 | |
24 | 23 | def custom_presenter(self, **keywords): |
25 | 24 | """docstring is assigned a few lines down the line""" |
41 | 40 | |
42 | 41 | |
43 | 42 | def sheet_presenter(attribute=None): |
44 | """make a custom presentation method for sheet | |
45 | """ | |
43 | """make a custom presentation method for sheet""" | |
46 | 44 | source_getter = SOURCE.get_writable_source |
47 | 45 | return make_presenter(source_getter, attribute) |
48 | 46 | |
49 | 47 | |
50 | 48 | def book_presenter(attribute=None): |
51 | """make a custom presentation method for book | |
52 | """ | |
49 | """make a custom presentation method for book""" | |
53 | 50 | source_getter = SOURCE.get_writable_book_source |
54 | 51 | return make_presenter(source_getter, attribute) |
55 | 52 | |
56 | 53 | |
57 | 54 | def importer(attribute=None): |
58 | """make a custom input method for sheet | |
59 | """ | |
55 | """make a custom input method for sheet""" | |
60 | 56 | |
61 | 57 | def custom_importer1(self, content, **keywords): |
62 | 58 | """docstring is assigned a few lines down the line""" |
78 | 74 | |
79 | 75 | |
80 | 76 | def book_importer(attribute=None): |
81 | """make a custom input method for book | |
82 | """ | |
77 | """make a custom input method for book""" | |
83 | 78 | |
84 | 79 | def custom_book_importer(self, content, **keywords): |
85 | 80 | """docstring is assigned a few lines down the line""" |
260 | 255 | |
261 | 256 | @append_doc(docs.SAVE_AS_OPTIONS) |
262 | 257 | def save_as(self, filename, **keywords): |
263 | """Save the content to a named file | |
264 | """ | |
258 | """Save the content to a named file""" | |
265 | 259 | return save_sheet(self, file_name=filename, **keywords) |
266 | 260 | |
267 | 261 | def save_to_memory(self, file_type, stream=None, **keywords): |
3 | 3 | |
4 | 4 | Renderer and parser plugin manager |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from lml.plugin import PluginManager |
10 | 10 | |
11 | 11 | |
12 | 12 | class IOPluginManager(PluginManager): |
13 | """Generic plugin manager for renderer and parser | |
14 | """ | |
13 | """Generic plugin manager for renderer and parser""" | |
15 | 14 | |
16 | 15 | def __init__(self, name): |
17 | 16 | PluginManager.__init__(self, name) |
18 | 17 | |
19 | 18 | 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""" | |
22 | 20 | __file_type = None |
23 | 21 | if key: |
24 | 22 | __file_type = key.lower() |
27 | 25 | return plugin_cls(__file_type) |
28 | 26 | |
29 | 27 | def get_all_file_types(self): |
30 | """get all supported file types | |
31 | """ | |
28 | """get all supported file types""" | |
32 | 29 | file_types = list(self.registry.keys()) |
33 | 30 | return file_types |
34 | 31 |
3 | 3 | |
4 | 4 | Locally shared utility functions |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | import re |
27 | 27 | raise NotImplementedError("Not implemented") |
28 | 28 | |
29 | 29 | def __add__(self, other): |
30 | """Overload += sign | |
30 | """Overload + sign | |
31 | 31 | |
32 | 32 | :return: self |
33 | 33 | """ |
46 | 46 | |
47 | 47 | |
48 | 48 | 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""" | |
51 | 50 | if aslice.start is None: |
52 | 51 | start = 0 |
53 | 52 | else: |
3 | 3 | |
4 | 4 | Generic table column |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | import copy | |
9 | 10 | import types |
10 | 11 | |
11 | import pyexcel._compact as compact | |
12 | from pyexcel import _compact as compact | |
12 | 13 | |
13 | 14 | from . import _shared as utils |
14 | 15 | |
249 | 250 | :return: self |
250 | 251 | """ |
251 | 252 | if isinstance(other, compact.OrderedDict): |
252 | self._ref.extend_columns(other) | |
253 | self._ref.extend_columns(copy.deepcopy(other)) | |
253 | 254 | elif isinstance(other, list): |
254 | self._ref.extend_columns(other) | |
255 | self._ref.extend_columns(copy.deepcopy(other)) | |
255 | 256 | 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 | ) | |
257 | 260 | else: |
258 | 261 | raise TypeError |
259 | 262 | |
260 | 263 | return self |
261 | 264 | |
262 | 265 | 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 | |
269 | 282 | |
270 | 283 | def __getattr__(self, attr): |
271 | 284 | """ |
280 | 293 | return self._ref.named_column_at(the_attr) |
281 | 294 | |
282 | 295 | def format(self, column_index=None, formatter=None, format_specs=None): |
283 | """Format a column | |
284 | """ | |
296 | """Format a column""" | |
285 | 297 | if column_index is not None: |
286 | 298 | self._handle_one_formatter(column_index, formatter) |
287 | 299 | elif format_specs: |
10 | 10 | import datetime |
11 | 11 | from decimal import Decimal |
12 | 12 | |
13 | import pyexcel.constants as constants | |
13 | from pyexcel import constants as constants | |
14 | 14 | from pyexcel._compact import PY2 |
15 | 15 | |
16 | 16 |
12 | 12 | from functools import partial |
13 | 13 | from itertools import chain |
14 | 14 | |
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 | |
17 | 17 | from pyexcel.internal.meta import SheetMeta |
18 | 18 | from pyexcel.internal.sheets.row import Row |
19 | 19 | from pyexcel.internal.sheets.column import Column |
81 | 81 | :param int column: column index which starts from 0 |
82 | 82 | :param any new_value: new value if this is to set the value |
83 | 83 | """ |
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: | |
87 | 87 | return self.__array[row][column] |
88 | 88 | else: |
89 | # set | |
90 | self.__array[row][column] = new_value | |
91 | else: | |
92 | if new_value is None: | |
93 | 89 | 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 | |
96 | 96 | |
97 | 97 | def row_at(self, index): |
98 | 98 | """ |
110 | 110 | raise IndexError(constants.MESSAGE_INDEX_OUT_OF_RANGE) |
111 | 111 | |
112 | 112 | def set_row_at(self, row_index, data_array): |
113 | """Update a row data range | |
114 | """ | |
113 | """Update a row data range""" | |
115 | 114 | nrows = self.number_of_rows() |
116 | 115 | if row_index < nrows: |
117 | 116 | self.__array[row_index] = data_array |
448 | 447 | self.__width, self.__array = uniform(self.__array) |
449 | 448 | |
450 | 449 | def delete_columns(self, column_indices): |
451 | """Delete columns by specified list of indices | |
452 | """ | |
450 | """Delete columns by specified list of indices""" | |
453 | 451 | if isinstance(column_indices, list) is False: |
454 | 452 | raise TypeError(constants.MESSAGE_DATA_ERROR_DATA_TYPE_MISMATCH) |
455 | 453 | if len(column_indices) > 0: |
501 | 499 | self.__width, self.__array = uniform(self.__array) |
502 | 500 | |
503 | 501 | def to_array(self): |
504 | """Get an array out | |
505 | """ | |
502 | """Get an array out""" | |
506 | 503 | return self.__array |
507 | 504 | |
508 | 505 | def __iter__(self): |
761 | 758 | value = custom_function(value) |
762 | 759 | self.cell_value(row, column, value) |
763 | 760 | |
761 | def __iadd__(self, other): | |
762 | return _add(self.name, self.__array, other) | |
763 | ||
764 | 764 | def __add__(self, other): |
765 | 765 | """Overload the + sign |
766 | 766 | |
767 | 767 | :returns: a new book |
768 | 768 | """ |
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)) | |
794 | 773 | |
795 | 774 | |
796 | 775 | def _unique(seq): |
817 | 796 | return 0 |
818 | 797 | |
819 | 798 | |
820 | def uniform(array): | |
799 | def uniform(array, min_rows=0, min_columns=0): | |
821 | 800 | """Fill-in empty strings to empty cells to make it MxN |
822 | 801 | |
823 | 802 | :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 | |
824 | 805 | """ |
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 | ||
826 | 810 | if width == 0: |
827 | 811 | return 0, array |
828 | 812 | else: |
833 | 817 | row[index] = constants.DEFAULT_NA |
834 | 818 | if row_length < width: |
835 | 819 | row += [constants.DEFAULT_NA] * (width - row_length) |
820 | for _ in range(array_length, height): | |
821 | row = [constants.DEFAULT_NA] * width | |
822 | array.append(row) | |
836 | 823 | return width, array |
837 | 824 | |
838 | 825 | |
860 | 847 | row_data.append(constants.DEFAULT_NA) |
861 | 848 | new_array.append(row_data) |
862 | 849 | 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 |
3 | 3 | |
4 | 4 | Generic table row |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | import copy | |
9 | 10 | import types |
10 | 11 | |
11 | import pyexcel._compact as compact | |
12 | from pyexcel import _compact as compact | |
12 | 13 | |
13 | 14 | from . import _shared as utils |
14 | 15 | |
209 | 210 | :return: self |
210 | 211 | """ |
211 | 212 | if isinstance(other, compact.OrderedDict): |
212 | self._ref.extend_rows(other) | |
213 | self._ref.extend_rows(copy.deepcopy(other)) | |
213 | 214 | elif isinstance(other, list): |
214 | self._ref.extend_rows(other) | |
215 | self._ref.extend_rows(copy.deepcopy(other)) | |
215 | 216 | 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())) | |
217 | 218 | else: |
218 | 219 | raise TypeError |
219 | 220 | return self |
220 | 221 | |
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 | ||
221 | 238 | def format(self, row_index=None, formatter=None, format_specs=None): |
222 | """Format a row | |
223 | """ | |
239 | """Format a row""" | |
224 | 240 | if row_index is not None: |
225 | 241 | self._handle_one_formatter(row_index, formatter) |
226 | 242 | elif format_specs: |
3 | 3 | |
4 | 4 | Second level abstraction |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
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 | |
11 | 12 | from pyexcel.internal.attributes import ( |
12 | 13 | register_book_attribute, |
13 | 14 | register_sheet_attribute, |
14 | 15 | ) |
15 | 16 | |
16 | import pyexcel_io.constants as io_constants | |
17 | from lml.plugin import PluginManager | |
17 | from pyexcel_io import constants as io_constants | |
18 | 18 | |
19 | 19 | REGISTRY_KEY_FORMAT = "%s-%s" |
20 | 20 | # ignore the following attributes |
59 | 59 | def get_a_plugin( |
60 | 60 | self, target=None, action=None, source_library=None, **keywords |
61 | 61 | ): |
62 | """obtain a source plugin for pyexcel signature functions""" | |
62 | """obtain a source plugin for signature functions""" | |
63 | 63 | key = REGISTRY_KEY_FORMAT % (target, action) |
64 | 64 | io_library = None |
65 | 65 | # backward support pyexcel-io library parameter |
74 | 74 | return source_instance |
75 | 75 | |
76 | 76 | 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""" | |
78 | 78 | return self.get_a_plugin( |
79 | 79 | target=constants.SHEET, action=constants.READ_ACTION, **keywords |
80 | 80 | ) |
81 | 81 | |
82 | 82 | 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""" | |
84 | 84 | return self.get_a_plugin( |
85 | 85 | target=constants.BOOK, action=constants.READ_ACTION, **keywords |
86 | 86 | ) |
87 | 87 | |
88 | 88 | 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""" | |
91 | 90 | return self.get_a_plugin( |
92 | 91 | target=constants.SHEET, action=constants.WRITE_ACTION, **keywords |
93 | 92 | ) |
94 | 93 | |
95 | 94 | 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""" | |
97 | 96 | return self.get_a_plugin( |
98 | 97 | target=constants.BOOK, action=constants.WRITE_ACTION, **keywords |
99 | 98 | ) |
3 | 3 | |
4 | 4 | Extract tabular data from external file, stream or content |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | import pyexcel.internal.garbagecollector as gc | |
9 | from pyexcel.internal import garbagecollector as gc | |
10 | 10 | |
11 | 11 | |
12 | 12 | class AbstractParser(object): |
3 | 3 | |
4 | 4 | Public interface for plugins |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | import types |
10 | 10 | from itertools import product |
11 | 11 | |
12 | import pyexcel.constants as constants | |
12 | from pyexcel import constants as constants | |
13 | from lml.plugin import PluginInfo, PluginInfoChain | |
13 | 14 | from pyexcel._compact import is_string |
14 | 15 | from pyexcel.exceptions import FileTypeNotSupported |
15 | 16 | from pyexcel.internal.plugins import PARSER, RENDERER |
16 | ||
17 | from lml.plugin import PluginInfo, PluginInfoChain | |
18 | 17 | |
19 | 18 | |
20 | 19 | class SourceInfo(PluginInfo): |
132 | 131 | |
133 | 132 | |
134 | 133 | class PyexcelPluginChain(PluginInfoChain): |
135 | """It is used by pyexcel plugins | |
136 | """ | |
134 | """It is used by pyexcel plugins""" | |
137 | 135 | |
138 | 136 | def add_a_source(self, relative_plugin_class_path=None, **keywords): |
139 | 137 | """ |
3 | 3 | |
4 | 4 | A list of built-in parsers |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from pyexcel.plugins import PyexcelPluginChain |
3 | 3 | |
4 | 4 | Export data into database datables |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from pyexcel.parser import DbParser |
10 | 10 | |
11 | import pyexcel_io.database.common as django | |
12 | 11 | from pyexcel_io import get_data, iget_data |
12 | from pyexcel_io.database import common as django | |
13 | 13 | |
14 | 14 | |
15 | 15 | class DjangoExporter(DbParser): |
3 | 3 | |
4 | 4 | Parsing excel sources |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from pyexcel.parser import AbstractParser |
3 | 3 | |
4 | 4 | Export data into database datables |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from pyexcel.parser import DbParser |
10 | 10 | |
11 | import pyexcel_io.database.common as sql | |
12 | 11 | from pyexcel_io import get_data, iget_data |
12 | from pyexcel_io.database import common as sql | |
13 | 13 | |
14 | 14 | |
15 | 15 | class SQLAlchemyExporter(DbParser): |
3 | 3 | |
4 | 4 | A list of built-in renderers |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from pyexcel.plugins import PyexcelPluginChain |
4 | 4 | Export data into texttable format. It also serves the default |
5 | 5 | presentation of pyexcel sheet and book. |
6 | 6 | |
7 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
7 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
8 | 8 | :license: New BSD License |
9 | 9 | """ |
10 | 10 | from types import GeneratorType |
11 | 11 | |
12 | import pyexcel.constants as constants | |
12 | from pyexcel import constants as constants | |
13 | from texttable import Texttable | |
13 | 14 | from pyexcel.renderer import Renderer |
14 | 15 | from pyexcel.internal.sheets.formatters import to_format |
15 | ||
16 | from texttable import Texttable | |
17 | 16 | |
18 | 17 | |
19 | 18 | class TextTableRenderer(Renderer): |
3 | 3 | |
4 | 4 | Export data into django models |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | import pyexcel.internal.common as common | |
10 | 9 | from pyexcel._compact import OrderedDict |
10 | from pyexcel.internal import common as common | |
11 | 11 | from pyexcel.renderer import DbRenderer |
12 | 12 | |
13 | import pyexcel_io.database.common as django | |
14 | 13 | from pyexcel_io import save_data |
14 | from pyexcel_io.database import common as django | |
15 | 15 | |
16 | 16 | NO_COLUMN_NAMES = "Only sheet with column names is accepted" |
17 | 17 |
3 | 3 | |
4 | 4 | Export data into excel files |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from pyexcel.renderer import AbstractRenderer |
10 | 10 | from pyexcel.constants import DEFAULT_SHEET_NAME |
11 | 11 | |
12 | import pyexcel_io.manager as manager | |
12 | from pyexcel_io import manager as manager | |
13 | 13 | from pyexcel_io import save_data |
14 | 14 | |
15 | 15 |
3 | 3 | |
4 | 4 | Export data into database datables |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | import pyexcel.internal.common as common | |
10 | 9 | from pyexcel._compact import OrderedDict |
10 | from pyexcel.internal import common as common | |
11 | 11 | from pyexcel.renderer import DbRenderer |
12 | 12 | |
13 | import pyexcel_io.database.common as sql | |
14 | 13 | from pyexcel_io import save_data |
14 | from pyexcel_io.database import common as sql | |
15 | 15 | |
16 | 16 | |
17 | 17 | class SQLAlchemyRenderer(DbRenderer): |
3 | 3 | |
4 | 4 | A list of built-in sources |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from pyexcel.plugins import PyexcelPluginChain |
3 | 3 | |
4 | 4 | Generic database sources |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from pyexcel.source import AbstractSource |
3 | 3 | |
4 | 4 | Representation of django sources |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from pyexcel_io.constants import DB_DJANGO |
3 | 3 | |
4 | 4 | Representation of input file sources |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | import os |
14 | 14 | |
15 | 15 | # pylint: disable=W0223 |
16 | 16 | 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""" | |
19 | 18 | |
20 | 19 | def __init__(self, file_name=None, parser_library=None, **keywords): |
21 | 20 | self.__file_name = file_name |
3 | 3 | |
4 | 4 | Representation of output file sources |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from pyexcel.source import AbstractSource |
13 | 13 | |
14 | 14 | # pylint: disable=W0223 |
15 | 15 | 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""" | |
18 | 17 | |
19 | 18 | def __init__(self, file_name=None, renderer_library=None, **keywords): |
20 | 19 | AbstractSource.__init__(self, **keywords) |
35 | 34 | |
36 | 35 | # pylint: disable=W0223 |
37 | 36 | 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""" | |
40 | 38 | |
41 | 39 | def write_data(self, book): |
42 | 40 | self._renderer.render_book_to_file( |
3 | 3 | |
4 | 4 | Representation of http sources |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | import pyexcel.constants as constants | |
9 | from pyexcel import constants as constants | |
10 | 10 | from pyexcel.source import AbstractSource |
11 | 11 | from pyexcel._compact import PY2, request |
12 | 12 | from pyexcel.internal import PARSER |
54 | 54 | file_type = _get_file_type_from_url(self.__url) |
55 | 55 | parser_library = self._keywords.get("parser_library", None) |
56 | 56 | 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) | |
58 | 59 | return sheets |
59 | 60 | |
60 | 61 | def get_source_info(self): |
3 | 3 | |
4 | 4 | Representation of input file sources |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from pyexcel.source import AbstractSource |
3 | 3 | |
4 | 4 | Representation of output file sources |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from pyexcel.source import AbstractSource, MemorySourceMixin |
3 | 3 | |
4 | 4 | Local magic workds |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | # keywords |
3 | 3 | |
4 | 4 | Representation of array source |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from pyexcel.source import AbstractSource, MemorySourceMixin |
3 | 3 | |
4 | 4 | Representation of book dict source |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from pyexcel.source import AbstractSource, MemorySourceMixin |
3 | 3 | |
4 | 4 | Representation of array, dict, records and book dict sources |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | import pyexcel.constants as constants | |
9 | from pyexcel import constants as constants | |
10 | 10 | from pyexcel._compact import PY2, OrderedDict, zip_longest |
11 | 11 | |
12 | 12 | from pyexcel_io.sheet import SheetReader |
3 | 3 | |
4 | 4 | Representation of dict sources |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from pyexcel.source import AbstractSource, MemorySourceMixin |
3 | 3 | |
4 | 4 | Representation of records source |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from pyexcel.source import AbstractSource, MemorySourceMixin |
3 | 3 | |
4 | 4 | Representation of querysets |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | import pyexcel.constants as constants | |
9 | from pyexcel import constants as constants | |
10 | 10 | from pyexcel.source import AbstractSource |
11 | 11 | |
12 | from pyexcel_io.database.querysets import QuerysetsReader | |
12 | from pyexcel_io import get_data | |
13 | from pyexcel_io.constants import DB_QUERYSET | |
13 | 14 | from . import params |
14 | 15 | |
15 | 16 | |
66 | 67 | ) |
67 | 68 | if self.__skip_row_func is not None: |
68 | 69 | 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 | |
71 | 76 | ) |
72 | data = reader.to_array() | |
73 | return {self.__sheet_name: data} | |
77 | return data |
3 | 3 | |
4 | 4 | Representation of array source |
5 | 5 | |
6 | :copyright: (c) 2015-2018 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from pyexcel.source import AbstractSource, MemorySourceMixin |
3 | 3 | |
4 | 4 | Representation of sqlalchemy sources |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | 9 | from pyexcel_io.constants import DB_SQL |
3 | 3 | |
4 | 4 | Renders pyexcel.Book and pyexcel.Sheet to any format |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | import pyexcel._compact as compact | |
9 | from pyexcel import _compact as compact | |
10 | 10 | |
11 | 11 | |
12 | 12 | class AbstractRenderer(object): |
8 | 8 | """ |
9 | 9 | from collections import defaultdict |
10 | 10 | |
11 | import pyexcel._compact as compact | |
12 | import pyexcel.constants as constants | |
11 | from pyexcel import _compact as compact | |
12 | from pyexcel import constants as constants | |
13 | from pyexcel._compact import OrderedDict | |
13 | 14 | from pyexcel.internal.sheets.row import Row as NamedRow |
14 | 15 | from pyexcel.internal.sheets.column import Column as NamedColumn |
15 | 16 | from pyexcel.internal.sheets.matrix import Matrix |
80 | 81 | """ |
81 | 82 | self.__column_names = [] |
82 | 83 | self.__row_names = [] |
83 | self.__row_index = 0 | |
84 | self.__row_index = -1 | |
85 | self.__column_index = -1 | |
84 | 86 | self.init( |
85 | 87 | sheet=sheet, |
86 | 88 | name=name, |
160 | 162 | if transpose_after: |
161 | 163 | self.transpose() |
162 | 164 | |
165 | def clone(self): | |
166 | import copy | |
167 | ||
168 | new_sheet = Sheet( | |
169 | copy.deepcopy(self.get_internal_array()), | |
170 | name_columns_by_row=self.__row_index, | |
171 | name_rows_by_column=self.__column_index, | |
172 | ) | |
173 | return new_sheet | |
174 | ||
163 | 175 | def transpose(self): |
164 | 176 | self.__column_names, self.__row_names = ( |
165 | 177 | self.__row_names, |
183 | 195 | The specified column will be deleted from the data |
184 | 196 | :param column_index: the index of the column that has the row names |
185 | 197 | """ |
198 | self.__column_index = column_index | |
186 | 199 | self.__row_names = make_names_unique(self.column_at(column_index)) |
187 | 200 | del self.column[column_index] |
188 | 201 | |
463 | 476 | else: |
464 | 477 | raise ValueError(constants.MESSAGE_DATA_ERROR_NO_SERIES) |
465 | 478 | |
479 | def project(self, new_ordered_columns, exclusion=False): | |
480 | """ | |
481 | Rearrange the sheet. | |
482 | ||
483 | :ivar new_ordered_columns: new columns | |
484 | :ivar exclusion: to exlucde named column or not. defaults to False | |
485 | ||
486 | Example:: | |
487 | ||
488 | >>> sheet = Sheet( | |
489 | ... [["A", "B", "C"], [1, 2, 3], [11, 22, 33], [111, 222, 333]], | |
490 | ... name_columns_by_row=0) | |
491 | >>> sheet.project(["B", "A", "C"]) | |
492 | pyexcel sheet: | |
493 | +-----+-----+-----+ | |
494 | | B | A | C | | |
495 | +=====+=====+=====+ | |
496 | | 2 | 1 | 3 | | |
497 | +-----+-----+-----+ | |
498 | | 22 | 11 | 33 | | |
499 | +-----+-----+-----+ | |
500 | | 222 | 111 | 333 | | |
501 | +-----+-----+-----+ | |
502 | >>> sheet.project(["B", "C"]) | |
503 | pyexcel sheet: | |
504 | +-----+-----+ | |
505 | | B | C | | |
506 | +=====+=====+ | |
507 | | 2 | 3 | | |
508 | +-----+-----+ | |
509 | | 22 | 33 | | |
510 | +-----+-----+ | |
511 | | 222 | 333 | | |
512 | +-----+-----+ | |
513 | >>> sheet.project(["B", "C"], exclusion=True) | |
514 | pyexcel sheet: | |
515 | +-----+ | |
516 | | A | | |
517 | +=====+ | |
518 | | 1 | | |
519 | +-----+ | |
520 | | 11 | | |
521 | +-----+ | |
522 | | 111 | | |
523 | +-----+ | |
524 | ||
525 | """ | |
526 | from pyexcel import get_array | |
527 | ||
528 | the_dict = self.to_dict() | |
529 | new_dict = OrderedDict() | |
530 | if exclusion: | |
531 | for column in the_dict.keys(): | |
532 | if column not in new_ordered_columns: | |
533 | new_dict[column] = the_dict[column] | |
534 | else: | |
535 | for column in new_ordered_columns: | |
536 | new_dict[column] = the_dict[column] | |
537 | ||
538 | array = get_array(adict=new_dict) | |
539 | return Sheet(array, name=self.name, name_columns_by_row=0) | |
540 | ||
466 | 541 | def to_dict(self, row=False): |
467 | 542 | """Returns a dictionary""" |
468 | 543 | the_dict = compact.OrderedDict() |
556 | 631 | for item in alist: |
557 | 632 | if not compact.is_string(type(item)): |
558 | 633 | item = str(item) |
634 | item = item.strip() | |
559 | 635 | if item in duplicates: |
560 | 636 | duplicates[item] = duplicates[item] + 1 |
561 | 637 | new_names.append("%s-%d" % (item, duplicates[item])) |
3 | 3 | |
4 | 4 | Generic data source definition |
5 | 5 | |
6 | :copyright: (c) 2015-2017 by Onni Software Ltd. | |
6 | :copyright: (c) 2015-2020 by Onni Software Ltd. | |
7 | 7 | :license: New BSD License |
8 | 8 | """ |
9 | import pyexcel.constants as constants | |
9 | from pyexcel import constants as constants | |
10 | 10 | |
11 | 11 | |
12 | 12 | class AbstractSource(object): |
0 | 0 | overrides: "pyexcel.yaml" |
1 | 1 | name: "pyexcel" |
2 | author: chfw | |
2 | 3 | nick_name: pyexcel |
3 | version: 0.5.14 | |
4 | current_version: 0.5.14 | |
5 | release: 0.5.14 | |
6 | copyright_year: 2014-2019 | |
4 | version: 0.6.6 | |
5 | current_version: 0.6.6 | |
6 | release: 0.6.6 | |
7 | copyright_year: 2014-2020 | |
7 | 8 | branch: master |
9 | is_on_conda: true | |
10 | setup_use_markers: true | |
11 | sphinx_extensions: | |
12 | - sphinx.ext.autosummary | |
13 | - sphinx.ext.autodoc | |
14 | - sphinx.ext.doctest | |
15 | - sphinx.ext.intersphinx | |
16 | - sphinx.ext.viewcode | |
17 | - sphinxcontrib.excel | |
8 | 18 | dependencies: |
9 | 19 | - 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" | |
20 | - pyexcel-io>=0.6.2 | |
21 | - texttable>=0.8.2 | |
16 | 22 | extra_dependencies: |
17 | 23 | - xls: |
18 | - pyexcel-xls>=0.5.0 | |
24 | - pyexcel-xls>=0.6.0 | |
19 | 25 | - xlsx: |
20 | - pyexcel-xlsx>=0.5.0 | |
26 | - pyexcel-xlsx>=0.6.0 | |
21 | 27 | - ods: |
22 | - pyexcel-ods3>=0.5.0 | |
28 | - pyexcel-ods3>=0.6.0 | |
29 | test_dependencies: | |
30 | - flask | |
31 | - SQLAlchemy | |
32 | - pyexcel-xlsx>=0.4.1 | |
33 | - pyexcel-xls>=0.4.1 | |
34 | - pyexcel-text>=0.2.0 | |
35 | - psutil | |
36 | - pyexcel-pygal | |
23 | 37 | description: A wrapper library that provides one API to read, manipulate and write data in different excel formats |
38 | python_requires: ">=3.6" | |
39 | min_python_version: "3.6" | |
40 | skip_readme: true | |
41 | moban_command: false |
0 | 0 | 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" | |
1 | pyexcel-io>=0.6.2 | |
2 | 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 |
0 | 0 | #!/usr/bin/env python3 |
1 | ||
2 | """ | |
3 | Template by pypi-mobans | |
4 | """ | |
1 | 5 | |
2 | 6 | import os |
3 | 7 | import sys |
4 | ||
5 | # Template by pypi-mobans | |
6 | 8 | import codecs |
7 | 9 | import locale |
8 | 10 | import platform |
28 | 30 | locale.setlocale(locale.LC_ALL, "en_US.UTF-8") |
29 | 31 | |
30 | 32 | NAME = "pyexcel" |
31 | AUTHOR = "C.W." | |
32 | VERSION = "0.5.14" | |
33 | AUTHOR = "chfw" | |
34 | VERSION = "0.6.6" | |
33 | 35 | EMAIL = "[email protected]" |
34 | 36 | LICENSE = "New BSD" |
35 | 37 | 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" | |
38 | 40 | ) |
39 | 41 | 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.6.6.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 | ] | |
43 | 54 | |
44 | 55 | CLASSIFIERS = [ |
45 | 56 | "Topic :: Software Development :: Libraries", |
46 | 57 | "Programming Language :: Python", |
47 | 58 | "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 | ||
53 | 64 | "Programming Language :: Python :: 3.6", |
54 | 65 | "Programming Language :: Python :: 3.7", |
55 | 66 | "Programming Language :: Python :: 3.8", |
56 | "Development Status :: 3 - Alpha", | |
57 | "Programming Language :: Python :: Implementation :: PyPy", | |
67 | ||
68 | 'Development Status :: 3 - Alpha', | |
58 | 69 | ] |
59 | 70 | |
60 | INSTALL_REQUIRES = ["lml>=0.0.4", "pyexcel-io>=0.5.18"] | |
71 | PYTHON_REQUIRES = ">=3.6" | |
72 | ||
73 | INSTALL_REQUIRES = [ | |
74 | "lml>=0.0.4", | |
75 | "pyexcel-io>=0.6.2", | |
76 | "texttable>=0.8.2", | |
77 | ] | |
61 | 78 | SETUP_COMMANDS = {} |
62 | 79 | |
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"]) | |
80 | PACKAGES = find_packages(exclude=["ez_setup", "examples", "tests", "tests.*"]) | |
75 | 81 | EXTRAS_REQUIRE = { |
76 | "xls": ["pyexcel-xls>=0.5.0"], | |
77 | "xlsx": ["pyexcel-xlsx>=0.5.0"], | |
78 | "ods": ["pyexcel-ods3>=0.5.0"], | |
82 | "xls": ['pyexcel-xls>=0.6.0'], | |
83 | "xlsx": ['pyexcel-xlsx>=0.6.0'], | |
84 | "ods": ['pyexcel-ods3>=0.6.0'], | |
79 | 85 | } |
80 | 86 | # 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 | ) | |
87 | PUBLISH_COMMAND = "{0} setup.py sdist bdist_wheel upload -r pypi".format(sys.executable) | |
88 | HERE = os.path.abspath(os.path.dirname(__file__)) | |
89 | ||
90 | GS_COMMAND = ("gease pyexcel v0.6.6 " + | |
91 | "Find 0.6.6 in changelog for more details") | |
92 | NO_GS_MESSAGE = ("Automatic github release is disabled. " + | |
93 | "Please install gease to enable it.") | |
91 | 94 | UPLOAD_FAILED_MSG = ( |
92 | 'Upload failed. please run "%s" yourself.' % PUBLISH_COMMAND | |
93 | ) | |
94 | HERE = os.path.abspath(os.path.dirname(__file__)) | |
95 | 'Upload failed. please run "%s" yourself.' % PUBLISH_COMMAND) | |
95 | 96 | |
96 | 97 | |
97 | 98 | class PublishCommand(Command): |
128 | 129 | self.status(NO_GS_MESSAGE) |
129 | 130 | if run_status: |
130 | 131 | if os.system(PUBLISH_COMMAND) != 0: |
131 | self.status(UPLOAD_FAILED_MSG % PUBLISH_COMMAND) | |
132 | self.status(UPLOAD_FAILED_MSG) | |
132 | 133 | |
133 | 134 | sys.exit() |
134 | 135 | |
135 | 136 | |
136 | SETUP_COMMANDS.update({"publish": PublishCommand}) | |
137 | ||
137 | SETUP_COMMANDS.update({ | |
138 | "publish": PublishCommand | |
139 | }) | |
138 | 140 | |
139 | 141 | def has_gease(): |
140 | 142 | """ |
144 | 146 | """ |
145 | 147 | try: |
146 | 148 | import gease # noqa |
147 | ||
148 | 149 | return True |
149 | 150 | except ImportError: |
150 | 151 | return False |
205 | 206 | long_description=read_files(*FILES), |
206 | 207 | license=LICENSE, |
207 | 208 | keywords=KEYWORDS, |
209 | python_requires=PYTHON_REQUIRES, | |
208 | 210 | extras_require=EXTRAS_REQUIRE, |
209 | 211 | tests_require=["nose"], |
210 | 212 | install_requires=INSTALL_REQUIRES, |
212 | 214 | include_package_data=True, |
213 | 215 | zip_safe=False, |
214 | 216 | classifiers=CLASSIFIERS, |
215 | cmdclass=SETUP_COMMANDS, | |
217 | cmdclass=SETUP_COMMANDS | |
216 | 218 | ) |
0 | 0 | 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 |
0 | 0 | #/bin/bash |
1 | 1 | 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 |
2 | 2 | |
3 | 3 | import pyexcel as pe |
4 | 4 | |
5 | import unittest | |
5 | 6 | from nose.tools import eq_, raises |
6 | 7 | |
7 | 8 | |
60 | 61 | table.append(array) |
61 | 62 | io = pe.save_as(dest_file_type=file_type, array=table) |
62 | 63 | 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 | [f"{row_no}_{col_no}" for col_no in range(10)] | |
72 | for row_no in range(10) | |
73 | ]) | |
63 | 74 | |
64 | 75 | |
65 | 76 | class PyexcelBase: |
2 | 2 | codecov |
3 | 3 | coverage |
4 | 4 | flake8 |
5 | black | |
6 | isort | |
7 | collective.checkdocs | |
8 | pygments | |
9 | moban | |
10 | moban_jinja2_github | |
11 | flask | |
5 | 12 | SQLAlchemy |
6 | flask | |
7 | lxml==4.0.0;platform_python_implementation=="PyPy" | |
8 | 13 | pyexcel-xlsx>=0.4.1 |
9 | 14 | pyexcel-xls>=0.4.1 |
10 | 15 | pyexcel-text>=0.2.0 |
11 | 16 | psutil |
12 | mock | |
13 | moban | |
14 | black;python_version>="3.6" | |
15 | isort;python_version>="3.6" | |
17 | pyexcel-pygal |
18 | 18 | |
19 | 19 | book = p.Book(book_dict) |
20 | 20 | 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"]) |
4 | 4 | from datetime import datetime |
5 | 5 | from textwrap import dedent |
6 | 6 | |
7 | import psutil | |
7 | 8 | import pyexcel as p |
8 | 9 | from _compact import StringIO, OrderedDict |
9 | 10 | |
10 | import psutil | |
11 | 11 | from nose.tools import eq_ |
12 | 12 | |
13 | 13 |
0 | 0 | import warnings |
1 | from unittest.mock import patch | |
1 | 2 | |
2 | 3 | import pyexcel.ext.ods |
3 | 4 | import pyexcel.ext.xls |
4 | 5 | import pyexcel.ext.ods3 |
5 | 6 | import pyexcel.ext.text |
6 | 7 | import pyexcel.ext.xlsx |
7 | ||
8 | from mock import patch | |
9 | 8 | |
10 | 9 | try: |
11 | 10 | reload |
0 | 0 | import os |
1 | 1 | from textwrap import dedent |
2 | 2 | |
3 | import pyexcel.constants as constants | |
4 | 3 | from pyexcel import Book, Sheet, get_book |
4 | from pyexcel import constants as constants | |
5 | 5 | from _compact import StringIO, OrderedDict |
6 | 6 | from pyexcel.source import AbstractSource, MemorySourceMixin |
7 | 7 | from pyexcel.plugins import SourceInfo |
221 | 221 | save_as(dest_file_name=self.testfile, adict=self.data) |
222 | 222 | |
223 | 223 | def test_general_usage(self): |
224 | """format a row | |
225 | """ | |
224 | """format a row""" | |
226 | 225 | r = get_sheet(file_name=self.testfile) |
227 | 226 | r.row.format(1, str) |
228 | 227 | c1 = r.row_at(1) |
230 | 229 | self.assertEqual(c1, c2) |
231 | 230 | |
232 | 231 | def test_general_usage2(self): |
233 | """format a row | |
234 | """ | |
232 | """format a row""" | |
235 | 233 | r = get_sheet(file_name=self.testfile) |
236 | 234 | r.row.format(1, str) |
237 | 235 | c1 = r.row_at(1) |
239 | 237 | self.assertEqual(c1, c2) |
240 | 238 | |
241 | 239 | def test_one_formatter_for_two_rows(self): |
242 | """format more than one row | |
243 | """ | |
240 | """format more than one row""" | |
244 | 241 | r = get_sheet(file_name=self.testfile) |
245 | 242 | r.row.format([1, 2], str) |
246 | 243 | c1 = r.row_at(2) |
0 | 0 | import os |
1 | 1 | |
2 | import pyexcel.internal.garbagecollector as gc | |
3 | 2 | from pyexcel import iget_array |
3 | from pyexcel.internal import garbagecollector as gc | |
4 | 4 | |
5 | 5 | from nose.tools import eq_ |
6 | 6 |
0 | 0 | from textwrap import dedent |
1 | 1 | from unittest import TestCase |
2 | from unittest.mock import MagicMock, patch | |
2 | 3 | |
3 | 4 | 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 | |
7 | 6 | |
8 | 7 | |
9 | 8 | class TestHttpBookSource(TestCase): |
11 | 10 | self.patcher = patch("pyexcel._compact.request.urlopen") |
12 | 11 | mock_open = self.patcher.start() |
13 | 12 | 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")) | |
15 | 17 | io.info = self.mocked_info |
16 | 18 | mock_open.return_value = io |
17 | 19 |
0 | from unittest.mock import MagicMock | |
1 | ||
0 | 2 | from pyexcel._compact import PY2 |
1 | 3 | from pyexcel.internal.core import _seek_at_zero |
2 | ||
3 | from mock import MagicMock | |
4 | 4 | |
5 | 5 | |
6 | 6 | def test_seek_at_zero(): |
81 | 81 | m.extend_columns(1.1) |
82 | 82 | |
83 | 83 | def test_iadd_list(self): |
84 | """Test in place add a list | |
85 | """ | |
84 | """Test in place add a list""" | |
86 | 85 | m2 = Matrix(self.data) |
87 | 86 | m2.column += self.data3 |
88 | 87 | eq_(self.result, m2.get_internal_array()) |
89 | 88 | |
90 | 89 | def test_add(self): |
91 | """Test operator add overload | |
92 | """ | |
90 | """Test operator add overload""" | |
93 | 91 | # + |
94 | 92 | m3 = Matrix(self.data) |
95 | 93 | m4 = m3.column + self.data3 |
3 | 3 | from base import PyexcelMultipleSheetBase, clean_up_files, create_sample_file1 |
4 | 4 | from _compact import OrderedDict |
5 | 5 | |
6 | from nose.tools import raises | |
6 | from nose.tools import eq_, raises | |
7 | 7 | |
8 | 8 | |
9 | 9 | class TestXlsNXlsmMultipleSheets(PyexcelMultipleSheetBase): |
445 | 445 | """ |
446 | 446 | b1 = pe.BookReader(self.testfile) |
447 | 447 | 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]) | |
454 | 450 | |
455 | 451 | @raises(TypeError) |
456 | 452 | 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] |
1 | 1 | import copy |
2 | 2 | from textwrap import dedent |
3 | 3 | |
4 | from pyexcel.sheet import Sheet | |
4 | from pyexcel.sheet import Sheet, make_names_unique | |
5 | 5 | from pyexcel.internal.meta import PyexcelObject |
6 | 6 | |
7 | 7 | from nose.tools import eq_, raises |
129 | 129 | def test_pyexcel_object(): |
130 | 130 | obj = PyexcelObject() |
131 | 131 | 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) |
0 | 0 | from pyexcel import Sheet |
1 | 1 | from _compact import OrderedDict |
2 | 2 | |
3 | from nose.tools import eq_, raises | |
3 | from nose.tools import eq_, raises, assert_not_in | |
4 | 4 | |
5 | 5 | |
6 | 6 | class TestSheetColumn: |
32 | 32 | eq_(s.column["Column 1"], ["1", "4", "7"]) |
33 | 33 | eq_(s.column["Column 3"], ["3", "6", "9"]) |
34 | 34 | |
35 | @raises(AttributeError) | |
35 | 36 | def test_add(self): |
36 | 37 | s = Sheet(self.data, "test") |
37 | 38 | s.name_columns_by_row(0) |
38 | 39 | 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 | |
40 | 49 | eq_(s.column.Column_4, [10, 11, 12]) |
41 | 50 | |
42 | 51 | @raises(TypeError) |
131 | 140 | s = Sheet(self.data, "test") |
132 | 141 | s.name_columns_by_row(2) |
133 | 142 | 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 | |
135 | 152 | assert s.column["Column 4"] == [10, 11, 12] |
136 | 153 | |
137 | 154 | def test_dot_notation(self): |
0 | 0 | from pyexcel import Sheet |
1 | 1 | from _compact import OrderedDict |
2 | 2 | |
3 | from nose.tools import eq_, raises | |
3 | from nose.tools import eq_, raises, assert_not_in | |
4 | 4 | |
5 | 5 | |
6 | 6 | class TestSheetRow: |
71 | 71 | s = Sheet(self.data, "test") |
72 | 72 | s.name_rows_by_column(0) |
73 | 73 | 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 | |
75 | 83 | assert s.row["Row 5"] == [10, 11, 12] |
76 | 84 | |
77 | 85 | def test_dot_notation(self): |
3 | 3 | from pyexcel.internal.generators import SheetStream |
4 | 4 | from pyexcel.plugins.sources.output_to_memory import WriteSheetToMemory |
5 | 5 | |
6 | import pyexcel_io.manager as manager | |
7 | 6 | from nose.tools import eq_ |
7 | from pyexcel_io import manager as manager | |
8 | 8 | |
9 | 9 | |
10 | 10 | def test_save_to(): |
0 | 0 | import copy |
1 | 1 | |
2 | 2 | from pyexcel import Sheet, load_from_dict, load_from_records |
3 | from _compact import OrderedDict | |
4 | 3 | |
5 | 4 | from nose.tools import eq_, raises |
6 | 5 | |
125 | 124 | s.paste((1, 2)) |
126 | 125 | |
127 | 126 | |
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 | ||
404 | 127 | class TestUniquenessOfNames: |
405 | 128 | def test_column_names(self): |
406 | 129 | data = [ |