From f991d2c272271f25873bdbad3daa009d323f5d97 Mon Sep 17 00:00:00 2001 From: phofl Date: Thu, 30 Dec 2021 20:11:46 +0100 Subject: [PATCH 1/7] REF: Deduplicate to_xml code --- pandas/io/formats/xml.py | 137 +++++++++-------------------- pandas/tests/io/xml/test_to_xml.py | 6 +- 2 files changed, 44 insertions(+), 99 deletions(-) diff --git a/pandas/io/formats/xml.py b/pandas/io/formats/xml.py index 1b11bb12757bb..bcb49f513be9f 100644 --- a/pandas/io/formats/xml.py +++ b/pandas/io/formats/xml.py @@ -97,7 +97,7 @@ def __init__( self, frame: DataFrame, path_or_buffer: FilePath | WriteBuffer[bytes] | None = None, - index: bool | None = True, + index: bool = True, root_name: str | None = "data", row_name: str | None = "row", na_rep: str | None = None, @@ -189,8 +189,8 @@ def process_dataframe(self) -> dict[int | str, dict[str, Any]]: if self.index: df = df.reset_index() - if self.na_rep: - df = df.replace({None: self.na_rep, float("nan"): self.na_rep}) + if self.na_rep is not None: + df = df.fillna(self.na_rep) return df.to_dict(orient="index") @@ -255,7 +255,27 @@ def build_attribs(self) -> None: works with tuples for multindex or hierarchical columns. """ - raise AbstractMethodError(self) + if not self.attr_cols: + return + + for col in self.attr_cols: + attr_name = self._get_flat_col_name(col) + try: + val = None if isna(self.d[col]) else str(self.d[col]) + if val is not None: + self.elem_row.attrib[attr_name] = val + except KeyError: + raise KeyError(f"no valid column, {col}") + + def _get_flat_col_name(self, col: str | tuple) -> str: + flat_col = col + if isinstance(col, tuple): + flat_col = ( + "".join([str(c) for c in col]).strip() + if "" in col + else "_".join([str(c) for c in col]).strip() + ) + return f"{self.prefix_uri}{flat_col}" def build_elems(self) -> None: """ @@ -267,6 +287,21 @@ def build_elems(self) -> None: raise AbstractMethodError(self) + def _build_elems(self, sub_element_cls) -> None: + + if not self.elem_cols: + return + + for col in self.elem_cols: + elem_name = self._get_flat_col_name(col) + try: + val = ( + None if isna(self.d[col]) or self.d[col] == "" else str(self.d[col]) + ) + sub_element_cls(self.elem_row, elem_name).text = val + except KeyError: + raise KeyError(f"no valid column, {col}") + def write_output(self) -> str | None: xml_doc = self.build_tree() @@ -357,56 +392,10 @@ def get_prefix_uri(self) -> str: return uri - def build_attribs(self) -> None: - if not self.attr_cols: - return - - for col in self.attr_cols: - flat_col = col - if isinstance(col, tuple): - flat_col = ( - "".join([str(c) for c in col]).strip() - if "" in col - else "_".join([str(c) for c in col]).strip() - ) - - attr_name = f"{self.prefix_uri}{flat_col}" - try: - val = ( - None - if self.d[col] is None or self.d[col] != self.d[col] - else str(self.d[col]) - ) - if val is not None: - self.elem_row.attrib[attr_name] = val - except KeyError: - raise KeyError(f"no valid column, {col}") - def build_elems(self) -> None: from xml.etree.ElementTree import SubElement - if not self.elem_cols: - return - - for col in self.elem_cols: - flat_col = col - if isinstance(col, tuple): - flat_col = ( - "".join([str(c) for c in col]).strip() - if "" in col - else "_".join([str(c) for c in col]).strip() - ) - - elem_name = f"{self.prefix_uri}{flat_col}" - try: - val = ( - None - if self.d[col] in [None, ""] or self.d[col] != self.d[col] - else str(self.d[col]) - ) - SubElement(self.elem_row, elem_name).text = val - except KeyError: - raise KeyError(f"no valid column, {col}") + self._build_elems(SubElement) def prettify_tree(self) -> bytes: """ @@ -529,54 +518,10 @@ def get_prefix_uri(self) -> str: return uri - def build_attribs(self) -> None: - if not self.attr_cols: - return - - for col in self.attr_cols: - flat_col = col - if isinstance(col, tuple): - flat_col = ( - "".join([str(c) for c in col]).strip() - if "" in col - else "_".join([str(c) for c in col]).strip() - ) - - attr_name = f"{self.prefix_uri}{flat_col}" - try: - val = ( - None - if self.d[col] is None or self.d[col] != self.d[col] - else str(self.d[col]) - ) - if val is not None: - self.elem_row.attrib[attr_name] = val - except KeyError: - raise KeyError(f"no valid column, {col}") - def build_elems(self) -> None: from lxml.etree import SubElement - if not self.elem_cols: - return - - for col in self.elem_cols: - flat_col = col - if isinstance(col, tuple): - flat_col = ( - "".join([str(c) for c in col]).strip() - if "" in col - else "_".join([str(c) for c in col]).strip() - ) - - elem_name = f"{self.prefix_uri}{flat_col}" - try: - val = ( - None if isna(self.d[col]) or self.d[col] == "" else str(self.d[col]) - ) - SubElement(self.elem_row, elem_name).text = val - except KeyError: - raise KeyError(f"no valid column, {col}") + self._build_elems(SubElement) def transform_doc(self) -> bytes: """ diff --git a/pandas/tests/io/xml/test_to_xml.py b/pandas/tests/io/xml/test_to_xml.py index c8828c08dba44..bb8914c56f599 100644 --- a/pandas/tests/io/xml/test_to_xml.py +++ b/pandas/tests/io/xml/test_to_xml.py @@ -1309,7 +1309,7 @@ def test_filename_and_suffix_comp(parser, compression_only): @td.skip_if_no("lxml") -def test_ea_dtypes(any_numeric_ea_dtype): +def test_ea_dtypes(any_numeric_ea_dtype, parser): # GH#43903 expected = """ @@ -1319,8 +1319,8 @@ def test_ea_dtypes(any_numeric_ea_dtype): """ df = DataFrame({"a": [NA]}).astype(any_numeric_ea_dtype) - result = df.to_xml() - assert result.strip() == expected + result = df.to_xml(parser=parser) + assert equalize_decl(result).strip() == expected def test_unsuported_compression(datapath, parser): From 97a6168e150bfa726cc25f053f824192259128af Mon Sep 17 00:00:00 2001 From: phofl Date: Thu, 30 Dec 2021 21:42:22 +0100 Subject: [PATCH 2/7] Refactor to fix mypy --- pandas/core/frame.py | 4 +-- pandas/io/formats/xml.py | 69 ++++++++++++++++------------------------ pandas/io/xml.py | 17 ++++++---- 3 files changed, 41 insertions(+), 49 deletions(-) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 252534a0cb790..21ce69a8838c7 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -2948,8 +2948,8 @@ def to_xml( root_name: str | None = "data", row_name: str | None = "row", na_rep: str | None = None, - attr_cols: str | list[str] | None = None, - elem_cols: str | list[str] | None = None, + attr_cols: list[str] | None = None, + elem_cols: list[str] | None = None, namespaces: dict[str | None, str] | None = None, prefix: str | None = None, encoding: str = "utf-8", diff --git a/pandas/io/formats/xml.py b/pandas/io/formats/xml.py index bcb49f513be9f..cb1d1abb9f2ec 100644 --- a/pandas/io/formats/xml.py +++ b/pandas/io/formats/xml.py @@ -10,7 +10,6 @@ from pandas._typing import ( CompressionOptions, FilePath, - ReadBuffer, StorageOptions, WriteBuffer, ) @@ -96,7 +95,7 @@ class BaseXMLFormatter: def __init__( self, frame: DataFrame, - path_or_buffer: FilePath | WriteBuffer[bytes] | None = None, + path_or_buffer: FilePath | WriteBuffer[bytes] | WriteBuffer[str] | None = None, index: bool = True, root_name: str | None = "data", row_name: str | None = "row", @@ -108,7 +107,7 @@ def __init__( encoding: str = "utf-8", xml_declaration: bool | None = True, pretty_print: bool | None = True, - stylesheet: FilePath | ReadBuffer[str] | None = None, + stylesheet: FilePath | WriteBuffer[bytes] | WriteBuffer[str] | None = None, compression: CompressionOptions = "infer", storage_options: StorageOptions = None, ) -> None: @@ -132,6 +131,11 @@ def __init__( self.orig_cols = self.frame.columns.tolist() self.frame_dicts = self.process_dataframe() + self.validate_columns() + self.validate_encoding() + self.prefix_uri = self.get_prefix_uri() + self.handle_indexes() + def build_tree(self) -> bytes: """ Build tree from data. @@ -247,7 +251,7 @@ def other_namespaces(self) -> dict: return nmsp_dict - def build_attribs(self) -> None: + def build_attribs(self, d: dict[str, Any], elem_row: Any) -> None: """ Create attributes of row. @@ -261,9 +265,9 @@ def build_attribs(self) -> None: for col in self.attr_cols: attr_name = self._get_flat_col_name(col) try: - val = None if isna(self.d[col]) else str(self.d[col]) + val = None if isna(d[col]) else str(d[col]) if val is not None: - self.elem_row.attrib[attr_name] = val + elem_row.attrib[attr_name] = val except KeyError: raise KeyError(f"no valid column, {col}") @@ -277,7 +281,7 @@ def _get_flat_col_name(self, col: str | tuple) -> str: ) return f"{self.prefix_uri}{flat_col}" - def build_elems(self) -> None: + def build_elems(self, d: dict[str, Any], elem_row: Any) -> None: """ Create child elements of row. @@ -287,7 +291,7 @@ def build_elems(self) -> None: raise AbstractMethodError(self) - def _build_elems(self, sub_element_cls) -> None: + def _build_elems(self, sub_element_cls, d: dict[str, Any], elem_row: Any) -> None: if not self.elem_cols: return @@ -295,10 +299,8 @@ def _build_elems(self, sub_element_cls) -> None: for col in self.elem_cols: elem_name = self._get_flat_col_name(col) try: - val = ( - None if isna(self.d[col]) or self.d[col] == "" else str(self.d[col]) - ) - sub_element_cls(self.elem_row, elem_name).text = val + val = None if isna(d[col]) or d[col] == "" else str(d[col]) + sub_element_cls(elem_row, elem_name).text = val except KeyError: raise KeyError(f"no valid column, {col}") @@ -326,14 +328,6 @@ class EtreeXMLFormatter(BaseXMLFormatter): modules: `xml.etree.ElementTree` and `xml.dom.minidom`. """ - def __init__(self, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) - - self.validate_columns() - self.validate_encoding() - self.handle_indexes() - self.prefix_uri = self.get_prefix_uri() - def build_tree(self) -> bytes: from xml.etree.ElementTree import ( Element, @@ -346,16 +340,15 @@ def build_tree(self) -> bytes: ) for d in self.frame_dicts.values(): - self.d = d - self.elem_row = SubElement(self.root, f"{self.prefix_uri}{self.row_name}") + elem_row = SubElement(self.root, f"{self.prefix_uri}{self.row_name}") if not self.attr_cols and not self.elem_cols: - self.elem_cols = list(self.d.keys()) - self.build_elems() + self.elem_cols = list(d.keys()) + self.build_elems(d, elem_row) else: - self.build_attribs() - self.build_elems() + self.build_attribs(d, elem_row) + self.build_elems(d, elem_row) self.out_xml = tostring(self.root, method="xml", encoding=self.encoding) @@ -392,10 +385,10 @@ def get_prefix_uri(self) -> str: return uri - def build_elems(self) -> None: + def build_elems(self, d: dict[str, Any], elem_row: Any) -> None: from xml.etree.ElementTree import SubElement - self._build_elems(SubElement) + self._build_elems(SubElement, d, elem_row) def prettify_tree(self) -> bytes: """ @@ -447,12 +440,7 @@ class LxmlXMLFormatter(BaseXMLFormatter): def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - self.validate_columns() - self.validate_encoding() - self.prefix_uri = self.get_prefix_uri() - self.convert_empty_str_key() - self.handle_indexes() def build_tree(self) -> bytes: """ @@ -470,16 +458,15 @@ def build_tree(self) -> bytes: self.root = Element(f"{self.prefix_uri}{self.root_name}", nsmap=self.namespaces) for d in self.frame_dicts.values(): - self.d = d - self.elem_row = SubElement(self.root, f"{self.prefix_uri}{self.row_name}") + elem_row = SubElement(self.root, f"{self.prefix_uri}{self.row_name}") if not self.attr_cols and not self.elem_cols: - self.elem_cols = list(self.d.keys()) - self.build_elems() + self.elem_cols = list(d.keys()) + self.build_elems(d, elem_row) else: - self.build_attribs() - self.build_elems() + self.build_attribs(d, elem_row) + self.build_elems(d, elem_row) self.out_xml = tostring( self.root, @@ -518,10 +505,10 @@ def get_prefix_uri(self) -> str: return uri - def build_elems(self) -> None: + def build_elems(self, d: dict[str, Any], elem_row: Any) -> None: from lxml.etree import SubElement - self._build_elems(SubElement) + self._build_elems(SubElement, d, elem_row) def transform_doc(self) -> bytes: """ diff --git a/pandas/io/xml.py b/pandas/io/xml.py index d72f02fa817ce..b856b7879f83f 100644 --- a/pandas/io/xml.py +++ b/pandas/io/xml.py @@ -11,6 +11,7 @@ FilePath, ReadBuffer, StorageOptions, + WriteBuffer, ) from pandas.compat._optional import import_optional_dependency from pandas.errors import ( @@ -569,11 +570,18 @@ def _transform_doc(self) -> bytes: def get_data_from_filepath( - filepath_or_buffer: FilePath | bytes | ReadBuffer[bytes] | ReadBuffer[str], + filepath_or_buffer: FilePath + | bytes + | ReadBuffer[bytes] + | ReadBuffer[str] + | WriteBuffer[bytes] + | WriteBuffer[str], encoding, compression: CompressionOptions, storage_options: StorageOptions, -) -> str | bytes | ReadBuffer[bytes] | ReadBuffer[str]: +) -> str | bytes | ReadBuffer[bytes] | ReadBuffer[str] | WriteBuffer[ + bytes +] | WriteBuffer[str]: """ Extract raw XML data. @@ -605,10 +613,7 @@ def get_data_from_filepath( storage_options=storage_options, ) as handle_obj: filepath_or_buffer = ( - # error: Incompatible types in assignment (expression has type - # "Union[str, IO[str]]", variable has type "Union[Union[str, - # PathLike[str]], bytes, ReadBuffer[bytes], ReadBuffer[str]]") - handle_obj.handle.read() # type: ignore[assignment] + handle_obj.handle.read() if hasattr(handle_obj.handle, "read") else handle_obj.handle ) From 97b90f1f8675b522f7bc76f0cd056da52668075d Mon Sep 17 00:00:00 2001 From: phofl Date: Thu, 30 Dec 2021 23:19:42 +0100 Subject: [PATCH 3/7] Return value --- pandas/io/formats/xml.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pandas/io/formats/xml.py b/pandas/io/formats/xml.py index cb1d1abb9f2ec..456132b28e2c9 100644 --- a/pandas/io/formats/xml.py +++ b/pandas/io/formats/xml.py @@ -251,7 +251,7 @@ def other_namespaces(self) -> dict: return nmsp_dict - def build_attribs(self, d: dict[str, Any], elem_row: Any) -> None: + def build_attribs(self, d: dict[str, Any], elem_row: Any) -> Any: """ Create attributes of row. @@ -270,6 +270,7 @@ def build_attribs(self, d: dict[str, Any], elem_row: Any) -> None: elem_row.attrib[attr_name] = val except KeyError: raise KeyError(f"no valid column, {col}") + return elem_row def _get_flat_col_name(self, col: str | tuple) -> str: flat_col = col @@ -347,7 +348,10 @@ def build_tree(self) -> bytes: self.build_elems(d, elem_row) else: - self.build_attribs(d, elem_row) + build_elem_row = self.build_attribs(d, elem_row) + if build_elem_row is not None: + elem_row = build_elem_row + self.build_elems(d, elem_row) self.out_xml = tostring(self.root, method="xml", encoding=self.encoding) @@ -465,7 +469,10 @@ def build_tree(self) -> bytes: self.build_elems(d, elem_row) else: - self.build_attribs(d, elem_row) + build_elem_row = self.build_attribs(d, elem_row) + if build_elem_row is not None: + elem_row = build_elem_row + self.build_elems(d, elem_row) self.out_xml = tostring( From 8e2d8993b1c7b323496522961398949f6ad8160b Mon Sep 17 00:00:00 2001 From: phofl Date: Thu, 30 Dec 2021 23:20:29 +0100 Subject: [PATCH 4/7] Return value --- pandas/io/formats/xml.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/pandas/io/formats/xml.py b/pandas/io/formats/xml.py index 456132b28e2c9..c5e1b331cf00a 100644 --- a/pandas/io/formats/xml.py +++ b/pandas/io/formats/xml.py @@ -260,7 +260,7 @@ def build_attribs(self, d: dict[str, Any], elem_row: Any) -> Any: """ if not self.attr_cols: - return + return elem_row for col in self.attr_cols: attr_name = self._get_flat_col_name(col) @@ -348,10 +348,7 @@ def build_tree(self) -> bytes: self.build_elems(d, elem_row) else: - build_elem_row = self.build_attribs(d, elem_row) - if build_elem_row is not None: - elem_row = build_elem_row - + elem_row = self.build_attribs(d, elem_row) self.build_elems(d, elem_row) self.out_xml = tostring(self.root, method="xml", encoding=self.encoding) @@ -469,10 +466,7 @@ def build_tree(self) -> bytes: self.build_elems(d, elem_row) else: - build_elem_row = self.build_attribs(d, elem_row) - if build_elem_row is not None: - elem_row = build_elem_row - + elem_row = self.build_attribs(d, elem_row) self.build_elems(d, elem_row) self.out_xml = tostring( From 2991eec9e3973df4bd1e5944518ae20c5edd3a10 Mon Sep 17 00:00:00 2001 From: phofl Date: Fri, 31 Dec 2021 15:21:52 +0100 Subject: [PATCH 5/7] Adjust type hints --- pandas/core/frame.py | 3 ++- pandas/io/formats/xml.py | 3 ++- pandas/io/xml.py | 17 ++++++----------- pandas/tests/io/xml/test_to_xml.py | 1 - 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 21ce69a8838c7..91f748f2f7d0b 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -63,6 +63,7 @@ IndexLabel, Level, PythonFuncType, + ReadBuffer, Renamer, Scalar, StorageOptions, @@ -2956,7 +2957,7 @@ def to_xml( xml_declaration: bool | None = True, pretty_print: bool | None = True, parser: str | None = "lxml", - stylesheet: FilePath | WriteBuffer[bytes] | WriteBuffer[str] | None = None, + stylesheet: FilePath | ReadBuffer[str] | None = None, compression: CompressionOptions = "infer", storage_options: StorageOptions = None, ) -> str | None: diff --git a/pandas/io/formats/xml.py b/pandas/io/formats/xml.py index c5e1b331cf00a..a9c029a493da5 100644 --- a/pandas/io/formats/xml.py +++ b/pandas/io/formats/xml.py @@ -10,6 +10,7 @@ from pandas._typing import ( CompressionOptions, FilePath, + ReadBuffer, StorageOptions, WriteBuffer, ) @@ -107,7 +108,7 @@ def __init__( encoding: str = "utf-8", xml_declaration: bool | None = True, pretty_print: bool | None = True, - stylesheet: FilePath | WriteBuffer[bytes] | WriteBuffer[str] | None = None, + stylesheet: FilePath | ReadBuffer[str] | None = None, compression: CompressionOptions = "infer", storage_options: StorageOptions = None, ) -> None: diff --git a/pandas/io/xml.py b/pandas/io/xml.py index b856b7879f83f..d72f02fa817ce 100644 --- a/pandas/io/xml.py +++ b/pandas/io/xml.py @@ -11,7 +11,6 @@ FilePath, ReadBuffer, StorageOptions, - WriteBuffer, ) from pandas.compat._optional import import_optional_dependency from pandas.errors import ( @@ -570,18 +569,11 @@ def _transform_doc(self) -> bytes: def get_data_from_filepath( - filepath_or_buffer: FilePath - | bytes - | ReadBuffer[bytes] - | ReadBuffer[str] - | WriteBuffer[bytes] - | WriteBuffer[str], + filepath_or_buffer: FilePath | bytes | ReadBuffer[bytes] | ReadBuffer[str], encoding, compression: CompressionOptions, storage_options: StorageOptions, -) -> str | bytes | ReadBuffer[bytes] | ReadBuffer[str] | WriteBuffer[ - bytes -] | WriteBuffer[str]: +) -> str | bytes | ReadBuffer[bytes] | ReadBuffer[str]: """ Extract raw XML data. @@ -613,7 +605,10 @@ def get_data_from_filepath( storage_options=storage_options, ) as handle_obj: filepath_or_buffer = ( - handle_obj.handle.read() + # error: Incompatible types in assignment (expression has type + # "Union[str, IO[str]]", variable has type "Union[Union[str, + # PathLike[str]], bytes, ReadBuffer[bytes], ReadBuffer[str]]") + handle_obj.handle.read() # type: ignore[assignment] if hasattr(handle_obj.handle, "read") else handle_obj.handle ) diff --git a/pandas/tests/io/xml/test_to_xml.py b/pandas/tests/io/xml/test_to_xml.py index bb8914c56f599..aeec163ed134a 100644 --- a/pandas/tests/io/xml/test_to_xml.py +++ b/pandas/tests/io/xml/test_to_xml.py @@ -1308,7 +1308,6 @@ def test_filename_and_suffix_comp(parser, compression_only): assert geom_xml == output.strip() -@td.skip_if_no("lxml") def test_ea_dtypes(any_numeric_ea_dtype, parser): # GH#43903 expected = """ From 1632c83927d95e79c208a844c1a5fa06373e1430 Mon Sep 17 00:00:00 2001 From: phofl Date: Fri, 31 Dec 2021 22:39:24 +0100 Subject: [PATCH 6/7] Adjust code --- pandas/io/formats/xml.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pandas/io/formats/xml.py b/pandas/io/formats/xml.py index a9c029a493da5..a34c25111c64d 100644 --- a/pandas/io/formats/xml.py +++ b/pandas/io/formats/xml.py @@ -266,9 +266,8 @@ def build_attribs(self, d: dict[str, Any], elem_row: Any) -> Any: for col in self.attr_cols: attr_name = self._get_flat_col_name(col) try: - val = None if isna(d[col]) else str(d[col]) - if val is not None: - elem_row.attrib[attr_name] = val + if not isna(d[col]): + elem_row.attrib[attr_name] = str(d[col]) except KeyError: raise KeyError(f"no valid column, {col}") return elem_row From aac9d31454399c141b94fd246150171c46bbfe19 Mon Sep 17 00:00:00 2001 From: phofl Date: Sat, 1 Jan 2022 00:11:50 +0100 Subject: [PATCH 7/7] Add hint --- pandas/core/frame.py | 2 +- pandas/io/formats/xml.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 91f748f2f7d0b..5b76e6ad29321 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -2957,7 +2957,7 @@ def to_xml( xml_declaration: bool | None = True, pretty_print: bool | None = True, parser: str | None = "lxml", - stylesheet: FilePath | ReadBuffer[str] | None = None, + stylesheet: FilePath | ReadBuffer[str] | ReadBuffer[bytes] | None = None, compression: CompressionOptions = "infer", storage_options: StorageOptions = None, ) -> str | None: diff --git a/pandas/io/formats/xml.py b/pandas/io/formats/xml.py index a34c25111c64d..aa69792cb1db0 100644 --- a/pandas/io/formats/xml.py +++ b/pandas/io/formats/xml.py @@ -108,7 +108,7 @@ def __init__( encoding: str = "utf-8", xml_declaration: bool | None = True, pretty_print: bool | None = True, - stylesheet: FilePath | ReadBuffer[str] | None = None, + stylesheet: FilePath | ReadBuffer[str] | ReadBuffer[bytes] | None = None, compression: CompressionOptions = "infer", storage_options: StorageOptions = None, ) -> None: