feat(db): scaffold litellm-db with typed Python codegen and a Prisma executor
Some checks failed
LiteLLM Rust / rust-lint (push) Has been cancelled
LiteLLM Rust / rust-test (push) Has been cancelled
LiteLLM Rust / rust-wheel (push) Has been cancelled

Port the minimal litellm-db and litellm-db-testing structure from the sqlx POC.
Queries are declared in Rust against shared .sql files, schemars feeds
datamodel-codegen to produce frozen pydantic models and query descriptors, and
PrismaExecutor runs them through Prisma raw SQL with failures as values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yujong Lee 2026-09-27 16:22:07 -07:00
parent 9b08112ed2
commit a017b85f81
19 changed files with 764 additions and 4 deletions

View file

@ -4,7 +4,7 @@
.PHONY: help test test-unit test-unit-llms test-unit-proxy-guardrails test-unit-proxy-core test-unit-proxy-misc \
test-unit-integrations test-unit-core-utils test-unit-other test-unit-root \
test-proxy-unit-a test-proxy-unit-b test-integration test-unit-helm \
test-rust-extension rust-sqlx-prepare \
test-rust-extension rust-sqlx-prepare db-codegen db-migration-report \
info lint lint-inner lint-dev lint-checks format \
lint-basedpyright lint-e2e-basedpyright lint-basedpyright-budget-update lint-type-discipline lint-type-discipline-budget-update \
lint-ruff-budget lint-ruff-budget-update lint-budget-update lint-gate \
@ -57,6 +57,8 @@ help:
@echo " make test-unit-helm - Run helm unit tests"
@echo " make test-rust-extension - Build the Rust extension and run its public Python tests"
@echo " make rust-sqlx-prepare - Refresh litellm-rust/crates/db/.sqlx against a migrated Postgres container"
@echo " make db-codegen - Regenerate litellm/proxy/db/queries/generated from litellm-rust/crates/db"
@echo " make db-migration-report - Count Prisma call sites per migration group (GROUP=O1 lists one group)"
@echo ""
@echo "Heavy targets (check, lint) queue for LITELLM_GATE_SLOTS machine-wide"
@echo "slots (default 2; 0 disables) so parallel sessions don't thrash one machine."
@ -307,9 +309,27 @@ test-rust-extension:
LITELLM_RUST=1 LITELLM_LOCAL_MODEL_COST_MAP=True \
"$$temporary/venv/bin/python" -I -m pytest --import-mode=importlib -m requires_rust_extension tests/test_litellm_rust
DB_GENERATED := litellm/proxy/db/queries/generated
DB_SKILLS := litellm-rust/crates/db/.agents/skills
rust-sqlx-prepare:
cd litellm-rust && cargo run -p litellm-db-testing --bin sqlx-prepare
db-codegen:
@schema=$$(mktemp) && \
trap 'rm -f "$$schema"' EXIT HUP INT TERM && \
(cd litellm-rust && cargo run -q --locked -p litellm-db --features schema --bin gen-python -- ../$(DB_GENERATED)/catalog.py) > "$$schema" && \
$(UV_RUN) datamodel-codegen --input "$$schema" --input-file-type jsonschema --output $(DB_GENERATED)/models.py \
--output-model-type pydantic_v2.BaseModel --target-python-version 3.10 --use-title-as-name \
--use-union-operator --use-standard-collections --use-annotated --enable-faux-immutability \
--use-type-alias --skip-root-model --use-one-literal-as-default --disable-timestamp \
--formatters builtin --custom-file-header '# @generated by `make db-codegen` from litellm-rust/crates/db. Do not edit.' && \
$(UV_RUN) ruff check --quiet --select I --fix $(DB_GENERATED) && \
$(UV_RUN) ruff format --quiet $(DB_GENERATED)
db-migration-report:
$(UV_RUN) python $(DB_SKILLS)/db-migrate-classify/scripts/report.py $(if $(GROUP),--group $(GROUP))
test: install-test-deps
$(UV_RUN) pytest tests/

View file

@ -3439,8 +3439,12 @@ dependencies = [
name = "litellm-db"
version = "0.1.0"
dependencies = [
"rstest",
"schemars 1.2.2",
"serde",
"serde_json",
"sqlx",
"thiserror 2.0.19",
]
[[package]]
@ -5560,6 +5564,7 @@ version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a"
dependencies = [
"chrono",
"dyn-clone",
"ref-cast",
"schemars_derive",

View file

@ -0,0 +1,31 @@
{
"db_name": "PostgreSQL",
"query": "SELECT $1::text AS first, $2::int8 AS second\n",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "first",
"type_info": "Text",
"origin": "Expression"
},
{
"ordinal": 1,
"name": "second",
"type_info": "Int8",
"origin": "Expression"
}
],
"parameters": {
"Left": [
"Text",
"Int8"
]
},
"nullable": [
null,
null
]
},
"hash": "34720197d10a6ea8fd4339bd40ccd6b77a6d9d4f3df9e4ecac85056709996254"
}

View file

@ -5,6 +5,19 @@ edition.workspace = true
license.workspace = true
repository.workspace = true
[features]
schema = ["dep:schemars", "dep:serde_json", "dep:thiserror"]
[[bin]]
name = "gen-python"
required-features = ["schema"]
[dependencies]
schemars = { workspace = true, optional = true, features = ["chrono04"] }
serde.workspace = true
serde_json = { workspace = true, optional = true }
sqlx.workspace = true
thiserror = { workspace = true, optional = true }
[dev-dependencies]
rstest.workspace = true

View file

@ -0,0 +1,37 @@
use std::{
env, fs,
io::{self, Write},
process::ExitCode,
};
use litellm_db::{
CATALOG,
codegen::{catalog_module, schema_document},
};
fn main() -> ExitCode {
let Some(catalog_path) = env::args().nth(1) else {
eprintln!("usage: gen-python <catalog.py>; the JSON Schema goes to stdout");
return ExitCode::FAILURE;
};
let queries: Vec<_> = CATALOG.iter().flat_map(|domain| domain.iter()).collect();
let catalog = match catalog_module(&queries) {
Ok(catalog) => catalog,
Err(error) => {
eprintln!("{error}");
return ExitCode::FAILURE;
}
};
if let Err(error) = fs::write(&catalog_path, catalog) {
eprintln!("writing {catalog_path}: {error}");
return ExitCode::FAILURE;
}
let schema = schema_document(&queries);
match writeln!(io::stdout().lock(), "{schema:#}") {
Ok(()) => ExitCode::SUCCESS,
Err(error) => {
eprintln!("writing the JSON Schema: {error}");
ExitCode::FAILURE
}
}
}

View file

@ -0,0 +1,151 @@
use std::{borrow::Cow, path::Path};
use schemars::{JsonSchema, Schema, SchemaGenerator, generate::SchemaSettings};
use serde_json::{Value, json};
use crate::DbFailure;
const QUERY_ROOT: &str = "../../../litellm/proxy/db/queries/";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Cardinality {
Many,
Optional,
One,
Execute,
}
impl Cardinality {
fn descriptor(self) -> &'static str {
match self {
Self::Many => "ManyQuery",
Self::Optional => "OptionalQuery",
Self::One => "OneQuery",
Self::Execute => "ExecuteQuery",
}
}
}
#[derive(Clone, Copy)]
pub struct Model {
name: fn() -> Cow<'static, str>,
schema: fn(&mut SchemaGenerator) -> Schema,
}
impl Model {
pub const fn of<T: JsonSchema>() -> Self {
Self {
name: T::schema_name,
schema: SchemaGenerator::subschema_for::<T>,
}
}
fn name(self) -> Cow<'static, str> {
(self.name)()
}
}
pub struct QuerySpec {
pub name: &'static str,
pub file: &'static str,
pub cardinality: Cardinality,
pub arguments: &'static [&'static str],
pub params: Model,
pub row: Option<Model>,
}
impl QuerySpec {
fn models(&self) -> impl Iterator<Item = Model> {
[Some(self.params), self.row].into_iter().flatten()
}
}
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum CodegenError {
#[error("`{query}` reads {file}, which is not a .sql file under {QUERY_ROOT}")]
OutsideQueryRoot {
query: &'static str,
file: &'static str,
},
}
pub fn schema_document(queries: &[&QuerySpec]) -> Value {
let mut generator = SchemaSettings::draft2020_12()
.for_serialize()
.into_generator();
generator.subschema_for::<DbFailure>();
for model in queries.iter().flat_map(|query| query.models()) {
(model.schema)(&mut generator);
}
json!({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": generator.take_definitions(true),
})
}
pub fn catalog_module(queries: &[&QuerySpec]) -> Result<String, CodegenError> {
let descriptors = queries
.iter()
.map(|query| descriptor(query))
.collect::<Result<Vec<_>, _>>()?;
if descriptors.is_empty() {
return Ok(String::from(HEADER));
}
let mut models: Vec<_> = queries
.iter()
.flat_map(|query| query.models())
.map(Model::name)
.collect();
models.sort();
models.dedup();
let mut kinds: Vec<_> = queries
.iter()
.map(|query| query.cardinality.descriptor())
.collect();
kinds.sort_unstable();
kinds.dedup();
Ok(format!(
"{HEADER}\nfrom typing import Final\n\nfrom litellm.proxy.db.queries import load\nfrom litellm.proxy.db.queries.generated.models import {}\nfrom litellm.proxy.db.queries.runtime import {}\n{}",
models.join(", "),
kinds.join(", "),
descriptors.concat(),
))
}
const HEADER: &str =
"# @generated by `make db-codegen` from litellm-rust/crates/db. Do not edit.\n";
fn descriptor(query: &QuerySpec) -> Result<String, CodegenError> {
let outside = || CodegenError::OutsideQueryRoot {
query: query.name,
file: query.file,
};
let relative = query
.file
.strip_prefix("../../../")
.and_then(|path| path.strip_suffix(".sql"))
.filter(|_| query.file.starts_with(QUERY_ROOT))
.ok_or_else(outside)?;
let path = Path::new(relative);
let (Some(package), Some(stem)) = (path.parent(), path.file_name()) else {
return Err(outside());
};
let package = package.to_string_lossy().replace('/', ".");
let stem = stem.to_string_lossy();
let arguments: String = query
.arguments
.iter()
.map(|argument| format!("\"{argument}\", "))
.collect();
let row = query
.row
.map(|row| format!(" row={},\n", row.name()))
.unwrap_or_default();
Ok(format!(
"\n{constant}: Final = {kind}(\n sql=load(\"{package}\", \"{stem}\"),\n arguments=({arguments}),\n params={params},\n{row})\n",
constant = query.name.to_uppercase(),
kind = query.cardinality.descriptor(),
arguments = arguments.trim_end_matches(' '),
params = query.params.name(),
))
}

View file

@ -1,10 +1,16 @@
use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schema", schemars(extend("discriminator" = {"propertyName": "kind"})))]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum DbFailure {
#[cfg_attr(feature = "schema", schemars(title = "UniqueViolation"))]
UniqueViolation,
#[cfg_attr(feature = "schema", schemars(title = "ForeignKeyViolation"))]
ForeignKeyViolation,
#[cfg_attr(feature = "schema", schemars(title = "WriteConflict"))]
WriteConflict,
#[cfg_attr(feature = "schema", schemars(title = "NotFound"))]
NotFound,
}

View file

@ -1,3 +1,8 @@
#[cfg(feature = "schema")]
pub mod codegen;
mod failure;
mod queries;
pub use failure::DbFailure;
#[cfg(feature = "schema")]
pub use queries::CATALOG;

View file

@ -0,0 +1 @@
SELECT $1::text AS first, $2::int8 AS second

View file

