LibreDevOpsHelpers.Python/LibreDevOpsHelpers.Python.psm1
# Create a .venv virtual environment in the current directory function New-Venv { [CmdletBinding()] param( [string]$VenvName = ".venv", [string]$VenvPath = $( Get-Location ).Path ) $inv = $MyInvocation.MyCommand.Name # Determine correct Python executable if (Get-Command python3 -ErrorAction SilentlyContinue) { $pythonCmd = "python3" } elseif (Get-Command python -ErrorAction SilentlyContinue) { $pythonCmd = "python" } else { _LogMessage -Level ERROR -Message "Python not found" -InvocationName $inv return } # Get Python version $pythonVersion = & $pythonCmd --version _LogMessage -Level INFO -Message "Python version: $pythonVersion" -InvocationName $inv $VirtualEnvPath = Join-Path $VenvPath $VenvName if (Test-Path $VirtualEnvPath) { _LogMessage -Level WARN -Message "Virtual environment $VirtualEnvPath already exists." -InvocationName $inv return } _LogMessage -Level INFO -Message "Running: $pythonCmd -m venv $VirtualEnvPath" -InvocationName $inv & $pythonCmd -m venv $VirtualEnvPath _LogMessage -Level INFO -Message "Virtual environment '$VenvName' created at '$VirtualEnvPath'" -InvocationName $inv } function Initialize-Venv { [CmdletBinding()] param( [string]$VenvName = '.venv', [string]$VenvPath = (Get-Location).Path, [switch]$VerifyVenv ) $VirtualEnvPath = Join-Path $VenvPath $VenvName if (-not (Test-Path $VirtualEnvPath)) { throw "No virtual environment at '$VirtualEnvPath'" } if ($IsWindows) { . (Join-Path $VirtualEnvPath 'Scripts\Activate.ps1') } else { $env:VIRTUAL_ENV = $VirtualEnvPath $env:PATH = "$VirtualEnvPath/bin:$env:PATH" if (-not (Test-Path function:\__origPrompt)) { Set-Item function:\__origPrompt (Get-Command prompt) } function global:prompt { "($( Split-Path $env:VIRTUAL_ENV -Leaf )) " + (& __origPrompt) } } if ($VerifyVenv) { $venvPython = if ($IsWindows) { Join-Path $VirtualEnvPath 'Scripts/python.exe' } else { Join-Path $VirtualEnvPath 'bin/python' } $prefix = & $venvPython -c 'import sys, pathlib; print(pathlib.Path(sys.prefix).resolve())' if ($prefix -ne (Get-Item $VirtualEnvPath).FullName) { throw "Venv verification failed – expected $VirtualEnvPath, got $prefix" } } } function Use-Venv { [CmdletBinding()] param( [string] $VenvPath = (Get-Location).Path, [string] $VenvName = '.venv' ) $inv = $MyInvocation.MyCommand.Name $venvRoot = Join-Path $VenvPath $VenvName # region ─── CREATE IF NEEDED ────────────────────────────────────────────── if (-not (Test-Path $venvRoot)) { _LogMessage -Level INFO -Message "Creating venv with: python -m venv $venvRoot" -InvocationName $inv try { python -m venv $venvRoot } catch { _LogMessage -Level ERROR -Message "Failed to create venv: $_" -InvocationName $inv throw } } # endregion # region ─── ACTIVATE ────────────────────────────────────────────────────── if ($IsWindows) { $activateScript = Join-Path $venvRoot 'Scripts\Activate.ps1' if (-not (Test-Path $activateScript)) { _LogMessage -Level ERROR -Message "Cannot find $activateScript" -InvocationName $inv throw } _LogMessage -Level INFO -Message "Dot-sourcing $activateScript" -InvocationName $inv . $activateScript } else { # Two env-vars = “activated” for PowerShell on Linux/macOS/WSL _LogMessage -Level INFO -Message "Setting PATH/VIRTUAL_ENV for POSIX PowerShell" -InvocationName $inv $env:VIRTUAL_ENV = (Resolve-Path $venvRoot) $env:PATH = "$env:VIRTUAL_ENV/bin$([IO.Path]::PathSeparator)$env:PATH" # Optional cosmetic prompt if (-not (Get-Command __origPrompt -ErrorAction SilentlyContinue)) { $orig = Get-Command prompt -ErrorAction SilentlyContinue if ($orig) { Set-Item function:\__origPrompt $orig } } function global:prompt { "($(Split-Path $env:VIRTUAL_ENV -Leaf)) " + (& { & __origPrompt }) } } _LogMessage -Level INFO -Message "Venv '$VenvName' is active." -InvocationName $inv } # Deactivate the current virtual environment function Clear-Venv { [CmdletBinding()] param( [string]$VenvName = ".venv" ) $inv = $MyInvocation.MyCommand.Name if (Get-Command -Name deactivate -CommandType Function -ErrorAction SilentlyContinue) { _LogMessage -Level INFO -Message "Running: deactivate" -InvocationName $inv deactivate _LogMessage -Level INFO -Message "Virtual environment '$VenvName' deactivated." -InvocationName $inv } else { _LogMessage -Level ERROR -Message "No virtual environment is currently active." -InvocationName $inv } } # Fully remove a virtual environment function Remove-Venv { [CmdletBinding()] param( [string]$VenvName = ".venv", [string]$VenvPath = $( Get-Location ).Path ) $inv = $MyInvocation.MyCommand.Name $VirtualEnvPath = Join-Path $VenvPath $VenvName if (Test-Path $VirtualEnvPath) { try { _LogMessage -Level INFO -Message "Running: Remove-Item -Path $VirtualEnvPath -Recurse -Force" -InvocationName $inv Remove-Item -Path $VirtualEnvPath -Recurse -Force _LogMessage -Level INFO -Message "Virtual environment '$VenvName' fully removed from '$VirtualEnvPath'" -InvocationName $inv } catch { _LogMessage -Level ERROR -Message "Failed to remove virtual environment '$VenvName' at '$VirtualEnvPath'" -InvocationName $inv } } else { _LogMessage -Level ERROR -Message "Virtual environment '$VenvName' not found at '$VirtualEnvPath'" -InvocationName $inv } } function Invoke-PythonInstallRequirements { [CmdletBinding()] param( [ValidateScript({ Test-Path $_ -PathType Container })] [string]$ProjectPath = $( Get-Location ).Path, [string]$RequirementsFile = 'requirements.txt', [switch]$Upgrade ) $inv = $MyInvocation.MyCommand.Name # Determine correct Python executable if (Get-Command python3 -ErrorAction SilentlyContinue) { $pythonCmd = "python3" } elseif (Get-Command python -ErrorAction SilentlyContinue) { $pythonCmd = "python" } else { Write-Host "Error: Python not found" return } try { $reqPath = Join-Path $ProjectPath $RequirementsFile if (-not (Test-Path $reqPath)) { throw "Requirements file not found: $reqPath" } $pyArgs = @('-m', 'pip', 'install', '-r', "$reqPath", '--target', "$ProjectPath/.python_packages/lib/site-packages") if ($Upgrade) { $pyArgs += '--upgrade' } _LogMessage -Level INFO -Message "$pythonCmd $( $pyArgs -join ' ' )" -InvocationName $inv & $pythonCmd @pyArgs if ($LASTEXITCODE) { throw "pip install failed (exit $LASTEXITCODE)." } _LogMessage -Level INFO -Message 'Dependencies installed OK.' -InvocationName $inv } catch { _LogMessage -Level ERROR -Message $_.Exception.Message -InvocationName $inv throw } } function Remove-PythonPackages { [CmdletBinding()] param( [string]$ProjectPath = $( Get-Location ).Path ) $packagesPath = Join-Path $ProjectPath ".python_packages" if (Test-Path $packagesPath) { try { Remove-Item -Path $packagesPath -Recurse -Force Write-Host "Python packages directory removed successfully." } catch { Write-Host "Error: Failed to remove the Python packages directory." } } else { Write-Host "Error: Python packages directory not found." } } ############################################################################# # Run pytest and optionally emit JUnit XML & coverage reports ############################################################################# function Invoke-PytestRun { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateScript({ Test-Path $_ -PathType Container })] [string]$ProjectPath, [string]$PythonExe = 'python', [string]$JUnitXmlPath = 'pytest-results.xml', # set '' to skip [string]$CoverageXmlPath = 'coverage.xml', # set '' to skip [string]$CoverageHtmlDir = 'htmlcov', # set '' to skip [string]$CliExtraArgsJson # JSON array ) $inv = $MyInvocation.MyCommand.Name $orig = Get-Location try { Set-Location $ProjectPath # ── Convert extra-args JSON ⇢ array ──────────────────────────────── $extra = @() if ($CliExtraArgsJson) { try { $extra = [string[]]($CliExtraArgsJson | ConvertFrom-Json) } catch { throw "CliExtraArgsJson is not valid JSON array: $( $_.Exception.Message )" } } # ── Assemble pytest command list ─────────────────────────────────── $cmd = @('-m', 'pytest') if ($JUnitXmlPath) { $cmd += "--junitxml=$JUnitXmlPath" } if ($CoverageXmlPath) { $cmd += @('--cov', '.', "--cov-report", "xml:$CoverageXmlPath") if ($CoverageHtmlDir) { $cmd += "--cov-report", "html:$CoverageHtmlDir" } } if ($extra) { $cmd += $extra } _LogMessage -Level INFO -Message "$PythonExe $( $cmd -join ' ' )" -InvocationName $inv & $PythonExe @cmd $code = $LASTEXITCODE _LogMessage -Level DEBUG -Message "pytest exit-code: $code" -InvocationName $inv if ($code) { throw "pytest failed (exit $code)." } _LogMessage -Level INFO -Message 'pytest completed successfully.' -InvocationName $inv } catch { _LogMessage -Level ERROR -Message $_.Exception.Message -InvocationName $inv throw } finally { Set-Location $orig } } ############################################################################# # Export public symbols ############################################################################# Export-ModuleMember -Function ` New-Venv, ` Initialize-Venv, ` Clear-Venv, ` Use-Venv, ` Remove-Venv, ` Invoke-PythonInstallRequirements, ` Remove-PythonPackages, ` Invoke-PytestRun |