fastapi-spawn 0.1.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.
- fastapi_spawn/__init__.py +6 -0
- fastapi_spawn/cli.py +387 -0
- fastapi_spawn/config.py +162 -0
- fastapi_spawn/constants.py +133 -0
- fastapi_spawn/generator.py +294 -0
- fastapi_spawn/interactive.py +192 -0
- fastapi_spawn/templates/alembic/alembic.ini.j2 +39 -0
- fastapi_spawn/templates/alembic/env.py.j2 +64 -0
- fastapi_spawn/templates/app/__init__.py.j2 +1 -0
- fastapi_spawn/templates/app/api/deps.py.j2 +39 -0
- fastapi_spawn/templates/app/api/v1/auth.py.j2 +59 -0
- fastapi_spawn/templates/app/api/v1/health.py.j2 +40 -0
- fastapi_spawn/templates/app/core/ai.py.j2 +76 -0
- fastapi_spawn/templates/app/core/config.py.j2 +177 -0
- fastapi_spawn/templates/app/core/exceptions.py.j2 +43 -0
- fastapi_spawn/templates/app/core/logging.py.j2 +70 -0
- fastapi_spawn/templates/app/core/security.py.j2 +42 -0
- fastapi_spawn/templates/app/core/storage.py.j2 +73 -0
- fastapi_spawn/templates/app/db/session.py.j2 +84 -0
- fastapi_spawn/templates/app/main.py.j2 +71 -0
- fastapi_spawn/templates/base/Makefile.j2 +45 -0
- fastapi_spawn/templates/base/README.md.j2 +74 -0
- fastapi_spawn/templates/base/env.j2 +82 -0
- fastapi_spawn/templates/base/env_example.j2 +85 -0
- fastapi_spawn/templates/base/gitignore.j2 +38 -0
- fastapi_spawn/templates/base/pre_commit.j2 +17 -0
- fastapi_spawn/templates/base/pyproject.toml.j2 +129 -0
- fastapi_spawn/templates/ci/github/publish.yml.j2 +32 -0
- fastapi_spawn/templates/ci/github/tests.yml.j2 +39 -0
- fastapi_spawn/templates/ci/gitlab/gitlab-ci.yml.j2 +29 -0
- fastapi_spawn/templates/docker/Dockerfile.j2 +17 -0
- fastapi_spawn/templates/docker/docker-compose.yml.j2 +97 -0
- fastapi_spawn/templates/docker/dockerignore.j2 +13 -0
- fastapi_spawn/templates/infra/docker/docker-compose.prod.yml.j2 +43 -0
- fastapi_spawn/templates/infra/helm/Chart.yaml.j2 +6 -0
- fastapi_spawn/templates/infra/helm/values.yaml.j2 +26 -0
- fastapi_spawn/templates/infra/terraform/main.tf.j2 +26 -0
- fastapi_spawn/templates/infra/terraform/variables.tf.j2 +17 -0
- fastapi_spawn/templates/root/main.py.j2 +16 -0
- fastapi_spawn/templates/tasks/celery_app.py.j2 +37 -0
- fastapi_spawn/templates/tasks/sample_tasks.py.j2 +27 -0
- fastapi_spawn/templates/tests/conftest.py.j2 +22 -0
- fastapi_spawn/templates/tests/test_health.py.j2 +30 -0
- fastapi_spawn/utils.py +58 -0
- fastapi_spawn/validators.py +67 -0
- fastapi_spawn-0.1.0.dist-info/METADATA +262 -0
- fastapi_spawn-0.1.0.dist-info/RECORD +50 -0
- fastapi_spawn-0.1.0.dist-info/WHEEL +4 -0
- fastapi_spawn-0.1.0.dist-info/entry_points.txt +2 -0
- fastapi_spawn-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
.PHONY: help install dev run test lint format clean
|
|
2
|
+
|
|
3
|
+
help: ## Show this help
|
|
4
|
+
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
|
|
5
|
+
|
|
6
|
+
install: ## Install dependencies
|
|
7
|
+
pip install -e .
|
|
8
|
+
|
|
9
|
+
dev: ## Install dev dependencies
|
|
10
|
+
pip install -e ".[dev]"
|
|
11
|
+
|
|
12
|
+
run: ## Run development server
|
|
13
|
+
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
|
|
14
|
+
|
|
15
|
+
{% if include_tests %}
|
|
16
|
+
test: ## Run tests
|
|
17
|
+
pytest --cov=app --cov-report=term-missing
|
|
18
|
+
|
|
19
|
+
test-ci: ## Run tests with XML report
|
|
20
|
+
pytest --cov=app --cov-report=xml
|
|
21
|
+
{% endif %}
|
|
22
|
+
|
|
23
|
+
lint: ## Run linters
|
|
24
|
+
ruff check .
|
|
25
|
+
mypy app/
|
|
26
|
+
|
|
27
|
+
format: ## Auto-format code
|
|
28
|
+
ruff format .
|
|
29
|
+
ruff check --fix .
|
|
30
|
+
|
|
31
|
+
clean: ## Remove build artifacts
|
|
32
|
+
find . -type d -name __pycache__ -exec rm -rf {} +
|
|
33
|
+
find . -type f -name "*.pyc" -delete
|
|
34
|
+
rm -rf dist/ build/ *.egg-info .pytest_cache .coverage htmlcov/
|
|
35
|
+
|
|
36
|
+
{% if has_docker %}
|
|
37
|
+
docker-build: ## Build Docker image
|
|
38
|
+
docker build -t {{ slug }} .
|
|
39
|
+
|
|
40
|
+
docker-up: ## Start all services
|
|
41
|
+
docker compose up --build
|
|
42
|
+
|
|
43
|
+
docker-down: ## Stop all services
|
|
44
|
+
docker compose down
|
|
45
|
+
{% endif %}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# {{ project_name }}
|
|
2
|
+
|
|
3
|
+
> Auto-generated by [fastapi-spawn](https://github.com/Bishwajitgarai/fastapi-spawn) 🚀
|
|
4
|
+
|
|
5
|
+
## Stack
|
|
6
|
+
|
|
7
|
+
| Component | Choice |
|
|
8
|
+
|-----------|--------|
|
|
9
|
+
| Database | `{{ db }}` |
|
|
10
|
+
| ORM / ODM | `{{ orm }}` |
|
|
11
|
+
| Auth | `{{ auth }}` |
|
|
12
|
+
| Broker | `{{ broker }}` |
|
|
13
|
+
| Cache | `{{ cache }}` |
|
|
14
|
+
| Stack | `{{ stack }}` |
|
|
15
|
+
| Logging | `{{ log_lib }}` |
|
|
16
|
+
|
|
17
|
+
## Getting Started
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# 1. Create virtual environment
|
|
21
|
+
python -m venv .venv
|
|
22
|
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
23
|
+
|
|
24
|
+
# 2. Install dependencies
|
|
25
|
+
pip install -e ".[dev]"
|
|
26
|
+
|
|
27
|
+
# 3. Copy env file and fill in values
|
|
28
|
+
cp .env.example .env
|
|
29
|
+
|
|
30
|
+
{% if has_docker %}
|
|
31
|
+
# 4. Start with Docker
|
|
32
|
+
docker compose up --build
|
|
33
|
+
{% else %}
|
|
34
|
+
# 4. Run locally
|
|
35
|
+
uvicorn app.main:app --reload
|
|
36
|
+
{% endif %}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## API Endpoints
|
|
40
|
+
|
|
41
|
+
| Method | Path | Description |
|
|
42
|
+
|--------|------|-------------|
|
|
43
|
+
| GET | `/health` | Health check |
|
|
44
|
+
| GET | `/api/v1/health/readiness` | Readiness probe |
|
|
45
|
+
| GET | `/api/v1/health/liveness` | Liveness probe |
|
|
46
|
+
{% if has_auth %}
|
|
47
|
+
| POST | `/api/v1/auth/login` | Obtain token |
|
|
48
|
+
| POST | `/api/v1/auth/refresh` | Refresh token |
|
|
49
|
+
{% endif %}
|
|
50
|
+
|
|
51
|
+
## Project Structure
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
{{ project_name }}/
|
|
55
|
+
├── app/
|
|
56
|
+
│ ├── main.py # FastAPI application entrypoint
|
|
57
|
+
│ ├── core/ # Config, logging, security
|
|
58
|
+
│ ├── api/v1/ # Versioned route handlers
|
|
59
|
+
│ ├── models/ # ORM models
|
|
60
|
+
│ ├── schemas/ # Pydantic schemas
|
|
61
|
+
│ ├── services/ # Business logic
|
|
62
|
+
│ └── repositories/ # Data access layer
|
|
63
|
+
{% if include_tests %}
|
|
64
|
+
├── tests/ # pytest test suite
|
|
65
|
+
{% endif %}
|
|
66
|
+
{% if has_docker %}
|
|
67
|
+
├── Dockerfile
|
|
68
|
+
└── docker-compose.yml
|
|
69
|
+
{% endif %}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## License
|
|
73
|
+
|
|
74
|
+
MIT
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Application
|
|
2
|
+
APP_NAME="{{ project_name }}"
|
|
3
|
+
ENVIRONMENT=dev
|
|
4
|
+
DEBUG=true
|
|
5
|
+
SECRET_KEY=super-secret-change-in-production
|
|
6
|
+
API_V1_PREFIX=/api/v1
|
|
7
|
+
CORS_ORIGINS=http://localhost:3000,http://localhost:8000
|
|
8
|
+
|
|
9
|
+
{% if db == "postgresql" %}
|
|
10
|
+
# PostgreSQL
|
|
11
|
+
POSTGRES_USER=postgres
|
|
12
|
+
POSTGRES_PASSWORD=postgres
|
|
13
|
+
POSTGRES_HOST=localhost
|
|
14
|
+
POSTGRES_PORT=5432
|
|
15
|
+
POSTGRES_DB={{ slug }}_db
|
|
16
|
+
{% endif %}
|
|
17
|
+
{% if db == "mysql" %}
|
|
18
|
+
# MySQL
|
|
19
|
+
MYSQL_USER=root
|
|
20
|
+
MYSQL_PASSWORD=mysql
|
|
21
|
+
MYSQL_HOST=localhost
|
|
22
|
+
MYSQL_PORT=3306
|
|
23
|
+
MYSQL_DB={{ slug }}_db
|
|
24
|
+
{% endif %}
|
|
25
|
+
{% if db == "sqlite" %}
|
|
26
|
+
# SQLite
|
|
27
|
+
SQLITE_FILE={{ slug }}.db
|
|
28
|
+
{% endif %}
|
|
29
|
+
{% if has_mongo %}
|
|
30
|
+
# MongoDB
|
|
31
|
+
MONGODB_USER=mongo
|
|
32
|
+
MONGODB_PASSWORD=mongo
|
|
33
|
+
MONGODB_HOST=localhost
|
|
34
|
+
MONGODB_PORT=27017
|
|
35
|
+
MONGODB_DB={{ slug }}_db
|
|
36
|
+
{% endif %}
|
|
37
|
+
{% if broker == "redis" or cache == "redis" %}
|
|
38
|
+
# Redis
|
|
39
|
+
REDIS_HOST=localhost
|
|
40
|
+
REDIS_PORT=6379
|
|
41
|
+
REDIS_PASSWORD=
|
|
42
|
+
REDIS_DB=0
|
|
43
|
+
{% endif %}
|
|
44
|
+
{% if broker == "rabbitmq" %}
|
|
45
|
+
# RabbitMQ
|
|
46
|
+
RABBITMQ_USER=guest
|
|
47
|
+
RABBITMQ_PASSWORD=guest
|
|
48
|
+
RABBITMQ_HOST=localhost
|
|
49
|
+
RABBITMQ_PORT=5672
|
|
50
|
+
RABBITMQ_VHOST=/
|
|
51
|
+
{% endif %}
|
|
52
|
+
{% if broker == "kafka" %}
|
|
53
|
+
# Kafka
|
|
54
|
+
KAFKA_HOST=localhost
|
|
55
|
+
KAFKA_PORT=9092
|
|
56
|
+
{% endif %}
|
|
57
|
+
{% if has_s3 %}
|
|
58
|
+
# AWS S3
|
|
59
|
+
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
|
|
60
|
+
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
|
|
61
|
+
AWS_REGION=us-east-1
|
|
62
|
+
AWS_S3_BUCKET={{ slug }}-bucket
|
|
63
|
+
AWS_S3_ENDPOINT_URL=
|
|
64
|
+
{% endif %}
|
|
65
|
+
{% if has_auth %}
|
|
66
|
+
# Auth / JWT
|
|
67
|
+
ACCESS_TOKEN_EXPIRE_MINUTES=30
|
|
68
|
+
REFRESH_TOKEN_EXPIRE_DAYS=7
|
|
69
|
+
ALGORITHM=HS256
|
|
70
|
+
{% endif %}
|
|
71
|
+
{% if ai == "openai" %}
|
|
72
|
+
# OpenAI
|
|
73
|
+
OPENAI_API_KEY=sk-placeholder
|
|
74
|
+
OPENAI_MODEL=gpt-4o
|
|
75
|
+
OPENAI_EMBEDDING_MODEL=text-embedding-3-small
|
|
76
|
+
OPENAI_BASE_URL= # leave blank for api.openai.com; set for Azure / LM Studio / local
|
|
77
|
+
{% endif %}
|
|
78
|
+
{% if ai == "anthropic" %}
|
|
79
|
+
# Anthropic
|
|
80
|
+
ANTHROPIC_API_KEY=sk-ant-placeholder
|
|
81
|
+
ANTHROPIC_MODEL=claude-3-5-sonnet-20241022
|
|
82
|
+
{% endif %}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Copy this file to .env and fill in real values.
|
|
2
|
+
# NEVER commit .env to version control.
|
|
3
|
+
|
|
4
|
+
# Application
|
|
5
|
+
APP_NAME="{{ project_name }}"
|
|
6
|
+
ENVIRONMENT=dev
|
|
7
|
+
DEBUG=true
|
|
8
|
+
SECRET_KEY=CHANGE_ME
|
|
9
|
+
API_V1_PREFIX=/api/v1
|
|
10
|
+
CORS_ORIGINS=http://localhost:3000,http://localhost:8000
|
|
11
|
+
|
|
12
|
+
{% if db == "postgresql" %}
|
|
13
|
+
# PostgreSQL
|
|
14
|
+
POSTGRES_USER=postgres
|
|
15
|
+
POSTGRES_PASSWORD=CHANGE_ME
|
|
16
|
+
POSTGRES_HOST=localhost
|
|
17
|
+
POSTGRES_PORT=5432
|
|
18
|
+
POSTGRES_DB={{ slug }}_db
|
|
19
|
+
{% endif %}
|
|
20
|
+
{% if db == "mysql" %}
|
|
21
|
+
# MySQL
|
|
22
|
+
MYSQL_USER=root
|
|
23
|
+
MYSQL_PASSWORD=CHANGE_ME
|
|
24
|
+
MYSQL_HOST=localhost
|
|
25
|
+
MYSQL_PORT=3306
|
|
26
|
+
MYSQL_DB={{ slug }}_db
|
|
27
|
+
{% endif %}
|
|
28
|
+
{% if db == "sqlite" %}
|
|
29
|
+
# SQLite
|
|
30
|
+
SQLITE_FILE={{ slug }}.db
|
|
31
|
+
{% endif %}
|
|
32
|
+
{% if has_mongo %}
|
|
33
|
+
# MongoDB
|
|
34
|
+
MONGODB_USER=mongo
|
|
35
|
+
MONGODB_PASSWORD=CHANGE_ME
|
|
36
|
+
MONGODB_HOST=localhost
|
|
37
|
+
MONGODB_PORT=27017
|
|
38
|
+
MONGODB_DB={{ slug }}_db
|
|
39
|
+
{% endif %}
|
|
40
|
+
{% if broker == "redis" or cache == "redis" %}
|
|
41
|
+
# Redis
|
|
42
|
+
REDIS_HOST=localhost
|
|
43
|
+
REDIS_PORT=6379
|
|
44
|
+
REDIS_PASSWORD=
|
|
45
|
+
REDIS_DB=0
|
|
46
|
+
{% endif %}
|
|
47
|
+
{% if broker == "rabbitmq" %}
|
|
48
|
+
# RabbitMQ
|
|
49
|
+
RABBITMQ_USER=guest
|
|
50
|
+
RABBITMQ_PASSWORD=CHANGE_ME
|
|
51
|
+
RABBITMQ_HOST=localhost
|
|
52
|
+
RABBITMQ_PORT=5672
|
|
53
|
+
RABBITMQ_VHOST=/
|
|
54
|
+
{% endif %}
|
|
55
|
+
{% if broker == "kafka" %}
|
|
56
|
+
# Kafka
|
|
57
|
+
KAFKA_HOST=localhost
|
|
58
|
+
KAFKA_PORT=9092
|
|
59
|
+
{% endif %}
|
|
60
|
+
{% if has_s3 %}
|
|
61
|
+
# AWS S3
|
|
62
|
+
AWS_ACCESS_KEY_ID=CHANGE_ME
|
|
63
|
+
AWS_SECRET_ACCESS_KEY=CHANGE_ME
|
|
64
|
+
AWS_REGION=us-east-1
|
|
65
|
+
AWS_S3_BUCKET={{ slug }}-bucket
|
|
66
|
+
AWS_S3_ENDPOINT_URL=
|
|
67
|
+
{% endif %}
|
|
68
|
+
{% if has_auth %}
|
|
69
|
+
# Auth / JWT
|
|
70
|
+
ACCESS_TOKEN_EXPIRE_MINUTES=30
|
|
71
|
+
REFRESH_TOKEN_EXPIRE_DAYS=7
|
|
72
|
+
ALGORITHM=HS256
|
|
73
|
+
{% endif %}
|
|
74
|
+
{% if ai == "openai" %}
|
|
75
|
+
# OpenAI
|
|
76
|
+
OPENAI_API_KEY=sk-placeholder
|
|
77
|
+
OPENAI_MODEL=gpt-4o
|
|
78
|
+
OPENAI_EMBEDDING_MODEL=text-embedding-3-small
|
|
79
|
+
OPENAI_BASE_URL= # leave blank for api.openai.com; set for Azure / LM Studio / local
|
|
80
|
+
{% endif %}
|
|
81
|
+
{% if ai == "anthropic" %}
|
|
82
|
+
# Anthropic
|
|
83
|
+
ANTHROPIC_API_KEY=sk-ant-placeholder
|
|
84
|
+
ANTHROPIC_MODEL=claude-3-5-sonnet-20241022
|
|
85
|
+
{% endif %}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
|
|
8
|
+
# Envs
|
|
9
|
+
.venv/
|
|
10
|
+
venv/
|
|
11
|
+
env/
|
|
12
|
+
|
|
13
|
+
# Distribution
|
|
14
|
+
dist/
|
|
15
|
+
build/
|
|
16
|
+
*.egg-info/
|
|
17
|
+
|
|
18
|
+
# Secrets
|
|
19
|
+
.env
|
|
20
|
+
.env.*
|
|
21
|
+
!.env.example
|
|
22
|
+
|
|
23
|
+
# Testing
|
|
24
|
+
.pytest_cache/
|
|
25
|
+
.coverage
|
|
26
|
+
htmlcov/
|
|
27
|
+
coverage.xml
|
|
28
|
+
|
|
29
|
+
# Type checking
|
|
30
|
+
.mypy_cache/
|
|
31
|
+
|
|
32
|
+
# IDE
|
|
33
|
+
.vscode/
|
|
34
|
+
.idea/
|
|
35
|
+
|
|
36
|
+
# OS
|
|
37
|
+
.DS_Store
|
|
38
|
+
Thumbs.db
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
3
|
+
rev: v0.4.4
|
|
4
|
+
hooks:
|
|
5
|
+
- id: ruff
|
|
6
|
+
args: [--fix]
|
|
7
|
+
- id: ruff-format
|
|
8
|
+
|
|
9
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
10
|
+
rev: v4.6.0
|
|
11
|
+
hooks:
|
|
12
|
+
- id: trailing-whitespace
|
|
13
|
+
- id: end-of-file-fixer
|
|
14
|
+
- id: check-yaml
|
|
15
|
+
- id: check-toml
|
|
16
|
+
- id: check-merge-conflict
|
|
17
|
+
- id: detect-private-key
|
|
@@ -0,0 +1,129 @@
|
|
|
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 = "A production-ready FastAPI application"
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
authors = [{ name = "Your Name", email = "you@example.com" }]
|
|
11
|
+
dependencies = [
|
|
12
|
+
"fastapi>=0.111.0",
|
|
13
|
+
"uvicorn[standard]>=0.29.0",
|
|
14
|
+
"pydantic>=2.7.0",
|
|
15
|
+
"pydantic-settings>=2.2.0",
|
|
16
|
+
{% if log_lib == "loguru" %}
|
|
17
|
+
"loguru>=0.7.2",
|
|
18
|
+
{% elif log_lib == "structlog" %}
|
|
19
|
+
"structlog>=24.1.0",
|
|
20
|
+
{% endif %}
|
|
21
|
+
{% if has_relational_db and orm == "sqlalchemy" %}
|
|
22
|
+
"sqlalchemy[asyncio]>=2.0.0",
|
|
23
|
+
{% if db == "postgresql" %}
|
|
24
|
+
"asyncpg>=0.29.0",
|
|
25
|
+
{% elif db == "mysql" %}
|
|
26
|
+
"aiomysql>=0.2.0",
|
|
27
|
+
{% elif db == "sqlite" %}
|
|
28
|
+
"aiosqlite>=0.20.0",
|
|
29
|
+
{% endif %}
|
|
30
|
+
{% endif %}
|
|
31
|
+
{% if has_relational_db and orm == "tortoise" %}
|
|
32
|
+
"tortoise-orm>=0.21.0",
|
|
33
|
+
{% endif %}
|
|
34
|
+
{% if has_migration and migration == "alembic" %}
|
|
35
|
+
"alembic>=1.13.0",
|
|
36
|
+
{% elif has_migration and migration == "aerich" %}
|
|
37
|
+
"aerich>=0.7.2",
|
|
38
|
+
{% endif %}
|
|
39
|
+
{% if has_mongo and orm == "beanie" %}
|
|
40
|
+
"beanie>=1.25.0",
|
|
41
|
+
"motor>=3.4.0",
|
|
42
|
+
{% endif %}
|
|
43
|
+
{% if auth == "jwt" or auth == "oauth2" %}
|
|
44
|
+
"python-jose[cryptography]>=3.3.0",
|
|
45
|
+
"passlib[bcrypt]>=1.7.4",
|
|
46
|
+
"python-multipart>=0.0.9",
|
|
47
|
+
{% elif auth == "api-key" %}
|
|
48
|
+
"python-multipart>=0.0.9",
|
|
49
|
+
{% endif %}
|
|
50
|
+
{% if broker == "redis" or broker == "rabbitmq" %}
|
|
51
|
+
"celery[redis]>=5.3.6",
|
|
52
|
+
{% elif broker == "kafka" %}
|
|
53
|
+
"aiokafka>=0.10.0",
|
|
54
|
+
{% endif %}
|
|
55
|
+
{% if cache == "redis" or broker == "redis" %}
|
|
56
|
+
"redis[hiredis]>=5.0.0",
|
|
57
|
+
{% elif cache == "memcached" %}
|
|
58
|
+
"aiomcache>=0.8.0",
|
|
59
|
+
{% endif %}
|
|
60
|
+
{% if has_s3 %}
|
|
61
|
+
"boto3>=1.34.0",
|
|
62
|
+
{% endif %}
|
|
63
|
+
{% if ai == "openai" %}
|
|
64
|
+
"openai>=1.30.0",
|
|
65
|
+
{% elif ai == "anthropic" %}
|
|
66
|
+
"anthropic>=0.28.0",
|
|
67
|
+
{% endif %}
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
[project.optional-dependencies]
|
|
71
|
+
dev = [
|
|
72
|
+
"pytest>=8.0.0",
|
|
73
|
+
"pytest-asyncio>=0.23.0",
|
|
74
|
+
"pytest-cov>=5.0.0",
|
|
75
|
+
"httpx>=0.27.0",
|
|
76
|
+
"anyio>=4.3.0",
|
|
77
|
+
"ruff>=0.4.0",
|
|
78
|
+
"mypy>=1.10.0",
|
|
79
|
+
"pre-commit>=3.7.0",
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
[tool.uv]
|
|
83
|
+
dev-dependencies = [
|
|
84
|
+
"pytest>=8.0.0",
|
|
85
|
+
"pytest-asyncio>=0.23.0",
|
|
86
|
+
"pytest-cov>=5.0.0",
|
|
87
|
+
"httpx>=0.27.0",
|
|
88
|
+
"anyio>=4.3.0",
|
|
89
|
+
"ruff>=0.4.0",
|
|
90
|
+
"mypy>=1.10.0",
|
|
91
|
+
"pre-commit>=3.7.0",
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
[tool.pytest.ini_options]
|
|
95
|
+
asyncio_mode = "auto"
|
|
96
|
+
testpaths = ["tests"]
|
|
97
|
+
addopts = "--cov=app --cov-report=term-missing"
|
|
98
|
+
|
|
99
|
+
[tool.ruff]
|
|
100
|
+
line-length = 100
|
|
101
|
+
target-version = "py310"
|
|
102
|
+
|
|
103
|
+
[tool.ruff.lint]
|
|
104
|
+
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
105
|
+
ignore = ["E501"]
|
|
106
|
+
|
|
107
|
+
[tool.mypy]
|
|
108
|
+
python_version = "3.10"
|
|
109
|
+
strict = true
|
|
110
|
+
ignore_missing_imports = true
|
|
111
|
+
|
|
112
|
+
# ── uv run scripts ─────────────────────────────────────────────────────────
|
|
113
|
+
# Usage: uv run <script-name>
|
|
114
|
+
[tool.uv.scripts]
|
|
115
|
+
dev = "uvicorn app.main:app --reload --host 0.0.0.0 --port 8000"
|
|
116
|
+
start = "python main.py"
|
|
117
|
+
test = "pytest --cov=app --cov-report=term-missing"
|
|
118
|
+
lint = "ruff check ."
|
|
119
|
+
format = "ruff format ."
|
|
120
|
+
typecheck = "mypy app/"
|
|
121
|
+
{% if has_alembic %}
|
|
122
|
+
migrate = "alembic upgrade head"
|
|
123
|
+
rollback = "alembic downgrade -1"
|
|
124
|
+
makemig = "alembic revision --autogenerate -m"
|
|
125
|
+
{% endif %}
|
|
126
|
+
{% if has_broker %}
|
|
127
|
+
worker = "celery -A tasks.celery_app worker --loglevel=info"
|
|
128
|
+
beat = "celery -A tasks.celery_app beat --loglevel=info"
|
|
129
|
+
{% endif %}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: Publish Docker Image
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
push:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
packages: write
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Log in to GHCR
|
|
19
|
+
uses: docker/login-action@v3
|
|
20
|
+
with:
|
|
21
|
+
registry: ghcr.io
|
|
22
|
+
username: ${{ "{{" }} github.actor {{ "}}" }}
|
|
23
|
+
password: ${{ "{{" }} secrets.GITHUB_TOKEN {{ "}}" }}
|
|
24
|
+
|
|
25
|
+
- name: Build and push
|
|
26
|
+
uses: docker/build-push-action@v5
|
|
27
|
+
with:
|
|
28
|
+
context: .
|
|
29
|
+
push: true
|
|
30
|
+
tags: |
|
|
31
|
+
ghcr.io/${{ "{{" }} github.repository {{ "}}" }}:latest
|
|
32
|
+
ghcr.io/${{ "{{" }} github.repository {{ "}}" }}:${{ "{{" }} github.ref_name {{ "}}" }}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: Tests
|
|
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
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Set up Python ${{ "{{" }} matrix.python-version {{ "}}" }}
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ "{{" }} matrix.python-version {{ "}}" }}
|
|
23
|
+
|
|
24
|
+
- name: Install uv
|
|
25
|
+
uses: astral-sh/setup-uv@v4
|
|
26
|
+
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: uv sync --all-extras
|
|
29
|
+
|
|
30
|
+
- name: Lint with ruff
|
|
31
|
+
run: uv run ruff check .
|
|
32
|
+
|
|
33
|
+
- name: Run tests
|
|
34
|
+
run: uv run pytest --cov=app --cov-report=xml
|
|
35
|
+
|
|
36
|
+
- name: Upload coverage
|
|
37
|
+
uses: codecov/codecov-action@v4
|
|
38
|
+
with:
|
|
39
|
+
file: coverage.xml
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
stages:
|
|
2
|
+
- test
|
|
3
|
+
- build
|
|
4
|
+
|
|
5
|
+
test:
|
|
6
|
+
stage: test
|
|
7
|
+
image: python:3.12-slim
|
|
8
|
+
before_script:
|
|
9
|
+
- pip install uv
|
|
10
|
+
- uv sync --all-extras
|
|
11
|
+
script:
|
|
12
|
+
- uv run ruff check .
|
|
13
|
+
- uv run pytest --cov=app --cov-report=xml
|
|
14
|
+
coverage: '/TOTAL.*\s+(\d+%)$/'
|
|
15
|
+
artifacts:
|
|
16
|
+
reports:
|
|
17
|
+
coverage_report:
|
|
18
|
+
coverage_format: cobertura
|
|
19
|
+
path: coverage.xml
|
|
20
|
+
|
|
21
|
+
build:
|
|
22
|
+
stage: build
|
|
23
|
+
image: docker:latest
|
|
24
|
+
services:
|
|
25
|
+
- docker:dind
|
|
26
|
+
script:
|
|
27
|
+
- docker build -t {{ slug }}:latest .
|
|
28
|
+
only:
|
|
29
|
+
- tags
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
FROM python:3.12-slim
|
|
2
|
+
|
|
3
|
+
# Install uv
|
|
4
|
+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
|
|
5
|
+
|
|
6
|
+
WORKDIR /app
|
|
7
|
+
|
|
8
|
+
# Install deps first for layer caching
|
|
9
|
+
COPY pyproject.toml ./
|
|
10
|
+
RUN uv sync --no-dev --frozen
|
|
11
|
+
|
|
12
|
+
# Copy source
|
|
13
|
+
COPY . .
|
|
14
|
+
|
|
15
|
+
EXPOSE 8000
|
|
16
|
+
|
|
17
|
+
CMD ["uv", "run", "main.py"]
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
services:
|
|
2
|
+
app:
|
|
3
|
+
build: .
|
|
4
|
+
ports:
|
|
5
|
+
- "8000:8000"
|
|
6
|
+
env_file:
|
|
7
|
+
- .env
|
|
8
|
+
depends_on:
|
|
9
|
+
{% if has_relational_db and db == "postgresql" %}
|
|
10
|
+
- postgres
|
|
11
|
+
{% endif %}
|
|
12
|
+
{% if has_mongo %}
|
|
13
|
+
- mongodb
|
|
14
|
+
{% endif %}
|
|
15
|
+
{% if broker == "redis" or cache == "redis" %}
|
|
16
|
+
- redis
|
|
17
|
+
{% endif %}
|
|
18
|
+
{% if broker == "rabbitmq" %}
|
|
19
|
+
- rabbitmq
|
|
20
|
+
{% endif %}
|
|
21
|
+
volumes:
|
|
22
|
+
- .:/app
|
|
23
|
+
restart: unless-stopped
|
|
24
|
+
|
|
25
|
+
{% if has_broker and broker != "kafka" %}
|
|
26
|
+
worker:
|
|
27
|
+
build: .
|
|
28
|
+
command: celery -A tasks.celery_app worker --loglevel=info --concurrency=2
|
|
29
|
+
env_file:
|
|
30
|
+
- .env
|
|
31
|
+
depends_on:
|
|
32
|
+
- app
|
|
33
|
+
restart: unless-stopped
|
|
34
|
+
{% endif %}
|
|
35
|
+
|
|
36
|
+
{% if has_relational_db and db == "postgresql" %}
|
|
37
|
+
postgres:
|
|
38
|
+
image: postgres:16-alpine
|
|
39
|
+
environment:
|
|
40
|
+
POSTGRES_DB: {{ slug }}_db
|
|
41
|
+
POSTGRES_USER: user
|
|
42
|
+
POSTGRES_PASSWORD: password
|
|
43
|
+
ports:
|
|
44
|
+
- "5432:5432"
|
|
45
|
+
volumes:
|
|
46
|
+
- postgres_data:/var/lib/postgresql/data
|
|
47
|
+
healthcheck:
|
|
48
|
+
test: ["CMD-SHELL", "pg_isready -U user -d {{ slug }}_db"]
|
|
49
|
+
interval: 10s
|
|
50
|
+
timeout: 5s
|
|
51
|
+
retries: 5
|
|
52
|
+
{% endif %}
|
|
53
|
+
|
|
54
|
+
{% if has_mongo %}
|
|
55
|
+
mongodb:
|
|
56
|
+
image: mongo:7
|
|
57
|
+
ports:
|
|
58
|
+
- "27017:27017"
|
|
59
|
+
volumes:
|
|
60
|
+
- mongo_data:/data/db
|
|
61
|
+
{% endif %}
|
|
62
|
+
|
|
63
|
+
{% if broker == "redis" or cache == "redis" %}
|
|
64
|
+
redis:
|
|
65
|
+
image: redis:7-alpine
|
|
66
|
+
ports:
|
|
67
|
+
- "6379:6379"
|
|
68
|
+
volumes:
|
|
69
|
+
- redis_data:/data
|
|
70
|
+
healthcheck:
|
|
71
|
+
test: ["CMD", "redis-cli", "ping"]
|
|
72
|
+
interval: 10s
|
|
73
|
+
timeout: 3s
|
|
74
|
+
retries: 3
|
|
75
|
+
{% endif %}
|
|
76
|
+
|
|
77
|
+
{% if broker == "rabbitmq" %}
|
|
78
|
+
rabbitmq:
|
|
79
|
+
image: rabbitmq:3-management-alpine
|
|
80
|
+
ports:
|
|
81
|
+
- "5672:5672"
|
|
82
|
+
- "15672:15672"
|
|
83
|
+
environment:
|
|
84
|
+
RABBITMQ_DEFAULT_USER: guest
|
|
85
|
+
RABBITMQ_DEFAULT_PASS: guest
|
|
86
|
+
{% endif %}
|
|
87
|
+
|
|
88
|
+
volumes:
|
|
89
|
+
{% if has_relational_db and db == "postgresql" %}
|
|
90
|
+
postgres_data:
|
|
91
|
+
{% endif %}
|
|
92
|
+
{% if has_mongo %}
|
|
93
|
+
mongo_data:
|
|
94
|
+
{% endif %}
|
|
95
|
+
{% if broker == "redis" or cache == "redis" %}
|
|
96
|
+
redis_data:
|
|
97
|
+
{% endif %}
|