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,241 @@
|
|
|
1
|
+
# --------------------------------------------------------------------------
|
|
2
|
+
# Selection logic and UI rendering for interactive mode
|
|
3
|
+
#
|
|
4
|
+
# Provides:
|
|
5
|
+
# - Table-based option displays using rich
|
|
6
|
+
# - Multi-select prompts
|
|
7
|
+
# - Confirmation dialogs
|
|
8
|
+
#
|
|
9
|
+
# @author bnbong bbbong9@gmail.com
|
|
10
|
+
# --------------------------------------------------------------------------
|
|
11
|
+
from typing import Any, Dict, List, Optional
|
|
12
|
+
|
|
13
|
+
from rich.panel import Panel
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
|
|
16
|
+
from fastapi_fastkit.utils.main import console
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def render_selection_table(
|
|
20
|
+
title: str, options: Dict[str, str], show_numbers: bool = True
|
|
21
|
+
) -> None:
|
|
22
|
+
"""
|
|
23
|
+
Render a selection table using rich.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
title: Table title
|
|
27
|
+
options: Dictionary of option_key: description
|
|
28
|
+
show_numbers: Whether to show numbered options (for single-select)
|
|
29
|
+
"""
|
|
30
|
+
table = Table(title=title, show_header=True, header_style="bold magenta")
|
|
31
|
+
|
|
32
|
+
if show_numbers:
|
|
33
|
+
table.add_column("Option", style="cyan", no_wrap=True)
|
|
34
|
+
table.add_column("Name", style="green")
|
|
35
|
+
table.add_column("Description", style="yellow")
|
|
36
|
+
|
|
37
|
+
for i, (key, description) in enumerate(options.items(), 1):
|
|
38
|
+
if show_numbers:
|
|
39
|
+
table.add_row(str(i), key, description)
|
|
40
|
+
else:
|
|
41
|
+
table.add_row(key, description)
|
|
42
|
+
|
|
43
|
+
console.print(table)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def render_feature_options(
|
|
47
|
+
category: str, options: List[str], descriptions: Dict[str, str]
|
|
48
|
+
) -> None:
|
|
49
|
+
"""
|
|
50
|
+
Render feature options for multi-select.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
category: Feature category name
|
|
54
|
+
options: List of option names
|
|
55
|
+
descriptions: Dictionary of option descriptions
|
|
56
|
+
"""
|
|
57
|
+
console.print(f"\n[bold cyan]{category}[/bold cyan]")
|
|
58
|
+
console.print(
|
|
59
|
+
"[dim]Select options by entering numbers (comma-separated, e.g., 1,3,4)[/dim]\n"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
for i, option in enumerate(options, 1):
|
|
63
|
+
desc = descriptions.get(option, "")
|
|
64
|
+
console.print(f" [cyan]{i}[/cyan]. {option} [dim]{desc}[/dim]")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def multi_select_prompt(
|
|
68
|
+
title: str, options: List[str], descriptions: Optional[Dict[str, str]] = None
|
|
69
|
+
) -> List[int]:
|
|
70
|
+
"""
|
|
71
|
+
Multi-select prompt with comma-separated input.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
title: Prompt title
|
|
75
|
+
options: List of options
|
|
76
|
+
descriptions: Optional descriptions for each option
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
List of selected indices (0-based)
|
|
80
|
+
"""
|
|
81
|
+
descriptions = descriptions or {}
|
|
82
|
+
|
|
83
|
+
console.print(f"\n[bold]{title}[/bold]")
|
|
84
|
+
render_feature_options(title, options, descriptions)
|
|
85
|
+
|
|
86
|
+
while True:
|
|
87
|
+
selected = console.input(
|
|
88
|
+
"\n[cyan]Your choice (or press Enter to skip):[/cyan] "
|
|
89
|
+
).strip()
|
|
90
|
+
|
|
91
|
+
if not selected:
|
|
92
|
+
return []
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
# Parse comma-separated numbers
|
|
96
|
+
indices = [int(x.strip()) for x in selected.split(",") if x.strip()]
|
|
97
|
+
|
|
98
|
+
# Validate indices
|
|
99
|
+
if all(1 <= idx <= len(options) for idx in indices):
|
|
100
|
+
# Convert to 0-based indices
|
|
101
|
+
return [idx - 1 for idx in indices]
|
|
102
|
+
else:
|
|
103
|
+
console.print(
|
|
104
|
+
f"[red]Invalid selection. Please enter numbers between 1 and {len(options)}[/red]"
|
|
105
|
+
)
|
|
106
|
+
except ValueError:
|
|
107
|
+
console.print(
|
|
108
|
+
"[red]Invalid input. Please enter comma-separated numbers (e.g., 1,3,4)[/red]"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def confirm_selections(config: Dict[str, Any]) -> bool:
|
|
113
|
+
"""
|
|
114
|
+
Display summary and confirm all selections.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
config: Complete project configuration
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
True if user confirms, False otherwise
|
|
121
|
+
"""
|
|
122
|
+
table = Table(
|
|
123
|
+
title="📋 Project Configuration Summary",
|
|
124
|
+
show_header=True,
|
|
125
|
+
header_style="bold magenta",
|
|
126
|
+
)
|
|
127
|
+
table.add_column("Setting", style="cyan", no_wrap=True)
|
|
128
|
+
table.add_column("Value", style="green")
|
|
129
|
+
|
|
130
|
+
# Basic information
|
|
131
|
+
table.add_row("Project Name", config.get("project_name", "N/A"))
|
|
132
|
+
table.add_row("Author", config.get("author", "N/A"))
|
|
133
|
+
table.add_row("Email", config.get("author_email", "N/A"))
|
|
134
|
+
table.add_row("Description", config.get("description", "N/A"))
|
|
135
|
+
|
|
136
|
+
# Template
|
|
137
|
+
if config.get("base_template"):
|
|
138
|
+
table.add_row("Base Template", config["base_template"])
|
|
139
|
+
|
|
140
|
+
# Database
|
|
141
|
+
db_info = config.get("database", {})
|
|
142
|
+
if db_info.get("type") != "None":
|
|
143
|
+
table.add_row("Database", db_info.get("type", "None"))
|
|
144
|
+
|
|
145
|
+
# Authentication
|
|
146
|
+
auth_type = config.get("authentication", "None")
|
|
147
|
+
if auth_type != "None":
|
|
148
|
+
table.add_row("Authentication", auth_type)
|
|
149
|
+
|
|
150
|
+
# Async tasks
|
|
151
|
+
task_type = config.get("async_tasks", "None")
|
|
152
|
+
if task_type != "None":
|
|
153
|
+
table.add_row("Async Tasks", task_type)
|
|
154
|
+
|
|
155
|
+
# Caching
|
|
156
|
+
cache_type = config.get("caching", "None")
|
|
157
|
+
if cache_type != "None":
|
|
158
|
+
table.add_row("Caching", cache_type)
|
|
159
|
+
|
|
160
|
+
# Monitoring
|
|
161
|
+
monitoring_type = config.get("monitoring", "None")
|
|
162
|
+
if monitoring_type != "None":
|
|
163
|
+
table.add_row("Monitoring", monitoring_type)
|
|
164
|
+
|
|
165
|
+
# Testing
|
|
166
|
+
testing_type = config.get("testing", "None")
|
|
167
|
+
if testing_type != "None":
|
|
168
|
+
table.add_row("Testing", testing_type)
|
|
169
|
+
|
|
170
|
+
# Utilities
|
|
171
|
+
utilities = config.get("utilities", [])
|
|
172
|
+
if utilities:
|
|
173
|
+
table.add_row("Utilities", ", ".join(utilities))
|
|
174
|
+
|
|
175
|
+
# Custom packages
|
|
176
|
+
custom = config.get("custom_packages", [])
|
|
177
|
+
if custom:
|
|
178
|
+
table.add_row("Custom Packages", ", ".join(custom))
|
|
179
|
+
|
|
180
|
+
# Package manager
|
|
181
|
+
table.add_row("Package Manager", config.get("package_manager", "uv"))
|
|
182
|
+
|
|
183
|
+
console.print("\n")
|
|
184
|
+
console.print(table)
|
|
185
|
+
console.print("\n")
|
|
186
|
+
|
|
187
|
+
# Show total dependencies count
|
|
188
|
+
total_deps = len(config.get("all_dependencies", []))
|
|
189
|
+
console.print(f"[bold cyan]Total dependencies to install:[/bold cyan] {total_deps}")
|
|
190
|
+
|
|
191
|
+
# Ask for confirmation
|
|
192
|
+
response = (
|
|
193
|
+
console.input(
|
|
194
|
+
"\n[bold yellow]Proceed with project creation? [Y/n]:[/bold yellow] "
|
|
195
|
+
)
|
|
196
|
+
.strip()
|
|
197
|
+
.lower()
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
return response in ["y", "yes", ""]
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def display_feature_catalog(
|
|
204
|
+
catalog: Dict[str, Dict[str, List[str]]], descriptions: Dict[str, str]
|
|
205
|
+
) -> None:
|
|
206
|
+
"""
|
|
207
|
+
Display the complete feature catalog.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
catalog: Package catalog from settings
|
|
211
|
+
descriptions: Feature descriptions from settings
|
|
212
|
+
"""
|
|
213
|
+
panel = Panel(
|
|
214
|
+
"[bold cyan]FastAPI-fastkit Feature Catalog[/bold cyan]\n\n"
|
|
215
|
+
"Available features and their associated packages for interactive project creation.",
|
|
216
|
+
title="📚 Feature Catalog",
|
|
217
|
+
border_style="cyan",
|
|
218
|
+
)
|
|
219
|
+
console.print(panel)
|
|
220
|
+
|
|
221
|
+
for category, options in catalog.items():
|
|
222
|
+
if category == "utilities":
|
|
223
|
+
continue # Skip utilities as they may have empty package lists
|
|
224
|
+
|
|
225
|
+
desc = descriptions.get(category, category.title())
|
|
226
|
+
table = Table(title=f"{desc}", show_header=True, header_style="bold yellow")
|
|
227
|
+
table.add_column("Option", style="cyan", no_wrap=True)
|
|
228
|
+
table.add_column("Packages", style="green")
|
|
229
|
+
|
|
230
|
+
for option_name, packages in options.items():
|
|
231
|
+
if option_name == "None":
|
|
232
|
+
continue
|
|
233
|
+
pkg_list = (
|
|
234
|
+
", ".join(packages)
|
|
235
|
+
if packages
|
|
236
|
+
else "[dim](No additional packages)[/dim]"
|
|
237
|
+
)
|
|
238
|
+
table.add_row(option_name, pkg_list)
|
|
239
|
+
|
|
240
|
+
console.print("\n")
|
|
241
|
+
console.print(table)
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# --------------------------------------------------------------------------
|
|
2
|
+
# Input validation utilities for interactive mode
|
|
3
|
+
#
|
|
4
|
+
# Provides validation functions for:
|
|
5
|
+
# - Package names
|
|
6
|
+
# - Project names
|
|
7
|
+
# - Feature compatibility checking
|
|
8
|
+
#
|
|
9
|
+
# @author bnbong bbbong9@gmail.com
|
|
10
|
+
# --------------------------------------------------------------------------
|
|
11
|
+
import re
|
|
12
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
13
|
+
|
|
14
|
+
from fastapi_fastkit.utils.main import REGEX as EMAIL_REGEX
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def validate_package_name(package: str) -> bool:
|
|
18
|
+
"""
|
|
19
|
+
Validate package name format.
|
|
20
|
+
|
|
21
|
+
Checks if the package name follows basic PyPI package naming conventions.
|
|
22
|
+
Does not validate existence on PyPI (that will be added in v1.2.1+).
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
package: Package name to validate
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
True if format is valid, False otherwise
|
|
29
|
+
"""
|
|
30
|
+
if not package or not isinstance(package, str):
|
|
31
|
+
return False
|
|
32
|
+
|
|
33
|
+
# Remove version specifiers and extras for validation
|
|
34
|
+
package_name = re.split(r"[<>=!\[]", package)[0].strip()
|
|
35
|
+
|
|
36
|
+
# Basic package name pattern: letters, numbers, hyphens, underscores, dots
|
|
37
|
+
# Must start with letter or number
|
|
38
|
+
pattern = r"^[a-zA-Z0-9][a-zA-Z0-9._-]*$"
|
|
39
|
+
|
|
40
|
+
return bool(re.match(pattern, package_name))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def validate_project_name(name: str) -> Tuple[bool, Optional[str]]:
|
|
44
|
+
"""
|
|
45
|
+
Validate project name.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
name: Project name to validate
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Tuple of (is_valid, error_message)
|
|
52
|
+
"""
|
|
53
|
+
if not name:
|
|
54
|
+
return False, "Project name cannot be empty"
|
|
55
|
+
|
|
56
|
+
if not name.replace("_", "").replace("-", "").isalnum():
|
|
57
|
+
return (
|
|
58
|
+
False,
|
|
59
|
+
"Project name must contain only letters, numbers, hyphens, and underscores",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
if name[0].isdigit():
|
|
63
|
+
return False, "Project name cannot start with a number"
|
|
64
|
+
|
|
65
|
+
# Check for Python keywords
|
|
66
|
+
import keyword
|
|
67
|
+
|
|
68
|
+
if keyword.iskeyword(name):
|
|
69
|
+
return False, f"Project name '{name}' is a Python keyword"
|
|
70
|
+
|
|
71
|
+
return True, None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def validate_email_format(email: str) -> bool:
|
|
75
|
+
"""
|
|
76
|
+
Validate email address format using utils/main.py REGEX pattern.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
email: Email address to validate
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
True if format is valid, False otherwise
|
|
83
|
+
"""
|
|
84
|
+
if not email:
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
return bool(re.match(EMAIL_REGEX, email))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def validate_feature_compatibility(
|
|
91
|
+
config: Dict[str, Any],
|
|
92
|
+
) -> Tuple[bool, Optional[str]]:
|
|
93
|
+
"""
|
|
94
|
+
Check compatibility between selected features.
|
|
95
|
+
|
|
96
|
+
Validates that selected features don't have conflicting requirements.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
config: Project configuration dictionary
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Tuple of (is_compatible, warning_message)
|
|
103
|
+
"""
|
|
104
|
+
warnings = []
|
|
105
|
+
|
|
106
|
+
# Check if FastAPI-Users is selected with database
|
|
107
|
+
if config.get("authentication") == "FastAPI-Users":
|
|
108
|
+
db_type = config.get("database", {}).get("type", "None")
|
|
109
|
+
if db_type == "None":
|
|
110
|
+
warnings.append(
|
|
111
|
+
"FastAPI-Users requires a database. Consider selecting PostgreSQL or MySQL."
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Check if Celery is selected with Redis
|
|
115
|
+
if config.get("async_tasks") == "Celery" or config.get("async_tasks") == "Dramatiq":
|
|
116
|
+
# These already include Redis in their dependencies
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
# Check if caching Redis is selected along with task queue Redis
|
|
120
|
+
cache_type = config.get("caching", "None")
|
|
121
|
+
task_type = config.get("async_tasks", "None")
|
|
122
|
+
if cache_type == "Redis" and task_type in ["Celery", "Dramatiq"]:
|
|
123
|
+
# This is actually compatible, just a note
|
|
124
|
+
warnings.append(
|
|
125
|
+
"Info: Redis will be used for both caching and task queue (shared dependency)."
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# If there are warnings, return them
|
|
129
|
+
if warnings:
|
|
130
|
+
return True, " | ".join(warnings)
|
|
131
|
+
|
|
132
|
+
return True, None
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def sanitize_custom_packages(packages: List[str]) -> List[str]:
|
|
136
|
+
"""
|
|
137
|
+
Clean and deduplicate custom package list.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
packages: List of package names (possibly with duplicates or empty strings)
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Cleaned list of unique package names
|
|
144
|
+
"""
|
|
145
|
+
if not packages:
|
|
146
|
+
return []
|
|
147
|
+
|
|
148
|
+
# Remove empty strings and strip whitespace
|
|
149
|
+
cleaned = [pkg.strip() for pkg in packages if pkg and pkg.strip()]
|
|
150
|
+
|
|
151
|
+
# Remove duplicates while preserving order
|
|
152
|
+
seen = set()
|
|
153
|
+
unique_packages = []
|
|
154
|
+
for pkg in cleaned:
|
|
155
|
+
pkg_lower = pkg.lower()
|
|
156
|
+
if pkg_lower not in seen:
|
|
157
|
+
seen.add(pkg_lower)
|
|
158
|
+
unique_packages.append(pkg)
|
|
159
|
+
|
|
160
|
+
# Validate each package name
|
|
161
|
+
valid_packages = [pkg for pkg in unique_packages if validate_package_name(pkg)]
|
|
162
|
+
|
|
163
|
+
return valid_packages
|
fastapi_fastkit/backend/main.py
CHANGED
|
@@ -308,6 +308,67 @@ def _process_pyproject_file(
|
|
|
308
308
|
raise BackendExceptions(f"Failed to process pyproject.toml: {e}")
|
|
309
309
|
|
|
310
310
|
|
|
311
|
+
def update_setup_py_dependencies(project_dir: str, dependencies: List[str]) -> None:
|
|
312
|
+
"""
|
|
313
|
+
Update setup.py file's install_requires list with new dependencies.
|
|
314
|
+
|
|
315
|
+
This function finds and updates the install_requires list in setup.py
|
|
316
|
+
to match the dependencies selected during interactive mode.
|
|
317
|
+
|
|
318
|
+
:param project_dir: Path to the project directory
|
|
319
|
+
:param dependencies: List of dependency specifications
|
|
320
|
+
"""
|
|
321
|
+
setup_py_path = os.path.join(project_dir, "setup.py")
|
|
322
|
+
|
|
323
|
+
if not os.path.exists(setup_py_path):
|
|
324
|
+
debug_log("setup.py not found, skipping dependency update", "info")
|
|
325
|
+
return
|
|
326
|
+
|
|
327
|
+
try:
|
|
328
|
+
with open(setup_py_path, "r", encoding="utf-8") as f:
|
|
329
|
+
content = f.read()
|
|
330
|
+
|
|
331
|
+
# Build the new install_requires list
|
|
332
|
+
deps_str = ",\n ".join(f'"{dep}"' for dep in dependencies)
|
|
333
|
+
|
|
334
|
+
# Try to replace existing install_requires first (with type annotation)
|
|
335
|
+
pattern = r"install_requires:\s*list\[str\]\s*=\s*\[(.*?)\]"
|
|
336
|
+
if re.search(pattern, content, re.DOTALL):
|
|
337
|
+
content = re.sub(
|
|
338
|
+
pattern,
|
|
339
|
+
f"install_requires: list[str] = [\n {deps_str},\n]",
|
|
340
|
+
content,
|
|
341
|
+
flags=re.DOTALL,
|
|
342
|
+
)
|
|
343
|
+
debug_log(
|
|
344
|
+
"Updated install_requires with type annotation in setup.py", "info"
|
|
345
|
+
)
|
|
346
|
+
else:
|
|
347
|
+
# Fallback: try without type annotation
|
|
348
|
+
pattern_old = r"install_requires\s*=\s*\[(.*?)\]"
|
|
349
|
+
if re.search(pattern_old, content, re.DOTALL):
|
|
350
|
+
content = re.sub(
|
|
351
|
+
pattern_old,
|
|
352
|
+
f"install_requires = [\n {deps_str},\n]",
|
|
353
|
+
content,
|
|
354
|
+
flags=re.DOTALL,
|
|
355
|
+
)
|
|
356
|
+
debug_log("Updated install_requires in setup.py", "info")
|
|
357
|
+
else:
|
|
358
|
+
debug_log("Could not find install_requires in setup.py", "warning")
|
|
359
|
+
return
|
|
360
|
+
|
|
361
|
+
# Write updated content
|
|
362
|
+
with open(setup_py_path, "w", encoding="utf-8") as f:
|
|
363
|
+
f.write(content)
|
|
364
|
+
|
|
365
|
+
print_info(f"Updated setup.py with {len(dependencies)} dependencies")
|
|
366
|
+
|
|
367
|
+
except (OSError, UnicodeDecodeError) as e:
|
|
368
|
+
debug_log(f"Error updating setup.py dependencies: {e}", "error")
|
|
369
|
+
print_warning(f"Could not update setup.py: {e}")
|
|
370
|
+
|
|
371
|
+
|
|
311
372
|
def create_venv_with_manager(project_dir: str, manager_type: str = "pip") -> str:
|
|
312
373
|
"""
|
|
313
374
|
Create a virtual environment using the specified package manager.
|
|
@@ -405,6 +466,22 @@ def generate_dependency_file_with_manager(
|
|
|
405
466
|
package_manager.generate_dependency_file(
|
|
406
467
|
dependencies, project_name, author, author_email, description
|
|
407
468
|
)
|
|
469
|
+
|
|
470
|
+
# Also generate requirements.txt for pip compatibility (if not using pip)
|
|
471
|
+
if manager_type != "pip":
|
|
472
|
+
from pathlib import Path
|
|
473
|
+
|
|
474
|
+
requirements_path = Path(project_dir) / "requirements.txt"
|
|
475
|
+
try:
|
|
476
|
+
with open(requirements_path, "w", encoding="utf-8") as f:
|
|
477
|
+
for dep in dependencies:
|
|
478
|
+
f.write(f"{dep}\n")
|
|
479
|
+
debug_log("Generated requirements.txt for pip compatibility", "info")
|
|
480
|
+
except (OSError, UnicodeEncodeError) as e:
|
|
481
|
+
debug_log(
|
|
482
|
+
f"Warning: Could not generate requirements.txt: {e}", "warning"
|
|
483
|
+
)
|
|
484
|
+
|
|
408
485
|
except Exception as e:
|
|
409
486
|
debug_log(f"Error generating dependency file with {manager_type}: {e}", "error")
|
|
410
487
|
raise BackendExceptions(f"Failed to generate dependency file: {str(e)}")
|
|
@@ -8,10 +8,14 @@ import subprocess
|
|
|
8
8
|
import sys
|
|
9
9
|
from typing import List
|
|
10
10
|
|
|
11
|
-
from fastapi_fastkit import console
|
|
12
11
|
from fastapi_fastkit.core.exceptions import BackendExceptions
|
|
13
12
|
from fastapi_fastkit.utils.logging import debug_log, get_logger
|
|
14
|
-
from fastapi_fastkit.utils.main import
|
|
13
|
+
from fastapi_fastkit.utils.main import (
|
|
14
|
+
console,
|
|
15
|
+
handle_exception,
|
|
16
|
+
print_error,
|
|
17
|
+
print_success,
|
|
18
|
+
)
|
|
15
19
|
|
|
16
20
|
from .base import BasePackageManager
|
|
17
21
|
|
|
@@ -8,10 +8,14 @@ import subprocess
|
|
|
8
8
|
import sys
|
|
9
9
|
from typing import List
|
|
10
10
|
|
|
11
|
-
from fastapi_fastkit import console
|
|
12
11
|
from fastapi_fastkit.core.exceptions import BackendExceptions
|
|
13
12
|
from fastapi_fastkit.utils.logging import debug_log, get_logger
|
|
14
|
-
from fastapi_fastkit.utils.main import
|
|
13
|
+
from fastapi_fastkit.utils.main import (
|
|
14
|
+
console,
|
|
15
|
+
handle_exception,
|
|
16
|
+
print_error,
|
|
17
|
+
print_success,
|
|
18
|
+
)
|
|
15
19
|
|
|
16
20
|
from .base import BasePackageManager
|
|
17
21
|
|
|
@@ -6,10 +6,14 @@
|
|
|
6
6
|
import subprocess
|
|
7
7
|
from typing import List
|
|
8
8
|
|
|
9
|
-
from fastapi_fastkit import console
|
|
10
9
|
from fastapi_fastkit.core.exceptions import BackendExceptions
|
|
11
10
|
from fastapi_fastkit.utils.logging import debug_log, get_logger
|
|
12
|
-
from fastapi_fastkit.utils.main import
|
|
11
|
+
from fastapi_fastkit.utils.main import (
|
|
12
|
+
console,
|
|
13
|
+
handle_exception,
|
|
14
|
+
print_error,
|
|
15
|
+
print_success,
|
|
16
|
+
)
|
|
13
17
|
|
|
14
18
|
from .base import BasePackageManager
|
|
15
19
|
|
|
@@ -7,10 +7,14 @@ import os
|
|
|
7
7
|
import subprocess
|
|
8
8
|
from typing import List
|
|
9
9
|
|
|
10
|
-
from fastapi_fastkit import console
|
|
11
10
|
from fastapi_fastkit.core.exceptions import BackendExceptions
|
|
12
11
|
from fastapi_fastkit.utils.logging import debug_log, get_logger
|
|
13
|
-
from fastapi_fastkit.utils.main import
|
|
12
|
+
from fastapi_fastkit.utils.main import (
|
|
13
|
+
console,
|
|
14
|
+
handle_exception,
|
|
15
|
+
print_error,
|
|
16
|
+
print_success,
|
|
17
|
+
)
|
|
14
18
|
|
|
15
19
|
from .base import BasePackageManager
|
|
16
20
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# --------------------------------------------------------------------------
|
|
2
|
+
# Project Builder module for FastAPI-fastkit
|
|
3
|
+
#
|
|
4
|
+
# Provides project building functionality including:
|
|
5
|
+
# - Dependency collection and management
|
|
6
|
+
# - Dynamic configuration file generation
|
|
7
|
+
# - Template merging
|
|
8
|
+
#
|
|
9
|
+
# @author bnbong bbbong9@gmail.com
|
|
10
|
+
# --------------------------------------------------------------------------
|
|
11
|
+
from .config_generator import DynamicConfigGenerator
|
|
12
|
+
from .dependency_collector import DependencyCollector
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"DependencyCollector",
|
|
16
|
+
"DynamicConfigGenerator",
|
|
17
|
+
]
|