Skip to content

Commit 4f61201

Browse files
committed
chore: add docker-compose with SQS for local tests
1 parent 9fab4b6 commit 4f61201

7 files changed

Lines changed: 179 additions & 28 deletions

File tree

.github/workflows/code-check.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ permissions:
1818

1919
env:
2020
AWS_DEFAULT_REGION: "us-east-1"
21+
AWS_ENDPOINT_URL: "http://localhost:4566"
22+
AWS_ACCESS_KEY_ID: "test"
23+
AWS_SECRET_ACCESS_KEY: "test" # pragma: allowlist secret
2124

2225
jobs:
2326
test:
@@ -26,6 +29,23 @@ jobs:
2629
matrix:
2730
os: [ubuntu-latest, macos-latest] # eventually add `windows-latest`
2831
python-version: ["3.10", "3.11", "3.12"]
32+
services:
33+
ministack:
34+
image: ministackorg/ministack:1.3.53
35+
ports:
36+
- 4566:4566
37+
env:
38+
AWS_DEFAULT_REGION: us-east-1
39+
GATEWAY_PORT: 4566
40+
MINISTACK_ACCOUNT_ID: "000000000000"
41+
MINISTACK_REGION: us-east-1
42+
LOG_LEVEL: INFO
43+
options: >-
44+
--health-cmd "curl -f http://localhost:4566/_ministack/health"
45+
--health-interval 2s
46+
--health-timeout 5s
47+
--health-retries 30
48+
--health-start-period 5s
2949
steps:
3050
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
3151
with:

docker-compose.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
services:
2+
ministack:
3+
image: ministackorg/ministack:1.3.53
4+
container_name: taskiq_sqs_ministack
5+
ports:
6+
- "4566:4566"
7+
environment:
8+
AWS_DEFAULT_REGION: us-east-1
9+
GATEWAY_PORT: 4566
10+
MINISTACK_ACCOUNT_ID: "000000000000"
11+
MINISTACK_REGION: us-east-1
12+
LOG_LEVEL: INFO
13+
PERSIST_STATE: "1"
14+
healthcheck:
15+
test: ["CMD", "curl", "-f", "http://localhost:4566/_ministack/health"]
16+
interval: 2s
17+
timeout: 5s
18+
retries: 30
19+
start_period: 5s
20+
volumes:
21+
- ministack-data:/tmp/ministack-state
22+
networks:
23+
- taskiq-sqs-network
24+
25+
redis:
26+
image: bitnamilegacy/redis:7.4.2
27+
environment:
28+
ALLOW_EMPTY_PASSWORD: "yes" # pragma: allowlist secret
29+
healthcheck:
30+
test: ["CMD", "redis-cli", "ping"]
31+
interval: 5s
32+
timeout: 5s
33+
retries: 3
34+
start_period: 10s
35+
ports:
36+
- 6379:6379
37+
38+
networks:
39+
taskiq-sqs-network:
40+
driver: bridge
41+
42+
volumes:
43+
ministack-data:

examples/example_broker.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""
2+
Run worker:
3+
taskiq worker examples.example_broker:broker
4+
5+
Run broker to send a task:
6+
python examples/example_broker.py
7+
"""
8+
9+
import asyncio
10+
11+
import boto3
12+
import dotenv
13+
from taskiq_redis import RedisAsyncResultBackend
14+
15+
from taskiq_sqs import SQSBroker
16+
17+
18+
dotenv.load_dotenv()
19+
20+
QUEUE_NAME = "my-queue"
21+
QUEUE_URL = f"http://localhost:4566/000000000000/{QUEUE_NAME}"
22+
23+
24+
boto3.client("sqs").create_queue(QueueName=QUEUE_NAME)
25+
26+
broker = SQSBroker(QUEUE_URL, sqs_region_override="us-east-1").with_result_backend(
27+
RedisAsyncResultBackend(redis_url="redis://localhost:6379")
28+
)
29+
30+
31+
@broker.task()
32+
async def i_love_aws() -> None:
33+
"""I hope my cloud bill doesn't get too high!"""
34+
await asyncio.sleep(5.5)
35+
print("Hello there!")
36+
37+
38+
async def main() -> None:
39+
await broker.startup()
40+
task = await i_love_aws.kiq()
41+
print(await task.wait_result())
42+
await broker.shutdown()
43+
44+
45+
if __name__ == "__main__":
46+
asyncio.run(main())

pyproject.toml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ dev = [
5050
test = [
5151
"pytest>=9.0.3",
5252
"pytest-asyncio>=0.23.8",
53-
"requests>=2.34.2",
5453
]
5554
lint = [
5655
"bandit>=1.9.4",
@@ -60,9 +59,12 @@ lint = [
6059
types = [
6160
"mypy>=2.1.0",
6261
"mypy-boto3-sqs>=1.34.101",
63-
"types-requests>=2.33.0.20260518",
6462
"boto3-stubs[essential]>=1.34.84",
6563
]
64+
examples = [
65+
"python-dotenv>=1.2.2",
66+
"taskiq-redis>=1.2.2",
67+
]
6668

6769

6870
[build-system]
@@ -138,6 +140,11 @@ ignore = [
138140
"S101", # assert usage
139141
"SLF001", # private member accessed
140142
]
143+
"examples/*" = [
144+
"T201", # print
145+
"D",
146+
"INP001",
147+
]
141148

142149
[tool.ruff.lint.pydocstyle]
143150
convention = "google"

src/taskiq_sqs/broker.py

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,24 @@
1-
from __future__ import (
2-
annotations, # Needed for conditional type import support
3-
)
4-
51
import asyncio
62
import logging
73
from collections import defaultdict
4+
from collections.abc import AsyncGenerator, Callable, Mapping
85
from datetime import datetime, timezone
96
from typing import TYPE_CHECKING
107

118
import boto3
129
from asyncer import asyncify
1310
from botocore.exceptions import ClientError
1411
from taskiq import AsyncBroker
12+
from taskiq.abc.result_backend import AsyncResultBackend
13+
from taskiq.acks import AckableMessage
14+
from taskiq.message import BrokerMessage
1515

1616
from taskiq_sqs.aws import get_container_credentials
1717

1818

1919
if TYPE_CHECKING:
20-
from collections.abc import AsyncGenerator, Callable, Mapping
21-
2220
from mypy_boto3_sqs.service_resource import Queue, SQSServiceResource
23-
from taskiq.abc.result_backend import AsyncResultBackend
24-
from taskiq.acks import AckableMessage
25-
from taskiq.message import BrokerMessage
21+
2622

2723
logger = logging.getLogger(__name__)
2824

@@ -51,7 +47,7 @@ def __init__( # noqa: D107
5147
super().__init__(result_backend, task_id_generator)
5248

5349
if not sqs_queue_url or not sqs_queue_url.startswith("http"):
54-
raise SQSBrokerError("A valid SQS Queue URL is required")
50+
raise SQSBrokerError("A valid SQS queue url is required")
5551

5652
# NOTE: This bypasses the normal order of operations for boto3 auth and
5753
# goes straight to using the ECS role creds from the metadata
@@ -62,6 +58,7 @@ def __init__( # noqa: D107
6258
self.sqs_queue_url = sqs_queue_url
6359
self._sqs: SQSServiceResource | None = None
6460
self._sqs_queue: Queue | None = None
61+
self._creds_expiration: datetime | None = None
6562

6663
if max_number_of_messages > 10: # noqa: PLR2004
6764
raise SQSBrokerError("MaxNumberOfMessages can be no greater than 10")
@@ -70,12 +67,10 @@ def __init__( # noqa: D107
7067
self.max_number_of_messages = max(max_number_of_messages, 1)
7168

7269
@property
73-
def _sqs_credentials_expired(self) -> datetime | bool:
74-
return self._creds_expiration and self._creds_expiration < datetime.now(
75-
tz=timezone.utc,
76-
)
70+
def _sqs_credentials_expired(self) -> datetime | bool | None:
71+
return self._creds_expiration and self._creds_expiration < datetime.now(tz=timezone.utc)
7772

78-
async def _sqs_client(self) -> SQSServiceResource:
73+
async def _sqs_client(self) -> "SQSServiceResource":
7974
if self._sqs and not self._sqs_credentials_expired:
8075
return self._sqs
8176

@@ -95,7 +90,7 @@ async def _sqs_client(self) -> SQSServiceResource:
9590
aws_session_token=creds.get("Token"),
9691
)
9792

98-
async def _get_queue(self) -> Queue:
93+
async def _get_queue(self) -> "Queue":
9994
if self._sqs_queue and not self._sqs_credentials_expired:
10095
return self._sqs_queue
10196

@@ -105,7 +100,7 @@ async def _get_queue(self) -> Queue:
105100
)
106101

107102
if not self._sqs_queue:
108-
exc_message = "SQS Queue not found"
103+
exc_message = "SQS queue not found"
109104
raise Exception(exc_message) # noqa: TRY002
110105

111106
return self._sqs_queue

tests/test_broker.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
from taskiq_sqs import SQSBroker
22

33

4-
def test_init() -> None:
5-
broker = SQSBroker("https://sqs.us-west-2.amazonaws.com/123456789012/queue-name")
6-
assert (
7-
broker.sqs_queue_url
8-
== "https://sqs.us-west-2.amazonaws.com/123456789012/queue-name"
9-
)
10-
assert broker.force_ecs_container_credentials is False
11-
assert broker.sqs_region_override is None
12-
assert broker._sqs_queue is None
4+
class TestInitParameters:
5+
async def test_initialization_logic(self) -> None:
6+
broker = SQSBroker("http://localhost:4566/000000000000/my-queue")
7+
assert broker.sqs_queue_url == "http://localhost:4566/000000000000/my-queue"
8+
assert broker.force_ecs_container_credentials is False
9+
assert broker.sqs_region_override is None
10+
assert broker._sqs_queue is None

uv.lock

Lines changed: 42 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)