Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(python): Add UnstableWarning for unstable functionality #13948

Merged
merged 13 commits into from
Jan 26, 2024
16 changes: 16 additions & 0 deletions docs/development/versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,22 @@ Examples of changes that are _not_ considered breaking:

Bug fixes are not considered a breaking change, even though it may impact some users' [workflows](https://xkcd.com/1172/).

### Unstable functionality

Some parts of the public API are marked as **unstable**.
You can recognize this functionality from the warning in the API reference, or from the warning issued when the configuration option `warn_unstable` is active.
There are a number of reasons functionality may be marked as unstable:

- We are unsure about the exact API. The name, function signature, or implementation are likely to change in the future.
- The functionality is not tested extensively yet. Bugs may pop up when used in real-world scenarios.
- The functionality does not integrate well with the full Polars API. You may find it works in one context but not in another.

Releasing functionality as unstable allows us to gather important feedback from users that use Polars in real-world scenarios.
This helps us fine-tune things before giving it the final stamp of approval.
Users are only interested in solid, well-tested functionality can avoid this part of the API.

Functionality marked as unstable may change at any point without it being considered a breaking change.

### Deprecation warnings

If we decide to introduce a breaking change, the existing behavior is deprecated _if possible_.
Expand Down
4 changes: 3 additions & 1 deletion py-polars/polars/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
SchemaFieldNotFoundError,
ShapeError,
StructFieldNotFoundError,
UnstableWarning,
)
from polars.expr import Expr
from polars.functions import (
Expand Down Expand Up @@ -221,7 +222,6 @@
"ArrowError",
"ColumnNotFoundError",
"ComputeError",
"ChronoFormatWarning",
"DuplicateError",
"InvalidOperationError",
"NoDataError",
Expand All @@ -235,6 +235,8 @@
# warnings
"PolarsWarning",
"CategoricalRemappingWarning",
"ChronoFormatWarning",
"UnstableWarning",
# core classes
"DataFrame",
"Expr",
Expand Down
25 changes: 23 additions & 2 deletions py-polars/polars/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@


# note: register all Config-specific environment variable names here; need to constrain
# which 'POLARS_' environment variables are recognised, as there are other lower-level
# and/or experimental settings that should not be saved or reset with the Config vars.
# which 'POLARS_' environment variables are recognized, as there are other lower-level
# and/or unstable settings that should not be saved or reset with the Config vars.
_POLARS_CFG_ENV_VARS = {
"POLARS_WARN_UNSTABLE",
"POLARS_ACTIVATE_DECIMAL",
"POLARS_AUTO_STRUCTIFY",
"POLARS_FMT_MAX_COLS",
Expand Down Expand Up @@ -1260,3 +1261,23 @@ def set_verbose(cls, active: bool | None = True) -> type[Config]:
else:
os.environ["POLARS_VERBOSE"] = str(int(active))
return cls

@classmethod
def warn_unstable(cls, active: bool | None = True) -> type[Config]:
"""
Issue a warning when unstable functionality is used.

Enabling this setting may help avoid functionality that is still evolving,
potentially reducing maintenance burden from API changes and bugs.

Examples
--------
>>> pl.Config.warn_unstable(True) # doctest: +SKIP
>>> pl.col("a").qcut(5) # doctest: +SKIP
UnstableWarning: `qcut` is considered unstable. It may be changed at any point without it being considered a breaking change.
""" # noqa: W505
if active is None:
os.environ.pop("POLARS_WARN_UNSTABLE", None)
else:
os.environ["POLARS_WARN_UNSTABLE"] = str(int(active))
return cls
29 changes: 19 additions & 10 deletions py-polars/polars/dataframe/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
deprecate_saturating,
issue_deprecation_warning,
)
from polars.utils.unstable import issue_unstable_warning, unstable
from polars.utils.various import (
_prepare_row_index_args,
_process_null_values,
Expand Down Expand Up @@ -3272,11 +3273,13 @@ def write_ipc(
compression : {'uncompressed', 'lz4', 'zstd'}
Compression method. Defaults to "uncompressed".
future
WARNING: this argument is unstable and will be removed without it being
considered a breaking change.
Setting this to `True` will write polars' internal data-structures that
Setting this to `True` will write Polars' internal data structures that
might not be available by other Arrow implementations.

.. warning::
This functionality is considered **unstable**. It may be changed
at any point without it being considered a breaking change.

Examples
--------
>>> import pathlib
Expand All @@ -3300,6 +3303,11 @@ def write_ipc(
if compression is None:
compression = "uncompressed"

if future:
issue_unstable_warning(
"The `future` parameter of `DataFrame.write_ipc` is considered unstable."
)

self._df.write_ipc(file, compression, future)
return file if return_bytes else None # type: ignore[return-value]

Expand Down Expand Up @@ -7549,6 +7557,7 @@ def melt(
self._df.melt(id_vars, value_vars, value_name, variable_name)
)

@unstable()
def unstack(
self,
step: int,
Expand All @@ -7559,12 +7568,11 @@ def unstack(
"""
Unstack a long table to a wide form without doing an aggregation.

This can be much faster than a pivot, because it can skip the grouping phase.
.. warning::
This functionality is considered **unstable**. It may be changed
at any point without it being considered a breaking change.

Warnings
--------
This functionality is experimental and may be subject to changes
without it being considered a breaking change.
This can be much faster than a pivot, because it can skip the grouping phase.

Parameters
----------
Expand Down Expand Up @@ -10389,6 +10397,7 @@ def set_sorted(
.collect(_eager=True)
)

@unstable()
def update(
self,
other: DataFrame,
Expand All @@ -10403,8 +10412,8 @@ def update(
Update the values in this `DataFrame` with the values in `other`.

.. warning::
This functionality is experimental and may change without it being
considered a breaking change.
This functionality is considered **unstable**. It may be changed
at any point without it being considered a breaking change.

By default, null values in the right frame are ignored. Use
`include_nulls=False` to overwrite values in this frame with
Expand Down
26 changes: 24 additions & 2 deletions py-polars/polars/datatypes/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,9 @@ class Decimal(NumericType):
Decimal 128-bit type with an optional precision and non-negative scale.

.. warning::
This is an experimental work-in-progress feature and may not work as expected.
This functionality is considered **unstable**.
It is a work-in-progress feature and may not always work as expected.
It may be changed at any point without it being considered a breaking change.
"""

precision: int | None
Expand All @@ -348,6 +350,15 @@ def __init__(
precision: int | None = None,
scale: int = 0,
):
# Issuing the warning on `__init__` does not trigger when the class is used
# without being instantiated, but it's better than nothing
from polars.utils.unstable import issue_unstable_warning

issue_unstable_warning(
"The Decimal data type is considered unstable."
" It is a work-in-progress feature and may not always work as expected."
)

self.precision = precision
self.scale = scale

Expand Down Expand Up @@ -528,7 +539,9 @@ class Enum(DataType):
A fixed set categorical encoding of a set of strings.

.. warning::
This is an experimental work-in-progress feature and may not work as expected.
This functionality is considered **unstable**.
It is a work-in-progress feature and may not always work as expected.
It may be changed at any point without it being considered a breaking change.
"""

categories: Series
Expand All @@ -542,6 +555,15 @@ def __init__(self, categories: Series | Iterable[str]):
categories
Valid categories in the dataset.
"""
# Issuing the warning on `__init__` does not trigger when the class is used
# without being instantiated, but it's better than nothing
from polars.utils.unstable import issue_unstable_warning

issue_unstable_warning(
"The Enum data type is considered unstable."
" It is a work-in-progress feature and may not always work as expected."
)

if not isinstance(categories, pl.Series):
categories = pl.Series(values=categories)

Expand Down
10 changes: 7 additions & 3 deletions py-polars/polars/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class UnsuitableSQLError(PolarsError): # type: ignore[misc]

class ChronoFormatWarning(PolarsWarning): # type: ignore[misc]
"""
Warning raised when a chrono format string contains dubious patterns.
Warning issued when a chrono format string contains dubious patterns.

Polars uses Rust's chrono crate to convert between string data and temporal data.
The patterns used by chrono differ slightly from Python's built-in datetime module.
Expand All @@ -106,11 +106,15 @@ class ChronoFormatWarning(PolarsWarning): # type: ignore[misc]


class PolarsInefficientMapWarning(PolarsWarning): # type: ignore[misc]
"""Warning raised when a potentially slow `map_*` operation is performed."""
"""Warning issued when a potentially slow `map_*` operation is performed."""


class TimeZoneAwareConstructorWarning(PolarsWarning): # type: ignore[misc]
"""Warning raised when constructing Series from non-UTC time-zone-aware inputs."""
"""Warning issued when constructing Series from non-UTC time-zone-aware inputs."""


class UnstableWarning(PolarsWarning): # type: ignore[misc]
"""Warning issued when unstable functionality is used."""


class ArrowError(Exception):
Expand Down
22 changes: 11 additions & 11 deletions py-polars/polars/expr/datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
issue_deprecation_warning,
rename_use_earliest_to_ambiguous,
)
from polars.utils.unstable import unstable

if TYPE_CHECKING:
from datetime import timedelta
Expand Down Expand Up @@ -206,6 +207,7 @@ def truncate(
)
)

@unstable()
def round(
self,
every: str | timedelta,
Expand All @@ -216,6 +218,10 @@ def round(
"""
Divide the date/datetime range into buckets.

.. warning::
This functionality is considered **unstable**. It may be changed
at any point without it being considered a breaking change.

Each date/datetime in the first half of the interval
is mapped to the start of its bucket.
Each date/datetime in the second half of the interval
Expand All @@ -241,6 +247,11 @@ def round(
.. deprecated: 0.19.3
This is now auto-inferred, you can safely remove this argument.

Returns
-------
Expr
Expression of data type :class:`Date` or :class:`Datetime`.

Notes
-----
The `every` and `offset` argument are created with the
Expand All @@ -260,21 +271,10 @@ def round(

eg: 3d12h4m25s # 3 days, 12 hours, 4 minutes, and 25 seconds


By "calendar day", we mean the corresponding time on the next day (which may
not be 24 hours, due to daylight savings). Similarly for "calendar week",
"calendar month", "calendar quarter", and "calendar year".

Returns
-------
Expr
Expression of data type :class:`Date` or :class:`Datetime`.

Warnings
--------
This functionality is currently experimental and may
change without it being considered a breaking change.

Examples
--------
>>> from datetime import timedelta, datetime
Expand Down
Loading