numbers-parser 4.10.4__py3-none-any.whl → 4.10.6__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.
Files changed (34) hide show
  1. numbers_parser/__init__.py +1 -1
  2. numbers_parser/_cat_numbers.py +4 -4
  3. numbers_parser/_unpack_numbers.py +5 -5
  4. numbers_parser/cell.py +150 -128
  5. numbers_parser/constants.py +5 -6
  6. numbers_parser/containers.py +11 -8
  7. numbers_parser/document.py +147 -108
  8. numbers_parser/exceptions.py +0 -7
  9. numbers_parser/formula.py +3 -4
  10. numbers_parser/generated/TNArchives_pb2.py +24 -26
  11. numbers_parser/generated/TSAArchives_pb2.py +26 -28
  12. numbers_parser/generated/TSCEArchives_pb2.py +8 -10
  13. numbers_parser/generated/TSCHArchives_GEN_pb2.py +28 -34
  14. numbers_parser/generated/TSCHArchives_pb2.py +61 -65
  15. numbers_parser/generated/TSDArchives_pb2.py +100 -110
  16. numbers_parser/generated/TSKArchives_pb2.py +168 -170
  17. numbers_parser/generated/TSPArchiveMessages_pb2.py +32 -34
  18. numbers_parser/generated/TSPMessages_pb2.py +40 -40
  19. numbers_parser/generated/TSSArchives_pb2.py +36 -36
  20. numbers_parser/generated/TSTArchives_pb2.py +321 -325
  21. numbers_parser/generated/TSTStylePropertyArchiving_pb2.py +8 -10
  22. numbers_parser/generated/TSWPArchives_pb2.py +250 -286
  23. numbers_parser/generated/TSWPCommandArchives_pb2.py +95 -95
  24. numbers_parser/iwafile.py +17 -17
  25. numbers_parser/iwork.py +33 -27
  26. numbers_parser/model.py +94 -52
  27. numbers_parser/numbers_uuid.py +6 -4
  28. {numbers_parser-4.10.4.dist-info → numbers_parser-4.10.6.dist-info}/METADATA +9 -9
  29. numbers_parser-4.10.6.dist-info/RECORD +59 -0
  30. numbers_parser-4.10.4.dist-info/RECORD +0 -59
  31. numbers_parser/{mapping.py → generated/mapping.py} +24 -24
  32. {numbers_parser-4.10.4.dist-info → numbers_parser-4.10.6.dist-info}/LICENSE.rst +0 -0
  33. {numbers_parser-4.10.4.dist-info → numbers_parser-4.10.6.dist-info}/WHEEL +0 -0
  34. {numbers_parser-4.10.4.dist-info → numbers_parser-4.10.6.dist-info}/entry_points.txt +0 -0
numbers_parser/cell.py CHANGED
@@ -10,7 +10,7 @@ from fractions import Fraction
10
10
  from hashlib import sha1
11
11
  from os.path import basename
12
12
  from struct import pack, unpack
13
- from typing import Any, List, Tuple, Union
13
+ from typing import Any, List, Optional, Tuple, Union
14
14
  from warnings import warn
15
15
 
16
16
  import sigfig
