Skip to content

feat: SimpleQ v2.0 — ground-up rewrite#8

Open
rdegges wants to merge 7 commits into
masterfrom
v2-rewrite
Open

feat: SimpleQ v2.0 — ground-up rewrite#8
rdegges wants to merge 7 commits into
masterfrom
v2-rewrite

Conversation

@rdegges
Copy link
Copy Markdown
Owner

@rdegges rdegges commented Mar 19, 2026

Summary

Complete rewrite of SimpleQ from Python 2 + boto to Python 3.10+ + boto3.

  • Core: SimpleQ() app, @sq.task decorator with .delay() / .apply(), Queue class with auto-create, Worker with burst mode and graceful shutdown
  • Features: named queues, retries with exponential/linear backoff, dead letter queues, FIFO queues, delayed jobs, async task support, Pydantic type-safe tasks, pickle serialization with 256KB check, cost tracking
  • CLI: simpleq worker, simpleq queue list/create/stats/purge/delete/dlq/redrive, simpleq stats
  • Modern packaging: pyproject.toml, ruff, mypy, pytest with moto
  • README-driven development: README is the spec per the gstack design doc

Test Coverage

104 tests across 9 test files covering:

  • Job serialization round-trips, 256KB limit, unpicklable args, corrupt body
  • Queue auto-create, FIFO, DLQ setup, send/receive, delete, purge, cost tracking
  • Task decorator (bare + options), FIFO suffix, .delay(), .apply()
  • Worker processing, burst mode, async tasks, retries, graceful shutdown, backoff math
  • CLI queue CRUD commands, stats, DLQ
  • SimpleQ app init, queue caching, task registry, shared cost tracker
  • Config defaults and env var overrides
  • CostTracker tracking, estimation, reset

Overall coverage: 84% (100% on core modules, lower on CLI/error paths)

Pre-Landing Review

4 issues found — all resolved:

  • [AUTO-FIXED] worker.py: logger.info() in signal handler moved to main loop (async-signal-safety)
  • [FIXED] job.py: added security docstring for pickle deserialization trust boundary
  • [FIXED] cli.py: added module path validation to reject file paths in -a flag
  • [FIXED] worker.py: added TODO comment for visibility timeout heartbeat

TODOS

Items completed in this PR:

  • SQS Message Size Validation (job.py 256KB check)
  • DLQ Existence Validation (queue.py raises QueueNotFoundError)
  • Branded AWS Credential Error (queue.py friendly error message)

Items remaining:

  • Visibility Timeout Heartbeat (noted as TODO in worker.py)
  • Zero-Config Lambda Mode (post-core, marked "coming soon" in README)

Test plan

  • All pytest tests pass (104 tests, 0 failures)
  • Pre-landing review completed, all findings resolved

🤖 Generated with Claude Code

rdegges added 7 commits March 18, 2026 21:40
Remove Python 2 + boto v1 source code (jobs.py, queues.py, workers.py),
old tests, setup.py, requirements.txt, and Travis CI config. This is a
ground-up rewrite — none of the v1 code is reusable.
pyproject.toml with Python 3.10+, boto3, pydantic, structlog, typer, rich.
Dev deps: pytest, moto, ruff, mypy. Updated .gitignore for modern Python.
- exceptions.py: 7-class hierarchy (SimpleQError → specific errors)
- config.py: Config dataclass with env auto-detection
- job.py: Job model with pickle serialization + 256KB size check
- cost.py: CostTracker for SQS API call accounting
- queue.py: boto3 SQS wrapper with auto-create, FIFO, DLQ, batch ops
- task.py: @sq.task decorator with .delay() and .apply()
- app.py: SimpleQ entry point with task registry and queue cache
- worker.py: polling loop, burst mode, async tasks, retries with
  backoff, SIGINT/SIGTERM graceful shutdown
- cli.py: typer CLI with worker, queue, and stats commands
- __init__.py: public API exports for SimpleQ, Queue, Task, Worker, Job
Tests cover: job serialization, queue CRUD, FIFO, DLQ, task decorator,
worker processing, burst mode, async tasks, retries, graceful shutdown,
backoff calculation, CLI commands, cost tracking, config env overrides,
and error cases (256KB limit, unpicklable args, auto_create=False).
README is the spec (README-driven development): quickstart, features
(tasks, queues, worker, retries, DLQ, FIFO, async, cost tracking),
CLI reference, configuration, Lambda mode teaser.
@rdegges
Copy link
Copy Markdown
Owner Author

rdegges commented Mar 19, 2026

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

SimpleQ v2.0 — Complete rewrite with Python 3.10+, boto3, and comprehensive test suite

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Complete ground-up rewrite from Python 2 + boto to Python 3.10+ + boto3
• Core library: SimpleQ app, @task decorator, Queue class, Worker with retries
• CLI: worker, queue CRUD, stats, DLQ management commands
• 104 comprehensive tests with moto covering all major features
• Modern packaging: pyproject.toml, ruff, mypy, pytest, structured logging
Diagram
flowchart LR
  A["Python 2 + boto v1"] -->|"Remove legacy code"| B["Clean slate"]
  B -->|"Add core modules"| C["Job, Config, Cost, Exceptions"]
  C -->|"Add Queue + Task"| D["Queue class, Task decorator"]
  D -->|"Add Worker + CLI"| E["Worker polling, CLI commands"]
  E -->|"Add 104 tests"| F["SimpleQ v2.0 complete"]
  F -->|"Modern stack"| G["Python 3.10+, boto3, pydantic, typer, rich"]
Loading

Grey Divider

File Changes

1. simpleq/__init__.py ✨ Enhancement +37/-0

Public API exports and version

simpleq/init.py


2. simpleq/exceptions.py ✨ Enhancement +39/-0

Exception hierarchy with 7 error types

simpleq/exceptions.py


3. simpleq/config.py ✨ Enhancement +32/-0

Configuration with env var auto-detection

simpleq/config.py


View more (30)
4. simpleq/job.py ✨ Enhancement +92/-0

Job serialization with 256KB validation

simpleq/job.py


5. simpleq/cost.py ✨ Enhancement +69/-0

SQS cost tracking per queue

simpleq/cost.py


6. simpleq/queue.py ✨ Enhancement +321/-0

SQS wrapper with auto-create, FIFO, DLQ

simpleq/queue.py


7. simpleq/task.py ✨ Enhancement +84/-0

Task decorator with delay/apply methods

simpleq/task.py


8. simpleq/app.py ✨ Enhancement +114/-0

SimpleQ entry point with task registry

simpleq/app.py


9. simpleq/worker.py ✨ Enhancement +181/-0

Worker with polling, retries, graceful shutdown

simpleq/worker.py


10. simpleq/cli.py ✨ Enhancement +375/-0

CLI with worker, queue, and stats commands

simpleq/cli.py


11. tests/conftest.py 🧪 Tests +64/-0

Shared pytest fixtures with moto mocking

tests/conftest.py


12. tests/test_job.py 🧪 Tests +103/-0

Job serialization and error handling tests

tests/test_job.py


13. tests/test_queue.py 🧪 Tests +256/-0

Queue CRUD, FIFO, DLQ, cost tracking tests

tests/test_queue.py


14. tests/test_task.py 🧪 Tests +176/-0

Task decorator, delay, apply method tests

tests/test_task.py


15. tests/test_app.py 🧪 Tests +93/-0

SimpleQ app init, queue caching, registry tests

tests/test_app.py


16. tests/test_worker.py 🧪 Tests +223/-0

Worker processing, retries, shutdown tests

tests/test_worker.py


17. tests/test_cost.py 🧪 Tests +82/-0

Cost tracker calculation and aggregation tests

tests/test_cost.py


18. tests/test_config.py 🧪 Tests +58/-0

Config defaults and env var override tests

tests/test_config.py


19. tests/test_cli.py 🧪 Tests +85/-0

CLI queue and stats command tests

tests/test_cli.py


20. pyproject.toml ⚙️ Configuration changes +91/-0

Modern Python packaging with dependencies

pyproject.toml


21. README.md 📝 Documentation +313/-43

Complete v2.0 documentation and quickstart

README.md


22. DESIGN.md 📝 Documentation +165/-0

Design system, aesthetics, and CLI guidelines

DESIGN.md


23. TODOS.md 📝 Documentation +37/-0

Implementation gaps and post-core features

TODOS.md


24. .travis.yml Additional files +0/-12

...

.travis.yml


25. requirements.txt Additional files +0/-4

...

requirements.txt


26. setup.py Additional files +0/-69

...

setup.py


27. simpleq/jobs.py Additional files +0/-82

...

simpleq/jobs.py


28. simpleq/queues.py Additional files +0/-154

...

simpleq/queues.py


29. simpleq/workers.py Additional files +0/-49

...

simpleq/workers.py


30. tests/__init__.py Additional files +0/-0

...

tests/init.py


31. tests/test_jobs.py Additional files +0/-49

...

tests/test_jobs.py


32. tests/test_queues.py Additional files +0/-69

...

tests/test_queues.py


33. tests/test_workers.py Additional files +0/-42

...

tests/test_workers.py


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Mar 19, 2026

Code Review by Qodo

🐞 Bugs (5) 📘 Rule violations (13) 📎 Requirement gaps (0) 📐 Spec deviations (0)

Grey Divider


Action required

1. Unbounded dependency versions 📘 Rule violation ⛯ Reliability
Description
pyproject.toml uses lower-bound-only constraints (e.g., boto3>=...) instead of bounded version
ranges. This can introduce breaking changes via transitive/major upgrades and violates the
dependency range policy.
Code

pyproject.toml[R2-45]

+requires = ["setuptools>=70.0"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "simpleq"
+version = "2.0.0"
+description = "The Python task queue for AWS. Dead simple. SQS native. Zero infrastructure."
+readme = "README.md"
+license = "ISC"
+requires-python = ">=3.10"
+authors = [
+    { name = "Randall Degges", email = "r@rdegges.com" },
+]
+keywords = ["sqs", "aws", "queue", "worker", "tasks", "background-jobs"]
+classifiers = [
+    "Development Status :: 4 - Beta",
+    "Environment :: Console",
+    "Intended Audience :: Developers",
+    "Operating System :: OS Independent",
+    "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
+    "Programming Language :: Python :: 3.12",
+    "Programming Language :: Python :: 3.13",
+    "Topic :: Software Development :: Libraries :: Python Modules",
+    "Topic :: System :: Distributed Computing",
+]
+dependencies = [
+    "boto3>=1.35.0",
+    "pydantic>=2.10.0",
+    "structlog>=24.4.0",
+    "typer>=0.15.0",
+    "rich>=13.9.0",
+]
+
+[project.optional-dependencies]
+dev = [
+    "pytest>=8.3.0",
+    "pytest-cov>=6.0.0",
+    "moto[sqs]>=5.0.0",
+    "mypy>=1.13.0",
+    "ruff>=0.8.0",
+    "boto3-stubs[sqs]>=1.35.0",
+]
Evidence
The compliance rule requires bounded version ranges in pyproject.toml, but the added dependencies
and build requirements specify only >= without an upper bound.

Rule 116648: Use version ranges in pyproject.toml and exact pins in lock files
pyproject.toml[2-45]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`pyproject.toml` dependency constraints are unbounded (lower-bound only), which violates the requirement to use bounded version ranges.

## Issue Context
The manifest currently contains entries like `boto3>=1.35.0` and `setuptools>=70.0` with no `<` upper bound.

## Fix Focus Areas
- pyproject.toml[2-45]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Mypy strict mode missing 📘 Rule violation ⚙ Maintainability
Description
The mypy configuration does not enable strict = true and omits many required strictness flags.
This weakens type-safety guarantees required by the checklist.
Code

pyproject.toml[R82-88]

+[tool.mypy]
+python_version = "3.10"
+warn_return_any = true
+warn_unused_configs = true
+disallow_untyped_defs = true
+check_untyped_defs = true
+
Evidence
The compliance rule requires mypy strict mode plus a specific set of options; the added
[tool.mypy] block includes only a small subset and does not set strict = true.

Rule 116650: Enforce mypy strict mode with specific options for Python 3.10
pyproject.toml[82-88]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Mypy is not configured per the required strict-mode policy (missing `strict = true` and multiple mandated flags).

## Issue Context
The project-level mypy config is defined in `pyproject.toml` under `[tool.mypy]`.

## Fix Focus Areas
- pyproject.toml[82-88]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Ruff config missing required rules 📘 Rule violation ⚙ Maintainability
Description
The Ruff configuration is missing required selections (e.g., TCH) and required global ignores
(e.g., B008), and the tests per-file ignore glob is too narrow. This violates the mandated Ruff
configuration policy.
Code

pyproject.toml[R58-81]

+[tool.ruff]
+target-version = "py310"
+line-length = 88
+
+[tool.ruff.lint]
+select = [
+    "E",
+    "W",
+    "F",
+    "I",
+    "B",
+    "C4",
+    "UP",
+    "ARG",
+    "SIM",
+    "PERF",
+]
+ignore = [
+    "E501",
+]
+
+[tool.ruff.lint.per-file-ignores]
+"tests/*" = ["ARG", "S101"]
+
Evidence
The compliance rule requires a specific set of select codes, global ignore including B008, and
a tests glob like tests/**/*.py; the current config omits these requirements.

Rule 116657: Enforce Ruff linting and formatting configuration for Python code
pyproject.toml[58-81]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Ruff is configured, but it does not match the required baseline (missing required rule groups and ignores, and tests per-file ignores are not applied broadly enough).

## Issue Context
The Ruff config lives in `pyproject.toml` under `[tool.ruff]`, `[tool.ruff.lint]`, and `[tool.ruff.lint.per-file-ignores]`.

## Fix Focus Areas
- pyproject.toml[58-81]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (10)
4. Tests not in required directories 📘 Rule violation ⚙ Maintainability
Description
Tests are placed directly under tests/ instead of tests/unit/ or tests/integration/. This
violates the required test directory layout policy.
Code

tests/test_app.py[1]

+"""Tests for the SimpleQ application class."""
Evidence
The compliance rule requires tests to live under tests/unit/ or tests/integration/, but this PR
adds test modules at tests/test_*.py.

Rule 116658: Place unit and integration tests in their designated directories
tests/test_app.py[1-1]
tests/test_queue.py[1-1]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Test files are located directly under `tests/` rather than the mandated `tests/unit/` and `tests/integration/` directories.

## Issue Context
Multiple new test modules use the `tests/test_*.py` pattern.

## Fix Focus Areas
- tests/test_app.py[1-1]
- tests/test_queue.py[1-1]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Task name violates allowed charset 📘 Rule violation ✓ Correctness
Description
Task.name is constructed using module.qualname which includes dots and can include <locals>,
violating the required [A-Za-z0-9_] task-name constraint and length policy. Invalid task names can
break downstream assumptions and validation requirements.
Code

simpleq/task.py[R34-38]

+        self.fn = fn
+        self.app = app
+        self.name = f"{fn.__module__}.{fn.__qualname__}"
+        self.retries = retries
+        self.backoff = backoff
Evidence
The rule requires task names to be alphanumeric/underscore only (1–255 chars), but the code sets
self.name to a dotted module + qualname string, which contains . and may contain other invalid
characters.

Rule 116668: Enforce alphanumeric (and underscore) task names with length 1–255
simpleq/task.py[34-38]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Task names are generated in a format that violates the required task-name character set and length constraints.

## Issue Context
`fn.__module__` and `fn.__qualname__` produce dotted names and may include `&lt;locals&gt;`.

## Fix Focus Areas
- simpleq/task.py[34-38]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. DLQ receive uses short polling 📘 Rule violation ➹ Performance
Description
SQS receive_message calls for DLQ reads are configured with WaitTimeSeconds=0, which is short
polling. This violates the requirement to use 20-second long polling for receive operations.
Code

simpleq/queue.py[R275-279]

+        response = self.client.receive_message(
+            QueueUrl=dlq_url,
+            MaxNumberOfMessages=self.config.batch_size,
+            WaitTimeSeconds=0,
+        )
Evidence
The compliance rule mandates WaitTimeSeconds=20 (or a default that enforces it), but the DLQ
receive paths explicitly set WaitTimeSeconds=0.

Rule 116662: Use SQS long polling with 20s WaitTimeSeconds for all receive operations
simpleq/queue.py[275-279]
simpleq/cli.py[280-284]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
DLQ receive operations use short polling (`WaitTimeSeconds=0`) instead of the required 20s long polling.

## Issue Context
This occurs in both the library DLQ reader and the CLI DLQ command.

## Fix Focus Areas
- simpleq/queue.py[275-279]
- simpleq/cli.py[280-284]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Receive batch size not fixed 10 📘 Rule violation ➹ Performance
Description
SQS receives use MaxNumberOfMessages derived from max_messages/config rather than enforcing
10. This violates the requirement to always request the maximum batch size unless explicitly
justified inline.
Code

simpleq/queue.py[R187-194]

+    def receive_jobs(self, max_messages: int | None = None) -> list[Job]:
+        """Receive jobs from this queue via long polling."""
+        response = self.client.receive_message(
+            QueueUrl=self.url,
+            MaxNumberOfMessages=max_messages or self.config.batch_size,
+            WaitTimeSeconds=self.config.wait_seconds,
+            AttributeNames=["ApproximateReceiveCount"],
+        )
Evidence
The rule requires MaxNumberOfMessages=10 on receive calls, but the implementation sets it to
max_messages or self.config.batch_size, allowing values <10 and omitting any inline justification
for deviations.

Rule 116661: Use max batch size for SQS receive (MaxNumberOfMessages=10)
simpleq/queue.py[187-194]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
SQS receive calls should always request the maximum batch size (10), but the implementation allows smaller values.

## Issue Context
This can reduce throughput and increase costs.

## Fix Focus Areas
- simpleq/queue.py[187-194]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. Missing exception chaining in Queue 📘 Rule violation ⛯ Reliability
Description
QueueNotFoundError is raised inside except blocks without preserving the original exception via
raise ... from .... This loses root-cause context and violates the exception chaining requirement.
Code

simpleq/queue.py[R85-91]

+        except self.client.exceptions.QueueDoesNotExist:
+            if not self.config.auto_create:
+                raise QueueNotFoundError(
+                    f"Queue '{self.name}' does not exist.\n\n"
+                    f"  Create it with: simpleq queue create {self.name}\n"
+                    f"  Or set auto_create=True to create queues automatically."
+                )
Evidence
The checklist requires exception chaining when translating exceptions, but QueueNotFoundError(...)
is raised without from e in exception translation paths.

Rule 116666: Preserve exception chaining with raise ... from ...
simpleq/queue.py[85-91]
simpleq/queue.py[266-273]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Exception translation drops the original exception cause by not using `raise ... from e`.

## Issue Context
This makes debugging harder and obscures the underlying boto3/botocore exception.

## Fix Focus Areas
- simpleq/queue.py[85-91]
- simpleq/queue.py[266-273]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. ClientError not mapped to SimpleQError 📘 Rule violation ⛯ Reliability
Description
Boto3 SQS operations can raise ClientError, but the code does not consistently catch and wrap it
into domain-specific SimpleQError subclasses. This can leak AWS exceptions through public SimpleQ
APIs.
Code

simpleq/queue.py[R172-185]

+        try:
+            self.client.send_message(**kwargs)
+        except Exception as e:
+            if "NoCredential" in type(e).__name__ or "NoCredential" in str(e):
+                raise ConfigurationError(
+                    "AWS credentials not found.\n\n"
+                    "  Set credentials using one of:\n"
+                    "    export AWS_ACCESS_KEY_ID=xxx\n"
+                    "    export AWS_SECRET_ACCESS_KEY=xxx\n\n"
+                    "  Or configure the AWS CLI:\n"
+                    "    aws configure"
+                ) from e
+            raise
+        self.cost_tracker.track_send(self.name)
Evidence
The rule requires ClientError to be caught and re-raised as SimpleQ domain exceptions (preserving
cause), but send_job catches generic Exception and re-raises raw exceptions in the
non-credential case.

Rule 116646: Wrap AWS ClientError in domain-specific SimpleQ exceptions
simpleq/queue.py[172-185]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
AWS/boto3 exceptions (notably `ClientError`) may escape SimpleQ public APIs because they are not translated into domain-specific exceptions.

## Issue Context
`send_job` currently re-raises the raw exception for non-credential errors.

## Fix Focus Areas
- simpleq/queue.py[172-185]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. Untyped helper functions in tests 📘 Rule violation ⚙ Maintainability
Description
Some new test-defined functions lack parameter and return type annotations. This violates the
requirement for complete type hints on all function definitions.
Code

tests/test_task.py[R123-126]

+        @sq.task
+        def add(a, b):
+            return a + b
+
Evidence
The compliance rule requires explicit type annotations for all parameters and return types, but the
added test helper function add(a, b) is untyped.

Rule 116655: Require complete type hints on all function definitions
tests/test_task.py[123-126]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Test-defined functions are missing required parameter and return type annotations.

## Issue Context
The policy applies to all function definitions in the change set, including those defined inside tests.

## Fix Focus Areas
- tests/test_task.py[123-126]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


11. FIFO MessageGroupId missing 🐞 Bug ✓ Correctness
Description
For FIFO queues, Task.delay() can call Queue.send_job() with message_group_id=None, and
Queue.send_job() will omit MessageGroupId from the send_message request. This breaks FIFO enqueue
(and FIFO DLQ redrive) because FIFO messages require a group id.
Code

simpleq/queue.py[R161-170]

+        kwargs: dict[str, Any] = {
+            "QueueUrl": self.url,
+            "MessageBody": job.serialize(),
+        }
+        if delay_seconds:
+            kwargs["DelaySeconds"] = delay_seconds
+        if message_group_id and self.fifo:
+            kwargs["MessageGroupId"] = message_group_id
+        if deduplication_id and self.fifo:
+            kwargs["MessageDeduplicationId"] = deduplication_id
Evidence
Task.delay() only computes a group id when message_group_id was configured, leaving FIFO tasks able
to enqueue with group_id=None; Queue.send_job() only includes MessageGroupId when the passed value
is truthy, so FIFO requests can be sent without it. Additionally, redrive_dlq() re-sends jobs with
send_job(job) without providing any group id, so FIFO DLQ redrive will also fail even if original
sends used a group id.

simpleq/task.py[57-80]
simpleq/queue.py[153-170]
simpleq/queue.py[292-314]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
FIFO queues can be used without a MessageGroupId because `Task.delay()` may pass `message_group_id=None`, and `Queue.send_job()` conditionally omits `MessageGroupId`. This causes FIFO enqueue (and FIFO DLQ redrive) to fail at runtime.

### Issue Context
- `Task.delay()` only sets `group_id` when `self.message_group_id` is not `None`.
- `Queue.send_job()` only adds `MessageGroupId` when `message_group_id` is truthy.
- `Queue.redrive_dlq()` re-sends jobs without any group id.

### Fix Focus Areas
- simpleq/task.py[57-80]
- simpleq/queue.py[153-170]
- simpleq/queue.py[292-314]

### Suggested fix approach
- Enforce FIFO correctness by either:
 1) raising a clear `ConfigurationError` when `fifo=True` and no `message_group_id` is provided, OR
 2) providing a deterministic default group id (e.g., a constant like &quot;default&quot; or derived from task name) when `fifo=True`.
