Skip to content

Commit e4353e7

Browse files
authored
refactor: Issue deprecation warnings for IO-functions (#368)
1 parent fd0386b commit e4353e7

7 files changed

Lines changed: 332 additions & 39 deletions

File tree

dataframely/_deprecation.py

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,58 @@
11
# Copyright (c) QuantCo 2025-2026
22
# SPDX-License-Identifier: BSD-3-Clause
33

4-
import os
5-
from collections.abc import Callable
6-
from functools import wraps
4+
from __future__ import annotations
75

8-
TRUTHY_VALUES = ["1", "true"]
6+
import sys
7+
import warnings
8+
from functools import wraps
9+
from typing import TYPE_CHECKING, TypeVar
910

11+
if TYPE_CHECKING:
12+
from collections.abc import Callable
13+
from typing import ParamSpec
1014

11-
def skip_if(env: str) -> Callable:
12-
"""Decorator to skip warnings based on environment variable.
15+
P = ParamSpec("P")
16+
T = TypeVar("T")
1317

14-
If the environment variable is equivalent to any of TRUTHY_VALUES, the wrapped
15-
function is skipped.
16-
"""
1718

18-
def decorator(fun: Callable) -> Callable:
19-
@wraps(fun)
20-
def wrapper() -> None:
21-
if os.getenv(env, "").lower() in TRUTHY_VALUES:
22-
return
23-
fun()
19+
def issue_deprecation_warning(message: str, *, version: str = "") -> None:
20+
"""Issue a deprecation warning pointing at the caller of the deprecated method.
2421
25-
return wrapper
22+
This must be called directly from the body of the deprecated (public) method so
23+
that the warning points at the user's code rather than at dataframely internals.
2624
27-
return decorator
25+
Args:
26+
message: The message associated with the warning.
27+
version: The dataframely version in which the deprecation occurred (if not
28+
already part of ``message``).
29+
"""
30+
if version:
31+
message = f"{message.strip()}\n(Deprecated in dataframely {version})"
32+
# `stacklevel=2` blames the caller of the deprecated method (one frame up from this
33+
# function). All call sites invoke this directly from the deprecated method body.
34+
warnings.warn(message, DeprecationWarning, stacklevel=2)
35+
36+
37+
if sys.version_info >= (3, 13):
38+
from warnings import deprecated
39+
else:
40+
try:
41+
from typing_extensions import deprecated
42+
except ImportError: # pragma: no cover
43+
44+
def deprecated( # type: ignore[no-redef]
45+
message: str,
46+
) -> Callable[[Callable[P, T]], Callable[P, T]]:
47+
"""Fallback for :func:`warnings.deprecated` without :pep:`702` support."""
48+
49+
def decorate(function: Callable[P, T]) -> Callable[P, T]:
50+
@wraps(function)
51+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
52+
issue_deprecation_warning(message)
53+
return function(*args, **kwargs)
54+
55+
wrapper.__deprecated__ = message # type: ignore[attr-defined]
56+
return wrapper
57+
58+
return decorate

dataframely/collection/collection.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import polars.exceptions as plexc
2929

3030
from dataframely._compat import deltalake
31+
from dataframely._deprecation import deprecated, issue_deprecation_warning
3132
from dataframely._filter import Filter
3233
from dataframely._native import format_rule_failures
3334
from dataframely._plugin import all_rules_required
@@ -63,6 +64,15 @@
6364

6465
_FILTER_COLUMN_PREFIX = "__DATAFRAMELY_FILTER_COLUMN__"
6566

67+
#: Deprecation message emitted when reading a collection with implicit validation, i.e.
68+
#: with any ``validation`` other than ``"skip"`` (see #367).
69+
_IMPLICIT_VALIDATION_DEPRECATION = (
70+
"Reading a collection with `validation != 'skip'` is deprecated. Starting with "
71+
"dataframely v3, data is read without inspecting schema metadata and without "
72+
"running validation. Pass `validation='skip'` to opt into the future behavior, or "
73+
"call `validate` explicitly if you require validation."
74+
)
75+
6676
P = ParamSpec("P")
6777
T = TypeVar("T")
6878

@@ -1061,7 +1071,16 @@ def read_parquet(
10611071
Attention:
10621072
Be aware that this method suffers from the same limitations as
10631073
:meth:`serialize`.
1074+
1075+
.. deprecated:: 3.0.0
1076+
Reading with `validation != "skip"` is deprecated. Starting with
1077+
dataframely v3, this method reads the data without inspecting any schema
1078+
metadata and without running validation. Pass `validation="skip"` to opt
1079+
into this behavior, or call :meth:`validate` explicitly if you require
1080+
validation.
10641081
"""
1082+
if validation != "skip":
1083+
issue_deprecation_warning(_IMPLICIT_VALIDATION_DEPRECATION)
10651084
return cls._read(
10661085
backend=ParquetStorageBackend(),
10671086
validation=validation,
@@ -1118,7 +1137,16 @@ def scan_parquet(
11181137
Attention:
11191138
Be aware that this method suffers from the same limitations as
11201139
:meth:`serialize`.
1140+
1141+
.. deprecated:: 3.0.0
1142+
Reading with `validation != "skip"` is deprecated. Starting with
1143+
dataframely v3, this method reads the data without inspecting any schema
1144+
metadata and without running validation. Pass `validation="skip"` to opt
1145+
into this behavior, or call :meth:`validate` explicitly if you require
1146+
validation.
11211147
"""
1148+
if validation != "skip":
1149+
issue_deprecation_warning(_IMPLICIT_VALIDATION_DEPRECATION)
11221150
return cls._read(
11231151
backend=ParquetStorageBackend(),
11241152
validation=validation,
@@ -1127,6 +1155,10 @@ def scan_parquet(
11271155
**kwargs,
11281156
)
11291157

1158+
@deprecated(
1159+
"`Collection.write_delta` is deprecated and will be removed in dataframely v3. "
1160+
"Write the individual members with `polars.DataFrame.write_delta` instead."
1161+
)
11301162
def write_delta(
11311163
self, target: str | Path | deltalake.DeltaTable, **kwargs: Any
11321164
) -> None:
@@ -1153,6 +1185,10 @@ def write_delta(
11531185
break your schema.
11541186
11551187
This method suffers from the same limitations as :meth:`~dataframely.Schema.serialize`.
1188+
1189+
.. deprecated:: 3.0.0
1190+
This method is deprecated and will be removed in dataframely v3. Write the
1191+
individual members with :meth:`polars.DataFrame.write_delta` instead.
11561192
"""
11571193
self._write(
11581194
backend=DeltaStorageBackend(),
@@ -1161,6 +1197,11 @@ def write_delta(
11611197
)
11621198

11631199
@classmethod
1200+
@deprecated(
1201+
"`Collection.scan_delta` is deprecated and will be removed in dataframely v3. "
1202+
"Read the individual members with `polars.scan_delta` and call `validate` "
1203+
"explicitly instead."
1204+
)
11641205
def scan_delta(
11651206
cls,
11661207
source: str | Path | deltalake.DeltaTable,
@@ -1214,6 +1255,11 @@ def scan_delta(
12141255
break your schema.
12151256
12161257
Be aware that this method suffers from the same limitations as :meth:`serialize`.
1258+
1259+
.. deprecated:: 3.0.0
1260+
This method is deprecated and will be removed in dataframely v3. Read the
1261+
individual members with :meth:`polars.scan_delta` and call :meth:`validate`
1262+
explicitly instead.
12171263
"""
12181264
return cls._read(
12191265
backend=DeltaStorageBackend(),
@@ -1223,6 +1269,11 @@ def scan_delta(
12231269
)
12241270

12251271
@classmethod
1272+
@deprecated(
1273+
"`Collection.read_delta` is deprecated and will be removed in dataframely v3. "
1274+
"Read the individual members with `polars.read_delta` and call `validate` "
1275+
"explicitly instead."
1276+
)
12261277
def read_delta(
12271278
cls,
12281279
source: str | Path | deltalake.DeltaTable,
@@ -1275,6 +1326,11 @@ def read_delta(
12751326
break your schema.
12761327
12771328
Be aware that this method suffers from the same limitations as :meth:`serialize`.
1329+
1330+
.. deprecated:: 3.0.0
1331+
This method is deprecated and will be removed in dataframely v3. Read the
1332+
individual members with :meth:`polars.read_delta` and call :meth:`validate`
1333+
explicitly instead.
12781334
"""
12791335
return cls._read(
12801336
backend=DeltaStorageBackend(),

dataframely/schema.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
from ._base_schema import ORIGINAL_COLUMN_PREFIX, BaseSchema
2020
from ._compat import PartitionSchemeOrSinkDirectory, deltalake, pa, pydantic, sa
21+
from ._deprecation import deprecated
2122
from ._match_to_schema import match_to_schema
2223
from ._native import format_rule_failures
2324
from ._plugin import all_rules, all_rules_horizontal, all_rules_required
@@ -897,6 +898,10 @@ def _as_dict(cls) -> dict[str, Any]:
897898
# ------------------------------------ PARQUET ----------------------------------- #
898899

899900
@classmethod
901+
@deprecated(
902+
"`Schema.write_parquet` is deprecated and will be removed in dataframely v3. "
903+
"Use `polars.DataFrame.write_parquet` directly instead."
904+
)
900905
def write_parquet(
901906
cls, df: DataFrame[Self], /, file: str | Path | IO[bytes], **kwargs: Any
902907
) -> None:
@@ -919,10 +924,18 @@ def write_parquet(
919924
Attention:
920925
Be aware that this method suffers from the same limitations as
921926
:meth:`serialize`.
927+
928+
.. deprecated:: 3.0.0
929+
This method is deprecated and will be removed in dataframely v3. Use
930+
:meth:`polars.DataFrame.write_parquet` directly instead.
922931
"""
923932
cls._write(df=df, backend=ParquetStorageBackend(), file=file, **kwargs)
924933

925934
@classmethod
935+
@deprecated(
936+
"`Schema.sink_parquet` is deprecated and will be removed in dataframely v3. "
937+
"Use `polars.LazyFrame.sink_parquet` directly instead."
938+
)
926939
def sink_parquet(
927940
cls,
928941
lf: LazyFrame[Self],
@@ -947,10 +960,18 @@ def sink_parquet(
947960
Attention:
948961
Be aware that this method suffers from the same limitations as
949962
:meth:`serialize`.
963+
964+
.. deprecated:: 3.0.0
965+
This method is deprecated and will be removed in dataframely v3. Use
966+
:meth:`polars.LazyFrame.sink_parquet` directly instead.
950967
"""
951968
cls._sink(lf=lf, backend=ParquetStorageBackend(), file=file, **kwargs)
952969

953970
@classmethod
971+
@deprecated(
972+
"`Schema.read_parquet` is deprecated and will be removed in dataframely v3. "
973+
"Use `polars.read_parquet` and call `validate` explicitly instead."
974+
)
954975
def read_parquet(
955976
cls,
956977
source: FileSource,
@@ -997,6 +1018,10 @@ def read_parquet(
9971018
Attention:
9981019
Be aware that this method suffers from the same limitations as
9991020
:meth:`serialize`.
1021+
1022+
.. deprecated:: 3.0.0
1023+
This method is deprecated and will be removed in dataframely v3. Use
1024+
:meth:`polars.read_parquet` and call :meth:`validate` explicitly instead.
10001025
"""
10011026
return cls._read(
10021027
ParquetStorageBackend(),
@@ -1007,6 +1032,10 @@ def read_parquet(
10071032
)
10081033

10091034
@classmethod
1035+
@deprecated(
1036+
"`Schema.scan_parquet` is deprecated and will be removed in dataframely v3. "
1037+
"Use `polars.scan_parquet` and call `validate` explicitly instead."
1038+
)
10101039
def scan_parquet(
10111040
cls,
10121041
source: FileSource,
@@ -1053,6 +1082,10 @@ def scan_parquet(
10531082
Attention:
10541083
Be aware that this method suffers from the same limitations as
10551084
:meth:`serialize`.
1085+
1086+
.. deprecated:: 3.0.0
1087+
This method is deprecated and will be removed in dataframely v3. Use
1088+
:meth:`polars.scan_parquet` and call :meth:`validate` explicitly instead.
10561089
"""
10571090
return cls._read(
10581091
ParquetStorageBackend(),
@@ -1099,6 +1132,10 @@ def _requires_validation_for_reading_parquet(
10991132

11001133
# --------------------------------- Delta -----------------------------------------#
11011134
@classmethod
1135+
@deprecated(
1136+
"`Schema.write_delta` is deprecated and will be removed in dataframely v3. "
1137+
"Use `polars.DataFrame.write_delta` directly instead."
1138+
)
11021139
def write_delta(
11031140
cls,
11041141
df: DataFrame[Self],
@@ -1127,6 +1164,10 @@ def write_delta(
11271164
in violation of group constraints that dataframely cannot catch
11281165
without re-validating. Only use appends if you are certain that they do not
11291166
break your schema.
1167+
1168+
.. deprecated:: 3.0.0
1169+
This method is deprecated and will be removed in dataframely v3. Use
1170+
:meth:`polars.DataFrame.write_delta` directly instead.
11301171
"""
11311172
DeltaStorageBackend().write_frame(
11321173
df=df,
@@ -1135,6 +1176,10 @@ def write_delta(
11351176
)
11361177

11371178
@classmethod
1179+
@deprecated(
1180+
"`Schema.scan_delta` is deprecated and will be removed in dataframely v3. "
1181+
"Use `polars.scan_delta` and call `validate` explicitly instead."
1182+
)
11381183
def scan_delta(
11391184
cls,
11401185
source: str | Path | deltalake.DeltaTable,
@@ -1182,6 +1227,10 @@ def scan_delta(
11821227
that are not through dataframely will result in losing the metadata.
11831228
11841229
This method suffers from the same limitations as :meth:`serialize`.
1230+
1231+
.. deprecated:: 3.0.0
1232+
This method is deprecated and will be removed in dataframely v3. Use
1233+
:meth:`polars.scan_delta` and call :meth:`validate` explicitly instead.
11851234
"""
11861235
return cls._read(
11871236
DeltaStorageBackend(),
@@ -1192,6 +1241,10 @@ def scan_delta(
11921241
)
11931242

11941243
@classmethod
1244+
@deprecated(
1245+
"`Schema.read_delta` is deprecated and will be removed in dataframely v3. "
1246+
"Use `polars.read_delta` and call `validate` explicitly instead."
1247+
)
11951248
def read_delta(
11961249
cls,
11971250
source: str | Path | deltalake.DeltaTable,
@@ -1244,6 +1297,10 @@ def read_delta(
12441297
break your schema.
12451298
12461299
This method suffers from the same limitations as :meth:`serialize`.
1300+
1301+
.. deprecated:: 3.0.0
1302+
This method is deprecated and will be removed in dataframely v3. Use
1303+
:meth:`polars.read_delta` and call :meth:`validate` explicitly instead.
12471304
"""
12481305
return cls._read(
12491306
DeltaStorageBackend(),

docs/guides/features/serialization.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
11
# Serialization
22

3+
```{warning}
4+
Most of the I/O functionality described on this page is **deprecated** and will be
5+
removed in dataframely v3 (see [#367](https://github.com/Quantco/dataframely/issues/367)).
6+
Calling any of these methods now emits a {class}`DeprecationWarning`. Specifically:
7+
8+
- All I/O methods on {class}`~dataframely.Schema`
9+
({meth}`~dataframely.Schema.write_parquet`, {meth}`~dataframely.Schema.sink_parquet`,
10+
{meth}`~dataframely.Schema.read_parquet`, {meth}`~dataframely.Schema.scan_parquet`,
11+
{meth}`~dataframely.Schema.write_delta`, {meth}`~dataframely.Schema.read_delta`,
12+
{meth}`~dataframely.Schema.scan_delta`) are deprecated. Use the corresponding
13+
`polars` functions directly and call {meth}`~dataframely.Schema.validate` explicitly
14+
where validation is required.
15+
- The `deltalake` I/O methods on {class}`~dataframely.Collection`
16+
({meth}`~dataframely.Collection.write_delta`, {meth}`~dataframely.Collection.read_delta`,
17+
{meth}`~dataframely.Collection.scan_delta`) are deprecated.
18+
- {meth}`~dataframely.Collection.read_parquet` and
19+
{meth}`~dataframely.Collection.scan_parquet` continue to exist, but reading with
20+
`validation != "skip"` is deprecated: metadata will no longer be inspected and
21+
validation will no longer run implicitly. Pass `validation="skip"` to opt into the
22+
future behavior, or call {meth}`~dataframely.Collection.validate` explicitly.
23+
```
24+
325
`dataframely` provides support for easily storing and reading validated data.
426
`polars` already provides native support for serializing data frames into different storage
527
backends. For the storage of the data itself, `dataframely` usually dispatches to polars-native

0 commit comments

Comments
 (0)