Skip to content

Commit 3f4113e

Browse files
committed
TaskIQ
1 parent a3feaea commit 3f4113e

18 files changed

Lines changed: 285 additions & 35 deletions

File tree

fastapi_forge/dtos.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ class ProjectSpec(_Base):
295295
use_builtin_auth: bool = False
296296
use_redis: bool = False
297297
use_rabbitmq: bool = False
298+
use_taskiq: bool = False
298299
models: list[Model] = []
299300

300301
@model_validator(mode="after")

fastapi_forge/example-projects/game_zone.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ project:
55
use_builtin_auth: true
66
use_redis: true
77
use_rabbitmq: true
8+
use_taskiq: true
89

910
models:
1011
- name: auth_user

fastapi_forge/frontend/panels/project_config_panel.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,10 @@ def _build(self) -> None:
8787

8888
with ui.column().classes("w-full gap-2"):
8989
ui.label("Task Queues").classes("text-lg font-bold")
90-
self.use_taskiq = (
91-
ui.checkbox("Taskiq")
92-
.classes("w-full")
93-
.tooltip("Coming soon!")
94-
.set_enabled(False)
95-
)
90+
self.use_taskiq = ui.checkbox(
91+
"Taskiq",
92+
value=state.use_taskiq,
93+
).classes("w-full")
9694
self.use_celery = (
9795
ui.checkbox("Celery")
9896
.classes("w-full")
@@ -142,6 +140,8 @@ def _bind_state_to_ui(self) -> None:
142140
self.use_alembic.bind_value_to(state, "use_alembic")
143141
self.use_builtin_auth.bind_value_to(state, "use_builtin_auth")
144142
self.use_rabbitmq.bind_value_to(state, "use_rabbitmq")
143+
self.use_redis.bind_value_to(state, "use_redis")
144+
self.use_taskiq.bind_value_to(state, "use_taskiq")
145145

146146
def _handle_builtin_auth_change(self, event: ValueChangeEventArguments) -> None:
147147
"""Handle JWT Auth checkbox changes"""
@@ -245,6 +245,7 @@ async def _create_project(self) -> None:
245245
state.use_builtin_auth = self.use_builtin_auth.value
246246
state.use_redis = self.use_redis.value
247247
state.use_rabbitmq = self.use_rabbitmq.value
248+
state.use_taskiq = self.use_taskiq.value
248249

249250
project_spec = state.get_project_spec()
250251
await build_project(project_spec)

fastapi_forge/frontend/state.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class ProjectState(BaseModel):
3737
use_builtin_auth: bool = False
3838
use_redis: bool = False
3939
use_rabbitmq: bool = False
40+
use_taskiq: bool = False
4041

4142
def initialize_from_project(self, project: ProjectSpec) -> None:
4243
"""Initialize state from an existing project specification."""
@@ -46,6 +47,7 @@ def initialize_from_project(self, project: ProjectSpec) -> None:
4647
self.use_builtin_auth = project.use_builtin_auth
4748
self.use_redis = project.use_redis
4849
self.use_rabbitmq = project.use_rabbitmq
50+
self.use_taskiq = project.use_taskiq
4951
self.models = project.models.copy()
5052

5153
self._trigger_ui_refresh()
@@ -111,6 +113,7 @@ def get_project_spec(self) -> ProjectSpec:
111113
use_builtin_auth=self.use_builtin_auth,
112114
use_redis=self.use_redis,
113115
use_rabbitmq=self.use_rabbitmq,
116+
use_taskiq=self.use_taskiq,
114117
models=self.models,
115118
)
116119

fastapi_forge/template/cookiecutter.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
"use_rabbitmq": {
1818
"default": true
1919
},
20+
"use_taskiq": {
21+
"default": true
22+
},
2023
"models": {
2124
"models": []
2225
},

fastapi_forge/template/{{cookiecutter.project_name}}/docker-compose.yaml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
services:
22
api:
3+
&api
34
build:
45
context: .
56
image: {{ cookiecutter.project_name }}:latest
@@ -71,6 +72,19 @@ services:
7172
path: pyproject.toml
7273
- action: rebuild
7374
path: uv.lock
75+
{% if cookiecutter.use_taskiq %}
76+
taskiq-worker:
77+
<<: *api
78+
container_name: game_zone-taskiq-worker
79+
ports: []
80+
command: [ taskiq, worker, -fsd, src.services.taskiq.broker:broker, -w, "1", --max-fails, "1"]
81+
82+
taskiq-scheduler:
83+
<<: *api
84+
container_name: game_zone-taskiq-scheduler
85+
ports: []
86+
command: [ taskiq, scheduler, -fsd, src.services.taskiq.scheduler:scheduler ]
87+
{%- endif %}
7488
{% if cookiecutter.use_postgres %}
7589
postgres:
7690
image: postgres:13.8-bullseye
@@ -108,7 +122,7 @@ services:
108122
{% endif %}
109123
{%- if cookiecutter.use_rabbitmq -%}
110124
rabbitmq:
111-
image: rabbitmq:3-management
125+
image: rabbitmq:3.8.27-management-alpine
112126
container_name: {{ cookiecutter.project_name }}-rabbitmq
113127
ports:
114128
- "15672:15672"

fastapi_forge/template/{{cookiecutter.project_name}}/forge-config.yaml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,17 @@ paths:
2828
enabled: {{cookiecutter.use_rabbitmq | lower}}
2929
paths:
3030
- src/services/rabbitmq
31-
- src/routes/demo_routes.py
31+
32+
use_taskiq:
33+
enabled: {{cookiecutter.use_taskiq | lower}}
34+
paths:
35+
- src/services/taskiq
3236

3337
constants:
3438
requires_all:
3539
- use_builtin_auth
3640
- use_rabbitmq
3741
paths:
38-
- src/constants.py
42+
- src/constants.py
43+
44+

fastapi_forge/template/{{cookiecutter.project_name}}/pyproject.toml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ dependencies = [
1414
"ruff>=0.9.4",
1515
{%- if cookiecutter.use_postgres -%}
1616
"sqlalchemy[asyncio]>=2.0.37",
17-
{% endif %}
18-
{%- if cookiecutter.use_postgres -%}
1917
"asyncpg>=0.30.0",
2018
{% endif %}
2119
{%- if cookiecutter.use_builtin_auth -%}
@@ -31,10 +29,18 @@ dependencies = [
3129
"factory-boy>=3.3.3",
3230
{%- if cookiecutter.use_redis -%}
3331
"redis>=5.2.1",
32+
"fakeredis>=2.28.1",
3433
{% endif %}
3534
{%- if cookiecutter.use_rabbitmq -%}
3635
"aio-pika>=9.5.5",
3736
{%- endif %}
37+
{%- if cookiecutter.use_taskiq -%}
38+
"taskiq>=0.11.16",
39+
"taskiq-aio-pika>=0.4.1",
40+
"taskiq-redis>=1.0.4",
41+
"taskiq-fastapi>=0.3.4",
42+
"orjson>=3.10.16",
43+
{%- endif %}
3844
]
3945

4046
[tool.pytest.ini_options]
@@ -64,7 +70,9 @@ ignore = [
6470
#### specific rules
6571
"A001",
6672
"A002",
73+
"ARG002",
6774
"ARG001",
75+
"B008",
6876
"B904",
6977
"BLE001",
7078
"D100",

fastapi_forge/template/{{cookiecutter.project_name}}/src/main.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
from src.services.rabbitmq import rabbitmq_lifetime
1717
from src.constants import QUEUE_CONFIGS
1818
{% endif %}
19+
{% if cookiecutter.use_taskiq %}
20+
from src.services.taskiq import taskiq_lifetime
21+
{% endif %}
1922

2023
@asynccontextmanager
2124
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
@@ -29,6 +32,9 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
2932
{%- if cookiecutter.use_rabbitmq -%}
3033
await rabbitmq_lifetime.setup_rabbitmq(app, configs=QUEUE_CONFIGS)
3134
{% endif %}
35+
{%- if cookiecutter.use_taskiq %}
36+
await taskiq_lifetime.setup_taskiq()
37+
{% endif %}
3238

3339
yield
3440

@@ -41,6 +47,9 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
4147
{%- if cookiecutter.use_rabbitmq -%}
4248
await rabbitmq_lifetime.shutdown_rabbitmq(app)
4349
{% endif %}
50+
{%- if cookiecutter.use_taskiq %}
51+
await taskiq_lifetime.shutdown_taskiq()
52+
{% endif %}
4453

4554

4655
def get_app() -> FastAPI:

fastapi_forge/template/{{cookiecutter.project_name}}/src/routes/demo_routes.py

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77
from pydantic import BaseModel
88
from typing import Any
99

10+
{% if cookiecutter.use_taskiq %}
11+
from datetime import UTC, datetime, timedelta
12+
from src.services.taskiq import tasks
13+
from src.services.taskiq.scheduler import redis_source
14+
{% endif %}
15+
1016
router = APIRouter(prefix="/demo")
1117

1218
{% if cookiecutter.use_rabbitmq %}
@@ -17,10 +23,8 @@ class RabbitMQDemoMessage(BaseModel):
1723

1824
{% if cookiecutter.use_redis %}
1925
@router.post("/set-redis")
20-
async def set_redis_value(key: str, value: str, redis: GetRedis,) -> dict[str, Any]:
26+
async def set_redis_value(key: str, value: str, redis: GetRedis,) -> None:
2127
await redis.set(key, value)
22-
return {"message": "Value set successfully", "key": key, "value": value}
23-
2428

2529
@router.get("/get-redis")
2630
async def get_redis_value(key: str, redis: GetRedis,) -> dict[str, Any]:
@@ -35,11 +39,22 @@ async def get_redis_value(key: str, redis: GetRedis,) -> dict[str, Any]:
3539
async def send_rabbitmq_message(
3640
message: RabbitMQDemoMessage,
3741
rabbitmq: GetRabbitMQ,
38-
) -> dict[str, Any]:
42+
) -> None:
3943
await rabbitmq.send_demo_message(message)
40-
return {
41-
"message": "RabbitMQ message sent successfully",
42-
"key": message.key,
43-
"value": message.value,
44-
}
44+
{% endif %}
45+
46+
{% if cookiecutter.use_taskiq %}
47+
@router.post("/taskiq-kiq")
48+
async def kick_taskiq_message() -> None:
49+
await tasks.demo_task.kiq(hello="hello taskiq", world="world taskiq")
50+
51+
52+
@router.post("/taskiq-scheduled")
53+
async def schedule_taskiq_message(delay_seconds: int = 10) -> None:
54+
await tasks.demo_task.schedule_by_time(
55+
redis_source,
56+
datetime.now(UTC) + timedelta(seconds=delay_seconds),
57+
hello="hello taskiq scheduled",
58+
world="world taskiq scheduled",
59+
)
4560
{% endif %}

0 commit comments

Comments
 (0)