4 Commits

Author SHA1 Message Date
Mondo Diaz
20e5a2948e Fix httpx.Timeout configuration in PyPI proxy 2026-01-29 16:39:13 -06:00
Mondo Diaz
b4e23d9899 Fix PyPI proxy tests to use ORCHARD_TEST_URL env var directly 2026-01-29 16:00:56 -06:00
Mondo Diaz
aa3bd05d46 Fix PyPI proxy tests to use unauthenticated_client fixture 2026-01-29 15:51:29 -06:00
Mondo Diaz
810e024d09 Update CHANGELOG with issue #108 2026-01-29 15:41:56 -06:00
3 changed files with 19 additions and 21 deletions

View File

@@ -7,17 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added ### Added
- Added transparent PyPI proxy implementing PEP 503 Simple API - Added transparent PyPI proxy implementing PEP 503 Simple API (#108)
- `GET /pypi/simple/` - package index (proxied from upstream) - `GET /pypi/simple/` - package index (proxied from upstream)
- `GET /pypi/simple/{package}/` - version list with rewritten download links - `GET /pypi/simple/{package}/` - version list with rewritten download links
- `GET /pypi/simple/{package}/{filename}` - download with automatic caching - `GET /pypi/simple/{package}/{filename}` - download with automatic caching
- Allows `pip install --index-url https://orchard.../pypi/simple/ <package>` - Allows `pip install --index-url https://orchard.../pypi/simple/ <package>`
- Artifacts cached on first access through configured upstream sources - Artifacts cached on first access through configured upstream sources
- Added `POST /api/v1/cache/resolve` endpoint to cache packages by coordinates instead of URL (#108)
### Changed ### Changed
- Upstream sources table text is now centered under column headers - Upstream sources table text is now centered under column headers (#108)
- ENV badge now appears inline with source name instead of separate column - ENV badge now appears inline with source name instead of separate column (#108)
- Test and Edit buttons now have more prominent button styling - Test and Edit buttons now have more prominent button styling (#108)
- Reduced footer padding for cleaner layout (#108)
### Fixed ### Fixed
- Fixed purge_seed_data crash when deleting access permissions - was comparing UUID to VARCHAR column (#107) - Fixed purge_seed_data crash when deleting access permissions - was comparing UUID to VARCHAR column (#107)

View File

@@ -156,10 +156,7 @@ async def pypi_simple_index(
simple_url = source.url.rstrip('/') + '/simple/' simple_url = source.url.rstrip('/') + '/simple/'
timeout = httpx.Timeout( timeout = httpx.Timeout(PROXY_READ_TIMEOUT, connect=PROXY_CONNECT_TIMEOUT)
connect=PROXY_CONNECT_TIMEOUT,
read=PROXY_READ_TIMEOUT,
)
with httpx.Client(timeout=timeout, follow_redirects=False) as client: with httpx.Client(timeout=timeout, follow_redirects=False) as client:
response = client.get( response = client.get(
@@ -247,10 +244,7 @@ async def pypi_package_versions(
package_url = source.url.rstrip('/') + f'/simple/{normalized_name}/' package_url = source.url.rstrip('/') + f'/simple/{normalized_name}/'
timeout = httpx.Timeout( timeout = httpx.Timeout(PROXY_READ_TIMEOUT, connect=PROXY_CONNECT_TIMEOUT)
connect=PROXY_CONNECT_TIMEOUT,
read=PROXY_READ_TIMEOUT,
)
with httpx.Client(timeout=timeout, follow_redirects=False) as client: with httpx.Client(timeout=timeout, follow_redirects=False) as client:
response = client.get( response = client.get(
@@ -385,10 +379,7 @@ async def pypi_download_file(
headers.update(_build_auth_headers(matched_source)) headers.update(_build_auth_headers(matched_source))
auth = _get_basic_auth(matched_source) if matched_source else None auth = _get_basic_auth(matched_source) if matched_source else None
timeout = httpx.Timeout( timeout = httpx.Timeout(300.0, connect=PROXY_CONNECT_TIMEOUT) # 5 minutes for large files
connect=PROXY_CONNECT_TIMEOUT,
read=300.0, # 5 minutes for large files
)
# Fetch the file # Fetch the file
logger.info(f"PyPI proxy: fetching {filename} from {upstream_url}") logger.info(f"PyPI proxy: fetching {filename} from {upstream_url}")

View File

@@ -1,9 +1,15 @@
"""Integration tests for PyPI transparent proxy.""" """Integration tests for PyPI transparent proxy."""
import os
import pytest import pytest
import httpx import httpx
def get_base_url():
"""Get the base URL for the Orchard server from environment."""
return os.environ.get("ORCHARD_TEST_URL", "http://localhost:8080")
class TestPyPIProxyEndpoints: class TestPyPIProxyEndpoints:
"""Tests for PyPI proxy endpoints. """Tests for PyPI proxy endpoints.
@@ -13,8 +19,7 @@ class TestPyPIProxyEndpoints:
@pytest.mark.integration @pytest.mark.integration
def test_pypi_simple_index_no_sources(self): def test_pypi_simple_index_no_sources(self):
"""Test that /pypi/simple/ returns 503 when no sources configured.""" """Test that /pypi/simple/ returns 503 when no sources configured."""
# Use unauthenticated client since PyPI proxy is public with httpx.Client(base_url=get_base_url(), timeout=30.0) as client:
with httpx.Client(base_url="http://localhost:8080") as client:
response = client.get("/pypi/simple/") response = client.get("/pypi/simple/")
# Should return 503 when no PyPI upstream sources are configured # Should return 503 when no PyPI upstream sources are configured
assert response.status_code == 503 assert response.status_code == 503
@@ -23,7 +28,7 @@ class TestPyPIProxyEndpoints:
@pytest.mark.integration @pytest.mark.integration
def test_pypi_package_no_sources(self): def test_pypi_package_no_sources(self):
"""Test that /pypi/simple/{package}/ returns 503 when no sources configured.""" """Test that /pypi/simple/{package}/ returns 503 when no sources configured."""
with httpx.Client(base_url="http://localhost:8080") as client: with httpx.Client(base_url=get_base_url(), timeout=30.0) as client:
response = client.get("/pypi/simple/requests/") response = client.get("/pypi/simple/requests/")
assert response.status_code == 503 assert response.status_code == 503
assert "No PyPI upstream sources configured" in response.json()["detail"] assert "No PyPI upstream sources configured" in response.json()["detail"]
@@ -31,7 +36,7 @@ class TestPyPIProxyEndpoints:
@pytest.mark.integration @pytest.mark.integration
def test_pypi_download_missing_upstream_param(self): def test_pypi_download_missing_upstream_param(self):
"""Test that /pypi/simple/{package}/{filename} requires upstream param.""" """Test that /pypi/simple/{package}/{filename} requires upstream param."""
with httpx.Client(base_url="http://localhost:8080") as client: with httpx.Client(base_url=get_base_url(), timeout=30.0) as client:
response = client.get("/pypi/simple/requests/requests-2.31.0.tar.gz") response = client.get("/pypi/simple/requests/requests-2.31.0.tar.gz")
assert response.status_code == 400 assert response.status_code == 400
assert "upstream" in response.json()["detail"].lower() assert "upstream" in response.json()["detail"].lower()
@@ -75,7 +80,7 @@ class TestPyPIPackageNormalization:
# requests, Requests, requests_, requests- # requests, Requests, requests_, requests-
# The endpoint normalizes to lowercase with hyphens # The endpoint normalizes to lowercase with hyphens
with httpx.Client(base_url="http://localhost:8080") as client: with httpx.Client(base_url=get_base_url(), timeout=30.0) as client:
# Without upstream sources, we get 503, but the normalization # Without upstream sources, we get 503, but the normalization
# happens before the source lookup # happens before the source lookup
response = client.get("/pypi/simple/Requests/") response = client.get("/pypi/simple/Requests/")