@ -0,0 +1,94 @@
#[cfg_attr(
not(all(test, feature = "schema")),
expect(
unused_macros,
reason = "no query is declared yet; the first ported query uses it"
)
)]
macro_rules! declare_queries {
($($cardinality:ident $name:ident($params:ident { $($field:ident: $type:ty),* $(,)? }) $(-> $row:ident)? => $file:literal;)+) => {
$(
#[cfg_attr(
not(feature = "schema"),
expect(dead_code, reason = "built by Python until Rust executes the query")
)]
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
struct $params {
$($field: $type),*
}
declare_queries!(@check $name($params { $($field),* }) $(-> $row)? => $file);
)+
#[cfg(feature = "schema")]
pub(crate) const QUERIES: &[$crate::codegen::QuerySpec] = &[
$(declare_queries!(@spec $cardinality $name($params { $($field),* }) $(-> $row)? => $file)),+
];
};
(@check $name:ident($params:ident { $($field:ident),* }) -> $row:ident => $file:literal) => {
#[expect(dead_code, reason = "declared for the compile-time check; Python executes it until Rust takes it over")]
fn $name(params: &$params) {
let _ = sqlx::query_file_as!($row, $file $(, params.$field)*);
}
};
(@check $name:ident($params:ident { $($field:ident),* }) => $file:literal) => {
#[expect(dead_code, reason = "declared for the compile-time check; Python executes it until Rust takes it over")]
fn $name(params: &$params) {
let _ = sqlx::query_file!($file $(, params.$field)*);
}
};
(@spec Execute $name:ident($params:ident { $($field:ident),* }) -> $row:ident => $file:literal) => {
compile_error!(concat!("`", stringify!($name), "` is Execute, which returns no rows; drop `-> ", stringify!($row), "`"))
};
(@spec $cardinality:ident $name:ident($params:ident { $($field:ident),* }) -> $row:ident => $file:literal) => {
$crate::codegen::QuerySpec {
name: stringify!($name),
file: $file,
cardinality: $crate::codegen::Cardinality::$cardinality,
arguments: &[$(stringify!($field)),*],
params: $crate::codegen::Model::of::<$params>(),
row: Some($crate::codegen::Model::of::<$row>()),
}
};
(@spec Execute $name:ident($params:ident { $($field:ident),* }) => $file:literal) => {
$crate::codegen::QuerySpec {
name: stringify!($name),
file: $file,
cardinality: $crate::codegen::Cardinality::Execute,
arguments: &[$(stringify!($field)),*],
params: $crate::codegen::Model::of::<$params>(),
row: None,
}
};
}
#[cfg_attr(
not(all(test, feature = "schema")),
expect(
unused_macros,
reason = "no query is declared yet; the first ported query uses it"
)
)]
macro_rules! declare_rows {
($($(#[$attr:meta])* struct $name:ident { $($field:ident: $type:ty),* $(,)? })+) => {
$(
#[cfg_attr(
not(feature = "schema"),
expect(dead_code, reason = "row of a query Python executes until Rust takes it over")
)]
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
$(#[$attr])*
struct $name {
$($field: $type),*
}
)+
};
}
#[cfg(feature = "schema")]
pub const CATALOG: &[&[crate::codegen::QuerySpec]] = &[];
#[cfg(all(test, feature = "schema"))]
mod tests;

View file

@ -0,0 +1,37 @@
use rstest::rstest;
use crate::codegen::Cardinality;
declare_queries! {
Many echo(EchoParams { first: String, second: i64 }) -> EchoRow => "src/queries/echo.sql";
Execute echo_discarded(EchoDiscardedParams { first: String, second: i64 }) => "src/queries/echo.sql";
}
declare_rows! {
struct EchoRow {
first: Option<String>,
second: Option<i64>,
}
}
#[rstest]
#[case::rows(0, "echo", Cardinality::Many, true)]
#[case::no_rows(1, "echo_discarded", Cardinality::Execute, false)]
fn declarations_keep_parameter_order_and_cardinality(
#[case] index: usize,
#[case] name: &str,
#[case] cardinality: Cardinality,
#[case] has_row: bool,
) {
let query = &QUERIES[index];
assert_eq!(
(
query.name,
query.arguments,
query.cardinality,
query.row.is_some()
),
(name, &["first", "second"][..], cardinality, has_row),
);
}

View file

@ -0,0 +1,5 @@
from importlib.resources import files
def load(package: str, name: str) -> str:
return files(package).joinpath(f"{name}.sql").read_text(encoding="utf-8")

View file

@ -0,0 +1 @@
# @generated by `make db-codegen` from litellm-rust/crates/db. Do not edit.

View file

@ -0,0 +1,45 @@
# @generated by `make db-codegen` from litellm-rust/crates/db. Do not edit.
from __future__ import annotations
from typing import Annotated, Literal
from pydantic import BaseModel, ConfigDict, Field
from typing_extensions import TypeAliasType
class UniqueViolation(BaseModel):
model_config = ConfigDict(
frozen=True,
)
kind: Literal["unique_violation"] = "unique_violation"
class ForeignKeyViolation(BaseModel):
model_config = ConfigDict(
frozen=True,
)
kind: Literal["foreign_key_violation"] = "foreign_key_violation"
class WriteConflict(BaseModel):
model_config = ConfigDict(
frozen=True,
)
kind: Literal["write_conflict"] = "write_conflict"
class NotFound(BaseModel):
model_config = ConfigDict(
frozen=True,
)
kind: Literal["not_found"] = "not_found"
DbFailure = TypeAliasType(
"DbFailure",
Annotated[
UniqueViolation | ForeignKeyViolation | WriteConflict | NotFound,
Field(discriminator="kind"),
],
)

View file

@ -0,0 +1,56 @@
from collections.abc import Mapping, Sequence
from dataclasses import dataclass
from types import MappingProxyType
from typing import Final, Protocol
from prisma.errors import RawQueryError
from pydantic import BaseModel, JsonValue
from litellm.proxy.db.queries.generated.models import DbFailure, ForeignKeyViolation, UniqueViolation, WriteConflict
FAILURES_BY_SQLSTATE: Final[Mapping[str, DbFailure]] = MappingProxyType(
{
"23505": UniqueViolation(),
"23503": ForeignKeyViolation(),
"40001": WriteConflict(),
"40P01": WriteConflict(),
}
)
class RawClient(Protocol):
async def query_raw(self, query: str, *args: JsonValue) -> Sequence[Mapping[str, object]]: ...
async def execute_raw(self, query: str, *args: JsonValue) -> int: ...
class _RawQueryMeta(BaseModel):
code: str | None = None
def _failure(error: RawQueryError) -> DbFailure | None:
meta: Final = _RawQueryMeta.model_validate(error.meta if isinstance(error.meta, Mapping) else {})
return FAILURES_BY_SQLSTATE.get(meta.code or "")
@dataclass(frozen=True, slots=True)
class PrismaExecutor:
client: RawClient
async def query(self, sql: str, arguments: tuple[JsonValue, ...]) -> Sequence[Mapping[str, object]] | DbFailure:
try:
return await self.client.query_raw(sql, *arguments)
except RawQueryError as error:
failure: Final = _failure(error)
if failure is None:
raise
return failure
async def execute(self, sql: str, arguments: tuple[JsonValue, ...]) -> int | DbFailure:
try:
return await self.client.execute_raw(sql, *arguments)
except RawQueryError as error:
failure: Final = _failure(error)
if failure is None:
raise
return failure

View file

@ -0,0 +1,85 @@
from collections.abc import Mapping, Sequence
from dataclasses import dataclass
from typing import Final, Generic, Protocol, TypeVar
from pydantic import BaseModel, JsonValue
from litellm.proxy.db.queries.generated.models import DbFailure, NotFound
ParamsT = TypeVar("ParamsT", bound=BaseModel)
RowT = TypeVar("RowT", bound=BaseModel)
class Executor(Protocol):
async def query(self, sql: str, arguments: tuple[JsonValue, ...]) -> Sequence[Mapping[str, object]] | DbFailure: ...
async def execute(self, sql: str, arguments: tuple[JsonValue, ...]) -> int | DbFailure: ...
@dataclass(frozen=True, slots=True)
class ManyQuery(Generic[ParamsT, RowT]):
sql: str
arguments: tuple[str, ...]
params: type[ParamsT]
row: type[RowT]
@dataclass(frozen=True, slots=True)
class OptionalQuery(Generic[ParamsT, RowT]):
sql: str
arguments: tuple[str, ...]
params: type[ParamsT]
row: type[RowT]
@dataclass(frozen=True, slots=True)
class OneQuery(Generic[ParamsT, RowT]):
sql: str
arguments: tuple[str, ...]
params: type[ParamsT]
row: type[RowT]
@dataclass(frozen=True, slots=True)
class ExecuteQuery(Generic[ParamsT]):
sql: str
arguments: tuple[str, ...]
params: type[ParamsT]
def _bind(arguments: tuple[str, ...], params: BaseModel) -> tuple[JsonValue, ...]:
values: Final[dict[str, JsonValue]] = params.model_dump(mode="json")
return tuple(values[name] for name in arguments)
async def _rows(
executor: Executor, query: ManyQuery[ParamsT, RowT] | OptionalQuery[ParamsT, RowT] | OneQuery[ParamsT, RowT], params: ParamsT
) -> tuple[RowT, ...] | DbFailure:
rows: Final = await executor.query(query.sql, _bind(query.arguments, params))
if not isinstance(rows, Sequence):
return rows
return tuple(query.row.model_validate(row) for row in rows)
async def fetch_many(executor: Executor, query: ManyQuery[ParamsT, RowT], params: ParamsT) -> tuple[RowT, ...] | DbFailure:
return await _rows(executor, query, params)
async def fetch_optional(
executor: Executor, query: OptionalQuery[ParamsT, RowT], params: ParamsT
) -> RowT | None | DbFailure:
rows: Final = await _rows(executor, query, params)
if not isinstance(rows, tuple):
return rows
return rows[0] if rows else None
async def fetch_one(executor: Executor, query: OneQuery[ParamsT, RowT], params: ParamsT) -> RowT | DbFailure:
rows: Final = await _rows(executor, query, params)
if not isinstance(rows, tuple):
return rows
return rows[0] if rows else NotFound()
async def execute(executor: Executor, query: ExecuteQuery[ParamsT], params: ParamsT) -> int | DbFailure:
return await executor.execute(query.sql, _bind(query.arguments, params))

View file

@ -236,6 +236,8 @@ dev = [
"pytest-timeout==2.4.0",
"vcrpy==8.2.1",
"pytest-recording==0.13.4",
"datamodel-code-generator==0.83.0",
"ast-grep-cli==0.45.3",
]
e2e-dev = [
"playwright==1.61.0",

172
uv.lock generated
View file

@ -339,6 +339,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/9f/64/2e54428beba8d9992aa478bb8f6de9e4ecaa5f8f513bcfd567ed7fb0262d/apscheduler-3.11.2-py3-none-any.whl", hash = "sha256:ce005177f741409db4e4dd40a7431b76feb856b9dd69d57e0da49d6715bfd26d", size = 64439, upload-time = "2025-12-22T00:39:33.303Z" },
]
[[package]]
name = "argcomplete"
version = "3.7.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/87/6f/5a73f04007ca950701765949209f068da628bd11f9c2da287278ce91e0ee/argcomplete-3.7.2.tar.gz", hash = "sha256:aad8b69a0b9969edb62db0d1752354c0d50717b10e0cbb00e2a958381b9fc6b9", size = 74473, upload-time = "2026-08-06T04:53:21.662Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/46/bd/551ee6af426af84ca33e02622be722925c196608e9127d731ef17c47f06e/argcomplete-3.7.2-py3-none-any.whl", hash = "sha256:6029205678bdd9c1c728a155f5f9ecf5812393f969eef58807641a2bc2aa5b19", size = 43294, upload-time = "2026-08-06T04:53:20.246Z" },
]
[[package]]
name = "argon2-cffi"
version = "25.1.0"
@ -415,6 +424,21 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/66/00/24d6c9d60e4f2036cee7cc28fb73a7ab5e71c0171201586c9fbc383021ea/assemblyai-0.52.4-py3-none-any.whl", hash = "sha256:4d358c9cc4c6685cde296ccfa0a544417272a2b74a248e92273db7af6ea60901", size = 53900, upload-time = "2026-02-25T15:30:21.337Z" },
]
[[package]]
name = "ast-grep-cli"
version = "0.45.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/80/46/35e89cbf27fe8a866796f9cb6ae711b88e0ff67e14e2580ced649b3d6a19/ast_grep_cli-0.45.3.tar.gz", hash = "sha256:d8b527b71655eede16637311bc3ca49624b8198c48bffeb5dcf06a78332bf917", size = 283689, upload-time = "2026-08-31T02:27:07.267Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/58/8d/fded0d3cf2138b56454101e061ba8525bd47c9d022d924fd92320fad4d98/ast_grep_cli-0.45.3-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:ebc1510a6775f65a8681345dd422590fb257fe48036a7c599a5ca9f54dea8801", size = 15743557, upload-time = "2026-08-31T01:50:19.053Z" },
{ url = "https://files.pythonhosted.org/packages/30/f8/d9b41ae197bf9a41034a03d742d9bb2ca05bf4f7da741c00242019b44d3e/ast_grep_cli-0.45.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7afbe49870d8a34c0b224ac940da1fbf736617fcc81aa846884e377eddf8c1a0", size = 7870417, upload-time = "2026-08-31T01:50:21.881Z" },
{ url = "https://files.pythonhosted.org/packages/a9/d5/584cc4cf46a751f35fc086e9dca9962c05de2b37f95056f3653dc4736e19/ast_grep_cli-0.45.3-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:ced023ae9fbc7c779570cd8bcc0fa7626d9561afd60369d3c335bfce2ca565b3", size = 7752458, upload-time = "2026-08-31T01:50:23.52Z" },
{ url = "https://files.pythonhosted.org/packages/c2/20/1445bff6593d492c31904fe309c157cb16bcb73cfd30b4a55718acef5cb1/ast_grep_cli-0.45.3-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:2d151b89c6d45c2640338fe5d24b2ee0810224902d603cb271803bf7418054ea", size = 8027608, upload-time = "2026-08-31T01:50:25.179Z" },
{ url = "https://files.pythonhosted.org/packages/2e/da/03562d9c4994d756d7fa7b751f7e92f4218d96fd86bdd27a5fe7be63e4d8/ast_grep_cli-0.45.3-py3-none-win32.whl", hash = "sha256:b96c897dc44052c7b1d08ba6086d0c270fe89d37645e38f08e2d4fa15948ea3c", size = 14970295, upload-time = "2026-08-31T01:50:27.325Z" },
{ url = "https://files.pythonhosted.org/packages/b2/cc/a8d5a4039470b58a4b0a5ce9826975796ff091539849984f04a0ac98582f/ast_grep_cli-0.45.3-py3-none-win_amd64.whl", hash = "sha256:13dfb43b2028bb585f41b2f399e8b30bf0310e9bbeff2369d60a2e36accbb2e6", size = 15914271, upload-time = "2026-08-31T01:50:30.211Z" },
{ url = "https://files.pythonhosted.org/packages/ec/8e/bb893d035ee6349a07287f83b471fc54b067c2c61b34dd6c9e2072a7064f/ast_grep_cli-0.45.3-py3-none-win_arm64.whl", hash = "sha256:6c4222e17ac7a9e23c839b6eefb3a7858349e7a37a704acd451c266beadf75b4", size = 15293472, upload-time = "2026-08-31T02:27:05.122Z" },
]
[[package]]
name = "astroid"
version = "4.0.4"
@ -758,6 +782,50 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5", size = 32764, upload-time = "2024-02-18T19:09:04.156Z" },
]
[[package]]
name = "black"
version = "26.5.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "mypy-extensions" },
{ name = "packaging" },
{ name = "pathspec" },
{ name = "platformdirs" },
{ name = "pytokens" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c0/37/5628dd55bf2b34257fc7603f0fe97c40e3aaf24265f416a9c85c95ca1436/black-26.5.1.tar.gz", hash = "sha256:dd321f668053961824bcc1be1cc1df748b2d7e4fa28086b08331e577b0100a73", size = 679439, upload-time = "2026-05-18T16:53:36.107Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/be/84/b3f55026206a9e8820a91503308075ca48eadc515e436731ca01dbe043b3/black-26.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9942db8888e06943c5dde66ca0037dcff82a2a4ec1ad0ada9e0d2ee9d9823893", size = 1987719, upload-time = "2026-05-18T17:05:02.757Z" },
{ url = "https://files.pythonhosted.org/packages/c6/34/7db312c5e5783d6e76cffd9d5ac8972a32badae4c6e3288dac0eed8d3bed/black-26.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:89c93167a74d3a75dfaa38a5c7cca015537d5820dd7f17d63267d674a61cae90", size = 1810083, upload-time = "2026-05-18T17:05:04.302Z" },
{ url = "https://files.pythonhosted.org/packages/33/e2/e0101e73c2c8727634e2efcb35e2b34bd23ad70dfa673789f5773a591b21/black-26.5.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f2cd76d069cc54c71f10360744ba8983fbb616903b4304a85b734915c8e1b4", size = 1860633, upload-time = "2026-05-18T17:05:06.391Z" },
{ url = "https://files.pythonhosted.org/packages/b0/4c/e15c0c5b23cf3651035fe5addcce90e283af3548a3f91bb03d81b83106ab/black-26.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:87ed5c6f450580a2f6790bc7cbfb016dfc73bc750249762268a3695361315eef", size = 1477886, upload-time = "2026-05-18T17:05:07.96Z" },
{ url = "https://files.pythonhosted.org/packages/9f/3f/59d43ade98d2ce5c8dc34a4e46cbecd177e6d55d7d4092969c6003ccc655/black-26.5.1-cp310-cp310-win_arm64.whl", hash = "sha256:58b4bd92cf88aacf83d88479c8f9caee044b1ec55f2451a337354a7ea2590a22", size = 1277111, upload-time = "2026-05-18T17:05:09.473Z" },
{ url = "https://files.pythonhosted.org/packages/4b/96/3c3e09f09f44a37aac36b178a279cd19aa7001bd796187a7b162a294c81f/black-26.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:96ae2c733b2aabdd9986e2c5df628ff3473676cd1c5faded1ff496cf6d74083c", size = 1970639, upload-time = "2026-05-18T17:05:11.461Z" },
{ url = "https://files.pythonhosted.org/packages/83/ea/5ad117b9ee3ecd933c712bcbae610006e5b7cc9f41c526cd7ed3b6c4124c/black-26.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0e48b87e03bf109288e55cfceadcfa15ff5470aca2851a851950ed2926f450d7", size = 1792130, upload-time = "2026-05-18T17:05:12.983Z" },
{ url = "https://files.pythonhosted.org/packages/06/3a/7c448bc623fcdfa96672531beb5a616ea5e64f6975955254d7731ffb0ad9/black-26.5.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5119fa92ae61f786e8c3662fd60aece1d0a2dd5cca5d0c79417a95e7a4272a59", size = 1846134, upload-time = "2026-05-18T17:05:14.506Z" },
{ url = "https://files.pythonhosted.org/packages/a1/5b/0b39b3a5917f0657ac014ad2edb58c139553a478adfe7f817abf1622ff6e/black-26.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:30d3c14661f2792e9142cce3eeeb1cbc175b3eb5f733be0c8eeb99651e52b0c3", size = 1478883, upload-time = "2026-05-18T17:05:16.542Z" },
{ url = "https://files.pythonhosted.org/packages/4c/48/dc222692e0f95030db1bbfb6c857e76858bad09058221ea7aae815255327/black-26.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:1ef92b76f7733f282fd096ea406200b5a286c42947412b0eaff3a74e3616cefe", size = 1277776, upload-time = "2026-05-18T17:05:18.029Z" },
{ url = "https://files.pythonhosted.org/packages/24/99/7744b906703228264ef73bdd534df88ec1ef3de45c4e78f6d31b9e32d0c9/black-26.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4ad6fa01f941920f54f2bbb35f3df7673428a0ef98a0b0840c2eaef3b110efa8", size = 2012518, upload-time = "2026-05-18T17:05:20.108Z" },
{ url = "https://files.pythonhosted.org/packages/b7/c0/c5a3b1636dfd09c42534f2b3cf33506814f6d3e066fb0879ffa16c1ae860/black-26.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3915f256e75a2d7cf88d8953d37f780455dc586cc72dee059c528fe77f581217", size = 1816016, upload-time = "2026-05-18T17:05:21.84Z" },
{ url = "https://files.pythonhosted.org/packages/1f/0e/36044316b65ca471d3bb6d3703fd06fb50c6b727c3562f6a5a3153634f88/black-26.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d98d4137277c75dfb898ec8d846c4fd68ba1e9cf77f95e2865c203dc18f4c3d", size = 1884150, upload-time = "2026-05-18T17:05:23.546Z" },
{ url = "https://files.pythonhosted.org/packages/b3/33/dafc5808c2af43672912111d7c3354af1615f7e2be3bed7a878461abbe4d/black-26.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:a1dca32d9f1784af512a13410ec204c6f7f0aa9797a111c42e1c03449821c264", size = 1486825, upload-time = "2026-05-18T17:05:25.004Z" },
{ url = "https://files.pythonhosted.org/packages/82/14/b965ee6ad2a311f28bdbf692def3ee9848d2ae289dab28b27657fcee3e78/black-26.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:1037d5ac7b7b310b2632ad867ec8d0e4c4819dcdb0b820f63135da746a24e418", size = 1288646, upload-time = "2026-05-18T17:05:26.477Z" },
{ url = "https://files.pythonhosted.org/packages/3f/5c/c384363980e11e25ca6b93205949bb331fbf35f4e0dbec376dfa6326cec8/black-26.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2b36cf2ddf5566e205f6535f782a62194a184d33e175b64ae8c40b1737522be3", size = 2009020, upload-time = "2026-05-18T17:05:28.132Z" },
{ url = "https://files.pythonhosted.org/packages/0b/df/9f31c5e0babbfed77d505fc5d120beb98b21b33feaeded3924ea941fe360/black-26.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f7ea64ebfa01b50f693508fc39f875e264446d3b097088f84f203b9d09618a0", size = 1813335, upload-time = "2026-05-18T17:05:31.266Z" },
{ url = "https://files.pythonhosted.org/packages/fb/24/8e7b9a2fa61b0afd82209efe937557d180a1fa055bd7f6161eb9defc3719/black-26.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecb3e624844c798144e9bd986954e0adc81d8911a1f30f375e1252fe26e8c294", size = 1881614, upload-time = "2026-05-18T17:05:32.718Z" },
{ url = "https://files.pythonhosted.org/packages/49/ad/b4e0d9365ba8ac34f6bbab62a4b1b2dd5d618fac3fa1b8db968c844201b5/black-26.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:e1a26503279b6b310669fb0b219c39e4820b77e8189fe80f522bb511f247db0a", size = 1488925, upload-time = "2026-05-18T17:05:34.259Z" },
{ url = "https://files.pythonhosted.org/packages/a1/4b/652b859bf5df88a751c30451b09338f7fd26a77d1271c666992f836b7711/black-26.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c34b25da232ead53a6f335b76dbea124f4d152ad568b9080d6f944bc2b34b52", size = 1289883, upload-time = "2026-05-18T17:05:36.019Z" },
{ url = "https://files.pythonhosted.org/packages/a6/16/a8da8eb208c51c7f4ce74609a45d0dcc6d8a2141e45e81ee5289d1bb0d59/black-26.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e88976690a64b0af98312ca958415849cb42423423c5f2ee74af4b49a97a2168", size = 2004800, upload-time = "2026-05-18T17:05:38.182Z" },
{ url = "https://files.pythonhosted.org/packages/11/8a/a479296a19e383b70a725882a6cf3d786540601ff03cabbaaf1cce864c5a/black-26.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32d5ea7f6c8bdfa6e648326ebca1f02b0764e2a029edc6f8dce2627e19d468c3", size = 1815576, upload-time = "2026-05-18T17:05:40.309Z" },
{ url = "https://files.pythonhosted.org/packages/81/6b/cfaf3d39f25132c156a068f6b805576c9103a84086019507c70e1911ee7d/black-26.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ea8d16dc41655aa113cd64665e7219446cd7e4ff2248d7178eaa905190c86b18", size = 1877927, upload-time = "2026-05-18T17:05:42.463Z" },
{ url = "https://files.pythonhosted.org/packages/66/76/302e313964bcff7e28df329d39f84f5270095730d85ff0acc260610a0d82/black-26.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:577f21094ea469ef92ec1adaf2c9441a226d2144d01a5be2fa823cecf6543e50", size = 1511860, upload-time = "2026-05-18T17:05:43.943Z" },
{ url = "https://files.pythonhosted.org/packages/27/4e/a3827e35e0e567f9f9ee59e2a0ab979267dca98718f25547ca8c6733afd4/black-26.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:ed1a20af114c301a0269bf01163d51dbef72737fd65f850001e7cbe7f3c7abae", size = 1316632, upload-time = "2026-05-18T17:05:45.521Z" },
{ url = "https://files.pythonhosted.org/packages/94/51/f975cae76d44274cc2868dc9040ac5d58d464784610234455b4e7b19c6ef/black-26.5.1-py3-none-any.whl", hash = "sha256:4ed7f7da04046d2e488437170797d3b4a4ad83906683bcb7dfc68b673bbce5e2", size = 213693, upload-time = "2026-05-18T16:53:33.964Z" },
]
[[package]]
name = "blinker"
version = "1.9.0"
@ -1615,6 +1683,27 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686, upload-time = "2024-06-09T16:20:16.715Z" },
]
[[package]]
name = "datamodel-code-generator"
version = "0.83.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "argcomplete" },
{ name = "black", marker = "sys_platform != 'emscripten'" },
{ name = "genson" },
{ name = "inflect" },
{ name = "isort", marker = "sys_platform != 'emscripten'" },
{ name = "jinja2" },
{ name = "pydantic" },
{ name = "pyyaml" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/cf/ad/bd0c954c4c4671e586b340059139ead38570ba916a237086e245982ab14d/datamodel_code_generator-0.83.0.tar.gz", hash = "sha256:30d841e31b82095c4ef11f3dbfc99525f559a146250b9e685e2a2e4a196ca0a1", size = 3074761, upload-time = "2026-09-24T08:56:40.622Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3a/4e/4a0231a7d3d4043cd27a746a50be87bc7637463023d7db9991610cd5fe56/datamodel_code_generator-0.83.0-py3-none-any.whl", hash = "sha256:371d80be360b2a57bc48462169553af57fa30225c0264ad85ae5f0d59b9d143a", size = 751087, upload-time = "2026-09-24T08:56:38.791Z" },
]
[[package]]
name = "ddtrace"
version = "4.11.0"
@ -2229,6 +2318,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/fd/3c/6a2bf344106328fd04963664a60b9bb6496fc25df8e962fcdc1367285fb9/fsspec-2026.7.0-py3-none-any.whl", hash = "sha256:b57ddbafedfaef7018c1ecab32aa200a9d7ca26b77965f64e48b70061249d279", size = 206583, upload-time = "2026-07-28T16:34:49.538Z" },
]
[[package]]
name = "genson"
version = "1.4.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f8/53/de162dc8e03fccd9ebe59d17c7812378fe8bd2b604f6b1b94d00165140ac/genson-1.4.0.tar.gz", hash = "sha256:bc7f1c1bae87a21ca44d81149aec95a3f4468d676de9b8b08caa064f3c50b3da", size = 47908, upload-time = "2026-07-06T08:21:50.331Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/16/02/767f744ab6d4cb7761e5008acc3d534b7a0481af62563d52e391fbcb2140/genson-1.4.0-py3-none-any.whl", hash = "sha256:03bc71bbe52defde70660cc4dcd1ea1097997da5a1cbb90a9dbd3acc7c9e1b65", size = 24484, upload-time = "2026-07-06T08:21:49.046Z" },
]
[[package]]
name = "gevent"
version = "25.9.1"
@ -3637,6 +3735,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a0/d9/a1e041c5e7caa9a05c925f4bdbdfb7f006d1f74996af53467bc394c97be7/importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", size = 26514, upload-time = "2024-09-11T14:56:07.019Z" },
]
[[package]]
name = "inflect"
version = "7.5.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "more-itertools" },
{ name = "typeguard" },
]
sdist = { url = "https://files.pythonhosted.org/packages/78/c6/943357d44a21fd995723d07ccaddd78023eace03c1846049a2645d4324a3/inflect-7.5.0.tar.gz", hash = "sha256:faf19801c3742ed5a05a8ce388e0d8fe1a07f8d095c82201eb904f5d27ad571f", size = 73751, upload-time = "2024-12-28T17:11:18.897Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8a/eb/427ed2b20a38a4ee29f24dbe4ae2dafab198674fe9a85e3d6adf9e5f5f41/inflect-7.5.0-py3-none-any.whl", hash = "sha256:2aea70e5e70c35d8350b8097396ec155ffd68def678c7ff97f51aa69c1d92344", size = 35197, upload-time = "2024-12-28T17:11:15.931Z" },
]
[[package]]
name = "inflection"
version = "0.5.1"
@ -4679,8 +4790,10 @@ ci = [
{ name = "traceloop-sdk" },
]
dev = [
{ name = "ast-grep-cli" },
{ name = "basedpyright" },
{ name = "botocore-stubs" },
{ name = "datamodel-code-generator" },
{ name = "diff-cover" },
{ name = "fakeredis" },
{ name = "fastapi-offline" },
@ -4894,8 +5007,10 @@ ci = [
{ name = "traceloop-sdk", specifier = "==0.34.0" },
]
dev = [
{ name = "ast-grep-cli", specifier = "==0.45.3" },
{ name = "basedpyright", specifier = "==1.39.7" },
{ name = "botocore-stubs", specifier = "==1.43.14" },
{ name = "datamodel-code-generator", specifier = "==0.83.0" },
{ name = "diff-cover", specifier = "==9.7.2" },
{ name = "fakeredis", specifier = "==2.34.1" },
{ name = "fastapi-offline", specifier = "==1.7.6" },
@ -8228,6 +8343,45 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/07/14/49d9828443b58bd5cc80a454c91b0f867fbf36a24975d501945e6cb9e32f/python3_saml-1.16.0-py3-none-any.whl", hash = "sha256:20b97d11b04f01ee22e98f4a38242e2fea2e28fbc7fbc9bdd57cab5ac7fc2d0d", size = 76155, upload-time = "2023-10-09T10:40:34.001Z" },
]
[[package]]
name = "pytokens"
version = "0.4.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/42/24/f206113e05cb8ef51b3850e7ef88f20da6f4bf932190ceb48bd3da103e10/pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5", size = 161522, upload-time = "2026-01-30T01:02:50.393Z" },
{ url = "https://files.pythonhosted.org/packages/d4/e9/06a6bf1b90c2ed81a9c7d2544232fe5d2891d1cd480e8a1809ca354a8eb2/pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe", size = 246945, upload-time = "2026-01-30T01:02:52.399Z" },
{ url = "https://files.pythonhosted.org/packages/69/66/f6fb1007a4c3d8b682d5d65b7c1fb33257587a5f782647091e3408abe0b8/pytokens-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c", size = 259525, upload-time = "2026-01-30T01:02:53.737Z" },
{ url = "https://files.pythonhosted.org/packages/04/92/086f89b4d622a18418bac74ab5db7f68cf0c21cf7cc92de6c7b919d76c88/pytokens-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7", size = 262693, upload-time = "2026-01-30T01:02:54.871Z" },
{ url = "https://files.pythonhosted.org/packages/b4/7b/8b31c347cf94a3f900bdde750b2e9131575a61fdb620d3d3c75832262137/pytokens-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2", size = 103567, upload-time = "2026-01-30T01:02:56.414Z" },
{ url = "https://files.pythonhosted.org/packages/3d/92/790ebe03f07b57e53b10884c329b9a1a308648fc083a6d4a39a10a28c8fc/pytokens-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440", size = 160864, upload-time = "2026-01-30T01:02:57.882Z" },
{ url = "https://files.pythonhosted.org/packages/13/25/a4f555281d975bfdd1eba731450e2fe3a95870274da73fb12c40aeae7625/pytokens-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc", size = 248565, upload-time = "2026-01-30T01:02:59.912Z" },
{ url = "https://files.pythonhosted.org/packages/17/50/bc0394b4ad5b1601be22fa43652173d47e4c9efbf0044c62e9a59b747c56/pytokens-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d", size = 260824, upload-time = "2026-01-30T01:03:01.471Z" },
{ url = "https://files.pythonhosted.org/packages/4e/54/3e04f9d92a4be4fc6c80016bc396b923d2a6933ae94b5f557c939c460ee0/pytokens-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16", size = 264075, upload-time = "2026-01-30T01:03:04.143Z" },
{ url = "https://files.pythonhosted.org/packages/d1/1b/44b0326cb5470a4375f37988aea5d61b5cc52407143303015ebee94abfd6/pytokens-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6", size = 103323, upload-time = "2026-01-30T01:03:05.412Z" },
{ url = "https://files.pythonhosted.org/packages/41/5d/e44573011401fb82e9d51e97f1290ceb377800fb4eed650b96f4753b499c/pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083", size = 160663, upload-time = "2026-01-30T01:03:06.473Z" },
{ url = "https://files.pythonhosted.org/packages/f0/e6/5bbc3019f8e6f21d09c41f8b8654536117e5e211a85d89212d59cbdab381/pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1", size = 255626, upload-time = "2026-01-30T01:03:08.177Z" },
{ url = "https://files.pythonhosted.org/packages/bf/3c/2d5297d82286f6f3d92770289fd439956b201c0a4fc7e72efb9b2293758e/pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1", size = 269779, upload-time = "2026-01-30T01:03:09.756Z" },
{ url = "https://files.pythonhosted.org/packages/20/01/7436e9ad693cebda0551203e0bf28f7669976c60ad07d6402098208476de/pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9", size = 268076, upload-time = "2026-01-30T01:03:10.957Z" },
{ url = "https://files.pythonhosted.org/packages/2e/df/533c82a3c752ba13ae7ef238b7f8cdd272cf1475f03c63ac6cf3fcfb00b6/pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68", size = 103552, upload-time = "2026-01-30T01:03:12.066Z" },
{ url = "https://files.pythonhosted.org/packages/cb/dc/08b1a080372afda3cceb4f3c0a7ba2bde9d6a5241f1edb02a22a019ee147/pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b", size = 160720, upload-time = "2026-01-30T01:03:13.843Z" },
{ url = "https://files.pythonhosted.org/packages/64/0c/41ea22205da480837a700e395507e6a24425151dfb7ead73343d6e2d7ffe/pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f", size = 254204, upload-time = "2026-01-30T01:03:14.886Z" },
{ url = "https://files.pythonhosted.org/packages/e0/d2/afe5c7f8607018beb99971489dbb846508f1b8f351fcefc225fcf4b2adc0/pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1", size = 268423, upload-time = "2026-01-30T01:03:15.936Z" },
{ url = "https://files.pythonhosted.org/packages/68/d4/00ffdbd370410c04e9591da9220a68dc1693ef7499173eb3e30d06e05ed1/pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4", size = 266859, upload-time = "2026-01-30T01:03:17.458Z" },
{ url = "https://files.pythonhosted.org/packages/a7/c9/c3161313b4ca0c601eeefabd3d3b576edaa9afdefd32da97210700e47652/pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78", size = 103520, upload-time = "2026-01-30T01:03:18.652Z" },
{ url = "https://files.pythonhosted.org/packages/8f/a7/b470f672e6fc5fee0a01d9e75005a0e617e162381974213a945fcd274843/pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321", size = 160821, upload-time = "2026-01-30T01:03:19.684Z" },
{ url = "https://files.pythonhosted.org/packages/80/98/e83a36fe8d170c911f864bfded690d2542bfcfacb9c649d11a9e6eb9dc41/pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa", size = 254263, upload-time = "2026-01-30T01:03:20.834Z" },
{ url = "https://files.pythonhosted.org/packages/0f/95/70d7041273890f9f97a24234c00b746e8da86df462620194cef1d411ddeb/pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d", size = 268071, upload-time = "2026-01-30T01:03:21.888Z" },
{ url = "https://files.pythonhosted.org/packages/da/79/76e6d09ae19c99404656d7db9c35dfd20f2086f3eb6ecb496b5b31163bad/pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324", size = 271716, upload-time = "2026-01-30T01:03:23.633Z" },
{ url = "https://files.pythonhosted.org/packages/79/37/482e55fa1602e0a7ff012661d8c946bafdc05e480ea5a32f4f7e336d4aa9/pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9", size = 104539, upload-time = "2026-01-30T01:03:24.788Z" },
{ url = "https://files.pythonhosted.org/packages/30/e8/20e7db907c23f3d63b0be3b8a4fd1927f6da2395f5bcc7f72242bb963dfe/pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb", size = 168474, upload-time = "2026-01-30T01:03:26.428Z" },
{ url = "https://files.pythonhosted.org/packages/d6/81/88a95ee9fafdd8f5f3452107748fd04c24930d500b9aba9738f3ade642cc/pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3", size = 290473, upload-time = "2026-01-30T01:03:27.415Z" },
{ url = "https://files.pythonhosted.org/packages/cf/35/3aa899645e29b6375b4aed9f8d21df219e7c958c4c186b465e42ee0a06bf/pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975", size = 303485, upload-time = "2026-01-30T01:03:28.558Z" },
{ url = "https://files.pythonhosted.org/packages/52/a0/07907b6ff512674d9b201859f7d212298c44933633c946703a20c25e9d81/pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a", size = 306698, upload-time = "2026-01-30T01:03:29.653Z" },
{ url = "https://files.pythonhosted.org/packages/39/2a/cbbf9250020a4a8dd53ba83a46c097b69e5eb49dd14e708f496f548c6612/pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918", size = 116287, upload-time = "2026-01-30T01:03:30.912Z" },
{ url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" },
]
[[package]]
name = "pytz"
version = "2026.2"
@ -9923,6 +10077,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" },
]
[[package]]
name = "typeguard"
version = "4.6.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b4/de/4420db493fa8fc0856d5e5c1b159c63a323d2de2317babe36b01568928e8/typeguard-4.6.0.tar.gz", hash = "sha256:e7414f09111317de3e335de92cd397c5c0ca00b1cc1676de12e1d444a79b3f21", size = 82330, upload-time = "2026-07-26T08:40:23.207Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8f/eb/461d5f167b6f5c7d97696f397c82f82e3480e003fce3f0a1cd1dd26e2eb2/typeguard-4.6.0-py3-none-any.whl", hash = "sha256:79878165bb86f2cf5d41d159a0ff1792a796cf496882d2fe1b1c6c7049b9cdd7", size = 36884, upload-time = "2026-07-26T08:40:21.868Z" },
]
[[package]]
name = "types-awscrt"
version = "0.34.1"
@ -10132,11 +10298,11 @@ wheels = [
[[package]]
name = "typing-extensions"
version = "4.15.0"
version = "4.16.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
{ url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" },
]
[[package]]