diff --git a/pyproject.toml b/pyproject.toml index 02834f604d2..a9099f5baea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,6 +73,10 @@ proxy = [ "hiredis>=3.0.0,<4.0; python_version < '3.15'", "hiredis>=3.5.0,<4.0; python_version >= '3.15'", "apscheduler>=3.11.2,<4.0", + # zoneinfo fallback tz database. The OS db drops legacy IANA aliases (Asia/Calcutta on + # Ubuntu 24.04+ needs the separate tzdata-legacy package), so proxy startup without the + # wheel dies with ZoneInfoNotFoundError. Ref: BerriAI/litellm#49813. + "tzdata>=2024.1", "fastapi-sso>=0.19.0,<1.0", "PyJWT>=2.13.0,<3.0", "python-multipart>=0.0.27,<1.0", diff --git a/tests/unit/test_proxy_extra_tzdata.py b/tests/unit/test_proxy_extra_tzdata.py new file mode 100644 index 00000000000..ccdd99e1603 --- /dev/null +++ b/tests/unit/test_proxy_extra_tzdata.py @@ -0,0 +1,37 @@ +""" +Static checks that the `proxy` extra ships tzdata. + +Legacy IANA aliases such as `Asia/Calcutta` are absent from the OS zone database +on stock Ubuntu 24.04+ (they moved to the separate `tzdata-legacy` package). +zoneinfo then falls back to the `tzdata` wheel, and `pip install litellm[proxy]` +without it exits at proxy startup with +`ZoneInfoNotFoundError: 'tzlocal() does not support non-zoneinfo timezones like +Asia/Calcutta. Please use a timezone in the form of Continent/City'` +(BerriAI/litellm#49813). The Docker images install the OS `tzdata` apk package, +so only the pip surface regresses when this extra loses the wheel +""" + +import sys +from pathlib import Path +from typing import Final + +from packaging.requirements import Requirement + +if sys.version_info >= (3, 11): + import tomllib +else: + import tomli as tomllib + +PYPROJECT: Final = Path(__file__).resolve().parents[2] / "pyproject.toml" + + +def test_proxy_extra_requires_tzdata_on_all_platforms() -> None: + manifest: Final = tomllib.loads(PYPROJECT.read_text()) + proxy_specs: Final = tuple(Requirement(spec) for spec in manifest["project"]["optional-dependencies"]["proxy"]) + tzdata_specs: Final = tuple(spec for spec in proxy_specs if spec.name == "tzdata") + assert len(tzdata_specs) == 1, ( + "pip install litellm[proxy] must ship tzdata: proxy startup with a legacy IANA alias like " + f"TZ=Asia/Calcutta dies with ZoneInfoNotFoundError without it, got specs {proxy_specs}" + ) + marker: Final = tzdata_specs[0].marker + assert marker is None, f"tzdata is required on every platform the proxy starts on, found marker {marker}" diff --git a/uv.lock b/uv.lock index 8712b78bbd6..6a8d012fb5b 100644 --- a/uv.lock +++ b/uv.lock @@ -5125,6 +5125,7 @@ proxy = [ { name = "soundfile" }, { name = "starlette" }, { name = "tomlkit" }, + { name = "tzdata" }, { name = "uvicorn" }, { name = "uvloop", marker = "sys_platform != 'win32'" }, { name = "websockets" }, @@ -5408,6 +5409,7 @@ requires-dist = [ { name = "tomlkit", marker = "extra == 'cli'", specifier = ">=0.13.3,<1.0" }, { name = "tomlkit", marker = "extra == 'proxy'", specifier = ">=0.13.3,<1.0" }, { name = "typing-extensions", specifier = ">=4.13.0,<5.0" }, + { name = "tzdata", marker = "extra == 'proxy'", specifier = ">=2024.1" }, { name = "uvicorn", marker = "extra == 'proxy'", specifier = ">=0.33.0,<1.0" }, { name = "uvloop", marker = "sys_platform != 'win32' and extra == 'proxy'", specifier = ">=0.22.1,<1.0" }, { name = "websockets", marker = "extra == 'proxy'", specifier = ">=15.0.1,<16.0" },