mergeron 2024.739079.13__tar.gz → 2024.739088.0__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.

Potentially problematic release.


This version of mergeron might be problematic. Click here for more details.

Files changed (37) hide show
  1. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/PKG-INFO +1 -1
  2. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/pyproject.toml +2 -1
  3. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/__init__.py +1 -1
  4. mergeron-2024.739088.0/src/mergeron/ext/xlsxw_helper.py +513 -0
  5. mergeron-2024.739079.13/src/mergeron/core/excel_helper.py +0 -373
  6. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/README.rst +0 -0
  7. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/License.txt +0 -0
  8. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/core/__init__.py +0 -0
  9. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/core/damodaran_margin_data.py +0 -0
  10. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/core/ftc_merger_investigations_data.py +0 -0
  11. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/core/guidelines_boundaries.py +0 -0
  12. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/core/guidelines_boundary_functions.py +0 -0
  13. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/core/guidelines_boundary_functions_extra.py +0 -0
  14. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/core/proportions_tests.py +0 -0
  15. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/core/pseudorandom_numbers.py +0 -0
  16. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/data/__init__.py +0 -0
  17. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/data/damodaran_margin_data.xls +0 -0
  18. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/data/damodaran_margin_data_dict.msgpack +0 -0
  19. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/data/ftc_invdata.msgpack +0 -0
  20. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/data/jinja2_LaTeX_templates/clrrate_cis_summary_table_template.tex.jinja2 +0 -0
  21. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/data/jinja2_LaTeX_templates/ftcinvdata_byhhianddelta_table_template.tex.jinja2 +0 -0
  22. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/data/jinja2_LaTeX_templates/ftcinvdata_summary_table_template.tex.jinja2 +0 -0
  23. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/data/jinja2_LaTeX_templates/ftcinvdata_summarypaired_table_template.tex.jinja2 +0 -0
  24. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/data/jinja2_LaTeX_templates/mergeron.cls +0 -0
  25. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/data/jinja2_LaTeX_templates/mergeron_table_collection_template.tex.jinja2 +0 -0
  26. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/data/jinja2_LaTeX_templates/setup_tikz_tables.tex +0 -0
  27. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/demo/__init__.py +0 -0
  28. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/demo/visualize_empirical_margin_distribution.py +0 -0
  29. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/ext/__init__.py +0 -0
  30. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/ext/tol_colors.py +0 -0
  31. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/gen/__init__.py +0 -0
  32. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/gen/_data_generation_functions.py +0 -0
  33. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/gen/data_generation.py +0 -0
  34. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/gen/enforcement_stats.py +0 -0
  35. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/gen/market_sample.py +0 -0
  36. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/gen/upp_tests.py +0 -0
  37. {mergeron-2024.739079.13 → mergeron-2024.739088.0}/src/mergeron/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mergeron
3
- Version: 2024.739079.13
3
+ Version: 2024.739088.0
4
4
  Summary: Merger Policy Analysis using Python
5
5
  License: MIT
6
6
  Keywords: merger policy analysis,merger guidelines,merger screening,policy presumptions,concentration standards,upward pricing pressure,GUPPI
@@ -13,7 +13,7 @@ keywords = [
13
13
  "upward pricing pressure",
14
14
  "GUPPI",
15
15
  ]
16
- version = "2024.739079.13"
16
+ version = "2024.739088.0"
17
17
 
18
18
  # Classifiers list: https://pypi.org/classifiers/
19
19
  classifiers = [
@@ -184,6 +184,7 @@ allow_redefinition = true
184
184
  plugins = "numpy.typing.mypy_plugin"
185
185
 
186
186
  [tool.pytest.ini_options]
187
+ log_auto_indent = 4
187
188
  minversion = "8.0"
188
189
  testpaths = ["tests"]
189
190
  addopts = ["--import-mode=importlib"]
@@ -11,7 +11,7 @@ from numpy.typing import NDArray
11
11
 
12
12
  _PKG_NAME: str = Path(__file__).parent.stem
13
13
 
14
- VERSION = "2024.739079.13"
14
+ VERSION = "2024.739088.0"
15
15
 
16
16
  __version__ = VERSION
17
17
 
@@ -0,0 +1,513 @@
1
+ """
2
+ Methods for writing data from Python to fresh Excel workbooks using
3
+ the third-party package, `xlsxwriter`.
4
+
5
+ Includes a flexible system of defining cell formats.
6
+
7
+ NOTES
8
+ -----
9
+
10
+ This module is desinged for producing formatted summary output. For
11
+ writing bulk data to Excel, facilities provided in third-party packages
12
+ such as `polars` likely provide better performance.
13
+
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from collections.abc import Mapping, Sequence
19
+ from typing import Any, ClassVar
20
+
21
+ import numpy as np
22
+ import numpy.typing as npt
23
+ import xlsxwriter # type: ignore
24
+ from aenum import Enum, extend_enum, unique # type: ignore
25
+
26
+ from .. import VERSION # noqa: TID252
27
+
28
+ __version__ = VERSION
29
+
30
+ Workbook = xlsxwriter.Workbook
31
+
32
+
33
+ @unique
34
+ class CFmt(dict, Enum): # type: ignore
35
+ """
36
+ Initialize cell formats for xlsxwriter.
37
+
38
+ The mappings included here, and unions, etc. of them
39
+ and any others added at runtime, are rendered
40
+ as xlsxWriter.Workbook.Format objects for writing
41
+ cell values to formatted cells in a spreadsheet.
42
+
43
+ NOTES
44
+ -----
45
+
46
+ For more information about xlsxwriter's cell formats,
47
+ see, https://xlsxwriter.readthedocs.io/format.html
48
+
49
+ """
50
+
51
+ XL_DEFAULT: ClassVar = {"font_name": "Calibri", "font_size": 11}
52
+ XL_DEFAULT_2003: ClassVar = {"font_name": "Arial", "font_size": 10}
53
+
54
+ A_CTR: ClassVar = {"align": "center"}
55
+ A_CTR_ACROSS: ClassVar = {"align": "center_across"}
56
+ A_LEFT: ClassVar = {"align": "left"}
57
+ A_RIGHT: ClassVar = {"align": "right"}
58
+ V_TOP: ClassVar = {"align": "top"}
59
+ V_BOTTOM: ClassVar = {"align": "bottom"}
60
+ V_CTR: ClassVar = {"align": "vcenter"}
61
+
62
+ TEXT_WRAP: ClassVar = {"text_wrap": True}
63
+ TEXT_ROTATE: ClassVar = {"rotation": 90}
64
+ IND_1: ClassVar = {"indent": 1}
65
+
66
+ BOLD: ClassVar = {"bold": True}
67
+ BOLD_ITALIC: ClassVar = {"bold": True, "italic": True}
68
+ ITALIC: ClassVar = {"italic": True}
69
+ ULINE: ClassVar = {"underline": True}
70
+ SOUT: ClassVar = {"font_strikeout": True}
71
+ # Useful with write_rich_text()
72
+ SUPERSCRIPT: ClassVar = {"font_script": 1}
73
+ SUBSCRIPT: ClassVar = {"font_script": 2}
74
+
75
+ AREA_NUM: ClassVar = ({"num_format": "0.00000000"},)
76
+ DOLLAR_NUM: ClassVar = {"num_format": "[$$-409]#,##0.00"}
77
+ DT_NUM: ClassVar = {"num_format": "mm/dd/yyyy"}
78
+ PCT_NUM: ClassVar = {"num_format": "##0%"}
79
+ PCT2_NUM: ClassVar = {"num_format": "##0.00%"}
80
+ PCT4_NUM: ClassVar = {"num_format": "##0.0000%"}
81
+ PCT6_NUM: ClassVar = {"num_format": "##0.000000%"}
82
+ PCT8_NUM: ClassVar = {"num_format": "##0.00000000%"}
83
+ QTY_NUM: ClassVar = {"num_format": "#,##0.0"}
84
+
85
+ BAR_FILL: ClassVar = {"pattern": 1, "bg_color": "dfeadf"}
86
+ HDR_FILL: ClassVar = {"pattern": 1, "bg_color": "e9e9e9"}
87
+
88
+ LEFT_BORDER: ClassVar = {"left": 1, "left_color": "000000"}
89
+ RIGHT_BORDER: ClassVar = {"right": 1, "right_color": "000000"}
90
+ BOT_BORDER: ClassVar = {"bottom": 1, "bottom_color": "000000"}
91
+ TOP_BORDER: ClassVar = {"top": 1, "top_color": "000000"}
92
+ HDR_BORDER: ClassVar = TOP_BORDER | BOT_BORDER
93
+
94
+ @classmethod
95
+ def add_new(self, _fmt_name: str, _xlsx_fmt_dict: dict[str, Any], /) -> CFmt:
96
+ """
97
+ Add new CFmt object to instance.
98
+
99
+ Parameters
100
+ ----------
101
+ _fmt_name
102
+ Name of new member to be added to CFmt
103
+ _xlsx_fmt_dict
104
+ Any valid argument to xlsxwriter.Workbook.add_format(), or union of
105
+ same with one or more CFmt objects with same, e.g.,
106
+ CFmt.HDR_BORDER | CFmt.HDR_FILL or
107
+ CFmt.HDR_BORDER | {"pattern": 1, "bg_color": "f2f2f2"}
108
+
109
+ Returns
110
+ -------
111
+ None
112
+
113
+ """
114
+
115
+ return extend_enum(self, _fmt_name, _xlsx_fmt_dict) # type: ignore
116
+
117
+ @classmethod
118
+ def ensure_cell_format_spec_tuple(
119
+ self, _cell_formats: Sequence[CFmt | Sequence[CFmt]], /
120
+ ) -> bool:
121
+ """
122
+ Test that a given format specification is a tuple of CFmt enums
123
+
124
+ Parameters
125
+ ----------
126
+ _cell_formats
127
+ Format specification
128
+
129
+ Raises
130
+ ------
131
+ ValueError
132
+ If format specification is not tuple of CFmt enums
133
+
134
+ Returns
135
+ -------
136
+ True if format specification passes, else False
137
+
138
+ """
139
+
140
+ for _cell_format in _cell_formats:
141
+ if isinstance(_cell_format, tuple):
142
+ self.ensure_cell_format_spec_tuple(_cell_format)
143
+
144
+ if not (isinstance(_cell_format, CFmt),):
145
+ raise ValueError(
146
+ "Improperly specified format tuple for writing array."
147
+ " Must be tuple of CFmt enums."
148
+ )
149
+
150
+ return True
151
+
152
+ @classmethod
153
+ def xl_fmt(
154
+ self,
155
+ _xl_book: xlsxwriter.Workbook,
156
+ _cell_fmt: Sequence[CFmt | Sequence[CFmt]] | CFmt | None,
157
+ /,
158
+ ) -> xlsxwriter.format.Format:
159
+ """
160
+ Return :code:`xlsxwriter` `Format` object given a CFmt aenum, or tuple thereof.
161
+
162
+ Parameters
163
+ ----------
164
+ _xl_book
165
+ :code:`xlsxwriter.Workbook` object
166
+
167
+ _cell_fmt
168
+ :code:`CFmt` aenum object, or tuple thereof
169
+
170
+ Raises
171
+ ------
172
+ ValueError
173
+ If format specification is not one of None, a CFmt aenum, or
174
+ a xlsxwriter.format.Format object
175
+
176
+ Returns
177
+ -------
178
+ :code:`xlsxwriter` `Format` object
179
+
180
+ """
181
+
182
+ if isinstance(_cell_fmt, xlsxwriter.format.Format):
183
+ return _cell_fmt
184
+ elif _cell_fmt is None:
185
+ return _xl_book.add_format(CFmt.XL_DEFAULT.value)
186
+
187
+ _cell_fmt_dict: Mapping[str, Any] = {}
188
+ if isinstance(_cell_fmt, Sequence):
189
+ self.ensure_cell_format_spec_tuple(_cell_fmt)
190
+ for _cf in _cell_fmt:
191
+ _cell_fmt_dict = (
192
+ (_cell_fmt_dict | _cfi.value for _cfi in _cf)
193
+ if isinstance(_cf, Sequence)
194
+ else _cell_fmt_dict | _cf.value
195
+ )
196
+ elif isinstance(_cell_fmt, CFmt):
197
+ _cell_fmt_dict = _cell_fmt.value
198
+ else:
199
+ raise ValueError("Improperly specified format specification.")
200
+
201
+ return _xl_book.add_format(_cell_fmt_dict)
202
+
203
+
204
+ def write_header(
205
+ _xl_sheet: xlsxwriter.worksheet.Worksheet,
206
+ /,
207
+ *,
208
+ center_header: str | None = None,
209
+ left_header: str | None = None,
210
+ right_header: str | None = None,
211
+ ) -> None:
212
+ """Write header text to given worksheet.
213
+
214
+ Parameters
215
+ ----------
216
+ _xl_sheet
217
+ Worksheet object
218
+ center_header
219
+ Text for center header
220
+ left_header
221
+ Text for left header
222
+ right_header
223
+ Text for right header
224
+
225
+ Raises
226
+ ------
227
+ ValueError
228
+ Must specify at least one header
229
+
230
+ Returns
231
+ -------
232
+ None
233
+ """
234
+ if any((center_header, left_header, right_header)):
235
+ _xl_sheet.set_header(
236
+ "".join([
237
+ f"&L{left_header}" if left_header else "",
238
+ f"&C{center_header}" if center_header else "",
239
+ f"&R{right_header}" if right_header else "",
240
+ ])
241
+ )
242
+
243
+ else:
244
+ raise ValueError("must specify at least one header")
245
+
246
+
247
+ def write_footer(
248
+ _xl_sheet: xlsxwriter.worksheet.Worksheet,
249
+ /,
250
+ *,
251
+ center_footer: str | None = None,
252
+ left_footer: str | None = None,
253
+ right_footer: str | None = None,
254
+ ) -> None:
255
+ """Write footer text to given worksheet.
256
+
257
+ Parameters
258
+ ----------
259
+ _xl_sheet
260
+ Worksheet object
261
+ center_footer
262
+ Text for center footer
263
+ left_footer
264
+ Text for left footer
265
+ right_footer
266
+ Text for right footer
267
+
268
+ Raises
269
+ ------
270
+ ValueError
271
+ Must specify at least one footer
272
+
273
+ Returns
274
+ -------
275
+ None
276
+ """
277
+
278
+ if not any((center_footer, left_footer, right_footer)):
279
+ _xl_sheet.set_footer(
280
+ "".join([
281
+ f"&L{left_footer}" if left_footer else "",
282
+ f"&C{center_footer}" if center_footer else "",
283
+ f"&R{right_footer}" if right_footer else "",
284
+ ])
285
+ )
286
+
287
+ else:
288
+ raise ValueError("must specify at least one footer")
289
+
290
+
291
+ def array_to_sheet(
292
+ _xl_book: xlsxwriter.workbook.Workbook,
293
+ _xl_sheet: xlsxwriter.worksheet.Worksheet,
294
+ _data_table: Sequence[Any] | npt.NDArray[Any],
295
+ _row_id: int,
296
+ _col_id: int = 0,
297
+ /,
298
+ *,
299
+ cell_format: Sequence[CFmt | Sequence[CFmt]] | CFmt | None = None,
300
+ green_bar_flag: bool = True,
301
+ ragged_flag: bool = True,
302
+ ) -> tuple[int, int]:
303
+ """
304
+ Write a 2-D array to a worksheet.
305
+
306
+ The given array is required be a two-dimensional array, whether
307
+ a nested list, nested tuple, or a 2-D numpy ndarray.
308
+
309
+
310
+ Parameters
311
+ ----------
312
+ _xl_book
313
+ Workbook object
314
+
315
+ _xl_sheet
316
+ Worksheet object to which to write the give array
317
+
318
+ _data_table
319
+ Array to be written
320
+
321
+ _row_id
322
+ Row number of top left corner of range to write to
323
+
324
+ _col_id
325
+ Column number of top left corner of range to write to
326
+
327
+ cell_format
328
+ Format specification for range to be written
329
+
330
+ green_bar_flag
331
+ Whether to highlight alternating rows as in green bar paper
332
+
333
+ ragged_flag
334
+ Whether to write ragged array, i.e. rows not all the same length
335
+ or not all cells are scalar-valued
336
+
337
+
338
+ Raises
339
+ ------
340
+ ValueError
341
+ If array is not two-dimensional
342
+
343
+ ValueError
344
+ If ragged_flag is False and array is not rectangular
345
+
346
+ ValueError
347
+ If array is not rectangular and cell_format is a Sequence
348
+
349
+ ValueError
350
+ If array is rectangular format tuple does not match data in length
351
+
352
+
353
+ Returns
354
+ -------
355
+ Tuple giving address of cell at right below and after range written
356
+
357
+
358
+ Notes
359
+ -----
360
+
361
+ The keyword argument cell_format may be passed a tuple of CFmt enums,
362
+ if, and only if, ragged_flag is False. If cell_format is a tuple, it must
363
+ have length equal to the number of cells in the range to be written. Further,
364
+ members of cell_format must each be a CFmt enum or a tuple of CFmt enums; in
365
+ other words, `CFmt.ensure_cell_format_spec_tuple(_c)` must return True for
366
+ any tuple `_c` passed as `cell_format`.
367
+
368
+ """
369
+
370
+ if not ragged_flag:
371
+ try:
372
+ if np.ndim(_data_table) != 2:
373
+ raise ValueError("Given array must be two-dimensional.")
374
+ except ValueError as _err:
375
+ raise ValueError(
376
+ "Given array must be rectangular and homogenous, with scalar members."
377
+ " Alternatively, try with ragged_flag=True."
378
+ )
379
+ raise _err
380
+ elif not (
381
+ isinstance(_data_table, Sequence | np.ndarray)
382
+ and hasattr(_data_table[0], "__len__")
383
+ ):
384
+ raise ValueError("Given array must be two-dimensional array.")
385
+
386
+ # Get the array dimensions and row and column numbers for Excel
387
+ _num_rows = len(_data_table)
388
+ _bottom_row_id = _row_id + _num_rows
389
+ _num_cols = len(_data_table[0])
390
+ _right_column_id = _col_id + _num_cols
391
+
392
+ if isinstance(cell_format, Sequence):
393
+ if ragged_flag:
394
+ raise ValueError(
395
+ "It is not clear whether the sequence of formats applies to all cells,"
396
+ " or to each cell respectively. Please provide a single-valued cell_format."
397
+ " Alternatively, you can iterate over the array using scalar_to_sheet()."
398
+ )
399
+ elif not len(cell_format) == len(_data_table[0]):
400
+ raise ValueError("Format tuple does not match data in length.")
401
+ CFmt.ensure_cell_format_spec_tuple(cell_format)
402
+ _cell_format: Sequence[CFmt | Sequence[CFmt]] = cell_format
403
+ elif isinstance(cell_format, CFmt):
404
+ _cell_format = (cell_format,) * len(_data_table[0])
405
+ else:
406
+ _cell_format = (CFmt.XL_DEFAULT,) * len(_data_table[0])
407
+
408
+ # construct vector of xlslwrter.format.Format objects
409
+ _wbk_formats = tuple(CFmt.xl_fmt(_xl_book, _cf) for _cf in _cell_format)
410
+ if _num_rows > 1:
411
+ _wbk_formats_greened = (
412
+ tuple(
413
+ CFmt.xl_fmt(
414
+ _xl_book,
415
+ (*_cf, CFmt.BAR_FILL)
416
+ if isinstance(_cf, Sequence)
417
+ else (_cf, CFmt.BAR_FILL),
418
+ )
419
+ for _cf in _cell_format
420
+ )
421
+ if green_bar_flag
422
+ else _wbk_formats
423
+ )
424
+
425
+ for _ri, _rv in enumerate(_data_table):
426
+ _wbk_fmt_tuple = _wbk_formats_greened if _ri % 2 else _wbk_formats
427
+ for _ci, _cv in enumerate(_rv):
428
+ _cf = _wbk_fmt_tuple[_ci]
429
+ scalar_to_sheet(_xl_sheet, _row_id + _ri, _col_id + _ci, _cv, _cf)
430
+
431
+ _right_column_id = _col_id + _ci + 1 if _ci > _num_cols else _right_column_id
432
+
433
+ return _bottom_row_id, _right_column_id
434
+
435
+
436
+ def scalar_to_sheet(
437
+ _xl_sheet: xlsxwriter.worksheet.Worksheet,
438
+ _cell_addr_0: str | int = "A1",
439
+ /,
440
+ *_s2s_args: Any,
441
+ empty_as_blank: bool = True,
442
+ ) -> None:
443
+ """
444
+ Write to a single cell in a worksheet.
445
+
446
+ Parameters
447
+ ----------
448
+ _xl_book
449
+ Workbook object
450
+
451
+ _xl_sheet
452
+ Worksheet object to which to write the give array
453
+
454
+ _cell_addr_0
455
+ First element of a cell address, which may be the entire address
456
+ in 'A1' format or the row-part in 'R1,C1' format
457
+
458
+ _s2s_args
459
+ Other arguments, which may be just the cell value to be written and the
460
+ cell format, or the column-part of the 'R1,C1' address along with
461
+ cell value and cell format.
462
+
463
+ Raises
464
+ ------
465
+ ValueError
466
+ If too many or too few arguments
467
+ ValueError
468
+ If incorrect/incomplete specification for Excel cell data
469
+
470
+ Returns
471
+ -------
472
+ None
473
+
474
+ """
475
+
476
+ _cell_addr: tuple[int | str, ...] = ()
477
+ _cell_val: Any = None
478
+ _cell_fmt: xlsxwriter.format.Format = CFmt.XL_DEFAULT
479
+
480
+ if isinstance(_cell_addr_0, str):
481
+ if len(_s2s_args) not in (1, 2):
482
+ raise ValueError("Incorrect number of arguments.")
483
+ _cell_addr = (_cell_addr_0,)
484
+ _cell_val = _s2s_args[0]
485
+ _cell_fmt = _s2s_args[1] if len(_s2s_args) == 2 else None # type: ignore
486
+ elif isinstance(_cell_addr_0, int):
487
+ if len(_s2s_args) not in (2, 3) or not isinstance(_s2s_args[0], int):
488
+ raise ValueError("Incorrect/incomplete specification for Excel cell data.")
489
+ _cell_addr = (_cell_addr_0, _s2s_args[0])
490
+ _cell_val = _s2s_args[1]
491
+ _cell_fmt = _s2s_args[2] if len(_s2s_args) == 3 else None # type: ignore
492
+ else:
493
+ raise ValueError("Incorrect/incomplete specification for Excel cell data.")
494
+
495
+ _write_args = (
496
+ (*_cell_addr, repr(_cell_val))
497
+ if np.ndim(_cell_val) or _cell_val in (np.inf, -np.inf, np.nan)
498
+ else (*_cell_addr, _cell_val)
499
+ )
500
+ _write_args = (*_write_args, _cell_fmt) if _cell_fmt else _write_args
501
+
502
+ if empty_as_blank and (_cell_val is None or _cell_val == ""):
503
+ _xl_sheet.write_blank(*_write_args)
504
+ elif (
505
+ _cell_val is None
506
+ or _cell_val == ""
507
+ or isinstance(_cell_val, str)
508
+ or np.ndim(_cell_val)
509
+ or _cell_val in (np.inf, -np.inf, np.nan)
510
+ ):
511
+ _xl_sheet.write_string(*_write_args)
512
+ else:
513
+ _xl_sheet.write(*_write_args)
@@ -1,373 +0,0 @@
1
- """
2
- Methods for writing data from Python to fresh Excel workbooks using
3
- the third-party package, `xlsxwriter`.
4
-
5
- Includes a flexible system of defining cell formats.
6
-
7
- NOTES
8
- -----
9
-
10
- This module is desinged for producing formatted summary output. For
11
- writing bulk data to Excel, facilities provided in third-party packages
12
- such as `polars` likely provide better performance.
13
-
14
- """
15
-
16
- from __future__ import annotations
17
-
18
- from collections.abc import Mapping, Sequence
19
- from typing import Any, ClassVar
20
-
21
- import numpy as np
22
- import numpy.typing as npt
23
- import xlsxwriter # type: ignore
24
- from aenum import Enum, unique # type: ignore
25
-
26
- from .. import VERSION # noqa: TID252
27
-
28
- __version__ = VERSION
29
-
30
-
31
- @unique
32
- class CFmt(dict, Enum): # type: ignore
33
- """
34
- Initialize cell formats for xlsxwriter.
35
-
36
- The mappings included here, and unions, etc. of them
37
- and any others added at runtime, will be rendered
38
- as xlsxWriter.Workbook.Format objects for writing
39
- cell values to formatted cells in a spreadsheet.
40
-
41
- NOTES
42
- -----
43
-
44
- For more information about xlsxwriter's cell formats,
45
- see, https://xlsxwriter.readthedocs.io/format.html
46
- """
47
-
48
- XL_DEFAULT: ClassVar = {"font_name": "Calibri", "font_size": 11}
49
- XL_DEFAULT_2003: ClassVar = {"font_name": "Arial", "font_size": 10}
50
-
51
- A_CTR: ClassVar = {"align": "center"}
52
- A_CTR_ACROSS: ClassVar = {"align": "center_across"}
53
- A_LEFT: ClassVar = {"align": "left"}
54
- A_RIGHT: ClassVar = {"align": "right"}
55
-
56
- BOLD: ClassVar = {"bold": True}
57
- BOLD_ITALIC: ClassVar = {"bold": True, "italic": True}
58
- ITALIC: ClassVar = {"italic": True}
59
- ULINE: ClassVar = {"underline": True}
60
-
61
- TEXT_WRAP: ClassVar = {"text_wrap": True}
62
- TEXT_ROTATE: ClassVar = {"rotation": 90}
63
- IND_1: ClassVar = {"indent": 1}
64
-
65
- DOLLAR_NUM: ClassVar = {"num_format": "[$$-409]#,##0.00"}
66
- DT_NUM: ClassVar = {"num_format": "mm/dd/yyyy"}
67
- QTY_NUM: ClassVar = {"num_format": "#,##0.0"}
68
- PCT_NUM: ClassVar = {"num_format": "##0%"}
69
- PCT2_NUM: ClassVar = {"num_format": "##0.00%"}
70
- PCT4_NUM: ClassVar = {"num_format": "##0.0000%"}
71
- PCT6_NUM: ClassVar = {"num_format": "##0.000000%"}
72
- PCT8_NUM: ClassVar = {"num_format": "##0.00000000%"}
73
- AREA_NUM: ClassVar = {"num_format": "0.00000000"}
74
-
75
- BAR_FILL: ClassVar = {"pattern": 1, "bg_color": "dfeadf"}
76
- HDR_FILL: ClassVar = {"pattern": 1, "bg_color": "999999"}
77
-
78
- LEFT_BORDER: ClassVar = {"left": 1, "left_color": "000000"}
79
- RIGHT_BORDER: ClassVar = {"right": 1, "right_color": "000000"}
80
- BOT_BORDER: ClassVar = {"bottom": 1, "bottom_color": "000000"}
81
- TOP_BORDER: ClassVar = {"top": 1, "top_color": "000000"}
82
- HDR_BORDER: ClassVar = TOP_BORDER | BOT_BORDER
83
-
84
-
85
- def write_header(
86
- _xl_sheet: xlsxwriter.worksheet.Worksheet,
87
- /,
88
- *,
89
- center_header: str | None = None,
90
- left_header: str | None = None,
91
- right_header: str | None = None,
92
- ) -> None:
93
- """Write header text to given worksheet.
94
-
95
- Parameters
96
- ----------
97
- _xl_sheet
98
- Worksheet object
99
- center_header
100
- Text for center header
101
- left_header
102
- Text for left header
103
- right_header
104
- Text for right header
105
-
106
- Raises
107
- ------
108
- ValueError
109
- Must specify at least one header
110
-
111
- Returns
112
- -------
113
- None
114
- """
115
- if not any((center_header, left_header, right_header)):
116
- raise ValueError("must specify at least one header")
117
- _xl_sheet.set_footer(
118
- "".join([
119
- f"&L{left_header}" if left_header else "",
120
- f"&C{center_header}" if center_header else "",
121
- f"&R{right_header}" if right_header else "",
122
- ])
123
- )
124
-
125
-
126
- def write_footer(
127
- _xl_sheet: xlsxwriter.worksheet.Worksheet,
128
- /,
129
- *,
130
- center_footer: str | None = None,
131
- left_footer: str | None = None,
132
- right_footer: str | None = None,
133
- ) -> None:
134
- """Write footer text to given worksheet.
135
-
136
- Parameters
137
- ----------
138
- _xl_sheet
139
- Worksheet object
140
- center_footer
141
- Text for center footer
142
- left_footer
143
- Text for left footer
144
- right_footer
145
- Text for right footer
146
-
147
- Raises
148
- ------
149
- ValueError
150
- Must specify at least one footer
151
-
152
- Returns
153
- -------
154
- None
155
- """
156
-
157
- if not any((center_footer, left_footer, right_footer)):
158
- raise ValueError("must specify at least one footer")
159
-
160
- _xl_sheet.set_footer(
161
- "".join([
162
- f"&L{left_footer}" if left_footer else "",
163
- f"&C{center_footer}" if center_footer else "",
164
- f"&R{right_footer}" if right_footer else "",
165
- ])
166
- )
167
-
168
-
169
- def array_to_sheet(
170
- _xl_book: xlsxwriter.workbook.Workbook,
171
- _xl_sheet: xlsxwriter.worksheet.Worksheet,
172
- _data_table: Sequence[Any] | npt.NDArray[Any],
173
- _row_id: int,
174
- _col_id: int = 0,
175
- /,
176
- *,
177
- cell_format: Sequence[CFmt] | CFmt | None = None,
178
- green_bar_flag: bool = True,
179
- ragged_flag: bool = True,
180
- ) -> tuple[int, int]:
181
- """
182
- Write a 2-D array to a worksheet.
183
-
184
- The given array is required be a two-dimensional array, whether
185
- a nested list, nested tuple, or a 2-D numpy ndarray.
186
-
187
- Parameters
188
- ----------
189
- _xl_book
190
- Workbook object
191
-
192
- _xl_sheet
193
- Worksheet object to which to write the give array
194
-
195
- _data_table
196
- Array to be written
197
-
198
- _row_id
199
- Row number of top left corner of range to write to
200
-
201
- _col_id
202
- Column number of top left corner of range to write to
203
-
204
- cell_format
205
- Format specification for range to be written
206
-
207
- green_bar_flag
208
- Whether to highlight alternating rows as in green bar paper
209
-
210
- Raises
211
- ------
212
- ValueError
213
- If format tuple does not match data in length
214
-
215
- Returns
216
- -------
217
- Tuple giving address of cell at right below and after range written
218
-
219
- """
220
-
221
- # Get the array dimensions and row and column numbers for Excel
222
- _num_rows = len(_data_table)
223
- _bottom_row_id = _row_id + _num_rows
224
- _num_cols = len(_data_table[0])
225
- _right_column_id = _col_id + _num_cols
226
-
227
- if isinstance(cell_format, tuple):
228
- ensure_cell_format_spec_tuple(cell_format)
229
- if not len(cell_format) == len(_data_table[0]):
230
- raise ValueError("Format tuple does not match data in length.")
231
- _cell_format: Sequence[CFmt] = cell_format
232
- elif isinstance(cell_format, CFmt):
233
- _cell_format = (cell_format,) * len(_data_table[0])
234
- else:
235
- _cell_format = (CFmt.XL_DEFAULT,) * len(_data_table[0])
236
-
237
- for _ri, _rv in enumerate(_data_table):
238
- for _ci, _cv in enumerate(_rv):
239
- _cell_fmt = _cell_format[_ci] | (
240
- CFmt.BAR_FILL if green_bar_flag and _ri % 2 else {}
241
- )
242
- scalar_to_sheet(
243
- _xl_book, _xl_sheet, _row_id + _ri, _col_id + _ci, _cv, _cell_fmt
244
- )
245
-
246
- _right_column_id = _col_id + _ci + 1 if _ci > _num_cols else _right_column_id
247
-
248
- return _bottom_row_id, _right_column_id
249
-
250
-
251
- def scalar_to_sheet(
252
- _xl_book: xlsxwriter.workbook.Workbook,
253
- _xl_sheet: xlsxwriter.worksheet.Worksheet,
254
- _cell_addr_0: str | int | float = "A1",
255
- /,
256
- *_s2s_args: Any,
257
- ) -> None:
258
- """
259
- Write to a single cell in a worksheet.
260
-
261
- Parameters
262
- ----------
263
- _xl_book
264
- Workbook object
265
-
266
- _xl_sheet
267
- Worksheet object to which to write the give array
268
-
269
- _cell_addr_0
270
- First element of a cell address, which may be the entire address
271
- in 'A1' format or the row-part in 'R1,C1' format
272
-
273
- _s2s_args
274
- Other arguments, which may be just the cell value to be written and the
275
- cell format, or the column-part of the 'R1,C1' address along with
276
- cell value and cell format.
277
-
278
- Raises
279
- ------
280
- ValueError
281
- If too many or too few arguments
282
- ValueError
283
- If incorrect/incomplete specification for Excel cell data
284
-
285
- Returns
286
- -------
287
- None
288
-
289
- """
290
-
291
- if isinstance(_cell_addr_0, str):
292
- if len(_s2s_args) not in (1, 2):
293
- raise ValueError("Too many or too few arguments.")
294
- _cell_addr: tuple[int | str, ...] = (_cell_addr_0,)
295
- _cell_val: Any = _s2s_args[0]
296
- _cell_fmt: CFmt | Sequence[CFmt] = _s2s_args[1] if len(_s2s_args) == 2 else None # type: ignore
297
- elif isinstance(_cell_addr_0, int):
298
- if len(_s2s_args) not in (2, 3):
299
- raise ValueError("Too many or too few arguments.")
300
- _cell_addr = (_cell_addr_0, _s2s_args[0])
301
- _cell_val = _s2s_args[1]
302
- _cell_fmt = _s2s_args[2] if len(_s2s_args) == 3 else None # type: ignore
303
- else:
304
- raise ValueError("Incorrect/incomplete specification for Excel cell data.")
305
-
306
- if isinstance(_cell_val, str):
307
- _xl_sheet.write_string(*_cell_addr, _cell_val, xl_fmt(_xl_book, _cell_fmt))
308
- else:
309
- _xl_sheet.write(
310
- *_cell_addr,
311
- repr(_cell_val) if np.ndim(_cell_val) else _cell_val,
312
- xl_fmt(_xl_book, _cell_fmt),
313
- )
314
-
315
-
316
- def xl_fmt(
317
- _xl_book: xlsxwriter.Workbook, _cell_fmt: Sequence[CFmt] | CFmt | None, /
318
- ) -> xlsxwriter.format.Format:
319
- """
320
- Return :code:`xlsxwriter` `Format` object given a CFmt aenum, or tuple thereof.
321
-
322
- Parameters
323
- ----------
324
- _xl_book
325
- :code:`xlsxwriter.Workbook` object
326
-
327
- _cell_fmt
328
- :code:`CFmt` aenum object, or tuple thereof
329
-
330
- Returns
331
- -------
332
- :code:`xlsxwriter` `Format` object
333
-
334
- """
335
- _cell_fmt_dict: Mapping[str, Any] = {}
336
- if isinstance(_cell_fmt, tuple):
337
- ensure_cell_format_spec_tuple(_cell_fmt)
338
- for _cf in _cell_fmt:
339
- _cell_fmt_dict = _cell_fmt_dict | _cf.value
340
- elif isinstance(_cell_fmt, CFmt):
341
- _cell_fmt_dict = _cell_fmt.value
342
- else:
343
- _cell_fmt_dict = CFmt.XL_DEFAULT.value
344
-
345
- return _xl_book.add_format(_cell_fmt_dict)
346
-
347
-
348
- def ensure_cell_format_spec_tuple(_cell_formats: Sequence[CFmt], /) -> None:
349
- """
350
- Test that a given format specification is tuple of CFmt enums
351
-
352
- Parameters
353
- ----------
354
- _cell_formats
355
- Format specification
356
-
357
- Raises
358
- ------
359
- ValueError
360
- If format specification is not tuple of CFmt aenums
361
-
362
- Returns
363
- -------
364
- True if format specification passes, else False
365
-
366
- """
367
-
368
- for _cell_format in _cell_formats:
369
- if isinstance(_cell_format, tuple):
370
- ensure_cell_format_spec_tuple(_cell_format)
371
-
372
- if not (isinstance(_cell_format, CFmt),):
373
- raise ValueError("Improperly specified format tuple.")