MPT-23347 refactor BucketDeleter scope resolution and trace scope deletes - #89
Conversation
📝 WalkthroughWalkthroughThe deletion flow now separates scope resolution from bucket deletion. ChangesScoped deletion flow
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Comment |
c480954 to
a4e78b1
Compare
77c20fa to
32c44a9
Compare
a4e78b1 to
457306f
Compare
32c44a9 to
8dad1a6
Compare
457306f to
d49dd9b
Compare
8dad1a6 to
36fb576
Compare
0ef2af5 to
2700d03
Compare
Split BucketDeleter into three collaborating pieces: ScopeResolver owns turning a selector scope into target ids (commerce-API agreement-id lookup with its MPTError-to-UpstreamSubscriptionError mapping, product/seller RQL query building, stored-agreement lookup); BucketDeleter keeps only the delete actions (delete_subscription, delete_agreement, delete_agreements_by_query, delete_all); and the new ScopeBucketDeleter converts a selector scope into those actions, owning the delete(scope) entrypoint with its dispatch, logging, and DeleteReport rendering. Fold the mutable statement_agreements property into DeleteOutcome as a frozenset field returned by the delete calls, removing the temporal coupling where callers had to read deleter state after the call. Trace the public delete actions with per-scope child spans under usage_reporting.delete_buckets: usage_reporting.delete_subscription, delete_agreement, and delete_agreements_by_query. MPT-23347 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2700d03 to
622c329
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (2)
backend/mpt_usage_reporting_extension/pipeline.py (1)
394-401: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winUnjustified
# noqa: WPS476suppression.
WPS476flags sequentialawaitin aforloop (recommendsasyncio.gatherfor independent coroutines) — here each product's delete runs one after another with no comment explaining why sequential processing is required (e.g., tracing/ordering constraints). As per path instructions, inline linter ignores must be "a narrow, justified last resort"; please add a rationale comment, or consider parallelizing the per-product deletes if they are independent.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/mpt_usage_reporting_extension/pipeline.py` around lines 394 - 401, The sequential await in _reset_products is suppressed without justification. Determine whether deleter.delete calls for each product_id are independent; if so, parallelize them with asyncio.gather and merge all outcomes afterward, otherwise retain the loop and add a concise rationale explaining the required ordering or tracing constraint next to the WPS476 suppression.Source: Path instructions
backend/tests/services/test_bucket_delete.py (1)
23-51: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated
_StubSubscriptions/_aitertest doubles across files.This stub (and
_aiter) is duplicated verbatim in the newbackend/tests/services/test_scope_resolver.py, and asubscriptions()fixture returning the same stub also appears inbackend/tests/cli/commands/test_push_estimates_by_id.pyper the graph evidence. Consider hoisting these into a shared fixtures module (e.g. aservices-scoped conftest or fixtures package registered viapytest_plugins) so all consumers share one definition.As per path instructions, backend tests should "share setup through fixtures (splitting a large conftest into a fixtures package registered via
pytest_plugins)".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/tests/services/test_bucket_delete.py` around lines 23 - 51, The test doubles _StubSubscriptions and _aiter are duplicated across backend test modules. Move their shared definitions into the backend test fixtures package or a services-scoped conftest registered through pytest_plugins, expose the subscriptions fixture there, and update test_bucket_delete.py, test_scope_resolver.py, and test_push_estimates_by_id.py to consume the shared fixtures instead of defining local copies.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@backend/mpt_usage_reporting_extension/pipeline.py`:
- Around line 394-401: The sequential await in _reset_products is suppressed
without justification. Determine whether deleter.delete calls for each
product_id are independent; if so, parallelize them with asyncio.gather and
merge all outcomes afterward, otherwise retain the loop and add a concise
rationale explaining the required ordering or tracing constraint next to the
WPS476 suppression.
In `@backend/tests/services/test_bucket_delete.py`:
- Around line 23-51: The test doubles _StubSubscriptions and _aiter are
duplicated across backend test modules. Move their shared definitions into the
backend test fixtures package or a services-scoped conftest registered through
pytest_plugins, expose the subscriptions fixture there, and update
test_bucket_delete.py, test_scope_resolver.py, and test_push_estimates_by_id.py
to consume the shared fixtures instead of defining local copies.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro
Run ID: e7062235-c143-4f2b-8030-be161b045603
📒 Files selected for processing (8)
backend/mpt_usage_reporting_extension/cli/commands/delete.pybackend/mpt_usage_reporting_extension/pipeline.pybackend/mpt_usage_reporting_extension/services/bucket_delete.pybackend/mpt_usage_reporting_extension/services/scope_resolver.pybackend/tests/cli/commands/test_delete.pybackend/tests/services/test_bucket_delete.pybackend/tests/services/test_scope_resolver.pybackend/tests/test_pipeline.py
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
softwareone-platform/mpt-extension-skills(manual)
📜 Review details
⏰ Context from checks skipped due to timeout. (6)
- GitHub Check: mpt-extension-usage-reporting (Prerequisites Create standard build artifact)
- GitHub Check: mpt-extension-usage-reporting (Prerequisites Set the version)
- GitHub Check: mpt-extension-usage-reporting (Helm Build Helm Chart)
- GitHub Check: mpt-extension-usage-reporting (Build Build Image)
- GitHub Check: mpt-extension-usage-reporting (Prerequisites Create standard build artifact)
- GitHub Check: mpt-extension-usage-reporting (Prerequisites Set the version)
⚠️ CI failures not shown inline (1)
GitHub Actions: PR build and merge / 0_build.txt: MPT-23347 refactor BucketDeleter scope resolution and trace scope deletes
Conclusion: failure
##[group]Run SonarSource/sonarqube-scan-action@v8.2.0
with:
projectBaseDir: backend
scannerVersion: 8.1.0.6389
scannerBinariesUrl: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli
skipSignatureVerification: false
env:
GITHUB_***REDACTED***
SONAR_***REDACTED***
##[endgroup]
Installing Sonar Scanner CLI 8.1.0.6389 for linux-x64...
Downloading from: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-8.1.0.6389-linux-x64.zip
Downloading signature from: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-8.1.0.6389-linux-x64.zip.asc
Importing SonarSource public key from hkps://keyserver.ubuntu.com...
[command]/usr/bin/gpg --homedir /home/runner/work/_temp/gpg-home-1784649246563-5205 --batch --keyserver hkps://keyserver.ubuntu.com --recv-keys 679F1EE92B19609DE816FDE81DB198F93525EC1A
gpg: keybox '/home/runner/work/_temp/gpg-home-1784649246563-5205/pubring.kbx' created
gpg: /home/runner/work/_temp/gpg-home-1784649246563-5205/trustdb.gpg: trustdb created
gpg: key 1DB198F93525EC1A: public key "SonarSource S.A. <infra@sonarsource.com>" imported
gpg: Total number processed: 1
gpg: imported: 1
Successfully imported key from hkps://keyserver.ubuntu.com
✓ SonarSource public key imported successfully
Verifying GPG signature...
[command]/usr/bin/gpg --homedir /home/runner/work/_temp/gpg-home-1784649246563-5205 --batch --verify /home/runner/work/_temp/2c648082-42cf-4c3f-8687-d7fc8481d90e /home/runner/work/_temp/bf66cb32-77cc-4a65-bd43-b21a213828f2
gpg: Signature made Tue Apr 21 07:20:26 2026 UTC
gpg: using RSA key D1436C0DBACEA48702AF97C363F1DD7753B8B315
gpg: Good signature from "SonarSource S.A. <infra@sonarsource.com>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner.
Primary key fingerprint: 679F 1EE9 2B19 609D E816 FDE8 1DB1 98F9 ...
🧰 Additional context used
📓 Path-based instructions (4)
**/*
⚙️ CodeRabbit configuration file
**/*: For each subsequent commit in this PR, explicitly verify if previous review comments have been resolved
Files:
backend/tests/cli/commands/test_delete.pybackend/mpt_usage_reporting_extension/services/scope_resolver.pybackend/mpt_usage_reporting_extension/cli/commands/delete.pybackend/tests/services/test_scope_resolver.pybackend/mpt_usage_reporting_extension/pipeline.pybackend/tests/services/test_bucket_delete.pybackend/tests/test_pipeline.pybackend/mpt_usage_reporting_extension/services/bucket_delete.py
backend/**/*.py
⚙️ CodeRabbit configuration file
backend/**/*.py: Follow the linting rules defined inbackend/pyproject.tomlunder[tool.ruff]and[tool.flake8].
For formatting, use Ruff instead of Black. Do not suggest Black formatting changes.
Review code against the linked repository'sstandards/python-coding.md.
Flag any code artifact not written in English: identifiers, comments, docstrings, log messages, error messages, or test names in any other language must be reported and translated to English.
Flag module-level docstrings in__init__.pyfiles, and redundant module-level docstrings that only restate the module name or path.
Verify modules are organized into cohesive packages instead of flat or grab-bagutils/helpersmodules, and flag inline linter or type-checker ignores (# noqa,# type: ignore) that are not a narrow, justified last resort.
Files:
backend/tests/cli/commands/test_delete.pybackend/mpt_usage_reporting_extension/services/scope_resolver.pybackend/mpt_usage_reporting_extension/cli/commands/delete.pybackend/tests/services/test_scope_resolver.pybackend/mpt_usage_reporting_extension/pipeline.pybackend/tests/services/test_bucket_delete.pybackend/tests/test_pipeline.pybackend/mpt_usage_reporting_extension/services/bucket_delete.py
backend/**
⚙️ CodeRabbit configuration file
backend/**: Review backend changes againstAGENTS.md,docs/architecture.md,docs/contributing.md, anddocs/testing.md.
Use relevant linked shared standards and operational guidance when those local documents reference them.
If the change adds or alters behaviour, components, configuration, or commands, verify the corresponding documentation (docs/*,README.md,AGENTS.md) is updated perstandards/documentation.md.
Files:
backend/tests/cli/commands/test_delete.pybackend/mpt_usage_reporting_extension/services/scope_resolver.pybackend/mpt_usage_reporting_extension/cli/commands/delete.pybackend/tests/services/test_scope_resolver.pybackend/mpt_usage_reporting_extension/pipeline.pybackend/tests/services/test_bucket_delete.pybackend/tests/test_pipeline.pybackend/mpt_usage_reporting_extension/services/bucket_delete.py
backend/tests/**
⚙️ CodeRabbit configuration file
backend/tests/**: Review backend test changes againstdocs/testing.mdand the linked repository'sstandards/unittests.md.
Verify that repository-specific test behavior and shared unit-test rules are followed.
Verify tests are written as functions (not classes), are grouped into packages mirroring the source, share setup through fixtures (splitting a large conftest into a fixtures package registered viapytest_plugins), keep fixture dependency depth at 3 or fewer levels, and usefreezeguninstead of patchingdatetime.
Files:
backend/tests/cli/commands/test_delete.pybackend/tests/services/test_scope_resolver.pybackend/tests/services/test_bucket_delete.pybackend/tests/test_pipeline.py
🔇 Additional comments (12)
backend/mpt_usage_reporting_extension/services/scope_resolver.py (1)
1-57: LGTM!backend/mpt_usage_reporting_extension/services/bucket_delete.py (1)
19-69: LGTM!Also applies to: 106-217, 219-258
backend/mpt_usage_reporting_extension/pipeline.py (1)
26-26: LGTM!Also applies to: 44-44, 133-147
backend/mpt_usage_reporting_extension/cli/commands/delete.py (1)
15-20: LGTM!Also applies to: 71-86
backend/tests/services/test_scope_resolver.py (2)
13-39: Duplicate of the_StubSubscriptions/_aitertest double already defined intest_bucket_delete.py.Same root cause as flagged in
backend/tests/services/test_bucket_delete.py— will be covered by the consolidated comment.
60-97: LGTM!
query_for,agreement_idsdedupe/skip-missing, upstream error wrapping, andsubscription_agreementsdelegation are all correctly exercised against theScopeResolvercontract shown in the snippets. Tests are plain functions with shallow fixture dependency depth, consistent with test guidance.backend/tests/services/test_bucket_delete.py (3)
15-20: LGTM!Fixture wiring (
resolver→deleter→scope_deleter) and the new multi-line imports correctly reflect theScopeResolver/BucketDeleter/ScopeBucketDeletercomposition shown inbucket_delete.py.Also applies to: 77-88
91-343: Test assertions correctly track theDeleteOutcome.statement_agreementscontract.All the refactored
scope_deleter.delete(...)calls, dedupe/skip-missing/zero-delete scenarios, and dry-run variants match the upstreamBucketDeleter/ScopeBucketDeleterbehavior shown in the referenced snippets (e.g.statement_agreementsbootstrapped even on zero-delete outcomes).
246-257: 🎯 Functional CorrectnessNo change needed.
DeleteReport.render()logs the per-subscription and per-agreement lines, plus the summary, sotest_delete_reports_the_summarymatches the emitted log text.> Likely an incorrect or invalid review comment.backend/tests/cli/commands/test_delete.py (1)
44-50: LGTM!Patching
ScopeBucketDeleterdirectly matches_build_scope_deleter's construction (ScopeBucketDeleter(BucketDeleter(...), resolver)), and the assertion ondeleter.deleteis unaffected by the internalBucketDeleter/ScopeResolverwiring.backend/tests/test_pipeline.py (2)
61-64: LGTM!Patching
pipeline.ScopeBucketDeleterinstead ofpipeline.BucketDeletercorrectly targets the classpipeline.reset()/_reset_products()actually instantiate.
157-160: LGTM!Each updated
DeleteOutcome(...)mock return correctly reflects the newstatement_agreementspropagation throughpipeline.reset()/_reset_products()for the corresponding scope (subscription/agreement/product/None), matching theResetScopeconstruction shown in the referencedpipeline.pysnippets.Also applies to: 169-173, 187-191, 208-214, 245-252, 284-287, 310-315, 359-362, 393-395, 426-427, 461-462, 487-490
|



🤖 AI-generated PR — Please review carefully.
What was done
BucketDeletermixed scope dispatch, deletion mechanics, commerce-API resolution, and mutablestatement_agreementsside-state read by callers after the delete returned. It is now three collaborating pieces:ScopeResolver(services/scope_resolver.py, new) — scope → target ids: the streaming commerce-API agreement-id lookup with itsMPTError→UpstreamSubscriptionErrormapping, the product/seller RQL query building, and the stored-agreement lookup for a subscription scope. Same split as the push-estimates resolvers.BucketDeleter— the delete actions only:delete_subscription,delete_agreement,delete_agreements_by_query,delete_all(public, was_delete_all). NoSelectorknowledge.ScopeBucketDeleter(new, same module) — converts a selector scope into delete actions: owns thedelete(scope)entrypoint with the match dispatch, outcome logging, andDeleteReportrendering; usesBucketDeleterto delete data.DeleteOutcomegains astatement_agreementsfrozenset returned by the delete calls; the mutable property and its cross-call|=accumulation are gone.pipeline.py's reset flow and thedeleteCLI command wireScopeBucketDeleter(BucketDeleter(...), resolver).Tracing —
trace_spanon the public delete actions, child spans underusage_reporting.delete_buckets(which sits onScopeBucketDeleter.delete):usage_reporting.delete_subscriptionwith the subscription idusage_reporting.delete_agreementwith the agreement idusage_reporting.delete_agreements_by_querywith the RQL queryThe resolver's streaming methods stay untraced — a span around an async generator would close at creation, before any row is fetched.
Testing
make checkandmake testpass (260 tests).test_scope_resolver.pysuite;test_bucket_delete.pyreworked around ascope_deleterfixture, including a dispatch-routing test (each selector kind → itsBucketDeleteraction,None→delete_all).Jira: MPT-23347
🤖 Generated with Claude Code
statement_agreementsinDeleteOutcome.ScopeBucketDeleter.make check.