Skip to content

v0.10.1: Cycle detection, strict property, & inner-order audit flag

Latest

Choose a tag to compare

@Borda Borda released this 03 Jul 07:57

pyDeprecate 0.10.1 closes two correctness gaps and ships one new audit diagnostic. Callable deprecation cycles now raise a clear RuntimeError instead of crashing with a RecursionError. The inner_order_property flag in audit results lets CI pipelines detect the silent setter/deleter gap from inner-order @property @deprecated stacking, and the new opt-in from deprecate import property catches that same mistake at class-body evaluation time — before any instance is created.

No migration required. All changes are additive.

✨ Spotlights

Circular deprecation chains raise RuntimeError

A target= chain that loops back (A → B → A) previously caused unbounded recursion. The decorator now detects re-entry via a ContextVar guard and raises a named error immediately. Async tasks each get an independent detection set — no false positives from concurrent coroutines.

@deprecated(deprecated_in="1.0", remove_in="2.0", target=lambda: cycle_b())
def cycle_a(): ...


@deprecated(deprecated_in="1.0", remove_in="2.0", target=cycle_a)
def cycle_b(): ...


cycle_a()
# RuntimeError: Circular deprecation cycle detected: `cycle_a` re-entered via its own target chain.

inner_order_property audit flag

find_deprecation_wrappers() now sets inner_order_property=True when a plain property has a @deprecated-wrapped fget — the inner-order shape where only the getter warns; setters and deleters added via @value.setter remain silently unprotected.

results = find_deprecation_wrappers(my_module)
flagged = [r for r in results if r.inner_order_property]
# Add to CI: assert not flagged, "Inner-order @property @deprecated found"

Opt-in strict property replacement

from deprecate import property shadows the builtin with _StrictProperty, which raises TypeError at class-body time when handed an already-@deprecated getter — turning the silent footgun into a loud, immediate failure.

from deprecate import deprecated
from deprecate import property  # opt-in strict mode for this module


class MyModel:
    @property  # TypeError raised here —
    @deprecated(deprecated_in="1.0", remove_in="2.0")  # before any instance is created
    def old_value(self): ...

Star imports (from deprecate import *) are unaffected — strictness is per-module opt-in only.

📝 Notable changes

🚀 Added

  • DeprecationWrapperInfo.inner_order_property flagfind_deprecation_wrappers() sets True for inner-order @property @deprecated where only fget warns; CI pipelines can filter on this field. (#201)
  • Opt-in strict property via from deprecate import property — raises TypeError at class-definition time when a getter already carries @deprecated metadata; star imports unaffected. (#201)

🔧 Fixed

  • Circular deprecation chains now raise RuntimeError at call timeContextVar re-entrancy guard replaces unbounded recursion; async cycle detection avoids false positives from concurrent tasks. (#200)
  • _StrictProperty TypeError message references correct module path — error now points to deprecate.deprecation._StrictProperty. (#201)

🏆 Contributors

  • Jirka Borovec (@Borda) — cycle detection implementation, inner-order audit flag, strict property enforcement

Full changelog: v0.10.0...v0.10.1