mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-11 08:38:24 +00:00
fix(proxy): add tzdata to the proxy extra so a legacy TZ doesn't stop startup (#49914)
Some checks are pending
CodSpeed Benchmarks / benchmarks (push) Waiting to run
Cost map pins recheck / rerun-cost-map-guard (push) Waiting to run
Publish lint base counts / publish (basedpyright, scripts/type_check_gate.py) (push) Waiting to run
Publish lint base counts / publish (openapi-docs, scripts/openapi_docs_gate.py) (push) Waiting to run
Publish lint base counts / publish (ruff-strict, scripts/ruff_strict_gate.py) (push) Waiting to run
Publish lint base counts / publish (test-quality, scripts/test_quality_gate.py) (push) Waiting to run
Publish lint base counts / publish (type-discipline, scripts/type_discipline_gate.py) (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
LiteLLM Rust DB / db-queries (push) Waiting to run
LiteLLM Rust / rust-lint (push) Waiting to run
LiteLLM Rust / rust-test (push) Waiting to run
LiteLLM Rust / rust-wheel (push) Waiting to run
Terraform Modules / fmt, validate, test (aws) (push) Waiting to run
Terraform Modules / fmt, validate, test (gcp) (push) Waiting to run
unit / enterprise-package (push) Blocked by required conditions
unit / integrations (push) Blocked by required conditions
unit / llms-anthropic (push) Blocked by required conditions
unit / llms-bedrock (push) Blocked by required conditions
unit / llms-openai-meta (push) Blocked by required conditions
unit / llms-providers (push) Blocked by required conditions
unit / llms-vertex-ai (push) Blocked by required conditions
unit / mcp-elicitation (push) Blocked by required conditions
unit / mcp-server (push) Blocked by required conditions
unit / proxy-auth (push) Blocked by required conditions
unit / proxy-endpoints (push) Blocked by required conditions
unit / proxy-extras (push) Blocked by required conditions
unit / proxy-feature-endpoints (push) Blocked by required conditions
unit / proxy-hooks-client (push) Blocked by required conditions
unit / proxy-infra-db-spend (push) Blocked by required conditions
unit / proxy-infra-root (push) Blocked by required conditions
unit / proxy-infra-routing (push) Blocked by required conditions
unit / router (push) Blocked by required conditions
unit / rust-bridge-harness (push) Blocked by required conditions
unit / auth-checks (push) Blocked by required conditions
unit / budgets (push) Blocked by required conditions
unit / custom-logging (push) Blocked by required conditions
unit / key-generation (push) Blocked by required conditions
unit / mcp-oauth (push) Blocked by required conditions
unit / proxy-runtime (push) Blocked by required conditions
unit / proxy-server-core (push) Blocked by required conditions
unit / root (push) Blocked by required conditions
unit / enterprise-repositories-secrets (push) Blocked by required conditions
unit / proxy-server (push) Blocked by required conditions
unit / lens-python-310 (push) Blocked by required conditions
unit / caching-local (push) Blocked by required conditions
unit / core-utils (push) Blocked by required conditions
unit / endpoints (push) Blocked by required conditions
unit / ui-unit (push) Waiting to run
unit / responses-caching-types (push) Blocked by required conditions
unit / db-and-spend (push) Blocked by required conditions
unit / endpoints-and-responses (push) Blocked by required conditions
unit / enterprise-routing (push) Blocked by required conditions
unit / guardrails-hooks (push) Blocked by required conditions
unit / jwt-and-keys (push) Blocked by required conditions
unit / logging-misc (push) Blocked by required conditions
unit / proxy-utils (push) Blocked by required conditions
unit / rust-bridge (push) Waiting to run
unit / assert-shard-coverage (push) Waiting to run
unit / enterprise-managed-files (push) Blocked by required conditions
unit / docs (push) Waiting to run
unit / helm (push) Waiting to run
unit / OpenAI 2.20.0 compatibility (push) Blocked by required conditions
unit / OpenAI 3.0.0 compatibility (push) Blocked by required conditions
unit / OpenAI 3.25.0 compatibility (push) Blocked by required conditions
unit / redis-py 5.3.1 compatibility (push) Blocked by required conditions
unit / redis-py 6.4.0 compatibility (push) Blocked by required conditions
unit / redis-py 7.4.1 compatibility (push) Blocked by required conditions
unit / redis-py 8.0.1 compatibility (push) Blocked by required conditions
unit / coverage (push) Blocked by required conditions
unit / unit passed (push) Blocked by required conditions
GitHub Actions Security Analysis / zizmor (push) Waiting to run
Some checks are pending
CodSpeed Benchmarks / benchmarks (push) Waiting to run
Cost map pins recheck / rerun-cost-map-guard (push) Waiting to run
Publish lint base counts / publish (basedpyright, scripts/type_check_gate.py) (push) Waiting to run
Publish lint base counts / publish (openapi-docs, scripts/openapi_docs_gate.py) (push) Waiting to run
Publish lint base counts / publish (ruff-strict, scripts/ruff_strict_gate.py) (push) Waiting to run
Publish lint base counts / publish (test-quality, scripts/test_quality_gate.py) (push) Waiting to run
Publish lint base counts / publish (type-discipline, scripts/type_discipline_gate.py) (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
LiteLLM Rust DB / db-queries (push) Waiting to run
LiteLLM Rust / rust-lint (push) Waiting to run
LiteLLM Rust / rust-test (push) Waiting to run
LiteLLM Rust / rust-wheel (push) Waiting to run
Terraform Modules / fmt, validate, test (aws) (push) Waiting to run
Terraform Modules / fmt, validate, test (gcp) (push) Waiting to run
unit / enterprise-package (push) Blocked by required conditions
unit / integrations (push) Blocked by required conditions
unit / llms-anthropic (push) Blocked by required conditions
unit / llms-bedrock (push) Blocked by required conditions
unit / llms-openai-meta (push) Blocked by required conditions
unit / llms-providers (push) Blocked by required conditions
unit / llms-vertex-ai (push) Blocked by required conditions
unit / mcp-elicitation (push) Blocked by required conditions
unit / mcp-server (push) Blocked by required conditions
unit / proxy-auth (push) Blocked by required conditions
unit / proxy-endpoints (push) Blocked by required conditions
unit / proxy-extras (push) Blocked by required conditions
unit / proxy-feature-endpoints (push) Blocked by required conditions
unit / proxy-hooks-client (push) Blocked by required conditions
unit / proxy-infra-db-spend (push) Blocked by required conditions
unit / proxy-infra-root (push) Blocked by required conditions
unit / proxy-infra-routing (push) Blocked by required conditions
unit / router (push) Blocked by required conditions
unit / rust-bridge-harness (push) Blocked by required conditions
unit / auth-checks (push) Blocked by required conditions
unit / budgets (push) Blocked by required conditions
unit / custom-logging (push) Blocked by required conditions
unit / key-generation (push) Blocked by required conditions
unit / mcp-oauth (push) Blocked by required conditions
unit / proxy-runtime (push) Blocked by required conditions
unit / proxy-server-core (push) Blocked by required conditions
unit / root (push) Blocked by required conditions
unit / enterprise-repositories-secrets (push) Blocked by required conditions
unit / proxy-server (push) Blocked by required conditions
unit / lens-python-310 (push) Blocked by required conditions
unit / caching-local (push) Blocked by required conditions
unit / core-utils (push) Blocked by required conditions
unit / endpoints (push) Blocked by required conditions
unit / ui-unit (push) Waiting to run
unit / responses-caching-types (push) Blocked by required conditions
unit / db-and-spend (push) Blocked by required conditions
unit / endpoints-and-responses (push) Blocked by required conditions
unit / enterprise-routing (push) Blocked by required conditions
unit / guardrails-hooks (push) Blocked by required conditions
unit / jwt-and-keys (push) Blocked by required conditions
unit / logging-misc (push) Blocked by required conditions
unit / proxy-utils (push) Blocked by required conditions
unit / rust-bridge (push) Waiting to run
unit / assert-shard-coverage (push) Waiting to run
unit / enterprise-managed-files (push) Blocked by required conditions
unit / docs (push) Waiting to run
unit / helm (push) Waiting to run
unit / OpenAI 2.20.0 compatibility (push) Blocked by required conditions
unit / OpenAI 3.0.0 compatibility (push) Blocked by required conditions
unit / OpenAI 3.25.0 compatibility (push) Blocked by required conditions
unit / redis-py 5.3.1 compatibility (push) Blocked by required conditions
unit / redis-py 6.4.0 compatibility (push) Blocked by required conditions
unit / redis-py 7.4.1 compatibility (push) Blocked by required conditions
unit / redis-py 8.0.1 compatibility (push) Blocked by required conditions
unit / coverage (push) Blocked by required conditions
unit / unit passed (push) Blocked by required conditions
GitHub Actions Security Analysis / zizmor (push) Waiting to run
* fix(proxy): add tzdata to the proxy extra so a legacy TZ doesn't stop startup * style(proxy): drop the inline comment on the tzdata requirement * test(proxy): check tzdata is installed with the proxy extra on every platform * fix(proxy): set the first spend report run in UTC so a scheduler zone that differs from the system clock can't skip it --------- Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
This commit is contained in:
parent
c2afb07f93
commit
3616014548
5 changed files with 53 additions and 1 deletions
|
|
@ -12453,7 +12453,7 @@ class ProxyStartupEvent:
|
|||
_scheduled_weekly_spend_report,
|
||||
"interval",
|
||||
days=days,
|
||||
next_run_time=datetime.now() + timedelta(seconds=10 + random.randint(0, 300)),
|
||||
next_run_time=datetime.now(timezone.utc) + timedelta(seconds=10 + random.randint(0, 300)),
|
||||
id=WEEKLY_SPEND_REPORT_JOB_ID,
|
||||
replace_existing=True,
|
||||
misfire_grace_time=APSCHEDULER_MISFIRE_GRACE_TIME,
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ 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",
|
||||
"tzdata>=2024.1",
|
||||
"fastapi-sso>=0.19.0,<1.0",
|
||||
"PyJWT>=2.13.0,<3.0",
|
||||
"python-multipart>=0.0.27,<1.0",
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import os
|
|||
import subprocess
|
||||
from collections.abc import AsyncIterator, Awaitable, Callable
|
||||
from contextlib import asynccontextmanager
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Final
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
|
|
@ -1393,6 +1394,25 @@ async def test_monthly_spend_report_sent_when_the_lock_is_free_or_absent(acquire
|
|||
proxy_logging_obj.slack_alerting_instance.send_monthly_spend_report.assert_awaited_once_with()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_weekly_spend_report_first_run_is_minutes_ahead_when_the_scheduler_zone_differs_from_the_clock():
|
||||
"""TZ=Asia/Calcutta on a host without the legacy alias puts the scheduler on IST through tzdata while
|
||||
datetime.now() stays on UTC, so a naive first run landed hours in the past, past the misfire grace."""
|
||||
local_offset: Final = datetime.now().astimezone().utcoffset() or timedelta(0)
|
||||
scheduler: Final = AsyncIOScheduler(timezone=timezone(local_offset + timedelta(hours=5, minutes=30)))
|
||||
registered_after: Final = datetime.now(timezone.utc)
|
||||
|
||||
await ProxyStartupEvent._initialize_slack_alerting_jobs(
|
||||
scheduler=scheduler,
|
||||
general_settings={},
|
||||
proxy_logging_obj=_make_slack_alerting_proxy_logging(acquire_lock_result=True),
|
||||
prisma_client=MagicMock(),
|
||||
)
|
||||
|
||||
first_run: Final = scheduler.get_job("weekly_spend_report_job").next_run_time
|
||||
assert registered_after < first_run < datetime.now(timezone.utc) + timedelta(seconds=311)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_spend_report_locks_are_never_released():
|
||||
"""The lock is a per-window marker, not a mutex: releasing it lets the next pod re-send."""
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import sys
|
|||
from pathlib import Path
|
||||
from typing import Final
|
||||
|
||||
import pytest
|
||||
from packaging.requirements import Requirement
|
||||
from packaging.specifiers import SpecifierSet
|
||||
from packaging.utils import NormalizedName, canonicalize_name
|
||||
|
|
@ -22,6 +23,11 @@ AIOHTTP_POOL_POISONING_SOURCE: Final = "aio-libs/aiohttp#12953, fixed in aiohttp
|
|||
ANYIO_STRANDED_LOCK_WAITER_FIX: Final = Version("4.14.0")
|
||||
ANYIO_STRANDED_LOCK_WAITER_SOURCE: Final = "agronholm/anyio#1145, fixed in anyio 4.14.0 released 2026-06-15"
|
||||
CLI_RUNTIME_DEPENDENCIES: Final = frozenset({"rich", "pyyaml", "requests"})
|
||||
PROXY_INSTALL_PLATFORMS: Final = (
|
||||
("linux", "Linux", "posix"),
|
||||
("darwin", "Darwin", "posix"),
|
||||
("win32", "Windows", "nt"),
|
||||
)
|
||||
PROXY_SERVER_ONLY_DEPENDENCIES: Final = frozenset(
|
||||
{
|
||||
"fastapi",
|
||||
|
|
@ -50,6 +56,20 @@ def _requirement_names(requirements: list[str]) -> frozenset[NormalizedName]:
|
|||
return frozenset(canonicalize_name(Requirement(requirement).name) for requirement in requirements)
|
||||
|
||||
|
||||
def _proxy_requirements_installed_on(sys_platform: str, platform_system: str, os_name: str) -> frozenset[NormalizedName]:
|
||||
environment: Final = {
|
||||
"sys_platform": sys_platform,
|
||||
"platform_system": platform_system,
|
||||
"os_name": os_name,
|
||||
"extra": "proxy",
|
||||
}
|
||||
return frozenset(
|
||||
canonicalize_name(requirement.name)
|
||||
for requirement in map(Requirement, OPTIONAL_DEPENDENCIES["proxy"])
|
||||
if requirement.marker is None or requirement.marker.evaluate(environment)
|
||||
)
|
||||
|
||||
|
||||
def test_importing_litellm_reports_the_declared_release_version() -> None:
|
||||
import litellm
|
||||
|
||||
|
|
@ -79,6 +99,15 @@ def test_cli_extra_is_a_thin_client_install() -> None:
|
|||
assert cli & PROXY_SERVER_ONLY_DEPENDENCIES == frozenset()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("sys_platform", "platform_system", "os_name"), PROXY_INSTALL_PLATFORMS)
|
||||
def test_proxy_extra_installs_tzdata_beside_its_scheduler_on_every_platform(
|
||||
sys_platform: str, platform_system: str, os_name: str
|
||||
) -> None:
|
||||
installed: Final = _proxy_requirements_installed_on(sys_platform, platform_system, os_name)
|
||||
assert canonicalize_name("apscheduler") in installed
|
||||
assert canonicalize_name("tzdata") in installed
|
||||
|
||||
|
||||
def test_declared_aiohttp_floor_excludes_pool_poisoning_releases() -> None:
|
||||
declared: Final = tuple(
|
||||
Requirement(requirement)
|
||||
|
|
|
|||
2
uv.lock
generated
2
uv.lock
generated
|
|
@ -5138,6 +5138,7 @@ proxy = [
|
|||
{ name = "soundfile" },
|
||||
{ name = "starlette" },
|
||||
{ name = "tomlkit" },
|
||||
{ name = "tzdata" },
|
||||
{ name = "uvicorn" },
|
||||
{ name = "uvloop", marker = "sys_platform != 'win32'" },
|
||||
{ name = "websockets" },
|
||||
|
|
@ -5423,6 +5424,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" },
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue