Skip to content

Commit 40e776f

Browse files
committed
ADD: Deprecation warnings for client SType enum
1 parent 81780d2 commit 40e776f

File tree

5 files changed

+113
-10
lines changed

5 files changed

+113
-10
lines changed

databento/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
import warnings
23

34
from databento.common import utility
45
from databento.common.dbnstore import DBNStore
@@ -56,6 +57,9 @@
5657
# Setup logging
5758
logging.getLogger(__name__).addHandler(logging.NullHandler())
5859

60+
# Setup deprecation warnings
61+
warnings.simplefilter("always", DeprecationWarning)
62+
5963
# Convenience imports
6064
enable_logging = utility.enable_logging
6165
from_dbn = DBNStore.from_file

databento/common/deprecated.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
def deprecated(func: Any) -> Any:
77
@functools.wraps(func)
88
def new_func(*args: Any, **kwargs: Any) -> Any:
9-
warnings.simplefilter("always", DeprecationWarning)
109
warnings.warn(
1110
func.__doc__,
1211
category=DeprecationWarning,

databento/common/enums.py

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
from enum import Enum, Flag, IntFlag, unique
2-
from typing import Callable, Type, TypeVar, Union
1+
import warnings
2+
from enum import Enum, EnumMeta, Flag, IntFlag, unique
3+
from typing import Any, Callable, Iterable, Type, TypeVar, Union
34

45

56
M = TypeVar("M", bound=Enum)
@@ -77,6 +78,46 @@ def coerced_new(enum: Type[M], value: object) -> M:
7778
return enum_type
7879

7980

81+
class DeprecatedAccess(EnumMeta):
82+
"""
83+
runs a user-specified function whenever member is accessed
84+
"""
85+
86+
def __getattribute__(cls, name: str) -> object:
87+
obj = super().__getattribute__(name)
88+
if isinstance(obj, Enum) and obj._on_access: # type: ignore
89+
obj._on_access() # type: ignore
90+
return obj
91+
92+
def __getitem__(cls, name: str, *_: Iterable[Any]) -> object: # type: ignore
93+
member: Any = super().__getitem__(name)
94+
if member._on_access:
95+
member._on_access()
96+
return member
97+
98+
def __call__( # type: ignore
99+
cls,
100+
value: str,
101+
names=None,
102+
*,
103+
module=None,
104+
qualname=None,
105+
type=None,
106+
start=1,
107+
) -> object:
108+
obj = super().__call__(
109+
value,
110+
names,
111+
module=module,
112+
qualname=qualname,
113+
type=type,
114+
start=start,
115+
)
116+
if isinstance(obj, Enum) and obj._on_access:
117+
obj._on_access()
118+
return obj
119+
120+
80121
class StringyMixin:
81122
"""
82123
Mixin class for overloading __str__ on Enum types.
@@ -206,19 +247,46 @@ class Delivery(StringyMixin, str, Enum):
206247
DISK = "disk"
207248

208249

250+
def deprecated_enum(old_value: str, new_value: str) -> str:
251+
warnings.warn(
252+
f"{old_value} is deprecated to {new_value}",
253+
category=DeprecationWarning,
254+
stacklevel=3, # This makes the error happen in user code
255+
)
256+
return new_value
257+
258+
209259
@unique
210260
@coercible
211-
class SType(StringyMixin, str, Enum):
261+
class SType(StringyMixin, str, Enum, metaclass=DeprecatedAccess):
212262
"""Represents a symbology type."""
213263

214-
PRODUCT_ID = "product_id" # Deprecated for `instrument_id`
215-
NATIVE = "native" # Deprecated for `raw_symbol`
216-
SMART = "smart" # Deprecated for `parent` and `continuous`
264+
PRODUCT_ID = "product_id", "instrument_id" # Deprecated for `instrument_id`
265+
NATIVE = "native", "raw_symbol" # Deprecated for `raw_symbol`
266+
SMART = "smart", "parent", "continuous" # Deprecated for `parent` and `continuous`
217267
INSTRUMENT_ID = "instrument_id"
218268
RAW_SYMBOL = "raw_symbol"
219269
PARENT = "parent"
220270
CONTINUOUS = "continuous"
221271

272+
def __new__(cls, value: str, *args: Iterable[str]) -> "SType":
273+
variant = super().__new__(cls, value)
274+
variant._value_ = value
275+
variant.__args = args # type: ignore
276+
variant._on_access = variant.__deprecated if args else None # type: ignore
277+
return variant
278+
279+
def __eq__(self, other: object) -> bool:
280+
return str(self) == str(other)
281+
282+
def __deprecated(self) -> None:
283+
other_values = " or ".join(self.__args) # type: ignore
284+
warnings.warn(
285+
f"SType of {self.value} is deprecated; use {other_values}",
286+
category=DeprecationWarning,
287+
stacklevel=3,
288+
)
289+
222290

223291
@unique
224292
@coercible

databento/common/parsing.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def optional_values_list_to_string(
5656
@singledispatch
5757
def optional_symbols_list_to_string(
5858
symbols: Optional[Union[Iterable[str], Iterable[int], str, int]],
59-
stype_in: SType,
59+
_: SType,
6060
) -> str:
6161
"""
6262
Concatenate a symbols string or iterable of symbol strings (if not None).
@@ -109,7 +109,7 @@ def _(symbols: int, stype_in: SType) -> str:
109109
optional_symbols_list_to_string
110110
111111
"""
112-
if stype_in == SType.INSTRUMENT_ID:
112+
if stype_in == SType.INSTRUMENT_ID or stype_in == SType.PRODUCT_ID:
113113
return str(symbols)
114114
raise ValueError(
115115
f"value `{symbols}` is not a valid symbol for stype {stype_in}; "
@@ -143,7 +143,7 @@ def _(symbols: str, stype_in: SType) -> str:
143143
return ",".join(map(symbol_to_string, symbol_list))
144144

145145
# TODO(cs): Temporary mapping until past stype rename deprecation period
146-
if stype_in in (SType.SMART, SType.CONTINUOUS):
146+
if stype_in in (SType.SMART, SType.PARENT, SType.CONTINUOUS):
147147
return validate_smart_symbol(symbols)
148148
return symbols.strip().upper()
149149

tests/test_common_enums.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,35 @@ def test_int_flags_stringy_mixin(enum_type: Type[Flag]) -> None:
147147
assert str(record_flags) == ", ".join(
148148
f.name.lower() for f in enum_type if f in record_flags
149149
)
150+
151+
152+
def test_stype_deprecation() -> None:
153+
"""
154+
Test that a deprecation warning is emitted when
155+
accessing a deprecated SType member.
156+
"""
157+
with pytest.deprecated_call():
158+
SType.PRODUCT_ID
159+
160+
with pytest.deprecated_call():
161+
SType.NATIVE
162+
163+
with pytest.deprecated_call():
164+
SType.SMART
165+
166+
167+
@pytest.mark.parametrize(
168+
"name",
169+
[
170+
"product_id",
171+
"native",
172+
"smart",
173+
],
174+
)
175+
def test_stype_deprecation_strings(name: str) -> None:
176+
"""
177+
Test that a deprecation warning is emitted when
178+
constructing an SType from strings.
179+
"""
180+
with pytest.deprecated_call():
181+
SType(name)

0 commit comments

Comments
 (0)