mergeron 2024.739087.0__tar.gz → 2024.739088.1__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 (36) hide show
  1. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/PKG-INFO +1 -1
  2. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/pyproject.toml +1 -1
  3. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/__init__.py +1 -1
  4. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/data/jinja2_LaTeX_templates/ftcinvdata_summarypaired_table_template.tex.jinja2 +11 -11
  5. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/ext/tol_colors.py +12 -15
  6. {mergeron-2024.739087.0/src/mergeron/core → mergeron-2024.739088.1/src/mergeron/ext}/xlsxw_helper.py +184 -114
  7. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/gen/enforcement_stats.py +144 -104
  8. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/gen/market_sample.py +8 -8
  9. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/gen/upp_tests.py +32 -32
  10. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/README.rst +0 -0
  11. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/License.txt +0 -0
  12. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/core/__init__.py +0 -0
  13. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/core/damodaran_margin_data.py +0 -0
  14. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/core/ftc_merger_investigations_data.py +0 -0
  15. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/core/guidelines_boundaries.py +0 -0
  16. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/core/guidelines_boundary_functions.py +0 -0
  17. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/core/guidelines_boundary_functions_extra.py +0 -0
  18. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/core/proportions_tests.py +0 -0
  19. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/core/pseudorandom_numbers.py +0 -0
  20. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/data/__init__.py +0 -0
  21. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/data/damodaran_margin_data.xls +0 -0
  22. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/data/damodaran_margin_data_dict.msgpack +0 -0
  23. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/data/ftc_invdata.msgpack +0 -0
  24. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/data/jinja2_LaTeX_templates/clrrate_cis_summary_table_template.tex.jinja2 +0 -0
  25. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/data/jinja2_LaTeX_templates/ftcinvdata_byhhianddelta_table_template.tex.jinja2 +0 -0
  26. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/data/jinja2_LaTeX_templates/ftcinvdata_summary_table_template.tex.jinja2 +0 -0
  27. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/data/jinja2_LaTeX_templates/mergeron.cls +0 -0
  28. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/data/jinja2_LaTeX_templates/mergeron_table_collection_template.tex.jinja2 +0 -0
  29. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/data/jinja2_LaTeX_templates/setup_tikz_tables.tex +0 -0
  30. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/demo/__init__.py +0 -0
  31. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/demo/visualize_empirical_margin_distribution.py +0 -0
  32. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/ext/__init__.py +0 -0
  33. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/gen/__init__.py +0 -0
  34. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/gen/_data_generation_functions.py +0 -0
  35. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/gen/data_generation.py +0 -0
  36. {mergeron-2024.739087.0 → mergeron-2024.739088.1}/src/mergeron/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mergeron
3
- Version: 2024.739087.0
3
+ Version: 2024.739088.1
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.739087.0"
16
+ version = "2024.739088.1"
17
17
 
18
18
  # Classifiers list: https://pypi.org/classifiers/
19
19
  classifiers = [
@@ -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.739087.0"
14
+ VERSION = "2024.739088.1"
15
15
 
16
16
  __version__ = VERSION
17
17
 
@@ -19,19 +19,19 @@
19
19
  Grouped by Entry Conditions and Reporting Period \\[0.5\baselineskip]}
20
20
  \begin{tikzpicture}[auto, font = \sffamily]
21
21
  \begin{pgfonlayer}{background}