@@ -104,8 +104,7 @@ __all__ = [
104
104
 
105
105
 
106
106
  class BackgroundImage:
107
- """
108
- A named document style that can be applied to cells.
107
+ """A named document style that can be applied to cells.
109
108
 
110
109
  .. code-block:: python
111
110
 
@@ -129,7 +128,7 @@ class BackgroundImage:
129
128
  Path to the image file.
130
129
  """
131
130
 
132
- def __init__(self, data: bytes = None, filename: str = None):
131
+ def __init__(self, data: Optional[bytes] = None, filename: Optional[str] = None) -> None:
133
132
  self._data = data
134
133
  self._filename = basename(filename)
135
134
 
@@ -180,13 +179,15 @@ class Alignment(_Alignment):
180
179
  if isinstance(horizontal, str):
181
180
  horizontal = horizontal.lower()
182
181
  if horizontal not in HORIZONTAL_MAP:
183
- raise TypeError("invalid horizontal alignment")
182
+ msg = "invalid horizontal alignment"
183
+ raise TypeError(msg)
184
184
  horizontal = HORIZONTAL_MAP[horizontal]
185
185
 
186
186
  if isinstance(vertical, str):
187
187
  vertical = vertical.lower()
188
188
  if vertical not in VERTICAL_MAP:
189
- raise TypeError("invalid vertical alignment")
189
+ msg = "invalid vertical alignment"
190
+ raise TypeError(msg)
190
191
  vertical = VERTICAL_MAP[vertical]
191
192
 
192
193
  return super(_Alignment, cls).__new__(cls, (horizontal, vertical))
@@ -199,8 +200,7 @@ RGB = namedtuple("RGB", ["r", "g", "b"])
199
200
 
200
201
  @dataclass
201
202
  class Style:
202
- """
203
- A named document style that can be applied to cells.
203
+ """A named document style that can be applied to cells.
204
204
 
205
205
  Parameters
206
206
  ----------
@@ -296,10 +296,7 @@ class Style:
296
296
 
297
297
  @classmethod
298
298
  def from_storage(cls, cell: object, model: object):
299
- if cell._image_data is not None:
300
- bg_image = BackgroundImage(*cell._image_data)
301
- else:
302
- bg_image = None
299
+ bg_image = BackgroundImage(*cell._image_data) if cell._image_data is not None else None
303
300
  return Style(
304
301
  alignment=model.cell_alignment(cell),
305
302
  bg_image=bg_image,
@@ -326,13 +323,16 @@ class Style:
326
323
  self.font_color = rgb_color(self.font_color)
327
324
 
328
325
  if not isinstance(self.font_size, float):
329
- raise TypeError("size must be a float number of points")
326
+ msg = "size must be a float number of points"
327
+ raise TypeError(msg)
330
328
  if not isinstance(self.font_name, str):
331
- raise TypeError("font name must be a string")
329
+ msg = "font name must be a string"
330
+ raise TypeError(msg)
332
331
 
333
332
  for attr in ["bold", "italic", "underline", "strikethrough"]:
334
333
  if not isinstance(getattr(self, attr), bool):
335
- raise TypeError(f"{attr} argument must be boolean")
334
+ msg = f"{attr} argument must be boolean"
335
+ raise TypeError(msg)
336
336
 
337
337
  def __setattr__(self, name: str, value: Any) -> None:
338
338
  """Detect changes to cell styles and flag the style for
@@ -359,11 +359,13 @@ def rgb_color(color) -> RGB:
359
359
  return color
360
360
  if isinstance(color, tuple):
361
361
  if not (len(color) == 3 and all(isinstance(x, int) for x in color)):
362
- raise TypeError("RGB color must be an RGB or a tuple of 3 integers")
362
+ msg = "RGB color must be an RGB or a tuple of 3 integers"
363
+ raise TypeError(msg)
363
364
  return RGB(*color)
364
365
  elif isinstance(color, list):
365
366
  return [rgb_color(c) for c in color]
366
- raise TypeError("RGB color must be an RGB or a tuple of 3 integers")
367
+ msg = "RGB color must be an RGB or a tuple of 3 integers"
368
+ raise TypeError(msg)
367
369
 
368
370
 
369
371
  def alignment(value) -> Alignment:
@@ -374,9 +376,11 @@ def alignment(value) -> Alignment:
374
376
  return value
375
377
  if isinstance(value, tuple):
376
378
  if not (len(value) == 2 and all(isinstance(x, (int, str)) for x in value)):
377
- raise TypeError("Alignment must be an Alignment or a tuple of 2 integers/strings")
379
+ msg = "Alignment must be an Alignment or a tuple of 2 integers/strings"
380
+ raise TypeError(msg)
378
381
  return Alignment(*value)
379
- raise TypeError("Alignment must be an Alignment or a tuple of 2 integers/strings")
382
+ msg = "Alignment must be an Alignment or a tuple of 2 integers/strings"
383
+ raise TypeError(msg)
380
384
 
381
385
 
382
386
  BORDER_STYLE_MAP = {"solid": 0, "dashes": 1, "dots": 2, "none": 3}
@@ -390,8 +394,7 @@ class BorderType(IntEnum):
390
394
 
391
395
 
392
396
  class Border:
393
- """
394
- Create a cell border to use with the :py:class:`~numbers_parser.Table` method
397
+ """Create a cell border to use with the :py:class:`~numbers_parser.Table` method
395
398
  :py:meth:`~numbers_parser.Table.set_cell_border`.
396
399
 
397
400
  .. code-block:: python
@@ -426,9 +429,10 @@ class Border:
426
429
  color: RGB = None,
427
430
  style: BorderType = None,
428
431
  _order: int = 0,
429
- ):
432
+ ) -> None:
430
433
  if not isinstance(width, float):
431
- raise TypeError("width must be a float number of points")
434
+ msg = "width must be a float number of points"
435
+ raise TypeError(msg)
432
436
  self.width = width
433
437
 
434
438
  if color is None:
@@ -440,7 +444,8 @@ class Border:
440
444
  if isinstance(style, str):
441
445
  style = style.lower()
442
446
  if style not in BORDER_STYLE_MAP:
443
- raise TypeError("invalid border style")
447
+ msg = "invalid border style"
448
+ raise TypeError(msg)
444
449
  self.style = BORDER_STYLE_MAP[style]
445
450
  else:
446
451
  self.style = style
@@ -453,7 +458,7 @@ class Border:
453
458
 
454
459
  def __eq__(self, value: object) -> bool:
455
460
  return all(
456
- [self.width == value.width, self.color == value.color, self.style == value.style]
461
+ [self.width == value.width, self.color == value.color, self.style == value.style],
457
462
  )
458
463
 
459
464
 
@@ -464,7 +469,7 @@ class CellBorder:
464
469
  right_merged: bool = False,
465
470
  bottom_merged: bool = False,
466
471
  left_merged: bool = False,
467
- ):
472
+ ) -> None:
468
473
  self._top = None
469
474
  self._right = None
470
475
  self._bottom = None
@@ -538,14 +543,14 @@ class CellBorder:
538
543
  class MergeReference:
539
544
  """Cell reference for cells eliminated by a merge."""
540
545
 
541
- def __init__(self, row_start: int, col_start: int, row_end: int, col_end: int):
546
+ def __init__(self, row_start: int, col_start: int, row_end: int, col_end: int) -> None:
542
547
  self.rect = (row_start, col_start, row_end, col_end)
543
548
 
544
549
 
545
550
  class MergeAnchor:
546
551
  """Cell reference for the merged cell."""
547
552
 
548
- def __init__(self, size: Tuple):
553
+ def __init__(self, size: Tuple) -> None:
549
554
  self.size = size
550
555
 
551
556
 
@@ -574,20 +579,19 @@ class CellStorageFlags:
574
579
  fields = [
575
580
  f"{k[1:]}={v}" for k, v in asdict(self).items() if k.endswith("_id") and v is not None
576
581
  ]
577
- fields = ", ".join([x for x in fields if x if not None])
578
- return fields
582
+ return ", ".join([x for x in fields if x if not None])
579
583
 
580
584
  def flags(self):
581
585
  return [x.name for x in fields(self)]
582
586
 
583
587
 
584
588
  class Cell(CellStorageFlags, Cacheable):
585
- """
586
- .. NOTE::
587
- Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
588
- """
589
+ """.. NOTE::
590
+
591
+ Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
592
+ """ # fmt: skip
589
593
 
590
- def __init__(self, row: int, col: int, value):
594
+ def __init__(self, row: int, col: int, value) -> None:
591
595
  self._value = value
592
596
  self.row = row
593
597
  self.col = col
@@ -599,14 +603,13 @@ class Cell(CellStorageFlags, Cacheable):
599
603
  self._seconds = None
600
604
  super().__init__()
601
605
 
602
- def __str__(self):
606
+ def __str__(self) -> str:
603
607
  table_name = self._model.table_name(self._table_id)
604
608
  sheet_name = self._model.sheet_name(self._model.table_id_to_sheet_id(self._table_id))
605
609
  cell_str = f"{sheet_name}@{table_name}[{self.row},{self.col}]:"
606
610
  cell_str += f"table_id={self._table_id}, type={self._type.name}, "
607
611
  cell_str += f"value={self._value}, flags={self._flags:08x}, extras={self._extras:04x}"
608
- cell_str = ", ".join([cell_str, super().__str__()])
609
- return cell_str
612
+ return ", ".join([cell_str, super().__str__()])
610
613
 
611
614
  @property
612
615
  def image_filename(self):
@@ -643,14 +646,14 @@ class Cell(CellStorageFlags, Cacheable):
643
646
  @property
644
647
  @cache(num_args=0)
645
648
  def formula(self) -> str:
646
- """
647
- str: The formula in a cell.
649
+ """str: The formula in a cell.
648
650
 
649
651
  Formula evaluation relies on Numbers storing current values which should
650
652
  usually be the case. In cells containing a formula, :py:meth:`numbers_parser.Cell.value`
651
653
  returns computed value of the formula.
652
654
 
653
- Returns:
655
+ Returns
656
+ -------
654
657
  str:
655
658
  The text of the foruma in a cell, or `None` if there is no formula
656
659
  present in a cell.
@@ -668,8 +671,7 @@ class Cell(CellStorageFlags, Cacheable):
668
671
 
669
672
  @property
670
673
  def bullets(self) -> Union[List[str], None]:
