Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 48 additions & 17 deletions dataframely/_deprecation.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,58 @@
# Copyright (c) QuantCo 2025-2026
# SPDX-License-Identifier: BSD-3-Clause

import os
from collections.abc import Callable
from functools import wraps
from __future__ import annotations

TRUTHY_VALUES = ["1", "true"]
import sys
import warnings
from functools import wraps
from typing import TYPE_CHECKING, TypeVar

if TYPE_CHECKING:
from collections.abc import Callable
from typing import ParamSpec

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

If the environment variable is equivalent to any of TRUTHY_VALUES, the wrapped
function is skipped.
"""

def decorator(fun: Callable) -> Callable:
@wraps(fun)
def wrapper() -> None:
if os.getenv(env, "").lower() in TRUTHY_VALUES:
return
fun()
def issue_deprecation_warning(message: str, *, version: str = "") -> None:
"""Issue a deprecation warning pointing at the caller of the deprecated method.

return wrapper
This must be called directly from the body of the deprecated (public) method so
that the warning points at the user's code rather than at dataframely internals.

return decorator
Args:
message: The message associated with the warning.
version: The dataframely version in which the deprecation occurred (if not
already part of ``message``).
"""
if version:
message = f"{message.strip()}\n(Deprecated in dataframely {version})"
# `stacklevel=2` blames the caller of the deprecated method (one frame up from this
# function). All call sites invoke this directly from the deprecated method body.
warnings.warn(message, DeprecationWarning, stacklevel=2)


if sys.version_info >= (3, 13):
from warnings import deprecated
else:
try:
from typing_extensions import deprecated
except ImportError: # pragma: no cover

def deprecated( # type: ignore[no-redef]
message: str,
) -> Callable[[Callable[P, T]], Callable[P, T]]:
"""Fallback for :func:`warnings.deprecated` without :pep:`702` support."""

def decorate(function: Callable[P, T]) -> Callable[P, T]:
@wraps(function)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
issue_deprecation_warning(message)
return function(*args, **kwargs)

wrapper.__deprecated__ = message # type: ignore[attr-defined]
return wrapper

return decorate
56 changes: 56 additions & 0 deletions dataframely/collection/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import polars.exceptions as plexc

from dataframely._compat import deltalake
from dataframely._deprecation import deprecated, issue_deprecation_warning
from dataframely._filter import Filter
from dataframely._native import format_rule_failures
from dataframely._plugin import all_rules_required
Expand Down Expand Up @@ -63,6 +64,15 @@

_FILTER_COLUMN_PREFIX = "__DATAFRAMELY_FILTER_COLUMN__"

#: Deprecation message emitted when reading a collection with implicit validation, i.e.
#: with any ``validation`` other than ``"skip"`` (see #367).
_IMPLICIT_VALIDATION_DEPRECATION = (
"Reading a collection with `validation != 'skip'` is deprecated. Starting with "
"dataframely v3, data is read without inspecting schema metadata and without "
"running validation. Pass `validation='skip'` to opt into the future behavior, or "
"call `validate` explicitly if you require validation."
)

P = ParamSpec("P")
T = TypeVar("T")

Expand Down Expand Up @@ -1061,7 +1071,16 @@ def read_parquet(
Attention:
Be aware that this method suffers from the same limitations as
:meth:`serialize`.

.. deprecated:: 3.0.0
Reading with `validation != "skip"` is deprecated. Starting with
dataframely v3, this method reads the data without inspecting any schema
metadata and without running validation. Pass `validation="skip"` to opt
into this behavior, or call :meth:`validate` explicitly if you require
validation.
"""
if validation != "skip":
issue_deprecation_warning(_IMPLICIT_VALIDATION_DEPRECATION)
return cls._read(
backend=ParquetStorageBackend(),
validation=validation,
Expand Down Expand Up @@ -1118,7 +1137,16 @@ def scan_parquet(
Attention:
Be aware that this method suffers from the same limitations as
:meth:`serialize`.

.. deprecated:: 3.0.0
Reading with `validation != "skip"` is deprecated. Starting with
dataframely v3, this method reads the data without inspecting any schema
metadata and without running validation. Pass `validation="skip"` to opt
into this behavior, or call :meth:`validate` explicitly if you require
validation.
"""
if validation != "skip":
issue_deprecation_warning(_IMPLICIT_VALIDATION_DEPRECATION)
return cls._read(
backend=ParquetStorageBackend(),
validation=validation,
Expand All @@ -1127,6 +1155,10 @@ def scan_parquet(
**kwargs,
)

@deprecated(
"`Collection.write_delta` is deprecated and will be removed in dataframely v3. "
"Write the individual members with `polars.DataFrame.write_delta` instead."
)
def write_delta(
self, target: str | Path | deltalake.DeltaTable, **kwargs: Any
) -> None:
Expand All @@ -1153,6 +1185,10 @@ def write_delta(
break your schema.

This method suffers from the same limitations as :meth:`~dataframely.Schema.serialize`.

.. deprecated:: 3.0.0
This method is deprecated and will be removed in dataframely v3. Write the
individual members with :meth:`polars.DataFrame.write_delta` instead.
"""
self._write(
backend=DeltaStorageBackend(),
Expand All @@ -1161,6 +1197,11 @@ def write_delta(
)

@classmethod
@deprecated(
"`Collection.scan_delta` is deprecated and will be removed in dataframely v3. "
"Read the individual members with `polars.scan_delta` and call `validate` "
"explicitly instead."
)
def scan_delta(
cls,
source: str | Path | deltalake.DeltaTable,
Expand Down Expand Up @@ -1214,6 +1255,11 @@ def scan_delta(
break your schema.

Be aware that this method suffers from the same limitations as :meth:`serialize`.

.. deprecated:: 3.0.0
This method is deprecated and will be removed in dataframely v3. Read the
individual members with :meth:`polars.scan_delta` and call :meth:`validate`
explicitly instead.
"""
return cls._read(
backend=DeltaStorageBackend(),
Expand All @@ -1223,6 +1269,11 @@ def scan_delta(
)

@classmethod
@deprecated(
"`Collection.read_delta` is deprecated and will be removed in dataframely v3. "
"Read the individual members with `polars.read_delta` and call `validate` "
"explicitly instead."
)
def read_delta(
cls,
source: str | Path | deltalake.DeltaTable,
Expand Down Expand Up @@ -1275,6 +1326,11 @@ def read_delta(
break your schema.

Be aware that this method suffers from the same limitations as :meth:`serialize`.

.. deprecated:: 3.0.0
This method is deprecated and will be removed in dataframely v3. Read the
individual members with :meth:`polars.read_delta` and call :meth:`validate`
explicitly instead.
"""
return cls._read(
backend=DeltaStorageBackend(),
Expand Down
57 changes: 57 additions & 0 deletions dataframely/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

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

@classmethod
@deprecated(
"`Schema.write_parquet` is deprecated and will be removed in dataframely v3. "
"Use `polars.DataFrame.write_parquet` directly instead."
)
def write_parquet(
cls, df: DataFrame[Self], /, file: str | Path | IO[bytes], **kwargs: Any
) -> None:
Expand All @@ -919,10 +924,18 @@ def write_parquet(
Attention:
Be aware that this method suffers from the same limitations as
:meth:`serialize`.

.. deprecated:: 3.0.0
This method is deprecated and will be removed in dataframely v3. Use
:meth:`polars.DataFrame.write_parquet` directly instead.
"""
cls._write(df=df, backend=ParquetStorageBackend(), file=file, **kwargs)

@classmethod
@deprecated(
"`Schema.sink_parquet` is deprecated and will be removed in dataframely v3. "
"Use `polars.LazyFrame.sink_parquet` directly instead."
)
def sink_parquet(
cls,
lf: LazyFrame[Self],
Expand All @@ -947,10 +960,18 @@ def sink_parquet(
Attention:
Be aware that this method suffers from the same limitations as
:meth:`serialize`.

.. deprecated:: 3.0.0
This method is deprecated and will be removed in dataframely v3. Use
:meth:`polars.LazyFrame.sink_parquet` directly instead.
"""
cls._sink(lf=lf, backend=ParquetStorageBackend(), file=file, **kwargs)

@classmethod
@deprecated(
"`Schema.read_parquet` is deprecated and will be removed in dataframely v3. "
"Use `polars.read_parquet` and call `validate` explicitly instead."
)
def read_parquet(
cls,
source: FileSource,
Expand Down Expand Up @@ -997,6 +1018,10 @@ def read_parquet(
Attention:
Be aware that this method suffers from the same limitations as
:meth:`serialize`.

.. deprecated:: 3.0.0
This method is deprecated and will be removed in dataframely v3. Use
:meth:`polars.read_parquet` and call :meth:`validate` explicitly instead.
"""
return cls._read(
ParquetStorageBackend(),
Expand All @@ -1007,6 +1032,10 @@ def read_parquet(
)

@classmethod
@deprecated(
"`Schema.scan_parquet` is deprecated and will be removed in dataframely v3. "
"Use `polars.scan_parquet` and call `validate` explicitly instead."
)
def scan_parquet(
cls,
source: FileSource,
Expand Down Expand Up @@ -1053,6 +1082,10 @@ def scan_parquet(
Attention:
Be aware that this method suffers from the same limitations as
:meth:`serialize`.

.. deprecated:: 3.0.0
This method is deprecated and will be removed in dataframely v3. Use
:meth:`polars.scan_parquet` and call :meth:`validate` explicitly instead.
"""
return cls._read(
ParquetStorageBackend(),
Expand Down Expand Up @@ -1099,6 +1132,10 @@ def _requires_validation_for_reading_parquet(

# --------------------------------- Delta -----------------------------------------#
@classmethod
@deprecated(
"`Schema.write_delta` is deprecated and will be removed in dataframely v3. "
"Use `polars.DataFrame.write_delta` directly instead."
)
def write_delta(
cls,
df: DataFrame[Self],
Expand Down Expand Up @@ -1127,6 +1164,10 @@ def write_delta(
in violation of group constraints that dataframely cannot catch
without re-validating. Only use appends if you are certain that they do not
break your schema.

.. deprecated:: 3.0.0
This method is deprecated and will be removed in dataframely v3. Use
:meth:`polars.DataFrame.write_delta` directly instead.
"""
DeltaStorageBackend().write_frame(
df=df,
Expand All @@ -1135,6 +1176,10 @@ def write_delta(
)

@classmethod
@deprecated(
"`Schema.scan_delta` is deprecated and will be removed in dataframely v3. "
"Use `polars.scan_delta` and call `validate` explicitly instead."
)
def scan_delta(
cls,
source: str | Path | deltalake.DeltaTable,
Expand Down Expand Up @@ -1182,6 +1227,10 @@ def scan_delta(
that are not through dataframely will result in losing the metadata.

This method suffers from the same limitations as :meth:`serialize`.

.. deprecated:: 3.0.0
This method is deprecated and will be removed in dataframely v3. Use
:meth:`polars.scan_delta` and call :meth:`validate` explicitly instead.
"""
return cls._read(
DeltaStorageBackend(),
Expand All @@ -1192,6 +1241,10 @@ def scan_delta(
)

@classmethod
@deprecated(
"`Schema.read_delta` is deprecated and will be removed in dataframely v3. "
"Use `polars.read_delta` and call `validate` explicitly instead."
)
def read_delta(
cls,
source: str | Path | deltalake.DeltaTable,
Expand Down Expand Up @@ -1244,6 +1297,10 @@ def read_delta(
break your schema.

This method suffers from the same limitations as :meth:`serialize`.

.. deprecated:: 3.0.0
This method is deprecated and will be removed in dataframely v3. Use
:meth:`polars.read_delta` and call :meth:`validate` explicitly instead.
"""
return cls._read(
DeltaStorageBackend(),
Expand Down
22 changes: 22 additions & 0 deletions docs/guides/features/serialization.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# Serialization

```{warning}
Most of the I/O functionality described on this page is **deprecated** and will be
removed in dataframely v3 (see [#367](https://github.com/Quantco/dataframely/issues/367)).
Calling any of these methods now emits a {class}`DeprecationWarning`. Specifically:

- All I/O methods on {class}`~dataframely.Schema`
({meth}`~dataframely.Schema.write_parquet`, {meth}`~dataframely.Schema.sink_parquet`,
{meth}`~dataframely.Schema.read_parquet`, {meth}`~dataframely.Schema.scan_parquet`,
{meth}`~dataframely.Schema.write_delta`, {meth}`~dataframely.Schema.read_delta`,
{meth}`~dataframely.Schema.scan_delta`) are deprecated. Use the corresponding
`polars` functions directly and call {meth}`~dataframely.Schema.validate` explicitly
where validation is required.
- The `deltalake` I/O methods on {class}`~dataframely.Collection`
({meth}`~dataframely.Collection.write_delta`, {meth}`~dataframely.Collection.read_delta`,
{meth}`~dataframely.Collection.scan_delta`) are deprecated.
- {meth}`~dataframely.Collection.read_parquet` and
{meth}`~dataframely.Collection.scan_parquet` continue to exist, but reading with
`validation != "skip"` is deprecated: metadata will no longer be inspected and
validation will no longer run implicitly. Pass `validation="skip"` to opt into the
future behavior, or call {meth}`~dataframely.Collection.validate` explicitly.
```

`dataframely` provides support for easily storing and reading validated data.
`polars` already provides native support for serializing data frames into different storage
backends. For the storage of the data itself, `dataframely` usually dispatches to polars-native
Expand Down
Loading
Loading