Skip to content

Commit fb11c1d

Browse files
committed
feat(plugin): add saq
1 parent 3c23765 commit fb11c1d

6 files changed

Lines changed: 115 additions & 6 deletions

File tree

src/Litestar/App/app.py.jinja

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ from litestar_vite import ViteConfig, VitePlugin
1717
{% if has_store %}
1818
from .config import redis_store
1919
{% endif %}
20+
{% if litestar_saq %}
21+
from .config import saq
22+
{% endif %}
2023

2124
@get("/health")
2225
async def health() -> dict[str, str]:
@@ -52,11 +55,15 @@ app = Litestar(
5255
RepositoryError: exception_handler,
5356
},
5457
{% endif %}
55-
{% if advanced_alchemy and litestar_vite %}
56-
plugins=[alchemy, VitePlugin(config=ViteConfig(dev_mode=settings.DEBUG))],
57-
{% elif advanced_alchemy %}
58-
plugins=[alchemy],
59-
{% elif litestar_vite %}
60-
plugins=[VitePlugin(config=ViteConfig(dev_mode=settings.DEBUG))],
58+
plugins=[
59+
{% if advanced_alchemy %}
60+
alchemy,
61+
{% endif %}
62+
{% if litestar_vite %}
63+
VitePlugin(config=ViteConfig(dev_mode=settings.DEBUG)),
64+
{% endif %}
65+
{% if litestar_saq %}
66+
saq,
6167
{% endif %}
68+
],
6269
)

src/Litestar/App/config.py.jinja

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ from litestar_vite import ViteConfig, PathConfig
1212
{% if has_store %}
1313
from litestar.stores.redis import RedisStore
1414
{% endif %}
15+
{% if litestar_saq %}
16+
from litestar_saq import QueueConfig, SAQConfig, SAQPlugin
17+
{% endif %}
1518

1619
from .settings import get_settings
1720

@@ -48,3 +51,14 @@ redis_store = RedisStore.with_client(
4851
db=0,
4952
)
5053
{% endif %}
54+
55+
{% if litestar_saq %}
56+
saq = SAQPlugin(
57+
config=SAQConfig(
58+
use_server_lifespan=True,
59+
queue_configs=[
60+
QueueConfig(name="default", dsn=settings.REDIS_URL),
61+
],
62+
)
63+
)
64+
{% endif %}

src/Litestar/Config/pyproject.toml.jinja

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ dependencies = [
2323
{% if has_store %}
2424
"redis>=5.2.1",
2525
{% endif %}
26+
{% if litestar_saq %}
27+
"litestar-saq>=0.7.0",
28+
{% endif %}
2629
]
2730

2831
[tool.ruff]
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""Background tasks."""
2+
3+
import asyncio
4+
import logging
5+
from saq.types import Context
6+
7+
logger = logging.getLogger(__name__)
8+
9+
10+
async def sample_task(ctx: Context) -> dict:
11+
"""A sample background task.
12+
13+
Args:
14+
ctx: The SAQ job context.
15+
16+
Returns:
17+
A dictionary with the task result.
18+
19+
"""
20+
logger.info("Running sample background task")
21+
await asyncio.sleep(1)
22+
return {"status": "complete"}

src/Litestar/Plugins/LitestarSAQ/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from src.models import MemoryStore, ProjectConfig
12
from src.plugin import BasePlugin
23

34

@@ -11,3 +12,6 @@ def name(self) -> str: # noqa: D102
1112
@property
1213
def description(self) -> str: # noqa: D102
1314
return "SAQ integration for background tasks in Litestar"
15+
16+
def is_applicable(self, config: ProjectConfig) -> bool: # noqa: D102, PLR6301
17+
return config.memory_store != MemoryStore.NONE

tests/test_generator.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,62 @@ def test_litestar_generator_docker_compose_rendering(tmp_path: Path) -> None:
124124
# Check services
125125
assert "image: postgres:17.7-alpine3.23" in content
126126
assert "image: redis:8.4.0-bookworm" in content
127+
128+
129+
def test_litestar_generator_saq_context(tmp_path: Path) -> None:
130+
"""Verify Litestar generator template context values with SAQ plugin enabled."""
131+
config = ProjectConfig(
132+
name="SAQ Test",
133+
framework=Framework.LITESTAR,
134+
database=Database.NONE,
135+
memory_store=MemoryStore.REDIS,
136+
plugins=["litestar_saq"],
137+
docker=False,
138+
docker_infra=False,
139+
)
140+
141+
generator = LitestarGenerator(config, tmp_path)
142+
context = generator._get_template_context()
143+
144+
assert context["litestar_saq"] is True
145+
assert context["has_store"] is True
146+
assert context["memory_store"] == MemoryStore.REDIS
147+
148+
149+
def test_litestar_generator_saq_rendering(tmp_path: Path) -> None:
150+
"""Verify that SAQ plugin templates are correctly rendered into the output directory."""
151+
config = ProjectConfig(
152+
name="SAQ Plugin Test",
153+
framework=Framework.LITESTAR,
154+
database=Database.NONE,
155+
memory_store=MemoryStore.REDIS,
156+
plugins=["litestar_saq"],
157+
docker=False,
158+
docker_infra=False,
159+
)
160+
161+
generator = LitestarGenerator(config, tmp_path)
162+
generator.generate()
163+
164+
# Verify base files
165+
assert (tmp_path / "pyproject.toml").exists()
166+
assert (tmp_path / "src" / "backend" / "app.py").exists()
167+
assert (tmp_path / "src" / "backend" / "config.py").exists()
168+
169+
# Verify SAQ plugin files
170+
assert (tmp_path / "src" / "backend" / "lib" / "tasks.py").exists()
171+
172+
# Verify SAQ config in config.py
173+
config_content = (tmp_path / "src" / "backend" / "config.py").read_text()
174+
assert "from litestar_saq import QueueConfig, SAQConfig, SAQPlugin" in config_content
175+
assert "saq = SAQPlugin(" in config_content
176+
assert 'QueueConfig(name="default"' in config_content
177+
178+
# Verify SAQ plugin in app.py
179+
app_content = (tmp_path / "src" / "backend" / "app.py").read_text()
180+
assert "from .config import saq" in app_content
181+
assert "saq," in app_content
182+
183+
# Verify SAQ dependency in pyproject.toml
184+
pyproject_content = (tmp_path / "pyproject.toml").read_text()
185+
assert "litestar-saq>=0.7.0" in pyproject_content

0 commit comments

Comments
 (0)