22
- \matrix[datatable, nodes = {text width = 27pt,},] (invres_rate_raw) {
22
+ \matrix[datatable, nodes = {text width = 27pt,},] (enf_rate_raw) {
23
23
  \JINVAR{ tmpl_data.invdata_datstr -}
24
24
  };
25
25
  % Horizontal scoring for totals
26
- \draw[color = OBSHDRFill, line width = 1pt] (invres_rate_raw-\JINVAR{- tmpl_data.invdata_numrows -}-1.north west) -- (invres_rate_raw-\JINVAR{- tmpl_data.invdata_numrows -}-8.north east);
26
+ \draw[color = OBSHDRFill, line width = 1pt] (enf_rate_raw-\JINVAR{- tmpl_data.invdata_numrows -}-1.north west) -- (enf_rate_raw-\JINVAR{- tmpl_data.invdata_numrows -}-8.north east);
27
27
  % Vertical scoring for column groups
28
- \draw[color = OBSHDRFill, line width = 1pt] (invres_rate_raw-1-2.north east) -- (invres_rate_raw-\JINVAR{- tmpl_data.invdata_numrows -}-2.south east);
29
- \draw[color = OBSHDRFill, line width = 1pt] (invres_rate_raw-1-4.north east) -- (invres_rate_raw-\JINVAR{- tmpl_data.invdata_numrows -}-4.south east);
30
- \draw[color = OBSHDRFill, line width = 1pt] (invres_rate_raw-1-6.north east) -- (invres_rate_raw-\JINVAR{- tmpl_data.invdata_numrows -}-6.south east);
28
+ \draw[color = OBSHDRFill, line width = 1pt] (enf_rate_raw-1-2.north east) -- (enf_rate_raw-\JINVAR{- tmpl_data.invdata_numrows -}-2.south east);
29
+ \draw[color = OBSHDRFill, line width = 1pt] (enf_rate_raw-1-4.north east) -- (enf_rate_raw-\JINVAR{- tmpl_data.invdata_numrows -}-4.south east);
30
+ \draw[color = OBSHDRFill, line width = 1pt] (enf_rate_raw-1-6.north east) -- (enf_rate_raw-\JINVAR{- tmpl_data.invdata_numrows -}-6.south east);
31
31
  \end{pgfonlayer}
32
32
 
33
33
  \matrix[hrow,
34
- above = 0 pt of invres_rate_raw,
34
+ above = 0 pt of enf_rate_raw,
35
35
  nodes = {minimum height = 33pt, text depth = 10pt, text width = 60pt, align = left, inner sep = 3pt,},
36
36
  ] (hdrrow_raw) {
37
37
  \node[rotate = 90] {Relative \\ Frequency \\}; &
@@ -64,12 +64,12 @@
64
64
  Observed Data \\
65
65
  };
66
66
  % Vertical scoring for column groups
67
- \draw[color = white, line width = 1pt] (descrow_raw_pds1-1-1.north east) -- (invres_rate_raw-1-2.north east);
68
- \draw[color = white, line width = 1pt] (descrow_raw_mkt1-1-1.north east) -- (invres_rate_raw-1-4.north east);
69
- \draw[color = white, line width = 1pt] (descrow_raw_pds2-1-1.north east) -- (invres_rate_raw-1-6.north east);
67
+ \draw[color = white, line width = 1pt] (descrow_raw_pds1-1-1.north east) -- (enf_rate_raw-1-2.north east);
68
+ \draw[color = white, line width = 1pt] (descrow_raw_mkt1-1-1.north east) -- (enf_rate_raw-1-4.north east);
69
+ \draw[color = white, line width = 1pt] (descrow_raw_pds2-1-1.north east) -- (enf_rate_raw-1-6.north east);
70
70
 
71
71
  % Header column - row heads
72
- \matrix[hcol, left = 0pt of invres_rate_raw, nodes = {
72
+ \matrix[hcol, left = 0pt of enf_rate_raw, nodes = {
73
73
  text width = \JINVAR{ tmpl_data.hdrcol_raw_width -},
74
74
  inner xsep = 3 pt,
75
75
  },] (hdrcol_raw) {
@@ -85,7 +85,7 @@
85
85
  (hdrcoldesc_raw.north east)
86
86
  ;
87
87
  ((# % Separator for header column
88
- \draw[color = white, line width = 1pt] (descrow_raw_mkt1-1-1.north west) -- (invres_rate_raw-1-1.north west); #))
88
+ \draw[color = white, line width = 1pt] (descrow_raw_mkt1-1-1.north west) -- (enf_rate_raw-1-1.north west); #))
89
89
 
90
90
  % Notes below table
91
91
  \matrix[anytable,
@@ -37,24 +37,21 @@ def discretemap(colormap: str, hexclrs: Sequence[str]) -> LinearSegmentedColorma
37
37
 
38
38
 
39
39
  class TOLcmaps:
40
- """
41
- Class TOLcmaps definition.
42
-
43
- Attributes
44
- ----------
45
- cmap
46
- A matploltib colormap
47
-
48
- cname
49
- Colormap name
40
+ def __init__(self) -> None:
41
+ """
42
+ Class TOLcmaps definition.
50
43
 
51
- namelist
52
- A list of colormap names
44
+ Attributes
45
+ ----------
46
+ cmap
47
+ A matploltib colormap
53
48
 
54
- """
49
+ cname
50
+ Colormap name
55
51
 
56
- def __init__(self) -> None:
57
- """ """
52
+ namelist
53
+ A list of colormap names
54
+ """
58
55
  # self.cmap: LinearSegmentedColormap | None = None
59
56
  self.cname: str = ""
60
57
  self.namelist: Sequence[str] = (
@@ -21,12 +21,14 @@ from typing import Any, ClassVar
21
21
  import numpy as np
22
22
  import numpy.typing as npt
23
23
  import xlsxwriter # type: ignore
24
- from aenum import Enum, unique # type: ignore
24
+ from aenum import Enum, extend_enum, unique # type: ignore
25
25
 
26
26
  from .. import VERSION # noqa: TID252
27
27
 
28
28
  __version__ = VERSION
29
29
 
30
+ Workbook = xlsxwriter.Workbook
31
+
30
32
 
31
33
  @unique
32
34
  class CFmt(dict, Enum): # type: ignore
@@ -34,7 +36,7 @@ class CFmt(dict, Enum): # type: ignore
34
36
  Initialize cell formats for xlsxwriter.
35
37
 
36
38
  The mappings included here, and unions, etc. of them
37
- and any others added at runtime, will be rendered
39
+ and any others added at runtime, are rendered
38
40
  as xlsxWriter.Workbook.Format objects for writing
39
41
  cell values to formatted cells in a spreadsheet.
40
42
 
@@ -43,6 +45,7 @@ class CFmt(dict, Enum): # type: ignore
43
45
 
44
46
  For more information about xlsxwriter's cell formats,
45
47
  see, https://xlsxwriter.readthedocs.io/format.html
48
+
46
49
  """
47
50
 
48
51
  XL_DEFAULT: ClassVar = {"font_name": "Calibri", "font_size": 11}
@@ -52,28 +55,35 @@ class CFmt(dict, Enum): # type: ignore
52
55
  A_CTR_ACROSS: ClassVar = {"align": "center_across"}
53
56
  A_LEFT: ClassVar = {"align": "left"}
54
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}
55
65
 
56
66
  BOLD: ClassVar = {"bold": True}
57
67
  BOLD_ITALIC: ClassVar = {"bold": True, "italic": True}
58
68
  ITALIC: ClassVar = {"italic": True}
59
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}
60
74
 
61
- TEXT_WRAP: ClassVar = {"text_wrap": True}
62
- TEXT_ROTATE: ClassVar = {"rotation": 90}
63
- IND_1: ClassVar = {"indent": 1}
64
-
75
+ AREA_NUM: ClassVar = ({"num_format": "0.00000000"},)
65
76
  DOLLAR_NUM: ClassVar = {"num_format": "[$$-409]#,##0.00"}
66
77
  DT_NUM: ClassVar = {"num_format": "mm/dd/yyyy"}
67
- QTY_NUM: ClassVar = {"num_format": "#,##0.0"}
68
78
  PCT_NUM: ClassVar = {"num_format": "##0%"}
69
79
  PCT2_NUM: ClassVar = {"num_format": "##0.00%"}
70
80
  PCT4_NUM: ClassVar = {"num_format": "##0.0000%"}
71
81
  PCT6_NUM: ClassVar = {"num_format": "##0.000000%"}
72
82
  PCT8_NUM: ClassVar = {"num_format": "##0.00000000%"}
73
- AREA_NUM: ClassVar = {"num_format": "0.00000000"}
83
+ QTY_NUM: ClassVar = {"num_format": "#,##0.0"}
74
84
 
75
85
  BAR_FILL: ClassVar = {"pattern": 1, "bg_color": "dfeadf"}
76
- HDR_FILL: ClassVar = {"pattern": 1, "bg_color": "999999"}
86
+ HDR_FILL: ClassVar = {"pattern": 1, "bg_color": "e9e9e9"}
77
87
 
78
88
  LEFT_BORDER: ClassVar = {"left": 1, "left_color": "000000"}
79
89
  RIGHT_BORDER: ClassVar = {"right": 1, "right_color": "000000"}
@@ -81,6 +91,115 @@ class CFmt(dict, Enum): # type: ignore
81
91
  TOP_BORDER: ClassVar = {"top": 1, "top_color": "000000"}
82
92
  HDR_BORDER: ClassVar = TOP_BORDER | BOT_BORDER
83
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
+
84
203
 
85
204
  def write_header(
86
205
  _xl_sheet: xlsxwriter.worksheet.Worksheet,
@@ -156,7 +275,7 @@ def write_footer(
156
275
  None
157
276
  """
158
277
 
159
- if not any((center_footer, left_footer, right_footer)):
278
+ if any((center_footer, left_footer, right_footer)):
160
279
  _xl_sheet.set_footer(
161
280
  "".join([
162
281
  f"&L{left_footer}" if left_footer else "",
@@ -235,6 +354,17 @@ def array_to_sheet(
235
354
  -------
236
355
  Tuple giving address of cell at right below and after range written
237
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
+
238
368
  """
239
369
 
240
370
  if not ragged_flag:
@@ -268,7 +398,7 @@ def array_to_sheet(
268
398
  )
269
399
  elif not len(cell_format) == len(_data_table[0]):
270
400
  raise ValueError("Format tuple does not match data in length.")
271
- ensure_cell_format_spec_tuple(cell_format)
401
+ CFmt.ensure_cell_format_spec_tuple(cell_format)
272
402
  _cell_format: Sequence[CFmt | Sequence[CFmt]] = cell_format
273
403
  elif isinstance(cell_format, CFmt):
274
404
  _cell_format = (cell_format,) * len(_data_table[0])
@@ -276,21 +406,27 @@ def array_to_sheet(
276
406
  _cell_format = (CFmt.XL_DEFAULT,) * len(_data_table[0])
277
407
 
278
408
  # construct vector of xlslwrter.format.Format objects
279
- _wbk_formats = tuple(xl_fmt(_xl_book, _cf) for _cf in _cell_format)
409
+ _wbk_formats = tuple(CFmt.xl_fmt(_xl_book, _cf) for _cf in _cell_format)
280
410
  if _num_rows > 1:
281
411
  _wbk_formats_greened = (
282
- tuple(xl_fmt(_xl_book, (_cf, CFmt.BAR_FILL)) for _cf in _cell_format)
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
+ )
283
421
  if green_bar_flag
284
422
  else _wbk_formats
285
423
  )
286
424
 
287
425
  for _ri, _rv in enumerate(_data_table):
288
- _fmt_tuple = _wbk_formats_greened if _ri % 2 else _wbk_formats
426
+ _wbk_fmt_tuple = _wbk_formats_greened if _ri % 2 else _wbk_formats
289
427
  for _ci, _cv in enumerate(_rv):
290
- _cell_fmt = _fmt_tuple[_ci]
291
- scalar_to_sheet(
292
- _xl_book, _xl_sheet, _row_id + _ri, _col_id + _ci, _cv, _cell_fmt
293
- )
428
+ _cf = _wbk_fmt_tuple[_ci]
429
+ scalar_to_sheet(_xl_sheet, _row_id + _ri, _col_id + _ci, _cv, _cf)
294
430
 
295
431
  _right_column_id = _col_id + _ci + 1 if _ci > _num_cols else _right_column_id
296
432
 
@@ -298,20 +434,17 @@ def array_to_sheet(
298
434
 
299
435
 
300
436
  def scalar_to_sheet(
301
- _xl_book: xlsxwriter.workbook.Workbook,
302
437
  _xl_sheet: xlsxwriter.worksheet.Worksheet,
303
- _cell_addr_0: str | int | float = "A1",
438
+ _cell_addr_0: str | int = "A1",
304
439
  /,
305
440
  *_s2s_args: Any,
441
+ empty_as_blank: bool = True,
306
442
  ) -> None:
307
443
  """
308
444
  Write to a single cell in a worksheet.
309
445
 
310
446
  Parameters
311
447
  ----------
312
- _xl_book
313
- Workbook object
314
-
315
448
  _xl_sheet
316
449
  Worksheet object to which to write the give array
317
450
 
@@ -337,104 +470,41 @@ def scalar_to_sheet(
337
470
 
338
471
  """
339
472
 
473
+ _cell_addr: tuple[int | str, ...] = ()
474
+ _cell_val: Any = None
475
+ _cell_fmt: xlsxwriter.format.Format = CFmt.XL_DEFAULT
476
+
340
477
  if isinstance(_cell_addr_0, str):
341
478
  if len(_s2s_args) not in (1, 2):
342
- raise ValueError("Too many or too few arguments.")
343
- _cell_addr: tuple[int | str, ...] = (_cell_addr_0,)
344
- _cell_val: Any = _s2s_args[0]
345
- _cell_fmt: CFmt | Sequence[CFmt] = _s2s_args[1] if len(_s2s_args) == 2 else None # type: ignore
479
+ raise ValueError("Incorrect number of arguments.")
480
+ _cell_addr = (_cell_addr_0,)
481
+ _cell_val = _s2s_args[0]
482
+ _cell_fmt = _s2s_args[1] if len(_s2s_args) == 2 else None # type: ignore
346
483
  elif isinstance(_cell_addr_0, int):
347
- if len(_s2s_args) not in (2, 3):
348
- raise ValueError("Too many or too few arguments.")
484
+ if len(_s2s_args) not in (2, 3) or not isinstance(_s2s_args[0], int):
485
+ raise ValueError("Incorrect/incomplete specification for Excel cell data.")
349
486
  _cell_addr = (_cell_addr_0, _s2s_args[0])
350
487
  _cell_val = _s2s_args[1]
351
488
  _cell_fmt = _s2s_args[2] if len(_s2s_args) == 3 else None # type: ignore
352
489
  else:
353
490
  raise ValueError("Incorrect/incomplete specification for Excel cell data.")
354
491
 
355
- _xl_fmt = xl_fmt(_xl_book, _cell_fmt)
356
- if isinstance(_cell_val, str):
357
- _xl_sheet.write_string(*_cell_addr, _cell_val, _xl_fmt)
358
- else:
359
- _xl_sheet.write(
360
- *_cell_addr, repr(_cell_val) if np.ndim(_cell_val) else _cell_val, _xl_fmt
361
- )
362
-
363
-
364
- def xl_fmt(
365
- _xl_book: xlsxwriter.Workbook,
366
- _cell_fmt: Sequence[CFmt | Sequence[CFmt]] | CFmt | None,
367
- /,
368
- ) -> xlsxwriter.format.Format:
369
- """
370
- Return :code:`xlsxwriter` `Format` object given a CFmt aenum, or tuple thereof.
371
-
372
- Parameters
373
- ----------
374
- _xl_book
375
- :code:`xlsxwriter.Workbook` object
376
-
377
- _cell_fmt
378
- :code:`CFmt` aenum object, or tuple thereof
379
-
380
- Raises
381
- ------
382
- ValueError
383
- If format specification is not one of None, a CFmt aenum, or
384
- a xlsxwriter.format.Format object
385
-
386
- Returns
387
- -------
388
- :code:`xlsxwriter` `Format` object
389
-
390
- """
391
-
392
- if isinstance(_cell_fmt, xlsxwriter.format.Format):
393
- return _cell_fmt
394
- elif _cell_fmt is None:
395
- return _xl_book.add_format(CFmt.XL_DEFAULT.value)
396
-
397
- _cell_fmt_dict: Mapping[str, Any] = {}
398
- if isinstance(_cell_fmt, Sequence):
399
- ensure_cell_format_spec_tuple(_cell_fmt)
400
- for _cf in _cell_fmt:
401
- _cell_fmt_dict = _cell_fmt_dict | _cf.value
402
- elif isinstance(_cell_fmt, CFmt):
403
- _cell_fmt_dict = _cell_fmt.value
492
+ _write_args = (
493
+ (*_cell_addr, repr(_cell_val))
494
+ if np.ndim(_cell_val) or _cell_val in (np.inf, -np.inf, np.nan)
495
+ else (*_cell_addr, _cell_val)
496
+ )
497
+ _write_args = (*_write_args, _cell_fmt) if _cell_fmt else _write_args
498
+
499
+ if empty_as_blank and (_cell_val is None or _cell_val == ""):
500
+ _xl_sheet.write_blank(*_write_args)
501
+ elif (
502
+ _cell_val is None
503
+ or _cell_val == ""
504
+ or isinstance(_cell_val, str)
505
+ or np.ndim(_cell_val)
506
+ or _cell_val in (np.inf, -np.inf, np.nan)
507
+ ):
508
+ _xl_sheet.write_string(*_write_args)
404
509
  else:
405
- raise ValueError("Improperly specified format specification.")
406
-
407
- return _xl_book.add_format(_cell_fmt_dict)
408
-
409
-
410
- def ensure_cell_format_spec_tuple(
411
- _cell_formats: Sequence[CFmt | Sequence[CFmt]], /
412
- ) -> None:
413
- """
414
- Test that a given format specification is tuple of CFmt enums
415
-
416
- Parameters
417
- ----------
418
- _cell_formats
419
- Format specification
420
-
421
- Raises
422
- ------
423
- ValueError
424
- If format specification is not tuple of CFmt enums
425
-
426
- Returns
427
- -------
428
- True if format specification passes, else False
429
-
430
- """
431
-
432
- for _cell_format in _cell_formats:
433
- if isinstance(_cell_format, tuple):
434
- ensure_cell_format_spec_tuple(_cell_format)
435
-
436
- if not (isinstance(_cell_format, CFmt),):
437
- raise ValueError(
438
- "Improperly specified format tuple for writing array."
439
- " Must be tuple of CFmt enums."
440
- )
510
+ _xl_sheet.write(*_write_args)