Skip to content

Commit

Permalink
refactor: use singular @deprecated, export in utils
Browse files Browse the repository at this point in the history
- The export is safe and doesn't leak to top level
  • Loading branch information
dangotbanned committed Jul 3, 2024
1 parent fe1917e commit 4e7ee2a
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 120 deletions.
3 changes: 2 additions & 1 deletion altair/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
)
from .html import spec_to_html
from .plugin_registry import PluginRegistry
from .deprecation import AltairDeprecationWarning
from .deprecation import AltairDeprecationWarning, deprecated
from .schemapi import Undefined, Optional


Expand All @@ -21,6 +21,7 @@
"PluginRegistry",
"SchemaBase",
"Undefined",
"deprecated",
"display_traceback",
"infer_encoding_types",
"infer_vegalite_type",
Expand Down
112 changes: 19 additions & 93 deletions altair/utils/deprecation.py
Original file line number Diff line number Diff line change
@@ -1,85 +1,34 @@
from __future__ import annotations

from typing import TypeVar
from inspect import ismethod
import typing_extensions as te
import sys
from typing import TYPE_CHECKING

if sys.version_info >= (3, 13):
from warnings import deprecated as _deprecated
else:
from typing_extensions import deprecated as _deprecated

_T = TypeVar("_T")

if TYPE_CHECKING:
if sys.version_info >= (3, 11):
from typing import LiteralString
else:
from typing_extensions import LiteralString

class AltairDeprecationWarning(DeprecationWarning): ...


class deprecated(te.deprecated):
"""Indicate that a class, function or overload is deprecated.
When this decorator is applied to an object, the type checker
will generate a diagnostic on usage of the deprecated object.
Parameters
----------
message
Additional message appended to ``version``, ``alternative``.
version
``altair`` version the deprecation first appeared.
alternative
Suggested replacement class/method/function.
category
If the *category* is ``None``, no warning is emitted at runtime.
stacklevel
The *stacklevel* determines where the
warning is emitted. If it is ``1`` (the default), the warning
is emitted at the direct caller of the deprecated object; if it
is higher, it is emitted further up the stack.
Static type checker behavior is not affected by the *category*
and *stacklevel* arguments.
"""

def __init__(
self,
message: te.LiteralString,
/,
*,
version: str,
alternative: str | None = None,
category: type[AltairDeprecationWarning] | None = AltairDeprecationWarning,
stacklevel: int = 1,
) -> None:
super().__init__(message, category=category, stacklevel=stacklevel)
self.version = version
self.alternative = alternative

def __call__(self, arg: _T, /) -> _T:
if name := getattr(arg, "__name__"): # noqa: B009
# HACK:
# - The annotation for `arg` is required for `mypy` to be happy
# - The attribute check is for `pyright`
...
else:
msg = (
f"{type(self).__qualname__!r} can only be used on"
f" types with a '__name__' attribute, and {arg!r} does not.\n\n"
"See https://docs.python.org/3/reference/datamodel.html#callable-types"
)
raise TypeError(msg)
msg = f"alt.{name} was deprecated in `altair={self.version}`."
if self.alternative:
prefix = arg.__qualname__.split(".")[0] if ismethod(arg) else "alt"
msg = f"{msg} Use {prefix}.{self.alternative} instead."
self.message = f"{msg}\n\n{self.message}" if self.message else msg # pyright: ignore[reportAttributeAccessIssue]
return super().__call__(arg)
class AltairDeprecationWarning(DeprecationWarning): ...


