v0.10.0: Property accessors & class attribute mapping
v0.10.0 brings fine-grained attribute-level deprecation to deprecated_class via a new attrs_mapping parameter — deprecate individual attribute names (reads, writes, deletes) with per-attribute warning budgets and, for dataclasses, automatic constructor-kwarg expansion. The release also closes two coverage gaps: @deprecated @property now wraps the setter and deleter in addition to the getter, and raw staticmethod/classmethod descriptors are accepted as target without the .__func__ workaround. A correctness fix ensures calls to targets with positional-only parameters no longer raise TypeError.
✨ Spotlights / highlights
1. deprecated_class(attrs_mapping={...}) — selective attribute deprecation
Deprecate individual attribute names with per-attribute budgets. Reads, writes, and deletes each fire FutureWarning independently. None as the redirect value means warn-only (no rename). TargetMode.ATTRS_REMAP is the corresponding mode. (#191)
from deprecate import deprecated_class
@deprecated_class(attrs_mapping={"color": "colour"}, deprecated_in="2.0", remove_in="3.0")
class Palette:
colour: str = "red" # canonical name
size: int = 10 # unlisted — always silent
print(Palette.color) # warns: FutureWarning → redirected to colour
print(Palette.colour) # silent2. Dataclass attrs_mapping auto-expand
When the wrapped class is a @dataclass, one attrs_mapping entry automatically covers both attribute access and constructor kwargs — no separate args_mapping needed.
from dataclasses import dataclass
from deprecate import deprecated_class
@deprecated_class(attrs_mapping={"px": "x", "py": "y"}, deprecated_in="2.0", remove_in="3.0")
@dataclass
class OldPoint:
x: float = 0.0
y: float = 0.0
pt = OldPoint(px=1.0, py=2.0) # warns on px and py → x=1.0, y=2.0Explicit args_mapping entries always win over auto-expanded ones. (#193)
3. @deprecated @property wraps fset and fdel
Outer-order @deprecated @property now wraps all three accessors. Previously only fget fired a warning. Chain-style @value.setter / @value.deleter re-wraps automatically via the new _DeprecatedProperty subclass.
from deprecate import deprecated
class Config:
def __init__(self) -> None:
self._timeout: int = 30
@deprecated(deprecated_in="1.0", remove_in="2.0")
@property
def timeout(self) -> int:
return self._timeout
@timeout.setter
def timeout(self, value: int) -> None:
self._timeout = value
@timeout.deleter
def timeout(self) -> None:
del self._timeout
cfg = Config()
_ = cfg.timeout # warns: FutureWarning (read)
cfg.timeout = 60 # warns: FutureWarning (write)
del cfg.timeout # warns: FutureWarning (delete)Inner order (@property @deprecated) still wraps fget only. (#190)
4. Raw staticmethod/classmethod descriptors as target
Inside a class body, pass the new method directly as target=new_method — no .__func__ needed. (#192)
from deprecate import deprecated, void
class Compute:
@staticmethod
def area(radius: float) -> float:
return 3.14159 * radius**2
@staticmethod
@deprecated(target=area, deprecated_in="1.0", remove_in="2.0")
def surface(radius: float) -> float:
"""Deprecated — use area() instead."""
return void(radius)
print(Compute.surface(3.0)) # warns: FutureWarning, returns area(3.0)5. Fix: @deprecated forwards calls to POSITIONAL_ONLY targets
Previously raised TypeError at call time when target declared positional-only parameters (def fn(x, /)). Now detects at decoration time, emits UserWarning, and splits call dispatch positionally. (#194)
from deprecate import deprecated
def new_fn(value: float, /) -> float:
return value * 2
@deprecated(target=new_fn, deprecated_in="1.0", remove_in="2.0")
def old_fn(value: float) -> float: ...
result = old_fn(value=5.0) # warns: FutureWarning; calls new_fn(5.0) correctly
print(result) # 10.0⬆️ Upgrade notes
No breaking changes. All existing code continues to work.
If you use @deprecated @property with a setter or deleter and run filterwarnings=error::FutureWarning: setter and deleter writes now correctly fire FutureWarning — they were silently skipped before (bug). Tests that write to or delete a deprecated property will now raise as expected. To intentionally keep only the getter warned, switch to inner order (@property @deprecated).
📋 Notable changes
🚀 Added
deprecated_class(attrs_mapping={...})for selective attribute deprecation. Deprecated attribute names emitFutureWarningon read, write, and delete with per-attribute warning budgets.Noneas the redirect value means warn-only.TargetMode.ATTRS_REMAPis the corresponding mode. Multi-hop chains allowed; cycles raiseValueErrorat decoration time. (#191)deprecated_classstacking now supported. Two@deprecated_classdecorators on the same class work correctly —isinstance()delegates through the chain, instantiation emits at most one warning. (#193)- Dataclass
attrs_mappingauto-expand. Singleattrs_mappingentry covers both attribute access and constructor kwargs on a@dataclass. Explicitargs_mappingalways wins. (#193) validate_mapping_compatibility()audit function. Returns alldeprecated_classproxies whoseargs_mappingremaps toPOSITIONAL_ONLYconstructor parameters. Use in CI alongsidevalidate_deprecation_expiry. (#193)@deprecated @propertywrapsfsetandfdel. Outer-order decoration now covers all three accessors via the new_DeprecatedPropertysubclass. (#190)- Raw
staticmethod/classmethoddescriptors accepted astarget._normalize_targetunwraps automatically inside class bodies. (#192)
🔧 Fixed
@deprecatedcorrectly forwards calls to targets withPOSITIONAL_ONLYparameters. Detects at decoration time, emitsUserWarning, splits call dispatch positionally.args_mappingapplied before split. (#194)args_mappingprecedence: explicit new-name always wins. When caller passes both deprecated old name and new name simultaneously, new-name value wins regardless of call-site order. (#198)
🏆 Contributors
Full changelog: v0.9.0...v0.10.0