671
- r"""
672
- List[str] | None: The bullets in a cell, or ``None``
674
+ r"""List[str] | None: The bullets in a cell, or ``None``.
673
675
 
674
676
  Cells that contain bulleted or numbered lists are identified
675
677
  by :py:attr:`numbers_parser.Cell.is_bulleted`. For these cells,
@@ -680,7 +682,6 @@ class Cell(CellStorageFlags, Cacheable):
680
682
 
681
683
  Example
682
684
  -------
683
-
684
685
  .. code-block:: python
685
686
 
686
687
  doc = Document("bullets.numbers")
@@ -698,8 +699,7 @@ class Cell(CellStorageFlags, Cacheable):
698
699
 
699
700
  @property
700
701
  def formatted_value(self) -> str:
701
- """
702
- str: The formatted value of the cell as it appears in Numbers.
702
+ """str: The formatted value of the cell as it appears in Numbers.
703
703
 
704
704
  Interactive elements are converted into a suitable text format where
705
705
  supported, or as their number values where there is no suitable
@@ -742,7 +742,8 @@ class Cell(CellStorageFlags, Cacheable):
742
742
  def style(self) -> Union[Style, None]:
743
743
  """Style | None: The :class:`Style` associated with the cell or ``None``.
744
744
 
745
- Warns:
745
+ Warns
746
+ -----
746
747
  UnsupportedWarning: On assignment; use
747
748
  :py:meth:`numbers_parser.Table.set_cell_style` instead.
748
749
  """
@@ -762,7 +763,8 @@ class Cell(CellStorageFlags, Cacheable):
762
763
  def border(self) -> Union[CellBorder, None]:
763
764
  """CellBorder| None: The :class:`CellBorder` associated with the cell or ``None``.
764
765
 
765
- Warns:
766
+ Warns
767
+ -----
766
768
  UnsupportedWarning: On assignment; use
767
769
  :py:meth:`numbers_parser.Table.set_cell_border` instead.
768
770
  """
@@ -818,8 +820,13 @@ class Cell(CellStorageFlags, Cacheable):
818
820
  return cell
819
821
 
820
822
  @classmethod
821
- def _from_storage( # noqa: PLR0913, PLR0915, PLR0912
822
- cls, table_id: int, row: int, col: int, buffer: bytearray, model: object
823
+ def _from_storage( # noqa: PLR0913, PLR0912
824
+ cls,
825
+ table_id: int,
826
+ row: int,
827
+ col: int,
828
+ buffer: bytearray,
829
+ model: object,
823
830
  ) -> None:
824
831
  d128 = None
825
832
  double = None
@@ -827,7 +834,8 @@ class Cell(CellStorageFlags, Cacheable):
827
834
 
828
835
  version = buffer[0]
829
836
  if version != 5:
830
- raise UnsupportedError(f"Cell storage version {version} is unsupported")
837
+ msg = f"Cell storage version {version} is unsupported"
838
+ raise UnsupportedError(msg)
831
839
 
832
840
  offset = 12
833
841
  storage_flags = CellStorageFlags()
@@ -921,7 +929,8 @@ class Cell(CellStorageFlags, Cacheable):
921
929
  elif cell_type == CURRENCY_CELL_TYPE:
922
930
  cell = NumberCell(row, col, d128, cell_type=CellType.CURRENCY)
923
931
  else:
924
- raise UnsupportedError(f"Cell type ID {cell_type} is not recognised")
932
+ msg = f"Cell type ID {cell_type} is not recognised"
933
+ raise UnsupportedError(msg)
925
934
 
926
935
  cell._copy_flags(storage_flags)
927
936
  cell._buffer = buffer
@@ -1026,7 +1035,7 @@ class Cell(CellStorageFlags, Cacheable):
1026
1035
  flags = 2
1027
1036
  length += 8
1028
1037
  cell_type = TSTArchives.durationCellType
1029
- value = value = pack("<d", float(self.value.total_seconds()))
1038
+ value = pack("<d", float(self.value.total_seconds()))
1030
1039
  elif isinstance(self, EmptyCell):
1031
1040
  flags = 0
1032
1041
  cell_type = TSTArchives.emptyCellValueType
@@ -1136,8 +1145,12 @@ class Cell(CellStorageFlags, Cacheable):
1136
1145
 
1137
1146
  image_id = style.cell_properties.cell_fill.image.imagedata.identifier
1138
1147
  datas = self._model.objects[PACKAGE_ID].datas
1139
- stored_filename = [x.file_name for x in datas if x.identifier == image_id][0]
1140
- preferred_filename = [x.preferred_file_name for x in datas if x.identifier == image_id][0]
1148
+ stored_filename = next(
1149
+ x.file_name for x in datas if x.identifier == image_id
1150
+ ) # pragma: nocover (issue-1333)
1151
+ preferred_filename = next(
1152
+ x.preferred_file_name for x in datas if x.identifier == image_id
1153
+ ) # pragma: nocover (issue-1333)
1141
1154
  all_paths = self._model.objects.file_store.keys()
1142
1155
  image_pathnames = [x for x in all_paths if x == f"Data/{stored_filename}"]
1143
1156
 
@@ -1147,6 +1160,7 @@ class Cell(CellStorageFlags, Cacheable):
1147
1160
  RuntimeWarning,
1148
1161
  stacklevel=3,
1149
1162
  )
1163
+ return None
1150
1164
  else:
1151
1165
  image_data = self._model.objects.file_store[image_pathnames[0]]
1152
1166
  digest = sha1(image_data).digest()
@@ -1182,7 +1196,9 @@ class Cell(CellStorageFlags, Cacheable):
1182
1196
  )
1183
1197
  else:
1184
1198
  formatted_value = _decode_number_format(
1185
- custom_format, self._d128, format_map[format_uuid].name
1199
+ custom_format,
1200
+ self._d128,
1201
+ format_map[format_uuid].name,
1186
1202
  )
1187
1203
  elif format.format_type == FormatType.DECIMAL:
1188
1204
  return _format_decimal(self._d128, format)
@@ -1309,7 +1325,7 @@ class Cell(CellStorageFlags, Cacheable):
1309
1325
  self,
1310
1326
  format_id: int,
1311
1327
  format_type: Union[FormattingType, CustomFormattingType],
1312
- control_id: int = None,
1328
+ control_id: Optional[int] = None,
1313
1329
  is_currency: bool = False,
1314
1330
  ) -> None:
1315
1331
  self._is_currency = is_currency
@@ -1339,12 +1355,12 @@ class Cell(CellStorageFlags, Cacheable):
1339
1355
 
1340
1356
 
1341
1357
  class NumberCell(Cell):
1342
- """
1343
- .. NOTE::
1344
- Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1345
- """
1358
+ """.. NOTE::
1359
+
1360
+ Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1361
+ """ # fmt: skip
1346
1362
 
1347
- def __init__(self, row: int, col: int, value: float, cell_type=CellType.NUMBER):
1363
+ def __init__(self, row: int, col: int, value: float, cell_type=CellType.NUMBER) -> None:
1348
1364
  self._type = cell_type
1349
1365
  super().__init__(row, col, value)
1350
1366
 
@@ -1354,7 +1370,7 @@ class NumberCell(Cell):
1354
1370
 
1355
1371
 
1356
1372
  class TextCell(Cell):
1357
- def __init__(self, row: int, col: int, value: str):
1373
+ def __init__(self, row: int, col: int, value: str) -> None:
1358
1374
  self._type = CellType.TEXT
1359
1375
  super().__init__(row, col, value)
1360
1376
 
@@ -1364,12 +1380,12 @@ class TextCell(Cell):
1364
1380
 
1365
1381
 
1366
1382
  class RichTextCell(Cell):
1367
- """
1368
- .. NOTE::
1369
- Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1370
- """
1383
+ """.. NOTE::
1384
+
1385
+ Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1386
+ """ # fmt: skip
1371
1387
 
1372
- def __init__(self, row: int, col: int, value):
1388
+ def __init__(self, row: int, col: int, value) -> None:
1373
1389
  super().__init__(row, col, value["text"])
1374
1390
  self._type = CellType.RICH_TEXT
1375
1391
  self._bullets = value["bullets"]
@@ -1401,8 +1417,7 @@ class RichTextCell(Cell):
1401
1417
 
1402
1418
  @property
1403
1419
  def hyperlinks(self) -> Union[List[Tuple], None]:
1404
- """
1405
- List[Tuple] | None: the hyperlinks in a cell or ``None``
1420
+ """List[Tuple] | None: the hyperlinks in a cell or ``None``.
1406
1421
 
1407
1422
  Numbers does not support hyperlinks to cells within a spreadsheet, but does
1408
1423
  allow embedding links in cells. When cells contain hyperlinks,
@@ -1412,7 +1427,6 @@ class RichTextCell(Cell):
1412
1427
 
1413
1428
  Example
1414
1429
  -------
1415
-
1416
1430
  .. code-block:: python
1417
1431
 
1418
1432
  cell = table.cell(0, 0)
@@ -1427,12 +1441,12 @@ class BulletedTextCell(RichTextCell):
1427
1441
 
1428
1442
 
1429
1443
  class EmptyCell(Cell):
1430
- """
1431
- .. NOTE::
1432
- Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1433
- """
1444
+ """.. NOTE::
1445
+
1446
+ Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1447
+ """ # fmt: skip
1434
1448
 
1435
- def __init__(self, row: int, col: int):
1449
+ def __init__(self, row: int, col: int) -> None:
1436
1450
  super().__init__(row, col, None)
1437
1451
  self._type = CellType.EMPTY
1438
1452
 
@@ -1446,12 +1460,12 @@ class EmptyCell(Cell):
1446
1460
 
1447
1461
 
1448
1462
  class BoolCell(Cell):
1449
- """
1450
- .. NOTE::
1451
- Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1452
- """
1463
+ """.. NOTE::
1464
+
1465
+ Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1466
+ """ # fmt: skip
1453
1467
 
1454
- def __init__(self, row: int, col: int, value: bool):
1468
+ def __init__(self, row: int, col: int, value: bool) -> None:
1455
1469
  super().__init__(row, col, value)
1456
1470
  self._type = CellType.BOOL
1457
1471
  self._value = value
@@ -1462,12 +1476,12 @@ class BoolCell(Cell):
1462
1476
 
1463
1477
 
1464
1478
  class DateCell(Cell):
1465
- """
1466
- .. NOTE::
1467
- Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1468
- """
1479
+ """.. NOTE::
1480
+
1481
+ Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1482
+ """ # fmt: skip
1469
1483
 
1470
- def __init__(self, row: int, col: int, value: DateTime):
1484
+ def __init__(self, row: int, col: int, value: DateTime) -> None:
1471
1485
  super().__init__(row, col, value)
1472
1486
  self._type = CellType.DATE
1473
1487
 
@@ -1477,7 +1491,7 @@ class DateCell(Cell):
1477
1491
 
1478
1492
 
1479
1493
  class DurationCell(Cell):
1480
- def __init__(self, row: int, col: int, value: Duration):
1494
+ def __init__(self, row: int, col: int, value: Duration) -> None:
1481
1495
  super().__init__(row, col, value)
1482
1496
  self._type = CellType.DURATION
1483
1497
 
@@ -1487,12 +1501,12 @@ class DurationCell(Cell):
1487
1501
 
1488
1502
 
1489
1503
  class ErrorCell(Cell):
1490
- """
1491
- .. NOTE::
1492
- Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1493
- """
1504
+ """.. NOTE::
1505
+
1506
+ Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1507
+ """ # fmt: skip
1494
1508
 
1495
- def __init__(self, row: int, col: int):
1509
+ def __init__(self, row: int, col: int) -> None:
1496
1510
  super().__init__(row, col, None)
1497
1511
  self._type = CellType.ERROR
1498
1512
 
@@ -1502,12 +1516,12 @@ class ErrorCell(Cell):
1502
1516
 
1503
1517
 
1504
1518
  class MergedCell(Cell):
1505
- """
1506
- .. NOTE::
1507
- Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1508
- """
1519
+ """.. NOTE::
1520
+
1521
+ Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1522
+ """ # fmt: skip
1509
1523
 
1510
- def __init__(self, row: int, col: int):
1524
+ def __init__(self, row: int, col: int) -> None:
1511
1525
  super().__init__(row, col, None)
1512
1526
  self._type = CellType.MERGED
1513
1527
 
@@ -1648,7 +1662,8 @@ def _decode_number_format(format, value, name): # noqa: PLR0912
1648
1662
  if format.currency_code != "":
1649
1663
  # Replace currency code with symbol and no-break space
1650
1664
  custom_format_string = custom_format_string.replace(
1651
- "\u00a4", format.currency_code + "\u00a0"
1665
+ "\u00a4",
1666
+ format.currency_code + "\u00a0",
1652
1667
  )
1653
1668
 
1654
1669
  if (match := re.search(r"([#0.,]+(E[+]\d+)?)", custom_format_string)) is None:
@@ -1799,7 +1814,9 @@ def _format_decimal(value: float, format, percent: bool = False) -> str:
1799
1814
  else:
1800
1815
  formatted_value = sigfig.round(value, MAX_SIGNIFICANT_DIGITS, type=str, warn=False)
1801
1816
  formatted_value = sigfig.round(
1802
- formatted_value, decimals=format.decimal_places, type=str
1817
+ formatted_value,
1818
+ decimals=format.decimal_places,
1819
+ type=str,
1803
1820
  )
1804
1821
  if format.show_thousands_separator:
