tableio 1.0__tar.gz → 1.2__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.
Files changed (44) hide show
  1. {tableio-1.0 → tableio-1.2}/PKG-INFO +56 -15
  2. {tableio-1.0 → tableio-1.2}/README.md +6 -5
  3. tableio-1.0/tableio.egg-info/PKG-INFO → tableio-1.2/README_pypi.md +45 -34
  4. {tableio-1.0 → tableio-1.2}/pyproject.toml +9 -4
  5. {tableio-1.0 → tableio-1.2}/setup.py +5 -9
  6. {tableio-1.0 → tableio-1.2}/src/tableio/__init__.py +6 -1
  7. {tableio-1.0 → tableio-1.2}/src/tableio/config_data.py +8 -1
  8. {tableio-1.0 → tableio-1.2}/src/tableio/config_data_apply.py +30 -6
  9. {tableio-1.0 → tableio-1.2}/src/tableio/config_data_describe.py +13 -1
  10. {tableio-1.0 → tableio-1.2}/src/tableio/config_data_validate.py +5 -1
  11. {tableio-1.0 → tableio-1.2}/src/tableio/factory.py +28 -12
  12. tableio-1.2/src/tableio/optional_args.py +143 -0
  13. {tableio-1.0 → tableio-1.2}/src/tableio/tableio.py +17 -2
  14. {tableio-1.0 → tableio-1.2}/src/tableio/tableio_csv.py +106 -23
  15. {tableio-1.0 → tableio-1.2}/src/tableio/tableio_excel_openpyxl.py +20 -5
  16. {tableio-1.0 → tableio-1.2}/src/tableio/tableio_excel_pylightxl.py +96 -23
  17. {tableio-1.0 → tableio-1.2}/src/tableio/tableio_excel_xlsxwriter.py +18 -13
  18. {tableio-1.0 → tableio-1.2}/src/tableio/tableio_excelbased.py +11 -3
  19. {tableio-1.0 → tableio-1.2}/src/tableio/tableio_mformat.py +73 -27
  20. {tableio-1.0 → tableio-1.2}/src/tableio/tableio_mformatbased.py +30 -6
  21. {tableio-1.0 → tableio-1.2}/src/tableio/tableio_ods_odfdo.py +114 -11
  22. {tableio-1.0 → tableio-1.2}/src/tableio/tableio_spreadsheetbased.py +20 -7
  23. {tableio-1.0 → tableio-1.2}/src/tableio/tableio_textbased.py +15 -8
  24. tableio-1.2/src/tableio/timedelta_helpers.py +179 -0
  25. {tableio-1.0 → tableio-1.2}/src/tableio/value_type.py +2 -2
  26. {tableio-1.0 → tableio-1.2}/src/tableio/valueconversion.py +70 -35
  27. tableio-1.0/README_pypi.md → tableio-1.2/tableio.egg-info/PKG-INFO +75 -6
  28. {tableio-1.0 → tableio-1.2}/tableio.egg-info/SOURCES.txt +1 -0
  29. tableio-1.2/tableio.egg-info/requires.txt +6 -0
  30. tableio-1.0/src/tableio/optional_args.py +0 -77
  31. tableio-1.0/tableio.egg-info/requires.txt +0 -10
  32. {tableio-1.0 → tableio-1.2}/LICENSE.txt +0 -0
  33. {tableio-1.0 → tableio-1.2}/setup.cfg +0 -0
  34. {tableio-1.0 → tableio-1.2}/src/tableio/_archive_rewrite.py +0 -0
  35. {tableio-1.0 → tableio-1.2}/src/tableio/access_capability.py +0 -0
  36. {tableio-1.0 → tableio-1.2}/src/tableio/border_helper.py +0 -0
  37. {tableio-1.0 → tableio-1.2}/src/tableio/capability.py +0 -0
  38. {tableio-1.0 → tableio-1.2}/src/tableio/color.py +0 -0
  39. {tableio-1.0 → tableio-1.2}/src/tableio/config_data_error.py +0 -0
  40. {tableio-1.0 → tableio-1.2}/src/tableio/py.typed +0 -0
  41. {tableio-1.0 → tableio-1.2}/src/tableio/reg_pkg_formats.py +0 -0
  42. {tableio-1.0 → tableio-1.2}/src/tableio/tableio_types.py +0 -0
  43. {tableio-1.0 → tableio-1.2}/tableio.egg-info/dependency_links.txt +0 -0
  44. {tableio-1.0 → tableio-1.2}/tableio.egg-info/top_level.txt +0 -0
