mirror of
https://github.com/tiennm99/litellm.git
synced 2026-06-17 22:48:35 +00:00
build: migrate packaging, CI, and Docker from Poetry to uv (#25007)
* build: migrate packaging metadata to uv * ci: move automation and local tooling to uv * docker: migrate image builds and runtime setup to uv * docs: update install and deployment guidance for uv * chore: align auxiliary scripts and tests with uv * test: harden test_litellm isolation * fix: keep release and health check images self-contained * build: pin uv tooling and health check deps * test: isolate bedrock image request formatting from suite state * test: cover sandbox executor requirements flow * ci: fix circleci no-op command steps * ci: fix circleci publish workflow parsing * fix: stabilize remaining uv migration CI checks * ci: increase matrix test timeout headroom * fix: restore published docker and license coverage * fix: restore proxy runtime build parity * fix: restore proxy extras parity and venv migrations * ci: persist uv path across circleci steps * fix: keep psycopg binary in default test env * docker: preserve prisma cache across stages * test: run local proxy checks through uv python * build: restore runtime deps moved into ci * build: refresh uv lock after upstream merge * fix: restore module import in test_check_migration after merge The conflict resolution imported only the function but the test body references check_migration as a module throughout. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: revert dependency promotions, remove nodejs-wheel-binaries, fix Docker layer caching - Move google-generativeai, Pillow, tenacity back to ci group (they are lazily imported and bloat the base SDK install needlessly) - Remove nodejs-wheel-binaries from extra_proxy and proxy-dev (redundant in Docker where system Node.js is already installed via apk) - Remove all nodejs-wheel node replacement and venv npm patching blocks from Dockerfiles since the wheel is no longer installed - Add --no-default-groups to CodSpeed benchmark workflow so the benchmark environment matches the old minimal pip install footprint - Apply standard uv two-phase Docker pattern: copy metadata first, install deps (cached layer), then copy source and install project - Replace CircleCI enterprise no-op with proper uv sync command Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: regenerate uv.lock after removing nodejs-wheel-binaries Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ci): use cache/restore instead of cache to prevent cache poisoning The old workflow used actions/cache/restore (read-only). The uv migration changed it to actions/cache (read-write), which zizmor flags as a cache poisoning risk. Restore the safer read-only variant. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ci): disable setup-uv built-in cache to silence cache-poisoning alert The setup-uv action enables caching by default, which zizmor flags as a cache poisoning risk. Disable it since we already use a read-only cache/restore step. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ci): disable setup-uv cache in publish workflow Silences zizmor cache-poisoning alert. Publishing workflow runs infrequently on protected branches so caching adds no real benefit. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(test): remove duplicate verbose_logger mock in test_check_migration The logger was patched twice — first via mocker.patch() then via mocker.patch.object(autospec=True). The second call fails because autospec cannot inspect an already-mocked attribute. Remove the redundant first patch. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ci): free disk space before Docker build in test-server-root-path The Dockerfile.non_root build ran out of disk on the CI runner. Remove Android SDK, .NET, Boost, and GHC toolchains (~12GB) to free space. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,31 +7,35 @@ import traceback
|
||||
|
||||
import pytest
|
||||
|
||||
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
|
||||
sys.path.insert(
|
||||
0, os.path.abspath("../..")
|
||||
) # Adds the parent directory to the system path
|
||||
|
||||
|
||||
def _run_uv(*args: str, **kwargs) -> subprocess.CompletedProcess:
|
||||
return subprocess.run(["uv", *args], check=True, cwd=PROJECT_ROOT, **kwargs)
|
||||
|
||||
|
||||
def test_using_litellm():
|
||||
try:
|
||||
import litellm
|
||||
|
||||
print("litellm imported successfully")
|
||||
except Exception as e:
|
||||
pytest.fail(
|
||||
f"Error occurred: {e}. Installing litellm on python3.8 failed please retry"
|
||||
)
|
||||
pytest.fail(f"Error occurred: {e}. Installing litellm failed please retry")
|
||||
|
||||
|
||||
def test_litellm_proxy_server():
|
||||
# Install the local litellm[proxy] package in development mode
|
||||
subprocess.run(["pip", "install", "-e", ".[proxy]"])
|
||||
# Sync the local litellm[proxy] dependencies into the project environment
|
||||
_run_uv("sync", "--frozen", "--extra", "proxy")
|
||||
|
||||
# Import the proxy_server module
|
||||
# Import through the uv-managed interpreter that uv sync populated.
|
||||
try:
|
||||
import litellm.proxy.proxy_server
|
||||
except ImportError:
|
||||
pytest.fail("Failed to import litellm.proxy_server")
|
||||
_run_uv("run", "--no-sync", "python", "-c", "import litellm.proxy.proxy_server")
|
||||
except subprocess.CalledProcessError:
|
||||
pytest.fail("Failed to import litellm.proxy.proxy_server")
|
||||
|
||||
# Assertion to satisfy the test, you can add other checks as needed
|
||||
assert True
|
||||
@@ -39,11 +43,12 @@ def test_litellm_proxy_server():
|
||||
|
||||
def test_package_dependencies():
|
||||
"""
|
||||
Test that all optional dependencies are correctly specified in extras.
|
||||
Test that all optional dependency entries are exposed via project optional-dependencies.
|
||||
"""
|
||||
try:
|
||||
import pathlib
|
||||
import litellm
|
||||
from packaging.requirements import Requirement
|
||||
|
||||
# Try to import tomllib (Python 3.11+) or tomli (older versions)
|
||||
try:
|
||||
@@ -62,28 +67,22 @@ def test_package_dependencies():
|
||||
with open(pyproject_path, "rb") as f:
|
||||
pyproject = tomli.load(f)
|
||||
|
||||
# Get all optional dependencies from poetry.dependencies
|
||||
poetry_deps = pyproject["tool"]["poetry"]["dependencies"]
|
||||
optional_deps = {
|
||||
name.lower()
|
||||
for name, value in poetry_deps.items()
|
||||
if isinstance(value, dict) and value.get("optional", False)
|
||||
}
|
||||
print(optional_deps)
|
||||
# Get all packages listed in extras
|
||||
extras = pyproject["tool"]["poetry"]["extras"]
|
||||
all_extra_deps = set()
|
||||
for extra_group in extras.values():
|
||||
all_extra_deps.update(dep.lower() for dep in extra_group)
|
||||
print(all_extra_deps)
|
||||
# Check that all optional dependencies are in some extras group
|
||||
missing_from_extras = optional_deps - all_extra_deps
|
||||
assert (
|
||||
not missing_from_extras
|
||||
), f"Optional dependencies missing from extras: {missing_from_extras}"
|
||||
optional_deps = pyproject["project"]["optional-dependencies"]
|
||||
assert optional_deps, "Expected project.optional-dependencies to be defined"
|
||||
|
||||
parsed_requirements = set()
|
||||
for extra_name, requirements in optional_deps.items():
|
||||
assert requirements, f"Optional dependency group '{extra_name}' is empty"
|
||||
for requirement in requirements:
|
||||
assert isinstance(
|
||||
requirement, str
|
||||
), f"Expected string requirement in extra '{extra_name}'"
|
||||
parsed = Requirement(requirement)
|
||||
parsed_requirements.add(parsed.name.lower())
|
||||
|
||||
print(parsed_requirements)
|
||||
print(
|
||||
f"All {len(optional_deps)} optional dependencies are correctly specified in extras"
|
||||
f"Validated {len(parsed_requirements)} optional dependencies across {len(optional_deps)} extras groups"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
@@ -102,24 +101,20 @@ import requests
|
||||
|
||||
|
||||
def test_litellm_proxy_server_config_no_general_settings():
|
||||
# Install the local litellm packages in development mode
|
||||
# Sync the local litellm packages into the project environment
|
||||
server_process = None
|
||||
try:
|
||||
subprocess.run(["pip", "install", "-e", ".[proxy]"])
|
||||
subprocess.run(["pip", "install", "-e", ".[extra_proxy]"])
|
||||
_run_uv("sync", "--frozen", "--group", "proxy-dev", "--extra", "proxy", "--extra", "extra_proxy")
|
||||
|
||||
# Ensure Prisma client is generated
|
||||
try:
|
||||
# Get the project root directory (where schema.prisma is located)
|
||||
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
print(f"Running prisma generate from: {project_root}")
|
||||
print(f"Running prisma generate from: {PROJECT_ROOT}")
|
||||
|
||||
result = subprocess.run(
|
||||
["prisma", "generate"],
|
||||
result = _run_uv(
|
||||
"run", "--no-sync", "prisma", "generate",
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
cwd=project_root
|
||||
check=True
|
||||
)
|
||||
print(f"Prisma generate stdout: {result.stdout}")
|
||||
except subprocess.CalledProcessError as e:
|
||||
@@ -129,13 +124,8 @@ def test_litellm_proxy_server_config_no_general_settings():
|
||||
filepath = os.path.dirname(os.path.abspath(__file__))
|
||||
config_fp = f"{filepath}/test_configs/test_config_no_auth.yaml"
|
||||
server_process = subprocess.Popen(
|
||||
[
|
||||
"python",
|
||||
"-m",
|
||||
"litellm.proxy.proxy_cli",
|
||||
"--config",
|
||||
config_fp,
|
||||
]
|
||||
["uv", "run", "--no-sync", "python", "-m", "litellm.proxy.proxy_cli", "--config", config_fp],
|
||||
cwd=PROJECT_ROOT,
|
||||
)
|
||||
|
||||
# Allow some time for the server to start (increased for CI environments)
|
||||
|
||||
Reference in New Issue
Block a user