Skip to content

Commit 603ee85

Browse files
Bordaclaude
andcommitted
docs: document attrs_mapping for selective attribute deprecation
- Add "Selective attribute deprecation" section to docs/guide/use-cases.md with example, output block, and audit-visibility note - Add attrs_mapping <details> example block to README.md Enums/dataclasses section; add attrs_mapping to deprecated_class param tip - Add recipe, Decision Table row, flowchart branch, numbered decision step, and Agent Notes entry to docs/llms.txt --- Co-authored-by: Claude Code <noreply@anthropic.com>
1 parent 578355a commit 603ee85

3 files changed

Lines changed: 108 additions & 1 deletion

File tree

README.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ Not sure which API to reach for? Start here.
253253
| `update_docstring` | `False` | Append Sphinx `.. deprecated::` notice to docstring |
254254

255255
> [!TIP]
256-
> `@deprecated_class()` shares `target`, `deprecated_in`, `remove_in`, `num_warns`, `stream`, `args_mapping`, `args_extra`, `template_mgs`, `update_docstring`, and `docstring_style`.
256+
> `@deprecated_class()` shares `target`, `deprecated_in`, `remove_in`, `num_warns`, `stream`, `args_mapping`, `attrs_mapping`, `args_extra`, `template_mgs`, `update_docstring`, and `docstring_style`.
257257
> `deprecated_instance()` shares `deprecated_in`, `remove_in`, `num_warns`, `stream`, `args_extra`, and `template_mgs`; it requires `obj` and adds `name` (display name) and `read_only`.
258258
259259
</details>
@@ -917,6 +917,36 @@ True
917917

918918
<br>
919919

920+
<details>
921+
<summary>Example: selective attribute deprecation with <code>attrs_mapping</code></summary>
922+
923+
Use `attrs_mapping` to deprecate only specific attribute names — other attributes pass through silently. Covers renames, misspelling corrections, and warn-only notices on individual attributes.
924+
925+
```python
926+
from deprecate import deprecated_class
927+
928+
929+
class Config:
930+
colour: str = "red"
931+
timeout: int = 30
932+
933+
934+
# "color" is a deprecated alias for "colour"; access warns and redirects
935+
DeprecatedConfig = deprecated_class(
936+
attrs_mapping={"color": "colour"},
937+
deprecated_in="1.0",
938+
remove_in="2.0",
939+
)(Config)
940+
941+
print(DeprecatedConfig.color) # warns → returns Config.colour ("red")
942+
print(DeprecatedConfig.colour) # silent passthrough ("red")
943+
print(DeprecatedConfig.timeout) # silent passthrough (30)
944+
```
945+
946+
</details>
947+
948+
<br>
949+
920950
### 📝 Automatic docstring updates
921951

922952
You can automatically inject deprecation information into your function's docstring:

docs/guide/use-cases.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,56 @@ True
695695

696696
</details>
697697

698+
## Selective attribute deprecation
699+
700+
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.
701+
702+
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 redirect. Reads, writes, and deletes on non-listed attribute names pass through without any warning.
703+
704+
```python
705+
from deprecate import deprecated_class
706+
707+
708+
class Config:
709+
# Canonical names — callers should use these
710+
colour: str = "red"
711+
timeout: int = 30
712+
713+
714+
# Misspelling migration: "color" → "colour"; "size" is warn-only (no rename)
715+
DeprecatedConfig = deprecated_class(
716+
attrs_mapping={"color": "colour", "size": None},
717+
deprecated_in="1.0",
718+
remove_in="2.0",
719+
)(Config)
720+
721+
# Deprecated alias — warns and returns Config.colour
722+
print(DeprecatedConfig.color)
723+
724+
# Canonical name — silent passthrough, no warning
725+
print(DeprecatedConfig.colour)
726+
727+
# Warn-only: no rename, "size" is still returned as-is (value shown as 0 since Config has no "size")
728+
print(DeprecatedConfig.timeout)
729+
```
730+
731+
<details>
732+
<summary>Output: <code>DeprecatedConfig.color; DeprecatedConfig.colour; DeprecatedConfig.timeout</code></summary>
733+
734+
```
735+
red
736+
red
737+
30
738+
```
739+
740+
</details>
741+
742+
`attrs_mapping` can be combined with `target=NewClass` — the class-level proxy warning fires on instantiation, while `attrs_mapping` intercepts individual attribute reads/writes/deletes.
743+
744+
!!! note "Audit visibility"
745+
746+
`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.
747+
698748
## Automatic docstring updates
699749

700750
Set `update_docstring=True` to inject a deprecation notice directly into the function's docstring at import time. The rendered API reference (Sphinx or MkDocs) always shows the deprecation status alongside the signature, with no manual upkeep.

docs/llms.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,27 @@ class OldClient:
9696
pass
9797
```
9898

99+
### Deprecate selected attributes on a class
100+
101+
```python
102+
from deprecate import deprecated_class
103+
104+
105+
class Config:
106+
colour: str = "red"
107+
timeout: int = 30
108+
109+
110+
# Only "color" warns and redirects to "colour"; all other attrs pass through silently.
111+
# Use None as value for warn-only (no rename): attrs_mapping={"size": None}
112+
DeprecatedConfig = deprecated_class(
113+
attrs_mapping={"color": "colour"},
114+
deprecated_in="1.2",
115+
remove_in="2.0",
116+
# FutureWarning on DeprecatedConfig.color: "The `color` was deprecated since v1.2 in favor of `Config.colour`. It will be removed in v2.0."
117+
)(Config)
118+
```
119+
99120
### Deprecate a constant or object
100121