@@ -1,24 +1,26 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tableio
3
- Version: 1.0
3
+ Version: 1.2
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>
7
7
  License-Expression: MIT
8
- Project-URL: Source code, https://bitbucket.org/tom-bjorkholm/table-io
8
+ Project-URL: Source code, https://github.com/tom-bjorkholm/table-io
9
+ Project-URL: Documentation, https://github.com/tom-bjorkholm/table-io/blob/master/doc/
9
10
  Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3.14
10
14
  Classifier: Operating System :: OS Independent
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Typing :: Typed
11
17
  Requires-Python: >=3.12
12
18
  Description-Content-Type: text/markdown
13
19
  License-File: LICENSE.txt
14
- Requires-Dist: pip>=26.1.1
15
- Requires-Dist: setuptools>=82.0.1
16
- Requires-Dist: build>=1.5.0
17
- Requires-Dist: wheel>=0.47.0
18
- Requires-Dist: mformat-ext>=0.6
20
+ Requires-Dist: mformat-ext>=0.7
19
21
  Requires-Dist: openpyxl>=3.1.5
20
- Requires-Dist: types-openpyxl>=3.1.5.20260518
21
- Requires-Dist: odfdo>=3.22.8
22
+ Requires-Dist: types-openpyxl>=3.1.5.20260827
23
+ Requires-Dist: odfdo>=3.26.3
22
24
  Requires-Dist: xlsxwriter>=3.2.9
23
25
  Requires-Dist: pylightxl>=1.61
24
26
  Dynamic: author
@@ -109,6 +111,7 @@ The main features are:
109
111
  - int
110
112
  - float
111
113
  - datetime
114
+ - timedelta (see below)
112
115
  - writing a table as a filtered data range
113
116
  - writing a table to a specific location in a spreadsheet (specified by a box)
114
117
  - reading table data from a specific location in a spreadsheet (specified by a box)
@@ -117,17 +120,54 @@ The main features are:
117
120
  at that position or at positions relative to that position.
118
121
  - several border styles for tables
119
122
 
123
+ ## Timedelta (duration) values
124
+
125
+ Excel (OpenPyXL, XlsxWriter, pylightxl) and ODS (odfdo) store `timedelta`
126
+ values natively, as duration cells, and read them back as `timedelta`.
127
+ (OpenPyXL reads durations with millisecond precision.)
128
+ The cells are displayed as `[hh]:mm:ss` (hours do not wrap at 24, seconds
129
+ are displayed rounded to whole seconds, the stored value is exact).
130
+ Microsoft Excel has no duration type, it stores a duration as a number of
131
+ days with a time number format. So by design Excel shows the cell as for
132
+ instance `72:00:00`, but shows the value as a date and time (like
133
+ `1/3/1900 12:00:00 AM`) in the formula bar when the cell is selected. Excel
134
+ does the same for durations typed in by hand, and LibreOffice shows
135
+ `72:00:00` in both places for the same file.
136
+ Notice also that Microsoft Excel cannot display negative durations (it
137
+ shows `####`). The stored value is still correct, it is read back
138
+ correctly and LibreOffice displays it as for instance `-00:03:58`. If
139
+ negative durations must be readable in Microsoft Excel, consider storing
140
+ the magnitude and the sign in separate columns.
141
+
142
+ Formats without native duration support (CSV and all write-only document
143
+ formats) write a fallback representation, selected with the optional
144
+ argument `timedelta_fallback` (a `TimeDeltaFallback` value):
145
+
146
+ | TimeDeltaFallback | Two days, three hours and 1.5 seconds |
147
+ |-----------------------|---------------------------------------|
148
+ | `HMS_STRING` (default)| `51:00:01.5` |
149
+ | `DHMS_STRING` | `2 d 03:00:01.5` |
150
+ | `DHMS_STRING_LONG` | `2 days 03:00:01.5` |
151
+ | `WDHMS_STRING` | `2 d 03:00:01.5` (with `W w` if a week or more) |
152
+ | `WDHMS_STRING_LONG` | `2 days 03:00:01.5` (with `W weeks` if a week or more) |
153
+ | `FLOATSECONDS` | `183601.5` |
154
+
155
+ Negative durations get a leading `-`. When reading such a file the values
156
+ are strings (CSV). Convert them with `parse_timedelta()`, that accepts all
157
+ fallback formats (and `timedelta` values, so it works for all formats).
158
+ `format_timedelta()` formats a `timedelta` in any of the fallback formats.
159
+
120
160
  ## Example programs
