Skip to content

Commit f525d3b

Browse files
authored
Merge pull request #48 from danfimov/feat-granian
feat: use graninan instead of uvicorn
2 parents 800f902 + 57c53df commit f525d3b

16 files changed

Lines changed: 568 additions & 361 deletions

File tree

Dockerfile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
FROM python:3.12-slim AS builder
1+
FROM python:3.13-slim AS builder
22

33
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
44

55
ENV UV_LINK_MODE=copy \
66
UV_COMPILE_BYTECODE=1 \
77
UV_PYTHON_DOWNLOADS=never \
8-
UV_PYTHON=python3.12 \
8+
UV_PYTHON=python3.13 \
99
UV_PROJECT_ENVIRONMENT="/app/.venv"
1010

1111
# Install dependencies
1212
COPY ./pyproject.toml ./uv.lock ./
13-
RUN uv sync --no-dev --locked --no-install-project
13+
RUN uv sync --extra server --no-dev --locked --no-install-project
1414

15-
FROM python:3.12-slim
15+
FROM python:3.13-slim
1616

1717
ENV PATH="/app/.venv/bin:$PATH"
1818

docs/examples/example_with_broker.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,16 @@ async def best_task_ever(*args, **kwargs) -> dict[str, tp.Any]:
4242
}
4343

4444

45-
def run_admin_panel() -> None:
45+
async def run_admin_panel() -> None:
4646
app = TaskiqDashboard(
4747
api_token='supersecret',
48+
storage_type='postgres',
49+
database_dsn=dsn.replace('postgres://', 'postgresql+asyncpg://'),
4850
broker=broker,
49-
host='0.0.0.0',
51+
address='0.0.0.0',
5052
port=8000,
5153
)
52-
app.run()
54+
await app.run()
5355

5456

5557
async def send_task() -> None:
@@ -62,7 +64,7 @@ async def send_task() -> None:
6264
if __name__ == '__main__':
6365
if sys.argv[1] == 'admin_panel':
6466
print('Starting admin panel...')
65-
run_admin_panel()
67+
asyncio.run(run_admin_panel())
6668
elif sys.argv[1] == 'send_task':
6769
print('Sending task to the broker...')
6870
asyncio.run(send_task())

docs/examples/example_with_scheduler.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,18 @@ async def best_task_ever(*args, **kwargs) -> dict[str, tp.Any]:
5656
}
5757

5858

59-
def run_admin_panel() -> None:
59+
async def run_admin_panel() -> None:
6060
app = TaskiqDashboard(
6161
api_token='supersecret',
62+
storage_type='postgres',
63+
database_dsn=dsn.replace('postgres://', 'postgresql+asyncpg://'),
6264
broker=broker,
6365
scheduler=scheduler,
64-
host='0.0.0.0',
66+
address='0.0.0.0',
6567
port=8000,
6668
)
67-
app.run()
69+
await app.run()
6870

6971

7072
if __name__ == '__main__':
71-
run_admin_panel()
73+
asyncio.run(run_admin_panel())

docs/index.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,19 @@ docker pull ghcr.io/danfimov/taskiq-dashboard:latest
105105

106106
You can also pass `broker` or `scheduler` instances directly to the `TaskiqDashboard` constructor and get additional features like actions with tasks or schedule configuration. Read more about it in the [tutorial](./tutorial/run_with_broker.md) section.
107107

108+
!!! note "Dashboard can be a part of your existing API server"
109+
110+
If you already have an API server running, you can mount admin panel routes to it:
111+
112+
```python
113+
from taskiq_dashboard import TaskiqDashboard
114+
import fastapi
115+
116+
app = fastapi.FastAPI(...)
117+
admin_dashboard = TaskiqDashboard(...)
118+
app.mount('/admin', admin_dashboard.application)
119+
```
120+
108121
### Run with docker compose
109122

110123
=== "postgres"

pyproject.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@ authors = [
3434
requires-python = ">=3.10"
3535
dependencies = [
3636
# api
37-
"fastapi>=0.120.0",
38-
"uvicorn[standard]>=0.40.0",
37+
"fastapi>=0.128.0",
3938
# html templates
4039
"jinja2>=3.1.6",
4140
# db
@@ -50,6 +49,11 @@ dependencies = [
5049
"taskiq>=0.12.1",
5150
]
5251

52+
[project.optional-dependencies]
53+
server = [
54+
"granian>=2.6.0",
55+
]
56+
5357
[project.urls]
5458
"Bug Tracker" = "https://github.com/danfimov/taskiq-dashboard/issues"
5559
"Repository" = "https://github.com/danfimov/taskiq-dashboard/"

taskiq_dashboard/api/__main__.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
1+
import asyncio
2+
13
from taskiq_dashboard import TaskiqDashboard
24
from taskiq_dashboard.infrastructure import get_settings
35

46

5-
if __name__ == '__main__':
7+
async def main() -> None:
68
settings = get_settings()
79
storage_type = settings.storage_type
8-
TaskiqDashboard(
10+
dashboard = TaskiqDashboard(
911
api_token=settings.api.token.get_secret_value(),
1012
storage_type=storage_type,
1113
database_dsn=(
12-
settings.postgres.dsn.get_secret_value() if storage_type == 'postgres'
14+
settings.postgres.dsn.get_secret_value()
15+
if storage_type == 'postgres'
1316
else settings.sqlite.dsn.get_secret_value()
1417
),
1518
**settings.api.model_dump(exclude='token'), # type: ignore[arg-type]
16-
).run()
19+
)
20+
await dashboard.run()
21+
22+
23+
if __name__ == '__main__':
24+
asyncio.run(main())

taskiq_dashboard/api/routers/action.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,13 @@ async def handle_task_rerun(
101101
name='Delete task',
102102
)
103103
async def handle_task_delete(
104+
request: fastapi.Request,
104105
task_id: uuid.UUID,
105106
repository: dishka_fastapi.FromDishka[AbstractTaskRepository],
106107
) -> Response:
107108
await repository.delete_task(task_id)
109+
mount_prefix = request.url.path.rsplit('/actions/delete/', 1)[0]
108110
return RedirectResponse(
109-
url='/',
111+
url=mount_prefix if mount_prefix else '/',
110112
status_code=status.HTTP_307_TEMPORARY_REDIRECT,
111113
)

taskiq_dashboard/api/routers/event.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,6 @@ async def handle_task_event(
4040
task_id: The unique identifier of the task.
4141
event: The type of event (e.g., 'queued', 'started', 'executed').
4242
"""
43-
# Here you would implement the logic to handle the task event,
44-
# such as updating a database record or logging the event.
4543
task_arguments: QueuedTask | StartedTask | ExecutedTask
4644
match event:
4745
case 'queued':

taskiq_dashboard/api/templates/task_details.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ <h3 class="mb-2">Started at</h3>
119119
{% if task.started_at %}
120120
<p class="font-light">{{ task.started_at.strftime('%Y-%m-%d %H:%M:%S') }}</p>
121121
{% else %}
122-
<p class="font-light">Not started yet</p>
122+
<p class="font-light">-</p>
123123
{% endif %}
124124
</div>
125125
<div>
@@ -128,7 +128,7 @@ <h3 class="mb-2">Finished at</h3>
128128
{% if task.finished_at %}
129129
{{ task.finished_at.strftime('%Y-%m-%d %H:%M:%S') }}
130130
{% else %}
131-
<span class="font-light">Not finished yet</span>
131+
<span class="font-light">-</span>
132132
{% endif %}
133133
</p>
134134
</div>

taskiq_dashboard/infrastructure/database/schemas.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,7 @@ class PostgresTask(BaseTableSchema):
4242

4343
queued_at: Mapped[dt.datetime] = mapped_column(
4444
sa.DateTime(timezone=True),
45-
nullable=False,
46-
default=dt.datetime.now,
45+
nullable=True,
4746
)
4847
started_at: Mapped[dt.datetime] = mapped_column(
4948
sa.DateTime(timezone=True),
@@ -77,8 +76,7 @@ class SqliteTask(BaseTableSchema):
7776

7877
queued_at: Mapped[dt.datetime] = mapped_column(
7978
sa.DateTime(timezone=True),
80-
nullable=False,
81-
default=dt.datetime.now,
79+
nullable=True,
8280
)
8381
started_at: Mapped[dt.datetime] = mapped_column(
8482
sa.DateTime(timezone=True),

0 commit comments

Comments
 (0)