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,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Application Entry Point
|
|
3
|
+
|
|
4
|
+
This module provides the main entry point for running the application.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import uvicorn
|
|
8
|
+
|
|
9
|
+
from app.core.config import get_config
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main():
|
|
13
|
+
"""Run the application with uvicorn."""
|
|
14
|
+
config = get_config()
|
|
15
|
+
uvicorn.run(
|
|
16
|
+
config.app.uvicorn,
|
|
17
|
+
host=config.app.host,
|
|
18
|
+
port=config.app.port,
|
|
19
|
+
reload=config.app.reload,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
if __name__ == "__main__":
|
|
24
|
+
main()
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
API Schemas
|
|
3
|
+
|
|
4
|
+
This module defines common API response schemas.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MessageResponse(BaseModel):
|
|
11
|
+
"""Generic message response schema."""
|
|
12
|
+
|
|
13
|
+
message: str = Field(..., description="Response message")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class HealthResponse(BaseModel):
|
|
17
|
+
"""Health check response schema."""
|
|
18
|
+
|
|
19
|
+
status: str = Field(..., description="Health status")
|
|
20
|
+
version: str = Field(..., description="Application version")
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Authentication Schemas
|
|
3
|
+
|
|
4
|
+
This module defines Pydantic schemas for authentication.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TokenResponse(BaseModel):
|
|
11
|
+
"""JWT token response schema."""
|
|
12
|
+
|
|
13
|
+
access_token: str = Field(..., description="JWT access token")
|
|
14
|
+
token_type: str = Field(default="bearer", description="Token type")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class LoginRequest(BaseModel):
|
|
18
|
+
"""Login request schema."""
|
|
19
|
+
|
|
20
|
+
email: str = Field(..., description="User email")
|
|
21
|
+
password: str = Field(..., description="User password")
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastAPI Application Server
|
|
3
|
+
|
|
4
|
+
This module configures and creates the FastAPI application instance.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from contextlib import asynccontextmanager
|
|
8
|
+
|
|
9
|
+
from fastapi import FastAPI
|
|
10
|
+
|
|
11
|
+
from app.api.health_api import healthAPI
|
|
12
|
+
{% if use_auth %}
|
|
13
|
+
from app.api.auth_api import authAPI
|
|
14
|
+
{% endif %}
|
|
15
|
+
from app.core.config import get_config
|
|
16
|
+
{% if use_database %}
|
|
17
|
+
from app.core.database import DatabaseUtil
|
|
18
|
+
{% endif %}
|
|
19
|
+
{% if use_redis %}
|
|
20
|
+
from app.core.redis import RedisUtil
|
|
21
|
+
{% endif %}
|
|
22
|
+
from app.exceptions.handler import register_exception_handlers
|
|
23
|
+
from app.utils.log import log
|
|
24
|
+
|
|
25
|
+
config = get_config()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def init_services(app: FastAPI):
|
|
29
|
+
"""
|
|
30
|
+
Initialize application services.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
app: FastAPI application instance
|
|
34
|
+
"""
|
|
35
|
+
app.state.config = config
|
|
36
|
+
{% if use_database %}
|
|
37
|
+
app.state.db_engine = await DatabaseUtil.init_engine(config.db)
|
|
38
|
+
{% endif %}
|
|
39
|
+
{% if use_redis %}
|
|
40
|
+
app.state.redis = await RedisUtil.init_redis(config.redis)
|
|
41
|
+
{% endif %}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def close_services(app: FastAPI):
|
|
45
|
+
"""
|
|
46
|
+
Close application services.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
app: FastAPI application instance
|
|
50
|
+
"""
|
|
51
|
+
{% if use_database %}
|
|
52
|
+
await DatabaseUtil.close_engine(app.state.db_engine)
|
|
53
|
+
{% endif %}
|
|
54
|
+
{% if use_redis %}
|
|
55
|
+
await RedisUtil.close_redis(app.state.redis)
|
|
56
|
+
{% endif %}
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@asynccontextmanager
|
|
61
|
+
async def lifespan(app: FastAPI):
|
|
62
|
+
"""
|
|
63
|
+
Application lifespan manager.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
app: FastAPI application instance
|
|
67
|
+
"""
|
|
68
|
+
# Startup
|
|
69
|
+
await init_services(app)
|
|
70
|
+
log.info(f"{config.app.name} started successfully")
|
|
71
|
+
|
|
72
|
+
yield
|
|
73
|
+
|
|
74
|
+
# Shutdown
|
|
75
|
+
await close_services(app)
|
|
76
|
+
log.info(f"{config.app.name} shutdown complete")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# Create FastAPI application
|
|
80
|
+
app = FastAPI(
|
|
81
|
+
title=config.app.name,
|
|
82
|
+
description=config.app.description,
|
|
83
|
+
version=config.app.version,
|
|
84
|
+
lifespan=lifespan,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Register exception handlers
|
|
88
|
+
register_exception_handlers(app)
|
|
89
|
+
|
|
90
|
+
# Register routers
|
|
91
|
+
API_ROUTES = [
|
|
92
|
+
{"router": healthAPI, "tags": ["Health"]},
|
|
93
|
+
{% if use_auth %}
|
|
94
|
+
{"router": authAPI, "tags": ["Authentication"]},
|
|
95
|
+
{% endif %}
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
for route in API_ROUTES:
|
|
99
|
+
app.include_router(router=route["router"], tags=route["tags"])
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Logging Utilities
|
|
3
|
+
|
|
4
|
+
This module provides logging configuration and utilities.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
# Configure logging
|
|
11
|
+
logging.basicConfig(
|
|
12
|
+
level=logging.INFO,
|
|
13
|
+
format="%(asctime)s - %(levelname)s - %(message)s",
|
|
14
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
15
|
+
handlers=[
|
|
16
|
+
logging.StreamHandler(sys.stdout),
|
|
17
|
+
],
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Create logger instance
|
|
21
|
+
log = logging.getLogger("{{ project_slug }}")
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
env: dev
|
|
2
|
+
|
|
3
|
+
# Development environment
|
|
4
|
+
dev:
|
|
5
|
+
app:
|
|
6
|
+
name: {{ project_name }} (dev)
|
|
7
|
+
description: {{ project_description }}
|
|
8
|
+
api: /api
|
|
9
|
+
host: 0.0.0.0
|
|
10
|
+
port: 8000
|
|
11
|
+
uvicorn: app.server:app
|
|
12
|
+
version: 0.1.0
|
|
13
|
+
reload: true
|
|
14
|
+
|
|
15
|
+
{% if use_database %}
|
|
16
|
+
db:
|
|
17
|
+
{% if use_postgres %}
|
|
18
|
+
host: localhost
|
|
19
|
+
port: 5432
|
|
20
|
+
user: postgres
|
|
21
|
+
password: postgres
|
|
22
|
+
database: {{ project_slug }}
|
|
23
|
+
driver_name: postgresql+asyncpg
|
|
24
|
+
{% elif use_mysql %}
|
|
25
|
+
host: localhost
|
|
26
|
+
port: 3306
|
|
27
|
+
user: root
|
|
28
|
+
password: root
|
|
29
|
+
database: {{ project_slug }}
|
|
30
|
+
driver_name: mysql+aiomysql
|
|
31
|
+
{% elif use_sqlite %}
|
|
32
|
+
host: ""
|
|
33
|
+
port: 0
|
|
34
|
+
user: ""
|
|
35
|
+
password: ""
|
|
36
|
+
database: {{ project_slug }}.db
|
|
37
|
+
driver_name: sqlite+aiosqlite
|
|
38
|
+
{% endif %}
|
|
39
|
+
echo: true
|
|
40
|
+
pool_size: 5
|
|
41
|
+
max_overflow: 10
|
|
42
|
+
pool_recycle: 3600
|
|
43
|
+
pool_timeout: 30
|
|
44
|
+
{% endif %}
|
|
45
|
+
|
|
46
|
+
{% if use_redis %}
|
|
47
|
+
redis:
|
|
48
|
+
host: localhost
|
|
49
|
+
port: 6379
|
|
50
|
+
db: 0
|
|
51
|
+
password: ""
|
|
52
|
+
{% endif %}
|
|
53
|
+
|
|
54
|
+
{% if use_auth %}
|
|
55
|
+
jwt:
|
|
56
|
+
secret_key: your-secret-key-change-in-production
|
|
57
|
+
algorithm: HS256
|
|
58
|
+
expire_minutes: 1440
|
|
59
|
+
{% endif %}
|
|
60
|
+
|
|
61
|
+
# Production environment
|
|
62
|
+
prod:
|
|
63
|
+
app:
|
|
64
|
+
name: {{ project_name }}
|
|
65
|
+
reload: false
|
|
66
|
+
|
|
67
|
+
{% if use_database %}
|
|
68
|
+
db:
|
|
69
|
+
echo: false
|
|
70
|
+
pool_size: 20
|
|
71
|
+
{% endif %}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
services:
|
|
2
|
+
app:
|
|
3
|
+
build:
|
|
4
|
+
context: .
|
|
5
|
+
dockerfile: Dockerfile
|
|
6
|
+
ports:
|
|
7
|
+
- "8000:8000"
|
|
8
|
+
environment:
|
|
9
|
+
- PYTHONPATH=/app
|
|
10
|
+
{% if use_database or use_redis %}
|
|
11
|
+
depends_on:
|
|
12
|
+
{% if "postgres" in docker_services %}
|
|
13
|
+
- postgres
|
|
14
|
+
{% endif %}
|
|
15
|
+
{% if "mysql" in docker_services %}
|
|
16
|
+
- mysql
|
|
17
|
+
{% endif %}
|
|
18
|
+
{% if "redis" in docker_services %}
|
|
19
|
+
- redis
|
|
20
|
+
{% endif %}
|
|
21
|
+
{% endif %}
|
|
22
|
+
volumes:
|
|
23
|
+
- .:/app
|
|
24
|
+
command: python -m app.main
|
|
25
|
+
{% if use_database or use_redis %}
|
|
26
|
+
networks:
|
|
27
|
+
- app-network
|
|
28
|
+
{% endif %}
|
|
29
|
+
|
|
30
|
+
{% if "postgres" in docker_services %}
|
|
31
|
+
postgres:
|
|
32
|
+
image: postgres:16-alpine
|
|
33
|
+
environment:
|
|
34
|
+
POSTGRES_USER: postgres
|
|
35
|
+
POSTGRES_PASSWORD: postgres
|
|
36
|
+
POSTGRES_DB: {{ project_slug }}
|
|
37
|
+
ports:
|
|
38
|
+
- "5432:5432"
|
|
39
|
+
volumes:
|
|
40
|
+
- postgres_data:/var/lib/postgresql/data
|
|
41
|
+
networks:
|
|
42
|
+
- app-network
|
|
43
|
+
{% endif %}
|
|
44
|
+
|
|
45
|
+
{% if "mysql" in docker_services %}
|
|
46
|
+
mysql:
|
|
47
|
+
image: mysql:8
|
|
48
|
+
environment:
|
|
49
|
+
MYSQL_ROOT_PASSWORD: root
|
|
50
|
+
MYSQL_DATABASE: {{ project_slug }}
|
|
51
|
+
ports:
|
|
52
|
+
- "3306:3306"
|
|
53
|
+
volumes:
|
|
54
|
+
- mysql_data:/var/lib/mysql
|
|
55
|
+
networks:
|
|
56
|
+
- app-network
|
|
57
|
+
{% endif %}
|
|
58
|
+
|
|
59
|
+
{% if "redis" in docker_services %}
|
|
60
|
+
redis:
|
|
61
|
+
image: redis:alpine
|
|
62
|
+
ports:
|
|
63
|
+
- "6379:6379"
|
|
64
|
+
networks:
|
|
65
|
+
- app-network
|
|
66
|
+
{% endif %}
|
|
67
|
+
|
|
68
|
+
{% if "rabbitmq" in docker_services %}
|
|
69
|
+
rabbitmq:
|
|
70
|
+
image: rabbitmq:3-management
|
|
71
|
+
ports:
|
|
72
|
+
- "5672:5672"
|
|
73
|
+
- "15672:15672"
|
|
74
|
+
environment:
|
|
75
|
+
RABBITMQ_DEFAULT_USER: admin
|
|
76
|
+
RABBITMQ_DEFAULT_PASS: admin
|
|
77
|
+
volumes:
|
|
78
|
+
- rabbitmq_data:/var/lib/rabbitmq
|
|
79
|
+
networks:
|
|
80
|
+
- app-network
|
|
81
|
+
{% endif %}
|
|
82
|
+
|
|
83
|
+
{% if "minio" in docker_services %}
|
|
84
|
+
minio:
|
|
85
|
+
image: minio/minio
|
|
86
|
+
command: server /data --console-address ":9001"
|
|
87
|
+
ports:
|
|
88
|
+
- "9000:9000"
|
|
89
|
+
- "9001:9001"
|
|
90
|
+
environment:
|
|
91
|
+
MINIO_ROOT_USER: minioadmin
|
|
92
|
+
MINIO_ROOT_PASSWORD: minioadmin
|
|
93
|
+
volumes:
|
|
94
|
+
- minio_data:/data
|
|
95
|
+
networks:
|
|
96
|
+
- app-network
|
|
97
|
+
{% endif %}
|
|
98
|
+
|
|
99
|
+
{% if use_database or use_redis %}
|
|
100
|
+
volumes:
|
|
101
|
+
{% if "postgres" in docker_services %}
|
|
102
|
+
postgres_data:
|
|
103
|
+
{% endif %}
|
|
104
|
+
{% if "mysql" in docker_services %}
|
|
105
|
+
mysql_data:
|
|
106
|
+
{% endif %}
|
|
107
|
+
{% if "rabbitmq" in docker_services %}
|
|
108
|
+
rabbitmq_data:
|
|
109
|
+
{% endif %}
|
|
110
|
+
{% if "minio" in docker_services %}
|
|
111
|
+
minio_data:
|
|
112
|
+
{% endif %}
|
|
113
|
+
|
|
114
|
+
networks:
|
|
115
|
+
app-network:
|
|
116
|
+
driver: bridge
|
|
117
|
+
{% endif %}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "{{ project_slug }}"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "{{ project_description }}"
|
|
5
|
+
authors = [
|
|
6
|
+
{ name = "{{ author_name }}", email = "{{ author_email }}" }
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">={{ python_version }}"
|
|
10
|
+
|
|
11
|
+
dependencies = [
|
|
12
|
+
"fastapi[standard]>=0.115.0",
|
|
13
|
+
"uvicorn>=0.34.0",
|
|
14
|
+
"pyyaml>=6.0.0",
|
|
15
|
+
"pydantic>=2.0.0",
|
|
16
|
+
"pydantic-settings>=2.0.0",
|
|
17
|
+
{% if use_database %}
|
|
18
|
+
"sqlalchemy[asyncio]>=2.0.0",
|
|
19
|
+
{% endif %}
|
|
20
|
+
{% if use_postgres %}
|
|
21
|
+
"asyncpg>=0.29.0",
|
|
22
|
+
"psycopg[binary]>=3.2.0",
|
|
23
|
+
{% elif use_mysql %}
|
|
24
|
+
"aiomysql>=0.2.0",
|
|
25
|
+
{% elif use_sqlite %}
|
|
26
|
+
"aiosqlite>=0.20.0",
|
|
27
|
+
{% endif %}
|
|
28
|
+
{% if use_alembic %}
|
|
29
|
+
"alembic>=1.15.0",
|
|
30
|
+
{% endif %}
|
|
31
|
+
{% if use_redis %}
|
|
32
|
+
"redis>=5.2.0",
|
|
33
|
+
{% endif %}
|
|
34
|
+
{% if use_auth %}
|
|
35
|
+
"pyjwt>=2.10.0",
|
|
36
|
+
"passlib[bcrypt]>=1.7.4",
|
|
37
|
+
"bcrypt==4.0.1",
|
|
38
|
+
{% endif %}
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[dependency-groups]
|
|
42
|
+
dev = [
|
|
43
|
+
"pytest>=8.3.0",
|
|
44
|
+
"pytest-asyncio>=0.26.0",
|
|
45
|
+
{% if use_ruff %}
|
|
46
|
+
"ruff>=0.11.0",
|
|
47
|
+
{% endif %}
|
|
48
|
+
{% if use_redis %}
|
|
49
|
+
"fakeredis>=2.27.0",
|
|
50
|
+
{% endif %}
|
|
51
|
+
{% if use_postgres %}
|
|
52
|
+
"pytest-postgresql>=4.1.0",
|
|
53
|
+
{% endif %}
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
[project.scripts]
|
|
57
|
+
start = "app.main:main"
|
|
58
|
+
|
|
59
|
+
[build-system]
|
|
60
|
+
requires = ["hatchling"]
|
|
61
|
+
build-backend = "hatchling.build"
|
|
62
|
+
|
|
63
|
+
[tool.hatch.build.targets.wheel]
|
|
64
|
+
packages = ["app"]
|
|
65
|
+
{% if use_ruff %}
|
|
66
|
+
|
|
67
|
+
[tool.ruff]
|
|
68
|
+
line-length = 88
|
|
69
|
+
indent-width = 4
|
|
70
|
+
target-version = "py{{ python_version | replace('.', '') }}"
|
|
71
|
+
|
|
72
|
+
[tool.ruff.lint]
|
|
73
|
+
select = ["E", "W", "F", "I", "B", "C4", "UP"]
|
|
74
|
+
ignore = ["E501"]
|
|
75
|
+
|
|
76
|
+
[tool.ruff.format]
|
|
77
|
+
quote-style = "double"
|
|
78
|
+
indent-style = "space"
|
|
79
|
+
{% endif %}
|
|
80
|
+
|
|
81
|
+
[tool.pytest.ini_options]
|
|
82
|
+
testpaths = ["tests"]
|
|
83
|
+
python_files = ["test_*.py"]
|
|
84
|
+
python_functions = ["test_*"]
|
|
85
|
+
asyncio_mode = "auto"
|
|
86
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Ruff Configuration
|
|
2
|
+
# https://docs.astral.sh/ruff/
|
|
3
|
+
|
|
4
|
+
line-length = 120
|
|
5
|
+
target-version = "py310"
|
|
6
|
+
|
|
7
|
+
[lint]
|
|
8
|
+
select = [
|
|
9
|
+
"E", # pycodestyle errors
|
|
10
|
+
"W", # pycodestyle warnings
|
|
11
|
+
"F", # pyflakes
|
|
12
|
+
"I", # isort
|
|
13
|
+
"B", # flake8-bugbear
|
|
14
|
+
"C4", # flake8-comprehensions
|
|
15
|
+
"UP", # pyupgrade
|
|
16
|
+
"ARG", # flake8-unused-arguments
|
|
17
|
+
"SIM", # flake8-simplify
|
|
18
|
+
]
|
|
19
|
+
ignore = [
|
|
20
|
+
"E501", # line too long (handled by formatter)
|
|
21
|
+
"B008", # do not perform function calls in argument defaults
|
|
22
|
+
"B904", # raise without from inside except
|
|
23
|
+
"ARG001", # unused function argument
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[lint.isort]
|
|
27
|
+
known-first-party = ["app"]
|
|
28
|
+
force-single-line = false
|
|
29
|
+
lines-after-imports = 2
|
|
30
|
+
|
|
31
|
+
[lint.per-file-ignores]
|
|
32
|
+
"tests/*" = ["ARG001", "S101"]
|
|
33
|
+
"alembic/*" = ["ARG001"]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pytest Configuration and Fixtures
|
|
3
|
+
|
|
4
|
+
This module provides test fixtures and configuration for pytest.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
from fastapi.testclient import TestClient
|
|
9
|
+
{% if use_redis %}
|
|
10
|
+
import fakeredis.aioredis
|
|
11
|
+
{% endif %}
|
|
12
|
+
|
|
13
|
+
from app.server import app
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.fixture
|
|
17
|
+
def client():
|
|
18
|
+
"""Create a test client for the FastAPI application."""
|
|
19
|
+
with TestClient(app) as test_client:
|
|
20
|
+
yield test_client
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
{% if use_redis %}
|
|
24
|
+
@pytest.fixture
|
|
25
|
+
async def fake_redis():
|
|
26
|
+
"""Create a fake Redis instance for testing."""
|
|
27
|
+
fake_redis = fakeredis.aioredis.FakeRedis(decode_responses=True)
|
|
28
|
+
yield fake_redis
|
|
29
|
+
await fake_redis.flushall()
|
|
30
|
+
await fake_redis.aclose()
|
|
31
|
+
{% endif %}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Health API Tests
|
|
3
|
+
|
|
4
|
+
This module contains tests for the health check endpoints.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_health_check(client):
|
|
9
|
+
"""Test the health check endpoint returns healthy status."""
|
|
10
|
+
response = client.get("/api/health")
|
|
11
|
+
assert response.status_code == 200
|
|
12
|
+
|
|
13
|
+
data = response.json()
|
|
14
|
+
assert data["status"] == "healthy"
|
|
15
|
+
assert "version" in data
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_readiness_check(client):
|
|
19
|
+
"""Test the readiness check endpoint returns ready status."""
|
|
20
|
+
response = client.get("/api/health/ready")
|
|
21
|
+
assert response.status_code == 200
|
|
22
|
+
|
|
23
|
+
data = response.json()
|
|
24
|
+
assert data["status"] == "ready"
|