Skip to content

Commit dfec192

Browse files
committed
[REFACTOR] major codes optimization & test coverage expansion
1 parent 58b4d00 commit dfec192

File tree

17 files changed

+1287
-495
lines changed

17 files changed

+1287
-495
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ coverage/
2323
# Built Visual Studio Code Extensions
2424
*.vsix
2525

26+
.cursor/
27+
2628
### JetBrains template
2729
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
2830
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839

src/fastapi_fastkit/backend/inspector.py

Lines changed: 107 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import subprocess
2525
import sys
2626
from pathlib import Path
27-
from typing import Any, Dict, List
27+
from typing import Any, Callable, Dict, List, Tuple
2828

2929
from fastapi_fastkit.backend.main import (
3030
create_venv,
@@ -33,46 +33,77 @@
3333
)
3434
from fastapi_fastkit.backend.transducer import copy_and_convert_template
3535
from fastapi_fastkit.core.settings import settings
36-
from fastapi_fastkit.utils.logging import get_logger
36+
from fastapi_fastkit.utils.logging import debug_log, get_logger
3737
from fastapi_fastkit.utils.main import print_error, print_success, print_warning
3838

39+
logger = get_logger(__name__)
40+
3941

4042
class TemplateInspector:
43+
"""
44+
Template inspector for validating FastAPI templates.
45+
46+
Uses context manager protocol for proper resource cleanup.
47+
"""
48+
4149
def __init__(self, template_path: str):
4250
self.template_path = Path(template_path)
4351
self.errors: List[str] = []
4452
self.warnings: List[str] = []
4553
self.temp_dir = os.path.join(os.path.dirname(__file__), "temp")
54+
self._cleanup_needed = False
4655

47-
# Create temp directory and copy template
48-
os.makedirs(self.temp_dir, exist_ok=True)
49-
copy_and_convert_template(str(self.template_path), self.temp_dir)
50-
51-
def __del__(self) -> None:
52-
"""Cleanup temp directory when inspector is destroyed."""
53-
if os.path.exists(self.temp_dir):
56+
def __enter__(self) -> "TemplateInspector":
57+
"""Enter context manager - create temp directory and copy template."""
58+
try:
59+
os.makedirs(self.temp_dir, exist_ok=True)
60+
copy_and_convert_template(str(self.template_path), self.temp_dir)
61+
self._cleanup_needed = True
62+
debug_log(f"Created temporary directory at {self.temp_dir}", "debug")
63+
return self
64+
except Exception as e:
65+
debug_log(f"Failed to setup template inspector: {e}", "error")
66+
self._cleanup()
67+
raise
68+
69+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
70+
"""Exit context manager - cleanup temp directory."""
71+
self._cleanup()
72+
73+
def _cleanup(self) -> None:
74+
"""Cleanup temp directory if it exists and cleanup is needed."""
75+
if self._cleanup_needed and os.path.exists(self.temp_dir):
5476
try:
5577
shutil.rmtree(self.temp_dir)
78+
debug_log(f"Cleaned up temp directory {self.temp_dir}", "debug")
5679
except OSError as e:
57-
if settings.DEBUG_MODE:
58-
logger = get_logger(__name__)
59-
logger.warning(
60-
f"Failed to cleanup temp directory {self.temp_dir}: {e}"
61-
)
80+
debug_log(
81+
f"Failed to cleanup temp directory {self.temp_dir}: {e}", "warning"
82+
)
83+
finally:
84+
self._cleanup_needed = False
6285

6386
def inspect_template(self) -> bool:
64-
"""Inspect the template is valid FastAPI application."""
65-
checks = [
66-
self._check_file_structure,
67-
self._check_file_extensions,
68-
self._check_dependencies,
69-
self._check_fastapi_implementation,
70-
self._test_template,
87+
"""
88+
Inspect the template to validate it's a proper FastAPI application.
89+
90+
:return: True if template is valid, False otherwise
91+
"""
92+
checks: List[Tuple[str, Callable[[], bool]]] = [
93+
("File Structure", self._check_file_structure),
94+
("File Extensions", self._check_file_extensions),
95+
("Dependencies", self._check_dependencies),
96+
("FastAPI Implementation", self._check_fastapi_implementation),
97+
("Template Tests", self._test_template),
7198
]
7299

73-
for check in checks:
74-
if not check():
100+
for check_name, check_func in checks:
101+
debug_log(f"Running check: {check_name}", "info")
102+
if not check_func():
103+
debug_log(f"Check failed: {check_name}", "error")
75104
return False
105+
debug_log(f"Check passed: {check_name}", "info")
106+
76107
return True
77108

78109
def _check_file_structure(self) -> bool:
@@ -144,74 +175,85 @@ def _check_fastapi_implementation(self) -> bool:
144175
return True
145176

146177
def _test_template(self) -> bool:
147-
"""Run the tests in the converted template directory."""
148-
test_dir = Path(self.temp_dir) / "tests"
149-
if not test_dir.exists():
150-
self.errors.append("Tests directory not found")
151-
return False
178+
"""Run tests on the template if they exist."""
179+
test_dir = os.path.join(self.temp_dir, "tests")
180+
if not os.path.exists(test_dir):
181+
self.warnings.append("No tests directory found")
182+
return True
152183

153184
try:
154-
# Create virtual environment
185+
# Create virtual environment for testing
155186
venv_path = create_venv(self.temp_dir)
156-
157-
# Install dependencies
158187
install_dependencies(self.temp_dir, venv_path)
159188

160-
# Run tests using the venv's pytest
189+
# Run tests
161190
if os.name == "nt": # Windows
162-
pytest_path = os.path.join(venv_path, "Scripts", "pytest")
163-
else: # Linux/Mac
164-
pytest_path = os.path.join(venv_path, "bin", "pytest")
191+
python_executable = os.path.join(venv_path, "Scripts", "python")
192+
else: # Unix-based
193+
python_executable = os.path.join(venv_path, "bin", "python")
165194

166195
result = subprocess.run(
167-
[pytest_path, str(test_dir)],
196+
[python_executable, "-m", "pytest", test_dir, "-v"],
197+
cwd=self.temp_dir,
168198
capture_output=True,
169199
text=True,
170-
cwd=self.temp_dir,
171-
timeout=300, # 5 minute timeout
200+
timeout=300, # 5 minutes timeout
172201
)
173202

174203
if result.returncode != 0:
175204
self.errors.append(f"Tests failed: {result.stderr}")
176205
return False
177206

207+
debug_log("All tests passed successfully", "info")
208+
return True
209+
178210
except subprocess.TimeoutExpired:
179211
self.errors.append("Tests timed out after 5 minutes")
180212
return False
181-
except (OSError, subprocess.SubprocessError) as e:
213+
except subprocess.CalledProcessError as e:
182214
self.errors.append(f"Error running tests: {e}")
183215
return False
216+
except Exception as e:
217+
self.errors.append(f"Unexpected error during testing: {e}")
218+
return False
219+
220+
def get_report(self) -> Dict[str, Any]:
221+
"""
222+
Get inspection report with errors and warnings.
223+
224+
:return: Dictionary containing inspection results
225+
"""
226+
return {
227+
"template_path": str(self.template_path),
228+
"errors": self.errors,
229+
"warnings": self.warnings,
230+
"is_valid": len(self.errors) == 0,
231+
}
184232

185-
return True
186233

234+
def inspect_fastapi_template(template_path: str) -> Dict[str, Any]:
235+
"""
236+
Convenience function to inspect a FastAPI template.
187237
188-
def inspect_template(template_path: str) -> Dict[str, Any]:
189-
"""Run the template inspection and return the result."""
190-
inspector = TemplateInspector(template_path)
191-
is_valid = inspector.inspect_template()
192-
result: Dict[str, Any] = {
193-
"valid": is_valid,
194-
"errors": inspector.errors,
195-
"warnings": inspector.warnings,
196-
}
238+
:param template_path: Path to the template to inspect
239+
:return: Inspection report dictionary
240+
"""
241+
with TemplateInspector(template_path) as inspector:
242+
is_valid = inspector.inspect_template()
243+
report = inspector.get_report()
197244

198-
if result["valid"]:
199-
print_success("Template inspection passed successfully! ✨")
200-
elif result["errors"]:
201-
error_messages = [str(error) for error in result["errors"]]
202-
print_error(
203-
"Template inspection failed with following errors:\n"
204-
+ "\n".join(f"- {error}" for error in error_messages)
205-
)
245+
if is_valid:
246+
print_success(f"Template {template_path} is valid!")
247+
else:
248+
print_error(f"Template {template_path} validation failed")
249+
for error in inspector.errors:
250+
print_error(f" - {error}")
206251

207-
if result["warnings"]:
208-
warning_messages = [str(warning) for warning in result["warnings"]]
209-
print_warning(
210-
"Template has following warnings:\n"
211-
+ "\n".join(f"- {warning}" for warning in warning_messages)
212-
)
252+
if inspector.warnings:
253+
for warning in inspector.warnings:
254+
print_warning(f" - {warning}")
213255

214-
return result
256+
return report
215257

216258

217259
if __name__ == "__main__":
@@ -220,4 +262,4 @@ def inspect_template(template_path: str) -> Dict[str, Any]:
220262
sys.exit(1)
221263

222264
template_dir = sys.argv[1]
223-
inspect_template(template_dir)
265+
inspect_fastapi_template(template_dir)

0 commit comments

Comments
 (0)