2424import subprocess
2525import sys
2626from pathlib import Path
27- from typing import Any , Dict , List
27+ from typing import Any , Callable , Dict , List , Tuple
2828
2929from fastapi_fastkit .backend .main import (
3030 create_venv ,
3333)
3434from fastapi_fastkit .backend .transducer import copy_and_convert_template
3535from 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
3737from fastapi_fastkit .utils .main import print_error , print_success , print_warning
3838
39+ logger = get_logger (__name__ )
40+
3941
4042class 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
217259if __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