Changelog🔗
[UnReleased] - 2026-MM-DD🔗
Added🔗
Changed🔗
Deprecated🔗
Removed🔗
Fixed🔗
[0.11.0] — 2026-07-15 — Operator forwarding, PEP 560 subclassing, & proxy identity fixes🔗
Added🔗
- Deprecated proxies now forward operator and protocol dunders to the wrapped object. Arithmetic (
proxy + 1), comparison/ordering, context managers (with proxy:), iteration (next,reversed), numeric conversion (int/float/round/abs),os.fspath,format, and the async protocols (async with,async for,await) now delegate to the active object instead of raisingTypeError. Binary operators preserveNotImplementedsemantics, so unsupported-operand errors surface normally. The warn-policy contract is documented in the_DeprecatedProxydocstring: data use warns (within thenum_warnsbudget), cheap probes stay silent. Caveat: in-place operators (+=,-=,*=, …) return the active object's result rather than a re-wrapped proxy — afterx += 1, the namexis rebound to a plainintand all subsequent uses are silent even if the deprecation window has not closed. (#214) - Subclassing a deprecated class alias now works (PEP 560).
class Child(OldName)on adeprecated_classalias previously raised a confusing metaclass arityTypeError;__mro_entries__now resolves the alias to the active class and emits the deprecation warning (subclassing is a use of the deprecated name), respecting the warn budget and staying silent forattrs_mapping-only andargs_mapping-only proxies. (#214)
Changed🔗
Performance
- Call forwarding is ~2.4× faster. Decoration-time-stable signature facts are now precomputed onto
DeprecationConfiginstead of re-derived on every call (uncachedinspect.getfullargspecremoved from the hot path); forwarded-call overhead drops from ~10.4 µs to ~4.3 µs with no behavior change. (#214)
Proxy identity
proxy.__class__now reports the wrapped object's type forisinstancetransparency._DeprecatedProxyexposes a__class__property returning the active object's type, so type checks in downstream code (JSON encoders, validators,functools.singledispatch) keep working when an object is wrapped;type(proxy)still reveals the proxy. Code that previously detected the proxy viaobj.__class__ is _DeprecatedProxyshould usetype(obj) is _DeprecatedProxyinstead. (#210)- Proxy identity operations now reflect the served (active) object.
repr(),str(),==, andhash()on a target-forwarding proxy previously used the deprecated source while attribute/item/call access used the active target, so a proxy could compare equal to an object it never served; all four now route through the active object for consistency. (#216) isinstance/issubclasswith an instance proxy as the second argument now raiseTypeError. Using adeprecated_instanceproxy (one wrapping a value rather than a class) as the second argument toisinstance/issubclasspreviously returnedFalsesilently, hiding the misuse; it now raises the sameTypeErrorthe builtins raise. Class-alias proxies are unaffected. (#216)
Audit & expiry
validate_deprecation_expiry()now scans class members by default (include_members=True). Previously the expiry gate defaulted toinclude_members=Falsewhilefind_deprecation_wrappers()defaulted toTrue, so CI gates silently skipped expired deprecated methods, constructors, classmethods, staticmethods, and properties. The flip can only surface additional expired wrappers — passinclude_members=Falseexplicitly to restore the old scope. (#210)- Batch expiry now warns on an unparsable
remove_ininstead of skipping it silently.validate_deprecation_expiry()(and the CLI expiry gate) previously dropped wrappers whoseremove_inversion could not be parsed, leaving them permanently un-expirable with no signal; such wrappers now emit aUserWarningnaming the callable while the rest of the scan continues. (#216)
Fixed🔗
Audit
- Audit scans no longer double-count re-exported wrappers. Recursive
find_deprecation_wrapperspreviously reported a wrapper once per importing module (e.g. once under the package root re-export and once under its defining submodule), inflating expiry counts and table rows; wrappers are now attributed to their defining module and deduplicated by identity across the scan. (#215) - Audit report formatting no longer triggers chained proxies. Formatting a report for a wrapper whose
targetis itself a deprecated proxy previously emitted a spuriousFutureWarningfrom inside the audit tooling, consumed the proxy's warn budget, and printed a fabricated module path; proxy targets are now read via static metadata access. (#215) - Audit scans survive submodules that fail to import.
find_deprecation_wrappers(recursive=True)previously aborted the whole scan when any submodule raised a non-ImportErrorat import time (e.g.RuntimeErrorfrom an optional dependency); such submodules are now skipped with awarnings.warnnaming the module and error. (#210) - Audit tools now surface deprecated private and dunder members.
_scan_classskipped every_-prefixed member except__init__, so a deprecated private method or dunder could never be flagged as expired; private/dunder members that carry deprecation metadata are now included. (#216) - Audit now detects proxy self-reference. A
deprecated_instance/deprecated_classproxy whose target is its own wrapped object was reported as effective because the self-reference check compared against the proxy rather than the wrapped object; it is now flagged as a no-op self-reference. (#216) - Recursive audit scans tolerate foreign objects that raise on attribute access. Probing a scanned object for deprecation metadata used
getattr(..., default), which only suppressesAttributeError; a third-party object whose__getattr__raised something else (e.g. a lazy proxy raisingRuntimeError) aborted the whole scan. Such failures are now treated as "no metadata". (#216)
CLI
allandstatusno longer fail on plain directories. Scanning a directory without__init__.pyexited 1 from the status-table step even when every check passed; module-name resolution is now lazy and a status-rendering failure cannot change the aggregate exit code. (#215)- Version auto-detection resolves distributions whose name differs from the import name.
importlib.metadatalookup previously failed for packages like pyDeprecate itself (importdeprecate, distributionpyDeprecate); the import name is now mapped viapackages_distributions(), and expiry prints an explicit note when it runs without a resolved version. (#215) - Version auto-detection no longer picks up an unrelated project's
pyproject.toml. When the scan path is an importable module name rather than a filesystem path,expiry/statuspreviously walked up from the current directory and could gate against whatever project the shell happened to be in; filesystem-based detection now runs only for real paths. (#210) - Unknown or misspelled flags now exit with an error instead of being silently ignored.
sys.exitinside the Fire invocation previously preempted Fire's unconsumed-argument check, so a typo such as--verisonwas dropped and the command ran with defaults; the CLI now lets Fire report the unconsumed flag and exits non-zero. (#210) - A stream whose
encodingisNoneno longer crashes UTF-8 setup._ensure_utf8_streamscalled.lower()on a possibly-Noneencodingattribute, raisingAttributeErrorfor some redirected streams; aNoneencoding is now tolerated. (#216) - An exception carrying no message now exits with a non-blank stderr line. The top-level handler called
sys.exit(str(exc)), which printed nothing (exit 1) for a message-less exception; the exception type now prefixes the exit message so CI shows what failed. (#216)
Call forwarding & signatures
- Sources with positional-only parameters no longer raise
TypeErroron every call.@deprecatedondef f(a, /, b=2)previously failed at call time in the default notify mode andTargetMode.ARGS_REMAPbecause positional-only arguments were re-passed as keywords; they are now split back out positionally for both sync and async sources. (#210) - Surplus
*argsare now forwarded to callable targets instead of being silently dropped. A*args-declaring source forwarding to a target previously discarded everything past the named positionals (old_sum(1, 2, 3)returned1); the positional tail is now forwarded, and incompatible targets raise the curated mappingTypeError. (#210) - Positional-only forwarding no longer misbinds values when an earlier parameter is absent. The positional split previously appended present values in declaration order, sliding a later parameter's value into an earlier defaulted slot; gaps now stop the split and conflicting later values raise
TypeError. The same signature-order dispatch replaces thesetattrfallback indeprecated_class(args_mapping=...), which also failed for required positional-only parameters and frozen dataclasses. (#210) - Cross-class forwarding between staticmethods no longer raises a spurious
TypeError. The cross-class guard exists to preventselfcarrying the wrong type, but staticmethods have noself;@deprecated(target=NewCls.compute)on a staticmethod forwarding to another class's staticmethod is now allowed. (#214) - The cross-class forwarding guard now fires for descriptor-decorated methods. The guard read the enclosing class name from a fixed stack depth, which the extra frames of
@property/@classmethod/@staticmethodwrapping pushed out of reach — silently disabling the check; a bounded frame walk now locates the class body regardless of descriptor frames. (#216) - Argument validation against a
**kwargs/*argstarget yields the curated message again. The internal signature helper leaked*args/**kwargsnames into caller-argument validation, producing a rawTypeErrorinstead of the "argument not accepted by target" message; the variadic names are now excluded as documented. (#216)
Proxy, decoration & config
num_warnsquota is now thread-safe. Concurrent first calls to a shared wrapper could each pass the quota check before any counter increment, emitting up to one warning per thread instead of the configured budget; the warn path now synchronizes on a per-wrapper lock, so exactlynum_warnswarnings are emitted under concurrency. (#214)- Instantiating an
attrs_mapping-only deprecated class no longer emits a class-level warning.TargetMode.ATTRS_REMAPscopes the deprecation to the listed attributes, yet plain instantiation fired the blanketFutureWarningand consumed the warn budget; construction is now silent and only deprecated-attribute access warns. (#214) - Deprecated proxies now support
copy.copy,copy.deepcopy, andpickle. Copying or pickling anydeprecated_class/deprecated_instanceproxy previously crashed withRecursionError— the_cfgproperty and__getattr__fell into infinite mutual recursion on half-initialized instances. Proxies now implement the copy/pickle protocol and reconstruct a functional proxy. Note:deprecated_instanceproxies wrapping plain objects (dicts, lists) are fully picklable;deprecated_classproxies may raisePicklingErrorwhen the decorated class name is replaced by the proxy (the common alias pattern), because pickle cannot find the original class by reference. (#212) - Proxy introspection no longer consumes the warning budget.
hasattr()probes on missing attributes,copy.deepcopyprotocol lookups, and dunder access (e.g.__mro__reads by doc tools) previously emitted the deprecation warning and exhausted the defaultnum_warns=1budget before any real usage. Warnings now fire only on successful non-dunder attribute access. (#210) - Bare
@deprecated(no parentheses) now raises a clearTypeErrorwhen the first argument is not callable. Forgetting the call parentheses previously surfaced as a crypticAttributeError: 'int' object has no attribute '__name__'on the first call; the decorator now explains that it must be called with arguments. (#216) - Custom
template_mgswith a bare%-conversion is now rejected at decoration time. A template containing%s/%d(rather than a%(name)smapping key) silently rendered the whole substitution dict into the warning; such templates now raiseValueErrorwhen the decorator is applied. (#216) args_mapping,args_extra, andattrs_mappingare defensively copied at decoration time. The frozen configuration previously aliased the caller's dict, so mutating it after decoration could silently change forwarding behavior (or introduce a redirect cycle that validation had already rejected); the mappings are now copied. (#216)- A warning stream that raises
TypeErrorinternally is no longer invoked twice. Both the decorator and proxy warning paths called the stream withstackleveland caughtTypeErrorto retry without it — which also swallowed aTypeErrorraised inside a stacklevel-accepting stream and re-ran it (duplicate log/print). The paths now decide once, via a cached signature probe, whether the stream acceptsstacklevel. (#216) - Version normalization preserves PEP 440 local segments.
_normalize_version_stringran its pre/post/dev label rule over the whole string, mangling a legitimate local version such as1.2.3+cudainto1.2.3+cuda0; the local segment (after+) is now split off and re-attached verbatim, and only a single leadingv/Vis stripped. (#216)
[0.10.1] — 2026-07-03 — Cycle detection, strict property, & inner-order audit flag🔗
Added🔗
DeprecationWrapperInfo.inner_order_propertyflag.find_deprecation_wrappers()now setsinner_order_property=Truewhen a plainproperty(not_DeprecatedProperty) has a@deprecated-wrappedfget— the inner-order@property @deprecatedshape where only the getter warns; setters and deleters added via@value.setter/@value.deleterremain silently unprotected. CI pipelines can filter on this field to reject the pattern. (#201)- Opt-in strict
propertyreplacement.from deprecate import propertynow exports_StrictProperty, apropertysubclass that raisesTypeErrorat class-definition time when handed an already-@deprecatedgetter (inner-order detection). Import it in modules that want compile-time enforcement; star imports (from deprecate import *) are unaffected. (#201)
Fixed🔗
- Circular deprecation chains now raise
RuntimeErrorat call time. Callabletargetchains that form a cycle (A → B → A) previously caused unbounded recursion and aRecursionError. The decorator now detects the cycle via aContextVarre-entrancy guard and raises a clearRuntimeErrornaming the circular path. Async deprecation cycle detection was also improved to avoid false positives from concurrent tasks sharing a threading-local guard. (#200) _StrictPropertyTypeErrormessage now references the correct module path. The error previously pointed todeprecate._StrictProperty; corrected todeprecate.deprecation._StrictProperty, which is the actual import path. (#201)
[0.10.0] — 2026-06-21 — Property accessors, class attribute mapping & descriptor targets🔗
Added🔗
Class attribute & dataclass mapping
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 (no rename).TargetMode.ATTRS_REMAPis the corresponding mode — can be combined with a callabletargetto redirect attribute access across class boundaries. Multi-hop chains and fan-in renames allowed; cycles raiseValueErrorat decoration time. (#191)- Dataclass
attrs_mappingauto-expand. When the wrapped class is a@dataclass, a singledeprecated_class(attrs_mapping={"old_field": "new_field"})call automatically generates the correspondingargs_mappingentry so both attribute access (obj.old_field) and constructor kwargs (DC(old_field=5)) emitFutureWarningfrom one decorator. Explicitly-providedargs_mappingkeys always win over auto-expanded entries. For non-dataclass targets,attrs_mappingcovers attribute access only. (#193)
Descriptors & property accessors
target=now accepts rawstaticmethod/classmethoddescriptors directly. Inside a class body the new method is still a raw descriptor (not yet bound); passing it astarget=new_methodno longer requires the explicit.__func__suffix._normalize_targetunwraps the descriptor automatically. Forclassmethoddescriptors the symmetric same-class pattern is supported (both deprecated and replacement are classmethods); asymmetric usage raisesTypeErrorat decoration time. (#192)@deprecated @propertynow wrapsfsetandfdelwithFutureWarning. Applying@deprecatedon the outside of@property(outer order, or explicitdeprecated(...)(property(fget, fset, fdel))) now wraps all three accessors. Previously, onlyfgetemitted a warning;fsetandfdelwere silently passed through. Consumers runningfilterwarnings=error::FutureWarningthat wrote to or deleted a deprecated property will now seeFutureWarningerrors — use inner-order (@property @deprecated) or decorate onlyfgetdirectly if you want a silent setter/deleter. Chain-style rebinding via@value.setter/@value.deleteris fully supported through the new_DeprecatedPropertysubclass. (#190)
Stacking & audit
deprecated_classstacking is now supported. Two@deprecated_classdecorators applied to the same class (each with its ownattrs_mappingand version pair) now work correctly:isinstance()resolves through the proxy chain, instantiation emits at most one warning instead of two, and the type annotation accepts_DeprecatedProxywithout acast. Stacking ATTRS_REMAP outer + ARGS_REMAP inner is also supported: the inner proxy no longer emits a spurious global warning on attribute access —TargetMode.ARGS_REMAPnow correctly restricts its warnings to call-time argument remapping only. No-target two-layer stacking (both layers deprecating the class in-place without forwarding to a different type) is also supported: the outerATTRS_REMAPproxy delegates__call__to the innerARGS_REMAPproxy without firing a second global warning. (#193)validate_mapping_compatibility()audit function. Returnslist[DeprecationWrapperInfo]for alldeprecated_classproxies whoseargs_mappingremaps deprecated names toPOSITIONAL_ONLYconstructor parameters — those proxies fall back tosetattrat call time instead of forwarding via kwargs. Use in CI to detect configurations that silently degrade to attribute assignment. (#193)
Fixed🔗
@deprecatednow correctly forwards calls to targets with POSITIONAL_ONLY parameters. When a callabletargetdeclares any parameter as positional-only (def new_fn(x, /): ...), the decorator previously raisedTypeErrorat call time because all arguments were forwarded as kwargs. The decorator now detects POSITIONAL_ONLY params at decoration time, emits aUserWarningnaming the affected parameters, and splits the call-time dispatch so those values are forwarded positionally.args_mappingremaps applied before the split — remapped names that land on a POSITIONAL_ONLY target param are handled correctly. The thin-adapter pattern (def new_fn_compat(x): return new_fn(x)astarget) remains valid and suppresses theUserWarning. (#194)args_mappingprecedence: explicit new-name always wins when both old and new kwargs passed. When a caller passed both the deprecated old argument name and the new name simultaneously (e.g.fn(val=5, new_val=6)), the remapped old-name value previously overwrote the explicit new-name value due to dict-comprehension last-write-wins ordering. The explicit new-name value now always wins, regardless of argument order at the call site. Affects@deprecatedwithtarget=TargetMode.ARGS_REMAPor a callable target, anddeprecated_class()withargs_mapping. (#198)
[0.9.0] — 2026-06-05 — Generators, async, & markdown audit tables🔗
Added🔗
Async & callable shapes
- Generator function support for
@deprecated. Decorating a generator function now emits the deprecation warning eagerly at call time — before the firstnext()— consistent with regular function behavior. The generator body executes lazily as normal when iterated. All threeTargetModevariants (NOTIFY,ARGS_REMAP, callable target) work transparently; noisgeneratorfunctioncheck is required. (#176) async defcoroutine wrapper support for@deprecated. Decorating anasync deffunction now produces anasync defwrapper —inspect.iscoroutinefunction(wrapper)returnsTrue. All threeTargetModevariants (NOTIFY,ARGS_REMAP, callable target) work with async sources and async targets. The deprecation warning fires when the coroutine is awaited, not when the wrapper is called.pytest-asynciois required in the test suite to run the async integration tests. (#180)- Async generator function support for
@deprecated. Decorating anasync def+yieldfunction no longer emits aUserWarningat decoration time. The wrapper is a sync callable that fires the deprecation warning eagerly at call time and returns the async generator object; callers iterate withasync for. All threeTargetModevariants work. Because the wrapper is sync,inspect.isasyncgenfunction(wrapper)returnsFalse— frameworks that branch on that flag may need a thin async generator passthrough. (#181) - Order-agnostic
@classmethod/@staticmethod. Both@classmethod @deprecatedand@deprecated @classmethod(and the equivalent for@staticmethod) now produceclassmethod(deprecated_wrapper)— the descriptor is unwrapped at decoration time, the inner function is deprecated, and the result is re-wrapped.FutureWarningfires at call time in either order; noUserWarningis emitted. (#178)
Stacking
- Stacked
@deprecated—ARGS_REMAP + NOTIFYcombination. Lifecycle pattern: rename arguments first, deprecate the whole function later. The outerARGS_REMAPremaps kwargs, then the innerNOTIFYwarns and runs the source body. Six other stacking shapes (e.g. callable-over-callable, callable-over-ARGS_REMAP) now emitUserWarningat decoration time naming the specific shape and will becomeTypeErrorin v1.0. (#172)
Audit & CLI tables
- Markdown deprecation tables —
generate_deprecation_table()+pydeprecate statusCLI subcommand. Renders compact or matrix-style Markdown reports grouped by module and API-type (function, method, classmethod, staticmethod, property, class, instance). Two new public enums:DeprecationStatus(lifecycle classification — active, expired, plus dev/alpha/beta/rc removal windows) andTableStyle(compact/matrix). New--styleand--outputCLI flags. Integrated intopydeprecate all. Auto-detects package version frompyproject.tomlor installed metadata. (#133) ChainTypeenum now exported as public API. Previously documented and returned byvalidate_deprecation_chains(); now listed indeprecate.__all__.- Audit discovery extended to class descriptors.
find_deprecation_wrappers()now inspectsclassmethodandstaticmethoddescriptors on class members so@deprecated-wrapped descriptors are found during scans. (#178)
Changed🔗
CLI
- Renamed
--skip_errorsto--exit-zeroacross all four subcommands (check,expiry,chains,all). (#187) Breaking change for existing scripts —--skip_errorsno longer accepted; update calls to--exit-zero. The new name matches the established linter convention (ruff, pylint, shellcheck) and accurately describes the behaviour: exit-code override only, no exception suppression. The canonical spelling is--exit-zero(dash); the CLI framework also accepts--exit_zero(underscore) as an alias. - CLI warning suppression narrowed to
deprecate.*warnings only. Third-party warnings emitted during a scan are no longer silenced. (#133)
Stacking
- Misconfigured stacking combinations warn at decoration time. Six previously-undefined
@deprecatedstacking shapes (e.g. callable-over-callable, callable-over-ARGS_REMAP) now emitUserWarningat decoration time naming the specific shape. Scheduled to becomeTypeErrorin v1.0. The new module-level_V1_BREAK_VERSION = "v1.0"constant centralises the "Will be TypeError in v1.0" wording across these warnings. (#172)
Audit tables
- Audit chain classification —
ARGS_REMAP + NOTIFYis now classified asSTACKEDrather thanTARGET. Fixes audit reports for the supported new stacking shape. (#172) generate_deprecation_table()gainsinclude_membersparameter for scanning descriptor members;validate_deprecation_expiry()default unchanged atinclude_members=Falseto preserve existing scan scope. (#133)
Fixed🔗
stacklevelattribution on Python 3.12+.inspect.signature(warnings.warn)raisesValueErroron Python 3.12+ because the C builtin lacks an introspectable signature. This caused every@deprecatedwarning to point todeprecation.pyinstead of the caller's file. Fixed by replacing theinspect.signatureprobe with a try/except at call site:stream(msg, stacklevel=N)is tried first; ifTypeErroris raised (stream does not acceptstacklevel), retried asstream(msg). (#176)find_deprecation_wrappers()no longer aborts on PEP 702typing_extensions.deprecatedobjects. The scanner previously checkedcallable(obj) and hasattr(obj, "__deprecated__"), which matched PEP 702 wrappers (whose__deprecated__is a string), causingvalidate_deprecation_wrapperto raiseValueErrorand abort the scan. Replaced with_has_deprecation_meta(obj), which checksisinstance(..., DeprecationConfig). (#178)- Short-circuit forwarding for
*argssources now preserves extra positional arguments. Previously, when forwarding from a*argssource, extra positional arguments past the named parameters were silently dropped. (#180)
[0.8.0] — 2026-05-21 — Default TargetMode enum & CLI audit tools🔗
Added🔗
Core API & config
TargetModeenum exported fromdeprecate.TargetMode.NOTIFYreplacestarget=NoneandTargetMode.ARGS_REMAPreplacestarget=True. Both are public API. (#150)args_extraparameter fordeprecated_class()anddeprecated_instance(). Injects fixed keyword arguments into forwarded calls afterargs_mappinghas been applied, matching the same semantics as@deprecated(args_extra=...). Ignored (with a construction-timeUserWarning) whentargetisTargetMode.NOTIFY. (#150)template_mgsparameter fordeprecated_class()anddeprecated_instance(). Overrides the built-in warning message template with a%-style format string, matching the same semantics as@deprecated(template_mgs=...). Available placeholders:%(source_name)s,%(deprecated_in)s,%(remove_in)s,%(target_name)s(callable target only),%(target_path)s(callable target only),%(argument_map)s(args_mappingwarnings only). (#150)DeprecationConfig.misconfiguredfield. Boolean field on the shared metadata dataclass;Truewhen an invalid raw target sentinel (False) was passed at decoration time. Audit tools surface this viaDeprecationWrapperInfo.misconfigured_target. (#150)template_mgsvalidated at decoration time. Malformed%-style format strings now raiseValueErrorimmediately at decoration time — not silently at call time. Applies to@deprecatedand thedeprecated_class()/deprecated_instance()proxy factories. (#169)- Stacked-callable-target guard. Applying
@deprecated(target=fn_a)on a callable whose target is itself a callable-target@deprecatedwrapper now emitsUserWarningat decoration time instead of crashing withTypeErrorat call time. (#169)
CLI & audit
pydeprecateCLI command. Runpydeprecate <subcommand> path/to/your/packageto scan any package or module for misconfigured@deprecatedwrappers — reports invalid argument mappings, identity mappings, and no-effect wrappers with rich-formatted output whenrichis available. Also available aspython -m deprecate. (#76)- Four CLI subcommands:
check,expiry,chains,all.checkvalidates wrapper configuration;expiryreports wrappers past theirremove_indeadline (requirespip install 'pyDeprecate[audit]');chainsdetects deprecated-to-deprecated forwarding chains;allruns all three in a single scan pass. Flags:--norecursive,--skip_errors. (#149) - New
DeprecationWrapperInfo.empty_deprecated_infield.Truewhendeprecated_inis absent on a wrapper; intended for CI pipeline introspection.dataclasses.asdict()output andrepr()now include this field. (#166)
Docs
- Multi-page topic documentation site. Replaced the monolithic README-copy home page with a curated 7-page MkDocs Material site: Home, Getting Started, User Guide (Use Cases / void() Helper / Audit Tools), Troubleshooting, and demo links. Switched theme to Material, added Open Graph tags, JSON-LD structured data (SoftwareApplication / FAQPage / TechArticle per page), spec-compliant
llms.txt, andgit-revision-date-localizedplugin. README is unchanged (still the PyPI cover page). (#146)
Deprecated🔗
target=Nonesentinel — useTargetMode.NOTIFY. Passingtarget=Nonenow emits aFutureWarningat decoration time. The sentinel remains accepted but will be removed in v1.0. Migrate totarget=TargetMode.NOTIFY. (#150)target=Truesentinel — useTargetMode.ARGS_REMAP. Passingtarget=Truenow emits aFutureWarningat decoration time. The sentinel remains accepted but will be removed in v1.0. Migrate totarget=TargetMode.ARGS_REMAP. (#150)DeprecationWrapperInfoattributesempty_mapping→empty_args_mappingandidentity_mapping→identity_args_mapping. Old names are kept as deprecated@propertyaliases that emitDeprecationWarningon access and will be removed in v1.0. (#166)
Changed🔗
Config & API
- Misconfigured
TargetModecombinations now warn at construction time.TargetMode.ARGS_REMAPwithoutargs_mapping,TargetMode.NOTIFYwithargs_mapping, andTargetMode.NOTIFYwithargs_extraall surface aUserWarningimmediately. (#150) DeprecationConfig.targetalways stores a normalisedTargetModeor callable. Legacy boolean sentinels (True/False) are now normalised at decoration time and are never stored verbatim inDeprecationConfig.target. Code that inspects__deprecated__.targetmust compare againstTargetMode.NOTIFY,TargetMode.ARGS_REMAP, a callable, orNone— never againstTrueorFalse. (#150)deprecated_class()withtarget=TargetMode.NOTIFYnow emitsUserWarningat decoration time whenargs_mappingorargs_extrais supplied. These parameters are ignored inNOTIFYmode; passing them has always been a misconfiguration. The warning will becomeTypeErrorin v1.0. (#150)targetparameter of@deprecatednow defaults toTargetMode.NOTIFY. Callers can omittargetentirely for warn-only deprecation:@deprecated(deprecated_in="1.0", remove_in="2.0")is now the canonical form. Passingtarget=TargetMode.NOTIFYexplicitly remains valid. This default is permanent and will not change in future releases. (#162)- Decoration-time
UserWarningwhen@deprecatedomitsdeprecated_in. Whendeprecated_inis absent aUserWarningis emitted immediately at decoration time (not at call time) regardless oftargetshape (TargetMode.NOTIFY, callable, orARGS_REMAP), even ifremove_inis set. Applies to functions and methods (not classes). Suppressed whenstream=Noneor when a customtemplate_mgsis provided. (#162)
CLI
- CLI chains reporting split.
checksubcommand reports deprecated-to-deprecated chains as warnings (exit 0);chainsandallsubcommands report chains as errors (exit 1). (#149)
Docs
- Docs site URL layout is now versioned. Content is published under
https://borda.github.io/pyDeprecate/stable/(for the stable alias) andhttps://borda.github.io/pyDeprecate/<tag>/(for release tags). The root URL (https://borda.github.io/pyDeprecate/) redirects tostable/. External bookmarks to flat paths like.../pyDeprecate/troubleshooting.htmlwill break on first deploy — update them to.../pyDeprecate/stable/troubleshooting.html. (#148)
Fixed🔗
Guards & compatibility
- PEP 702 compatibility crash fixed. When
@deprecatedwas stacked under a PEP 702@typing.deprecateddecorator,wrapped_fnattempted to look up__deprecated__on the outer wrapper and raisedAttributeError. Fixed by capturingdep_metaas a closure variable at decoration time instead of re-reading it from the wrapper. (#169) - Cross-class guard false positives resolved;
TypeErrorsemantics preserved. Two previously documented "irresolvable" false-positive scenarios are now handled: (1) targets with metaclass/dynamic-class qualnames (e.g.type("Name", bases, ns)or manualfn.__qualname__ = "FakeOwner.method") — guard now skips silently when the named class is absent from the target's module globals; (2) pre-applied decorators that rewrite the source's__qualname__— guard reads the true enclosing class from the Python class-body frame, which cannot be mutated by user decorators. The guard continues to raiseTypeErrorat decoration time for genuine cross-class forwarding. (#169) target=Falsesentinel now emitsUserWarningat decoration time.target=Falsewas never a valid configuration; previously the behavior was undefined. The sentinel now surfaces aUserWarningimmediately and will raiseTypeErrorin v1.0. (#150)
Warnings & forwarding
- Double
FutureWarningemission ondeprecated_class()in NOTIFY mode fixed. Usingdeprecated_class()withtarget=TargetMode.NOTIFYtriggered twoFutureWarningemissions per construction call. (#162) args_mappingrename no longer clobbers source default when both old and new parameter names are present. Previously, calling a deprecated wrapper with the old argument name while the source also accepted the new name could silently overwrite the new-name value. The remapping now correctly renamesold=Xtonew=Xwithout discarding a separately suppliednewvalue. (#150)
[0.7.0] — 2026-03-31 — Docstring Tooling🔗
Added🔗
Docstring injection
- MkDocs admonition output.
@deprecatednow acceptsdocstring_style="mkdocs"(alias:"markdown"). Whenupdate_docstring=True, the deprecation notice is injected as a!!! warning "Deprecated in X"admonition instead of a Sphinx.. deprecated::directive. Usedocstring_style="auto"to detect style automatically from existing docstring content. (#134) - Google / NumPy section-aware docstring injection.
update_docstring=Truenow inserts the deprecation notice before the first section (Args:,Returns:,Parameters, …) rather than appending it at the end. (#134) - Inline arg deprecation in docstrings. When
args_mappingis set andupdate_docstring=True, each renamed or removed argument is annotated directly in theArgs:/:paramsection of the docstring. (#136)
Extensions & demos
- Griffe extension for mkdocstrings (
deprecate.docstring.griffe_ext, beta) and Sphinx autodoc extension for deprecated classes (deprecate.docstring.sphinx_ext, beta). (#134) - Live demo documentation published to GitHub Pages — MkDocs demo, Sphinx demo, and portal landing page. (#134, #137)
Fixed🔗
- Fixed
getattr/setattrstring-literal calls (B009/B010) replaced with direct attribute access. (#139) - Fixed proxy swap skipped correctly when
super().import_object()returnsFalsein the Griffe extension; empty_proxy_docnow delegates tosuper().get_doc()in the Sphinx extension. (#139)
[0.6.0.post0] — 2026-03-14 — Deprecation Proxy for class/instances🔗
Changed🔗
- Softer class-deprecation fallback.
@deprecatedapplied directly to a class (Enum, dataclass, or plain class) now emits aUserWarningat decoration time and delegates todeprecated_class()internally, instead of raisingTypeError. Code using the old pattern continues to work; the warning points to the recommended API. (#132)
[0.6.0] — 2026-03-13🔗
Added🔗
deprecated_class()anddeprecated_instance()— full proxy support. Enum, dataclass, and built-in types can now be wrapped in a transparent proxy. Attribute access, item access, method calls, and class behaviour all forward to the underlying type with aFutureWarningemitted on first access. (#114)- Correct
isinstance()/issubclass()semantics on proxy classes.isinstance(x, proxy)andissubclass(Sub, proxy)now work as expected — previously raisedTypeError. Type checks do not consume the warning budget. (#126)
Changed🔗
@deprecatedon a class raisesTypeError. Applying@deprecateddirectly to a class now raisesTypeErrorat decoration time instead of silently misbehaving. Superseded inv0.6.0.post0by aUserWarning+ delegation todeprecated_class(). Use@deprecated_class()for class-level deprecation. (#120)
Deprecated🔗
- Audit API renamed for consistency. Old names remain as
@deprecatedshims until v1.0. (#125)
| Old name | New name |
|---|---|
find_deprecated_callables |
find_deprecation_wrappers |
validate_deprecated_callable |
validate_deprecation_wrapper |
DeprecatedCallableInfo |
DeprecationWrapperInfo |
no_warning_callrenamed toassert_no_warnings. The new name mirrorsassertWarns/assertRaisesfrom the standard library, making test intent immediately obvious. Old name kept as a deprecated alias until v1.0. (#131)
Fixed🔗
- Cross-class method forwarding now fails at decoration time. Passing a class as
targeton a non-__init__method previously silently forwardedselfof the wrong type — always a runtime bug, never a valid pattern. The guard now raisesTypeErrorat decoration time so the misconfiguration is caught immediately. (#121) find_deprecation_wrappers()no longer reports falseinvalid_argsfor proxy objects. The proxy__call__catch-all signature previously caused allargs_mappingkeys to be flagged as invalid; signature validation is now skipped for proxy objects. (#124)
[0.5.0] — 2026-02-23 — Deprecation Lifecycle Management🔗
Added🔗
deprecate.auditmodule — deprecation lifecycle management. A dedicated module grouping all inspection and enforcement utilities, designed to be called from pytest or CI scripts. Requires the optional[audit]extra:pip install pyDeprecate[audit]. (#111)find_deprecated_callables()/validate_deprecated_callable()— zero-impact wrapper detection. Scans a module or package for@deprecatedwrappers that have no real effect: invalidargs_mappingkeys, identity mappings, self-referencing targets, or missing version fields. ReturnsDeprecatedCallableInfodataclasses. (#72)validate_deprecation_expiry()— enforce removal deadlines in CI. Scans a module or package and returns all wrappers whoseremove_inversion has been reached or passed. Auto-detects the installed package version. Integrate as a pytest fixture or CI step to prevent zombie code from shipping past its scheduled removal. (#89)validate_deprecation_chains()— detect deprecated-to-deprecated forwarding. Identifies wrappers whosetargetis itself a deprecated callable, forming chains that users traverse unnecessarily. Reports two chain kinds via theChainTypeenum:TARGET(forwarding chain) andSTACKED(composed argument mappings). (#90)
Fixed🔗
@deprecatedwrappers now correctly handle var-positional Enum signatures. A subtle edge case where callables with var-positional parameters in their Enum signature caused incorrect argument forwarding is now resolved. (#104)
[0.4.0] — 2025-12-03 — Enhanced Documentation & Modernization🔗
Added🔗
update_docstringparameter — automatic Sphinx deprecation notices. Setupdate_docstring=Trueon@deprecatedto automatically append a.. deprecated::reStructuredText block to the function's docstring. IDE tooltips and Sphinx-generated API docs show the notice without any manual edits. (#31)
Changed🔗
- Deprecation warnings now use
FutureWarninginstead ofDeprecationWarning.DeprecationWarningis silenced by Python's default warning filters outside of test contexts, making it invisible to most end-users.FutureWarningis shown by default, ensuring callers actually see the migration message. (#16) - Minimum Python version raised to 3.9. Python 3.8 reached end-of-life in October 2024. (#73)
- License changed from MIT to Apache-2.0.
- Error messages now include the originating class or function name for easier debugging when a mapping fails. (#11)
[0.3.2] — 2021-06-11 — Support containing kwargs in target function🔗
Added🔗
targetfunctions using**kwargsare now supported. Previously, forwarding to a target that accepted**kwargsand accessed them viakwargs.get(...)raisedTypeErrorfor unrecognised argument names. Extra arguments from the deprecated call are now forwarded correctly. (#6)
[0.3.1] — 2021-05-31 — Fixed void typing🔗
Fixed🔗
void()type annotation corrected to satisfy mypy. The return type ofvoid()is now properly annotated — IDE and type checker warnings about unused parameters in deprecated function bodies are suppressed correctly.
[0.3.0] — 2021-04-21 — Conditional skip🔗
Added🔗
skip_ifparameter — conditional deprecation. Pass aboolor a zero-argument callable returningboolto skip the warning and forwarding when a runtime condition is true. Useful for gating deprecation behaviour on package version checks or feature flags. (#4)
[0.2.0] — 2021-03-29 — Improved self arg deprecations🔗
Added🔗
target=True— self-deprecation mode. Deprecate and remap arguments within the same function without forwarding to a separate callable. Use withargs_mappingto rename a parameter while keeping the function body intact. (#3)void()helper. Accepts any arguments and returnsNone. Silences IDE "unused parameter" warnings in deprecated function bodies where the body is never reached.no_warning_call()context manager. Assert that a block of code raises no deprecation warning — useful for verifying that new API paths are clean in tests. Renamed toassert_no_warnings()in v0.6.0. (#2)- Stacked
@deprecateddecorators. Multiple@deprecated(True, ...)decorators can be stacked on the same function for multi-hop argument migrations across versions, each with independent warning counts and version metadata.
[0.1.1] — 2021-03-21 — Allow infinite warning🔗
Added🔗
num_warns=-1— always-on warnings. Settingnum_warnsto-1causes the deprecation warning to fire on every call rather than stopping after N times.target=None— warn-only mode. The original function body still executes;@deprecatedadds only a warning with no call forwarding. Useful when you want to signal deprecation without changing any call behaviour.
[0.1.0] — 2021-03-20 — Initial release🔗
Added🔗
Core decorator & forwarding
@deprecated(target=callable)decorator. Marks a function as deprecated and automatically forwards all calls — including argument mapping — to a replacement function. The deprecated function body is never executed whentargetis a callable.- Automatic argument mapping. Positional arguments are resolved to keyword arguments and forwarded to the target's signature.
args_mappingrenames ({"old": "new"}) or drops ({"old": None}) individual arguments during forwarding. args_extra— inject additional kwargs into the target call. Pass adictof extra keyword arguments to merge into every forwarded call. Useful for providing default values or adapter arguments that the deprecated API never accepted.
Warning controls
- Configurable warning count (
num_warns). Warnings fire once per function by default; set to any positive integer to limit the total count per function lifetime. - Custom warning message template (
template_mgs). Format string with%(source_name)s,%(target_path)s,%(deprecated_in)s,%(remove_in)s, and%(argument_map)splaceholders. - Custom warning stream (
stream). Route warnings tologging.warning,warnings.warn, or any callable.