refine(proxy): expand deprecated_class & add design principles#193
Merged
Conversation
- Add `DeprecationEntry(target, deprecated_in, remove_in)` to `_types.py`: per-entry version overrides for `attrs_mapping`/`args_mapping` entries; plain strings and `None` (warn-only) continue to work as before with proxy-level fallback - Widen `attrs_mapping`/`args_mapping` to `dict[str, _MappingValue]` across `_types.py`, `proxy.py`, `deprecation.py`, `audit.py`, `docstring/inject.py`; add `_resolve_mapping_redirect`, `_get_entry_deprecated_in`, `_get_entry_remove_in` helpers - Fix `__instancecheck__`/`__subclasscheck__` to delegate through stacked `_DeprecatedProxy` chains (previously returned `False` when active object was a proxy, not a type) - Fix double `FutureWarning` on instantiation for stacked ATTRS_REMAP proxies; outer delegates to inner without global `_warn()` call - Fix `decorator(cls: type)` annotation in `deprecated_class` to accept `Union[type, _DeprecatedProxy]`; remove `cast(type, ...)` workaround from test fixture - Export `DeprecationEntry` from public `deprecate` namespace - Add `TestStackingCombinations` (7 new tests): three-layer chain, `isinstance`/`issubclass` recursion, canonical attr silent passthrough, blanket-outer + selective-inner, setattr propagation, `DeprecationEntry` in stacked outer layer - Update `test_proxy.py` stale "NOT supported" docstrings; update `docs/guide/use-cases.md` (replace do-not-stack warning, add `DeprecationEntry` section), `docs/llms.txt`, `README.md` --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR extends pyDeprecate’s proxy deprecation model to better support “stacked” @deprecated_class() proxies and introduces DeprecationEntry to allow per-entry deprecated_in/remove_in overrides inside attrs_mapping / args_mapping. It also updates tests and documentation to cover and describe the new behaviors.
Changes:
- Add
DeprecationEntryand mapping helpers in_types.py, widen mapping value types across proxy/deprecation/audit/docstring codepaths, and exportDeprecationEntrypublicly. - Fix stacked
_DeprecatedProxybehavior (notablyisinstance/issubclassdelegation and double-warning on stacked ATTRS_REMAP instantiation). - Add new unit tests for stacking +
DeprecationEntry, and update README/docs/changelog to document stacking and per-entry overrides.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unittests/test_proxy.py | Updates docstrings to reflect stacking support and points readers to new stacking tests. |
| tests/unittests/test_depr_entry.py | New test module covering stacked proxies and DeprecationEntry semantics. |
| tests/collection_deprecate.py | Removes cast() workaround now that deprecated_class() accepts _DeprecatedProxy inputs. |
| src/deprecate/proxy.py | Implements _MappingValue support, per-entry version extraction in warnings, and stacked-proxy fixes for __call__, __instancecheck__, __subclasscheck__, and typing. |
| src/deprecate/docstring/inject.py | Updates arg docstring annotation logic to handle mapping values (but currently doesn’t reflect per-entry version overrides). |
| src/deprecate/deprecation.py | Widens args mapping types and resolves mapping redirects when building warnings/call plans. |
| src/deprecate/audit.py | Updates args-mapping validation to work with _MappingValue and resolved redirects. |
| src/deprecate/_types.py | Adds DeprecationEntry, _MappingValue, and helper functions; updates config types to store richer mapping values. |
| src/deprecate/init.py | Exports DeprecationEntry from the public deprecate namespace. |
| README.md | Documents DeprecationEntry usage and mentions stacking support. |
| docs/llms.txt | Adds a DeprecationEntry documentation snippet for AI-facing guidance. |
| docs/guide/use-cases.md | Replaces the prior “do not stack” guidance with stacking + DeprecationEntry guidance and examples. |
| CHANGELOG.md | Adds release notes for DeprecationEntry and stacked deprecated_class support. |
`TargetMode.ARGS_REMAP` deprecates constructor argument names only — attribute access on the proxy has no connection to the deprecated call signature and must not emit a global `FutureWarning`. Previously, any proxy without `attrs_mapping` fell through to unconditional `self._warn()` in `__getattr__`, causing: - Double warning when stacking ATTRS_REMAP outer + ARGS_REMAP inner (inner fired a spurious global warn for every attribute access including during decoration) - Confusing single-layer behavior: `proxy.some_attr` warned even when no attribute name was deprecated, only the constructor argument Fix: skip `self._warn()` in the `__getattr__` blanket-warn path when `dep.target is TargetMode.ARGS_REMAP`. Blanket-warn mode (`target=None`) is unchanged. Adds `TestStackingCombinations::test_stacked_attrs_remap_outer_args_remap_inner`. --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
…stances - Extract 6 `_DEPRS_CASE_*_ARGS: dict[str, Any]` constants in `collection_deprecate.py`; splat with `**` at ~50 call sites — eliminates version drift across fixture groups - Hoist all `_class_deprecation_*` shared instances to the constants block at top of module; apply `**_DEPRS_CASE_STD_INF_ARGS` to `_class_deprecation_notify_only_callable_target` - Drop trailing commas before `)` in 31 test files (magic-comma removal lets ruff auto-collapse short calls) - Add `# type: ignore[arg-type]` to 3 `isinstance(inst, proxy)` assertions in `test_depr_entry.py` (same pattern as `test_classes.py`) - Document unification pattern and trailing comma rule in `CONTRIBUTING.md`; add quick-ref bullet to `AGENTS.md` --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Contributor
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Files Reviewed (7 changed files)
Reviewed by step-3.7-flash-20260528 · 452,991 tokens |
…ync docs - docs/guide/use-cases.md: add `from deprecate import deprecated_class` to stacking fenced block (line 1068) — phmdoctest test_code_1069 NameError CI fix - docs/guide/use-cases.md: standardise `# warns: FutureWarning` annotation in DeprecationEntry example (was `# warns: deprecated since v1.0`) - docs/guide/use-cases.md: document stacked ATTRS_REMAP instantiation warning asymmetry — only innermost layer fires at instantiation time - docs/llms.txt: extend attrs_mapping Decision Flowchart leaf to surface DeprecationEntry as per-version option per AGENTS.md sync rules - CHANGELOG.md: add (#193) PR links to DeprecationEntry and stacking bullets --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
…recationConfig Sphinx Napoleon renders only the first occurrence of a duplicate attribute key. The stale line 496 entry shadowed the new DeprecationEntry description at line 513, making per-arg version override documentation invisible in generated API docs. --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
…ring stacking When cls is a _DeprecatedProxy (blanket mode), cls.__name__ routes through __getattr__ which calls _warn(), emitting a warning at decoration time before any caller uses the API. Fix: retrieve the class name via object.__getattribute__(cls, '__deprecated__').name when cls is a _DeprecatedProxy instance. --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
…precated path Previously deprecation.py:812 resolved DeprecationEntry to Optional[str] before storing in reason_argument, discarding per-entry deprecated_in/remove_in. The @deprecated(args_mapping={'old': DeprecationEntry('new', deprecated_in='0.8')}) path always emitted proxy-level versions — inconsistent with the proxy path. - deprecation.py: keep raw _MappingValue in reason_argument (drop _resolve_mapping_redirect) - _raise_warn_arguments: group arguments by per-entry (deprecated_in, remove_in) pair via _get_entry_deprecated_in/_get_entry_remove_in; emit one warning per version group - docstring/inject.py: use _get_entry_deprecated_in/_get_entry_remove_in per arg entry so update_docstring=True generates accurate per-arg version strings --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
…d tests _detect_chain_type at audit.py:430-435 tested `new_attr in attrs_mapping` where new_attr could be a DeprecationEntry object (never equal to string keys), silently misclassifying DeprecationEntry-formed chains as non-stacked. Fix: resolve via _resolve_mapping_redirect(v) before the membership check. Test changes: - collection_targets.py: add V09TwoAttrClass (two-attr target) and v09_func_new_arg (target for DeprecationEntry args_mapping coverage), hoisting them out of test files - collection_deprecate.py: add StackedAttrProxy (two-layer deprecated_class stack), DeprecationEntryAttrProxy (single proxy with DeprecationEntry in attrs_mapping), depr_fn_with_entry_args_mapping (@deprecated with DeprecationEntry in args_mapping) - test_depr_entry.py: remove inline _V09Class and _Stacked definitions (three-layer rule); import from collection files; add TestDeprFnWithEntryArgsMapping (per-entry version propagation on @deprecated path) and TestFindDeprecationWrappersWithEntry (audit round-trip for DeprecationEntry values) --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
…ith 'import' and 'import from'' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
for more information, see https://pre-commit.ci
- `depr_fn_with_entry_args_mapping`: add `old_arg` to source signature so audit tool's `invalid_args` check passes (ARGS_REMAP expects old name in source sig, same pattern as `compute_power`); removes spurious "Wrong arguments: 1" in docs/guide/audit.md example test - `test_attrs_mapping_entry_survives_audit_round_trip`: use literal `"tests.collection_deprecate"` instead of `DeprecationEntryAttrProxy.__module__`; proxy `__getattr__` delegates `__module__` to wrapped class (`V09TwoAttrClass`) returning wrong module, causing scan to miss the proxy entirely - Remove `v09_func_new_arg` from `collection_targets.py` (no fixture uses it) --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
DeprecationEntry and enable stacking
Move 13 inline target class definitions (_Target, _Base, _Leaf, _Mutable) from test_depr_entry.py test methods into collection_targets.py with descriptive names. Add 11 new target classes: DeprEntryRedirectTarget, DeprEntryMixedTarget, DeprEntrySizeTarget, DeprEntryArgsInitTarget, StackingDeepBase, StackingLeafBase, StackingSilentBase, StackingBlanketBase, StackingMutableBase, StackingEntryBase, StackingArgsAttrsBase. Satisfies three-layer test rule: targets/wrappers out of test_*.py. All 964 tests pass; pre-commit hooks clean. --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
When deprecated_class(attrs_mapping={"old": "new"})(SomeDC) targets a
@DataClass, the proxy auto-expands into args_mapping so both the
attribute-access surface (proxy.old) and the constructor-kwarg surface
(DC(old=5)) emit FutureWarning from a single call. Records
args_mapping_auto_expanded on DeprecationConfig for audit tools.
Also guards args_mapping configured against POSITIONAL_ONLY constructor
params: emits UserWarning at decoration time and falls back to setattr at
call time instead of raising TypeError. Records incompatible_args_mapping
on DeprecationConfig. New validate_mapping_compatibility(module) audit
validator and _validate_attrs_redirect_targets now checks
__dataclass_fields__ so required dataclass fields are accepted.
---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Add AutoExpandDC, AutoExpandReqDC, PositionalOnlyTarget fixtures to collection_targets.py and their deprecated wrappers to collection_deprecate.py. Ten new tests: five for dataclass dual-surface auto-expand (constructor + class-proxy access + audit metadata + explicit args_mapping wins + DeprecationEntry preserved) and three for positional- only fallback (decoration warning + meta recorded + call-time setattr). Two audit tests verify validate_mapping_compatibility surfaces DepPositionalOnly and args_mapping_auto_expanded appears in scan results. --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
for more information, see https://pre-commit.ci
- Remove `_MappingValue = Optional[str]` type alias — no semantic value over the inlined form; replace all 22 usages across `_types.py`, `deprecation.py`, `proxy.py`, `audit.py` with `Optional[str]` directly - Remove `ArgsMapping = dict[str, Optional[str]]` — single call site; inline as `dict[str, Optional[str]]` --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
…mapping example - Add `## Detecting Mapping Incompatibilities` section to `audit.md` covering `validate_mapping_compatibility()` — explains POSITIONAL_ONLY fallback, shows usage and pytest integration; `validate_mapping_compatibility` was referenced in `use-cases.md` but had no dedicated audit doc - Expand `use-cases.md` `### Combining attribute and argument deprecation` with mixed redirect + warn-only + args_mapping example (`cuda: None`, `gpu → device`, `n_layers → num_layers`) and note on audit coverage - Inline `dep.deprecated_in` / `dep.remove_in` in `_build_attr_warning_msg` — two single-use local vars removed - Drop `stacked_proxy` pytest fixture from `test_stacking.py` — replace with direct `StackedAttrProxy` reference; fixture added no value over the module-level constant --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
- Extend AGENTS.md Never rule: ban all `catch_warnings(...)` forms in .md examples (both `simplefilter("always")` capture and `simplefilter("ignore", ...)` suppression variants); use `# warns: FutureWarning` / `# warns: UserWarning` inline annotations instead
- Replace three `catch_warnings` code blocks in audit.md, use-cases.md, troubleshooting.md with inline `# warns:` annotations
- Replace verbose `test_no_positional_only_mapping_incompatibilities` pytest block in audit.md with a compact advisory note — incompatible mapping is graceful degradation (setattr fallback works), not a hard CI blocker like chains or expiry
---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
for more information, see https://pre-commit.ci
DeprecationEntry and enable stackingdeprecated_class & add design principles
- SelfDeprecatedModel: new target with cuda/device attrs and num_layers ctor for self-deprecation stacking tests - DepCombinedSingleCall: single deprecated_class() call combining attrs_mapping + args_mapping with callable target - DepSelfCombinedTwoLayer: two stacked calls with no callable target — inner ARGS_REMAP (n_layers→num_layers), outer ATTRS_REMAP (cuda warn-only, gpu→device); outer delegates call to inner proxy without double-warning - TestCombinedSingleCallProxy: 3 tests for attr redirect, kwarg rename, and surface independence on single-call proxy - TestCombinedNoTargetTwoLayer: 5 tests for warn-only attr, redirect attr, ctor kwarg rename, silent canonical call, and surface independence on no-target two-layer proxy - Remove redundant "Feature 3" comment separator (class docstring carries context) - docs/guide/audit.md: add defaults to positional-only example params, replace prose paragraph with inline comment, revise setattr-fallback caveat to clarify default requirement --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
- Remove stale `DeprecationEntry` bullet from CHANGELOG Unreleased §Added — feature was removed in fbde70d, never shipped in a tagged release - Expand stacking CHANGELOG entry to cover no-target two-layer ATTRS_REMAP + ARGS_REMAP delegation - Add CHANGELOG entry for dataclass `attrs_mapping` auto-expand behavior - Add CHANGELOG entry for new `validate_mapping_compatibility()` public API - Add `validate_mapping_compatibility` section to README with pytest CI example --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
- Add test for validate_mapping_compatibility returning [] for clean module (no positional-only incompatibilities in collection_misconfigured) - Add test that args_mapping=None values are not flagged as incompatible - Add DepAutoExpandReqDC constructor and attribute-access tests (previously zero coverage despite fixture existing in collection_deprecate.py) - Add 3-layer ATTRS_REMAP instantiation test (asserts ≤1 FutureWarning; extends existing 2-layer no-double-warn coverage) - Add __instancecheck__/__subclasscheck__ non-type-active fallback tests via new TestDeprecatedProxyNonTypeFallback class - Add TestDepSelfCombinedTwoLayerProxy: two-layer stacking with attrs_mapping + args_mapping on same class (warn-only attr, kwarg rename, independence) --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
- Add seen:set[int] cycle guard to _get_static_attr_owner, __instancecheck__, and __subclasscheck__ delegation loops; returns False/None on revisit to prevent RecursionError when proxy chain contains a constructed cycle - Extend _validate_proxy to reject TargetMode.ATTRS_REMAP + args_extra misconfiguration; mirrors existing NOTIFY+args_extra rejection pattern --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
…tyle - Add validate_mapping_compatibility entry to docs/llms.txt Agent Notes section: return type, empty-list contract, recursive param, relationship to decoration-time UserWarning - Fix audit.py validate_mapping_compatibility docstring: Example: → Examples: (Google style required for Napoleon + --doctest-modules); RST :attr: cross- refs in Returns replaced with plain prose; example extended to show non-empty result against collection_deprecate.DepPositionalOnly fixture --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
for more information, see https://pre-commit.ci
Replace dataclasses.fields() with inspect.signature() when expanding attrs_mapping into args_mapping for @DataClass targets. - Excludes field(init=False) entries — not valid __init__ kwargs; passing them post-remap raised TypeError at call time with zero error message - Handles dataclasses with overridden __init__ — fields() returns @DataClass descriptor fields, not actual constructor params; signature correctly reflects the overridden signature - Removes the lazy `import dataclasses` block; inspect already imported Add AutoExpandInitFalseDC and AutoExpandOverriddenInitDC target fixtures; DepAutoExpandInitFalseDC and DepAutoExpandOverriddenInitDC wrappers; 5 new TestDataclassAutoExpand regression tests covering both exclusion cases. Add troubleshooting entry to docs/troubleshooting.md and FAQPage JSON-LD in docs/overrides/main.html explaining both exclusion cases with example. --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
…only Rename field and helper across all surfaces (src, tests, docs) for consistency with the args_mapping_auto_expanded naming convention; both fields now share the args_mapping_* prefix. - DeprecationConfig.incompatible_args_mapping → args_mapping_positional_only - DeprecationWrapperInfo.incompatible_args_mapping → args_mapping_positional_only - _get_incompatible_args_mapping_keys() → _get_args_mapping_positional_only_keys() - All doc references, repr strings, and test assertions updated Add known-limitation note for @deprecated crash when target function has POSITIONAL_ONLY parameters (/ syntax): docs/troubleshooting.md workaround entry, docs/llms.txt Known Limitations bullet, and docs/overrides/main.html FAQPage JSON-LD entry. --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
for more information, see https://pre-commit.ci
- Add _Reporter.positional_only_args() and wire into _Reporter.issues() so pydeprecate check surfaces wrappers whose args_mapping targets a POSITIONAL_ONLY constructor parameter - Simplify README "Detecting Positional-Only Incompatible Mappings" section to CLI-first (pydeprecate check) with brief programmatic note - Rework use-cases.md stacking section: demote audit-tip to !!! note admonition, promote to #### heading, replace abstract example with concrete LegacyConfig multi-version scenario - Move all local imports in test_proxy.py to module level; replace `import warnings as _warnings` alias with top-level `warnings` - Add "no local imports inside test functions" rule to CONTRIBUTING.md and AGENTS.md --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
for more information, see https://pre-commit.ci
…V09TwoAttrClass - Fix deprecated_class(Point, ...) → deprecated_class(...)(Point) in audit.md POSITIONAL_ONLY example (wrong calling convention caused TypeError) - Update validate_mapping_compatibility output in audit.md: Found 0 → Found 1 with DepPositionalOnly detail (DepPositionalOnly added in prior commit) - Add missing `from deprecate import deprecated` import to troubleshooting.md workaround block - Rename V09TwoAttrClass → StackedAttrTarget in collection_targets.py, collection_deprecate.py, test_stacking.py (version-encoded name replaced with descriptive one) - Add stacking multi-version use case to use-cases.md with concrete LegacyConfig scenario --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
ATTRS_REMAP governs attribute access only; _validate_proxy already warns that args_extra is a misconfiguration for this mode. Both the stacking delegation path and the non-stacking fallback now forward kwargs unchanged (matching the existing NOTIFY behaviour), so the code is consistent with the documented misconfig warning. --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
… annotations audit.md stated pydeprecate check exits 1 for positional-only mapping findings; the CLI exits 0 for those (advisory only; only invalid_args triggers exit 1). troubleshooting.md annotated two print() lines with # warns: FutureWarning but accessing DeprecationConfig fields does not emit any warning. --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Moves all local imports from inside test method bodies to module level, following the no-local-imports-in-tests rule. Removes redundant re-imports of warnings, find_deprecation_wrappers, and validate_deprecation_wrapper (already at module scope). Also removes trailing comma before ) in the mutable-stacking test fixture. --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Before submitting
What does this PR do?
This pull request updates documentation across several files to clarify and enforce design principles, improve test and documentation patterns, and document new features and best practices for deprecation handling. The changes focus on making the library's philosophy explicit, standardizing how deprecation and test cases are managed, and providing guidance for contributors to ensure consistency and maintainability.
Documentation and Design Principles:
.github/CONTRIBUTING.md,AGENTS.md, and theREADME.md, emphasizing simplicity, robustness, and flexibility as core values for the API and codebase. [1] [2] [3]README.mdto highlight these principles and their practical implications for users and contributors.Testing and Documentation Patterns:
.mdcode blocks must useprint()with output blocks instead ofassert; bareassertstatements andwarnings.catch_warningsare now explicitly discouraged in documentation examples. [1] [2]Deprecation Handling and Patterns:
Feature Documentation and Changelog:
CHANGELOG.mdto document new features: per-attribute/argumentDeprecationEntryversion overrides and support for stacking@deprecated_classdecorators, with examples and behavioral notes.@deprecated_classin theREADME.md.Audit and Validation Enhancements:
args_mapping_auto_expanded,incompatible_args_mapping) for better visibility of wrapper configuration diagnostics.PR review
Anyone in the community is free to review the PR once the tests have passed.
If we didn't discuss your PR in Github issues there's a high chance it will not be merged.
Did you have fun?
Make sure you had fun coding 🙃