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