fix(ci): stop deferred pydantic builds leaking caller locals and add the missing Lens FK migration (#44981)

* fix(ci): stop deferred pydantic builds leaking caller locals and add missing Lens FK migration

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(types): drop narrating comment from caller-locals regression test

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(proxy-extras): guard the Lens review FK migration with DO blocks

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(types): run the caller-locals regression in-process

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(proxy-extras): scope the Lens review FK guards to their table

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: mateo <mateo@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
devin-ai-integration[bot] 2026-10-06 23:20:54 -07:00 • committed by GitHub
parent 68c0a972a7
commit 43cf7e7493
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 53 additions and 0 deletions

View file

@ -0,0 +1,15 @@
-- DropForeignKey
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'LiteLLM_LensReview_lens_id_fkey' AND conrelid = '"LiteLLM_LensReview"'::regclass) THEN
ALTER TABLE "LiteLLM_LensReview" DROP CONSTRAINT "LiteLLM_LensReview_lens_id_fkey";
END IF;
END $$;
-- AddForeignKey
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'LiteLLM_LensReview_lens_id_fkey' AND conrelid = '"LiteLLM_LensReview"'::regclass) THEN
ALTER TABLE "LiteLLM_LensReview" ADD CONSTRAINT "LiteLLM_LensReview_lens_id_fkey" FOREIGN KEY ("lens_id") REFERENCES "LiteLLM_Lens"("id") ON DELETE CASCADE ON UPDATE CASCADE;
END IF;
END $$;

View file

@ -1,3 +1,4 @@
from collections.abc import Mapping
from typing import TYPE_CHECKING, Any, Final
from openai._models import BaseModel as OpenAIObject
@ -13,6 +14,24 @@ class LiteLLMBaseModel(BaseModel):
def __init__(self, /, **data: object) -> None: ...
@classmethod
def model_rebuild(
cls,
*,
force: bool = False,
raise_errors: bool = True,
_parent_namespace_depth: int = 2,
_types_namespace: Mapping[str, object] | None = None,
) -> bool | None:
# Resolve names from the model's own module, never a caller frame: a deferred first-use build
# reads f_locals 5 frames up, and on Python < 3.13 that rewrites the dict the caller's locals() returned
return super().model_rebuild(
force=force,
raise_errors=raise_errors,
_parent_namespace_depth=0,
_types_namespace=_types_namespace,
)
def model_post_init(self, context: object, /) -> None:
# Instances built by a parent's validator or by model_construct skip this class's own
# first-use build, leaving a MockValSer that fails when the instance is serialized as Any

View file

@ -4,6 +4,9 @@ import sys
from typing import Final
import pytest
from pydantic import ConfigDict
from litellm.types.llms.base import LiteLLMBaseModel
_PROBE: Final = """
from litellm.types.llms.base import LiteLLMBaseModel
@ -71,3 +74,19 @@ print(Holder(item=validated).model_dump_json(serialize_as_any=True), Holder(item
def test_deferred_instances_created_without_their_own_init_still_serialize_as_any() -> None:
assert _run_probe(_NESTED_PROBE, None) == '{"item":{"value":1}} {"item":{"value":2}}'
def test_deferred_first_use_build_leaves_caller_locals_snapshot_untouched() -> None:
class DeferredProbe(LiteLLMBaseModel):
model_config = ConfigDict(defer_build=True)
value: int
def build(model: type[DeferredProbe]) -> list[str]:
local_vars: Final = locals()
later: Final = 1
_ = model(value=later)
return sorted(local_vars)
assert not DeferredProbe.__pydantic_complete__
assert build(DeferredProbe) == ["model"]
assert DeferredProbe.__pydantic_complete__