feat(sdk): build core from an independent packaging manifest (#44340)

* feat(sdk): build core from an independent packaging manifest

* test(sdk): compare rebuilt core payload without generated SBOM identity

* fix(sdk): retain native build configuration in the core sdist

* test(sdk): collect coverage from the core build entry point

* fix(ci): isolate core packaging coverage configuration

* fix(test): identify installed core metadata on Python 3.10

* fix(packaging): preserve Git ignore rules in core staging

* fix(packaging): support source-only core builds

* fix(packaging): reject overlapping SDK distributions

* fix(packaging): align core requirements with current main

---------

Co-authored-by: Joshua Valluru <326636767+joshua-berri@users.noreply.github.com>
This commit is contained in:
joshua-berri 2026-10-08 18:40:14 -07:00 • committed by GitHub
parent 70fd2c19cf
commit f48d837cd2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 558 additions and 7 deletions

View file

@ -13,6 +13,9 @@ on:
- "rust-toolchain.toml"
- "litellm-rust/**"
- "litellm/__init__.py"
- "litellm/_version.py"
- "litellm/_lazy_imports_registry.py"
- "scripts/build_core_distribution.py"
- "litellm/proxy/proxy_server.py"
- "litellm/**/*mcp*"
- "litellm/**/*mcp*/**"
@ -115,3 +118,59 @@ jobs:
fi
(cd "$RUNNER_TEMP" && "$GITHUB_WORKSPACE/.venv-lowest-$extra/bin/python" "${checker[@]}")
done
core-distribution:
permissions:
contents: read
id-token: write
runs-on: ubuntu-latest
timeout-minutes: 30
env:
UV_PYTHON: ${{ matrix.python-version }}
LITELLM_LOCAL_MODEL_COST_MAP: "True"
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
with:
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: ${{ matrix.python-version }}
- uses: ./.github/actions/setup-uv-with-retries
with:
version: "0.10.9"
- uses: ./.github/actions/cache-cargo-build
- name: Build the core wheel and source distribution
run: >-
uv run --no-project --with coverage==7.14.0 python -m coverage run --rcfile=/dev/null
--branch --include="*/scripts/build_core_distribution.py"
scripts/build_core_distribution.py --out-dir dist/core
- name: Verify metadata, resources, and independent source rebuild
env:
CORE_DISTRIBUTION_DIR: ${{ github.workspace }}/dist/core
run: >-
uv run --no-project --with pytest==9.0.3 --with pytest-cov==5.0.0 --with coverage==7.14.0 --with 'tomli==2.4.1; python_version < "3.11"'
python -m pytest tests/base_sdk_tests/test_core_distribution.py -v
--cov-config=/dev/null --cov=scripts.build_core_distribution --cov-branch --cov-append --cov-report=
- name: Verify isolated core installations
run: |
wheel=$(realpath dist/core/litellm_core-*.whl)
for resolution in highest lowest-direct; do
uv venv --python ${{ matrix.python-version }} ".venv-core-$resolution"
uv pip install --python ".venv-core-$resolution" --resolution "$resolution" "$wheel"
uv pip check --python ".venv-core-$resolution"
(cd "$RUNNER_TEMP" && "$GITHUB_WORKSPACE/.venv-core-$resolution/bin/python" -I "$GITHUB_WORKSPACE/tests/base_sdk_tests/check_base_sdk_install.py")
done
uv pip install --python .venv-core-highest coverage==7.14.0
.venv-core-highest/bin/python -I -m coverage run --rcfile=/dev/null --append --branch --include="*/tests/base_sdk_tests/check_base_sdk_install.py" tests/base_sdk_tests/check_base_sdk_install.py
uv run --no-project --with coverage==7.14.0 python -m coverage xml --rcfile=/dev/null -o coverage-core.xml
- name: Upload core packaging coverage
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
with:
use_oidc: true
files: coverage-core.xml
flags: core-packaging
fail_ci_if_error: false

View file

@ -90,6 +90,24 @@ Managing LLM calls across providers gets complicated fast — different SDKs, au
uv add litellm
```
An independent `litellm-core` distribution provides the Python SDK with the same
`import litellm` API and runtime dependencies. It has no optional extras, CLI entry
points, or bundled dashboard. Install one SDK distribution per environment because
`litellm` and `litellm-core` own overlapping Python files. Use `litellm` for the
proxy, CLI, and optional extras
Build and install core from this checkout while its release integration is pending:
```shell
python scripts/build_core_distribution.py --out-dir dist/core
python -m pip install dist/core/litellm_core-*.whl
```
The builder requires Git, `uv` and the Rust build toolchain. It reads the release version
from the root `pyproject.toml`, leaves source files unchanged, and produces a wheel
and a self-contained sdist in `dist/core`. Run installation in a fresh environment
without `litellm`
```python
from litellm import completion
import os

View file

@ -1421,7 +1421,6 @@ from .exceptions import (
ModelNotMappedError as ModelNotMappedError,
)
from .budget_manager import BudgetManager
from .proxy.proxy_cli import run_server
from .router import Router
from .assistants.main import *
from .batches.main import *

View file

@ -22,6 +22,7 @@ LITELLM_LOGGING_NAMES: Final = (
# Utils names that support lazy loading via _lazy_import_utils
UTILS_NAMES: Final = (
"run_server",
"exception_type",
"get_optional_params",
"get_response_string",
@ -459,6 +460,7 @@ UTILS_MODULE_NAMES: Final = (
# Import maps for registry pattern - reduces repetition
_UTILS_IMPORT_MAP: Final = {
"run_server": ("litellm.proxy.proxy_cli", "run_server"),
"exception_type": (".utils", "exception_type"),
"get_optional_params": (".utils", "get_optional_params"),
"get_response_string": (".utils", "get_response_string"),

View file

@ -1,6 +1,22 @@
from typing import Final
import importlib_metadata
try:
version = importlib_metadata.version("litellm")
except Exception:
version = "unknown"
def _installed_version(distribution: str) -> str | None:
try:
return importlib_metadata.version(distribution)
except Exception:
return None
_legacy_version: Final = _installed_version("litellm")
_core_version: Final = _installed_version("litellm-core")
if _legacy_version is not None and _core_version is not None:
raise RuntimeError(
"litellm and litellm-core are both installed and share the litellm namespace. "
"Install them in separate environments."
)
version = _legacy_version or _core_version or "unknown"

View file

@ -0,0 +1,77 @@
[project]
name = "litellm-core"
version = "1.105.0"
description = "Library to easily interface with LLM API providers"
readme = "README.md"
requires-python = ">=3.10, <3.15"
license = "MIT"
license-files = ["LICENSE"]
authors = [
{ name = "BerriAI" },
]
dependencies = [
"fastuuid>=0.14.0,<1.0",
"filelock>=3.16.1,<4.0",
"httpx[http2]>=0.28.0,<1.0",
"openai>=2.20.0,<3.0.0",
"python-dotenv>=1.0.0,<2.0",
"pyyaml>=6.0.3,<7.0",
"packaging>=24.0",
"importlib-metadata>=8.0.0,<9.0",
"tiktoken>=0.8.0,<1.0; python_version < '3.14'",
"tiktoken>=0.12.0,<1.0; python_version >= '3.14'",
"tokenizers>=0.21.0,<1.0",
"huggingface-hub>=0.34.0,<2.0",
"click>=8.0.0,<9.0",
"jinja2>=3.1.6,<4.0",
"aiohttp>=3.14.2,<4.0",
"async-timeout>=4.0.3,<6.0; python_version < '3.11'",
"pydantic>=2.11.0,<3.0.0; python_version < '3.14'",
"pydantic>=2.12.0,<3.0.0; python_version >= '3.14'",
"pydantic-settings>=2.14.1,<3.0",
"jsonschema>=4.0.0,<5.0",
"boto3>=1.43.1,<2.0",
"typing-extensions>=4.13.0,<5.0",
]
[project.urls]
Homepage = "https://litellm.ai"
Repository = "https://github.com/BerriAI/litellm"
Documentation = "https://docs.litellm.ai"
[build-system]
requires = ["maturin==1.15.0"]
build-backend = "maturin"
[tool.maturin]
manifest-path = "litellm-rust/crates/python-bridge/Cargo.toml"
module-name = "litellm.rust_bridge._native"
python-source = "."
bindings = "pyo3"
features = ["extension-module"]
profile = "release"
editable-profile = "dev"
include = [
{ path = "rust-toolchain.toml", format = "sdist" },
{ path = ".cargo/config.toml", format = "sdist" },
"litellm/router_strategy/complexity_router/artifacts/*.json",
"litellm/router_strategy/complexity_router/fuse_presets.json",
"litellm/proxy/model_insights_tasks.json",
"litellm/proxy/common_utils/codex_base_instructions.md",
"litellm/proxy/common_utils/codex_bundled_models_0.159.3.json",
"litellm/proxy/lens/prompts/*.md",
]
exclude = [
"litellm/proxy/_experimental/out",
"litellm/proxy/_experimental/out/**",
"litellm/proxy/enterprise",
"litellm/proxy/enterprise/**",
"litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks",
"litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/**",
"**/__pycache__",
"**/__pycache__/**",
"**/.pytest_cache",
"**/.pytest_cache/**",
"**/.ruff_cache",
"**/.ruff_cache/**",
]

View file

@ -0,0 +1,86 @@
"""Build the independent core distribution from shared repository sources."""
import argparse
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
from typing import Final
ROOT: Final = Path(__file__).resolve().parents[1]
SOURCES: Final = ("litellm", "litellm-rust", ".cargo", "rust-toolchain.toml", "README.md", "LICENSE")
def stage_core_distribution(source: Path, destination: Path) -> None:
version: Final = subprocess.run(
["uv", "version", "--short"], cwd=source, check=True, capture_output=True, text=True
).stdout.strip()
ignored: Final = frozenset(
source / name
for name in subprocess.run(
[
"git",
"ls-files",
"--ignored",
"--cached",
"--others",
"--exclude-standard",
"--directory",
"-z",
"--",
*SOURCES,
],
cwd=source,
check=True,
capture_output=True,
text=True,
).stdout.split("\0")
if name
)
def ignored_sources(directory: str, names: list[str]) -> set[str]:
return {name for name in names if Path(directory) / name in ignored} | shutil.ignore_patterns(
"__pycache__", ".pytest_cache", ".ruff_cache", "target", ".git", "*.so", "*.pyd"
)(directory, names)
destination.mkdir(parents=True, exist_ok=True)
for name in SOURCES:
path: Final = source / name
if path.is_dir():
shutil.copytree(
path,
destination / name,
ignore=ignored_sources,
)
else:
shutil.copy2(path, destination / name)
shutil.copy2(source / "packaging/litellm-core/pyproject.toml", destination / "pyproject.toml")
subprocess.run(["uv", "version", version, "--frozen"], cwd=destination, check=True)
def build_core_distribution(output: Path, *, sdist_only: bool = False) -> None:
output_path: Final = output.resolve()
with tempfile.TemporaryDirectory(prefix="litellm-core-") as temporary:
stage: Final = Path(temporary)
stage_core_distribution(ROOT, stage)
subprocess.run(
["uv", "build", "--python", sys.executable, "--out-dir", str(output_path)]
+ (["--sdist"] if sdist_only else []),
cwd=stage,
check=True,
)
def main() -> None:
parser: Final = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--out-dir", type=Path, default=ROOT / "dist/core")
parser.add_argument(
"--sdist-only", action="store_true", help="Build only the source archive, without compiling a wheel"
)
args: Final = parser.parse_args()
build_core_distribution(Path(args.out_dir), sdist_only=args.sdist_only)
if __name__ == "__main__":
main()

View file

@ -29,12 +29,24 @@ def check_environment_is_base_only() -> str:
def check_import() -> str:
from importlib.metadata import distributions as installed_distributions
from importlib.metadata import version
import litellm
_require(bool(litellm.__file__), "litellm has no __file__")
return f"imported litellm {version('litellm')}"
from litellm._version import version as sdk_version
distributions = tuple(
distribution.metadata["Name"]
for distribution in installed_distributions()
if distribution.metadata["Name"] in ("litellm", "litellm-core")
)
_require(len(distributions) == 1, f"expected one SDK distribution, found {distributions}")
distribution = distributions[0]
_require(sdk_version == version(distribution), "SDK version does not match installed metadata")
_require("litellm.proxy.proxy_cli" not in sys.modules, "SDK import loaded the proxy CLI")
return f"imported {distribution} {sdk_version}"
def check_completion() -> str:

View file

@ -0,0 +1,242 @@
import email
import os
import subprocess
import sys
import tarfile
import zipfile
from pathlib import Path
from typing import Final
from unittest.mock import patch
import pytest
from packaging.requirements import Requirement
if sys.version_info >= (3, 11):
import tomllib
else:
import tomli as tomllib
ROOT: Final = Path(__file__).resolve().parents[2]
@pytest.mark.parametrize("sdist_only", [False, True])
def test_build_command_selects_requested_distributions(tmp_path: Path, sdist_only: bool) -> None:
from scripts.build_core_distribution import main
output: Final = tmp_path / "dist"
arguments: Final = ["build_core_distribution.py", "--out-dir", str(output)] + (
["--sdist-only"] if sdist_only else []
)
with (
patch.object(sys, "argv", arguments),
patch("scripts.build_core_distribution.stage_core_distribution") as stage,
patch("scripts.build_core_distribution.subprocess.run") as run,
):
main()
run.assert_called_once()
command: Final = run.call_args.args[0]
assert command[:2] == ["uv", "build"]
assert ("--sdist" in command) is sdist_only
assert "--wheel" not in command
assert command[command.index("--out-dir") + 1] == str(output)
assert run.call_args.kwargs["check"] is True
assert run.call_args.kwargs["cwd"] == stage.call_args.args[1]
assert not stage.call_args.args[1].exists()
@pytest.fixture
def source_repository(tmp_path: Path) -> Path:
source: Final = tmp_path / "source"
source.mkdir()
subprocess.run(["git", "init", "--quiet", str(source)], check=True)
return source
def test_core_manifest_preserves_runtime_dependencies_without_extras() -> None:
manifest: Final = ROOT / "packaging/litellm-core/pyproject.toml"
assert manifest.is_file(), "The core distribution needs its own build manifest"
core: Final = tomllib.loads(manifest.read_text())
legacy: Final = tomllib.loads((ROOT / "pyproject.toml").read_text())
assert core["project"]["name"] == "litellm-core"
assert core["project"]["dependencies"] == legacy["project"]["dependencies"]
assert not core["project"].get("optional-dependencies")
assert not core["project"].get("scripts")
def test_staging_stamps_release_version_without_modifying_sources(tmp_path: Path, source_repository: Path) -> None:
from scripts.build_core_distribution import SOURCES, stage_core_distribution
source: Final = source_repository
for name in SOURCES:
path: Final = source / name
path.write_text(f"shared {name}")
(source / "pyproject.toml").write_text('[project]\nname = "litellm"\nversion = "9.8.7rc1"\n')
manifest: Final = source / "packaging/litellm-core/pyproject.toml"
manifest.parent.mkdir(parents=True)
manifest.write_bytes((ROOT / "packaging/litellm-core/pyproject.toml").read_bytes())
original: Final = manifest.read_bytes()
stage: Final = tmp_path / "stage"
stage_core_distribution(source, stage)
assert tomllib.loads((stage / "pyproject.toml").read_text())["project"]["version"] == "9.8.7rc1"
assert manifest.read_bytes() == original
assert (source / "pyproject.toml").read_text() == '[project]\nname = "litellm"\nversion = "9.8.7rc1"\n'
assert all((stage / name).read_bytes() == (source / name).read_bytes() for name in SOURCES)
@pytest.fixture(scope="module")
def distribution_directory() -> Path:
directory: Final = os.environ.get("CORE_DISTRIBUTION_DIR")
if directory is None:
pytest.skip("CORE_DISTRIBUTION_DIR is supplied by the installed-distribution CI job")
return Path(directory)
@pytest.fixture(scope="module")
def distributions(distribution_directory: Path) -> tuple[Path, Path]:
wheels: Final = tuple(distribution_directory.glob("litellm_core-*.whl"))
sdists: Final = tuple(distribution_directory.glob("litellm_core-*.tar.gz"))
assert len(wheels) == len(sdists) == 1
return wheels[0], sdists[0]
def test_core_wheel_metadata_and_resources(distributions: tuple[Path, Path]) -> None:
with zipfile.ZipFile(distributions[0]) as wheel:
names: Final = wheel.namelist()
metadata: Final = email.message_from_bytes(
wheel.read(next(n for n in names if n.endswith(".dist-info/METADATA")))
)
assert metadata["Name"] == "litellm-core"
assert metadata["Version"] == tomllib.loads((ROOT / "pyproject.toml").read_text())["project"]["version"]
assert not metadata.get_all("Provides-Extra")
assert not any(n.endswith(".dist-info/entry_points.txt") for n in names)
requirements: Final = tomllib.loads((ROOT / "pyproject.toml").read_text())["project"]["dependencies"]
for python_version in ("3.10", "3.11", "3.12", "3.13", "3.14"):
environment: Final = {"python_version": python_version, "python_full_version": python_version + ".0"}
assert {
(requirement.name, requirement.specifier, frozenset(requirement.extras))
for item in metadata.get_all("Requires-Dist", ())
for requirement in (Requirement(item),)
if requirement.marker is None or requirement.marker.evaluate(environment)
} == {
(requirement.name, requirement.specifier, frozenset(requirement.extras))
for item in requirements
for requirement in (Requirement(item),)
if requirement.marker is None or requirement.marker.evaluate(environment)
}
assert "litellm/model_prices_and_context_window_backup.json" in names
assert "litellm/router_strategy/complexity_router/fuse_presets.json" in names
assert "litellm/proxy/proxy_cli.py" in names
assert any(n.startswith("litellm/rust_bridge/_native.") and n.endswith((".so", ".pyd")) for n in names)
assert not any(n.startswith("litellm/proxy/_experimental/out/") for n in names)
def test_core_artifacts_exclude_local_configuration(distributions: tuple[Path, Path]) -> None:
configurations: Final = (
"litellm/proxy/_new_secret_config.yaml",
"litellm/proxy/_new_new_secret_config.yaml",
"litellm/proxy/_super_secret_config.yaml",
)
with zipfile.ZipFile(distributions[0]) as wheel, tarfile.open(distributions[1]) as archive:
for name in configurations:
assert name not in wheel.namelist(), f"Core wheel contains ignored configuration {name}"
assert not any(member.name.endswith(f"/{name}") for member in archive.getmembers())
@pytest.mark.parametrize("relative_path", ["rust-toolchain.toml", ".cargo/config.toml"])
def test_core_sdist_preserves_native_build_configuration(distribution_directory: Path, relative_path: str) -> None:
sdists: Final = tuple(distribution_directory.glob("litellm_core-*.tar.gz"))
assert len(sdists) == 1
with tarfile.open(sdists[0]) as archive:
name: Final = f"{sdists[0].name.removesuffix('.tar.gz')}/{relative_path}"
assert name in archive.getnames(), f"Core source distribution is missing {relative_path}"
content: Final = archive.extractfile(name)
assert content is not None
assert content.read() == (ROOT / relative_path).read_bytes()
def test_core_sdist_rebuilds_without_repository(distributions: tuple[Path, Path], tmp_path: Path) -> None:
with tarfile.open(distributions[1]) as archive:
archive.extractall(tmp_path, filter="data")
source: Final = next(tmp_path.glob("litellm_core-*"))
assert not (source / "scripts/build_core_distribution.py").exists()
assert tomllib.loads((source / "pyproject.toml").read_text())["project"]["name"] == "litellm-core"
result: Final = subprocess.run(
["uv", "build", "--python", sys.executable, "--wheel", "--out-dir", str(tmp_path / "rebuilt")],
cwd=source,
capture_output=True,
text=True,
)
assert result.returncode == 0, result.stderr
rebuilt: Final = next((tmp_path / "rebuilt").glob("*.whl"))
with zipfile.ZipFile(distributions[0]) as original, zipfile.ZipFile(rebuilt) as wheel:
assert set(original.namelist()) == set(wheel.namelist())
for name in original.namelist():
if (name.startswith("litellm/") or name.endswith("/METADATA")) and not name.endswith((".so", ".pyd")):
assert original.read(name) == wheel.read(name), name
def test_staging_copies_sources_without_build_artifacts(tmp_path: Path, source_repository: Path) -> None:
from scripts.build_core_distribution import SOURCES, stage_core_distribution
source: Final = source_repository
for name in SOURCES:
(source / name).mkdir()
(source / name / "shared.txt").write_text("source payload")
(source / name / "stale.so").write_text("old native extension")
(source / name / "__pycache__").mkdir()
(source / name / "__pycache__/old.pyc").write_bytes(b"stale bytecode")
(source / "pyproject.toml").write_text('[project]\nname = "litellm"\nversion = "1.2.3"\n')
manifest: Final = source / "packaging/litellm-core/pyproject.toml"
manifest.parent.mkdir(parents=True)
manifest.write_bytes((ROOT / "packaging/litellm-core/pyproject.toml").read_bytes())
stage: Final = tmp_path / "stage"
stage_core_distribution(source, stage)
assert all((stage / name / "shared.txt").read_text() == "source payload" for name in SOURCES)
assert not tuple(stage.rglob("*.so"))
assert not tuple(stage.rglob("*.pyc"))
assert all((source / name / "stale.so").is_file() for name in SOURCES)
def test_staging_preserves_gitignore_rules(tmp_path: Path, source_repository: Path) -> None:
from scripts.build_core_distribution import SOURCES, stage_core_distribution
source: Final = source_repository
for name in SOURCES:
(source / name).mkdir()
(source / name / "shared.txt").write_text("shared payload")
(source / "pyproject.toml").write_text('[project]\nname = "litellm"\nversion = "1.2.3"\n')
manifest: Final = source / "packaging/litellm-core/pyproject.toml"
manifest.parent.mkdir(parents=True)
manifest.write_bytes((ROOT / "packaging/litellm-core/pyproject.toml").read_bytes())
(source / ".gitignore").write_text("*.cfg\n!keep.cfg\nbuild-artifacts/\n")
(source / "litellm/.gitignore").write_text(".env\n")
(source / ".git/info/exclude").write_text("litellm/private-local.json\n")
(source / "litellm/private-local.json").write_text("synthetic packaging canary")
for name in ("tracked.cfg", "local secret.cfg", "keep.cfg", ".env"):
(source / "litellm" / name).write_text("synthetic packaging canary")
(source / "litellm/build-artifacts").mkdir()
(source / "litellm/build-artifacts/local.txt").write_text("generated artifact")
subprocess.run(["git", "add", "--force", "litellm/tracked.cfg"], cwd=source, check=True)
stage: Final = tmp_path / "stage"
stage_core_distribution(source, stage)
assert (stage / "litellm/keep.cfg").read_text() == "synthetic packaging canary"
assert (stage / "litellm/shared.txt").read_text() == "shared payload"
assert not (stage / "litellm/tracked.cfg").exists()
assert not (stage / "litellm/local secret.cfg").exists()
assert not (stage / "litellm/.env").exists()
assert not (stage / "litellm/private-local.json").exists()
assert not (stage / "litellm/build-artifacts").exists()
assert (source / "litellm/tracked.cfg").is_file()
def test_staging_rejects_missing_release_version(tmp_path: Path) -> None:
from scripts.build_core_distribution import stage_core_distribution
manifest: Final = tmp_path / "packaging/litellm-core/pyproject.toml"
manifest.parent.mkdir(parents=True)
manifest.write_bytes((ROOT / "packaging/litellm-core/pyproject.toml").read_bytes())
(tmp_path / "pyproject.toml").write_text('[project]\nname = "litellm"\n')
with pytest.raises(subprocess.CalledProcessError):
stage_core_distribution(tmp_path, tmp_path / "stage")
assert not (tmp_path / "stage").exists()

View file

@ -47,7 +47,7 @@ def test_import_litellm_does_not_load_fastapi_or_bpe_table():
[
sys.executable,
"-c",
"import sys, litellm; print(','.join(m for m in ('fastapi','starlette','litellm.litellm_core_utils.default_encoding') if m in sys.modules))",
"import sys, litellm; print(','.join(m for m in ('fastapi','starlette','litellm.proxy.proxy_cli','litellm.litellm_core_utils.default_encoding') if m in sys.modules))",
],
check=True,
capture_output=True,

View file

@ -0,0 +1,40 @@
import runpy
from pathlib import Path
from typing import Final
from unittest.mock import patch
import importlib_metadata
import pytest
@pytest.mark.parametrize(
("installed", "expected"),
[
({"litellm": "1.2.3"}, "1.2.3"),
({"litellm-core": "2.3.4"}, "2.3.4"),
({}, "unknown"),
],
)
def test_version_uses_installed_distribution(installed: dict[str, str], expected: str) -> None:
def lookup(name: str) -> str:
if name not in installed:
raise importlib_metadata.PackageNotFoundError(name)
return installed[name]
with patch("importlib_metadata.version", side_effect=lookup):
result: Final = runpy.run_path(str(Path(__file__).resolve().parents[2] / "litellm/_version.py"))
assert result["version"] == expected
@pytest.mark.parametrize("core_version", ["1.2.3", "2.3.4"])
def test_version_rejects_overlapping_distributions(core_version: str) -> None:
installed: Final = {"litellm": "1.2.3", "litellm-core": core_version}
with patch("importlib_metadata.version", side_effect=installed.__getitem__):
with pytest.raises(RuntimeError, match=r"litellm and litellm-core.*separate environments"):
runpy.run_path(str(Path(__file__).resolve().parents[2] / "litellm/_version.py"))
def test_version_handles_unreadable_metadata() -> None:
with patch("importlib_metadata.version", side_effect=ValueError("Invalid metadata")):
result: Final = runpy.run_path(str(Path(__file__).resolve().parents[2] / "litellm/_version.py"))
assert result["version"] == "unknown"