Skip to content

feat(litellm): finish models/repository migration; route all table access through repositories - #29713

Closed
yassin-berriai wants to merge 35 commits into
litellm_internal_stagingfrom
claude/happy-feynman-laDY2
Closed

feat(litellm): finish models/repository migration; route all table access through repositories#29713
yassin-berriai wants to merge 35 commits into
litellm_internal_stagingfrom
claude/happy-feynman-laDY2

Conversation

@yassin-berriai

Copy link
Copy Markdown
Contributor

Relevant issues

Continues the models and repository layer effort from #29686

Linear ticket

https://linear.app/litellm-ai/issue/LIT-3570/create-litellm-models-and-repository-layers

Pre-Submission checklist

  • I have added meaningful tests
  • My PR passes all unit tests on make test-unit
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

Type

New Feature

Changes

This picks up where #29686 left off and finishes the migration so that every DB table definition lives in litellm/models/ and every inline Prisma table access goes through the repository layer.

Definitions: the remaining pydantic table classes move out of litellm/proxy/_types.py into litellm/models/ and are re-exported for backwards compatibility. That covers the MCP server table, spend and error logs, team membership, the managed file/object/vector-store tables, and LiteLLM_BudgetTableFull plus LiteLLM_TeamMemberTable which now sit next to the budget model. The models package __init__ is completed to export every domain model. While wiring mcp_server into the model layer surfaced an import cycle, so mcp_server_manager now imports MCPAuthType/MCPTransportType from their source litellm.types.mcp rather than the proxy._types re-export.

Operations: every prisma_client.db.litellm_<table>.<op>(...) call across the proxy now goes through <Table>Repository(...).table, so each table name is defined in exactly one place. The nine tables that already had a domain repository (team, user, verification token, budget, model, organization, project, config, object permission) get their call sites routed in; the remaining tables get a thin PrismaTableRepository subclass in repositories/table_repositories.py. The .table property returns the same Prisma delegate, so this is behavior preserving.

Two categories are intentionally left as direct access because they do not map to the non-transactional .table delegate: transaction and batch operations inside batch_()/tx() blocks (batcher.litellm_X, tx.litellm_X), and raw SQL via query_raw/execute_raw that names tables as SQL strings. Routing those through .table would break transactional semantics or would not apply.

Tests cover the relocated models (including the team-membership budget helpers and the managed tables) and the passthrough repository base (table binding, the no-DB guard, and that every generated repository binds a distinct litellm_ table name).

Screenshots / Proof of Fix

To be added with a live proxy run against real provider APIs


Generated by Claude Code

claude added 30 commits June 4, 2026 17:02
This commit introduces domain models under litellm/backend and repository
layer under litellm/gateway to centralize database operations and provide
a clean separation between business logic and data access.

Backend models (litellm/backend/models/):
- Budget: budget configuration with spend tracking
- Credentials: encrypted credential storage
- Model: proxy model definitions with encryption support
- ObjectPermission: MCP, vector store, and agent permissions
- Organization: organization management
- Project: project management between teams and keys
- Team: team management with member roles
- User: user management with budget tracking
- VerificationToken: API key/token management

Gateway repositories (litellm/gateway/repositories/):
- BaseRepository: abstract base with common CRUD operations
- BudgetRepository: budget table operations
- ConfigRepository: config reconciliation from DB and configmap
- CredentialsRepository: encrypted credential operations
- ModelRepository: proxy model operations with encryption
- ObjectPermissionRepository: permission table operations
- OrganizationRepository: organization operations
- ProjectRepository: project operations
- TeamRepository: team operations with audit logging
- UserRepository: user operations
- VerificationTokenRepository: token operations with audit logging

The ConfigRepository implements the config reconciliation strategy where
DB values override YAML configmap values, except for None values and
empty lists which preserve the YAML config.

Includes comprehensive unit tests for all models and repositories.

https://claude.ai/code/session_01MtrRXaRiaAgKqF4UcbyKhC
Replace read-modify-write patterns with atomic array push operations
for add_member, add_admin, add_models in TeamRepository and add_to_team
in UserRepository. Use transactions for delete_team and delete_token to
ensure archive-then-delete is atomic. Add scalability note for
find_by_team_id in ModelRepository.

https://claude.ai/code/session_01MtrRXaRiaAgKqF4UcbyKhC
Add type assertions for _to_model returns in create methods,
fix _to_model_list to properly filter None values, and cast
param_name to Literal type in config reconciliation.

https://claude.ai/code/session_01MtrRXaRiaAgKqF4UcbyKhC
Include tests/test_litellm/backend and tests/test_litellm/gateway
in the unit test misc workflow so the new repository and model
tests are picked up by CI coverage.

https://claude.ai/code/session_01MtrRXaRiaAgKqF4UcbyKhC
Add comprehensive tests for all repository types including
organization, project, object_permission, and credentials.
Fix credentials_repository _to_model to properly parse JSON fields.
Tests increased from 46 to 115.

