numbers-parser 4.14.1__tar.gz → 4.14.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 (66) hide show
  1. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/PKG-INFO +14 -1
  2. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/README.md +13 -0
  3. numbers_parser-4.14.2/pyproject.toml +193 -0
  4. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/__init__.py +3 -3
  5. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/_cat_numbers.py +10 -11
  6. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/_csv2numbers.py +13 -14
  7. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/_unpack_numbers.py +4 -4
  8. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/cell.py +257 -229
  9. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/constants.py +6 -3
  10. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/containers.py +11 -10
  11. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/document.py +196 -149
  12. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/exceptions.py +1 -8
  13. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/formula.py +28 -30
  14. numbers_parser-4.14.2/src/numbers_parser/generated/TSKArchives_pb2.py +146 -0
  15. numbers_parser-4.14.2/src/numbers_parser/generated/TSSArchives_pb2.py +64 -0
  16. numbers_parser-4.14.2/src/numbers_parser/generated/TSWPCommandArchives_pb2.py +133 -0
  17. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/fontmap.py +16 -10
  18. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/mapping.py +0 -1
  19. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/iwafile.py +16 -16
  20. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/iwork.py +32 -17
  21. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/model.py +222 -210
  22. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/numbers_cache.py +6 -7
  23. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/numbers_uuid.py +4 -1
  24. numbers_parser-4.14.2/src/numbers_parser/roman.py +32 -0
  25. numbers_parser-4.14.1/pyproject.toml +0 -147
  26. numbers_parser-4.14.1/src/numbers_parser/generated/TSKArchives_pb2.py +0 -146
  27. numbers_parser-4.14.1/src/numbers_parser/generated/TSSArchives_pb2.py +0 -64
  28. numbers_parser-4.14.1/src/numbers_parser/generated/TSWPCommandArchives_pb2.py +0 -133
  29. numbers_parser-4.14.1/src/numbers_parser/roman.py +0 -18
  30. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/LICENSE.rst +0 -0
  31. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/bullets.py +0 -0
  32. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/currencies.py +0 -0
  33. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/data/empty.numbers +0 -0
  34. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/experimental.py +0 -0
  35. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TNArchives_pb2.py +0 -0
  36. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TNArchives_sos_pb2.py +0 -0
  37. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TNCommandArchives_pb2.py +0 -0
  38. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TNCommandArchives_sos_pb2.py +0 -0
  39. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSAArchives_pb2.py +0 -0
  40. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSAArchives_sos_pb2.py +0 -0
  41. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSACommandArchives_sos_pb2.py +0 -0
  42. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCEArchives_pb2.py +0 -0
  43. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCH3DArchives_pb2.py +0 -0
  44. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCHArchives_Common_pb2.py +0 -0
  45. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCHArchives_GEN_pb2.py +0 -0
  46. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCHArchives_pb2.py +0 -0
  47. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCHArchives_sos_pb2.py +0 -0
  48. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCHCommandArchives_pb2.py +0 -0
  49. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCHPreUFFArchives_pb2.py +0 -0
  50. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCKArchives_pb2.py +0 -0
  51. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCKArchives_sos_pb2.py +0 -0
  52. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSDArchives_pb2.py +0 -0
  53. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSDArchives_sos_pb2.py +0 -0
  54. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSDCommandArchives_pb2.py +0 -0
  55. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSPArchiveMessages_pb2.py +0 -0
  56. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSPDatabaseMessages_pb2.py +0 -0
  57. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSPMessages_pb2.py +0 -0
  58. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSSArchives_sos_pb2.py +0 -0
  59. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSTArchives_pb2.py +0 -0
  60. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSTArchives_sos_pb2.py +0 -0
  61. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSTCommandArchives_pb2.py +0 -0
  62. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSTStylePropertyArchiving_pb2.py +0 -0
  63. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSWPArchives_pb2.py +0 -0
  64. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSWPArchives_sos_pb2.py +0 -0
  65. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/__init__.py +0 -0
  66. {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/functionmap.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: numbers-parser
3
- Version: 4.14.1
3
+ Version: 4.14.2
4
4
  Summary: Read and write Apple Numbers spreadsheets
5
5
  Home-page: https://github.com/masaccio/numbers-parser
6
6
  License: MIT
@@ -128,6 +128,18 @@ Cells are objects with a common base class of `Cell`. All cell types
128
128
  have a property `value` which returns the contents of the cell as a
129
129
  python datatype. Available cell types are:
130
130
 
131
+ | Cell type | value type | Additional properties |
132
+ |--------------|----------------------|--------------------------------------------------------------------------------------------------------|
133
+ | NumberCell | `float` | |
134
+ | TextCell | `str` | |
135
+ | RichTextCell | `str` | See [Rich text](https://masaccio.github.io/numbers-parser/api/cells.html#numbers_parser.RichTextCell) |
136
+ | EmptyCell | `None` | |
137
+ | BoolCell | `bool` | |
138
+ | DateCell | `datetime.datetime` | |
139
+ | DurationCell | `datetime.timedelta` | |
140
+ | ErrorCell | `None` | |
141
+ | MergedCell | `None` | See [Merged cells](https://masaccio.github.io/numbers-parser/api/cells.html#numbers_parser.MergedCell) |
142
+
131
143
  Cell references can be either zero-offset row/column integers or an
132
144
  Excel/Numbers A1 notation. Where cell values are not `None` the
133
145
  property `formatted_value` returns the cell value as a `str` as
@@ -489,6 +501,7 @@ The following limitations are expected to always remain:
489
501
  (see [issue 69](https://github.com/masaccio/numbers-parser/issues/69) for details).
490
502
  - Password-encrypted documents cannot be opened. You must first re-save without
491
503
  a password to read (see [issue 88](https://github.com/masaccio/numbers-parser/issues/88) for details).
504
+ A UnsupportedError exception is raised when such documents are opened.
492
505
 
493
506
  ## License
494
507
 
@@ -99,6 +99,18 @@ Cells are objects with a common base class of `Cell`. All cell types
99
99
  have a property `value` which returns the contents of the cell as a
100
100
  python datatype. Available cell types are:
101
101
 
102
+ | Cell type | value type | Additional properties |
103
+ |--------------|----------------------|--------------------------------------------------------------------------------------------------------|
104
+ | NumberCell | `float` | |
105
+ | TextCell | `str` | |
106
+ | RichTextCell | `str` | See [Rich text](https://masaccio.github.io/numbers-parser/api/cells.html#numbers_parser.RichTextCell) |
107
+ | EmptyCell | `None` | |
108
+ | BoolCell | `bool` | |
109
+ | DateCell | `datetime.datetime` | |
110
+ | DurationCell | `datetime.timedelta` | |
111
+ | ErrorCell | `None` | |
112
+ | MergedCell | `None` | See [Merged cells](https://masaccio.github.io/numbers-parser/api/cells.html#numbers_parser.MergedCell) |
113
+
102
114
  Cell references can be either zero-offset row/column integers or an
103
115
  Excel/Numbers A1 notation. Where cell values are not `None` the
104
116
  property `formatted_value` returns the cell value as a `str` as
@@ -460,6 +472,7 @@ The following limitations are expected to always remain:
460
472
  (see [issue 69](https://github.com/masaccio/numbers-parser/issues/69) for details).
461
473
  - Password-encrypted documents cannot be opened. You must first re-save without
462
474
  a password to read (see [issue 88](https://github.com/masaccio/numbers-parser/issues/88) for details).
475
+ A UnsupportedError exception is raised when such documents are opened.
463
476
 
464
477
  ## License
465
478
 
@@ -0,0 +1,193 @@
1
+ [tool.poetry]
2
+ authors = ["Jon Connell <python@figsandfudge.com>"]
3
+ classifiers = [
4
+ "Topic :: Office/Business :: Financial :: Spreadsheet",
5
+ "Programming Language :: Python :: 3",
6
+ "Operating System :: OS Independent",
7
+ ]
8
+ description = "Read and write Apple Numbers spreadsheets"
9
+ documentation = "https://github.com/masaccio/numbers-parser/blob/main/README.md"
10
+ license = "MIT"
11
+ name = "numbers-parser"
12
+ packages = [{include = "numbers_parser", from = "src"}]
13
+ readme = "README.md"
14
+ repository = "https://github.com/masaccio/numbers-parser"
15
+ version = "4.14.2"
16
+
17
+ [tool.poetry.scripts]
18
+ cat-numbers = "numbers_parser._cat_numbers:main"
19
+ unpack-numbers = "numbers_parser._unpack_numbers:main"
20
+ csv2numbers = "numbers_parser._csv2numbers:main"
21
+
22
+ [tool.poetry.dependencies]
23
+ compact-json = "^1.1.3"
24
+ protobuf = "*"
25
+ python = ">=3.9,<4.0"
26
+ python-snappy = "^0.7"
27
+ sigfig = "^1.3.3"
28
+ setuptools = ">=70.0.0"
29
+ importlib-resources = ">=6.1"
30
+ enum-tools = ">=0.11"
31
+ python-dateutil = "^2.9.0.post0"
32
+
33
+ [tool.poetry.group.dev.dependencies]
34
+ gprof2dot = "^2022.7.29"
35
+ line-profiler = "^4.0.3"
36
+ mock = ">=5.1.0"
37
+ psutil = ">=5.9"
38
+ pytest = ">=7.2.0"
39
+ pytest-check = ">=1.0"
40
+ pytest-console-scripts = "^1.3.1"
41
+ pytest-cov = ">=4.0,>=5.0"
42
+ pytest-xdist = "^3.3.1"
43
+ ruff = "*"
44
+ tox = "^4.11.4"
45
+ python-magic = ">=0.4"
46
+ tqdm = ">=4.66"
47
+ colorama = "^0.4.6"
48
+ roman = "^4.2"
49
+
50
+ [tool.poetry.group.docs]
51
+ optional = true
52
+
53
+ [tool.poetry.group.docs.dependencies]
54
+ sphinx = ">= 7.3"
55
+ enum-tools = ">=0.11"
56
+ sphinx-toolbox = ">=3.5"
57
+ sphinx-nefertiti = ">=0.3.3"
58
+ sphinx-markdown-builder = ">=0.6"
59
+ sphinx-copybutton = ">=0.5"
60
+
61
+ [tool.poetry.group.bootstrap]
62
+ optional = true
63
+
64
+ [tool.poetry.group.bootstrap.dependencies]
65
+ pyobjc-core = ">=10.2"
66
+ pyobjc-framework-Cocoa = ">=10.2"
67
+ py2app = ">=0.28"
68
+
69
+ [build-system]
70
+ build-backend = "poetry.core.masonry.api"
71
+ requires = ["poetry-core>=1.0.0"]
72
+
73
+ [tool.coverage.run]
74
+ branch = true
75
+ omit = ["src/numbers_parser/generated/*.py"]
76
+
77
+ [tool.coverage.html]
78
+ directory = "coverage_html_report"
79
+ show_contexts = true
80
+
81
+ [tool.pytest.ini_options]
82
+ addopts = "--cov=src/numbers_parser --cov-report=html --cov-report=term-missing:skip-covered --cov-context=test"
83
+
84
+ [tool.tox]
85
+ legacy_tox_ini = """
86
+ [tox]
87
+ isolated_build = true
88
+ envlist = py39, py310, py311, py312, py313
89
+ [testenv]
90
+ deps =
91
+ pytest
92
+ pytest-check
93
+ pytest-console-scripts
94
+ pytest-cov
95
+ pytest-xdist
96
+ python-magic
97
+ psutil
98
+ roman
99
+ commands =
100
+ pytest tests/ --import-mode importlib -n logical --no-cov
101
+ """
102
+
103
+ [tool.isort]
104
+ profile = "black"
105
+
106
+ [tool.ruff]
107
+ exclude = [
108
+ # Machine-generated files
109
+ "**/.bootstrap/*",
110
+ "**/.tox/*",
111
+ "**/.vscode/*",
112
+ "**/src/numbers_parser/generated/*",
113
+ # Third-party files not to lint
114
+ "**/src/debug/lldbutil.py",
115
+ ]
116
+ fix = true
117
+ lint.ignore = [
118
+ # "PLR2004", # Allow constant values
119
+ "T201", # Allow print()
120
+ # To fix:
121
+ "ANN001", #Missing type annotation
122
+ "ANN002", #Missing type annotation
123
+ "ANN003", #Missing type annotation
124
+ "ANN201", #Missing return type annotation
125
+ "ANN202", #Missing return type annotation
126
+ "ANN204", #Missing return type annotation
127
+ "ANN205", #Missing return type annotation
128
+ "ANN206", #Missing return type annotation
129
+ "ANN401", #Dynamically typed expressions
130
+ "ARG001", #Unused function argument
131
+ "ARG002", #Unused method argument
132
+ "ARG003", #Unused class method argument
133
+ "BLE001", #Do not catch blind exception
134
+ "C901", #code too complex
135
+ "D100", #Missing docstring
136
+ "D101", #Missing docstring
137
+ "D102", #Missing docstring
138
+ "D103", #Missing docstring
139
+ "D105", #Missing docstring
140
+ "D107", #Missing docstring
141
+ "D205", # Blank line - conflicts with black
142
+ "D210", #No whitespaces allowed surrounding docstring text
143
+ "D400", #First line should end with a period
144
+ "D401", #First line of docstring should be in imperative mood
145
+ "D415", #First line should end with a period, question mark, or exclamation point
146
+ "E501", #Line too long
147
+ "ERA001", #Found commented-out code
148
+ "FBT001", #Boolean-typed positional argument in function definition
149
+ "FBT002", #Boolean default positional argument in function definition
150
+ "FBT003", #Boolean positional value in function call
151
+ "FIX002", #Line contains TODO, consider resolving the issue
152
+ "ISC003", #Explicitly concatenated string should be implicitly concatenated
153
+ "PERF401", #Use a list comprehension to create a transformed list
154
+ "PLR0913", #Too many arguments
155
+ "PLR2004", #Magic value used
156
+ "PLW0603", #Using the global statement
157
+ "PTH103", # Use of os.makedirs()
158
+ "PTH118", # Use of os.path.join()
159
+ "PTH119", # Use of os.path.basename()
160
+ "PTH123", # Use of open()
161
+ "RUF001", #String contains ambiguous
162
+ "RUF002", #Docstring contains ambiguous
163
+ "RUF003", #Comment contains ambiguous
164
+ "SIM115", #Use a context manager for opening files
165
+ "SLF001", #Private member accessed
166
+ "TD002", # TODO author
167
+ "TD003", # TODO author
168
+ # Conflicts with other rules
169
+ "D212", # Multiline summary first line
170
+ "D203", # Blank lines before class docstrings
171
+ "D416", # Colon after after section titles
172
+ ]
173
+ line-length = 100
174
+ lint.select = [ "ALL" ]
175
+ src = ["src", "tests"]
176
+ target-version = "py39"
177
+ lint.unfixable = [
178
+ "ERA", # do not autoremove commented out code
179
+ ]
180
+
181
+ [tool.ruff.lint.pylint]
182
+ max-statements = 100
183
+ max-branches = 20
184
+
185
+ [tool.ruff.lint.flake8-tidy-imports]
186
+ ban-relative-imports = "all"
187
+
188
+ [tool.ruff.lint.per-file-ignores]
189
+ "docs/conf.py" = ["INP001"]
190
+ "src/build/**" = ["PLR2004", "INP001", "PTH"]
191
+ "src/build/protodump.py" = ["PLR2004", "INP001", "PTH", "S110", "N806"]
192
+ "src/debug/**" = ["INP001"]
193
+ "tests/**" = ["PLR2004", "S101", "D103", "ANN201", "ANN001"]
@@ -1,4 +1,4 @@
1
- __doc__ = """Parse and extract data from Apple Numbers spreadsheets"""
1
+ """Parse and extract data from Apple Numbers spreadsheets."""
2
2
 
3
3
  import importlib.metadata
4
4
  import os
@@ -20,11 +20,11 @@ _DEFAULT_NUMBERS_INSTALL_PATH = "/Applications/Numbers.app"
20
20
  _VERSION_PLIST_PATH = "Contents/version.plist"
21
21
 
22
22
 
23
- def _get_version():
23
+ def _get_version() -> str:
24
24
  return __version__
25
25
 
26
26
 
27
- def _check_installed_numbers_version():
27
+ def _check_installed_numbers_version() -> str:
28
28
  try:
29
29
  fp = open(os.path.join(_DEFAULT_NUMBERS_INSTALL_PATH, _VERSION_PLIST_PATH), "rb")
30
30
  except OSError:
@@ -71,12 +71,12 @@ def command_line_parser():
71
71
  return parser
72
72
 
73
73
 
74
- def print_sheet_names(filename):
74
+ def print_sheet_names(filename) -> None:
75
75
  for sheet in Document(filename).sheets:
76
76
  print(f"{filename}: {sheet.name}")
77
77
 
78
78
 
79
- def print_table_names(filename):
79
+ def print_table_names(filename) -> None:
80
80
  for sheet in Document(filename).sheets:
81
81
  for table in sheet.tables:
82
82
  print(f"{filename}: {sheet.name}: {table.name}")
@@ -85,19 +85,18 @@ def print_table_names(filename):
85
85
  def cell_as_string(args, cell):
86
86
  if isinstance(cell, ErrorCell) and not (args.formulas):
87
87
  return "#REF!"
88
- elif args.formulas and cell.formula is not None:
88
+ if args.formulas and cell.formula is not None:
89
89
  return cell.formula
90
- elif args.formatting and cell.formatted_value is not None:
90
+ if args.formatting and cell.formatted_value is not None:
91
91
  return cell.formatted_value
92
- elif isinstance(cell, NumberCell):
92
+ if isinstance(cell, NumberCell):
93
93
  return sigfig(cell.value, sigfigs=MAX_SIGNIFICANT_DIGITS, warn=False)
94
- elif cell.value is None:
94
+ if cell.value is None:
95
95
  return ""
96
- else:
97
- return str(cell.value)
96
+ return str(cell.value)
98
97
 
99
98
 
100
- def print_table(args, filename):
99
+ def print_table(args, filename) -> None:
101
100
  writer = csv.writer(sys.stdout, dialect="excel")
102
101
  for sheet in Document(filename).sheets:
103
102
  if args.sheet is not None and sheet.name not in args.sheet:
@@ -112,7 +111,7 @@ def print_table(args, filename):
112
111
  writer.writerow(cells)
113
112
 
114
113
 
115
- def main():
114
+ def main() -> None:
116
115
  parser = command_line_parser()
117
116
  args = parser.parse_args()
118
117
 
@@ -138,7 +137,7 @@ def main():
138
137
  print_table_names(filename)
139
138
  else:
140
139
  print_table(args, filename)
141
- except FileFormatError as e:
140
+ except FileFormatError as e: # noqa: PERF203
142
141
  print(f"{filename}:", str(e), file=sys.stderr)
143
142
  sys.exit(1)
144
143
  except FileError as e:
@@ -3,13 +3,14 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import argparse
6
+ import contextlib
6
7
  import csv
7
8
  import re
8
9
  from dataclasses import dataclass
9
10
  from datetime import datetime, timezone
10
11
  from pathlib import Path
11
12
  from sys import exit, stderr
12
- from typing import NamedTuple, Tuple # noqa: F401
13
+ from typing import NamedTuple
13
14
 
14
15
  from dateutil.parser import parse
15
16
 
@@ -68,18 +69,18 @@ class Converter:
68
69
  """Parse a date string and return a datetime."""
69
70
  return parse(x, dayfirst=self.day_first).replace(tzinfo=timezone.utc)
70
71
 
71
- def _transform_data(self):
72
+ def _transform_data(self) -> None:
72
73
  """Apply type transformations to the data based in current configuration."""
73
74
  # Convert data rows to dicts. csv.DictReader is not enough as we support CSV
74
75
  # files with no header.
75
76
  if self.no_header:
76
- self.header = [x for x in range(len(self.data[0]))]
77
- self.data = [{k: v for k, v in dict(zip(self.header, row)).items()} for row in self.data]
77
+ self.header = list(range(len(self.data[0])))
78
+ self.data = [dict(dict(zip(self.header, row)).items()) for row in self.data]
78
79
 
79
80
  if self.reverse:
80
81
  self.data = list(reversed(self.data))
81
82
  if self.date_columns is not None:
82
- is_date_column = {x: True if x in self.date_columns else False for x in self.header}
83
+ is_date_column = {x: x in self.date_columns for x in self.header}
83
84
  for row in self.data:
84
85
  for k, v in row.items():
85
86
  if self.whitespace:
@@ -88,17 +89,15 @@ class Converter:
88
89
  row[k] = self._parse_date(v)
89
90
  else:
90
91
  # Attempt to coerce value into float
91
- try:
92
+ with contextlib.suppress(ValueError):
92
93
  row[k] = float(v.replace(",", ""))
93
- except ValueError:
94
- pass
95
94
 
96
95
  def rename_columns(self: Converter, mapper: dict) -> None:
97
96
  """Rename columns using column map."""
98
97
  if mapper is None:
99
98
  return
100
99
  self.no_header = False
101
- self.header = [mapper[x] if x in mapper else x for x in self.header]
100
+ self.header = [mapper.get(x, x) for x in self.header]
102
101
 
103
102
  def delete_columns(self: Converter, columns: list) -> None:
104
103
  """Delete columns from the data."""
@@ -131,10 +130,7 @@ class Converter:
131
130
  doc = Document(num_rows=2, num_cols=2)
132
131
  table = doc.sheets[0].tables[0]
133
132
 
134
- if self.no_header:
135
- data = []
136
- else:
137
- data = [self.header]
133
+ data = [] if self.no_header else [self.header]
138
134
  data += [row.values() for row in self.data]
139
135
 
140
136
  for row_num, row in enumerate(data):
@@ -142,7 +138,10 @@ class Converter:
142
138
  table.write(row_num, col_num, value)
143
139
  if isinstance(value, datetime):
144
140
  table.set_cell_formatting(
145
- row_num, col_num, "datetime", date_time_format="d MMM yyyy"
141
+ row_num,
142
+ col_num,
143
+ "datetime",
144
+ date_time_format="d MMM yyyy",
146
145
  )
147
146
 
148
147
  doc.save(self.output_filename)
@@ -64,13 +64,13 @@ class NumbersUnpacker(IWorkHandler):
64
64
  with open(target_path, "wb") as out:
65
65
  out.write(blob)
66
66
 
67
- def ensure_directory_exists(self, path: str):
67
+ def ensure_directory_exists(self, path: str) -> None:
68
68
  """Ensure that a path's directory exists."""
69
69
  parts = os.path.split(path)
70
70
  with contextlib.suppress(OSError):
71
71
  os.makedirs(os.path.join(*([self.output_dir, *list(parts[:-1])])))
72
72
 
73
- def prettify_uuids(self, obj: object):
73
+ def prettify_uuids(self, obj: object) -> None:
74
74
  if isinstance(obj, dict):
75
75
  for k, v in obj.items():
76
76
  if isinstance(v, dict):
@@ -90,7 +90,7 @@ class NumbersUnpacker(IWorkHandler):
90
90
  elif isinstance(v, list):
91
91
  self.prettify_uuids(v)
92
92
 
93
- def prettify_cell_storage(self, obj: object):
93
+ def prettify_cell_storage(self, obj: object) -> None:
94
94
  if isinstance(obj, dict):
95
95
  for k, v in obj.items():
96
96
  if isinstance(v, (dict, list)):
@@ -117,7 +117,7 @@ class NumbersUnpacker(IWorkHandler):
117
117
  return version in SUPPORTED_NUMBERS_VERSIONS
118
118
 
119
119
 
120
- def main():
120
+ def main() -> None:
121
121
  parser = ArgumentParser()
122
122
  parser.add_argument("document", help="Apple Numbers file(s)", nargs="*")
123
123
  parser.add_argument("-V", "--version", action="store_true")