pandas-dev/pandas · #71328
ENH: warn when to_json/to_xml compression is ignored without a path (GH#32326)
doc/source/whatsnew/v3.2.0.rst1 + / 0 −
@@ -32,6 +32,7 @@ Other enhancements - :func:`read_csv` with the ``"c"`` and ``"python"`` engines now accepts a :class:`DatetimeTZDtype` in ``dtype`` (:issue:`24542`) - :meth:`.DataFrameGroupBy.rank` and :meth:`.SeriesGroupBy.rank` now accept a ``numeric_only`` argument, matching :meth:`DataFrame.rank` (:issue:`44438`) - :meth:`DataFrame.to_csv` and other writers now accept an ``os.PathLike`` such as :class:`pathlib.Path` for the ``archive_name`` entry of a zip or tar ``compression`` dict (:issue:`31934`)+- :meth:`DataFrame.to_json`, :meth:`Series.to_json` and :meth:`DataFrame.to_xml` now warn when a compression method is requested but no path is given, since the returned string is not compressed, and raise for an unrecognized ``compression`` as they do with a path (:issue:`32326`) - :meth:`Series.groupby` now accepts ``as_index=False``, returning aggregation results as a :class:`DataFrame` with the group labels as columns (:issue:`36507`) - Added :meth:`Categorical.to_dense` and :meth:`Series.cat.to_dense` to get the values with the dtype of the categories, including extension dtypes (:issue:`26410`) - Added :meth:`DatetimeIndex.set_freq` and :meth:`TimedeltaIndex.set_freq`, which return a new index with the given ``freq`` instead of modifying it in place (:issue:`61094`)pandas/io/common.py25 + / 6 −
@@ -354,11 +354,9 @@ def _get_filepath_or_buffer( compression_method = infer_compression(filepath_or_buffer, compression_method) # GH21227 internal compression is not used for non-binary handles.- if compression_method and hasattr(filepath_or_buffer, "write") and "b" not in mode:- warnings.warn(- "compression has no effect when passing a non-binary object as input.",- RuntimeWarning,- stacklevel=find_stack_level(),+ if hasattr(filepath_or_buffer, "write") and "b" not in mode:+ warn_unused_compression(+ compression_method, "passing a non-binary object as input" ) compression_method = None @@ -554,7 +552,7 @@ def file_path_to_url(path: str) -> str: def get_compression_method(- compression: CompressionOptions,+ compression: str | CompressionDict | None, ) -> tuple[str | None, CompressionDict]: """ Simplifies a compression argument to a compression method string and@@ -659,6 +657,27 @@ def infer_compression( raise ValueError(msg) +def warn_unused_compression(+ compression: str | CompressionDict | None,+ reason: str = "no path or buffer is given; the returned string is not compressed",+) -> None:+ """+ Warn that ``compression`` has no effect because of ``reason``.++ An unrecognized compression method still raises, as it would if it were used.+ """+ compression_method, _ = get_compression_method(compression)+ if compression_method is None or compression_method == "infer":+ return+ # the path is only consulted for "infer"; here this only validates the method+ infer_compression("", compression_method)+ warnings.warn(+ f"compression has no effect when {reason}.",+ RuntimeWarning,+ stacklevel=find_stack_level(),+ )++ def check_parent_directory(path: Path | str) -> None: """ Check if parent directory of a file exists, raise OSError if it does notpandas/io/formats/xml.py5 + / 1 −
@@ -18,7 +18,10 @@ from pandas.core.dtypes.common import is_list_like from pandas.core.dtypes.missing import isna -from pandas.io.common import get_handle+from pandas.io.common import (+ get_handle,+ warn_unused_compression,+) from pandas.io.xml import get_data_from_filepath if TYPE_CHECKING:@@ -354,6 +357,7 @@ def write_output(self) -> str | None: return None else:+ warn_unused_compression(self.compression) return xml_doc.decode(self.encoding).rstrip() pandas/io/json/_json.py2 + / 0 −
@@ -67,6 +67,7 @@ get_handle, is_potential_multi_index, stringify_path,+ warn_unused_compression, ) from pandas.io.json._normalize import convert_to_line_delimits from pandas.io.json._table_schema import (@@ -269,6 +270,7 @@ def to_json( ) as handles: handles.handle.write(s) else:+ warn_unused_compression(compression) return s return None pandas/tests/io/json/test_compression.py16 + / 0 −
@@ -81,6 +81,22 @@ def test_write_unsupported_compression_type(temp_file): df.to_json(temp_file, compression="unsupported") +def test_to_json_compression_without_path():+ # GH#32326+ df = pd.DataFrame({"A": [1]})+ msg = "compression has no effect when no path or buffer is given"+ with tm.assert_produces_warning(RuntimeWarning, match=msg):+ result = df.to_json(compression="gzip")+ assert result == df.to_json()+ with tm.assert_produces_warning(RuntimeWarning, match=msg):+ df["A"].to_json(compression={"method": "gzip", "mtime": 1})+ with tm.assert_produces_warning(None):+ df.to_json(compression="infer")+ df.to_json(compression=None)+ with pytest.raises(ValueError, match="Unrecognized compression type: unsupported"):+ df.to_json(compression="unsupported")++ def test_read_unsupported_compression_type(temp_file): msg = "Unrecognized compression type: unsupported" with pytest.raises(ValueError, match=msg):pandas/tests/io/xml/test_to_xml.py10 + / 0 −
@@ -1375,6 +1375,16 @@ def test_unsupported_compression(parser, geom_df, temp_file): geom_df.to_xml(path, parser=parser, compression="7z") +def test_compression_without_path(parser, geom_df):+ # GH#32326+ msg = "compression has no effect when no path or buffer is given"+ with tm.assert_produces_warning(RuntimeWarning, match=msg):+ result = geom_df.to_xml(parser=parser, compression="gzip")+ assert result == geom_df.to_xml(parser=parser)+ with pytest.raises(ValueError, match="Unrecognized compression type"):+ geom_df.to_xml(parser=parser, compression="7z")++ # STORAGE OPTIONS