https://claude.ai/code/session_01MtrRXaRiaAgKqF4UcbyKhC
Add missing organization_id and budget_id fields to VerificationToken
model. The DB columns were being silently dropped due to extra="ignore"
in the base model. Keep org_id for API compatibility while
organization_id matches the actual DB column name.

https://claude.ai/code/session_01MtrRXaRiaAgKqF4UcbyKhC
Both backend (management endpoints) and gateway routes use models and
repositories, so they should not be nested under either. This moves
litellm/backend/models/ to litellm/models/ and
litellm/gateway/repositories/ to litellm/repositories/.

The backend and gateway __init__.py files remain as backwards-compatible
re-exports from the new locations.

https://claude.ai/code/session_01MtrRXaRiaAgKqF4UcbyKhC
Tests for ConfigRepository reconcile_config, update_config_fields, and
environment variable handling. Tests for VerificationTokenRepository
and TeamRepository delete operations with transactions, JSON field
parsing, and extended update operations.

https://claude.ai/code/session_01MtrRXaRiaAgKqF4UcbyKhC
Tests for ModelRepository.find_by_team_id, BaseRepository pagination
and filtering, DomainModel.from_db_record edge cases. Coverage should
now exceed the threshold.

https://claude.ai/code/session_01MtrRXaRiaAgKqF4UcbyKhC
Tests all field branches in update_team, update_project,
update_organization, and update_permission to improve patch coverage.

https://claude.ai/code/session_01MtrRXaRiaAgKqF4UcbyKhC
Tests create_model with all fields, update_model with all fields, and
update_user with all fields to cover remaining branch coverage gaps.

https://claude.ai/code/session_01MtrRXaRiaAgKqF4UcbyKhC
…llow copy

Two bugs fixed based on Greptile review:

1. delete_team was using to_db_dict() which includes fields like
   default_team_member_models and budget_limits that don't exist in
   LiteLLM_DeletedTeamTable. Added _build_archive_data() method that
   explicitly includes only columns present in the archive table.

2. reconcile_config used yaml_config.copy() (shallow copy) which caused
   _deep_merge_dicts to silently mutate the caller's nested dicts. Changed
   to copy.deepcopy() to ensure the original config is not modified.

https://claude.ai/code/session_01MtrRXaRiaAgKqF4UcbyKhC
Move the canonical credential types (CredentialBase, CredentialItem,
CreateCredentialItem) into the model layer (litellm/models/credentials.py)
and re-export them from litellm.types.utils for backwards compatibility.
Replace the hand-rolled Credentials domain model with these.

CredentialsRepository is now the only code path that touches
litellm_credentialstable; the credential CRUD endpoints and
proxy_server.get_credentials go through it. Encryption and in-memory
credential_list syncing stay with the callers so stored-data behavior is
unchanged.

https://claude.ai/code/session_01MtrRXaRiaAgKqF4UcbyKhC
Standardize on LiteLLM_ObjectPermissionTable as the canonical type, now
defined in litellm/models/object_permission.py (subclassing the low-level
LiteLLMPydanticObjectBase) and re-exported from litellm.proxy._types. This
also reconciles a latent bug: the table has a models column that the old
_types definition omitted.

Relax BaseRepository's generic bound from DomainModel to BaseModel and make
the default record->model conversion generic so repositories can return the
canonical table types. Repoint ObjectPermissionRepository accordingly.

https://claude.ai/code/session_01MtrRXaRiaAgKqF4UcbyKhC
Standardize on LiteLLM_BudgetTable (the user-input allowlist base) as the
canonical type, now defined in litellm/models/budget.py and re-exported from
litellm.proxy._types; its API-request subclasses stay in _types. Repoint
BudgetRepository. Drop the hand-rolled Budget domain model and its unused
helper methods.

https://claude.ai/code/session_01MtrRXaRiaAgKqF4UcbyKhC
Standardize on LiteLLM_ProxyModelTable, now defined in litellm/models/model.py
and re-exported from litellm.proxy._types. Reconciles latent omissions vs the
schema (blocked column, nullable model_info) and keeps the team_id /
team_public_model_name / is_blocked helpers the repository relies on. Repoint
ModelRepository; drop the hand-rolled Model domain model.

https://claude.ai/code/session_01MtrRXaRiaAgKqF4UcbyKhC
Standardize on LiteLLM_ProjectTable, now defined in litellm/models/project.py
(referencing the relocated Budget and ObjectPermission models) and re-exported
from litellm.proxy._types. Repoint ProjectRepository; drop the hand-rolled
Project domain model. Teach the test MockTable to synthesize primary keys on
create so repositories returning PK-required canonical types are covered.

https://claude.ai/code/session_01MtrRXaRiaAgKqF4UcbyKhC
model_id is the primary key and always present on reads; restoring it to a
required str matches the original _types contract and fixes the audit-log call
site type error. The test MockTable synthesizes model_id on create.