- For FIFO DLQ redrive, ensure a group id is available when re-sending:
 - Option A (best): add an optional `message_group_id` field to `Job`, serialize it, set it in `Task.delay()`, and have `Queue.send_job()` use it (or pass it explicitly in `redrive_dlq`).
 - Option B: choose a documented fallback group id for redriven FIFO messages if the original is unavailable (less ideal for ordering semantics).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


12. FIFO DLQ name wrong 🐞 Bug ✓ Correctness
Description
simpleq queue dlq always looks for a DLQ named {queue}-dlq, but the Queue implementation creates
FIFO DLQs as {queue}-dlq.fifo (by replacing .fifo). This makes the CLI DLQ command fail for FIFO
queues and prints the wrong “expected DLQ name”.
Code

simpleq/cli.py[R266-276]

+    """List messages in a queue's dead letter queue."""
+    client = _get_sqs_client(region)
+    dlq_name = f"{name}-dlq"
+
+    try:
+        url = client.get_queue_url(QueueName=dlq_name)["QueueUrl"]
+    except client.exceptions.QueueDoesNotExist:
+        console.print(
+            f"[red]✗[/red] No DLQ found for queue '{name}'.\n\n"
+            f"  DLQ name expected: {dlq_name}\n"
+            f"  Enable DLQ with: SimpleQ(dead_letter_queue=True)"
Evidence
The CLI hard-codes dlq_name = f"{name}-dlq", while the library’s DLQ naming logic uses
name.replace('.fifo', '-dlq.fifo') for FIFO queues. These two names diverge for queues like
orders.fifo, so the CLI will query a non-existent queue name.

simpleq/cli.py[261-279]
simpleq/queue.py[118-123]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The CLI DLQ command uses a different DLQ naming scheme than the core Queue implementation for FIFO queues, causing `simpleq queue dlq &lt;fifo-queue&gt;` to fail.

### Issue Context
- CLI uses `{name}-dlq` unconditionally.
- Queue uses `{name}.replace(&#x27;.fifo&#x27;, &#x27;-dlq.fifo&#x27;)` when FIFO.

### Fix Focus Areas
- simpleq/cli.py[261-279]
- simpleq/queue.py[118-123]

### Suggested fix approach
- Implement a shared helper for DLQ naming (e.g., `_dlq_name(queue_name: str) -&gt; str`) and use it in:
 - `queue_dlq`
 - `queue_redrive` output text (it currently prints `&#x27;{name}-dlq&#x27;` even when FIFO)
- Ensure behavior matches Queue’s FIFO naming: `orders.fifo` -&gt; `orders-dlq.fifo`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


13. Concurrency flag unused 🐞 Bug ✓ Correctness
Description
The CLI exposes --concurrency but the value is never used; Worker is created without it and
processes jobs sequentially. This is a silent behavioral bug that makes the CLI throughput options
misleading.
Code

simpleq/cli.py[R110-139]

+@app.command()
+def worker(
+    app_module: str = typer.Option(
+        ..., "-a", "--app", help="Python module containing the SimpleQ app."
+    ),
+    queues: Optional[str] = typer.Option(
+        None, "-q", "--queues", help="Comma-separated queue names to listen to."
+    ),
+    concurrency: int = typer.Option(
+        10, "-c", "--concurrency", help="Number of concurrent jobs."
+    ),
+) -> None:
+    """Start a worker that processes background jobs."""
+    sq = _discover_app(app_module)
+
+    if queues:
+        queue_names = [q.strip() for q in queues.split(",")]
+    else:
+        # Use all queues from registered tasks
+        queue_names = list(
+            {t.queue_name for t in sq.tasks.values()} or {"default"}
+        )
+
+    queue_objects = [sq.queue(name) for name in queue_names]
+
+    _print_banner(sq, queue_names)
+
+    w = Worker(queues=queue_objects, tasks=sq.tasks)
+    w.work()
+
Evidence
The concurrency option is declared on the CLI worker command but never referenced, and the Worker
implementation loops over received jobs synchronously with no concurrency control. This means
simpleq worker -c 20 behaves the same as -c 1.

simpleq/cli.py[110-139]
simpleq/worker.py[47-85]
README.md[236-242]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`simpleq worker --concurrency/-c` is accepted but ignored; the worker runs single-threaded and sequential.

### Issue Context
- CLI defines `concurrency` but does not pass it to `Worker`.
- `Worker.work()` processes jobs in a simple nested loop.

### Fix Focus Areas
- simpleq/cli.py[110-139]
- simpleq/worker.py[47-85]
- README.md[236-242]

### Suggested fix approach
Choose one:
1) **Implement concurrency**:
  - Add a `concurrency: int` parameter to `Worker`.
  - Use a bounded executor (e.g., `concurrent.futures.ThreadPoolExecutor(max_workers=concurrency)`) for sync tasks and/or an asyncio-based runner for async tasks.
  - Ensure deletion/visibility changes happen after task completion.
