Skip to content

Extract bounty attempt reservation helpers#362

Open
TUPM96 wants to merge 1 commit into
ramimbo:mainfrom
TUPM96:codex/bounty-320-attempt-reservation-helpers
Open

Extract bounty attempt reservation helpers#362
TUPM96 wants to merge 1 commit into
ramimbo:mainfrom
TUPM96:codex/bounty-320-attempt-reservation-helpers

Conversation

@TUPM96
Copy link
Copy Markdown
Contributor

@TUPM96 TUPM96 commented May 26, 2026

Refs #320

Summary

  • Move bounty-attempt reservation status, serialization, active-filter, warning, and stale-expiry helpers out of app/main.py into app/bounty_attempts.py.
  • Keep API and MCP behavior unchanged while shrinking the route/API module.
  • Add focused helper tests for effective expired status, UTC serialization, stale attempt expiry, and overlap warnings.

Complexity reduced

app/main.py no longer owns the attempt-reservation helper logic used by both REST and MCP paths. The attempt-specific rules now live beside each other in a small module with direct tests, making future reservation behavior changes easier to review without scanning the main app wiring.

Validation

  • python -m pytest -> 337 passed
  • python -m ruff format --check . -> 50 files already formatted
  • python -m ruff check . -> All checks passed
  • python -m mypy app -> Success: no issues found in 15 source files

Copilot AI review requested due to automatic review settings May 26, 2026 01:59
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 26, 2026

Warning

Review limit reached

@TUPM96, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 25 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 407848d6-ac77-4258-a4ff-628038bec259

📥 Commits

Reviewing files that changed from the base of the PR and between ac99d79 and cf6efb3.

📒 Files selected for processing (3)
  • app/bounty_attempts.py
  • app/main.py
  • tests/test_bounty_attempt_helpers.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR extracts bounty-attempt helper logic from app/main.py into a dedicated app/bounty_attempts.py module and adds tests to validate key behaviors.

Changes:

  • Added app/bounty_attempts.py with reusable helpers for attempt status, serialization, warning generation, and expiring stale attempts.
  • Updated app/main.py to use the extracted helpers instead of inline implementations.
  • Added tests covering effective “expired” status in dict output and expiring stale attempts + overlap warnings.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
tests/test_bounty_attempt_helpers.py Adds tests for extracted bounty-attempt helper behavior (expiry + warnings).
app/main.py Removes inline helper implementations and switches usage to app.bounty_attempts.
app/bounty_attempts.py Introduces the extracted helper functions used by API and warning/expiry logic.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/bounty_attempts.py
Comment on lines +24 to +34
def bounty_attempt_to_dict(attempt: BountyAttempt, now: datetime) -> dict[str, Any]:
return {
"id": attempt.id,
"bounty_id": attempt.bounty_id,
"submitter_account": attempt.submitter_account,
"source_url": attempt.source_url,
"status": attempt_effective_status(attempt, now),
"expires_at": as_utc(attempt.expires_at).isoformat(),
"created_at": as_utc(attempt.created_at).isoformat(),
"updated_at": as_utc(attempt.updated_at).isoformat(),
}
Comment thread app/bounty_attempts.py
Comment on lines +18 to +21
def attempt_effective_status(attempt: BountyAttempt, now: datetime) -> str:
if attempt.status == "active" and as_utc(attempt.expires_at) <= now:
return "expired"
return attempt.status
Comment thread app/bounty_attempts.py
Comment on lines +45 to +60
def bounty_attempt_warnings(session: Session, bounty: Bounty, now: datetime) -> list[str]:
warnings: list[str] = []
awards_remaining = max(0, bounty.max_awards - bounty.awards_paid)
if bounty.status != "open":
warnings.append(f"bounty is {bounty.status}")
awards_remaining = 0
if awards_remaining <= 0:
warnings.append("bounty has no award slots remaining")
active_count = session.scalar(
select(func.count())
.select_from(BountyAttempt)
.where(*active_attempt_conditions(bounty.id, now))
)
if active_count and active_count > 1:
warnings.append(f"bounty has {active_count} active attempts")
return warnings
Copy link
Copy Markdown
Contributor

@Karry2019web Karry2019web left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review ✅ APPROVED

FILES INSPECTED:

  • app/bounty_attempts.py — new module (73 lines) with as_utc, attempt_effective_status, bounty_attempt_to_dict, active_attempt_conditions, bounty_attempt_warnings, expire_stale_bounty_attempts
  • app/main.py — removed _as_utc, _attempt_effective_status, bounty_attempt_to_dict (old version), _active_attempt_conditions, bounty_attempt_warnings, expire_stale_bounty_attempts; removed update from sqlalchemy imports (now only needed in new module); replaced 8 call sites with module functions
  • tests/test_bounty_attempt_helpers.py — new test file (108 lines), 2 focused tests

BEHAVIOR CHECKED:

  • as_utc: identical to original _as_utc. Naive → UTC assignment, aware → UTC conversion. No change.
  • attempt_effective_status: identical to _attempt_effective_status. Same "active + expired_at ≤ now → expired" logic.
  • bounty_attempt_to_dict: Notable change: parameter now is now required (datetime) instead of optional (datetime | None = None with _utc_now() fallback). Verified all 8 callers pass now explicitly — this is a safe simplification that removes the implicit fallback.
  • active_attempt_conditions: identical to _active_attempt_conditions.
  • bounty_attempt_warnings: identical logic — same award-remaining calculation, same status check, same active-count query. Only the reference to _active_attempt_conditions is replaced with active_attempt_conditions.
  • expire_stale_bounty_attempts: identical logic — same update query with bounty_id, status, expires_at conditions; same optional submitter_account filter.
  • update import moved from app/main.py to app/bounty_attempts.py — correct since main.py no longer uses update directly (verified: all SQL UPDATE operations in main.py already went through the extracted function).

TESTS VERIFIED:

  • test_bounty_attempt_dict_reports_effective_expired_status: Creates a BountyAttempt model object (not persisted) with expires_at in the past. Asserts attempt_effective_status returns "expired" and bounty_attempt_to_dict returns the correct dict shape with ISO-format timestamps and +00:00 UTC offset. Pure unit test — no DB needed.
  • test_bounty_attempt_helpers_expire_stale_attempts_and_warn_on_overlap: Full integration test using sqlite_url fixture. Creates a bounty with 3 attempts: 1 stale (alice, expired 5 min ago), 2 active (bob, carol). Calls expire_stale_bounty_attempts with alice's account and verifies only alice is expired. Then asserts bounty_attempt_warnings correctly reports "2 active attempts". Good coverage of expiration + warning paths.

CODE QUALITY:

  • Clean extraction: all 6 removed private helpers are exactly the logic moved to bounty_attempts.py. Zero behavioral changes (except the now parameter simplification which is strictly safer).
  • No new dependencies — only sqlalchemy + app.models.
  • from __future__ import annotations consistent with repo convention.
  • Narrow update import only in the module that needs it.
  • Tests are deterministic (no time.sleep, no flaky fixtures).
  • The as_utc helper is small but useful — it was fragmented across callers and now has a focused home.

SUMMARY: Clean extraction with excellent test coverage. The now parameter tightening from optional to required is a modest but correct API improvement since every caller already provides it. APPROVED — no blockers.

Copy link
Copy Markdown
Contributor

@tolga-tom-nook tolga-tom-nook left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed PR #362 at head cf6efb3 for Bounty #320 / review round #340.

Files inspected: app/bounty_attempts.py, app/main.py, and tests/test_bounty_attempt_helpers.py. The extraction keeps the route-level auth/validation/session flow in app.main while moving status derivation, active-attempt predicates, warning construction, serialization, and stale-attempt expiry into a small helper module. I checked the call sites for list/create/release paths and the helper tests cover naive/aware UTC serialization, effective expired status, scoped stale expiry, and overlap warnings.

Validation run locally on this head:

  • python3 -m pytest tests/test_bounty_attempt_helpers.py tests/test_api_mcp.py -q -> 78 passed
  • python3 -m ruff check app/bounty_attempts.py tests/test_bounty_attempt_helpers.py -> passed
  • python3 -m mypy app/bounty_attempts.py -> passed

No blocker found. One small non-blocking note: I intentionally included tests/test_api_mcp.py in the focused run because the PR leaves app.main wiring intact and this catches accidental MCP/app-construction regressions around the changed import surface.

Copy link
Copy Markdown

@carpedkm carpedkm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found one helper-boundary issue in the extraction.

attempt_effective_status() normalizes attempt.expires_at with as_utc(), but it compares that aware datetime directly to the caller-provided now. Because now is now part of the helper API, a direct helper caller that passes a naive UTC datetime gets a runtime TypeError instead of the old effective-status behavior.

Repro on current head cf6efb3:

attempt_effective_status TypeError can't compare offset-naive and offset-aware datetimes
bounty_attempt_to_dict TypeError can't compare offset-naive and offset-aware datetimes

The route callers currently pass an aware _utc_now(), so this is not showing up in the route tests. But the extraction makes the helper directly importable/tested, and it already has as_utc() specifically to normalize datetime inputs. I would normalize now inside attempt_effective_status() before comparing, and either do the same for active_attempt_conditions() or explicitly document/enforce aware now at that boundary. A focused regression can pass datetime(2026, 5, 26, 12, 0) as now and assert the effective status stays stable.

Validation I ran:

  • . .venv/bin/activate && python -m pytest tests/test_bounty_attempt_helpers.py tests/test_api_mcp.py -q -> 78 passed.
  • . .venv/bin/activate && python -m ruff check app/bounty_attempts.py tests/test_bounty_attempt_helpers.py -> passed.
  • . .venv/bin/activate && python -m mypy app/bounty_attempts.py app/main.py -> success.
  • git diff --check origin/main...HEAD -> clean.

No secrets, wallet private keys, payout credentials, private vulnerability details, deployment values, or MRWK price claims are included.

@ramimbo ramimbo added the mrwk:needs-info More information needed label May 26, 2026
@ramimbo
Copy link
Copy Markdown
Owner

ramimbo commented May 26, 2026

Maintainer note after #320 filled: this PR still references closed bounty #320. If you want it considered for the current code-health round, rebase against main, update the bounty reference to #377, and keep the scope distinct from already accepted #328, #337, #364, #375, and #376. This is not accepted or paid as-is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mrwk:needs-info More information needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants