tableio 0.2__tar.gz → 0.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tableio-0.2/tableio.egg-info → tableio-0.4}/PKG-INFO +23 -17
- {tableio-0.2 → tableio-0.4}/README.md +2 -2
- {tableio-0.2 → tableio-0.4}/README_pypi.md +17 -13
- {tableio-0.2 → tableio-0.4}/pyproject.toml +1 -1
- {tableio-0.2 → tableio-0.4}/setup.py +6 -4
- tableio-0.4/src/tableio/__init__.py +47 -0
- tableio-0.4/src/tableio/_archive_rewrite.py +80 -0
- {tableio-0.2 → tableio-0.4}/src/tableio/capability.py +14 -0
- {tableio-0.2 → tableio-0.4}/src/tableio/factory.py +61 -12
- {tableio-0.2 → tableio-0.4}/src/tableio/reg_pkg_formats.py +6 -1
- {tableio-0.2 → tableio-0.4}/src/tableio/tableio.py +161 -110
- {tableio-0.2 → tableio-0.4}/src/tableio/tableio_excel_openpyxl.py +77 -37
- tableio-0.4/src/tableio/tableio_excel_pylightxl.py +731 -0
- tableio-0.4/src/tableio/tableio_excel_xlsxwriter.py +407 -0
- {tableio-0.2 → tableio-0.4}/src/tableio/tableio_excelbased.py +49 -2
- {tableio-0.2 → tableio-0.4}/src/tableio/tableio_ods_odfdo.py +118 -35
- {tableio-0.2 → tableio-0.4}/src/tableio/tableio_spreadsheetbased.py +220 -26
- tableio-0.4/src/tableio/tableio_types.py +61 -0
- {tableio-0.2 → tableio-0.4}/src/tableio/valueconversion.py +3 -2
- {tableio-0.2 → tableio-0.4/tableio.egg-info}/PKG-INFO +23 -17
- {tableio-0.2 → tableio-0.4}/tableio.egg-info/SOURCES.txt +4 -0
- tableio-0.4/tableio.egg-info/requires.txt +10 -0
- tableio-0.2/src/tableio/__init__.py +0 -6
- tableio-0.2/tableio.egg-info/requires.txt +0 -8
- {tableio-0.2 → tableio-0.4}/LICENSE.txt +0 -0
- {tableio-0.2 → tableio-0.4}/setup.cfg +0 -0
- {tableio-0.2 → tableio-0.4}/src/tableio/color.py +0 -0
- {tableio-0.2 → tableio-0.4}/src/tableio/optional_args.py +0 -0
- {tableio-0.2 → tableio-0.4}/src/tableio/py.typed +0 -0
- {tableio-0.2 → tableio-0.4}/src/tableio/tableio_csv.py +0 -0
- {tableio-0.2 → tableio-0.4}/src/tableio/tableio_mformat.py +0 -0
- {tableio-0.2 → tableio-0.4}/src/tableio/tableio_mformatbased.py +0 -0
- {tableio-0.2 → tableio-0.4}/src/tableio/tableio_textbased.py +0 -0
- {tableio-0.2 → tableio-0.4}/src/tableio/value_type.py +0 -0
- {tableio-0.2 → tableio-0.4}/tableio.egg-info/dependency_links.txt +0 -0
- {tableio-0.2 → tableio-0.4}/tableio.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tableio
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4
|
|
4
4
|
Summary: Uniform way to write table data to and read from different file formats
|
|
5
5
|
Author: Tom Björkholm
|
|
6
6
|
Author-email: Tom Björkholm <klausuler_linnet0q@icloud.com>
|
|
@@ -13,12 +13,14 @@ Description-Content-Type: text/markdown
|
|
|
13
13
|
License-File: LICENSE.txt
|
|
14
14
|
Requires-Dist: pip>=26.0.1
|
|
15
15
|
Requires-Dist: setuptools>=82.0.1
|
|
16
|
-
Requires-Dist: build>=1.4.
|
|
16
|
+
Requires-Dist: build>=1.4.2
|
|
17
17
|
Requires-Dist: wheel>=0.46.3
|
|
18
18
|
Requires-Dist: mformat-ext>=0.6
|
|
19
19
|
Requires-Dist: openpyxl>=3.1.5
|
|
20
|
-
Requires-Dist: types-openpyxl>=3.1.5.
|
|
21
|
-
Requires-Dist: odfdo>=3.22.
|
|
20
|
+
Requires-Dist: types-openpyxl>=3.1.5.20260402
|
|
21
|
+
Requires-Dist: odfdo>=3.22.4
|
|
22
|
+
Requires-Dist: xlsxwriter>=3.2.9
|
|
23
|
+
Requires-Dist: pylightxl>=1.61
|
|
22
24
|
Dynamic: author
|
|
23
25
|
Dynamic: license-file
|
|
24
26
|
Dynamic: requires-dist
|
|
@@ -30,15 +32,15 @@ The tableio package contains a number of classes providing a uniform way for
|
|
|
30
32
|
a python program to write table data (rows of columns) to and read table data
|
|
31
33
|
from a number of different common file formats.
|
|
32
34
|
|
|
33
|
-
An increasing number of users want output from programs to
|
|
35
|
+
An increasing number of users want output from programs to be in a format
|
|
34
36
|
like a spreadsheet, and not the old fashoned raw text files. Similarly,
|
|
35
37
|
many users want the data they feed into programs to be in a particular format
|
|
36
|
-
(like a spreadsheet). The tableio
|
|
38
|
+
(like a spreadsheet). The tableio package tries to make it easier for the
|
|
37
39
|
programmer to fullfil requests like that.
|
|
38
40
|
|
|
39
|
-
The primary intended use is for
|
|
40
|
-
|
|
41
|
-
formats.
|
|
41
|
+
The primary intended use is for data table output from a python program
|
|
42
|
+
and data table input to a python program, where the programmer would like
|
|
43
|
+
the user to be able to select the input and output file formats.
|
|
42
44
|
|
|
43
45
|
The support for spreadsheets is for reading and writing data. There is no
|
|
44
46
|
intention to support reading or writing formulas. There is no support for
|
|
@@ -68,6 +70,8 @@ The currently supported formats are:
|
|
|
68
70
|
|-------------|----------------|-----------|----------|
|
|
69
71
|
| CSV | csv | yes | yes |
|
|
70
72
|
| Excel | OpenPyXL | yes | yes |
|
|
73
|
+
| Excel | XlsxWriter | yes | - |
|
|
74
|
+
| Excel | pylightxl | yes | yes |
|
|
71
75
|
| ODS | odfdo | yes | yes |
|
|
72
76
|
| HTML | mformat | yes | - |
|
|
73
77
|
| LaTeX | mformat | yes | - |
|
|
@@ -109,6 +113,8 @@ The main features are:
|
|
|
109
113
|
- writing a table to a specific location in a spreadsheet (specified by a box)
|
|
110
114
|
- reading table data from a specific location in a spreadsheet (specified by a box)
|
|
111
115
|
- using multiple sheets in spreadsheets
|
|
116
|
+
- finding location where some data is present in spreadsheet and doing modifications
|
|
117
|
+
at that position or at positions relative to that position.
|
|
112
118
|
|
|
113
119
|
## Example programs
|
|
114
120
|
|
|
@@ -118,8 +124,6 @@ example programs:
|
|
|
118
124
|
|
|
119
125
|
## API documentation
|
|
120
126
|
|
|
121
|
-
Be aware that this is still in early development. The APIs may change between versions.
|
|
122
|
-
|
|
123
127
|
You can find the public API documentation at [https://bitbucket.org/tom-bjorkholm/table-io/src/master/doc/api.md](https://bitbucket.org/tom-bjorkholm/table-io/src/master/doc/api.md)
|
|
124
128
|
|
|
125
129
|
You can find the protected API documentation at [https://bitbucket.org/tom-bjorkholm/table-io/src/master/doc/protected_api.md](https://bitbucket.org/tom-bjorkholm/table-io/src/master/doc/protected_api.md)
|
|
@@ -132,15 +136,17 @@ a better start by reading the examples.
|
|
|
132
136
|
|
|
133
137
|
## Version history
|
|
134
138
|
|
|
135
|
-
| Version | Date | Python version | Comment
|
|
136
|
-
|
|
137
|
-
| 0.
|
|
138
|
-
| 0.
|
|
139
|
+
| Version | Date | Python version | Comment |
|
|
140
|
+
|---------|-------------|----------------|-----------------------------|
|
|
141
|
+
| 0.4 | 2026 Apr 06 | 3.12 or newer | Fix .xlsx and .ods syntax |
|
|
142
|
+
| 0.3 | 2026 Apr 01 | 3.12 or newer | Improved API, more backends |
|
|
143
|
+
| 0.2 | 2026 Mar 28 | 3.12 or newer | More features, API changes |
|
|
144
|
+
| 0.1 | 2026 Mar 23 | 3.12 or newer | First released version |
|
|
139
145
|
|
|
140
146
|
## Test summary
|
|
141
147
|
|
|
142
|
-
- Test result:
|
|
148
|
+
- Test result: 1126 passed in 17s
|
|
143
149
|
- No flake8 warnings.
|
|
144
150
|
- No mypy errors found.
|
|
145
|
-
- Built version(s): 0.
|
|
151
|
+
- Built version(s): 0.4
|
|
146
152
|
- Build and test using Python 3.14.3
|
|
@@ -93,8 +93,8 @@ information about the build system. This README can also be viewed at
|
|
|
93
93
|
|
|
94
94
|
## Test summary
|
|
95
95
|
|
|
96
|
-
- Test result:
|
|
96
|
+
- Test result: 1126 passed in 17s
|
|
97
97
|
- No flake8 warnings.
|
|
98
98
|
- No mypy errors found.
|
|
99
|
-
- Built version(s): 0.
|
|
99
|
+
- Built version(s): 0.4
|
|
100
100
|
- Build and test using Python 3.14.3
|
|
@@ -4,15 +4,15 @@ The tableio package contains a number of classes providing a uniform way for
|
|
|
4
4
|
a python program to write table data (rows of columns) to and read table data
|
|
5
5
|
from a number of different common file formats.
|
|
6
6
|
|
|
7
|
-
An increasing number of users want output from programs to
|
|
7
|
+
An increasing number of users want output from programs to be in a format
|
|
8
8
|
like a spreadsheet, and not the old fashoned raw text files. Similarly,
|
|
9
9
|
many users want the data they feed into programs to be in a particular format
|
|
10
|
-
(like a spreadsheet). The tableio
|
|
10
|
+
(like a spreadsheet). The tableio package tries to make it easier for the
|
|
11
11
|
programmer to fullfil requests like that.
|
|
12
12
|
|
|
13
|
-
The primary intended use is for
|
|
14
|
-
|
|
15
|
-
formats.
|
|
13
|
+
The primary intended use is for data table output from a python program
|
|
14
|
+
and data table input to a python program, where the programmer would like
|
|
15
|
+
the user to be able to select the input and output file formats.
|
|
16
16
|
|
|
17
17
|
The support for spreadsheets is for reading and writing data. There is no
|
|
18
18
|
intention to support reading or writing formulas. There is no support for
|
|
@@ -42,6 +42,8 @@ The currently supported formats are:
|
|
|
42
42
|
|-------------|----------------|-----------|----------|
|
|
43
43
|
| CSV | csv | yes | yes |
|
|
44
44
|
| Excel | OpenPyXL | yes | yes |
|
|
45
|
+
| Excel | XlsxWriter | yes | - |
|
|
46
|
+
| Excel | pylightxl | yes | yes |
|
|
45
47
|
| ODS | odfdo | yes | yes |
|
|
46
48
|
| HTML | mformat | yes | - |
|
|
47
49
|
| LaTeX | mformat | yes | - |
|
|
@@ -83,6 +85,8 @@ The main features are:
|
|
|
83
85
|
- writing a table to a specific location in a spreadsheet (specified by a box)
|
|
84
86
|
- reading table data from a specific location in a spreadsheet (specified by a box)
|
|
85
87
|
- using multiple sheets in spreadsheets
|
|
88
|
+
- finding location where some data is present in spreadsheet and doing modifications
|
|
89
|
+
at that position or at positions relative to that position.
|
|
86
90
|
|
|
87
91
|
## Example programs
|
|
88
92
|
|
|
@@ -92,8 +96,6 @@ example programs:
|
|
|
92
96
|
|
|
93
97
|
## API documentation
|
|
94
98
|
|
|
95
|
-
Be aware that this is still in early development. The APIs may change between versions.
|
|
96
|
-
|
|
97
99
|
You can find the public API documentation at [https://bitbucket.org/tom-bjorkholm/table-io/src/master/doc/api.md](https://bitbucket.org/tom-bjorkholm/table-io/src/master/doc/api.md)
|
|
98
100
|
|
|
99
101
|
You can find the protected API documentation at [https://bitbucket.org/tom-bjorkholm/table-io/src/master/doc/protected_api.md](https://bitbucket.org/tom-bjorkholm/table-io/src/master/doc/protected_api.md)
|
|
@@ -106,15 +108,17 @@ a better start by reading the examples.
|
|
|
106
108
|
|
|
107
109
|
## Version history
|
|
108
110
|
|
|
109
|
-
| Version | Date | Python version | Comment
|
|
110
|
-
|
|
111
|
-
| 0.
|
|
112
|
-
| 0.
|
|
111
|
+
| Version | Date | Python version | Comment |
|
|
112
|
+
|---------|-------------|----------------|-----------------------------|
|
|
113
|
+
| 0.4 | 2026 Apr 06 | 3.12 or newer | Fix .xlsx and .ods syntax |
|
|
114
|
+
| 0.3 | 2026 Apr 01 | 3.12 or newer | Improved API, more backends |
|
|
115
|
+
| 0.2 | 2026 Mar 28 | 3.12 or newer | More features, API changes |
|
|
116
|
+
| 0.1 | 2026 Mar 23 | 3.12 or newer | First released version |
|
|
113
117
|
|
|
114
118
|
## Test summary
|
|
115
119
|
|
|
116
|
-
- Test result:
|
|
120
|
+
- Test result: 1126 passed in 17s
|
|
117
121
|
- No flake8 warnings.
|
|
118
122
|
- No mypy errors found.
|
|
119
|
-
- Built version(s): 0.
|
|
123
|
+
- Built version(s): 0.4
|
|
120
124
|
- Build and test using Python 3.14.3
|
|
@@ -5,7 +5,7 @@ from setuptools import setup
|
|
|
5
5
|
|
|
6
6
|
setup(
|
|
7
7
|
name='tableio',
|
|
8
|
-
version='0.
|
|
8
|
+
version='0.4',
|
|
9
9
|
description='Uniform way to write table data to and read from different ' \
|
|
10
10
|
'file formats',
|
|
11
11
|
author='Tom Björkholm',
|
|
@@ -17,11 +17,13 @@ setup(
|
|
|
17
17
|
install_requires=[ # pylint: disable=duplicate-code
|
|
18
18
|
'pip >= 26.0.1',
|
|
19
19
|
'setuptools >= 82.0.1',
|
|
20
|
-
'build >= 1.4.
|
|
20
|
+
'build >= 1.4.2',
|
|
21
21
|
'wheel >= 0.46.3',
|
|
22
22
|
'mformat-ext >= 0.6',
|
|
23
23
|
'openpyxl >= 3.1.5',
|
|
24
|
-
'types-openpyxl >= 3.1.5.
|
|
25
|
-
'odfdo >= 3.22.
|
|
24
|
+
'types-openpyxl >= 3.1.5.20260402',
|
|
25
|
+
'odfdo >= 3.22.4',
|
|
26
|
+
'xlsxwriter >= 3.2.9',
|
|
27
|
+
'pylightxl >= 1.61'
|
|
26
28
|
]
|
|
27
29
|
)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#! /usr/local/bin/python3
|
|
2
|
+
"""Curated top-level public API for the tableio package."""
|
|
3
|
+
|
|
4
|
+
# Copyright (c) 2026 Tom Björkholm
|
|
5
|
+
# MIT License
|
|
6
|
+
#
|
|
7
|
+
|
|
8
|
+
from tableio.capability import CAP_IGNORABLE, CAP_NEEDED, CAP_NOT_USED, \
|
|
9
|
+
Capabilities
|
|
10
|
+
from tableio.color import Color
|
|
11
|
+
from tableio.factory import create_tableio, filter_args_tableio, \
|
|
12
|
+
list_implementations_tableio, list_registered_tableio
|
|
13
|
+
from tableio.optional_args import CsvDialect, OptionalArgs, OptionalArgsDict
|
|
14
|
+
from tableio.tableio import Box, FileAccess, Position, TableIO
|
|
15
|
+
from tableio.value_type import CellT, DictData, DictDataMap, Fmt, \
|
|
16
|
+
FmtDictData, FmtDictRow, FmtListData, FmtListRow, ListData, \
|
|
17
|
+
ListDataSeq, ReadResult, Value, ValueFmt
|
|
18
|
+
|
|
19
|
+
__all__ = ['create_tableio',
|
|
20
|
+
'TableIO',
|
|
21
|
+
'FileAccess',
|
|
22
|
+
'Box',
|
|
23
|
+
'Position',
|
|
24
|
+
'OptionalArgs',
|
|
25
|
+
'OptionalArgsDict',
|
|
26
|
+
'Value',
|
|
27
|
+
'ListData',
|
|
28
|
+
'ListDataSeq',
|
|
29
|
+
'DictData',
|
|
30
|
+
'DictDataMap',
|
|
31
|
+
'ReadResult',
|
|
32
|
+
'Fmt',
|
|
33
|
+
'ValueFmt',
|
|
34
|
+
'FmtListRow',
|
|
35
|
+
'FmtDictRow',
|
|
36
|
+
'FmtListData',
|
|
37
|
+
'FmtDictData',
|
|
38
|
+
'CellT',
|
|
39
|
+
'Color',
|
|
40
|
+
'Capabilities',
|
|
41
|
+
'CAP_NEEDED',
|
|
42
|
+
'CAP_NOT_USED',
|
|
43
|
+
'CAP_IGNORABLE',
|
|
44
|
+
'filter_args_tableio',
|
|
45
|
+
'list_registered_tableio',
|
|
46
|
+
'list_implementations_tableio',
|
|
47
|
+
'CsvDialect']
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#! /usr/bin/env python3
|
|
2
|
+
"""Helpers for rewriting spreadsheet ZIP archives safely.
|
|
3
|
+
|
|
4
|
+
Spreadsheet writers first save library output to a temporary archive.
|
|
5
|
+
These helpers then build a rewritten copy in a second temporary archive
|
|
6
|
+
and replace the first archive only after both ZIP files are closed.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
# Copyright (c) 2026 Tom Björkholm
|
|
10
|
+
# MIT License
|
|
11
|
+
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from tempfile import NamedTemporaryFile
|
|
14
|
+
from typing import Callable, Optional
|
|
15
|
+
from zipfile import ZIP_DEFLATED, ZipFile, ZipInfo
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def temporary_output_path(source_path: Path, suffix: str) -> Path:
|
|
19
|
+
"""Return one missing temporary path next to ``source_path``.
|
|
20
|
+
|
|
21
|
+
The temporary file is created in the same directory as
|
|
22
|
+
``source_path`` so a later ``Path.replace()`` stays on the same
|
|
23
|
+
filesystem. The file name uses ``suffix`` and the returned path does
|
|
24
|
+
not exist when this function returns.
|
|
25
|
+
"""
|
|
26
|
+
with NamedTemporaryFile(delete=False, dir=source_path.parent,
|
|
27
|
+
suffix=suffix) as temp_file:
|
|
28
|
+
temp_path = Path(temp_file.name)
|
|
29
|
+
temp_path.unlink()
|
|
30
|
+
return temp_path
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def rewrite_zip_archive(
|
|
34
|
+
archive_path: Path,
|
|
35
|
+
rewrite_entry: Callable[[ZipInfo, bytes], Optional[bytes]],
|
|
36
|
+
extra_entries: Optional[dict[str, bytes]] = None) -> None:
|
|
37
|
+
"""Rewrite one ZIP archive by copying it into a new archive.
|
|
38
|
+
|
|
39
|
+
The original archive at ``archive_path`` is read entry by entry and
|
|
40
|
+
a rewritten archive is written to a sibling temporary file. The
|
|
41
|
+
callback receives each original ``ZipInfo`` together with its bytes.
|
|
42
|
+
Returning ``None`` drops the entry. Any mapping passed in
|
|
43
|
+
``extra_entries`` is appended after copied entries, except for names
|
|
44
|
+
that were already written.
|
|
45
|
+
|
|
46
|
+
The original archive is replaced only after both ZIP files have been
|
|
47
|
+
closed. This avoids replacing an archive while it is still open,
|
|
48
|
+
which is a safer pattern on Windows as well as on Unix-like systems.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
archive_path: Path to the archive to rewrite in place.
|
|
52
|
+
rewrite_entry: Callback invoked once for each original ZIP entry.
|
|
53
|
+
The callback receives the original ``ZipInfo`` together with
|
|
54
|
+
the entry bytes. It returns replacement bytes for the output
|
|
55
|
+
archive, or ``None`` to drop the entry completely.
|
|
56
|
+
extra_entries: Optional mapping of extra archive members to add
|
|
57
|
+
after copying rewritten entries. Names that were already
|
|
58
|
+
written are left unchanged.
|
|
59
|
+
"""
|
|
60
|
+
temp_path = temporary_output_path(archive_path, archive_path.suffix)
|
|
61
|
+
try:
|
|
62
|
+
with ZipFile(archive_path, 'r') as source_zip, \
|
|
63
|
+
ZipFile(temp_path, 'w',
|
|
64
|
+
compression=ZIP_DEFLATED) as target_zip:
|
|
65
|
+
written_names: set[str] = set()
|
|
66
|
+
for item in source_zip.infolist():
|
|
67
|
+
data = rewrite_entry(item, source_zip.read(item.filename))
|
|
68
|
+
if data is None:
|
|
69
|
+
continue
|
|
70
|
+
target_zip.writestr(item, data)
|
|
71
|
+
written_names.add(item.filename)
|
|
72
|
+
if extra_entries is not None:
|
|
73
|
+
for entry_name, entry_data in extra_entries.items():
|
|
74
|
+
if entry_name in written_names:
|
|
75
|
+
continue
|
|
76
|
+
target_zip.writestr(entry_name, entry_data)
|
|
77
|
+
temp_path.replace(archive_path)
|
|
78
|
+
finally:
|
|
79
|
+
if temp_path.exists():
|
|
80
|
+
temp_path.unlink()
|
|
@@ -87,6 +87,9 @@ class Capabilities(NamedTuple):
|
|
|
87
87
|
multi_sheet: SingleCapability = SingleCapability()
|
|
88
88
|
"""The reader/writer class can read from or write to multiple sheets."""
|
|
89
89
|
|
|
90
|
+
can_find_value_position: SingleCapability = SingleCapability()
|
|
91
|
+
"""The reader/writer class can find the position of a value."""
|
|
92
|
+
|
|
90
93
|
|
|
91
94
|
def single_capability_match(offered: SingleCapability,
|
|
92
95
|
will_use: SingleCapability,
|
|
@@ -233,3 +236,14 @@ CAP_UNSUPPORTED = SingleCapability(supported=False,
|
|
|
233
236
|
different location would not make sense. Thus the only sensible thing
|
|
234
237
|
to do is to raise an exception.
|
|
235
238
|
"""
|
|
239
|
+
|
|
240
|
+
CAP_ALL_IMPLEMENTED = Capabilities(can_read=CAP_IMPLEMENTED,
|
|
241
|
+
can_write=CAP_IMPLEMENTED,
|
|
242
|
+
can_fmt_row=CAP_IMPLEMENTED,
|
|
243
|
+
can_fmt_value=CAP_IMPLEMENTED,
|
|
244
|
+
filtered_data_range=CAP_IMPLEMENTED,
|
|
245
|
+
can_write_box=CAP_IMPLEMENTED,
|
|
246
|
+
can_read_box=CAP_IMPLEMENTED,
|
|
247
|
+
can_write_highlight=CAP_IMPLEMENTED,
|
|
248
|
+
multi_sheet=CAP_IMPLEMENTED,
|
|
249
|
+
can_find_value_position=CAP_IMPLEMENTED)
|
|
@@ -51,6 +51,37 @@ class TableIOFactoryNoCapabilityMatch(ValueError):
|
|
|
51
51
|
"""
|
|
52
52
|
|
|
53
53
|
|
|
54
|
+
class InsufficientCapabilities(ValueError):
|
|
55
|
+
"""Raised when requested capabilities contradict requested file access.
|
|
56
|
+
|
|
57
|
+
Error raised when the caller supplies both file access and an explicit
|
|
58
|
+
Capabilities object, but the requested capabilities do not include the
|
|
59
|
+
capability implied by that access mode. For example, READ requires
|
|
60
|
+
can_read, CREATE requires can_write, and UPDATE requires both.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _check_capabilities_for_file_access(
|
|
65
|
+
file_access: FileAccess,
|
|
66
|
+
capabilities: Optional[Capabilities]) -> None:
|
|
67
|
+
"""Raise if explicit capabilities not enough for requested access mode."""
|
|
68
|
+
if capabilities is None:
|
|
69
|
+
return
|
|
70
|
+
if file_access == FileAccess.READ and not capabilities.can_read.supported:
|
|
71
|
+
msg = 'FileAccess.READ requires can_read capability.'
|
|
72
|
+
raise InsufficientCapabilities(msg)
|
|
73
|
+
if file_access == FileAccess.CREATE and \
|
|
74
|
+
not capabilities.can_write.supported:
|
|
75
|
+
msg = 'FileAccess.CREATE requires can_write capability.'
|
|
76
|
+
raise InsufficientCapabilities(msg)
|
|
77
|
+
if file_access == FileAccess.UPDATE and (
|
|
78
|
+
not capabilities.can_write.supported or
|
|
79
|
+
not capabilities.can_read.supported):
|
|
80
|
+
msg = 'FileAccess.UPDATE requires both can_read and '
|
|
81
|
+
msg += 'can_write capabilities.'
|
|
82
|
+
raise InsufficientCapabilities(msg)
|
|
83
|
+
|
|
84
|
+
|
|
54
85
|
@total_ordering
|
|
55
86
|
class ImplPrio(NamedTuple):
|
|
56
87
|
"""Priority of an implementation."""
|
|
@@ -243,6 +274,17 @@ class FactoryFormatInfo:
|
|
|
243
274
|
raise TableIOFactoryNoCapabilityMatch(msg)
|
|
244
275
|
return ret
|
|
245
276
|
|
|
277
|
+
def correct_implementation_name(self, implementation_name: str) -> str:
|
|
278
|
+
"""Correct the implementation name to the correct case."""
|
|
279
|
+
if implementation_name in self._registry:
|
|
280
|
+
return implementation_name
|
|
281
|
+
if implementation_name.lower() in self._lower2correct:
|
|
282
|
+
return self._lower2correct[implementation_name.lower()]
|
|
283
|
+
raise TableIOFactoryNoSuchError(
|
|
284
|
+
f'Implementation "{implementation_name}" is not registered. ' +
|
|
285
|
+
'Available implementations: ' +
|
|
286
|
+
f'{", ".join(list(self._registry.keys()))}')
|
|
287
|
+
|
|
246
288
|
|
|
247
289
|
class TableIOFactory:
|
|
248
290
|
"""Factory class for creating instances of TableIO subclasses.
|
|
@@ -339,6 +381,7 @@ class TableIOFactory:
|
|
|
339
381
|
An instance of the requested TableIO subclass.
|
|
340
382
|
Intended to be used as context manager, using a with statement.
|
|
341
383
|
Raises:
|
|
384
|
+
InsufficientCapabilities: If capabilities contradict file_access.
|
|
342
385
|
TableIOFactoryNoSuchError: If the format_name is not registered
|
|
343
386
|
or the implementation name is not
|
|
344
387
|
registered.
|
|
@@ -357,7 +400,17 @@ class TableIOFactory:
|
|
|
357
400
|
file_access: FileAccess, args: OptionalArgs = None,
|
|
358
401
|
implementation: Optional[str] = None,
|
|
359
402
|
capabilities: Optional[Capabilities] = None) -> TableIO:
|
|
360
|
-
"""Internally create an instance of a registered subclass.
|
|
403
|
+
"""Internally create an instance of a registered subclass.
|
|
404
|
+
|
|
405
|
+
Raises:
|
|
406
|
+
InsufficientCapabilities: If capabilities contradict file_access.
|
|
407
|
+
TableIOFactoryNoSuchError: If the format_name is not registered
|
|
408
|
+
or the implementation name is not
|
|
409
|
+
registered.
|
|
410
|
+
TableIOFactoryNoCapabilityMatch: If the capabilities cannot be
|
|
411
|
+
matched to any implementation.
|
|
412
|
+
"""
|
|
413
|
+
_check_capabilities_for_file_access(file_access, capabilities)
|
|
361
414
|
correct_name: Optional[str] = None
|
|
362
415
|
if format_name in self._formats:
|
|
363
416
|
correct_name = format_name
|
|
@@ -371,24 +424,19 @@ class TableIOFactory:
|
|
|
371
424
|
)
|
|
372
425
|
assert correct_name is not None
|
|
373
426
|
format_info: FactoryFormatInfo = self._formats[correct_name]
|
|
374
|
-
best_matches = format_info.best_match_names(
|
|
375
|
-
|
|
427
|
+
best_matches = format_info.best_match_names(
|
|
428
|
+
capabilities=capabilities, empty_is_ok=False)
|
|
376
429
|
if implementation is not None:
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
raise TableIOFactoryNoSuchError(
|
|
380
|
-
f'Implementation "{implementation}" is not registered. ' +
|
|
381
|
-
'Available implementations: ' +
|
|
382
|
-
f'{", ".join(list(fikeys))}'
|
|
383
|
-
)
|
|
430
|
+
corrected_implementation = \
|
|
431
|
+
format_info.correct_implementation_name(implementation)
|
|
384
432
|
bmatches_impls = [impl.implementation
|
|
385
433
|
for impl in best_matches.combined()]
|
|
386
|
-
if
|
|
434
|
+
if corrected_implementation not in bmatches_impls:
|
|
387
435
|
msg3 = f'Implementation "{implementation}" '
|
|
388
436
|
msg3 += 'does not match the capabilities. Available matches: '
|
|
389
437
|
msg3 += f'{", ".join(bmatches_impls)}'
|
|
390
438
|
raise TableIOFactoryNoCapabilityMatch(msg3)
|
|
391
|
-
chosen_impl =
|
|
439
|
+
chosen_impl = corrected_implementation
|
|
392
440
|
else:
|
|
393
441
|
chosen_impl = best_matches.combined()[0].implementation
|
|
394
442
|
format_class = format_info._registry[chosen_impl] # pylint: disable=protected-access # noqa: E501
|
|
@@ -666,6 +714,7 @@ def create_tableio(format_name: str, # pylint: disable=too-many-arguments, too-
|
|
|
666
714
|
Returns:
|
|
667
715
|
An instance of the requested TableIO subclass.
|
|
668
716
|
Raises:
|
|
717
|
+
InsufficientCapabilities: If capabilities contradict file_access.
|
|
669
718
|
TableIOFactoryNoSuchError: If the format_name or implementation
|
|
670
719
|
name is not registered.
|
|
671
720
|
TableIOFactoryNoCapabilityMatch: If the capabilities cannot be
|
|
@@ -16,6 +16,8 @@ from tableio.tableio_mformat import TableIOMformatHtml, TableIOMformatMd, \
|
|
|
16
16
|
TableIOMformatRtf
|
|
17
17
|
from tableio.tableio_csv import TableIOCsv
|
|
18
18
|
from tableio.tableio_excel_openpyxl import TableIOExcelOpenPyXL
|
|
19
|
+
from tableio.tableio_excel_pylightxl import TableIOExcelPylightxl
|
|
20
|
+
from tableio.tableio_excel_xlsxwriter import TableIOExcelXlsxWriter
|
|
19
21
|
from tableio.tableio_ods_odfdo import TableIOOdsOdfdo
|
|
20
22
|
from tableio.tableio import TableIO
|
|
21
23
|
|
|
@@ -27,5 +29,8 @@ def register_formats_in_pkg() -> list[type[TableIO]]:
|
|
|
27
29
|
TableIOMformatLatex, TableIOMformatDocx,
|
|
28
30
|
TableIOMformatOdt, TableIOMformatPdf,
|
|
29
31
|
TableIOMformatRtf, TableIOCsv,
|
|
30
|
-
TableIOExcelOpenPyXL,
|
|
32
|
+
TableIOExcelOpenPyXL,
|
|
33
|
+
TableIOExcelPylightxl,
|
|
34
|
+
TableIOExcelXlsxWriter,
|
|
35
|
+
TableIOOdsOdfdo]
|
|
31
36
|
return ret
|