snapstack 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 (102) hide show
  1. pysnap/__init__.py +1 -0
  2. pysnap/_shared/Dockerfile.j2 +34 -0
  3. pysnap/_shared/ci.yml.j2 +53 -0
  4. pysnap/_shared/docker-compose.yml.j2 +46 -0
  5. pysnap/_shared/dockerignore.j2 +13 -0
  6. pysnap/_shared/env_example.j2 +24 -0
  7. pysnap/_shared/gitignore.j2 +15 -0
  8. pysnap/commands/__init__.py +1 -0
  9. pysnap/commands/add.py +171 -0
  10. pysnap/commands/create.py +136 -0
  11. pysnap/commands/templates_cmd.py +134 -0
  12. pysnap/commands/update.py +133 -0
  13. pysnap/community.py +113 -0
  14. pysnap/config.py +76 -0
  15. pysnap/generator.py +262 -0
  16. pysnap/main.py +65 -0
  17. pysnap/manifest.py +101 -0
  18. pysnap/plugins.py +123 -0
  19. pysnap/preview.py +131 -0
  20. pysnap/prompts.py +217 -0
  21. pysnap/registry.py +123 -0
  22. pysnap/templates/django/.dockerignore.j2 +15 -0
  23. pysnap/templates/django/.github/workflows/ci.yml.j2 +34 -0
  24. pysnap/templates/django/.gitignore.j2 +14 -0
  25. pysnap/templates/django/Dockerfile.j2 +14 -0
  26. pysnap/templates/django/README.md.j2 +36 -0
  27. pysnap/templates/django/apps/__init__.py.j2 +0 -0
  28. pysnap/templates/django/apps/core/__init__.py.j2 +0 -0
  29. pysnap/templates/django/apps/core/apps.py.j2 +6 -0
  30. pysnap/templates/django/apps/core/urls.py.j2 +7 -0
  31. pysnap/templates/django/apps/core/views.py.j2 +6 -0
  32. pysnap/templates/django/apps/users/__init__.py.j2 +0 -0
  33. pysnap/templates/django/apps/users/apps.py.j2 +6 -0
  34. pysnap/templates/django/apps/users/models.py.j2 +14 -0
  35. pysnap/templates/django/apps/users/serializers.py.j2 +13 -0
  36. pysnap/templates/django/apps/users/urls.py.j2 +10 -0
  37. pysnap/templates/django/apps/users/views.py.j2 +22 -0
  38. pysnap/templates/django/config/__init__.py.j2 +0 -0
  39. pysnap/templates/django/config/asgi.py.j2 +9 -0
  40. pysnap/templates/django/config/settings.py.j2 +110 -0
  41. pysnap/templates/django/config/urls.py.j2 +12 -0
  42. pysnap/templates/django/config/wsgi.py.j2 +9 -0
  43. pysnap/templates/django/docker-compose.yml.j2 +29 -0
  44. pysnap/templates/django/manage.py.j2 +22 -0
  45. pysnap/templates/django/pyproject.toml.j2 +40 -0
  46. pysnap/templates/django/template.json +50 -0
  47. pysnap/templates/django/tests/__init__.py.j2 +1 -0
  48. pysnap/templates/django/tests/conftest.py.j2 +6 -0
  49. pysnap/templates/django/tests/test_health.py.j2 +9 -0
  50. pysnap/templates/fastapi/.dockerignore.j2 +8 -0
  51. pysnap/templates/fastapi/.github/workflows/ci.yml.j2 +46 -0
  52. pysnap/templates/fastapi/.gitignore.j2 +13 -0
  53. pysnap/templates/fastapi/Dockerfile.j2 +14 -0
  54. pysnap/templates/fastapi/README.md.j2 +57 -0
  55. pysnap/templates/fastapi/api/__init__.py.j2 +0 -0
  56. pysnap/templates/fastapi/api/routes/__init__.py.j2 +0 -0
  57. pysnap/templates/fastapi/api/routes/auth.py.j2 +18 -0
  58. pysnap/templates/fastapi/api/routes/health.py.j2 +8 -0
  59. pysnap/templates/fastapi/app/__init__.py.j2 +1 -0
  60. pysnap/templates/fastapi/core/__init__.py.j2 +0 -0
  61. pysnap/templates/fastapi/core/config.py.j2 +26 -0
  62. pysnap/templates/fastapi/core/security.py.j2 +22 -0
  63. pysnap/templates/fastapi/db/__init__.py.j2 +0 -0
  64. pysnap/templates/fastapi/db/base.py.j2 +5 -0
  65. pysnap/templates/fastapi/db/session.py.j2 +15 -0
  66. pysnap/templates/fastapi/docker-compose.yml.j2 +30 -0
  67. pysnap/templates/fastapi/main.py.j2 +27 -0
  68. pysnap/templates/fastapi/models/__init__.py.j2 +0 -0
  69. pysnap/templates/fastapi/models/user.py.j2 +13 -0
  70. pysnap/templates/fastapi/pyproject.toml.j2 +48 -0
  71. pysnap/templates/fastapi/schemas/__init__.py.j2 +0 -0
  72. pysnap/templates/fastapi/schemas/user.py.j2 +18 -0
  73. pysnap/templates/fastapi/template.json +53 -0
  74. pysnap/templates/fastapi/tests/__init__.py.j2 +0 -0
  75. pysnap/templates/fastapi/tests/conftest.py.j2 +9 -0
  76. pysnap/templates/fastapi/tests/test_health.py.j2 +9 -0
  77. pysnap/templates/flask/.dockerignore.j2 +14 -0
  78. pysnap/templates/flask/.github/workflows/ci.yml.j2 +34 -0
  79. pysnap/templates/flask/.gitignore.j2 +13 -0
  80. pysnap/templates/flask/Dockerfile.j2 +14 -0
  81. pysnap/templates/flask/README.md.j2 +34 -0
  82. pysnap/templates/flask/app/__init__.py.j2 +30 -0
  83. pysnap/templates/flask/app/config.py.j2 +23 -0
  84. pysnap/templates/flask/app/extensions.py.j2 +9 -0
  85. pysnap/templates/flask/app/models/__init__.py.j2 +1 -0
  86. pysnap/templates/flask/app/models/user.py.j2 +16 -0
  87. pysnap/templates/flask/app/routes/__init__.py.j2 +1 -0
  88. pysnap/templates/flask/app/routes/auth.py.j2 +31 -0
  89. pysnap/templates/flask/app/routes/health.py.j2 +11 -0
  90. pysnap/templates/flask/docker-compose.yml.j2 +29 -0
  91. pysnap/templates/flask/pyproject.toml.j2 +39 -0
  92. pysnap/templates/flask/template.json +44 -0
  93. pysnap/templates/flask/tests/__init__.py.j2 +1 -0
  94. pysnap/templates/flask/tests/conftest.py.j2 +16 -0
  95. pysnap/templates/flask/tests/test_health.py.j2 +8 -0
  96. pysnap/templates/flask/wsgi.py.j2 +8 -0
  97. pysnap/validator.py +89 -0
  98. snapstack-1.0.0.dist-info/METADATA +267 -0
  99. snapstack-1.0.0.dist-info/RECORD +102 -0
  100. snapstack-1.0.0.dist-info/WHEEL +4 -0
  101. snapstack-1.0.0.dist-info/entry_points.txt +2 -0
  102. snapstack-1.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,15 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ venv/
5
+ .env
6
+ dist/
7
+ *.egg-info/
8
+ .pytest_cache/
9
+ .coverage
10
+ htmlcov/
11
+ *.log
12
+ .DS_Store
13
+ Thumbs.db
14
+ .git/
15
+ staticfiles/
@@ -0,0 +1,34 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ lint-and-test:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - name: Set up Python
15
+ uses: actions/setup-python@v5
16
+ with:
17
+ python-version: "3.11"
18
+ {% if package_manager == "uv" %}
19
+ - name: Install uv
20
+ run: pip install uv
21
+ - name: Install dependencies
22
+ run: uv sync
23
+ - name: Lint
24
+ run: uv run ruff check .
25
+ - name: Test
26
+ run: uv run pytest
27
+ {% else %}
28
+ - name: Install dependencies
29
+ run: pip install -e ".[dev]"
30
+ - name: Lint
31
+ run: ruff check .
32
+ - name: Test
33
+ run: pytest
34
+ {% endif %}
@@ -0,0 +1,14 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .env
5
+ .venv/
6
+ venv/
7
+ dist/
8
+ build/
9
+ *.db
10
+ .pytest_cache/
11
+ .ruff_cache/
12
+ htmlcov/
13
+ .coverage
14
+ staticfiles/
@@ -0,0 +1,14 @@
1
+ FROM python:3.11-slim
2
+ WORKDIR /app
3
+ {% if package_manager == "uv" -%}
4
+ COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
5
+ COPY pyproject.toml uv.lock* ./
6
+ RUN uv sync --frozen --no-dev
7
+ COPY . .
8
+ CMD ["uv", "run", "python", "manage.py", "runserver", "0.0.0.0:8000"]
9
+ {%- else -%}
10
+ COPY pyproject.toml ./
11
+ RUN pip install --no-cache-dir -e .
12
+ COPY . .
13
+ CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
14
+ {%- endif %}
@@ -0,0 +1,36 @@
1
+ # {{ project_name }}
2
+
3
+ A Django project generated by [pysnap](https://github.com/pysnap-dev/pysnap).
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ {% if package_manager == "uv" %}
9
+ uv sync
10
+ uv run python manage.py migrate
11
+ uv run python manage.py runserver
12
+ {% else %}
13
+ pip install -e ".[dev]"
14
+ python manage.py migrate
15
+ python manage.py runserver
16
+ {% endif %}
17
+ ```
18
+
19
+ {% if include_docker %}
20
+ ## Docker
21
+
22
+ ```bash
23
+ docker compose up
24
+ ```
25
+ {% endif %}
26
+ {% if include_tests %}
27
+ ## Tests
28
+
29
+ ```bash
30
+ {% if package_manager == "uv" %}
31
+ uv run pytest
32
+ {% else %}
33
+ pytest
34
+ {% endif %}
35
+ ```
36
+ {% endif %}
File without changes
File without changes
@@ -0,0 +1,6 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class CoreConfig(AppConfig):
5
+ default_auto_field = "django.db.models.BigAutoField"
6
+ name = "apps.core"
@@ -0,0 +1,7 @@
1
+ from django.urls import path
2
+
3
+ from apps.core import views
4
+
5
+ urlpatterns = [
6
+ path("health/", views.health_check, name="health-check"),
7
+ ]
@@ -0,0 +1,6 @@
1
+ from django.http import JsonResponse
2
+
3
+
4
+ def health_check(request):
5
+ """Health check endpoint."""
6
+ return JsonResponse({"status": "ok", "service": "{{ project_name }}"})
File without changes
@@ -0,0 +1,6 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class UsersConfig(AppConfig):
5
+ default_auto_field = "django.db.models.BigAutoField"
6
+ name = "apps.users"
@@ -0,0 +1,14 @@
1
+ from django.contrib.auth.models import AbstractUser
2
+ from django.db import models
3
+
4
+
5
+ class User(AbstractUser):
6
+ """Custom user model."""
7
+
8
+ email = models.EmailField(unique=True)
9
+
10
+ USERNAME_FIELD = "email"
11
+ REQUIRED_FIELDS = ["username"]
12
+
13
+ class Meta:
14
+ ordering = ["-date_joined"]
@@ -0,0 +1,13 @@
1
+ from rest_framework import serializers
2
+ from apps.users.models import User
3
+
4
+
5
+ class RegisterSerializer(serializers.ModelSerializer):
6
+ password = serializers.CharField(write_only=True, min_length=8)
7
+
8
+ class Meta:
9
+ model = User
10
+ fields = ["email", "username", "password"]
11
+
12
+ def create(self, validated_data):
13
+ return User.objects.create_user(**validated_data)
@@ -0,0 +1,10 @@
1
+ from django.urls import path
2
+ from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
3
+
4
+ from apps.users.views import RegisterView
5
+
6
+ urlpatterns = [
7
+ path("register/", RegisterView.as_view(), name="register"),
8
+ path("token/", TokenObtainPairView.as_view(), name="token-obtain"),
9
+ path("token/refresh/", TokenRefreshView.as_view(), name="token-refresh"),
10
+ ]
@@ -0,0 +1,22 @@
1
+ from rest_framework import status
2
+ from rest_framework.response import Response
3
+ from rest_framework.views import APIView
4
+ from rest_framework.permissions import AllowAny
5
+ from rest_framework_simplejwt.tokens import RefreshToken
6
+
7
+ from apps.users.serializers import RegisterSerializer
8
+
9
+
10
+ class RegisterView(APIView):
11
+ permission_classes = [AllowAny]
12
+
13
+ def post(self, request):
14
+ serializer = RegisterSerializer(data=request.data)
15
+ if serializer.is_valid():
16
+ user = serializer.save()
17
+ refresh = RefreshToken.for_user(user)
18
+ return Response(
19
+ {"access": str(refresh.access_token), "refresh": str(refresh)},
20
+ status=status.HTTP_201_CREATED,
21
+ )
22
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
File without changes
@@ -0,0 +1,9 @@
1
+ """ASGI config for {{ project_name }} project."""
2
+
3
+ import os
4
+
5
+ from django.core.asgi import get_asgi_application
6
+
7
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
8
+
9
+ application = get_asgi_application()
@@ -0,0 +1,110 @@
1
+ """
2
+ Django settings for {{ project_name }}.
3
+ """
4
+
5
+ from pathlib import Path
6
+
7
+ from decouple import Csv, config
8
+
9
+ BASE_DIR = Path(__file__).resolve().parent.parent
10
+
11
+ SECRET_KEY = config("SECRET_KEY", default="insecure-change-me-in-production")
12
+
13
+ DEBUG = config("DEBUG", default=False, cast=bool)
14
+
15
+ ALLOWED_HOSTS: list[str] = config("ALLOWED_HOSTS", default="localhost,127.0.0.1", cast=Csv())
16
+
17
+ INSTALLED_APPS = [
18
+ "django.contrib.admin",
19
+ "django.contrib.auth",
20
+ "django.contrib.contenttypes",
21
+ "django.contrib.sessions",
22
+ "django.contrib.messages",
23
+ "django.contrib.staticfiles",
24
+ "apps.core",
25
+ {% if include_auth %}
26
+ "rest_framework",
27
+ "rest_framework_simplejwt",
28
+ "apps.users",
29
+ {% endif %}
30
+ ]
31
+
32
+ MIDDLEWARE = [
33
+ "django.middleware.security.SecurityMiddleware",
34
+ "django.contrib.sessions.middleware.SessionMiddleware",
35
+ "django.middleware.common.CommonMiddleware",
36
+ "django.middleware.csrf.CsrfViewMiddleware",
37
+ "django.contrib.auth.middleware.AuthenticationMiddleware",
38
+ "django.contrib.messages.middleware.MessageMiddleware",
39
+ "django.middleware.clickjacking.XFrameOptionsMiddleware",
40
+ ]
41
+
42
+ ROOT_URLCONF = "config.urls"
43
+
44
+ TEMPLATES = [
45
+ {
46
+ "BACKEND": "django.template.backends.django.DjangoTemplates",
47
+ "DIRS": [],
48
+ "APP_DIRS": True,
49
+ "OPTIONS": {
50
+ "context_processors": [
51
+ "django.template.context_processors.debug",
52
+ "django.template.context_processors.request",
53
+ "django.contrib.auth.context_processors.auth",
54
+ "django.contrib.messages.context_processors.messages",
55
+ ],
56
+ },
57
+ },
58
+ ]
59
+
60
+ WSGI_APPLICATION = "config.wsgi.application"
61
+
62
+ {% if database == "postgresql" %}
63
+ DATABASES = {
64
+ "default": {
65
+ "ENGINE": "django.db.backends.postgresql",
66
+ "NAME": config("POSTGRES_DB", default="{{ project_name_slug }}"),
67
+ "USER": config("POSTGRES_USER", default="user"),
68
+ "PASSWORD": config("POSTGRES_PASSWORD", default="password"),
69
+ "HOST": config("POSTGRES_HOST", default="localhost"),
70
+ "PORT": config("POSTGRES_PORT", default="5432"),
71
+ }
72
+ }
73
+ {% else %}
74
+ DATABASES = {
75
+ "default": {
76
+ "ENGINE": "django.db.backends.sqlite3",
77
+ "NAME": BASE_DIR / "db.sqlite3",
78
+ }
79
+ }
80
+ {% endif %}
81
+
82
+ AUTH_PASSWORD_VALIDATORS = [
83
+ {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
84
+ {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
85
+ {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
86
+ {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
87
+ ]
88
+
89
+ LANGUAGE_CODE = "en-us"
90
+ TIME_ZONE = "UTC"
91
+ USE_I18N = True
92
+ USE_TZ = True
93
+
94
+ STATIC_URL = "static/"
95
+ STATIC_ROOT = BASE_DIR / "staticfiles"
96
+
97
+ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
98
+
99
+ {% if include_auth %}
100
+ AUTH_USER_MODEL = "users.User"
101
+
102
+ REST_FRAMEWORK = {
103
+ "DEFAULT_AUTHENTICATION_CLASSES": [
104
+ "rest_framework_simplejwt.authentication.JWTAuthentication",
105
+ ],
106
+ "DEFAULT_PERMISSION_CLASSES": [
107
+ "rest_framework.permissions.IsAuthenticated",
108
+ ],
109
+ }
110
+ {% endif %}
@@ -0,0 +1,12 @@
1
+ """config URL configuration."""
2
+
3
+ from django.contrib import admin
4
+ from django.urls import include, path
5
+
6
+ urlpatterns = [
7
+ path("admin/", admin.site.urls),
8
+ path("api/", include("apps.core.urls")),
9
+ {% if include_auth %}
10
+ path("api/auth/", include("apps.users.urls")),
11
+ {% endif %}
12
+ ]
@@ -0,0 +1,9 @@
1
+ """WSGI config for {{ project_name }} project."""
2
+
3
+ import os
4
+
5
+ from django.core.wsgi import get_wsgi_application
6
+
7
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
8
+
9
+ application = get_wsgi_application()
@@ -0,0 +1,29 @@
1
+ services:
2
+ app:
3
+ build: .
4
+ ports:
5
+ - "8000:8000"
6
+ env_file:
7
+ - .env
8
+ {% if database == "postgresql" %}
9
+ depends_on:
10
+ db:
11
+ condition: service_healthy
12
+ db:
13
+ image: postgres:16-alpine
14
+ environment:
15
+ POSTGRES_USER: ${POSTGRES_USER:-user}
16
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password}
17
+ POSTGRES_DB: ${POSTGRES_DB:-{{ project_name_slug }}}
18
+ ports:
19
+ - "5432:5432"
20
+ volumes:
21
+ - pgdata:/var/lib/postgresql/data
22
+ healthcheck:
23
+ test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER:-user}"]
24
+ interval: 5s
25
+ timeout: 5s
26
+ retries: 5
27
+ volumes:
28
+ pgdata:
29
+ {% endif %}
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env python
2
+ """Django's command-line utility for administrative tasks."""
3
+ import os
4
+ import sys
5
+
6
+
7
+ def main():
8
+ """Run administrative tasks."""
9
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
10
+ try:
11
+ from django.core.management import execute_from_command_line
12
+ except ImportError as exc:
13
+ raise ImportError(
14
+ "Couldn't import Django. Are you sure it's installed and "
15
+ "available on your PYTHONPATH environment variable? Did you "
16
+ "forget to activate a virtual environment?"
17
+ ) from exc
18
+ execute_from_command_line(sys.argv)
19
+
20
+
21
+ if __name__ == "__main__":
22
+ main()
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "{{ project_name }}"
7
+ version = "0.1.0"
8
+ description = "Generated by pysnap"
9
+ requires-python = ">=3.11"
10
+ dependencies = [
11
+ "django>=4.2.0",
12
+ "python-decouple>=3.8",
13
+ {%- if database == "postgresql" %}
14
+ "psycopg[binary]>=3.1.0",
15
+ {%- endif %}
16
+ {%- if include_auth %}
17
+ "djangorestframework>=3.15.0",
18
+ "djangorestframework-simplejwt>=5.3.0",
19
+ {%- endif %}
20
+ ]
21
+
22
+ [project.optional-dependencies]
23
+ dev = [
24
+ {%- if include_tests %}
25
+ "pytest>=8.0.0",
26
+ "pytest-django>=4.8.0",
27
+ {%- endif %}
28
+ "ruff>=0.8.0",
29
+ ]
30
+
31
+ [tool.hatch.build.targets.wheel]
32
+ packages = ["apps", "config"]
33
+
34
+ [tool.pytest.ini_options]
35
+ DJANGO_SETTINGS_MODULE = "config.settings"
36
+ pythonpath = ["."]
37
+
38
+ [tool.ruff]
39
+ line-length = 100
40
+ target-version = "py311"
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "django",
3
+ "display_name": "Django",
4
+ "description": "Classic Django project with best-practice structure, health check, and optional PostgreSQL",
5
+ "version": "1.0.0",
6
+ "files": {
7
+ "always": {
8
+ "manage.py.j2": "manage.py",
9
+ "pyproject.toml.j2": "pyproject.toml",
10
+ ".gitignore.j2": ".gitignore",
11
+ ".env.example.j2": ".env.example",
12
+ "README.md.j2": "README.md",
13
+ "config/__init__.py.j2": "config/__init__.py",
14
+ "config/settings.py.j2": "config/settings.py",
15
+ "config/urls.py.j2": "config/urls.py",
16
+ "config/wsgi.py.j2": "config/wsgi.py",
17
+ "config/asgi.py.j2": "config/asgi.py",
18
+ "apps/__init__.py.j2": "apps/__init__.py",
19
+ "apps/core/__init__.py.j2": "apps/core/__init__.py",
20
+ "apps/core/apps.py.j2": "apps/core/apps.py",
21
+ "apps/core/views.py.j2": "apps/core/views.py",
22
+ "apps/core/urls.py.j2": "apps/core/urls.py"
23
+ },
24
+ "when_auth": {
25
+ "apps/users/__init__.py.j2": "apps/users/__init__.py",
26
+ "apps/users/apps.py.j2": "apps/users/apps.py",
27
+ "apps/users/models.py.j2": "apps/users/models.py",
28
+ "apps/users/views.py.j2": "apps/users/views.py",
29
+ "apps/users/urls.py.j2": "apps/users/urls.py",
30
+ "apps/users/serializers.py.j2": "apps/users/serializers.py"
31
+ },
32
+ "when_docker": {
33
+ "Dockerfile.j2": "Dockerfile",
34
+ "docker-compose.yml.j2": "docker-compose.yml",
35
+ ".dockerignore.j2": ".dockerignore"
36
+ },
37
+ "when_tests": {
38
+ "tests/__init__.py.j2": "tests/__init__.py",
39
+ "tests/conftest.py.j2": "tests/conftest.py",
40
+ "tests/test_health.py.j2": "tests/test_health.py"
41
+ },
42
+ "when_ci": {
43
+ ".github/workflows/ci.yml.j2": ".github/workflows/ci.yml"
44
+ }
45
+ },
46
+ "prompts": [],
47
+ "hooks": {
48
+ "post_generate": []
49
+ }
50
+ }
@@ -0,0 +1 @@
1
+ """Django tests package."""
@@ -0,0 +1,6 @@
1
+ import pytest
2
+
3
+
4
+ @pytest.fixture(scope="session")
5
+ def django_db_setup():
6
+ pass
@@ -0,0 +1,9 @@
1
+ import pytest
2
+
3
+
4
+ @pytest.mark.django_db
5
+ def test_health_check(client):
6
+ response = client.get("/api/health/")
7
+ assert response.status_code == 200
8
+ data = response.json()
9
+ assert data["status"] == "ok"
@@ -0,0 +1,8 @@
1
+ .env
2
+ .venv/
3
+ venv/
4
+ __pycache__/
5
+ *.pyc
6
+ .git/
7
+ .pytest_cache/
8
+ *.db
@@ -0,0 +1,46 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, develop]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - name: Set up Python
16
+ uses: actions/setup-python@v5
17
+ with:
18
+ python-version: "3.11"
19
+ {%- if package_manager == "uv" %}
20
+
21
+ - name: Install uv
22
+ uses: astral-sh/setup-uv@v4
23
+
24
+ - name: Install dependencies
25
+ run: uv sync --frozen
26
+
27
+ - name: Lint
28
+ run: uv run ruff check .
29
+ {%- if include_tests %}
30
+
31
+ - name: Test
32
+ run: uv run pytest tests/ -v
33
+ {%- endif %}
34
+ {%- else %}
35
+
36
+ - name: Install dependencies
37
+ run: pip install -e ".[dev]"
38
+
39
+ - name: Lint
40
+ run: ruff check .
41
+ {%- if include_tests %}
42
+
43
+ - name: Test
44
+ run: pytest tests/ -v
45
+ {%- endif %}
46
+ {%- endif %}
@@ -0,0 +1,13 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .env
5
+ .venv/
6
+ venv/
7
+ dist/
8
+ build/
9
+ *.db
10
+ .pytest_cache/
11
+ .ruff_cache/
12
+ htmlcov/
13
+ .coverage
@@ -0,0 +1,14 @@
1
+ FROM python:3.11-slim
2
+ WORKDIR /app
3
+ {% if package_manager == "uv" -%}
4
+ COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
5
+ COPY pyproject.toml uv.lock* ./
6
+ RUN uv sync --frozen --no-dev
7
+ COPY . .
8
+ CMD ["uv", "run", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
9
+ {%- else -%}
10
+ COPY pyproject.toml app/__init__.py ./app/
11
+ RUN pip install --no-cache-dir .
12
+ COPY . .
13
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
14
+ {%- endif %}