1805
1822
  formatted_value = sigfig.round(formatted_value, spacer=",", spacing=3, type=str)
@@ -1832,16 +1849,16 @@ def _format_currency(value: float, format) -> str:
1832
1849
  return symbol + formatted_value
1833
1850
 
1834
1851
 
1835
- INT_TO_BASE_CHAR = [str(x) for x in range(0, 10)] + [chr(x) for x in range(ord("A"), ord("Z") + 1)]
1852
+ INT_TO_BASE_CHAR = [str(x) for x in range(10)] + [chr(x) for x in range(ord("A"), ord("Z") + 1)]
1836
1853
 
1837
1854
 
1838
1855
  def _invert_bit_str(value: str) -> str:
1839
- """Invert a binary value"""
1856
+ """Invert a binary value."""
1840
1857
  return "".join(["0" if b == "1" else "1" for b in value])
1841
1858
 
1842
1859
 
1843
1860
  def _twos_complement(value: int, base: int) -> str:
1844
- """Calculate the twos complement of a negative integer with minimum 32-bit precision"""
1861
+ """Calculate the twos complement of a negative integer with minimum 32-bit precision."""
1845
1862
  num_bits = max([32, math.ceil(math.log2(abs(value))) + 1])
1846
1863
  bin_value = bin(abs(value))[2:]
1847
1864
  inverted_bin_value = _invert_bit_str(bin_value).rjust(num_bits, "1")
@@ -1930,7 +1947,7 @@ def _format_scientific(value: float, format) -> str:
1930
1947
  return f"{formatted_value:.{format.decimal_places}E}"
1931
1948
 
1932
1949
 
1933
- def _unit_format(unit: str, value: int, style: int, abbrev: str = None):
1950
+ def _unit_format(unit: str, value: int, style: int, abbrev: Optional[str] = None):
1934
1951
  plural = "" if value == 1 else "s"
1935
1952
  if abbrev is None:
1936
1953
  abbrev = unit[0]
@@ -1985,8 +2002,7 @@ range_parts = re.compile(r"(\$?)([A-Z]{1,3})(\$?)(\d+)")
1985
2002
 
1986
2003
 
1987
2004
  def xl_cell_to_rowcol(cell_str: str) -> tuple:
1988
- """
1989
- Convert a cell reference in A1 notation to a zero indexed row and column.
2005
+ """Convert a cell reference in A1 notation to a zero indexed row and column.
1990
2006
 
1991
2007
  Parameters
1992
2008
  ----------
@@ -2003,7 +2019,8 @@ def xl_cell_to_rowcol(cell_str: str) -> tuple:
2003
2019
 
2004
2020
  match = range_parts.match(cell_str)
2005
2021
  if not match:
2006
- raise IndexError(f"invalid cell reference {cell_str}")
2022
+ msg = f"invalid cell reference {cell_str}"
2023
+ raise IndexError(msg)
2007
2024
 
2008
2025
  col_str = match.group(2)
2009
2026
  row_str = match.group(4)
@@ -2023,8 +2040,7 @@ def xl_cell_to_rowcol(cell_str: str) -> tuple:
2023
2040
 
2024
2041
 
2025
2042
  def xl_range(first_row, first_col, last_row, last_col):
2026
- """
2027
- Convert zero indexed row and col cell references to a A1:B1 range string.
2043
+ """Convert zero indexed row and col cell references to a A1:B1 range string.
2028
2044
 
2029
2045
  Parameters
2030
2046
  ----------
@@ -2052,8 +2068,7 @@ def xl_range(first_row, first_col, last_row, last_col):
2052
2068
 
2053
2069
 
2054
2070
  def xl_rowcol_to_cell(row, col, row_abs=False, col_abs=False):
2055
- """
2056
- Convert a zero indexed row and column cell reference to a A1 style string.
2071
+ """Convert a zero indexed row and column cell reference to a A1 style string.
2057
2072
 
2058
2073
  Parameters
2059
2074
  ----------
@@ -2072,10 +2087,12 @@ def xl_rowcol_to_cell(row, col, row_abs=False, col_abs=False):
2072
2087
  A1 style string.