2) **Remove the flag**:
  - Drop `--concurrency` from CLI and remove it from README until concurrency exists.
  - This avoids a silent misconfiguration.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

14. Worker.work exceeds 20 lines 📘 Rule violation ⚙ Maintainability
Description
Worker.work() contains more than 20 logical lines, exceeding the function-size limit. This
increases complexity and makes the polling loop harder to test and evolve.
Code

simpleq/worker.py[R56-93]

+    def work(self, burst: bool = False) -> None:
+        """Start processing jobs.
+
+        Args:
+            burst: If True, process all available jobs then exit.
+                   If False, poll forever until SIGINT/SIGTERM.
+        """
+        self._shutdown = False
+        self._setup_signal_handlers()
+
+        logger.info(
+            "worker_started",
+            queues=[q.name for q in self.queues],
+            tasks=list(self.tasks.keys()),
+            burst=burst,
+        )
+
+        while not self._shutdown:
+            processed = 0
+            for queue in self.queues:
+                if self._shutdown:
+                    break
+
+                jobs = queue.receive_jobs()
+                for job in jobs:
+                    if self._shutdown:
+                        break
+                    self._process_job(job, queue)
+                    processed += 1
+
+            if self._shutdown:
+                logger.info("shutdown_requested")
+                break
+
+            if burst and processed == 0:
+                break
+
+        logger.info("worker_stopped")
Evidence
The checklist limits function bodies to fewer than 20 logical lines; Worker.work() includes the
main polling loop, processing logic, and shutdown handling in a single long function.

Rule 116656: Limit function bodies to fewer than 20 logical lines
simpleq/worker.py[56-93]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`Worker.work()` is longer than the allowed limit and mixes multiple responsibilities.

## Issue Context
Refactoring into helper methods can improve readability and unit testability.

## Fix Focus Areas
- simpleq/worker.py[56-93]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


15. fn parameter name too cryptic 📘 Rule violation ⚙ Maintainability
Description
SimpleQ.task() uses the parameter name fn, which is a local abbreviation and violates the
descriptive naming requirement. This reduces readability and clarity for a public API.
Code