# NOTE: Annotating the return type breaks `pyright` detecting [reportDeprecated]
def deprecate(
# NOTE: `LiteralString` requirement is introduced by stubs
def deprecated(
*,
version: te.LiteralString,
alternative: te.LiteralString | None = None,
message: te.LiteralString | None = None,
version: LiteralString,
alternative: LiteralString | None = None,
message: LiteralString | None = None,
category: type[AltairDeprecationWarning] | None = AltairDeprecationWarning,
stacklevel: int = 1,
):
): # te.deprecated
"""Indicate that a class, function or overload is deprecated.
When this decorator is applied to an object, the type checker
Expand All @@ -106,31 +55,8 @@ def deprecate(
output = f"Deprecated in `altair={version}`."
if alternative:
output = f"{output} Use {alternative} instead."
return te.deprecated(
return _deprecated(
f"{output}\n\n{message}" if message else output,
category=category,
stacklevel=stacklevel,
)


def msg(
*,
version: te.LiteralString,
alternative: te.LiteralString | None = None,
message: te.LiteralString | None = None,
) -> te.LiteralString:
"""Generate a standard deprecation message.
Parameters
----------
version
``altair`` version the deprecation first appeared.
alternative
Suggested replacement class/method/function.
message
Additional message appended to ``version``, ``alternative``.
"""
output = f"Deprecated in `altair={version}`."
if alternative:
output = f"{output} Use {alternative} instead."
return f"{output}\n\n{message}" if message else output
36 changes: 16 additions & 20 deletions altair/vegalite/v5/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import jsonschema
import itertools
from typing import Union, cast, Any, Iterable, Literal, IO, TYPE_CHECKING
from typing_extensions import TypeAlias, deprecated
from typing_extensions import TypeAlias
import typing

from .schema import core, channels, mixins, Undefined, SCHEMA_URL
Expand Down Expand Up @@ -262,9 +262,7 @@ def __init__(
self.param = param
self.param_type = param_type

@utils.deprecation.deprecated(
"No need to call '.ref()' anymore.", version="5.0.0", alternative="to_dict"
)
@utils.deprecated(version="5.0.0", alternative="to_dict")
def ref(self) -> dict:
"'ref' is deprecated. No need to call '.ref()' anymore."
return self.to_dict()
Expand Down Expand Up @@ -533,12 +531,10 @@ def _selection(
return param(select=select, **param_kwds)


@deprecated(
utils.deprecation.msg(
version="5.0.0",
alternative="'selection_point()' or 'selection_interval()'",
message="These functions also include more helpful docstrings.",
)
@utils.deprecated(
version="5.0.0",
alternative="'selection_point()' or 'selection_interval()'",
message="These functions also include more helpful docstrings.",
)
def selection(
type: Optional[Literal["interval", "point"]] = Undefined, **kwds
Expand Down Expand Up @@ -793,13 +789,13 @@ def selection_point(
)


@utils.deprecation.deprecated("", version="5.0.0", alternative="selection_point")
@utils.deprecated(version="5.0.0", alternative="selection_point")
def selection_multi(**kwargs):
"""'selection_multi' is deprecated. Use 'selection_point'"""
return _selection(type="point", **kwargs)


@utils.deprecation.deprecate(version="5.0.0", alternative="selection_point")
@utils.deprecated(version="5.0.0", alternative="selection_point")
def selection_single(**kwargs):
"""'selection_single' is deprecated. Use 'selection_point'"""
return _selection(type="point", **kwargs)
Expand Down Expand Up @@ -2656,7 +2652,7 @@ def display(
else:
display(self)

@utils.deprecation.deprecated("", version="4.1.0", alternative="show")
@utils.deprecated(version="4.1.0", alternative="show")
def serve(
self,
ip="127.0.0.1",
Expand Down Expand Up @@ -3030,7 +3026,7 @@ def add_params(self, *params: Parameter) -> Self:
copy.params.append(s.param)
return copy

@utils.deprecation.deprecated("", version="5.0.0", alternative="add_params")
@utils.deprecated(version="5.0.0", alternative="add_params")
def add_selection(self, *params) -> Self:
"""'add_selection' is deprecated. Use 'add_params' instead."""
return self.add_params(*params)
Expand Down Expand Up @@ -3244,7 +3240,7 @@ def add_params(self, *params: Parameter) -> Self:
copy.spec = copy.spec.add_params(*params)
return copy.copy()

@utils.deprecation.deprecated("", version="5.0.0", alternative="add_params")
@utils.deprecated(version="5.0.0", alternative="add_params")
def add_selection(self, *selections) -> Self:
"""'add_selection' is deprecated. Use 'add_params' instead."""
return self.add_params(*selections)
Expand Down Expand Up @@ -3359,7 +3355,7 @@ def add_params(self, *params: Parameter) -> Self:
copy.concat = [chart.add_params(*params) for chart in copy.concat]
return copy

@utils.deprecation.deprecated("", version="5.0.0", alternative="add_params")
@utils.deprecated(version="5.0.0", alternative="add_params")
def add_selection(self, *selections) -> Self:
"""'add_selection' is deprecated. Use 'add_params' instead."""
return self.add_params(*selections)
Expand Down Expand Up @@ -3454,7 +3450,7 @@ def add_params(self, *params: Parameter) -> Self:
copy.hconcat = [chart.add_params(*params) for chart in copy.hconcat]
return copy

@utils.deprecation.deprecated("", version="5.0.0", alternative="add_params")
@utils.deprecated(version="5.0.0", alternative="add_params")
def add_selection(self, *selections) -> Self:
"""'add_selection' is deprecated. Use 'add_params' instead."""
return self.add_params(*selections)
Expand Down Expand Up @@ -3551,7 +3547,7 @@ def add_params(self, *params: Parameter) -> Self:
copy.vconcat = [chart.add_params(*params) for chart in copy.vconcat]
return copy

@utils.deprecation.deprecated("", version="5.0.0", alternative="add_params")
@utils.deprecated(version="5.0.0", alternative="add_params")
def add_selection(self, *selections) -> Self:
"""'add_selection' is deprecated. Use 'add_params' instead."""
return self.add_params(*selections)
Expand Down Expand Up @@ -3668,7 +3664,7 @@ def add_params(self, *params: Parameter) -> Self:
copy.layer[0] = copy.layer[0].add_params(*params)
return copy.copy()

@utils.deprecation.deprecated("", version="5.0.0", alternative="add_params")
@utils.deprecated(version="5.0.0", alternative="add_params")
def add_selection(self, *selections) -> Self:
"""'add_selection' is deprecated. Use 'add_params' instead."""
return self.add_params(*selections)
Expand Down Expand Up @@ -3754,7 +3750,7 @@ def add_params(self, *params: Parameter) -> Self:
copy.spec = copy.spec.add_params(*params)
return copy.copy()

@utils.deprecation.deprecated("", version="5.0.0", alternative="add_params")
@utils.deprecated(version="5.0.0", alternative="add_params")
def add_selection(self, *selections) -> Self:
"""'add_selection' is deprecated. Use 'add_params' instead."""
return self.add_params(*selections)
Expand Down
12 changes: 6 additions & 6 deletions tests/utils/test_deprecation.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
import pytest

from altair.utils import AltairDeprecationWarning
from altair.utils.deprecation import deprecated
from altair.utils.deprecation import AltairDeprecationWarning, deprecated


def test_deprecated_class():
class Dummy:
def __init__(self, *args) -> None:
self.args = args

OldChart = deprecated("", version="", alternative="LayerChart")(Dummy)
with pytest.warns(AltairDeprecationWarning, match=r"alt\.Dummy.+alt\.LayerChart"):
OldChart = deprecated(version="2.0.0", alternative="LayerChart")(Dummy)

with pytest.warns(AltairDeprecationWarning, match=r"altair=2\.0\.0.+LayerChart"):
OldChart()


def test_deprecation_decorator():
@deprecated("", version="999", alternative="12345")
@deprecated(version="999", alternative="func_12345")
def func(x):
return x + 1

with pytest.warns(
AltairDeprecationWarning, match=r"alt\.func.+altair=999.+12345 instead"
AltairDeprecationWarning, match=r"altair=999.+func_12345 instead"
):
y = func(1)
assert y == 2

0 comments on commit 4e7ee2a

Please sign in to comment.