Skip to content

Instantly share code, notes, and snippets.

@blakeNaccarato
Last active March 19, 2024 04:28
Show Gist options
  • Save blakeNaccarato/e9120dcb965c693941d01b2f5a5f4fd7 to your computer and use it in GitHub Desktop.
Save blakeNaccarato/e9120dcb965c693941d01b2f5a5f4fd7 to your computer and use it in GitHub Desktop.
Cross-platform PowerShell script (tested on Windows, Ubuntu, MacOS) to sync a Python environment with `uv`, example for `requirements.txt`, `pyproject.toml` is similar.
<#.SYNOPSIS
Sync Python dependencies.#>
Param(
# Python version.
[string]$Version = '3.11'
)
$REQUIREMENTS = 'requirements.txt'
function Sync-Py {
<#.SYNOPSIS
Sync Python dependencies.#>
'SYNCING' | Write-PyProgress
$py = Get-Py $Version
'INSTALLING UV' | Write-PyProgress
$uv = Get-Content $REQUIREMENTS | Select-String -Pattern '^uv'
Invoke-Expression "$py -m pip install $uv
'SYNCING DEPENDENCIES' | Write-PyProgress
Invoke-Expression "$py -m uv pip sync $REQUIREMENTS"
'SYNCED' | Write-PyProgress -Done
}
function Get-Py {
<#.SYNOPSIS
Get Python interpreter, global in CI, or activated virtual environment locally.#>
Param([Parameter(ValueFromPipeline)][string]$Version)
begin { $venvPath = '.venv' }
process {
$Version = $Version ? $Version : (Get-PyDevVersion)
$GlobalPy = Get-PyGlobal $Version
if (!(Test-Path $venvPath)) {
"CREATING VIRTUAL ENVIRONMENT: $venvPath" | Write-PyProgress
Invoke-Expression "$GlobalPy -m venv $venvPath"
}
$VenvPy = Start-PyEnv $venvPath
$foundVersion = Invoke-Expression "$VenvPy --version"
if ($foundVersion |
Select-String -Pattern "^Python $([Regex]::Escape($Version))\.\d+$") {
"SYNCING VIRTUAL ENVIRONMENT: $(Resolve-Path $VenvPy -Relative)" |
Write-PyProgress
return $VenvPy
}
"REMOVING VIRTUAL ENVIRONMENT WITH INCORRECT PYTHON: $Env:VIRTUAL_ENV" |
Write-PyProgress -Done
Remove-Item -Recurse -Force $Env:VIRTUAL_ENV
return Get-Py $Version
}
}
function Get-PyDevVersion {
<#.SYNOPSIS
Get the expected version of Python for development, from '.copier-answers.yml'.#>
$ver_pattern = '^python_version:\s?["'']([^"'']+)["'']$'
$re = Get-Content '.copier-answers.yml' | Select-String -Pattern $ver_pattern
return $re.Matches.Groups[1].value
}
function Get-PyGlobal {
<#.SYNOPSIS
Get global Python interpreter.#>
Param([Parameter(Mandatory, ValueFromPipeline)][string]$Version)
process {
if ((Test-Command 'py') -and
(py '--list' | Select-String -Pattern "^\s?-V:$([Regex]::Escape($Version))")) {
return "py -$Version"
}
elseif (Test-Command "python$Version") { return "python$Version" }
elseif (Test-Command 'python') { return 'python' }
throw "Expected Python $Version, which does not appear to be installed. Ensure it is installed (e.g. from https://www.python.org/downloads/) and run this script again."
}
}
function Start-PyEnv {
<#.SYNOPSIS
Activate and get the Python interpreter for the virtual environment.#>
Param([Parameter(Mandatory, ValueFromPipeline)][string]$venvPath)
process {
if ($IsWindows) { $bin = 'Scripts'; $py = 'python.exe' }
else { $bin = 'bin'; $py = 'python' }
Invoke-Expression "$venvPath/$bin/Activate.ps1"
return "$Env:VIRTUAL_ENV/$bin/$py"
}
}
function Write-PyProgress {
<#.SYNOPSIS
Write progress and completion messages.#>
Param([Parameter(Mandatory, ValueFromPipeline)][string]$Message,
[switch]$Done)
begin { $Color = $Done ? 'Green' : 'Yellow' }
process {
if (!$Done) { Write-Host }
Write-Host "$Message$($Done ? '' : '...')" -ForegroundColor $Color
}
}
function Test-Command {
<#.SYNOPSIS
Like `Get-Command` but errors are ignored.#>
return Get-Command @args -ErrorAction 'Ignore'
}
Sync-Py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment