tableio 1.1__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.
- {tableio-1.1 → tableio-1.2}/PKG-INFO +45 -6
- {tableio-1.1 → tableio-1.2}/README.md +4 -3
- {tableio-1.1 → tableio-1.2}/README_pypi.md +42 -3
- {tableio-1.1 → tableio-1.2}/pyproject.toml +1 -2
- {tableio-1.1 → tableio-1.2}/setup.py +3 -3
- {tableio-1.1 → tableio-1.2}/src/tableio/__init__.py +6 -1
- {tableio-1.1 → tableio-1.2}/src/tableio/config_data.py +8 -1
- {tableio-1.1 → tableio-1.2}/src/tableio/config_data_apply.py +30 -6
- {tableio-1.1 → tableio-1.2}/src/tableio/config_data_describe.py +13 -1
- {tableio-1.1 → tableio-1.2}/src/tableio/config_data_validate.py +5 -1
- {tableio-1.1 → tableio-1.2}/src/tableio/factory.py +28 -12
- tableio-1.2/src/tableio/optional_args.py +143 -0
- {tableio-1.1 → tableio-1.2}/src/tableio/tableio.py +17 -2
- {tableio-1.1 → tableio-1.2}/src/tableio/tableio_csv.py +106 -23
- {tableio-1.1 → tableio-1.2}/src/tableio/tableio_excel_openpyxl.py +20 -5
- {tableio-1.1 → tableio-1.2}/src/tableio/tableio_excel_pylightxl.py +96 -23
- {tableio-1.1 → tableio-1.2}/src/tableio/tableio_excel_xlsxwriter.py +18 -13
- {tableio-1.1 → tableio-1.2}/src/tableio/tableio_excelbased.py +11 -3
- {tableio-1.1 → tableio-1.2}/src/tableio/tableio_mformat.py +73 -27
- {tableio-1.1 → tableio-1.2}/src/tableio/tableio_mformatbased.py +30 -6
- {tableio-1.1 → tableio-1.2}/src/tableio/tableio_ods_odfdo.py +114 -11
- {tableio-1.1 → tableio-1.2}/src/tableio/tableio_spreadsheetbased.py +20 -7
- {tableio-1.1 → tableio-1.2}/src/tableio/tableio_textbased.py +15 -8
- tableio-1.2/src/tableio/timedelta_helpers.py +179 -0
- {tableio-1.1 → tableio-1.2}/src/tableio/value_type.py +2 -2
- {tableio-1.1 → tableio-1.2}/src/tableio/valueconversion.py +70 -35
- {tableio-1.1 → tableio-1.2}/tableio.egg-info/PKG-INFO +45 -6
- {tableio-1.1 → tableio-1.2}/tableio.egg-info/SOURCES.txt +1 -0
- {tableio-1.1 → tableio-1.2}/tableio.egg-info/requires.txt +2 -2
- tableio-1.1/src/tableio/optional_args.py +0 -77
- {tableio-1.1 → tableio-1.2}/LICENSE.txt +0 -0
- {tableio-1.1 → tableio-1.2}/setup.cfg +0 -0
- {tableio-1.1 → tableio-1.2}/src/tableio/_archive_rewrite.py +0 -0
- {tableio-1.1 → tableio-1.2}/src/tableio/access_capability.py +0 -0
- {tableio-1.1 → tableio-1.2}/src/tableio/border_helper.py +0 -0
- {tableio-1.1 → tableio-1.2}/src/tableio/capability.py +0 -0
- {tableio-1.1 → tableio-1.2}/src/tableio/color.py +0 -0
- {tableio-1.1 → tableio-1.2}/src/tableio/config_data_error.py +0 -0
- {tableio-1.1 → tableio-1.2}/src/tableio/py.typed +0 -0
- {tableio-1.1 → tableio-1.2}/src/tableio/reg_pkg_formats.py +0 -0
- {tableio-1.1 → tableio-1.2}/src/tableio/tableio_types.py +0 -0
- {tableio-1.1 → tableio-1.2}/tableio.egg-info/dependency_links.txt +0 -0
- {tableio-1.1 → tableio-1.2}/tableio.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tableio
|
|
3
|
-
Version: 1.
|
|
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>
|
|
@@ -19,8 +19,8 @@ Description-Content-Type: text/markdown
|
|
|
19
19
|
License-File: LICENSE.txt
|
|
20
20
|
Requires-Dist: mformat-ext>=0.7
|
|
21
21
|
Requires-Dist: openpyxl>=3.1.5
|
|
22
|
-
Requires-Dist: types-openpyxl>=3.1.5.
|
|
23
|
-
Requires-Dist: odfdo>=3.
|
|
22
|
+
Requires-Dist: types-openpyxl>=3.1.5.20260827
|
|
23
|
+
Requires-Dist: odfdo>=3.26.3
|
|
24
24
|
Requires-Dist: xlsxwriter>=3.2.9
|
|
25
25
|
Requires-Dist: pylightxl>=1.61
|
|
26
26
|
Dynamic: author
|
|
@@ -111,6 +111,7 @@ The main features are:
|
|
|
111
111
|
- int
|
|
112
112
|
- float
|
|
113
113
|
- datetime
|
|
114
|
+
- timedelta (see below)
|
|
114
115
|
- writing a table as a filtered data range
|
|
115
116
|
- writing a table to a specific location in a spreadsheet (specified by a box)
|
|
116
117
|
- reading table data from a specific location in a spreadsheet (specified by a box)
|
|
@@ -119,6 +120,43 @@ The main features are:
|
|
|
119
120
|
at that position or at positions relative to that position.
|
|
120
121
|
- several border styles for tables
|
|
121
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
|
+
|
|
122
160
|
## Example programs
|
|
123
161
|
|
|
124
162
|
The best way to learn to use this package is to use the provided
|
|
@@ -139,9 +177,10 @@ a better start by reading the examples.
|
|
|
139
177
|
|
|
140
178
|
## Test summary
|
|
141
179
|
|
|
142
|
-
- Test result:
|
|
180
|
+
- Test result: 1567 passed in 9s
|
|
143
181
|
- No flake8 warnings.
|
|
144
182
|
- No mypy errors found.
|
|
183
|
+
- No pylint warnings.
|
|
145
184
|
- No python layout warnings.
|
|
146
|
-
- Built version(s): 1.
|
|
147
|
-
- Build and test using Python 3.14.
|
|
185
|
+
- Built version(s): 1.2
|
|
186
|
+
- Build and test using Python 3.14.7
|
|
@@ -93,9 +93,10 @@ 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: 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.
|
|
101
|
-
- Build and test using Python 3.14.
|
|
101
|
+
- Built version(s): 1.2
|
|
102
|
+
- Build and test using Python 3.14.7
|
|
@@ -81,6 +81,7 @@ The main features are:
|
|
|
81
81
|
- int
|
|
82
82
|
- float
|
|
83
83
|
- datetime
|
|
84
|
+
- timedelta (see below)
|
|
84
85
|
- writing a table as a filtered data range
|
|
85
86
|
- writing a table to a specific location in a spreadsheet (specified by a box)
|
|
86
87
|
- reading table data from a specific location in a spreadsheet (specified by a box)
|
|
@@ -89,6 +90,43 @@ The main features are:
|
|
|
89
90
|
at that position or at positions relative to that position.
|
|
90
91
|
- several border styles for tables
|
|
91
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
|
+
|
|
92
130
|
## Example programs
|
|
93
131
|
|
|
94
132
|
The best way to learn to use this package is to use the provided
|
|
@@ -109,9 +147,10 @@ a better start by reading the examples.
|
|
|
109
147
|
|
|
110
148
|
## Test summary
|
|
111
149
|
|
|
112
|
-
- Test result:
|
|
150
|
+
- Test result: 1567 passed in 9s
|
|
113
151
|
- No flake8 warnings.
|
|
114
152
|
- No mypy errors found.
|
|
153
|
+
- No pylint warnings.
|
|
115
154
|
- No python layout warnings.
|
|
116
|
-
- Built version(s): 1.
|
|
117
|
-
- Build and test using Python 3.14.
|
|
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.1"
|
|
8
7
|
authors = [
|
|
9
8
|
{ name="Tom Björkholm", email="klausuler_linnet0q@icloud.com" },
|
|
10
9
|
]
|
|
@@ -22,7 +21,7 @@ classifiers = [
|
|
|
22
21
|
"Intended Audience :: Developers",
|
|
23
22
|
"Typing :: Typed"
|
|
24
23
|
]
|
|
25
|
-
dynamic = ["dependencies"]
|
|
24
|
+
dynamic = ["dependencies", "version"]
|
|
26
25
|
|
|
27
26
|
[project.urls]
|
|
28
27
|
"Source code" = "https://github.com/tom-bjorkholm/table-io"
|
|
@@ -5,7 +5,7 @@ from setuptools import setup
|
|
|
5
5
|
|
|
6
6
|
setup(
|
|
7
7
|
name='tableio',
|
|
8
|
-
version='1.
|
|
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',
|
|
@@ -17,8 +17,8 @@ setup(
|
|
|
17
17
|
install_requires=[
|
|
18
18
|
'mformat-ext >= 0.7',
|
|
19
19
|
'openpyxl >= 3.1.5',
|
|
20
|
-
'types-openpyxl >= 3.1.5.
|
|
21
|
-
'odfdo >= 3.
|
|
20
|
+
'types-openpyxl >= 3.1.5.20260827',
|
|
21
|
+
'odfdo >= 3.26.3',
|
|
22
22
|
'xlsxwriter >= 3.2.9',
|
|
23
23
|
'pylightxl >= 1.61'
|
|
24
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,
|
|
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,
|
|
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',
|
|
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,
|
|
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=
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if
|
|
78
|
-
return
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
103
|
+
return self.sort_key() == other.sort_key()
|
|
88
104
|
|
|
89
105
|
|
|
90
106
|
class BestMatch(NamedTuple):
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#! /usr/bin/env python3
|
|
2
|
+
"""Optional arguments for the tableio package."""
|
|
3
|
+
|
|
4
|
+
# Copyright (c) 2026 Tom Björkholm
|
|
5
|
+
# MIT License
|
|
6
|
+
|
|
7
|
+
from typing import Optional, cast
|
|
8
|
+
from enum import IntEnum, auto
|
|
9
|
+
from mformat.factory import OptArgsDict
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CsvDialect(IntEnum):
|
|
13
|
+
"""The type of CSV file to write."""
|
|
14
|
+
|
|
15
|
+
EXCEL = auto()
|
|
16
|
+
"""Excel CSV file type/dialect."""
|
|
17
|
+
UNIX = auto()
|
|
18
|
+
"""Unix CSV file type/dialect."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TimeDeltaFallback(IntEnum):
|
|
22
|
+
"""The fallback behavior for timedelta values.
|
|
23
|
+
|
|
24
|
+
How a timedelta value is written when an implementation does not support
|
|
25
|
+
writing timedelta values. If an implementation does support timedelta
|
|
26
|
+
values, the fallback behavior specified by this enum will be ignored.
|
|
27
|
+
|
|
28
|
+
For all string formats: A negative value gets a leading '-' that applies
|
|
29
|
+
to the whole duration (for example '-01:00:00' is minus one hour).
|
|
30
|
+
Fractional seconds are written with up to six decimals, and only when
|
|
31
|
+
non-zero (for example '00:00:01.5'). The 'HH' part is always at least two
|
|
32
|
+
digits. Use ``tableio.parse_timedelta`` to read the values back.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
FLOATSECONDS = auto()
|
|
36
|
+
"""Convert the timedelta to a float of the total number of seconds.
|
|
37
|
+
|
|
38
|
+
Microsecond precision is lost for very large durations (roughly
|
|
39
|
+
above 100 years), as a float has limited precision.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
HMS_STRING = auto()
|
|
43
|
+
"""Convert the timedelta to a string in the format 'HH:MM:SS'.
|
|
44
|
+
|
|
45
|
+
Days are included in the hours, so 'HH' may exceed 24 (for example
|
|
46
|
+
'26:03:04').
|
|
47
|
+
This is the default fallback.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
DHMS_STRING = auto()
|
|
51
|
+
"""Convert the timedelta to a string in the format 'D d HH:MM:SS'.
|
|
52
|
+
|
|
53
|
+
The 'D d' part will only be included if the number of days is non-zero.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
DHMS_STRING_LONG = auto()
|
|
57
|
+
"""Convert the timedelta to a string in the format 'D days HH:MM:SS'.
|
|
58
|
+
|
|
59
|
+
The 'D days' part will only be included if the number of days is non-zero.
|
|
60
|
+
If the number of days is one, it will be written as '1 day'.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
WDHMS_STRING = auto()
|
|
64
|
+
"""Convert the timedelta to a string in the format 'W w D d HH:MM:SS'.
|
|
65
|
+
|
|
66
|
+
The 'W w' part will only be included if the number of weeks is non-zero.
|
|
67
|
+
The 'D d' part will only be included if the number of days is non-zero.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
WDHMS_STRING_LONG = auto()
|
|
71
|
+
"""Convert the timedelta to a string as 'W weeks D days HH:MM:SS'.
|
|
72
|
+
|
|
73
|
+
The 'W weeks' part will only be included if the number of weeks is
|
|
74
|
+
non-zero. If the number of weeks is one, it will be written as '1 week'.
|
|
75
|
+
The 'D days' part will only be included if the number of days is non-zero.
|
|
76
|
+
If the number of days is one, it will be written as '1 day'.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class OptionalArgsDict(OptArgsDict, total=False):
|
|
81
|
+
"""Optional arguments for the tableio package.
|
|
82
|
+
|
|
83
|
+
This is a TypedDict that describes the optional arguments that can be
|
|
84
|
+
passed to the factory in the tableio package.
|
|
85
|
+
For description of the arguments, see the class derived from TableIO
|
|
86
|
+
that uses the arguments.
|
|
87
|
+
The possible optional arguments includ the arguments in
|
|
88
|
+
mformat.factory.OptArgsDict plus the arguments specific to the tableio
|
|
89
|
+
package.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
csv_dialect: Optional[CsvDialect]
|
|
93
|
+
"""The type/dialect of CSV file to write. None for default type."""
|
|
94
|
+
|
|
95
|
+
csv_delimiter: Optional[str]
|
|
96
|
+
"""The delimiter to use for CSV files. None for default delimiter."""
|
|
97
|
+
|
|
98
|
+
csv_quoting: Optional[str]
|
|
99
|
+
"""The quoting style to use for CSV files.
|
|
100
|
+
|
|
101
|
+
Allowed values (case-insensitive): 'all', 'minimal',
|
|
102
|
+
'nonnumeric', 'none', 'strings', 'notnull'.
|
|
103
|
+
None for default quoting."""
|
|
104
|
+
|
|
105
|
+
csv_quotechar: Optional[str]
|
|
106
|
+
"""The quote character to use for CSV files. None for default."""
|
|
107
|
+
|
|
108
|
+
csv_lineterminator: Optional[str]
|
|
109
|
+
"""The line terminator to use for CSV files. None for default."""
|
|
110
|
+
|
|
111
|
+
csv_escapechar: Optional[str]
|
|
112
|
+
"""The escape character to use for CSV files. None for default."""
|
|
113
|
+
|
|
114
|
+
timedelta_fallback: Optional[TimeDeltaFallback]
|
|
115
|
+
"""The fallback format to use for timedelta, when no native support.
|
|
116
|
+
|
|
117
|
+
Silently ignored if the implementation provides native support for
|
|
118
|
+
timedelta values. None for default (TimeDeltaFallback.HMS_STRING).
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
type OptionalArgs = Optional[OptionalArgsDict]
|
|
123
|
+
"""Optional arguments for the factory in the tableio package."""
|
|
124
|
+
|
|
125
|
+
_MFORMAT_OPTARG_NAMES: set[str] = set(OptArgsDict.__annotations__)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def mformat_optargs_from_optionalargs(optional_args: OptionalArgs) \
|
|
129
|
+
-> Optional[OptArgsDict]:
|
|
130
|
+
"""Convert the optional arguments to a dictionary of arguments for mformat.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
optional_args: The optional arguments to convert.
|
|
134
|
+
Returns:
|
|
135
|
+
A dictionary of arguments for mformat.
|
|
136
|
+
"""
|
|
137
|
+
if optional_args is None:
|
|
138
|
+
return None
|
|
139
|
+
ret = {
|
|
140
|
+
key: value for key, value in optional_args.items()
|
|
141
|
+
if key in _MFORMAT_OPTARG_NAMES and value is not None
|
|
142
|
+
}
|
|
143
|
+
return cast(OptArgsDict, ret)
|