mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-12 08:42:18 +00:00
chore: move config and docs off the repo root (#50966)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
45a31852a3
commit
b0e389503f
91 changed files with 297 additions and 508 deletions
|
|
@ -1,57 +0,0 @@
|
|||
{
|
||||
"name": "Python 3.11",
|
||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||
"image": "mcr.microsoft.com/devcontainers/python:3.11-bookworm",
|
||||
// https://github.com/devcontainers/images/tree/main/src/python
|
||||
// https://mcr.microsoft.com/en-us/product/devcontainers/python/tags
|
||||
|
||||
// "build": {
|
||||
// "dockerfile": "Dockerfile",
|
||||
// "context": ".."
|
||||
// },
|
||||
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "lts"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
|
||||
},
|
||||
|
||||
// Configure tool-specific properties.
|
||||
"customizations": {
|
||||
// Configure properties specific to VS Code.
|
||||
"vscode": {
|
||||
"settings": {},
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"ms-python.vscode-pylance",
|
||||
"GitHub.copilot",
|
||||
"GitHub.copilot-chat",
|
||||
"ms-python.autopep8"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
"forwardPorts": [4000],
|
||||
|
||||
"containerEnv": {
|
||||
"LITELLM_LOG": "DEBUG"
|
||||
},
|
||||
|
||||
// Use 'portsAttributes' to set default properties for specific forwarded ports.
|
||||
// More info: https://containers.dev/implementors/json_reference/#port-attributes
|
||||
"portsAttributes": {
|
||||
"4000": {
|
||||
"label": "LiteLLM Server",
|
||||
"onAutoForward": "notify"
|
||||
}
|
||||
},
|
||||
|
||||
// More info: https://aka.ms/dev-containers-non-root.
|
||||
// "remoteUser": "litellm",
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": "bash ./.devcontainer/post-create.sh"
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
echo "[post-create] Installing uv"
|
||||
curl -LsSf https://astral.sh/uv/0.10.9/install.sh | env UV_NO_MODIFY_PATH=1 sh
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
|
||||
echo "[post-create] Installing Python dependencies (uv)"
|
||||
uv sync --frozen --group proxy-dev --extra proxy
|
||||
|
||||
echo "[post-create] Generating Prisma client"
|
||||
uv run --no-sync prisma generate
|
||||
|
||||
echo "[post-create] Installing npm dependencies"
|
||||
cd ui/litellm-dashboard && npm ci
|
||||
|
||||
echo "[post-create] Done"
|
||||
32
.env.example
32
.env.example
|
|
@ -1,32 +0,0 @@
|
|||
# OpenAI
|
||||
OPENAI_API_KEY = ""
|
||||
OPENAI_BASE_URL = ""
|
||||
# Cohere
|
||||
COHERE_API_KEY = ""
|
||||
# OpenRouter
|
||||
OR_SITE_URL = ""
|
||||
OR_APP_NAME = "LiteLLM Example app"
|
||||
OR_API_KEY = ""
|
||||
# Azure API base URL
|
||||
AZURE_API_BASE = ""
|
||||
# Azure API version
|
||||
AZURE_API_VERSION = ""
|
||||
# Azure API key
|
||||
AZURE_API_KEY = ""
|
||||
# Replicate
|
||||
REPLICATE_API_KEY = ""
|
||||
REPLICATE_API_TOKEN = ""
|
||||
# Anthropic
|
||||
ANTHROPIC_API_KEY = ""
|
||||
# Infisical
|
||||
INFISICAL_TOKEN = ""
|
||||
# Novita AI
|
||||
NOVITA_API_KEY = ""
|
||||
# INFINITY
|
||||
INFINITY_API_KEY = ""
|
||||
|
||||
# Development Configs
|
||||
# Generate one with: echo "LITELLM_MASTER_KEY=sk-$(openssl rand -hex 32)"
|
||||
LITELLM_MASTER_KEY = ""
|
||||
DATABASE_URL = "postgresql://llmproxy:dbpassword9090@db:5432/litellm"
|
||||
STORE_MODEL_IN_DB = "True"
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
# Add the commit hash of any commit you want to ignore in `git blame` here.
|
||||
# One commit hash per line.
|
||||
#
|
||||
# The GitHub Blame UI will use this file automatically!
|
||||
#
|
||||
# Run this command to always ignore formatting commits in `git blame`
|
||||
# git config blame.ignoreRevsFile .git-blame-ignore-revs
|
||||
|
||||
# Update pydantic code to fix warnings (GH-3600)
|
||||
876840e9957bc7e9f7d6a2b58c4d7c53dad16481
|
||||
|
||||
# style(ui): run prettier --write across the dashboard (#29622)
|
||||
7edf3a9cb55548b143df1692f4ed7c4681d7fcf7
|
||||
|
||||
# style: reformat litellm/ with ruff format (#31317)
|
||||
17bfd415aeb5a57fb646b5cc67da1c730aa7c50b
|
||||
|
||||
# style: unify ruff format width on 120 (#31518)
|
||||
48b5a5a0cc5a694a11219416ee0b6eb6e620e74e
|
||||
|
||||
# refactor(imports): move collections.abc names out of typing (#35495)
|
||||
397e8e4918777e4e60a7f5e88699e0a9a7dabb3d
|
||||
|
||||
# refactor(lint): apply every safe ruff autofix and zero 28 strict-rule budgets (#35495)
|
||||
b604e2b20c6db2099085a2f0e59b7e99e87eed6f
|
||||
|
||||
# refactor(logging): drop redundant !s conversion flags from f-strings (#35546)
|
||||
7b2d3440cba3160277470f7a0180098ae9b87864
|
||||
|
||||
# perf: build log messages lazily so filtered-out log records cost nothing (#35703)
|
||||
c9887a1f94bc1e7e4bdfe64d640f0509a0bc19dd
|
||||
|
||||
# feat(lint): enforce Final on locals and freeze function parameters (#35807)
|
||||
2708620d6a599cc73c1950a942d26ac26a7ed3d4
|
||||
|
||||
# chore(lint): remove litellm/types from the ruff lint exclusion (#35926)
|
||||
4e32a8bf6a1e1af1e04b67c759841ccef44b2235
|
||||
|
||||
# chore(lint): strip inert type: ignore comments and zero LIT009/LIT010/LIT011 headroom (#35928)
|
||||
338e411103ad5d7003e97f34f04fa36bca542dbe
|
||||
2
.gitattributes
vendored
2
.gitattributes
vendored
|
|
@ -1,2 +0,0 @@
|
|||
*.ipynb linguist-vendored
|
||||
ui/litellm-dashboard/src/lib/http/schema.d.ts linguist-generated
|
||||
6
CONTRIBUTING.md → .github/CONTRIBUTING.md
vendored
6
CONTRIBUTING.md → .github/CONTRIBUTING.md
vendored
|
|
@ -276,15 +276,15 @@ When the provider reports its own usage or spend numbers, compare the proxy's nu
|
|||
|
||||
Provider work (a new provider, a new parameter, a new usage or cost field) is the most common kind of contribution, and it is where PRs get reworked most often. Follow these rules
|
||||
|
||||
**Preserve zero prices.** In [`model_prices_and_context_window.json`](model_prices_and_context_window.json), an explicit `0.0` means that the priced operation is free. A missing field or `null` means no rate is specified, so the relevant calculator may use a fallback. For example, when no batch input rate is configured, batch cache writes use `cache_creation_input_token_cost` if set, otherwise the regular input rate, then apply the batch discount. A zero ordinary input rate does not make separately priced cache reads or writes free. Test `rate is None` before substituting a fallback, since `row.get("cache_creation_input_token_cost") or fallback` would discard a valid zero. Speech keeps character pricing ahead of token pricing even when the character rate is zero. If neither rate is configured, speech cost calculation raises an error. Its model lookup preserves unset rates instead of treating compatibility defaults as explicit zero prices. Perplexity keeps a configured legacy search rate ahead of the current search rate, including zero. When registering custom prices, `None` leaves an existing rate unchanged and `0.0` overrides it
|
||||
**Preserve zero prices.** In [`model_prices_and_context_window.json`](../model_prices_and_context_window.json), an explicit `0.0` means that the priced operation is free. A missing field or `null` means no rate is specified, so the relevant calculator may use a fallback. For example, when no batch input rate is configured, batch cache writes use `cache_creation_input_token_cost` if set, otherwise the regular input rate, then apply the batch discount. A zero ordinary input rate does not make separately priced cache reads or writes free. Test `rate is None` before substituting a fallback, since `row.get("cache_creation_input_token_cost") or fallback` would discard a valid zero. Speech keeps character pricing ahead of token pricing even when the character rate is zero. If neither rate is configured, speech cost calculation raises an error. Its model lookup preserves unset rates instead of treating compatibility defaults as explicit zero prices. Perplexity keeps a configured legacy search rate ahead of the current search rate, including zero. When registering custom prices, `None` leaves an existing rate unchanged and `0.0` overrides it
|
||||
|
||||
**Find the existing machinery before you add plumbing.** LiteLLM already has generic paths that move data between the endpoints, the provider transformations, and cost tracking. A second, hand-rolled path for the same data is a common reason a provider PR gets rewritten before merge. [`ARCHITECTURE.md`](ARCHITECTURE.md) shows where the layers live, and the decision records in [`docs/adrs/`](docs/adrs/AGENTS.md) explain why they are shaped that way. For example, [the ADR on provider usage fields](docs/adrs/adr_001_provider_usage_fields_ride_as_usage_extras.md) covers how provider-specific usage fields reach cost tracking. If you cannot find a mechanism for what you need, say so in the PR or ask in [#pr-review on Slack](https://join.slack.com/t/litellmossslack/shared_invite/zt-3o7nkuyfr-p_kbNJj8taRfXGgQI1~YyA) before you build one
|
||||
**Find the existing machinery before you add plumbing.** LiteLLM already has generic paths that move data between the endpoints, the provider transformations, and cost tracking. A second, hand-rolled path for the same data is a common reason a provider PR gets rewritten before merge. [`ARCHITECTURE.md`](../docs/ARCHITECTURE.md) shows where the layers live, and the decision records in [`docs/adrs/`](../docs/adrs/AGENTS.md) explain why they are shaped that way. For example, [the ADR on provider usage fields](../docs/adrs/adr_001_provider_usage_fields_ride_as_usage_extras.md) covers how provider-specific usage fields reach cost tracking. If you cannot find a mechanism for what you need, say so in the PR or ask in [#pr-review on Slack](https://join.slack.com/t/litellmossslack/shared_invite/zt-3o7nkuyfr-p_kbNJj8taRfXGgQI1~YyA) before you build one
|
||||
|
||||
**Bill a new provider usage field from the usage object.** Leave the field on the usage object as an extra and read it in that provider's `cost_calculator.py`. A provider without one gets a new `cost_calculator.py` and an arm for it in `cost_per_token` (`litellm/cost_calculator.py`), which picks the calculator by provider. If the provider's transform builds `Usage` from named fields, as the Anthropic, Gemini, and Bedrock Converse ones do, pass the new field there too. Streamed requests rebuild usage in `ChunkProcessor.calculate_usage` (`litellm/litellm_core_utils/streaming_chunk_builder_utils.py`), which does not copy extras yet the way the Responses-to-chat bridge does (minus a short exclusion list), so until it does, bill the field on streams by naming it there, as Anthropic's `inference_geo` is. A provider that reports its own charge for the request, as OpenRouter does in `usage.cost`, is billed at that charge from `_hidden_params` before any calculator runs, unless the deployment sets its own prices. When the field counts a built-in tool such as web search, add the provider to `get_cost_for_web_search_request` in `litellm/llms/__init__.py`, and add a gate to `litellm/litellm_core_utils/llm_cost_calc/tool_call_cost_tracking.py` only when no existing gate fires. Then show the field's cost in your proof on every endpoint it reaches, streaming included
|
||||
|
||||
**Do not change what a caller sees to make internals easier.** Each endpoint keeps the schema of the API it emulates. A `/v1/responses` caller gets `input_tokens` and a `/v1/chat/completions` caller gets `prompt_tokens`, whatever the provider sent and whatever cost tracking needs. A provider usage field that shows up as an extra key is fine, since it only adds a key, but renaming, removing, or reshaping a key the endpoint defines is not. Reshaping a public response to feed an internal consumer breaks every user of that endpoint
|
||||
|
||||
If your change makes an architecture decision, such as a new cross-cutting mechanism or a deliberate difference from a provider's API shape, add an ADR to [`docs/adrs/`](docs/adrs/AGENTS.md) in the same PR
|
||||
If your change makes an architecture decision, such as a new cross-cutting mechanism or a deliberate difference from a provider's API shape, add an ADR to [`docs/adrs/`](../docs/adrs/AGENTS.md) in the same PR
|
||||
|
||||
## Building and Running Locally
|
||||
|
||||
0
SECURITY.md → .github/SECURITY.md
vendored
0
SECURITY.md → .github/SECURITY.md
vendored
72
.github/codecov.yml
vendored
Normal file
72
.github/codecov.yml
vendored
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
codecov:
|
||||
require_ci_to_pass: false # post coverage status even if CI has unrelated failures
|
||||
notify:
|
||||
wait_for_ci: false # post as soon as expected uploads arrive, don't wait on CI
|
||||
|
||||
ignore:
|
||||
- "litellm-rust/**"
|
||||
|
||||
# Flagged uploads avoid overlapping sessions on reruns.
|
||||
# Only the flags below reuse parent coverage, avoiding stale line maps elsewhere.
|
||||
flag_management:
|
||||
default_rules:
|
||||
carryforward: false
|
||||
individual_flags:
|
||||
- name: unit
|
||||
carryforward: true
|
||||
- name: core-packaging
|
||||
carryforward: true
|
||||
- name: mcp-integration
|
||||
carryforward: true
|
||||
|
||||
component_management:
|
||||
individual_components:
|
||||
- component_id: "Router"
|
||||
paths:
|
||||
- "router"
|
||||
- component_id: "LLMs"
|
||||
paths:
|
||||
- "*/llms/*"
|
||||
- component_id: "Caching"
|
||||
paths:
|
||||
- "*/caching/*"
|
||||
- ".*redis.*"
|
||||
- component_id: "litellm_logging"
|
||||
paths:
|
||||
- "*/integrations/*"
|
||||
- ".*litellm_logging.*"
|
||||
- component_id: "Proxy_Authentication"
|
||||
paths:
|
||||
- "*/proxy/auth/**"
|
||||
- component_id: "Enterprise"
|
||||
paths:
|
||||
- "enterprise/**"
|
||||
- component_id: "Batches"
|
||||
paths:
|
||||
- "*/proxy/batches_endpoints/**"
|
||||
- "litellm/batches/**"
|
||||
- "*/llms/*/batches/**"
|
||||
- component_id: "Videos"
|
||||
paths:
|
||||
- "litellm/videos/**"
|
||||
- "*/proxy/video_endpoints/**"
|
||||
- "*/llms/*/videos/**"
|
||||
- component_id: "Realtime"
|
||||
paths:
|
||||
- "litellm/realtime_api/**"
|
||||
- "*/proxy/realtime_endpoints/**"
|
||||
- "*/llms/*/realtime/**"
|
||||
- "litellm/litellm_core_utils/realtime_streaming.py"
|
||||
comment:
|
||||
layout: "header, diff, flags, components" # show component info in the PR comment
|
||||
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: auto
|
||||
threshold: 0% # do not allow project coverage to drop
|
||||
patch:
|
||||
default:
|
||||
target: auto
|
||||
threshold: 0% # patch coverage should be 100%
|
||||
0
.grype.yaml → .github/grype.yaml
vendored
0
.grype.yaml → .github/grype.yaml
vendored
|
|
@ -1,4 +1,4 @@
|
|||
extend = "ruff.toml"
|
||||
extend = "../../pyproject.toml"
|
||||
|
||||
[lint]
|
||||
preview = true
|
||||
|
|
@ -6,7 +6,7 @@ select = ["ANN", "ASYNC230", "B004", "B006", "B008", "B009", "B010", "B018", "B0
|
|||
extend-select = []
|
||||
# Overrides the inherited list: rules this gate enforces itself must NOT be external here,
|
||||
# so this config's RUF100 flags their stale `# noqa` directives. What remains external is
|
||||
# only what other tooling enforces: every base ruff.toml rule this select list doesn't
|
||||
# only what other tooling enforces: every base pyproject.toml rule this select list doesn't
|
||||
# re-enable (all of the default E/F families plus T20/PGH004/RUF008/RUF009, minus the
|
||||
# strict-selected F401 and RUF100; F4 is split out so stale F401 noqas stay detectable),
|
||||
# plus upstream litellm's ruff config.
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# Lint config for the test tree, which ruff.toml excludes from `ruff check`.
|
||||
# Lint config for the test tree, which pyproject.toml excludes from `ruff check`.
|
||||
#
|
||||
# Every rule here catches a test that cannot fail. Rules land one at a time, each
|
||||
# with its existing violations already fixed, so this list never needs a budget
|
||||
0
osv-scanner.toml → .github/osv-scanner.toml
vendored
0
osv-scanner.toml → .github/osv-scanner.toml
vendored
|
|
@ -5,13 +5,13 @@ Add custom rule YAML files here. Semgrep loads all `.yml`/`.yaml` files under th
|
|||
**Run only custom rules (CI / fail on findings):**
|
||||
|
||||
```bash
|
||||
semgrep scan --config .semgrep/rules . --error
|
||||
semgrep scan --config .github/semgrep/rules . --error
|
||||
```
|
||||
|
||||
**Run with registry + custom rules:**
|
||||
|
||||
```bash
|
||||
semgrep scan --config auto --config .semgrep/rules .
|
||||
semgrep scan --config auto --config .github/semgrep/rules .
|
||||
```
|
||||
|
||||
**Layout:**
|
||||
4
.github/workflows/conventional-commits.yml
vendored
4
.github/workflows/conventional-commits.yml
vendored
|
|
@ -3,7 +3,7 @@ name: Conventional PR Title
|
|||
# Squash-merge replaces the merge commit subject with the PR title, so
|
||||
# enforcing Conventional Commits at the PR-title level is what actually gates
|
||||
# the commits that land on the default branch. The local commit-msg hook
|
||||
# (.githooks/commit-msg) is a best-effort assist; this workflow is the gate.
|
||||
# (scripts/githooks/commit-msg) is a best-effort assist; this workflow is the gate.
|
||||
#
|
||||
# See https://www.conventionalcommits.org/en/v1.0.0/
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ jobs:
|
|||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
# Must mirror the type list in .githooks/commit-msg.
|
||||
# Must mirror the type list in scripts/githooks/commit-msg.
|
||||
types: |
|
||||
feat
|
||||
fix
|
||||
|
|
|
|||
7
.github/workflows/cost-map-guard.yml
vendored
7
.github/workflows/cost-map-guard.yml
vendored
|
|
@ -49,7 +49,12 @@ jobs:
|
|||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
HEAD_REF: ${{ github.event.pull_request.head.ref }}
|
||||
run: |
|
||||
uv run --no-project python ci_cd/cost_map_guard.py --base "$MERGE_BASE" --head "$HEAD_SHA" --head-ref "$HEAD_REF"
|
||||
guard=scripts/ci_cd/cost_map_guard.py
|
||||
# TODO: Drop the legacy path after this lands on main.
|
||||
if [ ! -f "$guard" ]; then
|
||||
guard=ci_cd/cost_map_guard.py
|
||||
fi
|
||||
uv run --no-project python "$guard" --base "$MERGE_BASE" --head "$HEAD_SHA" --head-ref "$HEAD_REF"
|
||||
- name: Check whether the pull request changes the cost map
|
||||
id: changed
|
||||
env:
|
||||
|
|
|
|||
4
.github/workflows/image-scan.yml
vendored
4
.github/workflows/image-scan.yml
vendored
|
|
@ -29,7 +29,7 @@ on:
|
|||
- ui/litellm-dashboard/package-lock.json
|
||||
- ui/nginx.conf
|
||||
- .github/workflows/image-scan.yml
|
||||
- .grype.yaml
|
||||
- .github/grype.yaml
|
||||
schedule:
|
||||
- cron: "41 6 * * *"
|
||||
workflow_dispatch:
|
||||
|
|
@ -201,7 +201,7 @@ jobs:
|
|||
GRYPE_MATCH_PYTHON_USING_CPES: "true"
|
||||
run: |
|
||||
"$RUNNER_TEMP/grype" "$IMAGE" \
|
||||
--config .grype.yaml \
|
||||
--config .github/grype.yaml \
|
||||
--only-fixed \
|
||||
--fail-on high \
|
||||
--output table
|
||||
|
|
|
|||
2
.github/workflows/osv-scan.yml
vendored
2
.github/workflows/osv-scan.yml
vendored
|
|
@ -40,7 +40,7 @@ jobs:
|
|||
- name: Scan lockfiles
|
||||
run: |
|
||||
"$RUNNER_TEMP/osv-scanner" scan source \
|
||||
--config osv-scanner.toml \
|
||||
--config .github/osv-scanner.toml \
|
||||
-L uv.lock \
|
||||
-L ui/litellm-dashboard/package-lock.json \
|
||||
-L vscode-extension/package-lock.json
|
||||
|
|
|
|||
7
.github/workflows/spend-logs-guard.yml
vendored
7
.github/workflows/spend-logs-guard.yml
vendored
|
|
@ -42,4 +42,9 @@ jobs:
|
|||
MERGE_BASE: ${{ steps.revisions.outputs.merge_base }}
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
run: |
|
||||
uv run --no-project --with pydantic==2.14.1 python ci_cd/spend_logs_guard.py --base "$MERGE_BASE" --head "$HEAD_SHA" --approver kerry-berri --reviews "$RUNNER_TEMP/reviews.json"
|
||||
guard=scripts/ci_cd/spend_logs_guard.py
|
||||
# TODO: Drop the legacy path after this lands on main.
|
||||
if [ ! -f "$guard" ]; then
|
||||
guard=ci_cd/spend_logs_guard.py
|
||||
fi
|
||||
uv run --no-project --with pydantic==2.14.1 python "$guard" --base "$MERGE_BASE" --head "$HEAD_SHA" --approver kerry-berri --reviews "$RUNNER_TEMP/reviews.json"
|
||||
|
|
|
|||
6
.github/workflows/test-linting.yml
vendored
6
.github/workflows/test-linting.yml
vendored
|
|
@ -145,7 +145,7 @@ jobs:
|
|||
- name: Run Ruff linting (test tree)
|
||||
if: steps.changes.outputs.decision != 'skip'
|
||||
run: |
|
||||
uv run --no-sync ruff check --config ruff-tests.toml tests
|
||||
uv run --no-sync ruff check --config .github/linters/ruff-tests.toml tests
|
||||
|
||||
- name: Check strict ruff rules (delta vs merge-base counts)
|
||||
if: steps.changes.outputs.decision != 'skip'
|
||||
|
|
@ -191,7 +191,7 @@ jobs:
|
|||
- name: Check tests/e2e basedpyright (zero errors)
|
||||
if: steps.changes.outputs.decision != 'skip'
|
||||
run: |
|
||||
if git diff --name-only --diff-filter=ACMRD "$GATE_BASE_SHA" HEAD -- ':(glob)tests/e2e/**/*.py' ':(glob)tests/e2e_harness/**/*.py' pyrightconfig.json | grep -q .; then
|
||||
if git diff --name-only --diff-filter=ACMRD "$GATE_BASE_SHA" HEAD -- ':(glob)tests/e2e/**/*.py' ':(glob)tests/e2e_harness/**/*.py' pyproject.toml | grep -q .; then
|
||||
uv run --no-sync basedpyright tests/e2e tests/e2e_harness
|
||||
else
|
||||
echo "No changed tests/e2e Python files; skipping."
|
||||
|
|
@ -703,7 +703,7 @@ jobs:
|
|||
version: "0.10.9"
|
||||
|
||||
- name: Run Semgrep (custom rules)
|
||||
run: uv tool run --from 'semgrep==1.157.0' semgrep scan --config .semgrep/rules . --error
|
||||
run: uv tool run --from 'semgrep==1.157.0' semgrep scan --config .github/semgrep/rules . --error
|
||||
lint-passed:
|
||||
name: lint passed
|
||||
needs: [python, secret-scan, ui, ui-api-types, code-quality, ci-coverage, semgrep]
|
||||
|
|
|
|||
5
.npmrc
5
.npmrc
|
|
@ -1,5 +0,0 @@
|
|||
# Supply-chain hardening
|
||||
# Packages needing lifecycle scripts: npm rebuild <pkg>
|
||||
ignore-scripts=true
|
||||
# Protects local npm install only — npm ci (used in CI) ignores this
|
||||
min-release-age=3
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
Do not write comments unless they are any of:
|
||||
- absolutely necessary to explain some very complex business logic (in which case, keep it concise and clear)
|
||||
- used as an input for tools to read and act on. For example:
|
||||
- entries in `.git-blame-ignore-revs` saying which commit is excluded from git blame
|
||||
- a lint or type checker suppression like `# mutable-ok` or `# pyright: ignore[reportArgumentType] # <reason>` when introducing a truly unavoidable violation
|
||||
- a TODO or FIXME
|
||||
- Not great to have those, but if it's unavoidable, make sure to include a strong, concise reason for why it's there or, better yet, link to a GitHub issue for the follow-up work
|
||||
|
|
@ -69,7 +68,7 @@ If you're trying to create a new function that relies on untyped stuff, instead
|
|||
|
||||
If you get an LIT001 fail, refactor the code to follow functional programming best practices rather than introducing mutable data structures. For example, build values in one shot with comprehensions or generators wrapped in `tuple()` / `MappingProxyType()` / `frozenset()` instead of seeding an empty `list`/`dict`/`set` and mutating it over time. Ideally, `# mutable-ok` is never used. Reach for it only as a genuine last resort when an immutable rewrite is truly impossible, and always pair it with a real reason
|
||||
|
||||
Every lint or type suppression must name the exact rule inside brackets and carry a reason comment, e.g. `# pyright: ignore[reportArgumentType] # stubs lack async overload` or `# noqa: TID251 # <reason>`. `# type: ignore` is banned (LIT009): pyrightconfig.json sets `enableTypeIgnoreComments` to false, so it silently does nothing
|
||||
Every lint or type suppression must name the exact rule inside brackets and carry a reason comment, e.g. `# pyright: ignore[reportArgumentType] # stubs lack async overload` or `# noqa: TID251 # <reason>`. `# type: ignore` is banned (LIT009): `[tool.pyright]` in pyproject.toml sets `enableTypeIgnoreComments` to false, so it silently does nothing
|
||||
|
||||
Commit and push your work when you're done without asking
|
||||
|
||||
|
|
|
|||
4
Makefile
4
Makefile
|
|
@ -126,7 +126,7 @@ install-hooks:
|
|||
./scripts/install_git_hooks.sh
|
||||
|
||||
# Formatting
|
||||
# Wrap width is ruff.toml's single source of truth (line-length = 120), shared by the
|
||||
# Wrap width is pyproject.toml's single source of truth (line-length = 120), shared by the
|
||||
# formatter and the import sorter so there's no 88-vs-120 split to reconcile.
|
||||
format: install-dev
|
||||
cd litellm && $(UV_RUN) ruff format --exclude '/enterprise/' . && cd ..
|
||||
|
|
@ -165,7 +165,7 @@ lint-format-check-changed: $(LINT_DEP_INSTALL) $(LINT_DEP_BASE)
|
|||
lint-ruff: $(LINT_DEP_INSTALL)
|
||||
$(UV_RUN) python scripts/check_mcp_operation_boundary.py
|
||||
cd litellm && $(UV_RUN) ruff check . && cd ..
|
||||
$(UV_RUN) ruff check --config ruff-tests.toml tests
|
||||
$(UV_RUN) ruff check --config .github/linters/ruff-tests.toml tests
|
||||
|
||||
# faster linter for developing ...
|
||||
# inspiration from:
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
</p>
|
||||
<p align="center">Open Source AI Gateway for 100+ LLMs. Self-hosted. Enterprise-ready. Call any LLM in OpenAI format.</p>
|
||||
<p align="center">
|
||||
<a href="https://render.com/deploy?repo=https://github.com/BerriAI/litellm" target="_blank" rel="nofollow"><img src="https://render.com/images/deploy-to-render-button.svg" alt="Deploy to Render" height="40"></a>
|
||||
<a href="https://render.com/deploy?repo=https://github.com/BerriAI/litellm&blueprintPath=litellm/deploy/render/render.yaml" target="_blank" rel="nofollow"><img src="https://render.com/images/deploy-to-render-button.svg" alt="Deploy to Render" height="40"></a>
|
||||
<a href="https://railway.com/deploy/RhvhdC?referralCode=7mRv9K&utm_medium=integration&utm_source=template&utm_campaign=generic"><img src="https://railway.com/button.svg" alt="Deploy on Railway" height="40"></a>
|
||||
<a href="https://console.aws.amazon.com/cloudshell/home" target="_blank" rel="nofollow"><img src="./.github/deploy-on-aws.png" alt="Deploy on AWS" height="40"></a>
|
||||
<a href="https://ssh.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https%3A%2F%2Fgithub.com%2FBerriAI%2Fterraform-google-litellm&cloudshell_git_branch=v1.105.0&cloudshell_workspace=examples%2Fdefault&cloudshell_tutorial=TUTORIAL.md&cloudshell_image=gcr.io/ds-artifacts-cloudshell/deploystack_custom_image&shellonly=true" target="_blank" rel="nofollow"><img src="./.github/deploy-on-gcp.png" alt="Deploy on GCP" height="40"></a>
|
||||
|
|
@ -725,7 +725,7 @@ make test-unit # Run unit tests
|
|||
make format-check # Check formatting only
|
||||
```
|
||||
|
||||
For detailed contributing guidelines, see [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
For detailed contributing guidelines, see [CONTRIBUTING.md](.github/CONTRIBUTING.md).
|
||||
|
||||
> **📖 Contributing to documentation?** The LiteLLM docs have moved to a separate repository: [BerriAI/litellm-docs](https://github.com/BerriAI/litellm-docs). Please open doc PRs there. Docs are served at [docs.litellm.ai](https://docs.litellm.ai).
|
||||
|
||||
|
|
|
|||
164
codecov.yaml
164
codecov.yaml
|
|
@ -1,164 +0,0 @@
|
|||
codecov:
|
||||
require_ci_to_pass: false # post coverage status even if CI has unrelated failures
|
||||
notify:
|
||||
wait_for_ci: false # post as soon as expected uploads arrive, don't wait on CI
|
||||
|
||||
ignore:
|
||||
- "litellm-rust/**"
|
||||
|
||||
# Uploads are flagged per workflow tier. carryforward makes
|
||||
# a re-upload of a flag replace its prior session instead of accumulating a
|
||||
# conflicting one, and lets a commit reuse a flag from its parent when that flag
|
||||
# was not re-uploaded. Required because the same commit can receive the
|
||||
# push-triggered workflows more than once (re-runs / branches cut at the same
|
||||
# SHA); flagless overlapping sessions made Codecov drop the largest files.
|
||||
flag_management:
|
||||
default_rules:
|
||||
carryforward: true
|
||||
# Dead flags no CI job uploads anymore, and flags whose job is path-gated on
|
||||
# PRs: their carried-forward sessions were measured against old revisions, and
|
||||
# the stale line maps mark comment lines of since-edited files as missed,
|
||||
# sinking patch coverage on unrelated PRs.
|
||||
individual_flags:
|
||||
- name: proxy-mgmt-behavior
|
||||
carryforward: false
|
||||
- name: security
|
||||
carryforward: false
|
||||
- name: proxy-db-schema-migration
|
||||
carryforward: false
|
||||
- name: circleci
|
||||
carryforward: false
|
||||
- name: core-utils
|
||||
carryforward: false
|
||||
- name: enterprise-routing
|
||||
carryforward: false
|
||||
- name: integrations
|
||||
carryforward: false
|
||||
- name: llm-vertex-ai
|
||||
carryforward: false
|
||||
- name: llm-other-providers
|
||||
carryforward: false
|
||||
- name: llm-openai-meta
|
||||
carryforward: false
|
||||
- name: misc
|
||||
carryforward: false
|
||||
- name: misc-dirs
|
||||
carryforward: false
|
||||
- name: proxy-auth
|
||||
carryforward: false
|
||||
- name: proxy-hooks-client
|
||||
carryforward: false
|
||||
- name: proxy-endpoints
|
||||
carryforward: false
|
||||
- name: proxy-feature-endpoints
|
||||
carryforward: false
|
||||
- name: proxy-server
|
||||
carryforward: false
|
||||
- name: mcp-elicitation
|
||||
carryforward: false
|
||||
- name: proxy-infra
|
||||
carryforward: false
|
||||
- name: proxy-infra-root
|
||||
carryforward: false
|
||||
- name: proxy-infra-root-a-p
|
||||
carryforward: false
|
||||
- name: proxy-infra-root-q-z
|
||||
carryforward: false
|
||||
- name: proxy-subprocess
|
||||
carryforward: false
|
||||
- name: caching-local
|
||||
carryforward: false
|
||||
- name: proxy-extras
|
||||
carryforward: false
|
||||
- name: enterprise-package
|
||||
carryforward: false
|
||||
- name: enterprise-managed-files
|
||||
carryforward: false
|
||||
- name: responses-caching-types
|
||||
carryforward: false
|
||||
- name: lens-python-310
|
||||
carryforward: false
|
||||
- name: lens-postgres
|
||||
carryforward: false
|
||||
- name: proxy-db-key-generation
|
||||
carryforward: false
|
||||
- name: proxy-db-auth-checks
|
||||
carryforward: false
|
||||
- name: proxy-db-jwt-and-keys
|
||||
carryforward: false
|
||||
- name: proxy-db-proxy-utils
|
||||
carryforward: false
|
||||
- name: proxy-db-proxy-server-core
|
||||
carryforward: false
|
||||
- name: proxy-db-proxy-runtime
|
||||
carryforward: false
|
||||
- name: proxy-db-mcp-oauth
|
||||
carryforward: false
|
||||
- name: proxy-db-custom-logging
|
||||
carryforward: false
|
||||
- name: proxy-db-logging-misc
|
||||
carryforward: false
|
||||
- name: proxy-db-db-and-spend
|
||||
carryforward: false
|
||||
- name: proxy-db-guardrails-hooks
|
||||
carryforward: false
|
||||
- name: proxy-db-budgets
|
||||
carryforward: false
|
||||
- name: proxy-db-endpoints-and-responses
|
||||
carryforward: false
|
||||
- name: roi-postgres
|
||||
carryforward: false
|
||||
- name: redis-compat
|
||||
carryforward: false
|
||||
|
||||
component_management:
|
||||
individual_components:
|
||||
- component_id: "Router"
|
||||
paths:
|
||||
- "router"
|
||||
- component_id: "LLMs"
|
||||
paths:
|
||||
- "*/llms/*"
|
||||
- component_id: "Caching"
|
||||
paths:
|
||||
- "*/caching/*"
|
||||
- ".*redis.*"
|
||||
- component_id: "litellm_logging"
|
||||
paths:
|
||||
- "*/integrations/*"
|
||||
- ".*litellm_logging.*"
|
||||
- component_id: "Proxy_Authentication"
|
||||
paths:
|
||||
- "*/proxy/auth/**"
|
||||
- component_id: "Enterprise"
|
||||
paths:
|
||||
- "enterprise/**"
|
||||
- component_id: "Batches"
|
||||
paths:
|
||||
- "*/proxy/batches_endpoints/**"
|
||||
- "litellm/batches/**"
|
||||
- "*/llms/*/batches/**"
|
||||
- component_id: "Videos"
|
||||
paths:
|
||||
- "litellm/videos/**"
|
||||
- "*/proxy/video_endpoints/**"
|
||||
- "*/llms/*/videos/**"
|
||||
- component_id: "Realtime"
|
||||
paths:
|
||||
- "litellm/realtime_api/**"
|
||||
- "*/proxy/realtime_endpoints/**"
|
||||
- "*/llms/*/realtime/**"
|
||||
- "litellm/litellm_core_utils/realtime_streaming.py"
|
||||
comment:
|
||||
layout: "header, diff, flags, components" # show component info in the PR comment
|
||||
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: auto
|
||||
threshold: 0% # do not allow project coverage to drop
|
||||
patch:
|
||||
default:
|
||||
target: auto
|
||||
threshold: 0% # patch coverage should be 100%
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
This document helps contributors understand where to make changes in LiteLLM.
|
||||
|
||||
For why a layer is shaped the way it is, read the architecture decision records in [`docs/adrs/`](docs/adrs/AGENTS.md) (Python SDK and proxy) and [`litellm-rust/docs/adrs/`](litellm-rust/docs/adrs/AGENTS.md) (Rust core)
|
||||
For why a layer is shaped the way it is, read the architecture decision records in [`docs/adrs/`](adrs/AGENTS.md) (Python SDK and proxy) and [`litellm-rust/docs/adrs/`](../litellm-rust/docs/adrs/AGENTS.md) (Rust core)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -4,7 +4,7 @@ background, once it serves).
|
|||
|
||||
A migration cannot build them: a plain `CREATE INDEX` blocks spend-log inserts for the
|
||||
whole build, and `CREATE INDEX CONCURRENTLY` is refused on a partitioned parent
|
||||
(db_scripts/partition_spend_logs.sql). `REQUEST_LOG_INDEXES` is the one list to extend;
|
||||
(scripts/db/partition_spend_logs.sql). `REQUEST_LOG_INDEXES` is the one list to extend;
|
||||
names match what Prisma derives from the `@@index` declarations in schema.prisma, so an
|
||||
index a database already has is recognized and never rebuilt.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ _SPEND_LOGS_PK_CLAUSE_RE = re.compile(
|
|||
)
|
||||
|
||||
PARTITIONED_SPEND_LOGS_PUSH_ERROR = (
|
||||
"LiteLLM_SpendLogs is a partitioned table (see db_scripts/partition_spend_logs.sql), "
|
||||
"LiteLLM_SpendLogs is a partitioned table (see scripts/db/partition_spend_logs.sql), "
|
||||
'so its primary key must include the partition key ("startTime"). `prisma db push` '
|
||||
"reconciles the database against schema.prisma, which declares the unpartitioned "
|
||||
'primary key ("request_id"), and Postgres rejects that rewrite with: unique '
|
||||
|
|
@ -179,7 +179,7 @@ def _without_spend_logs_pk_clauses(statement: str) -> str | None:
|
|||
|
||||
def filter_partitioned_spend_logs_diff(diff_sql: str) -> str:
|
||||
"""Drop statements from a `prisma migrate diff` script that fight the
|
||||
SpendLogs partitioning runbook (db_scripts/partition_spend_logs.sql): the
|
||||
SpendLogs partitioning runbook (scripts/db/partition_spend_logs.sql): the
|
||||
primary-key rewrite on "LiteLLM_SpendLogs", which Postgres rejects on a
|
||||
partitioned table, and drops of runbook artifacts such as
|
||||
"LiteLLM_SpendLogs_legacy"."""
|
||||
|
|
@ -679,7 +679,7 @@ class ProxyExtrasDBManager:
|
|||
"""True when the connected database's LiteLLM_SpendLogs is a
|
||||
partitioned table in Prisma's target schema (the `schema` URL param,
|
||||
falling back to Prisma's default target, public), i.e. the operator
|
||||
ran db_scripts/partition_spend_logs.sql. Returns False when psycopg is
|
||||
ran scripts/db/partition_spend_logs.sql. Returns False when psycopg is
|
||||
unavailable or the database cannot be reached, preserving the
|
||||
pre-existing behavior in those cases."""
|
||||
database_url = os.getenv("DATABASE_URL")
|
||||
|
|
@ -692,7 +692,7 @@ class ProxyExtrasDBManager:
|
|||
logger.warning(
|
||||
"psycopg is not installed; skipping the LiteLLM_SpendLogs "
|
||||
"partition check. If this table is partitioned (see "
|
||||
"db_scripts/partition_spend_logs.sql), schema reconciliation "
|
||||
"scripts/db/partition_spend_logs.sql), schema reconciliation "
|
||||
"will try to rewrite its primary key and fail. Install the "
|
||||
"litellm[extra_proxy] extra, which now includes psycopg."
|
||||
)
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ brew install postgresql@14 # macOS
|
|||
export PATH="/opt/homebrew/opt/postgresql@14/bin:$PATH"
|
||||
|
||||
# Run migration
|
||||
uv run --with testing.postgresql python ci_cd/run_migration.py "your_migration_name"
|
||||
uv run --with testing.postgresql python scripts/ci_cd/run_migration.py "your_migration_name"
|
||||
```
|
||||
|
||||
## What It Does
|
||||
|
|
@ -89,7 +89,7 @@ When the guard fires:
|
|||
4. Only if the drops are genuinely intentional, re-run with the flag:
|
||||
|
||||
```bash
|
||||
uv run --with testing.postgresql python ci_cd/run_migration.py "your_migration_name" --allow-destructive
|
||||
uv run --with testing.postgresql python scripts/ci_cd/run_migration.py "your_migration_name" --allow-destructive
|
||||
```
|
||||
|
||||
> **AI AGENTS / ASSISTANTS:** Do **not** automatically re-run the command with `--allow-destructive`. If the guard fires while you are driving the runbook for a human, stop, show them the error, and wait for their explicit confirmation before passing the flag. Auto-passing `--allow-destructive` is the exact failure mode this guard exists to prevent.
|
||||
|
|
@ -98,7 +98,7 @@ When the guard fires:
|
|||
|
||||
**Missing testing module:**
|
||||
```bash
|
||||
uv run --with testing.postgresql python ci_cd/run_migration.py "your_migration_name"
|
||||
uv run --with testing.postgresql python scripts/ci_cd/run_migration.py "your_migration_name"
|
||||
```
|
||||
|
||||
**initdb not found:**
|
||||
|
|
|
|||
|
|
@ -3034,7 +3034,7 @@ class ConfigGeneralSettings(LiteLLMPydanticObjectBase):
|
|||
)
|
||||
use_spend_logs_partitioning: bool | None = Field(
|
||||
None,
|
||||
description="If True and LiteLLM_SpendLogs has been converted to a range-partitioned table (db_scripts/partition_spend_logs.sql), retention cleanup drops expired partitions instead of deleting rows, and pre-creates upcoming partitions. Default is False.",
|
||||
description="If True and LiteLLM_SpendLogs has been converted to a range-partitioned table (scripts/db/partition_spend_logs.sql), retention cleanup drops expired partitions instead of deleting rows, and pre-creates upcoming partitions. Default is False.",
|
||||
)
|
||||
maximum_spend_logs_cleanup_batch_size: int | None = Field(
|
||||
None,
|
||||
|
|
|
|||
|
|
@ -294,7 +294,7 @@ keys = client.keys.list(
|
|||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please check out our [contributing guidelines](../../CONTRIBUTING.md) for details.
|
||||
Contributions are welcome! Please check out our [contributing guidelines](../../../.github/CONTRIBUTING.md) for details.
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ async def create_view_tolerating_race(db: SupportsExecuteRaw, view_name: str, dd
|
|||
async def create_missing_views(db: SupportsRawQueries) -> None:
|
||||
"""
|
||||
--------------------------------------------------
|
||||
NOTE: Copy of `litellm/db_scripts/create_views.py`.
|
||||
NOTE: Copy of `scripts/db/create_views.py`.
|
||||
--------------------------------------------------
|
||||
Checks if the LiteLLM_VerificationTokenView and MonthlyGlobalSpend exists in the user's db.
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ to the OS immediately, with no tombstones and no vacuum.
|
|||
|
||||
This manager only acts when use_spend_logs_partitioning is enabled in
|
||||
general_settings AND the table is already partitioned (set up via the
|
||||
db_scripts/partition_spend_logs.sql runbook). Without both, the cleanup job
|
||||
scripts/db/partition_spend_logs.sql runbook). Without both, the cleanup job
|
||||
keeps the batched-DELETE path, so existing deployments are untouched.
|
||||
"""
|
||||
|
||||
|
|
|
|||
|
|
@ -6006,7 +6006,7 @@ class PrismaClient:
|
|||
missing_views: Final = expected_views_set - ret_view_names_set
|
||||
|
||||
verbose_proxy_logger.warning(
|
||||
"\n\n\x1b[93mNot all views exist in db, needed for UI 'Usage' tab. Missing=%s.\nRun 'create_views.py' from https://github.com/BerriAI/litellm/tree/main/db_scripts to create missing views.\x1b[0m\n",
|
||||
"\n\n\x1b[93mNot all views exist in db, needed for UI 'Usage' tab. Missing=%s.\nRun 'create_views.py' from https://github.com/BerriAI/litellm/tree/main/scripts/db to create missing views.\x1b[0m\n",
|
||||
missing_views,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1289,7 +1289,7 @@ def function_setup(
|
|||
original_function: str,
|
||||
rules_obj: Rules,
|
||||
start_time: datetime.datetime,
|
||||
*args: Any, # positional passthrough to the wrapped LLM call (ANN401 ignored, see ruff-strict.toml)
|
||||
*args: Any, # positional passthrough to the wrapped LLM call (ANN401 ignored, see .github/linters/ruff-strict.toml)
|
||||
is_async_call: bool = True,
|
||||
**kwargs: Any, # kwargs-ok: forwarded to Logging()/callbacks, varies per call_type
|
||||
) -> tuple[LiteLLMLoggingObject, dict[str, Any]]:
|
||||
|
|
|
|||
|
|
@ -488,3 +488,79 @@ pytest_add_cli_args = [
|
|||
[tool.coverage.run]
|
||||
source = ["litellm"]
|
||||
relative_files = true
|
||||
|
||||
[tool.ruff]
|
||||
lint.ignore = ["F405", "E402", "F403"]
|
||||
# The second group is the strict gate's graduates: rules the codebase already has zero
|
||||
# violations of, so they hard-fail here instead of being counted by the strict gate.
|
||||
# That gives editors and `ruff check --fix` the diagnostic, which the gate script cannot.
|
||||
lint.extend-select = [
|
||||
"T20", "PGH004", "RUF008", "RUF009", "RUF100",
|
||||
"B004", "B018", "B021", "B033", "FURB136", "FURB168", "FURB188", "I001", "PERF402", "PIE790",
|
||||
"PIE800", "PLC0208", "PLR0124", "PLR0402", "PLR0206", "PLR1704", "PLR1711", "PLR1730", "PLR2044",
|
||||
"PLW0133", "PYI030", "PYI041", "PYI064", "RET501", "RUF010", "RUF022", "RUF023", "RUF051", "S113",
|
||||
"SIM114", "SIM118", "SIM201", "SIM211", "SIM222", "TC005", "UP006", "UP007", "UP008",
|
||||
"UP012", "UP018", "UP024", "UP032", "UP034", "UP035", "UP036", "UP037", "UP045",
|
||||
"C404", "C419", "TID253",
|
||||
]
|
||||
# RUF100 (unused-noqa) only knows the rules enabled in THIS config, so it would strip
|
||||
# `# noqa` directives that protect rules enforced elsewhere. List those codes as external
|
||||
# so RUF100 leaves their directives alone: the strict gate (.github/linters/ruff-strict.toml) and upstream
|
||||
# litellm's own ruff config both rely on suppressions this config can't see.
|
||||
lint.external = [
|
||||
# Enforced by the strict-rule gate (scripts/ruff_strict_gate.py + .github/linters/ruff-strict.toml).
|
||||
# Family entries whose every strict rule graduated into extend-select above (FURB), and
|
||||
# standalone graduated codes (I001, RUF010, RUF022, RUF023, RUF051), are dropped so this
|
||||
# config's RUF100 polices their directives itself.
|
||||
"ANN", "ASYNC230", "B", "C", "D202", "D403", "D417", "D419", "DTZ", "EXE", "LOG015", "N999", "PERF",
|
||||
"PIE", "PL", "PYI", "RET", "RUF012", "RUF015", "RUF019",
|
||||
"RUF046", "RUF059", "S110", "S112", "SIM", "TC", "TID251", "TRY", "UP",
|
||||
# Enforced by upstream litellm's ruff config, but not run in this repo's CI
|
||||
"PLC0415", "E402", "BLE001", "ARG002", "S102", "S324", "S606", "D401", "F403", "F405",
|
||||
]
|
||||
line-length = 120
|
||||
|
||||
extend-exclude = ["litellm/proxy/discovery_endpoints/skill_scanner_adapter"]
|
||||
|
||||
format.exclude = ["**/enterprise/**"]
|
||||
|
||||
# Was the top-level `exclude`. Scoped to lint so `ruff format` still formats these paths
|
||||
# (Black did) while `ruff check` keeps skipping them.
|
||||
lint.exclude = ["litellm/__init__.py", "litellm/proxy/example_config_yaml/*", "tests/*"]
|
||||
|
||||
|
||||
[tool.ruff.lint.flake8-tidy-imports]
|
||||
banned-module-level-imports = ["prisma"]
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"litellm/main.py" = ["F401"]
|
||||
"litellm/types/caching.py" = ["F401"]
|
||||
"litellm/types/integrations/slack_alerting.py" = ["F401"]
|
||||
"litellm/types/llms/custom_http.py" = ["F401"]
|
||||
"litellm/types/llms/openai.py" = ["F401"]
|
||||
"litellm/types/proxy/management_endpoints/scim_v2.py" = ["F401"]
|
||||
"litellm/types/responses/main.py" = ["F401"]
|
||||
"litellm/utils.py" = ["F401"]
|
||||
"litellm/proxy/proxy_server.py" = ["F401"]
|
||||
"litellm/caching/__init__.py" = ["F401"]
|
||||
"litellm/llms/anthropic/chat/__init__.py" = ["F401"]
|
||||
"litellm/llms/azure_ai/embed/__init__.py" = ["F401"]
|
||||
"litellm/llms/azure_ai/rerank/__init__.py" = ["F401"]
|
||||
"litellm/llms/bedrock/chat/__init__.py" = ["F401"]
|
||||
"litellm/proxy/utils.py" = ["F401"]
|
||||
|
||||
[tool.pyright]
|
||||
include = ["litellm", "litellm_cli"]
|
||||
ignore = []
|
||||
exclude = ["**/node_modules", "**/__pycache__", "tests/e2e/claude_code", "tests/e2e_harness/claude_code", "tests/e2e/ui", "litellm/types/utils.py", "litellm/proxy/_types.py", "litellm/proxy/discovery_endpoints/skill_scanner_adapter"]
|
||||
pythonVersion = "3.12"
|
||||
typeCheckingMode = "strict"
|
||||
enableTypeIgnoreComments = false
|
||||
reportMissingImports = false
|
||||
reportPrivateImportUsage = false
|
||||
reportExplicitAny = "error"
|
||||
reportAny = "error"
|
||||
|
||||
[[tool.pyright.executionEnvironments]]
|
||||
root = "tests/e2e_harness"
|
||||
extraPaths = ["tests/e2e", "tests/e2e/batches", "tests/e2e/guardrails", "tests/e2e/perf", "tests/e2e/logging"]
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"include": ["litellm", "litellm_cli"],
|
||||
"ignore": [],
|
||||
"exclude": ["**/node_modules", "**/__pycache__", "tests/e2e/claude_code", "tests/e2e_harness/claude_code", "tests/e2e/ui", "litellm/types/utils.py", "litellm/proxy/_types.py", "litellm/proxy/discovery_endpoints/skill_scanner_adapter"],
|
||||
"executionEnvironments": [
|
||||
{
|
||||
"root": "tests/e2e_harness",
|
||||
"extraPaths": ["tests/e2e", "tests/e2e/batches", "tests/e2e/guardrails", "tests/e2e/perf", "tests/e2e/logging"]
|
||||
}
|
||||
],
|
||||
"pythonVersion": "3.12",
|
||||
"typeCheckingMode": "strict",
|
||||
"enableTypeIgnoreComments": false,
|
||||
"reportMissingImports": false,
|
||||
"reportPrivateImportUsage": false,
|
||||
"reportExplicitAny": "error",
|
||||
"reportAny": "error"
|
||||
}
|
||||
58
ruff.toml
58
ruff.toml
|
|
@ -1,58 +0,0 @@
|
|||
lint.ignore = ["F405", "E402", "F403"]
|
||||
# The second group is the strict gate's graduates: rules the codebase already has zero
|
||||
# violations of, so they hard-fail here instead of being counted by the strict gate.
|
||||
# That gives editors and `ruff check --fix` the diagnostic, which the gate script cannot.
|
||||
lint.extend-select = [
|
||||
"T20", "PGH004", "RUF008", "RUF009", "RUF100",
|
||||
"B004", "B018", "B021", "B033", "FURB136", "FURB168", "FURB188", "I001", "PERF402", "PIE790",
|
||||
"PIE800", "PLC0208", "PLR0124", "PLR0402", "PLR0206", "PLR1704", "PLR1711", "PLR1730", "PLR2044",
|
||||
"PLW0133", "PYI030", "PYI041", "PYI064", "RET501", "RUF010", "RUF022", "RUF023", "RUF051", "S113",
|
||||
"SIM114", "SIM118", "SIM201", "SIM211", "SIM222", "TC005", "UP006", "UP007", "UP008",
|
||||
"UP012", "UP018", "UP024", "UP032", "UP034", "UP035", "UP036", "UP037", "UP045",
|
||||
"C404", "C419", "TID253",
|
||||
]
|
||||
# RUF100 (unused-noqa) only knows the rules enabled in THIS config, so it would strip
|
||||
# `# noqa` directives that protect rules enforced elsewhere. List those codes as external
|
||||
# so RUF100 leaves their directives alone: the strict gate (ruff-strict.toml) and upstream
|
||||
# litellm's own ruff config both rely on suppressions this config can't see.
|
||||
lint.external = [
|
||||
# Enforced by the strict-rule gate (scripts/ruff_strict_gate.py + ruff-strict.toml).
|
||||
# Family entries whose every strict rule graduated into extend-select above (FURB), and
|
||||
# standalone graduated codes (I001, RUF010, RUF022, RUF023, RUF051), are dropped so this
|
||||
# config's RUF100 polices their directives itself.
|
||||
"ANN", "ASYNC230", "B", "C", "D202", "D403", "D417", "D419", "DTZ", "EXE", "LOG015", "N999", "PERF",
|
||||
"PIE", "PL", "PYI", "RET", "RUF012", "RUF015", "RUF019",
|
||||
"RUF046", "RUF059", "S110", "S112", "SIM", "TC", "TID251", "TRY", "UP",
|
||||
# Enforced by upstream litellm's ruff config, but not run in this repo's CI
|
||||
"PLC0415", "E402", "BLE001", "ARG002", "S102", "S324", "S606", "D401", "F403", "F405",
|
||||
]
|
||||
line-length = 120
|
||||
|
||||
extend-exclude = ["litellm/proxy/discovery_endpoints/skill_scanner_adapter"]
|
||||
|
||||
format.exclude = ["**/enterprise/**"]
|
||||
|
||||
# Was the top-level `exclude`. Scoped to lint so `ruff format` still formats these paths
|
||||
# (Black did) while `ruff check` keeps skipping them.
|
||||
lint.exclude = ["litellm/__init__.py", "litellm/proxy/example_config_yaml/*", "tests/*"]
|
||||
|
||||
|
||||
[lint.flake8-tidy-imports]
|
||||
banned-module-level-imports = ["prisma"]
|
||||
|
||||
[lint.per-file-ignores]
|
||||
"litellm/main.py" = ["F401"]
|
||||
"litellm/types/caching.py" = ["F401"]
|
||||
"litellm/types/integrations/slack_alerting.py" = ["F401"]
|
||||
"litellm/types/llms/custom_http.py" = ["F401"]
|
||||
"litellm/types/llms/openai.py" = ["F401"]
|
||||
"litellm/types/proxy/management_endpoints/scim_v2.py" = ["F401"]
|
||||
"litellm/types/responses/main.py" = ["F401"]
|
||||
"litellm/utils.py" = ["F401"]
|
||||
"litellm/proxy/proxy_server.py" = ["F401"]
|
||||
"litellm/caching/__init__.py" = ["F401"]
|
||||
"litellm/llms/anthropic/chat/__init__.py" = ["F401"]
|
||||
"litellm/llms/azure_ai/embed/__init__.py" = ["F401"]
|
||||
"litellm/llms/azure_ai/rerank/__init__.py" = ["F401"]
|
||||
"litellm/llms/bedrock/chat/__init__.py" = ["F401"]
|
||||
"litellm/proxy/utils.py" = ["F401"]
|
||||
|
|
@ -32,7 +32,7 @@ LIT008 `**kwargs` parameter. The keyword contract is erased and everything it c
|
|||
syntax. Declare explicit keyword params, or accept one frozen payload. `*args`,
|
||||
by contrast, is fine when typed (it's just a tuple). Suppress: `# kwargs-ok: <reason>`.
|
||||
LIT009 `# type: ignore` in any shape (bare, with codes, with a reason).
|
||||
pyrightconfig.json sets enableTypeIgnoreComments to false, so basedpyright
|
||||
[tool.pyright] in pyproject.toml sets enableTypeIgnoreComments to false, so basedpyright
|
||||
never honors it: the comment is inert dead syntax that suppresses nothing.
|
||||
Use `# pyright: ignore[ruleName] # <reason>` instead.
|
||||
LIT010 Variable assignment without a `Final` declaration. Every local and module-level
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ def create_baseline():
|
|||
"""Create baseline migration in deploy/migrations"""
|
||||
try:
|
||||
# Get paths
|
||||
root_dir = Path(__file__).parent.parent
|
||||
root_dir = Path(__file__).resolve().parents[2]
|
||||
deploy_dir = root_dir / "deploy"
|
||||
migrations_dir = deploy_dir / "migrations"
|
||||
schema_path = root_dir / "schema.prisma"
|
||||
|
|
@ -96,7 +96,7 @@ def _default_base_branch(root_dir: Path) -> str:
|
|||
result: Final = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
str(Path(__file__).resolve().parents[1] / "scripts" / "default_branch.py"),
|
||||
str(Path(__file__).resolve().parents[1] / "default_branch.py"),
|
||||
"--repo-root",
|
||||
str(root_dir),
|
||||
"--branch",
|
||||
|
|
@ -238,7 +238,7 @@ def create_migration(
|
|||
skip_freshness_check (bool): Skip the "branch is up to date" check.
|
||||
Only for intentional migrations against an older base.
|
||||
"""
|
||||
root_dir = Path(__file__).parent.parent
|
||||
root_dir = Path(__file__).resolve().parents[2]
|
||||
|
||||
if skip_freshness_check:
|
||||
print(
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
-- such buckets keep the writer's numbers and skip the backfill's.
|
||||
--
|
||||
-- Usage:
|
||||
-- psql "$DATABASE_URL" -v cutover="'2026-07-25T00:00:00Z'" -f db_scripts/backfill_daily_tool_spend.sql
|
||||
-- psql "$DATABASE_URL" -v cutover="'2026-07-25T00:00:00Z'" -f scripts/db/backfill_daily_tool_spend.sql
|
||||
|
||||
SET TIME ZONE 'UTC';
|
||||
|
||||
|
|
@ -22,14 +22,14 @@
|
|||
-- budget_id links to a LiteLLM_BudgetTable row whose budget_duration IS
|
||||
-- NOT NULL (a linked budget resets the key's spend each period too). For
|
||||
-- those keys this script only recovers the current period;
|
||||
-- db_scripts/backfill_key_total_spend_from_spend_logs.sql is an optional
|
||||
-- scripts/db/backfill_key_total_spend_from_spend_logs.sql is an optional
|
||||
-- follow-up that rebuilds the earlier periods from LiteLLM_SpendLogs.
|
||||
--
|
||||
-- 3. No proxy restart is needed. The proxy picks up the corrected values on
|
||||
-- its next read of each key.
|
||||
--
|
||||
-- Usage:
|
||||
-- psql "$DATABASE_URL" -f db_scripts/backfill_key_total_spend.sql
|
||||
-- psql "$DATABASE_URL" -f scripts/db/backfill_key_total_spend.sql
|
||||
|
||||
UPDATE "LiteLLM_VerificationToken"
|
||||
SET total_spend = spend
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
-- Optional follow-up to db_scripts/backfill_key_total_spend.sql. Run that
|
||||
-- Optional follow-up to scripts/db/backfill_key_total_spend.sql. Run that
|
||||
-- script first; this one rebuilds earlier budget periods for the keys it
|
||||
-- can only partially fix: keys whose spend resets each period, because their own
|
||||
-- budget_duration IS NOT NULL or because their budget_id links to a
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
-- its next read of each key.
|
||||
--
|
||||
-- Usage:
|
||||
-- psql "$DATABASE_URL" -f db_scripts/backfill_key_total_spend_from_spend_logs.sql
|
||||
-- psql "$DATABASE_URL" -f scripts/db/backfill_key_total_spend_from_spend_logs.sql
|
||||
|
||||
-- Active keys whose spend resets (own budget_duration, or a linked
|
||||
-- LiteLLM_BudgetTable row with one). Rebuild from LiteLLM_SpendLogs,
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
-- The spend-log cleanup job then verifies the table is partitioned and reclaims
|
||||
-- disk by dropping expired partitions instead of deleting rows. It also
|
||||
-- pre-creates upcoming partitions on each run. To roll back, see
|
||||
-- db_scripts/unpartition_spend_logs.sql.
|
||||
-- scripts/db/unpartition_spend_logs.sql.
|
||||
|
||||
BEGIN;
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
-- Rolls back db_scripts/partition_spend_logs.sql: converts the native
|
||||
-- Rolls back scripts/db/partition_spend_logs.sql: converts the native
|
||||
-- range-partitioned "LiteLLM_SpendLogs" table back into a plain,
|
||||
-- non-partitioned table matching the default LiteLLM schema.
|
||||
--
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Install the repo's git hooks by pointing core.hooksPath at .githooks.
|
||||
# Install the repo's git hooks by pointing core.hooksPath at scripts/githooks.
|
||||
#
|
||||
# Idempotent: re-running just reaffirms the config and refreshes chmod bits.
|
||||
# Run from anywhere inside the repo.
|
||||
|
|
@ -13,7 +13,7 @@ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|||
fi
|
||||
|
||||
repo_root=$(git rev-parse --show-toplevel)
|
||||
hooks_dir="$repo_root/.githooks"
|
||||
hooks_dir="$repo_root/scripts/githooks"
|
||||
|
||||
if [ ! -d "$hooks_dir" ]; then
|
||||
echo "install_git_hooks: $hooks_dir does not exist" >&2
|
||||
|
|
@ -24,11 +24,11 @@ fi
|
|||
# filesystems sometimes lose the exec bit; this normalizes it.
|
||||
chmod +x "$hooks_dir"/* 2>/dev/null || true
|
||||
|
||||
git config core.hooksPath .githooks
|
||||
git config core.hooksPath scripts/githooks
|
||||
|
||||
cat <<EOF
|
||||
✓ Git hooks installed.
|
||||
core.hooksPath = .githooks
|
||||
core.hooksPath = scripts/githooks
|
||||
active hooks: $(ls "$hooks_dir" | tr '\n' ' ')
|
||||
|
||||
These hooks enforce Conventional Commits.
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@
|
|||
# - tests/e2e and tests/e2e_harness Python
|
||||
# -> `make lint-e2e-basedpyright` (test-linting.yml's e2e type-check step)
|
||||
# + raw HTTP client ban (test-linting.yml's check_e2e_no_raw_requests)
|
||||
# - tests/ Python, ruff-tests.toml, scripts/check_test_quality.py,
|
||||
# - tests/ Python, .github/linters/ruff-tests.toml, scripts/check_test_quality.py,
|
||||
# scripts/test_quality_gate.py
|
||||
# -> ruff over ruff-tests.toml + `make lint-test-quality` (test-linting.yml's
|
||||
# -> ruff over .github/linters/ruff-tests.toml + `make lint-test-quality` (test-linting.yml's
|
||||
# test-tree ruff and test-quality gate steps)
|
||||
# - dashboard -> prettier + eslint + lint budgets (test-linting.yml's ui job)
|
||||
# - spec_pattern -> regenerate the lazy OpenAPI snapshot and dashboard API types, fail on drift
|
||||
|
|
@ -98,7 +98,7 @@ existing_files() {
|
|||
|
||||
litellm_py_pattern='^litellm/.*\.py$'
|
||||
e2e_py_pattern='^tests/e2e(_harness)?/.*\.py$'
|
||||
test_tree_pattern='^(tests/.*\.py|ruff-tests\.toml|scripts/(check_test_quality|test_quality_gate)\.py)$'
|
||||
test_tree_pattern='^(tests/.*\.py|\.github/linters/ruff-tests\.toml|pyproject\.toml|scripts/(check_test_quality|test_quality_gate)\.py)$'
|
||||
spec_pattern='^(litellm/.*|enterprise/.*|pyproject\.toml|uv\.lock|ui/litellm-dashboard/(scripts/gen-api-types\.mjs|package\.json|package-lock\.json|src/lib/http/schema\.d\.ts))$'
|
||||
ui_prettier_pattern='^ui/litellm-dashboard/.*\.(js|jsx|ts|tsx|mjs|cjs|json|css|scss|md|mdx|yml|yaml|html)$'
|
||||
ui_eslint_pattern='^ui/litellm-dashboard/.*\.(js|jsx|ts|tsx|mjs|cjs)$'
|
||||
|
|
@ -145,7 +145,7 @@ if [ -n "$staged" ]; then
|
|||
}
|
||||
warn_skipped "Python lint (make lint)" "$litellm_py_pattern" "$litellm_py_files"
|
||||
warn_skipped "tests/e2e checks (basedpyright + raw HTTP client ban)" "$e2e_py_pattern" "$e2e_py_files"
|
||||
warn_skipped "test-tree lint (ruff-tests.toml + test-quality gate)" "$test_tree_pattern" "$test_tree_files"
|
||||
warn_skipped "test-tree lint (.github/linters/ruff-tests.toml + test-quality gate)" "$test_tree_pattern" "$test_tree_files"
|
||||
warn_skipped "dashboard lint (prettier + eslint + lint budgets)" "$ui_prettier_pattern" "$ui_prettier_changed"
|
||||
warn_skipped "dashboard API-type sync (npm run gen:api)" "$spec_pattern" "$spec_files"
|
||||
fi
|
||||
|
|
@ -299,8 +299,8 @@ if [ -n "$spec_files" ]; then
|
|||
fi
|
||||
|
||||
if [ -n "$test_tree_files" ] && [ -z "$litellm_py_files" ]; then
|
||||
echo "check: linting the test tree (ruff check --config ruff-tests.toml tests)"
|
||||
uv run --no-sync ruff check --config ruff-tests.toml tests \
|
||||
echo "check: linting the test tree (ruff check --config .github/linters/ruff-tests.toml tests)"
|
||||
uv run --no-sync ruff check --config .github/linters/ruff-tests.toml tests \
|
||||
|| { echo "✗ Test-tree ruff failed. Fix the errors above, then re-run make check." >&2; status=1; }
|
||||
echo "check: checking the test-quality gate (make lint-test-quality)"
|
||||
make lint-test-quality \
|
||||
|
|
@ -332,7 +332,7 @@ summary_item() {
|
|||
echo "check: summary"
|
||||
summary_item "Python lint (make lint)" "$litellm_py_files" "no litellm/ Python files in scope"
|
||||
summary_item "tests/e2e checks (basedpyright + raw HTTP client ban)" "$e2e_py_files" "no tests/e2e Python files in scope"
|
||||
summary_item "test-tree lint (ruff-tests.toml + test-quality gate)" "$test_tree_files" \
|
||||
summary_item "test-tree lint (.github/linters/ruff-tests.toml + test-quality gate)" "$test_tree_files" \
|
||||
"no tests/ Python files or test-tree lint inputs in scope"
|
||||
summary_item "dashboard lint (prettier + eslint + lint budgets)" "$ui_prettier_changed$ui_eslint_changed" "no dashboard files in scope"
|
||||
summary_item "dashboard API-type sync (npm run gen:api)" "$spec_files" "no litellm/, enterprise/, dependency, or generator files in scope"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Delta-vs-base gate for the strict ruff rules in ruff-strict.toml.
|
||||
"""Delta-vs-base gate for the strict ruff rules in .github/linters/ruff-strict.toml.
|
||||
|
||||
Each rule is counted across the whole tree at HEAD and at the merge-base with
|
||||
the branch this change merges into, and the gate fails only when a rule grew
|
||||
|
|
@ -37,8 +37,8 @@ from lint_base_counts import (
|
|||
)
|
||||
|
||||
REPO_ROOT: Final = Path(__file__).resolve().parent.parent
|
||||
STRICT_CONFIG: Final = REPO_ROOT / "ruff-strict.toml"
|
||||
BASE_CONFIG: Final = REPO_ROOT / "ruff.toml"
|
||||
STRICT_CONFIG: Final = REPO_ROOT / ".github/linters/ruff-strict.toml"
|
||||
BASE_CONFIG: Final = REPO_ROOT / "pyproject.toml"
|
||||
TARGET: Final = "litellm"
|
||||
|
||||
_HUNK: Final = re.compile(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@")
|
||||
|
|
@ -100,8 +100,10 @@ def base_counts(ref: str) -> dict[str, int]:
|
|||
try:
|
||||
_run(["git", "worktree", "add", "--detach", str(worktree), ref])
|
||||
shutil.copy(BASE_CONFIG, worktree / BASE_CONFIG.name)
|
||||
shutil.copy(STRICT_CONFIG, worktree / STRICT_CONFIG.name)
|
||||
items: Final = _ruff_json(worktree, worktree / STRICT_CONFIG.name)
|
||||
strict_config: Final = worktree / STRICT_CONFIG.relative_to(REPO_ROOT)
|
||||
strict_config.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy(STRICT_CONFIG, strict_config)
|
||||
items: Final = _ruff_json(worktree, strict_config)
|
||||
return dict(Counter(item["code"] for item in items))
|
||||
finally:
|
||||
_run(["git", "worktree", "remove", "--force", str(worktree)])
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ was one forgotten env line away from an 80-second crash. The base pass is a
|
|||
second basedpyright run over a detached worktree at the merge-base, under the
|
||||
same environment so import resolution matches, and scripts/lint_base_counts.py
|
||||
spares it whenever it can: the per-rule counts are cached under the repo's git
|
||||
common dir keyed by merge-base commit, ``pyrightconfig.json``, ``uv.lock``,
|
||||
common dir keyed by merge-base commit, ``pyproject.toml``, ``uv.lock``,
|
||||
the Prisma schema, and the dependency-group set, and on a disk-cache miss the
|
||||
artifact publish-lint-base-counts.yml uploaded for the merge-base is
|
||||
downloaded through the ``gh`` CLI (``--emit-counts-dir`` is the publisher's
|
||||
|
|
@ -62,6 +62,11 @@ from pathlib import Path
|
|||
from types import MappingProxyType
|
||||
from typing import Final
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
import tomllib
|
||||
else:
|
||||
import tomli as tomllib
|
||||
|
||||
from lint_base_counts import (
|
||||
Checker,
|
||||
base_counts_cached,
|
||||
|
|
@ -72,7 +77,7 @@ from lint_base_counts import (
|
|||
)
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
PYRIGHT_CONFIG = REPO_ROOT / "pyrightconfig.json"
|
||||
PYRIGHT_CONFIG = REPO_ROOT / "pyproject.toml"
|
||||
UV_LOCK = REPO_ROOT / "uv.lock"
|
||||
|
||||
# The one environment every basedpyright pass measures in. The group set is
|
||||
|
|
@ -140,12 +145,12 @@ def node_options_with_heap(base_env: Mapping[str, str]) -> str:
|
|||
return f"{base_env.get('NODE_OPTIONS', '')} {NODE_HEAP_OPTION}".strip()
|
||||
|
||||
|
||||
def typecheck_python_version() -> str | None:
|
||||
def typecheck_python_version(config_path: Path = PYRIGHT_CONFIG) -> str | None:
|
||||
"""The interpreter version to build the owned env with, read from
|
||||
pyrightconfig's `pythonVersion` so the packages installed for basedpyright
|
||||
to see always come from the same version it type-checks against."""
|
||||
try:
|
||||
config = json.loads(PYRIGHT_CONFIG.read_text())
|
||||
config: Final = tomllib.loads(config_path.read_text()).get("tool", {}).get("pyright", {})
|
||||
except (OSError, ValueError):
|
||||
return None
|
||||
version: Final = config.get("pythonVersion") if isinstance(config, dict) else None
|
||||
|
|
@ -214,6 +219,8 @@ def run_basedpyright(cwd: Path = REPO_ROOT, env_dir: Path = TYPECHECK_ENV_DIR) -
|
|||
[
|
||||
str(bin_dir / "basedpyright"),
|
||||
"--outputjson",
|
||||
"--project",
|
||||
str(cwd / PYRIGHT_CONFIG.name),
|
||||
"--pythonpath",
|
||||
str(bin_dir / "python"),
|
||||
],
|
||||
|
|
@ -250,7 +257,7 @@ def base_counts(ref: str) -> dict[str, int]:
|
|||
config is copied in so the base is judged by today's rules, and the run uses
|
||||
the head environment's basedpyright (on PATH) so imports resolve the same."""
|
||||
with _temp_worktree(ref) as worktree:
|
||||
shutil.copy(PYRIGHT_CONFIG, worktree / "pyrightconfig.json")
|
||||
shutil.copy(PYRIGHT_CONFIG, worktree / PYRIGHT_CONFIG.name)
|
||||
return count_basedpyright(run_basedpyright(worktree), root=worktree)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ make test
|
|||
|
||||
### Contributing
|
||||
|
||||
Contributions are welcome! Please read our [contributing guidelines](CONTRIBUTING.md) first.
|
||||
Contributions are welcome! Please read our [contributing guidelines](../../.github/CONTRIBUTING.md) first.
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ class LicenseChecker:
|
|||
self.authorized_packages = self._parse_authorized_packages()
|
||||
|
||||
# Initialize cache
|
||||
self.cache_file = Path("license_cache.json")
|
||||
self.cache_file = Path(__file__).with_name("dependency_license_cache.json")
|
||||
self.license_cache: Dict[str, str] = {}
|
||||
if self.cache_file.exists():
|
||||
with open(self.cache_file) as f:
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ table without a default is still free on every version.
|
|||
An index on a request-log table cannot ship as a migration at all. A plain `CREATE
|
||||
INDEX` blocks writes to the table until the build finishes, and `CREATE INDEX
|
||||
CONCURRENTLY` is refused by Postgres on a partitioned parent, which LiteLLM_SpendLogs
|
||||
is wherever the operator ran db_scripts/partition_spend_logs.sql. The migration job builds
|
||||
is wherever the operator ran scripts/db/partition_spend_logs.sql. The migration job builds
|
||||
those indexes after `migrate deploy`, concurrently and per partition, from the list in
|
||||
litellm_proxy_extras/request_log_indexes.py, so that list is where a new one goes.
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ Each subdirectory under `tests/e2e/` is one suite, scoped to an endpoint family
|
|||
- `claude_code/` - the Claude Code compatibility matrix: drives the real `claude` CLI (and HTTP probes) against a proxy for each feature x provider cell, reporting tagged-union outcomes via the `compat_result` fixture. Ships its own driver/builder/publisher, covered by `tests/e2e_harness/claude_code/`. The HTTP probes ride the shared transport (`ProxyClient.count_tokens` / `ProxyClient.messages`). The CLI-driving path stays bespoke. `pr_gate_version_resolver.py` only forwards to `cli_harness/npm_version_resolver.py`, because the Buildkite runner images still run that path and install @latest without it
|
||||
- `cli_harness/` - no tests, only the plumbing every suite that drives a real coding-agent CLI shares: `npm_version_resolver.py` picks the CLI release to install (the newest npm release at least three days old, run by CI as `python tests/e2e/cli_harness/npm_version_resolver.py <npm package>`, stdlib only), `lockdown.py` gives the CLI subprocess an allowlisted env and a fresh empty HOME so it never sees the runner's credentials or dotfiles, and `rate_limiter.py` is the cross-process token bucket per upstream that keeps parallel CLI runs under one request rate. Covered by `tests/e2e_harness/cli_harness/`. Code only one CLI suite needs stays in that suite
|
||||
- `codex/` - the Codex compatibility suite: drives the real `codex exec --json` CLI through the gateway, one class per row (the command round trip, a file edit with the patch tool, a long session that follows a chain of files one command at a time, auto-compaction, where Codex reads a random value, compacts through the gateway and is asked for the value after the file is deleted, and an MCP tool, where Codex calls the tool of `mcp_server.py`, a stdio MCP server the test hands it with `add_mcp_server`, on a first and a resumed turn, and a sub-agent, where Codex's multi-agent v2 hands a helper a random codeword as its task with shell tools off so the helper can only learn it from the task, and the helper's own final reply is read from its rollout), each row parametrized over `SETUPS` in `setups.py`, one `pytest.param` per setup (GPT on Bedrock Mantle, Bedrock Runtime, Azure OpenAI and OpenAI, plus Claude on Bedrock). A row with one setup broken by a known bug parametrizes over `with_expected_failure(<setup id>, reason=...)`, which is `SETUPS` with a strict xfail on that one setup. The `setup` fixture registers that setup's deployment under a fresh name via `/model/new` and the `codex` fixture makes a key scoped to it, and both are deleted on teardown. `harness.py` gives each test a throwaway HOME and CODEX_HOME whose `config.toml` points a custom `responses` provider at the gateway and has Codex read its model list from the gateway's `/v1/models` (`model_catalog_url` on the provider), with the sandbox on (`workspace-write`) and approvals off, and runs the CLI under `cli_harness`'s env lockdown and rate limiter. `run.py` reads the JSONL events into a `CodexRun`: the commands with their output, the patched files, the MCP tool calls with the text they returned, the last agent message as `answer`, the usage and the errors, where an error's `status_code` is what lets the suite's 5xx rerun rule catch a turn Codex gave up on. `codex exec --json` prints nothing when Codex compacts, so `rollout.py` reads the compactions from the thread's rollout file under CODEX_HOME/sessions, and a compaction is remote when its replacement history kept the provider's `compaction` item. `subagents` reads the sub-agents a thread spawned from their own rollouts, whose `session_meta` names that thread as their parent, each with its final replies and whether it forked the parent's conversation (`forked_from_id`), so the sub-agent row can reject a helper that saw the codeword in inherited history instead of its task. `exec` takes `enable` and `disable`, Codex feature names it passes as `--enable` and `--disable`. `exec` and `resume` take `compact_after_tokens`, which forces Codex's remote compaction (`compaction_trigger` items over `/v1/responses`, the path Codex otherwise only takes for OpenAI and Azure) on the gateway provider and compacts once the context grows past that many tokens. A test passes on a random value the model can only learn through the gateway, never on the model's wording or the exact command it typed. It needs `codex` on PATH and fails without it. Covered by `tests/e2e_harness/codex/`
|
||||
- `ui/` - the Admin UI browser suite: Playwright in TypeScript, driving the dashboard served by a live proxy on port 4000 (seeded postgres + mock LLM upstream, see its `run_e2e.sh`). It is a self-contained npm package with its own lockfile and does not use the Python harness, pytest markers, or the shared transport. The Python rules in this file (typed models, `Result` unions, basedpyright zero-error gate) do not apply inside it. Its only Python file, `fixtures/mock_llm_server/server.py`, is excluded from the e2e basedpyright gate via the root `pyrightconfig.json`. Its specs follow the dashboard's rule against testing what shadcn and Base UI already guarantee, in `ui/litellm-dashboard/AGENTS.md`
|
||||
- `ui/` - the Admin UI browser suite: Playwright in TypeScript, driving the dashboard served by a live proxy on port 4000 (seeded postgres + mock LLM upstream, see its `run_e2e.sh`). It is a self-contained npm package with its own lockfile and does not use the Python harness, pytest markers, or the shared transport. The Python rules in this file (typed models, `Result` unions, basedpyright zero-error gate) do not apply inside it. Its only Python file, `fixtures/mock_llm_server/server.py`, is excluded from the e2e basedpyright gate via the `[tool.pyright]` in `pyproject.toml`. Its specs follow the dashboard's rule against testing what shadcn and Base UI already guarantee, in `ui/litellm-dashboard/AGENTS.md`
|
||||
|
||||
## MCP suite: real Datadog only
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ def test_schema_migration_in_sync():
|
|||
|
||||
if not any(migrations_dir.iterdir()):
|
||||
pytest.fail(
|
||||
"No existing migrations found. Run `python litellm/ci_cd/baseline_db_migration.py`."
|
||||
"No existing migrations found. Run `python scripts/ci_cd/baseline_db.py`."
|
||||
)
|
||||
|
||||
subprocess.run(
|
||||
|
|
@ -67,7 +67,7 @@ def test_schema_migration_in_sync():
|
|||
if drift.strip():
|
||||
pytest.fail(
|
||||
"Schema changes detected that no migration captures. Run "
|
||||
"`python litellm/ci_cd/run_migration.py <migration_name>`.\n\n"
|
||||
"`python scripts/ci_cd/run_migration.py <migration_name>`.\n\n"
|
||||
+ drift
|
||||
)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ requires_db: Final = pytest.mark.skipif(
|
|||
|
||||
REPO: Final = Path(__file__).resolve().parents[4]
|
||||
PACKAGE: Final = REPO / "litellm-proxy-extras" / "litellm_proxy_extras"
|
||||
PARTITION_SCRIPT: Final = REPO / "db_scripts" / "partition_spend_logs.sql"
|
||||
PARTITION_SCRIPT: Final = REPO / "scripts" / "db" / "partition_spend_logs.sql"
|
||||
API_KEY_INDEX_MIGRATION: Final = "20260823000000_add_spend_logs_api_key_starttime_index"
|
||||
CALL_ID_INDEX_MIGRATION: Final = "20260831120001_spend_logs_litellm_call_id_index"
|
||||
API_KEY_INDEX: Final = "LiteLLM_SpendLogs_api_key_startTime_idx"
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ pytestmark: Final = pytest.mark.timeout(240)
|
|||
|
||||
REPO_ROOT: Final = Path(__file__).resolve().parents[3]
|
||||
PRISMA_DIR: Final = REPO_ROOT / "litellm-proxy-extras" / "litellm_proxy_extras"
|
||||
PARTITION_SCRIPT: Final = REPO_ROOT / "db_scripts" / "partition_spend_logs.sql"
|
||||
PARTITION_SCRIPT: Final = REPO_ROOT / "scripts" / "db" / "partition_spend_logs.sql"
|
||||
SHIPPED_MIGRATIONS: Final = tuple(sorted(path.name for path in (PRISMA_DIR / "migrations").iterdir() if path.is_dir()))
|
||||
API_KEY_INDEX_MIGRATION: Final = "20260823000000_add_spend_logs_api_key_starttime_index"
|
||||
CALL_ID_INDEX_MIGRATION: Final = "20260831120001_spend_logs_litellm_call_id_index"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"extends": "../../pyrightconfig.json",
|
||||
"extends": "../../pyproject.toml",
|
||||
"include": ["openai_sdk_compat.py"],
|
||||
"exclude": [],
|
||||
"extraPaths": ["../.."],
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from typing import Final
|
|||
import httpx
|
||||
import pytest
|
||||
|
||||
_SCRIPT: Final = Path(__file__).resolve().parents[3] / "db_scripts" / "strip_vector_store_internal_metadata.py"
|
||||
_SCRIPT: Final = Path(__file__).resolve().parents[3] / "scripts" / "db" / "strip_vector_store_internal_metadata.py"
|
||||
_spec: Final = importlib.util.spec_from_file_location("strip_vector_store_internal_metadata", _SCRIPT)
|
||||
strip_script: Final = importlib.util.module_from_spec(_spec)
|
||||
sys.modules[_spec.name] = strip_script
|
||||
|
|
|
|||
|
|
@ -621,7 +621,7 @@ DROP TABLE "LiteLLM_SpendLogs_legacy";
|
|||
|
||||
|
||||
class TestPartitionedSpendLogsDriftFilter:
|
||||
"""A doc-partitioned LiteLLM_SpendLogs (db_scripts/partition_spend_logs.sql) has a
|
||||
"""A doc-partitioned LiteLLM_SpendLogs (scripts/db/partition_spend_logs.sql) has a
|
||||
composite primary key that schema.prisma cannot express, so `prisma migrate diff`
|
||||
emits a primary-key rewrite that Postgres rejects, aborting the whole drift script
|
||||
before its legitimate statements run."""
|
||||
|
|
|
|||
|
|
@ -547,7 +547,7 @@ def test_e2e_proxy_config_opts_in_to_the_mock_params_its_suite_sends():
|
|||
for param in GATED_MOCK_PARAM_NAMES
|
||||
if f"{param}=" in source or f'"{param}"' in source
|
||||
)
|
||||
config = yaml.safe_load((repo_root / "proxy_server_config.yaml").read_text(encoding="utf-8"))
|
||||
config = yaml.safe_load(Path(__file__).with_name("proxy_server_config.yaml").read_text(encoding="utf-8"))
|
||||
general_settings = config.get("general_settings") or {}
|
||||
|
||||
assert not senders or general_settings.get(MOCK_TESTING_CONFIG_KEY) is True, (
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from typing import Final
|
|||
import pytest
|
||||
|
||||
ROOT: Final = Path(__file__).resolve().parents[2]
|
||||
CI_CD: Final = ROOT / "ci_cd"
|
||||
CI_CD: Final = ROOT / "scripts" / "ci_cd"
|
||||
|
||||
|
||||
def _load(name: str) -> ModuleType:
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ def _freshness(repo: Path, *args: str) -> subprocess.CompletedProcess[str]:
|
|||
sys.executable,
|
||||
"-c",
|
||||
"import sys; from pathlib import Path; "
|
||||
"from ci_cd.run_migration import _check_branch_freshness; "
|
||||
"from scripts.ci_cd.run_migration import _check_branch_freshness; "
|
||||
"_check_branch_freshness(Path(sys.argv[1]), sys.argv[2] if len(sys.argv) > 2 else None)",
|
||||
str(repo),
|
||||
*args,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""Tests for the repo's git hook scripts in ``.githooks/``.
|
||||
"""Tests for the repo's git hook scripts in ``scripts/githooks/``.
|
||||
|
||||
The commit-msg hook enforces Conventional Commits. It is exercised as a
|
||||
subprocess against representative valid / invalid inputs so that any future
|
||||
|
|
@ -16,7 +16,7 @@ from pathlib import Path
|
|||
import pytest
|
||||
|
||||
_REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
_HOOKS_DIR = _REPO_ROOT / ".githooks"
|
||||
_HOOKS_DIR = _REPO_ROOT / "scripts/githooks"
|
||||
_COMMIT_MSG_HOOK = _HOOKS_DIR / "commit-msg"
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
|
|
|
|||
|
|
@ -248,14 +248,14 @@ def test_diff_gate_selects_top_level_and_nested_python_files_only(tmp_path: Path
|
|||
(
|
||||
"tests/e2e_harness/top_level_module.py",
|
||||
"tests/e2e_harness/pkg/sub/nested_module.py",
|
||||
"pyrightconfig.json",
|
||||
"pyproject.toml",
|
||||
),
|
||||
)
|
||||
def test_e2e_basedpyright_gate_also_fires_on_harness_python_and_pyrightconfig(tmp_path: Path, trigger: str) -> None:
|
||||
selected = _changed_files_selected_by(
|
||||
tmp_path,
|
||||
_gate_rooted_at("tests/e2e/"),
|
||||
(trigger, "tests/e2e_harness/notes.md", "elsewhere/pyrightconfig.json", "tests/e2e_harnessish/module.py"),
|
||||
(trigger, "tests/e2e_harness/notes.md", "elsewhere/pyproject.toml", "tests/e2e_harnessish/module.py"),
|
||||
)
|
||||
assert selected == frozenset({trigger})
|
||||
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@ from tests._process_helpers import process_is_gone
|
|||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
SCRIPT = ROOT / "scripts" / "pre_commit_lint.sh"
|
||||
WHOLE_TREE_RUFF = "run --no-sync ruff check --config ruff-tests.toml tests"
|
||||
TEST_TREE_RAN = "ran: test-tree lint (ruff-tests.toml + test-quality gate)"
|
||||
WHOLE_TREE_RUFF = "run --no-sync ruff check --config .github/linters/ruff-tests.toml tests"
|
||||
TEST_TREE_RAN = "ran: test-tree lint (.github/linters/ruff-tests.toml + test-quality gate)"
|
||||
TEST_TREE_SKIPPED = (
|
||||
"skipped: test-tree lint (ruff-tests.toml + test-quality gate) "
|
||||
"skipped: test-tree lint (.github/linters/ruff-tests.toml + test-quality gate) "
|
||||
"(no tests/ Python files or test-tree lint inputs in scope)"
|
||||
)
|
||||
|
||||
|
|
@ -81,7 +81,7 @@ case "$*" in
|
|||
*orjson*)
|
||||
[ -n "${STUB_BARRIER_DIR:-}" ] && barrier_sync genapi "python dashboard"
|
||||
;;
|
||||
"run --no-sync ruff check --config ruff-tests.toml"*)
|
||||
"run --no-sync ruff check --config .github/linters/ruff-tests.toml"*)
|
||||
[ -n "${STUB_ARGS_DIR:-}" ] && echo "$*" >> "$STUB_ARGS_DIR/ruff_tests.args"
|
||||
[ "${STUB_FAIL:-}" = "tests-ruff" ] && exit 1
|
||||
;;
|
||||
|
|
@ -497,7 +497,7 @@ def test_tests_only_change_runs_the_whole_test_tree_ruff_and_the_quality_gate(tm
|
|||
@pytest.mark.parametrize(
|
||||
"changed",
|
||||
[
|
||||
"ruff-tests.toml",
|
||||
".github/linters/ruff-tests.toml",
|
||||
"scripts/check_test_quality.py",
|
||||
"scripts/test_quality_gate.py",
|
||||
"tests/e2e/test_x.py",
|
||||
|
|
@ -593,7 +593,7 @@ def test_partial_staging_warns_when_test_files_are_left_unstaged(tmp_path: Path)
|
|||
(repo / "tests" / "test_a.py").write_text("def test_a() -> None:\n assert True\n")
|
||||
proc = _run(repo, bin_dir, {"STUB_ARGS_DIR": str(args_dir)})
|
||||
assert proc.returncode == 0, proc.stdout + proc.stderr
|
||||
assert "SKIPPED test-tree lint (ruff-tests.toml + test-quality gate)" in proc.stdout
|
||||
assert "SKIPPED test-tree lint (.github/linters/ruff-tests.toml + test-quality gate)" in proc.stdout
|
||||
assert "tests/test_a.py" in proc.stdout
|
||||
assert _recorded(args_dir, "ruff_tests.args") == []
|
||||
assert _recorded(args_dir, "make.args") == []
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import pytest
|
|||
import yaml
|
||||
|
||||
_REPO_ROOT: Final = Path(__file__).resolve().parents[2]
|
||||
_SKILLS_DIR: Final = _REPO_ROOT / "skills"
|
||||
_SKILLS_DIR: Final = _REPO_ROOT / ".agents" / "skills"
|
||||
_SKILL_DIRECTORIES: Final = tuple(sorted(skill_file.parent for skill_file in _SKILLS_DIR.glob("*/SKILL.md")))
|
||||
_FULL_DEPTH_MAX_DEPTH: Final = 5
|
||||
_FULL_DEPTH_SKIPPED_NAMES: Final = frozenset(("node_modules", ".git", "dist", "build", "__pycache__"))
|
||||
|
|
|
|||
|
|
@ -50,19 +50,20 @@ def test_parse_changed_lines_handles_single_and_ranged_hunks(hunk):
|
|||
|
||||
|
||||
def _lint_section(config_name: str) -> dict:
|
||||
return tomllib.loads((_REPO_ROOT / config_name).read_text())["lint"]
|
||||
config: Final = tomllib.loads((_REPO_ROOT / config_name).read_text())
|
||||
return (config["tool"]["ruff"] if config_name == "pyproject.toml" else config)["lint"]
|
||||
|
||||
|
||||
def _base_external() -> tuple[str, ...]:
|
||||
return tuple(_lint_section("ruff.toml")["external"])
|
||||
return tuple(_lint_section("pyproject.toml")["external"])
|
||||
|
||||
|
||||
def _strict_external() -> tuple[str, ...]:
|
||||
return tuple(_lint_section("ruff-strict.toml")["external"])
|
||||
return tuple(_lint_section(".github/linters/ruff-strict.toml")["external"])
|
||||
|
||||
|
||||
def _strict_selected() -> frozenset:
|
||||
return frozenset(_lint_section("ruff-strict.toml")["select"])
|
||||
return frozenset(_lint_section(".github/linters/ruff-strict.toml")["select"])
|
||||
|
||||
|
||||
def _prefix_covered(code: str, prefixes: tuple[str, ...]) -> bool:
|
||||
|
|
@ -70,7 +71,7 @@ def _prefix_covered(code: str, prefixes: tuple[str, ...]) -> bool:
|
|||
|
||||
|
||||
def _selected_by_the_normal_config() -> frozenset:
|
||||
return frozenset(_lint_section("ruff.toml")["extend-select"]) | _ENABLED_BY_RUFF_DEFAULTS
|
||||
return frozenset(_lint_section("pyproject.toml")["extend-select"]) | _ENABLED_BY_RUFF_DEFAULTS
|
||||
|
||||
|
||||
def _ruff_binary() -> str | None:
|
||||
|
|
@ -110,7 +111,7 @@ def test_every_strict_gate_rule_is_protected_from_base_ruf100():
|
|||
)
|
||||
assert unprotected == frozenset(), (
|
||||
f"`ruff check` deletes any `# noqa` naming {sorted(unprotected)} as unused, so suppressing "
|
||||
"one of those strict-gate rules breaks lint. Cover them in ruff.toml's lint.external or "
|
||||
"one of those strict-gate rules breaks lint. Cover them in pyproject.toml's lint.external or "
|
||||
"enable them in its lint.extend-select."
|
||||
)
|
||||
|
||||
|
|
@ -132,7 +133,7 @@ def test_every_selected_rule_keeps_stale_noqa_detection_somewhere():
|
|||
assert shadowed == frozenset(), (
|
||||
f"no config's RUF100 can ever report a stale `# noqa` for {sorted(shadowed)}: every config "
|
||||
"that selects each of them also shadows it with an external entry. Narrow the external "
|
||||
"entry in ruff.toml or ruff-strict.toml."
|
||||
"entry in pyproject.toml or .github/linters/ruff-strict.toml."
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -167,7 +168,7 @@ def test_every_base_owned_rule_is_external_or_selected_in_the_strict_config(all_
|
|||
)
|
||||
assert stranded == frozenset(), (
|
||||
f"the strict gate's RUF100 reads a valid `# noqa` for {sorted(stranded)} as unused, the "
|
||||
"spurious-breach trap ruff-strict.toml's external override exists to prevent. Cover them "
|
||||
"spurious-breach trap .github/linters/ruff-strict.toml's external override exists to prevent. Cover them "
|
||||
"there."
|
||||
)
|
||||
double_booked = frozenset(
|
||||
|
|
@ -178,7 +179,7 @@ def test_every_base_owned_rule_is_external_or_selected_in_the_strict_config(all_
|
|||
assert double_booked == frozenset(), (
|
||||
f"{sorted(double_booked)} are selected by the strict config yet shadowed by its external "
|
||||
"list, so their stale suppressions can never be reported. Narrow the external entry in "
|
||||
"ruff-strict.toml."
|
||||
".github/linters/ruff-strict.toml."
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -225,8 +226,9 @@ def test_a_graduated_rule_can_still_be_suppressed_without_tripping_unused_noqa()
|
|||
|
||||
|
||||
def test_editing_either_ruff_config_or_upgrading_ruff_rekeys_the_base_counts(tmp_path: Path) -> None:
|
||||
strict: Final = tmp_path / "ruff-strict.toml"
|
||||
base: Final = tmp_path / "ruff.toml"
|
||||
strict: Final = tmp_path / ".github/linters/ruff-strict.toml"
|
||||
base: Final = tmp_path / "pyproject.toml"
|
||||
strict.parent.mkdir(parents=True)
|
||||
strict.write_text("strict v1\n")
|
||||
base.write_text("base v1\n")
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from typing import Final
|
|||
|
||||
import pytest
|
||||
|
||||
_SCRIPT: Final = Path(__file__).resolve().parents[2] / "ci_cd" / "spend_logs_guard.py"
|
||||
_SCRIPT: Final = Path(__file__).resolve().parents[2] / "scripts" / "ci_cd" / "spend_logs_guard.py"
|
||||
_SPEC: Final = importlib.util.spec_from_file_location("spend_logs_guard", _SCRIPT)
|
||||
assert _SPEC is not None and _SPEC.loader is not None
|
||||
guard: Final = importlib.util.module_from_spec(_SPEC)
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ def test_run_basedpyright_pins_import_resolution_to_the_owned_env(tmp_path):
|
|||
gate.run_basedpyright(cwd=tmp_path, env_dir=env_dir)
|
||||
argv = captured.read_text().split()
|
||||
assert argv[argv.index("--pythonpath") + 1] == str(env_dir / "bin" / "python")
|
||||
assert argv[argv.index("--project") + 1] == str(tmp_path / "pyproject.toml")
|
||||
|
||||
|
||||
def test_run_basedpyright_fails_loudly_on_a_crash_exit_code(tmp_path):
|
||||
|
|
@ -173,14 +174,26 @@ def test_env_commands_sync_the_canonical_groups_then_generate_prisma():
|
|||
|
||||
|
||||
def test_env_interpreter_pin_tracks_pyrightconfigs_python_version():
|
||||
configured = json.loads((ROOT / "pyrightconfig.json").read_text())[
|
||||
"pythonVersion"
|
||||
]
|
||||
configured = gate.tomllib.loads((ROOT / "pyproject.toml").read_text())["tool"]["pyright"]["pythonVersion"]
|
||||
assert gate.typecheck_python_version() == configured
|
||||
sync = gate.typecheck_env_commands()[0]
|
||||
assert sync[sync.index("--python") + 1] == configured
|
||||
|
||||
|
||||
def test_env_interpreter_pin_reads_project_toml(tmp_path):
|
||||
config: Final = tmp_path / "pyproject.toml"
|
||||
config.write_text('[tool.pyright]\npythonVersion = "3.12"\n')
|
||||
assert gate.typecheck_python_version(config) == "3.12"
|
||||
|
||||
|
||||
def test_env_interpreter_pin_handles_missing_empty_or_invalid_config(tmp_path):
|
||||
config: Final = tmp_path / "pyproject.toml"
|
||||
assert gate.typecheck_python_version(config) is None
|
||||
for content in ("", "[tool.pyright", "[tool.pyright]\npythonVersion = 312\n"):
|
||||
config.write_text(content)
|
||||
assert gate.typecheck_python_version(config) is None
|
||||
|
||||
|
||||
def test_ensure_env_targets_the_owned_dir_and_runs_sync_then_generate(tmp_path):
|
||||
calls = []
|
||||
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ def _enforce_bedrock_converse_models(
|
|||
|
||||
|
||||
def _read_whitelisted_bedrock_models() -> tuple[str, ...]:
|
||||
path: Final = Path(__file__).resolve().parents[2] / "whitelisted_bedrock_models.txt"
|
||||
path: Final = Path(__file__).with_name("whitelisted_bedrock_models.txt")
|
||||
return tuple(path.read_text().splitlines())
|
||||
|
||||
|
||||
|
|
@ -191,7 +191,7 @@ def test_model_info_bedrock_converse(monkeypatch):
|
|||
litellm.model_cost = litellm.get_model_cost_map(url="")
|
||||
try:
|
||||
# Load whitelist models from file
|
||||
with open("whitelisted_bedrock_models.txt", "r") as file:
|
||||
with open(Path(__file__).with_name("whitelisted_bedrock_models.txt"), "r") as file:
|
||||
whitelist_models = [line.strip() for line in file.readlines()]
|
||||
except FileNotFoundError:
|
||||
pytest.skip("whitelisted_bedrock_models.txt not found")
|
||||
|
|
@ -217,7 +217,7 @@ def test_model_info_bedrock_converse_enforcement(monkeypatch):
|
|||
|
||||
try:
|
||||
# Load whitelist models from file
|
||||
with open("whitelisted_bedrock_models.txt", "r") as file:
|
||||
with open(Path(__file__).with_name("whitelisted_bedrock_models.txt"), "r") as file:
|
||||
whitelist_models = [line.strip() for line in file.readlines()]
|
||||
|
||||
# Check for unwhitelisted models
|
||||
|
|
|
|||
1
ui/litellm-dashboard/src/lib/http/.gitattributes
vendored
Normal file
1
ui/litellm-dashboard/src/lib/http/.gitattributes
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
schema.d.ts linguist-generated
|
||||
2
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
2
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
|
|
@ -31785,7 +31785,7 @@ export interface components {
|
|||
use_spend_logs_ledger: boolean;
|
||||
/**
|
||||
* Use Spend Logs Partitioning
|
||||
* @description If True and LiteLLM_SpendLogs has been converted to a range-partitioned table (db_scripts/partition_spend_logs.sql), retention cleanup drops expired partitions instead of deleting rows, and pre-creates upcoming partitions. Default is False.
|
||||
* @description If True and LiteLLM_SpendLogs has been converted to a range-partitioned table (scripts/db/partition_spend_logs.sql), retention cleanup drops expired partitions instead of deleting rows, and pre-creates upcoming partitions. Default is False.
|
||||
*/
|
||||
use_spend_logs_partitioning?: boolean | null;
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue