numbers-parser 4.4.6__py3-none-any.whl → 4.4.8__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/model.py CHANGED
@@ -1,30 +1,18 @@
1
1
  import math
2
2
  import re
3
-
4
3
  from array import array
5
4
  from collections import defaultdict
6
- from numbers_parser.numbers_cache import cache, Cacheable
7
5
  from struct import pack
8
6
  from typing import Dict, List, Tuple, Union
9
7
  from warnings import warn
10
8
 
11
- from numbers_parser.containers import ObjectStore
12
- from numbers_parser.iwafile import find_extension
13
- from numbers_parser.constants import (
14
- EPOCH,
15
- DEFAULT_COLUMN_WIDTH,
16
- DEFAULT_DOCUMENT,
17
- DEFAULT_PRE_BNC_BYTES,
18
- DEFAULT_ROW_HEIGHT,
19
- DEFAULT_TABLE_OFFSET,
20
- DEFAULT_TILE_SIZE,
21
- DEFAULT_TEXT_INSET,
22
- DEFAULT_TEXT_WRAP,
23
- DOCUMENT_ID,
24
- PACKAGE_ID,
25
- MAX_TILE_SIZE,
9
+ from numbers_parser.bullets import (
10
+ BULLET_CONVERSION,
11
+ BULLET_PREFIXES,
12
+ BULLET_SUFFIXES,
26
13
  )
27
14
  from numbers_parser.cell import (
15
+ RGB,
28
16
  Alignment,
29
17
  BoolCell,
30
18
  Border,
@@ -37,7 +25,6 @@ from numbers_parser.cell import (
37
25
  MergedCell,
38
26
  MergeReference,
39
27
  NumberCell,
40
- RGB,
41
28
  RichTextCell,
42
29
  Style,
43
30
  TextCell,
@@ -46,31 +33,42 @@ from numbers_parser.cell import (
46
33
  xl_range,
47
34
  xl_rowcol_to_cell,
48
35
  )
36
+ from numbers_parser.cell_storage import CellStorage
37
+ from numbers_parser.constants import (
38
+ DEFAULT_COLUMN_WIDTH,
39
+ DEFAULT_DOCUMENT,
40
+ DEFAULT_PRE_BNC_BYTES,
41
+ DEFAULT_ROW_HEIGHT,
42
+ DEFAULT_TABLE_OFFSET,
43
+ DEFAULT_TEXT_INSET,
44
+ DEFAULT_TEXT_WRAP,
45
+ DEFAULT_TILE_SIZE,
46
+ DOCUMENT_ID,
47
+ EPOCH,
48
+ MAX_TILE_SIZE,
49
+ PACKAGE_ID,
50
+ )
51
+ from numbers_parser.containers import ObjectStore
49
52
  from numbers_parser.exceptions import UnsupportedError, UnsupportedWarning
50
53
  from numbers_parser.formula import TableFormulas
51
- from numbers_parser.bullets import (
52
- BULLET_PREFIXES,
53
- BULLET_CONVERSION,
54
- BULLET_SUFFIXES,
55
- )
56
- from numbers_parser.cell_storage import CellStorage
57
- from numbers_parser.numbers_uuid import NumbersUUID
58
-
59
- from numbers_parser.generated.fontmap import FONT_NAME_TO_FAMILY
60
54
  from numbers_parser.generated import TNArchives_pb2 as TNArchives
55
+ from numbers_parser.generated import TSCEArchives_pb2 as TSCEArchives
61
56
  from numbers_parser.generated import TSDArchives_pb2 as TSDArchives
62
- from numbers_parser.generated import TSPMessages_pb2 as TSPMessages
63
57
  from numbers_parser.generated import TSPArchiveMessages_pb2 as TSPArchiveMessages
58
+ from numbers_parser.generated import TSPMessages_pb2 as TSPMessages
59
+ from numbers_parser.generated import TSSArchives_pb2 as TSSArchives
64
60
  from numbers_parser.generated import TSTArchives_pb2 as TSTArchives
65
- from numbers_parser.generated import TSCEArchives_pb2 as TSCEArchives
66
61
  from numbers_parser.generated import TSWPArchives_pb2 as TSWPArchives
67
- from numbers_parser.generated import TSSArchives_pb2 as TSSArchives
68
- from numbers_parser.generated.TSWPArchives_pb2 import (
69
- CharacterStylePropertiesArchive as CharacterStyle,
70
- )
62
+ from numbers_parser.generated.fontmap import FONT_NAME_TO_FAMILY
71
63
  from numbers_parser.generated.TSDArchives_pb2 import (
72
64
  StrokePatternArchive as StrokePattern,
73
65
  )
66
+ from numbers_parser.generated.TSWPArchives_pb2 import (
67
+ CharacterStylePropertiesArchive as CharacterStyle,
68
+ )
69
+ from numbers_parser.iwafile import find_extension
70
+ from numbers_parser.numbers_cache import Cacheable, cache
71
+ from numbers_parser.numbers_uuid import NumbersUUID
74
72
 
75
73
 
76
74
  def create_font_name_map(font_map: dict) -> dict:
@@ -110,21 +108,19 @@ class MergeCells:
110
108
  return self._references[row_col].rect
111
109
 
112
110
  def merge_cell_names(self):
113
- ranges = [
111
+ return [
114
112
  xl_range(*v.rect) for k, v in self._references.items() if self.is_merge_reference(k)
115
113
  ]
116
- return ranges
117
114
 
118
115
  def merge_cells(self):
119
- ranges = [k for k, v in self._references.items() if self.is_merge_anchor(k)]
120
- return ranges
116
+ return [k for k, v in self._references.items() if self.is_merge_anchor(k)]
121
117
 
122
118
  def __len__(self):
123
119
  return len(self._references.keys())
124
120
 
125
121
 
126
122
  class DataLists(Cacheable):
127
- """Model for TST.DataList with caching and key generation for new values"""
123
+ """Model for TST.DataList with caching and key generation for new values."""
128
124
 
129
125
  def __init__(self, model: object, datalist_name: str, value_attr: str = None):
130
126
  self._model = model
@@ -134,7 +130,7 @@ class DataLists(Cacheable):
134
130
 
135
131
  @cache()
136
132
  def add_table(self, table_id: int):
137
- """Cache a new datalist for a table if not already seen"""
133
+ """Cache a new datalist for a table if not already seen."""
138
134
  base_data_store = self._model.objects[table_id].base_data_store
139
135
  datalist_id = getattr(base_data_store, self._datalist_name).identifier
140
136
  datalist = self._model.objects[datalist_id]
@@ -160,12 +156,12 @@ class DataLists(Cacheable):
160
156
  return self._datalists[table_id]["id"]
161
157
 
162
158
  def lookup_value(self, table_id: int, key: int):
163
- """Return the an entry in a table's datalist matching a key"""
159
+ """Return the an entry in a table's datalist matching a key."""
164
160
  self.add_table(table_id)
165
161
  return self._datalists[table_id]["by_key"][key]
166
162
 
167
163
  def init(self, table_id: int):
168
- """Remove all entries from a datalist"""
164
+ """Remove all entries from a datalist."""
169
165
  self.add_table(table_id)
170
166
  self._datalists[table_id]["by_key"] = {}
171
167
  self._datalists[table_id]["by_value"] = {}
@@ -176,7 +172,8 @@ class DataLists(Cacheable):
176
172
  def lookup_key(self, table_id: int, value) -> int:
177
173
  """Return the key associated with a value for a particular table entry.
178
174
  If the value is not in the datalist, allocate a new entry with the
179
- next available key"""
175
+ next available key.
176
+ """
180
177
  self.add_table(table_id)
181
178
  value_key = value.identifier if isinstance(value, TSPMessages.Reference) else value
182
179
  if value_key not in self._datalists[table_id]["by_value"]:
@@ -198,8 +195,7 @@ class DataLists(Cacheable):
198
195
 
199
196
 
200
197
  class _NumbersModel(Cacheable):
201
- """
202
- Loads all objects from Numbers document and provides decoding
198
+ """Loads all objects from Numbers document and provides decoding
203
199
  methods for other classes in the module to abstract away the
204
200
  internal structures of Numbers document data structures.
205
201
 
@@ -246,7 +242,7 @@ class _NumbersModel(Cacheable):
246
242
 
247
243
  # Don't cache: new tables can be added at runtime
248
244
  def table_ids(self, sheet_id: int) -> list:
249
- """Return a list of table IDs for a given sheet ID"""
245
+ """Return a list of table IDs for a given sheet ID."""
250
246
  table_info_ids = self.find_refs("TableInfoArchive")
251
247
  return [
252
248
  self.objects[t_id].tableModel.identifier
@@ -256,7 +252,7 @@ class _NumbersModel(Cacheable):
256
252
 
257
253
  # Don't cache: new tables can be added at runtime
258
254
  def table_info_id(self, table_id: int) -> int:
259
- """Return the TableInfoArchive ID for a given table ID"""
255
+ """Return the TableInfoArchive ID for a given table ID."""
260
256
  ids = [
261
257
  x
262
258
  for x in self.objects.find_refs("TableInfoArchive")
@@ -337,36 +333,36 @@ class _NumbersModel(Cacheable):
337
333
  def custom_format_map(self):
338
334
  custom_format_list_id = self.objects[DOCUMENT_ID].super.custom_format_list.identifier
339
335
  custom_format_list = self.objects[custom_format_list_id]
340
- custom_format_map = {
336
+ return {
341
337
  NumbersUUID(u).hex: custom_format_list.custom_formats[i]
342
338
  for i, u in enumerate(custom_format_list.uuids)
343
339
  }
344
- return custom_format_map
345
340
 
346
341
  @cache(num_args=2)
347
342
  def table_format(self, table_id: int, key: int) -> str:
348
- """Return the format associated with a format ID for a particular table"""
343
+ """Return the format associated with a format ID for a particular table."""
349
344
  return self._table_formats.lookup_value(table_id, key).format
350
345
 
351
346
  @cache(num_args=2)
352
347
  def table_style(self, table_id: int, key: int) -> str:
353
- """Return the style associated with a style ID for a particular table"""
348
+ """Return the style associated with a style ID for a particular table."""
354
349
  style_entry = self._table_styles.lookup_value(table_id, key)
355
350
  return self.objects[style_entry.reference.identifier]
356
351
 
357
352
  @cache(num_args=2)
358
353
  def table_string(self, table_id: int, key: int) -> str:
359
- """Return the string associated with a string ID for a particular table"""
354
+ """Return the string associated with a string ID for a particular table."""
360
355
  return self._table_strings.lookup_value(table_id, key).string
361
356
 
362
357
  def init_table_strings(self, table_id: int):
363
- """Cache table strings reference and delete all existing keys/values"""
358
+ """Cache table strings reference and delete all existing keys/values."""
364
359
  self._table_strings.init(table_id)
365
360
 
366
361
  def table_string_key(self, table_id: int, value: str) -> int:
367
362
  """Return the key associated with a string for a particular table. If
368
363
  the string is not in the strings table, allocate a new entry with the
369
- next available key"""
364
+ next available key.
365
+ """
370
366
  return self._table_strings.lookup_key(table_id, value)
371
367
 
372
368
  @cache(num_args=0)
@@ -398,7 +394,7 @@ class _NumbersModel(Cacheable):
398
394
 
399
395
  @cache()
400
396
  def table_base_id(self, table_id: int) -> int:
401
- """ "Finds the UUID of a table"""
397
+ """ "Finds the UUID of a table."""
402
398
  # Look for a TSCE.FormulaOwnerDependenciesArchive objects with the following at the
403
399
  # root level of the protobuf:
404
400
  #
@@ -442,7 +438,7 @@ class _NumbersModel(Cacheable):
442
438
 
443
439
  @cache(num_args=0)
444
440
  def calc_engine_id(self):
445
- """Return the CalculationEngine ID for the current document"""
441
+ """Return the CalculationEngine ID for the current document."""
446
442
  ce_id = self.find_refs("CalculationEngineArchive")
447
443
  if len(ce_id) == 0:
448
444
  return 0
@@ -451,7 +447,7 @@ class _NumbersModel(Cacheable):
451
447
 
452
448
  @cache(num_args=0)
453
449
  def calc_engine(self):
454
- """Return the CalculationEngine object for the current document"""
450
+ """Return the CalculationEngine object for the current document."""
455
451
  ce_id = self.calc_engine_id()
456
452
  if ce_id == 0:
457
453
  return None
@@ -488,8 +484,8 @@ class _NumbersModel(Cacheable):
488
484
  if base_data_store.merge_region_map.identifier == 0:
489
485
  return
490
486
 
491
- cell_range = self.objects[base_data_store.merge_region_map.identifier]
492
- for cell_range in cell_range.cell_range:
487
+ cell_ranges = self.objects[base_data_store.merge_region_map.identifier]
488
+ for cell_range in cell_ranges.cell_range:
493
489
  (col_start, row_start) = (
494
490
  cell_range.origin.packedData >> 16,
495
491
  cell_range.origin.packedData & 0xFFFF,
@@ -716,7 +712,7 @@ class _NumbersModel(Cacheable):
716
712
 
717
713
  @cache()
718
714
  def metadata_component(self, reference: Union[str, int] = None) -> int:
719
- """Return the ID of an object in the document metadata given it's name or ID"""
715
+ """Return the ID of an object in the document metadata given it's name or ID."""
720
716
  component_map = {c.identifier: c for c in self.objects[PACKAGE_ID].components}
721
717
  if isinstance(reference, str):
722
718
  component_ids = [
@@ -727,7 +723,7 @@ class _NumbersModel(Cacheable):
727
723
  return component_map[component_ids[0]]
728
724
 
729
725
  def add_component_metadata(self, object_id: int, parent: str, locator: str):
730
- """Add a new ComponentInfo record to the parent object in the document metadata"""
726
+ """Add a new ComponentInfo record to the parent object in the document metadata."""
731
727
  locator = locator.format(object_id)
732
728
  preferred_locator = re.sub(r"\-\d+.*", "", locator)
733
729
  component_info = TSPArchiveMessages.ComponentInfo(
@@ -749,7 +745,7 @@ class _NumbersModel(Cacheable):
749
745
  parent_id: int = None,
750
746
  is_weak: bool = False,
751
747
  ):
752
- """Add an external reference to an object in a metadata component"""
748
+ """Add an external reference to an object in a metadata component."""
753
749
  component = self.metadata_component(location or parent_id)
754
750
  if parent_id is not None:
755
751
  params = {"object_identifier": object_id, "component_identifier": parent_id}
@@ -830,7 +826,7 @@ class _NumbersModel(Cacheable):
830
826
  return table_strings_id, table_strings
831
827
 
832
828
  def table_height(self, table_id: int) -> int:
833
- """Return the height of a table in points"""
829
+ """Return the height of a table in points."""
834
830
  table_model = self.objects[table_id]
835
831
  bds = self.objects[table_id].base_data_store
836
832
  buckets = self.objects[bds.rowHeaders.buckets[0].identifier].headers
@@ -866,7 +862,7 @@ class _NumbersModel(Cacheable):
866
862
  return round(table_model.default_row_height)
867
863
 
868
864
  def table_width(self, table_id: int) -> int:
869
- """Return the width of a table in points"""
865
+ """Return the width of a table in points."""
870
866
  table_model = self.objects[table_id]
871
867
  bds = self.objects[table_id].base_data_store
872
868
  buckets = self.objects[bds.columnHeaders.identifier].headers
@@ -902,14 +898,14 @@ class _NumbersModel(Cacheable):
902
898
  return round(table_model.default_column_width)
903
899
 
904
900
  def num_header_rows(self, table_id: int, num_headers: int = None) -> int:
905
- """Return/set the number of header rows"""
901
+ """Return/set the number of header rows."""
906
902
  table_model = self.objects[table_id]
907
903
  if num_headers is not None:
908
904
  table_model.number_of_header_rows = num_headers
909
905
  return table_model.number_of_header_rows
910
906
 
911
907
  def num_header_cols(self, table_id: int, num_headers: int = None) -> int:
912
- """Return/set the number of header columns"""
908
+ """Return/set the number of header columns."""
913
909
  table_model = self.objects[table_id]
914
910
  if num_headers is not None:
915
911
  table_model.number_of_header_columns = num_headers
@@ -923,7 +919,7 @@ class _NumbersModel(Cacheable):
923
919
  )
924
920
 
925
921
  def last_table_offset(self, sheet_id):
926
- """Y offset of the last table in a sheet"""
922
+ """Y offset of the last table in a sheet."""
927
923
  table_id = self.table_ids(sheet_id)[-1]
928
924
  y_offset = [
929
925
  self.objects[self.table_info_id(x)].super.geometry.position.y
@@ -934,16 +930,10 @@ class _NumbersModel(Cacheable):
934
930
  return self.table_height(table_id) + y_offset
935
931
 
936
932
  def create_drawable(self, sheet_id: int, x: float, y: float) -> object:
937
- """Create a DrawableArchive for a new table in a sheet"""
938
- if x is not None:
939
- table_x = x
940
- else:
941
- table_x = 0.0
942
- if y is not None:
943
- table_y = y
944
- else:
945
- table_y = self.last_table_offset(sheet_id) + DEFAULT_TABLE_OFFSET
946
- drawable = TSDArchives.DrawableArchive(
933
+ """Create a DrawableArchive for a new table in a sheet."""
934
+ table_x = x if x is not None else 0.0
935
+ table_y = y if y is not None else self.last_table_offset(sheet_id) + DEFAULT_TABLE_OFFSET
936
+ return TSDArchives.DrawableArchive(
947
937
  parent=TSPMessages.Reference(identifier=sheet_id),
948
938
  geometry=TSDArchives.GeometryArchive(
949
939
  angle=0.0,
@@ -952,7 +942,6 @@ class _NumbersModel(Cacheable):
952
942
  size=TSPMessages.Size(height=231.0, width=494.0),
953
943
  ),
954
944
  )
955
- return drawable
956
945
 
957
946
  def add_table(
958
947
  self,
@@ -1050,8 +1039,8 @@ class _NumbersModel(Cacheable):
1050
1039
  )
1051
1040
 
1052
1041
  data = [
1053
- [EmptyCell(row_num, col_num) for col_num in range(0, num_cols)]
1054
- for row_num in range(0, num_rows)
1042
+ [EmptyCell(row_num, col_num) for col_num in range(num_cols)]
1043
+ for row_num in range(num_rows)
1055
1044
  ]
1056
1045
 
1057
1046
  row_headers_id, _ = self.objects.create_object_from_dict(
@@ -1100,7 +1089,8 @@ class _NumbersModel(Cacheable):
1100
1089
  number_of_header_columns: int,
1101
1090
  ):
1102
1091
  """Create a FormulaOwnerDependenciesArchive that references a TableInfoArchive
1103
- so that cross-references to cells in this table will work."""
1092
+ so that cross-references to cells in this table will work.
1093
+ """
1104
1094
  formula_owner_uuid = NumbersUUID()
1105
1095
  calc_engine = self.calc_engine()
1106
1096
  owner_id_map = calc_engine.dependency_tracker.owner_id_map.map_entry
@@ -1168,7 +1158,7 @@ class _NumbersModel(Cacheable):
1168
1158
  )
1169
1159
 
1170
1160
  def add_sheet(self, sheet_name: str) -> int:
1171
- """Add a new sheet with a copy of a table from another sheet"""
1161
+ """Add a new sheet with a copy of a table from another sheet."""
1172
1162
  sheet_id, _ = self.objects.create_object_from_dict(
1173
1163
  "Document", {"name": sheet_name}, TNArchives.SheetArchive
1174
1164
  )
@@ -1319,7 +1309,8 @@ class _NumbersModel(Cacheable):
1319
1309
 
1320
1310
  def update_paragraph_styles(self):
1321
1311
  """Create new paragraph style archives for any new styles that
1322
- have been created for this document"""
1312
+ have been created for this document.
1313
+ """
1323
1314
  new_styles = [x for x in self.styles.values() if x._text_style_obj_id is None]
1324
1315
  updated_styles = [
1325
1316
  x
@@ -1335,10 +1326,11 @@ class _NumbersModel(Cacheable):
1335
1326
 
1336
1327
  def update_cell_styles(self, table_id: int, data: List):
1337
1328
  """Create new cell style archives for any cells whose styles
1338
- have changes that require a cell style"""
1329
+ have changes that require a cell style.
1330
+ """
1339
1331
  cell_styles = {}
1340
- for row_num, row in enumerate(data):
1341
- for col_num, cell in enumerate(row):
1332
+ for _, row in enumerate(data):
1333
+ for _, cell in enumerate(row):
1342
1334
  if cell._style is not None and cell._style._update_cell_style:
1343
1335
  fingerprint = (
1344
1336
  str(cell.style.alignment.vertical)
@@ -1424,7 +1416,8 @@ class _NumbersModel(Cacheable):
1424
1416
 
1425
1417
  def custom_style_name(self) -> Tuple[str, str]:
1426
1418
  """Find custom styles in the current document and return the next
1427
- highest numbered style"""
1419
+ highest numbered style.
1420
+ """
1428
1421
  stylesheet_id = self.objects[DOCUMENT_ID].stylesheet.identifier
1429
1422
  current_styles = self.styles.keys()
1430
1423
  custom_styles = [x for x in current_styles if re.fullmatch(r"Custom Style \d+", x)]
@@ -1441,10 +1434,10 @@ class _NumbersModel(Cacheable):
1441
1434
  else:
1442
1435
  return "Custom Style 1"
1443
1436
 
1444
- def pack_cell_storage( # noqa: C901
1437
+ def pack_cell_storage(
1445
1438
  self, table_id: int, data: List, row_num: int, col_num: int
1446
1439
  ) -> bytearray:
1447
- """Create a storage buffer for a cell using v5 (modern) layout"""
1440
+ """Create a storage buffer for a cell using v5 (modern) layout."""
1448
1441
  cell = data[row_num][col_num]
1449
1442
  if cell._style is not None:
1450
1443
  if cell._style._text_style_obj_id is not None:
@@ -1514,6 +1507,7 @@ class _NumbersModel(Cacheable):
1514
1507
  warn(
1515
1508
  f"@{table_name}:[{row_num},{col_num}]: unsupported data type {data_type} for save",
1516
1509
  UnsupportedWarning,
1510
+ stacklevel=1,
1517
1511
  )
1518
1512
  return None
1519
1513
 
@@ -1580,19 +1574,16 @@ class _NumbersModel(Cacheable):
1580
1574
  return TableFormulas(self, table_id)
1581
1575
 
1582
1576
  @cache(num_args=3)
1583
- def table_cell_decode(self, table_id: int, row_num: int, col_num: int) -> Dict: # noqa: C901
1577
+ def table_cell_decode(self, table_id: int, row_num: int, col_num: int) -> Dict:
1584
1578
  buffer = self.storage_buffer(table_id, row_num, col_num)
1585
1579
  if buffer is None:
1586
1580
  return None
1587
1581
 
1588
- cell = CellStorage(self, table_id, buffer, row_num, col_num)
1589
- return cell
1582
+ return CellStorage(self, table_id, buffer, row_num, col_num)
1590
1583
 
1591
1584
  @cache(num_args=2)
1592
1585
  def table_rich_text(self, table_id: int, string_key: int) -> Dict:
1593
- """
1594
- Extract bullets and hyperlinks from a rich text data cell.
1595
- """
1586
+ """Extract bullets and hyperlinks from a rich text data cell."""
1596
1587
  # The table model base data store contains a richTextTable field
1597
1588
  # which is a reference to a TST.TableDataList. The TableDataList
1598
1589
  # has a list of payloads in a field called entries. This will be
@@ -1675,7 +1666,7 @@ class _NumbersModel(Cacheable):
1675
1666
 
1676
1667
  return {
1677
1668
  "text": cell_text,
1678
- "bulleted": any([c is not None for c in bullet_chars]),
1669
+ "bulleted": any(c is not None for c in bullet_chars),
1679
1670
  "bullets": bullets,
1680
1671
  "bullet_chars": bullet_chars,
1681
1672
  "hyperlinks": hyperlinks,
@@ -1683,14 +1674,15 @@ class _NumbersModel(Cacheable):
1683
1674
 
1684
1675
  def cell_text_style(self, cell_storage: object) -> object:
1685
1676
  """Return the text style object for the cell or, if none
1686
- is defined, the default header, footer or body style"""
1677
+ is defined, the default header, footer or body style.
1678
+ """
1687
1679
  if cell_storage.text_style_id is not None:
1688
1680
  return self.table_style(cell_storage.table_id, cell_storage.text_style_id)
1689
1681
 
1690
1682
  table_model = self.objects[cell_storage.table_id]
1691
- if cell_storage.row_num in range(0, table_model.number_of_header_rows):
1683
+ if cell_storage.row_num in range(table_model.number_of_header_rows):
1692
1684
  return self.objects[table_model.header_row_text_style.identifier]
1693
- elif cell_storage.col_num in range(0, table_model.number_of_header_columns):
1685
+ elif cell_storage.col_num in range(table_model.number_of_header_columns):
1694
1686
  return self.objects[table_model.header_column_text_style.identifier]
1695
1687
  elif table_model.number_of_footer_rows > 0:
1696
1688
  start_row_num = table_model.number_of_rows - table_model.number_of_footer_rows
@@ -1724,7 +1716,8 @@ class _NumbersModel(Cacheable):
1724
1716
 
1725
1717
  def char_property(self, style: object, field: str):
1726
1718
  """Return a char_property field from a style if present
1727
- in the style, or from the parent if not"""
1719
+ in the style, or from the parent if not.
1720
+ """
1728
1721
  if not style.char_properties.HasField(field):
1729
1722
  parent = self.objects[style.super.parent.identifier]
1730
1723
  return getattr(parent.char_properties, field)
@@ -1733,7 +1726,8 @@ class _NumbersModel(Cacheable):
1733
1726
 
1734
1727
  def para_property(self, style: object, field: str) -> float:
1735
1728
  """Return a para_property field from a style if present
1736
- in the style, or from the parent if not"""
1729
+ in the style, or from the parent if not.
1730
+ """
1737
1731
  if not style.para_properties.HasField(field):
1738
1732
  parent = self.objects[style.super.parent.identifier]
1739
1733
  return getattr(parent.para_properties, field)
@@ -1742,7 +1736,8 @@ class _NumbersModel(Cacheable):
1742
1736
 
1743
1737
  def cell_property(self, style: object, field: str) -> float:
1744
1738
  """Return a cell_property field from a style if present
1745
- in the style, or from the parent if not"""
1739
+ in the style, or from the parent if not.
1740
+ """
1746
1741
  if not style.cell_properties.HasField(field):
1747
1742
  parent = self.objects[style.super.parent.identifier]
1748
1743
  return getattr(parent.cell_properties, field)
@@ -1803,8 +1798,7 @@ class _NumbersModel(Cacheable):
1803
1798
  style = self.table_style(cell_storage.table_id, cell_storage.cell_style_id)
1804
1799
  padding = self.cell_property(style, "padding")
1805
1800
  # Padding is always identical (only one UI setting)
1806
- text_inset = padding.left
1807
- return text_inset
1801
+ return padding.left
1808
1802
 
1809
1803
  def cell_text_wrap(self, cell_storage: CellStorage) -> float:
1810
1804
  if cell_storage.cell_style_id is None:
@@ -1814,7 +1808,7 @@ class _NumbersModel(Cacheable):
1814
1808
  return self.cell_property(style, "text_wrap")
1815
1809
 
1816
1810
  def stroke_type(self, stroke_run: object) -> str:
1817
- """Return the stroke type for a stroke run"""
1811
+ """Return the stroke type for a stroke run."""
1818
1812
  stroke_type = stroke_run.stroke.pattern.type
1819
1813
  if stroke_type == StrokePattern.StrokePatternType.TSDSolidPattern:
1820
1814
  return "solid"
@@ -1842,7 +1836,7 @@ class _NumbersModel(Cacheable):
1842
1836
  ):
1843
1837
  return cell
1844
1838
  elif cell.is_merged:
1845
- if side == "top" or side == "left":
1839
+ if side in ["top", "left"]:
1846
1840
  return cell
1847
1841
  else:
1848
1842
  return cell
@@ -1851,7 +1845,7 @@ class _NumbersModel(Cacheable):
1851
1845
  def set_cell_border(
1852
1846
  self, table_id: int, row_num: int, col_num: int, side: str, border_value: Border
1853
1847
  ):
1854
- """Set the 2 borders adjacent to a stroke if within the table range"""
1848
+ """Set the 2 borders adjacent to a stroke if within the table range."""
1855
1849
  if side == "top":
1856
1850
  if (cell := self.cell_for_stroke(table_id, "top", row_num, col_num)) is not None:
1857
1851
  cell._border.top = border_value
@@ -2039,12 +2033,12 @@ class _NumbersModel(Cacheable):
2039
2033
 
2040
2034
 
2041
2035
  def rgb(obj) -> RGB:
2042
- """Convert a TSPArchives.Color into an RGB tuple"""
2036
+ """Convert a TSPArchives.Color into an RGB tuple."""
2043
2037
  return RGB(round(obj.r * 255), round(obj.g * 255), round(obj.b * 255))
2044
2038
 
2045
2039
 
2046
2040
  def range_end(obj):
2047
- """Select end range for a IndexSetArchive.IndexSetEntry"""
2041
+ """Select end range for a IndexSetArchive.IndexSetEntry."""
2048
2042
  if obj.HasField("range_end"):
2049
2043
  return obj.range_end
2050
2044
  else:
@@ -2052,7 +2046,7 @@ def range_end(obj):
2052
2046
 
2053
2047
 
2054
2048
  def formatted_number(number_type, index):
2055
- """Returns the numbered index bullet formatted for different types"""
2049
+ """Returns the numbered index bullet formatted for different types."""
2056
2050
  bullet_char = BULLET_PREFIXES[number_type]
2057
2051
  bullet_char += BULLET_CONVERSION[number_type](index)
2058
2052
  bullet_char += BULLET_SUFFIXES[number_type]
@@ -2061,10 +2055,7 @@ def formatted_number(number_type, index):
2061
2055
 
2062
2056
 
2063
2057
  def node_to_col_ref(node: object, table_name: str, col_num: int) -> str:
2064
- if node.AST_column.absolute:
2065
- col = node.AST_column.column
2066
- else:
2067
- col = col_num + node.AST_column.column
2058
+ col = node.AST_column.column if node.AST_column.absolute else col_num + node.AST_column.column
2068
2059
 
2069
2060
  col_name = xl_col_to_name(col, node.AST_column.absolute)
2070
2061
  if table_name is not None:
@@ -2074,10 +2065,7 @@ def node_to_col_ref(node: object, table_name: str, col_num: int) -> str:
2074
2065
 
2075
2066
 
2076
2067
  def node_to_row_ref(node: object, table_name: str, row_num: int) -> str:
2077
- if node.AST_row.absolute:
2078
- row = node.AST_row.row
2079
- else:
2080
- row = row_num + node.AST_row.row
2068
+ row = node.AST_row.row if node.AST_row.absolute else row_num + node.AST_row.row
2081
2069
 
2082
2070
  row_name = f"${row+1}" if node.AST_row.absolute else f"{row+1}"
2083
2071
  if table_name is not None:
@@ -2087,14 +2075,8 @@ def node_to_row_ref(node: object, table_name: str, row_num: int) -> str:
2087
2075
 
2088
2076
 
2089
2077
  def node_to_row_col_ref(node: object, table_name: str, row_num: int, col_num: int) -> str:
2090
- if node.AST_row.absolute:
2091
- row = node.AST_row.row
2092
- else:
2093
- row = row_num + node.AST_row.row
2094
- if node.AST_column.absolute:
2095
- col = node.AST_column.column
2096
- else:
2097
- col = col_num + node.AST_column.column
2078
+ row = node.AST_row.row if node.AST_row.absolute else row_num + node.AST_row.row
2079
+ col = node.AST_column.column if node.AST_column.absolute else col_num + node.AST_column.column
2098
2080
 
2099
2081
  ref = xl_rowcol_to_cell(
2100
2082
  row,
@@ -2111,8 +2093,7 @@ def node_to_row_col_ref(node: object, table_name: str, row_num: int, col_num: in
2111
2093
  def get_storage_buffers_for_row(
2112
2094
  storage_buffer: bytes, offsets: list, num_cols: int, has_wide_offsets: bool
2113
2095
  ) -> List[bytes]:
2114
- """
2115
- Extract storage buffers for each cell in a table row
2096
+ """Extract storage buffers for each cell in a table row.
2116
2097
 
2117
2098
  Args:
2118
2099
  storage_buffer: cell_storage_buffer or cell_storage_buffer for a table row
@@ -2155,7 +2136,8 @@ def get_storage_buffers_for_row(
2155
2136
 
2156
2137
  def clear_field_container(obj):
2157
2138
  """Remove all entries from a protobuf RepeatedCompositeFieldContainer
2158
- in a portable fashion"""
2139
+ in a portable fashion.
2140
+ """
2159
2141
  while len(obj) > 0:
2160
2142
  _ = obj.pop()
2161
2143
 
@@ -2178,10 +2160,9 @@ def pack_decimal128(value: float) -> bytearray:
2178
2160
 
2179
2161
 
2180
2162
  def field_references(obj: object) -> dict:
2181
- """Return a dict of all fields in an object that are references to other objects"""
2182
- refs = {
2163
+ """Return a dict of all fields in an object that are references to other objects."""
2164
+ return {
2183
2165
  x[0].name: {"identifier": getattr(obj, x[0].name).identifier}
2184
2166
  for x in obj.ListFields()
2185
2167
  if type(getattr(obj, x[0].name)) == TSPMessages.Reference
2186
2168
  }
2187
- return refs
@@ -1,5 +1,5 @@
1
- from functools import wraps
2
1
  from collections import defaultdict
2
+ from functools import wraps
3
3
 
4
4
 
5
5
  class Cacheable:
@@ -11,7 +11,8 @@ class Cacheable:
11
11
 
12
12
  def cache(num_args=1):
13
13
  """Decorator to memoize a class method using a precise subset of
14
- its arguments"""
14
+ its arguments.
15
+ """
15
16
 
16
17
  def cache_decorator(func):
17
18
  @wraps(func)