simpleq/app.py[R71-82]

+    def task(
+        self,
+        fn: Callable[..., Any] | None = None,
+        *,
+        queue: str = "default",
+        retries: int = 0,
+        backoff: str = "exponential",
+        async_: bool = False,
+        fifo: bool = False,
+        message_group_id: Callable[..., str] | str | None = None,
+        lambda_: bool = False,
+    ) -> Any:
Evidence
The naming rule disallows cryptic abbreviations in function parameters; fn is used as a
public-facing parameter name in SimpleQ.task().

Rule 116652: Use descriptive names for functions and parameters (no single-letter or cryptic abbreviations)
simpleq/app.py[71-82]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A public API parameter is named `fn`, which is a cryptic abbreviation under the naming policy.

## Issue Context
Renaming to `func`/`function` improves clarity with minimal impact.

## Fix Focus Areas
- simpleq/app.py[71-82]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


16. Public API docstrings not Google-style 📘 Rule violation ⚙ Maintainability
Description
New public methods lack Google-style docstring sections such as Args: (and where relevant
Raises:/Example:). This violates the required docstring format policy for public APIs.
Code

simpleq/queue.py[R153-161]

+    def send_job(
+        self,
+        job: Job,
+        delay_seconds: int = 0,
+        message_group_id: str | None = None,
+        deduplication_id: str | None = None,
+    ) -> None:
+        """Send a job to this queue."""
+        kwargs: dict[str, Any] = {
Evidence
The checklist requires Google-style docstrings with sections like Args: for public callables with
parameters, but Queue.send_job() only has a brief one-line docstring and does not document its
parameters/behavior in the required format.

Rule 116642: Enforce Google-style docstrings with Args, Raises, and Example sections
simpleq/queue.py[153-161]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Public API docstrings do not follow the required Google-style sections (`Args:`, `Raises:`, `Example:` as appropriate).

## Issue Context
This impacts generated documentation quality and API usability.

## Fix Focus Areas
- simpleq/queue.py[153-161]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (2)
17. Retry backoff misindexed 🐞 Bug ✓ Correctness
Description
Worker computes backoff using Job.receive_count directly, but Queue populates receive_count from SQS
ApproximateReceiveCount which starts at 1. This shifts the backoff schedule (e.g., first retry uses
the “2nd retry” delay).
Code

simpleq/worker.py[R138-142]

+            if task.retries > 0 and job.receive_count <= task.retries:
+                # Apply backoff by changing visibility timeout
+                backoff_seconds = _calculate_backoff(
+                    task.backoff, job.receive_count
+                )
Evidence
Queue.receive_jobs() sets receive_count from ApproximateReceiveCount defaulting to "1" on first
receive, while _calculate_backoff() semantics (and tests) treat retry_count=0 as the first retry
interval. Passing the 1-indexed receive_count into _calculate_backoff() therefore produces
larger-than-intended initial delays and shifts subsequent delays.

simpleq/queue.py[187-209]
simpleq/worker.py[23-32]
simpleq/worker.py[138-151]
tests/test_worker.py[204-215]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Backoff calculation is misaligned because SQS receive counts are 1-indexed, but `_calculate_backoff()` is written/tested as if the first retry is index 0.

### Issue Context
- `Queue.receive_jobs()` sets `receive_count` from SQS `ApproximateReceiveCount` (first delivery =&gt; 1).
- `_calculate_backoff(&#x27;exponential&#x27;, 0)` is treated as the first retry delay in tests.

### Fix Focus Areas
- simpleq/worker.py[138-151]
- simpleq/queue.py[187-209]
- tests/test_worker.py[204-215]

### Suggested fix approach
- In `Worker._process_job`, compute `retry_index = max(0, job.receive_count - 1)` and pass that to `_calculate_backoff()`.
- Consider logging both `receive_count` and `retry_index` to avoid confusion.
- Keep/explain semantics consistently in tests and docs.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


18. DLQ receive count mismatch 🐞 Bug ✓ Correctness
Description
DLQ setup hard-codes maxReceiveCount to 5, but retry behavior is configured per-task via
Task.retries and the README claims jobs move to DLQ after retries are exhausted. With DLQ enabled,
messages will instead be moved after 5 receives regardless of task retry settings, causing extra
retries or early DLQ moves.
Code

simpleq/queue.py[R144-146]

+        redrive_policy = json.dumps(
+            {"deadLetterTargetArn": dlq_arn, "maxReceiveCount": "5"}
+        )
Evidence
Queue._setup_dlq() always configures maxReceiveCount to 5. Worker schedules retries based on
task.retries but (when DLQ is enabled) does not delete the job when retries are exhausted, so the
message continues being received until it hits the queue’s configured maxReceiveCount, which is
unrelated to the task’s retry setting—contradicting the README’s stated behavior.

simpleq/queue.py[118-151]
simpleq/worker.py[138-166]
README.md[123-147]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
DLQ movement is controlled by a hard-coded queue-level `maxReceiveCount=5`, but task retries are configured per task (`Task.retries`) and the README promises DLQ after retries are exhausted. This creates retry/DLQ behavior that can’t match configured retries.

### Issue Context
- Queue configures DLQ redrive policy with a fixed maxReceiveCount.
- Worker retry logic is per-task and stops adjusting visibility once retries are exhausted; with DLQ enabled it leaves the message for SQS.

### Fix Focus Areas
- simpleq/queue.py[118-151]
- simpleq/worker.py[138-166]
- README.md[123-147]

### Suggested fix approach
Pick an explicit contract and implement it:
1) **Queue-level retries (simpler):** make `maxReceiveCount` configurable via `Config` (e.g., `dlq_max_receive_count`) and update README to describe queue-level behavior.
2) **Per-task DLQ (matches README intent):** when retries are exhausted and DLQ is enabled, explicitly move the message to the DLQ (send to DLQ + delete original), rather than relying on SQS `maxReceiveCount`.

Either way, avoid a fixed value that can’t be reconciled with `Task.retries`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment thread pyproject.toml
Comment on lines +2 to +45
requires = ["setuptools>=70.0"]
build-backend = "setuptools.build_meta"

