Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/pandas/io/excel/_util.py: 24%
102 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1from __future__ import annotations
3from typing import (
4 TYPE_CHECKING,
5 Any,
6 Callable,
7 Hashable,
8 Iterable,
9 Literal,
10 MutableMapping,
11 Sequence,
12 TypeVar,
13 overload,
14)
16from pandas.compat._optional import import_optional_dependency
18from pandas.core.dtypes.common import (
19 is_integer,
20 is_list_like,
21)
23if TYPE_CHECKING: 23 ↛ 24line 23 didn't jump to line 24, because the condition on line 23 was never true
24 from pandas.io.excel._base import ExcelWriter
26 ExcelWriter_t = type[ExcelWriter]
27 usecols_func = TypeVar("usecols_func", bound=Callable[[Hashable], object])
29_writers: MutableMapping[str, ExcelWriter_t] = {}
32def register_writer(klass: ExcelWriter_t) -> None:
33 """
34 Add engine to the excel writer registry.io.excel.
36 You must use this method to integrate with ``to_excel``.
38 Parameters
39 ----------
40 klass : ExcelWriter
41 """
42 if not callable(klass): 42 ↛ 43line 42 didn't jump to line 43, because the condition on line 42 was never true
43 raise ValueError("Can only register callables as engines")
44 engine_name = klass._engine
45 _writers[engine_name] = klass
48def get_default_engine(ext: str, mode: Literal["reader", "writer"] = "reader") -> str:
49 """
50 Return the default reader/writer for the given extension.
52 Parameters
53 ----------
54 ext : str
55 The excel file extension for which to get the default engine.
56 mode : str {'reader', 'writer'}
57 Whether to get the default engine for reading or writing.
58 Either 'reader' or 'writer'
60 Returns
61 -------
62 str
63 The default engine for the extension.
64 """
65 _default_readers = {
66 "xlsx": "openpyxl",
67 "xlsm": "openpyxl",
68 "xlsb": "pyxlsb",
69 "xls": "xlrd",
70 "ods": "odf",
71 }
72 _default_writers = {
73 "xlsx": "openpyxl",
74 "xlsm": "openpyxl",
75 "xlsb": "pyxlsb",
76 "xls": "xlwt",
77 "ods": "odf",
78 }
79 assert mode in ["reader", "writer"]
80 if mode == "writer":
81 # Prefer xlsxwriter over openpyxl if installed
82 xlsxwriter = import_optional_dependency("xlsxwriter", errors="warn")
83 if xlsxwriter:
84 _default_writers["xlsx"] = "xlsxwriter"
85 return _default_writers[ext]
86 else:
87 return _default_readers[ext]
90def get_writer(engine_name: str) -> ExcelWriter_t:
91 try:
92 return _writers[engine_name]
93 except KeyError as err:
94 raise ValueError(f"No Excel writer '{engine_name}'") from err
97def _excel2num(x: str) -> int:
98 """
99 Convert Excel column name like 'AB' to 0-based column index.
101 Parameters
102 ----------
103 x : str
104 The Excel column name to convert to a 0-based column index.
106 Returns
107 -------
108 num : int
109 The column index corresponding to the name.
111 Raises
112 ------
113 ValueError
114 Part of the Excel column name was invalid.
115 """
116 index = 0
118 for c in x.upper().strip():
119 cp = ord(c)
121 if cp < ord("A") or cp > ord("Z"):
122 raise ValueError(f"Invalid column name: {x}")
124 index = index * 26 + cp - ord("A") + 1
126 return index - 1
129def _range2cols(areas: str) -> list[int]:
130 """
131 Convert comma separated list of column names and ranges to indices.
133 Parameters
134 ----------
135 areas : str
136 A string containing a sequence of column ranges (or areas).
138 Returns
139 -------
140 cols : list
141 A list of 0-based column indices.
143 Examples
144 --------
145 >>> _range2cols('A:E')
146 [0, 1, 2, 3, 4]
147 >>> _range2cols('A,C,Z:AB')
148 [0, 2, 25, 26, 27]
149 """
150 cols: list[int] = []
152 for rng in areas.split(","):
153 if ":" in rng:
154 rngs = rng.split(":")
155 cols.extend(range(_excel2num(rngs[0]), _excel2num(rngs[1]) + 1))
156 else:
157 cols.append(_excel2num(rng))
159 return cols
162@overload
163def maybe_convert_usecols(usecols: str | list[int]) -> list[int]:
164 ...
167@overload
168def maybe_convert_usecols(usecols: list[str]) -> list[str]:
169 ...
172@overload
173def maybe_convert_usecols(usecols: usecols_func) -> usecols_func:
174 ...
177@overload
178def maybe_convert_usecols(usecols: None) -> None:
179 ...
182def maybe_convert_usecols(
183 usecols: str | list[int] | list[str] | usecols_func | None,
184) -> None | list[int] | list[str] | usecols_func:
185 """
186 Convert `usecols` into a compatible format for parsing in `parsers.py`.
188 Parameters
189 ----------
190 usecols : object
191 The use-columns object to potentially convert.
193 Returns
194 -------
195 converted : object
196 The compatible format of `usecols`.
197 """
198 if usecols is None:
199 return usecols
201 if is_integer(usecols):
202 raise ValueError(
203 "Passing an integer for `usecols` is no longer supported. "
204 "Please pass in a list of int from 0 to `usecols` inclusive instead."
205 )
207 if isinstance(usecols, str):
208 return _range2cols(usecols)
210 return usecols
213@overload
214def validate_freeze_panes(freeze_panes: tuple[int, int]) -> Literal[True]:
215 ...
218@overload
219def validate_freeze_panes(freeze_panes: None) -> Literal[False]:
220 ...
223def validate_freeze_panes(freeze_panes: tuple[int, int] | None) -> bool:
224 if freeze_panes is not None:
225 if len(freeze_panes) == 2 and all(
226 isinstance(item, int) for item in freeze_panes
227 ):
228 return True
230 raise ValueError(
231 "freeze_panes must be of form (row, column) "
232 "where row and column are integers"
233 )
235 # freeze_panes wasn't specified, return False so it won't be applied
236 # to output sheet
237 return False
240def fill_mi_header(
241 row: list[Hashable], control_row: list[bool]
242) -> tuple[list[Hashable], list[bool]]:
243 """
244 Forward fill blank entries in row but only inside the same parent index.
246 Used for creating headers in Multiindex.
248 Parameters
249 ----------
250 row : list
251 List of items in a single row.
252 control_row : list of bool
253 Helps to determine if particular column is in same parent index as the
254 previous value. Used to stop propagation of empty cells between
255 different indexes.
257 Returns
258 -------
259 Returns changed row and control_row
260 """
261 last = row[0]
262 for i in range(1, len(row)):
263 if not control_row[i]:
264 last = row[i]
266 if row[i] == "" or row[i] is None:
267 row[i] = last
268 else:
269 control_row[i] = False
270 last = row[i]
272 return row, control_row
275def pop_header_name(
276 row: list[Hashable], index_col: int | Sequence[int]
277) -> tuple[Hashable | None, list[Hashable]]:
278 """
279 Pop the header name for MultiIndex parsing.
281 Parameters
282 ----------
283 row : list
284 The data row to parse for the header name.
285 index_col : int, list
286 The index columns for our data. Assumed to be non-null.
288 Returns
289 -------
290 header_name : str
291 The extracted header name.
292 trimmed_row : list
293 The original data row with the header name removed.
294 """
295 # Pop out header name and fill w/blank.
296 if is_list_like(index_col):
297 assert isinstance(index_col, Iterable)
298 i = max(index_col)
299 else:
300 assert not isinstance(index_col, Iterable)
301 i = index_col
303 header_name = row[i]
304 header_name = None if header_name == "" else header_name
306 return header_name, row[:i] + [""] + row[i + 1 :]
309def combine_kwargs(engine_kwargs: dict[str, Any] | None, kwargs: dict) -> dict:
310 """
311 Used to combine two sources of kwargs for the backend engine.
313 Use of kwargs is deprecated, this function is solely for use in 1.3 and should
314 be removed in 1.4/2.0. Also _base.ExcelWriter.__new__ ensures either engine_kwargs
315 or kwargs must be None or empty respectively.
317 Parameters
318 ----------
319 engine_kwargs: dict
320 kwargs to be passed through to the engine.
321 kwargs: dict
322 kwargs to be psased through to the engine (deprecated)
324 Returns
325 -------
326 engine_kwargs combined with kwargs
327 """
328 if engine_kwargs is None:
329 result = {}
330 else:
331 result = engine_kwargs.copy()
332 result.update(kwargs)
333 return result