forgeapi 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_forge/__init__.py +15 -0
- fastapi_forge/cli.py +125 -0
- fastapi_forge/commands/__init__.py +5 -0
- fastapi_forge/commands/create.py +107 -0
- fastapi_forge/generator.py +300 -0
- fastapi_forge/models.py +187 -0
- fastapi_forge/prompts.py +364 -0
- fastapi_forge/templates/base/.dockerignore.jinja +20 -0
- fastapi_forge/templates/base/.github/workflows/ci.yml.jinja +155 -0
- fastapi_forge/templates/base/.gitignore.jinja +68 -0
- fastapi_forge/templates/base/Dockerfile.jinja +69 -0
- fastapi_forge/templates/base/README.md.jinja +141 -0
- fastapi_forge/templates/base/alembic/README.md.jinja +43 -0
- fastapi_forge/templates/base/alembic/env.py.jinja +93 -0
- fastapi_forge/templates/base/alembic/script.py.mako.jinja +26 -0
- fastapi_forge/templates/base/alembic/versions/.gitkeep.jinja +1 -0
- fastapi_forge/templates/base/alembic.ini.jinja +70 -0
- fastapi_forge/templates/base/app/__init__.py.jinja +5 -0
- fastapi_forge/templates/base/app/api/__init__.py.jinja +3 -0
- fastapi_forge/templates/base/app/api/auth_api.py.jinja +72 -0
- fastapi_forge/templates/base/app/api/health_api.py.jinja +41 -0
- fastapi_forge/templates/base/app/config/__init__.py.jinja +31 -0
- fastapi_forge/templates/base/app/config/base.py.jinja +52 -0
- fastapi_forge/templates/base/app/config/env.py.jinja +75 -0
- fastapi_forge/templates/base/app/core/__init__.py.jinja +3 -0
- fastapi_forge/templates/base/app/core/auth.py.jinja +96 -0
- fastapi_forge/templates/base/app/core/config.py.jinja +56 -0
- fastapi_forge/templates/base/app/core/database.py.jinja +68 -0
- fastapi_forge/templates/base/app/core/deps.py.jinja +55 -0
- fastapi_forge/templates/base/app/core/redis.py.jinja +41 -0
- fastapi_forge/templates/base/app/daos/__init__.py.jinja +3 -0
- fastapi_forge/templates/base/app/exceptions/__init__.py.jinja +7 -0
- fastapi_forge/templates/base/app/exceptions/exception.py.jinja +34 -0
- fastapi_forge/templates/base/app/exceptions/handler.py.jinja +56 -0
- fastapi_forge/templates/base/app/main.py.jinja +24 -0
- fastapi_forge/templates/base/app/models/__init__.py.jinja +7 -0
- fastapi_forge/templates/base/app/models/base.py.jinja +13 -0
- fastapi_forge/templates/base/app/schemas/__init__.py.jinja +3 -0
- fastapi_forge/templates/base/app/schemas/api_schema.py.jinja +20 -0
- fastapi_forge/templates/base/app/schemas/auth_schema.py.jinja +21 -0
- fastapi_forge/templates/base/app/server.py.jinja +99 -0
- fastapi_forge/templates/base/app/services/__init__.py.jinja +3 -0
- fastapi_forge/templates/base/app/utils/__init__.py.jinja +3 -0
- fastapi_forge/templates/base/app/utils/log.py.jinja +21 -0
- fastapi_forge/templates/base/config.yaml.jinja +71 -0
- fastapi_forge/templates/base/docker-compose.yml.jinja +117 -0
- fastapi_forge/templates/base/pyproject.toml.jinja +86 -0
- fastapi_forge/templates/base/pytest.ini.jinja +10 -0
- fastapi_forge/templates/base/ruff.toml.jinja +33 -0
- fastapi_forge/templates/base/tests/__init__.py.jinja +3 -0
- fastapi_forge/templates/base/tests/conftest.py.jinja +31 -0
- fastapi_forge/templates/base/tests/test_health.py.jinja +24 -0
- forgeapi-0.1.0.dist-info/METADATA +182 -0
- forgeapi-0.1.0.dist-info/RECORD +57 -0
- forgeapi-0.1.0.dist-info/WHEEL +4 -0
- forgeapi-0.1.0.dist-info/entry_points.txt +2 -0
- forgeapi-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, develop]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, develop]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
{% if database_type != 'none' %}
|
|
14
|
+
services:
|
|
15
|
+
{% if database_type == 'postgres' %}
|
|
16
|
+
postgres:
|
|
17
|
+
image: postgres:15-alpine
|
|
18
|
+
env:
|
|
19
|
+
POSTGRES_USER: test
|
|
20
|
+
POSTGRES_PASSWORD: test
|
|
21
|
+
POSTGRES_DB: test_db
|
|
22
|
+
ports:
|
|
23
|
+
- 5432:5432
|
|
24
|
+
options: >-
|
|
25
|
+
--health-cmd pg_isready
|
|
26
|
+
--health-interval 10s
|
|
27
|
+
--health-timeout 5s
|
|
28
|
+
--health-retries 5
|
|
29
|
+
{% elif database_type == 'mysql' %}
|
|
30
|
+
mysql:
|
|
31
|
+
image: mysql:8.0
|
|
32
|
+
env:
|
|
33
|
+
MYSQL_ROOT_PASSWORD: test
|
|
34
|
+
MYSQL_DATABASE: test_db
|
|
35
|
+
MYSQL_USER: test
|
|
36
|
+
MYSQL_PASSWORD: test
|
|
37
|
+
ports:
|
|
38
|
+
- 3306:3306
|
|
39
|
+
options: >-
|
|
40
|
+
--health-cmd "mysqladmin ping"
|
|
41
|
+
--health-interval 10s
|
|
42
|
+
--health-timeout 5s
|
|
43
|
+
--health-retries 5
|
|
44
|
+
{% endif %}
|
|
45
|
+
{% if use_redis %}
|
|
46
|
+
redis:
|
|
47
|
+
image: redis:7-alpine
|
|
48
|
+
ports:
|
|
49
|
+
- 6379:6379
|
|
50
|
+
options: >-
|
|
51
|
+
--health-cmd "redis-cli ping"
|
|
52
|
+
--health-interval 10s
|
|
53
|
+
--health-timeout 5s
|
|
54
|
+
--health-retries 5
|
|
55
|
+
{% endif %}
|
|
56
|
+
{% endif %}
|
|
57
|
+
|
|
58
|
+
steps:
|
|
59
|
+
- uses: actions/checkout@v4
|
|
60
|
+
|
|
61
|
+
- name: Set up Python
|
|
62
|
+
uses: actions/setup-python@v5
|
|
63
|
+
with:
|
|
64
|
+
python-version: "{{ python_version }}"
|
|
65
|
+
|
|
66
|
+
{% if use_uv %}
|
|
67
|
+
- name: Install uv
|
|
68
|
+
uses: astral-sh/setup-uv@v4
|
|
69
|
+
with:
|
|
70
|
+
version: "latest"
|
|
71
|
+
|
|
72
|
+
- name: Install dependencies
|
|
73
|
+
run: uv sync --group dev
|
|
74
|
+
|
|
75
|
+
- name: Run linting
|
|
76
|
+
run: uv run ruff check .
|
|
77
|
+
|
|
78
|
+
- name: Run type checking
|
|
79
|
+
run: uv run mypy app
|
|
80
|
+
|
|
81
|
+
- name: Run tests
|
|
82
|
+
run: uv run pytest -v --cov=app --cov-report=xml
|
|
83
|
+
env:
|
|
84
|
+
{% if database_type == 'postgres' %}
|
|
85
|
+
DATABASE_URL: "postgresql+asyncpg://test:test@localhost:5432/test_db"
|
|
86
|
+
{% elif database_type == 'mysql' %}
|
|
87
|
+
DATABASE_URL: "mysql+asyncmy://test:test@localhost:3306/test_db"
|
|
88
|
+
{% elif database_type == 'sqlite' %}
|
|
89
|
+
DATABASE_URL: "sqlite+aiosqlite:///./test.db"
|
|
90
|
+
{% endif %}
|
|
91
|
+
{% if use_redis %}
|
|
92
|
+
REDIS_URL: "redis://localhost:6379/0"
|
|
93
|
+
{% endif %}
|
|
94
|
+
{% elif use_poetry %}
|
|
95
|
+
- name: Install Poetry
|
|
96
|
+
uses: snok/install-poetry@v1
|
|
97
|
+
with:
|
|
98
|
+
virtualenvs-create: true
|
|
99
|
+
virtualenvs-in-project: true
|
|
100
|
+
|
|
101
|
+
- name: Install dependencies
|
|
102
|
+
run: poetry install --with dev
|
|
103
|
+
|
|
104
|
+
- name: Run linting
|
|
105
|
+
run: poetry run ruff check .
|
|
106
|
+
|
|
107
|
+
- name: Run type checking
|
|
108
|
+
run: poetry run mypy app
|
|
109
|
+
|
|
110
|
+
- name: Run tests
|
|
111
|
+
run: poetry run pytest -v --cov=app --cov-report=xml
|
|
112
|
+
env:
|
|
113
|
+
{% if database_type == 'postgres' %}
|
|
114
|
+
DATABASE_URL: "postgresql+asyncpg://test:test@localhost:5432/test_db"
|
|
115
|
+
{% elif database_type == 'mysql' %}
|
|
116
|
+
DATABASE_URL: "mysql+asyncmy://test:test@localhost:3306/test_db"
|
|
117
|
+
{% elif database_type == 'sqlite' %}
|
|
118
|
+
DATABASE_URL: "sqlite+aiosqlite:///./test.db"
|
|
119
|
+
{% endif %}
|
|
120
|
+
{% if use_redis %}
|
|
121
|
+
REDIS_URL: "redis://localhost:6379/0"
|
|
122
|
+
{% endif %}
|
|
123
|
+
{% else %}
|
|
124
|
+
- name: Install dependencies
|
|
125
|
+
run: |
|
|
126
|
+
python -m pip install --upgrade pip
|
|
127
|
+
pip install -r requirements.txt
|
|
128
|
+
pip install -r requirements-dev.txt
|
|
129
|
+
|
|
130
|
+
- name: Run linting
|
|
131
|
+
run: ruff check .
|
|
132
|
+
|
|
133
|
+
- name: Run type checking
|
|
134
|
+
run: mypy app
|
|
135
|
+
|
|
136
|
+
- name: Run tests
|
|
137
|
+
run: pytest -v --cov=app --cov-report=xml
|
|
138
|
+
env:
|
|
139
|
+
{% if database_type == 'postgres' %}
|
|
140
|
+
DATABASE_URL: "postgresql+asyncpg://test:test@localhost:5432/test_db"
|
|
141
|
+
{% elif database_type == 'mysql' %}
|
|
142
|
+
DATABASE_URL: "mysql+asyncmy://test:test@localhost:3306/test_db"
|
|
143
|
+
{% elif database_type == 'sqlite' %}
|
|
144
|
+
DATABASE_URL: "sqlite+aiosqlite:///./test.db"
|
|
145
|
+
{% endif %}
|
|
146
|
+
{% if use_redis %}
|
|
147
|
+
REDIS_URL: "redis://localhost:6379/0"
|
|
148
|
+
{% endif %}
|
|
149
|
+
{% endif %}
|
|
150
|
+
|
|
151
|
+
- name: Upload coverage to Codecov
|
|
152
|
+
uses: codecov/codecov-action@v4
|
|
153
|
+
with:
|
|
154
|
+
file: ./coverage.xml
|
|
155
|
+
fail_ci_if_error: false
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
*.egg-info/
|
|
24
|
+
.installed.cfg
|
|
25
|
+
*.egg
|
|
26
|
+
|
|
27
|
+
# PyInstaller
|
|
28
|
+
*.manifest
|
|
29
|
+
*.spec
|
|
30
|
+
|
|
31
|
+
# Unit test / coverage reports
|
|
32
|
+
htmlcov/
|
|
33
|
+
.tox/
|
|
34
|
+
.nox/
|
|
35
|
+
.coverage
|
|
36
|
+
.coverage.*
|
|
37
|
+
.cache
|
|
38
|
+
nosetests.xml
|
|
39
|
+
coverage.xml
|
|
40
|
+
*.cover
|
|
41
|
+
*.py,cover
|
|
42
|
+
.hypothesis/
|
|
43
|
+
.pytest_cache/
|
|
44
|
+
|
|
45
|
+
# Environments
|
|
46
|
+
.env
|
|
47
|
+
.venv
|
|
48
|
+
env/
|
|
49
|
+
venv/
|
|
50
|
+
ENV/
|
|
51
|
+
env.bak/
|
|
52
|
+
venv.bak/
|
|
53
|
+
|
|
54
|
+
# IDE
|
|
55
|
+
.vscode/
|
|
56
|
+
.idea/
|
|
57
|
+
|
|
58
|
+
# macOS
|
|
59
|
+
.DS_Store
|
|
60
|
+
|
|
61
|
+
# uv
|
|
62
|
+
uv.lock
|
|
63
|
+
|
|
64
|
+
# Ruff
|
|
65
|
+
.ruff_cache/
|
|
66
|
+
|
|
67
|
+
# mypy
|
|
68
|
+
.mypy_cache/
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
FROM python:{{ python_version }}-slim
|
|
2
|
+
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
|
|
5
|
+
# Install system dependencies
|
|
6
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
7
|
+
gcc \
|
|
8
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
9
|
+
|
|
10
|
+
{% if use_uv %}
|
|
11
|
+
# Install uv
|
|
12
|
+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
|
|
13
|
+
|
|
14
|
+
# Copy project files
|
|
15
|
+
COPY pyproject.toml ./
|
|
16
|
+
COPY app ./app
|
|
17
|
+
{% if use_alembic %}
|
|
18
|
+
COPY alembic ./alembic
|
|
19
|
+
COPY alembic.ini ./
|
|
20
|
+
{% endif %}
|
|
21
|
+
COPY config.yaml ./
|
|
22
|
+
|
|
23
|
+
# Install dependencies
|
|
24
|
+
RUN uv sync --frozen --no-dev
|
|
25
|
+
|
|
26
|
+
# Set Python path
|
|
27
|
+
ENV PYTHONPATH=/app
|
|
28
|
+
ENV PATH="/app/.venv/bin:$PATH"
|
|
29
|
+
|
|
30
|
+
{% elif use_poetry %}
|
|
31
|
+
# Install Poetry
|
|
32
|
+
RUN pip install poetry
|
|
33
|
+
|
|
34
|
+
# Copy project files
|
|
35
|
+
COPY pyproject.toml poetry.lock* ./
|
|
36
|
+
COPY app ./app
|
|
37
|
+
{% if use_alembic %}
|
|
38
|
+
COPY alembic ./alembic
|
|
39
|
+
COPY alembic.ini ./
|
|
40
|
+
{% endif %}
|
|
41
|
+
COPY config.yaml ./
|
|
42
|
+
|
|
43
|
+
# Install dependencies
|
|
44
|
+
RUN poetry config virtualenvs.create false \
|
|
45
|
+
&& poetry install --no-interaction --no-ansi --without dev
|
|
46
|
+
|
|
47
|
+
# Set Python path
|
|
48
|
+
ENV PYTHONPATH=/app
|
|
49
|
+
|
|
50
|
+
{% else %}
|
|
51
|
+
# Copy requirements
|
|
52
|
+
COPY requirements.txt ./
|
|
53
|
+
RUN pip install --no-cache-dir -r requirements.txt
|
|
54
|
+
|
|
55
|
+
# Copy project files
|
|
56
|
+
COPY app ./app
|
|
57
|
+
{% if use_alembic %}
|
|
58
|
+
COPY alembic ./alembic
|
|
59
|
+
COPY alembic.ini ./
|
|
60
|
+
{% endif %}
|
|
61
|
+
COPY config.yaml ./
|
|
62
|
+
|
|
63
|
+
# Set Python path
|
|
64
|
+
ENV PYTHONPATH=/app
|
|
65
|
+
{% endif %}
|
|
66
|
+
|
|
67
|
+
EXPOSE 8000
|
|
68
|
+
|
|
69
|
+
CMD ["python", "-m", "app.main"]
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# {{ project_name }}
|
|
2
|
+
|
|
3
|
+
{{ project_description }}
|
|
4
|
+
|
|
5
|
+
## 🚀 Quick Start
|
|
6
|
+
|
|
7
|
+
### Prerequisites
|
|
8
|
+
|
|
9
|
+
- Python {{ python_version }}+
|
|
10
|
+
{% if use_uv %}
|
|
11
|
+
- [uv](https://docs.astral.sh/uv/) (recommended)
|
|
12
|
+
{% elif use_poetry %}
|
|
13
|
+
- [Poetry](https://python-poetry.org/)
|
|
14
|
+
{% endif %}
|
|
15
|
+
{% if use_docker %}
|
|
16
|
+
- Docker & Docker Compose
|
|
17
|
+
{% endif %}
|
|
18
|
+
|
|
19
|
+
### Installation
|
|
20
|
+
|
|
21
|
+
{% if use_uv %}
|
|
22
|
+
```bash
|
|
23
|
+
# Install dependencies
|
|
24
|
+
uv sync
|
|
25
|
+
|
|
26
|
+
# Activate virtual environment
|
|
27
|
+
source .venv/bin/activate
|
|
28
|
+
```
|
|
29
|
+
{% elif use_poetry %}
|
|
30
|
+
```bash
|
|
31
|
+
# Install dependencies
|
|
32
|
+
poetry install
|
|
33
|
+
|
|
34
|
+
# Activate virtual environment
|
|
35
|
+
poetry shell
|
|
36
|
+
```
|
|
37
|
+
{% else %}
|
|
38
|
+
```bash
|
|
39
|
+
# Create virtual environment
|
|
40
|
+
python -m venv .venv
|
|
41
|
+
source .venv/bin/activate
|
|
42
|
+
|
|
43
|
+
# Install dependencies
|
|
44
|
+
pip install -r requirements.txt
|
|
45
|
+
```
|
|
46
|
+
{% endif %}
|
|
47
|
+
|
|
48
|
+
{% if use_docker_compose %}
|
|
49
|
+
### Running with Docker
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Start all services
|
|
53
|
+
docker-compose up -d
|
|
54
|
+
|
|
55
|
+
# View logs
|
|
56
|
+
docker-compose logs -f
|
|
57
|
+
```
|
|
58
|
+
{% endif %}
|
|
59
|
+
|
|
60
|
+
### Running Locally
|
|
61
|
+
|
|
62
|
+
{% if use_alembic %}
|
|
63
|
+
```bash
|
|
64
|
+
# Run database migrations
|
|
65
|
+
{% if use_uv %}
|
|
66
|
+
uv run alembic upgrade head
|
|
67
|
+
{% elif use_poetry %}
|
|
68
|
+
poetry run alembic upgrade head
|
|
69
|
+
{% else %}
|
|
70
|
+
alembic upgrade head
|
|
71
|
+
{% endif %}
|
|
72
|
+
```
|
|
73
|
+
{% endif %}
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# Start the development server
|
|
77
|
+
{% if use_uv %}
|
|
78
|
+
uv run python -m app.main
|
|
79
|
+
{% elif use_poetry %}
|
|
80
|
+
poetry run python -m app.main
|
|
81
|
+
{% else %}
|
|
82
|
+
python -m app.main
|
|
83
|
+
{% endif %}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The API will be available at http://localhost:8000
|
|
87
|
+
|
|
88
|
+
- 📚 API Documentation: http://localhost:8000/docs
|
|
89
|
+
- 📖 ReDoc: http://localhost:8000/redoc
|
|
90
|
+
|
|
91
|
+
## 📁 Project Structure
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
{{ project_name }}/
|
|
95
|
+
├── app/
|
|
96
|
+
│ ├── api/ # API routes
|
|
97
|
+
│ ├── config/ # Configuration
|
|
98
|
+
│ ├── core/ # Core utilities
|
|
99
|
+
{% if use_database %}
|
|
100
|
+
│ ├── daos/ # Data Access Objects
|
|
101
|
+
{% endif %}
|
|
102
|
+
│ ├── exceptions/ # Exception handlers
|
|
103
|
+
{% if use_database %}
|
|
104
|
+
│ ├── models/ # SQLAlchemy models
|
|
105
|
+
{% endif %}
|
|
106
|
+
│ ├── schemas/ # Pydantic schemas
|
|
107
|
+
│ ├── services/ # Business logic
|
|
108
|
+
│ └── utils/ # Utilities
|
|
109
|
+
{% if use_alembic %}
|
|
110
|
+
├── alembic/ # Database migrations
|
|
111
|
+
{% endif %}
|
|
112
|
+
├── tests/ # Test suite
|
|
113
|
+
{% if use_docker %}
|
|
114
|
+
├── Dockerfile
|
|
115
|
+
{% endif %}
|
|
116
|
+
{% if use_docker_compose %}
|
|
117
|
+
├── docker-compose.yml
|
|
118
|
+
{% endif %}
|
|
119
|
+
├── pyproject.toml
|
|
120
|
+
└── README.md
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## 🧪 Testing
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
{% if use_uv %}
|
|
127
|
+
uv run pytest
|
|
128
|
+
{% elif use_poetry %}
|
|
129
|
+
poetry run pytest
|
|
130
|
+
{% else %}
|
|
131
|
+
pytest
|
|
132
|
+
{% endif %}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## 📄 License
|
|
136
|
+
|
|
137
|
+
This project is licensed under the MIT License.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
Generated with ❤️ by [FastAPI-Forge](https://github.com/zachary/forgeapi)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Database Migrations
|
|
2
|
+
|
|
3
|
+
This directory contains Alembic database migration scripts.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Create a new migration
|
|
9
|
+
{% if use_uv %}
|
|
10
|
+
uv run alembic revision --autogenerate -m "description"
|
|
11
|
+
{% elif use_poetry %}
|
|
12
|
+
poetry run alembic revision --autogenerate -m "description"
|
|
13
|
+
{% else %}
|
|
14
|
+
alembic revision --autogenerate -m "description"
|
|
15
|
+
{% endif %}
|
|
16
|
+
|
|
17
|
+
# Apply migrations
|
|
18
|
+
{% if use_uv %}
|
|
19
|
+
uv run alembic upgrade head
|
|
20
|
+
{% elif use_poetry %}
|
|
21
|
+
poetry run alembic upgrade head
|
|
22
|
+
{% else %}
|
|
23
|
+
alembic upgrade head
|
|
24
|
+
{% endif %}
|
|
25
|
+
|
|
26
|
+
# Rollback one migration
|
|
27
|
+
{% if use_uv %}
|
|
28
|
+
uv run alembic downgrade -1
|
|
29
|
+
{% elif use_poetry %}
|
|
30
|
+
poetry run alembic downgrade -1
|
|
31
|
+
{% else %}
|
|
32
|
+
alembic downgrade -1
|
|
33
|
+
{% endif %}
|
|
34
|
+
|
|
35
|
+
# Show migration history
|
|
36
|
+
{% if use_uv %}
|
|
37
|
+
uv run alembic history
|
|
38
|
+
{% elif use_poetry %}
|
|
39
|
+
poetry run alembic history
|
|
40
|
+
{% else %}
|
|
41
|
+
alembic history
|
|
42
|
+
{% endif %}
|
|
43
|
+
```
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Alembic Environment Configuration
|
|
3
|
+
|
|
4
|
+
This module configures Alembic for database migrations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
from logging.config import fileConfig
|
|
9
|
+
|
|
10
|
+
from sqlalchemy import pool
|
|
11
|
+
from sqlalchemy.engine import Connection
|
|
12
|
+
from sqlalchemy.ext.asyncio import async_engine_from_config
|
|
13
|
+
|
|
14
|
+
from alembic import context
|
|
15
|
+
|
|
16
|
+
from app.core.config import get_config
|
|
17
|
+
from app.models.base import Base
|
|
18
|
+
|
|
19
|
+
# Import all models here to ensure they are registered with SQLAlchemy
|
|
20
|
+
# from app.models.user import User
|
|
21
|
+
|
|
22
|
+
# Alembic Config object
|
|
23
|
+
config = context.config
|
|
24
|
+
|
|
25
|
+
# Interpret the config file for Python logging
|
|
26
|
+
if config.config_file_name is not None:
|
|
27
|
+
fileConfig(config.config_file_name)
|
|
28
|
+
|
|
29
|
+
# Add your model's MetaData object here for 'autogenerate' support
|
|
30
|
+
target_metadata = Base.metadata
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_database_url() -> str:
|
|
34
|
+
"""Get the database URL from application config."""
|
|
35
|
+
app_config = get_config()
|
|
36
|
+
return app_config.db.database_url
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def run_migrations_offline() -> None:
|
|
40
|
+
"""
|
|
41
|
+
Run migrations in 'offline' mode.
|
|
42
|
+
|
|
43
|
+
This configures the context with just a URL and not an Engine,
|
|
44
|
+
though an Engine is acceptable here as well.
|
|
45
|
+
"""
|
|
46
|
+
url = get_database_url()
|
|
47
|
+
context.configure(
|
|
48
|
+
url=url,
|
|
49
|
+
target_metadata=target_metadata,
|
|
50
|
+
literal_binds=True,
|
|
51
|
+
dialect_opts={"paramstyle": "named"},
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
with context.begin_transaction():
|
|
55
|
+
context.run_migrations()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def do_run_migrations(connection: Connection) -> None:
|
|
59
|
+
"""Run migrations with the given connection."""
|
|
60
|
+
context.configure(connection=connection, target_metadata=target_metadata)
|
|
61
|
+
|
|
62
|
+
with context.begin_transaction():
|
|
63
|
+
context.run_migrations()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
async def run_async_migrations() -> None:
|
|
67
|
+
"""
|
|
68
|
+
Run migrations in 'online' mode with async engine.
|
|
69
|
+
"""
|
|
70
|
+
configuration = config.get_section(config.config_ini_section)
|
|
71
|
+
configuration["sqlalchemy.url"] = get_database_url()
|
|
72
|
+
|
|
73
|
+
connectable = async_engine_from_config(
|
|
74
|
+
configuration,
|
|
75
|
+
prefix="sqlalchemy.",
|
|
76
|
+
poolclass=pool.NullPool,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
async with connectable.connect() as connection:
|
|
80
|
+
await connection.run_sync(do_run_migrations)
|
|
81
|
+
|
|
82
|
+
await connectable.dispose()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def run_migrations_online() -> None:
|
|
86
|
+
"""Run migrations in 'online' mode."""
|
|
87
|
+
asyncio.run(run_async_migrations())
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
if context.is_offline_mode():
|
|
91
|
+
run_migrations_offline()
|
|
92
|
+
else:
|
|
93
|
+
run_migrations_online()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""${message}
|
|
2
|
+
|
|
3
|
+
Revision ID: ${up_revision}
|
|
4
|
+
Revises: ${down_revision | comma,n}
|
|
5
|
+
Create Date: ${create_date}
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
from typing import Sequence, Union
|
|
9
|
+
|
|
10
|
+
from alembic import op
|
|
11
|
+
import sqlalchemy as sa
|
|
12
|
+
${imports if imports else ""}
|
|
13
|
+
|
|
14
|
+
# revision identifiers, used by Alembic.
|
|
15
|
+
revision: str = ${repr(up_revision)}
|
|
16
|
+
down_revision: Union[str, None] = ${repr(down_revision)}
|
|
17
|
+
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
|
|
18
|
+
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def upgrade() -> None:
|
|
22
|
+
${upgrades if upgrades else "pass"}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def downgrade() -> None:
|
|
26
|
+
${downgrades if downgrades else "pass"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Migration versions directory
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Alembic Configuration
|
|
2
|
+
|
|
3
|
+
A generic, single database configuration.
|
|
4
|
+
|
|
5
|
+
[alembic]
|
|
6
|
+
# Path to migration scripts
|
|
7
|
+
script_location = alembic
|
|
8
|
+
|
|
9
|
+
# Template used to generate migration file names
|
|
10
|
+
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
|
|
11
|
+
|
|
12
|
+
# sys.path path, will be prepended to sys.path if present
|
|
13
|
+
prepend_sys_path = .
|
|
14
|
+
|
|
15
|
+
# Timezone to use when rendering the date within the migration file
|
|
16
|
+
# timezone =
|
|
17
|
+
|
|
18
|
+
# Max length of characters to apply to the "slug" field
|
|
19
|
+
# truncate_slug_length = 40
|
|
20
|
+
|
|
21
|
+
# Set to 'true' to run the environment during the 'revision' command
|
|
22
|
+
# revision_environment = false
|
|
23
|
+
|
|
24
|
+
# Set to 'true' to allow .pyc and .pyo files without a source .py file
|
|
25
|
+
# sourceless = false
|
|
26
|
+
|
|
27
|
+
# Version path separator
|
|
28
|
+
version_path_separator = os
|
|
29
|
+
|
|
30
|
+
# Output encoding used when revision files are written from script.py.mako
|
|
31
|
+
# output_encoding = utf-8
|
|
32
|
+
|
|
33
|
+
# SQLAlchemy URL - This is set dynamically in env.py
|
|
34
|
+
sqlalchemy.url = driver://user:pass@localhost/dbname
|
|
35
|
+
|
|
36
|
+
[post_write_hooks]
|
|
37
|
+
|
|
38
|
+
[loggers]
|
|
39
|
+
keys = root,sqlalchemy,alembic
|
|
40
|
+
|
|
41
|
+
[handlers]
|
|
42
|
+
keys = console
|
|
43
|
+
|
|
44
|
+
[formatters]
|
|
45
|
+
keys = generic
|
|
46
|
+
|
|
47
|
+
[logger_root]
|
|
48
|
+
level = WARN
|
|
49
|
+
handlers = console
|
|
50
|
+
qualname =
|
|
51
|
+
|
|
52
|
+
[logger_sqlalchemy]
|
|
53
|
+
level = WARN
|
|
54
|
+
handlers =
|
|
55
|
+
qualname = sqlalchemy.engine
|
|
56
|
+
|
|
57
|
+
[logger_alembic]
|
|
58
|
+
level = INFO
|
|
59
|
+
handlers =
|
|
60
|
+
qualname = alembic
|
|
61
|
+
|
|
62
|
+
[handler_console]
|
|
63
|
+
class = StreamHandler
|
|
64
|
+
args = (sys.stderr,)
|
|
65
|
+
level = NOTSET
|
|
66
|
+
formatter = generic
|
|
67
|
+
|
|
68
|
+
[formatter_generic]
|
|
69
|
+
format = %(levelname)-5.5s [%(name)s] %(message)s
|
|
70
|
+
datefmt = %H:%M:%S
|