Classes🔗
This page covers deprecation patterns for classes, Enums, dataclasses, and module-level constants: forwarding an old class name to a replacement, wrapping with a transparent proxy, deprecating only selected attributes, and stacking multiple deprecation layers for multi-version migrations. For function deprecation see Functions.
Class deprecation🔗
Two common patterns here. First, renaming a method within a class: apply @deprecated(target=execute) on the old method name and calls forward to the new method. Second, deprecating an entire class by decorating __init__ to emit a notice at instantiation time and optionally forward construction to a successor class.
Method rename within a class:
from deprecate import deprecated, void
class MyService:
# NEW/FUTURE API — renamed from run() for clarity
def execute(self, x: int) -> int:
"""Current method."""
return x * 2
# DEPRECATED API — `run` was the original name before the rename
@deprecated(target=execute, deprecated_in="1.0", remove_in="2.0")
def run(self, x: int) -> int:
"""Deprecated — renamed to execute()."""
return void(x)
svc = MyService()
# calling this method will raise a deprecation warning:
# The `run` was deprecated since v1.0 in favor of `your_module.execute`.
# It will be removed in v2.0.
print(svc.run(5))
Forwarding __init__ to a successor class — the deprecated class inherits from the successor so all methods and properties are available on instances:
# NEW/FUTURE API — renamed to be more descriptive
class HttpClient:
"""My new class anywhere in the codebase or other package."""
def __init__(self, c: float, d: str = "abc"):
self.my_c = c
self.my_d = d
# ---------------------------
from deprecate import deprecated, void
# DEPRECATED API — `Client` was the original name before it was renamed to HttpClient
class Client(HttpClient):
"""
The deprecated class should be inherited from the successor class
to hold all methods and properties.
"""
@deprecated(target=HttpClient, deprecated_in="0.2", remove_in="0.4")
def __init__(self, c: int, d: str = "efg"):
"""
You place the decorator around __init__ as you want
to warn user just at the time of creating object.
Decorating __init__ warns at instantiation time and optionally
forwards to another class. For deprecating the class itself
(name change, Enum, dataclass), use @deprecated_class() instead.
"""
void(c, d)
# calling this function will raise a deprecation warning:
# The `Client` was deprecated since v0.2 in favor of `your_module.HttpClient`.
# It will be removed in v0.4.
inst = Client(7)
print(inst.my_c) # returns: 7
print(inst.my_d) # returns: "efg"
Constants and instances🔗
deprecated_instance wraps module-level objects (dicts, lists, custom objects) in a transparent proxy that emits a deprecation notice on attribute, item, or call access. Use read_only=True to prevent callers from mutating shared state through the deprecated alias.
Heads up: primitive protocol methods (arithmetic on float, concatenation on str) are not intercepted by the proxy. For primitive constants, wrap them in a container or update call sites directly. See Troubleshooting for details.
from deprecate import deprecated_instance
# NEW/FUTURE API — renamed to be more explicit about its scope
TRAINING_CONFIG = {"lr": 0.001, "batch_size": 32, "epochs": 10}
# What it looked like before the rename:
# DEFAULTS = {"lr": 0.001, "batch_size": 32, "epochs": 10}
# DEPRECATED API — `DEFAULTS` was the original name; read-only so
# callers cannot mutate shared state through the deprecated alias
DEFAULTS = deprecated_instance(
TRAINING_CONFIG,
deprecated_in="1.2",
remove_in="2.0",
read_only=True,
)
# Reading still works but emits a FutureWarning once:
# The `dict` was deprecated since v1.2. It will be removed in v2.0.
print(DEFAULTS["lr"]) # 0.001
Enums and dataclasses🔗
deprecated_class() wraps an Enum or dataclass in a transparent proxy that emits a deprecation notice on access and forwards attribute, item, and call operations to the replacement. Use args_mapping to rename or drop kwargs when the deprecated class is called. When args_mapping is provided without an explicit target, the proxy auto-resolves to TargetMode.ARGS_REMAP and warns only when an old argument name is actually used — matching the per-argument behaviour of @deprecated(target=TargetMode.ARGS_REMAP, args_mapping=...). Callers already using the new argument names see no warning. Type checks (isinstance, issubclass) pass through without emitting notices, since they are structural checks rather than usage of the deprecated API. Use args_extra to inject fixed kwargs into every forwarded call, and template_mgs to override the default warning message — both work identically to their @deprecated counterparts.
from enum import Enum
from dataclasses import dataclass
from deprecate import deprecated_class
# mypackage/theme.py — what it looked like before the rename:
#
# class Color(Enum):
# RED = 1
# BLUE = 2
# NEW/FUTURE API — renamed to be more descriptive
class ThemeColor(Enum):
RED = 1
BLUE = 2
# DEPRECATED API — `Color` was the original name; no class body needed,
# the proxy forwards all access to ThemeColor
Color = deprecated_class(target=ThemeColor, deprecated_in="1.0", remove_in="2.0")(ThemeColor)
# All access is forwarded to ThemeColor — a FutureWarning is emitted once:
# The `Color` was deprecated since v1.0. It will be removed in v2.0.
print(Color.RED is ThemeColor.RED) # True
print(Color(1) is ThemeColor.RED) # True
print(Color["RED"] is ThemeColor.RED) # True
# Precision migration story:
# - PointV1 used integer pixel coordinates.
# - PointV2 supports float coordinates for sub-pixel precision and smoother transforms.
# NEW/FUTURE API — extended to float precision
@dataclass
class PointV2:
x: float
y: float
# DEPRECATED API — PointV1 was the original integer-coordinate implementation
@deprecated_class(target=PointV2, deprecated_in="1.8", remove_in="2.0")
@dataclass
class PointV1:
x: int
y: int
# Existing callers using integer coordinates still work and are forwarded to PointV2:
p_old = PointV1(3, 4)
print(isinstance(p_old, PointV2))
print((p_old.x, p_old.y))
# New callers can use higher precision directly:
p_new = PointV2(3.25, 4.75)
print((p_new.x, p_new.y))
Selective attribute deprecation🔗
Use attrs_mapping on deprecated_class() to deprecate only specific attribute names — all other attributes pass through silently. This covers attribute renames, misspelling corrections (e.g. color → colour), and warn-only notices on individual attributes.
The mapping keys are the deprecated attribute names; values are either the canonical replacement name (string) or None for a warn-only notice with no rename. Reads, writes, and deletes on deprecated attribute names all warn and resolve against the active class. Non-listed attribute names pass through without any warning. Non-None values must exist on the target class when target= is provided, or on the wrapped source class otherwise. Redirect chains such as {"a": "b", "b": "c"} are allowed at decoration time and reported by audit as ChainType.STACKED; cycles such as {"a": "b", "b": "a"} raise immediately.
Decorator syntax — attribute rename🔗
Apply @deprecated_class(attrs_mapping=...) at class definition time. Only the attribute names listed as keys emit a FutureWarning; all others pass through silently:
from deprecate import deprecated_class
@deprecated_class(
attrs_mapping={"color": "colour"}, # "color" is the deprecated spelling
deprecated_in="2.0",
remove_in="3.0",
)
class Palette:
colour: str = "red" # canonical name
size: int = 10 # unlisted — silent passthrough, no warning
# Deprecated alias — warns: "The `color` was deprecated since v2.0 in favor of `Palette.colour`."
print(Palette.color) # red
# Canonical names — silent passthrough, no warning
print(Palette.colour) # red
print(Palette.size) # 10
Wrapper form — equivalent to decorator syntax, useful when wrapping an already-existing class or applying deprecation outside the class definition:
from deprecate import deprecated_class
class Config:
colour: str = "red"
size: int = 42
timeout: int = 30
# Misspelling migration: "color" → "colour"; "size" is warn-only (no rename)
DeprecatedConfig = deprecated_class(
attrs_mapping={"color": "colour", "size": None},
deprecated_in="1.0",
remove_in="2.0",
)(Config)
print(DeprecatedConfig.color) # warns → returns Config.colour ("red")
print(DeprecatedConfig.colour) # silent passthrough ("red")
print(DeprecatedConfig.size) # warns (warn-only, size=42 unchanged)
Reads, writes, and deletes all redirect🔗
The attrs_mapping interception applies to all three access modes. Writing to a deprecated attribute alias warns and sets the canonical attribute instead:
from deprecate import deprecated_class
class Palette:
colour: str = "red"
DeprecatedPalette = deprecated_class(
attrs_mapping={"color": "colour"}, # "color" is the deprecated spelling
deprecated_in="1.0",
remove_in="2.0",
)(Palette)
# Write — warns: FutureWarning and redirects to Palette.colour
DeprecatedPalette.color = "blue" # warns: FutureWarning
# Canonical attribute now holds the new value (no warning on canonical reads)
print(Palette.colour) # blue
Warn-only with None redirect🔗
Map a deprecated attribute to None to emit a warning on access without renaming anything. The attribute is fetched by its original name on the active class after the warning fires. Use this when an attribute is going away with no replacement:
from deprecate import deprecated_class
class Widget:
size: int = 42 # scheduled for removal — callers should stop reading it
DeprecatedWidget = deprecated_class(
attrs_mapping={"size": None}, # warn-only, no rename
deprecated_in="1.0",
remove_in="2.0",
)(Widget)
# Warns: "The `size` was deprecated since v1.0. It will be removed in v2.0."
print(DeprecatedWidget.size) # 42 — value still returned, just warned
# Second access is silent — num_warns=1 budget exhausted
print(DeprecatedWidget.size) # 42 — no second warning
Per-attribute independent warning budgets🔗
Each deprecated attribute name has its own warning counter. With num_warns=1 (the default), accessing two different deprecated aliases each emits one warning independently — two warnings total, not one shared budget:
from deprecate import deprecated_class
class Config:
colour: str = "red"
text: str = "hello"
proxy = deprecated_class(
attrs_mapping={"color": "colour", "txt": "text"},
deprecated_in="1.0",
remove_in="2.0",
)(Config)
print(proxy.color) # warns: FutureWarning — "color" budget consumed
print(proxy.txt) # warns: FutureWarning — "txt" budget consumed (independent counter)
# Both budgets now exhausted — subsequent accesses are silent
print(proxy.color) # silent
print(proxy.txt) # silent
Enum — deprecated member aliases🔗
attrs_mapping works on Enum proxies too. Use it when Enum member names changed (for example, a casing convention migration) and callers may still be using the old names. Wrap the canonical Enum in a proxy that registers the deprecated names as aliases:
from enum import Enum
from deprecate import deprecated_class
class Direction(Enum):
NORTH = "N"
SOUTH = "S"
EAST = "E"
WEST = "W"
# Wrap the canonical Enum and add deprecated lowercase aliases that redirect to canonical members
LegacyDirection = deprecated_class(
attrs_mapping={"north": "NORTH", "south": "SOUTH"},
deprecated_in="1.0",
remove_in="2.0",
)(Direction)
# Deprecated lowercase alias — warns and returns the canonical Direction.NORTH member
print(LegacyDirection.north is Direction.NORTH) # True
# Canonical uppercase name — silent passthrough, no warning
print(LegacyDirection.NORTH is Direction.NORTH) # True
Output: LegacyDirection.north is Direction.NORTH; LegacyDirection.NORTH is Direction.NORTH
attrs_mapping can be combined with target=NewClass; listed attribute aliases redirect to their canonical counterparts on the target class. Unlisted attributes and calls continue to use the normal target-forwarding behaviour.
Audit visibility
find_deprecation_wrappers discovers the proxy via its class-level __deprecated__. Individual attrs_mapping entries are data inside the single proxy config and are not emitted as separate DeprecationWrapperInfo records. All entries share the same deprecated_in/remove_in lifecycle.
Explicit TargetMode.ATTRS_REMAP form🔗
Passing attrs_mapping alone auto-resolves the mode to TargetMode.ATTRS_REMAP. The equivalent self-documenting form is to pass target=TargetMode.ATTRS_REMAP together with attrs_mapping — both forms are behaviourally identical, and the explicit form makes the intent visible at the call site without changing semantics:
from deprecate import TargetMode, deprecated_class
class Palette:
colour: str = "red" # canonical name
# Explicit form — equivalent to passing `attrs_mapping` alone
DeprecatedPalette = deprecated_class(
target=TargetMode.ATTRS_REMAP,
attrs_mapping={"color": "colour"},
deprecated_in="1.0",
remove_in="2.0",
)(Palette)
print(DeprecatedPalette.color) # warns → returns "red"
Three misconfiguration combinations are caught at decoration time and emit a UserWarning (planned to become TypeError in v1.0):
| Misconfiguration | Why it is wrong |
|---|---|
target=TargetMode.NOTIFY + attrs_mapping=... |
NOTIFY means "warn on every access"; attrs_mapping switches to selective per-attribute warning. They contradict each other — drop one. |
target=TargetMode.ATTRS_REMAP without attrs_mapping |
ATTRS_REMAP requires the deprecated attribute names listed via attrs_mapping. Without it the proxy has zero selective effect. |
attrs_mapping={} (empty dict) |
An empty mapping has no effect. Remove it or add deprecated attribute names. |
TargetMode.ATTRS_REMAP is a proxy-only mode: applying it via @deprecated(target=TargetMode.ATTRS_REMAP) on a function, method, or property raises TypeError at decoration time, with the error message pointing to deprecated_class(attrs_mapping=...) as the correct API.
Callable target with attribute redirection🔗
When deprecated_class receives both target=NewClass and attrs_mapping, the two features compose cleanly: listed deprecated attribute aliases resolve against NewClass, while unlisted attributes and instantiation calls also forward to NewClass. Entries mapped to None keep the same attribute name on NewClass; for example, attrs_mapping={"size": None} warns and then reads, writes, or deletes NewClass.size. Use this pattern for a full class replacement where some attribute names changed between the old and the new class.
from deprecate import deprecated_class
class Config:
lr: float = 0.01 # canonical name in the new class
batch_size: int = 32 # unchanged attribute
@deprecated_class(
target=Config,
attrs_mapping={"learning_rate": "lr"}, # "learning_rate" was renamed to "lr"
deprecated_in="2.0",
remove_in="3.0",
num_warns=-1,
)
class LegacyConfig:
learning_rate: float = 0.01 # old name — will warn
lr: float = 0.01 # canonical alias also present
print(LegacyConfig.learning_rate) # warns: FutureWarning — value from Config.lr
print(LegacyConfig.lr) # silent — canonical name
print(LegacyConfig.batch_size) # silent — unlisted attribute
Instantiation calls are also forwarded to Config — LegacyConfig(lr=0.05) returns a Config instance. The attrs_mapping applies only to class-level attribute access on the proxy, not to the returned instance.
Dataclass field renames🔗
When the wrapped class is a @dataclass, deprecated_class(attrs_mapping=...) automatically covers both surfaces in a single call: attribute access on an existing instance (obj.old_field) and constructor kwargs (DC(old_field=5)) both emit FutureWarning. The auto-expand copies each attrs_mapping entry whose redirect target is a dataclass field into args_mapping, so you do not need to set args_mapping separately for a pure field rename. Entries already present in an explicit args_mapping are never overwritten — explicit user values always win.
from dataclasses import dataclass
from deprecate import deprecated_class
@dataclass
class NewPoint:
x: float = 0.0
y: float = 0.0
OldPoint = deprecated_class(
attrs_mapping={"px": "x", "py": "y"},
deprecated_in="2.0",
remove_in="3.0",
num_warns=-1,
)(NewPoint)
# Constructor kwarg warns: FutureWarning — "px" remapped to "x"
pt = OldPoint(px=1.0) # warns: FutureWarning
print(pt.x)
Class-type compatibility🔗
C-extension types, classes whose constructor accepts only positional-only parameters (e.g. def __init__(self, val, /): ...), and tuple/frozenset subclasses emit UserWarning at decoration time when args_mapping remaps a deprecated kwarg to a POSITIONAL_ONLY constructor parameter. At call time the proxy falls back to setattr for those entries instead of passing the remapped name as a constructor kwarg, so the instance is created and then the field is patched in — which behaves correctly for regular dataclasses but may not suit all class types. Run validate_mapping_compatibility(module) in CI to surface these patterns before they reach users.
Combining attribute and argument deprecation🔗
attrs_mapping and args_mapping operate on orthogonal surfaces: attrs_mapping intercepts class-level attribute access (__getattr__ / __setattr__ / __delattr__ on the proxy), while args_mapping intercepts call arguments (__call__). Both can be combined on the same proxy when target is a callable class with renamed class attributes and a renamed constructor parameter.
from deprecate import deprecated_class
class NewTrainer:
epochs: int = 10 # class-level default, required for attrs_mapping validation
lr: float = 0.01 # class-level default, required for attrs_mapping validation
def __init__(self, lr: float = 0.01, epochs: int = 10) -> None:
self.lr = lr
self.epochs = epochs
@deprecated_class(
target=NewTrainer,
attrs_mapping={"n_epochs": "epochs"}, # class-level attribute rename
args_mapping={"learning_rate": "lr"}, # constructor argument rename
deprecated_in="2.0",
remove_in="3.0",
num_warns=-1,
)
class LegacyTrainer:
pass
# Warning path 1 — args_mapping fires: old kwarg "learning_rate" remapped to "lr"
trainer = LegacyTrainer(learning_rate=0.05) # warns: FutureWarning
print(trainer.lr) # NewTrainer instance has lr=0.05
# Warning path 2 — attrs_mapping fires: class-level "n_epochs" redirects to "epochs"
default_epochs = LegacyTrainer.n_epochs # warns: FutureWarning
print(default_epochs) # value from NewTrainer.epochs
The two warning budgets are independent — exhausting one does not affect the other. Each deprecated name (argument or attribute) maintains its own counter, so num_warns=1 (the default) allows each old name to warn exactly once before silencing.
Mixed redirect and warn-only entries🔗
attrs_mapping values can be a string (redirect to a new name) or None (warn but keep the same attribute name, no rename). Both forms can appear in the same mapping alongside args_mapping, making a single proxy the authoritative record for every deprecated surface on the class.
The example below deprecates a Model class that renamed its gpu attribute to device and retired the cuda flag entirely. The constructor kwarg n_layers was also renamed to num_layers:
from deprecate import deprecated_class
class Model:
device: str = "cpu"
num_layers: int = 4
def __init__(self, num_layers: int = 4, device: str = "cpu") -> None:
self.num_layers = num_layers
self.device = device
@deprecated_class(
target=Model,
attrs_mapping={
"cuda": None, # warn-only — flag is being removed, still served from LegacyModel
"gpu": "device", # redirect — old name "gpu" resolves to Model.device
},
args_mapping={"n_layers": "num_layers"}, # constructor kwarg rename
deprecated_in="3.0",
remove_in="4.0",
num_warns=-1,
)
class LegacyModel:
cuda: bool = False # being-removed attribute; must live on LegacyModel for warn-only validation
# 1. Constructor — args_mapping fires: "n_layers" remapped to "num_layers"
m = LegacyModel(n_layers=8) # warns: FutureWarning
print(m.num_layers)
# 2. Attribute redirect — attrs_mapping "gpu" -> "device" fires
print(LegacyModel.gpu) # warns: FutureWarning
# 3. Warn-only — "cuda" warns but is still served from LegacyModel.cuda
print(LegacyModel.cuda) # warns: FutureWarning
"cuda": None emits a FutureWarning on every access but serves the value from LegacyModel.cuda (the source class) because Model does not define cuda. "gpu": "device" warns and redirects the lookup to Model.device. Validation at decoration time requires that every None-value key exists on at least one of the two classes, so cuda must be defined on LegacyModel (or on Model if keeping it in the new API).
Audit tip — mapping compatibility
After combining attrs_mapping and args_mapping, run validate_mapping_compatibility(module) from the audit module in CI to surface any args_mapping entries that remap a deprecated kwarg to a POSITIONAL_ONLY constructor parameter — those fall back to setattr at call time instead of forwarding the kwarg. The function returns a list of DeprecationWrapperInfo objects whose args_mapping_positional_only field is non-empty. See the Audit guide for the full CI integration pattern.
Stacking deprecated_class() for multi-version deprecations🔗
Use a single deprecated_class() call when all attributes and arguments share the same deprecated_in/remove_in — it is the simplest form and keeps both mappings in one place.
Stack two @deprecated_class() decorators when different attributes were deprecated at different releases and each rename needs its own version pair. A common scenario: a library renamed steps in v0.8 and lr in v1.0 — each rename carries its own removal deadline.
from deprecate import deprecated_class
# outer layer: v1.0 rename (lr → learning_rate, remove in v2.0)
@deprecated_class(
attrs_mapping={"lr": "learning_rate"},
deprecated_in="1.0",
remove_in="2.0",
)
# inner layer: v0.8 rename (steps → max_steps, remove in v1.0)
@deprecated_class(
attrs_mapping={"steps": "max_steps"},
deprecated_in="0.8",
remove_in="1.0",
)
class LegacyConfig:
lr: float = 1e-3 # deprecated since 1.0
learning_rate: float = 1e-3 # canonical
steps: int = 1000 # deprecated since 0.8
max_steps: int = 1000 # canonical
cfg = LegacyConfig()
print(cfg.lr) # warns: FutureWarning (deprecated in 1.0, remove in 2.0)
print(cfg.steps) # warns: FutureWarning (deprecated in 0.8, remove in 1.0)
print(isinstance(cfg, LegacyConfig))
Each proxy layer carries its own deprecated_in/remove_in, so attribute-access warnings are version-accurate — cfg.lr reports the v1.0 deadline while cfg.steps reports the earlier v0.8 deadline. Stacking is fully supported: isinstance() and issubclass() resolve through the proxy chain, and instantiation fires at most one global warning. When stacking two ATTRS_REMAP layers, only the innermost layer's instantiation warning fires — the outer layer's version pair appears only in attribute-access warnings for that layer's keys.
Chained redirect🔗
attrs_mapping supports multi-hop rename chains. {"num_iters": "num_steps", "num_steps": "max_steps"} is a valid chain — accessing proxy.num_iters warns once (for num_iters) and resolves directly to the value stored under num_steps on the active class; accessing proxy.num_steps warns once (for num_steps) and resolves to max_steps. Audit reports this mapping structure as ChainType.STACKED. Cycles such as {"a": "b", "b": "a"} raise ValueError at decoration time.
Every non-None redirect target in the chain must be a static class attribute. In the example below, num_steps must exist on the class because it is a redirect target for num_iters:
from deprecate import deprecated_class
class TrainLoop:
max_steps: int = 200
num_steps: int = max_steps # redirect target — must exist as a static class attribute
proxy = deprecated_class(
attrs_mapping={"num_iters": "num_steps", "num_steps": "max_steps"},
deprecated_in="2.0",
remove_in="3.0",
num_warns=-1,
)(TrainLoop)
val1 = proxy.num_iters # warns: FutureWarning — deprecated v1.0 name
print(val1)
val2 = proxy.num_steps # warns: FutureWarning — deprecated v2.0 name
print(val2)
val3 = proxy.max_steps # silent — canonical name
print(val3)
Each deprecated name in the chain fires exactly one warning per access (not two). The resolution is a single lookup hop: proxy.num_iters warns for num_iters and then reads TrainLoop.num_steps directly, which at the class level holds the same value as max_steps.
Nested proxy wrappers🔗
A deprecated_class proxy can wrap another deprecated_class proxy. The inner proxy handles selective attribute deprecation; the outer proxy adds a blanket class-level deprecation warning on every access regardless of attribute name. The two warning budgets are independent.
from deprecate import deprecated_class
class Palette:
colour: str = "red"
color: str = colour # deprecated alias kept for backwards compatibility
# Inner proxy: warns only when the deprecated alias "color" is accessed
selective_proxy = deprecated_class(
attrs_mapping={"color": "colour"},
deprecated_in="1.0",
remove_in="2.0",
num_warns=-1,
)(Palette)
# Outer proxy: warns on every attribute access regardless of name
blanket_proxy = deprecated_class(
deprecated_in="1.0",
remove_in="2.0",
num_warns=-1,
)(selective_proxy)
# Accessing the deprecated alias through the outer proxy: two warnings fire —
# one from the outer blanket proxy ("Palette" is deprecated) and one from the
# inner selective proxy ("color" is deprecated in favor of "colour").
_ = blanket_proxy.color # warns: FutureWarning × 2 — outer blanket + inner selective
# Accessing the canonical name through the outer proxy: one warning fires —
# only the outer blanket proxy warns; the inner proxy forwards silently.
_ = blanket_proxy.colour # warns: FutureWarning × 1 — outer blanket only
The outer proxy issues its blanket class-deprecation warning first; the inner proxy then handles the attribute redirect. Two warnings fire for blanket_proxy.color — one per proxy layer. Accessing blanket_proxy.colour fires only the outer proxy warning because colour is not listed in the inner proxy's attrs_mapping.
Real-world migration: ML training config🔗
The following end-to-end example shows a typical ML library migration where a TrainingConfig dataclass renames several fields across versions. Existing code using the old attribute names continues to work with deprecation notices guiding users toward the canonical API.
Migration summary:
- v1.0 → v2.0:
lrrenamed tolearning_rate,n_epochsrenamed tomax_epochs - v2.0:
sizeattribute removed with no replacement (warn-only,Noneredirect) - Constructor:
hidden_dimrenamed tohidden_size
from dataclasses import dataclass
from deprecate import deprecated_class, find_deprecation_wrappers
@dataclass
class TrainingConfig:
learning_rate: float = 0.001
max_epochs: int = 100
hidden_size: int = 256
@deprecated_class(
target=TrainingConfig,
attrs_mapping={
"lr": "learning_rate", # v2.0 rename
"n_epochs": "max_epochs", # v2.0 rename
"size": None, # removed in v2.0 — warn-only, no replacement
},
args_mapping={"hidden_dim": "hidden_size"}, # constructor rename
deprecated_in="2.0",
remove_in="3.0",
)
class LegacyTrainingConfig:
lr: float = 0.001
learning_rate: float = 0.001
n_epochs: int = 100
max_epochs: int = 100
size: int = 128 # removed in v2.0
hidden_size: int = 256
# Old attribute names still work — each emits one FutureWarning
print(LegacyTrainingConfig.lr) # warns: FutureWarning — "lr" → "learning_rate"
print(LegacyTrainingConfig.n_epochs) # warns: FutureWarning — "n_epochs" → "max_epochs"
print(LegacyTrainingConfig.size) # warns: FutureWarning — "size" removed (no replacement)
Output: LegacyTrainingConfig.lr; LegacyTrainingConfig.n_epochs; LegacyTrainingConfig.size
# phmdoctest:skip
# Old constructor argument still works — emits one FutureWarning
cfg = LegacyTrainingConfig(hidden_dim=512) # warns: FutureWarning
print(cfg.hidden_size)
# phmdoctest:skip
# Audit tools discover the proxy — useful for CI expiry checks
import sys
import types
mod = types.ModuleType("my_ml_lib")
mod.LegacyTrainingConfig = LegacyTrainingConfig
sys.modules["my_ml_lib"] = mod
results = find_deprecation_wrappers(mod)
print(results[0].function, results[0].deprecated_info.deprecated_in)
See also🔗
- Use Cases overview — start here for a guided tour of all deprecation patterns
- Functions — function and method deprecation patterns
- Properties —
@propertyand@cached_propertydeprecation - Async — async functions and async generators
- Advanced — docstring updates,
args_extra, testing helpers, class/static methods - Audit Tools — enforce removal deadlines and detect deprecation chains in CI
- Troubleshooting — common errors and fixes for
deprecated_classconfiguration
Next: Properties — deprecating @property and @cached_property accessors.