FastAPI-fastkit 1.1.4__py3-none-any.whl → 1.2.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_fastkit/__init__.py +1 -1
- fastapi_fastkit/backend/inspector.py +170 -12
- fastapi_fastkit/backend/interactive/__init__.py +51 -0
- fastapi_fastkit/backend/interactive/config_builder.py +186 -0
- fastapi_fastkit/backend/interactive/prompts.py +528 -0
- fastapi_fastkit/backend/interactive/selectors.py +241 -0
- fastapi_fastkit/backend/interactive/validators.py +163 -0
- fastapi_fastkit/backend/main.py +77 -0
- fastapi_fastkit/backend/package_managers/pdm_manager.py +6 -2
- fastapi_fastkit/backend/package_managers/pip_manager.py +6 -2
- fastapi_fastkit/backend/package_managers/poetry_manager.py +6 -2
- fastapi_fastkit/backend/package_managers/uv_manager.py +6 -2
- fastapi_fastkit/backend/project_builder/__init__.py +17 -0
- fastapi_fastkit/backend/project_builder/config_generator.py +594 -0
- fastapi_fastkit/backend/project_builder/dependency_collector.py +210 -0
- fastapi_fastkit/cli.py +218 -20
- fastapi_fastkit/core/settings.py +74 -0
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/pyproject.toml-tpl +2 -1
- fastapi_fastkit/fastapi_project_template/fastapi-async-crud/requirements.txt-tpl +1 -0
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/pyproject.toml-tpl +2 -1
- fastapi_fastkit/fastapi_project_template/fastapi-custom-response/requirements.txt-tpl +1 -0
- fastapi_fastkit/fastapi_project_template/fastapi-default/pyproject.toml-tpl +2 -1
- fastapi_fastkit/fastapi_project_template/fastapi-default/requirements.txt-tpl +1 -0
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/pyproject.toml-tpl +2 -1
- fastapi_fastkit/fastapi_project_template/fastapi-dockerized/requirements.txt-tpl +1 -0
- fastapi_fastkit/fastapi_project_template/fastapi-empty/pyproject.toml-tpl +2 -1
- fastapi_fastkit/fastapi_project_template/fastapi-empty/requirements.txt-tpl +1 -0
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/pyproject.toml-tpl +2 -1
- fastapi_fastkit/fastapi_project_template/fastapi-mcp/requirements.txt-tpl +1 -0
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/pyproject.toml-tpl +2 -1
- fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/requirements.txt-tpl +1 -0
- fastapi_fastkit/fastapi_project_template/fastapi-single-module/pyproject.toml-tpl +2 -1
- fastapi_fastkit/fastapi_project_template/fastapi-single-module/requirements.txt-tpl +1 -0
- fastapi_fastkit/utils/main.py +121 -10
- {fastapi_fastkit-1.1.4.dist-info → fastapi_fastkit-1.2.0.dist-info}/METADATA +9 -6
- {fastapi_fastkit-1.1.4.dist-info → fastapi_fastkit-1.2.0.dist-info}/RECORD +39 -31
- {fastapi_fastkit-1.1.4.dist-info → fastapi_fastkit-1.2.0.dist-info}/WHEEL +1 -1
- {fastapi_fastkit-1.1.4.dist-info → fastapi_fastkit-1.2.0.dist-info}/entry_points.txt +0 -0
- {fastapi_fastkit-1.1.4.dist-info → fastapi_fastkit-1.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
# --------------------------------------------------------------------------
|
|
2
|
+
# Dynamic configuration file generation based on user selections
|
|
3
|
+
#
|
|
4
|
+
# Generates:
|
|
5
|
+
# - main.py with selected features
|
|
6
|
+
# - Database configuration files
|
|
7
|
+
# - Authentication setup files
|
|
8
|
+
# - Docker files
|
|
9
|
+
# - Testing configuration
|
|
10
|
+
#
|
|
11
|
+
# @author bnbong bbbong9@gmail.com
|
|
12
|
+
# --------------------------------------------------------------------------
|
|
13
|
+
import os
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, Dict, List, Optional
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DynamicConfigGenerator:
|
|
19
|
+
"""
|
|
20
|
+
Generates configuration files based on project config.
|
|
21
|
+
|
|
22
|
+
This class generates various configuration and setup files
|
|
23
|
+
dynamically based on the user's feature selections.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, config: Dict[str, Any], project_dir: str) -> None:
|
|
27
|
+
"""
|
|
28
|
+
Initialize config generator.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
config: Project configuration dictionary
|
|
32
|
+
project_dir: Path to the project directory
|
|
33
|
+
"""
|
|
34
|
+
self.config = config
|
|
35
|
+
self.project_dir = Path(project_dir)
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def _build_header(title: str) -> list[str]:
|
|
39
|
+
"""
|
|
40
|
+
Build a standard FastAPI-fastkit header comment.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
title: Title of the file
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
List of header lines
|
|
47
|
+
"""
|
|
48
|
+
return [
|
|
49
|
+
"# --------------------------------------------------------------------------",
|
|
50
|
+
f"# {title}",
|
|
51
|
+
"# Generated by FastAPI-fastkit",
|
|
52
|
+
"# --------------------------------------------------------------------------",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def _join_lines(lines: list[str]) -> str:
|
|
57
|
+
"""
|
|
58
|
+
Join lines with newline and ensure proper ending.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
lines: List of code lines
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Joined string with newlines
|
|
65
|
+
"""
|
|
66
|
+
return "\n".join(lines) + "\n"
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def _build_code_block(indent: str, *lines: str) -> list[str]:
|
|
70
|
+
"""
|
|
71
|
+
Build a code block with consistent indentation.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
indent: Indentation string (e.g., " " for 4 spaces)
|
|
75
|
+
lines: Lines of code to indent
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
List of indented lines
|
|
79
|
+
"""
|
|
80
|
+
return [f"{indent}{line}" if line else "" for line in lines]
|
|
81
|
+
|
|
82
|
+
def generate_all_files(self) -> None:
|
|
83
|
+
"""Generate all configuration files."""
|
|
84
|
+
# This method will be called by the main CLI
|
|
85
|
+
# Individual file generation will be handled by specific methods
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
def generate_main_py(self) -> str:
|
|
89
|
+
"""
|
|
90
|
+
Generate main.py with selected features.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Content of main.py as string
|
|
94
|
+
"""
|
|
95
|
+
imports = []
|
|
96
|
+
app_config = []
|
|
97
|
+
startup_code = []
|
|
98
|
+
middleware_code = []
|
|
99
|
+
route_includes: List[str] = []
|
|
100
|
+
|
|
101
|
+
# Base imports
|
|
102
|
+
imports.append("from fastapi import FastAPI")
|
|
103
|
+
imports.append("from fastapi.middleware.cors import CORSMiddleware")
|
|
104
|
+
|
|
105
|
+
# Database imports
|
|
106
|
+
db_type = self.config.get("database", {}).get("type", "None")
|
|
107
|
+
if db_type in ["PostgreSQL", "MySQL", "SQLite"]:
|
|
108
|
+
imports.append(
|
|
109
|
+
"from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession"
|
|
110
|
+
)
|
|
111
|
+
imports.append("from sqlalchemy.orm import sessionmaker")
|
|
112
|
+
startup_code.append(" # Initialize database")
|
|
113
|
+
startup_code.append(" # await init_db()")
|
|
114
|
+
elif db_type == "MongoDB":
|
|
115
|
+
imports.append("from motor.motor_asyncio import AsyncIOMotorClient")
|
|
116
|
+
startup_code.append(" # Initialize MongoDB")
|
|
117
|
+
startup_code.append(
|
|
118
|
+
" # client = AsyncIOMotorClient(settings.MONGODB_URL)"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Authentication imports
|
|
122
|
+
auth_type = self.config.get("authentication", "None")
|
|
123
|
+
if auth_type == "JWT":
|
|
124
|
+
imports.append("from fastapi.security import HTTPBearer")
|
|
125
|
+
elif auth_type == "FastAPI-Users":
|
|
126
|
+
imports.append("# FastAPI-Users setup will be in separate auth module")
|
|
127
|
+
|
|
128
|
+
# CORS configuration
|
|
129
|
+
utilities = self.config.get("utilities", [])
|
|
130
|
+
if "CORS" in utilities:
|
|
131
|
+
middleware_code.append("# CORS middleware")
|
|
132
|
+
middleware_code.append("app.add_middleware(")
|
|
133
|
+
middleware_code.append(" CORSMiddleware,")
|
|
134
|
+
middleware_code.append(
|
|
135
|
+
" allow_origins=['*'], # Configure appropriately"
|
|
136
|
+
)
|
|
137
|
+
middleware_code.append(" allow_credentials=True,")
|
|
138
|
+
middleware_code.append(" allow_methods=['*'],")
|
|
139
|
+
middleware_code.append(" allow_headers=['*'],")
|
|
140
|
+
middleware_code.append(")")
|
|
141
|
+
|
|
142
|
+
# Rate limiting
|
|
143
|
+
if "Rate-Limiting" in utilities:
|
|
144
|
+
imports.append("from slowapi import Limiter, _rate_limit_exceeded_handler")
|
|
145
|
+
imports.append("from slowapi.util import get_remote_address")
|
|
146
|
+
imports.append("from slowapi.errors import RateLimitExceeded")
|
|
147
|
+
app_config.append("limiter = Limiter(key_func=get_remote_address)")
|
|
148
|
+
app_config.append("app.state.limiter = limiter")
|
|
149
|
+
app_config.append(
|
|
150
|
+
"app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Monitoring
|
|
154
|
+
monitoring_type = self.config.get("monitoring", "None")
|
|
155
|
+
if monitoring_type == "Loguru":
|
|
156
|
+
imports.append("from loguru import logger")
|
|
157
|
+
elif monitoring_type == "Prometheus":
|
|
158
|
+
imports.append(
|
|
159
|
+
"from prometheus_fastapi_instrumentator import Instrumentator"
|
|
160
|
+
)
|
|
161
|
+
startup_code.append(" # Initialize Prometheus metrics")
|
|
162
|
+
startup_code.append(" Instrumentator().instrument(app).expose(app)")
|
|
163
|
+
|
|
164
|
+
# Build main.py content
|
|
165
|
+
content_parts = []
|
|
166
|
+
|
|
167
|
+
# Imports section
|
|
168
|
+
content_parts.append(
|
|
169
|
+
"# --------------------------------------------------------------------------"
|
|
170
|
+
)
|
|
171
|
+
content_parts.append("# FastAPI Application Main Entry Point")
|
|
172
|
+
content_parts.append("# Generated by FastAPI-fastkit")
|
|
173
|
+
content_parts.append(
|
|
174
|
+
"# --------------------------------------------------------------------------"
|
|
175
|
+
)
|
|
176
|
+
content_parts.extend(imports)
|
|
177
|
+
content_parts.append("")
|
|
178
|
+
|
|
179
|
+
# App initialization
|
|
180
|
+
project_name = self.config.get("project_name", "FastAPI App")
|
|
181
|
+
description = self.config.get("description", "")
|
|
182
|
+
content_parts.append(f"app = FastAPI(")
|
|
183
|
+
content_parts.append(f' title="{project_name}",')
|
|
184
|
+
content_parts.append(f' description="{description}",')
|
|
185
|
+
content_parts.append(f' version="0.1.0",')
|
|
186
|
+
content_parts.append(f")")
|
|
187
|
+
content_parts.append("")
|
|
188
|
+
|
|
189
|
+
# App configuration
|
|
190
|
+
if app_config:
|
|
191
|
+
content_parts.extend(app_config)
|
|
192
|
+
content_parts.append("")
|
|
193
|
+
|
|
194
|
+
# Middleware
|
|
195
|
+
if middleware_code:
|
|
196
|
+
content_parts.extend(middleware_code)
|
|
197
|
+
content_parts.append("")
|
|
198
|
+
|
|
199
|
+
# Startup event
|
|
200
|
+
if startup_code:
|
|
201
|
+
content_parts.append("@app.on_event('startup')")
|
|
202
|
+
content_parts.append("async def startup_event():")
|
|
203
|
+
content_parts.extend(startup_code)
|
|
204
|
+
content_parts.append("")
|
|
205
|
+
|
|
206
|
+
# Basic health check endpoint
|
|
207
|
+
content_parts.append("@app.get('/', tags=['Health'])")
|
|
208
|
+
content_parts.append("async def root():")
|
|
209
|
+
content_parts.append(" return {")
|
|
210
|
+
content_parts.append(f" 'message': 'Welcome to {project_name}',")
|
|
211
|
+
content_parts.append(" 'status': 'healthy',")
|
|
212
|
+
content_parts.append(" }")
|
|
213
|
+
content_parts.append("")
|
|
214
|
+
|
|
215
|
+
content_parts.append("@app.get('/health', tags=['Health'])")
|
|
216
|
+
content_parts.append("async def health_check():")
|
|
217
|
+
content_parts.append(" return {'status': 'ok'}")
|
|
218
|
+
content_parts.append("")
|
|
219
|
+
|
|
220
|
+
# Route includes (placeholder)
|
|
221
|
+
if route_includes:
|
|
222
|
+
content_parts.extend(route_includes)
|
|
223
|
+
content_parts.append("")
|
|
224
|
+
|
|
225
|
+
return "\n".join(content_parts)
|
|
226
|
+
|
|
227
|
+
def generate_database_config(self) -> Optional[str]:
|
|
228
|
+
"""
|
|
229
|
+
Generate database configuration.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
Content of database config file or None
|
|
233
|
+
"""
|
|
234
|
+
db_type = self.config.get("database", {}).get("type", "None")
|
|
235
|
+
|
|
236
|
+
if db_type == "None":
|
|
237
|
+
return None
|
|
238
|
+
|
|
239
|
+
if db_type in ["PostgreSQL", "MySQL", "SQLite"]:
|
|
240
|
+
return self._generate_sqlalchemy_config(db_type)
|
|
241
|
+
elif db_type == "MongoDB":
|
|
242
|
+
return self._generate_mongodb_config()
|
|
243
|
+
|
|
244
|
+
return None
|
|
245
|
+
|
|
246
|
+
def _generate_sqlalchemy_config(self, db_type: str) -> str:
|
|
247
|
+
"""Generate SQLAlchemy database configuration."""
|
|
248
|
+
content = []
|
|
249
|
+
content.append(
|
|
250
|
+
"# --------------------------------------------------------------------------"
|
|
251
|
+
)
|
|
252
|
+
content.append("# Database Configuration")
|
|
253
|
+
content.append("# Generated by FastAPI-fastkit")
|
|
254
|
+
content.append(
|
|
255
|
+
"# --------------------------------------------------------------------------"
|
|
256
|
+
)
|
|
257
|
+
content.append(
|
|
258
|
+
"from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession"
|
|
259
|
+
)
|
|
260
|
+
content.append("from sqlalchemy.orm import sessionmaker, declarative_base")
|
|
261
|
+
content.append("from pydantic_settings import BaseSettings")
|
|
262
|
+
content.append("")
|
|
263
|
+
content.append("class DatabaseSettings(BaseSettings):")
|
|
264
|
+
content.append(' """Database configuration settings."""')
|
|
265
|
+
|
|
266
|
+
if db_type == "PostgreSQL":
|
|
267
|
+
content.append(
|
|
268
|
+
" DATABASE_URL: str = 'postgresql+asyncpg://user:password@localhost/dbname'"
|
|
269
|
+
)
|
|
270
|
+
elif db_type == "MySQL":
|
|
271
|
+
content.append(
|
|
272
|
+
" DATABASE_URL: str = 'mysql+aiomysql://user:password@localhost/dbname'"
|
|
273
|
+
)
|
|
274
|
+
elif db_type == "SQLite":
|
|
275
|
+
content.append(" DATABASE_URL: str = 'sqlite+aiosqlite:///./app.db'")
|
|
276
|
+
|
|
277
|
+
content.append("")
|
|
278
|
+
content.append(" class Config:")
|
|
279
|
+
content.append(" env_file = '.env'")
|
|
280
|
+
content.append("")
|
|
281
|
+
content.append("settings = DatabaseSettings()")
|
|
282
|
+
content.append("")
|
|
283
|
+
content.append("# Create async engine")
|
|
284
|
+
content.append("engine = create_async_engine(")
|
|
285
|
+
content.append(" settings.DATABASE_URL,")
|
|
286
|
+
content.append(" echo=True,")
|
|
287
|
+
content.append(" future=True,")
|
|
288
|
+
content.append(")")
|
|
289
|
+
content.append("")
|
|
290
|
+
content.append("# Create async session factory")
|
|
291
|
+
content.append("AsyncSessionLocal = sessionmaker(")
|
|
292
|
+
content.append(" engine,")
|
|
293
|
+
content.append(" class_=AsyncSession,")
|
|
294
|
+
content.append(" expire_on_commit=False,")
|
|
295
|
+
content.append(")")
|
|
296
|
+
content.append("")
|
|
297
|
+
content.append("# Create base class for models")
|
|
298
|
+
content.append("Base = declarative_base()")
|
|
299
|
+
content.append("")
|
|
300
|
+
content.append("async def get_db():")
|
|
301
|
+
content.append(' """Dependency for getting async database session."""')
|
|
302
|
+
content.append(" async with AsyncSessionLocal() as session:")
|
|
303
|
+
content.append(" try:")
|
|
304
|
+
content.append(" yield session")
|
|
305
|
+
content.append(" await session.commit()")
|
|
306
|
+
content.append(" except Exception:")
|
|
307
|
+
content.append(" await session.rollback()")
|
|
308
|
+
content.append(" raise")
|
|
309
|
+
content.append("")
|
|
310
|
+
|
|
311
|
+
return "\n".join(content)
|
|
312
|
+
|
|
313
|
+
def _generate_mongodb_config(self) -> str:
|
|
314
|
+
"""Generate MongoDB configuration."""
|
|
315
|
+
content = []
|
|
316
|
+
content.append(
|
|
317
|
+
"# --------------------------------------------------------------------------"
|
|
318
|
+
)
|
|
319
|
+
content.append("# MongoDB Configuration")
|
|
320
|
+
content.append("# Generated by FastAPI-fastkit")
|
|
321
|
+
content.append(
|
|
322
|
+
"# --------------------------------------------------------------------------"
|
|
323
|
+
)
|
|
324
|
+
content.append("from motor.motor_asyncio import AsyncIOMotorClient")
|
|
325
|
+
content.append("from pydantic_settings import BaseSettings")
|
|
326
|
+
content.append("")
|
|
327
|
+
content.append("class DatabaseSettings(BaseSettings):")
|
|
328
|
+
content.append(' """MongoDB configuration settings."""')
|
|
329
|
+
content.append(" MONGODB_URL: str = 'mongodb://localhost:27017'")
|
|
330
|
+
content.append(" MONGODB_DB_NAME: str = 'fastapi_db'")
|
|
331
|
+
content.append("")
|
|
332
|
+
content.append(" class Config:")
|
|
333
|
+
content.append(" env_file = '.env'")
|
|
334
|
+
content.append("")
|
|
335
|
+
content.append("settings = DatabaseSettings()")
|
|
336
|
+
content.append("")
|
|
337
|
+
content.append("# MongoDB client")
|
|
338
|
+
content.append("client = AsyncIOMotorClient(settings.MONGODB_URL)")
|
|
339
|
+
content.append("database = client[settings.MONGODB_DB_NAME]")
|
|
340
|
+
content.append("")
|
|
341
|
+
|
|
342
|
+
return "\n".join(content)
|
|
343
|
+
|
|
344
|
+
def generate_auth_config(self) -> Optional[str]:
|
|
345
|
+
"""
|
|
346
|
+
Generate authentication configuration.
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
Content of auth config file or None
|
|
350
|
+
"""
|
|
351
|
+
auth_type = self.config.get("authentication", "None")
|
|
352
|
+
|
|
353
|
+
if auth_type == "None":
|
|
354
|
+
return None
|
|
355
|
+
|
|
356
|
+
if auth_type == "JWT":
|
|
357
|
+
return self._generate_jwt_config()
|
|
358
|
+
elif auth_type == "FastAPI-Users":
|
|
359
|
+
return self._generate_fastapi_users_config()
|
|
360
|
+
|
|
361
|
+
return None
|
|
362
|
+
|
|
363
|
+
def _generate_jwt_config(self) -> str:
|
|
364
|
+
"""Generate JWT authentication configuration."""
|
|
365
|
+
content = []
|
|
366
|
+
content.append(
|
|
367
|
+
"# --------------------------------------------------------------------------"
|
|
368
|
+
)
|
|
369
|
+
content.append("# JWT Authentication Configuration")
|
|
370
|
+
content.append("# Generated by FastAPI-fastkit")
|
|
371
|
+
content.append(
|
|
372
|
+
"# --------------------------------------------------------------------------"
|
|
373
|
+
)
|
|
374
|
+
content.append("from datetime import datetime, timedelta")
|
|
375
|
+
content.append("from typing import Optional")
|
|
376
|
+
content.append("from jose import JWTError, jwt")
|
|
377
|
+
content.append("from passlib.context import CryptContext")
|
|
378
|
+
content.append("from pydantic_settings import BaseSettings")
|
|
379
|
+
content.append("")
|
|
380
|
+
content.append("class AuthSettings(BaseSettings):")
|
|
381
|
+
content.append(' """Authentication configuration settings."""')
|
|
382
|
+
content.append(
|
|
383
|
+
" SECRET_KEY: str = 'your-secret-key-here-change-in-production'"
|
|
384
|
+
)
|
|
385
|
+
content.append(" ALGORITHM: str = 'HS256'")
|
|
386
|
+
content.append(" ACCESS_TOKEN_EXPIRE_MINUTES: int = 30")
|
|
387
|
+
content.append("")
|
|
388
|
+
content.append(" class Config:")
|
|
389
|
+
content.append(" env_file = '.env'")
|
|
390
|
+
content.append("")
|
|
391
|
+
content.append("settings = AuthSettings()")
|
|
392
|
+
content.append("")
|
|
393
|
+
content.append("# Password hashing")
|
|
394
|
+
content.append(
|
|
395
|
+
"pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto')"
|
|
396
|
+
)
|
|
397
|
+
content.append("")
|
|
398
|
+
content.append(
|
|
399
|
+
"def verify_password(plain_password: str, hashed_password: str) -> bool:"
|
|
400
|
+
)
|
|
401
|
+
content.append(' """Verify a password against a hash."""')
|
|
402
|
+
content.append(" return pwd_context.verify(plain_password, hashed_password)")
|
|
403
|
+
content.append("")
|
|
404
|
+
content.append("def get_password_hash(password: str) -> str:")
|
|
405
|
+
content.append(' """Hash a password."""')
|
|
406
|
+
content.append(" return pwd_context.hash(password)")
|
|
407
|
+
content.append("")
|
|
408
|
+
content.append(
|
|
409
|
+
"def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:"
|
|
410
|
+
)
|
|
411
|
+
content.append(' """Create JWT access token."""')
|
|
412
|
+
content.append(" to_encode = data.copy()")
|
|
413
|
+
content.append(" if expires_delta:")
|
|
414
|
+
content.append(" expire = datetime.utcnow() + expires_delta")
|
|
415
|
+
content.append(" else:")
|
|
416
|
+
content.append(
|
|
417
|
+
" expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)"
|
|
418
|
+
)
|
|
419
|
+
content.append(" to_encode.update({'exp': expire})")
|
|
420
|
+
content.append(
|
|
421
|
+
" encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)"
|
|
422
|
+
)
|
|
423
|
+
content.append(" return encoded_jwt")
|
|
424
|
+
content.append("")
|
|
425
|
+
|
|
426
|
+
return "\n".join(content)
|
|
427
|
+
|
|
428
|
+
def _generate_fastapi_users_config(self) -> str:
|
|
429
|
+
"""Generate FastAPI-Users configuration."""
|
|
430
|
+
content = []
|
|
431
|
+
content.append(
|
|
432
|
+
"# --------------------------------------------------------------------------"
|
|
433
|
+
)
|
|
434
|
+
content.append("# FastAPI-Users Authentication Configuration")
|
|
435
|
+
content.append("# Generated by FastAPI-fastkit")
|
|
436
|
+
content.append("#")
|
|
437
|
+
content.append("# For full setup guide, visit:")
|
|
438
|
+
content.append("# https://fastapi-users.github.io/fastapi-users/")
|
|
439
|
+
content.append(
|
|
440
|
+
"# --------------------------------------------------------------------------"
|
|
441
|
+
)
|
|
442
|
+
content.append("# TODO: Implement FastAPI-Users setup")
|
|
443
|
+
content.append(
|
|
444
|
+
"# This requires database models, user manager, and auth backends"
|
|
445
|
+
)
|
|
446
|
+
content.append("# See the official documentation for complete setup")
|
|
447
|
+
content.append("")
|
|
448
|
+
|
|
449
|
+
return "\n".join(content)
|
|
450
|
+
|
|
451
|
+
def generate_docker_files(self) -> None:
|
|
452
|
+
"""Generate Dockerfile and docker-compose.yml."""
|
|
453
|
+
deployment = self.config.get("deployment", [])
|
|
454
|
+
|
|
455
|
+
if "Docker" in deployment:
|
|
456
|
+
dockerfile_content = self._generate_dockerfile()
|
|
457
|
+
dockerfile_path = self.project_dir / "Dockerfile"
|
|
458
|
+
with open(dockerfile_path, "w") as f:
|
|
459
|
+
f.write(dockerfile_content)
|
|
460
|
+
|
|
461
|
+
if "docker-compose" in deployment:
|
|
462
|
+
compose_content = self._generate_docker_compose()
|
|
463
|
+
compose_path = self.project_dir / "docker-compose.yml"
|
|
464
|
+
with open(compose_path, "w") as f:
|
|
465
|
+
f.write(compose_content)
|
|
466
|
+
|
|
467
|
+
def _generate_dockerfile(self) -> str:
|
|
468
|
+
"""Generate Dockerfile content."""
|
|
469
|
+
content = []
|
|
470
|
+
content.append(
|
|
471
|
+
"# --------------------------------------------------------------------------"
|
|
472
|
+
)
|
|
473
|
+
content.append("# Dockerfile")
|
|
474
|
+
content.append("# Generated by FastAPI-fastkit")
|
|
475
|
+
content.append(
|
|
476
|
+
"# --------------------------------------------------------------------------"
|
|
477
|
+
)
|
|
478
|
+
content.append("FROM python:3.11-slim")
|
|
479
|
+
content.append("")
|
|
480
|
+
content.append("WORKDIR /app")
|
|
481
|
+
content.append("")
|
|
482
|
+
content.append("# Install dependencies")
|
|
483
|
+
content.append("COPY requirements.txt .")
|
|
484
|
+
content.append("RUN pip install --no-cache-dir -r requirements.txt")
|
|
485
|
+
content.append("")
|
|
486
|
+
content.append("# Copy application")
|
|
487
|
+
content.append("COPY . .")
|
|
488
|
+
content.append("")
|
|
489
|
+
content.append("# Run application")
|
|
490
|
+
content.append(
|
|
491
|
+
"CMD ['uvicorn', 'src.main:app', '--host', '0.0.0.0', '--port', '8000']"
|
|
492
|
+
)
|
|
493
|
+
content.append("")
|
|
494
|
+
|
|
495
|
+
return "\n".join(content)
|
|
496
|
+
|
|
497
|
+
def _generate_docker_compose(self) -> str:
|
|
498
|
+
"""Generate docker-compose.yml content."""
|
|
499
|
+
content = []
|
|
500
|
+
content.append(
|
|
501
|
+
"# --------------------------------------------------------------------------"
|
|
502
|
+
)
|
|
503
|
+
content.append("# docker-compose.yml")
|
|
504
|
+
content.append("# Generated by FastAPI-fastkit")
|
|
505
|
+
content.append(
|
|
506
|
+
"# --------------------------------------------------------------------------"
|
|
507
|
+
)
|
|
508
|
+
content.append("version: '3.8'")
|
|
509
|
+
content.append("")
|
|
510
|
+
content.append("services:")
|
|
511
|
+
content.append(" app:")
|
|
512
|
+
content.append(" build: .")
|
|
513
|
+
content.append(" ports:")
|
|
514
|
+
content.append(" - '8000:8000'")
|
|
515
|
+
content.append(" environment:")
|
|
516
|
+
content.append(" - DATABASE_URL=${DATABASE_URL}")
|
|
517
|
+
content.append(" depends_on:")
|
|
518
|
+
|
|
519
|
+
db_type = self.config.get("database", {}).get("type", "None")
|
|
520
|
+
if db_type == "PostgreSQL":
|
|
521
|
+
content.append(" - db")
|
|
522
|
+
content.append("")
|
|
523
|
+
content.append(" db:")
|
|
524
|
+
content.append(" image: postgres:15")
|
|
525
|
+
content.append(" environment:")
|
|
526
|
+
content.append(" - POSTGRES_USER=user")
|
|
527
|
+
content.append(" - POSTGRES_PASSWORD=password")
|
|
528
|
+
content.append(" - POSTGRES_DB=dbname")
|
|
529
|
+
content.append(" ports:")
|
|
530
|
+
content.append(" - '5432:5432'")
|
|
531
|
+
content.append(" volumes:")
|
|
532
|
+
content.append(" - postgres_data:/var/lib/postgresql/data")
|
|
533
|
+
content.append("")
|
|
534
|
+
content.append("volumes:")
|
|
535
|
+
content.append(" postgres_data:")
|
|
536
|
+
elif db_type == "MongoDB":
|
|
537
|
+
content.append(" - mongodb")
|
|
538
|
+
content.append("")
|
|
539
|
+
content.append(" mongodb:")
|
|
540
|
+
content.append(" image: mongo:6")
|
|
541
|
+
content.append(" ports:")
|
|
542
|
+
content.append(" - '27017:27017'")
|
|
543
|
+
content.append(" volumes:")
|
|
544
|
+
content.append(" - mongo_data:/data/db")
|
|
545
|
+
content.append("")
|
|
546
|
+
content.append("volumes:")
|
|
547
|
+
content.append(" mongo_data:")
|
|
548
|
+
else:
|
|
549
|
+
content.append(" # Add database service if needed")
|
|
550
|
+
|
|
551
|
+
content.append("")
|
|
552
|
+
|
|
553
|
+
return "\n".join(content)
|
|
554
|
+
|
|
555
|
+
def generate_test_config(self) -> Optional[str]:
|
|
556
|
+
"""
|
|
557
|
+
Generate pytest configuration.
|
|
558
|
+
|
|
559
|
+
Returns:
|
|
560
|
+
Content of pytest.ini or None
|
|
561
|
+
"""
|
|
562
|
+
testing_type = self.config.get("testing", "None")
|
|
563
|
+
|
|
564
|
+
if testing_type == "None":
|
|
565
|
+
return None
|
|
566
|
+
|
|
567
|
+
content = []
|
|
568
|
+
content.append(
|
|
569
|
+
"# --------------------------------------------------------------------------"
|
|
570
|
+
)
|
|
571
|
+
content.append("# pytest configuration")
|
|
572
|
+
content.append("# Generated by FastAPI-fastkit")
|
|
573
|
+
content.append(
|
|
574
|
+
"# --------------------------------------------------------------------------"
|
|
575
|
+
)
|
|
576
|
+
content.append("[pytest]")
|
|
577
|
+
content.append("testpaths = tests")
|
|
578
|
+
content.append("python_files = test_*.py")
|
|
579
|
+
content.append("python_classes = Test*")
|
|
580
|
+
content.append("python_functions = test_*")
|
|
581
|
+
content.append("asyncio_mode = auto")
|
|
582
|
+
|
|
583
|
+
if "Coverage" in testing_type or "Advanced" in testing_type:
|
|
584
|
+
content.append("")
|
|
585
|
+
content.append("[coverage:run]")
|
|
586
|
+
content.append("source = .")
|
|
587
|
+
content.append("omit = ")
|
|
588
|
+
content.append(" tests/*")
|
|
589
|
+
content.append(" .venv/*")
|
|
590
|
+
content.append(" */__pycache__/*")
|
|
591
|
+
|
|
592
|
+
content.append("")
|
|
593
|
+
|
|
594
|
+
return "\n".join(content)
|