1
1
"""
2
2
Utilities for conversion to writer-agnostic Excel representation.
3
3
"""
4
+ from __future__ import annotations
4
5
5
6
from functools import reduce
6
7
import itertools
7
8
import re
8
9
from typing import (
9
10
Callable ,
10
- Dict ,
11
11
Hashable ,
12
12
Iterable ,
13
13
Mapping ,
14
- Optional ,
15
14
Sequence ,
16
- Union ,
17
15
cast ,
18
16
)
19
17
import warnings
@@ -61,8 +59,8 @@ def __init__(
61
59
col : int ,
62
60
val ,
63
61
style = None ,
64
- mergestart : Optional [ int ] = None ,
65
- mergeend : Optional [ int ] = None ,
62
+ mergestart : int | None = None ,
63
+ mergeend : int | None = None ,
66
64
):
67
65
self .row = row
68
66
self .col = col
@@ -135,17 +133,17 @@ class CSSToExcelConverter:
135
133
# and __call__ make use of instance attributes. We leave them as
136
134
# instancemethods so that users can easily experiment with extensions
137
135
# without monkey-patching.
138
- inherited : Optional [ Dict [ str , str ]]
136
+ inherited : dict [ str , str ] | None
139
137
140
- def __init__ (self , inherited : Optional [ str ] = None ):
138
+ def __init__ (self , inherited : str | None = None ):
141
139
if inherited is not None :
142
140
self .inherited = self .compute_css (inherited )
143
141
else :
144
142
self .inherited = None
145
143
146
144
compute_css = CSSResolver ()
147
145
148
- def __call__ (self , declarations_str : str ) -> Dict [str , Dict [str , str ]]:
146
+ def __call__ (self , declarations_str : str ) -> dict [str , dict [str , str ]]:
149
147
"""
150
148
Convert CSS declarations to ExcelWriter style.
151
149
@@ -165,7 +163,7 @@ def __call__(self, declarations_str: str) -> Dict[str, Dict[str, str]]:
165
163
properties = self .compute_css (declarations_str , self .inherited )
166
164
return self .build_xlstyle (properties )
167
165
168
- def build_xlstyle (self , props : Mapping [str , str ]) -> Dict [str , Dict [str , str ]]:
166
+ def build_xlstyle (self , props : Mapping [str , str ]) -> dict [str , dict [str , str ]]:
169
167
out = {
170
168
"alignment" : self .build_alignment (props ),
171
169
"border" : self .build_border (props ),
@@ -176,7 +174,7 @@ def build_xlstyle(self, props: Mapping[str, str]) -> Dict[str, Dict[str, str]]:
176
174
177
175
# TODO: handle cell width and height: needs support in pandas.io.excel
178
176
179
- def remove_none (d : Dict [str , str ]) -> None :
177
+ def remove_none (d : dict [str , str ]) -> None :
180
178
"""Remove key where value is None, through nested dicts"""
181
179
for k , v in list (d .items ()):
182
180
if v is None :
@@ -189,30 +187,28 @@ def remove_none(d: Dict[str, str]) -> None:
189
187
remove_none (out )
190
188
return out
191
189
192
- def build_alignment (
193
- self , props : Mapping [str , str ]
194
- ) -> Dict [str , Optional [Union [bool , str ]]]:
190
+ def build_alignment (self , props : Mapping [str , str ]) -> dict [str , bool | str | None ]:
195
191
# TODO: text-indent, padding-left -> alignment.indent
196
192
return {
197
193
"horizontal" : props .get ("text-align" ),
198
194
"vertical" : self ._get_vertical_alignment (props ),
199
195
"wrap_text" : self ._get_is_wrap_text (props ),
200
196
}
201
197
202
- def _get_vertical_alignment (self , props : Mapping [str , str ]) -> Optional [ str ] :
198
+ def _get_vertical_alignment (self , props : Mapping [str , str ]) -> str | None :
203
199
vertical_align = props .get ("vertical-align" )
204
200
if vertical_align :
205
201
return self .VERTICAL_MAP .get (vertical_align )
206
202
return None
207
203
208
- def _get_is_wrap_text (self , props : Mapping [str , str ]) -> Optional [ bool ] :
204
+ def _get_is_wrap_text (self , props : Mapping [str , str ]) -> bool | None :
209
205
if props .get ("white-space" ) is None :
210
206
return None
211
207
return bool (props ["white-space" ] not in ("nowrap" , "pre" , "pre-line" ))
212
208
213
209
def build_border (
214
210
self , props : Mapping [str , str ]
215
- ) -> Dict [str , Dict [str , Optional [ str ] ]]:
211
+ ) -> dict [str , dict [str , str | None ]]:
216
212
return {
217
213
side : {
218
214
"style" : self ._border_style (
@@ -224,7 +220,7 @@ def build_border(
224
220
for side in ["top" , "right" , "bottom" , "left" ]
225
221
}
226
222
227
- def _border_style (self , style : Optional [ str ] , width : Optional [ str ] ):
223
+ def _border_style (self , style : str | None , width : str | None ):
228
224
# convert styles and widths to openxml, one of:
229
225
# 'dashDot'
230
226
# 'dashDotDot'
@@ -263,7 +259,7 @@ def _border_style(self, style: Optional[str], width: Optional[str]):
263
259
return "dashed"
264
260
return "mediumDashed"
265
261
266
- def _get_width_name (self , width_input : Optional [ str ] ) -> Optional [ str ] :
262
+ def _get_width_name (self , width_input : str | None ) -> str | None :
267
263
width = self ._width_to_float (width_input )
268
264
if width < 1e-5 :
269
265
return None
@@ -273,7 +269,7 @@ def _get_width_name(self, width_input: Optional[str]) -> Optional[str]:
273
269
return "medium"
274
270
return "thick"
275
271
276
- def _width_to_float (self , width : Optional [ str ] ) -> float :
272
+ def _width_to_float (self , width : str | None ) -> float :
277
273
if width is None :
278
274
width = "2pt"
279
275
return self ._pt_to_float (width )
@@ -289,12 +285,12 @@ def build_fill(self, props: Mapping[str, str]):
289
285
if fill_color not in (None , "transparent" , "none" ):
290
286
return {"fgColor" : self .color_to_excel (fill_color ), "patternType" : "solid" }
291
287
292
- def build_number_format (self , props : Mapping [str , str ]) -> Dict [str , Optional [ str ] ]:
288
+ def build_number_format (self , props : Mapping [str , str ]) -> dict [str , str | None ]:
293
289
return {"format_code" : props .get ("number-format" )}
294
290
295
291
def build_font (
296
292
self , props : Mapping [str , str ]
297
- ) -> Dict [str , Optional [ Union [ bool , int , float , str ]] ]:
293
+ ) -> dict [str , bool | int | float | str | None ]:
298
294
font_names = self ._get_font_names (props )
299
295
decoration = self ._get_decoration (props )
300
296
return {
@@ -316,13 +312,13 @@ def build_font(
316
312
# 'condense': ,
317
313
}
318
314
319
- def _get_is_bold (self , props : Mapping [str , str ]) -> Optional [ bool ] :
315
+ def _get_is_bold (self , props : Mapping [str , str ]) -> bool | None :
320
316
weight = props .get ("font-weight" )
321
317
if weight :
322
318
return self .BOLD_MAP .get (weight )
323
319
return None
324
320
325
- def _get_is_italic (self , props : Mapping [str , str ]) -> Optional [ bool ] :
321
+ def _get_is_italic (self , props : Mapping [str , str ]) -> bool | None :
326
322
font_style = props .get ("font-style" )
327
323
if font_style :
328
324
return self .ITALIC_MAP .get (font_style )
@@ -335,12 +331,12 @@ def _get_decoration(self, props: Mapping[str, str]) -> Sequence[str]:
335
331
else :
336
332
return ()
337
333
338
- def _get_underline (self , decoration : Sequence [str ]) -> Optional [ str ] :
334
+ def _get_underline (self , decoration : Sequence [str ]) -> str | None :
339
335
if "underline" in decoration :
340
336
return "single"
341
337
return None
342
338
343
- def _get_shadow (self , props : Mapping [str , str ]) -> Optional [ bool ] :
339
+ def _get_shadow (self , props : Mapping [str , str ]) -> bool | None :
344
340
if "text-shadow" in props :
345
341
return bool (re .search ("^[^#(]*[1-9]" , props ["text-shadow" ]))
346
342
return None
@@ -371,13 +367,13 @@ def _get_font_names(self, props: Mapping[str, str]) -> Sequence[str]:
371
367
font_names .append (name )
372
368
return font_names
373
369
374
- def _get_font_size (self , props : Mapping [str , str ]) -> Optional [ float ] :
370
+ def _get_font_size (self , props : Mapping [str , str ]) -> float | None :
375
371
size = props .get ("font-size" )
376
372
if size is None :
377
373
return size
378
374
return self ._pt_to_float (size )
379
375
380
- def _select_font_family (self , font_names ) -> Optional [ int ] :
376
+ def _select_font_family (self , font_names ) -> int | None :
381
377
family = None
382
378
for name in font_names :
383
379
family = self .FAMILY_MAP .get (name )
@@ -386,7 +382,7 @@ def _select_font_family(self, font_names) -> Optional[int]:
386
382
387
383
return family
388
384
389
- def color_to_excel (self , val : Optional [ str ] ) -> Optional [ str ] :
385
+ def color_to_excel (self , val : str | None ) -> str | None :
390
386
if val is None :
391
387
return None
392
388
@@ -463,14 +459,14 @@ def __init__(
463
459
self ,
464
460
df ,
465
461
na_rep : str = "" ,
466
- float_format : Optional [ str ] = None ,
467
- cols : Optional [ Sequence [Hashable ]] = None ,
468
- header : Union [ Sequence [Hashable ], bool ] = True ,
462
+ float_format : str | None = None ,
463
+ cols : Sequence [Hashable ] | None = None ,
464
+ header : Sequence [Hashable ] | bool = True ,
469
465
index : bool = True ,
470
- index_label : Optional [ IndexLabel ] = None ,
466
+ index_label : IndexLabel | None = None ,
471
467
merge_cells : bool = False ,
472
468
inf_rep : str = "inf" ,
473
- style_converter : Optional [ Callable ] = None ,
469
+ style_converter : Callable | None = None ,
474
470
):
475
471
self .rowcounter = 0
476
472
self .na_rep = na_rep
0 commit comments