snek.psm1


function Import-PythonRuntime {
    param(
        [ValidateSet("v3.7", 'v3.8', 'v3.9', 'v3.10', 'v3.11')]
        $Version = "v3.11",
        [Parameter()]
        $PythonDLL
    )

    if (-not $PythonDLL) {
        if ($IsCoreCLR) {
            if ($IsWindows) {
                $pythonDll = "python$($Version.Replace('v', '').Replace('.', '')).dll"
            }
            elseif ($IsLinux) {
                $pythonDll = "libpython$($Version.Replace('v', '')).so"
            }
            else {
                $pythonDll = "libpython$($Version.Replace('v', '')).dylib"
            }
        }
        else {
            $pythonDll = "python$($Version.Replace('v', '').Replace('.', '')).dll"
        }
    }

    $Runtime = [System.IO.Path]::Combine($PSScriptRoot, "Python.Runtime.dll")
    [System.Reflection.Assembly]::LoadFrom($Runtime) | Out-Null

    [Python.Runtime.Runtime]::PythonDLL = $pythonDll
    [Python.Runtime.PythonEngine]::Initialize()  | Out-Null
    [Python.Runtime.PythonEngine]::BeginAllowThreads() | Out-Null
}

function Use-Python {
    param(
        [ScriptBlock]$Script,
        [ValidateSet("v3.7", 'v3.8', 'v3.9', 'v3.10', 'v3.11')]
        $Version = "v3.11",
        [Parameter()]
        $PythonDLL
    )

    Import-PythonRuntime -Version $Version

    $runtime = $null
    try {
        $runtime = [Python.Runtime.Py]::Gil()

        $Script.Invoke()
    } 
    Finally {
        $runtime.Dispose()
    }
}

function Import-PythonPackage {
    param(
        $Name
    )

    # Wrap in an ArrayList so PowerShell doesn't try to unwind the dynamic because it will throw
    # an exception
    $ar = [System.Collections.ArrayList]::new()
    $ar.add([Python.Runtime.Py]::Import($Name)) | Out-Null
    $ar
}

function Invoke-Python {
    param(
        [Parameter(Mandatory)]
        $Code,
        [Parameter()]
        [type]$ReturnType
    )

    if ($ReturnType) {
        if ($null -eq $Scope) {
            [Python.Runtime.PythonEngine]::Eval($Code) -as $ReturnType
        }
        else {
            $Scope.Eval($Code) -as $ReturnType
        }
        
    }
    else {
        if ($null -eq $Scope) {
            [Python.Runtime.PythonEngine]::Exec($Code)
        }
        else {
            $Scope.Exec($Code)
        }
    }
}

function Use-PythonScope {
    param(
        [Parameter(Mandatory)]
        [scriptblock]$ScriptBlock
    )

    $Scope = $null
    try {
        $Scope = [Python.Runtime.Py]::CreateScope()
        $ScriptBlock.Invoke()
    }
    finally {
        $Scope.Dispose()
    }
}

function Set-PythonVariable {
    param(
        [Parameter(Mandatory)]
        $Name,
        [Parameter(Mandatory)]
        $Value
    )

    if ($null -eq $Scope) {
        throw "Set-PythonVariable must be called within a Use-PythonScope" 
    }

    $PyObject = [Python.Runtime.ConverterExtension]::ToPython($Value)
    $Scope.Set($Name, $PyObject)
}

function Install-PythonPackage {
    param(
        $Name,
        [ValidateSet("v3.7", 'v3.8', 'v3.9', 'v3.10', 'v3.11')]
        $Version = "v3.11",
        [Parameter()]
        $PythonDLL
    )

    Invoke-Pip -Action "install" -Name $Name -Version $Version
}

function Uninstall-PythonPackage {
    param(
        $Name,
        [ValidateSet("v3.7", 'v3.8', 'v3.9', 'v3.10', 'v3.11')]
        $Version = "v3.11",
        [Parameter()]
        $PythonDLL
    )

    Invoke-Pip -Action "uninstall" -Name $Name -Version $Version
}

function Invoke-Pip {
    param(
        $Action,
        $Name,
        [ValidateSet("v3.7", 'v3.8', 'v3.9', 'v3.10', 'v3.11')]
        $Version = "v3.11",
        [Parameter()]
        $PythonDLL
    )

    Use-Python -Version $Version -Script {
        Invoke-Python -Code "import subprocess
subprocess.check_call(['python', '-m', 'pip', '$Action', '$Name'])"

    }
}