[project]
name = "simpleq"
version = "2.0.0"
description = "The Python task queue for AWS. Dead simple. SQS native. Zero infrastructure."
readme = "README.md"
license = "ISC"
requires-python = ">=3.10"
authors = [
{ name = "Randall Degges", email = "r@rdegges.com" },
]
keywords = ["sqs", "aws", "queue", "worker", "tasks", "background-jobs"]
classifiers = [
"Development Status :: 4 - Beta",
"Environment :: Console",
"Intended Audience :: Developers",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: System :: Distributed Computing",
]
dependencies = [
"boto3>=1.35.0",
"pydantic>=2.10.0",
"structlog>=24.4.0",
"typer>=0.15.0",
"rich>=13.9.0",
]

[project.optional-dependencies]
dev = [
"pytest>=8.3.0",
"pytest-cov>=6.0.0",
"moto[sqs]>=5.0.0",
"mypy>=1.13.0",
"ruff>=0.8.0",
"boto3-stubs[sqs]>=1.35.0",
]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Unbounded dependency versions 📘 Rule violation ⛯ Reliability

pyproject.toml uses lower-bound-only constraints (e.g., boto3>=...) instead of bounded version
ranges. This can introduce breaking changes via transitive/major upgrades and violates the
dependency range policy.
Agent Prompt
## Issue description
`pyproject.toml` dependency constraints are unbounded (lower-bound only), which violates the requirement to use bounded version ranges.

## Issue Context
The manifest currently contains entries like `boto3>=1.35.0` and `setuptools>=70.0` with no `<` upper bound.

## Fix Focus Areas
- pyproject.toml[2-45]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread pyproject.toml
Comment on lines +82 to +88
[tool.mypy]
python_version = "3.10"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
check_untyped_defs = true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. Mypy strict mode missing 📘 Rule violation ⚙ Maintainability

The mypy configuration does not enable strict = true and omits many required strictness flags.
This weakens type-safety guarantees required by the checklist.
Agent Prompt
## Issue description
Mypy is not configured per the required strict-mode policy (missing `strict = true` and multiple mandated flags).

## Issue Context
The project-level mypy config is defined in `pyproject.toml` under `[tool.mypy]`.

## Fix Focus Areas
- pyproject.toml[82-88]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread pyproject.toml
Comment on lines +58 to +81
[tool.ruff]
target-version = "py310"
line-length = 88

[tool.ruff.lint]
select = [
"E",
"W",
"F",
"I",
"B",
"C4",
"UP",
"ARG",
"SIM",
"PERF",
]
ignore = [
"E501",
]

[tool.ruff.lint.per-file-ignores]
"tests/*" = ["ARG", "S101"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

3. Ruff config missing required rules 📘 Rule violation ⚙ Maintainability

The Ruff configuration is missing required selections (e.g., TCH) and required global ignores
(e.g., B008), and the tests per-file ignore glob is too narrow. This violates the mandated Ruff
configuration policy.
Agent Prompt
## Issue description
Ruff is configured, but it does not match the required baseline (missing required rule groups and ignores, and tests per-file ignores are not applied broadly enough).

## Issue Context
The Ruff config lives in `pyproject.toml` under `[tool.ruff]`, `[tool.ruff.lint]`, and `[tool.ruff.lint.per-file-ignores]`.

## Fix Focus Areas
- pyproject.toml[58-81]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread tests/test_app.py
@@ -0,0 +1,93 @@
"""Tests for the SimpleQ application class."""
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

4. Tests not in required directories 📘 Rule violation ⚙ Maintainability

Tests are placed directly under tests/ instead of tests/unit/ or tests/integration/. This
violates the required test directory layout policy.
Agent Prompt
## Issue description
Test files are located directly under `tests/` rather than the mandated `tests/unit/` and `tests/integration/` directories.

## Issue Context
Multiple new test modules use the `tests/test_*.py` pattern.

## Fix Focus Areas
- tests/test_app.py[1-1]
- tests/test_queue.py[1-1]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread simpleq/task.py
Comment on lines +34 to +38
self.fn = fn
self.app = app
self.name = f"{fn.__module__}.{fn.__qualname__}"
self.retries = retries
self.backoff = backoff
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

5. Task name violates allowed charset 📘 Rule violation ✓ Correctness

Task.name is constructed using module.qualname which includes dots and can include <locals>,
violating the required [A-Za-z0-9_] task-name constraint and length policy. Invalid task names can
break downstream assumptions and validation requirements.
Agent Prompt
## Issue description
Task names are generated in a format that violates the required task-name character set and length constraints.

## Issue Context
`fn.__module__` and `fn.__qualname__` produce dotted names and may include `<locals>`.

## Fix Focus Areas
- simpleq/task.py[34-38]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread simpleq/queue.py
Comment on lines +172 to +185
try:
self.client.send_message(**kwargs)
except Exception as e:
if "NoCredential" in type(e).__name__ or "NoCredential" in str(e):
raise ConfigurationError(
"AWS credentials not found.\n\n"
" Set credentials using one of:\n"
" export AWS_ACCESS_KEY_ID=xxx\n"
" export AWS_SECRET_ACCESS_KEY=xxx\n\n"
" Or configure the AWS CLI:\n"
" aws configure"
) from e
raise
self.cost_tracker.track_send(self.name)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

9. Clienterror not mapped to simpleqerror 📘 Rule violation ⛯ Reliability

Boto3 SQS operations can raise ClientError, but the code does not consistently catch and wrap it
into domain-specific SimpleQError subclasses. This can leak AWS exceptions through public SimpleQ
APIs.
Agent Prompt
## Issue description
AWS/boto3 exceptions (notably `ClientError`) may escape SimpleQ public APIs because they are not translated into domain-specific exceptions.

## Issue Context
`send_job` currently re-raises the raw exception for non-credential errors.

## Fix Focus Areas
- simpleq/queue.py[172-185]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread tests/test_task.py
Comment on lines +123 to +126
@sq.task
def add(a, b):
return a + b

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

10. Untyped helper functions in tests 📘 Rule violation ⚙ Maintainability

Some new test-defined functions lack parameter and return type annotations. This violates the
requirement for complete type hints on all function definitions.
Agent Prompt
## Issue description
Test-defined functions are missing required parameter and return type annotations.

## Issue Context
The policy applies to all function definitions in the change set, including those defined inside tests.

## Fix Focus Areas
- tests/test_task.py[123-126]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread simpleq/queue.py
Comment on lines +161 to +170
kwargs: dict[str, Any] = {
"QueueUrl": self.url,
"MessageBody": job.serialize(),
}
if delay_seconds:
kwargs["DelaySeconds"] = delay_seconds
if message_group_id and self.fifo:
kwargs["MessageGroupId"] = message_group_id
if deduplication_id and self.fifo:
kwargs["MessageDeduplicationId"] = deduplication_id
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

11. Fifo messagegroupid missing 🐞 Bug ✓ Correctness

For FIFO queues, Task.delay() can call Queue.send_job() with message_group_id=None, and
Queue.send_job() will omit MessageGroupId from the send_message request. This breaks FIFO enqueue
(and FIFO DLQ redrive) because FIFO messages require a group id.
Agent Prompt
### Issue description
FIFO queues can be used without a MessageGroupId because `Task.delay()` may pass `message_group_id=None`, and `Queue.send_job()` conditionally omits `MessageGroupId`. This causes FIFO enqueue (and FIFO DLQ redrive) to fail at runtime.

### Issue Context
- `Task.delay()` only sets `group_id` when `self.message_group_id` is not `None`.
- `Queue.send_job()` only adds `MessageGroupId` when `message_group_id` is truthy.
- `Queue.redrive_dlq()` re-sends jobs without any group id.

### Fix Focus Areas
- simpleq/task.py[57-80]
- simpleq/queue.py[153-170]
- simpleq/queue.py[292-314]

### Suggested fix approach
- Enforce FIFO correctness by either:
  1) raising a clear `ConfigurationError` when `fifo=True` and no `message_group_id` is provided, OR
  2) providing a deterministic default group id (e.g., a constant like "default" or derived from task name) when `fifo=True`.
