numbers-parser 4.17.0.post1__py3-none-any.whl → 4.18.1__py3-none-any.whl
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.
- numbers_parser/_cat_numbers.py +15 -4
- numbers_parser/cell.py +3 -15
- numbers_parser/constants.py +5 -0
- numbers_parser/document.py +85 -40
- numbers_parser/exceptions.py +1 -1
- numbers_parser/experimental.py +21 -7
- numbers_parser/formula.py +2 -413
- numbers_parser/model.py +159 -149
- numbers_parser/xrefs.py +0 -2
- {numbers_parser-4.17.0.post1.dist-info → numbers_parser-4.18.1.dist-info}/METADATA +35 -121
- {numbers_parser-4.17.0.post1.dist-info → numbers_parser-4.18.1.dist-info}/RECORD +15 -16
- numbers_parser/tokenizer.py +0 -548
- {numbers_parser-4.17.0.post1.dist-info → numbers_parser-4.18.1.dist-info}/WHEEL +0 -0
- {numbers_parser-4.17.0.post1.dist-info → numbers_parser-4.18.1.dist-info}/entry_points.txt +0 -0
- {numbers_parser-4.17.0.post1.dist-info → numbers_parser-4.18.1.dist-info}/licenses/LICENSE.rst +0 -0
- {numbers_parser-4.17.0.post1.dist-info → numbers_parser-4.18.1.dist-info}/top_level.txt +0 -0
numbers_parser/_cat_numbers.py
CHANGED
|
@@ -16,11 +16,19 @@ from numbers_parser import (
|
|
|
16
16
|
)
|
|
17
17
|
from numbers_parser import __name__ as numbers_parser_name
|
|
18
18
|
from numbers_parser.constants import MAX_SIGNIFICANT_DIGITS
|
|
19
|
-
from numbers_parser.experimental import
|
|
19
|
+
from numbers_parser.experimental import ExperimentalFeatures, enable_experimental_feature
|
|
20
20
|
|
|
21
21
|
logger = logging.getLogger(numbers_parser_name)
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
def experimental_feature_choice(s: str) -> ExperimentalFeatures:
|
|
25
|
+
try:
|
|
26
|
+
return ExperimentalFeatures[s]
|
|
27
|
+
except KeyError:
|
|
28
|
+
msg = f"invalid experimental feature: {s}"
|
|
29
|
+
raise argparse.ArgumentTypeError(msg) # noqa: B904
|
|
30
|
+
|
|
31
|
+
|
|
24
32
|
def command_line_parser():
|
|
25
33
|
parser = argparse.ArgumentParser(
|
|
26
34
|
description="Export data from Apple Numbers spreadsheet tables",
|
|
@@ -70,10 +78,13 @@ def command_line_parser():
|
|
|
70
78
|
)
|
|
71
79
|
parser.add_argument("document", nargs="*", help="Document(s) to export")
|
|
72
80
|
parser.add_argument("--debug", default=False, action="store_true", help="Enable debug logging")
|
|
81
|
+
experimental_choices = [
|
|
82
|
+
f.name for f in ExperimentalFeatures if f is not ExperimentalFeatures.NONE
|
|
83
|
+
]
|
|
73
84
|
parser.add_argument(
|
|
74
85
|
"--experimental",
|
|
75
|
-
|
|
76
|
-
|
|
86
|
+
type=experimental_feature_choice,
|
|
87
|
+
choices=[ExperimentalFeatures[name] for name in experimental_choices],
|
|
77
88
|
help=argparse.SUPPRESS,
|
|
78
89
|
)
|
|
79
90
|
return parser
|
|
@@ -136,7 +147,7 @@ def main() -> None:
|
|
|
136
147
|
else:
|
|
137
148
|
logger.setLevel("ERROR")
|
|
138
149
|
if args.experimental:
|
|
139
|
-
|
|
150
|
+
enable_experimental_feature(args.experimental)
|
|
140
151
|
for filename in args.document:
|
|
141
152
|
try:
|
|
142
153
|
if args.list_sheets:
|
numbers_parser/cell.py
CHANGED
|
@@ -56,7 +56,6 @@ from numbers_parser.constants import (
|
|
|
56
56
|
)
|
|
57
57
|
from numbers_parser.currencies import CURRENCIES, CURRENCY_SYMBOLS
|
|
58
58
|
from numbers_parser.exceptions import UnsupportedError, UnsupportedWarning
|
|
59
|
-
from numbers_parser.formula import Formula
|
|
60
59
|
from numbers_parser.generated import TSPMessages_pb2 as TSPMessages
|
|
61
60
|
from numbers_parser.generated import TSTArchives_pb2 as TSTArchives
|
|
62
61
|
from numbers_parser.generated.TSWPArchives_pb2 import (
|
|
@@ -257,7 +256,7 @@ class Style:
|
|
|
257
256
|
"""
|
|
258
257
|
|
|
259
258
|
alignment: Alignment = DEFAULT_ALIGNMENT_CLASS # : horizontal and vertical alignment
|
|
260
|
-
bg_image: object = None # :
|
|
259
|
+
bg_image: object = None # : background image
|
|
261
260
|
bg_color: RGB | list[RGB] = None
|
|
262
261
|
font_color: RGB = field(default_factory=default_color)
|
|
263
262
|
font_size: float = DEFAULT_FONT_SIZE
|
|
@@ -651,7 +650,7 @@ class Cell(CellStorageFlags, Cacheable):
|
|
|
651
650
|
Returns
|
|
652
651
|
-------
|
|
653
652
|
str:
|
|
654
|
-
The text of the
|
|
653
|
+
The text of the formula in a cell, or `None` if there is no formula
|
|
655
654
|
present in a cell.
|
|
656
655
|
|
|
657
656
|
"""
|
|
@@ -660,17 +659,6 @@ class Cell(CellStorageFlags, Cacheable):
|
|
|
660
659
|
return table_formulas.formula(self._formula_id, self.row, self.col)
|
|
661
660
|
return None
|
|
662
661
|
|
|
663
|
-
@formula.setter
|
|
664
|
-
def formula(self, value: str) -> None:
|
|
665
|
-
self._formula_id = Formula.from_str(
|
|
666
|
-
self._model,
|
|
667
|
-
self._table_id,
|
|
668
|
-
self.row,
|
|
669
|
-
self.col,
|
|
670
|
-
value,
|
|
671
|
-
)
|
|
672
|
-
self._model.add_formula_dependency(self.row, self.col, self._table_id)
|
|
673
|
-
|
|
674
662
|
@property
|
|
675
663
|
def is_bulleted(self) -> bool:
|
|
676
664
|
"""bool: ``True`` if the cell contains text bullets."""
|
|
@@ -933,7 +921,7 @@ class Cell(CellStorageFlags, Cacheable):
|
|
|
933
921
|
elif cell_type == CURRENCY_CELL_TYPE:
|
|
934
922
|
cell = NumberCell(row, col, d128, cell_type=CellType.CURRENCY)
|
|
935
923
|
else:
|
|
936
|
-
msg = f"Cell type ID {cell_type} is not
|
|
924
|
+
msg = f"Cell type ID {cell_type} is not recognized"
|
|
937
925
|
raise UnsupportedError(msg)
|
|
938
926
|
|
|
939
927
|
cell._copy_flags(storage_flags)
|
numbers_parser/constants.py
CHANGED
|
@@ -121,6 +121,7 @@ def _week_of_month(value: datetime) -> int:
|
|
|
121
121
|
|
|
122
122
|
DATETIME_FIELD_MAP = OrderedDict(
|
|
123
123
|
[
|
|
124
|
+
# Cell formats
|
|
124
125
|
("a", lambda x: x.strftime("%p").lower()),
|
|
125
126
|
("EEEE", "%A"),
|
|
126
127
|
("EEE", "%a"),
|
|
@@ -157,6 +158,10 @@ DATETIME_FIELD_MAP = OrderedDict(
|
|
|
157
158
|
("SSS", lambda x: str(x.microsecond).zfill(6)[0:3]),
|
|
158
159
|
("SSSS", lambda x: str(x.microsecond).zfill(6)[0:4]),
|
|
159
160
|
("SSSSS", lambda x: str(x.microsecond).zfill(6)[0:5]),
|
|
161
|
+
# Table category formats
|
|
162
|
+
("QQQ", lambda x: "Q" + str(int(x.month / 3) + 1)),
|
|
163
|
+
("LLLL", "%B"),
|
|
164
|
+
("w", "%-W"),
|
|
160
165
|
],
|
|
161
166
|
)
|
|
162
167
|
|
numbers_parser/document.py
CHANGED
|
@@ -45,7 +45,7 @@ class Document:
|
|
|
45
45
|
Create an instance of a new Numbers document.
|
|
46
46
|
|
|
47
47
|
If ``filename`` is ``None``, an empty document is created using the defaults
|
|
48
|
-
defined by the class constructor. You can
|
|
48
|
+
defined by the class constructor. You can optionally override these
|
|
49
49
|
defaults at object construction time.
|
|
50
50
|
|
|
51
51
|
Parameters
|
|
@@ -303,7 +303,7 @@ class Document:
|
|
|
303
303
|
* **num_decimals** (``int``, *optional*, default: ``0``) - Integer precision
|
|
304
304
|
when decimals are padded.
|
|
305
305
|
* **show_thousands_separator** (``bool``, *optional*, default: ``False``) - ``True``
|
|
306
|
-
if the number should include a thousands
|
|
306
|
+
if the number should include a thousands separator.
|
|
307
307
|
|
|
308
308
|
:``"datetime"``:
|
|
309
309
|
* **format** (``str``, *optional*, default: ``"d MMM y"``) - A POSIX strftime-like
|
|
@@ -327,7 +327,7 @@ class Document:
|
|
|
327
327
|
try:
|
|
328
328
|
kwargs["type"] = CustomFormattingType[format_type]
|
|
329
329
|
except (KeyError, AttributeError):
|
|
330
|
-
msg = f"
|
|
330
|
+
msg = f"unsupported cell format type '{format_type}'"
|
|
331
331
|
raise TypeError(msg) from None
|
|
332
332
|
|
|
333
333
|
custom_format = CustomFormatting(**kwargs)
|
|
@@ -474,7 +474,7 @@ class Table(Cacheable):
|
|
|
474
474
|
self._table_id = table_id
|
|
475
475
|
self.num_rows = self._model.number_of_rows(self._table_id)
|
|
476
476
|
self.num_cols = self._model.number_of_columns(self._table_id)
|
|
477
|
-
# Cache all data now to
|
|
477
|
+
# Cache all data now to facilitate write(). Performance impact
|
|
478
478
|
# of computing all cells is minimal compared to file IO
|
|
479
479
|
self._data = []
|
|
480
480
|
self._model.set_table_data(table_id, self._data)
|
|
@@ -545,7 +545,7 @@ class Table(Cacheable):
|
|
|
545
545
|
------
|
|
546
546
|
ValueError:
|
|
547
547
|
If the number of headers is negative, exceeds the number of rows in the
|
|
548
|
-
table, or exceeds Numbers
|
|
548
|
+
table, or exceeds Numbers maximum number of headers (``MAX_HEADER_COUNT``).
|
|
549
549
|
|
|
550
550
|
"""
|
|
551
551
|
return self._model.num_header_rows(self._table_id)
|
|
@@ -579,7 +579,7 @@ class Table(Cacheable):
|
|
|
579
579
|
------
|
|
580
580
|
ValueError:
|
|
581
581
|
If the number of headers is negative, exceeds the number of rows in the
|
|
582
|
-
table, or exceeds Numbers
|
|
582
|
+
table, or exceeds Numbers maximum number of headers (``MAX_HEADER_COUNT``).
|
|
583
583
|
|
|
584
584
|
"""
|
|
585
585
|
return self._model.num_header_cols(self._table_id)
|
|
@@ -756,6 +756,10 @@ class Table(Cacheable):
|
|
|
756
756
|
msg = f"column {col} out of range"
|
|
757
757
|
raise IndexError(msg)
|
|
758
758
|
|
|
759
|
+
self._model.calculate_table_categories(self._table_id)
|
|
760
|
+
row_mapper = self._model._table_categories_row_mapper[self._table_id]
|
|
761
|
+
if row_mapper is not None:
|
|
762
|
+
return self._data[row_mapper[row]][col]
|
|
759
763
|
return self._data[row][col]
|
|
760
764
|
|
|
761
765
|
def iter_rows(
|
|
@@ -803,10 +807,10 @@ class Table(Cacheable):
|
|
|
803
807
|
sum += row
|
|
804
808
|
|
|
805
809
|
"""
|
|
806
|
-
min_row = min_row
|
|
807
|
-
max_row = max_row
|
|
808
|
-
min_col = min_col
|
|
809
|
-
max_col = max_col
|
|
810
|
+
min_row = min_row if min_row is not None else 0
|
|
811
|
+
max_row = max_row if max_row is not None else self.num_rows - 1
|
|
812
|
+
min_col = min_col if min_col is not None else 0
|
|
813
|
+
max_col = max_col if max_col is not None else self.num_cols - 1
|
|
810
814
|
|
|
811
815
|
if min_row < 0:
|
|
812
816
|
msg = f"row {min_row} out of range"
|
|
@@ -822,11 +826,18 @@ class Table(Cacheable):
|
|
|
822
826
|
raise IndexError(msg)
|
|
823
827
|
|
|
824
828
|
rows = self.rows()
|
|
825
|
-
|
|
829
|
+
self._model.calculate_table_categories(self._table_id)
|
|
830
|
+
row_mapper = self._model._table_categories_row_mapper[self._table_id]
|
|
831
|
+
if row_mapper is not None:
|
|
832
|
+
rows = [rows[row_mapper[row]] for row in range(min_row, max_row + 1)]
|
|
833
|
+
else:
|
|
834
|
+
rows = rows[min_row : max_row + 1]
|
|
835
|
+
|
|
836
|
+
for row in rows:
|
|
826
837
|
if values_only:
|
|
827
|
-
yield tuple(cell.value for cell in
|
|
838
|
+
yield tuple(cell.value for cell in row[min_col : max_col + 1])
|
|
828
839
|
else:
|
|
829
|
-
yield tuple(
|
|
840
|
+
yield tuple(row[min_col : max_col + 1])
|
|
830
841
|
|
|
831
842
|
def iter_cols(
|
|
832
843
|
self,
|
|
@@ -873,10 +884,10 @@ class Table(Cacheable):
|
|
|
873
884
|
sum += col.value
|
|
874
885
|
|
|
875
886
|
"""
|
|
876
|
-
min_row = min_row
|
|
877
|
-
max_row = max_row
|
|
878
|
-
min_col = min_col
|
|
879
|
-
max_col = max_col
|
|
887
|
+
min_row = min_row if min_row is not None else 0
|
|
888
|
+
max_row = max_row if max_row is not None else self.num_rows - 1
|
|
889
|
+
min_col = min_col if min_col is not None else 0
|
|
890
|
+
max_col = max_col if max_col is not None else self.num_cols - 1
|
|
880
891
|
|
|
881
892
|
if min_row < 0:
|
|
882
893
|
msg = f"row {min_row} out of range"
|
|
@@ -892,11 +903,18 @@ class Table(Cacheable):
|
|
|
892
903
|
raise IndexError(msg)
|
|
893
904
|
|
|
894
905
|
rows = self.rows()
|
|
906
|
+
self._model.calculate_table_categories(self._table_id)
|
|
907
|
+
row_mapper = self._model._table_categories_row_mapper[self._table_id]
|
|
908
|
+
if row_mapper is not None:
|
|
909
|
+
rows = [rows[row_mapper[row_num]] for row_num in range(min_row, max_row + 1)]
|
|
910
|
+
else:
|
|
911
|
+
rows = rows[min_row : max_row + 1]
|
|
912
|
+
|
|
895
913
|
for col in range(min_col, max_col + 1):
|
|
896
914
|
if values_only:
|
|
897
|
-
yield tuple(row[col].value for row in rows
|
|
915
|
+
yield tuple(row[col].value for row in rows)
|
|
898
916
|
else:
|
|
899
|
-
yield tuple(row[col] for row in rows
|
|
917
|
+
yield tuple(row[col] for row in rows)
|
|
900
918
|
|
|
901
919
|
def _validate_cell_coords(self, *args):
|
|
902
920
|
if isinstance(args[0], str):
|
|
@@ -961,7 +979,7 @@ class Table(Cacheable):
|
|
|
961
979
|
Raises
|
|
962
980
|
------
|
|
963
981
|
IndexError:
|
|
964
|
-
If the style name cannot be
|
|
982
|
+
If the style name cannot be found in the document.
|
|
965
983
|
TypeError:
|
|
966
984
|
If the style parameter is an invalid type.
|
|
967
985
|
ValueError:
|
|
@@ -999,33 +1017,60 @@ class Table(Cacheable):
|
|
|
999
1017
|
msg = "style must be a Style object or style name"
|
|
1000
1018
|
raise TypeError(msg)
|
|
1001
1019
|
|
|
1002
|
-
def categorized_data(self) -> dict | None:
|
|
1020
|
+
def categorized_data(self, values_only: bool = False) -> dict | None:
|
|
1003
1021
|
"""
|
|
1004
|
-
Return the table's data
|
|
1005
|
-
if the table has not had
|
|
1022
|
+
Return the table's data organized into categories, if enabled or ``None``
|
|
1023
|
+
if the table has not had categories enabled.
|
|
1024
|
+
|
|
1025
|
+
The data is a set of nested dictionaries and lists. Dictionary keys are
|
|
1026
|
+
Category keys and values are either another dictionary in the case of
|
|
1027
|
+
nested categories or a list of rows. Each row is itself a list such that
|
|
1028
|
+
the data is the same as returned by :py:meth:`numbers_parser.Table.rows`.
|
|
1006
1029
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1030
|
+
Parameters
|
|
1031
|
+
----------
|
|
1032
|
+
values_only:
|
|
1033
|
+
If ``True``, return cell values instead of :class:`Cell` objects
|
|
1034
|
+
|
|
1035
|
+
Returns
|
|
1036
|
+
-------
|
|
1037
|
+
Dict[str, Dict | List]:
|
|
1038
|
+
Nested dictionary of lists of rows or dictionaries of the next group
|
|
1039
|
+
of categories down. Row data is returned as :py:class:`Cell` classes
|
|
1040
|
+
unless ``values_only`` is ``True``.
|
|
1010
1041
|
|
|
1011
1042
|
Example
|
|
1012
1043
|
-------
|
|
1013
1044
|
.. code:: python
|
|
1014
1045
|
|
|
1015
1046
|
"Transport": [
|
|
1016
|
-
{"
|
|
1017
|
-
{"
|
|
1018
|
-
{"
|
|
1047
|
+
{"Airplane", "Air": 5 },
|
|
1048
|
+
{"Helicopter": "Air", 2 },
|
|
1049
|
+
{"Bus": "Road", 10 },
|
|
1019
1050
|
],
|
|
1020
1051
|
"Fruit": [
|
|
1021
|
-
{"
|
|
1022
|
-
{"
|
|
1052
|
+
{"Apple", "Green": 7 },
|
|
1053
|
+
{"Banana", "Yellow", 6 },
|
|
1023
1054
|
],
|
|
1024
1055
|
|
|
1025
1056
|
For tables with multiple categories, the top-level dictionary is nested.
|
|
1026
1057
|
|
|
1027
1058
|
"""
|
|
1028
|
-
|
|
1059
|
+
|
|
1060
|
+
def data_to_values(item):
|
|
1061
|
+
if isinstance(item, dict):
|
|
1062
|
+
return {k: data_to_values(v) for k, v in item.items()}
|
|
1063
|
+
|
|
1064
|
+
if isinstance(item, list):
|
|
1065
|
+
return [data_to_values(v) for v in item]
|
|
1066
|
+
|
|
1067
|
+
return item.value
|
|
1068
|
+
|
|
1069
|
+
self._model.calculate_table_categories(self._table_id)
|
|
1070
|
+
data = self._model._table_categories_data[self._table_id]
|
|
1071
|
+
if values_only:
|
|
1072
|
+
return data_to_values(data)
|
|
1073
|
+
return data
|
|
1029
1074
|
|
|
1030
1075
|
def add_row(
|
|
1031
1076
|
self,
|
|
@@ -1279,7 +1324,7 @@ class Table(Cacheable):
|
|
|
1279
1324
|
"""
|
|
1280
1325
|
Set the borders for a cell.
|
|
1281
1326
|
|
|
1282
|
-
Cell references can be row-column
|
|
1327
|
+
Cell references can be row-column offsets or Excel/Numbers-style A1 notation. Borders
|
|
1283
1328
|
can be applied to multiple sides of a cell by passing a list of sides. The name(s)
|
|
1284
1329
|
of the side(s) must be one of ``"top"``, ``"right"``, ``"bottom"`` or ``"left"``.
|
|
1285
1330
|
|
|
@@ -1299,7 +1344,7 @@ class Table(Cacheable):
|
|
|
1299
1344
|
* **param2** (*int*): The column number (zero indexed).
|
|
1300
1345
|
* **param3** (*str | List[str]*): Which side(s) of the cell to apply the border to.
|
|
1301
1346
|
* **param4** (:py:class:`Border`): The border to add.
|
|
1302
|
-
* **param5** (*int*, *
|
|
1347
|
+
* **param5** (*int*, *optional*, default: 1): The length of the stroke to add.
|
|
1303
1348
|
|
|
1304
1349
|
:Args (A1):
|
|
1305
1350
|
* **param1** (*str*): A cell reference using Excel/Numbers-style A1 notation.
|
|
@@ -1380,7 +1425,7 @@ class Table(Cacheable):
|
|
|
1380
1425
|
r"""
|
|
1381
1426
|
Set the data format for a cell.
|
|
1382
1427
|
|
|
1383
|
-
Cell references can be **row-column**
|
|
1428
|
+
Cell references can be **row-column** offsets or Excel/Numbers-style **A1** notation.
|
|
1384
1429
|
|
|
1385
1430
|
.. code:: python
|
|
1386
1431
|
|
|
@@ -1463,8 +1508,8 @@ class Table(Cacheable):
|
|
|
1463
1508
|
decimal places, or ``None`` for automatic.
|
|
1464
1509
|
|
|
1465
1510
|
:``"custom"``:
|
|
1466
|
-
* **format** (*str |
|
|
1467
|
-
|
|
1511
|
+
* **format** (*str | CustomFormatting*) - The name of a custom
|
|
1512
|
+
formatting the document or a :py:class:`~numbers_parser.CustomFormatting`
|
|
1468
1513
|
object.
|
|
1469
1514
|
|
|
1470
1515
|
:``"currency"``:
|
|
@@ -1475,7 +1520,7 @@ class Table(Cacheable):
|
|
|
1475
1520
|
* **negative_style** (*:py:class:`~numbers_parser.NegativeNumberStyle`, optional, default: NegativeNumberStyle.MINUS*) - How negative numbers are represented.
|
|
1476
1521
|
See `Negative number formats <#negative-formats>`_.
|
|
1477
1522
|
* **show_thousands_separator** (*bool, optional, default: False*) - ``True``
|
|
1478
|
-
if the number should include a thousands
|
|
1523
|
+
if the number should include a thousands separator, e.g. ``,``
|
|
1479
1524
|
* **use_accounting_style** (*bool, optional, default: False*) - ``True``
|
|
1480
1525
|
if the currency symbol should be formatted to the left of the cell and
|
|
1481
1526
|
separated from the number value by a tab.
|
|
@@ -1495,7 +1540,7 @@ class Table(Cacheable):
|
|
|
1495
1540
|
* **negative_style** (*:py:class:`~numbers_parser.NegativeNumberStyle`, optional, default: NegativeNumberStyle.MINUS*) - How negative numbers are represented.
|
|
1496
1541
|
See `Negative number formats <#negative-formats>`_.
|
|
1497
1542
|
* **show_thousands_separator** (*bool, optional, default: False*) - ``True``
|
|
1498
|
-
if the number should include a thousands
|
|
1543
|
+
if the number should include a thousands separator, e.g. ``,``
|
|
1499
1544
|
|
|
1500
1545
|
:``"scientific"``:
|
|
1501
1546
|
* **decimal_places** (*float, optional, default: None*) - number of
|
|
@@ -1586,7 +1631,7 @@ class Table(Cacheable):
|
|
|
1586
1631
|
format_type = FormattingType[format_type_name.upper()]
|
|
1587
1632
|
_ = FORMATTING_ALLOWED_CELLS[format_type_name]
|
|
1588
1633
|
except (KeyError, AttributeError):
|
|
1589
|
-
msg = f"
|
|
1634
|
+
msg = f"unsupported cell format type '{format_type_name}'"
|
|
1590
1635
|
raise TypeError(msg) from None
|
|
1591
1636
|
|
|
1592
1637
|
cell = self._data[row][col]
|
numbers_parser/exceptions.py
CHANGED
numbers_parser/experimental.py
CHANGED
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from enum import Flag, auto
|
|
2
3
|
|
|
3
4
|
logger = logging.getLogger(__name__)
|
|
4
5
|
debug = logger.debug
|
|
5
6
|
|
|
6
|
-
_EXPERIMENTAL_FEATURES = False
|
|
7
7
|
|
|
8
|
+
class ExperimentalFeatures(Flag):
|
|
9
|
+
NONE = auto()
|
|
10
|
+
TESTING = auto()
|
|
11
|
+
GROUPED_CATEGORIES = auto()
|
|
8
12
|
|
|
9
|
-
def _enable_experimental_features(status: bool) -> None:
|
|
10
|
-
global _EXPERIMENTAL_FEATURES
|
|
11
|
-
_EXPERIMENTAL_FEATURES = status
|
|
12
|
-
debug("Experimental features %s", "on" if status else "off")
|
|
13
13
|
|
|
14
|
+
EXPERIMENTAL_FEATURES = ExperimentalFeatures.NONE
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
|
|
17
|
+
def enable_experimental_feature(flags: ExperimentalFeatures) -> None:
|
|
18
|
+
global EXPERIMENTAL_FEATURES
|
|
19
|
+
EXPERIMENTAL_FEATURES |= flags
|
|
20
|
+
debug("Experimental features: enabling %s, flags=%s", flags, EXPERIMENTAL_FEATURES)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def disable_experimental_feature(flags: ExperimentalFeatures) -> None:
|
|
24
|
+
global EXPERIMENTAL_FEATURES
|
|
25
|
+
EXPERIMENTAL_FEATURES ^= flags
|
|
26
|
+
debug("Experimental features: disabling %s, flags=%s", flags, EXPERIMENTAL_FEATURES)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def experimental_features() -> ExperimentalFeatures:
|
|
30
|
+
return EXPERIMENTAL_FEATURES
|