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,528 @@
1
+ # --------------------------------------------------------------------------
2
+ # Interactive prompt definitions for FastAPI-fastkit CLI
3
+ #
4
+ # Provides user-facing prompts for:
5
+ # - Basic project information
6
+ # - Template selection
7
+ # - Database selection
8
+ # - Authentication method
9
+ # - Additional features
10
+ # - Testing configuration
11
+ # - Deployment options
12
+ #
13
+ # @author bnbong bbbong9@gmail.com
14
+ # --------------------------------------------------------------------------
15
+ from typing import Any, Dict, List, Optional, cast
16
+
17
+ import click
18
+ from rich.panel import Panel
19
+
20
+ from fastapi_fastkit.utils.main import console, print_error
21
+
22
+ from .selectors import render_selection_table
23
+ from .validators import validate_email_format, validate_project_name
24
+
25
+
26
+ def prompt_basic_info() -> Dict[str, str]:
27
+ """
28
+ Prompt for basic project information.
29
+
30
+ Returns:
31
+ Dictionary with project_name, author, author_email, description
32
+ """
33
+ console.print("\n[bold cyan]📝 Project Information[/bold cyan]")
34
+ console.print(
35
+ "[dim]Let's start with some basic information about your project[/dim]\n"
36
+ )
37
+
38
+ # Project name
39
+ while True:
40
+ project_name = click.prompt("Project name", type=str)
41
+ is_valid, error = validate_project_name(project_name)
42
+ if is_valid:
43
+ break
44
+ print_error(error or "Invalid project name")
45
+
46
+ # Author
47
+ author = click.prompt("Author name", type=str)
48
+
49
+ # Email
50
+ while True:
51
+ author_email = click.prompt("Author email", type=str)
52
+ if validate_email_format(author_email):
53
+ break
54
+ print_error("Invalid email format. Please enter a valid email address.")
55
+
56
+ # Description
57
+ description = click.prompt("Project description", type=str)
58
+
59
+ return {
60
+ "project_name": project_name,
61
+ "author": author,
62
+ "author_email": author_email,
63
+ "description": description,
64
+ }
65
+
66
+
67
+ def prompt_template_selection(settings: Any) -> Optional[str]:
68
+ """
69
+ Prompt for base template selection.
70
+
71
+ Args:
72
+ settings: FastkitConfig instance
73
+
74
+ Returns:
75
+ Template name or None for empty project
76
+ """
77
+ import os
78
+
79
+ console.print("\n[bold cyan]📦 Base Template Selection[/bold cyan]")
80
+ console.print("[dim]Choose a base template or start from scratch[/dim]\n")
81
+
82
+ template_dir = settings.FASTKIT_TEMPLATE_ROOT
83
+ excluded_dirs = ["__pycache__", "modules", "fastapi-empty"]
84
+
85
+ templates = [
86
+ d
87
+ for d in os.listdir(template_dir)
88
+ if os.path.isdir(os.path.join(template_dir, d)) and d not in excluded_dirs
89
+ ]
90
+
91
+ # Add "Empty Project" option
92
+ options = {"Empty Project": "Start with minimal FastAPI setup"}
93
+
94
+ # Add available templates
95
+ for template in templates:
96
+ readme_path = os.path.join(template_dir, template, "README.md-tpl")
97
+ description = "No description"
98
+ if os.path.exists(readme_path):
99
+ with open(readme_path, "r") as f:
100
+ first_line = f.readline().strip()
101
+ if first_line.startswith("# "):
102
+ description = first_line[2:]
103
+ options[template] = description
104
+
105
+ render_selection_table("Available Templates", options)
106
+
107
+ choice = click.prompt(
108
+ "\nSelect template",
109
+ type=click.IntRange(1, len(options)),
110
+ default=1,
111
+ )
112
+
113
+ selected_key = list(options.keys())[choice - 1]
114
+
115
+ return cast(
116
+ Optional[str], selected_key if selected_key != "Empty Project" else None
117
+ )
118
+
119
+
120
+ def prompt_database_selection(settings: Any) -> Dict[str, Any]:
121
+ """
122
+ Prompt for database selection.
123
+
124
+ Args:
125
+ settings: FastkitConfig instance
126
+
127
+ Returns:
128
+ Dictionary with type and packages
129
+ """
130
+ console.print("\n[bold cyan]🗄️ Database Selection[/bold cyan]")
131
+ console.print("[dim]Choose your database backend[/dim]\n")
132
+
133
+ db_catalog = settings.PACKAGE_CATALOG["database"]
134
+ options = {
135
+ name: f"Packages: {', '.join(pkgs) if pkgs else 'None'}"
136
+ for name, pkgs in db_catalog.items()
137
+ }
138
+
139
+ render_selection_table("Database Options", options)
140
+
141
+ choice = click.prompt(
142
+ "\nSelect database",
143
+ type=click.IntRange(1, len(options)),
144
+ default=len(options), # Default to "None"
145
+ )
146
+
147
+ selected_type = list(options.keys())[choice - 1]
148
+
149
+ return {
150
+ "type": selected_type,
151
+ "packages": db_catalog[selected_type],
152
+ }
153
+
154
+
155
+ def prompt_authentication_selection(settings: Any) -> str:
156
+ """
157
+ Prompt for authentication method.
158
+
159
+ Args:
160
+ settings: FastkitConfig instance
161
+
162
+ Returns:
163
+ Authentication type name
164
+ """
165
+ console.print("\n[bold cyan]🔐 Authentication Selection[/bold cyan]")
166
+ console.print("[dim]Choose your authentication strategy[/dim]\n")
167
+
168
+ auth_catalog = settings.PACKAGE_CATALOG["authentication"]
169
+ options = {}
170
+
171
+ for name, pkgs in auth_catalog.items():
172
+ if name == "None":
173
+ options[name] = "No authentication"
174
+ elif name == "JWT":
175
+ options[name] = "JSON Web Tokens (recommended for APIs)"
176
+ elif name == "OAuth2":
177
+ options[name] = "OAuth2 with authlib"
178
+ elif name == "FastAPI-Users":
179
+ options[name] = "Complete user management system (includes JWT)"
180
+ elif name == "Session-based":
181
+ options[name] = "Traditional session-based authentication"
182
+ else:
183
+ options[name] = f"Packages: {', '.join(pkgs)}"
184
+
185
+ render_selection_table("Authentication Options", options)
186
+
187
+ choice = click.prompt(
188
+ "\nSelect authentication method",
189
+ type=click.IntRange(1, len(options)),
190
+ default=len(options), # Default to "None"
191
+ )
192
+
193
+ return cast(str, list(options.keys())[choice - 1])
194
+
195
+
196
+ def prompt_async_tasks_selection(settings: Any) -> str:
197
+ """
198
+ Prompt for async task queue selection.
199
+
200
+ Args:
201
+ settings: FastkitConfig instance
202
+
203
+ Returns:
204
+ Task queue type name
205
+ """
206
+ console.print("\n[bold cyan]⚡ Background Tasks Selection[/bold cyan]")
207
+ console.print("[dim]Choose a task queue for background processing[/dim]\n")
208
+
209
+ task_catalog = settings.PACKAGE_CATALOG["async_tasks"]
210
+ options = {
211
+ name: f"Packages: {', '.join(pkgs) if pkgs else 'None'}"
212
+ for name, pkgs in task_catalog.items()
213
+ }
214
+
215
+ render_selection_table("Task Queue Options", options)
216
+
217
+ choice = click.prompt(
218
+ "\nSelect task queue",
219
+ type=click.IntRange(1, len(options)),
220
+ default=len(options), # Default to "None"
221
+ )
222
+
223
+ return cast(str, list(options.keys())[choice - 1])
224
+
225
+
226
+ def prompt_caching_selection(settings: Any) -> str:
227
+ """
228
+ Prompt for caching selection.
229
+
230
+ Args:
231
+ settings: FastkitConfig instance
232
+
233
+ Returns:
234
+ Caching type name
235
+ """
236
+ console.print("\n[bold cyan]💾 Caching Selection[/bold cyan]")
237
+ console.print("[dim]Add caching layer for better performance[/dim]\n")
238
+
239
+ cache_catalog = settings.PACKAGE_CATALOG["caching"]
240
+ options = {
241
+ name: f"Packages: {', '.join(pkgs) if pkgs else 'None'}"
242
+ for name, pkgs in cache_catalog.items()
243
+ }
244
+
245
+ render_selection_table("Caching Options", options)
246
+
247
+ choice = click.prompt(
248
+ "\nSelect caching",
249
+ type=click.IntRange(1, len(options)),
250
+ default=len(options), # Default to "None"
251
+ )
252
+
253
+ return cast(str, list(options.keys())[choice - 1])
254
+
255
+
256
+ def prompt_monitoring_selection(settings: Any) -> str:
257
+ """
258
+ Prompt for monitoring/logging selection.
259
+
260
+ Args:
261
+ settings: FastkitConfig instance
262
+
263
+ Returns:
264
+ Monitoring type name
265
+ """
266
+ console.print("\n[bold cyan]📊 Monitoring & Logging Selection[/bold cyan]")
267
+ console.print("[dim]Add monitoring and logging capabilities[/dim]\n")
268
+
269
+ monitoring_catalog = settings.PACKAGE_CATALOG["monitoring"]
270
+ options = {}
271
+
272
+ for name, pkgs in monitoring_catalog.items():
273
+ if name == "None":
274
+ options[name] = "No additional monitoring"
275
+ elif name == "Loguru":
276
+ options[name] = "Enhanced logging with Loguru"
277
+ elif name == "OpenTelemetry":
278
+ options[name] = "Distributed tracing and metrics"
279
+ elif name == "Prometheus":
280
+ options[name] = "Prometheus metrics instrumentation"
281
+ else:
282
+ options[name] = f"Packages: {', '.join(pkgs)}"
283
+
284
+ render_selection_table("Monitoring Options", options)
285
+
286
+ choice = click.prompt(
287
+ "\nSelect monitoring",
288
+ type=click.IntRange(1, len(options)),
289
+ default=len(options), # Default to "None"
290
+ )
291
+
292
+ return cast(str, list(options.keys())[choice - 1])
293
+
294
+
295
+ def prompt_testing_selection(settings: Any) -> str:
296
+ """
297
+ Prompt for testing framework selection.
298
+
299
+ Args:
300
+ settings: FastkitConfig instance
301
+
302
+ Returns:
303
+ Testing type name
304
+ """
305
+ console.print("\n[bold cyan]🧪 Testing Framework Selection[/bold cyan]")
306
+ console.print("[dim]Choose testing tools and coverage[/dim]\n")
307
+
308
+ testing_catalog = settings.PACKAGE_CATALOG["testing"]
309
+ options = {}
310
+
311
+ for name, pkgs in testing_catalog.items():
312
+ if name == "None":
313
+ options[name] = "No testing framework"
314
+ elif name == "Basic":
315
+ options[name] = "pytest + httpx for API testing"
316
+ elif name == "Coverage":
317
+ options[name] = "Basic + code coverage"
318
+ elif name == "Advanced":
319
+ options[name] = "Coverage + faker + factory-boy for fixtures"
320
+ else:
321
+ options[name] = f"Packages: {', '.join(pkgs)}"
322
+
323
+ render_selection_table("Testing Options", options)
324
+
325
+ choice = click.prompt(
326
+ "\nSelect testing framework",
327
+ type=click.IntRange(1, len(options)),
328
+ default=2, # Default to "Basic"
329
+ )
330
+
331
+ return cast(str, list(options.keys())[choice - 1])
332
+
333
+
334
+ def prompt_utilities_selection(settings: Any) -> List[str]:
335
+ """
336
+ Prompt for utilities selection (multi-select).
337
+
338
+ Args:
339
+ settings: FastkitConfig instance
340
+
341
+ Returns:
342
+ List of selected utility names
343
+ """
344
+ console.print("\n[bold cyan]🛠️ Additional Utilities[/bold cyan]")
345
+ console.print(
346
+ "[dim]Select additional utilities (comma-separated numbers, e.g., 1,3,4)[/dim]\n"
347
+ )
348
+
349
+ utilities_catalog = settings.PACKAGE_CATALOG["utilities"]
350
+ options = []
351
+ descriptions = {}
352
+
353
+ for name, pkgs in utilities_catalog.items():
354
+ if name == "None":
355
+ continue
356
+ options.append(name)
357
+ if name == "CORS":
358
+ descriptions[name] = "(Built-in, will configure in settings)"
359
+ elif name == "Rate-Limiting":
360
+ descriptions[name] = "API rate limiting with slowapi"
361
+ elif name == "Pagination":
362
+ descriptions[name] = "Response pagination utilities"
363
+ elif name == "WebSocket":
364
+ descriptions[name] = "(Built-in, will configure routes)"
365
+ else:
366
+ descriptions[name] = f"({', '.join(pkgs)})"
367
+
368
+ for i, option in enumerate(options, 1):
369
+ desc = descriptions.get(option, "")
370
+ console.print(f" [cyan]{i}[/cyan]. {option} {desc}")
371
+
372
+ selected_input = console.input(
373
+ "\n[cyan]Your choice (or press Enter to skip):[/cyan] "
374
+ ).strip()
375
+
376
+ if not selected_input:
377
+ return []
378
+
379
+ try:
380
+ indices = [int(x.strip()) for x in selected_input.split(",") if x.strip()]
381
+ selected = [options[idx - 1] for idx in indices if 1 <= idx <= len(options)]
382
+ return selected
383
+ except (ValueError, IndexError):
384
+ console.print("[yellow]Invalid selection. Skipping utilities.[/yellow]")
385
+ return []
386
+
387
+
388
+ def prompt_deployment_options() -> List[str]:
389
+ """
390
+ Prompt for deployment configuration.
391
+
392
+ Returns:
393
+ List of deployment options
394
+ """
395
+ console.print("\n[bold cyan]🚀 Deployment Configuration[/bold cyan]")
396
+ console.print("[dim]Select deployment options (comma-separated numbers)[/dim]\n")
397
+
398
+ options = [
399
+ "Docker",
400
+ "docker-compose",
401
+ "None",
402
+ ]
403
+
404
+ descriptions = {
405
+ "Docker": "Generate Dockerfile",
406
+ "docker-compose": "Generate docker-compose.yml (includes Docker)",
407
+ "None": "No deployment configuration",
408
+ }
409
+
410
+ for i, option in enumerate(options, 1):
411
+ desc = descriptions.get(option, "")
412
+ console.print(f" [cyan]{i}[/cyan]. {option} - {desc}")
413
+
414
+ choice = click.prompt(
415
+ "\nSelect deployment option",
416
+ type=click.IntRange(1, len(options)),
417
+ default=3, # Default to "None"
418
+ )
419
+
420
+ selected = options[choice - 1]
421
+
422
+ if selected == "None":
423
+ return []
424
+ elif selected == "docker-compose":
425
+ return ["Docker", "docker-compose"]
426
+ else:
427
+ return [selected]
428
+
429
+
430
+ def prompt_package_manager_selection(settings: Any) -> str:
431
+ """
432
+ Prompt for package manager selection.
433
+
434
+ Args:
435
+ settings: FastkitConfig instance
436
+
437
+ Returns:
438
+ Package manager name
439
+ """
440
+ console.print("\n[bold cyan]📦 Package Manager Selection[/bold cyan]")
441
+ console.print("[dim]Choose your preferred package manager[/dim]\n")
442
+
443
+ options = {}
444
+ for manager, config in settings.PACKAGE_MANAGER_CONFIG.items():
445
+ options[manager] = config["description"]
446
+
447
+ render_selection_table("Package Managers", options)
448
+
449
+ # Find default index
450
+ default_idx = list(options.keys()).index(settings.DEFAULT_PACKAGE_MANAGER) + 1
451
+
452
+ choice = click.prompt(
453
+ "\nSelect package manager",
454
+ type=click.IntRange(1, len(options)),
455
+ default=default_idx,
456
+ )
457
+
458
+ return cast(str, list(options.keys())[choice - 1])
459
+
460
+
461
+ def prompt_custom_packages() -> List[str]:
462
+ """
463
+ Prompt for custom package names (manual entry).
464
+
465
+ Returns:
466
+ List of custom package names
467
+ """
468
+ console.print("\n[bold cyan]➕ Additional Custom Packages[/bold cyan]")
469
+ console.print(
470
+ "[dim]Enter any additional packages (comma-separated) or press Enter to skip[/dim]\n"
471
+ )
472
+
473
+ packages_input = console.input("[cyan]Package names:[/cyan] ").strip()
474
+
475
+ if not packages_input:
476
+ return []
477
+
478
+ # Split by comma and clean
479
+ packages = [pkg.strip() for pkg in packages_input.split(",") if pkg.strip()]
480
+
481
+ return packages
482
+
483
+
484
+ def prompt_additional_features(settings: Any) -> Dict[str, Any]:
485
+ """
486
+ Prompt for all additional features in sequence.
487
+
488
+ This is a convenience function that calls all feature prompts.
489
+
490
+ Args:
491
+ settings: FastkitConfig instance
492
+
493
+ Returns:
494
+ Dictionary with all feature selections
495
+ """
496
+ features: Dict[str, Any] = {}
497
+
498
+ # Database
499
+ features["database"] = prompt_database_selection(settings)
500
+
501
+ # Authentication
502
+ features["authentication"] = prompt_authentication_selection(settings)
503
+
504
+ # Async tasks
505
+ features["async_tasks"] = prompt_async_tasks_selection(settings)
506
+
507
+ # Caching
508
+ features["caching"] = prompt_caching_selection(settings)
509
+
510
+ # Monitoring
511
+ features["monitoring"] = prompt_monitoring_selection(settings)
512
+
513
+ # Testing
514
+ features["testing"] = prompt_testing_selection(settings)
515
+
516
+ # Utilities
517
+ features["utilities"] = prompt_utilities_selection(settings)
518
+
519
+ # Deployment
520
+ features["deployment"] = prompt_deployment_options()
521
+
522
+ # Package manager
523
+ features["package_manager"] = prompt_package_manager_selection(settings)
524
+
525
+ # Custom packages
526
+ features["custom_packages"] = prompt_custom_packages()
527
+
528
+ return features