2073
2088
  """
2074
2089
  if row < 0:
2075
- raise IndexError(f"row reference {row} below zero")
2090
+ msg = f"row reference {row} below zero"
2091
+ raise IndexError(msg)
2076
2092
 
2077
2093
  if col < 0:
2078
- raise IndexError(f"column reference {col} below zero")
2094
+ msg = f"column reference {col} below zero"
2095
+ raise IndexError(msg)
2079
2096
 
2080
2097
  row += 1 # Change to 1-index.
2081
2098
  row_abs = "$" if row_abs else ""
@@ -2086,8 +2103,7 @@ def xl_rowcol_to_cell(row, col, row_abs=False, col_abs=False):
2086
2103
 
2087
2104
 
2088
2105
  def xl_col_to_name(col, col_abs=False):
2089
- """
2090
- Convert a zero indexed column cell reference to a string.
2106
+ """Convert a zero indexed column cell reference to a string.
2091
2107
 
2092
2108
  Parameters
2093
2109
  ----------
@@ -2095,12 +2111,15 @@ def xl_col_to_name(col, col_abs=False):
2095
2111
  The column number (zero indexed).
2096
2112
  col_abs: bool, default: False
2097
2113
  If ``True``, make the column absolute.
2098
- Returns:
2114
+
2115
+ Returns
2116
+ -------
2099
2117
  str:
2100
2118
  Column in A1 notation.
2101
2119
  """
2102
2120
  if col < 0:
2103
- raise IndexError(f"column reference {col} below zero")
2121
+ msg = f"column reference {col} below zero"
2122
+ raise IndexError(msg)
2104
2123
 
2105
2124
  col += 1 # Change to 1-index.
2106
2125
  col_str = ""
@@ -2149,7 +2168,8 @@ class Formatting:
2149
2168
  def __post_init__(self):
2150
2169
  if not isinstance(self.type, FormattingType):
2151
2170
  type_name = type(self.type).__name__
2152
- raise TypeError(f"Invalid format type '{type_name}'")
2171
+ msg = f"Invalid format type '{type_name}'"
2172
+ raise TypeError(msg)
2153
2173
 
2154
2174
  if self.use_accounting_style and self.negative_style != NegativeNumberStyle.MINUS:
2155
2175
  warn(
@@ -2161,12 +2181,12 @@ class Formatting:
2161
2181
  if self.type == FormattingType.DATETIME:
2162
2182
  formats = re.sub(r"[^a-zA-Z\s]", " ", self.date_time_format).split()
2163
2183
  for el in formats:
2164
- if el not in DATETIME_FIELD_MAP.keys():
2165
- raise TypeError(f"Invalid format specifier '{el}' in date/time format")
2184
+ if el not in DATETIME_FIELD_MAP:
2185
+ msg = f"Invalid format specifier '{el}' in date/time format"
2186
+ raise TypeError(msg)
2166
2187
 
2167
- if self.type == FormattingType.CURRENCY:
2168
- if self.currency_code not in CURRENCIES:
2169
- raise TypeError(f"Unsupported currency code '{self.currency_code}'")
2188
+ if self.type == FormattingType.CURRENCY and self.currency_code not in CURRENCIES:
2189
+ raise TypeError(f"Unsupported currency code '{self.currency_code}'")
2170
2190
 
2171
2191
  if self.decimal_places is None:
2172
2192
  if self.type == FormattingType.CURRENCY:
@@ -2179,10 +2199,12 @@ class Formatting:
2179
2199
  and not self.base_use_minus_sign
2180
2200
  and self.base not in (2, 8, 16)
2181
2201
  ):
2182
- raise TypeError(f"base_use_minus_sign must be True for base {self.base}")
2202
+ msg = f"base_use_minus_sign must be True for base {self.base}"
2203
+ raise TypeError(msg)
2183
2204
 
2184
2205
  if self.type == FormattingType.BASE and (self.base < 2 or self.base > MAX_BASE):
2185
- raise TypeError("base must be in range 2-36")
2206
+ msg = "base must be in range 2-36"
2207
+ raise TypeError(msg)
2186
2208
 
2187
2209
 
2188
2210
  @dataclass
@@ -2199,11 +2221,11 @@ class CustomFormatting:
2199
2221
  def __post_init__(self):
2200
2222
  if not isinstance(self.type, CustomFormattingType):
2201
2223
  type_name = type(self.type).__name__
2202
- raise TypeError(f"Invalid format type '{type_name}'")
2224
+ msg = f"Invalid format type '{type_name}'"
2225
+ raise TypeError(msg)
2203
2226
 
2204
- if self.type == CustomFormattingType.TEXT:
2205
- if self.format.count("%s") > 1:
2206
- raise TypeError("Custom formats only allow one text substitution")
2227
+ if self.type == CustomFormattingType.TEXT and self.format.count("%s") > 1:
2228
+ raise TypeError("Custom formats only allow one text substitution")
2207
2229
 
2208
2230
  @classmethod
2209
2231
  def from_archive(cls, archive: object):