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

1from __future__ import annotations 

2 

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) 

15 

16from pandas.compat._optional import import_optional_dependency 

17 

18from pandas.core.dtypes.common import ( 

19 is_integer, 

20 is_list_like, 

21) 

22 

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 

25 

26 ExcelWriter_t = type[ExcelWriter] 

27 usecols_func = TypeVar("usecols_func", bound=Callable[[Hashable], object]) 

28 

29_writers: MutableMapping[str, ExcelWriter_t] = {} 

30 

31 

32def register_writer(klass: ExcelWriter_t) -> None: 

33 """ 

34 Add engine to the excel writer registry.io.excel. 

35 

36 You must use this method to integrate with ``to_excel``. 

37 

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 

46 

47 

48def get_default_engine(ext: str, mode: Literal["reader", "writer"] = "reader") -> str: 

49 """ 

50 Return the default reader/writer for the given extension. 

51 

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' 

59 

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] 

88 

89 

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 

95 

96 

97def _excel2num(x: str) -> int: 

98 """ 

99 Convert Excel column name like 'AB' to 0-based column index. 

100 

101 Parameters 

102 ---------- 

103 x : str 

104 The Excel column name to convert to a 0-based column index. 

105 

106 Returns 

107 ------- 

108 num : int 

109 The column index corresponding to the name. 

110 

111 Raises 

112 ------ 

113 ValueError 

114 Part of the Excel column name was invalid. 

115 """ 

116 index = 0 

117 

118 for c in x.upper().strip(): 

119 cp = ord(c) 

120 

121 if cp < ord("A") or cp > ord("Z"): 

122 raise ValueError(f"Invalid column name: {x}") 

123 

124 index = index * 26 + cp - ord("A") + 1 

125 

126 return index - 1 

127 

128 

129def _range2cols(areas: str) -> list[int]: 

130 """ 

131 Convert comma separated list of column names and ranges to indices. 

132 

133 Parameters 

134 ---------- 

135 areas : str 

136 A string containing a sequence of column ranges (or areas). 

137 

138 Returns 

139 ------- 

140 cols : list 

141 A list of 0-based column indices. 

142 

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] = [] 

151 

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)) 

158 

159 return cols 

160 

161 

162@overload 

163def maybe_convert_usecols(usecols: str | list[int]) -> list[int]: 

164 ... 

165 

166 

167@overload 

168def maybe_convert_usecols(usecols: list[str]) -> list[str]: 

169 ... 

170 

171 

172@overload 

173def maybe_convert_usecols(usecols: usecols_func) -> usecols_func: 

174 ... 

175 

176 

177@overload 

178def maybe_convert_usecols(usecols: None) -> None: 

179 ... 

180 

181 

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`. 

187 

188 Parameters 

189 ---------- 

190 usecols : object 

191 The use-columns object to potentially convert. 

192 

193 Returns 

194 ------- 

195 converted : object 

196 The compatible format of `usecols`. 

197 """ 

198 if usecols is None: 

199 return usecols 

200 

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 ) 

206 

207 if isinstance(usecols, str): 

208 return _range2cols(usecols) 

209 

210 return usecols 

211 

212 

213@overload 

214def validate_freeze_panes(freeze_panes: tuple[int, int]) -> Literal[True]: 

215 ... 

216 

217 

218@overload 

219def validate_freeze_panes(freeze_panes: None) -> Literal[False]: 

220 ... 

221 

222 

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 

229 

230 raise ValueError( 

231 "freeze_panes must be of form (row, column) " 

232 "where row and column are integers" 

233 ) 

234 

235 # freeze_panes wasn't specified, return False so it won't be applied 

236 # to output sheet 

237 return False 

238 

239 

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. 

245 

246 Used for creating headers in Multiindex. 

247 

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. 

256 

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] 

265 

266 if row[i] == "" or row[i] is None: 

267 row[i] = last 

268 else: 

269 control_row[i] = False 

270 last = row[i] 

271 

272 return row, control_row 

273 

274 

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. 

280 

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. 

287 

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 

302 

303 header_name = row[i] 

304 header_name = None if header_name == "" else header_name 

305 

306 return header_name, row[:i] + [""] + row[i + 1 :] 

307 

308 

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. 

312 

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. 

316 

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) 

323 

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