https://claude.ai/code/session_01MtrRXaRiaAgKqF4UcbyKhC
…layer

Standardize on LiteLLM_UserTable, now defined in litellm/models/user.py and
re-exported from litellm.proxy._types. Merge in the schema columns the prior
canonical class omitted (team_id, organization_id, object_permission_id,
password, max_parallel_requests, allowed_cache_controls, policies) so reads
through UserRepository no longer drop data. Relocate the referenced
LiteLLM_OrganizationMembershipTable alongside it. Repoint UserRepository; drop
the hand-rolled User domain model.

https://claude.ai/code/session_01MtrRXaRiaAgKqF4UcbyKhC
Standardize on LiteLLM_OrganizationTable, now defined in
litellm/models/organization.py (referencing the relocated Budget,
ObjectPermission and User models) and re-exported from litellm.proxy._types.
Reconciles the missing model_spend column. Repoint OrganizationRepository;
drop the hand-rolled Organization domain model.

https://claude.ai/code/session_01MtrRXaRiaAgKqF4UcbyKhC
Standardize on LiteLLM_VerificationToken (and LiteLLM_DeletedVerificationToken),
now defined in litellm/models/verification_token.py and re-exported from
litellm.proxy._types; LiteLLM_VerificationTokenView stays in _types since it
references Member. Repoint VerificationTokenRepository and build the archive
record via model_dump (dropping the object_permission relation) instead of the
removed DomainModel helper. The canonical class is moved verbatim, preserving
its existing org_id semantics rather than changing auth-facing behavior.

https://claude.ai/code/session_01MtrRXaRiaAgKqF4UcbyKhC
…model layer

Move the team cluster (MemberBase, Member, BudgetLimitEntry, LiteLLM_ModelTable,
TeamBase, LiteLLM_TeamTable, LiteLLM_TeamTableCachedObj, LiteLLM_DeletedTeamTable)
into litellm/models/team.py, re-exported from litellm.proxy._types so the many
importers and request-model subclasses (OrgMember, NewTeamRequest, etc.) keep
working. Reconciles columns the canonical class omitted but the repository's
archive path uses (model_spend, model_max_budget, policies,
allow_team_guardrail_config). Repoint TeamRepository; drop the hand-rolled Team
domain model.

https://claude.ai/code/session_01MtrRXaRiaAgKqF4UcbyKhC
The Team relocation script's class-replacement consumed the following
LiteLLM_ProxyModelTable re-export line, breaking import of
litellm.proxy.proxy_server across the proxy test suites. Restore it.

https://claude.ai/code/session_01MtrRXaRiaAgKqF4UcbyKhC
Relocate LiteLLM_Config, LiteLLM_SkillsTable, LiteLLM_AccessGroupTable,
LiteLLM_TagTable and LiteLLM_EndUserTable into litellm/models/, re-exported
from litellm.proxy._types. Also reformat _types.py.

https://claude.ai/code/session_01MtrRXaRiaAgKqF4UcbyKhC
claude added 4 commits June 4, 2026 21:41
policies was reconciled as a required List, but team creation passes
policies=None; make it Optional[List[str]] to restore team creation. Harden
the team archive build to coerce None arrays/spend to schema-safe defaults.
Add unit tests for the relocated Config, Skills, AccessGroup, Tag and EndUser
table models.

https://claude.ai/code/session_01MtrRXaRiaAgKqF4UcbyKhC
…le tables into the model layer

Moves the remaining standalone DB-table pydantic definitions out of
litellm/proxy/_types.py and into litellm/models/, re-exported from _types
for backwards compatibility. Also moves LiteLLM_BudgetTableFull and
LiteLLM_TeamMemberTable next to LiteLLM_BudgetTable, and completes the
models package __init__ exports.

Imports MCPAuthType/MCPTransportType in mcp_server_manager from their source
litellm.types.mcp instead of the proxy._types re-export, breaking an import
cycle that otherwise surfaced once the model layer pulled in mcp_server.
…pository

Replaces direct prisma_client.db.litellm_objectpermissiontable access across
the proxy with ObjectPermissionRepository(...).table, centralizing the table
name in the repository as the single data-access chokepoint. Behavior is
unchanged; the property returns the same Prisma delegate.
Replaces direct prisma_client.db.litellm_<table> access across the proxy with
<Table>Repository(...).table so each table is named in exactly one place, the
repository layer. For tables that already had a domain repository (team, user,
verification token, budget, model, organization, project, config) the existing
class gains the call sites; the rest get a thin PrismaTableRepository subclass
in table_repositories.py. The .table property returns the same Prisma delegate,
so behavior is unchanged.

Adds tests covering the passthrough base (table binding, no-DB guard, and that
every generated repository binds a distinct litellm_ table name).
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

The generated passthrough repository module was committed without black
formatting (it was untracked when the earlier black pass ran over the tracked
diff), so it lacked the blank lines black wants between top-level classes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants