numbers-parser 4.4.6__tar.gz → 4.4.8__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 (59) hide show
  1. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/PKG-INFO +8 -5
  2. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/README.md +5 -3
  3. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/pyproject.toml +65 -26
  4. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/__init__.py +7 -5
  5. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/_cat_numbers.py +1 -2
  6. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/_unpack_numbers.py +14 -16
  7. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/cell.py +33 -29
  8. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/cell_storage.py +36 -29
  9. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/constants.py +2 -2
  10. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/containers.py +12 -14
  11. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/document.py +54 -45
  12. numbers_parser-4.4.8/src/numbers_parser/exceptions.py +40 -0
  13. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/file.py +18 -8
  14. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/formula.py +7 -3
  15. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/iwafile.py +23 -27
  16. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/model.py +112 -131
  17. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/numbers_cache.py +3 -2
  18. numbers_parser-4.4.6/src/numbers_parser/exceptions.py +0 -40
  19. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/LICENSE.rst +0 -0
  20. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/bullets.py +1 -1
  21. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/data/empty.numbers +0 -0
  22. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/experimental.py +0 -0
  23. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TNArchives_pb2.py +0 -0
  24. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TNArchives_sos_pb2.py +0 -0
  25. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TNCommandArchives_pb2.py +0 -0
  26. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TNCommandArchives_sos_pb2.py +0 -0
  27. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSAArchives_pb2.py +0 -0
  28. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSAArchives_sos_pb2.py +0 -0
  29. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSACommandArchives_sos_pb2.py +0 -0
  30. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSCEArchives_pb2.py +0 -0
  31. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSCH3DArchives_pb2.py +0 -0
  32. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSCHArchives_Common_pb2.py +0 -0
  33. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSCHArchives_GEN_pb2.py +0 -0
  34. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSCHArchives_pb2.py +0 -0
  35. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSCHArchives_sos_pb2.py +0 -0
  36. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSCHCommandArchives_pb2.py +0 -0
  37. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSCHPreUFFArchives_pb2.py +0 -0
  38. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSDArchives_pb2.py +0 -0
  39. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSDArchives_sos_pb2.py +0 -0
  40. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSDCommandArchives_pb2.py +0 -0
  41. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSKArchives_pb2.py +0 -0
  42. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSKArchives_sos_pb2.py +0 -0
  43. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSPArchiveMessages_pb2.py +0 -0
  44. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSPDatabaseMessages_pb2.py +0 -0
  45. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSPMessages_pb2.py +0 -0
  46. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSSArchives_pb2.py +0 -0
  47. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSSArchives_sos_pb2.py +0 -0
  48. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSTArchives_pb2.py +0 -0
  49. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSTArchives_sos_pb2.py +0 -0
  50. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSTCommandArchives_pb2.py +0 -0
  51. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSTStylePropertyArchiving_pb2.py +0 -0
  52. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSWPArchives_pb2.py +0 -0
  53. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSWPArchives_sos_pb2.py +0 -0
  54. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSWPCommandArchives_pb2.py +0 -0
  55. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/__init__.py +0 -0
  56. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/fontmap.py +0 -0
  57. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/functionmap.py +0 -0
  58. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/mapping.py +24 -24
  59. {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/numbers_uuid.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: numbers-parser
3
- Version: 4.4.6
3
+ Version: 4.4.8
4
4
  Summary: Read and write Apple Numbers spreadsheets
5
5
  Home-page: https://github.com/masaccio/numbers-parser
6
6
  License: MIT
@@ -17,11 +17,12 @@ Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3
18
18
  Classifier: Topic :: Office/Business :: Financial :: Spreadsheet
19
19
  Requires-Dist: compact-json (>=1.1.3,<2.0.0)
20
- Requires-Dist: pendulum (>=2.1.2,<3.0.0)
20
+ Requires-Dist: pendulum (>=3.0,<4.0)
21
21
  Requires-Dist: protobuf (>=4.21.1,<5.0.0)
22
22
  Requires-Dist: python-snappy (>=0.6.1,<0.7.0)
23
23
  Requires-Dist: regex (>=2022.9.13,<2023.0.0)
24
24
  Requires-Dist: roman (>=3.3,<4.0)
25
+ Requires-Dist: setuptools (>=69.0.3,<70.0.0)
25
26
  Requires-Dist: sigfig (>=1.3.2,<2.0.0)
26
27
  Project-URL: Documentation, https://github.com/masaccio/numbers-parser/blob/main/README.md
27
28
  Project-URL: Repository, https://github.com/masaccio/numbers-parser
@@ -34,9 +35,9 @@ Description-Content-Type: text/markdown
34
35
  [![codecov](https://codecov.io/gh/masaccio/numbers-parser/branch/main/graph/badge.svg?token=EKIUFGT05E)](https://codecov.io/gh/masaccio/numbers-parser)
35
36
  [![PyPI version](https://badge.fury.io/py/numbers-parser.svg)](https://badge.fury.io/py/numbers-parser)
36
37
 
37
- `numbers-parser` is a Python module for parsing [Apple Numbers](https://www.apple.com/numbers/)`.numbers` files. It supports Numbers files generated by Numbers version 10.3, and up with the latest tested version being 13.1 (current as of June 2023).
38
+ `numbers-parser` is a Python module for parsing [Apple Numbers](https://www.apple.com/numbers/)`.numbers` files. It supports Numbers files generated by Numbers version 10.3, and up with the latest tested version being 13.2 (current as of September 2023).
38
39
 
39
- It supports and is tested against Python versions from 3.8 onwards. It is not compatible with earlier versions of Python.
40
+ It supports and is tested against Python versions from 3.8 onwards. It is not compatible with earlier versions of Python. Python 3.12 is not supported because of dependencies that do not currently work with 3.12. This is expected to be resolved by November 2023.
40
41
 
41
42
  ## Installation
42
43
 
@@ -78,7 +79,7 @@ C:\Users\Jon>pip install C:\Users\Jon\Downloads\python_snappy-0.6.1-cp311-cp311-
78
79
 
79
80
  To better partition cell styles, background image data which was supported in earlier versions through the methods `image_data` and `image_filename` is now part of the new `cell_style` property. Using the deprecated methods `image_data` and `image_filename` will issue a `DeprecationWarning` if used.The legacy methods will be removed in a future version of numbers-parser.
80
81
 
81
- `NumberCell` cell values are now limited to 15 significant figures to match the implementation of floating point numbers in Apple Numbers. For example, the value `1234567890123456` is rounded to `1234567890123460` in the same was as in Numbers. Previously, using native `float` with no checking resulted in rounding errors in unpacking internal numbers. Attempting to write a number with too many significant digits results in a `RuntimeWarning`.
82
+ `NumberCell` cell values are now limited to 15 significant figures to match the implementation of floating point numbers in Apple Numbers. For example, the value `1234567890123456` is rounded to `1234567890123460` in the same way as in Numbers. Previously, using native `float` with no checking resulted in rounding errors in unpacking internal numbers. Attempting to write a number with too many significant digits results in a `RuntimeWarning`.
82
83
 
83
84
  The previously deprecated methods `Document.sheets()` and `Sheet.tables()` are now only available using the properties of the same name (see examples in this README).
84
85
 
@@ -282,6 +283,8 @@ with open (cell.style.bg_image.filename, "wb") as f:
282
283
  f.write(cell.style.bg_image.data)
283
284
  ```
284
285
 
286
+ Due to a limitation in Python's [ZipFile](https://docs.python.org/3/library/zipfile.html), Python versions older than 3.11 do not support image filenames with UTF-8 characters (see [issue 69](https://github.com/masaccio/numbers-parser/issues/69)). `cell.style.bg_image` returns `None` for such files and issues a `RuntimeWarning`.
287
+
285
288
  ### Borders
286
289
 
287
290
  `numbers-parser` supports reading and writing cell borders, though the interface for each differs. Individual cells can have each of their four borders tested, but when drawing new borders, these are set for the table to allow for drawing borders across multiple cells. Setting the border of merged cells is not possible unless the edge of the cells is at the end of the merged region.
@@ -5,9 +5,9 @@
5
5
  [![codecov](https://codecov.io/gh/masaccio/numbers-parser/branch/main/graph/badge.svg?token=EKIUFGT05E)](https://codecov.io/gh/masaccio/numbers-parser)
6
6
  [![PyPI version](https://badge.fury.io/py/numbers-parser.svg)](https://badge.fury.io/py/numbers-parser)
7
7
 
8
- `numbers-parser` is a Python module for parsing [Apple Numbers](https://www.apple.com/numbers/)`.numbers` files. It supports Numbers files generated by Numbers version 10.3, and up with the latest tested version being 13.1 (current as of June 2023).
8
+ `numbers-parser` is a Python module for parsing [Apple Numbers](https://www.apple.com/numbers/)`.numbers` files. It supports Numbers files generated by Numbers version 10.3, and up with the latest tested version being 13.2 (current as of September 2023).
9
9
 
10
- It supports and is tested against Python versions from 3.8 onwards. It is not compatible with earlier versions of Python.
10
+ It supports and is tested against Python versions from 3.8 onwards. It is not compatible with earlier versions of Python. Python 3.12 is not supported because of dependencies that do not currently work with 3.12. This is expected to be resolved by November 2023.
11
11
 
12
12
  ## Installation
13
13
 
@@ -49,7 +49,7 @@ C:\Users\Jon>pip install C:\Users\Jon\Downloads\python_snappy-0.6.1-cp311-cp311-
49
49
 
50
50
  To better partition cell styles, background image data which was supported in earlier versions through the methods `image_data` and `image_filename` is now part of the new `cell_style` property. Using the deprecated methods `image_data` and `image_filename` will issue a `DeprecationWarning` if used.The legacy methods will be removed in a future version of numbers-parser.
51
51
 
52
- `NumberCell` cell values are now limited to 15 significant figures to match the implementation of floating point numbers in Apple Numbers. For example, the value `1234567890123456` is rounded to `1234567890123460` in the same was as in Numbers. Previously, using native `float` with no checking resulted in rounding errors in unpacking internal numbers. Attempting to write a number with too many significant digits results in a `RuntimeWarning`.
52
+ `NumberCell` cell values are now limited to 15 significant figures to match the implementation of floating point numbers in Apple Numbers. For example, the value `1234567890123456` is rounded to `1234567890123460` in the same way as in Numbers. Previously, using native `float` with no checking resulted in rounding errors in unpacking internal numbers. Attempting to write a number with too many significant digits results in a `RuntimeWarning`.
53
53
 
54
54
  The previously deprecated methods `Document.sheets()` and `Sheet.tables()` are now only available using the properties of the same name (see examples in this README).
55
55
 
@@ -253,6 +253,8 @@ with open (cell.style.bg_image.filename, "wb") as f:
253
253
  f.write(cell.style.bg_image.data)
254
254
  ```
255
255
 
256
+ Due to a limitation in Python's [ZipFile](https://docs.python.org/3/library/zipfile.html), Python versions older than 3.11 do not support image filenames with UTF-8 characters (see [issue 69](https://github.com/masaccio/numbers-parser/issues/69)). `cell.style.bg_image` returns `None` for such files and issues a `RuntimeWarning`.
257
+
256
258
  ### Borders
257
259
 
258
260
  `numbers-parser` supports reading and writing cell borders, though the interface for each differs. Individual cells can have each of their four borders tested, but when drawing new borders, these are set for the table to allow for drawing borders across multiple cells. Setting the border of merged cells is not possible unless the edge of the cells is at the end of the merged region.
@@ -12,7 +12,7 @@ name = "numbers-parser"
12
12
  packages = [{include = "numbers_parser", from = "src"}]
13
13
  readme = "README.md"
14
14
  repository = "https://github.com/masaccio/numbers-parser"
15
- version = "4.4.6"
15
+ version = "4.4.8"
16
16
 
17
17
  [tool.poetry.scripts]
18
18
  cat-numbers = "numbers_parser._cat_numbers:main"
@@ -20,29 +20,31 @@ unpack-numbers = "numbers_parser._unpack_numbers:main"
20
20
 
21
21
  [tool.poetry.dependencies]
22
22
  compact-json = "^1.1.3"
23
- pendulum = "^2.1.2"
23
+ pendulum = "^3.0"
24
24
  protobuf = "^4.21.1"
25
25
  python = "^3.8"
26
26
  python-snappy = "^0.6.1"
27
27
  regex = "^2022.9.13"
28
28
  roman = "^3.3"
29
29
  sigfig = "^1.3.2"
30
+ setuptools = "^69.0.3"
30
31
 
31
32
  [tool.poetry.group.dev.dependencies]
32
33
  black = {version = "^22.10.0", allow-prereleases = true}
33
- pylama = {extras = ["toml"], version = "^8.4.1"}
34
+ gprof2dot = "^2022.7.29"
35
+ line-profiler = "^4.0.3"
36
+ mock = "^5.1.0"
37
+ psutil = "^5.9.5"
34
38
  pytest = "^7.2.0"
35
39
  pytest-check = "^1.0.10"
36
40
  pytest-console-scripts = "^1.3.1"
37
41
  pytest-cov = "^4.0.0"
38
42
  pytest-profiling = "^1.7.0"
39
- python-magic = "^0.4.27"
40
- termcolor = "^2.2.0"
41
- gprof2dot = "^2022.7.29"
42
- line-profiler = "^4.0.3"
43
- mock = "^5.1.0"
44
43
  pytest-xdist = "^3.3.1"
45
- psutil = "^5.9.5"
44
+ ruff = "^0.0.292"
45
+ termcolor = "^2.2.0"
46
+ tox = "^4.11.4"
47
+ python-magic = "^0.4.27"
46
48
 
47
49
  [build-system]
48
50
  build-backend = "poetry.core.masonry.api"
@@ -58,31 +60,68 @@ directory = "coverage_html_report"
58
60
  show_contexts = true
59
61
 
60
62
  [tool.pytest.ini_options]
61
- addopts = "--cov=src/numbers_parser --cov-report=term-missing:skip-covered --cov-context=test"
63
+ addopts = "--cov=src/numbers_parser --cov-report=html --cov-report=term-missing:skip-covered --cov-context=test"
62
64
 
63
65
  [tool.tox]
64
66
  legacy_tox_ini = """
65
67
  [tox]
66
68
  isolated_build = true
67
- envlist = py38, py39, py310, py311
69
+ envlist = py38, py39, py310, py311, py312
68
70
  [testenv]
69
- allowlist_externals = poetry
70
- commands_pre = poetry install --no-root --sync
71
- commands = poetry run -vvv pytest --import-mode importlib
71
+ deps =
72
+ pytest
73
+ pytest-check
74
+ pytest-console-scripts
75
+ pytest-cov
76
+ pytest-xdist
77
+ python-magic
78
+ psutil
79
+ commands =
80
+ pytest tests/ --import-mode importlib -n logical --no-cov
72
81
  """
73
82
 
74
- # linter settings
75
- [tool.pylama]
76
- linters = "pylint,mccabe,pyflakes"
77
- max_line_length = 100
78
- skip = ".bootstrap/*,.tox/*,src/numbers_parser/generated/*,src/debug/lldbutil.py"
83
+ [tool.isort]
84
+ profile = "black"
85
+
86
+ [tool.ruff]
87
+ exclude = [
88
+ # Machine-generated files
89
+ ".bootstrap/*",
90
+ "src/numbers_parser/generated/*",
91
+ # Third-party files not to lint
92
+ "src/debug/lldbutil.py", # Tox
93
+ ".tox/*",
94
+ ]
95
+ fix = true
96
+ ignore = [
97
+ "PLR2004", # Allow constant values
98
+ ]
99
+ line-length = 100
100
+ select = [
101
+ # Pyflakes including bugbears
102
+ "F",
103
+ "B", # Pycodestyle
104
+ "E",
105
+ "W", # isort
106
+ "I", # PEP naming
107
+ "N", # pyupgrade
108
+ "UP", # Pylama
109
+ "PL",
110
+ ]
111
+ src = ["src", "tests"]
112
+ target-version = "py38"
113
+ unfixable = [
114
+ "ERA", # do not autoremove commented out code
115
+ ]
116
+
117
+ [tool.ruff.pylint]
118
+ max-statements = 100
119
+ max-branches = 20
79
120
 
80
- [tool.pylama.linter.pycodestyle]
81
- format = "pylint"
82
- ignore = "E203,E231,W503"
121
+ [tool.ruff.flake8-tidy-imports]
122
+ ban-relative-imports = "all"
83
123
 
84
- [tool.pylama.linter.mccabe]
85
- max-complexity = 15
124
+ [tool.ruff.per-file-ignores]
125
+ "src/bootstrap/**" = ["PLR2004"]
86
126
 
87
- [tool.pylama.linter.pyflakes]
88
- builtins = "_"
127
+ "tests/**" = ["PLR2004", "S101", "D103", "ANN201", "ANN001"]
@@ -23,9 +23,9 @@ import os
23
23
  import plistlib
24
24
  import warnings
25
25
 
26
- from numbers_parser.document import Document # NOQA
27
- from numbers_parser.cell import * # NOQA
28
- from numbers_parser.exceptions import * # NOQA
26
+ from numbers_parser.cell import *
27
+ from numbers_parser.document import Document
28
+ from numbers_parser.exceptions import *
29
29
 
30
30
  __version__ = importlib.metadata.version("numbers-parser")
31
31
 
@@ -53,12 +53,14 @@ def _get_version():
53
53
  def _check_installed_numbers_version():
54
54
  try:
55
55
  fp = open(os.path.join(_DEFAULT_NUMBERS_INSTALL_PATH, _VERSION_PLIST_PATH), "rb")
56
- except IOError:
56
+ except OSError:
57
57
  return None
58
58
  version_dict = plistlib.load(fp)
59
59
  installed_version = version_dict["CFBundleShortVersionString"]
60
60
  if installed_version not in _SUPPORTED_NUMBERS_VERSIONS:
61
- warnings.warn(f"Numbers version {installed_version} not tested with this version")
61
+ warnings.warn(
62
+ f"Numbers version {installed_version} not tested with this version", stacklevel=2
63
+ )
62
64
  fp.close()
63
65
  return installed_version
64
66
 
@@ -5,9 +5,8 @@ import sys
5
5
 
6
6
  import sigfig
7
7
 
8
- from numbers_parser import Document, ErrorCell, FileFormatError, NumberCell, FileError
8
+ from numbers_parser import Document, ErrorCell, FileError, FileFormatError, NumberCell, _get_version
9
9
  from numbers_parser import __name__ as numbers_parser_name
10
- from numbers_parser import _get_version
11
10
  from numbers_parser.constants import MAX_SIGNIFICANT_DIGITS
12
11
  from numbers_parser.experimental import _enable_experimental_features
13
12
 
@@ -1,33 +1,31 @@
1
- import os
1
+ import contextlib
2
2
  import json
3
3
  import logging
4
- import regex
4
+ import os
5
5
  import sys
6
-
7
- from array import array
8
6
  from argparse import ArgumentParser
7
+ from array import array
9
8
  from base64 import b64decode
10
9
  from binascii import hexlify
10
+
11
+ import regex
11
12
  from compact_json import Formatter
12
13
 
13
- from numbers_parser.file import read_numbers_file
14
- from numbers_parser import _get_version
15
14
  from numbers_parser import __name__ as numbers_parser_name
15
+ from numbers_parser import _get_version
16
+ from numbers_parser.exceptions import FileError, FileFormatError, UnsupportedError
17
+ from numbers_parser.file import read_numbers_file
16
18
  from numbers_parser.iwafile import IWAFile
17
- from numbers_parser.exceptions import FileFormatError, UnsupportedError, FileError
18
19
  from numbers_parser.numbers_uuid import NumbersUUID
19
20
 
20
-
21
21
  logger = logging.getLogger(numbers_parser_name)
22
22
 
23
23
 
24
24
  def ensure_directory_exists(prefix, path):
25
25
  """Ensure that a path's directory exists."""
26
26
  parts = os.path.split(path)
27
- try:
27
+ with contextlib.suppress(OSError):
28
28
  os.makedirs(os.path.join(*([prefix] + list(parts[:-1]))))
29
- except OSError:
30
- pass
31
29
 
32
30
 
33
31
  def prettify_uuids(obj):
@@ -54,18 +52,18 @@ def prettify_uuids(obj):
54
52
  def prettify_cell_storage(obj):
55
53
  if isinstance(obj, dict):
56
54
  for k, v in obj.items():
57
- if isinstance(v, dict) or isinstance(v, list):
55
+ if isinstance(v, (dict, list)):
58
56
  prettify_cell_storage(v)
59
- elif k == "cell_storage_buffer" or k == "cell_storage_buffer_pre_bnc":
57
+ elif k in ["cell_storage_buffer", "cell_storage_buffer_pre_bnc"]:
60
58
  obj[k] = str(hexlify(b64decode(obj[k]), sep=":"))
61
59
  obj[k] = obj[k].replace("b'", "").replace("'", "")
62
- elif k == "cell_offsets" or k == "cell_offsets_pre_bnc":
60
+ elif k in ["cell_offsets", k == "cell_offsets_pre_bnc"]:
63
61
  offsets = array("h", b64decode(obj[k])).tolist()
64
62
  obj[k] = ",".join([str(x) for x in offsets])
65
63
  obj[k] = regex.sub(r"(?:,-1)+$", ",[...]", obj[k])
66
64
  else: # list
67
65
  for v in obj:
68
- if isinstance(v, dict) or isinstance(v, list):
66
+ if isinstance(v, (dict, list)):
69
67
  prettify_cell_storage(v)
70
68
 
71
69
 
@@ -137,7 +135,7 @@ def main():
137
135
  try:
138
136
  read_numbers_file(
139
137
  document,
140
- file_handler=lambda filename, blob: process_file(
138
+ file_handler=lambda filename, blob, output_dir=output_dir: process_file(
141
139
  filename, blob, output_dir, args
142
140
  ),
143
141
  )
@@ -29,7 +29,7 @@ from numbers_parser.generated import TSTArchives_pb2 as TSTArchives
29
29
  from numbers_parser.generated.TSWPArchives_pb2 import (
30
30
  ParagraphStylePropertiesArchive as ParagraphStyle,
31
31
  )
32
- from numbers_parser.numbers_cache import cache, Cacheable
32
+ from numbers_parser.numbers_cache import Cacheable, cache
33
33
 
34
34
  __all__ = [
35
35
  "Alignment",
@@ -122,8 +122,7 @@ class Alignment(_Alignment):
122
122
  raise TypeError("invalid vertical alignment")
123
123
  vertical = VERTICAL_MAP[vertical]
124
124
 
125
- self = super(_Alignment, cls).__new__(cls, (horizontal, vertical))
126
- return self
125
+ return super(_Alignment, cls).__new__(cls, (horizontal, vertical))
127
126
 
128
127
 
129
128
  DEFAULT_ALIGNMENT_CLASS = Alignment(*DEFAULT_ALIGNMENT)
@@ -186,13 +185,11 @@ class Style:
186
185
 
187
186
  @classmethod
188
187
  def from_storage(cls, cell_storage: object, model: object):
189
- style = Style()
190
-
191
188
  if cell_storage.image_data is not None:
192
189
  bg_image = BackgroundImage(*cell_storage.image_data)
193
190
  else:
194
191
  bg_image = None
195
- style = Style(
192
+ return Style(
196
193
  alignment=model.cell_alignment(cell_storage),
197
194
  bg_image=bg_image,
198
195
  bg_color=model.cell_bg_color(cell_storage),
@@ -212,7 +209,6 @@ class Style:
212
209
  _text_style_obj_id=model.text_style_object_id(cell_storage),
213
210
  _cell_style_obj_id=model.cell_style_object_id(cell_storage),
214
211
  )
215
- return style
216
212
 
217
213
  def __post_init__(self):
218
214
  self.bg_color = rgb_color(self.bg_color)
@@ -229,7 +225,8 @@ class Style:
229
225
 
230
226
  def __setattr__(self, name: str, value: Any) -> None:
231
227
  """Detect changes to cell styles and flag the style for
232
- possible updates when saving the document"""
228
+ possible updates when saving the document.
229
+ """
233
230
  if name in ["bg_color", "font_color"]:
234
231
  value = rgb_color(value)
235
232
  if name == "alignment":
@@ -244,13 +241,13 @@ class Style:
244
241
 
245
242
 
246
243
  def rgb_color(color) -> RGB:
247
- """Raise a TypeError if a color is not a valid RGB value"""
244
+ """Raise a TypeError if a color is not a valid RGB value."""
248
245
  if color is None:
249
246
  return None
250
247
  if isinstance(color, RGB):
251
248
  return color
252
249
  if isinstance(color, tuple):
253
- if not (len(color) == 3 and all([isinstance(x, int) for x in color])):
250
+ if not (len(color) == 3 and all(isinstance(x, int) for x in color)):
254
251
  raise TypeError("RGB color must be an RGB or a tuple of 3 integers")
255
252
  return RGB(*color)
256
253
  elif isinstance(color, list):
@@ -259,13 +256,13 @@ def rgb_color(color) -> RGB:
259
256
 
260
257
 
261
258
  def alignment(value) -> Alignment:
262
- """Raise a TypeError if a alignment is not a valid"""
259
+ """Raise a TypeError if a alignment is not a valid."""
263
260
  if value is None:
264
261
  return Alignment()
265
262
  if isinstance(value, Alignment):
266
263
  return value
267
264
  if isinstance(value, tuple):
268
- if not (len(value) == 2 and all([isinstance(x, (int, str)) for x in value])):
265
+ if not (len(value) == 2 and all(isinstance(x, (int, str)) for x in value)):
269
266
  raise TypeError("Alignment must be an Alignment or a tuple of 2 integers/strings")
270
267
  return Alignment(*value)
271
268
  raise TypeError("Alignment must be an Alignment or a tuple of 2 integers/strings")
@@ -285,16 +282,20 @@ class Border:
285
282
  def __init__(
286
283
  self,
287
284
  width: float = DEFAULT_BORDER_WIDTH,
288
- color: RGB = RGB(*DEFAULT_BORDER_COLOR),
289
- style: BorderType = BorderType(BORDER_STYLE_MAP[DEFAULT_BORDER_STYLE]),
285
+ color: RGB = None,
286
+ style: BorderType = None,
290
287
  _order: int = 0,
291
288
  ):
292
289
  if not isinstance(width, float):
293
290
  raise TypeError("width must be a float number of points")
294
291
  self.width = width
295
292
 
293
+ if color is None:
294
+ color = RGB(*DEFAULT_BORDER_COLOR)
296
295
  self.color = rgb_color(color)
297
296
 
297
+ if style is None:
298
+ style = BorderType(BORDER_STYLE_MAP[DEFAULT_BORDER_STYLE])
298
299
  if isinstance(style, str):
299
300
  style = style.lower()
300
301
  if style not in BORDER_STYLE_MAP:
@@ -394,14 +395,14 @@ class CellBorder:
394
395
 
395
396
 
396
397
  class MergeReference:
397
- """Cell reference for cells eliminated by a merge"""
398
+ """Cell reference for cells eliminated by a merge."""
398
399
 
399
400
  def __init__(self, row_start: int, col_start: int, row_end: int, col_end: int):
400
401
  self.rect = (row_start, col_start, row_end, col_end)
401
402
 
402
403
 
403
404
  class MergeAnchor:
404
- """Cell reference for the merged cell"""
405
+ """Cell reference for the merged cell."""
405
406
 
406
407
  def __init__(self, size: Tuple):
407
408
  self.size = size
@@ -475,11 +476,12 @@ class Cell(Cacheable):
475
476
  warn(
476
477
  f"'{value}' rounded to {MAX_SIGNIFICANT_DIGITS} significant digits",
477
478
  RuntimeWarning,
479
+ stacklevel=2,
478
480
  )
479
481
  return NumberCell(row_num, col_num, rounded_value)
480
- elif isinstance(value, builtin_datetime) or isinstance(value, DateTime):
482
+ elif isinstance(value, (DateTime, builtin_datetime)):
481
483
  return DateCell(row_num, col_num, pendulum_instance(value))
482
- elif isinstance(value, builtin_timedelta) or isinstance(value, Duration):
484
+ elif isinstance(value, (Duration, builtin_timedelta)):
483
485
  return DurationCell(row_num, col_num, value)
484
486
  else:
485
487
  raise ValueError("Can't determine cell type from type " + type(value).__name__)
@@ -527,6 +529,7 @@ class Cell(Cacheable):
527
529
  "image_filename is deprecated and will be removed in the future. "
528
530
  + "Please use the style property",
529
531
  DeprecationWarning,
532
+ stacklevel=2,
530
533
  )
531
534
  if self.style is not None and self.style.bg_image is not None:
532
535
  return self.style.bg_image.filename
@@ -539,6 +542,7 @@ class Cell(Cacheable):
539
542
  "image_data is deprecated and will be removed in the future. "
540
543
  + "Please use the style property",
541
544
  DeprecationWarning,
545
+ stacklevel=2,
542
546
  )
543
547
  if self.style is not None and self.style.bg_image is not None:
544
548
  return self.style.bg_image.data
@@ -555,8 +559,7 @@ class Cell(Cacheable):
555
559
  def formula(self):
556
560
  if self._formula_key is not None:
557
561
  table_formulas = self._model.table_formulas(self._table_id)
558
- formula = table_formulas.formula(self._formula_key, self.row, self.col)
559
- return formula
562
+ return table_formulas.formula(self._formula_key, self.row, self.col)
560
563
  else:
561
564
  return None
562
565
 
@@ -580,7 +583,11 @@ class Cell(Cacheable):
580
583
 
581
584
  @style.setter
582
585
  def style(self, _):
583
- warn("cell style cannot be set; use Table.set_cell_style() instead", UnsupportedWarning)
586
+ warn(
587
+ "cell style cannot be set; use Table.set_cell_style() instead",
588
+ UnsupportedWarning,
589
+ stacklevel=2,
590
+ )
584
591
 
585
592
  @property
586
593
  def border(self):
@@ -592,6 +599,7 @@ class Cell(Cacheable):
592
599
  warn(
593
600
  "cell border values cannot be set; use Table.set_cell_border() instead",
594
601
  UnsupportedWarning,
602
+ stacklevel=2,
595
603
  )
596
604
 
597
605
 
@@ -718,8 +726,7 @@ range_parts = re.compile(r"(\$?)([A-Z]{1,3})(\$?)(\d+)")
718
726
 
719
727
 
720
728
  def xl_cell_to_rowcol(cell_str: str) -> tuple:
721
- """
722
- Convert a cell reference in A1 notation to a zero indexed row and column.
729
+ """Convert a cell reference in A1 notation to a zero indexed row and column.
723
730
  Args:
724
731
  cell_str: A1 style string.
725
732
  Returns:
@@ -750,8 +757,7 @@ def xl_cell_to_rowcol(cell_str: str) -> tuple:
750
757
 
751
758
 
752
759
  def xl_range(first_row, first_col, last_row, last_col):
753
- """
754
- Convert zero indexed row and col cell references to a A1:B1 range string.
760
+ """Convert zero indexed row and col cell references to a A1:B1 range string.
755
761
  Args:
756
762
  first_row: The first cell row. Int.
757
763
  first_col: The first cell column. Int.
@@ -770,8 +776,7 @@ def xl_range(first_row, first_col, last_row, last_col):
770
776
 
771
777
 
772
778
  def xl_rowcol_to_cell(row, col, row_abs=False, col_abs=False):
773
- """
774
- Convert a zero indexed row and column cell reference to a A1 style string.
779
+ """Convert a zero indexed row and column cell reference to a A1 style string.
775
780
  Args:
776
781
  row: The cell row. Int.
777
782
  col: The cell column. Int.
@@ -795,8 +800,7 @@ def xl_rowcol_to_cell(row, col, row_abs=False, col_abs=False):
795
800
 
796
801
 
797
802
  def xl_col_to_name(col, col_abs=False):
798
- """
799
- Convert a zero indexed column cell reference to a string.
803
+ """Convert a zero indexed column cell reference to a string.
800
804
  Args:
801
805
  col: The cell column. Int.
802
806
  col_abs: Optional flag to make the column absolute. Bool.