diff --git a/PLAN.md b/PLAN.md index bb4bfa0..c4c4602 100644 --- a/PLAN.md +++ b/PLAN.md @@ -558,7 +558,7 @@ Design notes worth keeping. `ExerciseStateChanged` deliberately does **not** car **Audit retention & token purge (#251)**: `auditevent` and `authtoken` previously grew without bound and nothing ever deleted from either. `app/services/retention_service.py` adds one daily in-process sweep (`retention_task` = `sweep_once` then `asyncio.sleep`, so the "startup pass plus daily timer" the issue asks for is a single construct; created in `main`'s lifespan beside `heartbeat_task`, **not** awaited inline like `rehydrate_schedules` because a first pass over a legacy table is unbounded and would delay readiness). **Audit pruning is opt-in**: `AuditSettings.retention_days` (seeded from `AUDIT_RETENTION_DAYS`, edited at `/admin/audit`, bounded `0..3650` at the router) defaults to **0 = keep forever** — an upgrade must never silently destroy security records, and SIEM forwarding is only an archival path if an operator can enable a forwarder *first*. The knob deliberately lives on `AuditSettings` rather than `GeneralConfig`: its only reader is this async sweep, which already holds a session, so it needs no process-global cache and is **not** projected into `SiemConfig` (that snapshot exists solely for the sync `emit` path). `retention_days <= 0` short-circuits, which also neutralises a hand-edited negative row that would otherwise compute a cutoff in the *future* and delete everything; the cutoff is strict `<`, so a row landing exactly on it survives. Deletes run in `_PURGE_BATCH`-sized transactions capped at `_MAX_BATCHES` per sweep — one unbounded `DELETE` would be a giant transaction, a long lock window and a WAL spike, and the remainder is simply picked up next pass. **Token purging is unconditional** and needs no knob: `authtoken` is not a log but the live state behind an emailed single-use link, so the row is required while the link is live and worthless after — unused rows go 7 days past `expires_at`, used rows 24 hours past `used_at` (module constants), `consume()` already refuses both classes so nothing live races the delete, and the audit trail independently records issuance and acceptance. Migration `b3c4d5e6f7a8` adds the column (with a `server_default` — the singleton is lazily seeded and may already exist) plus `ix_authtoken_expires_at`; `expires_at` also carries `index=True` on the model because the test schema is built from SQLModel metadata, not Alembic. The sweep emits `audit.retention_purge` (severity `warning`) **only when something was deleted**, so a non-pruning deployment emits nothing ever and a pruning one emits at most one per day — destroying security records is itself security-relevant, and without it a purged window is indistinguishable from tampering; the event cannot eat itself because its `created_at` is newer than any cutoff by construction. It records no domain event and dispatches no WS frame. At shutdown both recurring loops are cancelled **before** `background.drain`: they are bare `create_task`s the drain never sees, and cancelling first lets a shutdown-time purge event's persist/forward children be drained rather than abandoned. Caveat to state plainly: once pruning is on, forwarding is the archive, and it is **best-effort with no outbox or retry** — a forwarder outage overlapping a purge window loses those events permanently. The sweep is a **periodic job** on the task queue (#213): procrastinate's periodic defer is unique per (task, timestamp), so exactly one replica purges each night however many are running. `exercisestatetransition`, communications and responses are deliberately out of scope (domain data with real read paths). -**Outbound proxy (#97)**: corporate egress proxying for the three outbound surfaces — the **LLM** API, the **SIEM `http`** sink, and **OIDC** discovery/JWKS. `app/services/proxy.py` is a pure `resolve(cfg, url) -> dict` returning httpx kwargs, with three modes on a `ProxyMode` StrEnum: `SYSTEM` → `{"trust_env": True}` (honour `HTTP(S)_PROXY`/`NO_PROXY`; the **default**, and exactly what httpx did implicitly before this feature, so upgrades are a no-op — a `proxy: None` key here would *override* the env proxy), `NONE` → always direct, `EXPLICIT` → route via `proxy_url` unless the target host matches the no-proxy list (standard `NO_PROXY` semantics: `*`, CIDR, domain+subdomain, exact). The **bypass decision is per target URL** because an httpx client takes a single `proxy` (no per-host `mounts`). **Caller contract**: `resolve_kwargs(url)` returns `{}` when the cache is unloaded, and every call site splats `**kwargs` last — so an unloaded cache is byte-for-byte pre-feature behaviour (each wiring test has a paired "unset" case). Routing lives in the admin-editable **`ProxySettings` singleton** (`app/models/proxy_settings.py`, row id=1, `mode` a plain string column, **no credential column**) managed by `proxy_settings_service.py`; **credentials are env-only** (`PROXY_USERNAME`/`PROXY_PASSWORD`), injected into the proxy URL's userinfo at call time by `_with_credentials()` and never persisted, returned, or logged. Like `SiemConfig`, an in-memory `ProxyConfig` cache is read by the **sync** `audit_service.emit` → SIEM path; loaded at startup by `main._load_proxy_config()`, which **must run before `register_providers()`** (OIDC bakes the resolved proxy into its Authlib `client_kwargs` at registration). A save invalidates both caches that captured the old proxy at construction: `reset_provider_cache()` (LLM adapters hold a long-lived SDK client, so the proxy is resolved once against the provider's base URL — Bedrock against the real `bedrock-runtime..amazonaws.com`, **not** the Anthropic host) and `oidc_service.reset_registration()` (Authlib's `register()` overwrites its `_registry` but `create_client()` returns the **cached** client, so re-registering alone would silently keep the old proxy — the reset rebinds a fresh `OAuth()`). All three SDKs take `http_client=`, incl. `AsyncAnthropicBedrock` (no botocore special-case). Admin API `app/routers/proxy.py` (`/api/proxy/settings|targets|test`, `require_admin`) backs the **`/admin/proxy`** page. The connectivity test takes a **target label, never a URL** — `egress_targets()` builds the label→URL map server-side from the configured LLM/SIEM/OIDC endpoints, so the route is not an SSRF oracle (CodeQL flagged the earlier free-text-URL form as critical); it returns only `ok: HTTP ` or `error: `, with the exception *message* logged server-side after `_scrub()` strips the credentials an httpx error can echo from the proxy URL. The raw-socket `syslog` sink **cannot** be proxied. Seeded from `PROXY_*` env; wired in compose + `k8s/base/configmap.yaml` (routing) + `k8s/base/secrets.yaml` (credentials). +**Outbound proxy (#97)**: corporate egress proxying for the three outbound surfaces — the **LLM** API, the **SIEM `http`** sink, and **OIDC** discovery/JWKS. `app/services/proxy.py` is a pure `resolve(cfg, url) -> dict` returning httpx kwargs, with three modes on a `ProxyMode` StrEnum: `SYSTEM` → `{"trust_env": True}` (honour `HTTP(S)_PROXY`/`NO_PROXY`; the **default**, and exactly what httpx did implicitly before this feature, so upgrades are a no-op — a `proxy: None` key here would *override* the env proxy), `NONE` → always direct, `EXPLICIT` → route via `proxy_url` unless the target host matches the no-proxy list (standard `NO_PROXY` semantics: `*`, CIDR, domain+subdomain, exact). The **bypass decision is per target URL** because an httpx client takes a single `proxy` (no per-host `mounts`). **Caller contract**: `resolve_kwargs(url)` returns `{}` when the cache is unloaded, and every call site splats `**kwargs` last — so an unloaded cache is byte-for-byte pre-feature behaviour (each wiring test has a paired "unset" case). Routing lives in the admin-editable **`ProxySettings` singleton** (`app/models/proxy_settings.py`, row id=1, `mode` a plain string column, **no credential column**) managed by `proxy_settings_service.py`; **credentials are env-only** (`PROXY_USERNAME`/`PROXY_PASSWORD`), injected into the proxy URL's userinfo at call time by `_with_credentials()` and never persisted, returned, or logged. Like `SiemConfig`, an in-memory `ProxyConfig` cache is read by the **sync** `audit_service.emit` → SIEM path; loaded at startup by `main._load_proxy_config()`, which **must run before `register_providers()`** (OIDC bakes the resolved proxy into its Authlib `client_kwargs` at registration). A save invalidates both caches that captured the old proxy at construction: `reset_provider_cache()` (LLM adapters hold a long-lived SDK client, so the proxy is resolved once against the provider's base URL — Bedrock against the real `bedrock-runtime..amazonaws.com`, **not** the Anthropic host) and `oidc_service.reset_registration()` (Authlib's `register()` overwrites its `_registry` but `create_client()` returns the **cached** client, so re-registering alone would silently keep the old proxy — the reset rebinds a fresh `OAuth()`). All three SDKs take `http_client=`, incl. `AsyncAnthropicBedrock` (no botocore special-case) — but **not the same client class**: anthropic 1.x / openai 3.x are built on **`httpx2`**, so the LLM adapters hand them an `httpx2.AsyncClient`, while OIDC (Authlib) and the SIEM `http` sink stay on `httpx`. `resolve()` is unaffected — `proxy` / `trust_env` are spelled identically in both — so the kwargs contract is shared and only the constructor differs. `httpx2` arrives with the SDK, never as a core dependency, so the adapters import it **lazily inside `_http_client()`** (the adapter modules themselves import eagerly, to register); the `llm-*` extras floor at the first httpx2-era majors (`anthropic>=1.2.0`, `openai>=3.6.0`) because an older SDK would reject an `httpx2` client. Admin API `app/routers/proxy.py` (`/api/proxy/settings|targets|test`, `require_admin`) backs the **`/admin/proxy`** page. The connectivity test takes a **target label, never a URL** — `egress_targets()` builds the label→URL map server-side from the configured LLM/SIEM/OIDC endpoints, so the route is not an SSRF oracle (CodeQL flagged the earlier free-text-URL form as critical); it returns only `ok: HTTP ` or `error: `, with the exception *message* logged server-side after `_scrub()` strips the credentials an httpx error can echo from the proxy URL. The raw-socket `syslog` sink **cannot** be proxied. Seeded from `PROXY_*` env; wired in compose + `k8s/base/configmap.yaml` (routing) + `k8s/base/secrets.yaml` (credentials). **Facilitator ownership scoping (#12)**: facilitator access to **exercises** is scoped per-exercise, not global. `require_exercise_access` (read gate) and `require_exercise_owner` (mutation gate) in `access_control.py` grant access only to: the creator (`Exercise.created_by`), a **co-facilitator** (a facilitator enrolled as an `ExerciseMember` — reuses the existing membership mechanism, no new field), or a **global admin** (`User.is_admin`, assigned out-of-band like the facilitator role — never via registration). Any other facilitator gets `403` + an `authz.denied` audit event. The same gates must run before nested-resource side effects: inject deletion, assessment reads/queueing, and suggested-inject list/approve/reject routes are explicitly covered alongside injects/responses/communications/inject-comments/ws and the mutation/lifecycle/member/export routes in `exercises.py`; `GET /exercises` is filtered to owned-or-member (admins see all). **Scenarios remain a shared library** (any facilitator lists/reads/edits/exports — intentional, they're reusable templates), and `GET /users` stays facilitator-wide (it's the member-enrolment picker). `is_admin` is a real column so it survives role-preview `model_copy` and is unspoofable. diff --git a/app/services/llm/anthropic_provider.py b/app/services/llm/anthropic_provider.py index 894539b..f2a4664 100644 --- a/app/services/llm/anthropic_provider.py +++ b/app/services/llm/anthropic_provider.py @@ -18,12 +18,12 @@ from typing import TYPE_CHECKING -import httpx - from app.services import proxy from app.services.llm.base import register_adapter if TYPE_CHECKING: + import httpx2 + from app.config import LLMProviderConfig ANTHROPIC_API_BASE = "https://api.anthropic.com" @@ -50,14 +50,24 @@ def api_base(self) -> str: return _BEDROCK_HOST.format(region=self.cfg.aws_region) return ANTHROPIC_API_BASE - def _http_client(self) -> httpx.AsyncClient | None: - """A proxied httpx client, or None to let the SDK build its own default. + def _http_client(self) -> httpx2.AsyncClient | None: + """A proxied httpx2 client, or None to let the SDK build its own default. Resolved once here, against the provider's base URL, because the SDK client is long-lived; a proxy change invalidates it via ``reset_provider_cache()``. + + ``httpx2`` (not ``httpx``) because anthropic>=1 is built on it; it ships with + the SDK, so the import is lazy like the SDK's own — core installs without an + ``llm-anthropic``/``llm-bedrock`` extra have neither, and this module imports + eagerly. The proxy kwargs (``proxy`` / ``trust_env``) are spelled the same in + both. """ proxy_kwargs = proxy.resolve_kwargs(self.api_base()) - return httpx.AsyncClient(**proxy_kwargs) if proxy_kwargs else None + if not proxy_kwargs: + return None + import httpx2 + + return httpx2.AsyncClient(**proxy_kwargs) def _get_client(self): if self._client is not None: @@ -88,7 +98,7 @@ def _get_client(self): return self._client async def aclose(self) -> None: - """Close the built SDK client and its httpx pool; a no-op when unbuilt (#269).""" + """Close the built SDK client and its HTTP pool; a no-op when unbuilt (#269).""" client, self._client = self._client, None if client is not None: await client.close() diff --git a/app/services/llm/openai_provider.py b/app/services/llm/openai_provider.py index d76655e..62b4a6d 100644 --- a/app/services/llm/openai_provider.py +++ b/app/services/llm/openai_provider.py @@ -14,12 +14,12 @@ from typing import TYPE_CHECKING -import httpx - from app.services import proxy from app.services.llm.base import register_adapter if TYPE_CHECKING: + import httpx2 + from app.config import LLMProviderConfig OPENAI_API_BASE = "https://api.openai.com" @@ -40,11 +40,21 @@ def api_base(self) -> str: Ollama's local endpoint is covered by the default no-proxy list.""" return self.cfg.base_url or OPENAI_API_BASE - def _http_client(self) -> httpx.AsyncClient | None: - """A proxied httpx client, or None to let the SDK build its own default. - Resolved once, against the base URL — the SDK client is long-lived.""" + def _http_client(self) -> httpx2.AsyncClient | None: + """A proxied httpx2 client, or None to let the SDK build its own default. + Resolved once, against the base URL — the SDK client is long-lived. + + ``httpx2`` (not ``httpx``) because openai>=3 is built on it; it ships with + the SDK, so the import is lazy like the SDK's own — core installs without + the ``llm-openai`` extra have neither, and this module imports eagerly. + The proxy kwargs (``proxy`` / ``trust_env``) are spelled the same in both. + """ proxy_kwargs = proxy.resolve_kwargs(self.api_base()) - return httpx.AsyncClient(**proxy_kwargs) if proxy_kwargs else None + if not proxy_kwargs: + return None + import httpx2 + + return httpx2.AsyncClient(**proxy_kwargs) def _get_client(self): if self._client is not None: @@ -65,7 +75,7 @@ def _get_client(self): return self._client async def aclose(self) -> None: - """Close the built SDK client and its httpx pool; a no-op when unbuilt (#269).""" + """Close the built SDK client and its HTTP pool; a no-op when unbuilt (#269).""" client, self._client = self._client, None if client is not None: await client.close() diff --git a/pyproject.toml b/pyproject.toml index 44e9b4e..e77d92f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,11 +55,14 @@ dependencies = [ # your LLM_PROVIDER: llm-anthropic (direct Anthropic), llm-bedrock (Anthropic on # AWS — the anthropic SDK's Bedrock client, pulls boto3), or llm-openai (covers # LLM_PROVIDER=openai, ollama, and gemini via the OpenAI-compatible surface). -llm-anthropic = ["anthropic>=0.120.2"] -llm-bedrock = ["anthropic[bedrock]>=0.120.2"] -llm-openai = ["openai>=2.51.0"] +# The floors are the first majors built on httpx2 (anthropic 1.x, openai 3.x): the +# adapters hand the SDK an ``httpx2.AsyncClient`` for outbound proxying (#97), which +# the httpx-era releases would reject. +llm-anthropic = ["anthropic>=1.2.0"] +llm-bedrock = ["anthropic[bedrock]>=1.2.0"] +llm-openai = ["openai>=3.6.0"] # Install every provider SDK at once (used by the container image). -llm-all = ["anthropic[bedrock]>=0.120.2", "openai>=2.51.0"] +llm-all = ["anthropic[bedrock]>=1.2.0", "openai>=3.6.0"] dev = [ "pytest>=8.0", "pytest-asyncio>=0.24", @@ -77,8 +80,8 @@ dev = [ "pytest-playwright>=0.7", # All provider SDKs so a dev env can run/smoke any LLM_PROVIDER (the test suite # itself mocks providers and needs none of these). - "anthropic[bedrock]>=0.120.2", - "openai>=2.51.0", + "anthropic[bedrock]>=1.2.0", + "openai>=3.6.0", ] [project.scripts] diff --git a/uv.lock b/uv.lock index a1ad8a2..370dee6 100644 --- a/uv.lock +++ b/uv.lock @@ -22,16 +22,16 @@ wheels = [ [[package]] name = "alembic" -version = "1.18.5" +version = "1.19.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mako" }, { name = "sqlalchemy" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1a/cc/ac0bed8e562e7407fe55c3ba85a4dce86e6dbd8730887bd1e406a6c5c18a/alembic-1.18.5.tar.gz", hash = "sha256:1554982221dd17e9a749b53902407578eb305e453f71999e8c7f0a48389fff8e", size = 2060480, upload-time = "2026-06-25T15:20:54.888Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/10/181eecdd552217d0342492bd6f3b8a96e973083379aace3d3402830ddc03/alembic-1.19.2.tar.gz", hash = "sha256:297950a8a91f6770eb82bfbce9bea55c728b90a5386c6e81430191a319d138b0", size = 2082643, upload-time = "2026-09-04T17:10:11.212Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/96/78/5fe6dc3a3a5b2f5a2a4faef8bfe336d5fa049a38884ab3172e0098160c01/alembic-1.18.5-py3-none-any.whl", hash = "sha256:06d8ba9d04558022f5395e9317de03d270f3dced49cee01f89fe7a13c26f14bc", size = 264664, upload-time = "2026-06-25T15:20:56.673Z" }, + { url = "https://files.pythonhosted.org/packages/9c/cb/9014784dcb0585977ae23b6f43331d0a33c51ac0d692506d12b4f5ee9f3b/alembic-1.19.2-py3-none-any.whl", hash = "sha256:32d553dcd577e6fe5c3c63e91468526d35e4dcecafe865d7db4e9b328fa93cb2", size = 267399, upload-time = "2026-09-04T17:10:12.796Z" }, ] [[package]] @@ -54,21 +54,20 @@ wheels = [ [[package]] name = "anthropic" -version = "0.120.2" +version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, - { name = "distro" }, { name = "docstring-parser" }, - { name = "httpx" }, + { name = "httpx2" }, { name = "jiter" }, { name = "pydantic" }, { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d7/10/4ca013cb166f226bd89e0aeb0fcaff94f45ddf716d4925ce89475d3c587b/anthropic-0.120.2.tar.gz", hash = "sha256:9722efc10c27a30a69f5338ddacdb35bc6a64297a4e4ba729bf83af873d5fb3a", size = 1008421, upload-time = "2026-07-28T17:38:26.986Z" } +sdist = { url = "https://files.pythonhosted.org/packages/75/6d/793f5cfe2cd444c43b4eeb4cb7c3cc55ebcb38929fdfe81aa1f2fced7326/anthropic-1.4.0.tar.gz", hash = "sha256:f0d017e901e48b343520b5d458f8240c283c8d850bf6d119834c622207e0a74c", size = 1150831, upload-time = "2026-09-04T22:20:31.355Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/af/0f5db57b9397a0f3b7fc204cbef143401a7cadaf982330f97f1ce3d39f34/anthropic-0.120.2-py3-none-any.whl", hash = "sha256:0f0bc2b381dc0eb41c8d886b815d79c2041cd2374f83aed36f574b6dc9c579c1", size = 1022851, upload-time = "2026-07-28T17:38:25.466Z" }, + { url = "https://files.pythonhosted.org/packages/dd/e3/34a88f0e1e854022a352d67f72ca9baa8b952d4541315088411ba2bfbc2a/anthropic-1.4.0-py3-none-any.whl", hash = "sha256:590e85bff75b713a123b03f586d68f02266b5fdc49f70dd75f721ced93a4716c", size = 1300390, upload-time = "2026-09-04T22:20:33.078Z" }, ] [package.optional-dependencies] @@ -133,15 +132,15 @@ wheels = [ [[package]] name = "authlib" -version = "1.7.2" +version = "1.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, { name = "joserfc" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/36/98/7d93f30d029643c0275dbc0bd6d5a6f670661ee6c9a94d93af7ab4887600/authlib-1.7.2.tar.gz", hash = "sha256:2cea25fefcd4e7173bdf1372c0afc265c8034b23a8cd5dcb6a9164b826c64231", size = 176511, upload-time = "2026-05-06T08:10:23.116Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/51/bc1729d3cfdc214b4935f4e886e4dd443c3065fd8e1e66423fe84b490f81/authlib-1.8.0.tar.gz", hash = "sha256:f3ecd5f1da737262fb53bf1a4d95c4ea1ad9dd509316587a255c99ab1838a4f0", size = 177759, upload-time = "2026-08-30T12:12:34.833Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/95/adcb68e20c34162e9135f370d6e31737719c2b6f94bc953fe7ed1f10fe21/authlib-1.7.2-py2.py3-none-any.whl", hash = "sha256:3e1faedc9d87e7d56a164eca3ccb6ace0d61b94abe83e92242f8dc8bba9b4a9f", size = 259548, upload-time = "2026-05-06T08:10:21.436Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c6/6f124bcfbbfb20fba22c939b4e43a06dccfc0e1ca20e5634ca573cb1e271/authlib-1.8.0-py2.py3-none-any.whl", hash = "sha256:88aebbd9af6757e14e912d5dc007ae1dc1f3e27e3b2152ce7c552ee2c3b3c121", size = 260804, upload-time = "2026-08-30T12:12:33.162Z" }, ] [[package]] @@ -475,15 +474,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, ] -[[package]] -name = "distro" -version = "1.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, -] - [[package]] name = "dnspython" version = "2.8.0" @@ -632,6 +622,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] +[[package]] +name = "httpcore2" +version = "2.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11" }, + { name = "truststore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/ad/f4f0e57345f1870f3e8cb624e058d7eca6e5a27d33bcc3311d9b618734cd/httpcore2-2.12.0.tar.gz", hash = "sha256:9293522bba0aa7c4c8e9e3f040c16575bd8868e155a77fa30c7a9085a5eae648", size = 67548, upload-time = "2026-08-18T13:22:08.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/74/d370e55600d9bcfa0d9794b0166126d49291a3d2b20c268fc98c453a4948/httpcore2-2.12.0-py3-none-any.whl", hash = "sha256:7e04258ce01013d7d615e5b910a3b27fac937d7a95038227e79652b4ba3b4ceb", size = 83074, upload-time = "2026-08-18T13:22:05.854Z" }, +] + [[package]] name = "httptools" version = "0.8.0" @@ -684,6 +687,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/98/f8/a6bc80313a9e93c888fa10534dfce2ad76ff86911b6f485777ce6de6a073/httpx_ws-0.9.0-py3-none-any.whl", hash = "sha256:71640d2fb1bf9a225775015b33cd755cfd4c5f7e21c885192fe3adc4c387b248", size = 15759, upload-time = "2026-03-28T14:11:11.887Z" }, ] +[[package]] +name = "httpx2" +version = "2.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "sys_platform != 'emscripten'" }, + { name = "httpcore2", marker = "sys_platform != 'emscripten'" }, + { name = "httpx2-jsfetch", marker = "sys_platform == 'emscripten'" }, + { name = "idna" }, + { name = "truststore", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7f/f8/579a8b51e42e38ee32647df9f08aa25643ae788e275cc625b199829c4671/httpx2-2.12.0.tar.gz", hash = "sha256:7631fe9887a8a2275f4a2540e053aa670fcc50742864a9ae7c66e609fdcf12cf", size = 100040, upload-time = "2026-08-18T13:22:09.086Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/95/411ba65569158e862368917aaf56597f3e5fa3b91b0502919638465a08f3/httpx2-2.12.0-py3-none-any.whl", hash = "sha256:cc8b6eecb8661c146b8f89a60e97456ee086e91a784ed31ac450c3a9e613dd36", size = 95427, upload-time = "2026-08-18T13:22:06.834Z" }, +] + +[[package]] +name = "httpx2-jsfetch" +version = "1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/c4/0e5636363151a2a1795e0a77617168b9ca438e1748ec05fc9b5687f93d64/httpx2_jsfetch-1.0.tar.gz", hash = "sha256:70a0e3eabfef7cce5ad9c629f7d01ca05e418f586646f4ddf14782e4c1454c60", size = 6872, upload-time = "2026-08-07T00:13:07.492Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/43/832f631d32e4f1211caa2ba368317739fe71f0b8530e4c9d15dc454bac2a/httpx2_jsfetch-1.0-py3-none-any.whl", hash = "sha256:cb916b707601e69a07721aabc8f3f6659be3a6893bc1ff5c6f9e02241df2da32", size = 6382, upload-time = "2026-08-07T00:13:06.567Z" }, +] + [[package]] name = "iceberg-ttx" version = "0.1.0b3" @@ -747,10 +775,10 @@ requires-dist = [ { name = "aiofiles", specifier = ">=23.0" }, { name = "aiosmtplib", specifier = ">=3.0" }, { name = "alembic", specifier = ">=1.13" }, - { name = "anthropic", marker = "extra == 'llm-anthropic'", specifier = ">=0.120.2" }, - { name = "anthropic", extras = ["bedrock"], marker = "extra == 'dev'", specifier = ">=0.120.2" }, - { name = "anthropic", extras = ["bedrock"], marker = "extra == 'llm-all'", specifier = ">=0.120.2" }, - { name = "anthropic", extras = ["bedrock"], marker = "extra == 'llm-bedrock'", specifier = ">=0.120.2" }, + { name = "anthropic", marker = "extra == 'llm-anthropic'", specifier = ">=1.2.0" }, + { name = "anthropic", extras = ["bedrock"], marker = "extra == 'dev'", specifier = ">=1.2.0" }, + { name = "anthropic", extras = ["bedrock"], marker = "extra == 'llm-all'", specifier = ">=1.2.0" }, + { name = "anthropic", extras = ["bedrock"], marker = "extra == 'llm-bedrock'", specifier = ">=1.2.0" }, { name = "asyncpg", specifier = ">=0.29" }, { name = "authlib", specifier = ">=1.6" }, { name = "bandit", marker = "extra == 'dev'", specifier = ">=1.7" }, @@ -762,9 +790,9 @@ requires-dist = [ { name = "httpx-ws", marker = "extra == 'dev'", specifier = ">=0.6" }, { name = "itsdangerous", specifier = ">=2.0" }, { name = "jinja2", specifier = ">=3.1" }, - { name = "openai", marker = "extra == 'dev'", specifier = ">=2.51.0" }, - { name = "openai", marker = "extra == 'llm-all'", specifier = ">=2.51.0" }, - { name = "openai", marker = "extra == 'llm-openai'", specifier = ">=2.51.0" }, + { name = "openai", marker = "extra == 'dev'", specifier = ">=3.6.0" }, + { name = "openai", marker = "extra == 'llm-all'", specifier = ">=3.6.0" }, + { name = "openai", marker = "extra == 'llm-openai'", specifier = ">=3.6.0" }, { name = "pip-audit", marker = "extra == 'dev'", specifier = ">=2.7" }, { name = "playwright", marker = "extra == 'dev'", specifier = ">=1.52" }, { name = "procrastinate", specifier = ">=3.9" }, @@ -998,21 +1026,19 @@ wheels = [ [[package]] name = "openai" -version = "2.51.0" +version = "3.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, - { name = "distro" }, - { name = "httpx" }, + { name = "httpx2" }, { name = "jiter" }, { name = "pydantic" }, { name = "sniffio" }, - { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/75/d8/06fda9685e47d9a8fc177ef57f8af75207938fad49a45ce23bfa7b6a2a5c/openai-2.51.0.tar.gz", hash = "sha256:4d61287c42eba54086d09346e709cbf7f8cec51822efce9cc399450b9385fba5", size = 1083410, upload-time = "2026-07-30T17:43:26.507Z" } +sdist = { url = "https://files.pythonhosted.org/packages/14/77/3508ca0f04f42124bb89ab6e3e853c144660b9a680165beb49cd48634bce/openai-3.9.0.tar.gz", hash = "sha256:fea0fe63d04a27e9da588f673e291183fbf4813767e9df743e351bb548afc60b", size = 1481863, upload-time = "2026-09-08T16:43:15.033Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/57/6f/c49e21245ad4865e886f6885e95e998bc024335b392c202bd571639e9d50/openai-2.51.0-py3-none-any.whl", hash = "sha256:91db13ce59a670fddc820a6983989650095e43e3acac09288bceb69356a8904e", size = 1652344, upload-time = "2026-07-30T17:43:24.225Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ec/59307303cdb38d89f73555245cec54ab5fe34628c39b78b24ea48d0bbed8/openai-3.9.0-py3-none-any.whl", hash = "sha256:8e0e9a1abea664507f03fdad204579092d0fdf1aff54e8335c2098041d4c0345", size = 1741583, upload-time = "2026-09-08T16:43:12.369Z" }, ] [[package]] @@ -1099,21 +1125,21 @@ wheels = [ [[package]] name = "playwright" -version = "1.61.0" +version = "1.62.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "greenlet" }, { name = "pyee" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/44/ee/31e4e0db36588b817a10b299a0285082545fde7d36543c2abe498bb3d61a/playwright-1.61.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:ff138c3a604f69911e9d42fd036e55c2a171e5616edf04c1e7f60a2a285540b0", size = 43421877, upload-time = "2026-06-29T10:32:48.428Z" }, - { url = "https://files.pythonhosted.org/packages/42/35/71395dd3ecc798965be4a3ef8c443217d4abca168e7cb34536304f9489e6/playwright-1.61.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:009588c2a7e499bc5a8b425b61fa65490968bbda9cd69e0cf2cff10f8304659a", size = 42205016, upload-time = "2026-06-29T10:32:52.104Z" }, - { url = "https://files.pythonhosted.org/packages/f4/44/323164cf5cd1647bdefce76ffce27651aadb959d089b48f53ea40918276e/playwright-1.61.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:9f7de4536088d12037c13a52b7ea34b59270b78926bb56935070597ffac6b1af", size = 43421884, upload-time = "2026-06-29T10:32:55.773Z" }, - { url = "https://files.pythonhosted.org/packages/ab/f8/a35bf179e4ba2522c1893635094a64e407572547bd61528820fc0abc87fe/playwright-1.61.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:54f3b39f6eab832e33458c1dd7da0b5682aedab3b09ae731b5c59fa12fd2024e", size = 47421381, upload-time = "2026-06-29T10:32:59.903Z" }, - { url = "https://files.pythonhosted.org/packages/b7/eb/e3f922348ec17c315f98c463f72faa1181a1c3de0bfe31a8d2edf6561723/playwright-1.61.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93454322ade8c11d5d6c211bfd91bdfb9ffb4810e3e026371bcbc4bec1b7ee4c", size = 47120545, upload-time = "2026-06-29T10:33:03.574Z" }, - { url = "https://files.pythonhosted.org/packages/c2/a6/5be4e52b40a9c0c8a073e7c5b0785c05cf5a9ea8f8a7b5b260e32d970342/playwright-1.61.0-py3-none-win32.whl", hash = "sha256:372d55a6f1248fa1dd47599686980cb8fb5bbe6fcda59eab793eb657c11d8a9b", size = 37844841, upload-time = "2026-06-29T10:33:07.361Z" }, - { url = "https://files.pythonhosted.org/packages/6c/fd/2b78036e5fbe9d5f5645bbe08a1eac7160c51243c0093963edbcf67c35d9/playwright-1.61.0-py3-none-win_amd64.whl", hash = "sha256:35c6cc4589a5d00964a59d7b3e59641e0aac0c02f15479a7af77d20f6bc79597", size = 37844846, upload-time = "2026-06-29T10:33:10.637Z" }, - { url = "https://files.pythonhosted.org/packages/27/0d/1b0f3c4ee4eb0514bc805b5c2f9a223e5b6de4f11a926f5235d51d0fc81b/playwright-1.61.0-py3-none-win_arm64.whl", hash = "sha256:e9fcbffcf557a8620fdedd92491eb59a32d18e23d6f3b4f6214b952be324fe51", size = 33955127, upload-time = "2026-06-29T10:33:14.008Z" }, + { url = "https://files.pythonhosted.org/packages/6c/5b/ca2abcf3aa69f9fb510215e3064f30b57fe57657c8d04ede45bb966d5606/playwright-1.62.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:d8da938f3748841a8754f2e1f0216902c1c8f8ae3720de8b32ccf8e6913a7c4f", size = 43732091, upload-time = "2026-07-31T17:00:44.178Z" }, + { url = "https://files.pythonhosted.org/packages/af/1a/0bfbe9904350961f4dbb713f04342e40d548c5fc26c8157bd13617c81492/playwright-1.62.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:db755ab27db21a04186f1fe8169888e42356086e439b1059b923ef417f0b6034", size = 42510842, upload-time = "2026-07-31T17:00:48.596Z" }, + { url = "https://files.pythonhosted.org/packages/66/dc/c0486b407ad0699a250f6bbe3066fca95344009a99ca66e88ca175c69dc1/playwright-1.62.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:5108bd5b3e87169ddf269feee097da5893af7f8aea4634dfc840518d64c1f1da", size = 43732093, upload-time = "2026-07-31T17:00:52.218Z" }, + { url = "https://files.pythonhosted.org/packages/43/6b/b24aebc2b04bffcb342bccf96e287c78b363e1615bed5cea97500cc0393a/playwright-1.62.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:ba33bae6a13b3d9d354c751cb618af357d20fe1d57767cbcce52079bbef17ad3", size = 47748926, upload-time = "2026-07-31T17:00:56.438Z" }, + { url = "https://files.pythonhosted.org/packages/36/43/b4b18bdc87e1949568fffdcde3ff9a0456266b2d0c6d4432cc34d89ea6eb/playwright-1.62.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db2d76613a57ad844362ce42f7d0c2fa26b19a4f7a46d4f76b891c631e6e5aff", size = 47441423, upload-time = "2026-07-31T17:01:00.404Z" }, + { url = "https://files.pythonhosted.org/packages/81/22/af5d926fc2c32a339eec00a443644bc40ab9db1dd2dd9017873c59773c0c/playwright-1.62.0-py3-none-win32.whl", hash = "sha256:e5614fa89355d7081457680324bb219f79f69c423c5cb6fa250e30b0d8aebf1c", size = 38164450, upload-time = "2026-07-31T17:01:04.187Z" }, + { url = "https://files.pythonhosted.org/packages/2b/a9/4160c1033c07af98bf841ad079457dd78408a5ee0dd56cbfe50b8b6a1c22/playwright-1.62.0-py3-none-win_amd64.whl", hash = "sha256:92c0d98ed04eb35af557b709875edba415b1f548bdb22ddb5bb3e1e6c835c2f1", size = 38164458, upload-time = "2026-07-31T17:01:08.459Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ec/06b55d619a7082a766aa04f2c6bb31435c87f02930087d8a0517119408fa/playwright-1.62.0-py3-none-win_arm64.whl", hash = "sha256:ea8d3055aa9d5a9f1832ac82517bd8b42c78fac7ebcbebb0107116735c8cb6a1", size = 34208868, upload-time = "2026-07-31T17:01:11.818Z" }, ] [[package]] @@ -1272,16 +1298,16 @@ wheels = [ [[package]] name = "pydantic-settings" -version = "2.14.2" +version = "2.15.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5c/b5/8f48e906c3e0205276e8bd8cb7512217a87b2685304d64be27cad5b3019f/pydantic_settings-2.14.2.tar.gz", hash = "sha256:c19dd64b19097f1de80184f0cc7b0272a13ae6e170cbf240a3e27e381ed14a5f", size = 237700, upload-time = "2026-06-19T13:44:56.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/68/ca/31c57507b13119d7d3cfa1576dad2911a4861e3be07b579395f4e9d393f9/pydantic_settings-2.15.0.tar.gz", hash = "sha256:694b793e84f766ba76a90ebdefc01d0a9a045dab0382bee70393da93712ad117", size = 261253, upload-time = "2026-08-07T09:24:57.419Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/c1/6e422f34e569cf8e18df68d1939c81c099d2b61e4f7d9621c8a77560799c/pydantic_settings-2.14.2-py3-none-any.whl", hash = "sha256:a20c97b37910b6550d5ea50fbcc2d4187defe58cd57070b73863d069419c9440", size = 61715, upload-time = "2026-06-19T13:44:55.02Z" }, + { url = "https://files.pythonhosted.org/packages/30/a4/2bffa9f8e804325a09867f0e9d30795c80ea9f8d62560bd1b6ad6220eb2f/pydantic_settings-2.15.0-py3-none-any.whl", hash = "sha256:0ba092c291c94baceb5eff768aa0d56400a457585bc0175925a5a5510303da42", size = 69413, upload-time = "2026-08-07T09:24:55.839Z" }, ] [[package]] @@ -1388,7 +1414,7 @@ wheels = [ [[package]] name = "pytest-playwright" -version = "0.8.0" +version = "0.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "playwright" }, @@ -1396,9 +1422,9 @@ dependencies = [ { name = "pytest-base-url" }, { name = "python-slugify" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/ef/172eb8e23c80491fc72f1401c72f9305663873649351306a38b18406b0c9/pytest_playwright-0.8.0.tar.gz", hash = "sha256:7888d4a2443160c82e0c506c437076679f86b36d1910427250d90fbf1843a981", size = 17132, upload-time = "2026-05-18T10:16:15.919Z" } +sdist = { url = "https://files.pythonhosted.org/packages/26/c4/31cab1a9edfa35546950bb30c033dbfe4d4f3a216f4bafb0537a1469a70c/pytest_playwright-0.9.0.tar.gz", hash = "sha256:bd44daa852b0fb8b0e55a2f88b727507dedda0e350bea5d09420647252f2454b", size = 17899, upload-time = "2026-08-10T10:20:16.407Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/71/1c545fac6a9054b52b3771238fb2dc6e8f1d0ccec116e1c7786ec191887c/pytest_playwright-0.8.0-py3-none-any.whl", hash = "sha256:856aae6efd4bc055f2ef229c647768760bcaad5cd3a5983c314ac260a974a933", size = 17143, upload-time = "2026-05-18T10:16:18.226Z" }, + { url = "https://files.pythonhosted.org/packages/ab/70/72ff5c3833f0faa9b5fb23f24c0a606f070ad33bb61b802ba38dfe125e7d/pytest_playwright-0.9.0-py3-none-any.whl", hash = "sha256:9d9dc74e335c647944cecfffa706cc9e6e4bf4c25e84592c128b584d494d4471", size = 17915, upload-time = "2026-08-10T10:20:18.253Z" }, ] [[package]] @@ -1525,27 +1551,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.16.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/70/25/7113f6d5498888c5fb7db34081cba7d5971c4cb1bfb26819966eee68f003/ruff-0.16.1.tar.gz", hash = "sha256:fedad7c801dabd3fb9741d76aca39246e6ddd9ca446a015875207bf19f1e6bc7", size = 4877500, upload-time = "2026-07-30T19:37:01.379Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/bd/694da69368e0973de65df2ddc73ab18d43c469d5963d9b150911de6bc513/ruff-0.16.1-py3-none-linux_armv6l.whl", hash = "sha256:58edb313b88f0c5460a26adf5f39a37a3be789494a15e3e411e35fa78b89f9a0", size = 10839126, upload-time = "2026-07-30T19:36:13.697Z" }, - { url = "https://files.pythonhosted.org/packages/3f/f0/b626e5d5bd0dd9576263658ef12885e2288afd1029a48e26ffed65ec1ac1/ruff-0.16.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fde5a99e2f97479af66edd6622c6d5a2a7592c77cf4153d9e4428f5eeb55b60c", size = 11070253, upload-time = "2026-07-30T19:36:17.14Z" }, - { url = "https://files.pythonhosted.org/packages/83/63/f40acfb6b35b88623e71684942b552c3edd96035f5d98f313815f7b277de/ruff-0.16.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e0d4c20532fca4f7fa609369161d968dd28f65d83dabbd61d8e9c7edbf7001f6", size = 10561425, upload-time = "2026-07-30T19:36:20.04Z" }, - { url = "https://files.pythonhosted.org/packages/aa/dd/14ec0e9c2b4d315547dd38765004b4863e354e1b52cb308272215d9f6f6d/ruff-0.16.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30affbcedf59ad5703d9c91f82266e02b47739f797e1a7b6e158e5526a6dae38", size = 10948879, upload-time = "2026-07-30T19:36:22.476Z" }, - { url = "https://files.pythonhosted.org/packages/33/e9/9d870cbae575030fdef595f04b4b97573c525b5497cce4f4498cf2f85446/ruff-0.16.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:24e9c631573cbca9d20f1283f8f479b2afa4a8503504822bd71a293889f16743", size = 10643691, upload-time = "2026-07-30T19:36:24.914Z" }, - { url = "https://files.pythonhosted.org/packages/c4/09/12743d544e2173f53ecd27217c65f90d2bc0f8424a66a60339e56bbc0457/ruff-0.16.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b41bdd48fb420987a9b5212e4957c26ad4abce401fa9ea9d4d85843727945f4f", size = 11435354, upload-time = "2026-07-30T19:36:28.447Z" }, - { url = "https://files.pythonhosted.org/packages/7f/89/a1652b2daee52083c9554a6333b678a8b01d0400f976827bb87857f9449a/ruff-0.16.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b0d1e1393b7648079e13669de1c1f4fde06d4583e84d8fd5c1551e0a77a2aa75", size = 12259033, upload-time = "2026-07-30T19:36:31.326Z" }, - { url = "https://files.pythonhosted.org/packages/16/96/ecdcb8c54ee7b123b487f807eb014e6e019155a0b81dfb669acd52f28ce3/ruff-0.16.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07bf434b1c95f4e093be4532068ef4fcf00924eb2ade8796075980902d6fd54a", size = 11667981, upload-time = "2026-07-30T19:36:34.394Z" }, - { url = "https://files.pythonhosted.org/packages/cd/90/c52e12e0d862e9572f2a33aa227409143520abe53111e9a6babbac7b4af8/ruff-0.16.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39897739f112253ee4fdd2e8aa9a4f9ded99fb2be367d5f31dfa4ded6025584c", size = 11468183, upload-time = "2026-07-30T19:36:37.339Z" }, - { url = "https://files.pythonhosted.org/packages/2c/6b/4ffb7ad1d83eb16cf8cbb3c8815d3f11c88460fd162d4b372a2059be1c2a/ruff-0.16.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:82ae3c0c0d74daf17b968a10b7b3bb3ef297ab7de0c1f749646b25e690ccb150", size = 11470071, upload-time = "2026-07-30T19:36:39.91Z" }, - { url = "https://files.pythonhosted.org/packages/9c/72/32ae7db4c0b5e32ab611787caa19d1546800676d79f7483b7100a3561bf4/ruff-0.16.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4d5f2ed10f8242d83fc08d521301089364e3375375705356f20c0e31606ef3ef", size = 10919503, upload-time = "2026-07-30T19:36:42.65Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ca/3d901ba6ad6fc38da39c3448fc6c59ac945679293a17c3ceb6d6c1cba13e/ruff-0.16.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a4665b309891f83f3e3c25447935f1213e9abbd4b5640af7a1f2def9f8d413c1", size = 10649861, upload-time = "2026-07-30T19:36:45.18Z" }, - { url = "https://files.pythonhosted.org/packages/92/79/894ef1ced26552d5f8c9cf6d85b0687840e1128c55aeab7b9c2d54a0d880/ruff-0.16.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:26e9ca5c9bc3971f20d3cf18a957f52ffd6a5f6564ff15c4912a144dcac22494", size = 11148137, upload-time = "2026-07-30T19:36:47.936Z" }, - { url = "https://files.pythonhosted.org/packages/2d/69/3609a09fa1cb46cc28b762363e440a354204e5dff01bd0c8d7437874d6b9/ruff-0.16.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:67e1e1e3fa4f0c82f0e36d4cd61e661f6e7a6196cb1aa92fe0828fa7b8f257cd", size = 11559211, upload-time = "2026-07-30T19:36:50.448Z" }, - { url = "https://files.pythonhosted.org/packages/fc/8a/fb22af2fd78a736e241fabf67e30ce1799a64244026377a49e133af90762/ruff-0.16.1-py3-none-win32.whl", hash = "sha256:d31765e131295b8445caf301e3e8a85b34d1b9b211b4109b7ba457888b051806", size = 10838258, upload-time = "2026-07-30T19:36:53.298Z" }, - { url = "https://files.pythonhosted.org/packages/d4/35/e57fd9fb5d423961df087a00b12d42c0a830288dc2f3b45ecca299158b4f/ruff-0.16.1-py3-none-win_amd64.whl", hash = "sha256:09b05e8b90c2cb06ad63464350e7a45e8e44a2dfe52072ebfba6666ca8d3f596", size = 11961111, upload-time = "2026-07-30T19:36:56.107Z" }, - { url = "https://files.pythonhosted.org/packages/cb/46/240ea004bf6dc4feb40e9832f2205a476a47dd5b8a3f8211a5fc5f95e20e/ruff-0.16.1-py3-none-win_arm64.whl", hash = "sha256:dbaadaac38c70239f056d306b7476f246b0bf000fa6b3876402acbf5b227eaf8", size = 11309414, upload-time = "2026-07-30T19:36:58.79Z" }, +version = "0.16.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a4/7c/6adb35d70e7c027e308274557901c7e00fb3407750faf3620c184ae058cb/ruff-0.16.6.tar.gz", hash = "sha256:dcf8a73d2ff77e99dde91244b4da16feba7f14e6beeb4015dee7c5a909e99050", size = 4921251, upload-time = "2026-09-03T16:57:29.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/28/9cc1b79639e284ec103f43c88c644db4eb58cbd0ea1ca11f1193435369ac/ruff-0.16.6-py3-none-linux_armv6l.whl", hash = "sha256:61c368c26bf8e973e5ab14a2772de587bc068ea3f9a277f673380749b4898fb8", size = 10015638, upload-time = "2026-09-03T16:56:40.986Z" }, + { url = "https://files.pythonhosted.org/packages/71/11/627d342ef727ea7794edf74fe23d60a074b02c3acc2e9436684e782286ca/ruff-0.16.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ecf4f068e2e123e43a26e9db4e19524cc56563912404e83bbfca375757e45a32", size = 10220762, upload-time = "2026-09-03T16:56:44.681Z" }, + { url = "https://files.pythonhosted.org/packages/43/d9/b75668ce41e4c8d073d18d6d08672ba6906ce45d5c06ea4fdb2e84ce3853/ruff-0.16.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:99b62ea33baf130f50368798d841f0d95527b6d817bf31817b65dd058f1d314c", size = 9835082, upload-time = "2026-09-03T16:56:47.142Z" }, + { url = "https://files.pythonhosted.org/packages/99/97/123ab10b05cde889c107c20f5a9774955104b5552796a2a8584b089ae8eb/ruff-0.16.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fbf89013f2bb3f6835a6038ff658dc8a1b38c98dc8e724b964168ad4e881876", size = 9949304, upload-time = "2026-09-03T16:56:49.813Z" }, + { url = "https://files.pythonhosted.org/packages/3e/58/a4a2c59dd2e5b85929c912d9cac3056eb9ee8c7e75e9b9fe3e109174966b/ruff-0.16.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56a67065e22efa6bc4d498299d3bb06c0c90aace8fac2068b5a12f9dc4d8d51d", size = 9840612, upload-time = "2026-09-03T16:56:52.368Z" }, + { url = "https://files.pythonhosted.org/packages/61/6a/ff8c8626a786c4f49d48ced4a752dadbca65f5263005f9c2416578194694/ruff-0.16.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e25cc89174874b176a157e4428d66761c2c0c006654419bf384f967f361ff1b1", size = 10543465, upload-time = "2026-09-03T16:56:55.089Z" }, + { url = "https://files.pythonhosted.org/packages/ad/bb/c47535923365f337b82e28192e4e9eef2176511007cfd99a62fc22df5dad/ruff-0.16.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0700580ed5303723cb3c11c2f1d2a8913ce77b7ea86646dddb887f5417a9ba70", size = 11267576, upload-time = "2026-09-03T16:56:57.791Z" }, + { url = "https://files.pythonhosted.org/packages/ba/50/e5119a5212b5cd63b51e1f4b25e7bd636a6668fc069a3160b108ad7e3c16/ruff-0.16.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15f1d0b6e165a6e56567befb6629f8209271311d990bae0f37e6d065035ef5f3", size = 10781993, upload-time = "2026-09-03T16:57:00.666Z" }, + { url = "https://files.pythonhosted.org/packages/8b/98/083d8b4ef3c51a0d19db84367791cbe9f44e4b53343d19dfa83556e1cd9a/ruff-0.16.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d72c591a96986ee4268860e2b7235082129ca5e4cb9cbba653a4b57c11893757", size = 10317748, upload-time = "2026-09-03T16:57:03.428Z" }, + { url = "https://files.pythonhosted.org/packages/9a/29/68f7ff2c5ad95f19f00627ac2de95644e25fe47371ea60b2db1fd952315e/ruff-0.16.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:65a006baa18f33324325814c864daef03541d51564b98c517610ea756ab7003e", size = 10540096, upload-time = "2026-09-03T16:57:06.182Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f9/79a8f6de85968641d68a7863aeec577551924ef066a990a48ff93167beab/ruff-0.16.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:cd02a7bf1a21a8735228a3e8c95a9dc5cf86bd2a52194f4aaae2a5755b4de0f4", size = 10100494, upload-time = "2026-09-03T16:57:09.194Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e8/b81a22d9b90c00b892ccf2fa2ac36fa95de4c13ab85aea3e73795cfe4651/ruff-0.16.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:31b36f1e5ad85e0737f09d2be4e512e2e283583c14015da3b9dc07359ac0fc88", size = 9843663, upload-time = "2026-09-03T16:57:12.168Z" }, + { url = "https://files.pythonhosted.org/packages/39/aa/54f516ec5e5a11c4afdceb1c454ebb054ffb96e4f4a1705580b4346abd35/ruff-0.16.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:61029b4ab4aa723fd3064fab96b1d814492596bf0c792679fffcbde1e1679953", size = 10282461, upload-time = "2026-09-03T16:57:15.077Z" }, + { url = "https://files.pythonhosted.org/packages/52/0b/38d0aa8aa32372b96dc44f97b22e576c4147808271aab7b2cb1e353d4445/ruff-0.16.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9ac8998457832c2061709d900856b7ad271dace0cb41f346588d540162bfa718", size = 10728808, upload-time = "2026-09-03T16:57:17.797Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e5/9e274e24eeb027640ffc7442f21239f16d17f47acec15ae34f32e03a5c79/ruff-0.16.6-py3-none-win32.whl", hash = "sha256:0b87d9d16fcb63e8018423ca1d50b7260f15cb2da33e30db4baad4183a948c25", size = 10049212, upload-time = "2026-09-03T16:57:20.55Z" }, + { url = "https://files.pythonhosted.org/packages/22/31/72472449414223ed1a2da236b992adbb1a2ae59e34794574810f60ce068e/ruff-0.16.6-py3-none-win_amd64.whl", hash = "sha256:10d21c51c3495d8eaea7b703a16592117ea6eb1d649e36335aa965ff1173eb39", size = 10556402, upload-time = "2026-09-03T16:57:23.501Z" }, + { url = "https://files.pythonhosted.org/packages/fc/07/d781f8f8e1ac24bef9f3269cf62ffb1407ca24c3a8f12e5e22874f90528c/ruff-0.16.6-py3-none-win_arm64.whl", hash = "sha256:7a976c79b958f94e50a022a19f0f8c87387448020935ec14fc74331bd0a7f2c5", size = 10412850, upload-time = "2026-09-03T16:57:26.416Z" }, ] [[package]] @@ -1616,16 +1642,16 @@ wheels = [ [[package]] name = "sqlmodel" -version = "0.0.39" +version = "0.0.42" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "sqlalchemy" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/68/ee/22a0559283c3cf6048678e787ed5d4959dcd00dedd8ba4567eeae684eeb1/sqlmodel-0.0.39.tar.gz", hash = "sha256:23d8e50a8d8ee936032ed79c55023a5d618dd6bc3c510bbf4909d1a7a605a570", size = 91057, upload-time = "2026-06-25T13:01:38.475Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ab/50/27188d8cbccabf9968d74d4095765399dbc4f9fcae95e928fda69aa44a21/sqlmodel-0.0.42.tar.gz", hash = "sha256:9ecec2b6aa4c2aa58da39d2572e7bef4373419dcd6ec52973c07de7be2b1c516", size = 91737, upload-time = "2026-08-28T19:42:01.611Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/7d/b9813a582d4eb310be35e1fc7dfaae71207d7b62e9e53be314ebd251b53b/sqlmodel-0.0.39-py3-none-any.whl", hash = "sha256:90ebe92ce5cc11d7fff8dc7cb594790a102333c8fe7c14865254f6fc5c939795", size = 29680, upload-time = "2026-06-25T13:01:37.494Z" }, + { url = "https://files.pythonhosted.org/packages/67/37/d7d6b12b9066005c237de3d52b599298a2693ac423bff0e6b97a90b90bf8/sqlmodel-0.0.42-py3-none-any.whl", hash = "sha256:a732dab1a40e5cbd6ac250cf2b12e8d9b1bf1a47edf27c34d612f359e8875c63", size = 29991, upload-time = "2026-08-28T19:42:00.568Z" }, ] [[package]] @@ -1711,15 +1737,12 @@ wheels = [ ] [[package]] -name = "tqdm" -version = "4.68.4" +name = "truststore" +version = "0.10.4" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ae/5f/57ff8b434839e70dab45601284ea413e947a63799891b7553e5960a793a8/tqdm-4.68.4.tar.gz", hash = "sha256:19829c9673638f2a0b8617da4cdcb927e831cd88bcfcb6e78d42a4d1af131520", size = 792418, upload-time = "2026-07-07T09:58:18.369Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/2a/5e5e750890ada51017d18d0d4c30da696e5b5bd3180947729927628fc3cb/tqdm-4.68.4-py3-none-any.whl", hash = "sha256:5168118b2368f48c561afda8020fd79195b1bdb0bdf8086b88442c267a315dc2", size = 676612, upload-time = "2026-07-07T09:58:16.256Z" }, + { 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]]