101122
```python
@@ -148,6 +169,9 @@ What is being deprecated?
148169
├─ A class, Enum, or dataclass?
149170
│ → use @deprecated_class(target=NewCls, ...) — NOT @deprecated
150171
│ (classes are callables, but @deprecated is for functions/methods only)
172+
│ └─ Only some attributes deprecated (rename or misspelling fix)?
173+
│ → also add attrs_mapping={"old_attr": "new_attr"} (or None for warn-only)
174+
│ only listed attrs warn; all others pass through silently
151175
152176
├─ A `@property`?
153177
│ → use `@deprecated @property` (outer order); all three accessors (`fget`/`fset`/`fdel`)
@@ -211,6 +235,7 @@ pydeprecate status src/ # standalone deprecation table only (no checks)
211235
| Drop deprecated argument | `args_mapping={"old": None}` |
212236
| No replacement | `@deprecated(target=TargetMode.NOTIFY)` or omit `target` |
213237
| Class rename | `@deprecated_class(target=NewClass)` |
238+
| Deprecate selected class attributes | `deprecated_class(attrs_mapping={"old": "new"}, ...)` |
214239
| Constant/object alias | `deprecated_instance(obj, ...)` |
215240
| CI expiry check | `validate_deprecation_expiry(...)` or `pydeprecate all src/` |
216241
| Docs table | `generate_deprecation_table(pkg, style="compact")` or `style="matrix"` |
@@ -221,12 +246,14 @@ pydeprecate status src/ # standalone deprecation table only (no checks)
221246
1. Are you renaming a callable? Use `@deprecated(target=new_callable)`.
222247
2. Are you renaming or dropping arguments? Use `TargetMode.ARGS_REMAP`.
223248
3. Are you renaming a class? Use `deprecated_class(target=NewClass)`.
249+
3a. Deprecating only some attributes (rename or misspelling)? Use `deprecated_class(attrs_mapping={"old": "new"}, ...)`.
224250
4. Are you keeping an object alias? Use `deprecated_instance(obj, ...)`.
225251
5. Do you only need a warning? Use `TargetMode.NOTIFY` or omit `target`.
226252
6. Do you need removal governance? Add audit checks in CI.
227253

228254
## Agent Notes
229255

256+
- `deprecated_class(attrs_mapping={...})` deprecates only the listed attribute names on a class proxy. Keys are deprecated names; values are canonical replacement names (string) or `None` for warn-only. Reads, writes, and deletes on deprecated names warn and redirect; all other attribute access is silent. Circular mappings (a key that is also a redirect target) raise `ValueError` at decoration time. All entries share the same `deprecated_in`/`remove_in`; per-attribute lifecycle is not supported. `find_deprecation_wrappers` discovers the proxy but does not emit separate records for individual `attrs_mapping` entries.
230257
- `@deprecated` works with `@classmethod`, `@staticmethod`, `@property`, and `@cached_property` in either decorator order — `@classmethod @deprecated` and `@deprecated @classmethod` both produce `classmethod(deprecated_wrapper)`; `@staticmethod @deprecated` and `@deprecated @staticmethod` both produce `staticmethod(deprecated_wrapper)`. Both fire `FutureWarning` at call time.
231258
- `@property` with `@deprecated`: when `@deprecated` is on the **outside** (`@deprecated @property` order or explicit `deprecated(...)(property(...))`), all three accessors are wrapped — `fget`, `fset`, and `fdel` each fire `FutureWarning` when accessed, assigned, or deleted. Warning fires on attribute access (`obj.prop` triggers it, not `obj.prop()`). When `@deprecated` is on the **inside** (`@property @deprecated`), only `fget` is wrapped — warning fires on read but not on write or delete. For setter/deleter wrapping use the outer order: `@deprecated @property` then chain `@value.setter` / `@value.deleter` (re-wrapped via `_DeprecatedProperty`), or explicit `property(fget, fset[, fdel])` construction. Note: `type(x) == property` returns `False` on a decorated property; `isinstance(x, property)` is preserved. Audit: `find_deprecation_wrappers` scans the first accessor that carries `__deprecated__` metadata (`fget` → `fset` → `fdel`); setter-only properties (`fget=None`) are discovered via `fset`. `target=<callable>` and `target=TargetMode.ARGS_REMAP` / `True` both raise `TypeError` when decorating a property — property deprecation is warn-only; to redirect, delegate manually in the accessor body (e.g. `return self.new_prop`). Dataclass field alias: add a deprecated `@property` with the old name delegating to the new field; do NOT reuse an existing dataclass field name (the `@dataclass`-generated `__init__` assigns `self.field = value` which conflicts with a property descriptor of the same name).
232259
- `@cached_property` with `@deprecated` in either order: warning fires on **first access only** — subsequent accesses hit the cached value and do not emit a warning.

0 commit comments

Comments
 (0)