numbers-parser 4.13.3__py3-none-any.whl → 4.14.2__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/cell.py CHANGED
@@ -1,25 +1,21 @@
1
+ from __future__ import annotations
2
+
1
3
  import logging
2
4
  import math
3
5
  import re
4
- from collections import namedtuple
5
6
  from dataclasses import asdict, dataclass, field, fields
6
- from datetime import datetime as builtin_datetime
7
- from datetime import timedelta as builtin_timedelta
7
+ from datetime import datetime, timedelta
8
8
  from enum import IntEnum
9
9
  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, Optional, Tuple, Union
13
+ from typing import Any, NamedTuple
14
14
  from warnings import warn
15
15
 
16
- import sigfig
17
- from pendulum import DateTime, Duration, datetime, duration
18
- from pendulum import instance as pendulum_instance
16
+ from sigfig import round as sigfig
19
17
 
20
18
  from numbers_parser import __name__ as numbers_parser_name
21
-
22
- # from numbers_parser.cell_storage import CellStorage, CellType
23
19
  from numbers_parser.constants import (
24
20
  CHECKBOX_FALSE_VALUE,
25
21
  CHECKBOX_TRUE_VALUE,
@@ -104,7 +100,8 @@ __all__ = [
104
100
 
105
101
 
106
102
  class BackgroundImage:
107
- """A named document style that can be applied to cells.
103
+ """
104
+ A named document style that can be applied to cells.
108
105
 
109
106
  .. code-block:: python
110
107
 
@@ -126,9 +123,10 @@ class BackgroundImage:
126
123
  Raw image data for a cell background image.
127
124
  filename: str
128
125
  Path to the image file.
126
+
129
127
  """
130
128
 
131
- def __init__(self, data: Optional[bytes] = None, filename: Optional[str] = None) -> None:
129
+ def __init__(self, data: bytes | None = None, filename: str | None = None) -> None:
132
130
  self._data = data
133
131
  self._filename = basename(filename)
134
132
 
@@ -171,7 +169,12 @@ VERTICAL_MAP = {
171
169
  "bottom": VerticalJustification.BOTTOM,
172
170
  }
173
171
 
174
- _Alignment = namedtuple("Alignment", ["horizontal", "vertical"])
172
+
173
+ class _Alignment(NamedTuple):
174
+ """Class for internal alignment type."""
175
+
176
+ horizontal: str
177
+ vertical: str
175
178
 
176
179
 
177
180
  class Alignment(_Alignment):
@@ -195,12 +198,23 @@ class Alignment(_Alignment):
195
198
 
196
199
  DEFAULT_ALIGNMENT_CLASS = Alignment(*DEFAULT_ALIGNMENT)
197
200
 
198
- RGB = namedtuple("RGB", ["r", "g", "b"])
201
+
202
+ class RGB(NamedTuple):
203
+ """A color in RGB."""
204
+
205
+ r: int
206
+ g: int
207
+ b: int
208
+
209
+
210
+ def default_color() -> RGB:
211
+ return RGB(0, 0, 0)
199
212
 
200
213
 
201
214
  @dataclass
202
215
  class Style:
203
- """A named document style that can be applied to cells.
216
+ """
217
+ A named document style that can be applied to cells.
204
218
 
205
219
  Parameters
206
220
  ----------
@@ -240,12 +254,13 @@ class Style:
240
254
  If arguments do not match the specified type or for objects have invalid arguments
241
255
  IndexError:
242
256
  If an image filename already exists in document
257
+
243
258
  """
244
259
 
245
260
  alignment: Alignment = DEFAULT_ALIGNMENT_CLASS # : horizontal and vertical alignment
246
261
  bg_image: object = None # : backgroung image
247
- bg_color: Union[RGB, List[RGB]] = None
248
- font_color: RGB = RGB(0, 0, 0)
262
+ bg_color: RGB | list[RGB] = None
263
+ font_color: RGB = field(default_factory=default_color)
249
264
  font_size: float = DEFAULT_FONT_SIZE
250
265
  font_name: str = DEFAULT_FONT
251
266
  bold: bool = False
@@ -335,7 +350,8 @@ class Style:
335
350
  raise TypeError(msg)
336
351
 
337
352
  def __setattr__(self, name: str, value: Any) -> None:
338
- """Detect changes to cell styles and flag the style for
353
+ """
354
+ Detect changes to cell styles and flag the style for
339
355
  possible updates when saving the document.
340
356
  """
341
357
  if name in ["bg_color", "font_color"]:
@@ -362,7 +378,7 @@ def rgb_color(color) -> RGB:
362
378
  msg = "RGB color must be an RGB or a tuple of 3 integers"
363
379
  raise TypeError(msg)
364
380
  return RGB(*color)
365
- elif isinstance(color, list):
381
+ if isinstance(color, list):
366
382
  return [rgb_color(c) for c in color]
367
383
  msg = "RGB color must be an RGB or a tuple of 3 integers"
368
384
  raise TypeError(msg)
@@ -394,7 +410,8 @@ class BorderType(IntEnum):
394
410
 
395
411
 
396
412
  class Border:
397
- """Create a cell border to use with the :py:class:`~numbers_parser.Table` method
413
+ """
414
+ Create a cell border to use with the :py:class:`~numbers_parser.Table` method
398
415
  :py:meth:`~numbers_parser.Table.set_cell_border`.
399
416
 
400
417
  .. code-block:: python
@@ -421,6 +438,7 @@ class Border:
421
438
  ------
422
439
  TypeError:
423
440
  If the width is not a float, or the border type is invalid.
441
+
424
442
  """
425
443
 
426
444
  def __init__(
@@ -481,62 +499,46 @@ class CellBorder:
481
499
 
482
500
  @property
483
501
  def top(self):
484
- if self._top_merged:
485
- return None
486
- elif self._top is None:
502
+ if self._top_merged or self._top is None:
487
503
  return None
488
504
  return self._top
489
505
 
490
506
  @top.setter
491
- def top(self, value):
492
- if self._top is None:
493
- self._top = value
494
- elif value._order > self.top._order:
507
+ def top(self, value) -> None:
508
+ if self._top is None or value._order > self.top._order:
495
509
  self._top = value
496
510
 
497
511
  @property
498
512
  def right(self):
499
- if self._right_merged:
500
- return None
501
- elif self._right is None:
513
+ if self._right_merged or self._right is None:
502
514
  return None
503
515
  return self._right
504
516
 
505
517
  @right.setter
506
- def right(self, value):
507
- if self._right is None:
508
- self._right = value
509
- elif value._order > self._right._order:
518
+ def right(self, value) -> None:
519
+ if self._right is None or value._order > self._right._order:
510
520
  self._right = value
511
521
 
512
522
  @property
513
523
  def bottom(self):
514
- if self._bottom_merged:
515
- return None
516
- elif self._bottom is None:
524
+ if self._bottom_merged or self._bottom is None:
517
525
  return None
518
526
  return self._bottom
519
527
 
520
528
  @bottom.setter
521
- def bottom(self, value):
522
- if self._bottom is None:
523
- self._bottom = value
524
- elif value._order > self._bottom._order:
529
+ def bottom(self, value) -> None:
530
+ if self._bottom is None or value._order > self._bottom._order:
525
531
  self._bottom = value
526
532
 
527
533
  @property
528
534
  def left(self):
529
- if self._left_merged:
530
- return None
531
- elif self._left is None:
535
+ if self._left_merged or self._left is None:
532
536
  return None
533
537
  return self._left
534
538
 
535
539
  @left.setter
536
- def left(self, value):
537
- if self._left is None:
538
- self._left = value
539
- elif value._order > self._left._order:
540
+ def left(self, value) -> None:
541
+ if self._left is None or value._order > self._left._order:
540
542
  self._left = value
541
543
 
542
544
 
@@ -550,7 +552,7 @@ class MergeReference:
550
552
  class MergeAnchor:
551
553
  """Cell reference for the merged cell."""
552
554
 
553
- def __init__(self, size: Tuple) -> None:
555
+ def __init__(self, size: tuple) -> None:
554
556
  self.size = size
555
557
 
556
558
 
@@ -586,10 +588,11 @@ class CellStorageFlags:
586
588
 
587
589
 
588
590
  class Cell(CellStorageFlags, Cacheable):
589
- """.. NOTE::
591
+ """
592
+ .. NOTE::
590
593
 
591
- Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
592
- """ # fmt: skip
594
+ Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
595
+ """
593
596
 
594
597
  def __init__(self, row: int, col: int, value) -> None:
595
598
  self._value = value
@@ -621,8 +624,7 @@ class Cell(CellStorageFlags, Cacheable):
621
624
  )
622
625
  if self.style is not None and self.style.bg_image is not None:
623
626
  return self.style.bg_image.filename
624
- else:
625
- return None
627
+ return None
626
628
 
627
629
  @property
628
630
  def image_data(self):
@@ -634,8 +636,7 @@ class Cell(CellStorageFlags, Cacheable):
634
636
  )
635
637
  if self.style is not None and self.style.bg_image is not None:
636
638
  return self.style.bg_image.data
637
- else:
638
- return None
639
+ return None
639
640
 
640
641
  @property
641
642
  def is_formula(self) -> bool:
@@ -646,7 +647,8 @@ class Cell(CellStorageFlags, Cacheable):
646
647
  @property
647
648
  @cache(num_args=0)
648
649
  def formula(self) -> str:
649
- """str: The formula in a cell.
650
+ """
651
+ str: The formula in a cell.
650
652
 
651
653
  Formula evaluation relies on Numbers storing current values which should
652
654
  usually be the case. In cells containing a formula, :py:meth:`numbers_parser.Cell.value`
@@ -657,12 +659,12 @@ class Cell(CellStorageFlags, Cacheable):
657
659
  str:
658
660
  The text of the foruma in a cell, or `None` if there is no formula
659
661
  present in a cell.
662
+
660
663
  """
661
664
  if self._formula_id is not None:
662
665
  table_formulas = self._model.table_formulas(self._table_id)
663
666
  return table_formulas.formula(self._formula_id, self.row, self.col)
664
- else:
665
- return None
667
+ return None
666
668
 
667
669
  @property
668
670
  def is_bulleted(self) -> bool:
@@ -670,8 +672,9 @@ class Cell(CellStorageFlags, Cacheable):
670
672
  return self._is_bulleted
671
673
 
672
674
  @property
673
- def bullets(self) -> Union[List[str], None]:
674
- r"""List[str] | None: The bullets in a cell, or ``None``.
675
+ def bullets(self) -> list[str] | None:
676
+ r"""
677
+ List[str] | None: The bullets in a cell, or ``None``.
675
678
 
676
679
  Cells that contain bulleted or numbered lists are identified
677
680
  by :py:attr:`numbers_parser.Cell.is_bulleted`. For these cells,
@@ -694,12 +697,14 @@ class Cell(CellStorageFlags, Cacheable):
694
697
  bullets = ["* " + s for s in table.cell(0, 1).bullets]
695
698
  print("\n".join(bullets))
696
699
  return None
700
+
697
701
  """
698
702
  return None
699
703
 
700
704
  @property
701
705
  def formatted_value(self) -> str:
702
- """str: The formatted value of the cell as it appears in Numbers.
706
+ """
707
+ str: The formatted value of the cell as it appears in Numbers.
703
708
 
704
709
  Interactive elements are converted into a suitable text format where
705
710
  supported, or as their number values where there is no suitable
@@ -726,33 +731,34 @@ class Cell(CellStorageFlags, Cacheable):
726
731
  """
727
732
  if self._duration_format_id is not None and self._double is not None:
728
733
  return self._duration_format()
729
- elif self._date_format_id is not None and self._seconds is not None:
734
+ if self._date_format_id is not None and self._seconds is not None:
730
735
  return self._date_format()
731
- elif (
736
+ if (
732
737
  self._text_format_id is not None
733
738
  or self._num_format_id is not None
734
739
  or self._currency_format_id is not None
735
740
  or self._bool_format_id is not None
736
741
  ):
737
742
  return self._custom_format()
738
- else:
739
- return str(self.value)
743
+ return str(self.value)
740
744
 
741
745
  @property
742
- def style(self) -> Union[Style, None]:
743
- """Style | None: The :class:`Style` associated with the cell or ``None``.
746
+ def style(self) -> Style | None:
747
+ """
748
+ Style | None: The :class:`Style` associated with the cell or ``None``.
744
749
 
745
750
  Warns
746
751
  -----
747
752
  UnsupportedWarning: On assignment; use
748
753
  :py:meth:`numbers_parser.Table.set_cell_style` instead.
754
+
749
755
  """
750
756
  if self._style is None:
751
757
  self._style = Style.from_storage(self, self._model)
752
758
  return self._style
753
759
 
754
760
  @style.setter
755
- def style(self, _):
761
+ def style(self, _) -> None:
756
762
  warn(
757
763
  "cell style cannot be set; use Table.set_cell_style() instead",
758
764
  UnsupportedWarning,
@@ -760,19 +766,21 @@ class Cell(CellStorageFlags, Cacheable):
760
766
  )
761
767
 
762
768
  @property
763
- def border(self) -> Union[CellBorder, None]:
764
- """CellBorder| None: The :class:`CellBorder` associated with the cell or ``None``.
769
+ def border(self) -> CellBorder | None:
770
+ """
771
+ CellBorder| None: The :class:`CellBorder` associated with the cell or ``None``.
765
772
 
766
773
  Warns
767
774
  -----
768
775
  UnsupportedWarning: On assignment; use
769
776
  :py:meth:`numbers_parser.Table.set_cell_border` instead.
777
+
770
778
  """
771
779
  self._model.extract_strokes(self._table_id)
772
780
  return self._border
773
781
 
774
782
  @border.setter
775
- def border(self, _):
783
+ def border(self, _) -> None:
776
784
  warn(
777
785
  "cell border values cannot be set; use Table.set_cell_border() instead",
778
786
  UnsupportedWarning,
@@ -802,7 +810,7 @@ class Cell(CellStorageFlags, Cacheable):
802
810
  elif isinstance(value, int):
803
811
  cell = NumberCell(row, col, value)
804
812
  elif isinstance(value, float):
805
- rounded_value = sigfig.round(value, sigfigs=MAX_SIGNIFICANT_DIGITS, warn=False)
813
+ rounded_value = sigfig(value, sigfigs=MAX_SIGNIFICANT_DIGITS, warn=False)
806
814
  if rounded_value != value:
807
815
  warn(
808
816
  f"'{value}' rounded to {MAX_SIGNIFICANT_DIGITS} significant digits",
@@ -810,17 +818,18 @@ class Cell(CellStorageFlags, Cacheable):
810
818
  stacklevel=2,
811
819
  )
812
820
  cell = NumberCell(row, col, rounded_value)
813
- elif isinstance(value, (DateTime, builtin_datetime)):
814
- cell = DateCell(row, col, pendulum_instance(value))
815
- elif isinstance(value, (Duration, builtin_timedelta)):
821
+ elif isinstance(value, datetime):
822
+ cell = DateCell(row, col, value)
823
+ elif isinstance(value, timedelta):
816
824
  cell = DurationCell(row, col, value)
817
825
  else:
818
- raise ValueError("Can't determine cell type from type " + type(value).__name__)
826
+ msg = "Can't determine cell type from type " + type(value).__name__
827
+ raise ValueError(msg) # noqa: TRY004
819
828
 
820
829
  return cell
821
830
 
822
831
  @classmethod
823
- def _from_storage( # noqa: PLR0913, PLR0912
832
+ def _from_storage( # noqa: PLR0912
824
833
  cls,
825
834
  table_id: int,
826
835
  row: int,
@@ -882,7 +891,6 @@ class Cell(CellStorageFlags, Cacheable):
882
891
  offset += 4
883
892
  # Skip unused flags
884
893
  offset += 4 * bin(flags & 0x900).count("1")
885
- #
886
894
  if flags & 0x2000:
887
895
  storage_flags._num_format_id = unpack("<i", buffer[offset : offset + 4])[0]
888
896
  offset += 4
@@ -916,12 +924,12 @@ class Cell(CellStorageFlags, Cacheable):
916
924
  elif cell_type == TSTArchives.textCellType:
917
925
  cell = TextCell(row, col, model.table_string(table_id, storage_flags._string_id))
918
926
  elif cell_type == TSTArchives.dateCellType:
919
- cell = DateCell(row, col, EPOCH + duration(seconds=seconds))
927
+ cell = DateCell(row, col, EPOCH + timedelta(seconds=seconds))
920
928
  cell._datetime = cell._value
921
929
  elif cell_type == TSTArchives.boolCellType:
922
930
  cell = BoolCell(row, col, double > 0.0)
923
931
  elif cell_type == TSTArchives.durationCellType:
924
- cell = DurationCell(row, col, duration(seconds=double))
932
+ cell = DurationCell(row, col, timedelta(seconds=double))
925
933
  elif cell_type == TSTArchives.formulaErrorCellType:
926
934
  cell = ErrorCell(row, col)
927
935
  elif cell_type == TSTArchives.automaticCellType:
@@ -951,11 +959,11 @@ class Cell(CellStorageFlags, Cacheable):
951
959
 
952
960
  return cell
953
961
 
954
- def _copy_flags(self, storage_flags: CellStorageFlags):
962
+ def _copy_flags(self, storage_flags: CellStorageFlags) -> None:
955
963
  for flag in storage_flags.flags():
956
964
  setattr(self, flag, getattr(storage_flags, flag))
957
965
 
958
- def _set_merge(self, merge_ref):
966
+ def _set_merge(self, merge_ref) -> None:
959
967
  if isinstance(merge_ref, MergeAnchor):
960
968
  self.is_merged = True
961
969
  self.size = merge_ref.size
@@ -1024,7 +1032,11 @@ class Cell(CellStorageFlags, Cacheable):
1024
1032
  flags = 4
1025
1033
  length += 8
1026
1034
  cell_type = TSTArchives.dateCellType
1027
- date_delta = self._value.astimezone() - EPOCH
1035
+ # date_delta = self._value.astimezone() - EPOCH
1036
+ if self._value.tzinfo is None:
1037
+ date_delta = self._value - EPOCH
1038
+ else:
1039
+ date_delta = self._value - EPOCH.astimezone(self._value.tzinfo)
1028
1040
  value = pack("<d", float(date_delta.total_seconds()))
1029
1041
  elif isinstance(self, BoolCell):
1030
1042
  flags = 2
@@ -1135,7 +1147,7 @@ class Cell(CellStorageFlags, Cacheable):
1135
1147
 
1136
1148
  @property
1137
1149
  @cache(num_args=0)
1138
- def _image_data(self) -> Tuple[bytes, str]:
1150
+ def _image_data(self) -> tuple[bytes, str]:
1139
1151
  """Return the background image data for a cell or None if no image."""
1140
1152
  if self._cell_style_id is None:
1141
1153
  return None
@@ -1161,30 +1173,34 @@ class Cell(CellStorageFlags, Cacheable):
1161
1173
  stacklevel=3,
1162
1174
  )
1163
1175
  return None
1164
- else:
1165
- image_data = self._model.objects.file_store[image_pathnames[0]]
1166
- digest = sha1(image_data).digest()
1167
- if digest not in self._model._images:
1168
- self._model._images[digest] = image_id
1176
+ image_data = self._model.objects.file_store[image_pathnames[0]]
1177
+ digest = sha1(image_data).digest() # noqa: S324
1178
+ if digest not in self._model._images:
1179
+ self._model._images[digest] = image_id
1169
1180
 
1170
- return (image_data, preferred_filename)
1181
+ return (image_data, preferred_filename)
1171
1182
 
1172
1183
  def _custom_format(self) -> str: # noqa: PLR0911
1173
1184
  if self._text_format_id is not None and self._type == CellType.TEXT:
1174
- format = self._model.table_format(self._table_id, self._text_format_id)
1185
+ custom_format = self._model.table_format(self._table_id, self._text_format_id)
1175
1186
  elif self._currency_format_id is not None:
1176
- format = self._model.table_format(self._table_id, self._currency_format_id)
1187
+ custom_format = self._model.table_format(self._table_id, self._currency_format_id)
1177
1188
  elif self._bool_format_id is not None and self._type == CellType.BOOL:
1178
- format = self._model.table_format(self._table_id, self._bool_format_id)
1189
+ custom_format = self._model.table_format(self._table_id, self._bool_format_id)
1179
1190
  elif self._num_format_id is not None:
1180
- format = self._model.table_format(self._table_id, self._num_format_id)
1191
+ custom_format = self._model.table_format(self._table_id, self._num_format_id)
1181
1192
  else:
1182
1193
  return str(self.value)
1183
1194
 
1184
- debug("custom_format: @[%d,%d]: format_type=%s, ", self.row, self.col, format.format_type)
1195
+ debug(
1196
+ "custom_format: @[%d,%d]: format_type=%s, ",
1197
+ self.row,
1198
+ self.col,
1199
+ custom_format.format_type,
1200
+ )
1185
1201
 
1186
- if format.HasField("custom_uid"):
1187
- format_uuid = NumbersUUID(format.custom_uid).hex
1202
+ if custom_format.HasField("custom_uid"):
1203
+ format_uuid = NumbersUUID(custom_format.custom_uid).hex
1188
1204
  format_map = self._model.custom_format_map()
1189
1205
  custom_format = format_map[format_uuid].default_format
1190
1206
  if custom_format.requires_fraction_replacement:
@@ -1200,32 +1216,32 @@ class Cell(CellStorageFlags, Cacheable):
1200
1216
  self._d128,
1201
1217
  format_map[format_uuid].name,
1202
1218
  )
1203
- elif format.format_type == FormatType.DECIMAL:
1204
- return _format_decimal(self._d128, format)
1205
- elif format.format_type == FormatType.CURRENCY:
1206
- return _format_currency(self._d128, format)
1207
- elif format.format_type == FormatType.BOOLEAN:
1219
+ elif custom_format.format_type == FormatType.DECIMAL:
1220
+ return _format_decimal(self._d128, custom_format)
1221
+ elif custom_format.format_type == FormatType.CURRENCY:
1222
+ return _format_currency(self._d128, custom_format)
1223
+ elif custom_format.format_type == FormatType.BOOLEAN:
1208
1224
  return "TRUE" if self.value else "FALSE"
1209
- elif format.format_type == FormatType.PERCENT:
1210
- return _format_decimal(self._d128 * 100, format, percent=True)
1211
- elif format.format_type == FormatType.BASE:
1212
- return _format_base(self._d128, format)
1213
- elif format.format_type == FormatType.FRACTION:
1214
- return _format_fraction(self._d128, format)
1215
- elif format.format_type == FormatType.SCIENTIFIC:
1216
- return _format_scientific(self._d128, format)
1217
- elif format.format_type == FormatType.CHECKBOX:
1225
+ elif custom_format.format_type == FormatType.PERCENT:
1226
+ return _format_decimal(self._d128 * 100, custom_format, percent=True)
1227
+ elif custom_format.format_type == FormatType.BASE:
1228
+ return _format_base(self._d128, custom_format)
1229
+ elif custom_format.format_type == FormatType.FRACTION:
1230
+ return _format_fraction(self._d128, custom_format)
1231
+ elif custom_format.format_type == FormatType.SCIENTIFIC:
1232
+ return _format_scientific(self._d128, custom_format)
1233
+ elif custom_format.format_type == FormatType.CHECKBOX:
1218
1234
  return CHECKBOX_TRUE_VALUE if self.value else CHECKBOX_FALSE_VALUE
1219
- elif format.format_type == FormatType.RATING:
1235
+ elif custom_format.format_type == FormatType.RATING:
1220
1236
  return STAR_RATING_VALUE * int(self._d128)
1221
1237
  else:
1222
1238
  formatted_value = str(self.value)
1223
1239
  return formatted_value
1224
1240
 
1225
1241
  def _date_format(self) -> str:
1226
- format = self._model.table_format(self._table_id, self._date_format_id)
1227
- if format.HasField("custom_uid"):
1228
- format_uuid = NumbersUUID(format.custom_uid).hex
1242
+ date_format = self._model.table_format(self._table_id, self._date_format_id)
1243
+ if date_format.HasField("custom_uid"):
1244
+ format_uuid = NumbersUUID(date_format.custom_uid).hex
1229
1245
  format_map = self._model.custom_format_map()
1230
1246
  custom_format = format_map[format_uuid].default_format
1231
1247
  custom_format_string = custom_format.custom_format_string
@@ -1239,25 +1255,25 @@ class Cell(CellStorageFlags, Cacheable):
1239
1255
  )
1240
1256
  return ""
1241
1257
  else:
1242
- formatted_value = _decode_date_format(format.date_time_format, self._datetime)
1258
+ formatted_value = _decode_date_format(date_format.date_time_format, self._datetime)
1243
1259
  return formatted_value
1244
1260
 
1245
1261
  def _duration_format(self) -> str:
1246
- format = self._model.table_format(self._table_id, self._duration_format_id)
1262
+ duration_format = self._model.table_format(self._table_id, self._duration_format_id)
1247
1263
  debug(
1248
1264
  "duration_format: @[%d,%d]: table_id=%d, duration_format_id=%d, duration_style=%s",
1249
1265
  self.row,
1250
1266
  self.col,
1251
1267
  self._table_id,
1252
1268
  self._duration_format_id,
1253
- format.duration_style,
1269
+ duration_format.duration_style,
1254
1270
  )
1255
1271
 
1256
- duration_style = format.duration_style
1257
- unit_largest = format.duration_unit_largest
1258
- unit_smallest = format.duration_unit_smallest
1259
- if format.use_automatic_duration_units:
1260
- unit_smallest, unit_largest = _auto_units(self._double, format)
1272
+ duration_style = duration_format.duration_style
1273
+ unit_largest = duration_format.duration_unit_largest
1274
+ unit_smallest = duration_format.duration_unit_smallest
1275
+ if duration_format.use_automatic_duration_units:
1276
+ unit_smallest, unit_largest = _auto_units(self._double, duration_format)
1261
1277
 
1262
1278
  d = self._double
1263
1279
  dd = int(self._double)
@@ -1324,8 +1340,8 @@ class Cell(CellStorageFlags, Cacheable):
1324
1340
  def _set_formatting(
1325
1341
  self,
1326
1342
  format_id: int,
1327
- format_type: Union[FormattingType, CustomFormattingType],
1328
- control_id: Optional[int] = None,
1343
+ format_type: FormattingType | CustomFormattingType,
1344
+ control_id: int | None = None,
1329
1345
  is_currency: bool = False,
1330
1346
  ) -> None:
1331
1347
  self._is_currency = is_currency
@@ -1357,10 +1373,11 @@ class Cell(CellStorageFlags, Cacheable):
1357
1373
 
1358
1374
 
1359
1375
  class NumberCell(Cell):
1360
- """.. NOTE::
1376
+ """
1377
+ .. NOTE::
1361
1378
 
1362
- Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1363
- """ # fmt: skip
1379
+ Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1380
+ """
1364
1381
 
1365
1382
  def __init__(self, row: int, col: int, value: float, cell_type=CellType.NUMBER) -> None:
1366
1383
  self._type = cell_type
@@ -1382,10 +1399,11 @@ class TextCell(Cell):
1382
1399
 
1383
1400
 
1384
1401
  class RichTextCell(Cell):
1385
- """.. NOTE::
1402
+ """
1403
+ .. NOTE::
1386
1404
 
1387
- Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1388
- """ # fmt: skip
1405
+ Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1406
+ """
1389
1407
 
1390
1408
  def __init__(self, row: int, col: int, value) -> None:
1391
1409
  super().__init__(row, col, value["text"])
@@ -1408,7 +1426,7 @@ class RichTextCell(Cell):
1408
1426
  return self._value
1409
1427
 
1410
1428
  @property
1411
- def bullets(self) -> List[str]:
1429
+ def bullets(self) -> list[str]:
1412
1430
  """List[str]: A list of the text bullets in the cell."""
1413
1431
  return self._bullets
1414
1432
 
@@ -1418,8 +1436,9 @@ class RichTextCell(Cell):
1418
1436
  return self._formatted_bullets
1419
1437
 
1420
1438
  @property
1421
- def hyperlinks(self) -> Union[List[Tuple], None]:
1422
- """List[Tuple] | None: the hyperlinks in a cell or ``None``.
1439
+ def hyperlinks(self) -> list[tuple] | None:
1440
+ """
1441
+ List[Tuple] | None: the hyperlinks in a cell or ``None``.
1423
1442
 
1424
1443
  Numbers does not support hyperlinks to cells within a spreadsheet, but does
1425
1444
  allow embedding links in cells. When cells contain hyperlinks,
@@ -1433,6 +1452,7 @@ class RichTextCell(Cell):
1433
1452
 
1434
1453
  cell = table.cell(0, 0)
1435
1454
  (text, url) = cell.hyperlinks[0]
1455
+
1436
1456
  """
1437
1457
  return self._hyperlinks
1438
1458
 
@@ -1443,29 +1463,31 @@ class BulletedTextCell(RichTextCell):
1443
1463
 
1444
1464
 
1445
1465
  class EmptyCell(Cell):
1446
- """.. NOTE::
1466
+ """
1467
+ .. NOTE::
1447
1468
 
1448
- Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1449
- """ # fmt: skip
1469
+ Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1470
+ """
1450
1471
 
1451
1472
  def __init__(self, row: int, col: int) -> None:
1452
1473
  super().__init__(row, col, None)
1453
1474
  self._type = CellType.EMPTY
1454
1475
 
1455
1476
  @property
1456
- def value(self):
1477
+ def value(self) -> None:
1457
1478
  return None
1458
1479
 
1459
1480
  @property
1460
- def formatted_value(self):
1481
+ def formatted_value(self) -> str:
1461
1482
  return ""
1462
1483
 
1463
1484
 
1464
1485
  class BoolCell(Cell):
1465
- """.. NOTE::
1486
+ """
1487
+ .. NOTE::
1466
1488
 
1467
- Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1468
- """ # fmt: skip
1489
+ Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1490
+ """
1469
1491
 
1470
1492
  def __init__(self, row: int, col: int, value: bool) -> None:
1471
1493
  super().__init__(row, col, value)
@@ -1478,12 +1500,13 @@ class BoolCell(Cell):
1478
1500
 
1479
1501
 
1480
1502
  class DateCell(Cell):
1481
- """.. NOTE::
1503
+ """
1504
+ .. NOTE::
1482
1505
 
1483
- Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1484
- """ # fmt: skip
1506
+ Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1507
+ """
1485
1508
 
1486
- def __init__(self, row: int, col: int, value: DateTime) -> None:
1509
+ def __init__(self, row: int, col: int, value: datetime) -> None:
1487
1510
  super().__init__(row, col, value)
1488
1511
  self._type = CellType.DATE
1489
1512
 
@@ -1493,42 +1516,44 @@ class DateCell(Cell):
1493
1516
 
1494
1517
 
1495
1518
  class DurationCell(Cell):
1496
- def __init__(self, row: int, col: int, value: Duration) -> None:
1519
+ def __init__(self, row: int, col: int, value: timedelta) -> None:
1497
1520
  super().__init__(row, col, value)
1498
1521
  self._type = CellType.DURATION
1499
1522
 
1500
1523
  @property
1501
- def value(self) -> duration:
1524
+ def value(self) -> timedelta:
1502
1525
  return self._value
1503
1526
 
1504
1527
 
1505
1528
  class ErrorCell(Cell):
1506
- """.. NOTE::
1529
+ """
1530
+ .. NOTE::
1507
1531
 
1508
- Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1509
- """ # fmt: skip
1532
+ Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1533
+ """
1510
1534
 
1511
1535
  def __init__(self, row: int, col: int) -> None:
1512
1536
  super().__init__(row, col, None)
1513
1537
  self._type = CellType.ERROR
1514
1538
 
1515
1539
  @property
1516
- def value(self):
1540
+ def value(self) -> None:
1517
1541
  return None
1518
1542
 
1519
1543
 
1520
1544
  class MergedCell(Cell):
1521
- """.. NOTE::
1545
+ """
1546
+ .. NOTE::
1522
1547
 
1523
- Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1524
- """ # fmt: skip
1548
+ Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1549
+ """
1525
1550
 
1526
1551
  def __init__(self, row: int, col: int) -> None:
1527
1552
  super().__init__(row, col, None)
1528
1553
  self._type = CellType.MERGED
1529
1554
 
1530
1555
  @property
1531
- def value(self):
1556
+ def value(self) -> None:
1532
1557
  return None
1533
1558
 
1534
1559
 
@@ -1566,16 +1591,14 @@ def _decode_date_format_field(field: str, value: datetime) -> str:
1566
1591
  s = DATETIME_FIELD_MAP[field]
1567
1592
  if callable(s):
1568
1593
  return s(value)
1569
- else:
1570
- return value.strftime(s)
1571
- else:
1572
- warn(f"Unsupported field code '{field}'", UnsupportedWarning, stacklevel=4)
1573
- return ""
1594
+ return value.strftime(s)
1595
+ warn(f"Unsupported field code '{field}'", UnsupportedWarning, stacklevel=4)
1596
+ return ""
1574
1597
 
1575
1598
 
1576
- def _decode_date_format(format, value):
1599
+ def _decode_date_format(date_format, value):
1577
1600
  """Parse a custom date format string and return a formatted datetime value."""
1578
- chars = [*format]
1601
+ chars = [*date_format]
1579
1602
  index = 0
1580
1603
  in_string = False
1581
1604
  in_field = False
@@ -1587,7 +1610,7 @@ def _decode_date_format(format, value):
1587
1610
  if current_char == "'":
1588
1611
  if next_char is None:
1589
1612
  break
1590
- elif chars[index + 1] == "'":
1613
+ if chars[index + 1] == "'":
1591
1614
  result += "'"
1592
1615
  index += 2
1593
1616
  elif in_string:
@@ -1621,9 +1644,9 @@ def _decode_date_format(format, value):
1621
1644
  return result
1622
1645
 
1623
1646
 
1624
- def _decode_text_format(format, value: str):
1647
+ def _decode_text_format(text_format, value: str):
1625
1648
  """Parse a custom date format string and return a formatted number value."""
1626
- custom_format_string = format.custom_format_string
1649
+ custom_format_string = text_format.custom_format_string
1627
1650
  return custom_format_string.replace(CUSTOM_TEXT_PLACEHOLDER, value)
1628
1651
 
1629
1652
 
@@ -1638,7 +1661,7 @@ def _expand_quotes(value: str) -> str:
1638
1661
  if current_char == "'":
1639
1662
  if next_char is None:
1640
1663
  break
1641
- elif chars[index + 1] == "'":
1664
+ if chars[index + 1] == "'":
1642
1665
  formatted_value += "'"
1643
1666
  index += 2
1644
1667
  elif in_string:
@@ -1653,19 +1676,19 @@ def _expand_quotes(value: str) -> str:
1653
1676
  return formatted_value
1654
1677
 
1655
1678
 
1656
- def _decode_number_format(format, value, name): # noqa: PLR0912
1679
+ def _decode_number_format(number_format, value, name): # noqa: PLR0912
1657
1680
  """Parse a custom date format string and return a formatted number value."""
1658
- custom_format_string = format.custom_format_string
1659
- value *= format.scale_factor
1660
- if "%" in custom_format_string and format.scale_factor == 1.0:
1681
+ custom_format_string = number_format.custom_format_string
1682
+ value *= number_format.scale_factor
1683
+ if "%" in custom_format_string and number_format.scale_factor == 1.0:
1661
1684
  # Per cent scale has 100x but % does not
1662
1685
  value *= 100.0
1663
1686
 
1664
- if format.currency_code != "":
1687
+ if number_format.currency_code != "":
1665
1688
  # Replace currency code with symbol and no-break space
1666
1689
  custom_format_string = custom_format_string.replace(
1667
1690
  "\u00a4",
1668
- format.currency_code + "\u00a0",
1691
+ number_format.currency_code + "\u00a0",
1669
1692
  )
1670
1693
 
1671
1694
  if (match := re.search(r"([#0.,]+(E[+]\d+)?)", custom_format_string)) is None:
@@ -1695,7 +1718,7 @@ def _decode_number_format(format, value, name): # noqa: PLR0912
1695
1718
  if num_decimals > 0:
1696
1719
  if dec_part[0] == "#":
1697
1720
  dec_pad = None
1698
- elif format.num_nonspace_decimal_digits > 0:
1721
+ elif number_format.num_nonspace_decimal_digits > 0:
1699
1722
  dec_pad = CellPadding.ZERO
1700
1723
  else:
1701
1724
  dec_pad = CellPadding.SPACE
@@ -1712,15 +1735,15 @@ def _decode_number_format(format, value, name): # noqa: PLR0912
1712
1735
  decimal = float(f"0.{decimal}")
1713
1736
 
1714
1737
  num_integers = len(int_part.replace(",", ""))
1715
- if not format.show_thousands_separator:
1738
+ if not number_format.show_thousands_separator:
1716
1739
  int_part = int_part.replace(",", "")
1717
1740
  if num_integers > 0:
1718
1741
  if int_part[0] == "#":
1719
1742
  int_pad = None
1720
1743
  int_width = len(int_part)
1721
- elif format.num_nonspace_integer_digits > 0:
1744
+ elif number_format.num_nonspace_integer_digits > 0:
1722
1745
  int_pad = CellPadding.ZERO
1723
- if format.show_thousands_separator:
1746
+ if number_format.show_thousands_separator:
1724
1747
  num_commas = int(math.floor(math.log10(integer)) / 3) if integer != 0 else 0
1725
1748
  num_commas = max([num_commas, int((num_integers - 1) / 3)])
1726
1749
  int_width = num_integers + num_commas
@@ -1734,7 +1757,7 @@ def _decode_number_format(format, value, name): # noqa: PLR0912
1734
1757
  int_width = num_integers
1735
1758
 
1736
1759
  # value_1 = str(value).split(".")[0]
1737
- # value_2 = sigfig.round(str(value).split(".")[1], sigfig=MAX_SIGNIFICANT_DIGITS, warn=False)
1760
+ # value_2 = sigfig(str(value).split(".")[1], sigfig=MAX_SIGNIFICANT_DIGITS, warn=False)
1738
1761
  # int_pad_space_as_zero = (
1739
1762
  # num_integers > 0
1740
1763
  # and num_decimals > 0
@@ -1756,26 +1779,29 @@ def _decode_number_format(format, value, name): # noqa: PLR0912
1756
1779
  formatted_value = "".rjust(int_width)
1757
1780
  elif integer == 0 and int_pad is None and dec_pad == CellPadding.SPACE:
1758
1781
  formatted_value = ""
1759
- elif integer == 0 and int_pad == CellPadding.SPACE and dec_pad is not None:
1760
- formatted_value = "".rjust(int_width)
1761
1782
  elif (
1762
1783
  integer == 0
1763
1784
  and int_pad == CellPadding.SPACE
1764
- and dec_pad is None
1765
- and len(str(decimal)) > num_decimals
1785
+ and dec_pad is not None
1786
+ or (
1787
+ integer == 0
1788
+ and int_pad == CellPadding.SPACE
1789
+ and dec_pad is None
1790
+ and len(str(decimal)) > num_decimals
1791
+ )
1766
1792
  ):
1767
1793
  formatted_value = "".rjust(int_width)
1768
1794
  elif int_pad_space_as_zero or int_pad == CellPadding.ZERO:
1769
- if format.show_thousands_separator:
1795
+ if number_format.show_thousands_separator:
1770
1796
  formatted_value = f"{integer:0{int_width},}"
1771
1797
  else:
1772
1798
  formatted_value = f"{integer:0{int_width}}"
1773
1799
  elif int_pad == CellPadding.SPACE:
1774
- if format.show_thousands_separator:
1800
+ if number_format.show_thousands_separator:
1775
1801
  formatted_value = f"{integer:,}".rjust(int_width)
1776
1802
  else:
1777
1803
  formatted_value = str(integer).rjust(int_width)
1778
- elif format.show_thousands_separator:
1804
+ elif number_format.show_thousands_separator:
1779
1805
  formatted_value = f"{integer:,}"
1780
1806
  else:
1781
1807
  formatted_value = str(integer)
@@ -1795,33 +1821,33 @@ def _decode_number_format(format, value, name): # noqa: PLR0912
1795
1821
  return _expand_quotes(formatted_value)
1796
1822
 
1797
1823
 
1798
- def _format_decimal(value: float, format, percent: bool = False) -> str:
1824
+ def _format_decimal(value: float, number_format, percent: bool = False) -> str:
1799
1825
  if value is None:
1800
1826
  return ""
1801
- if value < 0 and format.negative_style == 1:
1827
+ if value < 0 and number_format.negative_style == 1:
1802
1828
  accounting_style = False
1803
1829
  value = -value
1804
- elif value < 0 and format.negative_style >= 2:
1830
+ elif value < 0 and number_format.negative_style >= 2:
1805
1831
  accounting_style = True
1806
1832
  value = -value
1807
1833
  else:
1808
1834
  accounting_style = False
1809
- thousands = "," if format.show_thousands_separator else ""
1835
+ thousands = "," if number_format.show_thousands_separator else ""
1810
1836
 
1811
- if value.is_integer() and format.decimal_places >= DECIMAL_PLACES_AUTO:
1837
+ if value.is_integer() and number_format.decimal_places >= DECIMAL_PLACES_AUTO:
1812
1838
  formatted_value = f"{int(value):{thousands}}"
1813
1839
  else:
1814
- if format.decimal_places >= DECIMAL_PLACES_AUTO:
1815
- formatted_value = str(sigfig.round(value, MAX_SIGNIFICANT_DIGITS, warn=False))
1840
+ if number_format.decimal_places >= DECIMAL_PLACES_AUTO:
1841
+ formatted_value = str(sigfig(value, MAX_SIGNIFICANT_DIGITS, warn=False))
1816
1842
  else:
1817
- formatted_value = sigfig.round(value, MAX_SIGNIFICANT_DIGITS, type=str, warn=False)
1818
- formatted_value = sigfig.round(
1843
+ formatted_value = sigfig(value, MAX_SIGNIFICANT_DIGITS, type=str, warn=False)
1844
+ formatted_value = sigfig(
1819
1845
  formatted_value,
1820
- decimals=format.decimal_places,
1846
+ decimals=number_format.decimal_places,
1821
1847
  type=str,
1822
1848
  )
1823
- if format.show_thousands_separator:
1824
- formatted_value = sigfig.round(formatted_value, spacer=",", spacing=3, type=str)
1849
+ if number_format.show_thousands_separator:
1850
+ formatted_value = sigfig(formatted_value, spacer=",", spacing=3, type=str)
1825
1851
  try:
1826
1852
  (integer, decimal) = formatted_value.split(".")
1827
1853
  formatted_value = integer + "." + decimal.replace(",", "")
@@ -1833,22 +1859,20 @@ def _format_decimal(value: float, format, percent: bool = False) -> str:
1833
1859
 
1834
1860
  if accounting_style:
1835
1861
  return f"({formatted_value})"
1836
- else:
1837
- return formatted_value
1862
+ return formatted_value
1838
1863
 
1839
1864
 
1840
- def _format_currency(value: float, format) -> str:
1841
- formatted_value = _format_decimal(value, format)
1842
- if format.currency_code in CURRENCY_SYMBOLS:
1843
- symbol = CURRENCY_SYMBOLS[format.currency_code]
1865
+ def _format_currency(value: float, number_format) -> str:
1866
+ formatted_value = _format_decimal(value, number_format)
1867
+ if number_format.currency_code in CURRENCY_SYMBOLS:
1868
+ symbol = CURRENCY_SYMBOLS[number_format.currency_code]
1844
1869
  else:
1845
- symbol = format.currency_code + " "
1846
- if format.use_accounting_style and value < 0:
1870
+ symbol = number_format.currency_code + " "
1871
+ if number_format.use_accounting_style and value < 0:
1847
1872
  return f"{symbol}\t({formatted_value[1:]})"
1848
- elif format.use_accounting_style:
1873
+ if number_format.use_accounting_style:
1849
1874
  return f"{symbol}\t{formatted_value}"
1850
- else:
1851
- return symbol + formatted_value
1875
+ return symbol + formatted_value
1852
1876
 
1853
1877
 
1854
1878
  INT_TO_BASE_CHAR = [str(x) for x in range(10)] + [chr(x) for x in range(ord("A"), ord("Z") + 1)]
@@ -1868,49 +1892,45 @@ def _twos_complement(value: int, base: int) -> str:
1868
1892
 
1869
1893
  if base == 2:
1870
1894
  return bin(twos_complement_dec)[2:].rjust(num_bits, "1")
1871
- elif base == 8:
1895
+ if base == 8:
1872
1896
  return oct(twos_complement_dec)[2:]
1873
- else:
1874
- return hex(twos_complement_dec)[2:].upper()
1897
+ return hex(twos_complement_dec)[2:].upper()
1875
1898
 
1876
1899
 
1877
- def _format_base(value: float, format) -> str:
1900
+ def _format_base(value: float, number_format) -> str:
1878
1901
  if value == 0:
1879
- return "0".zfill(format.base_places)
1902
+ return "0".zfill(number_format.base_places)
1880
1903
 
1881
1904
  value = round(value)
1882
1905
 
1883
1906
  is_negative = False
1884
- if not format.base_use_minus_sign and format.base in [2, 8, 16]:
1907
+ if not number_format.base_use_minus_sign and number_format.base in [2, 8, 16]:
1885
1908
  if value < 0:
1886
- return _twos_complement(value, format.base)
1887
- else:
1888
- value = abs(value)
1909
+ return _twos_complement(value, number_format.base)
1910
+ value = abs(value)
1889
1911
  elif value < 0:
1890
1912
  is_negative = True
1891
1913
  value = abs(value)
1892
1914
 
1893
1915
  formatted_value = []
1894
1916
  while value:
1895
- formatted_value.append(int(value % format.base))
1896
- value //= format.base
1917
+ formatted_value.append(int(value % number_format.base))
1918
+ value //= number_format.base
1897
1919
  formatted_value = "".join([INT_TO_BASE_CHAR[x] for x in formatted_value[::-1]])
1898
1920
 
1899
1921
  if is_negative:
1900
- return "-" + formatted_value.zfill(format.base_places)
1901
- else:
1902
- return formatted_value.zfill(format.base_places)
1922
+ return "-" + formatted_value.zfill(number_format.base_places)
1923
+ return formatted_value.zfill(number_format.base_places)
1903
1924
 
1904
1925
 
1905
1926
  def _format_fraction_parts_to(whole: int, numerator: int, denominator: int):
1906
1927
  if whole > 0:
1907
1928
  if numerator == 0:
1908
1929
  return str(whole)
1909
- else:
1910
- return f"{whole} {numerator}/{denominator}"
1911
- elif numerator == 0:
1930
+ return f"{whole} {numerator}/{denominator}"
1931
+ if numerator == 0:
1912
1932
  return "0"
1913
- elif numerator == denominator:
1933
+ if numerator == denominator:
1914
1934
  return "1"
1915
1935
  return f"{numerator}/{denominator}"
1916
1936
 
@@ -1923,7 +1943,8 @@ def _float_to_fraction(value: float, denominator: int) -> str:
1923
1943
 
1924
1944
 
1925
1945
  def _float_to_n_digit_fraction(value: float, max_digits: int) -> str:
1926
- """Convert a float to a fraction of a maxinum number of digits
1946
+ """
1947
+ Convert a float to a fraction of a maxinum number of digits
1927
1948
  and return as a string.
1928
1949
  """
1929
1950
  max_denominator = 10**max_digits - 1
@@ -1935,35 +1956,33 @@ def _float_to_n_digit_fraction(value: float, max_digits: int) -> str:
1935
1956
  return _format_fraction_parts_to(whole, numerator, denominator)
1936
1957
 
1937
1958
 
1938
- def _format_fraction(value: float, format) -> str:
1939
- accuracy = format.fraction_accuracy
1959
+ def _format_fraction(value: float, number_format) -> str:
1960
+ accuracy = number_format.fraction_accuracy
1940
1961
  if accuracy & 0xFF000000:
1941
1962
  num_digits = 0x100000000 - accuracy
1942
1963
  return _float_to_n_digit_fraction(value, num_digits)
1943
- else:
1944
- return _float_to_fraction(value, accuracy)
1964
+ return _float_to_fraction(value, accuracy)
1945
1965
 
1946
1966
 
1947
- def _format_scientific(value: float, format) -> str:
1948
- formatted_value = sigfig.round(value, sigfigs=MAX_SIGNIFICANT_DIGITS, warn=False)
1949
- return f"{formatted_value:.{format.decimal_places}E}"
1967
+ def _format_scientific(value: float, number_format) -> str:
1968
+ formatted_value = sigfig(value, sigfigs=MAX_SIGNIFICANT_DIGITS, warn=False)
1969
+ return f"{formatted_value:.{number_format.decimal_places}E}"
1950
1970
 
1951
1971
 
1952
- def _unit_format(unit: str, value: int, style: int, abbrev: Optional[str] = None):
1972
+ def _unit_format(unit: str, value: int, style: int, abbrev: str | None = None):
1953
1973
  plural = "" if value == 1 else "s"
1954
1974
  if abbrev is None:
1955
1975
  abbrev = unit[0]
1956
1976
  if style == DurationStyle.COMPACT:
1957
1977
  return ""
1958
- elif style == DurationStyle.SHORT:
1978
+ if style == DurationStyle.SHORT:
1959
1979
  return f"{abbrev}"
1960
- else:
1961
- return f" {unit}" + plural
1980
+ return f" {unit}" + plural
1962
1981
 
1963
1982
 
1964
- def _auto_units(cell_value, format):
1965
- unit_largest = format.duration_unit_largest
1966
- unit_smallest = format.duration_unit_smallest
1983
+ def _auto_units(cell_value, number_format):
1984
+ unit_largest = number_format.duration_unit_largest
1985
+ unit_smallest = number_format.duration_unit_smallest
1967
1986
 
1968
1987
  if cell_value == 0:
1969
1988
  unit_largest = DurationUnits.DAY
@@ -1992,8 +2011,7 @@ def _auto_units(cell_value, format):
1992
2011
  unit_smallest = DurationUnits.HOUR
1993
2012
  elif cell_value % SECONDS_IN_WEEK:
1994
2013
  unit_smallest = DurationUnits.DAY
1995
- if unit_smallest < unit_largest:
1996
- unit_smallest = unit_largest
2014
+ unit_smallest = max(unit_smallest, unit_largest)
1997
2015
 
1998
2016
  return unit_smallest, unit_largest
1999
2017
 
@@ -2004,7 +2022,8 @@ range_parts = re.compile(r"(\$?)([A-Z]{1,3})(\$?)(\d+)")
2004
2022
 
2005
2023
 
2006
2024
  def xl_cell_to_rowcol(cell_str: str) -> tuple:
2007
- """Convert a cell reference in A1 notation to a zero indexed row and column.
2025
+ """
2026
+ Convert a cell reference in A1 notation to a zero indexed row and column.
2008
2027
 
2009
2028
  Parameters
2010
2029
  ----------
@@ -2015,6 +2034,7 @@ def xl_cell_to_rowcol(cell_str: str) -> tuple:
2015
2034
  -------
2016
2035
  row, col: int, int
2017
2036
  Cell row and column numbers (zero indexed).
2037
+
2018
2038
  """
2019
2039
  if not cell_str:
2020
2040
  return 0, 0
@@ -2028,11 +2048,9 @@ def xl_cell_to_rowcol(cell_str: str) -> tuple:
2028
2048
  row_str = match.group(4)
2029
2049
 
2030
2050
  # Convert base26 column string to number.
2031
- expn = 0
2032
2051
  col = 0
2033
- for char in reversed(col_str):
2052
+ for expn, char in enumerate(reversed(col_str)):
2034
2053
  col += (ord(char) - ord("A") + 1) * (26**expn)
2035
- expn += 1
2036
2054
 
2037
2055
  # Convert 1-index to zero-index
2038
2056
  row = int(row_str) - 1
@@ -2042,7 +2060,8 @@ def xl_cell_to_rowcol(cell_str: str) -> tuple:
2042
2060
 
2043
2061
 
2044
2062
  def xl_range(first_row, first_col, last_row, last_col):
2045
- """Convert zero indexed row and col cell references to a A1:B1 range string.
2063
+ """
2064
+ Convert zero indexed row and col cell references to a A1:B1 range string.
2046
2065
 
2047
2066
  Parameters
2048
2067
  ----------
@@ -2059,18 +2078,19 @@ def xl_range(first_row, first_col, last_row, last_col):
2059
2078
  -------
2060
2079
  str:
2061
2080
  A1:B1 style range string.
2081
+
2062
2082
  """
2063
2083
  range1 = xl_rowcol_to_cell(first_row, first_col)
2064
2084
  range2 = xl_rowcol_to_cell(last_row, last_col)
2065
2085
 
2066
2086
  if range1 == range2:
2067
2087
  return range1
2068
- else:
2069
- return range1 + ":" + range2
2088
+ return range1 + ":" + range2
2070
2089
 
2071
2090
 
2072
2091
  def xl_rowcol_to_cell(row, col, row_abs=False, col_abs=False):
2073
- """Convert a zero indexed row and column cell reference to a A1 style string.
2092
+ """
2093
+ Convert a zero indexed row and column cell reference to a A1 style string.
2074
2094
 
2075
2095
  Parameters
2076
2096
  ----------
@@ -2087,6 +2107,7 @@ def xl_rowcol_to_cell(row, col, row_abs=False, col_abs=False):
2087
2107
  -------
2088
2108
  str:
2089
2109
  A1 style string.
2110
+
2090
2111
  """
2091
2112
  if row < 0:
2092
2113
  msg = f"row reference {row} below zero"
@@ -2105,7 +2126,8 @@ def xl_rowcol_to_cell(row, col, row_abs=False, col_abs=False):
2105
2126
 
2106
2127
 
2107
2128
  def xl_col_to_name(col, col_abs=False):
2108
- """Convert a zero indexed column cell reference to a string.
2129
+ """
2130
+ Convert a zero indexed column cell reference to a string.
2109
2131
 
2110
2132
  Parameters
2111
2133
  ----------
@@ -2118,6 +2140,7 @@ def xl_col_to_name(col, col_abs=False):
2118
2140
  -------
2119
2141
  str:
2120
2142
  Column in A1 notation.
2143
+
2121
2144
  """
2122
2145
  if col < 0:
2123
2146
  msg = f"column reference {col} below zero"
@@ -2160,7 +2183,7 @@ class Formatting:
2160
2183
  increment: float = 1.0
2161
2184
  maximum: float = 100.0
2162
2185
  minimum: float = 1.0
2163
- popup_values: List[str] = field(default_factory=lambda: ["Item 1"])
2186
+ popup_values: list[str] = field(default_factory=lambda: ["Item 1"])
2164
2187
  negative_style: NegativeNumberStyle = NegativeNumberStyle.MINUS
2165
2188
  show_thousands_separator: bool = False
2166
2189
  type: FormattingType = FormattingType.NUMBER
@@ -2188,7 +2211,8 @@ class Formatting:
2188
2211
  raise TypeError(msg)
2189
2212
 
2190
2213
  if self.type == FormattingType.CURRENCY and self.currency_code not in CURRENCIES:
2191
- raise TypeError(f"Unsupported currency code '{self.currency_code}'")
2214
+ msg = f"Unsupported currency code '{self.currency_code}'"
2215
+ raise TypeError(msg)
2192
2216
 
2193
2217
  if self.decimal_places is None:
2194
2218
  if self.type == FormattingType.CURRENCY:
@@ -2227,7 +2251,8 @@ class CustomFormatting:
2227
2251
  raise TypeError(msg)
2228
2252
 
2229
2253
  if self.type == CustomFormattingType.TEXT and self.format.count("%s") > 1:
2230
- raise TypeError("Custom formats only allow one text substitution")
2254
+ msg = "Custom formats only allow one text substitution"
2255
+ raise TypeError(msg)
2231
2256
 
2232
2257
  @classmethod
2233
2258
  def from_archive(cls, archive: object):