pyfastexcel 0.0.8__tar.gz → 0.0.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (21) hide show
  1. {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/PKG-INFO +4 -4
  2. {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/README.md +3 -3
  3. pyfastexcel-0.0.9/pyfastexcel/_typing.py +25 -0
  4. {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel/driver.py +13 -7
  5. {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel/pyfastexcel.dll +0 -0
  6. {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel/pyfastexcel.so +0 -0
  7. {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel/utils.py +64 -2
  8. {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel/workbook.py +76 -2
  9. {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel/worksheet.py +242 -22
  10. {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel.egg-info/PKG-INFO +4 -4
  11. {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel.egg-info/SOURCES.txt +1 -0
  12. {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/setup.cfg +1 -1
  13. {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/LICENSE +0 -0
  14. {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel/__init__.py +0 -0
  15. {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel/style.py +0 -0
  16. {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel/writer.py +0 -0
  17. {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel.egg-info/dependency_links.txt +0 -0
  18. {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel.egg-info/not-zip-safe +0 -0
  19. {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel.egg-info/requires.txt +0 -0
  20. {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel.egg-info/top_level.txt +0 -0
  21. {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyfastexcel
3
- Version: 0.0.8
3
+ Version: 0.0.9
4
4
  Summary: High performace excel file generation library.
5
5
  Home-page: https://github.com/Zncl2222/pyfastexcel
6
6
  Author: Zncl2222
@@ -100,7 +100,7 @@ If you prefer to build the package manually, follow these steps:
100
100
 
101
101
  ## Usage
102
102
 
103
- The index assignment is now avaliable in `Workbook` and the `FastWriter`.
103
+ The index assignment is now avaliable in `Workbook` and the `StreamWriter`.
104
104
  Here is the example usage:
105
105
 
106
106
  ```python
@@ -146,8 +146,8 @@ if __name__ == '__main__':
146
146
 
147
147
  ```
148
148
 
149
- You can also using the `FastWriter` or `NormalWriter` which was the
150
- subclass of `Workbook` to write excel row by row, see the more in documentations [quickstart](https://pyfastexcel.readthedocs.io/en/stable/quickstart/).
149
+ You can also using the `StreamWriter` which was the
150
+ subclass of `Workbook` to write excel row by row, see more in [StreamWriter](https://pyfastexcel.readthedocs.io/en/stable/writer/).
151
151
 
152
152
  ## Documentation
153
153
 
@@ -76,7 +76,7 @@ If you prefer to build the package manually, follow these steps:
76
76
 
77
77
  ## Usage
78
78
 
79
- The index assignment is now avaliable in `Workbook` and the `FastWriter`.
79
+ The index assignment is now avaliable in `Workbook` and the `StreamWriter`.
80
80
  Here is the example usage:
81
81
 
82
82
  ```python
@@ -122,8 +122,8 @@ if __name__ == '__main__':
122
122
 
123
123
  ```
124
124
 
125
- You can also using the `FastWriter` or `NormalWriter` which was the
126
- subclass of `Workbook` to write excel row by row, see the more in documentations [quickstart](https://pyfastexcel.readthedocs.io/en/stable/quickstart/).
125
+ You can also using the `StreamWriter` which was the
126
+ subclass of `Workbook` to write excel row by row, see more in [StreamWriter](https://pyfastexcel.readthedocs.io/en/stable/writer/).
127
127
 
128
128
  ## Documentation
129
129
 
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal, TypedDict, Optional, List, Union
4
+
5
+
6
+ class CommentTextDict(TypedDict, total=False):
7
+ text: str
8
+ size: Optional[int]
9
+ name: Optional[str]
10
+ bold: Optional[bool]
11
+ italic: Optional[bool]
12
+ underline: Optional[Literal['single', 'double']]
13
+ strike: Optional[bool]
14
+ vert_align: Optional[str]
15
+ color: Optional[str]
16
+
17
+
18
+ class SelectionDict(TypedDict, total=False):
19
+ sq_ref: str
20
+ active_cell: str
21
+ pane: str
22
+
23
+
24
+ CommentTextStructure = Union[str, List[str], CommentTextDict, List[CommentTextDict]]
25
+ SetPanesSelection = List[SelectionDict]
@@ -20,10 +20,9 @@ class ExcelDriver:
20
20
  A driver class to write data to Excel files using custom styles.
21
21
 
22
22
  ### Attributes:
23
- BORDER_TO_INDEX (dict[str, int]): Mapping of border styles to excelize's
24
- corresponding index.
25
23
  _FILE_PROPS (dict[str, str]): Default file properties for the Excel
26
24
  file.
25
+ _PROTECT_ALGORITHM (tuple[str]): Algorithm for the workbook protection
27
26
 
28
27
  ### Methods:
29
28
  __init__(): Initializes the ExcelDriver.
@@ -58,15 +57,22 @@ class ExcelDriver:
58
57
  'SHA-512',
59
58
  )
60
59
 
61
- def __init__(self):
60
+ def __init__(self, pre_allocate: dict[str, int] = None, plain_data: list[list[str]] = None):
62
61
  """
63
- Initializes the PyExcelizeDriver.
62
+ Initializes the Workbook with default settings and initializes Sheet1.
64
63
 
65
- It initializes the Excel data, file properties, default sheet,
66
- current sheet, and style mappings.
64
+ It initializes the workbook structure with Sheet1 as the default sheet,
65
+ sets default file properties, initializes current sheet and sheet list,
66
+ and initializes dictionaries for workbook and protection settings.
67
+
68
+ Args:
69
+ pre_allocate (dict[str, int], optional): A dictionary containing 'n_rows' and 'n_cols'
70
+ keys specifying the dimensions for pre-allocating data in Sheet1.
71
+ plain_data (list[list[str]], optional): A 2D list of strings representing initial data
72
+ to populate Sheet1.
67
73
  """
68
74
  self.workbook = {
69
- 'Sheet1': WorkSheet(),
75
+ 'Sheet1': WorkSheet(pre_allocate=pre_allocate, plain_data=plain_data),
70
76
  }
71
77
  self.file_props = self._get_default_file_props()
72
78
  self.sheet = 'Sheet1'
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import re
4
4
  import string
5
5
  import warnings
6
+ from dataclasses import dataclass
6
7
  from typing import Any, Literal
7
8
 
8
9
  from openpyxl_style_writer import CustomStyle
@@ -10,6 +11,44 @@ from openpyxl_style_writer import CustomStyle
10
11
  warnings.simplefilter('always', DeprecationWarning)
11
12
 
12
13
 
14
+ @dataclass
15
+ class CommentText:
16
+ text: str
17
+ size: int | None = None
18
+ name: str | None = None
19
+ bold: bool | None = None
20
+ italic: bool | None = None
21
+ underline: Literal['single', 'double'] | None = None
22
+ strike: bool | None = None
23
+ vert_align: str | None = None
24
+ color: str | None = None
25
+
26
+ def to_dict(self):
27
+ result = {
28
+ k[0].upper() + k[1:]: v
29
+ for k, v in self.__dict__.items()
30
+ if v is not None and k != 'text'
31
+ }
32
+ if result.get('Vert_align') is not None:
33
+ result['VertAlign'] = result['Vert_align']
34
+ result['text'] = self.text
35
+ return result
36
+
37
+
38
+ @dataclass
39
+ class Selection:
40
+ sq_ref: str
41
+ active_cell: str
42
+ pane: str
43
+
44
+ def to_dict(self):
45
+ return {
46
+ 'sq_ref': self.sq_ref,
47
+ 'active_cell': self.active_cell,
48
+ 'pane': self.pane,
49
+ }
50
+
51
+
13
52
  def deprecated_warning(msg: str):
14
53
  warnings.warn(
15
54
  msg,
@@ -48,6 +87,17 @@ def validate_and_format_value(
48
87
  return (value, 'DEFAULT_STYLE') if set_default_style else value
49
88
 
50
89
 
90
+ def transfer_string_slice_to_slice(string_slice: str) -> slice:
91
+ '''
92
+ Transfer a string slice to a slice object.
93
+ For example, 'A1:B2' will be transfered to slice(1, 2, 1).
94
+ '''
95
+ start, end = string_slice.split(':')
96
+ alpha_start, row_start = _separate_alpha_numeric(start)
97
+ alpha_end, row_end = _separate_alpha_numeric(end)
98
+ return slice(f'{alpha_start}{row_start}', f'{alpha_end}{row_end}')
99
+
100
+
51
101
  def _separate_alpha_numeric(input_string: str) -> tuple[str, str]:
52
102
  '''
53
103
  Separate the alpha and numeric part of a string.
@@ -61,6 +111,9 @@ def _separate_alpha_numeric(input_string: str) -> tuple[str, str]:
61
111
 
62
112
 
63
113
  def _is_valid_column(column: str) -> bool:
114
+ """
115
+ Validate the alphabet part of the column.
116
+ """
64
117
  column = column.upper()
65
118
  index = 0
66
119
  for c in column:
@@ -69,6 +122,9 @@ def _is_valid_column(column: str) -> bool:
69
122
 
70
123
 
71
124
  def column_to_index(column: str) -> int:
125
+ """
126
+ Translate the column name to the column index.
127
+ """
72
128
  if not isinstance(column, str):
73
129
  raise TypeError(f'Invalid type ({type(column)}). Column should be a string.')
74
130
  if len(column) > 3:
@@ -85,6 +141,9 @@ def column_to_index(column: str) -> int:
85
141
 
86
142
 
87
143
  def index_to_column(index: int) -> str:
144
+ """
145
+ Translate the index to the column name.
146
+ """
88
147
  if not isinstance(index, int):
89
148
  raise TypeError(f'Invalid type ({type(index)}). Index should be a string.')
90
149
  if index < 1 or index > 16384:
@@ -96,14 +155,17 @@ def index_to_column(index: int) -> str:
96
155
  return name
97
156
 
98
157
 
99
- def excel_index_to_list_index(index: str) -> tuple[int, int]:
158
+ def cell_reference_to_index(index: str) -> tuple[int, int]:
159
+ """
160
+ Return the row and column index of the given Excel cell reference.
161
+ """
100
162
  alpha, num = _separate_alpha_numeric(index)
101
163
  column = column_to_index(alpha)
102
164
  row = int(num)
103
165
  return row - 1, column - 1
104
166
 
105
167
 
106
- def _validate_excel_index(index: str) -> bool:
168
+ def _validate_cell_reference(index: str) -> bool:
107
169
  alpha, num = _separate_alpha_numeric(index)
108
170
  num = int(num)
109
171
  if not all(c in string.ascii_uppercase for c in alpha):
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing import Literal
4
+
3
5
  from pyfastexcel.driver import ExcelDriver, WorkSheet
4
6
  from pyfastexcel.utils import deprecated_warning
5
7
 
@@ -43,16 +45,26 @@ class Workbook(ExcelDriver):
43
45
  self._sheet_list = tuple(self.workbook.keys())
44
46
  self.sheet = self._sheet_list[0]
45
47
 
46
- def create_sheet(self, sheet_name: str) -> None:
48
+ def create_sheet(
49
+ self,
50
+ sheet_name: str,
51
+ pre_allocate: dict[str, int] = None,
52
+ plain_data: list[list] = None,
53
+ ) -> None:
47
54
  """
48
55
  Creates a new sheet, and set it as current self.sheet.
49
56
 
50
57
  Args:
51
58
  sheet_name (str): The name of the new sheet.
59
+ pre_allocate (dict[str, int], optional): A dictionary containing
60
+ 'n_rows' and 'n_cols' keys specifying the dimensions
61
+ for pre-allocating data in new sheet.
62
+ plain_data (list[list[str]], optional): A 2D list of strings
63
+ representing initial data to populate new sheet.
52
64
  """
53
65
  if self.workbook.get(sheet_name) is not None:
54
66
  raise ValueError(f'Sheet {sheet_name} already exists.')
55
- self.workbook[sheet_name] = WorkSheet()
67
+ self.workbook[sheet_name] = WorkSheet(pre_allocate=pre_allocate, plain_data=plain_data)
56
68
  self.sheet = sheet_name
57
69
  self._sheet_list = tuple([x for x in self._sheet_list] + [sheet_name])
58
70
 
@@ -122,3 +134,65 @@ class Workbook(ExcelDriver):
122
134
  def auto_filter(self, sheet: str, target_range: str) -> None:
123
135
  self._check_if_sheet_exists(sheet)
124
136
  self.workbook[sheet].auto_filter(target_range)
137
+
138
+ def set_panes(
139
+ self,
140
+ sheet: str,
141
+ freeze: bool = False,
142
+ split: bool = False,
143
+ x_split: int = 0,
144
+ y_split: int = 0,
145
+ top_left_cell: str = '',
146
+ active_pane: Literal['bottomLeft', 'bottomRight', 'topLeft', 'topRight', ''] = '',
147
+ selection: list[dict[str, str]] = None,
148
+ ) -> None:
149
+ self._check_if_sheet_exists(sheet)
150
+ self.workbook[sheet].set_panes(
151
+ freeze=freeze,
152
+ split=split,
153
+ x_split=x_split,
154
+ y_split=y_split,
155
+ top_left_cell=top_left_cell,
156
+ active_pane=active_pane,
157
+ selection=selection,
158
+ )
159
+
160
+ def set_data_validation(
161
+ self,
162
+ sheet: str,
163
+ sq_ref: str = '',
164
+ set_range: list[int | float] = None,
165
+ input_msg: list[str] = None,
166
+ drop_list: list[str] | str = None,
167
+ error_msg: list[str] = None,
168
+ ):
169
+ self._check_if_sheet_exists(sheet)
170
+ self.workbook[sheet].set_data_validation(
171
+ sq_ref=sq_ref,
172
+ set_range=set_range,
173
+ input_msg=input_msg,
174
+ drop_list=drop_list,
175
+ error_msg=error_msg,
176
+ )
177
+
178
+ def add_comment(
179
+ self,
180
+ sheet: str,
181
+ cell: str,
182
+ author: str,
183
+ text: str | dict[str, str] | list[str | dict[str, str]],
184
+ ) -> None:
185
+ """
186
+ Adds a comment to the specified cell.
187
+ Args:
188
+ sheet (str): The name of the sheet.
189
+ cell (str): The cell location to add the comment.
190
+ author (str): The author of the comment.
191
+ text (str | dict[str, str] | list[str | dict[str, str]]): The text of the comment.
192
+ Raises:
193
+ ValueError: If the cell location is invalid.
194
+ Returns:
195
+ None
196
+ """
197
+ self._check_if_sheet_exists(sheet)
198
+ self.workbook[sheet].add_comment(cell, author, text)
@@ -1,18 +1,22 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Any
3
+ from typing import Any, Literal, Optional, List
4
4
 
5
5
  from openpyxl_style_writer import CustomStyle
6
6
 
7
7
  from .style import StyleManager
8
+ from ._typing import CommentTextStructure, SetPanesSelection
8
9
  from .utils import (
10
+ CommentText,
11
+ Selection,
9
12
  _separate_alpha_numeric,
10
- _validate_excel_index,
13
+ _validate_cell_reference,
11
14
  column_to_index,
12
15
  deprecated_warning,
13
- excel_index_to_list_index,
16
+ cell_reference_to_index,
14
17
  validate_and_format_value,
15
18
  validate_and_register_style,
19
+ transfer_string_slice_to_slice,
16
20
  )
17
21
 
18
22
 
@@ -59,13 +63,37 @@ class WorkSheet:
59
63
  index. Raises TypeError if index_supported is False.
60
64
  """
61
65
 
62
- def __init__(self):
66
+ def __init__(
67
+ self,
68
+ pre_allocate: Optional[dict[str, int]] = None,
69
+ plain_data: Optional[list[list[str]]] = None,
70
+ ):
63
71
  """
64
- Initializes a WorkSheet instance.
72
+ Initializes a WorkSheet instance with optional pre-allocation of data or initialization
73
+ with plain data.
65
74
 
66
75
  Args:
67
- index_supported (bool, optional): A flag indicating whether
68
- index-based access is supported. Defaults to False.
76
+ pre_allocate (dict[str, int], optional): A dictionary containing 'n_rows' and 'n_cols'
77
+ keys specifying the dimensions for pre-allocating data.
78
+ This can enhancement the performance when you need to write a large excel
79
+ plain_data (list[list[str]], optional): A 2D list of strings representing the
80
+ initial data to populate the worksheet.
81
+
82
+ Notes:
83
+ If both `pre_allocate` and `plain_data` are provided, `plain_data` takes precedence.
84
+
85
+ Attributes:
86
+ sheet (dict): Default sheet settings.
87
+ data (list[list]): The main data structure holding worksheet contents.
88
+ header (list): List of header row items.
89
+ merge_cells (list): List of merged cell coordinates.
90
+ width (dict): Column widths.
91
+ height (dict): Row heights.
92
+ auto_filter_set (set): Set of auto-filter settings.
93
+
94
+ Raises:
95
+ TypeError: If `plain_data` is provided but is not a valid 2D list of strings.
96
+
69
97
  """
70
98
  self.sheet = self._get_default_sheet()
71
99
  self.data = [[('', 'DEFAULT_STYLE')]]
@@ -73,7 +101,37 @@ class WorkSheet:
73
101
  self.merge_cells = []
74
102
  self.width = {}
75
103
  self.height = {}
104
+ self.panes = {}
105
+ self.comment = []
76
106
  self.auto_filter_set = set()
107
+ self.data_validation_set = []
108
+
109
+ if plain_data is not None and pre_allocate is not None:
110
+ raise ValueError(
111
+ "You can only specify either 'pre_allocate' or 'plain_data' at a time, not both.",
112
+ )
113
+
114
+ if pre_allocate is not None:
115
+ if (
116
+ not isinstance(pre_allocate, dict)
117
+ or 'n_rows' not in pre_allocate
118
+ or 'n_cols' not in pre_allocate
119
+ ):
120
+ raise TypeError('Invalid pre_allocate dictionary format.')
121
+ if not isinstance(pre_allocate['n_rows'], int) or not isinstance(
122
+ pre_allocate['n_cols'],
123
+ int,
124
+ ):
125
+ raise TypeError('n_rows and n_cols must be integers.')
126
+ self.data = [[None] * pre_allocate['n_cols'] for _ in range(pre_allocate['n_rows'])]
127
+
128
+ if plain_data is not None:
129
+ if not isinstance(plain_data, list) or any(
130
+ not isinstance(row, list) for row in plain_data
131
+ ):
132
+ raise TypeError('plain_data should be a valid 2D list of strings.')
133
+ self.data = plain_data
134
+ self.sheet['NoStyle'] = True
77
135
 
78
136
  def cell(
79
137
  self,
@@ -135,7 +193,11 @@ class WorkSheet:
135
193
  style = StyleManager._STYLE_NAME_MAP[style]
136
194
 
137
195
  if isinstance(target, str):
138
- self._apply_style_to_string_target(target, style)
196
+ if ':' in target:
197
+ target = transfer_string_slice_to_slice(target)
198
+ self._apply_style_to_slice_target(target, style)
199
+ else:
200
+ self._apply_style_to_string_target(target, style)
139
201
  elif isinstance(target, slice):
140
202
  self._apply_style_to_slice_target(target, style)
141
203
  elif isinstance(target, list) and len(target) == 2:
@@ -144,13 +206,8 @@ class WorkSheet:
144
206
  raise TypeError('Target should be a string, slice, or list[row, index].')
145
207
 
146
208
  def _apply_style_to_string_target(self, target: str, style: str):
147
- if ':' not in target:
148
- row, col = excel_index_to_list_index(target)
149
- self.data[row][col] = (self.data[row][col][0], style)
150
- else:
151
- target_slice = target.split(':')
152
- target = slice(target_slice[0], target_slice[1])
153
- self._apply_style_to_slice_target(target, style)
209
+ row, col = cell_reference_to_index(target)
210
+ self.data[row][col] = (self.data[row][col][0], style)
154
211
 
155
212
  def _apply_style_to_slice_target(self, target: slice, style: str):
156
213
  start_row, start_col, col_stop = self._extract_slice_indices(target)
@@ -251,13 +308,161 @@ class WorkSheet:
251
308
  if ':' not in target_range:
252
309
  raise ValueError('Invalid target range. Target range should be in the format "A1:B2".')
253
310
  target_list = target_range.split(':')
254
- _validate_excel_index(target_list[0])
255
- _validate_excel_index(target_list[1])
311
+ _validate_cell_reference(target_list[0])
312
+ _validate_cell_reference(target_list[1])
256
313
  self.auto_filter_set.add(target_range)
257
314
 
315
+ def set_panes(
316
+ self,
317
+ freeze: bool = False,
318
+ split: bool = False,
319
+ x_split: int = 0,
320
+ y_split: int = 0,
321
+ top_left_cell: str = '',
322
+ active_pane: Literal['bottomLeft', 'bottomRight', 'topLeft', 'topRight', ''] = '',
323
+ selection: Optional[SetPanesSelection | list[Selection]] = None,
324
+ ) -> None:
325
+ if x_split < 0 or y_split < 0:
326
+ raise ValueError('Split position should be positive.')
327
+ if top_left_cell != '':
328
+ _validate_cell_reference(top_left_cell)
329
+ if active_pane not in ['bottomLeft', 'bottomRight', 'topLeft', 'topRight', '']:
330
+ raise ValueError(
331
+ 'Invalid active pane. The options are bottomLeft, bottomRight, topLeft, topRight.',
332
+ )
333
+
334
+ if selection is None:
335
+ selection = []
336
+ self.panes = {
337
+ 'freeze': freeze,
338
+ 'split': split,
339
+ 'x_split': x_split,
340
+ 'y_split': y_split,
341
+ 'top_left_cell': top_left_cell,
342
+ 'active_pane': active_pane,
343
+ 'selection': selection,
344
+ }
345
+
346
+ def set_data_validation(
347
+ self,
348
+ sq_ref: str = '',
349
+ set_range: Optional[list[int | float]] = None,
350
+ input_msg: Optional[list[str]] = None,
351
+ drop_list: Optional[list[str] | str] = None,
352
+ error_msg: Optional[list[str]] = None,
353
+ ):
354
+ """
355
+ Set data validation for the specified range.
356
+
357
+ Args:
358
+ sq_ref (str): The range to set the data validation.
359
+ set_range (list[int | float]): The range of values to set the data validation.
360
+ input (list[str]): The input message for the data validation.
361
+ drop_list (list[str] | str): The drop list for the data validation.
362
+ error (list[str]): The error message for the data validation.
363
+
364
+ Raises:
365
+ ValueError: If the range is invalid.
366
+
367
+ Returns:
368
+ None
369
+ """
370
+ if ':' in sq_ref:
371
+ sq_ref_list = sq_ref.split(':')
372
+ _validate_cell_reference(sq_ref_list[0])
373
+ _validate_cell_reference(sq_ref_list[1])
374
+ else:
375
+ _validate_cell_reference(sq_ref)
376
+
377
+ drop_list_key = 'drop_list'
378
+ if isinstance(drop_list, str):
379
+ if ':' not in drop_list:
380
+ raise ValueError(
381
+ 'Invalid drop list. Sequential Reference'
382
+ 'Drop list should be in the format "A1:B2".',
383
+ )
384
+ drop_list_split = drop_list.split(':')
385
+ _validate_cell_reference(drop_list_split[0])
386
+ _validate_cell_reference(drop_list_split[1])
387
+ drop_list_key = 'sqref_drop_list'
388
+ elif drop_list is not None:
389
+ if not isinstance(drop_list, list):
390
+ raise ValueError('Drop list should be a list of strings.')
391
+ drop_list = [str(x) for x in drop_list]
392
+
393
+ dv = {}
394
+ dv['sq_ref'] = sq_ref
395
+ if set_range is not None:
396
+ if not isinstance(set_range, list) or len(set_range) != 2:
397
+ raise ValueError('Set range should be a list of two elements. Like [1, 10].')
398
+ dv['set_range'] = set_range
399
+ if input_msg is not None:
400
+ if not isinstance(input_msg, list) or len(input_msg) != 2:
401
+ raise ValueError(
402
+ 'Input message should be a list of two elements. Like ["Title", "Body"].',
403
+ )
404
+ dv['input_title'] = input_msg[0]
405
+ dv['input_body'] = input_msg[1]
406
+ if drop_list is not None:
407
+ dv[drop_list_key] = drop_list
408
+ if error_msg is not None:
409
+ if not isinstance(error_msg, list) or len(error_msg) != 2:
410
+ raise ValueError(
411
+ 'Error message should be a list of two elements. Like ["Title", "Body"].',
412
+ )
413
+ dv['error_title'] = error_msg[0]
414
+ dv['error_body'] = error_msg[1]
415
+
416
+ self.data_validation_set.append(dv)
417
+
418
+ def add_comment(
419
+ self,
420
+ cell: str,
421
+ author: str,
422
+ text: CommentTextStructure | CommentText | List[CommentText],
423
+ ) -> None:
424
+ """
425
+ Adds a comment to the specified cell.
426
+ Args:
427
+ cell (str): The cell location to add the comment.
428
+ author (str): The author of the comment.
429
+ text (str | dict[str, str] | list[str | dict[str, str]]): The text of the comment.
430
+ Raises:
431
+ ValueError: If the cell location is invalid.
432
+ Returns:
433
+ None
434
+ """
435
+ _validate_cell_reference(cell)
436
+ text = (
437
+ [text]
438
+ if isinstance(text, (str, CommentText))
439
+ else text
440
+ if isinstance(text, list)
441
+ else [text]
442
+ )
443
+ if all(isinstance(item, (dict, str)) for item in text):
444
+ for idx, item in enumerate(text):
445
+ if isinstance(item, str):
446
+ text[idx] = {'text': item}
447
+ else:
448
+ if 'text' not in item:
449
+ raise ValueError('Comment text should contain the key "text".')
450
+ text[idx] = {
451
+ k[0].upper() + k[1:] if k != 'text' else k: v for k, v in item.items()
452
+ }
453
+ elif all(isinstance(item, CommentText) for item in text):
454
+ text = [t.to_dict() for t in text]
455
+ else:
456
+ raise ValueError('Comment text should be a string or a list of dictionaries.')
457
+ self.comment.append({'cell': cell, 'author': author, 'paragraph': text})
458
+
258
459
  def _expand_row_and_cols(self, target_row: int, target_col: int):
259
460
  data_row_len = len(self.data)
260
461
  data_col_len = len(self.data[0])
462
+
463
+ if data_row_len > target_row and len(self.data[target_row]) > target_col:
464
+ return
465
+
261
466
  # Case when the memory space of self.data row is enough
262
467
  # but the memory space of the target_col is not enough
263
468
  if data_row_len > target_row:
@@ -284,6 +489,10 @@ class WorkSheet:
284
489
  'Width': self.width,
285
490
  'Height': self.height,
286
491
  'AutoFilter': self.auto_filter_set,
492
+ 'Panes': self.panes,
493
+ 'DataValidation': self.data_validation_set,
494
+ 'NoStyle': self.sheet['NoStyle'],
495
+ 'Comment': self.comment,
287
496
  }
288
497
  return self.sheet
289
498
 
@@ -295,6 +504,10 @@ class WorkSheet:
295
504
  'Width': {},
296
505
  'Height': {},
297
506
  'AutoFilter': set(),
507
+ 'Panes': {},
508
+ 'DataValidation': [],
509
+ 'NoStyle': False,
510
+ 'Comment': [],
298
511
  }
299
512
 
300
513
  def _validate_value_and_set_default(self, value: Any):
@@ -349,6 +562,9 @@ class WorkSheet:
349
562
  elif isinstance(key, int):
350
563
  return self.data[key]
351
564
  elif isinstance(key, str):
565
+ if ':' in key:
566
+ target = transfer_string_slice_to_slice(key)
567
+ return self._get_cell_by_slice(target)
352
568
  return self._get_cell_by_location(key)
353
569
 
354
570
  def __setitem__(self, key: str | slice | int, value: Any) -> None:
@@ -357,7 +573,11 @@ class WorkSheet:
357
573
  elif isinstance(key, int):
358
574
  self._set_row_by_index(key, value)
359
575
  elif isinstance(key, str):
360
- self._set_cell_by_location(key, value)
576
+ if ':' in key:
577
+ target = transfer_string_slice_to_slice(key)
578
+ self._set_cell_by_slice(target, value)
579
+ else:
580
+ self._set_cell_by_location(key, value)
361
581
  else:
362
582
  raise TypeError('Key should be a string or slice.')
363
583
 
@@ -369,7 +589,7 @@ class WorkSheet:
369
589
  return self.data[int(start_row) - 1]
370
590
 
371
591
  def _get_cell_by_location(self, key: str) -> tuple:
372
- row, col = excel_index_to_list_index(key)
592
+ row, col = cell_reference_to_index(key)
373
593
  return self.data[row][col]
374
594
 
375
595
  def _extract_slice_indices(self, cell_slice: slice) -> tuple[int, int, int]:
@@ -377,8 +597,8 @@ class WorkSheet:
377
597
  _, stop_row = _separate_alpha_numeric(cell_slice.stop)
378
598
  if start_row != stop_row:
379
599
  raise ValueError('Only support row-wise slicing.')
380
- start_row, start_col = excel_index_to_list_index(cell_slice.start)
381
- _, col_stop = excel_index_to_list_index(cell_slice.stop)
600
+ start_row, start_col = cell_reference_to_index(cell_slice.start)
601
+ _, col_stop = cell_reference_to_index(cell_slice.stop)
382
602
  self._expand_row_and_cols(start_row, col_stop)
383
603
  return start_row, start_col, col_stop
384
604
 
@@ -398,7 +618,7 @@ class WorkSheet:
398
618
  self.data[row] = value
399
619
 
400
620
  def _set_cell_by_location(self, key: str, value: Any) -> None:
401
- row, col = excel_index_to_list_index(key)
621
+ row, col = cell_reference_to_index(key)
402
622
  value = self._validate_value_and_set_default(value)
403
623
  try:
404
624
  self.data[row][col] = value
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyfastexcel
3
- Version: 0.0.8
3
+ Version: 0.0.9
4
4
  Summary: High performace excel file generation library.
5
5
  Home-page: https://github.com/Zncl2222/pyfastexcel
6
6
  Author: Zncl2222
@@ -100,7 +100,7 @@ If you prefer to build the package manually, follow these steps:
100
100
 
101
101
  ## Usage
102
102
 
103
- The index assignment is now avaliable in `Workbook` and the `FastWriter`.
103
+ The index assignment is now avaliable in `Workbook` and the `StreamWriter`.
104
104
  Here is the example usage:
105
105
 
106
106
  ```python
@@ -146,8 +146,8 @@ if __name__ == '__main__':
146
146
 
147
147
  ```
148
148
 
149
- You can also using the `FastWriter` or `NormalWriter` which was the
150
- subclass of `Workbook` to write excel row by row, see the more in documentations [quickstart](https://pyfastexcel.readthedocs.io/en/stable/quickstart/).
149
+ You can also using the `StreamWriter` which was the
150
+ subclass of `Workbook` to write excel row by row, see more in [StreamWriter](https://pyfastexcel.readthedocs.io/en/stable/writer/).
151
151
 
152
152
  ## Documentation
153
153
 
@@ -3,6 +3,7 @@ README.md
3
3
  setup.cfg
4
4
  setup.py
5
5
  pyfastexcel/__init__.py
6
+ pyfastexcel/_typing.py
6
7
  pyfastexcel/driver.py
7
8
  pyfastexcel/pyfastexcel.dll
8
9
  pyfastexcel/pyfastexcel.so
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = pyfastexcel
3
- version = 0.0.8
3
+ version = 0.0.9
4
4
  description = High performace excel file generation library.
5
5
  long_description = file: README.md
6
6
  long_description_content_type = text/markdown
File without changes
File without changes