11"""Core scaffolding logic for creating Python projects."""
22
3+ import logging
34import shutil
45import subprocess
56import sys
67from pathlib import Path
78
8- from jinja2 import Environment , FileSystemLoader
9+ from jinja2 import Environment , FileSystemLoader , select_autoescape
910
1011from python_project_deployment .models import ProjectConfig
1112
@@ -30,8 +31,8 @@ def __init__(self, config: ProjectConfig) -> None:
3031 config: Project configuration with all necessary settings
3132 """
3233 self .config = config
34+ self .logger = logging .getLogger (__name__ )
3335 self .template_dir = Path (__file__ ).parent / "templates"
34- from jinja2 import select_autoescape
3536
3637 self .jinja_env = Environment (
3738 loader = FileSystemLoader (str (self .template_dir )),
@@ -119,20 +120,24 @@ def _init_git(self, dest: Path) -> None:
119120 Args:
120121 dest: Project directory path
121122 """
123+ git_path = shutil .which ("git" )
124+ if not git_path :
125+ raise RuntimeError ("git not found in PATH; please install git" )
126+
122127 subprocess .run (
123- ["git" , "init" , "-q" ],
128+ [git_path , "init" , "-q" ],
124129 cwd = dest ,
125130 check = True ,
126131 capture_output = True ,
127132 )
128133 subprocess .run (
129- ["git" , "add" , "." ],
134+ [git_path , "add" , "." ],
130135 cwd = dest ,
131136 check = True ,
132137 capture_output = True ,
133138 )
134139 subprocess .run (
135- ["git" , "commit" , "-q" , "-m" , "chore: initial scaffold" ],
140+ [git_path , "commit" , "-q" , "-m" , "chore: initial scaffold" ],
136141 cwd = dest ,
137142 check = True ,
138143 capture_output = True ,
@@ -150,6 +155,7 @@ def _setup_environment(self, dest: Path) -> None:
150155
151156 if uv_path :
152157 # Use uv to create venv and run installs
158+ self .logger .info ("Using uv binary: %s" , uv_path )
153159 subprocess .run ([uv_path , "venv" ], cwd = dest , check = True , capture_output = True )
154160 subprocess .run (
155161 [uv_path , "pip" , "install" , "-e" , ".[dev]" ],
@@ -176,9 +182,8 @@ def _setup_environment(self, dest: Path) -> None:
176182 if proc .returncode != 0 :
177183 raise RuntimeError ("pre-commit hooks failed:\n " + proc .stdout + proc .stderr )
178184 except Exception as exc : # pragma: no cover - best-effort
179- raise RuntimeError (
180- f"Failed to install/run pre-commit hooks using uv: { exc } "
181- ) from exc
185+ msg = "Failed to install/run pre-commit hooks using uv: " + str (exc )
186+ raise RuntimeError (msg ) from exc
182187 else :
183188 # No uv: create a standard venv and install uv + pre-commit into it
184189 try :
@@ -206,6 +211,7 @@ def _setup_environment(self, dest: Path) -> None:
206211
207212 # Run pre-commit install and run using the venv's pre-commit binary
208213 precommit_bin = venv_dir / "bin" / "pre-commit"
214+ self .logger .info ("Using venv pre-commit: %s" , precommit_bin )
209215 subprocess .run (
210216 [str (precommit_bin ), "install" ],
211217 cwd = dest ,
@@ -222,45 +228,65 @@ def _setup_environment(self, dest: Path) -> None:
222228 if proc .returncode != 0 :
223229 raise RuntimeError ("pre-commit hooks failed:\n " + proc .stdout + proc .stderr )
224230 except Exception as exc : # pragma: no cover - best-effort
225- raise RuntimeError (
226- f"Failed to create venv/install tools/run pre-commit: { exc } "
227- ) from exc
231+ msg = "Failed to create venv/install tools/run pre-commit: " + str (exc )
232+ raise RuntimeError (msg ) from exc
228233
229234 def _run_tests (self , dest : Path ) -> None :
230235 """Run pytest with coverage.
231236
232237 Args:
233238 dest: Project directory path
234239 """
235- subprocess .run (
236- [
237- "uv" ,
240+ uv_path = shutil .which ("uv" )
241+ if uv_path :
242+ cmd = [
243+ uv_path ,
238244 "run" ,
239245 "pytest" ,
240246 f"--cov={ self .config .package_name } " ,
241247 "--cov-report=term-missing" ,
242- ],
243- cwd = dest ,
244- check = True ,
245- )
248+ ]
249+ self .logger .info ("Running tests via uv: %s" , uv_path )
250+ else :
251+ python_bin = dest / ".venv" / "bin" / "python"
252+ if python_bin .exists ():
253+ cmd = [
254+ str (python_bin ),
255+ "-m" ,
256+ "pytest" ,
257+ f"--cov={ self .config .package_name } " ,
258+ "--cov-report=term-missing" ,
259+ ]
260+ self .logger .info ("Running tests via venv python: %s" , python_bin )
261+ else :
262+ cmd = [
263+ sys .executable ,
264+ "-m" ,
265+ "pytest" ,
266+ f"--cov={ self .config .package_name } " ,
267+ "--cov-report=term-missing" ,
268+ ]
269+ self .logger .info ("Running tests via system python: %s" , sys .executable )
270+
271+ subprocess .run (cmd , cwd = dest , check = True )
246272
247273 def _build_docs (self , dest : Path ) -> None :
248274 """Build Sphinx documentation.
249275
250276 Args:
251277 dest: Project directory path
252278 """
253- subprocess . run (
254- [
255- "uv" ,
256- "run" ,
257- "sphinx-build" ,
258- "-b" ,
259- "html" ,
260- " docs" ,
261- " docs/_build/html" ,
262- ],
263- cwd = dest ,
264- check = True ,
265- capture_output = True ,
266- )
279+ uv_path = shutil . which ( "uv" )
280+ if uv_path :
281+ cmd = [ uv_path , "run" , "sphinx-build" , "-b" , "html" , "docs" , "docs/_build/html" ]
282+ self . logger . info ( "Building docs via uv: %s" , uv_path )
283+ else :
284+ python_bin = dest / ".venv" / "bin" / "python"
285+ if python_bin . exists ():
286+ cmd = [ str ( python_bin ), "-m" , "sphinx" , "-b" , "html" , " docs" , "docs/_build/html" ]
287+ self . logger . info ( "Building docs via venv python: %s" , python_bin )
288+ else :
289+ cmd = [ sys . executable , "-m" , "sphinx" , "-b" , "html" , "docs" , "docs/_build/html" ]
290+ self . logger . info ( "Building docs via system python: %s" , sys . executable )
291+
292+ subprocess . run ( cmd , cwd = dest , check = True , capture_output = True )
0 commit comments