Skip to content

Commit 07ee77a

Browse files
committed
make sure celery tasks actually run
1 parent 0cd3857 commit 07ee77a

File tree

6 files changed

+269
-23
lines changed

6 files changed

+269
-23
lines changed

packages/celery-library/requirements/_test.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@
1111
# testing
1212
coverage
1313
faker
14+
fakeredis
1415
fastapi
1516
docker
1617
httpx
1718
pint
1819
pytest
1920
pytest-asyncio
2021
pytest-benchmark
22+
pytest-celery
2123
pytest-cov
2224
pytest-icdiff
2325
pytest-instafail

packages/celery-library/requirements/_test.txt

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
amqp==5.3.1
2+
# via
3+
# -c requirements/_base.txt
4+
# kombu
15
annotated-types==0.7.0
26
# via
37
# -c requirements/_base.txt
@@ -7,6 +11,14 @@ anyio==4.9.0
711
# -c requirements/_base.txt
812
# httpx
913
# starlette
14+
billiard==4.2.1
15+
# via
16+
# -c requirements/_base.txt
17+
# celery
18+
celery==5.5.2
19+
# via
20+
# -c requirements/_base.txt
21+
# pytest-celery
1022
certifi==2025.4.26
1123
# via
1224
# -c requirements/../../../requirements/constraints.txt
@@ -18,14 +30,40 @@ charset-normalizer==3.4.2
1830
# via
1931
# -c requirements/_base.txt
2032
# requests
33+
click==8.2.1
34+
# via
35+
# -c requirements/_base.txt
36+
# celery
37+
# click-didyoumean
38+
# click-plugins
39+
# click-repl
40+
click-didyoumean==0.3.1
41+
# via
42+
# -c requirements/_base.txt
43+
# celery
44+
click-plugins==1.1.1
45+
# via
46+
# -c requirements/_base.txt
47+
# celery
48+
click-repl==0.3.0
49+
# via
50+
# -c requirements/_base.txt
51+
# celery
2152
coverage==7.8.2
2253
# via
2354
# -r requirements/_test.in
2455
# pytest-cov
56+
debugpy==1.8.14
57+
# via pytest-celery
2558
docker==7.1.0
26-
# via -r requirements/_test.in
59+
# via
60+
# -r requirements/_test.in
61+
# pytest-celery
62+
# pytest-docker-tools
2763
faker==37.3.0
2864
# via -r requirements/_test.in
65+
fakeredis==2.29.0
66+
# via -r requirements/_test.in
2967
fastapi==0.115.12
3068
# via -r requirements/_test.in
3169
flexcache==0.3
@@ -50,6 +88,11 @@ idna==3.10
5088
# requests
5189
iniconfig==2.1.0
5290
# via pytest
91+
kombu==5.5.3
92+
# via
93+
# -c requirements/_base.txt
94+
# celery
95+
# pytest-celery
5396
packaging==25.0
5497
# via
5598
# -c requirements/_base.txt
@@ -63,6 +106,14 @@ pluggy==1.6.0
63106
# via pytest
64107
pprintpp==0.4.0
65108
# via pytest-icdiff
109+
prompt-toolkit==3.0.51
110+
# via
111+
# -c requirements/_base.txt
112+
# click-repl
113+
psutil==7.0.0
114+
# via
115+
# -c requirements/_base.txt
116+
# pytest-celery
66117
py-cpuinfo==9.0.0
67118
# via pytest-benchmark
68119
pydantic==2.11.5
@@ -74,12 +125,17 @@ pydantic-core==2.33.2
74125
# via
75126
# -c requirements/_base.txt
76127
# pydantic
128+
pyjwt==2.9.0
129+
# via
130+
# -c requirements/_base.txt
131+
# redis
77132
pytest==8.3.5
78133
# via
79134
# -r requirements/_test.in
80135
# pytest-asyncio
81136
# pytest-benchmark
82137
# pytest-cov
138+
# pytest-docker-tools
83139
# pytest-icdiff
84140
# pytest-instafail
85141
# pytest-mock
@@ -88,8 +144,12 @@ pytest-asyncio==1.0.0
88144
# via -r requirements/_test.in
89145
pytest-benchmark==5.1.0
90146
# via -r requirements/_test.in
147+
pytest-celery==1.2.0
148+
# via -r requirements/_test.in
91149
pytest-cov==6.1.1
92150
# via -r requirements/_test.in
151+
pytest-docker-tools==3.1.9
152+
# via pytest-celery
93153
pytest-icdiff==0.9
94154
# via -r requirements/_test.in
95155
pytest-instafail==0.5.0
@@ -100,6 +160,10 @@ pytest-runner==6.0.1
100160
# via -r requirements/_test.in
101161
pytest-sugar==1.0.0
102162
# via -r requirements/_test.in
163+
python-dateutil==2.9.0.post0
164+
# via
165+
# -c requirements/_base.txt
166+
# celery
103167
python-dotenv==1.1.0
104168
# via
105169
# -c requirements/_base.txt
@@ -109,18 +173,35 @@ pyyaml==6.0.2
109173
# -c requirements/../../../requirements/constraints.txt
110174
# -c requirements/_base.txt
111175
# -r requirements/_test.in
176+
redis==5.3.0
177+
# via
178+
# -c requirements/../../../requirements/constraints.txt
179+
# -c requirements/_base.txt
180+
# fakeredis
112181
requests==2.32.3
113182
# via
114183
# -c requirements/_base.txt
115184
# docker
185+
setuptools==80.9.0
186+
# via pytest-celery
187+
six==1.17.0
188+
# via
189+
# -c requirements/_base.txt
190+
# python-dateutil
116191
sniffio==1.3.1
117192
# via
118193
# -c requirements/_base.txt
119194
# anyio
195+
sortedcontainers==2.4.0
196+
# via fakeredis
120197
starlette==0.46.2
121198
# via
122199
# -c requirements/../../../requirements/constraints.txt
123200
# fastapi
201+
tenacity==9.1.2
202+
# via
203+
# -c requirements/_base.txt
204+
# pytest-celery
124205
termcolor==3.1.0
125206
# via pytest-sugar
126207
typing-extensions==4.13.2
@@ -142,9 +223,20 @@ tzdata==2025.2
142223
# via
143224
# -c requirements/_base.txt
144225
# faker
226+
# kombu
145227
urllib3==2.4.0
146228
# via
147229
# -c requirements/../../../requirements/constraints.txt
148230
# -c requirements/_base.txt
149231
# docker
150232
# requests
233+
vine==5.1.0
234+
# via
235+
# -c requirements/_base.txt
236+
# amqp
237+
# celery
238+
# kombu
239+
wcwidth==0.2.13
240+
# via
241+
# -c requirements/_base.txt
242+
# prompt-toolkit

packages/celery-library/requirements/_tools.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ cfgv==3.4.0
1111
click==8.2.1
1212
# via
1313
# -c requirements/_base.txt
14+
# -c requirements/_test.txt
1415
# black
1516
# pip-tools
1617
dill==0.4.0
@@ -70,7 +71,9 @@ pyyaml==6.0.2
7071
ruff==0.11.11
7172
# via -r requirements/../../../requirements/devenv.txt
7273
setuptools==80.9.0
73-
# via pip-tools
74+
# via
75+
# -c requirements/_test.txt
76+
# pip-tools
7477
tomlkit==0.13.2
7578
# via pylint
7679
typing-extensions==4.13.2
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
from collections.abc import AsyncIterator, Awaitable, Callable
2+
from functools import partial
3+
from typing import Final
4+
5+
import pytest
6+
from asgi_lifespan import LifespanManager
7+
from celery import Celery
8+
from celery.contrib.testing.worker import TestWorkController, start_worker
9+
from celery.signals import worker_init, worker_shutdown
10+
from celery.worker.worker import WorkController
11+
from celery_library import setup_celery_client
12+
from celery_library.routes.rpc import router as async_jobs_router
13+
from celery_library.signals import on_worker_init, on_worker_shutdown
14+
from celery_library.utils import get_celery_worker
15+
from celery_library.worker import CeleryTaskWorker
16+
from faker import Faker
17+
from fastapi import FastAPI
18+
from models_library.products import ProductName
19+
from models_library.rabbitmq_basic_types import RPCNamespace
20+
from models_library.users import UserID
21+
from pydantic import TypeAdapter
22+
from servicelib.rabbitmq import RabbitMQRPCClient
23+
from settings_library.celery import CelerySettings
24+
from settings_library.rabbit import RabbitSettings
25+
from settings_library.redis import RedisSettings
26+
27+
pytest_simcore_core_services_selection = [
28+
"rabbit",
29+
"redis",
30+
"postgres",
31+
]
32+
33+
pytest_plugins = [
34+
"pytest_simcore.docker_compose",
35+
"pytest_simcore.docker_swarm",
36+
"pytest_simcore.rabbit_service",
37+
"pytest_simcore.redis_service",
38+
"pytest_simcore.repository_paths",
39+
]
40+
41+
_LIFESPAN_TIMEOUT: Final[int] = 10
42+
43+
44+
@pytest.fixture
45+
def rpc_namespace() -> RPCNamespace:
46+
return TypeAdapter(RPCNamespace).validate_python("test")
47+
48+
49+
@pytest.fixture
50+
def celery_settings(
51+
rabbit_service: RabbitSettings,
52+
redis_service: RedisSettings,
53+
) -> CelerySettings:
54+
return CelerySettings.create_from_envs()
55+
56+
57+
@pytest.fixture
58+
async def initialized_fast_api(
59+
rabbitmq_rpc_client: Callable[[str], Awaitable[RabbitMQRPCClient]],
60+
celery_settings: CelerySettings,
61+
rpc_namespace: RPCNamespace,
62+
) -> AsyncIterator[FastAPI]:
63+
app = FastAPI(
64+
title="master_fastapi_app",
65+
description="Service that manages osparc storage backend",
66+
version="0.0.0",
67+
)
68+
69+
setup_celery_client(app, celery_settings=celery_settings)
70+
rpc_client = await rabbitmq_rpc_client("celery_test_client")
71+
app.state.rabbitmq_rpc_client = rpc_client
72+
73+
async def startup() -> None:
74+
rpc_server = app.state.rabbitmq_rpc_client
75+
assert isinstance(rpc_server, RabbitMQRPCClient)
76+
await rpc_server.register_router(async_jobs_router, rpc_namespace, app)
77+
78+
app.add_event_handler("startup", startup)
79+
80+
async with LifespanManager(
81+
app, startup_timeout=_LIFESPAN_TIMEOUT, shutdown_timeout=_LIFESPAN_TIMEOUT
82+
):
83+
yield app
84+
85+
86+
@pytest.fixture
87+
def register_celery_tasks() -> Callable[[Celery], None]:
88+
"""override if tasks are needed"""
89+
90+
def _(celery_app: Celery) -> None: ...
91+
92+
return _
93+
94+
95+
@pytest.fixture
96+
async def celery_worker_controller(
97+
celery_settings: CelerySettings,
98+
celery_app: Celery,
99+
register_celery_tasks: Callable[[Celery], None],
100+
) -> AsyncIterator[TestWorkController]:
101+
102+
def _create_app() -> FastAPI:
103+
104+
return FastAPI(
105+
title="worker_fastapi_app",
106+
description="Test application for celery_library",
107+
version="0.0.0",
108+
)
109+
110+
def _on_worker_init_wrapper(sender: WorkController, **_kwargs) -> None:
111+
return partial(on_worker_init, _create_app, celery_settings)(sender, **_kwargs)
112+
113+
worker_init.connect(_on_worker_init_wrapper)
114+
worker_shutdown.connect(on_worker_shutdown)
115+
116+
register_celery_tasks(celery_app)
117+
118+
with start_worker(
119+
celery_app,
120+
pool="threads",
121+
concurrency=1,
122+
loglevel="info",
123+
perform_ping_check=False,
124+
queues="default,cpu_bound",
125+
) as worker:
126+
yield worker
127+
128+
129+
@pytest.fixture
130+
def with_storage_celery_worker(
131+
celery_worker_controller: TestWorkController,
132+
) -> CeleryTaskWorker:
133+
assert isinstance(celery_worker_controller.app, Celery)
134+
return get_celery_worker(celery_worker_controller.app)
135+
136+
137+
@pytest.fixture
138+
def user_id(faker: Faker) -> UserID:
139+
return TypeAdapter(UserID).validate_python(faker.pyint(min_value=1, max_value=1000))
140+
141+
142+
@pytest.fixture
143+
def product_name() -> ProductName:
144+
return TypeAdapter(ProductName).validate_python("pytest-product")

0 commit comments

Comments
 (0)