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.
Files changed (39) hide show
  1. fastapi_fastkit/__init__.py +1 -1
  2. fastapi_fastkit/backend/inspector.py +170 -12
  3. fastapi_fastkit/backend/interactive/__init__.py +51 -0
  4. fastapi_fastkit/backend/interactive/config_builder.py +186 -0
  5. fastapi_fastkit/backend/interactive/prompts.py +528 -0
  6. fastapi_fastkit/backend/interactive/selectors.py +241 -0
  7. fastapi_fastkit/backend/interactive/validators.py +163 -0
  8. fastapi_fastkit/backend/main.py +77 -0
  9. fastapi_fastkit/backend/package_managers/pdm_manager.py +6 -2
  10. fastapi_fastkit/backend/package_managers/pip_manager.py +6 -2
  11. fastapi_fastkit/backend/package_managers/poetry_manager.py +6 -2
  12. fastapi_fastkit/backend/package_managers/uv_manager.py +6 -2
  13. fastapi_fastkit/backend/project_builder/__init__.py +17 -0
  14. fastapi_fastkit/backend/project_builder/config_generator.py +594 -0
  15. fastapi_fastkit/backend/project_builder/dependency_collector.py +210 -0
  16. fastapi_fastkit/cli.py +218 -20
  17. fastapi_fastkit/core/settings.py +74 -0
  18. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/pyproject.toml-tpl +2 -1
  19. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/requirements.txt-tpl +1 -0
  20. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/pyproject.toml-tpl +2 -1
  21. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/requirements.txt-tpl +1 -0
  22. fastapi_fastkit/fastapi_project_template/fastapi-default/pyproject.toml-tpl +2 -1
  23. fastapi_fastkit/fastapi_project_template/fastapi-default/requirements.txt-tpl +1 -0
  24. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/pyproject.toml-tpl +2 -1
  25. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/requirements.txt-tpl +1 -0
  26. fastapi_fastkit/fastapi_project_template/fastapi-empty/pyproject.toml-tpl +2 -1
  27. fastapi_fastkit/fastapi_project_template/fastapi-empty/requirements.txt-tpl +1 -0
  28. fastapi_fastkit/fastapi_project_template/fastapi-mcp/pyproject.toml-tpl +2 -1
  29. fastapi_fastkit/fastapi_project_template/fastapi-mcp/requirements.txt-tpl +1 -0
  30. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/pyproject.toml-tpl +2 -1
  31. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/requirements.txt-tpl +1 -0
  32. fastapi_fastkit/fastapi_project_template/fastapi-single-module/pyproject.toml-tpl +2 -1
  33. fastapi_fastkit/fastapi_project_template/fastapi-single-module/requirements.txt-tpl +1 -0
  34. fastapi_fastkit/utils/main.py +121 -10
  35. {fastapi_fastkit-1.1.4.dist-info → fastapi_fastkit-1.2.0.dist-info}/METADATA +9 -6
  36. {fastapi_fastkit-1.1.4.dist-info → fastapi_fastkit-1.2.0.dist-info}/RECORD +39 -31
  37. {fastapi_fastkit-1.1.4.dist-info → fastapi_fastkit-1.2.0.dist-info}/WHEEL +1 -1
  38. {fastapi_fastkit-1.1.4.dist-info → fastapi_fastkit-1.2.0.dist-info}/entry_points.txt +0 -0
  39. {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)