Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 58 additions & 7 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Test

on:
push:
branches: [main]
branches: [main, python2-tes]
pull_request:
branches: [main]

Expand All @@ -13,18 +13,25 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["2.7", "3.9", "3.10", "3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v4

- name: Build test image
run: |
docker build \
--build-arg PY_VERSION=${{ matrix.python-version }} \
-t uv-anywhere-test:${{ matrix.python-version }} \
-f tests/Dockerfile \
.
if [ "${{ matrix.python-version }}" = "2.7" ]; then
docker build \
-t uv-anywhere-test:${{ matrix.python-version }} \
-f tests/Dockerfile.py27 \
.
else
docker build \
--build-arg PY_VERSION=${{ matrix.python-version }} \
-t uv-anywhere-test:${{ matrix.python-version }} \
-f tests/Dockerfile \
.
fi

- name: Run tests
run: |
Expand All @@ -49,6 +56,28 @@ jobs:
- name: Run tests
run: python tests/run_tests.py

test-macos-py27:
name: macOS (Python 2.7)
runs-on: macos-latest

steps:
- uses: actions/checkout@v4

- name: Install Python 2.7
run: |
# Download and install Python 2.7 from python.org (Intel build)
# This will run under Rosetta on ARM Macs
curl -LO https://www.python.org/ftp/python/2.7.18/python-2.7.18-macosx10.9.pkg
sudo installer -pkg python-2.7.18-macosx10.9.pkg -target /
# Add to PATH
echo "/Library/Frameworks/Python.framework/Versions/2.7/bin" >> $GITHUB_PATH

- name: Run tests
run: |
# Run under Rosetta on ARM Macs
arch -x86_64 /Library/Frameworks/Python.framework/Versions/2.7/bin/python --version
arch -x86_64 /Library/Frameworks/Python.framework/Versions/2.7/bin/python tests/run_tests.py

test-windows:
name: Windows (Python ${{ matrix.python-version }})
runs-on: windows-latest
Expand All @@ -67,3 +96,25 @@ jobs:

- name: Run tests
run: python tests/run_tests.py

test-windows-py27:
name: Windows (Python 2.7)
runs-on: windows-latest

steps:
- uses: actions/checkout@v4

- name: Install Python 2.7
shell: pwsh
run: |
$url = "https://www.python.org/ftp/python/2.7.18/python-2.7.18.amd64.msi"
$output = "$env:TEMP\python-2.7.18.amd64.msi"
Invoke-WebRequest -Uri $url -OutFile $output
Start-Process msiexec.exe -Wait -ArgumentList "/i $output /quiet TARGETDIR=C:\Python27 ALLUSERS=1"
echo "C:\Python27" | Out-File -Append -FilePath $env:GITHUB_PATH
echo "C:\Python27\Scripts" | Out-File -Append -FilePath $env:GITHUB_PATH

- name: Run tests
run: |
C:\Python27\python.exe --version
C:\Python27\python.exe tests/run_tests.py
17 changes: 17 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# /// script
# requires-python = "==3.10.19"
# dependencies = ["requests==2.32.4"]
# ///
exec(
__import__("urllib.request", fromlist=[""])
.urlopen("https://raw.githubusercontent.com/nathom/uv-anywhere/main/uv_anywhere.py")
.read()
.decode()
)
# from uv_anywhere import ensure_uv

ensure_uv()
import sys, requests

print("Python version:", sys.version)
print("Requests version:", requests.__version__)
22 changes: 22 additions & 0 deletions tests/Dockerfile.py27
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
FROM python:2.7-slim

# The Python 2.7 image is based on Debian Buster which has been archived.
# Update apt sources to use the archive.
RUN sed -i 's|deb.debian.org|archive.debian.org|g' /etc/apt/sources.list && \
sed -i 's|security.debian.org|archive.debian.org|g' /etc/apt/sources.list && \
sed -i '/buster-updates/d' /etc/apt/sources.list

# Install curl for the uv installer (it shells out to curl)
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*

# Ensure uv is NOT pre-installed (clean environment)
RUN which uv && exit 1 || true

WORKDIR /app

# Copy the project
COPY uv_anywhere.py .
COPY tests/ tests/

# Run the tests (use 'python' which works for both Python 2 and 3)
CMD ["python", "tests/run_tests.py"]
54 changes: 37 additions & 17 deletions uv_anywhere.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,35 +99,55 @@ def _parse_deps(path):

def _install_uv():
"""Install uv, return path to binary."""
import subprocess, tempfile
import subprocess, tempfile, ssl

try:
from urllib.request import urlopen
except ImportError:
from urllib2 import urlopen

is_win = sys.platform == "win32"
url = "https://astral.sh/uv/install." + ("ps1" if is_win else "sh")
# Create SSL context (unverified for Python 2.7 compatibility)
try:
ctx = ssl.create_default_context()
except AttributeError:
ctx = None
if ctx:
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE

resp = urlopen(url, timeout=30)
script = resp.read().decode("utf-8")
resp.close()
is_win = sys.platform == "win32"

if is_win:
fd, path = tempfile.mkstemp(suffix=".ps1")
os.write(fd, script.encode("utf-8"))
os.close(fd)
# Download binary directly (avoids PowerShell module issues)
import platform, zipfile
home = os.environ.get("USERPROFILE", os.path.expanduser("~"))
uv_dir = os.path.join(home, ".local", "bin")
if not os.path.exists(uv_dir):
os.makedirs(uv_dir)
arch = "x86_64" if platform.machine().lower() in ("amd64", "x86_64") else "i686"
url = "https://github.com/astral-sh/uv/releases/latest/download/uv-%s-pc-windows-msvc.zip" % arch
resp = urlopen(url, timeout=60, context=ctx) if ctx else urlopen(url, timeout=60)
fd, zip_path = tempfile.mkstemp(suffix=".zip")
try:
from shutil import which as _which
ps_exe = "pwsh" if _which("pwsh") else "powershell"
subprocess.Popen(
[ps_exe, "-ExecutionPolicy", "Bypass", "-NoProfile", "-File", path],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
).communicate()
os.write(fd, resp.read())
os.close(fd)
resp.close()
with zipfile.ZipFile(zip_path) as z:
for name in z.namelist():
if name.endswith("uv.exe"):
with z.open(name) as src, open(os.path.join(uv_dir, "uv.exe"), "wb") as dst:
dst.write(src.read())
break
finally:
os.unlink(path)
try:
os.unlink(zip_path)
except OSError:
pass
else:
url = "https://astral.sh/uv/install.sh"
resp = urlopen(url, timeout=30, context=ctx) if ctx else urlopen(url, timeout=30)
script = resp.read().decode("utf-8")
resp.close()
subprocess.Popen(["sh", "-c", script], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()

return _find_uv()
Expand Down