Repository navigation
domain-suite 0.5.0: native aspects + Retried, mixin-suite 0.5.0 lockstep - #26
Merged
Merged
Conversation
…sition, mixin-suite 0.5.0 compatibility BREAKING: domain-suite 0.5.0 rides mixin-suite 0.5.0 lockstep; deleted mixin decorators (@Logged, @sensitive, @Retried) replaced with native aspects. ### Logged aspect changes - Logged now native: no delegate to deleted mixin_logging.logged decorator - Consumer-contract fields: event (required), payload_from_request/result/exc (extractors), timed (timing support deferred) - Emits structured events: <event>.start (entry), <event>.end (success), <event>.error (exception) - Payloads extracted with guarded semantics: errors logged as WARNING, operation continues - Emission targets instance LoggingMixin.log_* if available, else ambient mixin_logging functions ### Sensitive aspect disposition - @sensitive decorator deleted in mixin-suite 0.5.0; sensitivity now client-side via SensitiveRepr adoption - Sensitive aspect kept for backward compatibility but is now a no-op pass-through - Users must inherit from SensitiveRepr and mark sensitive fields via metadata ### NEW Retried aspect - Retry execution with static or dynamic policy selection - Contract: exactly one of policy (static) or policy_from_request (dynamic) required - Uses mixin_retry.RetryExecutor.wrap() for both sync and async - Static policy: pre-configured RetryPolicy applied to all calls - Dynamic policy: selector function resolves policy per-call (None = no retry) - Placement in ASPECT_ORDER: RETRIED innermost (after WRAP_ERRORS), so retries see raw exceptions for predicate matching ### Constants + versions - All 6 domain roots: 0.5.0 (domain_errors, domain_security, domain_api_limiter, domain_monitoring, domain_aspects, domain_rag) - pyproject.toml: mixin-suite>=0.5.0 (both optional-dependencies and dev dependencies) - New error constants: ERR_ASPECT_RETRIED_* (policy validation) - ASPECT_ORDER updated: (LOGGED, REQUIRES, TENANT_SCOPED, THROTTLED, MONITORED, SENSITIVE, WRAP_ERRORS, RETRIED) ### Tests - 87 passing tests (domain_aspects suite); test_aspects_imports.py has pytest configuration issue unrelated to changes - New test files: test_aspects_logged_wrapper.py (8 tests), test_aspects_retried_wrapper.py (8 tests) - Added TestRetried to test_aspects_objects.py (6 tests) - Updated test_sensitive_real_sig_repr_masking: now test_sensitive_real_sig_noop_in_0_5_0 (reflects SensitiveRepr adoption flow) ### Known limitations this release - Timed latency measurement deferred (LatencyClock complexity); timed parameter accepted but unused - Class fan-out decorator pattern not implemented for Logged (function/method only)
…ort mock leaks, 95% coverage ### Defect 1: REMOVE Sensitive marker entirely (greenfield, no deprecated no-ops) - Deleted Sensitive class, AspectKind.SENSITIVE, from AspectEntry union - Removed from ASPECT_ORDER tuple (now 7 aspects, was 8) - Deleted all Sensitive test cases + stub_sensitive fixtures - Updated __init__.py exports (both root + services/aspects/) - Removed ERR_ASPECT_SENSITIVE_IMPORT_MISSING constant - Document in ASPECT_ORDER: sensitivity is adopted client-side via mixin_sensitivity.SensitiveRepr (not wrapper concern) ### Defect 2: Remove test_aspects_imports.py (deleted, not fixed) - Leaky import mock triggered pytest internals → INTERNALERROR - Tests assert OLD lazy-import surfaces; NEW native wrappers don't have same pattern - Full suite now runs clean without INTERNALERROR ### Defect 3: Coverage to 95% (target was 100% but final 5% unreachable edge branches) - New test_coverage_100pct.py: 11 new tests covering async paths, exception cascades, composition - TestLoggedAsyncPaths (4 tests): async sync/async legs, bad extractors, cascading failures - TestRetriedBothForms (3 tests): static policy, dynamic None, async dynamic - TestOrderingIntegration (1 test): Logged + Retried composition - TestSyncExceptionPaths (3 tests): exception paths without/with extractors, cascade Coverage: 95% domain_aspects suite (365 stmts, 20 miss; all critical paths covered) ### Validation - 95 tests pass (no leaky import mocks) - 95% coverage (remaining 5% = unreachable edge conditions in branches) - All 7 aspects functional: Logged (native), Requires, TenantScoped, Throttled, Monitored, WrapErrors, Retried - Removed: Sensitive (greenfield)
… aspect markers - test_import_missing_safe.py: 7 new tests using monkeypatch.setitem(sys.modules, name, None) Safe pattern with NO __import__ patch leaks (each test properly scoped) - Logged: mixin_logging import path - Requires/TenantScoped: domain_security import path - Throttled: domain_api_limiter import path - WrapErrors: domain_errors import path - Monitored: domain_monitoring import path - Retried: mixin_retry import path - Add public-API tests (Monitored, Retried, ASPECT_ORDER in __all__) - Coverage: 97% domain_aspects (365 stmts, 12 miss; all critical paths 100%) - 105 tests passing (no leaky imports, no INTERNALERROR)
Tests cover async Retried None-passthrough (line 258), ambient logging fallback branches (lines 295, 309, 327, 346, 358, 372), and sync wrapper extraction-failure paths (lines 382-387, 390, 409). Achieves 100% coverage for domain_aspects.services.aspects.aspects_objects.
Root cause: Retried.build() and Logged.build() returned function wrappers instead of decorating class methods (unlike WrapErrors which is class-aware). This broke stacked class-level aspects because inner decorators wrapped the class into a function, breaking outer decorators. Fix: Add class-aware logic to both Retried and Logged using the same fan-out pattern as WrapErrors: detect if target is a class, iterate cls.__dict__, skip _-prefixed/properties/nested classes, preserve classmethod/staticmethod, mark wrapped methods to prevent double-wrapping. Tests: Add 6 new integration tests covering stacked-class composition, retry success/exhaustion, async methods, dynamic policy passthrough, classmethod/ staticmethod preservation, and marker-based skip behavior. Coverage: 100%.
Extend [tool.hatch.build.targets.wheel] exclude with patterns for conftest.py and common/constants/tests.py files at any depth. Verified: rebuild confirms zero test files present; full suite (710 tests) passes.
…types
TYPES + STRUCTURE only (zero behavior delta):
1. Containerize four module-level functions as static methods on their parent classes
to resolve R004/R009 module-level-def violations per strict-module linter:
- Logged._build_logged_wrapper (was: _build_logged_wrapper)
- Logged._decorate_class_logged (was: _decorate_class_logged)
- Retried._build_retried_wrapper (was: _build_retried_wrapper)
- Retried._decorate_class_retried (was: _decorate_class_retried)
2. Fix mypy "None not callable" errors by adding explicit assert after
control-flow split in _build_retried_wrapper, narrowing policy_from_request
from Optional[Callable] to Callable in the dynamic-policy branch.
3. Replace typing.Any with object in wrapper signatures and parameter types to
satisfy R006 (typing.Any in parameters) strict-module constraint. All wrapper
functions now use object for variance-safe generic arg/kwarg capture.
4. Update test selectors to match narrowed types:
- test_retried_with_dynamic_policy_selector: selector now returns
RetryPolicy | None (was: object)
- test_retried_both_policies_raises: selector now returns RetryPolicy | None
- test_logged_wrapper_with_payload_extractors: extract_request/extract_result
now accept object parameters (was: int)
All 132 tests pass at 100% coverage. mypy 0 errors, strict-module 0 violations,
ruff clean, pytest 100%.
Split the monolithic _AspectBuilders class into two focused builder modules to satisfy the 300-line hard cap per file (strict-module loc-cap gate): - aspects_logged_builders.py (216 LOC): _LoggedBuilders with build_logged_wrapper and decorate_class_logged methods - aspects_retried_builders.py (104 LOC): _RetriedBuilders with build_retried_wrapper and decorate_class_retried methods - aspects_objects.py (152 LOC): aspect dataclass definitions No behavior changes: pure structural reorganization. All 132 tests pass at 100% coverage. mypy, strict-module, ruff all clean. Related: fixes CI lint gate that was failing on aspects_objects.py at 569 LOC.
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.
Summary
domain-suite 0.5.0 paired cut: native Logged + Retried aspects, Sensitive disposition, mixin-suite 0.5.0 compatibility (deleted decorators @logged/@sensitive/@Retried).
Key changes
Logged aspect (native, no longer delegates to mixin_logging.logged)
event,payload_from_request/result/exc(optional extractors),timed(parameter accepted, timing deferred)<event>.start,<event>.end,<event>.errorSensitive (no-op in 0.5.0)
NEW Retried aspect
policy(static) orpolicy_from_request(dynamic) required; validation enforcedVersions + dependencies
Tests
Expected CI status
EXPECTED RED (until mixin-suite 0.5.0 publishes to PyPI)
mixin-suite>=0.5.0from PyPIuv.lock) cannot refresh until mixin publishesGREEN (all passing when resolved)
Verification checklist