121
161
 
122
162
  The best way to learn to use this package is to use the provided
123
163
  example programs:
124
- [https://bitbucket.org/tom-bjorkholm/table-io/src/master/example/src/example/README.md](https://bitbucket.org/tom-bjorkholm/table-io/src/master/example/src/example/README.md).
164
+ [https://github.com/tom-bjorkholm/table-io/blob/master/example/src/example/README.md](https://github.com/tom-bjorkholm/table-io/blob/master/example/src/example/README.md).
125
165
 
126
166
  ## API documentation
127
167
 
128
- 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)
168
+ You can find the public API documentation at [https://github.com/tom-bjorkholm/table-io/blob/master/doc/api.md](https://github.com/tom-bjorkholm/table-io/blob/master/doc/api.md)
129
169
 
130
- 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)
170
+ You can find the protected API documentation at [https://github.com/tom-bjorkholm/table-io/blob/master/doc/protected_api.md](https://github.com/tom-bjorkholm/table-io/blob/master/doc/protected_api.md)
131
171
  The protected API documentation is only for developers that want to
132
172
  extend the framework by adding their own classes as registered
133
173
  readers/writers to the factory.
@@ -137,9 +177,10 @@ a better start by reading the examples.
137
177
 
138
178
  ## Test summary
139
179
 
140
- - Test result: 1347 passed in 22s
180
+ - Test result: 1567 passed in 9s
141
181
  - No flake8 warnings.
142
182
  - No mypy errors found.
183
+ - No pylint warnings.
143
184
  - No python layout warnings.
144
- - Built version(s): 1.0
145
- - Build and test using Python 3.14.5
185
+ - Built version(s): 1.2
186
+ - Build and test using Python 3.14.7
@@ -29,7 +29,7 @@ formulas in the received spreadsheet).
29
29
  The tableio repo uses submodules. To clone it use the command:
30
30
 
31
31
  ````sh
32
- git clone --recurse-submodules git@bitbucket.org:tom-bjorkholm/table-io.git
32
+ git clone --recurse-submodules git@github.com:tom-bjorkholm/table-io.git
33
33
  ````
34
34
 
35
35
  If you forgot to include the `--recurse-submodules` in your `git clone` command
@@ -89,13 +89,14 @@ reports.
89
89
 
90
90
  The file `./common_build_tools/README.md` (in git submodule - see above) contains more
91
91
  information about the build system. This README can also be viewed at
92
- [https://bitbucket.org/tom-bjorkholm/common_build_tools/src/master/README.md](https://bitbucket.org/tom-bjorkholm/common_build_tools/src/master/README.md)
92
+ [https://github.com/tom-bjorkholm/common_build_tools/blob/master/README.md](https://github.com/tom-bjorkholm/common_build_tools/blob/master/README.md)
93
93
 
94
94
  ## Test summary
95
95
 
96
- - Test result: 1347 passed in 22s
96
+ - Test result: 1567 passed in 9s
97
97
  - No flake8 warnings.
98
98
  - No mypy errors found.
99
+ - No pylint warnings.
99
100
  - No python layout warnings.
100
- - Built version(s): 1.0
101
- - Build and test using Python 3.14.5
101
+ - Built version(s): 1.2
102
+ - Build and test using Python 3.14.7
@@ -1,31 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: tableio
3
- Version: 1.0
4
- Summary: Uniform way to write table data to and read from different file formats
5
- Author: Tom Björkholm
6
- Author-email: Tom Björkholm <klausuler_linnet0q@icloud.com>
7
- License-Expression: MIT
8
- Project-URL: Source code, https://bitbucket.org/tom-bjorkholm/table-io
9
- Classifier: Programming Language :: Python :: 3
10
- Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.12
12
- Description-Content-Type: text/markdown
13
- License-File: LICENSE.txt
14
- Requires-Dist: pip>=26.1.1
15
- Requires-Dist: setuptools>=82.0.1
16
- Requires-Dist: build>=1.5.0
17
- Requires-Dist: wheel>=0.47.0
18
- Requires-Dist: mformat-ext>=0.6
19
- Requires-Dist: openpyxl>=3.1.5
20
- Requires-Dist: types-openpyxl>=3.1.5.20260518
21
- Requires-Dist: odfdo>=3.22.8
22
- Requires-Dist: xlsxwriter>=3.2.9
23
- Requires-Dist: pylightxl>=1.61
24
- Dynamic: author
25
- Dynamic: license-file
26
- Dynamic: requires-dist
27
- Dynamic: requires-python
28
-
29
1
  # tableio
30
2
 
31
3
  The tableio package contains a number of classes providing a uniform way for
@@ -109,6 +81,7 @@ The main features are:
109
81
  - int
110
82
  - float
111
83
  - datetime
84
+ - timedelta (see below)
112
85
  - writing a table as a filtered data range
113
86
  - writing a table to a specific location in a spreadsheet (specified by a box)
114
87
  - reading table data from a specific location in a spreadsheet (specified by a box)
@@ -117,17 +90,54 @@ The main features are:
117
90
  at that position or at positions relative to that position.
118
91
  - several border styles for tables
119
92
 
93
+ ## Timedelta (duration) values
94
+
95
+ Excel (OpenPyXL, XlsxWriter, pylightxl) and ODS (odfdo) store `timedelta`
96
+ values natively, as duration cells, and read them back as `timedelta`.
97
+ (OpenPyXL reads durations with millisecond precision.)
98
+ The cells are displayed as `[hh]:mm:ss` (hours do not wrap at 24, seconds
99
+ are displayed rounded to whole seconds, the stored value is exact).
100
+ Microsoft Excel has no duration type, it stores a duration as a number of
101
+ days with a time number format. So by design Excel shows the cell as for
102
+ instance `72:00:00`, but shows the value as a date and time (like
103
+ `1/3/1900 12:00:00 AM`) in the formula bar when the cell is selected. Excel
104
+ does the same for durations typed in by hand, and LibreOffice shows
105
+ `72:00:00` in both places for the same file.
106
+ Notice also that Microsoft Excel cannot display negative durations (it
107
+ shows `####`). The stored value is still correct, it is read back
108
+ correctly and LibreOffice displays it as for instance `-00:03:58`. If
109
+ negative durations must be readable in Microsoft Excel, consider storing
110
+ the magnitude and the sign in separate columns.
111
+
112
+ Formats without native duration support (CSV and all write-only document
113
+ formats) write a fallback representation, selected with the optional
114
+ argument `timedelta_fallback` (a `TimeDeltaFallback` value):
115
+
116
+ | TimeDeltaFallback | Two days, three hours and 1.5 seconds |
117
+ |-----------------------|---------------------------------------|
118
+ | `HMS_STRING` (default)| `51:00:01.5` |
119
+ | `DHMS_STRING` | `2 d 03:00:01.5` |
120
+ | `DHMS_STRING_LONG` | `2 days 03:00:01.5` |
121
+ | `WDHMS_STRING` | `2 d 03:00:01.5` (with `W w` if a week or more) |
122
+ | `WDHMS_STRING_LONG` | `2 days 03:00:01.5` (with `W weeks` if a week or more) |
123
+ | `FLOATSECONDS` | `183601.5` |
124
+
125
+ Negative durations get a leading `-`. When reading such a file the values
126
+ are strings (CSV). Convert them with `parse_timedelta()`, that accepts all
127
+ fallback formats (and `timedelta` values, so it works for all formats).
128
+ `format_timedelta()` formats a `timedelta` in any of the fallback formats.
129
+
120
130
  ## Example programs
121
131
 
122
132
  The best way to learn to use this package is to use the provided
123
133
  example programs:
124
- [https://bitbucket.org/tom-bjorkholm/table-io/src/master/example/src/example/README.md](https://bitbucket.org/tom-bjorkholm/table-io/src/master/example/src/example/README.md).
134
+ [https://github.com/tom-bjorkholm/table-io/blob/master/example/src/example/README.md](https://github.com/tom-bjorkholm/table-io/blob/master/example/src/example/README.md).
125
135
 
126
136
  ## API documentation
127
137
 
128
- 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)
138
+ You can find the public API documentation at [https://github.com/tom-bjorkholm/table-io/blob/master/doc/api.md](https://github.com/tom-bjorkholm/table-io/blob/master/doc/api.md)
129
139
 
130
- 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)
140
+ You can find the protected API documentation at [https://github.com/tom-bjorkholm/table-io/blob/master/doc/protected_api.md](https://github.com/tom-bjorkholm/table-io/blob/master/doc/protected_api.md)
131
141
  The protected API documentation is only for developers that want to
132
142
  extend the framework by adding their own classes as registered
133
143
  readers/writers to the factory.
@@ -137,9 +147,10 @@ a better start by reading the examples.
137
147
 
138
148
  ## Test summary
139
149
 
140
- - Test result: 1347 passed in 22s
150
+ - Test result: 1567 passed in 9s
141
151
  - No flake8 warnings.
142
152
  - No mypy errors found.
153
+ - No pylint warnings.
143
154
  - No python layout warnings.
144
- - Built version(s): 1.0
145
- - Build and test using Python 3.14.5
155
+ - Built version(s): 1.2
156
+ - Build and test using Python 3.14.7
@@ -4,7 +4,6 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tableio"
7
- version = "1.0"
8
7
  authors = [
9
8
  { name="Tom Björkholm", email="klausuler_linnet0q@icloud.com" },
10
9
  ]
@@ -15,9 +14,15 @@ license = "MIT"
15
14
  license-files = ["LICENSE.txt"]
16
15
  classifiers = [
17
16
  "Programming Language :: Python :: 3",
18
- "Operating System :: OS Independent"
17
+ "Programming Language :: Python :: 3.12",
18
+ "Programming Language :: Python :: 3.13",
19
+ "Programming Language :: Python :: 3.14",
20
+ "Operating System :: OS Independent",
21
+ "Intended Audience :: Developers",
22
+ "Typing :: Typed"
19
23
  ]
20
- dynamic = ["dependencies"]
24
+ dynamic = ["dependencies", "version"]
21
25
 
22
26
  [project.urls]
23
- "Source code" = "https://bitbucket.org/tom-bjorkholm/table-io"
27
+ "Source code" = "https://github.com/tom-bjorkholm/table-io"
28
+ "Documentation" = "https://github.com/tom-bjorkholm/table-io/blob/master/doc/"
@@ -5,7 +5,7 @@ from setuptools import setup
5
5
 
6
6
  setup(
7
7
  name='tableio',
8
- version='1.0',
8
+ version='1.2',
9
9
  description='Uniform way to write table data to and read from different ' \
10
10
  'file formats',
11
11
  author='Tom Björkholm',
@@ -14,15 +14,11 @@ setup(
14
14
  packages=['tableio'],
15
15
  package_dir={'tableio': 'src/tableio'},
16
16
  package_data={'tableio': ['src/py.typed']},
17
- install_requires=[ # pylint: disable=duplicate-code
18
- 'pip >= 26.1.1',
19
- 'setuptools >= 82.0.1',
20
- 'build >= 1.5.0',
21
- 'wheel >= 0.47.0',
22
- 'mformat-ext >= 0.6',
17
+ install_requires=[
18
+ 'mformat-ext >= 0.7',
23
19
  'openpyxl >= 3.1.5',
24
- 'types-openpyxl >= 3.1.5.20260518',
25
- 'odfdo >= 3.22.8',
20
+ 'types-openpyxl >= 3.1.5.20260827',
21
+ 'odfdo >= 3.26.3',
26
22
  'xlsxwriter >= 3.2.9',
27
23
  'pylightxl >= 1.61'
28
24
  ]
@@ -21,9 +21,11 @@ from tableio.config_data_error import ConfigError, ConfigIssue
21
21
  from tableio.config_data_validate import tio_config_validate
22
22
  from tableio.factory import create_tableio, filter_args_tableio, \
23
23
  list_implementations_tableio, list_registered_tableio
24
- from tableio.optional_args import CsvDialect, OptionalArgs, OptionalArgsDict
24
+ from tableio.optional_args import CsvDialect, OptionalArgs, \
25
+ OptionalArgsDict, TimeDeltaFallback
25
26
  from tableio.tableio import TableIO
26
27
  from tableio.tableio_types import TableBorderStyle, Box, FileAccess, Position
28
+ from tableio.timedelta_helpers import format_timedelta, parse_timedelta
27
29
  from tableio.value_type import CellT, DictData, DictDataMap, Fmt, \
28
30
  FmtDictData, FmtDictRow, FmtListData, FmtListRow, ListData, \
29
31
  ListDataSeq, ReadResult, Value, ValueFmt
@@ -43,6 +45,8 @@ __all__ = ['create_tableio',
43
45
  'DictDataMap',
44
46
  'ReadResult',
45
47
  'Fmt',
48
+ 'format_timedelta',
49
+ 'parse_timedelta',
46
50
  'ValueFmt',
47
51
  'FmtListRow',
48
52
  'FmtDictRow',
@@ -63,6 +67,7 @@ __all__ = ['create_tableio',
63
67
  'list_registered_tableio',
64
68
  'list_implementations_tableio',
65
69
  'CsvDialect',
70
+ 'TimeDeltaFallback',
66
71
  'ConfigData', 'ConfigError', 'ConfigIssue', 'ConfigSpec',
67
72
  'CsvConfigData', 'HtmlConfigData', 'LatexConfigData',
68
73
  'tio_config_create', 'tio_config_default',
@@ -6,7 +6,7 @@
6
6
 
7
7
  from dataclasses import dataclass
8
8
  from typing import Optional
9
- from tableio.optional_args import CsvDialect
9
+ from tableio.optional_args import CsvDialect, TimeDeltaFallback
10
10
 
11
11
 
12
12
  @dataclass
@@ -115,3 +115,10 @@ class ConfigData: # pylint: disable=too-many-instance-attributes
115
115
 
116
116
  latex: Optional[LatexConfigData] = None
117
117
  """LaTeX-specific configuration values, or ``None`` when unset."""
118
+
119
+ timedelta_fallback: Optional[TimeDeltaFallback] = None
120
+ """How formats without native timedelta write it, or ``None``.
121
+
122
+ ``None`` means backend default (``TimeDeltaFallback.HMS_STRING``).
123
+ Formats with native timedelta support ignore this value.
124
+ """
@@ -7,6 +7,8 @@
7
7
  from typing import Callable, NoReturn, Optional, TypeVar, cast
8
8
 
9
9
  from mformat.mformat import PathLike
10
+ from mformat.paper_size import PaperSize
11
+ from mformat.plain_text_table import TableAlignment
10
12
  from tableio.access_capability import add_access_capabilities
11
13
  from tableio.capability import Capabilities, capability_match
12
14
  from tableio.config_data import ConfigData, CsvConfigData, HtmlConfigData, \
@@ -15,7 +17,8 @@ from tableio.config_data_error import ConfigError, ConfigIssue
15
17
  from tableio.config_data_validate import tio_config_validate
16
18
  from tableio.factory import create_tableio, filter_args_tableio, \
17
19
  list_implementations_tableio, list_registered_tableio, usage_tableio
18
- from tableio.optional_args import CsvDialect, OptionalArgs, OptionalArgsDict
20
+ from tableio.optional_args import CsvDialect, OptionalArgs, \
21
+ OptionalArgsDict, TimeDeltaFallback
19
22
  from tableio.tableio import TableIO
20
23
  from tableio.tableio_types import FileAccess
21
24
 
@@ -111,6 +114,16 @@ def _best_default_names(capabilities: Capabilities, format_name: Optional[str],
111
114
  _config_error(name, 'does not match registered TableIO backends.')
112
115
 
113
116
 
117
+ def _paper_size(value: Optional[str]) -> Optional[PaperSize]:
118
+ """Return a validated paper size text as the backend enum value."""
119
+ return None if value is None else PaperSize.from_str(value)
120
+
121
+
122
+ def _table_alignment(value: Optional[str]) -> Optional[TableAlignment]:
123
+ """Return a validated table alignment text as the backend enum value."""
124
+ return None if value is None else TableAlignment[value.upper()]
125
+
126
+
114
127
  def _base_arg_items(config: ConfigData) -> list[tuple[str, str, object]]:
115
128
  """Return configured top-level optional argument values."""
116
129
  return [
@@ -118,11 +131,14 @@ def _base_arg_items(config: ConfigData) -> list[tuple[str, str, object]]:
118
131
  config.character_encoding),
119
132
  ('language', 'lang', config.language),
120
133
  ('title', 'title', config.title),
121
- ('paper_size', 'paper_size', config.paper_size),
134
+ ('paper_size', 'paper_size', _paper_size(config.paper_size)),
122
135
  ('line_length', 'line_length', config.line_length),
123
136
  ('table_max_line_length', 'table_max_line_length',
124
137
  config.table_max_line_length),
125
- ('table_alignment', 'table_alignment', config.table_alignment)
138
+ ('table_alignment', 'table_alignment',
139
+ _table_alignment(config.table_alignment)),
140
+ ('timedelta_fallback', 'timedelta_fallback',
141
+ config.timedelta_fallback)
126
142
  ]
127
143
 
128
144
 
@@ -201,7 +217,8 @@ def _all_option_config(format_name: str,
201
217
  title='HTML file', paper_size='A4', line_length=79,
202
218
  table_max_line_length=140,
203
219
  table_alignment='CENTER_BUT_DIGITS_RIGHT', csv=csv,
204
- html=html, latex=latex)
220
+ html=html, latex=latex,
221
+ timedelta_fallback=TimeDeltaFallback.HMS_STRING)
205
222
 
206
223
 
207
224
  def _include_default_impl(implementation: Optional[str],
@@ -306,6 +323,8 @@ def tio_config_create(
306
323
  TableIO:
307
324
  """Create a TableIO object from configuration and runtime values.
308
325
 
326
+ The implementation is selected using the capabilities together with the
327
+ capabilities implied by file_access, as done by the validation.
309
328
  Args:
310
329
  config: Durable configuration data.
311
330
  file_name: Runtime file name to open.
@@ -317,16 +336,18 @@ def tio_config_create(
317
336
  """
318
337
  tio_config_validate(config, capabilities=capabilities,
319
338
  file_access=file_access)
339
+ match_caps = add_access_capabilities(file_access,
340
+ capabilities or Capabilities())
320
341
  extra_args = None
321
342
  if file_exists_callback is not None:
322
343
  extra_args = cast(OptionalArgsDict, {
323
344
  'file_exists_callback': file_exists_callback
324
345
  })
325
- args = _filtered_args(config, capabilities, extra_args)
346
+ args = _filtered_args(config, match_caps, extra_args)
326
347
  return create_tableio(format_name=config.format_name, file_name=file_name,
327
348
  file_access=file_access, args=args,
328
349
  implementation=config.implementation,
329
- capabilities=capabilities)
350
+ capabilities=match_caps)
330
351
 
331
352
 
332
353
  def tio_config_ignored_names(config: ConfigData,
@@ -386,6 +407,9 @@ def tio_config_trim(config: ConfigData,
386
407
  filtered_names),
387
408
  table_alignment=_kept(config.table_alignment,
388
409
  'table_alignment', filtered_names),
410
+ timedelta_fallback=_kept(config.timedelta_fallback,
411
+ 'timedelta_fallback',
412
+ filtered_names),
389
413
  csv=csv, html=html, latex=latex)
390
414
 
391
415
 
@@ -13,7 +13,7 @@ from mformat.plain_text_table import TableAlignment
13
13
 
14
14
  from tableio.factory import list_implementations_tableio, \
15
15
  list_registered_tableio, usage_tableio
16
- from tableio.optional_args import CsvDialect
16
+ from tableio.optional_args import CsvDialect, TimeDeltaFallback
17
17
 
18
18
 
19
19
  @dataclass
@@ -58,6 +58,11 @@ def _csv_dialect_choices() -> tuple[str, ...]:
58
58
  return tuple(member.name for member in CsvDialect)
59
59
 
60
60
 
61
+ def _timedelta_fallback_choices() -> tuple[str, ...]:
62
+ """Return advertised timedelta fallback choices."""
63
+ return tuple(member.name for member in TimeDeltaFallback)
64
+
65
+
61
66
  def _table_alignment_choices() -> tuple[str, ...]:
62
67
  """Return advertised plain text table alignment choices."""
63
68
  return tuple(member.name for member in TableAlignment)
@@ -147,6 +152,13 @@ def tio_config_specs() -> dict[str, ConfigSpec]:
147
152
  default_text='None means backend default.',
148
153
  choices=_table_alignment_choices(),
149
154
  optional_arg='table_alignment')),
155
+ _arg_spec(ConfigSpec(name='timedelta_fallback',
156
+ description='How timedelta values are written '
157
+ 'by formats without native timedelta support.',
158
+ value_type='Optional[TimeDeltaFallback]',
159
+ default_text='None means backend default.',
160
+ choices=_timedelta_fallback_choices(),
161
+ optional_arg='timedelta_fallback')),
150
162
  _arg_spec(ConfigSpec(name='csv.dialect',
151
163
  description='The CSV dialect template.',
152
164
  value_type='Optional[CsvDialect]',
@@ -17,7 +17,7 @@ from tableio.config_data_error import ConfigError, ConfigIssue
17
17
  from tableio.factory import TableIOFactoryNoCapabilityMatch, \
18
18
  TableIOFactoryNoSuchError, filter_args_tableio, \
19
19
  list_implementations_tableio
20
- from tableio.optional_args import CsvDialect
20
+ from tableio.optional_args import CsvDialect, TimeDeltaFallback
21
21
  from tableio.tableio_types import FileAccess
22
22
 
23
23
 
@@ -156,6 +156,10 @@ def _validate_top_values(config: ConfigData, specs: dict[str, ConfigSpec],
156
156
  _validate_int_min(issues, 'table_max_line_length',
157
157
  config.table_max_line_length, 10, True)
158
158
  _valid_choice(config.table_alignment, specs['table_alignment'], issues)
159
+ if config.timedelta_fallback is not None and \
160
+ not isinstance(config.timedelta_fallback, TimeDeltaFallback):
161
+ _add_issue(issues, 'timedelta_fallback',
162
+ 'must be a TimeDeltaFallback value or None.')
159
163
 
160
164
 
161
165
  def _validate_csv(config: ConfigData, specs: dict[str, ConfigSpec],
@@ -6,7 +6,6 @@
6
6
  #
7
7
 
8
8
  from typing import Optional, NamedTuple
9
- from functools import total_ordering
10
9
  import warnings
11
10
  from mformat.mformat import PathLike
12
11
  from tableio.access_capability import check_access_capabilities
@@ -53,7 +52,6 @@ class TableIOFactoryNoCapabilityMatch(ValueError):
53
52
  """
54
53
 
55
54
 
56
- @total_ordering
57
55
  class ImplPrio(NamedTuple):
58
56
  """Priority of an implementation."""
59
57
 
@@ -66,25 +64,43 @@ class ImplPrio(NamedTuple):
66
64
  priority: int
67
65
  """The priority of the implementation."""
68
66
 
67
+ def sort_key(self) -> tuple[int, str, str]:
68
+ """Return the ordering key: priority, format name, implementation.
69
+
70
+ All comparison operators are defined from this key, as the
71
+ inherited tuple comparisons use the field order instead.
72
+ """
73
+ return (self.priority, self.format_name, self.implementation)
74
+
69
75
  def __lt__(self, other: object) -> bool:
70
76
  """Compare two implementation priorities."""
71
77
  if not isinstance(other, ImplPrio):
72
78
  return NotImplemented
73
- if self.priority < other.priority:
74
- return True
75
- if self.priority > other.priority:
76
- return False
77
- if self.format_name < other.format_name:
78
- return True
79
- if self.format_name > other.format_name:
80
- return False
81
- return self.implementation < other.implementation
79
+ return self.sort_key() < other.sort_key()
80
+
81
+ def __le__(self, other: object) -> bool:
82
+ """Compare two implementation priorities."""
83
+ if not isinstance(other, ImplPrio):
84
+ return NotImplemented
85
+ return self.sort_key() <= other.sort_key()
86
+
87
+ def __gt__(self, other: object) -> bool:
88
+ """Compare two implementation priorities."""
89
+ if not isinstance(other, ImplPrio):
90
+ return NotImplemented
91
+ return self.sort_key() > other.sort_key()
92
+
93
+ def __ge__(self, other: object) -> bool:
94
+ """Compare two implementation priorities."""
95
+ if not isinstance(other, ImplPrio):
96
+ return NotImplemented
97
+ return self.sort_key() >= other.sort_key()
82
98
 
83
99
  def __eq__(self, other: object) -> bool:
84
100
  """Compare two implementation priorities."""
85
101
  if not isinstance(other, ImplPrio):
86
102
  return NotImplemented
87
- return not self < other and not other < self
103
+ return self.sort_key() == other.sort_key()
88
104
 
89
105
 
90
106
  class BestMatch(NamedTuple):