zenit 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. scaffolder/__init__.py +0 -0
  2. scaffolder/addons/__pycache__/_registry.cpython-314.pyc +0 -0
  3. scaffolder/addons/_registry.py +30 -0
  4. scaffolder/addons/celery/__pycache__/addon.cpython-314.pyc +0 -0
  5. scaffolder/addons/celery/addon.py +68 -0
  6. scaffolder/addons/celery/files/tasks/celery_app.py.j2 +37 -0
  7. scaffolder/addons/celery/files/tasks/example_tasks.py.j2 +15 -0
  8. scaffolder/addons/docker/__pycache__/addon.cpython-314.pyc +0 -0
  9. scaffolder/addons/docker/addon.py +31 -0
  10. scaffolder/addons/docker/files/.dockerignore +14 -0
  11. scaffolder/addons/docker/files/Dockerfile.j2 +34 -0
  12. scaffolder/addons/docker/files/compose.yml.j2 +26 -0
  13. scaffolder/addons/github-actions/__pycache__/addon.cpython-314.pyc +0 -0
  14. scaffolder/addons/github-actions/addon.py +18 -0
  15. scaffolder/addons/github-actions/files/ci.yml.j2 +83 -0
  16. scaffolder/addons/redis/__pycache__/addon.cpython-314.pyc +0 -0
  17. scaffolder/addons/redis/addon.py +61 -0
  18. scaffolder/addons/redis/files/compose.redis.yml.j2 +11 -0
  19. scaffolder/addons/redis/files/redis.py +48 -0
  20. scaffolder/addons/sentry/__pycache__/addon.cpython-314.pyc +0 -0
  21. scaffolder/addons/sentry/addon.py +41 -0
  22. scaffolder/addons/sentry/files/sentry.py.j2 +35 -0
  23. scaffolder/assembler.py +266 -0
  24. scaffolder/context.py +85 -0
  25. scaffolder/dryrun.py +159 -0
  26. scaffolder/exceptions.py +2 -0
  27. scaffolder/generate/justfile.j2 +30 -0
  28. scaffolder/generate/pyproject.toml.j2 +58 -0
  29. scaffolder/generate.py +86 -0
  30. scaffolder/git.py +25 -0
  31. scaffolder/main.py +209 -0
  32. scaffolder/prompt.py +384 -0
  33. scaffolder/render.py +47 -0
  34. scaffolder/rollback.py +35 -0
  35. scaffolder/schema.py +118 -0
  36. scaffolder/templates/__pycache__/_load_config.cpython-314.pyc +0 -0
  37. scaffolder/templates/_common/__pycache__/apply.cpython-314.pyc +0 -0
  38. scaffolder/templates/_common/apply.py +42 -0
  39. scaffolder/templates/_common/envrc +4 -0
  40. scaffolder/templates/_common/gitattributes +13 -0
  41. scaffolder/templates/_common/gitignore +26 -0
  42. scaffolder/templates/_common/pre-commit-config.yaml +22 -0
  43. scaffolder/templates/_common/shell.nix +7 -0
  44. scaffolder/templates/_load_config.py +22 -0
  45. scaffolder/templates/blank/__pycache__/template.cpython-314.pyc +0 -0
  46. scaffolder/templates/blank/files/__main__.py +3 -0
  47. scaffolder/templates/blank/files/main.py.j2 +7 -0
  48. scaffolder/templates/blank/files/tests/test_main.py.j2 +9 -0
  49. scaffolder/templates/blank/template.py +51 -0
  50. scaffolder/templates/fastapi/__pycache__/template.cpython-314.pyc +0 -0
  51. scaffolder/templates/fastapi/files/.env.example +4 -0
  52. scaffolder/templates/fastapi/files/.env.j2 +4 -0
  53. scaffolder/templates/fastapi/files/alembic/env.py.j2 +50 -0
  54. scaffolder/templates/fastapi/files/alembic/script.py.mako +24 -0
  55. scaffolder/templates/fastapi/files/alembic.ini.j2 +33 -0
  56. scaffolder/templates/fastapi/files/api/router.py +12 -0
  57. scaffolder/templates/fastapi/files/api/routes/health.py +8 -0
  58. scaffolder/templates/fastapi/files/core/security.py +44 -0
  59. scaffolder/templates/fastapi/files/db/base.py +5 -0
  60. scaffolder/templates/fastapi/files/db/session.py +13 -0
  61. scaffolder/templates/fastapi/files/exceptions.py +25 -0
  62. scaffolder/templates/fastapi/files/lifecycle.py +14 -0
  63. scaffolder/templates/fastapi/files/main.py.j2 +7 -0
  64. scaffolder/templates/fastapi/files/models/mixins.py +27 -0
  65. scaffolder/templates/fastapi/files/schemas/common.py +14 -0
  66. scaffolder/templates/fastapi/files/scripts/wait_db.py +29 -0
  67. scaffolder/templates/fastapi/files/settings.py.j2 +20 -0
  68. scaffolder/templates/fastapi/files/tests/conftest.py.j2 +64 -0
  69. scaffolder/templates/fastapi/files/tests/test_health.py +7 -0
  70. scaffolder/templates/fastapi/template.py +172 -0
  71. scaffolder/ui.py +166 -0
  72. scaffolder/validate.py +85 -0
  73. zenit-1.0.0.dist-info/METADATA +419 -0
  74. zenit-1.0.0.dist-info/RECORD +78 -0
  75. zenit-1.0.0.dist-info/WHEEL +5 -0
  76. zenit-1.0.0.dist-info/entry_points.txt +3 -0
  77. zenit-1.0.0.dist-info/licenses/LICENSE +21 -0
  78. zenit-1.0.0.dist-info/top_level.txt +1 -0
scaffolder/__init__.py ADDED
File without changes
@@ -0,0 +1,30 @@
1
+ """Addon registry — discovers ``addon.py`` files and returns ``AddonConfig`` objects."""
2
+
3
+ import importlib.util
4
+ from pathlib import Path
5
+
6
+ from scaffolder.schema import AddonConfig
7
+
8
+ _HERE = Path(__file__).parent.absolute()
9
+
10
+
11
+ def get_available_addons() -> list[AddonConfig]:
12
+ """Return one ``AddonConfig`` for every addon directory found under this package."""
13
+ addons: list[AddonConfig] = []
14
+ for addon_dir in sorted(
15
+ p for p in _HERE.iterdir() if p.is_dir() and not p.name.startswith("_")
16
+ ):
17
+ try:
18
+ spec = importlib.util.spec_from_file_location(
19
+ "addon_config", addon_dir / "addon.py"
20
+ )
21
+ if spec is None:
22
+ continue
23
+ mod = importlib.util.module_from_spec(spec)
24
+ spec.loader.exec_module(mod) # type: ignore[union-attr]
25
+ cfg: AddonConfig = mod.config
26
+ cfg._module = mod # attach module so post_apply can be called later
27
+ addons.append(cfg)
28
+ except FileNotFoundError, AttributeError:
29
+ continue
30
+ return addons
@@ -0,0 +1,68 @@
1
+ from pathlib import Path
2
+
3
+ from scaffolder.schema import AddonConfig, ComposeService, FileContribution
4
+
5
+ _HERE = Path(__file__).parent.absolute()
6
+
7
+ config = AddonConfig(
8
+ id="celery",
9
+ description="Celery worker + beat scheduler, backed by Redis",
10
+ requires=["redis"],
11
+ files=[
12
+ FileContribution(
13
+ dest="src/{{pkg_name}}/tasks/__init__.py",
14
+ content="",
15
+ ),
16
+ FileContribution(
17
+ dest="src/{{pkg_name}}/tasks/celery_app.py",
18
+ source=str(_HERE / "files" / "tasks" / "celery_app.py.j2"),
19
+ template=True,
20
+ ),
21
+ FileContribution(
22
+ dest="src/{{pkg_name}}/tasks/example_tasks.py",
23
+ source=str(_HERE / "files" / "tasks" / "example_tasks.py.j2"),
24
+ template=True,
25
+ ),
26
+ ],
27
+ compose_services=[
28
+ ComposeService(
29
+ name="celery-worker",
30
+ build=".",
31
+ command="celery -A {{pkg_name}}.tasks.celery_app worker --loglevel=info",
32
+ env_file=[".env"],
33
+ environment={"REDIS_URL": "redis://redis:6379/0"},
34
+ depends_on={
35
+ "redis": {"condition": "service_healthy"},
36
+ },
37
+ develop_watch=[{"action": "sync", "path": "./src", "target": "/app/src"}],
38
+ ),
39
+ ComposeService(
40
+ name="celery-beat",
41
+ build=".",
42
+ command="celery -A {{pkg_name}}.tasks.celery_app beat --loglevel=info",
43
+ env_file=[".env"],
44
+ environment={"REDIS_URL": "redis://redis:6379/0"},
45
+ depends_on={
46
+ "redis": {"condition": "service_healthy"},
47
+ },
48
+ develop_watch=[{"action": "sync", "path": "./src", "target": "/app/src"}],
49
+ ),
50
+ ],
51
+ deps=["celery[redis]>=5", "flower"],
52
+ dev_deps=["pytest-celery"],
53
+ just_recipes=[
54
+ "# start celery worker and beat scheduler\n"
55
+ "celery-up:\n"
56
+ " docker compose up -d celery-worker celery-beat",
57
+ "# stop celery worker and beat scheduler\n"
58
+ "celery-down:\n"
59
+ " docker compose stop celery-worker celery-beat",
60
+ "# open flower monitoring UI on port 5555\n"
61
+ "celery-flower:\n"
62
+ " docker compose run --rm celery-worker "
63
+ "celery -A (( pkg_name )).tasks.celery_app flower --port=5555",
64
+ "# tail celery worker logs\n"
65
+ "celery-logs:\n"
66
+ " docker compose logs -f celery-worker",
67
+ ],
68
+ )
@@ -0,0 +1,37 @@
1
+ """Celery application instance.
2
+
3
+ Start the worker:
4
+ celery -A (( pkg_name )).tasks.celery_app worker --loglevel=info
5
+
6
+ Start the beat scheduler:
7
+ celery -A (( pkg_name )).tasks.celery_app beat --loglevel=info
8
+ """
9
+
10
+ import os
11
+
12
+ from celery import Celery
13
+
14
+ REDIS_URL = os.getenv("REDIS_URL", "redis://localhost:6379/0")
15
+
16
+ celery_app = Celery(
17
+ __name__,
18
+ broker=REDIS_URL,
19
+ backend=REDIS_URL,
20
+ include=["(( pkg_name )).tasks.example_tasks"],
21
+ )
22
+
23
+ celery_app.conf.update(
24
+ task_serializer="json",
25
+ result_serializer="json",
26
+ accept_content=["json"],
27
+ timezone="UTC",
28
+ enable_utc=True,
29
+ # Beat schedule — add periodic tasks here:
30
+ # beat_schedule={
31
+ # "example-every-30s": {
32
+ # "task": "(( pkg_name )).tasks.example_tasks.add",
33
+ # "schedule": 30.0,
34
+ # "args": (1, 2),
35
+ # },
36
+ # },
37
+ )
@@ -0,0 +1,15 @@
1
+ """Example Celery tasks.
2
+
3
+ Usage:
4
+ from (( pkg_name )).tasks.example_tasks import add
5
+ result = add.delay(1, 2)
6
+ print(result.get(timeout=10)) # 3
7
+ """
8
+
9
+ from .celery_app import celery_app
10
+
11
+
12
+ @celery_app.task # type: ignore[untyped-decorator]
13
+ def add(x: int, y: int) -> int:
14
+ """Example task — replace or delete."""
15
+ return x + y
@@ -0,0 +1,31 @@
1
+ from pathlib import Path
2
+
3
+ from scaffolder.schema import AddonConfig, FileContribution
4
+
5
+ _HERE = Path(__file__).parent.absolute()
6
+
7
+ config = AddonConfig(
8
+ id="docker",
9
+ description="Dockerfile + compose.yml + .dockerignore",
10
+ requires=[],
11
+ files=[
12
+ FileContribution(
13
+ dest="Dockerfile",
14
+ source=str(_HERE / "files" / "Dockerfile.j2"),
15
+ template=True,
16
+ ),
17
+ FileContribution(
18
+ dest="compose.yml",
19
+ source=str(_HERE / "files" / "compose.yml.j2"),
20
+ template=True,
21
+ ),
22
+ FileContribution(
23
+ dest=".dockerignore",
24
+ source=str(_HERE / "files" / ".dockerignore"),
25
+ ),
26
+ ],
27
+ just_recipes=[
28
+ "# build and start all services\ndocker-up:\n docker compose up --build",
29
+ "# stop all services\ndocker-down:\n docker compose down",
30
+ ],
31
+ )
@@ -0,0 +1,14 @@
1
+ .venv/
2
+ .direnv/
3
+ .git/
4
+ .mypy_cache/
5
+ .ruff_cache/
6
+ .pytest_cache/
7
+ __pycache__/
8
+ *.py[cod]
9
+ htmlcov/
10
+ .coverage
11
+ *.db
12
+ .env
13
+ result
14
+ result-*
@@ -0,0 +1,34 @@
1
+ # syntax=docker/dockerfile:1
2
+ FROM python:3.14-slim AS base
3
+
4
+ ENV PYTHONDONTWRITEBYTECODE=1 \
5
+ PYTHONUNBUFFERED=1 \
6
+ UV_PYTHON_DOWNLOADS=never
7
+
8
+ WORKDIR /app
9
+
10
+ COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
11
+
12
+ RUN addgroup --system app && adduser --system --ingroup app app
13
+
14
+ COPY pyproject.toml uv.lock ./
15
+ RUN uv sync --frozen --no-dev --no-install-project
16
+
17
+ COPY src/ ./src/
18
+
19
+ RUN uv sync --frozen --no-dev
20
+
21
+ RUN chown -R app:app /app
22
+
23
+ USER app
24
+
25
+ ENV PATH="/app/.venv/bin:$PATH" \
26
+ PYTHONPATH="/app/src"
27
+
28
+ EXPOSE 8000
29
+
30
+ [% if template == "fastapi" %]
31
+ CMD ["uvicorn", "(( pkg_name )).main:app", "--host", "0.0.0.0", "--port", "8000"]
32
+ [% else %]
33
+ CMD ["python", "-m", "(( pkg_name ))"]
34
+ [% endif %]
@@ -0,0 +1,26 @@
1
+ services:
2
+ app:
3
+ build: .
4
+ ports:
5
+ - "8000:8000"
6
+ [% if template == "fastapi" %]
7
+ env_file:
8
+ - .env
9
+ command: uvicorn (( pkg_name )).main:app --host 0.0.0.0 --port 8000 --reload
10
+ [% endif %]
11
+ develop:
12
+ watch:
13
+ - action: sync
14
+ path: ./src
15
+ target: /app/src
16
+ [% if template == "fastapi" %]
17
+
18
+ db:
19
+ image: postgres:16
20
+ environment:
21
+ POSTGRES_PASSWORD: postgres
22
+ ports:
23
+ - "5432:5432"
24
+ volumes:
25
+ - ./.pgdata:/var/lib/postgresql/data
26
+ [% endif %]
@@ -0,0 +1,18 @@
1
+ from pathlib import Path
2
+
3
+ from scaffolder.schema import AddonConfig, FileContribution
4
+
5
+ _HERE = Path(__file__).parent.absolute()
6
+
7
+ config = AddonConfig(
8
+ id="github-actions",
9
+ description="CI workflow (lint, type-check, test on push/PR)",
10
+ requires=[],
11
+ files=[
12
+ FileContribution(
13
+ dest=".github/workflows/ci.yml",
14
+ source=str(_HERE / "files" / "ci.yml.j2"),
15
+ template=True,
16
+ ),
17
+ ],
18
+ )
@@ -0,0 +1,83 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ ci:
11
+ runs-on: ubuntu-latest
12
+ [% if has_postgres or has_redis %]
13
+ services:
14
+ [% if has_postgres %]
15
+ postgres:
16
+ image: postgres:16
17
+ env:
18
+ POSTGRES_USER: postgres
19
+ POSTGRES_PASSWORD: postgres
20
+ POSTGRES_DB: (( name ))_test
21
+ ports:
22
+ - 5432:5432
23
+ options: >-
24
+ --health-cmd pg_isready
25
+ --health-interval 10s
26
+ --health-timeout 5s
27
+ --health-retries 5
28
+ [% endif %]
29
+ [% if has_redis %]
30
+ redis:
31
+ image: redis:7-alpine
32
+ ports:
33
+ - 6379:6379
34
+ options: >-
35
+ --health-cmd "redis-cli ping"
36
+ --health-interval 10s
37
+ --health-timeout 5s
38
+ --health-retries 5
39
+ [% endif %]
40
+ [% endif %]
41
+
42
+ steps:
43
+ - uses: actions/checkout@v4
44
+
45
+ - name: Install uv
46
+ uses: astral-sh/setup-uv@v4
47
+ with:
48
+ version: "latest"
49
+ enable-cache: true
50
+ cache-dependency-glob: "uv.lock"
51
+
52
+ - name: Set up Python
53
+ run: uv python install 3.14
54
+
55
+ - name: Install dependencies
56
+ run: uv sync --all-extras
57
+ [% if has_postgres %]
58
+
59
+ - name: Run migrations
60
+ env:
61
+ DATABASE_URL: postgresql+asyncpg://postgres:postgres@localhost:5432/(( name ))_test
62
+ run: uv run alembic upgrade head
63
+ [% endif %]
64
+
65
+ - name: Lint
66
+ run: uv run ruff check .
67
+
68
+ - name: Format check
69
+ run: uv run ruff format --check .
70
+
71
+ - name: Type check
72
+ run: uv run mypy src/
73
+
74
+ - name: Test
75
+ env:
76
+ [% if has_postgres %]
77
+ DATABASE_URL: postgresql+asyncpg://postgres:postgres@localhost:5432/(( name ))_test
78
+ [% endif %]
79
+ [% if has_redis %]
80
+ REDIS_URL: redis://localhost:6379/0
81
+ [% endif %]
82
+ run: uv run pytest -v
83
+
@@ -0,0 +1,61 @@
1
+ """Declarative config for the redis addon."""
2
+
3
+ from pathlib import Path
4
+
5
+ from scaffolder.schema import (
6
+ AddonConfig,
7
+ ComposeService,
8
+ EnvVar,
9
+ FileContribution,
10
+ Injection,
11
+ )
12
+
13
+ _HERE = Path(__file__).parent.absolute()
14
+
15
+ config = AddonConfig(
16
+ id="redis",
17
+ description="Redis service + connection helper + compose service",
18
+ requires=[],
19
+ files=[
20
+ FileContribution(
21
+ dest="src/{{pkg_name}}/integrations/__init__.py",
22
+ content="",
23
+ ),
24
+ FileContribution(
25
+ dest="src/{{pkg_name}}/integrations/redis.py",
26
+ source=str(_HERE / "files" / "redis.py"),
27
+ ),
28
+ ],
29
+ compose_services=[
30
+ ComposeService(
31
+ name="redis",
32
+ image="redis:7-alpine",
33
+ ports=["6379:6379"],
34
+ volumes=["redis-data:/data"],
35
+ command="redis-server --appendonly yes",
36
+ healthcheck={
37
+ "test": ["CMD", "redis-cli", "ping"],
38
+ "interval": "1s",
39
+ "timeout": "3s",
40
+ "retries": 5,
41
+ },
42
+ )
43
+ ],
44
+ compose_volumes=["redis-data"],
45
+ env_vars=[
46
+ EnvVar(key="REDIS_URL", default="redis://localhost:6379/0"),
47
+ ],
48
+ deps=["redis>=5", "hiredis"],
49
+ dev_deps=["fakeredis[aioredis]"],
50
+ just_recipes=[
51
+ "# start redis\nredis-up:\n docker compose up -d redis",
52
+ "# stop redis\nredis-down:\n docker compose stop redis",
53
+ "# open redis-cli\nredis-cli:\n redis-cli",
54
+ ],
55
+ injections=[
56
+ Injection(
57
+ point="settings_fields",
58
+ content=' redis_url: str = "redis://localhost:6379/0"',
59
+ ),
60
+ ],
61
+ )
@@ -0,0 +1,11 @@
1
+ services:
2
+ redis:
3
+ image: redis:7-alpine
4
+ ports:
5
+ - "6379:6379"
6
+ volumes:
7
+ - redis-data:/data
8
+ command: redis-server --appendonly yes
9
+
10
+ volumes:
11
+ redis-data:
@@ -0,0 +1,48 @@
1
+ """Redis connection helper.
2
+
3
+ Usage:
4
+ from mypackage.integrations.redis import get_redis
5
+
6
+ @router.get("/example")
7
+ async def example(redis: Redis = Depends(get_redis)) -> dict:
8
+ await redis.set("key", "value", ex=60)
9
+ value = await redis.get("key")
10
+ return {"value": value}
11
+ """
12
+
13
+ import os
14
+ from collections.abc import AsyncGenerator
15
+
16
+ import redis.asyncio as aioredis
17
+ from redis.asyncio import Redis
18
+
19
+ # REDIS_URL is read from the environment directly.
20
+ # For FastAPI projects, settings.py exposes this via the redis_url field,
21
+ # which is set from the REDIS_URL environment variable.
22
+ REDIS_URL = os.getenv("REDIS_URL", "redis://localhost:6379/0")
23
+
24
+ _pool: aioredis.ConnectionPool | None = None
25
+
26
+
27
+ def _get_pool() -> aioredis.ConnectionPool:
28
+ global _pool
29
+ if _pool is None:
30
+ _pool = aioredis.ConnectionPool.from_url(
31
+ REDIS_URL,
32
+ decode_responses=True,
33
+ )
34
+ return _pool
35
+
36
+
37
+ async def get_redis() -> AsyncGenerator[Redis]:
38
+ """FastAPI dependency that yields a Redis client from the shared pool."""
39
+ async with aioredis.Redis(connection_pool=_get_pool()) as client:
40
+ yield client
41
+
42
+
43
+ async def close_redis() -> None:
44
+ """Call from lifespan shutdown to cleanly drain the pool."""
45
+ global _pool
46
+ if _pool is not None:
47
+ await _pool.aclose()
48
+ _pool = None
@@ -0,0 +1,41 @@
1
+ from pathlib import Path
2
+
3
+ from scaffolder.schema import AddonConfig, EnvVar, FileContribution, Injection
4
+
5
+ _HERE = Path(__file__).parent.absolute()
6
+
7
+ config = AddonConfig(
8
+ id="sentry",
9
+ description="Sentry error tracking + performance monitoring",
10
+ requires=[],
11
+ files=[
12
+ FileContribution(
13
+ dest="src/{{pkg_name}}/integrations/__init__.py",
14
+ content="",
15
+ ),
16
+ FileContribution(
17
+ dest="src/{{pkg_name}}/integrations/sentry.py",
18
+ source=str(_HERE / "files" / "sentry.py.j2"),
19
+ template=True,
20
+ ),
21
+ ],
22
+ env_vars=[
23
+ EnvVar(key="SENTRY_DSN", default=""),
24
+ EnvVar(key="SENTRY_ENVIRONMENT", default="development"),
25
+ ],
26
+ deps=["sentry-sdk[fastapi]"],
27
+ just_recipes=[
28
+ "# print sentry-sdk version\nsentry-check:\n uv run python -c \"import sentry_sdk; print('sentry-sdk', sentry_sdk.VERSION)\"",
29
+ "# check whether SENTRY_DSN is set\nsentry-test:\n uv run python -c \"from (( pkg_name )).integrations.sentry import init_sentry; import os; init_sentry(); print('Sentry DSN:', os.environ.get('SENTRY_DSN') or 'not set')\"",
30
+ ],
31
+ injections=[
32
+ Injection(
33
+ point="lifespan_startup",
34
+ content=" from .integrations.sentry import init_sentry\n init_sentry()",
35
+ ),
36
+ Injection(
37
+ point="settings_fields",
38
+ content=' sentry_dsn: str = ""\n sentry_environment: str = "development"',
39
+ ),
40
+ ],
41
+ )
@@ -0,0 +1,35 @@
1
+ """Sentry initialisation.
2
+
3
+ Set SENTRY_DSN in your .env to enable. Leave blank to disable (safe for local dev).
4
+
5
+ Get your DSN at: https://sentry.io/settings/<org>/projects/<project>/keys/
6
+ """
7
+
8
+ # ruff: noqa: I001
9
+ import os
10
+
11
+ import sentry_sdk
12
+ [% if template == "fastapi" %]
13
+ from sentry_sdk.integrations.fastapi import FastApiIntegration
14
+ from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration
15
+ [% endif %]
16
+
17
+
18
+ def init_sentry() -> None:
19
+ dsn = os.getenv("SENTRY_DSN", "")
20
+ if not dsn:
21
+ return
22
+
23
+ sentry_sdk.init(
24
+ dsn=dsn,
25
+ environment=os.getenv("SENTRY_ENVIRONMENT", "development"),
26
+ traces_sample_rate=1.0,
27
+ profiles_sample_rate=1.0,
28
+ [% if template == "fastapi" %]
29
+ integrations=[
30
+ FastApiIntegration(),
31
+ SqlalchemyIntegration(),
32
+ ],
33
+ [% endif %]
34
+ send_default_pii=False,
35
+ )