- For FIFO DLQ redrive, ensure a group id is available when re-sending:
  - Option A (best): add an optional `message_group_id` field to `Job`, serialize it, set it in `Task.delay()`, and have `Queue.send_job()` use it (or pass it explicitly in `redrive_dlq`).
  - Option B: choose a documented fallback group id for redriven FIFO messages if the original is unavailable (less ideal for ordering semantics).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread simpleq/cli.py
Comment on lines +266 to +276
"""List messages in a queue's dead letter queue."""
client = _get_sqs_client(region)
dlq_name = f"{name}-dlq"

try:
url = client.get_queue_url(QueueName=dlq_name)["QueueUrl"]
except client.exceptions.QueueDoesNotExist:
console.print(
f"[red]✗[/red] No DLQ found for queue '{name}'.\n\n"
f" DLQ name expected: {dlq_name}\n"
f" Enable DLQ with: SimpleQ(dead_letter_queue=True)"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

12. Fifo dlq name wrong 🐞 Bug ✓ Correctness

simpleq queue dlq always looks for a DLQ named {queue}-dlq, but the Queue implementation creates
FIFO DLQs as {queue}-dlq.fifo (by replacing .fifo). This makes the CLI DLQ command fail for FIFO
queues and prints the wrong “expected DLQ name”.
Agent Prompt
### Issue description
The CLI DLQ command uses a different DLQ naming scheme than the core Queue implementation for FIFO queues, causing `simpleq queue dlq <fifo-queue>` to fail.

### Issue Context
- CLI uses `{name}-dlq` unconditionally.
- Queue uses `{name}.replace('.fifo', '-dlq.fifo')` when FIFO.

### Fix Focus Areas
- simpleq/cli.py[261-279]
- simpleq/queue.py[118-123]

### Suggested fix approach
- Implement a shared helper for DLQ naming (e.g., `_dlq_name(queue_name: str) -> str`) and use it in:
  - `queue_dlq`
  - `queue_redrive` output text (it currently prints `'{name}-dlq'` even when FIFO)
- Ensure behavior matches Queue’s FIFO naming: `orders.fifo` -> `orders-dlq.fifo`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread simpleq/cli.py
Comment on lines +110 to +139
@app.command()
def worker(
app_module: str = typer.Option(
..., "-a", "--app", help="Python module containing the SimpleQ app."
),
queues: Optional[str] = typer.Option(
None, "-q", "--queues", help="Comma-separated queue names to listen to."
),
concurrency: int = typer.Option(
10, "-c", "--concurrency", help="Number of concurrent jobs."
),
) -> None:
"""Start a worker that processes background jobs."""
sq = _discover_app(app_module)

if queues:
queue_names = [q.strip() for q in queues.split(",")]
else:
# Use all queues from registered tasks
queue_names = list(
{t.queue_name for t in sq.tasks.values()} or {"default"}
)

queue_objects = [sq.queue(name) for name in queue_names]

_print_banner(sq, queue_names)

w = Worker(queues=queue_objects, tasks=sq.tasks)
w.work()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

13. Concurrency flag unused 🐞 Bug ✓ Correctness

The CLI exposes --concurrency but the value is never used; Worker is created without it and
processes jobs sequentially. This is a silent behavioral bug that makes the CLI throughput options
misleading.
Agent Prompt
### Issue description
`simpleq worker --concurrency/-c` is accepted but ignored; the worker runs single-threaded and sequential.

### Issue Context
- CLI defines `concurrency` but does not pass it to `Worker`.
- `Worker.work()` processes jobs in a simple nested loop.

### Fix Focus Areas
- simpleq/cli.py[110-139]
- simpleq/worker.py[47-85]
- README.md[236-242]

### Suggested fix approach
Choose one:
1) **Implement concurrency**:
   - Add a `concurrency: int` parameter to `Worker`.
   - Use a bounded executor (e.g., `concurrent.futures.ThreadPoolExecutor(max_workers=concurrency)`) for sync tasks and/or an asyncio-based runner for async tasks.
   - Ensure deletion/visibility changes happen after task completion.
2) **Remove the flag**:
   - Drop `--concurrency` from CLI and remove it from README until concurrency exists.
   - This avoids a silent misconfiguration.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant