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.
Files changed (57) hide show
  1. fastapi_forge/__init__.py +15 -0
  2. fastapi_forge/cli.py +125 -0
  3. fastapi_forge/commands/__init__.py +5 -0
  4. fastapi_forge/commands/create.py +107 -0
  5. fastapi_forge/generator.py +300 -0
  6. fastapi_forge/models.py +187 -0
  7. fastapi_forge/prompts.py +364 -0
  8. fastapi_forge/templates/base/.dockerignore.jinja +20 -0
  9. fastapi_forge/templates/base/.github/workflows/ci.yml.jinja +155 -0
  10. fastapi_forge/templates/base/.gitignore.jinja +68 -0
  11. fastapi_forge/templates/base/Dockerfile.jinja +69 -0
  12. fastapi_forge/templates/base/README.md.jinja +141 -0
  13. fastapi_forge/templates/base/alembic/README.md.jinja +43 -0
  14. fastapi_forge/templates/base/alembic/env.py.jinja +93 -0
  15. fastapi_forge/templates/base/alembic/script.py.mako.jinja +26 -0
  16. fastapi_forge/templates/base/alembic/versions/.gitkeep.jinja +1 -0
  17. fastapi_forge/templates/base/alembic.ini.jinja +70 -0
  18. fastapi_forge/templates/base/app/__init__.py.jinja +5 -0
  19. fastapi_forge/templates/base/app/api/__init__.py.jinja +3 -0
  20. fastapi_forge/templates/base/app/api/auth_api.py.jinja +72 -0
  21. fastapi_forge/templates/base/app/api/health_api.py.jinja +41 -0
  22. fastapi_forge/templates/base/app/config/__init__.py.jinja +31 -0
  23. fastapi_forge/templates/base/app/config/base.py.jinja +52 -0
  24. fastapi_forge/templates/base/app/config/env.py.jinja +75 -0
  25. fastapi_forge/templates/base/app/core/__init__.py.jinja +3 -0
  26. fastapi_forge/templates/base/app/core/auth.py.jinja +96 -0
  27. fastapi_forge/templates/base/app/core/config.py.jinja +56 -0
  28. fastapi_forge/templates/base/app/core/database.py.jinja +68 -0
  29. fastapi_forge/templates/base/app/core/deps.py.jinja +55 -0
  30. fastapi_forge/templates/base/app/core/redis.py.jinja +41 -0
  31. fastapi_forge/templates/base/app/daos/__init__.py.jinja +3 -0
  32. fastapi_forge/templates/base/app/exceptions/__init__.py.jinja +7 -0
  33. fastapi_forge/templates/base/app/exceptions/exception.py.jinja +34 -0
  34. fastapi_forge/templates/base/app/exceptions/handler.py.jinja +56 -0
  35. fastapi_forge/templates/base/app/main.py.jinja +24 -0
  36. fastapi_forge/templates/base/app/models/__init__.py.jinja +7 -0
  37. fastapi_forge/templates/base/app/models/base.py.jinja +13 -0
  38. fastapi_forge/templates/base/app/schemas/__init__.py.jinja +3 -0
  39. fastapi_forge/templates/base/app/schemas/api_schema.py.jinja +20 -0
  40. fastapi_forge/templates/base/app/schemas/auth_schema.py.jinja +21 -0
  41. fastapi_forge/templates/base/app/server.py.jinja +99 -0
  42. fastapi_forge/templates/base/app/services/__init__.py.jinja +3 -0
  43. fastapi_forge/templates/base/app/utils/__init__.py.jinja +3 -0
  44. fastapi_forge/templates/base/app/utils/log.py.jinja +21 -0
  45. fastapi_forge/templates/base/config.yaml.jinja +71 -0
  46. fastapi_forge/templates/base/docker-compose.yml.jinja +117 -0
  47. fastapi_forge/templates/base/pyproject.toml.jinja +86 -0
  48. fastapi_forge/templates/base/pytest.ini.jinja +10 -0
  49. fastapi_forge/templates/base/ruff.toml.jinja +33 -0
  50. fastapi_forge/templates/base/tests/__init__.py.jinja +3 -0
  51. fastapi_forge/templates/base/tests/conftest.py.jinja +31 -0
  52. fastapi_forge/templates/base/tests/test_health.py.jinja +24 -0
  53. forgeapi-0.1.0.dist-info/METADATA +182 -0
  54. forgeapi-0.1.0.dist-info/RECORD +57 -0
  55. forgeapi-0.1.0.dist-info/WHEEL +4 -0
  56. forgeapi-0.1.0.dist-info/entry_points.txt +2 -0
  57. forgeapi-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,187 @@
1
+ """
2
+ Project Configuration Models
3
+
4
+ This module defines the data structures for project configuration using Pydantic.
5
+ """
6
+
7
+ from enum import Enum
8
+ from typing import Literal
9
+
10
+ from pydantic import BaseModel, Field, field_validator
11
+
12
+
13
+ class PackageManager(str, Enum):
14
+ """Supported package managers."""
15
+
16
+ UV = "uv"
17
+ POETRY = "poetry"
18
+ PIP = "pip"
19
+
20
+
21
+ class DatabaseType(str, Enum):
22
+ """Supported database types."""
23
+
24
+ POSTGRES = "postgres"
25
+ MYSQL = "mysql"
26
+ SQLITE = "sqlite"
27
+ NONE = "none"
28
+
29
+
30
+ class ProjectConfig(BaseModel):
31
+ """
32
+ Project configuration model.
33
+
34
+ This model holds all the configuration options for generating a new FastAPI project.
35
+ """
36
+
37
+ # Basic Information
38
+ project_name: str = Field(
39
+ ...,
40
+ min_length=1,
41
+ max_length=100,
42
+ description="Name of the project",
43
+ )
44
+ project_slug: str = Field(
45
+ default="",
46
+ description="Project identifier in snake_case",
47
+ )
48
+ project_description: str = Field(
49
+ default="A FastAPI project generated by FastAPI-Forge",
50
+ description="Project description",
51
+ )
52
+ author_name: str = Field(
53
+ default="Your Name",
54
+ description="Author name",
55
+ )
56
+ author_email: str = Field(
57
+ default="your@email.com",
58
+ description="Author email",
59
+ )
60
+ python_version: Literal["3.10", "3.11", "3.12", "3.13"] = Field(
61
+ default="3.11",
62
+ description="Python version",
63
+ )
64
+
65
+ # Package Manager
66
+ package_manager: PackageManager = Field(
67
+ default=PackageManager.UV,
68
+ description="Package manager to use",
69
+ )
70
+
71
+ # Database
72
+ database: DatabaseType = Field(
73
+ default=DatabaseType.POSTGRES,
74
+ description="Database type",
75
+ )
76
+ use_alembic: bool = Field(
77
+ default=True,
78
+ description="Whether to use Alembic for database migrations",
79
+ )
80
+
81
+ # Features
82
+ use_auth: bool = Field(
83
+ default=True,
84
+ description="Whether to include JWT authentication",
85
+ )
86
+ use_redis: bool = Field(
87
+ default=True,
88
+ description="Whether to include Redis support",
89
+ )
90
+
91
+ # Docker
92
+ use_docker: bool = Field(
93
+ default=True,
94
+ description="Whether to generate Docker configuration",
95
+ )
96
+ use_docker_compose: bool = Field(
97
+ default=True,
98
+ description="Whether to generate docker-compose.yml",
99
+ )
100
+ docker_services: list[str] = Field(
101
+ default_factory=lambda: ["postgres", "redis"],
102
+ description="Services to include in docker-compose",
103
+ )
104
+
105
+ # Testing & Quality
106
+ use_pytest: bool = Field(
107
+ default=True,
108
+ description="Whether to configure Pytest",
109
+ )
110
+ use_ruff: bool = Field(
111
+ default=True,
112
+ description="Whether to configure Ruff linter/formatter",
113
+ )
114
+
115
+ # CI/CD & Editor
116
+ use_github_actions: bool = Field(
117
+ default=True,
118
+ description="Whether to generate GitHub Actions CI",
119
+ )
120
+ use_vscode: bool = Field(
121
+ default=True,
122
+ description="Whether to generate VS Code configuration",
123
+ )
124
+
125
+ @field_validator("project_slug", mode="before")
126
+ @classmethod
127
+ def generate_slug(cls, v: str, info) -> str:
128
+ """Generate project slug from project name if not provided."""
129
+ if v:
130
+ return v
131
+ # Get project_name from the data being validated
132
+ project_name = info.data.get("project_name", "")
133
+ if project_name:
134
+ # Convert to snake_case
135
+ slug = project_name.lower()
136
+ slug = slug.replace("-", "_")
137
+ slug = slug.replace(" ", "_")
138
+ # Remove any non-alphanumeric characters except underscore
139
+ slug = "".join(c for c in slug if c.isalnum() or c == "_")
140
+ return slug
141
+ return v
142
+
143
+ def model_post_init(self, __context) -> None:
144
+ """Post-initialization processing."""
145
+ # Ensure project_slug is set
146
+ if not self.project_slug and self.project_name:
147
+ slug = self.project_name.lower()
148
+ slug = slug.replace("-", "_")
149
+ slug = slug.replace(" ", "_")
150
+ slug = "".join(c for c in slug if c.isalnum() or c == "_")
151
+ object.__setattr__(self, "project_slug", slug)
152
+
153
+ # If database is none, disable alembic
154
+ if self.database == DatabaseType.NONE:
155
+ object.__setattr__(self, "use_alembic", False)
156
+
157
+ # Update docker_services based on selections
158
+ services = []
159
+ if self.database == DatabaseType.POSTGRES:
160
+ services.append("postgres")
161
+ elif self.database == DatabaseType.MYSQL:
162
+ services.append("mysql")
163
+ if self.use_redis:
164
+ services.append("redis")
165
+ if services != self.docker_services:
166
+ object.__setattr__(self, "docker_services", services)
167
+
168
+
169
+ # Default configuration for quick start
170
+ DEFAULT_CONFIG = ProjectConfig(
171
+ project_name="my-fastapi-app",
172
+ project_description="A FastAPI project generated by FastAPI-Forge",
173
+ author_name="Your Name",
174
+ author_email="your@email.com",
175
+ python_version="3.11",
176
+ package_manager=PackageManager.UV,
177
+ database=DatabaseType.POSTGRES,
178
+ use_alembic=True,
179
+ use_auth=True,
180
+ use_redis=True,
181
+ use_docker=True,
182
+ use_docker_compose=True,
183
+ use_pytest=True,
184
+ use_ruff=True,
185
+ use_github_actions=True,
186
+ use_vscode=True,
187
+ )
@@ -0,0 +1,364 @@
1
+ """
2
+ Interactive Prompts Module
3
+
4
+ This module provides interactive command-line prompts using Questionary
5
+ for collecting project configuration from users.
6
+ """
7
+
8
+ import questionary
9
+ from questionary import Style
10
+ from rich.console import Console
11
+
12
+ from fastapi_forge.models import (
13
+ DatabaseType,
14
+ PackageManager,
15
+ ProjectConfig,
16
+ )
17
+
18
+ console = Console()
19
+
20
+ # Custom style for questionary prompts
21
+ CUSTOM_STYLE = Style(
22
+ [
23
+ ("qmark", "fg:cyan bold"),
24
+ ("question", "fg:white bold"),
25
+ ("answer", "fg:green bold"),
26
+ ("pointer", "fg:cyan bold"),
27
+ ("highlighted", "fg:cyan bold"),
28
+ ("selected", "fg:green"),
29
+ ("separator", "fg:gray"),
30
+ ("instruction", "fg:gray italic"),
31
+ ("text", "fg:white"),
32
+ ]
33
+ )
34
+
35
+
36
+ def prompt_basic_info(project_name: str | None = None) -> dict:
37
+ """
38
+ Prompt for basic project information.
39
+
40
+ Args:
41
+ project_name: Optional pre-filled project name
42
+
43
+ Returns:
44
+ Dictionary with basic project info
45
+ """
46
+ console.print("\n[bold cyan]📝 Basic Project Information[/bold cyan]\n")
47
+
48
+ # Project name
49
+ if project_name:
50
+ name = project_name
51
+ console.print(f" Project name: [green]{name}[/green]")
52
+ else:
53
+ name = questionary.text(
54
+ "Project name:",
55
+ default="my-fastapi-app",
56
+ style=CUSTOM_STYLE,
57
+ ).ask()
58
+
59
+ if not name:
60
+ raise KeyboardInterrupt("Project creation cancelled.")
61
+
62
+ # Project description
63
+ description = questionary.text(
64
+ "Project description:",
65
+ default="A FastAPI project generated by FastAPI-Forge",
66
+ style=CUSTOM_STYLE,
67
+ ).ask()
68
+
69
+ # Author name
70
+ author_name = questionary.text(
71
+ "Author name:",
72
+ default="Your Name",
73
+ style=CUSTOM_STYLE,
74
+ ).ask()
75
+
76
+ # Author email
77
+ author_email = questionary.text(
78
+ "Author email:",
79
+ default="your@email.com",
80
+ style=CUSTOM_STYLE,
81
+ ).ask()
82
+
83
+ # Python version
84
+ python_version = questionary.select(
85
+ "Python version:",
86
+ choices=["3.10", "3.11", "3.12", "3.13"],
87
+ default="3.11",
88
+ style=CUSTOM_STYLE,
89
+ ).ask()
90
+
91
+ return {
92
+ "project_name": name,
93
+ "project_description": description or "A FastAPI project",
94
+ "author_name": author_name or "Your Name",
95
+ "author_email": author_email or "your@email.com",
96
+ "python_version": python_version or "3.11",
97
+ }
98
+
99
+
100
+ def prompt_package_manager() -> PackageManager:
101
+ """
102
+ Prompt for package manager selection.
103
+
104
+ Returns:
105
+ Selected PackageManager enum value
106
+ """
107
+ console.print("\n[bold cyan]📦 Package Manager[/bold cyan]\n")
108
+
109
+ choice = questionary.select(
110
+ "Select package manager:",
111
+ choices=[
112
+ questionary.Choice("uv (recommended - faster)", value="uv"),
113
+ questionary.Choice("Poetry", value="poetry"),
114
+ questionary.Choice("pip (requirements.txt only)", value="pip"),
115
+ ],
116
+ default="uv",
117
+ style=CUSTOM_STYLE,
118
+ ).ask()
119
+
120
+ return PackageManager(choice) if choice else PackageManager.UV
121
+
122
+
123
+ def prompt_database() -> tuple[DatabaseType, bool]:
124
+ """
125
+ Prompt for database configuration.
126
+
127
+ Returns:
128
+ Tuple of (DatabaseType, use_alembic)
129
+ """
130
+ console.print("\n[bold cyan]🗄️ Database Configuration[/bold cyan]\n")
131
+
132
+ db_choice = questionary.select(
133
+ "Select database:",
134
+ choices=[
135
+ questionary.Choice("PostgreSQL (recommended)", value="postgres"),
136
+ questionary.Choice("MySQL", value="mysql"),
137
+ questionary.Choice("SQLite (development only)", value="sqlite"),
138
+ questionary.Choice("None (no database)", value="none"),
139
+ ],
140
+ default="postgres",
141
+ style=CUSTOM_STYLE,
142
+ ).ask()
143
+
144
+ database = DatabaseType(db_choice) if db_choice else DatabaseType.POSTGRES
145
+
146
+ use_alembic = False
147
+ if database != DatabaseType.NONE:
148
+ use_alembic = questionary.confirm(
149
+ "Enable database migrations (Alembic)?",
150
+ default=True,
151
+ style=CUSTOM_STYLE,
152
+ ).ask()
153
+ use_alembic = use_alembic if use_alembic is not None else True
154
+
155
+ return database, use_alembic
156
+
157
+
158
+ def prompt_features() -> dict:
159
+ """
160
+ Prompt for feature selection.
161
+
162
+ Returns:
163
+ Dictionary with feature flags
164
+ """
165
+ console.print("\n[bold cyan]🔧 Features[/bold cyan]\n")
166
+
167
+ use_auth = questionary.confirm(
168
+ "Include JWT authentication module?",
169
+ default=True,
170
+ style=CUSTOM_STYLE,
171
+ ).ask()
172
+
173
+ use_redis = questionary.confirm(
174
+ "Include Redis support?",
175
+ default=True,
176
+ style=CUSTOM_STYLE,
177
+ ).ask()
178
+
179
+ return {
180
+ "use_auth": use_auth if use_auth is not None else True,
181
+ "use_redis": use_redis if use_redis is not None else True,
182
+ }
183
+
184
+
185
+ def prompt_docker() -> dict:
186
+ """
187
+ Prompt for Docker configuration.
188
+
189
+ Returns:
190
+ Dictionary with Docker settings
191
+ """
192
+ console.print("\n[bold cyan]🐳 Docker Configuration[/bold cyan]\n")
193
+
194
+ use_docker = questionary.confirm(
195
+ "Generate Dockerfile?",
196
+ default=True,
197
+ style=CUSTOM_STYLE,
198
+ ).ask()
199
+ use_docker = use_docker if use_docker is not None else True
200
+
201
+ use_docker_compose = False
202
+ docker_services: list[str] = []
203
+
204
+ if use_docker:
205
+ use_docker_compose = questionary.confirm(
206
+ "Generate docker-compose.yml?",
207
+ default=True,
208
+ style=CUSTOM_STYLE,
209
+ ).ask()
210
+ use_docker_compose = use_docker_compose if use_docker_compose is not None else True
211
+
212
+ if use_docker_compose:
213
+ docker_services = questionary.checkbox(
214
+ "Select services for docker-compose:",
215
+ choices=[
216
+ questionary.Choice("PostgreSQL", value="postgres", checked=True),
217
+ questionary.Choice("Redis", value="redis", checked=True),
218
+ questionary.Choice("RabbitMQ", value="rabbitmq"),
219
+ questionary.Choice("MinIO (Object Storage)", value="minio"),
220
+ ],
221
+ style=CUSTOM_STYLE,
222
+ ).ask()
223
+ docker_services = docker_services or ["postgres", "redis"]
224
+
225
+ return {
226
+ "use_docker": use_docker,
227
+ "use_docker_compose": use_docker_compose,
228
+ "docker_services": docker_services,
229
+ }
230
+
231
+
232
+ def prompt_extras() -> dict:
233
+ """
234
+ Prompt for extra configuration options.
235
+
236
+ Returns:
237
+ Dictionary with extra settings
238
+ """
239
+ console.print("\n[bold cyan]✨ Extra Options[/bold cyan]\n")
240
+
241
+ use_pytest = questionary.confirm(
242
+ "Configure Pytest for testing?",
243
+ default=True,
244
+ style=CUSTOM_STYLE,
245
+ ).ask()
246
+
247
+ use_ruff = questionary.confirm(
248
+ "Configure Ruff (linter/formatter)?",
249
+ default=True,
250
+ style=CUSTOM_STYLE,
251
+ ).ask()
252
+
253
+ use_github_actions = questionary.confirm(
254
+ "Generate GitHub Actions CI?",
255
+ default=True,
256
+ style=CUSTOM_STYLE,
257
+ ).ask()
258
+
259
+ use_vscode = questionary.confirm(
260
+ "Generate VS Code configuration?",
261
+ default=True,
262
+ style=CUSTOM_STYLE,
263
+ ).ask()
264
+
265
+ return {
266
+ "use_pytest": use_pytest if use_pytest is not None else True,
267
+ "use_ruff": use_ruff if use_ruff is not None else True,
268
+ "use_github_actions": use_github_actions if use_github_actions is not None else True,
269
+ "use_vscode": use_vscode if use_vscode is not None else True,
270
+ }
271
+
272
+
273
+ def collect_project_config(project_name: str | None = None) -> ProjectConfig:
274
+ """
275
+ Collect all project configuration through interactive prompts.
276
+
277
+ Args:
278
+ project_name: Optional pre-filled project name
279
+
280
+ Returns:
281
+ Complete ProjectConfig object
282
+ """
283
+ console.print(
284
+ "\n[bold cyan]🚀 FastAPI-Forge[/bold cyan] - "
285
+ "[white]Create New Project[/white]\n"
286
+ )
287
+ console.print("[dim]Press Ctrl+C to cancel at any time.[/dim]\n")
288
+
289
+ try:
290
+ # Collect all configuration
291
+ basic_info = prompt_basic_info(project_name)
292
+ package_manager = prompt_package_manager()
293
+ database, use_alembic = prompt_database()
294
+ features = prompt_features()
295
+ docker = prompt_docker()
296
+ extras = prompt_extras()
297
+
298
+ # Combine all config
299
+ config = ProjectConfig(
300
+ **basic_info,
301
+ package_manager=package_manager,
302
+ database=database,
303
+ use_alembic=use_alembic,
304
+ **features,
305
+ **docker,
306
+ **extras,
307
+ )
308
+
309
+ return config
310
+
311
+ except KeyboardInterrupt:
312
+ console.print("\n\n[yellow]Project creation cancelled.[/yellow]")
313
+ raise
314
+
315
+
316
+ def confirm_config(config: ProjectConfig) -> bool:
317
+ """
318
+ Display configuration summary and ask for confirmation.
319
+
320
+ Args:
321
+ config: ProjectConfig to display
322
+
323
+ Returns:
324
+ True if user confirms, False otherwise
325
+ """
326
+ console.print("\n[bold cyan]📋 Configuration Summary[/bold cyan]\n")
327
+
328
+ console.print(f" [white]Project:[/white] [green]{config.project_name}[/green]")
329
+ console.print(f" [white]Description:[/white] {config.project_description}")
330
+ console.print(f" [white]Author:[/white] {config.author_name} <{config.author_email}>")
331
+ console.print(f" [white]Python:[/white] {config.python_version}")
332
+ console.print(f" [white]Package Manager:[/white] {config.package_manager.value}")
333
+ console.print(f" [white]Database:[/white] {config.database.value}")
334
+
335
+ features = []
336
+ if config.use_alembic:
337
+ features.append("Alembic")
338
+ if config.use_auth:
339
+ features.append("JWT Auth")
340
+ if config.use_redis:
341
+ features.append("Redis")
342
+ if config.use_docker:
343
+ features.append("Docker")
344
+ if config.use_pytest:
345
+ features.append("Pytest")
346
+ if config.use_ruff:
347
+ features.append("Ruff")
348
+ if config.use_github_actions:
349
+ features.append("GitHub Actions")
350
+
351
+ console.print(f" [white]Features:[/white] {', '.join(features)}")
352
+
353
+ if config.use_docker_compose and config.docker_services:
354
+ console.print(f" [white]Docker Services:[/white] {', '.join(config.docker_services)}")
355
+
356
+ console.print()
357
+
358
+ confirm = questionary.confirm(
359
+ "Create project with this configuration?",
360
+ default=True,
361
+ style=CUSTOM_STYLE,
362
+ ).ask()
363
+
364
+ return confirm if confirm is not None else False
@@ -0,0 +1,20 @@
1
+ __pycache__
2
+ *.py[cod]
3
+ *$py.class
4
+ *.so
5
+ .Python
6
+ .venv
7
+ venv
8
+ env
9
+ .git
10
+ .gitignore
11
+ .idea
12
+ .vscode
13
+ *.md
14
+ tests
15
+ .pytest_cache
16
+ .ruff_cache
17
+ .mypy_cache
18
+ *.egg-info
19
+ dist
20
+ build