[
https://issues.apache.org/jira/browse/FLINK-40529?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Deepyaman Datta updated FLINK-40529:
------------------------------------
Release Note:
PyFlink's `@Deprecated` decorator now emits its `DeprecationWarning` when a
deprecated API is used, rather than when the module defining it is imported.
Importing `pyflink.table` (or any other PyFlink package) no longer warns about
deprecated APIs the application never touches. Instead, calling a deprecated
function or instantiating a deprecated class warns once, attributed to the
calling code.
Applications that turn warnings into errors—`python -W
error::DeprecationWarning`, or pytest's `filterwarnings = ["error", ...]`—may
see a failure at the call site that previously appeared at import time, or that
previously did not appear at all, since using a deprecated API used to warn
nowhere.
As in PEP 702, instantiating a subclass of a deprecated class does not warn
unless that subclass is itself deprecated.
> PyFlink @Deprecated decorator warns at import time instead of on use
> ----------------------------------------------------------------------
>
> Key: FLINK-40529
> URL: https://issues.apache.org/jira/browse/FLINK-40529
> Project: Flink
> Issue Type: Bug
> Affects Versions: 2.1.0, 2.2.0, 2.1.1, 2.1.2, 2.3.0, 2.2.1, 2.1.3
> Reporter: Deepyaman Datta
> Priority: Minor
> Labels: pull-request-available
>
> h2. Problem
> {\{Deprecated}} in \{{flink-python/pyflink/util/api_stability_decorators.py}}
> calls \{{warnings.warn(...)}} inside \{{__call__}}. Since
> \{{@Deprecated(since=...)}} constructs the decorator instance and the
> decorator syntax then invokes \{{__call__}} to *apply* it, the warning fires
> at decoration time — that is, at import — and the decorated function/class is
> returned unwrapped.
> As a result:
> * Importing \{{pyflink.table}} emits a \{{DeprecationWarning}} for every
> deprecated API it defines, whether or not the user ever touches them.
> * Actually calling a deprecated API emits nothing.
> * \{{stacklevel=2}} points at the decoration site inside PyFlink's own
> source, not at user code.
> h2. How to reproduce
> {code}
> $ cd flink-python
> $ python -W error::DeprecationWarning -c "import pyflink.table"
> Traceback (most recent call last):
> ...
> File ".../pyflink/table/table_schema.py", line 28, in <module>
> @Deprecated(since="2.1.0", detail="""
> File ".../pyflink/util/api_stability_decorators.py", line 141, in __call__
> warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
> DeprecationWarning: TableSchema has been deprecated since version 2.1.0. ...
> {code}
> Conversely, calling \{{Table.get_schema()}} or instantiating \{{TableSchema}}
> produces no warning at all.
> FLINK-37365, which introduced these decorators, describes the intended
> behaviour as warning "at runtime on their invocation", so this appears to be
> an oversight rather than a deliberate choice.
> h2. Related defects in the same decorator
> * \{{get_directive}} calls \{{dedent(self.detail)}} unguarded, so
> \{{@Deprecated(since="1.0.0")}} without a \{{detail}} — the form used in the
> decorator's own docstring example — raises \{{AttributeError}}.
> * On Python 3.9/3.10, \{{staticmethod}}/\{{classmethod}} objects do not
> expose \{{__qualname__}}, and \{{property}} objects reject attribute
> assignment, so decorating either one raises.
> h2. Proposed fix
> Make \{{Deprecated}} warn on *use* instead of on decoration. PyFlink supports
> Python >= 3.9 (\{{setup.py}}, \{{tox.ini}}), so \{{warnings.deprecated}} (PEP
> 702) is not available; implement it by hand, mirroring PEP 702's semantics
> where reasonable.
> * *Functions*: return a \{{functools.wraps}}-preserving wrapper that warns
> with a \{{stacklevel}} pointing at the caller.
> * *Classes*: return the class unchanged — replacing it would break
> \{{isinstance}} checks and subclassing — and wrap \{{__init__}} on the class
> instead. Following PEP 702, only instantiating the deprecated class itself
> warns, which also avoids warning twice when a deprecated class inherits the
> \{{__init__}} of a deprecated base class.
> * *\{{staticmethod}}/\{{classmethod}}*: decorate the wrapped function and
> re-package the descriptor. Properties, ABCs and \{{Enum}} subclasses must not
> raise; where wrapping is not safe, degrade to applying the docstring
> directive only.
> * The message format, the \{{DeprecationWarning}} category, the
> docstring/Sphinx-directive behaviour and the \{{__stability_decorators}}
> attribute (introspected by \{{PythonAPICompletenessTestCase}} in
> \{{pyflink/testing/test_case_utils.py}}) stay as they are, and
> \{{Experimental}}, \{{Internal}}, \{{Public}} and \{{PublicEvolving}} are
> unaffected.
> h2. Tests
> Add \{{pyflink/util/tests/test_api_stability_decorators.py}} covering: no
> warning at import/decoration (including a regression test that imports
> \{{pyflink.table}} in a fresh interpreter); warning on function call and on
> class instantiation; attribution to the caller's frame; docstring directives
> still applied; \{{__stability_decorators}} still populated; the other four
> decorators still silent. \{{pyflink/util}} is not currently in the list of
> modules that \{{dev/integration_test.sh}} runs, so it needs to be added there
> for the new tests to run in CI.
> ----
> _Description generated by Claude Code._
> I ran into this while upgrading Ibis' Flink test infrastructure from PyFlink
> 1.20.2 to 2.3.0 in
> [ibis-project/ibis#12093|https://github.com/ibis-project/ibis/pull/12093].
> Ibis runs pytest with \{{filterwarnings = ["error", ...]}}, so once the
> import started emitting these warnings, every test that imports
> \{{pyflink.table}} failed on deprecations for APIs we never call.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)