perf: parallelize scout opportunity creation and remove sleep(2) (#101) #156

Merged
cal merged 2 commits from issue/101-perf-parallelize-scout-opportunity-creation-and-re into main 2026-04-08 10:26:19 +00:00
Collaborator

Closes #101

Summary

Replace the sequential for loop + asyncio.sleep(2) in open_st_pr_packs() with a single asyncio.gather() call that creates all scout opportunities concurrently.

Before: Each pack's scout opportunity was created one at a time with a 2-second sleep between each — adding ~8 seconds of post-display delay for a 5-pack open.

After: All scout opportunities are created simultaneously. Zero artificial delay.

Changes

  • helpers/main.py: Replace sequential loop and asyncio.sleep(2) with asyncio.gather() over all pack IDs. The if pack_cards: guard is removed — create_scout_opportunity() already has an early if not pack_cards: return guard internally.

Why the sleep was there

The sleep was a blunt rate-limiting workaround for sequential Discord channel sends. With parallel execution, discord.py's built-in rate-limit handling is sufficient — it retries with backoff automatically if the API returns 429.

Other observations

  • The sleep fired even when pack_cards was empty (sleep was outside the if pack_cards: block) — minor inefficiency in the original, now moot.
Closes #101 ## Summary Replace the sequential `for` loop + `asyncio.sleep(2)` in `open_st_pr_packs()` with a single `asyncio.gather()` call that creates all scout opportunities concurrently. **Before:** Each pack's scout opportunity was created one at a time with a 2-second sleep between each — adding ~8 seconds of post-display delay for a 5-pack open. **After:** All scout opportunities are created simultaneously. Zero artificial delay. ## Changes - `helpers/main.py`: Replace sequential loop and `asyncio.sleep(2)` with `asyncio.gather()` over all pack IDs. The `if pack_cards:` guard is removed — `create_scout_opportunity()` already has an early `if not pack_cards: return` guard internally. ## Why the sleep was there The sleep was a blunt rate-limiting workaround for sequential Discord channel sends. With parallel execution, discord.py's built-in rate-limit handling is sufficient — it retries with backoff automatically if the API returns 429. ## Other observations - The sleep fired even when `pack_cards` was empty (sleep was outside the `if pack_cards:` block) — minor inefficiency in the original, now moot.
Claude added 1 commit 2026-04-08 06:02:24 +00:00
perf: parallelize scout opportunity creation and remove sleep(2) (#101)
All checks were successful
Ruff Lint / lint (pull_request) Successful in 12s
8ddd58101c
Closes #101

Replace sequential for-loop with asyncio.gather() so all scout
opportunities are created concurrently. Remove asyncio.sleep(2) that
added ~8s of post-display delay for multi-pack opens. create_scout_opportunity()
already guards against empty pack_cards with an early return.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude added the
ai-reviewing
label 2026-04-08 09:54:46 +00:00
Claude reviewed 2026-04-08 09:56:09 +00:00
Claude left a comment
Author
Collaborator

AI Code Review

Files Reviewed

  • helpers/main.py (modified — lines 1788–1796)

Findings

Correctness

  • if pack_cards: guard removal is safe. create_scout_opportunity() has its own if not pack_cards: return guard at helpers/scouting.py:197. The outer guard is redundant and its removal is correct.
  • asyncio.sleep(2) removal is correct. The original sleep fired even when pack_cards was empty (it was outside the if pack_cards: block), making it unconditional per-pack delay. Removing it is sound — discord.py's rate-limit handler retries with backoff on 429 responses without requiring manual sleeps.
  • asyncio.gather() usage is correct. All shared arguments (team, pack_channel, author, context) are read-only within create_scout_opportunity(). The return value (list of None) is discarded. asyncio.gather(*[]) with an empty pack_ids list returns [] safely.
  • Error propagation unchanged. Both the db_post and channel.send calls inside create_scout_opportunity() are wrapped in their own try/except blocks, so individual failures degrade gracefully without affecting sibling coroutines.

Security

  • No issues found.

Style & Conventions

  • Follows the same asyncio.gather() pattern established in PR #157 (display_cards). Consistent.

Suggestions

  • No blocking suggestions. Worth a smoke-test on a 5-pack open to confirm Discord message ordering is acceptable — concurrent sends may arrive slightly out of sequence compared to the old sequential behaviour, but this is cosmetic.

Verdict: COMMENT

Functionally correct. The if pack_cards: guard removal is safe due to the internal early-return in create_scout_opportunity(). The sleep removal is justified and the parallelisation matches the existing asyncio.gather() pattern in this codebase. Gitea blocks self-approval — posting as COMMENT.


Automated review by Claude PR Reviewer

## AI Code Review ### Files Reviewed - `helpers/main.py` (modified — lines 1788–1796) ### Findings #### Correctness - **`if pack_cards:` guard removal is safe.** `create_scout_opportunity()` has its own `if not pack_cards: return` guard at `helpers/scouting.py:197`. The outer guard is redundant and its removal is correct. - **`asyncio.sleep(2)` removal is correct.** The original sleep fired even when `pack_cards` was empty (it was outside the `if pack_cards:` block), making it unconditional per-pack delay. Removing it is sound — discord.py's rate-limit handler retries with backoff on 429 responses without requiring manual sleeps. - **`asyncio.gather()` usage is correct.** All shared arguments (`team`, `pack_channel`, `author`, `context`) are read-only within `create_scout_opportunity()`. The return value (list of `None`) is discarded. `asyncio.gather(*[])` with an empty `pack_ids` list returns `[]` safely. - **Error propagation unchanged.** Both the `db_post` and `channel.send` calls inside `create_scout_opportunity()` are wrapped in their own try/except blocks, so individual failures degrade gracefully without affecting sibling coroutines. #### Security - No issues found. #### Style & Conventions - Follows the same `asyncio.gather()` pattern established in PR #157 (`display_cards`). Consistent. #### Suggestions - No blocking suggestions. Worth a smoke-test on a 5-pack open to confirm Discord message ordering is acceptable — concurrent sends may arrive slightly out of sequence compared to the old sequential behaviour, but this is cosmetic. ### Verdict: COMMENT Functionally correct. The `if pack_cards:` guard removal is safe due to the internal early-return in `create_scout_opportunity()`. The sleep removal is justified and the parallelisation matches the existing `asyncio.gather()` pattern in this codebase. Gitea blocks self-approval — posting as COMMENT. --- *Automated review by Claude PR Reviewer*
Claude added
ai-reviewed
and removed
ai-reviewing
labels 2026-04-08 09:56:28 +00:00
cal approved these changes 2026-04-08 10:26:09 +00:00
cal left a comment
Owner

Approved via pd-pr

Approved via pd-pr
cal added 1 commit 2026-04-08 10:26:14 +00:00
Merge branch 'main' into issue/101-perf-parallelize-scout-opportunity-creation-and-re
All checks were successful
Ruff Lint / lint (pull_request) Successful in 15s
e5ec88f794
cal merged commit 63a25ea0ba into main 2026-04-08 10:26:19 +00:00
cal deleted branch issue/101-perf-parallelize-scout-opportunity-creation-and-re 2026-04-08 10:26:20 +00:00
Sign in to join this conversation.
No reviewers
cal
No Milestone
No project
No Assignees
2 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: cal/paper-dynasty-discord#156
No description provided.