diff --git a/.gitignore b/.gitignore index b154d3b56..7cf97c0d0 100644 --- a/.gitignore +++ b/.gitignore @@ -77,6 +77,10 @@ __pypackages__/ # Environments .env .envrc +*.crt +*.key +*.pem +credentials.json .venv .venvs env/ @@ -98,6 +102,7 @@ dmypy.json .vercel .turbo +node_modules/ .editorconfig .scratch .worktrees/ diff --git a/libs/checkpoint-conformance/langgraph/checkpoint/conformance/spec/test_delta_channel_history.py b/libs/checkpoint-conformance/langgraph/checkpoint/conformance/spec/test_delta_channel_history.py index 06fddd51d..330e6507d 100644 --- a/libs/checkpoint-conformance/langgraph/checkpoint/conformance/spec/test_delta_channel_history.py +++ b/libs/checkpoint-conformance/langgraph/checkpoint/conformance/spec/test_delta_channel_history.py @@ -6,9 +6,12 @@ import traceback from collections.abc import Callable from uuid import uuid4 -from langgraph.checkpoint.base import BaseCheckpointSaver +from langgraph.checkpoint.base import BaseCheckpointSaver, Checkpoint +from langgraph.checkpoint.base.id import uuid6 +from langgraph.checkpoint.serde.types import _DeltaSnapshot from langgraph.checkpoint.conformance.spec._delta_fixtures import build_delta_chain +from langgraph.checkpoint.conformance.test_utils import generate_metadata async def test_history_returns_writes_oldest_first( @@ -48,8 +51,6 @@ async def test_history_seed_is_nearest_snapshot( result = await saver.aget_delta_channel_history(config=head, channels=["ch"]) assert "seed" in result["ch"], "Expected seed from snapshot at step 3" seed = result["ch"]["seed"] - from langgraph.checkpoint.serde.types import _DeltaSnapshot - actual_value = seed.value if isinstance(seed, _DeltaSnapshot) else seed assert actual_value == 3, f"Expected seed value 3 (step 3), got {actual_value}" writes = result["ch"]["writes"] @@ -81,11 +82,6 @@ async def test_history_multi_channel( tid = str(uuid4()) configs: list = [] parent_cfg = None - from langgraph.checkpoint.base import Checkpoint - from langgraph.checkpoint.base.id import uuid6 - from langgraph.checkpoint.serde.types import _DeltaSnapshot - - from langgraph.checkpoint.conformance.test_utils import generate_metadata for step in range(5): config = {"configurable": {"thread_id": tid, "checkpoint_ns": ""}} @@ -161,11 +157,6 @@ async def test_history_migration_plain_value_as_seed( channel_values[ch] (not a _DeltaSnapshot). The walk should treat it as the seed and terminate there. """ - from langgraph.checkpoint.base import Checkpoint - from langgraph.checkpoint.base.id import uuid6 - - from langgraph.checkpoint.conformance.test_utils import generate_metadata - tid = str(uuid4()) configs: list = [] parent_cfg = None @@ -208,6 +199,74 @@ async def test_history_migration_plain_value_as_seed( assert values == [2], f"Expected [2], got {values}" +async def test_history_seed_ancestor_own_writes_are_replayed( + saver: BaseCheckpointSaver, +) -> None: + """Writes stored AT the seed ancestor must be included in `writes`. + + A stored value is the state ENTERING its checkpoint; the writes stored + under that same checkpoint are what produced its child and are therefore + NOT subsumed by it. Only writes at ancestors OLDER than the seed are + subsumed, and the walk terminates before reaching them. + + This holds for plain-value seeds (migration from a pre-delta channel type) + exactly as it does for `_DeltaSnapshot` seeds. Skipping the seed + ancestor's own writes silently drops the first post-migration write. + """ + tid = str(uuid4()) + configs: list = [] + parent_cfg = None + + # Each step's write is labelled by the role it plays, so the assertion + # below reads directly rather than by step index. + writes_by_step = { + 0: "older-than-seed", # subsumed by the value stored at step 1 + 1: "at-seed", # the seed's own write, produced step 2 + 2: "after-seed", # delta-era write on the path to the head + 3: "pending-at-head", # pending for the next step, never replayed + } + # Steps 0 and 1 store a plain value; 1 is the nearest, so it is the seed. + values_by_step = {0: [10], 1: [10, 20]} + + for step in range(4): + config = {"configurable": {"thread_id": tid, "checkpoint_ns": ""}} + if parent_cfg: + config["configurable"]["checkpoint_id"] = parent_cfg["configurable"][ + "checkpoint_id" + ] + cv: dict = {} + cvs: dict = {} + if step in values_by_step: + cv["ch"] = values_by_step[step] + cvs["ch"] = step + 1 + cp = Checkpoint( + v=1, + id=str(uuid6(clock_seq=-1)), + ts="", + channel_values=cv, + channel_versions=cvs, + versions_seen={}, + updated_channels=None, + ) + parent_cfg = await saver.aput(config, cp, generate_metadata(step=step), cvs) + configs.append(parent_cfg) + await saver.aput_writes( + parent_cfg, [("ch", writes_by_step[step])], str(uuid4()) + ) + + head = configs[-1] + result = await saver.aget_delta_channel_history(config=head, channels=["ch"]) + + assert "seed" in result["ch"], "Expected seed from plain value at step 1" + assert result["ch"]["seed"] == [10, 20], ( + f"Expected nearest plain value [10, 20], got {result['ch']['seed']}" + ) + values = [w[2] for w in result["ch"]["writes"]] + assert values == ["at-seed", "after-seed"], ( + f'Expected ["at-seed", "after-seed"], got {values}' + ) + + ALL_DELTA_CHANNEL_HISTORY_TESTS = [ test_history_returns_writes_oldest_first, test_history_seed_is_nearest_snapshot, @@ -216,6 +275,7 @@ ALL_DELTA_CHANNEL_HISTORY_TESTS = [ test_history_empty_channels_returns_empty, test_history_walk_to_root_no_seed, test_history_migration_plain_value_as_seed, + test_history_seed_ancestor_own_writes_are_replayed, ] diff --git a/libs/checkpoint-conformance/pyproject.toml b/libs/checkpoint-conformance/pyproject.toml index 2d9a0701f..4278393cc 100644 --- a/libs/checkpoint-conformance/pyproject.toml +++ b/libs/checkpoint-conformance/pyproject.toml @@ -58,8 +58,14 @@ lint.select = [ "UP", # pyupgrade "B", # flake8-bugbear "I", # isort + "PLC0415", # import-outside-top-level + "RUF100", # unused noqa directive ] lint.ignore = ["E501", "B008"] +# PLC0415 (import-outside-top-level) is enforced in tests only. Library code +# still has deferred imports that have not been reviewed, so it stays exempt +# for now. +lint.per-file-ignores = { "langgraph/**" = ["PLC0415"] } target-version = "py310" [tool.uv.sources] diff --git a/libs/checkpoint-conformance/uv.lock b/libs/checkpoint-conformance/uv.lock index 599dc9032..5ac5ad8e3 100644 --- a/libs/checkpoint-conformance/uv.lock +++ b/libs/checkpoint-conformance/uv.lock @@ -279,7 +279,7 @@ wheels = [ [[package]] name = "langgraph-checkpoint" -version = "4.1.1" +version = "4.2.0" source = { editable = "../checkpoint" } dependencies = [ { name = "langchain-core" }, diff --git a/libs/checkpoint-postgres/README.md b/libs/checkpoint-postgres/README.md index f3ccc0da2..048d11882 100644 --- a/libs/checkpoint-postgres/README.md +++ b/libs/checkpoint-postgres/README.md @@ -67,24 +67,12 @@ with PostgresSaver.from_conn_string(DB_URI) as checkpointer: "v": 4, "ts": "2024-07-31T20:14:19.804150+00:00", "id": "1ef4f797-8335-6428-8001-8a1503f9b875", - "channel_values": { - "my_key": "meow", - "node": "node" - }, - "channel_versions": { - "__start__": 2, - "my_key": 3, - "start:node": 3, - "node": 3 - }, + "channel_values": {"my_key": "meow", "node": "node"}, + "channel_versions": {"__start__": 2, "my_key": 3, "start:node": 3, "node": 3}, "versions_seen": { "__input__": {}, - "__start__": { - "__start__": 1 - }, - "node": { - "start:node": 2 - } + "__start__": {"__start__": 1}, + "node": {"start:node": 2}, }, } @@ -108,24 +96,12 @@ async with AsyncPostgresSaver.from_conn_string(DB_URI) as checkpointer: "v": 4, "ts": "2024-07-31T20:14:19.804150+00:00", "id": "1ef4f797-8335-6428-8001-8a1503f9b875", - "channel_values": { - "my_key": "meow", - "node": "node" - }, - "channel_versions": { - "__start__": 2, - "my_key": 3, - "start:node": 3, - "node": 3 - }, + "channel_values": {"my_key": "meow", "node": "node"}, + "channel_versions": {"__start__": 2, "my_key": 3, "start:node": 3, "node": 3}, "versions_seen": { "__input__": {}, - "__start__": { - "__start__": 1 - }, - "node": { - "start:node": 2 - } + "__start__": {"__start__": 1}, + "node": {"start:node": 2}, }, } diff --git a/libs/checkpoint-postgres/langgraph/checkpoint/postgres/__init__.py b/libs/checkpoint-postgres/langgraph/checkpoint/postgres/__init__.py index 18186d89b..c2a8ea985 100644 --- a/libs/checkpoint-postgres/langgraph/checkpoint/postgres/__init__.py +++ b/libs/checkpoint-postgres/langgraph/checkpoint/postgres/__init__.py @@ -223,7 +223,7 @@ class PostgresSaver(BasePostgresSaver): >>> checkpoint_tuple = memory.get_tuple(config) >>> print(checkpoint_tuple) CheckpointTuple(...) - """ # noqa + """ thread_id = config["configurable"]["thread_id"] checkpoint_id = get_checkpoint_id(config) checkpoint_ns = config["configurable"].get("checkpoint_ns", "") @@ -478,9 +478,11 @@ class PostgresSaver(BasePostgresSaver): stage1_sql = _build_delta_stage1_sql(channels, paged=True) parent_of: dict[str, str | None] = {} ver_by_i_by_cid: list[dict[str, str | None]] = [{} for _ in channels] - hs_by_i_by_cid: list[dict[str, bool]] = [{} for _ in channels] + hb_by_i_by_cid: list[dict[str, bool]] = [{} for _ in channels] + inline_by_i_by_cid: list[dict[str, Any]] = [{} for _ in channels] chain_by_ch: dict[str, list[str]] = {ch: [] for ch in channels} seed_ver_by_ch: dict[str, str | None] = {ch: None for ch in channels} + seed_inline_by_ch: dict[str, Any] = {} walk_cursor_by_ch: dict[str, str | None] = {} seeded: set[str] = set() cursor: str | None = None @@ -489,7 +491,8 @@ class PostgresSaver(BasePostgresSaver): while True: stage1_params: list[Any] = [] for ch in channels: - stage1_params.extend([ch, ch]) + # ver_i, blob channel, blob version, inline_i + stage1_params.extend([ch, ch, ch, ch]) stage1_params.extend( [thread_id, checkpoint_ns, cursor, cursor, _DELTA_PAGE_SIZE] ) @@ -502,16 +505,19 @@ class PostgresSaver(BasePostgresSaver): channels, parent_of, ver_by_i_by_cid, - hs_by_i_by_cid, + hb_by_i_by_cid, + inline_by_i_by_cid, ) self._try_advance_walks( checkpoint_id, channels, parent_of, ver_by_i_by_cid, - hs_by_i_by_cid, + hb_by_i_by_cid, + inline_by_i_by_cid, chain_by_ch, seed_ver_by_ch, + seed_inline_by_ch, walk_cursor_by_ch, seeded, ) @@ -546,6 +552,7 @@ class PostgresSaver(BasePostgresSaver): channels=channels, chain_by_ch=chain_by_ch, seed_ver_by_ch=seed_ver_by_ch, + seed_inline_by_ch=seed_inline_by_ch, stage2_rows=cast("list[_DeltaStage2Row]", stage2_rows), ) diff --git a/libs/checkpoint-postgres/langgraph/checkpoint/postgres/aio.py b/libs/checkpoint-postgres/langgraph/checkpoint/postgres/aio.py index 09fb964d5..ab48f6670 100644 --- a/libs/checkpoint-postgres/langgraph/checkpoint/postgres/aio.py +++ b/libs/checkpoint-postgres/langgraph/checkpoint/postgres/aio.py @@ -426,9 +426,11 @@ class AsyncPostgresSaver(BasePostgresSaver): stage1_sql = _build_delta_stage1_sql(channels, paged=True) parent_of: dict[str, str | None] = {} ver_by_i_by_cid: list[dict[str, str | None]] = [{} for _ in channels] - hs_by_i_by_cid: list[dict[str, bool]] = [{} for _ in channels] + hb_by_i_by_cid: list[dict[str, bool]] = [{} for _ in channels] + inline_by_i_by_cid: list[dict[str, Any]] = [{} for _ in channels] chain_by_ch: dict[str, list[str]] = {ch: [] for ch in channels} seed_ver_by_ch: dict[str, str | None] = {ch: None for ch in channels} + seed_inline_by_ch: dict[str, Any] = {} walk_cursor_by_ch: dict[str, str | None] = {} seeded: set[str] = set() cursor: str | None = None @@ -437,7 +439,8 @@ class AsyncPostgresSaver(BasePostgresSaver): while True: stage1_params: list[Any] = [] for ch in channels: - stage1_params.extend([ch, ch]) + # ver_i, blob channel, blob version, inline_i + stage1_params.extend([ch, ch, ch, ch]) stage1_params.extend( [thread_id, checkpoint_ns, cursor, cursor, _DELTA_PAGE_SIZE] ) @@ -450,16 +453,19 @@ class AsyncPostgresSaver(BasePostgresSaver): channels, parent_of, ver_by_i_by_cid, - hs_by_i_by_cid, + hb_by_i_by_cid, + inline_by_i_by_cid, ) self._try_advance_walks( checkpoint_id, channels, parent_of, ver_by_i_by_cid, - hs_by_i_by_cid, + hb_by_i_by_cid, + inline_by_i_by_cid, chain_by_ch, seed_ver_by_ch, + seed_inline_by_ch, walk_cursor_by_ch, seeded, ) @@ -490,6 +496,7 @@ class AsyncPostgresSaver(BasePostgresSaver): channels=channels, chain_by_ch=chain_by_ch, seed_ver_by_ch=seed_ver_by_ch, + seed_inline_by_ch=seed_inline_by_ch, stage2_rows=cast("list[_DeltaStage2Row]", stage2_rows), ) @@ -573,7 +580,7 @@ class AsyncPostgresSaver(BasePostgresSaver): while True: try: yield asyncio.run_coroutine_threadsafe( - anext(aiter_), # type: ignore[arg-type] # noqa: F821 + anext(aiter_), # type: ignore[arg-type] self.loop, ).result() except StopAsyncIteration: diff --git a/libs/checkpoint-postgres/langgraph/checkpoint/postgres/base.py b/libs/checkpoint-postgres/langgraph/checkpoint/postgres/base.py index beb1e9972..d58d2cc38 100644 --- a/libs/checkpoint-postgres/langgraph/checkpoint/postgres/base.py +++ b/libs/checkpoint-postgres/langgraph/checkpoint/postgres/base.py @@ -199,27 +199,68 @@ class _DeltaStage2Row(TypedDict, total=False): def _build_delta_stage1_sql(channels: Sequence[str], *, paged: bool) -> str: - """Build stage 1 SQL with 2K parallel JSONB key lookups. + """Build stage 1 SQL with K parallel version lookups + seed probes. For channels=["messages", "files"] (with `paged=True`) the result is:: SELECT checkpoint_id, parent_checkpoint_id, checkpoint -> 'channel_versions' ->> %s AS ver_0, - (checkpoint -> 'channel_values' -> %s) IS NOT NULL AS hs_0, + EXISTS (SELECT 1 FROM checkpoint_blobs b0 + WHERE b0.thread_id = checkpoints.thread_id + AND b0.checkpoint_ns = checkpoints.checkpoint_ns + AND b0.channel = %s + AND b0.version = checkpoint -> 'channel_versions' ->> %s + AND b0.type <> 'empty') AS hb_0, + checkpoint -> 'channel_values' -> %s AS inline_0, checkpoint -> 'channel_versions' ->> %s AS ver_1, - (checkpoint -> 'channel_values' -> %s) IS NOT NULL AS hs_1 + EXISTS (...) AS hb_1, + checkpoint -> 'channel_values' -> %s AS inline_1 FROM checkpoints WHERE thread_id = %s AND checkpoint_ns = %s AND (%s::text IS NULL OR checkpoint_id < %s) ORDER BY checkpoint_id DESC LIMIT %s - Channel names are passed as `%s` parameters (safe from SQL injection). - Only the column aliases `ver_i` / `hs_i` are interpolated into the - SQL string (i is bounded by len(channels) and uses safe identifiers). + A stored value for a channel lives in one of two places, because `put` + splits them: - Caller must extend params with `[ch_0, ch_0, ch_1, ch_1, ..., - thread_id, ns, cursor, cursor, page_size]` when `paged=True`. + * **blob** — non-primitive values (and `_DeltaSnapshot`) are moved to + `checkpoint_blobs`. `hb_i` ("has blob") probes for one. The probe hits + that table's primary key `(thread_id, checkpoint_ns, channel, version)` + exactly, so it is an index lookup per row per channel. + * **inline** — `None`, `str`, `int`, `float` and `bool` stay in the + checkpoint's own `channel_values` and get no blob row at all. `inline_i` + returns that value. + + Testing only for a key in `channel_values` (the previous approach) missed + blob-stored plain values, since `put` leaves an inline marker there for + `_DeltaSnapshot` but not for a plain value — which is what a thread + migrated from a pre-delta channel type leaves behind. Probing only the + blobs table would conversely miss inline primitives. Both are needed, and + the caller treats "either present" as the seed. + + `hb_i` also disambiguates the two: for a `_DeltaSnapshot`, `inline_i` is the + literal `true` marker rather than the value, so a blob must win over an + inline reading whenever one exists. That ordering is what makes a genuine + inline `true` (a bool channel) distinguishable from the marker. + + The `type <> 'empty'` predicate mirrors the check stage 2 already applies + when resolving the seed blob. `put` does not currently produce `empty` rows + on this path — `blob_versions` is filtered to keys present in + `channel_values`, so `_dump_blobs`' empty branch is unreachable from it — + but without the predicate the two stages could disagree: stage 1 would + terminate the walk on a row stage 2 then discards, yielding no seed *and* a + truncated write chain, which is the failure this function exists to avoid. + + Channel names are passed as `%s` parameters (safe from SQL injection). + Only the column aliases `ver_i` / `hb_i` / `inline_i` and the subquery alias + `b{i}` are interpolated into the SQL string (i is bounded by len(channels) + and uses safe identifiers). + + Caller must extend params with `[ch_0 x4, ch_1 x4, ..., thread_id, ns, + cursor, cursor, page_size]` when `paged=True` — four per channel: the + version lookup, the blob's channel, the version the blob must match, and the + inline lookup. When `paged=False`, the WHERE has no cursor predicate and there's no LIMIT/ORDER BY — kept as a non-public helper for tests/diagnostics. @@ -228,7 +269,13 @@ def _build_delta_stage1_sql(channels: Sequence[str], *, paged: bool) -> str: for i in range(len(channels)): cols.append( f"checkpoint -> 'channel_versions' ->> %s AS ver_{i}, " - f"(checkpoint -> 'channel_values' -> %s) IS NOT NULL AS hs_{i}" + f"EXISTS (SELECT 1 FROM checkpoint_blobs b{i} " + f"WHERE b{i}.thread_id = checkpoints.thread_id " + f"AND b{i}.checkpoint_ns = checkpoints.checkpoint_ns " + f"AND b{i}.channel = %s " + f"AND b{i}.version = checkpoint -> 'channel_versions' ->> %s " + f"AND b{i}.type <> 'empty') AS hb_{i}, " + f"checkpoint -> 'channel_values' -> %s AS inline_{i}" ) sql = ( "SELECT checkpoint_id, parent_checkpoint_id, " @@ -342,7 +389,8 @@ class BasePostgresSaver(BaseCheckpointSaver[str]): channels: Sequence[str], parent_of: dict[str, str | None], ver_by_i_by_cid: list[dict[str, str | None]], - hs_by_i_by_cid: list[dict[str, bool]], + hb_by_i_by_cid: list[dict[str, bool]], + inline_by_i_by_cid: list[dict[str, Any]], ) -> str | None: """Fold one stage-1 page into the running walk-state mappings. @@ -356,7 +404,8 @@ class BasePostgresSaver(BaseCheckpointSaver[str]): parent_of[cid] = cast("str | None", r["parent_checkpoint_id"]) for i in range(len(channels)): ver_by_i_by_cid[i][cid] = cast("str | None", r.get(f"ver_{i}")) - hs_by_i_by_cid[i][cid] = bool(r.get(f"hs_{i}")) + hb_by_i_by_cid[i][cid] = bool(r.get(f"hb_{i}")) + inline_by_i_by_cid[i][cid] = r.get(f"inline_{i}") # Rows are DESC; the last one is the smallest cid in the page. oldest = cid return oldest @@ -367,9 +416,11 @@ class BasePostgresSaver(BaseCheckpointSaver[str]): channels: Sequence[str], parent_of: Mapping[str, str | None], ver_by_i_by_cid: Sequence[Mapping[str, str | None]], - hs_by_i_by_cid: Sequence[Mapping[str, bool]], + hb_by_i_by_cid: Sequence[Mapping[str, bool]], + inline_by_i_by_cid: Sequence[Mapping[str, Any]], chain_by_ch: dict[str, list[str]], seed_ver_by_ch: dict[str, str | None], + seed_inline_by_ch: dict[str, Any], walk_cursor_by_ch: dict[str, str | None], seeded: set[str], ) -> None: @@ -377,14 +428,15 @@ class BasePostgresSaver(BaseCheckpointSaver[str]): Uses the partial `parent_of` map accumulated so far. A walk stops either because: - (a) it found a snapshot for its channel (channel becomes seeded), + (a) it found a stored value for its channel — a blob or an inline + primitive (channel becomes seeded), (b) it reached a real root (parent_of[cid] is None — fully materialized at this point), or (c) the next ancestor cid isn't in `parent_of` yet (waiting for a later page; the cursor stays put). - Mutates `chain_by_ch`, `seed_ver_by_ch`, `walk_cursor_by_ch`, and - `seeded` in place. + Mutates `chain_by_ch`, `seed_ver_by_ch`, `seed_inline_by_ch`, + `walk_cursor_by_ch`, and `seeded` in place. """ for i, ch in enumerate(channels): if ch in seeded: @@ -394,15 +446,22 @@ class BasePostgresSaver(BaseCheckpointSaver[str]): walk_cursor_by_ch[ch] = parent_of.get(target_id) cur_cid = walk_cursor_by_ch[ch] ch_chain = chain_by_ch[ch] - hs_i = hs_by_i_by_cid[i] + hb_i = hb_by_i_by_cid[i] + inline_i = inline_by_i_by_cid[i] ver_i = ver_by_i_by_cid[i] while cur_cid is not None: if cur_cid not in parent_of: # Need more pages to continue this walk. break ch_chain.append(cur_cid) - if hs_i.get(cur_cid, False): + has_blob = hb_i.get(cur_cid, False) + inline = inline_i.get(cur_cid) + if has_blob or inline is not None: + # A blob wins: for a `_DeltaSnapshot` the inline reading is + # the `true` marker, not the value. seed_ver_by_ch[ch] = ver_i.get(cur_cid) + if not has_blob: + seed_inline_by_ch[ch] = inline seeded.add(ch) cur_cid = None break @@ -415,16 +474,23 @@ class BasePostgresSaver(BaseCheckpointSaver[str]): channels: Sequence[str], chain_by_ch: Mapping[str, list[str]], seed_ver_by_ch: Mapping[str, str | None], + seed_inline_by_ch: Mapping[str, Any], stage2_rows: Sequence[_DeltaStage2Row], ) -> dict[str, DeltaChannelHistory]: """Demux stage 2 rows per channel; produce per-channel histories. stage2_rows carry `channel` on every row. We build per-channel `writes_by_cid` and per-channel `seed_blob` dicts, then assemble - a `DeltaChannelHistory` per requested channel. The `seed` key is omitted - when the walk reached root with no snapshot found, or when the - seed blob is sentinel "empty" — in both cases the consumer treats - absence as "start empty". + a `DeltaChannelHistory` per requested channel. + + A seed comes from the blobs table when the walk found one there, and + otherwise from `seed_inline_by_ch` — `put` keeps `None`, `str`, `int`, + `float` and `bool` values in the checkpoint's own `channel_values` with + no blob row, so those never appear in `stage2_rows`. + + The `seed` key is omitted when the walk reached root without finding a + stored value, or when the seed blob is sentinel "empty" — in both cases + the consumer treats absence as "start empty". """ # writes_by_ch_by_cid[channel][cid] = list of (type, blob, task_id, idx) writes_by_ch_by_cid: dict[str, dict[str, list[tuple[str, bytes, str, int]]]] = { @@ -473,6 +539,10 @@ class BasePostgresSaver(BaseCheckpointSaver[str]): blob = seed_blob_by_ver.get((ch, seed_version)) if blob is not None and blob[0] != "empty": entry["seed"] = self.serde.loads_typed(blob) + elif ch in seed_inline_by_ch: + # Inline primitive: stored in the checkpoint, not the blobs + # table, so stage 2 never returned a row for it. + entry["seed"] = seed_inline_by_ch[ch] result[ch] = entry return result diff --git a/libs/checkpoint-postgres/langgraph/checkpoint/postgres/shallow.py b/libs/checkpoint-postgres/langgraph/checkpoint/postgres/shallow.py index 90fc95e74..350b20bb5 100644 --- a/libs/checkpoint-postgres/langgraph/checkpoint/postgres/shallow.py +++ b/libs/checkpoint-postgres/langgraph/checkpoint/postgres/shallow.py @@ -334,7 +334,7 @@ class ShallowPostgresSaver(BasePostgresSaver): >>> checkpoint_tuple = memory.get_tuple(config) >>> print(checkpoint_tuple) CheckpointTuple(...) - """ # noqa + """ thread_id = config["configurable"]["thread_id"] checkpoint_ns = config["configurable"].get("checkpoint_ns", "") args = (thread_id, checkpoint_ns) @@ -885,7 +885,7 @@ class AsyncShallowPostgresSaver(BasePostgresSaver): while True: try: yield asyncio.run_coroutine_threadsafe( - anext(aiter_), # type: ignore[arg-type] # noqa: F821 + anext(aiter_), # type: ignore[arg-type] self.loop, ).result() except StopAsyncIteration: diff --git a/libs/checkpoint-postgres/pyproject.toml b/libs/checkpoint-postgres/pyproject.toml index 180166b86..72e918826 100644 --- a/libs/checkpoint-postgres/pyproject.toml +++ b/libs/checkpoint-postgres/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "langgraph-checkpoint-postgres" -version = "3.1.1" +version = "3.1.2" description = "Library with a Postgres implementation of LangGraph checkpoint saver." authors = [] requires-python = ">=3.10" @@ -32,6 +32,7 @@ test = [ "pytest-mock", "psycopg[binary]", "langgraph-checkpoint", + "langgraph-checkpoint-conformance", "pytest-watcher", ] lint = [ @@ -49,6 +50,7 @@ default-groups = ['dev'] [tool.uv.sources] langgraph-checkpoint = { path = "../checkpoint", editable = true } +langgraph-checkpoint-conformance = { path = "../checkpoint-conformance", editable = true } [tool.hatch.build.targets.wheel] include = ["langgraph"] @@ -64,6 +66,8 @@ lint.select = [ "UP", # pyupgrade "B", # flake8-bugbear "I", # isort + "PLC0415", # import-outside-top-level + "RUF100", # unused noqa directive "UP", # pyupgrade ] lint.ignore = ["E501", "B008"] diff --git a/libs/checkpoint-postgres/tests/test_async.py b/libs/checkpoint-postgres/tests/test_async.py index fd42146cf..ec941da90 100644 --- a/libs/checkpoint-postgres/tests/test_async.py +++ b/libs/checkpoint-postgres/tests/test_async.py @@ -380,13 +380,15 @@ async def test_delta_channel_chain_reconstruction(saver_name: str) -> None: "langgraph.channels.delta", reason="langgraph core not installed" ) - from typing import Annotated + # Deferred on purpose: langgraph core is not a test dependency of this + # package, so these must stay behind the importorskip above. + from typing import Annotated # noqa: PLC0415 - from langchain_core.messages import AIMessage, HumanMessage - from langgraph.channels.delta import DeltaChannel - from langgraph.graph import START, StateGraph - from langgraph.graph.message import _messages_delta_reducer - from typing_extensions import TypedDict + from langchain_core.messages import AIMessage, HumanMessage # noqa: PLC0415 + from langgraph.channels.delta import DeltaChannel # noqa: PLC0415 + from langgraph.graph import START, StateGraph # noqa: PLC0415 + from langgraph.graph.message import _messages_delta_reducer # noqa: PLC0415 + from typing_extensions import TypedDict # noqa: PLC0415 class State(TypedDict): messages: Annotated[list, DeltaChannel(_messages_delta_reducer)] diff --git a/libs/checkpoint-postgres/tests/test_conformance_delta.py b/libs/checkpoint-postgres/tests/test_conformance_delta.py new file mode 100644 index 000000000..b641d345e --- /dev/null +++ b/libs/checkpoint-postgres/tests/test_conformance_delta.py @@ -0,0 +1,30 @@ +"""Run delta-channel conformance capabilities against AsyncPostgresSaver.""" + +from __future__ import annotations + +import pytest +from langgraph.checkpoint.conformance import validate +from langgraph.checkpoint.conformance.initializer import checkpointer_test + +from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver +from tests.conftest import DEFAULT_URI + + +@pytest.mark.asyncio +async def test_delta_channel_conformance(): + @checkpointer_test(name="AsyncPostgresSaver") + async def postgres_saver(): + async with AsyncPostgresSaver.from_conn_string(DEFAULT_URI) as saver: + await saver.setup() + yield saver + + report = await validate( + postgres_saver, + capabilities={ + "delta_channel_history", + }, + ) + for cap, result in report.results.items(): + if result.passed is False: + details = "\n".join(result.failures or []) + pytest.fail(f"Capability {cap} failed:\n{details}") diff --git a/libs/checkpoint-postgres/tests/test_delta_plain_value_seed.py b/libs/checkpoint-postgres/tests/test_delta_plain_value_seed.py new file mode 100644 index 000000000..f441f9160 --- /dev/null +++ b/libs/checkpoint-postgres/tests/test_delta_plain_value_seed.py @@ -0,0 +1,204 @@ +"""Seed detection for `DeltaChannel` histories on Postgres. + +`put` splits stored values in two: primitives stay inline in the checkpoint's +`channel_values`, everything else moves to `checkpoint_blobs`. Only +`_DeltaSnapshot` leaves an inline marker behind when it moves, so the stage-1 +walk has to check both places — a blob probe alone misses inline primitives, and +an inline-key check alone missed blob-stored plain values, which is what a thread +migrated from a pre-delta channel type leaves behind. See #8534. +""" + +from __future__ import annotations + +from typing import Any +from uuid import uuid4 + +import pytest +from langgraph.checkpoint.base import Checkpoint, empty_checkpoint +from langgraph.checkpoint.base.id import uuid6 +from langgraph.checkpoint.serde.types import _DeltaSnapshot + +from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver +from tests.conftest import DEFAULT_URI + +CHANNEL = "items" + + +async def _build_chain(saver: AsyncPostgresSaver, seed_value: Any) -> tuple[str, dict]: + """Store `seed_value` at step 1, then two steps that store nothing. + + Every step carries a write so the walk has something to collect. + Returns `(thread_id, head_config)`. + """ + thread_id = str(uuid4()) + parent: dict | None = None + for step in range(4): + config: dict = {"configurable": {"thread_id": thread_id, "checkpoint_ns": ""}} + if parent is not None: + config["configurable"]["checkpoint_id"] = parent["configurable"][ + "checkpoint_id" + ] + cp: Checkpoint = empty_checkpoint() + cp["id"] = str(uuid6(clock_seq=step)) + new_versions: dict[str, Any] = {} + if step == 1: + cp["channel_values"][CHANNEL] = seed_value + cp["channel_versions"][CHANNEL] = "v1" + new_versions[CHANNEL] = "v1" + else: + cp["channel_versions"][CHANNEL] = f"v{step}" + parent = await saver.aput( + config, cp, {"source": "loop", "step": step, "parents": {}}, new_versions + ) + await saver.aput_writes(parent, [(CHANNEL, f"w{step}")], str(uuid4())) + assert parent is not None + return thread_id, parent + + +@pytest.mark.asyncio +async def test_plain_value_seed_is_found() -> None: + """A pre-delta plain value must be located as the seed. + + Before #8534 the walk ran to the root and returned no seed, which happens + to reconstruct correctly for additive reducers while costing an + O(thread length) replay on every read. + """ + async with AsyncPostgresSaver.from_conn_string(DEFAULT_URI) as saver: + await saver.setup() + _, head = await _build_chain(saver, [10, 20]) + + result = await saver.aget_delta_channel_history(config=head, channels=[CHANNEL]) + entry = result[CHANNEL] + + assert entry.get("seed") == [10, 20], ( + f"expected the plain value as seed, got {entry.get('seed', '')}" + ) + # Only the writes between the seed and the head's parent replay: step 1 + # (the seed's own) and step 2. Step 0 is older than the seed, step 3 is + # pending at the head. + assert [w[2] for w in entry["writes"]] == ["w1", "w2"] + + +@pytest.mark.asyncio +async def test_delta_snapshot_seed_is_found() -> None: + """The `_DeltaSnapshot` path keeps working, so both seed kinds agree.""" + async with AsyncPostgresSaver.from_conn_string(DEFAULT_URI) as saver: + await saver.setup() + _, head = await _build_chain(saver, _DeltaSnapshot([10, 20])) + + result = await saver.aget_delta_channel_history(config=head, channels=[CHANNEL]) + entry = result[CHANNEL] + + seed = entry.get("seed") + assert isinstance(seed, _DeltaSnapshot), f"expected a snapshot, got {seed!r}" + assert seed.value == [10, 20] + assert [w[2] for w in entry["writes"]] == ["w1", "w2"] + + +@pytest.mark.asyncio +async def test_version_bump_without_a_value_does_not_hide_an_older_seed() -> None: + """A delta-era step bumps `channel_versions` without storing a value, so no + blob exists for that version. The probe must report no seed there and keep + walking rather than stopping at a version it cannot resolve. + + Step 0 holds the real value; step 1 bumps the version with nothing stored. + Walking back from the head has to pass step 1 to reach step 0. + """ + async with AsyncPostgresSaver.from_conn_string(DEFAULT_URI) as saver: + await saver.setup() + thread_id = str(uuid4()) + parent: dict | None = None + for step in range(4): + config: dict = { + "configurable": {"thread_id": thread_id, "checkpoint_ns": ""} + } + if parent is not None: + config["configurable"]["checkpoint_id"] = parent["configurable"][ + "checkpoint_id" + ] + cp: Checkpoint = empty_checkpoint() + cp["id"] = str(uuid6(clock_seq=step)) + new_versions: dict[str, Any] = {} + if step == 0: + cp["channel_values"][CHANNEL] = [10, 20] + cp["channel_versions"][CHANNEL] = "v0" + new_versions[CHANNEL] = "v0" + elif step == 1: + # Version bumped, value absent -> no blob row written. + cp["channel_versions"][CHANNEL] = "v1" + new_versions[CHANNEL] = "v1" + else: + cp["channel_versions"][CHANNEL] = "v1" + parent = await saver.aput( + config, + cp, + {"source": "loop", "step": step, "parents": {}}, + new_versions, + ) + await saver.aput_writes(parent, [(CHANNEL, f"w{step}")], str(uuid4())) + assert parent is not None + + result = await saver.aget_delta_channel_history( + config=parent, channels=[CHANNEL] + ) + entry = result[CHANNEL] + + assert entry.get("seed") == [10, 20], ( + "the walk stopped at the empty blob instead of reaching the real " + f"value at step 0; got {entry.get('seed', '')}" + ) + assert [w[2] for w in entry["writes"]] == ["w0", "w1", "w2"] + + +@pytest.mark.asyncio +async def test_inline_primitive_seed_is_found() -> None: + """`put` keeps `None`, `str`, `int`, `float` and `bool` in the checkpoint's + own `channel_values` with no blob row, so a blob probe alone cannot see + them. Stage 1 reads the inline value too and uses it when there is no blob. + """ + async with AsyncPostgresSaver.from_conn_string(DEFAULT_URI) as saver: + await saver.setup() + for seed_value in (42, "x", 3.5, None): + _, head = await _build_chain(saver, seed_value) + entry = ( + await saver.aget_delta_channel_history(config=head, channels=[CHANNEL]) + )[CHANNEL] + if seed_value is None: + # A JSON null is indistinguishable from "no value stored", so + # the walk keeps going; replay from empty is the correct result. + assert "seed" not in entry + else: + assert entry.get("seed") == seed_value, ( + f"inline {type(seed_value).__name__} seed not found: " + f"{entry.get('seed', '')!r}" + ) + assert [w[2] for w in entry["writes"]] == ["w1", "w2"] + + +@pytest.mark.asyncio +async def test_inline_true_is_not_read_as_a_snapshot_marker() -> None: + """`put` inlines a literal `true` in `channel_values` as the marker for a + `_DeltaSnapshot`, which is also what a genuine `bool` channel holding + `True` looks like. A blob exists only in the snapshot case, so preferring + the blob keeps the two apart. + """ + async with AsyncPostgresSaver.from_conn_string(DEFAULT_URI) as saver: + await saver.setup() + + _, head = await _build_chain(saver, True) + entry = ( + await saver.aget_delta_channel_history(config=head, channels=[CHANNEL]) + )[CHANNEL] + assert entry.get("seed") is True, ( + f"a real inline True must survive, got {entry.get('seed', '')!r}" + ) + + _, snap_head = await _build_chain(saver, _DeltaSnapshot(True)) + snap_entry = ( + await saver.aget_delta_channel_history(config=snap_head, channels=[CHANNEL]) + )[CHANNEL] + seed = snap_entry.get("seed") + assert isinstance(seed, _DeltaSnapshot), ( + f"the marker must resolve to the blob, not inline true; got {seed!r}" + ) + assert seed.value is True diff --git a/libs/checkpoint-postgres/uv.lock b/libs/checkpoint-postgres/uv.lock index 839dc8bdf..a701fce35 100644 --- a/libs/checkpoint-postgres/uv.lock +++ b/libs/checkpoint-postgres/uv.lock @@ -159,7 +159,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -276,7 +276,7 @@ wheels = [ [[package]] name = "langgraph-checkpoint" -version = "4.1.1" +version = "4.2.0" source = { editable = "../checkpoint" } dependencies = [ { name = "langchain-core" }, @@ -322,9 +322,36 @@ test = [ { name = "redis" }, ] +[[package]] +name = "langgraph-checkpoint-conformance" +version = "0.0.2" +source = { editable = "../checkpoint-conformance" } +dependencies = [ + { name = "langgraph-checkpoint" }, +] + +[package.metadata] +requires-dist = [{ name = "langgraph-checkpoint", editable = "../checkpoint" }] + +[package.metadata.requires-dev] +dev = [ + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "ruff" }, + { name = "ty" }, +] +lint = [ + { name = "ruff" }, + { name = "ty" }, +] +test = [ + { name = "pytest" }, + { name = "pytest-asyncio" }, +] + [[package]] name = "langgraph-checkpoint-postgres" -version = "3.1.1" +version = "3.1.2" source = { editable = "." } dependencies = [ { name = "langgraph-checkpoint" }, @@ -338,6 +365,7 @@ dev = [ { name = "anyio" }, { name = "codespell" }, { name = "langgraph-checkpoint" }, + { name = "langgraph-checkpoint-conformance" }, { name = "psycopg", extra = ["binary"] }, { name = "pytest" }, { name = "pytest-asyncio" }, @@ -354,6 +382,7 @@ lint = [ test = [ { name = "anyio" }, { name = "langgraph-checkpoint" }, + { name = "langgraph-checkpoint-conformance" }, { name = "psycopg", extra = ["binary"] }, { name = "pytest" }, { name = "pytest-asyncio" }, @@ -374,6 +403,7 @@ dev = [ { name = "anyio" }, { name = "codespell" }, { name = "langgraph-checkpoint", editable = "../checkpoint" }, + { name = "langgraph-checkpoint-conformance", editable = "../checkpoint-conformance" }, { name = "psycopg", extras = ["binary"] }, { name = "pytest" }, { name = "pytest-asyncio" }, @@ -390,6 +420,7 @@ lint = [ test = [ { name = "anyio" }, { name = "langgraph-checkpoint", editable = "../checkpoint" }, + { name = "langgraph-checkpoint-conformance", editable = "../checkpoint-conformance" }, { name = "psycopg", extras = ["binary"] }, { name = "pytest" }, { name = "pytest-asyncio" }, diff --git a/libs/checkpoint-sqlite/langgraph/checkpoint/sqlite/__init__.py b/libs/checkpoint-sqlite/langgraph/checkpoint/sqlite/__init__.py index 6ca2448d0..3259ff150 100644 --- a/libs/checkpoint-sqlite/langgraph/checkpoint/sqlite/__init__.py +++ b/libs/checkpoint-sqlite/langgraph/checkpoint/sqlite/__init__.py @@ -77,7 +77,7 @@ class SqliteSaver(BaseCheckpointSaver[str]): >>> result = graph.invoke(3, config) >>> graph.get_state(config) StateSnapshot(values=4, next=(), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '0c62ca34-ac19-445d-bbb0-5b4984975b2a'}}, parent_config=None) - """ # noqa + """ conn: sqlite3.Connection is_setup: bool @@ -222,7 +222,7 @@ class SqliteSaver(BaseCheckpointSaver[str]): >>> checkpoint_tuple = memory.get_tuple(config) >>> print(checkpoint_tuple) CheckpointTuple(...) - """ # noqa + """ checkpoint_ns = config["configurable"].get("checkpoint_ns", "") with self.cursor(transaction=False) as cur: # find the latest checkpoint for the thread_id diff --git a/libs/checkpoint-sqlite/langgraph/checkpoint/sqlite/aio.py b/libs/checkpoint-sqlite/langgraph/checkpoint/sqlite/aio.py index 368428c68..1ad0777c1 100644 --- a/libs/checkpoint-sqlite/langgraph/checkpoint/sqlite/aio.py +++ b/libs/checkpoint-sqlite/langgraph/checkpoint/sqlite/aio.py @@ -212,7 +212,7 @@ class AsyncSqliteSaver(BaseCheckpointSaver[str]): while True: try: yield asyncio.run_coroutine_threadsafe( - anext(aiter_), # type: ignore[arg-type] # noqa: F821 + anext(aiter_), # type: ignore[arg-type] self.loop, ).result() except StopAsyncIteration: diff --git a/libs/checkpoint-sqlite/pyproject.toml b/libs/checkpoint-sqlite/pyproject.toml index c0a040655..fe0f42b32 100644 --- a/libs/checkpoint-sqlite/pyproject.toml +++ b/libs/checkpoint-sqlite/pyproject.toml @@ -30,6 +30,7 @@ test = [ "pytest-mock", "pytest-watcher", "langgraph-checkpoint", + "langgraph-checkpoint-conformance", "pytest-retry>=1.7.0", ] lint = [ @@ -47,6 +48,7 @@ default-groups = ['dev'] [tool.uv.sources] langgraph-checkpoint = { path = "../checkpoint", editable = true } +langgraph-checkpoint-conformance = { path = "../checkpoint-conformance", editable = true } [tool.hatch.build.targets.wheel] include = ["langgraph"] @@ -62,6 +64,8 @@ lint.select = [ "UP", # pyupgrade "B", # flake8-bugbear "I", # isort + "PLC0415", # import-outside-top-level + "RUF100", # unused noqa directive "UP", # pyupgrade ] lint.ignore = ["E501", "B008"] diff --git a/libs/checkpoint-sqlite/tests/test_conformance_delta.py b/libs/checkpoint-sqlite/tests/test_conformance_delta.py index ba0e90f18..2e0b58dba 100644 --- a/libs/checkpoint-sqlite/tests/test_conformance_delta.py +++ b/libs/checkpoint-sqlite/tests/test_conformance_delta.py @@ -3,21 +3,14 @@ from __future__ import annotations import pytest +from langgraph.checkpoint.conformance import validate +from langgraph.checkpoint.conformance.initializer import checkpointer_test -pytest.importorskip( - "langgraph.checkpoint.conformance", - reason="langgraph-checkpoint-conformance not installed", -) -pytest.importorskip("aiosqlite", reason="aiosqlite not installed") +from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver @pytest.mark.asyncio async def test_delta_channel_conformance(): - from langgraph.checkpoint.conformance import validate - from langgraph.checkpoint.conformance.initializer import checkpointer_test - - from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver - @checkpointer_test(name="AsyncSqliteSaver") async def sqlite_saver(): async with AsyncSqliteSaver.from_conn_string(":memory:") as saver: diff --git a/libs/checkpoint-sqlite/tests/test_delta_channel_migration.py b/libs/checkpoint-sqlite/tests/test_delta_channel_migration.py index c71f3b12c..f9f511c1e 100644 --- a/libs/checkpoint-sqlite/tests/test_delta_channel_migration.py +++ b/libs/checkpoint-sqlite/tests/test_delta_channel_migration.py @@ -29,13 +29,13 @@ pytest.importorskip("langgraph.channels.delta", reason="langgraph core not insta pytest.importorskip("langgraph.channels.binop", reason="langgraph core not installed") pytest.importorskip("langgraph.graph", reason="langgraph core not installed") -from langgraph.channels.binop import BinaryOperatorAggregate # type: ignore[import-untyped] # noqa: E402,I001 -from langgraph.channels.delta import DeltaChannel # type: ignore[import-untyped] # noqa: E402 -from langgraph.graph import END, START, StateGraph # type: ignore[import-untyped] # noqa: E402 -from typing_extensions import TypedDict # noqa: E402 +from langgraph.channels.binop import BinaryOperatorAggregate # type: ignore[import-untyped] # noqa: I001 +from langgraph.channels.delta import DeltaChannel # type: ignore[import-untyped] +from langgraph.graph import END, START, StateGraph # type: ignore[import-untyped] +from typing_extensions import TypedDict -from langgraph.checkpoint.sqlite import SqliteSaver # noqa: E402 -from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver # noqa: E402 +from langgraph.checkpoint.sqlite import SqliteSaver +from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver pytestmark = pytest.mark.anyio diff --git a/libs/checkpoint-sqlite/tests/test_get_delta_channel_history.py b/libs/checkpoint-sqlite/tests/test_get_delta_channel_history.py index 8e19c8b85..20c694223 100644 --- a/libs/checkpoint-sqlite/tests/test_get_delta_channel_history.py +++ b/libs/checkpoint-sqlite/tests/test_get_delta_channel_history.py @@ -32,13 +32,13 @@ from langchain_core.runnables import RunnableConfig pytest.importorskip("langgraph.channels.delta", reason="langgraph core not installed") pytest.importorskip("langgraph.graph", reason="langgraph core not installed") -from langgraph.channels.delta import DeltaChannel # type: ignore[import-untyped] # noqa: E402,I001 -from langgraph.checkpoint.serde.types import _DeltaSnapshot # noqa: E402 -from langgraph.graph import END, START, StateGraph # type: ignore[import-untyped] # noqa: E402 -from typing_extensions import TypedDict # noqa: E402 +from langgraph.channels.delta import DeltaChannel # type: ignore[import-untyped] # noqa: I001 +from langgraph.checkpoint.serde.types import _DeltaSnapshot +from langgraph.graph import END, START, StateGraph # type: ignore[import-untyped] +from typing_extensions import TypedDict -from langgraph.checkpoint.sqlite import SqliteSaver # noqa: E402 -from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver # noqa: E402 +from langgraph.checkpoint.sqlite import SqliteSaver +from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver pytestmark = pytest.mark.anyio diff --git a/libs/checkpoint-sqlite/tests/test_store.py b/libs/checkpoint-sqlite/tests/test_store.py index e930208f1..d4b06847e 100644 --- a/libs/checkpoint-sqlite/tests/test_store.py +++ b/libs/checkpoint-sqlite/tests/test_store.py @@ -1,7 +1,11 @@ +import math import os +import random import re import tempfile +import time import uuid +from collections import Counter, defaultdict from collections.abc import Generator, Iterable from contextlib import contextmanager from typing import Any, Literal, cast @@ -33,10 +37,6 @@ class CharacterEmbeddings(Embeddings): def __init__(self, dims: int = 50, seed: int = 42): """Initialize with embedding dimensions and random seed.""" - import math - import random - from collections import defaultdict - self._rng = random.Random(seed) self.dims = dims # Create projection vector for each character lazily @@ -48,9 +48,6 @@ class CharacterEmbeddings(Embeddings): def _embed_one(self, text: str) -> list[float]: """Embed a single text.""" - import math - from collections import Counter - counts = Counter(text) total = sum(counts.values()) @@ -338,8 +335,6 @@ class TestSqliteStore: # Test update # Small delay to ensure the updated timestamp is different - import time - time.sleep(0.01) updated_value = {"title": "Updated Document", "content": "Hello, Updated!"} diff --git a/libs/checkpoint-sqlite/uv.lock b/libs/checkpoint-sqlite/uv.lock index 4b8839aee..b6a8e9616 100644 --- a/libs/checkpoint-sqlite/uv.lock +++ b/libs/checkpoint-sqlite/uv.lock @@ -168,7 +168,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -285,7 +285,7 @@ wheels = [ [[package]] name = "langgraph-checkpoint" -version = "4.1.1" +version = "4.2.0" source = { editable = "../checkpoint" } dependencies = [ { name = "langchain-core" }, @@ -331,6 +331,33 @@ test = [ { name = "redis" }, ] +[[package]] +name = "langgraph-checkpoint-conformance" +version = "0.0.2" +source = { editable = "../checkpoint-conformance" } +dependencies = [ + { name = "langgraph-checkpoint" }, +] + +[package.metadata] +requires-dist = [{ name = "langgraph-checkpoint", editable = "../checkpoint" }] + +[package.metadata.requires-dev] +dev = [ + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "ruff" }, + { name = "ty" }, +] +lint = [ + { name = "ruff" }, + { name = "ty" }, +] +test = [ + { name = "pytest" }, + { name = "pytest-asyncio" }, +] + [[package]] name = "langgraph-checkpoint-sqlite" version = "3.1.1" @@ -345,6 +372,7 @@ dependencies = [ dev = [ { name = "codespell" }, { name = "langgraph-checkpoint" }, + { name = "langgraph-checkpoint-conformance" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-mock" }, @@ -360,6 +388,7 @@ lint = [ ] test = [ { name = "langgraph-checkpoint" }, + { name = "langgraph-checkpoint-conformance" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-mock" }, @@ -378,6 +407,7 @@ requires-dist = [ dev = [ { name = "codespell" }, { name = "langgraph-checkpoint", editable = "../checkpoint" }, + { name = "langgraph-checkpoint-conformance", editable = "../checkpoint-conformance" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-mock" }, @@ -393,6 +423,7 @@ lint = [ ] test = [ { name = "langgraph-checkpoint", editable = "../checkpoint" }, + { name = "langgraph-checkpoint-conformance", editable = "../checkpoint-conformance" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-mock" }, diff --git a/libs/checkpoint/langgraph/checkpoint/memory/__init__.py b/libs/checkpoint/langgraph/checkpoint/memory/__init__.py index 80043c710..0f0719c61 100644 --- a/libs/checkpoint/langgraph/checkpoint/memory/__init__.py +++ b/libs/checkpoint/langgraph/checkpoint/memory/__init__.py @@ -148,17 +148,16 @@ class InMemorySaver( whose stored blob is non-empty. Other channels keep walking until they find their own terminator or hit the root. - Pre-delta plain-value blobs subsume their ancestor's pending - writes (the value already includes them); `_DeltaSnapshot` blobs - do not (snapshot is the value AT that ancestor, prior to its own - pending writes that produce the child). + A blob is the value AT its ancestor, prior to the writes stored + under that same ancestor (those writes produce its child, which + is on the path to the target). This holds for `_DeltaSnapshot` + blobs and for pre-delta plain values alike, so the seed + ancestor's own writes are always collected. Writes at ancestors + older than the seed are subsumed by the seed value and are never + reached — the walk terminates there. """ if not channels: return {} - # Imported lazily to avoid a hard checkpoint→serde-types coupling at - # module import; only this override needs the runtime check. - from langgraph.checkpoint.serde.types import _DeltaSnapshot - thread_id = config["configurable"]["thread_id"] checkpoint_ns = config["configurable"].get("checkpoint_ns", "") checkpoint_id = config["configurable"].get("checkpoint_id", "") @@ -205,11 +204,6 @@ class InMemorySaver( ): if ch not in remaining: continue - blob_value = blob_value_by_ch.get(ch) - if blob_value is not None and not isinstance( - blob_value, _DeltaSnapshot - ): - continue collected_by_ch[ch].append( (tid, ch, self.serde.loads_typed(serialized)) ) diff --git a/libs/checkpoint/pyproject.toml b/libs/checkpoint/pyproject.toml index 8e03c391c..d98630b4c 100644 --- a/libs/checkpoint/pyproject.toml +++ b/libs/checkpoint/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "langgraph-checkpoint" -version = "4.1.1" +version = "4.2.0" description = "Library with base interfaces for LangGraph checkpoint savers." authors = [] requires-python = ">=3.10" @@ -59,9 +59,15 @@ lint.select = [ "UP", # pyupgrade "B", # flake8-bugbear "I", # isort + "PLC0415", # import-outside-top-level + "RUF100", # unused noqa directive "UP", # pyupgrade ] lint.ignore = ["E501", "B008"] +# PLC0415 (import-outside-top-level) is enforced in tests only. Library code +# still has deferred imports that have not been reviewed, so it stays exempt +# for now. +lint.per-file-ignores = { "langgraph/**" = ["PLC0415"] } target-version = "py310" [tool.ty.rules] diff --git a/libs/checkpoint/tests/test_conformance_delta.py b/libs/checkpoint/tests/test_conformance_delta.py index 82a52a93e..5443601cf 100644 --- a/libs/checkpoint/tests/test_conformance_delta.py +++ b/libs/checkpoint/tests/test_conformance_delta.py @@ -12,10 +12,14 @@ conformance = pytest.importorskip( @pytest.mark.asyncio async def test_delta_channel_conformance(): - from langgraph.checkpoint.conformance import validate - from langgraph.checkpoint.conformance.initializer import checkpointer_test + # Imported inside the test: the module-level importorskip above is what + # makes these safe, so they cannot move to the top of the file. + from langgraph.checkpoint.conformance import validate # noqa: PLC0415 + from langgraph.checkpoint.conformance.initializer import ( # noqa: PLC0415 + checkpointer_test, + ) - from langgraph.checkpoint.memory import InMemorySaver + from langgraph.checkpoint.memory import InMemorySaver # noqa: PLC0415 @checkpointer_test(name="InMemorySaver") async def mem_saver(): diff --git a/libs/checkpoint/tests/test_encrypted.py b/libs/checkpoint/tests/test_encrypted.py index f1fc14e94..43f342485 100644 --- a/libs/checkpoint/tests/test_encrypted.py +++ b/libs/checkpoint/tests/test_encrypted.py @@ -307,8 +307,6 @@ class TestWithMsgpackAllowlistEncrypted: def loads_typed(self, data: tuple[str, bytes]) -> None: return None - from langgraph.checkpoint.serde.base import CipherProtocol - class DummyCipher(CipherProtocol): def encrypt(self, plaintext: bytes) -> tuple[str, bytes]: return "dummy", plaintext diff --git a/libs/checkpoint/tests/test_jsonplus.py b/libs/checkpoint/tests/test_jsonplus.py index b0999b0ed..c7e7cc995 100644 --- a/libs/checkpoint/tests/test_jsonplus.py +++ b/libs/checkpoint/tests/test_jsonplus.py @@ -1,9 +1,12 @@ import dataclasses import json import logging +import os import pathlib +import pickle import re import sys +import tempfile import uuid from collections import deque from datetime import date, datetime, time, timezone @@ -18,7 +21,7 @@ import ormsgpack import pandas as pd import pytest from langchain_core.documents.base import Document -from langchain_core.messages import HumanMessage +from langchain_core.messages import AIMessage, HumanMessage from pydantic import BaseModel, SecretStr from pydantic.v1 import BaseModel as BaseModelV1 from pydantic.v1 import SecretStr as SecretStrV1 @@ -341,7 +344,6 @@ def test_lc2_json_safe_type_revives_without_allowlist() -> None: constructor dicts. Resuming those threads must reconstruct proper BaseMessage objects rather than returning raw dicts that cause MESSAGE_COERCION_FAILURE in add_messages. """ - from langchain_core.messages import AIMessage serde = JsonPlusSerializer() # default: _allowed_json_modules=None @@ -410,7 +412,6 @@ def test_lc2_json_method_field_is_ignored() -> None: to that method: the result is whatever ``AIMessage(*args, **kwargs)`` would produce, which proves the default constructor ran instead of ``parse_raw``. """ - from langchain_core.messages import AIMessage serde = JsonPlusSerializer() load = { @@ -436,7 +437,6 @@ def test_lc2_json_method_field_is_ignored_for_allowlisted_types() -> None: method dispatch as a side effect. Revival is restricted to the default constructor regardless of how the class reached the revival path. """ - from langchain_core.messages import AIMessage serde = JsonPlusSerializer( allowed_json_modules=[("langchain_core.messages.ai", "AIMessage")] @@ -455,7 +455,6 @@ def test_lc2_json_method_field_is_ignored_for_allowlisted_types() -> None: def test_lc2_json_safe_type_init_still_works() -> None: """SAFE-type lc=2 revival without a `method` field still constructs the class.""" - from langchain_core.messages import AIMessage serde = JsonPlusSerializer() load = { @@ -479,7 +478,6 @@ def test_lc2_json_legacy_pydantic_method_list_falls_back_to_default() -> None: this shape continue to revive correctly as long as the default constructor accepts the serialized kwargs. """ - from langchain_core.messages import AIMessage serde = JsonPlusSerializer() load = { @@ -551,9 +549,6 @@ def test_lc2_json_safe_type_pickle_payload_does_not_execute() -> None: With method dispatch removed from `_revive_lc2`, the gadget bytes are never passed to `parse_raw` and therefore never reach `pickle.loads`. """ - import os - import pickle - import tempfile marker = tempfile.NamedTemporaryFile( prefix="lc2_block_proof_", suffix=".out", delete=False diff --git a/libs/checkpoint/tests/test_memory.py b/libs/checkpoint/tests/test_memory.py index 70e22e0d8..51d10a5dd 100644 --- a/libs/checkpoint/tests/test_memory.py +++ b/libs/checkpoint/tests/test_memory.py @@ -1,3 +1,4 @@ +import asyncio import logging from typing import Any @@ -523,7 +524,6 @@ class TestBaseFallbackGetChannelWrites: `threading.local()` guard would let whichever task set it first short-circuit the other to `writes=[]`. """ - import asyncio saver, thread_id, ns = self._build_saver_with_chain() @@ -577,9 +577,19 @@ class TestPreDeltaBlobTerminator: """ def _build_mixed_thread(self) -> tuple[InMemorySaver, str, str, str, str]: - """Three-checkpoint chain: cp1 (pre-delta, blob=[A]), cp2 (delta, - write=B), cp3 (delta, write=C). Reconstructing at cp3 must yield - seed=[A] + writes=[B, C]. + """Four-checkpoint chain spanning the migration boundary: + + * `cp0` — pre-delta ancestor OLDER than the seed. Its write + (`OLDER-WRITE`) is already folded into `cp1`'s stored value, so the + walk must terminate at `cp1` and never reach it. + * `cp1` — pre-delta, blob `["A"]`. That value is the state ENTERING + `cp1`; the write stored under `cp1` (`PRE-DELTA-WRITE`) is what + produced `cp2` and is NOT subsumed by the blob. + * `cp2` — delta-era, no stored value, write `B`. + * `cp3` — target, delta-era, write `PENDING-AT-TARGET`. + + Reconstructing at `cp3` must yield seed `["A"]` plus writes + `["PRE-DELTA-WRITE", "B"]`. Returns `(saver, thread_id, ns, channel, cp3_id)`. """ @@ -587,16 +597,21 @@ class TestPreDeltaBlobTerminator: serde = JsonPlusSerializer() thread_id, ns, channel = "t1", "", "messages" + v0 = "00000000000000000000000000000000.0" v1 = "00000000000000000000000000000001.0" v2 = "00000000000000000000000000000002.0" v3 = "00000000000000000000000000000003.0" - # Pre-delta: cp1 stored a real blob for the channel. + # Pre-delta: cp0 and cp1 stored real blobs for the channel. + saver.blobs[(thread_id, ns, channel, v0)] = serde.dumps_typed([]) saver.blobs[(thread_id, ns, channel, v1)] = serde.dumps_typed(["A"]) # Delta-era: cp2 and cp3 store "empty"; real writes in checkpoint_writes. saver.blobs[(thread_id, ns, channel, v2)] = ("empty", b"") saver.blobs[(thread_id, ns, channel, v3)] = ("empty", b"") + cp0 = empty_checkpoint() + cp0["id"] = "cp0" + cp0["channel_versions"][channel] = v0 cp1 = empty_checkpoint() cp1["id"] = "cp1" cp1["channel_versions"][channel] = v1 @@ -608,16 +623,24 @@ class TestPreDeltaBlobTerminator: cp3["channel_versions"][channel] = v3 saver.storage[thread_id][ns] = { - "cp1": (serde.dumps_typed(cp1), serde.dumps_typed({}), None), + "cp0": (serde.dumps_typed(cp0), serde.dumps_typed({}), None), + "cp1": (serde.dumps_typed(cp1), serde.dumps_typed({}), "cp0"), "cp2": (serde.dumps_typed(cp2), serde.dumps_typed({}), "cp1"), "cp3": (serde.dumps_typed(cp3), serde.dumps_typed({}), "cp2"), } - # Write under cp1 would be from the pre-delta era and MUST be ignored - # (the blob already captures it). We add one and assert it is not - # folded into the reconstructed result. - saver.writes[(thread_id, ns, "cp1")][("task0", 0)] = ( + # Write under cp0 is older than the seed — cp1's blob already folded + # it in, and the terminator must stop before reaching it. + saver.writes[(thread_id, ns, "cp0")][("task0", 0)] = ( "task0", channel, + serde.dumps_typed("OLDER-WRITE"), + "", + ) + # Write under cp1 postdates cp1's blob (it is what produced cp2, which + # stores no value of its own) and MUST be replayed. + saver.writes[(thread_id, ns, "cp1")][("task1", 0)] = ( + "task1", + channel, serde.dumps_typed("PRE-DELTA-WRITE"), "", ) @@ -651,15 +674,19 @@ class TestPreDeltaBlobTerminator: # Seed came from the pre-delta blob at cp1. assert result["seed"] == ["A"] - # Delta-era writes from cp2 replay through the reducer on top of seed. - # cp3 is the target — its own write is pending for the NEXT step and - # must be excluded. + # The seed ancestor's own write and the delta-era write from cp2 both + # replay through the reducer on top of the seed, oldest first. cp3 is + # the target — its own write is pending for the NEXT step and must be + # excluded. values = [v for _, _, v in result["writes"]] - assert values == ["B"] + assert values == ["PRE-DELTA-WRITE", "B"] - def test_pre_delta_blob_terminates_walk_before_older_writes(self) -> None: - """Writes stored at the pre-delta ancestor itself must not be replayed - (the blob subsumes them).""" + def test_seed_bounds_walk_without_dropping_its_own_writes(self) -> None: + """The seed terminator bounds the walk: writes at ancestors OLDER than + the seed are already folded into the seed value and must not be + replayed. The seed ancestor's own write is not one of them — it + postdates the stored value and produced the next checkpoint. + """ saver, thread_id, ns, channel, target = self._build_mixed_thread() config: RunnableConfig = { "configurable": { @@ -674,7 +701,9 @@ class TestPreDeltaBlobTerminator: ] values = [v for _, _, v in result["writes"]] - # The pre-delta write under cp1 must not appear (the blob subsumes it). - assert "PRE-DELTA-WRITE" not in values + # Older than the seed — subsumed by cp1's blob, so the walk stops first. + assert "OLDER-WRITE" not in values + # Stored AT the seed ancestor — not subsumed, so it must be replayed. + assert "PRE-DELTA-WRITE" in values # And the pending write at the target is never folded in. assert "PENDING-AT-TARGET" not in values diff --git a/libs/checkpoint/uv.lock b/libs/checkpoint/uv.lock index bc8ac7324..054ce0a4b 100644 --- a/libs/checkpoint/uv.lock +++ b/libs/checkpoint/uv.lock @@ -300,7 +300,7 @@ wheels = [ [[package]] name = "langgraph-checkpoint" -version = "4.1.1" +version = "4.2.0" source = { editable = "." } dependencies = [ { name = "langchain-core" }, diff --git a/libs/cli/langgraph_cli/uv_lock.py b/libs/cli/langgraph_cli/uv_lock.py index 1e33d16d7..985b7d9ef 100644 --- a/libs/cli/langgraph_cli/uv_lock.py +++ b/libs/cli/langgraph_cli/uv_lock.py @@ -970,6 +970,22 @@ def python_config_to_docker_uv_lock( f"{uv_export_project_dir}/uv.lock", ) ) + for package_root in sorted( + plan.all_workspace_roots, + key=lambda root: root.as_posix(), + ): + if package_root == plan.project_root: + continue + package_relative_path = pathlib.PurePosixPath( + package_root.relative_to(plan.project_root).as_posix() + ) + package_pyproject_path = package_relative_path / "pyproject.toml" + docker_plan.add_raw( + copy_from_project_root( + package_pyproject_path, + f"{uv_export_project_dir}/{package_pyproject_path.as_posix()}", + ) + ) docker_plan.add_instruction("WORKDIR", uv_export_project_dir) docker_plan.add_instruction( "RUN", diff --git a/libs/cli/pyproject.toml b/libs/cli/pyproject.toml index 7012daa22..8a301f8a8 100644 --- a/libs/cli/pyproject.toml +++ b/libs/cli/pyproject.toml @@ -72,9 +72,15 @@ lint.select = [ "UP", # pyupgrade "B", # flake8-bugbear "I", # isort + "PLC0415", # import-outside-top-level + "RUF100", # unused noqa directive "UP", # pyupgrade ] lint.ignore = ["E501", "B008"] +# PLC0415 (import-outside-top-level) is enforced in tests only. Library code +# still has deferred imports that have not been reviewed, so it stays exempt +# for now. +lint.per-file-ignores = { "langgraph_cli/**" = ["PLC0415"], "generate_schema.py" = ["PLC0415"] } target-version = "py310" [tool.ty.rules] diff --git a/libs/cli/tests/unit_tests/test_archive.py b/libs/cli/tests/unit_tests/test_archive.py index 5b8ce8223..8f4dafc7d 100644 --- a/libs/cli/tests/unit_tests/test_archive.py +++ b/libs/cli/tests/unit_tests/test_archive.py @@ -11,6 +11,7 @@ from langgraph_cli.archive import ( _tar_filter, create_archive, ) +from langgraph_cli.config import LocalDeps # --------------------------------------------------------------------------- # _tar_filter @@ -198,7 +199,6 @@ class TestCreateArchive: @patch("langgraph_cli.archive._assemble_local_deps") def test_yields_archive_with_config(self, mock_deps, tmp_path): - from langgraph_cli.config import LocalDeps config_file = self._make_project(tmp_path) mock_deps.return_value = LocalDeps( @@ -218,7 +218,6 @@ class TestCreateArchive: @patch("langgraph_cli.archive._assemble_local_deps") def test_excludes_pycache(self, mock_deps, tmp_path): - from langgraph_cli.config import LocalDeps config_file = self._make_project(tmp_path) mock_deps.return_value = LocalDeps( @@ -232,7 +231,6 @@ class TestCreateArchive: @patch("langgraph_cli.archive._assemble_local_deps") def test_cleans_up_tmp_dir_on_normal_exit(self, mock_deps, tmp_path): - from langgraph_cli.config import LocalDeps config_file = self._make_project(tmp_path) mock_deps.return_value = LocalDeps( @@ -247,7 +245,6 @@ class TestCreateArchive: @patch("langgraph_cli.archive._assemble_local_deps") def test_cleans_up_tmp_dir_on_exception(self, mock_deps, tmp_path): - from langgraph_cli.config import LocalDeps config_file = self._make_project(tmp_path) mock_deps.return_value = LocalDeps( @@ -264,7 +261,6 @@ class TestCreateArchive: @patch("langgraph_cli.archive._assemble_local_deps") @patch("langgraph_cli.archive._MAX_SIZE", 10) def test_raises_on_oversized_archive(self, mock_deps, tmp_path): - from langgraph_cli.config import LocalDeps config_file = self._make_project(tmp_path) mock_deps.return_value = LocalDeps( @@ -278,7 +274,6 @@ class TestCreateArchive: @patch("langgraph_cli.archive._assemble_local_deps") def test_handles_extra_contexts(self, mock_deps, tmp_path): """Monorepo case: project + sibling dependency directory.""" - from langgraph_cli.config import LocalDeps project = tmp_path / "myproject" project.mkdir() diff --git a/libs/cli/tests/unit_tests/test_config.py b/libs/cli/tests/unit_tests/test_config.py index c8b24020c..fb9a36ccf 100644 --- a/libs/cli/tests/unit_tests/test_config.py +++ b/libs/cli/tests/unit_tests/test_config.py @@ -1403,6 +1403,19 @@ def test_config_to_docker_uv_lock(): "COPY --from=uv-workspace-root uv.lock /tmp/uv_export/project/uv.lock" in docker ) + workspace_pyprojects = [ + "apps/agent/pyproject.toml", + "libs/extra/pyproject.toml", + "libs/shared/pyproject.toml", + ] + export_instruction = "RUN uv export --package agent" + for pyproject_path in workspace_pyprojects: + copy_instruction = ( + "COPY --from=uv-workspace-root " + f"{pyproject_path} /tmp/uv_export/project/{pyproject_path}" + ) + assert copy_instruction in docker + assert docker.index(copy_instruction) < docker.index(export_instruction) assert additional_contexts == {"uv-workspace-root": str(project_root.resolve())} assert ( diff --git a/libs/cli/tests/unit_tests/test_deploy_helpers.py b/libs/cli/tests/unit_tests/test_deploy_helpers.py index cdeb0d3f5..5112450f8 100644 --- a/libs/cli/tests/unit_tests/test_deploy_helpers.py +++ b/libs/cli/tests/unit_tests/test_deploy_helpers.py @@ -347,7 +347,6 @@ class TestCallHostBackendWithOptionalTenant: def test_workspace_prompt_blocked_by_no_input(self, monkeypatch): """With _no_input=True, 403 requiring workspace should raise ClickException.""" - import langgraph_cli.deploy as deploy_mod monkeypatch.setattr(deploy_mod, "_no_input", True) @@ -515,7 +514,6 @@ class TestEmitterTextMode: class TestCreateHostBackendClientNoInput: def test_raises_when_no_api_key_and_no_input(self, monkeypatch, tmp_path): - import langgraph_cli.deploy as deploy_mod monkeypatch.setattr(deploy_mod, "_no_input", True) monkeypatch.delenv("LANGSMITH_API_KEY", raising=False) @@ -530,7 +528,6 @@ class TestCreateHostBackendClientNoInput: ) def test_succeeds_with_api_key_in_env(self, monkeypatch, tmp_path): - import langgraph_cli.deploy as deploy_mod monkeypatch.setattr(deploy_mod, "_no_input", True) monkeypatch.setenv("LANGSMITH_API_KEY", "lsv2_test") diff --git a/libs/cli/uv-examples/monorepo/uv.lock b/libs/cli/uv-examples/monorepo/uv.lock index 623882281..b7a1c926c 100644 --- a/libs/cli/uv-examples/monorepo/uv.lock +++ b/libs/cli/uv-examples/monorepo/uv.lock @@ -266,20 +266,20 @@ wheels = [ [[package]] name = "langgraph-checkpoint" -version = "4.0.1" +version = "4.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, { name = "ormsgpack" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/44/a8df45d1e8b4637e29789fa8bae1db022c953cc7ac80093cfc52e923547e/langgraph_checkpoint-4.0.1.tar.gz", hash = "sha256:b433123735df11ade28829e40ce25b9be614930cd50245ff2af60629234befd9", size = 158135, upload-time = "2026-02-27T21:06:16.092Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/e1/089c4c9e0a2fec7f883f82ae8e6a727138d50074cfeb6644bc2d13b1019b/langgraph_checkpoint-4.2.0.tar.gz", hash = "sha256:51a593b6bee684b0818e5d6e58e28ab340c6db7794575056ce7bd1b746a84ed7", size = 180239, upload-time = "2026-08-07T20:05:03.756Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/65/4c/09a4a0c42f5d2fc38d6c4d67884788eff7fd2cfdf367fdf7033de908b4c0/langgraph_checkpoint-4.0.1-py3-none-any.whl", hash = "sha256:e3adcd7a0e0166f3b48b8cf508ce0ea366e7420b5a73aa81289888727769b034", size = 50453, upload-time = "2026-02-27T21:06:14.293Z" }, + { url = "https://files.pythonhosted.org/packages/05/71/3b475f09bd57d3a5649792c66353312b4432afd843f301739dfcebd157f0/langgraph_checkpoint-4.2.0-py3-none-any.whl", hash = "sha256:0547fd228935a0b758865de3a3d6d7a2537c308895d0f9ab092ce9151b5da942", size = 56833, upload-time = "2026-08-07T20:05:02.655Z" }, ] [[package]] name = "langgraph-checkpoint-postgres" -version = "3.0.5" +version = "3.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langgraph-checkpoint" }, @@ -287,9 +287,9 @@ dependencies = [ { name = "psycopg" }, { name = "psycopg-pool" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/7a/8f439966643d32111248a225e6cb33a182d07c90de780c4dbfc1e0377832/langgraph_checkpoint_postgres-3.0.5.tar.gz", hash = "sha256:a8fd7278a63f4f849b5cbc7884a15ca8f41e7d5f7467d0a66b31e8c24492f7eb", size = 127856, upload-time = "2026-03-18T21:25:29.785Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/92/1e8959f8cd1b56e672fde3227f6fd642be85af6c5fd662d73921074aa39d/langgraph_checkpoint_postgres-3.1.1.tar.gz", hash = "sha256:d320e147ddad8c374cd546df0b52b532dd54d0541dd9fd23fc738cbd5de76f41", size = 150413, upload-time = "2026-07-30T19:15:39.014Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/87/b0f98b33a67204bca9d5619bcd9574222f6b025cf3c125eedcec9a50ecbc/langgraph_checkpoint_postgres-3.0.5-py3-none-any.whl", hash = "sha256:86d7040a88fd70087eaafb72251d796696a0a2d856168f5c11ef620771411552", size = 42907, upload-time = "2026-03-18T21:25:28.75Z" }, + { url = "https://files.pythonhosted.org/packages/03/32/ba457698a48a0e18d786caa770033067049fbe36d6846f8e50f13b594b51/langgraph_checkpoint_postgres-3.1.1-py3-none-any.whl", hash = "sha256:6e353aecd8150de144fef8e51a49076f58b7d6830d4cf51392b7ad4d79832ba7", size = 50778, upload-time = "2026-07-30T19:15:37.405Z" }, ] [[package]] diff --git a/libs/langgraph/langgraph/_internal/_pydantic.py b/libs/langgraph/langgraph/_internal/_pydantic.py index 0d93f085e..33319d0ee 100644 --- a/libs/langgraph/langgraph/_internal/_pydantic.py +++ b/libs/langgraph/langgraph/_internal/_pydantic.py @@ -68,7 +68,7 @@ def _create_root_model( def schema( cls: type[BaseModel], - by_alias: bool = True, # noqa: FBT001,FBT002 + by_alias: bool = True, ref_template: str = DEFAULT_REF_TEMPLATE, ) -> dict[str, Any]: # Complains about schema not being defined in superclass @@ -80,7 +80,7 @@ def _create_root_model( def model_json_schema( cls: type[BaseModel], - by_alias: bool = True, # noqa: FBT001,FBT002 + by_alias: bool = True, ref_template: str = DEFAULT_REF_TEMPLATE, schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema, mode: JsonSchemaMode = "validation", diff --git a/libs/langgraph/langgraph/_internal/_runnable.py b/libs/langgraph/langgraph/_internal/_runnable.py index 0c110b96f..aa7e51425 100644 --- a/libs/langgraph/langgraph/_internal/_runnable.py +++ b/libs/langgraph/langgraph/_internal/_runnable.py @@ -3,6 +3,7 @@ from __future__ import annotations import asyncio import enum import inspect +import logging import sys import warnings from collections.abc import ( @@ -63,6 +64,26 @@ try: except ImportError: _StreamingCallbackHandler = None # type: ignore +logger = logging.getLogger(__name__) + + +def _trace_payload(value: Any, transform: Callable[[Any], Any] | None) -> Any: + """Return the payload to record on a run for `value`. + + When `transform` is unset this is a passthrough, so unspecified nodes record exactly + as before. When set it always runs (regardless of tracing), but never affects + execution: if it raises, the untransformed value is recorded instead. + """ + if transform is None: + return value + try: + return transform(value) + except Exception: + logger.exception( + "trace input/output processor raised; recording untransformed payload" + ) + return value + def _set_config_context( config: RunnableConfig, run: Any = None @@ -572,6 +593,7 @@ class RunnableSeq(Runnable): *steps: RunnableLike, name: str | None = None, trace_inputs: Callable[[Any], Any] | None = None, + trace_outputs: Callable[[Any], Any] | None = None, ) -> None: """Create a new RunnableSeq. @@ -597,6 +619,7 @@ class RunnableSeq(Runnable): self.steps = steps_flat self.name = name self.trace_inputs = trace_inputs + self.trace_outputs = trace_outputs def __or__( self, @@ -658,7 +681,7 @@ class RunnableSeq(Runnable): # start the root run run_manager = callback_manager.on_chain_start( None, - self.trace_inputs(input) if self.trace_inputs is not None else input, + _trace_payload(input, self.trace_inputs), name=config.get("run_name") or self.get_name(), run_id=config.pop("run_id", None), ) @@ -689,7 +712,7 @@ class RunnableSeq(Runnable): run_manager.on_chain_error(e) raise else: - run_manager.on_chain_end(input) + run_manager.on_chain_end(_trace_payload(input, self.trace_outputs)) return input async def ainvoke( @@ -705,7 +728,7 @@ class RunnableSeq(Runnable): # start the root run run_manager = await callback_manager.on_chain_start( None, - self.trace_inputs(input) if self.trace_inputs is not None else input, + _trace_payload(input, self.trace_inputs), name=config.get("run_name") or self.get_name(), run_id=config.pop("run_id", None), ) @@ -742,7 +765,7 @@ class RunnableSeq(Runnable): await run_manager.on_chain_error(e) raise else: - await run_manager.on_chain_end(input) + await run_manager.on_chain_end(_trace_payload(input, self.trace_outputs)) return input def stream( @@ -758,7 +781,7 @@ class RunnableSeq(Runnable): # start the root run run_manager = callback_manager.on_chain_start( None, - self.trace_inputs(input) if self.trace_inputs is not None else input, + _trace_payload(input, self.trace_inputs), name=config.get("run_name") or self.get_name(), run_id=config.pop("run_id", None), ) @@ -803,7 +826,7 @@ class RunnableSeq(Runnable): run_manager.on_chain_error(e) raise else: - run_manager.on_chain_end(output) + run_manager.on_chain_end(_trace_payload(output, self.trace_outputs)) async def astream( self, @@ -818,7 +841,7 @@ class RunnableSeq(Runnable): # start the root run run_manager = await callback_manager.on_chain_start( None, - self.trace_inputs(input) if self.trace_inputs is not None else input, + _trace_payload(input, self.trace_inputs), name=config.get("run_name") or self.get_name(), run_id=config.pop("run_id", None), ) @@ -873,7 +896,9 @@ class RunnableSeq(Runnable): await run_manager.on_chain_error(e) raise else: - await run_manager.on_chain_end(output) + await run_manager.on_chain_end( + _trace_payload(output, self.trace_outputs) + ) else: try: async with AsyncExitStack() as stack: @@ -903,7 +928,9 @@ class RunnableSeq(Runnable): await run_manager.on_chain_error(e) raise else: - await run_manager.on_chain_end(output) + await run_manager.on_chain_end( + _trace_payload(output, self.trace_outputs) + ) def _consume_iter(it: Iterator[Any]) -> Any: diff --git a/libs/langgraph/langgraph/_internal/_serde.py b/libs/langgraph/langgraph/_internal/_serde.py index 775242a87..933f76ec5 100644 --- a/libs/langgraph/langgraph/_internal/_serde.py +++ b/libs/langgraph/langgraph/_internal/_serde.py @@ -22,7 +22,7 @@ from pydantic import BaseModel from typing_extensions import NotRequired, Required, is_typeddict try: - from langgraph.checkpoint.serde._msgpack import ( # noqa: F401 + from langgraph.checkpoint.serde._msgpack import ( STRICT_MSGPACK_ENABLED, ) except ImportError: diff --git a/libs/langgraph/langgraph/errors.py b/libs/langgraph/langgraph/errors.py index 47d30de08..65b57d61e 100644 --- a/libs/langgraph/langgraph/errors.py +++ b/libs/langgraph/langgraph/errors.py @@ -7,7 +7,7 @@ from typing import Any, Literal from warnings import warn # EmptyChannelError is re-exported from langgraph.channels.base -from langgraph.checkpoint.base import EmptyChannelError # noqa: F401 +from langgraph.checkpoint.base import EmptyChannelError from typing_extensions import deprecated from langgraph.types import Command, Interrupt diff --git a/libs/langgraph/langgraph/graph/_node.py b/libs/langgraph/langgraph/graph/_node.py index d8238c2b3..4cb2676a9 100644 --- a/libs/langgraph/langgraph/graph/_node.py +++ b/libs/langgraph/langgraph/graph/_node.py @@ -9,7 +9,13 @@ from langgraph.store.base import BaseStore from langgraph._internal._typing import EMPTY_SEQ from langgraph.runtime import Runtime -from langgraph.types import CachePolicy, RetryPolicy, StreamWriter, TimeoutPolicy +from langgraph.types import ( + CachePolicy, + RetryPolicy, + StreamWriter, + TimeoutPolicy, + TracePolicy, +) from langgraph.typing import ContextT, NodeInputT, NodeInputT_contra @@ -93,3 +99,5 @@ class StateNodeSpec(Generic[NodeInputT, ContextT]): ends: tuple[str, ...] | dict[str, str] | None = EMPTY_SEQ defer: bool = False timeout: TimeoutPolicy | None = None + trace_policy: TracePolicy | None = None + """Optional policy controlling what this node records on its trace run.""" diff --git a/libs/langgraph/langgraph/graph/state.py b/libs/langgraph/langgraph/graph/state.py index deb0e9e94..9a87f16fa 100644 --- a/libs/langgraph/langgraph/graph/state.py +++ b/libs/langgraph/langgraph/graph/state.py @@ -85,6 +85,7 @@ from langgraph.types import ( RetryPolicy, Send, TimeoutPolicy, + TracePolicy, ensure_valid_checkpointer, ) from langgraph.typing import ContextT, InputT, NodeInputT, OutputT, StateT @@ -384,6 +385,7 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]): error_handler: StateNode[Any, ContextT] | None = None, destinations: dict[str, str] | tuple[str, ...] | None = None, timeout: float | timedelta | TimeoutPolicy | None = None, + trace_policy: TracePolicy | None = None, **kwargs: Unpack[DeprecatedKwargs], ) -> Self: """Add a new node to the `StateGraph`, input schema is inferred as the state schema. @@ -453,6 +455,7 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]): error_handler: StateNode[Any, ContextT] | None = None, destinations: dict[str, str] | tuple[str, ...] | None = None, timeout: float | timedelta | TimeoutPolicy | None = None, + trace_policy: TracePolicy | None = None, **kwargs: Unpack[DeprecatedKwargs], ) -> Self: """Add a new node to the `StateGraph` where input schema is specified. @@ -527,6 +530,7 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]): error_handler: StateNode[Any, ContextT] | None = None, destinations: dict[str, str] | tuple[str, ...] | None = None, timeout: float | timedelta | TimeoutPolicy | None = None, + trace_policy: TracePolicy | None = None, **kwargs: Unpack[DeprecatedKwargs], ) -> Self: """Add a new node to the `StateGraph`, input schema is inferred as the state schema. @@ -596,6 +600,7 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]): error_handler: StateNode[Any, ContextT] | None = None, destinations: dict[str, str] | tuple[str, ...] | None = None, timeout: float | timedelta | TimeoutPolicy | None = None, + trace_policy: TracePolicy | None = None, **kwargs: Unpack[DeprecatedKwargs], ) -> Self: """Add a new node to the `StateGraph`, input schema is specified. @@ -672,6 +677,7 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]): error_handler: StateNode[Any, ContextT] | None = None, destinations: dict[str, str] | tuple[str, ...] | None = None, timeout: float | timedelta | TimeoutPolicy | None = None, + trace_policy: TracePolicy | None = None, **kwargs: Unpack[DeprecatedKwargs], ) -> Self: """Add a new node to the `StateGraph`. @@ -691,6 +697,10 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]): If a sequence is provided, the first matching policy will be applied. cache_policy: The cache policy for the node. error_handler: Optional node-level error handler callable for this node. + trace_policy: Optional policy controlling how this node's run is traced. Its + `process_inputs` callable transforms the node's input before it is + recorded (e.g. to omit or summarize large message history) without + changing the value passed to the node. Does not affect execution. destinations: Destinations that indicate where a node can route to. Useful for edgeless graphs with nodes that return `Command` objects. @@ -880,6 +890,7 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]): ends=ends, defer=defer, timeout=timeout, + trace_policy=trace_policy, ) elif inferred_input_schema is not None: self.nodes[node] = StateNodeSpec( @@ -892,6 +903,7 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]): ends=ends, defer=defer, timeout=timeout, + trace_policy=trace_policy, ) else: self.nodes[node] = StateNodeSpec[StateT, ContextT]( @@ -904,6 +916,7 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]): ends=ends, defer=defer, timeout=timeout, + trace_policy=trace_policy, ) input_schema = input_schema or inferred_input_schema @@ -995,7 +1008,7 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]): Without type hints on the `path` function's return value (e.g., `-> Literal["foo", "__end__"]:`) or a path_map, the graph visualization assumes the edge could transition to any node in the graph. - """ # noqa: E501 + """ if self.compiled: logger.warning( "Adding an edge to a graph that has already been compiled. This will " @@ -1530,6 +1543,7 @@ class CompiledStateGraph( error_handler_node=node.error_handler_node, bound=node.runnable, # type: ignore[arg-type] timeout=node.timeout, + trace_policy=node.trace_policy, ) else: raise RuntimeError diff --git a/libs/langgraph/langgraph/pregel/_read.py b/libs/langgraph/langgraph/pregel/_read.py index 7d49fce85..7e1f9a686 100644 --- a/libs/langgraph/langgraph/pregel/_read.py +++ b/libs/langgraph/langgraph/pregel/_read.py @@ -16,7 +16,7 @@ from langgraph._internal._timeout import coerce_timeout_policy from langgraph.pregel._utils import find_subgraph_pregel from langgraph.pregel._write import ChannelWrite from langgraph.pregel.protocol import PregelProtocol -from langgraph.types import CachePolicy, RetryPolicy, TimeoutPolicy +from langgraph.types import CachePolicy, RetryPolicy, TimeoutPolicy, TracePolicy READ_TYPE = Callable[[str | Sequence[str], bool], Any | dict[str, Any]] INPUT_CACHE_KEY_TYPE = tuple[Callable[..., Any], tuple[str, ...]] @@ -138,6 +138,9 @@ class PregelNode: metadata: Mapping[str, Any] | None """Metadata to attach to the node for tracing.""" + trace_policy: TracePolicy | None + """Optional policy controlling what this node records on its trace run.""" + is_error_handler: bool """Whether this node is registered as an error handler node.""" @@ -156,6 +159,7 @@ class PregelNode: writers: list[Runnable] | None = None, tags: list[str] | None = None, metadata: Mapping[str, Any] | None = None, + trace_policy: TracePolicy | None = None, bound: Runnable[Any, Any] | None = None, retry_policy: RetryPolicy | Sequence[RetryPolicy] | None = None, cache_policy: CachePolicy | None = None, @@ -177,6 +181,7 @@ class PregelNode: self.timeout = coerce_timeout_policy(timeout) self.tags = tags self.metadata = metadata + self.trace_policy = trace_policy self.is_error_handler = is_error_handler self.error_handler_node = error_handler_node if subgraphs is not None: @@ -222,14 +227,23 @@ class PregelNode: def node(self) -> Runnable[Any, Any] | None: """Get a runnable that combines `bound` and `writers`.""" writers = self.flat_writers + trace_inputs = self.trace_policy.process_inputs if self.trace_policy else None + trace_outputs = self.trace_policy.process_outputs if self.trace_policy else None if self.bound is DEFAULT_BOUND and not writers: return None elif self.bound is DEFAULT_BOUND and len(writers) == 1: return writers[0] elif self.bound is DEFAULT_BOUND: - return RunnableSeq(*writers) + return RunnableSeq( + *writers, trace_inputs=trace_inputs, trace_outputs=trace_outputs + ) elif writers: - return RunnableSeq(self.bound, *writers) + return RunnableSeq( + self.bound, + *writers, + trace_inputs=trace_inputs, + trace_outputs=trace_outputs, + ) else: return self.bound diff --git a/libs/langgraph/langgraph/pregel/_utils.py b/libs/langgraph/langgraph/pregel/_utils.py index f7f91e08b..dabc309e0 100644 --- a/libs/langgraph/langgraph/pregel/_utils.py +++ b/libs/langgraph/langgraph/pregel/_utils.py @@ -1,11 +1,10 @@ from __future__ import annotations -import ast -import inspect +import dis import re -import textwrap from collections.abc import Callable, Sequence from functools import partial +from types import CodeType, FunctionType from typing import Any from langchain_core.runnables import ( @@ -17,7 +16,6 @@ from langchain_core.runnables import ( from langchain_core.runnables.base import RunnableBindingBase from langchain_core.runnables.config import run_in_executor from langgraph.checkpoint.base import ChannelVersions -from typing_extensions import override from langgraph._internal._runnable import RunnableCallable, RunnableSeq from langgraph._internal._timeout import sync_timeout_unsupported @@ -137,155 +135,87 @@ def validate_timeout_supported(runnable: Runnable, *, name: str) -> None: raise sync_timeout_unsupported(name) +# Values treated as dead ends when deciding whether to walk a function's +# bytecode. A container can hold a graph, but `find_subgraph_pregel` does not +# look inside one, so skipping it costs nothing while that holds. Matched by +# exact type, since a subclass of a builtin can carry attributes. +_LEAF_TYPES = frozenset( + { + int, + float, + complex, + bool, + str, + bytes, + bytearray, + list, + tuple, + dict, + set, + frozenset, + type(None), + } +) + + def get_function_nonlocals(func: Callable) -> list[Any]: - """Get the nonlocal variables accessed by a function. + """Get the values a function reaches from outside its own scope. Args: func: The function to check. Returns: - List[Any]: The nonlocal variables accessed by the function. + Every captured cell value, the globals the function names, and each + value along an attribute path it loads. Over-approximates: a value can + come back without the function reaching it at runtime. """ - try: - code = inspect.getsource(func) - tree = ast.parse(textwrap.dedent(code)) - visitor = FunctionNonLocals() - visitor.visit(tree) - values: list[Any] = [] - closure = ( - inspect.getclosurevars(func.__wrapped__) - if hasattr(func, "__wrapped__") and callable(func.__wrapped__) - else inspect.getclosurevars(func) - ) - candidates = {**closure.globals, **closure.nonlocals} - for k, v in candidates.items(): - if k in visitor.nonlocals: - values.append(v) - for kk in visitor.nonlocals: - if "." in kk and kk.startswith(k): - vv = v - for part in kk.split(".")[1:]: - if vv is None: - break - else: - try: - vv = getattr(vv, part) - except AttributeError: - break - else: - values.append(vv) - except (SyntaxError, TypeError, OSError, SystemError): + func = getattr(func, "__func__", func) # bound method -> function + wrapped = getattr(func, "__wrapped__", None) + if callable(wrapped): + func = getattr(wrapped, "__func__", wrapped) + if not isinstance(func, FunctionType): return [] + code = func.__code__ + cells: dict[str, Any] = {} + for name, cell in zip(code.co_freevars, func.__closure__ or ()): + try: + cells[name] = cell.cell_contents + except ValueError: + continue # empty cell: a recursive def not yet bound + + # Every captured value counts, referenced or not: over-declaring costs an + # introspection entry, under-declaring drops the subgraph's checkpoints and + # stream events. Checking each cell against the bytecode would cost more and + # only trade the cheap error for the expensive one. + values: list[Any] = list(cells.values()) + global_ns = func.__globals__ + globals_ = {name: global_ns[name] for name in code.co_names if name in global_ns} + if all(type(v) in _LEAF_TYPES for v in (*cells.values(), *globals_.values())): + return values + + # Nested code objects hold the references made by inner defs, lambdas and + # comprehensions, which resolve against the namespaces gathered above. + codes = [code] + for c in codes: + codes.extend(k for k in c.co_consts if isinstance(k, CodeType)) + value: Any = None + for instruction in dis.get_instructions(c): + opname = instruction.opname + if opname == "LOAD_GLOBAL": + value = globals_.get(instruction.argval) + elif opname == "LOAD_DEREF": + value = cells.get(instruction.argval) + elif opname in ("LOAD_ATTR", "LOAD_METHOD"): + value = getattr(value, instruction.argval, None) + else: + value = None # anything else ends the chain: `a, b.c` is not `a.c` + continue + if value is not None: + values.append(value) return values -class FunctionNonLocals(ast.NodeVisitor): - """Get the nonlocal variables accessed of a function.""" - - def __init__(self) -> None: - self.nonlocals: set[str] = set() - - @override - def visit_FunctionDef(self, node: ast.FunctionDef) -> Any: - """Visit a function definition. - - Args: - node: The node to visit. - - Returns: - Any: The result of the visit. - """ - visitor = NonLocals() - visitor.visit(node) - self.nonlocals.update(visitor.loads - visitor.stores) - - @override - def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> Any: - """Visit an async function definition. - - Args: - node: The node to visit. - - Returns: - Any: The result of the visit. - """ - visitor = NonLocals() - visitor.visit(node) - self.nonlocals.update(visitor.loads - visitor.stores) - - @override - def visit_Lambda(self, node: ast.Lambda) -> Any: - """Visit a lambda function. - - Args: - node: The node to visit. - - Returns: - Any: The result of the visit. - """ - visitor = NonLocals() - visitor.visit(node) - self.nonlocals.update(visitor.loads - visitor.stores) - - -class NonLocals(ast.NodeVisitor): - """Get nonlocal variables accessed.""" - - def __init__(self) -> None: - self.loads: set[str] = set() - self.stores: set[str] = set() - - @override - def visit_Name(self, node: ast.Name) -> Any: - """Visit a name node. - - Args: - node: The node to visit. - - Returns: - Any: The result of the visit. - """ - if isinstance(node.ctx, ast.Load): - self.loads.add(node.id) - elif isinstance(node.ctx, ast.Store): - self.stores.add(node.id) - - @override - def visit_Attribute(self, node: ast.Attribute) -> Any: - """Visit an attribute node. - - Args: - node: The node to visit. - - Returns: - Any: The result of the visit. - """ - if isinstance(node.ctx, ast.Load): - parent = node.value - attr_expr = node.attr - while isinstance(parent, ast.Attribute): - attr_expr = parent.attr + "." + attr_expr - parent = parent.value - if isinstance(parent, ast.Name): - self.loads.add(parent.id + "." + attr_expr) - self.loads.discard(parent.id) - elif isinstance(parent, ast.Call): - if isinstance(parent.func, ast.Name): - self.loads.add(parent.func.id) - else: - parent = parent.func - attr_expr = "" - while isinstance(parent, ast.Attribute): - if attr_expr: - attr_expr = parent.attr + "." + attr_expr - else: - attr_expr = parent.attr - parent = parent.value - if isinstance(parent, ast.Name): - self.loads.add(parent.id + "." + attr_expr) - - def is_xxh3_128_hexdigest(value: str) -> bool: """Check if the given string matches the format of xxh3_128_hexdigest.""" return bool(re.fullmatch(r"[0-9a-f]{32}", value)) diff --git a/libs/langgraph/langgraph/types.py b/libs/langgraph/langgraph/types.py index ac9aa9b00..2fcf5bef9 100644 --- a/libs/langgraph/langgraph/types.py +++ b/libs/langgraph/langgraph/types.py @@ -70,6 +70,8 @@ __all__ = ( "RetryPolicy", "TimeoutPolicy", "CachePolicy", + "TracePolicy", + "omit_payload", "Interrupt", "StateUpdate", "PregelTask", @@ -527,6 +529,44 @@ class CachePolicy(Generic[KeyFuncT]): """Time to live for the cache entry in seconds. If `None`, the entry never expires.""" +@dataclass(**_DC_KWARGS) +class TracePolicy: + """Configuration for how a node's run is traced. + + Scope: this only transforms what the node's *own* run records. Child runs created + by a traced `bound` runnable and the root graph run are not affected. Plain + function nodes are traced with `trace=False`, so they have no such child runs. + + Not intended to redact secrets. To redact inputs/outputs across all runs + (children included), use the LangSmith client's + `hide_inputs`/`hide_outputs`/`anonymizer` instead. + + Each processor receives the node's raw input/output value (not a normalized + kwargs dict) and returns the value to record. + """ + + process_inputs: Callable[[Any], Any] | None = None + """Optional callable to transform the node's input before it is recorded on the + node's trace run. Can be used to omit or summarize large payloads + (e.g. message history). Not intended to affect the value passed to the node; avoid + mutating arguments in place.""" + + process_outputs: Callable[[Any], Any] | None = None + """Optional callable to transform the node's output before it is recorded on the + node's trace run. Can be used to omit or summarize large payloads + (e.g. message history). Not intended to affect the value returned by the node; avoid + mutating arguments in place.""" + + +def omit_payload(_value: Any) -> dict[str, Any]: + """`TracePolicy` helper that records an empty payload, dropping the value entirely. + + Use as `process_inputs` and/or `process_outputs` on a `TracePolicy` to keep a node's + span and its timing while omitting its inputs/outputs from the trace. + """ + return {} + + _DEFAULT_INTERRUPT_ID = "placeholder-id" diff --git a/libs/langgraph/pyproject.toml b/libs/langgraph/pyproject.toml index fba4ddeed..e3d95cfec 100644 --- a/libs/langgraph/pyproject.toml +++ b/libs/langgraph/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "langgraph" -version = "1.2.10" +version = "1.2.11" description = "Building stateful, multi-actor applications with LLMs" authors = [] requires-python = ">=3.10" @@ -89,8 +89,12 @@ langgraph-sdk = { path = "../sdk-py", editable = true } langgraph-cli = { path = "../cli", editable = true } [tool.ruff] -lint.select = [ "E", "F", "I", "TID251", "UP" ] +lint.select = [ "E", "F", "I", "PLC0415", "RUF100", "TID251", "UP" ] lint.ignore = [ "E501" ] +# PLC0415 (import-outside-top-level) is enforced in tests only. Library code +# still has deferred imports that have not been reviewed, so it stays exempt +# for now. +lint.per-file-ignores = { "langgraph/**" = ["PLC0415"] } line-length = 88 indent-width = 4 extend-include = ["*.ipynb"] diff --git a/libs/langgraph/tests/memory_assert.py b/libs/langgraph/tests/memory_assert.py index d9ca4904c..3987d422b 100644 --- a/libs/langgraph/tests/memory_assert.py +++ b/libs/langgraph/tests/memory_assert.py @@ -1,5 +1,6 @@ import os import tempfile +import time from collections import defaultdict from functools import partial from typing import Any @@ -73,8 +74,6 @@ class MemorySaverAssertImmutable(InMemorySaver): new_versions: ChannelVersions, ) -> None: if self.put_sleep: - import time - time.sleep(self.put_sleep) # assert checkpoint hasn't been modified since last written thread_id = config["configurable"]["thread_id"] diff --git a/libs/langgraph/tests/test_channels.py b/libs/langgraph/tests/test_channels.py index 0e7e512d1..1e4066c4c 100644 --- a/libs/langgraph/tests/test_channels.py +++ b/libs/langgraph/tests/test_channels.py @@ -2,14 +2,16 @@ import operator from collections.abc import Sequence from typing import Annotated +import orjson import pytest from langchain_core.messages import AIMessage, HumanMessage, RemoveMessage from langgraph.checkpoint.memory import InMemorySaver from langgraph.checkpoint.serde.types import _DeltaSnapshot from typing_extensions import NotRequired, TypedDict +from langgraph._internal._constants import OVERWRITE from langgraph._internal._typing import MISSING -from langgraph.channels.binop import BinaryOperatorAggregate +from langgraph.channels.binop import BinaryOperatorAggregate, _get_overwrite from langgraph.channels.delta import DeltaChannel from langgraph.channels.last_value import LastValue from langgraph.channels.topic import Topic @@ -194,10 +196,6 @@ def test_overwrite_dataclass_form_survives_json_roundtrip() -> None: ...}`) is indistinguishable from a literal channel value, and downstream reducers raise `MESSAGE_COERCION_FAILURE` (or similar) on read. """ - import orjson - - from langgraph._internal._constants import OVERWRITE - from langgraph.channels.binop import _get_overwrite ow = Overwrite(value=[HumanMessage(content="new", id="h2")]) erased = orjson.loads(orjson.dumps(ow, default=lambda o: o.model_dump())) @@ -213,8 +211,6 @@ def test_overwrite_sentinel_dict_still_recognised() -> None: """The pre-existing `{"__overwrite__": value}` dict form continues to be recognised. This is the canonical sentinel emitted by producers that do not have an `Overwrite` dataclass available.""" - from langgraph._internal._constants import OVERWRITE - from langgraph.channels.binop import _get_overwrite is_overwrite, value = _get_overwrite({OVERWRITE: ["b"]}) assert is_overwrite @@ -224,7 +220,6 @@ def test_overwrite_sentinel_dict_still_recognised() -> None: def test_overwrite_non_matching_dict_not_recognised() -> None: """Dicts that resemble the erased shape but do not carry the `__overwrite__` discriminator must not be misclassified as overwrites.""" - from langgraph.channels.binop import _get_overwrite assert _get_overwrite({"value": ["b"]}) == (False, None) assert _get_overwrite({"type": "human", "value": "hi"}) == (False, None) diff --git a/libs/langgraph/tests/test_config_async.py b/libs/langgraph/tests/test_config_async.py index 6ad51c5b2..13f922534 100644 --- a/libs/langgraph/tests/test_config_async.py +++ b/libs/langgraph/tests/test_config_async.py @@ -24,7 +24,7 @@ class _TrackingCallback(BaseCallbackHandler): def __init__(self) -> None: self.called = False - def on_chain_start(self, *args, **kwargs) -> None: # noqa: ANN002, ANN003 + def on_chain_start(self, *args, **kwargs) -> None: self.called = True @@ -55,7 +55,7 @@ async def test_with_config_configurable_preserved_on_invoke() -> None: builder = StateGraph(dict) captured: dict = {} - def node(state, config): # noqa: ANN001 + def node(state, config): captured.update(config.get("configurable") or {}) return state @@ -79,7 +79,7 @@ async def test_with_config_metadata_preserved_on_invoke() -> None: builder = StateGraph(dict) captured: dict = {} - def node(state, config): # noqa: ANN001 + def node(state, config): captured.update(config.get("metadata") or {}) return state @@ -104,7 +104,7 @@ async def test_with_config_tags_preserved_on_invoke() -> None: builder = StateGraph(dict) captured: list = [] - def node(state, config): # noqa: ANN001 + def node(state, config): captured.extend(config.get("tags") or []) return state diff --git a/libs/langgraph/tests/test_delta_channel_benchmark.py b/libs/langgraph/tests/test_delta_channel_benchmark.py index 0e45ffdaf..41ae6b7d2 100644 --- a/libs/langgraph/tests/test_delta_channel_benchmark.py +++ b/libs/langgraph/tests/test_delta_channel_benchmark.py @@ -220,7 +220,7 @@ def _checkpointers() -> list[tuple[str, Any]]: result: list[tuple[str, Any]] = [("InMemory", None)] if _POSTGRES_AVAILABLE: try: - import psycopg + import psycopg # noqa: PLC0415 psycopg.connect(_POSTGRES_URI).close() result.append(("Postgres", "postgres")) diff --git a/libs/langgraph/tests/test_delta_channel_migration.py b/libs/langgraph/tests/test_delta_channel_migration.py index 50e21ffcb..ec183239d 100644 --- a/libs/langgraph/tests/test_delta_channel_migration.py +++ b/libs/langgraph/tests/test_delta_channel_migration.py @@ -616,3 +616,117 @@ async def test_add_messages_to_delta_migration_preserves_message_history_async() assert [m.id for m in snap.values["messages"]] == ["h1", "a1"], ( f"async tip hydration mismatch: got {[m.id for m in snap.values['messages']]}" ) + + +# --------------------------------------------------------------------------- +# 8. First post-migration write, read back cold (regression for #8384) +# +# The migration boundary produces a checkpoint that carries BOTH a pre-delta +# plain-value blob AND the pending write that produced its (delta-era) child. +# That write is not subsumed by the blob — the blob is the value ENTERING that +# checkpoint. A saver whose ancestor walk skips the seed checkpoint's own +# writes silently drops the first post-migration write. +# +# The failure is invisible to the live `invoke` return value (computed +# in-memory before persistence), so these tests must assert on a COLD read. +# It is also invisible at `snapshot_frequency=1`, where every write is its own +# snapshot boundary and the walk never terminates on a plain value — hence the +# explicit default-frequency coverage. +# --------------------------------------------------------------------------- + + +def test_first_post_migration_write_survives_cold_read() -> None: + """One non-snapshotting write after migrating a thread to `DeltaChannel` + must still be present when the state is read back from the checkpointer. + + Regression for #8384: `invoke` returned the correct value while + `get_state` dropped the write permanently. + """ + + checkpointer = InMemorySaver() + config = {"configurable": {"thread_id": "first-post-migration"}} + + binop = _binop_graph(checkpointer) + binop.invoke({"items": ["a"]}, config) + + delta = _delta_graph(checkpointer) + live = delta.invoke({"items": ["b"]}, config) + assert list(live["items"]) == ["a", "b"], "live invoke lost the write" + + cold = delta.get_state(config) + assert list(cold.values["items"]) == ["a", "b"], ( + "first post-migration write dropped on cold read: " + f"got {list(cold.values['items'])}" + ) + + +async def test_first_post_migration_write_survives_cold_read_async() -> None: + """Async variant of the #8384 regression.""" + + checkpointer = InMemorySaver() + config = {"configurable": {"thread_id": "first-post-migration-async"}} + + binop = _binop_graph(checkpointer) + await binop.ainvoke({"items": ["a"]}, config) + + delta = _delta_graph(checkpointer) + live = await delta.ainvoke({"items": ["b"]}, config) + assert list(live["items"]) == ["a", "b"], "live ainvoke lost the write" + + cold = await delta.aget_state(config) + assert list(cold.values["items"]) == ["a", "b"], ( + "first post-migration write dropped on cold read: " + f"got {list(cold.values['items'])}" + ) + + +def test_post_migration_writes_match_base_saver_fallback() -> None: + """Parity across the migration boundary WITH post-migration writes. + + `test_base_saver_fallback_matches_optimized_override` only reads a + pre-migration chain, so the optimized override and the reference walk + never disagree there. Driving writes after the migration is what + separates them. + """ + + def _run(saver: Any, thread: str) -> list[tuple[Any, list]]: + config = {"configurable": {"thread_id": thread}} + _drive(_binop_graph(saver), config, "u", 2) + delta = _delta_graph(saver) + _drive(delta, config, "d", 3) + return [ + (s.next, list(s.values.get("items", []))) + for s in delta.get_state_history(config) + ] + + fast = _run(InMemorySaver(), "fast") + slow = _run(_ThirdPartyStyleSaver(), "slow") + + assert fast == slow, ( + "optimized override diverges from the base-saver fallback once " + f"post-migration writes exist; fast={fast}, slow={slow}" + ) + # Guard the assertion above against both paths being wrong in the same way. + assert fast[0][1] == ["u0", "u1", "d0", "d1", "d2"], ( + f"unexpected accumulated state: {fast[0][1]}" + ) + + +def test_add_messages_migration_keeps_first_post_migration_message() -> None: + """The `add_messages` -> `DeltaChannel` path is the one Deep Agents takes; + dropping the first post-migration write loses a real user message. + """ + + checkpointer = InMemorySaver() + config = {"configurable": {"thread_id": "add-messages-first-write"}} + + pre_graph = _add_messages_graph(checkpointer) + pre_graph.invoke({"messages": [HumanMessage(content="hello", id="h1")]}, config) + + delta_graph = _delta_messages_graph(checkpointer) + delta_graph.invoke({"messages": [HumanMessage(content="second", id="h2")]}, config) + + ids = [m.id for m in delta_graph.get_state(config).values["messages"]] + # h1 is the pre-migration seed, h2 the write that was being dropped; both + # have to survive, and in order. + assert ids == ["h1", "h2"], f"expected ['h1', 'h2'], got {ids}" diff --git a/libs/langgraph/tests/test_delta_channel_update_state.py b/libs/langgraph/tests/test_delta_channel_update_state.py index a0930496d..da6db40f6 100644 --- a/libs/langgraph/tests/test_delta_channel_update_state.py +++ b/libs/langgraph/tests/test_delta_channel_update_state.py @@ -27,6 +27,7 @@ from typing_extensions import TypedDict from langgraph.channels.delta import DeltaChannel from langgraph.graph import START, StateGraph from langgraph.graph.message import _messages_delta_reducer +from langgraph.types import StateUpdate pytestmark = pytest.mark.anyio @@ -277,7 +278,6 @@ def test_bulk_update_state_multi_task_per_superstep_delta_channel() -> None: different `StateUpdate`s targeting the same node — otherwise both share the deterministic interrupt-derived id and collide in the saver. """ - from langgraph.types import StateUpdate saver = InMemorySaver() graph = _build_graph(saver) diff --git a/libs/langgraph/tests/test_deprecation.py b/libs/langgraph/tests/test_deprecation.py index 54e1e3812..e57d57283 100644 --- a/libs/langgraph/tests/test_deprecation.py +++ b/libs/langgraph/tests/test_deprecation.py @@ -88,13 +88,13 @@ def test_constants_deprecation() -> None: LangGraphDeprecatedSinceV10, match="Importing Send from langgraph.constants is deprecated. Please use 'from langgraph.types import Send' instead.", ): - from langgraph.constants import Send # noqa: F401 + from langgraph.constants import Send # noqa: PLC0415, F401 with pytest.warns( LangGraphDeprecatedSinceV10, match="Importing Interrupt from langgraph.constants is deprecated. Please use 'from langgraph.types import Interrupt' instead.", ): - from langgraph.constants import Interrupt # noqa: F401 + from langgraph.constants import Interrupt # noqa: PLC0415, F401 def test_pregel_types_deprecation() -> None: @@ -102,7 +102,7 @@ def test_pregel_types_deprecation() -> None: LangGraphDeprecatedSinceV10, match="Importing from langgraph.pregel.types is deprecated. Please use 'from langgraph.types import ...' instead.", ): - from langgraph.pregel.types import StateSnapshot # noqa: F401 + from langgraph.pregel.types import StateSnapshot # noqa: PLC0415, F401 def test_config_schema_deprecation() -> None: @@ -195,7 +195,7 @@ def test_deprecated_import() -> None: LangGraphDeprecatedSinceV10, match="Importing PREVIOUS from langgraph.constants is deprecated. This constant is now private and should not be used directly.", ): - from langgraph.constants import PREVIOUS # noqa: F401 + from langgraph.constants import PREVIOUS # noqa: PLC0415, F401 @pytest.mark.filterwarnings( diff --git a/libs/langgraph/tests/test_graph_callbacks.py b/libs/langgraph/tests/test_graph_callbacks.py index 09c3ab417..941cf415d 100644 --- a/libs/langgraph/tests/test_graph_callbacks.py +++ b/libs/langgraph/tests/test_graph_callbacks.py @@ -13,6 +13,7 @@ from langgraph.callbacks import ( GraphCallbackHandler, GraphInterruptEvent, GraphResumeEvent, + _GraphCallbackManager, ) from langgraph.graph import START, StateGraph from langgraph.types import Command, Interrupt, interrupt @@ -286,7 +287,6 @@ def test_non_graph_handler_via_add_handler_does_not_crash() -> None: GraphCallbackHandler. They must be silently accepted — graph lifecycle events will simply not be dispatched to them. """ - from langgraph.callbacks import _GraphCallbackManager manager = _GraphCallbackManager() plain_handler = _LangChainCustomEventHandler() diff --git a/libs/langgraph/tests/test_large_cases.py b/libs/langgraph/tests/test_large_cases.py index 890866924..5d831a071 100644 --- a/libs/langgraph/tests/test_large_cases.py +++ b/libs/langgraph/tests/test_large_cases.py @@ -2,11 +2,26 @@ import json import operator import re import time +from copy import deepcopy from dataclasses import replace from typing import Annotated, Any, Literal, cast import pytest -from langchain_core.messages import AIMessage, AnyMessage, ToolCall +from langchain_core.callbacks import CallbackManagerForLLMRun +from langchain_core.language_models.fake import FakeStreamingListLLM +from langchain_core.language_models.fake_chat_models import ( + FakeMessagesListChatModel, +) +from langchain_core.messages import ( + AIMessage, + AnyMessage, + BaseMessage, + HumanMessage, + ToolCall, + ToolMessage, +) +from langchain_core.outputs import ChatGeneration, ChatResult +from langchain_core.prompts import PromptTemplate from langchain_core.runnables import RunnableConfig, RunnableMap, RunnablePick from langchain_core.tools import tool from langchain_core.version import VERSION as LANGCHAIN_CORE_VERSION @@ -484,9 +499,6 @@ def test_conditional_state_graph( snapshot: SnapshotAssertion, sync_checkpointer: BaseCheckpointSaver, ) -> None: - from langchain_core.language_models.fake import FakeStreamingListLLM - from langchain_core.prompts import PromptTemplate - from langchain_core.tools import tool class AgentState(TypedDict, total=False): input: Annotated[str, UntrackedValue] @@ -1261,8 +1273,6 @@ def test_conditional_state_graph( def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None: - from langchain_core.messages import AIMessage, HumanMessage - from langchain_core.tools import tool @tool() def search_api(query: str) -> str: @@ -1626,17 +1636,6 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None: def test_state_graph_packets( sync_checkpointer: BaseCheckpointSaver, mocker: MockerFixture ) -> None: - from langchain_core.language_models.fake_chat_models import ( - FakeMessagesListChatModel, - ) - from langchain_core.messages import ( - AIMessage, - BaseMessage, - HumanMessage, - ToolCall, - ToolMessage, - ) - from langchain_core.tools import tool class AgentState(TypedDict): messages: Annotated[list[BaseMessage], add_messages] @@ -2381,15 +2380,6 @@ def test_message_graph( deterministic_uuids: MockerFixture, sync_checkpointer: BaseCheckpointSaver, ) -> None: - from copy import deepcopy - - from langchain_core.callbacks import CallbackManagerForLLMRun - from langchain_core.language_models.fake_chat_models import ( - FakeMessagesListChatModel, - ) - from langchain_core.messages import AIMessage, BaseMessage, HumanMessage - from langchain_core.outputs import ChatGeneration, ChatResult - from langchain_core.tools import tool class FakeFunctionChatModel(FakeMessagesListChatModel): def bind_functions(self, functions: list): @@ -3099,20 +3089,6 @@ def test_root_graph( deterministic_uuids: MockerFixture, sync_checkpointer: BaseCheckpointSaver, ) -> None: - from copy import deepcopy - - from langchain_core.callbacks import CallbackManagerForLLMRun - from langchain_core.language_models.fake_chat_models import ( - FakeMessagesListChatModel, - ) - from langchain_core.messages import ( - AIMessage, - BaseMessage, - HumanMessage, - ToolMessage, - ) - from langchain_core.outputs import ChatGeneration, ChatResult - from langchain_core.tools import tool class FakeFunctionChatModel(FakeMessagesListChatModel): def bind_functions(self, functions: list): @@ -5837,7 +5813,6 @@ def test_send_to_nested_graphs(sync_checkpointer: BaseCheckpointSaver) -> None: def test_send_react_interrupt( sync_checkpointer: BaseCheckpointSaver, ) -> None: - from langchain_core.messages import AIMessage, HumanMessage, ToolCall, ToolMessage ai_message = AIMessage( "", @@ -6228,7 +6203,6 @@ def test_send_react_interrupt( def test_send_react_interrupt_control( sync_checkpointer: BaseCheckpointSaver, snapshot: SnapshotAssertion ) -> None: - from langchain_core.messages import AIMessage, HumanMessage, ToolCall, ToolMessage ai_message = AIMessage( "", @@ -6455,9 +6429,6 @@ def test_send_react_interrupt_control( def test_weather_subgraph( sync_checkpointer: BaseCheckpointSaver, snapshot: SnapshotAssertion ) -> None: - from langchain_core.language_models.fake_chat_models import ( - FakeMessagesListChatModel, - ) # setup subgraph diff --git a/libs/langgraph/tests/test_large_cases_async.py b/libs/langgraph/tests/test_large_cases_async.py index 1db144aa4..d90ed5b14 100644 --- a/libs/langgraph/tests/test_large_cases_async.py +++ b/libs/langgraph/tests/test_large_cases_async.py @@ -9,8 +9,22 @@ from typing import ( ) import pytest -from langchain_core.messages import AnyMessage, ToolCall +from langchain_core.agents import AgentAction, AgentFinish +from langchain_core.language_models.fake import FakeStreamingListLLM +from langchain_core.language_models.fake_chat_models import ( + FakeMessagesListChatModel, +) +from langchain_core.messages import ( + AIMessage, + AnyMessage, + BaseMessage, + HumanMessage, + ToolCall, + ToolMessage, +) +from langchain_core.prompts import PromptTemplate from langchain_core.runnables import RunnableConfig, RunnablePick +from langchain_core.tools import tool from langchain_core.version import VERSION as LANGCHAIN_CORE_VERSION from langgraph.checkpoint.base import BaseCheckpointSaver from langgraph.prebuilt.chat_agent_executor import create_react_agent @@ -22,6 +36,7 @@ from langgraph._internal._constants import PULL, PUSH from langgraph.channels.last_value import LastValue from langgraph.channels.untracked_value import UntrackedValue from langgraph.constants import END, START +from langgraph.graph import MessagesState from langgraph.graph.message import add_messages from langgraph.graph.state import StateGraph from langgraph.pregel import NodeBuilder, Pregel @@ -479,10 +494,6 @@ async def test_fork_always_re_runs_nodes( async def test_conditional_graph_state(async_checkpointer: BaseCheckpointSaver) -> None: - from langchain_core.agents import AgentAction, AgentFinish - from langchain_core.language_models.fake import FakeStreamingListLLM - from langchain_core.prompts import PromptTemplate - from langchain_core.tools import tool class AgentState(TypedDict): input: Annotated[str, UntrackedValue] @@ -1017,8 +1028,6 @@ async def test_conditional_graph_state(async_checkpointer: BaseCheckpointSaver) async def test_prebuilt_tool_chat() -> None: - from langchain_core.messages import AIMessage, HumanMessage - from langchain_core.tools import tool model = FakeChatModel( messages=[ @@ -1358,16 +1367,6 @@ async def test_prebuilt_tool_chat() -> None: async def test_state_graph_packets(async_checkpointer: BaseCheckpointSaver) -> None: - from langchain_core.language_models.fake_chat_models import ( - FakeMessagesListChatModel, - ) - from langchain_core.messages import ( - AIMessage, - BaseMessage, - HumanMessage, - ToolMessage, - ) - from langchain_core.tools import tool class AgentState(TypedDict): messages: Annotated[list[BaseMessage], add_messages] @@ -2072,11 +2071,6 @@ async def test_state_graph_packets(async_checkpointer: BaseCheckpointSaver) -> N async def test_message_graph(async_checkpointer: BaseCheckpointSaver) -> None: - from langchain_core.language_models.fake_chat_models import ( - FakeMessagesListChatModel, - ) - from langchain_core.messages import AIMessage, HumanMessage - from langchain_core.tools import tool class FakeFunctionChatModel(FakeMessagesListChatModel): def bind_functions(self, functions: list): @@ -3537,13 +3531,6 @@ async def test_send_to_nested_graphs(async_checkpointer: BaseCheckpointSaver) -> async def test_weather_subgraph( async_checkpointer: BaseCheckpointSaver, ) -> None: - from langchain_core.language_models.fake_chat_models import ( - FakeMessagesListChatModel, - ) - from langchain_core.messages import AIMessage, ToolCall - from langchain_core.tools import tool - - from langgraph.graph import MessagesState # setup subgraph diff --git a/libs/langgraph/tests/test_pregel.py b/libs/langgraph/tests/test_pregel.py index 58dd294f4..c166c5837 100644 --- a/libs/langgraph/tests/test_pregel.py +++ b/libs/langgraph/tests/test_pregel.py @@ -4,25 +4,39 @@ import gc import json import logging import operator +import random import threading import time import uuid -from collections import Counter, deque +from collections import Counter, defaultdict, deque from collections.abc import Sequence from concurrent.futures import ThreadPoolExecutor from dataclasses import dataclass, field from random import randrange from typing import Annotated, Any, Literal, get_type_hints +from unittest.mock import patch import pytest from langchain_core.language_models import GenericFakeChatModel -from langchain_core.messages import AIMessage, AnyMessage, HumanMessage, RemoveMessage +from langchain_core.language_models.fake import FakeStreamingListLLM +from langchain_core.language_models.fake_chat_models import ( + FakeMessagesListChatModel, +) +from langchain_core.messages import ( + AIMessage, + AnyMessage, + BaseMessage, + HumanMessage, + RemoveMessage, +) +from langchain_core.prompts import ChatPromptTemplate, PromptTemplate from langchain_core.runnables import ( RunnableConfig, RunnableLambda, RunnablePassthrough, ) from langchain_core.runnables.graph import Edge +from langchain_core.tools import tool from langchain_core.version import VERSION as LANGCHAIN_CORE_VERSION from langgraph.cache.base import BaseCache from langgraph.checkpoint.base import ( @@ -56,8 +70,9 @@ from langgraph.pregel import ( NodeBuilder, Pregel, ) -from langgraph.pregel._loop import SyncPregelLoop +from langgraph.pregel._loop import PregelLoop, SyncPregelLoop from langgraph.pregel._runner import PregelRunner +from langgraph.runtime import RunControl from langgraph.types import ( CachePolicy, Command, @@ -125,7 +140,6 @@ def test_graph_validation() -> None: def test_request_drain_allows_inflight_call_scheduling( sync_checkpointer: BaseCheckpointSaver, ) -> None: - from langgraph.runtime import RunControl @task def child(x: int) -> int: @@ -1769,9 +1783,6 @@ def test_conditional_state_graph_with_list_edge_inputs(snapshot: SnapshotAsserti def test_state_graph_w_config_inherited_state_keys(snapshot: SnapshotAssertion) -> None: - from langchain_core.language_models.fake import FakeStreamingListLLM - from langchain_core.prompts import PromptTemplate - from langchain_core.tools import tool class BaseState(TypedDict): input: str @@ -3769,12 +3780,6 @@ def test_checkpoint_metadata(sync_checkpointer: BaseCheckpointSaver) -> None: previous checkpoint config for each step in the run. """ # set up test - from langchain_core.language_models.fake_chat_models import ( - FakeMessagesListChatModel, - ) - from langchain_core.messages import AIMessage, AnyMessage - from langchain_core.prompts import ChatPromptTemplate - from langchain_core.tools import tool # graph state class BaseState(TypedDict): @@ -3940,7 +3945,6 @@ def test_checkpoint_metadata(sync_checkpointer: BaseCheckpointSaver) -> None: def test_remove_message_via_state_update( sync_checkpointer: BaseCheckpointSaver, ) -> None: - from langchain_core.messages import AIMessage, HumanMessage, RemoveMessage workflow = StateGraph(state_schema=Annotated[list[AnyMessage], add_messages]) # type: ignore[arg-type] workflow.add_node( @@ -3973,7 +3977,6 @@ def test_remove_message_via_state_update( def test_remove_message_from_node(): - from langchain_core.messages import AIMessage, HumanMessage, RemoveMessage workflow = StateGraph(state_schema=Annotated[list[AnyMessage], add_messages]) # type: ignore[arg-type] workflow.add_node( @@ -3999,7 +4002,6 @@ def test_remove_message_from_node(): def test_xray_lance(snapshot: SnapshotAssertion): - from langchain_core.messages import AnyMessage, HumanMessage class Analyst(BaseModel): affiliation: str = Field( @@ -4483,7 +4485,6 @@ def test_debug_subgraphs( def test_debug_nested_subgraphs( sync_checkpointer: BaseCheckpointSaver, durability: Durability ): - from collections import defaultdict class State(TypedDict): messages: Annotated[list[str], operator.add] @@ -4743,8 +4744,6 @@ def test_runnable_passthrough_node_graph() -> None: def test_parent_command( sync_checkpointer: BaseCheckpointSaver, subgraph_persist: bool ) -> None: - from langchain_core.messages import BaseMessage - from langchain_core.tools import tool @tool(return_direct=True) def get_user_name() -> Command: @@ -5164,7 +5163,6 @@ def test_command_with_static_breakpoints( def test_multistep_plan(sync_checkpointer: BaseCheckpointSaver): - from langchain_core.messages import AnyMessage class State(TypedDict, total=False): plan: list[str | list[str]] @@ -5910,9 +5908,6 @@ def test_no_redundant_put_writes_for_cached_task( sync_checkpointer: BaseCheckpointSaver, ) -> None: """Cached @tasks on resume must not trigger redundant put_writes.""" - from unittest.mock import patch - - from langgraph.pregel._loop import PregelLoop @task def setup(x: int) -> int: @@ -6975,7 +6970,6 @@ def test_configurable_propagates_to_stream_metadata() -> None: def test_stream_mode_messages_command() -> None: - from langchain_core.messages import HumanMessage def my_node(state): return {"messages": HumanMessage(content="foo")} @@ -7243,7 +7237,6 @@ def test_get_stream_writer() -> None: def test_stream_messages_dedupe_inputs() -> None: - from langchain_core.messages import AIMessage def call_model(state): return {"messages": AIMessage("hi", id="1")} @@ -7281,7 +7274,6 @@ def test_stream_messages_dedupe_inputs() -> None: def test_stream_messages_dedupe_state(sync_checkpointer: BaseCheckpointSaver) -> None: - from langchain_core.messages import AIMessage to_emit = [AIMessage("bye", id="1"), AIMessage("bye again", id="2")] @@ -8253,7 +8245,6 @@ def test_get_graph_loop(snapshot: SnapshotAssertion) -> None: def test_get_graph_self_loop(snapshot: SnapshotAssertion) -> None: - import random subgraph_builder = StateGraph(MessagesState) subgraph_builder.add_node("agent", lambda x: x) diff --git a/libs/langgraph/tests/test_pregel_async.py b/libs/langgraph/tests/test_pregel_async.py index 1a1a4734b..150801ffc 100644 --- a/libs/langgraph/tests/test_pregel_async.py +++ b/libs/langgraph/tests/test_pregel_async.py @@ -7,7 +7,7 @@ import operator import random import sys import uuid -from collections import Counter, deque +from collections import Counter, defaultdict, deque from dataclasses import replace from time import perf_counter from typing import ( @@ -21,8 +21,20 @@ from uuid import UUID import pytest from langchain_core.language_models import GenericFakeChatModel -from langchain_core.messages import HumanMessage +from langchain_core.language_models.fake_chat_models import ( + FakeMessagesListChatModel, +) +from langchain_core.messages import ( + AIMessage, + AnyMessage, + BaseMessage, + HumanMessage, + ToolCall, + ToolMessage, +) +from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnableConfig, RunnableLambda, RunnablePassthrough +from langchain_core.tools import tool from langchain_core.utils.aiter import aclosing from langchain_core.version import VERSION as LANGCHAIN_CORE_VERSION from langgraph.cache.base import BaseCache @@ -45,6 +57,7 @@ from typing_extensions import NotRequired, TypedDict from langgraph._internal._constants import CONFIG_KEY_NODE_FINISHED, ERROR, PULL from langgraph._internal._queue import AsyncQueue from langgraph.channels.binop import BinaryOperatorAggregate +from langgraph.channels.delta import DeltaChannel from langgraph.channels.last_value import LastValue from langgraph.channels.topic import Topic from langgraph.errors import ( @@ -55,10 +68,11 @@ from langgraph.errors import ( ) from langgraph.func import entrypoint, task from langgraph.graph import END, START, StateGraph -from langgraph.graph.message import MessagesState, add_messages +from langgraph.graph.message import MessagesState, _messages_delta_reducer, add_messages from langgraph.pregel import NodeBuilder, Pregel -from langgraph.pregel._loop import AsyncPregelLoop +from langgraph.pregel._loop import AsyncPregelLoop, PregelLoop from langgraph.pregel._runner import PregelRunner +from langgraph.runtime import RunControl from langgraph.types import ( CachePolicy, Command, @@ -222,7 +236,6 @@ async def test_checkpoint_errors() -> None: async def test_request_drain_allows_inflight_acall_scheduling( async_checkpointer: BaseCheckpointSaver, ) -> None: - from langgraph.runtime import RunControl @task async def child(x: int) -> int: @@ -2868,7 +2881,6 @@ async def test_send_dedupe_on_resume( async def test_send_react_interrupt(async_checkpointer: BaseCheckpointSaver) -> None: - from langchain_core.messages import AIMessage, HumanMessage, ToolCall, ToolMessage ai_message = AIMessage( "", @@ -3259,7 +3271,6 @@ async def test_send_react_interrupt(async_checkpointer: BaseCheckpointSaver) -> async def test_send_react_interrupt_control( async_checkpointer: BaseCheckpointSaver, snapshot: SnapshotAssertion ) -> None: - from langchain_core.messages import AIMessage, HumanMessage, ToolCall, ToolMessage ai_message = AIMessage( "", @@ -5538,12 +5549,6 @@ async def test_checkpoint_metadata(async_checkpointer: BaseCheckpointSaver) -> N previous checkpoint config for each step in the run. """ # set up test - from langchain_core.language_models.fake_chat_models import ( - FakeMessagesListChatModel, - ) - from langchain_core.messages import AIMessage, AnyMessage - from langchain_core.prompts import ChatPromptTemplate - from langchain_core.tools import tool # graph state class BaseState(TypedDict): @@ -5944,7 +5949,6 @@ async def test_debug_subgraphs( async def test_debug_nested_subgraphs( async_checkpointer: BaseCheckpointSaver, durability: Durability ) -> None: - from collections import defaultdict class State(TypedDict): messages: Annotated[list[str], operator.add] @@ -6061,8 +6065,6 @@ async def test_debug_nested_subgraphs( async def test_parent_command( async_checkpointer: BaseCheckpointSaver, subgraph_persist: bool ) -> None: - from langchain_core.messages import BaseMessage - from langchain_core.tools import tool @tool(return_direct=True) def get_user_name() -> Command: @@ -6130,10 +6132,6 @@ async def test_parent_command( async def test_delta_channel_durability_exit_stores_snapshot_async() -> None: """DeltaChannel must reload from an async durability='exit' checkpoint.""" - from langchain_core.messages import AIMessage - - from langgraph.channels.delta import DeltaChannel - from langgraph.graph.message import _messages_delta_reducer class State(TypedDict): messages: Annotated[list, DeltaChannel(_messages_delta_reducer)] @@ -6420,7 +6418,6 @@ async def test_command_with_static_breakpoints( async def test_multistep_plan(async_checkpointer: BaseCheckpointSaver) -> None: - from langchain_core.messages import AnyMessage class State(TypedDict, total=False): plan: list[str | list[str]] @@ -6758,7 +6755,6 @@ async def test_multiple_interrupts_functional( async_checkpointer: BaseCheckpointSaver, ) -> None: """Test multiple interrupts with functional API.""" - from langgraph.func import entrypoint, task counter = 0 @@ -7674,7 +7670,6 @@ async def test_configurable_propagates_to_stream_metadata() -> None: async def test_stream_mode_messages_command() -> None: - from langchain_core.messages import HumanMessage async def my_node(state): return {"messages": HumanMessage(content="foo")} @@ -7723,7 +7718,6 @@ async def test_stream_mode_messages_command() -> None: async def test_stream_messages_dedupe_inputs() -> None: - from langchain_core.messages import AIMessage async def call_model(state): return {"messages": AIMessage("hi", id="1")} @@ -7763,7 +7757,6 @@ async def test_stream_messages_dedupe_inputs() -> None: async def test_stream_messages_dedupe_state( async_checkpointer: BaseCheckpointSaver, ) -> None: - from langchain_core.messages import AIMessage to_emit = [AIMessage("bye", id="1"), AIMessage("bye again", id="2")] @@ -8142,9 +8135,6 @@ async def test_no_redundant_put_writes_for_cached_task( async_checkpointer: BaseCheckpointSaver, ) -> None: """Cached @tasks on resume must not trigger redundant put_writes.""" - from unittest.mock import patch - - from langgraph.pregel._loop import PregelLoop @task async def setup(x: int) -> int: @@ -8646,7 +8636,6 @@ async def test_batch_update_as_input( async def test_draw_invalid(): - from langchain_core.messages import BaseMessage class AgentState(TypedDict): messages: Annotated[list[BaseMessage], add_messages] diff --git a/libs/langgraph/tests/test_pydantic.py b/libs/langgraph/tests/test_pydantic.py index f49a02871..062a450fe 100644 --- a/libs/langgraph/tests/test_pydantic.py +++ b/libs/langgraph/tests/test_pydantic.py @@ -4,10 +4,13 @@ import ipaddress import pathlib import re import sys +import typing import uuid from enum import Enum from typing import Annotated, Literal, Optional +import pydantic +import typing_extensions from langgraph.checkpoint.base import BaseCheckpointSaver from pydantic import ( BaseModel, @@ -32,10 +35,6 @@ from tests.any_str import AnyStr def test_is_supported_by_pydantic() -> None: """Test if types are supported by pydantic.""" - import typing - - import pydantic - import typing_extensions class TypedDictExtensions(typing_extensions.TypedDict): x: int diff --git a/libs/langgraph/tests/test_remote_graph.py b/libs/langgraph/tests/test_remote_graph.py index 4edb9325e..8e10e209e 100644 --- a/libs/langgraph/tests/test_remote_graph.py +++ b/libs/langgraph/tests/test_remote_graph.py @@ -10,12 +10,14 @@ from langchain_core.messages import AnyMessage, BaseMessage from langchain_core.runnables import RunnableConfig from langchain_core.runnables.graph import Edge as DrawableEdge from langchain_core.runnables.graph import Node as DrawableNode +from langgraph.checkpoint.memory import InMemorySaver +from langgraph_sdk.client import get_client, get_sync_client from langgraph_sdk.schema import StreamPart from pydantic import BaseModel from typing_extensions import TypedDict from langgraph.errors import GraphInterrupt -from langgraph.graph import StateGraph, add_messages +from langgraph.graph import END, START, MessagesState, StateGraph, add_messages from langgraph.pregel import Pregel from langgraph.pregel.remote import RemoteGraph from langgraph.types import Interrupt, StateSnapshot @@ -1097,10 +1099,6 @@ def test_stream_context_base_model(): ) @pytest.mark.anyio async def test_langgraph_cloud_integration(): - from langgraph.checkpoint.memory import InMemorySaver - from langgraph_sdk.client import get_client, get_sync_client - - from langgraph.graph import END, START, MessagesState, StateGraph # create RemotePregel instance client = get_client(url="http://localhost:8123") diff --git a/libs/langgraph/tests/test_remote_graph_v3.py b/libs/langgraph/tests/test_remote_graph_v3.py index c85594c71..2239b73fb 100644 --- a/libs/langgraph/tests/test_remote_graph_v3.py +++ b/libs/langgraph/tests/test_remote_graph_v3.py @@ -4,6 +4,7 @@ from unittest.mock import AsyncMock, MagicMock import pytest +from langgraph.pregel import remote as remote_mod from langgraph.pregel._remote_run_stream import ( _AsyncRemoteGraphRunStream, _ChannelProjection, @@ -577,7 +578,6 @@ def test_stream_events_v3_strips_checkpoint_keys_from_configurable(): def test_stream_events_v3_merges_tracing_headers_when_distributed_tracing( monkeypatch, ): - from langgraph.pregel import remote as remote_mod sync_client = MagicMock() sync_client.threads.stream.return_value = MagicMock() diff --git a/libs/langgraph/tests/test_retry.py b/libs/langgraph/tests/test_retry.py index fcdde937b..aa48b0812 100644 --- a/libs/langgraph/tests/test_retry.py +++ b/libs/langgraph/tests/test_retry.py @@ -11,7 +11,9 @@ from typing import Annotated, Any from unittest.mock import Mock, patch from uuid import uuid4 +import httpx import pytest +import requests from langchain_core.callbacks import AsyncCallbackManagerForLLMRun, BaseCallbackHandler from langchain_core.language_models.fake_chat_models import GenericFakeChatModel from langchain_core.messages import AIMessage, AIMessageChunk, BaseMessage, HumanMessage @@ -63,6 +65,7 @@ from langgraph.types import ( RetryPolicy, Send, TimeoutPolicy, + interrupt, ) NEEDS_CONTEXTVARS = pytest.mark.skipif( @@ -171,8 +174,6 @@ def test_checkpoint_ns_for_parent_command() -> None: def test_should_retry_default_retry_on(): """Test the default retry_on function.""" - import httpx - import requests # Create a RetryPolicy with default_retry_on policy = RetryPolicy() @@ -2198,7 +2199,6 @@ def test_graph_error_handler_does_not_swallow_interrupt_concurrent(): """When a graph error handler is configured and a node calls interrupt() concurrently with other nodes, the interrupt must still be raised — not silently swallowed.""" - from langgraph.types import interrupt class State(TypedDict): foo: str @@ -2587,8 +2587,6 @@ async def test_set_node_defaults_timeout(): .compile() ) - from langgraph.errors import NodeTimeoutError - with pytest.raises(NodeTimeoutError): await graph.ainvoke({"foo": ""}) diff --git a/libs/langgraph/tests/test_runtime.py b/libs/langgraph/tests/test_runtime.py index f2f2fcc23..0f8cc944b 100644 --- a/libs/langgraph/tests/test_runtime.py +++ b/libs/langgraph/tests/test_runtime.py @@ -6,9 +6,11 @@ from typing import Any import pytest from langgraph.checkpoint.memory import MemorySaver +from langgraph.store.memory import InMemoryStore from pydantic import BaseModel, ValidationError from typing_extensions import TypedDict +from langgraph._internal._constants import CONFIG_KEY_RUNTIME from langgraph.errors import GraphDrained from langgraph.graph import END, START, StateGraph from langgraph.runtime import ( @@ -1177,9 +1179,6 @@ def test_foreign_object_in_runtime_slot_is_coerced() -> None: `merge` when no per-run `context` is provided. `store` is resolved separately, so it is not read off the foreign object in the coercion. """ - from langgraph.store.memory import InMemoryStore - - from langgraph._internal._constants import CONFIG_KEY_RUNTIME store = InMemoryStore() graph_level_context = {"source": "graph-level"} diff --git a/libs/langgraph/tests/test_serde_allowlist.py b/libs/langgraph/tests/test_serde_allowlist.py index 2a90389da..b6bc93b2f 100644 --- a/libs/langgraph/tests/test_serde_allowlist.py +++ b/libs/langgraph/tests/test_serde_allowlist.py @@ -79,7 +79,7 @@ class DummyChannel: def test_curated_core_allowlist_includes_messages() -> None: try: - from langchain_core.messages import BaseMessage + from langchain_core.messages import BaseMessage # noqa: PLC0415 except Exception: pytest.skip("langchain_core not available") allowlist = curated_core_allowlist() diff --git a/libs/langgraph/tests/test_stream_data_transformers.py b/libs/langgraph/tests/test_stream_data_transformers.py index 5aa37e5e3..ae94c8943 100644 --- a/libs/langgraph/tests/test_stream_data_transformers.py +++ b/libs/langgraph/tests/test_stream_data_transformers.py @@ -13,8 +13,10 @@ import operator import time from typing import Annotated, Any +from langgraph.checkpoint.memory import InMemorySaver from typing_extensions import TypedDict +from langgraph.config import get_stream_writer from langgraph.constants import END, START from langgraph.graph import StateGraph from langgraph.stream._mux import StreamMux @@ -488,7 +490,6 @@ class _State(TypedDict): def _my_node(state: _State) -> dict[str, Any]: - from langgraph.config import get_stream_writer writer = get_stream_writer() writer({"status": "working", "node": "my_node"}) @@ -606,7 +607,6 @@ def test_stream_events_v3_all_transformers_interleaved() -> None: def test_stream_events_v3_all_transformers_with_checkpointer() -> None: """All transformers with a checkpointer — run.checkpoints populated.""" - from langgraph.checkpoint.memory import InMemorySaver builder = StateGraph(_State, input_schema=_State) builder.add_node("my_node", _my_node) @@ -645,7 +645,6 @@ def test_stream_events_v3_all_transformers_with_checkpointer() -> None: def test_stream_events_v3_checkpoints_projection_opt_in() -> None: """run.checkpoints surfaces checkpoint data when opted in with a checkpointer.""" - from langgraph.checkpoint.memory import InMemorySaver builder = StateGraph(_State, input_schema=_State) builder.add_node("my_node", _my_node) diff --git a/libs/langgraph/tests/test_stream_messages_transformer.py b/libs/langgraph/tests/test_stream_messages_transformer.py index 6e13ef47e..30443b773 100644 --- a/libs/langgraph/tests/test_stream_messages_transformer.py +++ b/libs/langgraph/tests/test_stream_messages_transformer.py @@ -3,8 +3,10 @@ legacy v1 chunk filtering, and end-to-end via stream_events(version="v3") / astr from __future__ import annotations +import asyncio import time from typing import Any +from uuid import uuid4 import pytest from langchain_core.language_models import GenericFakeChatModel @@ -13,11 +15,13 @@ from langchain_core.language_models.chat_model_stream import ( ChatModelStream, ) from langchain_core.messages import AIMessage, AIMessageChunk, ToolMessage +from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, LLMResult from langchain_core.runnables import RunnableConfig from typing_extensions import TypedDict from langgraph.constants import END, START from langgraph.graph import MessagesState, StateGraph +from langgraph.pregel._messages import StreamMessagesHandlerV2 from langgraph.stream._mux import StreamMux from langgraph.stream.run_stream import GraphRunStream from langgraph.stream.stream_channel import StreamChannel @@ -607,7 +611,6 @@ class TestEndToEnd: @pytest.mark.anyio async def test_nested_async_iteration_yields_text_deltas(self) -> None: """Inner stream.text drives the shared graph pump via the async pump binding.""" - import asyncio model = GenericFakeChatModel(messages=iter(["hello world"])) @@ -870,11 +873,6 @@ class TestDirectMessagesModeStaysV1: class TestStreamMessagesHandlerV2Unit: def test_on_llm_new_token_is_noop(self) -> None: """v2 handler must not emit v1 chunks even when on_llm_new_token fires.""" - from uuid import uuid4 - - from langchain_core.outputs import ChatGenerationChunk - - from langgraph.pregel._messages import StreamMessagesHandlerV2 emitted: list[Any] = [] handler = StreamMessagesHandlerV2(emitted.append, subgraphs=False) @@ -890,9 +888,6 @@ class TestStreamMessagesHandlerV2Unit: assert emitted == [] def test_on_chain_end_does_not_emit_tool_messages(self) -> None: - from uuid import uuid4 - - from langgraph.pregel._messages import StreamMessagesHandlerV2 emitted: list[Any] = [] handler = StreamMessagesHandlerV2(emitted.append, subgraphs=False) @@ -909,11 +904,6 @@ class TestStreamMessagesHandlerV2Unit: def test_on_llm_end_dedupes_when_final_message_id_differs(self) -> None: """A streamed v2 message should not be emitted again from the final AIMessage fallback when its final id does not match `message-start`.""" - from uuid import uuid4 - - from langchain_core.outputs import ChatGeneration, LLMResult - - from langgraph.pregel._messages import StreamMessagesHandlerV2 emitted: list[Any] = [] handler = StreamMessagesHandlerV2(emitted.append, subgraphs=False) diff --git a/libs/langgraph/tests/test_subgraph_detection.py b/libs/langgraph/tests/test_subgraph_detection.py new file mode 100644 index 000000000..dca56139f --- /dev/null +++ b/libs/langgraph/tests/test_subgraph_detection.py @@ -0,0 +1,286 @@ +"""Tests for subgraph auto-detection (`pregel/_utils.py`). + +Detection failing is silent — the graph still runs, only introspection goes +quiet — so every shape a node can hold a graph in is pinned here. The expected +values are what the source-parsing implementation this replaced produced for +the same shapes, except for `sourceless`, whose source it could not read, +`empty_closure_cell`, on which it raised, and `unreachable_attribute_chain`, +where it reported a graph that dropped code could never invoke. +""" + +import functools +import operator +from typing import Annotated, Any + +import pytest +from typing_extensions import TypedDict + +from langgraph.graph import END, START, StateGraph +from langgraph.pregel._utils import get_function_nonlocals + + +class State(TypedDict): + log: Annotated[list, operator.add] + + +def _leaf(tag: str) -> Any: + """Return a compiled graph that reports itself as `tag`.""" + builder = StateGraph(State) + builder.add_node(tag, lambda s: {"log": [tag]}) + builder.add_edge(START, tag) + builder.add_edge(tag, END) + compiled = builder.compile() + compiled.name = tag + return compiled + + +def _detect(node: Any) -> str | None: + """Return the name of the subgraph detected for `node`, or None.""" + builder = StateGraph(State) + builder.add_node("n", node) + builder.add_edge(START, "n") + builder.add_edge("n", END) + subgraphs = builder.compile().nodes["n"].subgraphs + return getattr(subgraphs[0], "name", "?") if subgraphs else None + + +class _Box: + def __init__(self, payload: Any) -> None: + self.payload = payload + + +class _ListSubclass(list): + pass + + +class _MethodHolder: + def __init__(self) -> None: + self.graph = _leaf("via_self") + + def as_node(self, state: State) -> Any: + return self.graph.invoke(state) + + +MODULE_GRAPH = _leaf("module_global") +CHAIN = _Box(_Box(_leaf("attr_chain"))) +GRAPH_IN_PLAIN_LIST = [_leaf("in_list")] +METHOD_HOLDER = _MethodHolder() + + +def closure_capture() -> Any: + sub = _leaf("closure") + + def node(state: State) -> Any: + return sub.invoke(state) + + return node + + +def module_global() -> Any: + def node(state: State) -> Any: + return MODULE_GRAPH.invoke(state) + + return node + + +def attribute_chain() -> Any: + def node(state: State) -> Any: + return CHAIN.payload.payload.invoke(state) + + return node + + +def nested_def_captured_attribute() -> Any: + """A chain on a captured holder, named only inside a nested code object. + + The captured value is the holder, not the graph, so the chain itself has to + be recovered from the nested scope. + """ + holder = _Box(_leaf("nested_captured")) + + def node(state: State) -> Any: + def inner() -> Any: + return holder.payload.invoke(state) + + return inner() + + return node + + +def unreachable_branch() -> Any: + """A captured graph referenced only from code the compiler removes.""" + sub = _leaf("unreachable") + + def node(state: State) -> Any: + if False: + sub.invoke(state) + return {"log": []} + + return node + + +def unreachable_attribute_chain() -> Any: + """A graph named only along an attribute path the compiler dropped. + + The closure keeps `holder`, but the `.payload` load is gone. The source + parser reported this one; dropped code cannot invoke anything, so that was + a phantom rather than a detection. + """ + holder = _Box(_leaf("unreachable_attr")) + + def node(state: State) -> Any: + if False: + holder.payload.invoke(state) + return {"log": []} + + return node + + +def wrapper_referencing_nothing() -> Any: + """A wrapper whose own scope holds nothing, so only `__wrapped__` leads on. + + `functools.wraps` would leave the wrapper closing over the inner function; + setting the attribute by hand does not. + """ + sub = _leaf("via_wrapped") + + def inner(state: State) -> Any: + return sub.invoke(state) + + def wrapper(state: State) -> Any: + return {"log": []} + + wrapper.__wrapped__ = inner + return wrapper + + +def captured_list_subclass() -> Any: + """A `list` subclass is not a leaf: it can carry a graph as an attribute.""" + holder = _ListSubclass() + holder.payload = _leaf("list_subclass") + + def node(state: State) -> Any: + return holder.payload.invoke(state) + + return node + + +def empty_closure_cell() -> Any: + """An unassigned closure variable leaves a cell that cannot be read.""" + sub = _leaf("beside_empty_cell") + + def node(state: State) -> Any: + return unassigned, sub.invoke(state) + + return node + unassigned = 1 # never runs, so the cell it creates is never filled + + +def sourceless() -> Any: + """A node compiled without a source file, which `getsource` could not read.""" + namespace: dict[str, Any] = {"SOURCELESS": _leaf("sourceless")} + exec( + compile( + "def node(state):\n return SOURCELESS.invoke(state)", "", "exec" + ), + namespace, + ) + return namespace["node"] + + +async def _async_node(state: State) -> Any: + return await MODULE_GRAPH.ainvoke(state) + + +def async_node() -> Any: + return _async_node + + +def no_subgraph() -> Any: + """Nothing but leaf values in reach, so the bytecode walk is skipped.""" + + def node(state: State) -> Any: + return {"log": [len("abc") + 1]} + + return node + + +def recombined_names() -> Any: + """Loads `CHAIN.payload` and `local.payload`, never `CHAIN.payload.payload`.""" + + def node(state: State) -> Any: + local = _Box("not a graph") + return {"log": [CHAIN.payload, local.payload]} + + return node + + +def broken_attribute_chain() -> Any: + holder = _Box("a string, so `.payload.missing` cannot resolve") + + def node(state: State) -> Any: + return holder.payload.missing.invoke(state) + + return node + + +def nested_def_global() -> Any: + """A global named only in a nested code object: out of reach, as before.""" + + def node(state: State) -> Any: + def inner() -> Any: + return MODULE_GRAPH.invoke(state) + + return inner() + + return node + + +def graph_in_plain_list() -> Any: + def node(state: State) -> Any: + return GRAPH_IN_PLAIN_LIST[0].invoke(state) + + return node + + +def bound_method_self() -> Any: + return METHOD_HOLDER.as_node + + +@pytest.mark.parametrize( + ("factory", "expected"), + [ + (closure_capture, "closure"), + (module_global, "module_global"), + (attribute_chain, "attr_chain"), + (nested_def_captured_attribute, "nested_captured"), + (unreachable_branch, "unreachable"), + (wrapper_referencing_nothing, "via_wrapped"), + (captured_list_subclass, "list_subclass"), + (empty_closure_cell, "beside_empty_cell"), + (sourceless, "sourceless"), + (async_node, "module_global"), + # Shapes no reference chain reaches: a subscript, an instance attribute + # of `self`, a global named only in a nested scope, and an attribute + # path the compiler dropped. + (no_subgraph, None), + (recombined_names, None), + (broken_attribute_chain, None), + (nested_def_global, None), + (graph_in_plain_list, None), + (bound_method_self, None), + (unreachable_attribute_chain, None), + ], + ids=lambda value: value.__name__ if callable(value) else str(value), +) +def test_subgraph_detection(factory: Any, expected: str | None) -> None: + assert _detect(factory()) == expected + + +@pytest.mark.parametrize( + "candidate", + [functools.partial(lambda state, extra: {"log": [extra]}, extra="x"), len], + ids=["partial", "builtin"], +) +def test_callables_without_a_code_object_are_handled(candidate: Any) -> None: + assert get_function_nonlocals(candidate) == [] diff --git a/libs/langgraph/tests/test_trace_policy.py b/libs/langgraph/tests/test_trace_policy.py new file mode 100644 index 000000000..9496525a9 --- /dev/null +++ b/libs/langgraph/tests/test_trace_policy.py @@ -0,0 +1,149 @@ +"""End-to-end tests for `TracePolicy` input processing on node trace runs.""" + +from typing import Any + +import pytest +from typing_extensions import TypedDict + +from langgraph.graph import END, START, StateGraph +from langgraph.types import TracePolicy +from tests.fake_tracer import FakeTracer, Run + + +class State(TypedDict): + value: int + + +def _node_run(tracer: FakeTracer, name: str) -> Run: + return next(r for r in tracer.flattened_runs() if r.name == name) + + +def _incr(state: State) -> State: + return {"value": state["value"] + 1} + + +def test_trace_policy_transforms_recorded_inputs() -> None: + seen: dict[str, Any] = {} + + def process_inputs(inp: Any) -> Any: + seen["inputs"] = inp + return {"scrubbed_in": True} + + graph = ( + StateGraph(State) + .add_node("n", _incr, trace_policy=TracePolicy(process_inputs=process_inputs)) + .add_edge(START, "n") + .add_edge("n", END) + .compile() + ) + + tracer = FakeTracer() + # the real graph output is unaffected by the trace policy + assert graph.invoke({"value": 1}, {"callbacks": [tracer]}) == {"value": 2} + + run = _node_run(tracer, "n") + # the recorded input is transformed; the output is recorded as-is + assert run.inputs == {"scrubbed_in": True} + assert run.outputs == {"value": 2} + # process_inputs observed the real, untransformed input + assert seen["inputs"] == {"value": 1} + + +def test_trace_policy_transforms_recorded_outputs() -> None: + seen: dict[str, Any] = {} + + def process_outputs(out: Any) -> Any: + seen["outputs"] = out + return {"scrubbed_out": True} + + graph = ( + StateGraph(State) + .add_node("n", _incr, trace_policy=TracePolicy(process_outputs=process_outputs)) + .add_edge(START, "n") + .add_edge("n", END) + .compile() + ) + + tracer = FakeTracer() + # the real graph output is unaffected by the trace policy + assert graph.invoke({"value": 1}, {"callbacks": [tracer]}) == {"value": 2} + + run = _node_run(tracer, "n") + # the recorded output is transformed; the input is recorded as-is + assert run.inputs == {"value": 1} + assert run.outputs == {"scrubbed_out": True} + # process_outputs observed the real, untransformed output + assert seen["outputs"] == {"value": 2} + + +def test_trace_policy_none_records_real_payloads() -> None: + graph = ( + StateGraph(State) + .add_node("n", _incr) + .add_edge(START, "n") + .add_edge("n", END) + .compile() + ) + + tracer = FakeTracer() + assert graph.invoke({"value": 1}, {"callbacks": [tracer]}) == {"value": 2} + + run = _node_run(tracer, "n") + assert run.inputs == {"value": 1} + assert run.outputs == {"value": 2} + + +def test_trace_policy_processor_error_safe_without_callbacks() -> None: + def boom(_inp: Any) -> Any: + raise RuntimeError("processor failed") + + graph = ( + StateGraph(State) + .add_node("n", _incr, trace_policy=TracePolicy(process_inputs=boom)) + .add_edge(START, "n") + .add_edge("n", END) + .compile() + ) + + # no callbacks: the processor still runs but is fail-open, so execution is unaffected + assert graph.invoke({"value": 1}) == {"value": 2} + + +def test_trace_policy_processor_error_does_not_break_execution() -> None: + def boom(_inp: Any) -> Any: + raise RuntimeError("processor failed") + + graph = ( + StateGraph(State) + .add_node("n", _incr, trace_policy=TracePolicy(process_inputs=boom)) + .add_edge(START, "n") + .add_edge("n", END) + .compile() + ) + + tracer = FakeTracer() + # a raising processor must not abort the node; the untransformed input is recorded + assert graph.invoke({"value": 1}, {"callbacks": [tracer]}) == {"value": 2} + assert _node_run(tracer, "n").inputs == {"value": 1} + + +@pytest.mark.anyio +async def test_trace_policy_transforms_recorded_inputs_async() -> None: + graph = ( + StateGraph(State) + .add_node( + "n", + _incr, + trace_policy=TracePolicy(process_inputs=lambda _: {"scrubbed_in": True}), + ) + .add_edge(START, "n") + .add_edge("n", END) + .compile() + ) + + tracer = FakeTracer() + assert await graph.ainvoke({"value": 5}, {"callbacks": [tracer]}) == {"value": 6} + + run = _node_run(tracer, "n") + assert run.inputs == {"scrubbed_in": True} + assert run.outputs == {"value": 6} diff --git a/libs/langgraph/tests/test_utils.py b/libs/langgraph/tests/test_utils.py index 523528561..9efbd0269 100644 --- a/libs/langgraph/tests/test_utils.py +++ b/libs/langgraph/tests/test_utils.py @@ -2,6 +2,7 @@ import functools import sys import uuid from collections.abc import Callable +from dataclasses import dataclass from typing import ( Annotated, Any, @@ -17,7 +18,10 @@ import langsmith import pytest from langchain_core.callbacks import BaseCallbackHandler, CallbackManager from langchain_core.runnables import RunnableConfig +from langchain_core.runnables.config import var_child_runnable_config from langchain_core.tracers import LangChainTracer +from langsmith import get_current_run_tree # type: ignore +from pydantic import BaseModel, Field from typing_extensions import NotRequired, Required, TypedDict from langgraph._internal._config import ( @@ -118,7 +122,6 @@ def rt_graph() -> CompiledStateGraph: node_run_id: int def node(_: State): - from langsmith import get_current_run_tree # type: ignore return {"node_run_id": get_current_run_tree().id} # type: ignore @@ -243,10 +246,6 @@ def test_is_required(): def test_enhanced_type_hints() -> None: - from dataclasses import dataclass - from typing import Annotated - - from pydantic import BaseModel, Field class MyTypedDict(TypedDict): val_1: str @@ -510,7 +509,6 @@ def test_ensure_config_explicit_configurable_replaces_ambient() -> None: # An explicit checkpoint coordinate (here a new thread_id) starts a fresh # lineage and drops the ambient run context (e.g. a parent task's # checkpoint_ns), so a child graph does not inherit it. - from langchain_core.runnables.config import var_child_runnable_config token = var_child_runnable_config.set( {"configurable": {"checkpoint_ns": "p:parent-task", "checkpoint_id": "cid"}} @@ -527,7 +525,6 @@ def test_ensure_config_explicit_configurable_replaces_ambient() -> None: def test_ensure_config_ambient_inherited_when_no_explicit_configurable() -> None: # With no explicit configurable, the ambient run context is inherited # unchanged (stateless subgraph / interrupt-resume pattern). - from langchain_core.runnables.config import var_child_runnable_config token = var_child_runnable_config.set( {"configurable": {"checkpoint_ns": "p:parent-task"}} @@ -543,7 +540,6 @@ def test_ensure_config_explicit_configurables_still_merge_over_ambient() -> None # A new thread_id drops the ambient, but explicit configs still shallow-merge # among themselves, so a with_config(...) value (ls_agent_type) survives # alongside an invoke-time thread_id. - from langchain_core.runnables.config import var_child_runnable_config token = var_child_runnable_config.set( {"configurable": {"checkpoint_ns": "p:parent-task"}} @@ -564,7 +560,6 @@ def test_ensure_config_non_coordinate_config_keeps_ambient_checkpoint_ns() -> No # A nested subagent is invoked with a non-coordinate configurable key # (ls_agent_type) and no thread_id; it must keep the inherited checkpoint_ns # so it stays a discoverable child of the parent run (deepagents `task` tool). - from langchain_core.runnables.config import var_child_runnable_config token = var_child_runnable_config.set( {"configurable": {"thread_id": "parent", "checkpoint_ns": "p:parent-task"}} @@ -582,7 +577,6 @@ def test_ensure_config_same_thread_id_still_clears_ambient() -> None: # A child that reuses the parent's thread_id is still addressing its own root # namespace on that thread, so the parent task's checkpoint_ns must not leak # in; otherwise the child writes state that get_state cannot read back. - from langchain_core.runnables.config import var_child_runnable_config token = var_child_runnable_config.set( {"configurable": {"thread_id": "shared", "checkpoint_ns": "p:parent-task"}} diff --git a/libs/langgraph/uv.lock b/libs/langgraph/uv.lock index 67a74dd00..a3d49f446 100644 --- a/libs/langgraph/uv.lock +++ b/libs/langgraph/uv.lock @@ -28,16 +28,16 @@ wheels = [ [[package]] name = "anyio" -version = "4.14.1" +version = "4.14.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "idna" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3b/72/5562aabb8dd7181e8e860622a38bea08d17842b99ecd4c91f84ac95251b0/anyio-4.14.1.tar.gz", hash = "sha256:8d648a3544c1a700e3ff78615cd679e4c5c3f149904287e73687b2596963629e", size = 254831, upload-time = "2026-06-24T20:56:06.017Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/7b/90df4a0a816d98d6ea26f559d87836d494a2cf1fcf063be67df50a7bcc30/anyio-4.14.1-py3-none-any.whl", hash = "sha256:4e5533c5b8ff0a24f5d7a176cbe6877129cd183893f66b537f8f227d10527d72", size = 124875, upload-time = "2026-06-24T20:56:04.413Z" }, + { url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" }, ] [[package]] @@ -193,7 +193,7 @@ name = "blockbuster" version = "1.5.26" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "forbiddenfruit", marker = "implementation_name == 'cpython'" }, + { name = "forbiddenfruit", marker = "python_full_version >= '3.11' and python_full_version < '3.14' and implementation_name == 'cpython'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/55/e0/dcbab602790a576b0b94108c07e2c048e5897df7cc83722a89582d733987/blockbuster-1.5.26.tar.gz", hash = "sha256:cc3ce8c70fa852a97ee3411155f31e4ad2665cd1c6c7d2f8bb1851dab61dc629", size = 36085, upload-time = "2025-12-05T10:43:47.735Z" } wheels = [ @@ -385,7 +385,7 @@ name = "click" version = "8.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "colorama", marker = "python_full_version < '3.14' and sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" } wheels = [ @@ -528,7 +528,7 @@ name = "croniter" version = "6.2.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "python-dateutil" }, + { name = "python-dateutil", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/df/de/5832661ed55107b8a09af3f0a2e71e0957226a59eb1dcf0a445cce6daf20/croniter-6.2.2.tar.gz", hash = "sha256:ba60832a5ec8e12e51b8691c3309a113d1cf6526bdf1a48150ce8ec7a532d0ab", size = 113762, upload-time = "2026-03-15T08:43:48.112Z" } wheels = [ @@ -540,7 +540,7 @@ name = "cryptography" version = "50.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, + { name = "cffi", marker = "python_full_version >= '3.11' and python_full_version < '3.14' and platform_python_implementation != 'PyPy'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/de/41/6cbdcf9142d00fe82836fbb51e503e58088575cf7a0fe1dbff6695bf0840/cryptography-50.0.0.tar.gz", hash = "sha256:eeac2acb5a20ed25e0ad6d1df9891a520b78b404266b6d11778f25d5d691a6c9", size = 880201, upload-time = "2026-07-31T14:25:10.11Z" } wheels = [ @@ -643,7 +643,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -697,7 +697,7 @@ name = "googleapis-common-protos" version = "1.72.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "protobuf" }, + { name = "protobuf", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e5/7b/adfd75544c415c487b33061fe7ae526165241c1ea133f9a9125a56b39fd8/googleapis_common_protos-1.72.0.tar.gz", hash = "sha256:e55a601c1b32b52d7a3e65f43563e2aa61bcd737998ee672ac9b951cd49319f5", size = 147433, upload-time = "2025-11-06T18:29:24.087Z" } wheels = [ @@ -709,7 +709,7 @@ name = "grpcio" version = "1.80.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions" }, + { name = "typing-extensions", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b7/48/af6173dbca4454f4637a4678b67f52ca7e0c1ed7d5894d89d434fecede05/grpcio-1.80.0.tar.gz", hash = "sha256:29aca15edd0688c22ba01d7cc01cb000d72b2033f4a3c72a81a19b56fd143257", size = 12978905, upload-time = "2026-03-30T08:49:10.502Z" } wheels = [ @@ -770,8 +770,8 @@ name = "grpcio-health-checking" version = "1.80.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "grpcio" }, - { name = "protobuf" }, + { name = "grpcio", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "protobuf", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d1/a2/aa3cc47f19c03f8e5287b987317059753141a3af8f66b96d5a64b3be10b8/grpcio_health_checking-1.80.0.tar.gz", hash = "sha256:2cc5f08bc8b816b8655ab6f59c71450063ba20766d31e21a493e912e3560c8b1", size = 17117, upload-time = "2026-03-30T08:54:41.899Z" } wheels = [ @@ -783,9 +783,9 @@ name = "grpcio-tools" version = "1.80.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "grpcio" }, - { name = "protobuf" }, - { name = "setuptools" }, + { name = "grpcio", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "protobuf", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "setuptools", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/94/c8/1223f29c84a143ae9a56c084fc96894de0ba84b6e8d60a26241abd81d278/grpcio_tools-1.80.0.tar.gz", hash = "sha256:26052b19c6ce0dcf52d1024496aea3e2bdfa864159f06dc7b97b22d041a94b26", size = 6133212, upload-time = "2026-03-30T08:52:39.077Z" } wheels = [ @@ -936,7 +936,7 @@ name = "importlib-metadata" version = "8.7.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "zipp" }, + { name = "zipp", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } wheels = [ @@ -985,17 +985,17 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "decorator" }, - { name = "exceptiongroup" }, - { name = "jedi" }, - { name = "matplotlib-inline" }, - { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit" }, - { name = "pygments" }, - { name = "stack-data" }, - { name = "traitlets" }, - { name = "typing-extensions" }, + { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version < '3.11'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "jedi", marker = "python_full_version < '3.11'" }, + { name = "matplotlib-inline", marker = "python_full_version < '3.11'" }, + { name = "pexpect", marker = "python_full_version < '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version < '3.11'" }, + { name = "pygments", marker = "python_full_version < '3.11'" }, + { name = "stack-data", marker = "python_full_version < '3.11'" }, + { name = "traitlets", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e5/61/1810830e8b93c72dcd3c0f150c80a00c3deb229562d9423807ec92c3a539/ipython-8.38.0.tar.gz", hash = "sha256:9cfea8c903ce0867cc2f23199ed8545eb741f3a69420bfcf3743ad1cec856d39", size = 5513996, upload-time = "2026-01-05T10:59:06.901Z" } wheels = [ @@ -1012,17 +1012,17 @@ resolution-markers = [ "python_full_version >= '3.11' and python_full_version < '3.14'", ] dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "decorator" }, - { name = "ipython-pygments-lexers" }, - { name = "jedi" }, - { name = "matplotlib-inline" }, - { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit" }, - { name = "pygments" }, - { name = "stack-data" }, - { name = "traitlets" }, - { name = "typing-extensions", marker = "python_full_version < '3.12'" }, + { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version >= '3.11'" }, + { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.11'" }, + { name = "jedi", marker = "python_full_version >= '3.11'" }, + { name = "matplotlib-inline", marker = "python_full_version >= '3.11'" }, + { name = "pexpect", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version >= '3.11'" }, + { name = "pygments", marker = "python_full_version >= '3.11'" }, + { name = "stack-data", marker = "python_full_version >= '3.11'" }, + { name = "traitlets", marker = "python_full_version >= '3.11'" }, + { name = "typing-extensions", marker = "python_full_version == '3.11.*'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/46/dd/fb08d22ec0c27e73c8bc8f71810709870d51cadaf27b7ddd3f011236c100/ipython-9.9.0.tar.gz", hash = "sha256:48fbed1b2de5e2c7177eefa144aba7fcb82dac514f09b57e2ac9da34ddb54220", size = 4425043, upload-time = "2026-01-05T12:36:46.233Z" } wheels = [ @@ -1034,7 +1034,7 @@ name = "ipython-pygments-lexers" version = "1.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pygments" }, + { name = "pygments", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } wheels = [ @@ -1404,7 +1404,7 @@ wheels = [ [[package]] name = "langchain-core" -version = "1.4.8" +version = "1.5.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonpatch" }, @@ -1417,9 +1417,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "uuid-utils" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/12/e3/bea6d0080acf183332f24dcd74c208aee5857cf8f783c3fb0bd86027d8fb/langchain_core-1.4.8.tar.gz", hash = "sha256:5bf1f8411077c904182ad8f975943d36adcbf579c4e017b3a118b719229ebf9a", size = 957974, upload-time = "2026-06-18T19:39:23.636Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/3e/63af6b9d76d9be907c7c524d6ec18a2efed7e0e2d123fea0230d78dbd73f/langchain_core-1.5.3.tar.gz", hash = "sha256:a56457ac444fef41e9404443c187f0ecea708d36e816ea4ba9573c027f7d1a2d", size = 972461, upload-time = "2026-07-30T14:55:55.833Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/d6/bdf6f0481cc57ef300d6b1eb48cf1400c0409be715d6eb3cabadd1142a09/langchain_core-1.4.8-py3-none-any.whl", hash = "sha256:d84c28b05e3ba8d4271d0827aad5b592ccdaaf986e76768c23503f0a2045e8aa", size = 557416, upload-time = "2026-06-18T19:39:21.902Z" }, + { url = "https://files.pythonhosted.org/packages/36/e6/c7c39efe0bc7e1b7c3d8f54f85846e04c901913c3d3e99068b218558c6f1/langchain_core-1.5.3-py3-none-any.whl", hash = "sha256:48b56fa580277209594dd7baf837f5b9a2a3651613f34ff9fb1728b429df015f", size = 561687, upload-time = "2026-07-30T14:55:54.419Z" }, ] [[package]] @@ -1436,7 +1436,7 @@ wheels = [ [[package]] name = "langgraph" -version = "1.2.10" +version = "1.2.11" source = { editable = "." } dependencies = [ { name = "langchain-core" }, @@ -1582,38 +1582,38 @@ name = "langgraph-api" version = "0.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cloudpickle" }, - { name = "cryptography" }, - { name = "grpcio" }, - { name = "grpcio-health-checking" }, - { name = "grpcio-tools" }, - { name = "httptools", marker = "sys_platform != 'win32'" }, - { name = "httpx" }, - { name = "jsonschema-rs" }, - { name = "langchain-core" }, - { name = "langchain-protocol" }, - { name = "langgraph" }, - { name = "langgraph-checkpoint" }, - { name = "langgraph-runtime-inmem" }, - { name = "langgraph-sdk" }, - { name = "langsmith", extra = ["otel"] }, - { name = "opentelemetry-api" }, - { name = "opentelemetry-exporter-otlp-proto-http" }, - { name = "opentelemetry-sdk" }, - { name = "orjson" }, - { name = "protobuf" }, - { name = "pyjwt" }, - { name = "sse-starlette" }, - { name = "starlette" }, - { name = "structlog" }, - { name = "tenacity" }, - { name = "truststore" }, - { name = "uuid-utils" }, - { name = "uvicorn" }, - { name = "uvloop", marker = "sys_platform != 'win32'" }, - { name = "watchfiles" }, - { name = "websockets" }, - { name = "zstandard" }, + { name = "cloudpickle", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "cryptography", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "grpcio", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "grpcio-health-checking", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "grpcio-tools", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "httptools", marker = "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'win32'" }, + { name = "httpx", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "jsonschema-rs", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "langchain-core", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "langchain-protocol", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "langgraph", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "langgraph-checkpoint", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "langgraph-runtime-inmem", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "langgraph-sdk", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "langsmith", extra = ["otel"], marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "opentelemetry-api", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "opentelemetry-exporter-otlp-proto-http", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "opentelemetry-sdk", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "orjson", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "protobuf", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "pyjwt", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "sse-starlette", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "starlette", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "structlog", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "tenacity", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "truststore", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "uuid-utils", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "uvicorn", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "uvloop", marker = "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'win32'" }, + { name = "watchfiles", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "websockets", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "zstandard", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7d/17/a23ecdad3de7e48209bc8d534312ba83e332126ec60da9e6487461b02597/langgraph_api-0.10.0.tar.gz", hash = "sha256:f594a160857e2cd7fb2352bba585111177a4a394625ad0cf5898814754fb7bc1", size = 713903, upload-time = "2026-06-11T14:36:50.469Z" } wheels = [ @@ -1622,7 +1622,7 @@ wheels = [ [[package]] name = "langgraph-checkpoint" -version = "4.1.1" +version = "4.2.0" source = { editable = "../checkpoint" } dependencies = [ { name = "langchain-core" }, @@ -1670,7 +1670,7 @@ test = [ [[package]] name = "langgraph-checkpoint-postgres" -version = "3.1.1" +version = "3.1.2" source = { editable = "../checkpoint-postgres" } dependencies = [ { name = "langgraph-checkpoint" }, @@ -1692,6 +1692,7 @@ dev = [ { name = "anyio" }, { name = "codespell" }, { name = "langgraph-checkpoint", editable = "../checkpoint" }, + { name = "langgraph-checkpoint-conformance", editable = "../checkpoint-conformance" }, { name = "psycopg", extras = ["binary"] }, { name = "pytest" }, { name = "pytest-asyncio" }, @@ -1708,6 +1709,7 @@ lint = [ test = [ { name = "anyio" }, { name = "langgraph-checkpoint", editable = "../checkpoint" }, + { name = "langgraph-checkpoint-conformance", editable = "../checkpoint-conformance" }, { name = "psycopg", extras = ["binary"] }, { name = "pytest" }, { name = "pytest-asyncio" }, @@ -1736,6 +1738,7 @@ requires-dist = [ dev = [ { name = "codespell" }, { name = "langgraph-checkpoint", editable = "../checkpoint" }, + { name = "langgraph-checkpoint-conformance", editable = "../checkpoint-conformance" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-mock" }, @@ -1751,6 +1754,7 @@ lint = [ ] test = [ { name = "langgraph-checkpoint", editable = "../checkpoint" }, + { name = "langgraph-checkpoint-conformance", editable = "../checkpoint-conformance" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-mock" }, @@ -1762,12 +1766,12 @@ test = [ name = "langgraph-cli" source = { editable = "../cli" } dependencies = [ - { name = "click" }, - { name = "httpx" }, + { name = "click", marker = "python_full_version < '3.14'" }, + { name = "httpx", marker = "python_full_version < '3.14'" }, { name = "langgraph-sdk", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, - { name = "pathspec" }, - { name = "python-dotenv" }, - { name = "tomli", marker = "python_full_version < '3.11' or python_full_version >= '3.14'" }, + { name = "pathspec", marker = "python_full_version < '3.14'" }, + { name = "python-dotenv", marker = "python_full_version < '3.14'" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, ] [package.optional-dependencies] @@ -1870,13 +1874,13 @@ name = "langgraph-runtime-inmem" version = "0.30.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "blockbuster" }, - { name = "croniter" }, - { name = "langgraph" }, - { name = "langgraph-checkpoint" }, - { name = "sse-starlette" }, - { name = "starlette" }, - { name = "structlog" }, + { name = "blockbuster", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "croniter", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "langgraph", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "langgraph-checkpoint", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "sse-starlette", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "starlette", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "structlog", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/98/19/5d330e22e3ee2569742d3577b4ddfbb6b8e383f2edd9724b4a4b73735717/langgraph_runtime_inmem-0.30.0.tar.gz", hash = "sha256:31954e1de6cd43c284b425243d4faeae818f68b3daca4dbe4313e651b6f5e11d", size = 129940, upload-time = "2026-06-11T14:22:46.985Z" } wheels = [ @@ -1912,13 +1916,13 @@ dev = [ { name = "pytest-asyncio" }, { name = "pytest-mock" }, { name = "pytest-watch" }, - { name = "ruff", specifier = "==0.15.20" }, + { name = "ruff", specifier = "==0.16.1" }, { name = "starlette" }, { name = "ty" }, ] lint = [ { name = "codespell" }, - { name = "ruff", specifier = "==0.15.20" }, + { name = "ruff", specifier = "==0.16.1" }, { name = "starlette" }, { name = "ty" }, ] @@ -1952,9 +1956,9 @@ wheels = [ [package.optional-dependencies] otel = [ - { name = "opentelemetry-api" }, - { name = "opentelemetry-exporter-otlp-proto-http" }, - { name = "opentelemetry-sdk" }, + { name = "opentelemetry-api", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "opentelemetry-exporter-otlp-proto-http", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "opentelemetry-sdk", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, ] [[package]] @@ -2172,8 +2176,8 @@ name = "opentelemetry-api" version = "1.39.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, - { name = "typing-extensions" }, + { name = "importlib-metadata", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/97/b9/3161be15bb8e3ad01be8be5a968a9237c3027c5be504362ff800fca3e442/opentelemetry_api-1.39.1.tar.gz", hash = "sha256:fbde8c80e1b937a2c61f20347e91c0c18a1940cecf012d62e65a7caf08967c9c", size = 65767, upload-time = "2025-12-11T13:32:39.182Z" } wheels = [ @@ -2185,7 +2189,7 @@ name = "opentelemetry-exporter-otlp-proto-common" version = "1.39.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "opentelemetry-proto" }, + { name = "opentelemetry-proto", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e9/9d/22d241b66f7bbde88a3bfa6847a351d2c46b84de23e71222c6aae25c7050/opentelemetry_exporter_otlp_proto_common-1.39.1.tar.gz", hash = "sha256:763370d4737a59741c89a67b50f9e39271639ee4afc999dadfe768541c027464", size = 20409, upload-time = "2025-12-11T13:32:40.885Z" } wheels = [ @@ -2197,13 +2201,13 @@ name = "opentelemetry-exporter-otlp-proto-http" version = "1.39.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "googleapis-common-protos" }, - { name = "opentelemetry-api" }, - { name = "opentelemetry-exporter-otlp-proto-common" }, - { name = "opentelemetry-proto" }, - { name = "opentelemetry-sdk" }, - { name = "requests" }, - { name = "typing-extensions" }, + { name = "googleapis-common-protos", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "opentelemetry-api", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "opentelemetry-exporter-otlp-proto-common", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "opentelemetry-proto", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "opentelemetry-sdk", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "requests", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/80/04/2a08fa9c0214ae38880df01e8bfae12b067ec0793446578575e5080d6545/opentelemetry_exporter_otlp_proto_http-1.39.1.tar.gz", hash = "sha256:31bdab9745c709ce90a49a0624c2bd445d31a28ba34275951a6a362d16a0b9cb", size = 17288, upload-time = "2025-12-11T13:32:42.029Z" } wheels = [ @@ -2215,7 +2219,7 @@ name = "opentelemetry-proto" version = "1.39.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "protobuf" }, + { name = "protobuf", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/49/1d/f25d76d8260c156c40c97c9ed4511ec0f9ce353f8108ca6e7561f82a06b2/opentelemetry_proto-1.39.1.tar.gz", hash = "sha256:6c8e05144fc0d3ed4d22c2289c6b126e03bcd0e6a7da0f16cedd2e1c2772e2c8", size = 46152, upload-time = "2025-12-11T13:32:48.681Z" } wheels = [ @@ -2227,9 +2231,9 @@ name = "opentelemetry-sdk" version = "1.39.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "opentelemetry-api" }, - { name = "opentelemetry-semantic-conventions" }, - { name = "typing-extensions" }, + { name = "opentelemetry-api", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "opentelemetry-semantic-conventions", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/eb/fb/c76080c9ba07e1e8235d24cdcc4d125ef7aa3edf23eb4e497c2e50889adc/opentelemetry_sdk-1.39.1.tar.gz", hash = "sha256:cf4d4563caf7bff906c9f7967e2be22d0d6b349b908be0d90fb21c8e9c995cc6", size = 171460, upload-time = "2025-12-11T13:32:49.369Z" } wheels = [ @@ -2241,8 +2245,8 @@ name = "opentelemetry-semantic-conventions" version = "0.60b1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "opentelemetry-api" }, - { name = "typing-extensions" }, + { name = "opentelemetry-api", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/91/df/553f93ed38bf22f4b999d9be9c185adb558982214f33eae539d3b5cd0858/opentelemetry_semantic_conventions-0.60b1.tar.gz", hash = "sha256:87c228b5a0669b748c76d76df6c364c369c28f1c465e50f661e39737e84bc953", size = 137935, upload-time = "2025-12-11T13:32:50.487Z" } wheels = [ @@ -3139,14 +3143,14 @@ wheels = [ [[package]] name = "redis" -version = "8.0.1" +version = "8.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "async-timeout", marker = "python_full_version < '3.11.3'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cc/c3/928b290c2c0ca99ab96eea5b4ff8f30be8112b075301a7d3ba214a3c8c12/redis-8.0.1.tar.gz", hash = "sha256:afc5a7a2f5a084f5b1880dec548dd45be17db7e43c82a30d84f952aefb05cfb0", size = 5114170, upload-time = "2026-06-23T14:52:37.728Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a8/99/604f0b666d4c616d891cf77ebb9db6bb21601344c051aebf1b72b9ff915f/redis-8.1.0.tar.gz", hash = "sha256:6e1a19beef9225c83efd689c7e6b7da2d5215b1f42cd13b7fc3714d0a09c7b25", size = 5254356, upload-time = "2026-07-30T08:51:00.269Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/0a/c2345ebf1ebe70840ce3f6c6ee612f8fa749cfbd1b03069c53bf0c62aaad/redis-8.0.1-py3-none-any.whl", hash = "sha256:47daa35a058c23468d6437f17a8c76882cb316b838ef763036af99b96cedd743", size = 502406, upload-time = "2026-06-23T14:52:36.137Z" }, + { url = "https://files.pythonhosted.org/packages/66/9d/c5731f6e3608663d4d3656fd8d3aecee8b509c3082818f5a13eae925baea/redis-8.1.0-py3-none-any.whl", hash = "sha256:a4fe1aac3d3b3cc791d4b3d5931c5a956045dc951ee74d1c913ee3ac4d2ee9fb", size = 560618, upload-time = "2026-07-30T08:50:58.497Z" }, ] [[package]] @@ -3347,27 +3351,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.20" +version = "0.16.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/dc/35b341fc554ba02f217fc10da57d1a75168cfbcf75b0ef2202176d4c4f2d/ruff-0.15.20.tar.gz", hash = "sha256:1416eb04349192646b54de98f146c4f59afe37d0decfc02c3cbbf396f3a28566", size = 4755489, upload-time = "2026-06-25T17:20:37.578Z" } +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/94/d9/2d5014f0253ba541d2061d9fa7193f48e941c8b21bb88a7ff9bbe0bd0596/ruff-0.15.20-py3-none-linux_armv6l.whl", hash = "sha256:00e188c53e499c3c1637f73c91dcf2fb56d576cab76ce1be50a27c4e80e37078", size = 10839665, upload-time = "2026-06-25T17:19:44.702Z" }, - { url = "https://files.pythonhosted.org/packages/c6/d3/ac1798ba64f670698867fcfc591d50e7e421bef137db564858f619a30fcf/ruff-0.15.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9ebd1fd9b9c95fc0bd7b2761aebec1f030013d2e193a2901b224af68fe47251b", size = 11208649, upload-time = "2026-06-25T17:19:48.787Z" }, - { url = "https://files.pythonhosted.org/packages/47/47/d3ac899991202095dfcf3d5176be4272642be3cf981a2f1a30f72a2afb95/ruff-0.15.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5b16cdd67ca108185cd36dce98c576350c03b1660a751de725fb049193a0632", size = 10622638, upload-time = "2026-06-25T17:19:51.354Z" }, - { url = "https://files.pythonhosted.org/packages/33/13/4e043fe30aa94d4ff5213a9881fc296d12960f5971b234a5263fdc225312/ruff-0.15.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3413bb3c3d2ca6a8208f1f4809cd2dca3c6de6d0b491c0e70847672bde6e6efd", size = 10984227, upload-time = "2026-06-25T17:19:54.044Z" }, - { url = "https://files.pythonhosted.org/packages/76/e6/92e7bf40388bc5800073b96564f56264f7e48bfd1a498f5ced6ae6d5a769/ruff-0.15.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd7ec42b3bb3da066488db093308a69c4ac5ee6d2af333a86ba6e2eb2e7dd44b", size = 10622882, upload-time = "2026-06-25T17:19:57.037Z" }, - { url = "https://files.pythonhosted.org/packages/13/7a/43460be3f24495a3aa46d4b16873e2c4941b3b5f0b00cf88c03b7b94b339/ruff-0.15.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1a36ad0eb77fba9aabfb69ede54de6f376d04ac18ebea022847046d340a8267", size = 11474808, upload-time = "2026-06-25T17:20:00.357Z" }, - { url = "https://files.pythonhosted.org/packages/27/a0/f37077884873221c6b33b4ab49eb18f9f88e54a16a25a5bca59bef46dd66/ruff-0.15.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6df3b1e4610432f0386dba04d853b5f08cbbc903410c6fcc02f620f05aff53c", size = 12293094, upload-time = "2026-06-25T17:20:03.446Z" }, - { url = "https://files.pythonhosted.org/packages/a6/74/165545b60256a9704c21ac0ec4a0d07933b320812f9584836c9f4aca4292/ruff-0.15.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e89f198a1ea6ef0d727c1cf16088bc91a6cb0ab947dedc966715691647186eae", size = 11526176, upload-time = "2026-06-25T17:20:06.301Z" }, - { url = "https://files.pythonhosted.org/packages/86/b1/a976a136d40ade83ce743578399865f57001003a409acadc0ecbb3051082/ruff-0.15.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309809086c2acb67624950a3c8133e80f32d0d3e27106c0cd60ff26657c9f24b", size = 11520767, upload-time = "2026-06-25T17:20:09.191Z" }, - { url = "https://files.pythonhosted.org/packages/19/0f/f032696cb01c9b54c0263fa393474d7758f1cdc021a01b04e3cbc2500999/ruff-0.15.20-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2d2374caa2f2c2f9e2b7da0a50802cfb8b79f55a9b5e49379f564544fbf56487", size = 11500132, upload-time = "2026-06-25T17:20:13.602Z" }, - { url = "https://files.pythonhosted.org/packages/4b/f4/51b1a14bc69e8c224b15dab9cce8e99b425e0455d462caa2b3c9be2b6a8e/ruff-0.15.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a1ed17b65293e0c2f22fc387bc13198a5de94bf4429589b0ff6946b0feaf21a3", size = 10943828, upload-time = "2026-06-25T17:20:16.635Z" }, - { url = "https://files.pythonhosted.org/packages/71/4b/fe267640783cd02bf6c5cc290b1df1051be2ec294c678b5c15fe19e52343/ruff-0.15.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f701305e66b38ea6c91882490eb73459796808e4c6362a1b765255e0cdcd4053", size = 10645418, upload-time = "2026-06-25T17:20:19.4Z" }, - { url = "https://files.pythonhosted.org/packages/b0/c0/a65aa4ec2f5e87a1df32dc3ec1fede434fe3dfd5cbcf3b503cafc676ab54/ruff-0.15.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b9c0c367ad8e5d0d5b5b8537864c469a0a0e55417aadfbeca41fa61333be9f4", size = 11211770, upload-time = "2026-06-25T17:20:22.033Z" }, - { url = "https://files.pythonhosted.org/packages/5a/a4/0caa331d954ae2723d729d351c989cb4ca8b6077d5c6c2cb6de75e98c041/ruff-0.15.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:01cc00dd58f0df339d0e902219dd53990ea99996a0344e5d9cc8d45d5307e460", size = 11618698, upload-time = "2026-06-25T17:20:25.259Z" }, - { url = "https://files.pythonhosted.org/packages/10/9b/5f14927848d2fd4aa891fd88d883788c5a7baba561c7874732364045708c/ruff-0.15.20-py3-none-win32.whl", hash = "sha256:ed65ef510e43a137207e0f01cfcf998aeddb1aeeda5c9d35023e910284d7cf21", size = 10857322, upload-time = "2026-06-25T17:20:28.612Z" }, - { url = "https://files.pythonhosted.org/packages/fa/f0/fe47c501f9dea92a26d788ff98bb5d92ed4cb4c88792c5c88af6b697dc8e/ruff-0.15.20-py3-none-win_amd64.whl", hash = "sha256:a525c81c70fb0380344dd1d8745d8cc1c890b7fc94a58d5a07bd8eb9557b8415", size = 11993274, upload-time = "2026-06-25T17:20:31.871Z" }, - { url = "https://files.pythonhosted.org/packages/d7/2b/9555445e1201d92b3195f45cdb153a0b68f24e0a4273f6e3d5ab46e212bb/ruff-0.15.20-py3-none-win_arm64.whl", hash = "sha256:2f5b2a6d614e8700388806a14996c40fab2c47b819ef57d790a34878858ed9ca", size = 11343498, upload-time = "2026-06-25T17:20:35.03Z" }, + { 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" }, ] [[package]] @@ -3423,9 +3427,9 @@ name = "sse-starlette" version = "2.1.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "anyio" }, - { name = "starlette" }, - { name = "uvicorn" }, + { name = "anyio", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "starlette", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "uvicorn", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/72/fc/56ab9f116b2133521f532fce8d03194cf04dcac25f583cf3d839be4c0496/sse_starlette-2.1.3.tar.gz", hash = "sha256:9cd27eb35319e1414e3d2558ee7414487f9529ce3b3cf9b21434fd110e017169", size = 19678, upload-time = "2024-08-01T08:52:50.248Z" } wheels = [ @@ -3451,8 +3455,8 @@ name = "starlette" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "anyio" }, - { name = "typing-extensions", marker = "python_full_version != '3.13.*'" }, + { name = "anyio", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/eb/e3/7c1dc7381d9f8ab7d854328ebfa884e62cb3f3d8549ddfd37c7814f42afa/starlette-1.3.1.tar.gz", hash = "sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0", size = 2703240, upload-time = "2026-06-12T09:23:11.602Z" } wheels = [ @@ -3470,14 +3474,14 @@ wheels = [ [[package]] name = "syrupy" -version = "5.3.4" +version = "5.5.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/81/17/95753634cd02a892cbc74d8df9c15f5ad053885a913eb11aa6061f278344/syrupy-5.3.4.tar.gz", hash = "sha256:10f192655a2d42473ad5772653e64b281f6823fe5e30458d92f8ca8e882884c1", size = 87078, upload-time = "2026-06-26T10:55:42.522Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/39/17dde9f0c76cc5abcdeef1b2243791bb850d498f784147b8e460dd23abe8/syrupy-5.5.3.tar.gz", hash = "sha256:fa21e4ae77c89ec5abfca513338d8a7eb916da6618ca0f8db301476e0768e57a", size = 91614, upload-time = "2026-07-11T15:54:31.46Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/7a/254f95860cebb6d51ddb3e815ee30085e8c8a3ba6df1f44ef4605b385621/syrupy-5.3.4-py3-none-any.whl", hash = "sha256:409348aa20afc4b7dab7c965331de0cd79ab46977877fb189014ce9294e947e5", size = 53103, upload-time = "2026-06-26T10:55:41.007Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f9/617a194c1a4203279998e1859426cf2635042533a99a93d63d3ee1fb967e/syrupy-5.5.3-py3-none-any.whl", hash = "sha256:0b260a0c9dad55e1fb83818973dc36fbc1aea3fd5381592f442a986686c4171a", size = 54959, upload-time = "2026-07-11T15:54:29.836Z" }, ] [[package]] @@ -3606,27 +3610,27 @@ wheels = [ [[package]] name = "ty" -version = "0.0.55" +version = "0.0.66" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/08/48/f687c8d268e3581f2f104d1f2ac5944d5b5e841b3695c613b3f263e5bbf7/ty-0.0.55.tar.gz", hash = "sha256:88ca87073825a79a8327c550efcc86cec94344890244c5946f84c9e44a969f31", size = 6040230, upload-time = "2026-06-27T00:27:29.385Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/58/4f6ab2a86589e422a3cf840bcf6114c565e4c39ddf4d0b7cd328af5b52b4/ty-0.0.66.tar.gz", hash = "sha256:24bddd4479ce445b51ac015410dd2d34af1cadd62a77f5b3cb269149ed83f9b5", size = 6520402, upload-time = "2026-08-04T01:09:47.714Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/a3/1a90ba7e5a61c6d09adb92346ddba97668095fc257b577af433e5ac4f404/ty-0.0.55-py3-none-linux_armv6l.whl", hash = "sha256:31e83eef512d066542fe990fe1a3b814423abd1616376c54e48af7045b3e1749", size = 11677249, upload-time = "2026-06-27T00:26:52.18Z" }, - { url = "https://files.pythonhosted.org/packages/82/3a/669f9aa478c38243e213a2684db1502086026cfadc15bb1b29b7cbde030d/ty-0.0.55-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ab4bca857950608fea73e269e2da369d43e6467131de85160d68e2fa466fa248", size = 11444180, upload-time = "2026-06-27T00:26:54.576Z" }, - { url = "https://files.pythonhosted.org/packages/15/a4/6a4b2507a53ce6530c66c5b4fe0d58551eb1748ffa9e0696c32fdd55bbd4/ty-0.0.55-py3-none-macosx_11_0_arm64.whl", hash = "sha256:55032bfd31bf2c5355ee81bdc6407b144a1cc7ee41e5681dd1368e4cef2ba327", size = 10963134, upload-time = "2026-06-27T00:26:57.348Z" }, - { url = "https://files.pythonhosted.org/packages/ce/ae/a3b1a0f1cc83b7d258662cb98aa80a720c2e671d0e8fa0d17a4d5d057a7a/ty-0.0.55-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1e049f69ce65b3c269af67624607f435e1c32319786c1e453ef9611502f295", size = 11493517, upload-time = "2026-06-27T00:26:59.26Z" }, - { url = "https://files.pythonhosted.org/packages/0d/9f/311ce39065a979ef40a9b847f685c8e02464e53adf1671e081eea90640ca/ty-0.0.55-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:631409975c681d5a280fc5a99b7b32e9e801f33be7567c6b42ec331362f59d7d", size = 11460590, upload-time = "2026-06-27T00:27:01.425Z" }, - { url = "https://files.pythonhosted.org/packages/cd/8f/3bf29aa77bd78aae48275153135a2052fa7d3ccdf1ecabeb99c8773abd66/ty-0.0.55-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e08cb0436e68b9351555ae8f2697138c9009b4d5b4ae4272232988b2a431a98f", size = 12098430, upload-time = "2026-06-27T00:27:03.596Z" }, - { url = "https://files.pythonhosted.org/packages/bc/6e/e88411a88240b94640bba06fb6d0d92b247fbeef47ee2bc71f39e58c2558/ty-0.0.55-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16c215ad9f823829409b94ee188cfaa4563f6e1384f6ce3fecb1db75f6c7cf7c", size = 12673086, upload-time = "2026-06-27T00:27:05.589Z" }, - { url = "https://files.pythonhosted.org/packages/6c/7e/8f1762fb7f9245a68ba5ae338d73c59403ce57554e5d311b8bb55027b0ec/ty-0.0.55-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b510eb8f4032baf11b7aee2f1d53babc3b4ca03939b9cdcf6a9d15761d575188", size = 12242559, upload-time = "2026-06-27T00:27:07.714Z" }, - { url = "https://files.pythonhosted.org/packages/72/1f/143657daf2670d977dac83435f1fe03d4843efb798d8e1e75950e541aadd/ty-0.0.55-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ddc05e7959709c3b9b83aa627128a80446865e3c1a4882638dcff6d776dc34a", size = 12021409, upload-time = "2026-06-27T00:27:09.881Z" }, - { url = "https://files.pythonhosted.org/packages/6d/30/69487c439dd1fad3a4a3d96f0a472193de297eaba6fc4b8ea687ce434ac2/ty-0.0.55-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:636e8e5078787b8c6916c94e1406719f10189a4ca6b37b813a5922ce5857a8c7", size = 12303807, upload-time = "2026-06-27T00:27:11.986Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ca/cd88b6493dafc7db077f5e17c0438eb3af6e2d6d08f616dbb52a8ddfd567/ty-0.0.55-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ef7d6deaacb73fec603666b5471f1dc5a5699aa84e11a6d4d644dd07ca72121e", size = 11441263, upload-time = "2026-06-27T00:27:14.087Z" }, - { url = "https://files.pythonhosted.org/packages/aa/fe/66b6915671653ab739f71e4f1b0528e69da64429b7ebf3840c625b6e43f2/ty-0.0.55-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9aeea0fe5875d3cf37faf0e44d0fdf9669335467749741b8fc0103916fb5cd32", size = 11484584, upload-time = "2026-06-27T00:27:16.311Z" }, - { url = "https://files.pythonhosted.org/packages/4a/4f/7a9c0bbac8b899e9f6c0ec110c6612f52e4db35f6bb17ddc0ef60384fa3e/ty-0.0.55-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0b699c01310dbd2705a07c97c5f4aaeedef61bd9adeea2e7c46aed32401d3576", size = 11759309, upload-time = "2026-06-27T00:27:18.471Z" }, - { url = "https://files.pythonhosted.org/packages/ca/de/b6f8b1b69aa631b5716ef3f985c3b56de0e46c2499cc00d30c402b41f714/ty-0.0.55-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:32cbeba543e46de2a983ec6d525d8b56514f7422bd1e1b57c44ccf7bfa72c38a", size = 12128755, upload-time = "2026-06-27T00:27:20.55Z" }, - { url = "https://files.pythonhosted.org/packages/7d/90/a912531e51ee7e076b42972479290fa687c0f5e747b7e773f3033164acaa/ty-0.0.55-py3-none-win32.whl", hash = "sha256:52b968e24eb4f7a5c3bd251db1f99f60dd385890356d38fc619d84f1b423446a", size = 11117501, upload-time = "2026-06-27T00:27:22.714Z" }, - { url = "https://files.pythonhosted.org/packages/4c/7a/99d59843bf8908a7f9f4d13fda107dbad07b7faa28ecd7860eacf363fb1c/ty-0.0.55-py3-none-win_amd64.whl", hash = "sha256:bf39cbfdc0add44d94bd3fff1f53c351418d134b6a66b87efdb7876d7b7a2224", size = 12150106, upload-time = "2026-06-27T00:27:24.881Z" }, - { url = "https://files.pythonhosted.org/packages/b3/44/20987505cedf2a865b08482f0eabc181fd9599b062964057ec8a128a4296/ty-0.0.55-py3-none-win_arm64.whl", hash = "sha256:f7f3700a9a060e8f1af11e4fb63fafcaf272b041781f4ccdfda2b3b5c6c1e439", size = 11560157, upload-time = "2026-06-27T00:27:27.332Z" }, + { url = "https://files.pythonhosted.org/packages/45/4d/bbc28310d6d887ef73e5800f062c4bf54caa35a1b47d70c7b03d0515ecf1/ty-0.0.66-py3-none-linux_armv6l.whl", hash = "sha256:8b46450438b54b732338e4d7a78a7d2f5e1a012a13d77d121aacaca20fb814e2", size = 12409743, upload-time = "2026-08-04T01:09:01.229Z" }, + { url = "https://files.pythonhosted.org/packages/73/4e/c3d2eb2242fa0a2ef445ca2c7009dc9118e5b3dcb8b8a8bec70d58c8e4bc/ty-0.0.66-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8e4adbe662bc3c62b52b83d46b07f703fdc3c123bb72601606c58be5ea017ed3", size = 12078362, upload-time = "2026-08-04T01:09:04.233Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/7a4b5e45d701a8de6b714dacf6ffca91b0411baaceaa781d494037623a18/ty-0.0.66-py3-none-macosx_11_0_arm64.whl", hash = "sha256:776814351735847eb934f9a3cbea21d2278ba14aa0fc099f683da11bc2d5c90a", size = 11583289, upload-time = "2026-08-04T01:09:06.973Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/fbb1f71ee2999f981f8c4b3b139231e4bcff4ede0c3b37e858fd1334ed26/ty-0.0.66-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cca1da877f613965b954bfd22d495386e260ec767c5536c8022cca98a260ea5d", size = 12137274, upload-time = "2026-08-04T01:09:09.592Z" }, + { url = "https://files.pythonhosted.org/packages/d5/07/9e0662f6603a5ac171ae6b314396e677ead4b45695fc948efb0a4837051f/ty-0.0.66-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aa523e777bc36c0fbf8ded5844096f46cbfc3712fe5a003351a94259b7e86cf4", size = 12207047, upload-time = "2026-08-04T01:09:12.533Z" }, + { url = "https://files.pythonhosted.org/packages/bb/cc/12e01bc2ea47fa7bf20fc6cdd3249d6a340be9be4edc52b1d77256245dd0/ty-0.0.66-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f7993c1f95f80a4e44056e6aaf8e46e608d778db131ae5ce59262ab59358f9a", size = 12919304, upload-time = "2026-08-04T01:09:15.175Z" }, + { url = "https://files.pythonhosted.org/packages/75/88/c6c0d3a8e71c9cdb560cc5c627a4bba6c47b5eec460b2f8c449b57c6d03a/ty-0.0.66-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f9b29354bcbac9f53b6952d8f46789bd81eeb9bdd7a68df7d26e654ca7498c3c", size = 13470963, upload-time = "2026-08-04T01:09:17.849Z" }, + { url = "https://files.pythonhosted.org/packages/27/c2/2f8c18063412ad80e1b3f87afce7bd50982b4a048166607b67f80660a9fb/ty-0.0.66-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbf4e5325f7f584d9c346946e7c415b2e3cd3b8f1119d468082a90a6afd020ce", size = 13244773, upload-time = "2026-08-04T01:09:20.59Z" }, + { url = "https://files.pythonhosted.org/packages/ed/f6/fdae2b95831116dffc055ad53a99a8f21437263651047b3ad46def4950a3/ty-0.0.66-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bd304363764bd723c22fb20b17035c345420fdf66fee856934b158ebde08a91", size = 12751343, upload-time = "2026-08-04T01:09:23.225Z" }, + { url = "https://files.pythonhosted.org/packages/14/1e/7e75f0371d11463a256dc2bee98b0df401e0fa06021e200b8b601d21949d/ty-0.0.66-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:205d8589bd957ea9718d488b731b2fbdd0d1b1cefd37c79f52c0deb7cafddfef", size = 13068057, upload-time = "2026-08-04T01:09:26.052Z" }, + { url = "https://files.pythonhosted.org/packages/b4/84/f20f24518f6f0bea936e2e668ede250b9ce0774624559931599bd1f42772/ty-0.0.66-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7304a1df54741a2343801354f41d7ad87974acb541ee3e93c26cb6a1a0b863af", size = 12082318, upload-time = "2026-08-04T01:09:28.877Z" }, + { url = "https://files.pythonhosted.org/packages/08/20/70ca0eac2427d4a58a81a3a9426b20e46fb4a5a13aefc9edc2e5172e1243/ty-0.0.66-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:cf2c062863e5da0f588b0b120fec0da2e81c0913ee4cd07b50d77aa60ffd8deb", size = 12228978, upload-time = "2026-08-04T01:09:31.905Z" }, + { url = "https://files.pythonhosted.org/packages/7c/01/5a461ab34456d788248780830aed673c9e0e796f6e9dd92d3dbf02e04d7f/ty-0.0.66-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a58d32c879d86428978332adf21e85009ec269314a23d330c3483c65b57aedc7", size = 12471917, upload-time = "2026-08-04T01:09:34.418Z" }, + { url = "https://files.pythonhosted.org/packages/46/ed/f8eb5eff7c9ee644490c6d2626425935c097acc54871adf659ea70624a2c/ty-0.0.66-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b2f810fa3516c977630d78dbe9161592b3c27029fa9bb81074366624d9b5e4b6", size = 12858086, upload-time = "2026-08-04T01:09:37.602Z" }, + { url = "https://files.pythonhosted.org/packages/02/7f/8a78361f752274a550a7f6f07f127b246ece7d7fdde31121d22074e46eab/ty-0.0.66-py3-none-win32.whl", hash = "sha256:d28c3df565a387c1c5ea359aa452a46d19163616ef71be819058a424a62f1ae1", size = 11782494, upload-time = "2026-08-04T01:09:40.136Z" }, + { url = "https://files.pythonhosted.org/packages/7d/e2/deed22b823ce309b8410d50414fd15afe9e90aee8b8ca04789ba55c21231/ty-0.0.66-py3-none-win_amd64.whl", hash = "sha256:e3a457f3312c078f24c47d0da6e4f73de34d0a77ed2de22571c066c80b2fd5e7", size = 12893338, upload-time = "2026-08-04T01:09:42.825Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ce/6c828f42ef1ed39f53f57f9c0cdcdf03e23666fa7a29d799042a2c34bcb4/ty-0.0.66-py3-none-win_arm64.whl", hash = "sha256:2f62ae247b9c75674fcc060635f9f00210357da7681de59234d97b50fb9e9e94", size = 12227381, upload-time = "2026-08-04T01:09:45.365Z" }, ] [[package]] @@ -3723,8 +3727,8 @@ name = "uvicorn" version = "0.40.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "click" }, - { name = "h11" }, + { name = "click", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "h11", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c3/d1/8f3c683c9561a4e6689dd3b1d345c815f10f86acd044ee1fb9a4dcd0b8c5/uvicorn-0.40.0.tar.gz", hash = "sha256:839676675e87e73694518b5574fd0f24c9d97b46bea16df7b8c05ea1a51071ea", size = 81761, upload-time = "2025-12-21T14:16:22.45Z" } wheels = [ @@ -3812,7 +3816,7 @@ name = "watchfiles" version = "1.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "anyio" }, + { name = "anyio", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } wheels = [ @@ -4025,159 +4029,159 @@ wheels = [ [[package]] name = "xxhash" -version = "3.8.0" +version = "3.8.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/78/ed/07e560876a4458987511461187b285071f53cde49dd5b25cd8c51091522b/xxhash-3.8.0.tar.gz", hash = "sha256:d72b2204f37840b0f16f34192c09b994b97bd25823d723d47a1eddfacf06eb43", size = 86107, upload-time = "2026-06-27T08:17:28.798Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/63/71aa56b151a1b28770037a61bd4e461c2619cfc8866a4fcaf1548605e325/xxhash-3.8.1.tar.gz", hash = "sha256:b0de4bf3aa66363552d52c6a89003c479911f12098cd48a53d44a0f7a25f7c46", size = 86223, upload-time = "2026-07-06T10:49:58.937Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/d1/36cdfc7d9a5cdcebb2ff3eeeaebae2c51a7aca50de27a44520af4d6923fa/xxhash-3.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2289857ab90ebb2408d4ac2b7cf7e9ff29bba9d2cb21020c9d11fbbaef78eea", size = 34638, upload-time = "2026-06-27T08:12:17.753Z" }, - { url = "https://files.pythonhosted.org/packages/8e/37/f3439475537ca4c59e9b8cbc2b934672d1965b13b6e5fb32b1796c76e517/xxhash-3.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d211cfa927a107df09359d1f31070883a11121ddc88fd6dd27eda3a497a88f3d", size = 32316, upload-time = "2026-06-27T08:12:19.332Z" }, - { url = "https://files.pythonhosted.org/packages/a7/3e/3878d943d9169fd8f5ad8d2bffa7dfec14430f8240ef20213772a7ef3dce/xxhash-3.8.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ba02f4cc4e71e1315ecac0468189b49bf3970da05ddf0b6965b4a9b1fe147e62", size = 217379, upload-time = "2026-06-27T08:12:20.742Z" }, - { url = "https://files.pythonhosted.org/packages/af/8a/096f0bf4e4d33b5afcb27e7907d54f84ae3c581509188dca1083995aefd9/xxhash-3.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:342d1a6f161741f8612dc38d940ec0019ae3362c0ede2d16554c1b4e3f1d5444", size = 237734, upload-time = "2026-06-27T08:12:22.312Z" }, - { url = "https://files.pythonhosted.org/packages/0e/5b/811939d5d3fdf9b4a9cad7591759cc82c3c4734afb0138917ec3b3fc4fd5/xxhash-3.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:75feec84a48cafd3b2446cb41910bebaf9a8150e2313c1f42887435818fb7b4c", size = 262522, upload-time = "2026-06-27T08:12:23.869Z" }, - { url = "https://files.pythonhosted.org/packages/3e/e4/50e2b55b1390895214bdd9dc6a75d4c31e0283d646d2cae424962585427a/xxhash-3.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:53e8f6cc0cc24283d98e9c742a0f0a5ded7a810abc4038b9e885e419fcd44e43", size = 238441, upload-time = "2026-06-27T08:12:25.95Z" }, - { url = "https://files.pythonhosted.org/packages/4b/fa/725fbd70cc69b2738599c3e1b499941663b6ccef92aec7c78a4c9968f2d0/xxhash-3.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:73d04a4520cc7313acf4ff2122f783056d0592c71fc3a59e90fe0baeb499d124", size = 469833, upload-time = "2026-06-27T08:12:27.678Z" }, - { url = "https://files.pythonhosted.org/packages/86/e6/d80a2fcbd80f024d8e74a579aad538c5a24c6b672e6ce8180a9a8bfc2231/xxhash-3.8.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5a7fdfde5022f5000c8e6565db954580d19a8aa497ef80875f461e4546ed182", size = 217094, upload-time = "2026-06-27T08:12:29.221Z" }, - { url = "https://files.pythonhosted.org/packages/a9/c0/6d85ebdc1e488df9e37c3a2267a8b98a936a36d968560cfb0389307fd19f/xxhash-3.8.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6a667f0dd160ec0ff6dddf42f2d75ad82660074285855f6037d6ecb57d40d0f8", size = 307502, upload-time = "2026-06-27T08:12:30.782Z" }, - { url = "https://files.pythonhosted.org/packages/ca/a2/4b97a5e4fb3450fe0c4b361399f74679a491b3b0bed914bff6d00e70425f/xxhash-3.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aaaf53eb633205f01bb5fb807f6244bd34af121bfb1e21eedc925374aff5723e", size = 234622, upload-time = "2026-06-27T08:12:32.075Z" }, - { url = "https://files.pythonhosted.org/packages/e7/7e/5a227460f92ec7309219730ddfb7451e09e8aa3e0704cfb0f24746686a0f/xxhash-3.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:71b2e99a02fd5275b7ecab0b01130395beed4c6f027b6ce9f0730025634e7091", size = 265697, upload-time = "2026-06-27T08:12:33.559Z" }, - { url = "https://files.pythonhosted.org/packages/4e/31/56327a7b39dd3c605034f9b51c89d66aad022aacbe12aabeb6e335652d48/xxhash-3.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b25437ffd781d4cb98acef87f4bc32e27682f603ffd27ed5962948b516e777ff", size = 221932, upload-time = "2026-06-27T08:12:34.997Z" }, - { url = "https://files.pythonhosted.org/packages/41/a0/312504d1851969c62e3f2836eec5b16f3682edfae19aa60e6d69ee80d111/xxhash-3.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fe0ee773fd6c211b3b0134ee5d6fd6348411bd7bd79cdb4151d0aaf732179571", size = 236819, upload-time = "2026-06-27T08:12:36.66Z" }, - { url = "https://files.pythonhosted.org/packages/5d/23/d8f80cb1b1acede29ce76a39e013e5782712ab895bbffb32fe2e42b8eadd/xxhash-3.8.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:06c74e537f45c2f71010738d4d20741186cac29a035ec5c1c621c723d656c2fd", size = 297860, upload-time = "2026-06-27T08:12:38.103Z" }, - { url = "https://files.pythonhosted.org/packages/34/e9/4fdc697dcff5a73157ee34331e37849ada645448d4e47a38cb8a4044eafd/xxhash-3.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:718162a608eb85a22470725f95d63d834b1d7db98a2008b10309cd5a552d91ad", size = 439263, upload-time = "2026-06-27T08:12:39.808Z" }, - { url = "https://files.pythonhosted.org/packages/60/07/41a5144d7fd1c1f2b380de36521f7f34d624eef0374736515087ead7b925/xxhash-3.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:934cd5008d86e201818ca4416a4202039ea29edd89047166fea5c49999677bea", size = 213953, upload-time = "2026-06-27T08:12:41.528Z" }, - { url = "https://files.pythonhosted.org/packages/44/e4/7bc12b2fc9f340c446054b6f0e90e5b54c8021a4f9f6b1650054796009e9/xxhash-3.8.0-cp310-cp310-win32.whl", hash = "sha256:1f2c243a385e2c2ce72f5b7d68f3a621cc7d2ee2d0f35e0ca6bf5427ef1922a4", size = 31858, upload-time = "2026-06-27T08:12:43.139Z" }, - { url = "https://files.pythonhosted.org/packages/46/6a/3a61102925bf65ad81827a4586553a357f8a5316a25b938ef435e0bfabf8/xxhash-3.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:cb4996d43a42d825e2aa6f2b6a978b2a7779397b6a28e4fab5eb9505457023e4", size = 32659, upload-time = "2026-06-27T08:12:45.029Z" }, - { url = "https://files.pythonhosted.org/packages/06/c6/39d915926f45f72059519688b538a068efbea0307a294eba1ddb18887c0e/xxhash-3.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:b3a79d694adcfd70d118c73d244eaece7f5f5ab424feb44573bd1d377e1bf0ea", size = 29128, upload-time = "2026-06-27T08:12:46.447Z" }, - { url = "https://files.pythonhosted.org/packages/e3/1b/73aaae7755372ff0cd5788c9955abb64b34d519dd84f2f4f081e2082119b/xxhash-3.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:08c34553cd7ceb3bfcfca344dc70305a45430429b5d58a67750f2a58364f638f", size = 34641, upload-time = "2026-06-27T08:12:47.579Z" }, - { url = "https://files.pythonhosted.org/packages/53/08/fdb1cb1001ed15b1f74a8eb70457dbdcd6df8375e27e3fe0d0225dbab170/xxhash-3.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:842d147983110e5a4f533f98f4f5bc851a08c7ca00aaa30649e8d5f9a6d4e47a", size = 32316, upload-time = "2026-06-27T08:12:48.695Z" }, - { url = "https://files.pythonhosted.org/packages/d7/05/c004e99c4292a9dde76c9157e8e51c73c6db2dd7e4a876712e6a6113e3b0/xxhash-3.8.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:37c9943e18f569f76a8b7d5d01bfe0716f7762c396096ceb42a47eb3d5ecf641", size = 220196, upload-time = "2026-06-27T08:12:49.964Z" }, - { url = "https://files.pythonhosted.org/packages/a0/b2/8696a2008d59c3dc9346b26f7d64f5ec342cacc4051664e3b0201354fe58/xxhash-3.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21f6797afdc7abb0ffae059a0d1619c84a5368115bc0abd48f9803ab56a5d35e", size = 240908, upload-time = "2026-06-27T08:12:51.544Z" }, - { url = "https://files.pythonhosted.org/packages/da/ee/2415c55a17f525bcfa38b5b51d69381d6485b1c320eff373b263403b5e6b/xxhash-3.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5875d99d3540367d43779551dd22c813420b84a103e418d791095b9808fdca57", size = 264445, upload-time = "2026-06-27T08:12:53.08Z" }, - { url = "https://files.pythonhosted.org/packages/23/25/056d30ed2e500d0a993e4589da8cdbe50cbf4809c1b1ac84f6f9559d99ba/xxhash-3.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1a54ad5a2a96cdf1ee7a935d38bc63daa6095530095a916f644f1ab76604ced5", size = 241295, upload-time = "2026-06-27T08:12:54.703Z" }, - { url = "https://files.pythonhosted.org/packages/7e/70/5d8c9b65ae05725c2ea8f331705e1382fc4817911eb159450aecb2905c6b/xxhash-3.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b32e50dd85f0b67b2b95eb59cd3242052f6b27b70e9e73b27629686c592e3ea3", size = 473113, upload-time = "2026-06-27T08:12:56.159Z" }, - { url = "https://files.pythonhosted.org/packages/6f/d4/734dd8e6eaa03b0c4e3044127755221ebf153260a3c5de0382430486fcaf/xxhash-3.8.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4208fb85c950ddf7118b040bca15179c3bf9b7eb8bebe5e6ef067fc8af16a7", size = 220001, upload-time = "2026-06-27T08:12:57.869Z" }, - { url = "https://files.pythonhosted.org/packages/5b/cc/a0d92359d499db55f83fe6de13188125515319b968bd627b591a0984c454/xxhash-3.8.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9f17e09b035f2a0139536da53deb392b62ee259dc2a2189be12b06a7dd50489b", size = 309757, upload-time = "2026-06-27T08:12:59.438Z" }, - { url = "https://files.pythonhosted.org/packages/bc/dd/a20949401cfb9c940ef858d93b41ded90382ff4be0f7e8a5249edd95ff18/xxhash-3.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7d6dbb976d6e3b3be51bad16b13de7f4980e6aebd0aa51c5a14dfcc0fedd495e", size = 237596, upload-time = "2026-06-27T08:13:00.992Z" }, - { url = "https://files.pythonhosted.org/packages/99/5d/6963ee0c245a69d9c4a2583da603915f9288f1df23700a0ec705239ef014/xxhash-3.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:281897e5c516769694c999f5c50fd1e9acb27acbff187282a8ac77c38b6a9be5", size = 268683, upload-time = "2026-06-27T08:13:02.577Z" }, - { url = "https://files.pythonhosted.org/packages/db/ea/3489cde91ccd91230efbb2351a6d9358e8a63a9954cb8f071fa9c32a2558/xxhash-3.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8fba3d08c246201a1a0a6cece53a0b3b0890fc16adbe1edb245fcfcbf4eb0ce2", size = 224882, upload-time = "2026-06-27T08:13:04.21Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f6/179847064c92a07bba7381e9cd7132c380a17aad31e176a2d6f6e73eed48/xxhash-3.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:14ebc1559e8a9a481d0d5506b87678942fcdfa794d4aa55cdd2a0fb175d4245a", size = 239563, upload-time = "2026-06-27T08:13:05.96Z" }, - { url = "https://files.pythonhosted.org/packages/2d/83/dd599670efd161d31fba4149e20694f140ae5707068d38ac480dac1c8cd5/xxhash-3.8.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5e7a3e3bbe3a56bff70acc9b72576670e793b0184de3d1b9cda2bf697d17f630", size = 300148, upload-time = "2026-06-27T08:13:07.495Z" }, - { url = "https://files.pythonhosted.org/packages/e8/a8/a474f136610594b464ad813f6badf00b931211a69fc86542c21daf5d2a4d/xxhash-3.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9c71e3755a8320d29c351126d550930349be22b44bac1a559caf12ab78b53e9f", size = 442448, upload-time = "2026-06-27T08:13:09.467Z" }, - { url = "https://files.pythonhosted.org/packages/75/86/054032919fc73b72917054cf731be76be3a984e8f53b1d0ba6f22fb9cffc/xxhash-3.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:715c611582004e75010517b919776c5dbc00aae03054dc9fd72484a23fd1862f", size = 216755, upload-time = "2026-06-27T08:13:10.902Z" }, - { url = "https://files.pythonhosted.org/packages/3c/16/2eb382a78f12e3fde1c735b57607498c0efe897e8859484d69d9446bba55/xxhash-3.8.0-cp311-cp311-win32.whl", hash = "sha256:41a30a1d0ba978238742a374875c15979e0faed0a65294f3ff4d9410057ee8b6", size = 31851, upload-time = "2026-06-27T08:13:12.281Z" }, - { url = "https://files.pythonhosted.org/packages/dd/53/a07ad4dbdc32118b3bd190f5d54ee2ed28c1a0a994b52ae493435cfb4de7/xxhash-3.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:43705f917b8b817d6994851bf3725b98b4c95e64186404d9a6dbc1acf12fd140", size = 32655, upload-time = "2026-06-27T08:13:13.394Z" }, - { url = "https://files.pythonhosted.org/packages/e1/87/d76bef62a288a1f2441404b33cb757047cf555cd5956b36ed718a38b81e9/xxhash-3.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:35c5d843bb7ac1dfdb125ef4181fe4c2e01c2275856e6b699de89e9eb5c69c8d", size = 29128, upload-time = "2026-06-27T08:13:15.371Z" }, - { url = "https://files.pythonhosted.org/packages/17/2e/4b7c3ab28b7a54ac17eae7e02471c49609d6fc5900856a455feeb847a2a3/xxhash-3.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fc4bd14f873cd0b420f6f1ff5b5cd0dbfeb05b044a11bb9345bcbbf9749636e3", size = 34623, upload-time = "2026-06-27T08:13:16.696Z" }, - { url = "https://files.pythonhosted.org/packages/43/e4/09eea3e1bba6a59d64599cb8fba39f2a0872d06e85420eae989a4da61a9d/xxhash-3.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:31904979198e913239cb61b49f5b849696aeb3b03340da815d1491ec74dcc602", size = 32318, upload-time = "2026-06-27T08:13:18.036Z" }, - { url = "https://files.pythonhosted.org/packages/01/59/688bbae31e4e2d6d6eb92acbd3837c0e44ff8c7d435e6da922844ff6efda/xxhash-3.8.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7338ad13f2b273a1ef0ea97b2db0a059fdb3a1a29298bfa145937c0e4152d341", size = 220461, upload-time = "2026-06-27T08:13:19.311Z" }, - { url = "https://files.pythonhosted.org/packages/2d/de/71484ce0dab2fa4a475705d1ebc37a17ff02d40e5df6767b3255cc53120e/xxhash-3.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54e80e803cb34c8a1d278b491e543af40a588d288589c3e6becc991d5328b46b", size = 241110, upload-time = "2026-06-27T08:13:20.844Z" }, - { url = "https://files.pythonhosted.org/packages/bf/f9/1ac88f02e7df7898541490260b21f2b7f7bd2b233038a0cbd3a3b1bffdc2/xxhash-3.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:353953ea18f5c3fbdd13936fb536aacfb47d5bc06eef0919b1a355df61f7cc31", size = 264779, upload-time = "2026-06-27T08:13:22.485Z" }, - { url = "https://files.pythonhosted.org/packages/25/49/7ea1f128d2fe948ed679020f97a0896cdc6c975da5cc69b53a4a9c4a5def/xxhash-3.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d761f983a315630eff18c2fec7360c6b6946f82748026e779336eb8141ef3eba", size = 242609, upload-time = "2026-06-27T08:13:24.277Z" }, - { url = "https://files.pythonhosted.org/packages/a0/da/7d237278dfa1c48722c31010c84a328a317b8885429c8cb6ae4a8fa3e3db/xxhash-3.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f3786a9beb9a3b76241cb7db5f5388b460682c12204236389e3221963fc626a6", size = 473472, upload-time = "2026-06-27T08:13:25.877Z" }, - { url = "https://files.pythonhosted.org/packages/9b/5f/980fda82620a07d80026b4df371cbca12fca0fd94d7087c4ec5d898da76f/xxhash-3.8.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c94f5a9a775f36cc522fa2a7e8e2cec512e252d2ac056759f753dc68a79ffc", size = 220374, upload-time = "2026-06-27T08:13:27.366Z" }, - { url = "https://files.pythonhosted.org/packages/14/71/efa37bc3e91e1c801972bcef99eab877fcbd17ec10aca16c550ee2951107/xxhash-3.8.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:55ce59f9af37ac861947b43ea3ce7b294b5de77a1234b558d0f07ffad0197624", size = 310220, upload-time = "2026-06-27T08:13:28.804Z" }, - { url = "https://files.pythonhosted.org/packages/9d/48/19e40320044dc7051e8446505f18557d5661853b87a8770ad399325bb3c8/xxhash-3.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3afa1422a32c7c8e79ad5121dc21eaa5cee9e9e67bffca3f15d15d220d371908", size = 238100, upload-time = "2026-06-27T08:13:30.378Z" }, - { url = "https://files.pythonhosted.org/packages/d3/0d/588499f4d7cd064864ada7adfb9e8785f88a988f1332ed4c1be73d249c15/xxhash-3.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:551fda694938be910529452a89175137c58b4739e41fadff3c047e24b1d74a3b", size = 268937, upload-time = "2026-06-27T08:13:31.867Z" }, - { url = "https://files.pythonhosted.org/packages/54/18/fb2ad593572a33d1b6864b33047b8ca7269273a3c56107b5fd33e0b9c8fb/xxhash-3.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:512eb937c9457e6057e230e005c4709dd2ab63a5989f854d69f31db905750a62", size = 224910, upload-time = "2026-06-27T08:13:33.659Z" }, - { url = "https://files.pythonhosted.org/packages/63/9e/b880f9ed61b73492e24bb962d76aeb63f18ccb895f0edfb52e20d45ed6f2/xxhash-3.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4931ea93840f750a908efebaf23c71004feacc1a4649ef601b96d400a505c9a9", size = 240742, upload-time = "2026-06-27T08:13:35.237Z" }, - { url = "https://files.pythonhosted.org/packages/3f/89/fc682f93e54e486fc338b26a7d6d0d5cb0ab366269273c2608ac62b51afb/xxhash-3.8.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2fd4b60e8d9fc3923f39079f185b3425e6d76636fcb66d82a33dd7eba7c30f2f", size = 300527, upload-time = "2026-06-27T08:13:36.997Z" }, - { url = "https://files.pythonhosted.org/packages/80/71/a4b4122afb2d17ad69e0922cfeddb5ad5c25b02f37eed3dd3819d42e5f55/xxhash-3.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1da00075f1605794298878cb587f7533329693e2a0c45bbd25d6353644add675", size = 443195, upload-time = "2026-06-27T08:13:38.719Z" }, - { url = "https://files.pythonhosted.org/packages/d5/e5/ed3930f5dc90f4b1bab5ac3be099e8b2e81c1262d85e4adb5f2758e30d23/xxhash-3.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba73801c87d44fa37b2a5feab3004f0a654506027bf032ceb154d94bb74ea772", size = 217252, upload-time = "2026-06-27T08:13:41.179Z" }, - { url = "https://files.pythonhosted.org/packages/44/ae/128ea5794387ca54bb4084566db20dbdfc9c21cb17b67d3fcb403927b5ba/xxhash-3.8.0-cp312-cp312-win32.whl", hash = "sha256:0b0836dee6022e22ba516ebfa8f76c6e4bda08d6c166c553e40867bac89e4a54", size = 31890, upload-time = "2026-06-27T08:13:42.568Z" }, - { url = "https://files.pythonhosted.org/packages/4f/04/a6c182dc566c88e8d1a497d22cc4ffdcfcc0a9fa80325efa6cd4b9002c54/xxhash-3.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:3bc2a09b98b8f85c75208cd2b2d2aecf40c77ecb2d72f6bf9757db51a98d3499", size = 32677, upload-time = "2026-06-27T08:13:43.705Z" }, - { url = "https://files.pythonhosted.org/packages/93/b5/aeda4e79f962c8d58ec60cb20a5abfe91c9f7d62e626f69f6659bc0bd0c4/xxhash-3.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:208e6a8b93426896d803224e9fabe26f8b9c651e8381a80b1fa31812faa091e3", size = 29155, upload-time = "2026-06-27T08:13:44.903Z" }, - { url = "https://files.pythonhosted.org/packages/ec/1f/96f43c5c7c7c4d44721f8d2e5d74698c667a30283c4b10a7e50a56804ee3/xxhash-3.8.0-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:36434c1d1b0a4729df1fa26ab11bffed1ba52666c0beb605c98a995b470cd143", size = 38508, upload-time = "2026-06-27T08:13:46.152Z" }, - { url = "https://files.pythonhosted.org/packages/1c/d9/7d5d6af4876c6481f2e0acb2dda64dd5209574bf7ba1ad4f6af7a1f8d473/xxhash-3.8.0-cp313-cp313-android_21_x86_64.whl", hash = "sha256:a5e6497cefcb2d67f1745c66df9718a99112583af6cc2b70da0312a2eb939f1e", size = 36542, upload-time = "2026-06-27T08:13:47.497Z" }, - { url = "https://files.pythonhosted.org/packages/32/ff/66fed439d78c5a09a1491a85af29bf8923b516530116731a9ac6b14dee2b/xxhash-3.8.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:5b00b82f1be708da9404fefd658cf5cf3be5ee3be2aae4bfe3b874255badd342", size = 31102, upload-time = "2026-06-27T08:13:48.721Z" }, - { url = "https://files.pythonhosted.org/packages/56/b8/9fae0399281095f8aca1f32b21947b3c3c75ad6021b255c5c6e4b11d3866/xxhash-3.8.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:38b0cb0ab7f283413b7cace2bf710d7cf8f702ea82cbc683908691d52028a89b", size = 32096, upload-time = "2026-06-27T08:13:50.138Z" }, - { url = "https://files.pythonhosted.org/packages/61/a4/e53d162c74a8a2950dc063969914387b0680da4c7c20ad17744ec03a3b0a/xxhash-3.8.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:084312171a9798dea85e924b2674f5e1a44933050a1ea1cb1c6b1364e004c66c", size = 34585, upload-time = "2026-06-27T08:13:51.572Z" }, - { url = "https://files.pythonhosted.org/packages/69/f5/e12397e3f2c4917b6572e103a3277cd27cc56330e304bba61d195d7e5224/xxhash-3.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6a1a9e845bd3bbc57d9356819e0d198fe23282e0576b398a6282a0f8fdc75aef", size = 34622, upload-time = "2026-06-27T08:13:52.818Z" }, - { url = "https://files.pythonhosted.org/packages/70/80/c053dc51af5c942229689a0e9cb66fdc999bbd840f645e761f5ab73cbb17/xxhash-3.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9ffbde09743ebaf8957b8426948fbe85eab5e5de0d29eec407fcff5a2812a3cc", size = 32320, upload-time = "2026-06-27T08:13:54.04Z" }, - { url = "https://files.pythonhosted.org/packages/f8/a3/294171b67dfe770e1293edcf2a3f7e41302cdb8aefb258585312191b3ffe/xxhash-3.8.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a6dee3952c2b6e82e7f1dbc5dbc6167f9c84126851def7926e32827c2816169c", size = 220532, upload-time = "2026-06-27T08:13:55.448Z" }, - { url = "https://files.pythonhosted.org/packages/80/c3/d141bfdeca785c8c680abf867d4b52a5e64a55d90df242c3141a3e58c4b2/xxhash-3.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf8ff8e12416c9fa05b43c7509b9332d6ffc4090413c4e7a1dee8599763b6d59", size = 241215, upload-time = "2026-06-27T08:13:57.047Z" }, - { url = "https://files.pythonhosted.org/packages/09/5a/aeaf35143a6f3d44db73298e861405bdd9c9dacaedfc369cb43d9fd65282/xxhash-3.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:cebbb322df4d97d8ef2704f49ed2f6f21f6702fafa0dc0c2a6ae70e904205689", size = 264615, upload-time = "2026-06-27T08:13:58.912Z" }, - { url = "https://files.pythonhosted.org/packages/bf/3e/f8ca782bb34f99693faab70a7989bcc84f62ffe93c9a4cca464a33507a4b/xxhash-3.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9a8d08707b4100ebce598fc59fadf04b42d79b855818d6994f8f0fffd1df8edb", size = 242682, upload-time = "2026-06-27T08:14:00.483Z" }, - { url = "https://files.pythonhosted.org/packages/d4/fa/ddbee4ff1542c2e88e72269a5a6bd18c3f26a80c2514e0918f5d1f3e9ec5/xxhash-3.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cf5427602dda15d8ce3c6d870d29bf07d43975f59c9d6d3f7f6f93a901b28b12", size = 473551, upload-time = "2026-06-27T08:14:02.17Z" }, - { url = "https://files.pythonhosted.org/packages/25/f5/a680d48dddab37ab2fd9189ca03f775e29e3627122e30790816d7eb365af/xxhash-3.8.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97d7bd715ea5050b6c9638b52c62adf3055b648ef6eee6892a4cd9697b530191", size = 220485, upload-time = "2026-06-27T08:14:03.765Z" }, - { url = "https://files.pythonhosted.org/packages/22/b1/7ac129b74981c07f1ff9c649f204465e86f83f9f29b2ebdc70d91514c365/xxhash-3.8.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0cd25bbbab37d898f6e5a90905ce6ae2c1f8bd6668c07cef406fb3e8c8c570dd", size = 310307, upload-time = "2026-06-27T08:14:05.366Z" }, - { url = "https://files.pythonhosted.org/packages/67/e6/43e673411249dd63f6cd974523a1b32fad75cf5453e363bc8f44af215fb9/xxhash-3.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3e30e5c057f483c3c53a11b53eba091a737cb19dfead36c8b23bf5beb4a169cd", size = 238164, upload-time = "2026-06-27T08:14:07.149Z" }, - { url = "https://files.pythonhosted.org/packages/e5/95/87f8baf41f63130f3637104b7a610f82b20106332fc6e289c8dbf7955d0e/xxhash-3.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:07dd44d992ebd456752bc25b1c42cd172d94bd8cb24049300449ad0716081c3a", size = 269062, upload-time = "2026-06-27T08:14:08.834Z" }, - { url = "https://files.pythonhosted.org/packages/38/c9/3369b497cd1f926b930c52fd2400606f177790d887b49f9e86bddcc24562/xxhash-3.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3118600a3102d4707dc1c485dbc3acbbbf37819069ad3e7854e77b923745d76b", size = 225007, upload-time = "2026-06-27T08:14:10.689Z" }, - { url = "https://files.pythonhosted.org/packages/34/c8/03dceb86a8128858ac105bd6e282d62b3db6fd421a79bd8a9f6b8cdc47a7/xxhash-3.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7ed37b0c95d8fb3fbaad5e13cc0a9727eb8739d1d54b2adef28108c250cada3a", size = 240815, upload-time = "2026-06-27T08:14:12.195Z" }, - { url = "https://files.pythonhosted.org/packages/47/a5/ebd43eeb1af1dd8f0201943688b20958e99d3f6eb36481fb8c37b55ef139/xxhash-3.8.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bb043da412e478e7b1db3407051124b85b133803794d3809ad6d92870b304fc7", size = 300632, upload-time = "2026-06-27T08:14:13.916Z" }, - { url = "https://files.pythonhosted.org/packages/df/24/c873e41a3c00dacc385c8ff08c007723f6a528922c1cea7fd9684e86dae7/xxhash-3.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:196fc132683d9311a0bdce8388ee52bfa07fdc1987cc428a27956e47ccd7b50d", size = 443293, upload-time = "2026-06-27T08:14:15.446Z" }, - { url = "https://files.pythonhosted.org/packages/4f/1b/c671272fe28f70574e3c574d58465f26460154bcc68876121872afa1c14d/xxhash-3.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfb5411af3b77c75e99db100aa15c5ba623c85d72c565e4d7a0ed1a986ff766e", size = 217327, upload-time = "2026-06-27T08:14:17.28Z" }, - { url = "https://files.pythonhosted.org/packages/57/43/b45a52f795812cb769b6ac159e69b605d18b1c067749e63dcac159e90064/xxhash-3.8.0-cp313-cp313-win32.whl", hash = "sha256:6d1d6179e26830c6690fac63f76d372f69714b977e12ca9c42188a60f51c59f5", size = 31898, upload-time = "2026-06-27T08:14:18.952Z" }, - { url = "https://files.pythonhosted.org/packages/a1/42/2bd70e4eec25dc5990652979d708d4d7c999793d7d5af5d0e48ab4374dc1/xxhash-3.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:7c92427a56a12f4d5c7bb26dbb9e9a4658c313ecb6c2f1dca349902e3822df07", size = 32680, upload-time = "2026-06-27T08:14:20.277Z" }, - { url = "https://files.pythonhosted.org/packages/d5/c8/2fe61edb6144183cf094035a8c5354c65a073127acf6379655ed1e705b70/xxhash-3.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:9fc8453642c1c6d38b4fbac8901c2452ce1fa88b27f003bfee6703cbfae9bd63", size = 29157, upload-time = "2026-06-27T08:14:21.674Z" }, - { url = "https://files.pythonhosted.org/packages/b9/b8/81d17a993b9a4750ba426ce966421681bb4b8e82a460cd346756491b8cc2/xxhash-3.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:efcacb644a915f010dc477447b045e5dcde1afaa40d16b2f0f8e7cd99c9e1635", size = 34897, upload-time = "2026-06-27T08:14:23.044Z" }, - { url = "https://files.pythonhosted.org/packages/bb/3b/f5a368e3273440b3ea58fbd3f0b08c19f552b25ca59f43f5732ca96d2126/xxhash-3.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d1e0dbc510cff94c5efbcc2b82c28b41519fad09b5b1f9f3d99c63e3940e49a0", size = 32630, upload-time = "2026-06-27T08:14:24.603Z" }, - { url = "https://files.pythonhosted.org/packages/a6/ab/f424359c91c55f564fbbe4e454a126eb522471109f67376f20ad19c5e663/xxhash-3.8.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ff19d016a41c90d1f519005887191896b6da1274e1d5d48b347e17eb798ffc5a", size = 225874, upload-time = "2026-06-27T08:14:25.992Z" }, - { url = "https://files.pythonhosted.org/packages/ac/c2/434579ef9235123b6c9bfa89c5614e0001e988613b91557b24aa326d9faa/xxhash-3.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aafc3eab99c50508852e34307e9565933bf128cad084cac7d2471b7ab1743de0", size = 249705, upload-time = "2026-06-27T08:14:27.607Z" }, - { url = "https://files.pythonhosted.org/packages/c0/6c/3c0c917331ca3c71f826cedce2127f230624e2b49b992472dd5e9e72101c/xxhash-3.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5e521368ed79ae6c4d31e1e417726643c49d7d6e286f4fdabf9a8330ed8a8ff7", size = 274716, upload-time = "2026-06-27T08:14:29.495Z" }, - { url = "https://files.pythonhosted.org/packages/c1/f3/a8bb98d3307c67e88be9642dff52854c3de3f488f95989b60ff69c8dcc42/xxhash-3.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6a0127688d116ec0c225e7e1f744e3f206de2b8822ffeb31a9ab5cc6384f92c5", size = 252019, upload-time = "2026-06-27T08:14:31.247Z" }, - { url = "https://files.pythonhosted.org/packages/f7/73/fab69a2e5b6353dde643209fe9b6adf4fbd64c888e531deffc476bfb2635/xxhash-3.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:22c0b17da2f9fea0f8836538512249871b359141616bad44c58d238b5f011f40", size = 482024, upload-time = "2026-06-27T08:14:32.973Z" }, - { url = "https://files.pythonhosted.org/packages/a3/5b/ba34099b5278097ec9c68c0b740719813553bfd11ca17e7353de6d2a41e3/xxhash-3.8.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3d49465646b1a5e3b1729c5f636e05676a2fb52e203e3b22a5411c416c4c5302", size = 226655, upload-time = "2026-06-27T08:14:34.608Z" }, - { url = "https://files.pythonhosted.org/packages/76/0c/90aba4708a37fe752b324a7cbf10058eaa33e892cdd62751ff17a5137b93/xxhash-3.8.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c2853dea1e30ed00ca87dd87d76da5da063d302b823b3fb80ccd18421de0f251", size = 319583, upload-time = "2026-06-27T08:14:36.419Z" }, - { url = "https://files.pythonhosted.org/packages/38/46/42e349e2d3017b2688f4cb301742c37c438e77963e3fef711edce2fc5c65/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:82f0102a2a3760287b7cd7f9e0a30edd4c3b18762ed1a242208d43c8e2bcf30b", size = 246000, upload-time = "2026-06-27T08:14:38.104Z" }, - { url = "https://files.pythonhosted.org/packages/ee/15/741b947ae3c768e82018c46846f8616f6aa9b5042649f318a1a6897defe3/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:b8414a66a7524596d841cad5dc1adab6ce76848db5ab2b83db911fbdab1417af", size = 275455, upload-time = "2026-06-27T08:14:39.841Z" }, - { url = "https://files.pythonhosted.org/packages/c6/b4/a9db84c9458fc8f53eaf0051377d1e9eecd9f330fb1225640027417a309d/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0dbaa73df10414ea1e41b98691a9d8241d4c47ad8d02c726587a3cda05278e53", size = 231209, upload-time = "2026-06-27T08:14:41.543Z" }, - { url = "https://files.pythonhosted.org/packages/20/92/60a868cd34851746d0b0d95dced0f42867c7c00606f6e5dba85b70b232ce/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:43fc9aaba10ab4267c90793601f60d35c3c9caa1544eceb483618a71ad9ce7da", size = 250416, upload-time = "2026-06-27T08:14:43.193Z" }, - { url = "https://files.pythonhosted.org/packages/7a/6a/168ca46a4679c32aae9246caa1fddf35981d6304487e45e992b3d4530324/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ec5eb3d28fbb9802c6d2526f772133a06c91d6f03756fcc67c834b642ffdd51d", size = 309764, upload-time = "2026-06-27T08:14:44.79Z" }, - { url = "https://files.pythonhosted.org/packages/18/0b/13646b348c07679c818791ab2d35415db5cb20f3bc77daaa255909a401b4/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:2b77c301b644cd9b4d0749a3291081ec2048a6bef7fe0487c993bbba3efb9ce0", size = 448650, upload-time = "2026-06-27T08:14:46.562Z" }, - { url = "https://files.pythonhosted.org/packages/59/9a/3d244b2acf6bbd86a363817ee09084b4684e8e11840663e19869e9e0d952/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d7ece11a132325353890a144c30119073617a1299c593ca29b96c315b07e1edd", size = 223572, upload-time = "2026-06-27T08:14:48.294Z" }, - { url = "https://files.pythonhosted.org/packages/6f/c7/143410d026a6e0d86dc69037ec2a3b8db810a54e7f443b340ac17612be2e/xxhash-3.8.0-cp313-cp313t-win32.whl", hash = "sha256:b21db84df7b9d54d9e4195a964243c1b32d745c6fbc0cfcfffee1d4bd297196a", size = 32301, upload-time = "2026-06-27T08:14:49.687Z" }, - { url = "https://files.pythonhosted.org/packages/6c/db/2240b0638161637b2f310231748a7a6a06c79fb43a3adb34c96f359762bf/xxhash-3.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:0643b7d9f598f6da6f1f6b899f4358250d0fb853242e2d712cbde27bf5a99d29", size = 33221, upload-time = "2026-06-27T08:14:51.404Z" }, - { url = "https://files.pythonhosted.org/packages/e0/d8/52038e4fa5baf4f00654a225516168d02908edfec7ca104fbefc58af394f/xxhash-3.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:4bbacf2e938526969f8ab3334d4ac3da14ea059e1dfd1339a92f9091467e750f", size = 29294, upload-time = "2026-06-27T08:14:52.778Z" }, - { url = "https://files.pythonhosted.org/packages/e5/ef/a09907aa28bdcdf6810d5c26656b154c60c0f06bb8db8442a1192d9c227a/xxhash-3.8.0-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:557e2a7cc0b6a634cf9c8e5c975d96b7da796fdeb1824569d760cf0f25b6f33f", size = 38365, upload-time = "2026-06-27T08:14:54.166Z" }, - { url = "https://files.pythonhosted.org/packages/d2/4d/d991ff77bc489c2231025e64e570502156d573c7bff69c917589cc307089/xxhash-3.8.0-cp314-cp314-android_24_x86_64.whl", hash = "sha256:dad744d1613cbfddb844dad93adbffbd51c3e9f53ceea9568f7c3b94bedc19a4", size = 36477, upload-time = "2026-06-27T08:14:55.427Z" }, - { url = "https://files.pythonhosted.org/packages/cc/0e/553eab001f1e274da73da074968cdc8be8cacfb318937ab9871b8e1909cb/xxhash-3.8.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:953f29b22c04b123cf3cd2e08bccde3a73184aeda5a1038e0054cb3355644120", size = 31116, upload-time = "2026-06-27T08:14:56.897Z" }, - { url = "https://files.pythonhosted.org/packages/55/d5/d0f4dbe7b4d9ce0125f16e45ec0be5e04f6a172edb4e2fa551c4f2eb5d7a/xxhash-3.8.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:aa699e0253ceffecf41cae858d0a11f2439d6874a0890b556387bffe11dc1c08", size = 32112, upload-time = "2026-06-27T08:14:58.126Z" }, - { url = "https://files.pythonhosted.org/packages/2e/2f/b332c7bede6a676343f2c9c8dea233c8c82753eaeda6f7a2c321d8c58ca3/xxhash-3.8.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e232c82466babc13e956d53aa84d0149660ed6886bc195248bb4d03bf2eca301", size = 34618, upload-time = "2026-06-27T08:14:59.458Z" }, - { url = "https://files.pythonhosted.org/packages/b3/5b/2bf3c9e61c7cf8f53bce937af45e22b72bb1f224d5afb20352beba0d628d/xxhash-3.8.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7f75fd1c6a5028f345cd4a8c52f4774d2e5b7809fa58111c60a5502b528914a4", size = 34739, upload-time = "2026-06-27T08:15:00.863Z" }, - { url = "https://files.pythonhosted.org/packages/64/b6/e88521f5736c181b89bfb7ab756f0ca658a8a1ecece7277b75e167717614/xxhash-3.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b49d7e09b211a1ad658dbe2dbf6561eb92f2e6926bd1101e2d023178371f2d6f", size = 32332, upload-time = "2026-06-27T08:15:02.383Z" }, - { url = "https://files.pythonhosted.org/packages/ed/a2/fba440739fa5f86d2c28738c202e88d3dd063290c8bbb20e183c5334456a/xxhash-3.8.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ceb702bc8e56b7f1f1413d42aa294045b9a0e4c9888e07edc5cd153e8c4c948f", size = 220479, upload-time = "2026-06-27T08:15:03.785Z" }, - { url = "https://files.pythonhosted.org/packages/2c/1c/4a1639efec16416695d6c7bc6b224d3f607e0b8cbe2409fa81081a849d1c/xxhash-3.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f3c96e06bdb122e8cc84f5c7088579f3102b828efd62e9dc964a9d17c7b89e", size = 241409, upload-time = "2026-06-27T08:15:05.439Z" }, - { url = "https://files.pythonhosted.org/packages/92/d1/8ce471f8d6752384f972fd5f6363f2e8d8b867a89fbd724c6dbd91d2bb98/xxhash-3.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:415a8d06ac9bea36b1e06b603a347e0f62401042a97d7bfccec8ae2da12ad784", size = 264433, upload-time = "2026-06-27T08:15:07.027Z" }, - { url = "https://files.pythonhosted.org/packages/95/77/400a281683fd39c54e2ac497fa67bdf886baaadb8c0ba58f7e1ea1d7692e/xxhash-3.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7f5ccdd2deb5dce31201cc0eec94388cce97e681429073db50903fab0a0a8a0d", size = 242835, upload-time = "2026-06-27T08:15:08.703Z" }, - { url = "https://files.pythonhosted.org/packages/aa/a6/edda651cfa0ba8e921791e93468fae655b63894d89730fcbfe46704f0d0a/xxhash-3.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1a6cf81bc699d3a5ebfcf2fdb2a7bd2e096708d7de193f6f322944a02ba00953", size = 473800, upload-time = "2026-06-27T08:15:10.503Z" }, - { url = "https://files.pythonhosted.org/packages/dd/da/50f764ec6a93d3961fce294567e41bfca0e66d168deed354a3dc90ebeba6/xxhash-3.8.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e4d12a04d7ffc0359f0eadc4535a53cab113044c8d2f262c7e9a56950a5ed50e", size = 220677, upload-time = "2026-06-27T08:15:12.622Z" }, - { url = "https://files.pythonhosted.org/packages/bb/49/9fe4ed5aac6f38629cc83b34f84748b83ad8295a578ec6a49d8bf896cafb/xxhash-3.8.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d209373fcb66138c652cf843385ee60866e50158a7869bbbf8b322d9a822b765", size = 310385, upload-time = "2026-06-27T08:15:14.384Z" }, - { url = "https://files.pythonhosted.org/packages/83/f5/1147e03c0553ed22bbae9ce47503c37ee0c5f95592aae10f339c25f61de9/xxhash-3.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b88a3fe28277811e599efa6e1c96abce8a77d60dd79c94da7a9b5c377c172b7b", size = 238330, upload-time = "2026-06-27T08:15:16.201Z" }, - { url = "https://files.pythonhosted.org/packages/b8/d8/92daf66c1966c84da5c97a06ced1480208d3a3bd465cb0630565ec00d1b9/xxhash-3.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5d5a888a5ef997cb35f1aad346eb861cd87ecfe24f5e25d5aa4c9fd1bd3950c2", size = 268667, upload-time = "2026-06-27T08:15:18.064Z" }, - { url = "https://files.pythonhosted.org/packages/bd/c0/080c1a92972667e183c04b03f33c877f8ec61cfa3570e61731077286648d/xxhash-3.8.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:de2836e0329c01555957a603dcd113c337c577081153d691c12a51c5be3282b0", size = 224934, upload-time = "2026-06-27T08:15:19.972Z" }, - { url = "https://files.pythonhosted.org/packages/d9/d5/cbc4e5b2bee10c94cba05b5bb2b8033e7ef44ae742583fdafcd9188e33ed/xxhash-3.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:4bc74eedb0dd5827b3be748bacf9fdb50004037a3e16c7ddb5defae2682cef71", size = 240870, upload-time = "2026-06-27T08:15:22.04Z" }, - { url = "https://files.pythonhosted.org/packages/76/f7/09679b00e192b741b65c230440c4f7e6df3251a9ad427a518ddf262ec71a/xxhash-3.8.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:c571b03d59e339b010dc84f15a6f1cff80212f3a3116c2a71e2303c95065b1f6", size = 300683, upload-time = "2026-06-27T08:15:23.647Z" }, - { url = "https://files.pythonhosted.org/packages/5f/1b/f43ec36e8c6a20c77be0bcca23f0b133ed8a0312681500d1676eebd71924/xxhash-3.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:87626acdd6e2d762c588a4ffe94258c5ef34fb6049a4a3b25019bdb7f9267a9b", size = 443407, upload-time = "2026-06-27T08:15:25.504Z" }, - { url = "https://files.pythonhosted.org/packages/45/2e/a3e3a779c5e4789daf975e05cc1c7f11bae724a03855120029d4592c8e63/xxhash-3.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:076d8a4fb290af952826922aa42a46bfc64caa31662ce4e2925a445d0e6ce57f", size = 217559, upload-time = "2026-06-27T08:15:27.234Z" }, - { url = "https://files.pythonhosted.org/packages/44/da/1c1e078ac290afff304a541a2a60965beb369ad65b4f30ec93ea1e0b7210/xxhash-3.8.0-cp314-cp314-win32.whl", hash = "sha256:52f8c7c9833d947e60df830671f6eca810d7c667051243985a561c79f1a3d545", size = 32602, upload-time = "2026-06-27T08:15:28.809Z" }, - { url = "https://files.pythonhosted.org/packages/d7/7a/d455cb83d5e3c94046234294fb5dbbe5da600d1bbdf76b9527756920cce9/xxhash-3.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:4fbfcb7dd307e23189a71050f6e27746926590330f37d5fd2ffcb8ea78de1f42", size = 33393, upload-time = "2026-06-27T08:15:30.166Z" }, - { url = "https://files.pythonhosted.org/packages/89/8f/1b14471f617bc96edbb9566099a162d918a981381c398114726cc600b76c/xxhash-3.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:ecef1e65b4715c7326002073763fe94cc44c756a0698508abb915ab3d6be6e3d", size = 30007, upload-time = "2026-06-27T08:15:31.634Z" }, - { url = "https://files.pythonhosted.org/packages/11/8d/51ad2f9f784121c8057ef1ba36362f58d4595cbcad16322941f5b73eb53d/xxhash-3.8.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:02ed856a765cb6e006168595d9455ac8c3c4d60cc04cd47a158a1ac677d68f0f", size = 34957, upload-time = "2026-06-27T08:15:33.292Z" }, - { url = "https://files.pythonhosted.org/packages/1b/14/175c573ae4fac48bf21a82e5b9ceec75d64c520c51ca08de3105de539438/xxhash-3.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:eec30461a7b457611098ba7ab09363e36c8b2645b4687fb6f3d405bb646e3410", size = 32635, upload-time = "2026-06-27T08:15:34.766Z" }, - { url = "https://files.pythonhosted.org/packages/96/08/f83efabd350a50c31c851b88891e318a6f07bdbf40a43d0f7bb6cedade7f/xxhash-3.8.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b471744912d1ce5dd6d3975b7525e77518359ebf3aa1bd7d501e199f5ae488ea", size = 225969, upload-time = "2026-06-27T08:15:36.35Z" }, - { url = "https://files.pythonhosted.org/packages/7c/78/2b6d12da9cf572c84d93b88ecbf9bf6539a7c5219bde128b214396b97c8b/xxhash-3.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3748d71202bf3f279e77cb8b273b6d0f29d1bcaefb6ce6cb03b95f358863ba37", size = 249851, upload-time = "2026-06-27T08:15:38.087Z" }, - { url = "https://files.pythonhosted.org/packages/d0/0a/755eeb1882634983b24e6375a95ed233228dc48f0ef12655388bf3c7eeaf/xxhash-3.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3b3bf59ea94b2a23b0f992769804ab9401d5cdcd9df0062fe2cd78a491ae8851", size = 274842, upload-time = "2026-06-27T08:15:39.808Z" }, - { url = "https://files.pythonhosted.org/packages/77/f2/09b1231cad17c314e51664c4a004c919108ec59aba10f9a28fa061e7b8be/xxhash-3.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:40f061aa5379eba249e9367b179515571e632be6d1b6f55ac139e6fe3d08463c", size = 252218, upload-time = "2026-06-27T08:15:42.105Z" }, - { url = "https://files.pythonhosted.org/packages/b2/24/de756d55547953494eb6775aea92e258035647b3ecb8547618cd549001e1/xxhash-3.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:680d70896a61fc920cc717a0a8fe8a9fb5858c563184666e31874caa54a16d9e", size = 482135, upload-time = "2026-06-27T08:15:44.476Z" }, - { url = "https://files.pythonhosted.org/packages/e5/63/b8147633e32f98ef2b4bb0dfca82f0f63e2b02ff179f20664af64c4216a7/xxhash-3.8.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14973fbdee136588e57447401b521f466a42faca41eecdf35123c73103512ca8", size = 226776, upload-time = "2026-06-27T08:15:46.597Z" }, - { url = "https://files.pythonhosted.org/packages/29/37/ba051d8f0380d3cf845b23ba058a17d32025846463eb6bf885887fc8effe/xxhash-3.8.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:96c6bca2486cdc58b125966817a92a6abe6ef1fab86b2f8798a7e93488782540", size = 319738, upload-time = "2026-06-27T08:15:48.394Z" }, - { url = "https://files.pythonhosted.org/packages/4f/6f/36e0a27dd27ffa3f7b521650cbcd52a00fb86b71343ffadb642374e8263c/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0b1109ae238e932d8482f9cb568b56a405cc73bc7a36b837844087f1298dd218", size = 246136, upload-time = "2026-06-27T08:15:50.981Z" }, - { url = "https://files.pythonhosted.org/packages/fe/73/2663dbf4c09386a9dcc8a94d7a14b4609ed4bad8180ced5b848e60a9b660/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:1da5db0863400eade7c5a31969754d1392189f26b4105f6631da2c6c7ea3bccc", size = 275568, upload-time = "2026-06-27T08:15:52.735Z" }, - { url = "https://files.pythonhosted.org/packages/d6/58/f3ce1bc3bb3971191f6521273ddae98d3c610bcefbbed5327c3b3627c12f/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:c61b5a0f21ace5e886f177cce43826d85a7c84e35a9e17cb6d1b4ac0b7a7d833", size = 231314, upload-time = "2026-06-27T08:15:54.73Z" }, - { url = "https://files.pythonhosted.org/packages/4d/51/835706a36cdc00e5b638fba9b22218b3d40d23a7677c923feca8a3f55b98/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:1db4f27835a450c7e729bc9330c6e702113711cea1f873d646e3a31fe96a9732", size = 250521, upload-time = "2026-06-27T08:15:56.853Z" }, - { url = "https://files.pythonhosted.org/packages/c1/47/b0b62caa3caee58ab9de8969f66aef1c3729886f3ff60e173fda3f2762be/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4788a470f946df34383abc6cd345088c13f897a5ee580c4cdd12b1d32ad218ef", size = 309926, upload-time = "2026-06-27T08:15:58.704Z" }, - { url = "https://files.pythonhosted.org/packages/69/c4/60e6d18a0e131c7af622374af9deede15d3c47d8e5e7221933481b57b319/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3b6dfa83096cb1e54d082acebaf67f0c42667c56dc48ba536a76cac08d46391e", size = 448812, upload-time = "2026-06-27T08:16:00.619Z" }, - { url = "https://files.pythonhosted.org/packages/12/9f/c9627daa052be39a932d0e17c6bf6a9041d2cde3afacbded9196acf70261/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:57ec0ba5299a9a7df376063c139f5826ff0c89b438703939af3d252c31ca96a4", size = 223639, upload-time = "2026-06-27T08:16:02.784Z" }, - { url = "https://files.pythonhosted.org/packages/a9/38/92916e008a84c1f1a9aef82e4363cdc478a722ff69e59c6afbf93d3d1fda/xxhash-3.8.0-cp314-cp314t-win32.whl", hash = "sha256:d9a61f23b999baeb84102aba767b1b3e94958eab94e6c11b08927e7dc4200795", size = 33078, upload-time = "2026-06-27T08:16:04.639Z" }, - { url = "https://files.pythonhosted.org/packages/31/7c/e413bc75121d9628bf023b2ed251411ca3a447cf00cd9aa3438ab17f6c67/xxhash-3.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:61069b260fff84116235bb93845f319284dc6b42527c215af59264f4c2ee3468", size = 33953, upload-time = "2026-06-27T08:16:06.347Z" }, - { url = "https://files.pythonhosted.org/packages/f6/eb/21a96e218375bd8b6ecd6d07cf60c8ff1a046e93cdedc3cf7bc3309edf7b/xxhash-3.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:73cecd431b4f572d38fcf1a7fe85b30eb987778ef9e7a70bc9ffcf2d64810e6f", size = 30164, upload-time = "2026-06-27T08:16:08.009Z" }, - { url = "https://files.pythonhosted.org/packages/96/84/9bb3cc67475ac7678476b30eed2f1140431f06386d637534194037c0624f/xxhash-3.8.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ba14843f20df2dce6ff6684411a56ae53da44336546c55f8947e70aebb8cdd21", size = 32604, upload-time = "2026-06-27T08:17:19.291Z" }, - { url = "https://files.pythonhosted.org/packages/42/6d/e98f9dd62c89e8895e4f3b525b6dbc3efcf27e2b99800e51388c59eb96dd/xxhash-3.8.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ec6666a5311beae3f6cb5f2fd28c2b77e2df32702c8206f45c786a6ef81b3751", size = 29787, upload-time = "2026-06-27T08:17:21.001Z" }, - { url = "https://files.pythonhosted.org/packages/db/51/e7844a65c62d6d78747e4d149508d65a3df6fb65d72322c2526789e9f600/xxhash-3.8.0-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1ec9afdd53ac5f4fd1d8918807ba6c35ba62269086af794884b9f168a73331ea", size = 43155, upload-time = "2026-06-27T08:17:22.721Z" }, - { url = "https://files.pythonhosted.org/packages/3f/5d/652c47481053fabc33ea229540bd330a45f68d7a5277f45e6cf879c29965/xxhash-3.8.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68594a54be2eb5992d9b0d0a0ec7c32a7a8e930f06d6cb951d69708055680994", size = 38137, upload-time = "2026-06-27T08:17:24.295Z" }, - { url = "https://files.pythonhosted.org/packages/7d/a5/7b6e961a03ee713cbdbaa3d2cf3ddd33453a4d4112bbde58f2f607ab64d2/xxhash-3.8.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:591d5eb256abf59438800ace2730ac33f77bc6ab8c3623fab1ea24d9d8b28f3a", size = 34376, upload-time = "2026-06-27T08:17:25.688Z" }, - { url = "https://files.pythonhosted.org/packages/da/aa/95d36393bf732df516a2dcf4fd7e9e851bc033a5970e30774b972137f4da/xxhash-3.8.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:7f4eecf800275e62b6bcb41e65f361f2277cc886c2bff4e299959d701e5fcf93", size = 32798, upload-time = "2026-06-27T08:17:27.188Z" }, + { url = "https://files.pythonhosted.org/packages/55/97/1a8cebf0a6650417f08a18231590e2515aacd5ce39c3ad8b9e013ebd437d/xxhash-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:27a9e475157f7315826118e3f3127909a0fe25f1b43d3d3be9c584f9d265f937", size = 34695, upload-time = "2026-07-06T10:43:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/2f/cf/745b9bc0dd9c341bc074b5fc700db7bbef0f3b69ab21446492296ab37e50/xxhash-3.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b2ce44bf8f4a1d01f418b3110ff8dff32fd3f3e836c0e06333c3725f243fa6c", size = 32376, upload-time = "2026-07-06T10:43:41.97Z" }, + { url = "https://files.pythonhosted.org/packages/65/a4/8512a901b1d6ad4a9838d1b40385907a879d7e005a5afbec5d39526b69f6/xxhash-3.8.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:942bc86e9be6fdd6e1175048f5fe8f8fdaaf2309dd1323ef1e155a69cd346780", size = 217470, upload-time = "2026-07-06T10:43:43.572Z" }, + { url = "https://files.pythonhosted.org/packages/a0/ad/0ffd8094ea29579bb2dc42fa74d08570e9ea3d95db561e6b1105e69b9ca6/xxhash-3.8.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0204701e6d01f64254e0e5ff4255812b1febe027ddd7dda63372e27f98b5e91f", size = 237799, upload-time = "2026-07-06T10:43:45.248Z" }, + { url = "https://files.pythonhosted.org/packages/b3/90/783c6b3f9336bd07449fe672be32cef6833633936bbfda8d3b23ee18d202/xxhash-3.8.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7dc4bdf008f77c88d544849c48c1a40faf25a5eff6cc466de2e8edc37c191fce", size = 262587, upload-time = "2026-07-06T10:43:46.733Z" }, + { url = "https://files.pythonhosted.org/packages/c4/77/ba0316a7c3e661b86830a47ae4987798616ce1b15af8d2a6358e2d89ef60/xxhash-3.8.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c566b123dce7e4867ca518434cdfb9f84e5023771235b2e3107a26c9a41cbd8", size = 238484, upload-time = "2026-07-06T10:43:48.453Z" }, + { url = "https://files.pythonhosted.org/packages/09/79/33001037c1cba90f4ced38b257161c13452024c0db44208f883e2e47f3fc/xxhash-3.8.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9f23083e1bd9d901f844af7a126727c486e7eada9a1a6791c8f7e73f94fac656", size = 469909, upload-time = "2026-07-06T10:43:50.188Z" }, + { url = "https://files.pythonhosted.org/packages/45/90/237eded9dd6ae638083294e5a9f77b317aaebd480a330806b39c192a0de1/xxhash-3.8.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64af54dd1c3a45a27c04942f9a1a4683322bdd127f4745cca4e02549c1d2d2bb", size = 217166, upload-time = "2026-07-06T10:43:51.816Z" }, + { url = "https://files.pythonhosted.org/packages/0b/6a/8cb439dc9920e1468e1c2d69ef77cbeb4be3b1ae9f4b5344c07a2b59af18/xxhash-3.8.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8ea8a141eeced4f6262ab6dd71c681ac546a558c30bb586abe087d814b5f85ea", size = 307593, upload-time = "2026-07-06T10:43:53.436Z" }, + { url = "https://files.pythonhosted.org/packages/ec/c6/c0607d373c8affea92101a3926c4fc8b026bcf8983e05fd58f3a0380ebf8/xxhash-3.8.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a98b2f95cab589e0f5e92c48431afb4d56238b8bf6668edcc66166180e9b509b", size = 234702, upload-time = "2026-07-06T10:43:55.042Z" }, + { url = "https://files.pythonhosted.org/packages/5b/cb/f4cfd456624c1f017858168b7ba9443dad810da8aac779a612658450e827/xxhash-3.8.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1b86ae798a976ccbc1d02af6ccb98f5b4d24756b1f65e995f11d10fe071f486f", size = 265749, upload-time = "2026-07-06T10:43:56.749Z" }, + { url = "https://files.pythonhosted.org/packages/33/f3/9006669c04b01206e21b2177425c649461ba188930a052c2f1728d6ec6a8/xxhash-3.8.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:81f4ed9ca9644bc95cd976bfe10f7a4cafab8ffdc3aed52877d4600e445be7ef", size = 221992, upload-time = "2026-07-06T10:43:58.12Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0b/7e6f3eaa05df5e0b6c94aa452b0672801f7031e602081f07fd441aaaaed5/xxhash-3.8.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:cb3fe820c27593f170770d6c8d791936cf6275d9269405fbb7b30a55363c10c8", size = 236899, upload-time = "2026-07-06T10:43:59.562Z" }, + { url = "https://files.pythonhosted.org/packages/da/cc/bbaee4987f3aab1d7b33bb430bb49e940646160af448b9167431c931126d/xxhash-3.8.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:7345007c12780985de4fd740148776d1eee18c0d41407c6fa1e48c5450304fe5", size = 297934, upload-time = "2026-07-06T10:44:01.132Z" }, + { url = "https://files.pythonhosted.org/packages/a7/97/6bee358660eb8b4f73c00b00b00bc616ebde00e1ab4b67c63486ce360648/xxhash-3.8.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:12eaeaa9ab8b9e6033a1fa5f6b338aaf55ff4df4bee11b59fd6ee03b19186ee4", size = 439315, upload-time = "2026-07-06T10:44:02.878Z" }, + { url = "https://files.pythonhosted.org/packages/c6/50/7e35275f39256bedace0c3cd5be3c72d4ac9d5aecf5e5fdc3530337cd263/xxhash-3.8.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2a845687219ba3214126f14a8a5861f97c9e065a7d0b8252adb6df13eea86fb", size = 214038, upload-time = "2026-07-06T10:44:04.504Z" }, + { url = "https://files.pythonhosted.org/packages/59/2d/69d02d096ee50bdf3ef0d208d874f52c71b1aa6906066bce3c52fedb8bc6/xxhash-3.8.1-cp310-cp310-win32.whl", hash = "sha256:656256c9f9303e47f07d5cb8ae4468285370adfafd7ba48aea33a458e7697626", size = 31939, upload-time = "2026-07-06T10:44:06.213Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1d/e06fca9844919ca91c6587d530cfa1e745830ec73ad38f44f04b25d1bfb7/xxhash-3.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:27cfc2f1ed76f956f36dfe0c56e5f5a3e94cd91eb78b893f63e2ef2ae404fcdf", size = 32729, upload-time = "2026-07-06T10:44:07.621Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c2/800648d99039927b5a86d8ae02cd86a556a5ee1678d388216f6b44c8966c/xxhash-3.8.1-cp310-cp310-win_arm64.whl", hash = "sha256:c85949d02c85adf6d786eb94858e124989a632a4e65739835b2fc5761827fac3", size = 29215, upload-time = "2026-07-06T10:44:08.916Z" }, + { url = "https://files.pythonhosted.org/packages/8a/5a/05eaa129555f85476a3e16ff869e95f81a78bbe4647eef9d0229f515a317/xxhash-3.8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602efcad4a42c184e81d43a2b7e6e4f524d619878f2b6ee2ba469011f47c8147", size = 34699, upload-time = "2026-07-06T10:44:10.14Z" }, + { url = "https://files.pythonhosted.org/packages/80/59/0df1133958b2228929355e022aab1e958c7b2c43e27bf7f59bc9edfa8a54/xxhash-3.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:131324f719957b988861714de7d6ddf57b47abec3b0cc691302ffeaba0e05e10", size = 32373, upload-time = "2026-07-06T10:44:11.353Z" }, + { url = "https://files.pythonhosted.org/packages/3e/bf/1cfda5b5e6bf26617812b4a31662ef2220d2ad04e0a55b8ff9eb36e56a5c/xxhash-3.8.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:db77278a6eddadbf44ce5aae2fee5ebb4d061f026b1ce2130d058cd4d7a7b670", size = 220284, upload-time = "2026-07-06T10:44:12.683Z" }, + { url = "https://files.pythonhosted.org/packages/70/93/45dc0ad7913b69e5b08bd039236cf628380e4c9cc76a8a4c6625a328e058/xxhash-3.8.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c332dd48b8cb050da2bb2a3c96d72b1664168650a250ef9718e423df7989e05", size = 240980, upload-time = "2026-07-06T10:44:14.297Z" }, + { url = "https://files.pythonhosted.org/packages/e9/02/f28ba7d17f2c1410ee397982c817ab1bd5b2701070c2d2c373539aad000a/xxhash-3.8.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a5cd96f6dcdf4fa657b2d95668d71d58455248f98712ecffaa9c528edf40ccae", size = 264526, upload-time = "2026-07-06T10:44:16.017Z" }, + { url = "https://files.pythonhosted.org/packages/5c/d0/f10651cec2c7981b20d693deae6bdfc438427d92be2db4ccabb6181f0021/xxhash-3.8.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c959f88160b13b4e730b0d75b459b7929fc0d2225c284c9683ac95d6feeeac6a", size = 241369, upload-time = "2026-07-06T10:44:17.698Z" }, + { url = "https://files.pythonhosted.org/packages/ff/40/136e0cbaf5db51e191423b1c98643593189f02b6cd90837bf64b19113d70/xxhash-3.8.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:027dee4355f3fcc41481650d846cf6cfc895c85a1ab7acd063063821a0df5b4c", size = 473186, upload-time = "2026-07-06T10:44:19.354Z" }, + { url = "https://files.pythonhosted.org/packages/4b/3f/6aa808a96bdc43dba9a740dec56c744526ee3c0019e32c75e810fa90ae4d/xxhash-3.8.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad52a0e4bcc0ba956a953a169d1feec2734a64981d689e4fc8f490f7bf91af60", size = 220092, upload-time = "2026-07-06T10:44:20.956Z" }, + { url = "https://files.pythonhosted.org/packages/47/28/a8675e78a9ced96dab853416162268e10e05b452e95db7888cf69f58ac5f/xxhash-3.8.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5d3dfb1f0ff146da7952867a9414f0c7a29762f8825a84879592612fd6139342", size = 309846, upload-time = "2026-07-06T10:44:22.543Z" }, + { url = "https://files.pythonhosted.org/packages/89/0f/7fe4d4ef4e69f0033e012396ee2a115886bca7b10b7e45ce398626436bfc/xxhash-3.8.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4482380b462ca9e59994d072a877ecadd1cf51102daeeab2db696f96ab763723", size = 237659, upload-time = "2026-07-06T10:44:24.135Z" }, + { url = "https://files.pythonhosted.org/packages/38/8f/83e9e31d4ed57fe963b99cb5b13a23e3e0f0dad1885aa0ebd2a7819dd423/xxhash-3.8.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:950ac754d16daea42038f38e7465eb84cda4d08d7343c1c915771b29470f065a", size = 268737, upload-time = "2026-07-06T10:44:25.875Z" }, + { url = "https://files.pythonhosted.org/packages/57/79/7e7de46dbe5d1f49afc96a0bc42e6b8df24eae3d6bad6007b99e42f48430/xxhash-3.8.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0418ec8b2331b9d4d575fc9284427e8e69449d7172e99e1a86fcdd1f51a0a937", size = 224955, upload-time = "2026-07-06T10:44:27.777Z" }, + { url = "https://files.pythonhosted.org/packages/ec/34/b8540839e958d5ef5c6101af6f16032109e7099698ae8edbc8dcefe4d8f4/xxhash-3.8.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:32a94ad2763e0263d9102037d349002c3d3c401e42770542c3eeb4801f311661", size = 239653, upload-time = "2026-07-06T10:44:29.422Z" }, + { url = "https://files.pythonhosted.org/packages/ce/87/a735d05f7f859354acadabe470ff40e2c46672275f96dcf096a761904def/xxhash-3.8.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:89b11a5cdd441aa463f6d34ca0241602bc09b001a76994b6059828494108c673", size = 300213, upload-time = "2026-07-06T10:44:31.401Z" }, + { url = "https://files.pythonhosted.org/packages/98/31/3e1cb020237b68117fc212dc5f9753b87f865b4dfee7c1ce62d0836955b5/xxhash-3.8.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:09a204dd4bb0823daf938cdd0dc8057d5f1e14fe3cbde929424255f23f9de872", size = 442508, upload-time = "2026-07-06T10:44:33.023Z" }, + { url = "https://files.pythonhosted.org/packages/23/bf/f80090622141cc734b039ce1d15ce3ff6dced375e9680249bf5b9b8c6bf9/xxhash-3.8.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e710ad822c493fb80a4fbc1e3d0a807b1422cb90adbe64378f98291b7fa48fef", size = 216853, upload-time = "2026-07-06T10:44:34.983Z" }, + { url = "https://files.pythonhosted.org/packages/a6/a3/60157acecc307b238d3651c2483168e224b48b23a36ae6d6903588341d80/xxhash-3.8.1-cp311-cp311-win32.whl", hash = "sha256:5013be3bea7612852c62a7437f3302c1cfb91ca7e703b194459db0b2b2e0d792", size = 31936, upload-time = "2026-07-06T10:44:36.542Z" }, + { url = "https://files.pythonhosted.org/packages/59/5c/ef70c418d878d187b8da56d4cdc06aea6cf5e456b301e96e51e1d2cc8625/xxhash-3.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:f377012b86c0a23a1df0cf5a1b05aa7187649e472f71c7892e5f2c2815bbe74f", size = 32724, upload-time = "2026-07-06T10:44:38.177Z" }, + { url = "https://files.pythonhosted.org/packages/2c/25/f008db952cec6b2a26445b456eeed2ebebd65e08e848ebe09ed6ac0634e6/xxhash-3.8.1-cp311-cp311-win_arm64.whl", hash = "sha256:836f11d4474d3228e9909d97216faa4f7505df41cfaf3927eb29809de785a78d", size = 29212, upload-time = "2026-07-06T10:44:39.577Z" }, + { url = "https://files.pythonhosted.org/packages/42/91/f65c34a7aa7b4e7cf4854f8e6ef3f7ee32ceac41d4f008da0780db0612f6/xxhash-3.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e6e49370822c1f4d8d90e678b06dbcb08b51a026a7c4b55479e7d467f2e813bc", size = 34680, upload-time = "2026-07-06T10:44:40.932Z" }, + { url = "https://files.pythonhosted.org/packages/57/04/b10a245a4c09a9cfa88f8e9ae755029413ad1ac17047f9a61906e5ae0799/xxhash-3.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:220d68130f83f7cc86d6edfdeab176adc73d7200bf3a8ec10c629e8cf605c215", size = 32397, upload-time = "2026-07-06T10:44:42.196Z" }, + { url = "https://files.pythonhosted.org/packages/3a/75/45ab795b5945b6388583bd75202106af505537935566c15a1577797a0e08/xxhash-3.8.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4d365ee1892c1fa803536f8c6ce21d24b29c9718ec75eb856095c07830f8c478", size = 220549, upload-time = "2026-07-06T10:44:43.603Z" }, + { url = "https://files.pythonhosted.org/packages/13/44/5ba2bd0a14ddf4193fc7d8ec29625f659f22c06d60b28f04bf46305d8330/xxhash-3.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:852bfe059720632e2f16a6a4745e41d20937b2bf2a42a401e2412046bb6971cc", size = 241186, upload-time = "2026-07-06T10:44:45.534Z" }, + { url = "https://files.pythonhosted.org/packages/23/32/c4147def4d1e4538b906f82731e0ba23424377fc50a7cddd03cd284c8f63/xxhash-3.8.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2f8c25a7061d952de589bd0ea0eaadee32378ff83dd6a677b267f9cd86f401f8", size = 264852, upload-time = "2026-07-06T10:44:47.199Z" }, + { url = "https://files.pythonhosted.org/packages/6c/bd/71ed14f4f0318bb7fd7b2ec51999413487fa8da8d41208e84d50d1ef0f98/xxhash-3.8.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:868a8dcaff1a84ba78038e1cef14fc88ccf84d9b4d12ea604696e0693296aa56", size = 242663, upload-time = "2026-07-06T10:44:48.846Z" }, + { url = "https://files.pythonhosted.org/packages/91/09/70af22c565a8473b3f2ae73f88e7721af281bc4a575236dbd1970c9f76f6/xxhash-3.8.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6536d8677d2fff7e64cd0b98b976df9de7aee0e69590044c2af5f51b76b7a170", size = 473510, upload-time = "2026-07-06T10:44:50.695Z" }, + { url = "https://files.pythonhosted.org/packages/18/96/34db781c8f0cf99c544ca1f2bc2e5bf55426e1eb4ca6de8ea5da56a9f352/xxhash-3.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:82c0cedd280eab2e8291270e6c04894dbc096f8159a39dcf1807429f026ca3cc", size = 220469, upload-time = "2026-07-06T10:44:52.422Z" }, + { url = "https://files.pythonhosted.org/packages/93/5f/9a184f615fa5a4dce30c01534f62946ce5a11ce40f73785cbd356ccabaa9/xxhash-3.8.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:daa86e4b68221d38e669bb236ba112d0335353829fb627c82e5909e4bbe8694c", size = 310290, upload-time = "2026-07-06T10:44:54.142Z" }, + { url = "https://files.pythonhosted.org/packages/a9/dc/9b9a9789011ee153723a5eb9e7dd7fcbae2ba9b3fe7a729249ca7c252056/xxhash-3.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2bc7113e6f2b6b3922dd61796ca9f36af09da3773898e7003038dc992fc83b8d", size = 238173, upload-time = "2026-07-06T10:44:55.693Z" }, + { url = "https://files.pythonhosted.org/packages/ec/4d/71c6005ada9dcb608a4e1902e8475ecadb5f3fbfa04e1e244d276a2d0c43/xxhash-3.8.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5eed32dad81d6ba8e62dc7b9ffa0500199385d7810a8dd9d4eafaceb8c6e20bb", size = 269026, upload-time = "2026-07-06T10:44:57.424Z" }, + { url = "https://files.pythonhosted.org/packages/2f/87/d6c036ba25dfbd9c8633be5aa86fc9474bbb9e2c68212a841d090abe7344/xxhash-3.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:83697b0ea1f10e7f5d8b26a4906fa851393c61546c63839643a2b7fe2d868061", size = 224970, upload-time = "2026-07-06T10:44:59.085Z" }, + { url = "https://files.pythonhosted.org/packages/48/62/4c1f035a41c5752aa05e195b6c904c07b94fe9061a16de61e72a6e6b135f/xxhash-3.8.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:36fc69160465ae75c6ec4ac9f781bb2aa16ae7ff869e73c26fee85fbb11b9887", size = 240820, upload-time = "2026-07-06T10:45:00.746Z" }, + { url = "https://files.pythonhosted.org/packages/da/14/d39d565069b87e86d21a2af2a31d04db79249d25aa8d5b62959056a89857/xxhash-3.8.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:445e0f5a31f2f3546ae0895d4811e159518cdc9d824c11419898d40cfadb677e", size = 300619, upload-time = "2026-07-06T10:45:02.716Z" }, + { url = "https://files.pythonhosted.org/packages/13/22/75467acc887edc8cf71c97ab1708feb3df7a88bda589b9f399765c6387d2/xxhash-3.8.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:dfe0580fbfd5e4af87d0cc52d2044f155d55ebd8c8a93568758a2ea7d8e15975", size = 443267, upload-time = "2026-07-06T10:45:04.653Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b6/1da3baa5fa6ef705e3425fddd382be7dfc4dfba2686df90a20f16e9c7b1b/xxhash-3.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:095e1323fa108be1292c54c86da3ef3c7a7dc015b105a52133973bc07a6ad11a", size = 217338, upload-time = "2026-07-06T10:45:06.304Z" }, + { url = "https://files.pythonhosted.org/packages/78/dd/b5295a9f97484e7a1c2b283a742ca45e3104991c55a1ef670dde161829ba/xxhash-3.8.1-cp312-cp312-win32.whl", hash = "sha256:bf28f55e427e0483acb1f666bd0d869b6d5e5a716680c216ad7befe3d4cfba2e", size = 31970, upload-time = "2026-07-06T10:45:07.823Z" }, + { url = "https://files.pythonhosted.org/packages/ec/31/3fa0b807d7e21515cd975e7fe5c039d52ac3e9401a96d6ad68dae6305215/xxhash-3.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:2256e80e4960ee282f63428adb349cb7f8bd8efe4db770d88eb815f4b9860724", size = 32741, upload-time = "2026-07-06T10:45:09.42Z" }, + { url = "https://files.pythonhosted.org/packages/b8/05/86feada74e239600e6875aa507afb40482a89b92700aa74a92da83bdcb77/xxhash-3.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:9df56e6df96a60590935e22373041cccc91fd55858763dcffb55bf63b3a2b396", size = 29234, upload-time = "2026-07-06T10:45:10.809Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8c/446bb782cd0d27007a917b5569a08dd73219c3e8d6e459014db104b27bdb/xxhash-3.8.1-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:3c682fcd96eb4bf64be32a4d95f96107e1588005831bd8a741b324fdda01b913", size = 38562, upload-time = "2026-07-06T10:45:12.425Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ec/c0c45627eaa6be7a5d6117423adf8f7a15b17ee74b4b17072cca5959a225/xxhash-3.8.1-cp313-cp313-android_21_x86_64.whl", hash = "sha256:036a024d8b9c01f70782e09ed98d532e76fd23f950ae7154bd950fe94e90ebec", size = 36656, upload-time = "2026-07-06T10:45:13.932Z" }, + { url = "https://files.pythonhosted.org/packages/f6/94/8324c04cc7597154caaeba6c094e01fbd2e7601d01e7a13eea9f5420e77b/xxhash-3.8.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d6a5c0bce213b23b0166fe0d35bcbbe23ce4b968f257cc7eb6fd57cb8e1e6297", size = 31169, upload-time = "2026-07-06T10:45:15.687Z" }, + { url = "https://files.pythonhosted.org/packages/40/a4/beb6bb26e1184e126dbe7a5682330214ef54dcfbf882078aa9f4b5428d42/xxhash-3.8.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:5177aa44eddaa97c6ef0cc00c6d540edb64d51781d2f8fb941612ec61a92c9ed", size = 32177, upload-time = "2026-07-06T10:45:17.035Z" }, + { url = "https://files.pythonhosted.org/packages/56/0f/fc4c92a5a528f839b34b6419b2e53c8597f2a629d5a1f5d721f65bfa1fd6/xxhash-3.8.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7801b7223db017b9c0c9ccf37e44524edb35a1544a1c032add22c061c6af0276", size = 34642, upload-time = "2026-07-06T10:45:18.39Z" }, + { url = "https://files.pythonhosted.org/packages/d4/58/edbfb141d4000767ac6a9694f8ac0763e2c2e983e65c9e31620ba56e2667/xxhash-3.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9e80238259655bf69d7bcd08226a970d7f42605f3157786bfa76dd13472d7fa0", size = 34684, upload-time = "2026-07-06T10:45:20.033Z" }, + { url = "https://files.pythonhosted.org/packages/07/3f/5072f1f0f5714186f0ac2a0b5a4929ce30d4b845e94886b6c01b6ebda0be/xxhash-3.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bcab50a389cc04d87f90092af78a6adba2ab3deca63175a3344ca83514045315", size = 32401, upload-time = "2026-07-06T10:45:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/49/c7/802ea2f9c2ed59219934d6d65c470d502b1788043eae277a52af8658bda6/xxhash-3.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a2489d3a776fa380cb8e71f54c7fda268a9baf3de9b1395093fd280f95735907", size = 220617, upload-time = "2026-07-06T10:45:23.234Z" }, + { url = "https://files.pythonhosted.org/packages/99/a8/e10488efd31fcb13fcd6acbc6e788f10c6f8e3a0cc4ae3eb89dc19c55a12/xxhash-3.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32ab1e5432690276e71192be7401b55f96db2d0eedea5d44eb1f164505669cc0", size = 241295, upload-time = "2026-07-06T10:45:25.364Z" }, + { url = "https://files.pythonhosted.org/packages/18/cc/14180b17d44892a631f8ae7323c30bfbb1328efc8209e528a480293528ac/xxhash-3.8.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b30e01a0b97a4bc3f519a4d7a82da3dc53251fb0de5eeea8660dcd4ff094c0c2", size = 264688, upload-time = "2026-07-06T10:45:27.09Z" }, + { url = "https://files.pythonhosted.org/packages/a9/72/a14019d0c5f6c41ee407a503036ae32787c91325ca218a96a9b5627be651/xxhash-3.8.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1f44275ddb0978b67a58a951501903f04d49335a91f7681c9ce122ecb8ccb329", size = 242740, upload-time = "2026-07-06T10:45:28.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/08/92550e556c6fcfcb96c6a336945eb53a431ed43120ed749636debb16c5cf/xxhash-3.8.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e3b87cbd974512c0c5fc7b469c36b2cdc9ee6d76e4ec78bccb2c7184611c49b0", size = 473599, upload-time = "2026-07-06T10:45:30.524Z" }, + { url = "https://files.pythonhosted.org/packages/29/83/e361d3c1acd1b21e1d489616de6fa4aaf843365d8179f612e3743eac20a9/xxhash-3.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98ee81b4b7f3023c9cb04a78cc67610baffcb5812d92f2096cb5a5efc6f19437", size = 220559, upload-time = "2026-07-06T10:45:32.979Z" }, + { url = "https://files.pythonhosted.org/packages/05/01/006a4243c2c2a6831827f9999f6d1c23feeef100eb023c1f886022a00bf3/xxhash-3.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2666f059a1588a99267e33605365ed89cea92f424b3522806a9f4bd8ad2e3d62", size = 310383, upload-time = "2026-07-06T10:45:35.875Z" }, + { url = "https://files.pythonhosted.org/packages/d8/20/af388e8bf9f9a0f89eeef7d2a1935d176ee1c20bc6adeda05035879379cf/xxhash-3.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0093cf7eeb91b84776e8742113afa4bdf47533d36cf719179aaaf1f56f6f8bf", size = 238228, upload-time = "2026-07-06T10:45:38.02Z" }, + { url = "https://files.pythonhosted.org/packages/63/6b/4666579a87eebd1744663c404297355fa0658617b015cedfa58810ee7036/xxhash-3.8.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3a800912a2e5e975d4128969d645c4a2a80aa886ccd6c9b1c6f44529e327e8cf", size = 269137, upload-time = "2026-07-06T10:45:39.954Z" }, + { url = "https://files.pythonhosted.org/packages/de/d3/e963a8a46f900a137d91b02144d8ea07a8f812971b138204a3b2f8b8e55c/xxhash-3.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0fe37f72a207223d22a4eddc3149d4298993385aa9daef25c039246ca5a309f3", size = 225068, upload-time = "2026-07-06T10:45:41.718Z" }, + { url = "https://files.pythonhosted.org/packages/aa/80/9d181dbcde4b0fe48375f48833a5832d4b8cd2b349b15110c92ee472d874/xxhash-3.8.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5db43f249b4be9f99ef4b967863f37094fb40e67effafb78ba4f0356b6396104", size = 240874, upload-time = "2026-07-06T10:45:43.414Z" }, + { url = "https://files.pythonhosted.org/packages/39/15/ce3ab5a1cd27ead25a5196e55a7284220f6ad6e316da494ffd900b2b600f/xxhash-3.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c4ed42965c2cd9081f011be22f69d0e65d3b6165fe7734072fd0c232840bbd4e", size = 300702, upload-time = "2026-07-06T10:45:45.135Z" }, + { url = "https://files.pythonhosted.org/packages/96/c0/2281a8ab5f2a62dbf57a23c58a01ccc1d98abf40f71193c8a81f59e759b5/xxhash-3.8.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3557bec8fcb11738a8920eeb68974bc76b75262f6947998d3147954ce0a4b893", size = 443351, upload-time = "2026-07-06T10:45:47.188Z" }, + { url = "https://files.pythonhosted.org/packages/81/2e/071a58c1a53a52d4f7a3aa0987be0c396dffd40da8204805fe1b130a81f4/xxhash-3.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:00de40f3b42240db23a82a5c682b55d7263d84a26a953240c1aee463409660e3", size = 217396, upload-time = "2026-07-06T10:45:48.925Z" }, + { url = "https://files.pythonhosted.org/packages/68/44/36ab58134badd9d3433fc7b53c4ca8d113d8e807782885628640f8297a4d/xxhash-3.8.1-cp313-cp313-win32.whl", hash = "sha256:b5196cc2574cfec572a5f3fb7cfa5ade27305ae3d06516a082132441aff4c83a", size = 31974, upload-time = "2026-07-06T10:45:50.591Z" }, + { url = "https://files.pythonhosted.org/packages/96/2a/2a0b84798448e766f7b89ceed073cb0cb5a43fc9ebbacbdea74a38de18e3/xxhash-3.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:538f5f865df6cd8c32dd63158a0e5b4f5dd08d732a7da8b7228a5a0776c8ce55", size = 32739, upload-time = "2026-07-06T10:45:52.221Z" }, + { url = "https://files.pythonhosted.org/packages/d4/60/bb51dbf7c363ff88a7cbd50b7959718219577ef44d7cf255929ffc4a2194/xxhash-3.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:a6617f30641ba0d8baa1635fbefb1dffc5165ec36d26921bd5cee13497cd937a", size = 29239, upload-time = "2026-07-06T10:45:53.714Z" }, + { url = "https://files.pythonhosted.org/packages/56/d3/827ca123c2ee5443a6aaed3c5dd199237dc2f010e2bebd7ec09ef36f3a5f/xxhash-3.8.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:bfcd82852c62a60e314670a9602de354c4460f8adad916e2e42a20860c7870bc", size = 34964, upload-time = "2026-07-06T10:45:55.535Z" }, + { url = "https://files.pythonhosted.org/packages/05/67/67ae2a3ccdeb8b8ef025d35aee9edd1d26c3abe5051d47da9286232afbf8/xxhash-3.8.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:08ea2081f5e88615fec8622a9f87fbe21b8ea58d88cfc02163ca11026ee62a92", size = 32697, upload-time = "2026-07-06T10:45:57.288Z" }, + { url = "https://files.pythonhosted.org/packages/38/5a/3d3994346e1f45493679cb5c1ffc2bf454e410e9d1e8a662d253becee91e/xxhash-3.8.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2e32855b6f9e5b18f449e59d45e3d5778bdeb660632ef2693cca267a11246c75", size = 225954, upload-time = "2026-07-06T10:45:58.897Z" }, + { url = "https://files.pythonhosted.org/packages/3f/2c/53169270309b7cd8e05504e07fe123bac053b89d00ac63617faacf0a2ec0/xxhash-3.8.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6e088bd7870775624256a0d84c2a6714afd223b2eeb56b0ca58398e52a32fda", size = 249776, upload-time = "2026-07-06T10:46:00.977Z" }, + { url = "https://files.pythonhosted.org/packages/70/e0/5c551d8d592f944506f7c5185e210255c15e672a3c6008c156a1bd9b775e/xxhash-3.8.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:72eb5ae575cc7ae2b23f6f8064a8b10f638c7149819ae9cc6d20ebd4d37a1629", size = 274776, upload-time = "2026-07-06T10:46:02.869Z" }, + { url = "https://files.pythonhosted.org/packages/a0/2a/d3a762270cee2d7bcd0e25e28c623e5f3f5c0dc637b66e3e47dd5b0bb3f0/xxhash-3.8.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d0b48cdf690a64cedf7258c3dc9506cc41fc86edd7739c40e3098952265dc068", size = 252056, upload-time = "2026-07-06T10:46:04.688Z" }, + { url = "https://files.pythonhosted.org/packages/c1/8f/b78e4373b2cb6d1c42af60ea2d7e9146ad0710b239ac7f706d5d31d5bb98/xxhash-3.8.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb9e256a357dfcede7818c6d34e70db2d6b664394803d1de4b6984d2de76c0f1", size = 482108, upload-time = "2026-07-06T10:46:06.498Z" }, + { url = "https://files.pythonhosted.org/packages/e6/0d/642d923336ea61a15f8ce64fc7e078729e6e06c3a026e517fa79b2c23b7a/xxhash-3.8.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51f71a6e2ad071e70c937e41fcb6c19f82c3f9f49831eba850ed4a106ffbb647", size = 226739, upload-time = "2026-07-06T10:46:08.598Z" }, + { url = "https://files.pythonhosted.org/packages/a6/0a/a37d6da6427d45a8d23e3ee3a0ca9c9d4a90364849c6637fe2963a755f9b/xxhash-3.8.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e4a6443968c4e8dc69967e12776776a5952c119cc1bd94168ad1c5ad667c2be1", size = 319658, upload-time = "2026-07-06T10:46:10.504Z" }, + { url = "https://files.pythonhosted.org/packages/4a/51/ebbd40da8a3f1bc53b4b7a9a87f8e28bd95c5f21bc14b8a57860cf367d1b/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:714503083a1f2065c9ad15340dd49ac8a8e948a505a705ffa1750cb951519113", size = 246059, upload-time = "2026-07-06T10:46:12.634Z" }, + { url = "https://files.pythonhosted.org/packages/24/4c/d9014030147e1f0bb26e7da47aa240dd9ec61c763c573e558111d869f8e1/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:77f74e45a1e5574bbbf80181c8027b3a4c65c2248fffbd557bd596fff13102f9", size = 275535, upload-time = "2026-07-06T10:46:14.614Z" }, + { url = "https://files.pythonhosted.org/packages/84/86/caee2db41fadcd5a25aa4323213f9afec5a8586d4e419241e3d659362bd7/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:4e0e1b0fb0259c1b75d1251ac0bb4d7ab675d36f7a6bf4ba6aa630dae94f9ffa", size = 231292, upload-time = "2026-07-06T10:46:16.452Z" }, + { url = "https://files.pythonhosted.org/packages/0b/60/f52f08bcdc904c4514ea5c25caa19e9f3214144434a6ff96dc82dc1cbddd/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:10e4393ec33633c2f05ad01869e546ad080b1a18f2650503731f153774608b31", size = 250490, upload-time = "2026-07-06T10:46:18.318Z" }, + { url = "https://files.pythonhosted.org/packages/24/a0/94dc7ae310838f250669c6ad7168e6d6fca17d49dac1053f06dc232c4a56/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:b3ba794c3d885803db6c3116686923f1ec13bc86e621e169a375282b63ea1cc6", size = 309861, upload-time = "2026-07-06T10:46:20.503Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f9/adeead7d0eb28cdfc2832544ea639ffbc6749ccde47a8e228d667459182e/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:57189a69c0891e4818853feaa521c972d22c880a001453addea015f48e3c3398", size = 448739, upload-time = "2026-07-06T10:46:22.79Z" }, + { url = "https://files.pythonhosted.org/packages/04/a4/22ec0e07db57d901c9298ae98aa3cf2be45bafded6f07c13131e85b89032/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d59e71153fe9ff85648d00e18649b07e9b22c797291abb7e27274fa06df8b838", size = 223657, upload-time = "2026-07-06T10:46:24.831Z" }, + { url = "https://files.pythonhosted.org/packages/94/32/8a9531f37b59e5a013003db7cb7414baf4ce7e0e1268e0d5947cd3d6a2df/xxhash-3.8.1-cp313-cp313t-win32.whl", hash = "sha256:5b96f0024e9840f449bd91b2d005c921a4b666055a0d1b6492463799f32aae22", size = 32377, upload-time = "2026-07-06T10:46:26.86Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ab/2ca45fd7f671de5f81fc297ef1c95080b40c86ec6be0cc6034b8f7707ac8/xxhash-3.8.1-cp313-cp313t-win_amd64.whl", hash = "sha256:37d5a56c36dcc0b9a87b814cd992598d33863ff683749de6c86081f278d5e629", size = 33274, upload-time = "2026-07-06T10:46:28.39Z" }, + { url = "https://files.pythonhosted.org/packages/5a/54/20d7163463ddb6438b73a427d1655a77a502cf9b9b0c3ada3599629d9c0a/xxhash-3.8.1-cp313-cp313t-win_arm64.whl", hash = "sha256:6696c8752aded28ff3b16f33ef28ce28fb5d209b80c206746f943199fcf5fd65", size = 29375, upload-time = "2026-07-06T10:46:29.962Z" }, + { url = "https://files.pythonhosted.org/packages/c2/8b/df2ba04f22a6cd6b39f96a6577329a8471a55c90ef8d8e2f7c102363613f/xxhash-3.8.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:9db455cb649dcfe4504d6d68a6d83a7315a99a3ca59871dc3ff840671f99adba", size = 38430, upload-time = "2026-07-06T10:46:31.496Z" }, + { url = "https://files.pythonhosted.org/packages/b2/4f/6a059e8ad3ca8deedc91dfe335b211204900895152212c03ebbe721de68b/xxhash-3.8.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:affb37f152e55b5e4494bb9d0107f7bb08515c6704fbed82d9f61214d74adc17", size = 36558, upload-time = "2026-07-06T10:46:33.078Z" }, + { url = "https://files.pythonhosted.org/packages/cb/95/40be178205acce092ae418feb20ac737b32a02c7b864926ed0717354c9f8/xxhash-3.8.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:460261045936975193bfd20549a0de1cd52a33b405cbb972f0d80940c42266cd", size = 31181, upload-time = "2026-07-06T10:46:34.793Z" }, + { url = "https://files.pythonhosted.org/packages/3f/89/2da4dbf051bafa156c0e3f12012db2b0ac3b84ff37ca1f021f6bfffcdfbb/xxhash-3.8.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:38c887aedb696ef8bca19983206d270848558cfae4a91afa6a2fb05dde58ffc5", size = 32192, upload-time = "2026-07-06T10:46:36.393Z" }, + { url = "https://files.pythonhosted.org/packages/7c/4e/e000bbae3566bc8e0be771a8a0f294aa99075e3f0bc4ef43922ebffdebc8/xxhash-3.8.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:594131ce1aad18db3689781f806db1b065cdaa04f4df36b4c038d2013aefd0bf", size = 34691, upload-time = "2026-07-06T10:46:38.1Z" }, + { url = "https://files.pythonhosted.org/packages/b4/4a/ea954aacc7d1c8711880ac2b55da94429a9b4296b151c4fc0966549ca1ee/xxhash-3.8.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:78c794b643d214f1522e7a288bcf5a2de120d26cd170516749a4009dc92722c9", size = 34807, upload-time = "2026-07-06T10:46:39.647Z" }, + { url = "https://files.pythonhosted.org/packages/ca/29/df598e738ff37558ac627264deb2e560902d9bf7f46d3bd5175c9eee593e/xxhash-3.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:af0c9fedc4a2c24e8664953882fe8185f3790b8338c9c700f76f5ad660817711", size = 32410, upload-time = "2026-07-06T10:46:41.359Z" }, + { url = "https://files.pythonhosted.org/packages/59/9c/81ab40e7d33ada0b3df5d1bc884894d15dbf4f805cd645b685e4606bb8e0/xxhash-3.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:115772daeb71b2f3b9381177017f53e6cf3f3439c840737fdabd21aba6e54920", size = 220564, upload-time = "2026-07-06T10:46:43.463Z" }, + { url = "https://files.pythonhosted.org/packages/fd/6f/62ae6f5c8606320a0e2a41c2dc8c6d91cc5d63d0f84dd9582e9543779dd8/xxhash-3.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:000435984a0469b0f822fe76f35bddea0f96a4d6521b3339a60a6428cdee1edc", size = 241462, upload-time = "2026-07-06T10:46:45.509Z" }, + { url = "https://files.pythonhosted.org/packages/15/a1/9c3a0ec6cb524396f551eddd102a76690a795494eb9784fc67542b0daa37/xxhash-3.8.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2f1c68394818e0595569c2ff3cbc1e6d5a36a434e796f5c526b987b80c8a8c62", size = 264491, upload-time = "2026-07-06T10:46:47.655Z" }, + { url = "https://files.pythonhosted.org/packages/64/f2/700a4674e4308eb59d2fdb973977e82eae231bea5044753fee5c9eec0e0c/xxhash-3.8.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:46b39976d008e2a845758650f0ff7136bca004f40da0c8798bd37ac37860154f", size = 242905, upload-time = "2026-07-06T10:46:49.857Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8a/72d9874375c8d4cbc64a8cd1d659d5695a8765c3db82efa82dc5bd9f14d0/xxhash-3.8.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d5006c65ec507a333479e76e00e2c368781f16c24ededa764763956b32a0e93e", size = 473873, upload-time = "2026-07-06T10:46:51.953Z" }, + { url = "https://files.pythonhosted.org/packages/03/f0/6db07590ed7e0a77f186ef0bcea8d52553bf1ba57833e09467a2411f0f2d/xxhash-3.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31a2649bcf1fe97cf11c79848d761df33ac46b3896942d31b640557b486ff6b", size = 220765, upload-time = "2026-07-06T10:46:55.41Z" }, + { url = "https://files.pythonhosted.org/packages/8f/10/00d12d8b8beabbf49a8bbc626fb9f40445145a8887eb41a6acfb69149ac4/xxhash-3.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8f759eed402448c2bdbb492e4fba1f20668ffe29688605ea61f0f67f9e4e386d", size = 310478, upload-time = "2026-07-06T10:46:57.729Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f9/12a82394eefb0f185d15a7f7b9f627c61c475a72dd83718436a5b84b42ac/xxhash-3.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7b5f97ecfede10d5b2870383620e2d25c8561e217c7bf9081073802b54248d2b", size = 238393, upload-time = "2026-07-06T10:46:59.87Z" }, + { url = "https://files.pythonhosted.org/packages/20/f3/53f963e320b9ce678337aa7273f39ce692ded8b99e3d22a866ec722159ab/xxhash-3.8.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1da930bbcac3e8fbe2191850e2abb57977a99348c12c4b385e1058ac1b0a9ecc", size = 268704, upload-time = "2026-07-06T10:47:01.806Z" }, + { url = "https://files.pythonhosted.org/packages/0a/50/5b5badbd87c82d9f9b5f58ac74a3f29ef08f6fc387b324b8fd482450b862/xxhash-3.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:747476436f6891b9773374ce8d48edcc8b12cb5b61b67c6fb6289633747d088f", size = 225015, upload-time = "2026-07-06T10:47:03.784Z" }, + { url = "https://files.pythonhosted.org/packages/30/93/3ca68265afe7b4e69435e08a7b6a1d9d0f2a071e889da1f8041ed00fe878/xxhash-3.8.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef09bbc2519a93cd0f95f2ceb5f7b85919dffea643278e02362bf40e3c4bed1", size = 240951, upload-time = "2026-07-06T10:47:05.816Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a6/27e19670c40f46b5e76e11f2f4713d21054804568425d870670e757172ad/xxhash-3.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:a5eed9d41995a83f3332b4e3396abb7f433cac584222bd7e305b606d8353861e", size = 300751, upload-time = "2026-07-06T10:47:07.95Z" }, + { url = "https://files.pythonhosted.org/packages/bc/fb/b33e27689959fe7ed2ae0b830af41560d65213943983afa9db3a8d481bce/xxhash-3.8.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:53f3ed9118397074ff63a79b66b7fec1c84c782eecde35c5bc94e420a971c231", size = 443480, upload-time = "2026-07-06T10:47:10Z" }, + { url = "https://files.pythonhosted.org/packages/26/60/0e0d973be5fe280753ef02fbc89349492ad6e903bf1dcb870b668f94b662/xxhash-3.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d247b34bf433c92b41689318fd25d246313cab2275a6a47e2efac178b80d6efe", size = 217657, upload-time = "2026-07-06T10:47:12.196Z" }, + { url = "https://files.pythonhosted.org/packages/ad/68/c9e3ecef4a9a417d464cb5bd200aa12f73192dee677901b9e08e0ad0d1bb/xxhash-3.8.1-cp314-cp314-win32.whl", hash = "sha256:d58ce8b6cfa9c4d2f230557f69caf7c06369e318015d0b19485095bc2c5963ab", size = 32690, upload-time = "2026-07-06T10:47:14.204Z" }, + { url = "https://files.pythonhosted.org/packages/d7/99/e9e44588c0b62837bbec5ba7927816de0afa03406b1a0b6c7a7e1d1a30a0/xxhash-3.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:6cee733fe4ccb1737e0997135283c82341e5cfa9cf214b165f9087fb663aaf4f", size = 33460, upload-time = "2026-07-06T10:47:16.021Z" }, + { url = "https://files.pythonhosted.org/packages/45/2b/64f36d86380b3657ad9031967ab814f3ef31307174650853f69c18932ebc/xxhash-3.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:58346024d47e84f7d8b3e7f5d6faa1d58acbbe49a8771497872059f58c1d8ea5", size = 30092, upload-time = "2026-07-06T10:47:17.81Z" }, + { url = "https://files.pythonhosted.org/packages/92/cb/18b64bff88c58a0ca209dc533e63cf02d7ae5aa6b1b9a9fd14e81b5dbd60/xxhash-3.8.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:01cab782f8a0a05ecad2c63d7ef10f7ab475f660e0d6419d069418c14d88de7c", size = 35024, upload-time = "2026-07-06T10:47:19.821Z" }, + { url = "https://files.pythonhosted.org/packages/af/1d/72d8a70520e5dcddb472ea0486d299da3240745a10658290cd7b5690ede2/xxhash-3.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:717b12fdc51819833704e85e6926d76981ffa3f780ef92e33ebb8b26d46bb230", size = 32697, upload-time = "2026-07-06T10:47:21.649Z" }, + { url = "https://files.pythonhosted.org/packages/9c/b8/e041f555903c56db3d0a731b3d72a6575d75e0ed868b1bd2e5176111ca44/xxhash-3.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ec55d80e9b8a519d742669e0b49e8ce9e6747be42bf3c138158b6543a9c8e489", size = 226044, upload-time = "2026-07-06T10:47:23.612Z" }, + { url = "https://files.pythonhosted.org/packages/3a/7e/5cdcf06bf6ec4b5d2ac073feb23432ec1d603fd438864cbd2c09c7cb45e1/xxhash-3.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98d8ac1129b4dd39098cffed94d1284aceb61c3aa396757ccc736ac392e4cee5", size = 249899, upload-time = "2026-07-06T10:47:25.812Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c0/eb7e059cb5e1dba11fd30d2fdf882f56e5a417a3eaa43669d43623767f45/xxhash-3.8.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3bc0fa90830df1e1277f33cc6e55de9990b83c0319fd8c7412866cfde38b025e", size = 274892, upload-time = "2026-07-06T10:47:27.931Z" }, + { url = "https://files.pythonhosted.org/packages/66/74/a600aaf7cd39957fd1510adeedb1749c1e7eb82bd632a1153d9c664c3135/xxhash-3.8.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c73b6f652f0745425aa6378319c331293b5341756262e9408ed3d45f183375e6", size = 252243, upload-time = "2026-07-06T10:47:30.288Z" }, + { url = "https://files.pythonhosted.org/packages/ad/04/78d88fa75a6763e5d09bf1b947a392a27988903381b219006f92f3c68fc8/xxhash-3.8.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6114692261eff4266386cdec0f7d87eee24e317ab397c218b7ae6a76b4c6339", size = 482191, upload-time = "2026-07-06T10:47:32.45Z" }, + { url = "https://files.pythonhosted.org/packages/7f/06/07a8aea1108d682de8791ce608cdf367d75ff4e7e57cd3c154bdc6f47b23/xxhash-3.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4df57c0b161ec1b3ed0526a67b0db0914b557e86ee8aae51887aec941b261542", size = 226877, upload-time = "2026-07-06T10:47:34.705Z" }, + { url = "https://files.pythonhosted.org/packages/ed/b5/86bade5618a524d2c06c4041aa2fe8e5749ce16e88afba60d67c1684a21f/xxhash-3.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9043877a917be88ccf230aa5667c1bd059bce80f4c2727e4defa1b29b7f48b08", size = 319794, upload-time = "2026-07-06T10:47:37.08Z" }, + { url = "https://files.pythonhosted.org/packages/23/69/9b1a2b89b1621bb740fbcb7beb512f60f99480c1bdc680c0c90e1f56ff75/xxhash-3.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:559e3cabe522231909f9de98ef06929edbd53782046bd21aae0c72db6f2a0775", size = 246202, upload-time = "2026-07-06T10:47:39.676Z" }, + { url = "https://files.pythonhosted.org/packages/08/ea/662ed6cb49f1d34078b6a3a3e0f3d29ff93fd7b5a03c0bc9ecfd9b2159c3/xxhash-3.8.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:264710bd335016f303763ce1275c6486df30bb57c2245c91b224c983d7ac39b8", size = 275628, upload-time = "2026-07-06T10:47:41.99Z" }, + { url = "https://files.pythonhosted.org/packages/13/f5/49fc9e4c6728a5a3bd8fe639199d2fa67609b3a84f938aff6e8568dd3e4f/xxhash-3.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e14800b9b10bb39d7a60ad4a310e403164d7b8988a27ae933d4e40618a44088e", size = 231390, upload-time = "2026-07-06T10:47:44.233Z" }, + { url = "https://files.pythonhosted.org/packages/64/9d/3acaf8f599c0e0b30e910a3a11ba32929da53c86dc73c7c55fe6a010b4e9/xxhash-3.8.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:ea6a3e734b0fd41b82784a400be946821900daebe610c050a5e0760838a34f99", size = 250600, upload-time = "2026-07-06T10:47:47.611Z" }, + { url = "https://files.pythonhosted.org/packages/23/64/8acab4c5ec60dbe664b5b9858fd44c2413b07e535b09556a0a5022e78aa6/xxhash-3.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:cf399fac542a1c7a4734a435b93df2c55e858c7d31abf6c1bdf46f9ae67fbfd0", size = 310032, upload-time = "2026-07-06T10:47:49.88Z" }, + { url = "https://files.pythonhosted.org/packages/56/47/a0288d7329b1fe63e2734a32d19d444a96ae2b4810f545bc61e561224917/xxhash-3.8.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:44c89d915a75c11d2547eaee9098fcd80398987c4bff2974a0497a925bf92c07", size = 448882, upload-time = "2026-07-06T10:47:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/01/e7/3071dfd3beb5c38204ce1cf56bf7749fce08de900fa92714b81d1d8ca1f2/xxhash-3.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:358650d5bda9c635da699c53adf4e8134af492ecc79c960f917eebf088bb6799", size = 223728, upload-time = "2026-07-06T10:47:55.093Z" }, + { url = "https://files.pythonhosted.org/packages/12/11/b99949f0ba2b07e9f9ffe83b9c86faa685f9080725dc21a916a607313be5/xxhash-3.8.1-cp314-cp314t-win32.whl", hash = "sha256:c240939e963653054fc7e4a17c382829cda4aa88a7daf0af841715dbded1b497", size = 33150, upload-time = "2026-07-06T10:47:57.274Z" }, + { url = "https://files.pythonhosted.org/packages/54/1c/09703eb341f8416e74e58d6c6732d4b5c46de59c942363203cb237cc95b0/xxhash-3.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:7258ee276e8772599bc19e14b36f6260306e21b637190cd7cb489a2449d48684", size = 34005, upload-time = "2026-07-06T10:47:59.434Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f9/6ed7251bb6a8af10ac73b1821c60583d2826e5b2064e45a979c935287c98/xxhash-3.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:8f454166c2ffed45636c8d501741e649851ba2f346c4eb73a64c07ac00428f20", size = 30239, upload-time = "2026-07-06T10:48:01.874Z" }, + { url = "https://files.pythonhosted.org/packages/99/e4/4d8040435aeac814fc69ba63621565fbeb19229a138e2568324a26b2a45c/xxhash-3.8.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:39c9d5b61508b0bb68f29e54546de0ed2a74943c6a18585535a7e37356f1dd12", size = 32687, upload-time = "2026-07-06T10:49:42.803Z" }, + { url = "https://files.pythonhosted.org/packages/da/6a/975f1f2318c760e5bcec109ed379713ae645d8d856c2a3b9ec5d26857087/xxhash-3.8.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:83b9130b80b216d56fdf9e87131946b353c9627930c061955a101ea82b09fed9", size = 29879, upload-time = "2026-07-06T10:49:45.172Z" }, + { url = "https://files.pythonhosted.org/packages/08/0b/40a2a55ff52cf635bfdc5eae67a772bec85b4f44c6c737f73f6f528d51d1/xxhash-3.8.1-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8304be0982130954b7fd3aad18e2c6f8ee40254bc3d2e635991c16d77c91e2bd", size = 43246, upload-time = "2026-07-06T10:49:47.905Z" }, + { url = "https://files.pythonhosted.org/packages/9c/6d/56ed2b6b200f26fb474f3fd387d95d0601efcd5bb33430c90c68924bdd77/xxhash-3.8.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b512261801b1e5fde7b6ebf2fef7977339c620cbbca88a0040ad9ad134f4d02", size = 38202, upload-time = "2026-07-06T10:49:50.59Z" }, + { url = "https://files.pythonhosted.org/packages/0d/a3/56864d895d1161a9f17502088e9c1fb7c06bde2c2efdde620d22bb7a9c43/xxhash-3.8.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49aa8692507835dcc1e8ad8021f20c74c2dc13d83b5112e87877faa2a0035b20", size = 34448, upload-time = "2026-07-06T10:49:53.242Z" }, + { url = "https://files.pythonhosted.org/packages/6b/57/5c6e0908a47f61dca96d01c8ee6fce01ed1050611eb779083ba8758fed81/xxhash-3.8.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:345b07b78e2bf583d71682aa34ae5b5fab575f7a1cb31e10263ebbc6f89f8c42", size = 32869, upload-time = "2026-07-06T10:49:55.972Z" }, ] [[package]] diff --git a/libs/prebuilt/README.md b/libs/prebuilt/README.md index b97ac6715..b3da58470 100644 --- a/libs/prebuilt/README.md +++ b/libs/prebuilt/README.md @@ -37,6 +37,7 @@ uv add langchain-anthropic from langchain_anthropic import ChatAnthropic from langgraph.prebuilt import create_react_agent + # Define the tools for the agent to use def search(query: str): """Call to surf the web.""" @@ -45,6 +46,7 @@ def search(query: str): return "It's 60 degrees and foggy." return "It's 90 degrees and sunny." + tools = [search] model = ChatAnthropic(model="claude-3-7-sonnet-latest") @@ -65,6 +67,7 @@ app.invoke( from langgraph.prebuilt import ToolNode from langchain_core.messages import AIMessage + def search(query: str): """Call to surf the web.""" # This is a placeholder, but don't tell the LLM that... @@ -72,8 +75,11 @@ def search(query: str): return "It's 60 degrees and foggy." return "It's 90 degrees and sunny." + tool_node = ToolNode([search]) -tool_calls = [{"name": "search", "args": {"query": "what is the weather in sf"}, "id": "1"}] +tool_calls = [ + {"name": "search", "args": {"query": "what is the weather in sf"}, "id": "1"} +] ai_message = AIMessage(content="", tool_calls=tool_calls) # execute tool call tool_node.invoke({"messages": [ai_message]}) @@ -98,10 +104,17 @@ class SelectNumber(BaseModel): raise ValueError("Only 37 is allowed") return v + validation_node = ValidationNode([SelectNumber]) -validation_node.invoke({ - "messages": [AIMessage("", tool_calls=[{"name": "SelectNumber", "args": {"a": 42}, "id": "1"}])] -}) +validation_node.invoke( + { + "messages": [ + AIMessage( + "", tool_calls=[{"name": "SelectNumber", "args": {"a": 42}, "id": "1"}] + ) + ] + } +) ``` ## Agent Inbox diff --git a/libs/prebuilt/langgraph/prebuilt/tool_node.py b/libs/prebuilt/langgraph/prebuilt/tool_node.py index 25d2425f9..95e161b90 100644 --- a/libs/prebuilt/langgraph/prebuilt/tool_node.py +++ b/libs/prebuilt/langgraph/prebuilt/tool_node.py @@ -87,8 +87,8 @@ from langgraph._internal._runnable import RunnableCallable from langgraph.errors import GraphBubbleUp from langgraph.graph.message import REMOVE_ALL_MESSAGES from langgraph.pregel._tools import _tool_call_writer -from langgraph.runtime import ExecutionInfo, ServerInfo # noqa: TC002 -from langgraph.store.base import BaseStore # noqa: TC002 +from langgraph.runtime import ExecutionInfo, ServerInfo +from langgraph.store.base import BaseStore from langgraph.types import Command, Send, StreamWriter from pydantic import BaseModel, ValidationError from typing_extensions import TypeVar, Unpack @@ -332,7 +332,7 @@ def msg_content_output(output: Any) -> str | list[dict]: # any existing ToolNode usage. try: return json.dumps(output, ensure_ascii=False) - except Exception: # noqa: BLE001 + except Exception: return str(output) @@ -736,7 +736,7 @@ class ToolNode(RunnableCallable): tool_node = ToolNode([my_tool], handle_tool_errors=handle_errors) ``` - """ # noqa: E501 + """ name: str = "tools" diff --git a/libs/prebuilt/pyproject.toml b/libs/prebuilt/pyproject.toml index b7a100db2..3c9c57315 100644 --- a/libs/prebuilt/pyproject.toml +++ b/libs/prebuilt/pyproject.toml @@ -75,8 +75,12 @@ addopts = "--strict-markers --strict-config --durations=5 -vv" asyncio_mode = "auto" [tool.ruff] -lint.select = [ "E", "F", "I", "TID251", "UP" ] +lint.select = [ "E", "F", "I", "PLC0415", "RUF100", "TID251", "UP" ] lint.ignore = [ "E501" ] +# PLC0415 (import-outside-top-level) is enforced in tests only. Library code +# still has deferred imports that have not been reviewed, so it stays exempt +# for now. +lint.per-file-ignores = { "langgraph/**" = ["PLC0415"] } target-version = "py310" [tool.ty.rules] diff --git a/libs/prebuilt/tests/memory_assert.py b/libs/prebuilt/tests/memory_assert.py index c09c2d78d..3c602c413 100644 --- a/libs/prebuilt/tests/memory_assert.py +++ b/libs/prebuilt/tests/memory_assert.py @@ -1,5 +1,6 @@ import os import tempfile +import time from collections import defaultdict from functools import partial @@ -38,8 +39,6 @@ class MemorySaverAssertImmutable(InMemorySaver): new_versions: ChannelVersions, ) -> None: if self.put_sleep: - import time - time.sleep(self.put_sleep) # assert checkpoint hasn't been modified since last written thread_id = config["configurable"]["thread_id"] diff --git a/libs/prebuilt/tests/test_injected_state_not_required.py b/libs/prebuilt/tests/test_injected_state_not_required.py index 36b9a1d17..58b77eaeb 100644 --- a/libs/prebuilt/tests/test_injected_state_not_required.py +++ b/libs/prebuilt/tests/test_injected_state_not_required.py @@ -9,16 +9,19 @@ handle missing fields by injecting None instead of raising KeyError. import sys from typing import Annotated +from unittest.mock import Mock import pytest from langchain_core.messages import AIMessage, AnyMessage, HumanMessage, ToolMessage from langchain_core.tools import tool from langgraph.graph.message import add_messages +from langgraph.runtime import Runtime from pydantic import BaseModel, Field from typing_extensions import NotRequired from langgraph.prebuilt import InjectedState, ToolNode, create_react_agent from langgraph.prebuilt.chat_agent_executor import AgentState +from langgraph.prebuilt.tool_node import ToolRuntime from .model import FakeToolCallingModel @@ -50,9 +53,6 @@ def _create_mock_runtime( store=None, ): """Create a mock Runtime for testing ToolNode directly.""" - from unittest.mock import Mock - - from langgraph.runtime import Runtime mock_runtime = Mock(spec=Runtime) mock_runtime.context = {} @@ -61,7 +61,6 @@ def _create_mock_runtime( def _create_config_with_runtime(store=None, state=None): """Create a RunnableConfig with mocked runtime for direct ToolNode testing.""" - from langgraph.prebuilt.tool_node import ToolRuntime tool_runtime = ToolRuntime( state=state or {}, diff --git a/libs/prebuilt/tests/test_on_tool_call.py b/libs/prebuilt/tests/test_on_tool_call.py index f1143c96b..f2af11b46 100644 --- a/libs/prebuilt/tests/test_on_tool_call.py +++ b/libs/prebuilt/tests/test_on_tool_call.py @@ -1,5 +1,6 @@ """Unit tests for tool call interceptor in ToolNode.""" +import functools from collections.abc import Callable from unittest.mock import Mock @@ -1331,14 +1332,13 @@ def _config_with_channel_read( learn channel names. The stub matches the shape: partial whose second and third positional args are `channels` and `managed` mappings. """ - import functools channels_stub = {k: None for k in channel_values} managed_stub: dict[str, object] = {} # Shape matches pregel's real partial: # functools.partial(local_read, scratchpad, channels, managed, task) - def _read(scratchpad, channels, managed, task, select, fresh): # noqa: ARG001 + def _read(scratchpad, channels, managed, task, select, fresh): if isinstance(select, str): return channel_values[select] return {k: channel_values[k] for k in select if k in channel_values} diff --git a/libs/prebuilt/tests/test_tool_node.py b/libs/prebuilt/tests/test_tool_node.py index acc5522fa..47ebdcae0 100644 --- a/libs/prebuilt/tests/test_tool_node.py +++ b/libs/prebuilt/tests/test_tool_node.py @@ -2,6 +2,7 @@ import contextlib import dataclasses import json import sys +import warnings from functools import partial from typing import ( Annotated, @@ -23,10 +24,12 @@ from langchain_core.messages import ( from langchain_core.runnables.config import RunnableConfig from langchain_core.tools import BaseTool, InjectedToolArg, ToolException from langchain_core.tools import tool as dec_tool +from langchain_core.tools.base import InjectedToolCallId from langgraph.config import get_stream_writer from langgraph.errors import GraphBubbleUp, GraphInterrupt from langgraph.graph import START, MessagesState, StateGraph from langgraph.graph.message import REMOVE_ALL_MESSAGES, add_messages +from langgraph.runtime import ExecutionInfo, ServerInfo from langgraph.store.base import BaseStore from langgraph.store.memory import InMemoryStore from langgraph.types import Command, Send @@ -41,6 +44,7 @@ from langgraph.prebuilt import ( ) from langgraph.prebuilt.tool_node import ( TOOL_CALL_ERROR_TEMPLATE, + ToolCallRequest, ToolInvocationError, ToolRuntime, tools_condition, @@ -59,7 +63,6 @@ def _create_mock_runtime(store: BaseStore | None = None) -> Mock: which is injected by RunnableCallable from config["configurable"]["__pregel_runtime"]. When testing ToolNode directly (outside a graph), we need to provide this manually. """ - from langgraph.runtime import ExecutionInfo mock_runtime = Mock() mock_runtime.store = store @@ -625,7 +628,6 @@ def test_tool_node_node_interrupt() -> None: @pytest.mark.parametrize("input_type", ["dict", "tool_calls"]) async def test_tool_node_command(input_type: str) -> None: - from langchain_core.tools.base import InjectedToolCallId @dec_tool def transfer_to_bob(tool_call_id: Annotated[str, InjectedToolCallId]): @@ -934,7 +936,6 @@ async def test_tool_node_command(input_type: str) -> None: async def test_tool_node_command_list_input() -> None: - from langchain_core.tools.base import InjectedToolCallId @dec_tool def transfer_to_bob(tool_call_id: Annotated[str, InjectedToolCallId]): @@ -1194,7 +1195,6 @@ async def test_tool_node_command_list_input() -> None: def test_tool_node_parent_command_with_send() -> None: - from langchain_core.tools.base import InjectedToolCallId @dec_tool def transfer_to_alice(tool_call_id: Annotated[str, InjectedToolCallId]): @@ -1282,7 +1282,6 @@ def test_tool_node_parent_command_with_send() -> None: async def test_tool_node_command_remove_all_messages() -> None: - from langchain_core.tools.base import InjectedToolCallId @dec_tool def remove_all_messages_tool(tool_call_id: Annotated[str, InjectedToolCallId]): @@ -1621,9 +1620,6 @@ def test_tool_node_stream_writer() -> None: def test_tool_call_request_setattr_deprecation_warning(): """Test that ToolCallRequest raises a deprecation warning on direct attribute modification.""" - import warnings - - from langgraph.prebuilt.tool_node import ToolCallRequest # Create a mock ToolCall tool_call = {"name": "test", "args": {"a": 1}, "id": "call_1", "type": "tool_call"} @@ -2031,7 +2027,6 @@ def test_tool_runtime_defaults_tools_to_empty_list() -> None: def test_tool_runtime_forwards_execution_info_server_info_and_tools() -> None: """Test that execution_info, server_info, and tools are forwarded from Runtime to ToolRuntime.""" - from langgraph.runtime import ExecutionInfo, ServerInfo exec_info = ExecutionInfo( thread_id="t-1", @@ -2088,7 +2083,6 @@ async def test_tool_runtime_forwards_execution_info_server_info_and_tools_async( None ): """Test that execution_info, server_info, and tools are forwarded in async path.""" - from langgraph.runtime import ExecutionInfo, ServerInfo exec_info = ExecutionInfo( thread_id="t-2", diff --git a/libs/prebuilt/uv.lock b/libs/prebuilt/uv.lock index 236e552e4..53e34cdb5 100644 --- a/libs/prebuilt/uv.lock +++ b/libs/prebuilt/uv.lock @@ -285,7 +285,7 @@ wheels = [ [[package]] name = "langgraph" -version = "1.2.10" +version = "1.2.11" source = { editable = "../langgraph" } dependencies = [ { name = "langchain-core" }, @@ -369,7 +369,7 @@ test = [ [[package]] name = "langgraph-checkpoint" -version = "4.1.1" +version = "4.2.0" source = { editable = "../checkpoint" } dependencies = [ { name = "langchain-core" }, @@ -417,7 +417,7 @@ test = [ [[package]] name = "langgraph-checkpoint-postgres" -version = "3.1.1" +version = "3.1.2" source = { editable = "../checkpoint-postgres" } dependencies = [ { name = "langgraph-checkpoint" }, @@ -439,6 +439,7 @@ dev = [ { name = "anyio" }, { name = "codespell" }, { name = "langgraph-checkpoint", editable = "../checkpoint" }, + { name = "langgraph-checkpoint-conformance", editable = "../checkpoint-conformance" }, { name = "psycopg", extras = ["binary"] }, { name = "pytest" }, { name = "pytest-asyncio" }, @@ -455,6 +456,7 @@ lint = [ test = [ { name = "anyio" }, { name = "langgraph-checkpoint", editable = "../checkpoint" }, + { name = "langgraph-checkpoint-conformance", editable = "../checkpoint-conformance" }, { name = "psycopg", extras = ["binary"] }, { name = "pytest" }, { name = "pytest-asyncio" }, @@ -483,6 +485,7 @@ requires-dist = [ dev = [ { name = "codespell" }, { name = "langgraph-checkpoint", editable = "../checkpoint" }, + { name = "langgraph-checkpoint-conformance", editable = "../checkpoint-conformance" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-mock" }, @@ -498,6 +501,7 @@ lint = [ ] test = [ { name = "langgraph-checkpoint", editable = "../checkpoint" }, + { name = "langgraph-checkpoint-conformance", editable = "../checkpoint-conformance" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-mock" }, @@ -621,13 +625,13 @@ dev = [ { name = "pytest-asyncio" }, { name = "pytest-mock" }, { name = "pytest-watch" }, - { name = "ruff", specifier = "==0.15.20" }, + { name = "ruff", specifier = "==0.16.1" }, { name = "starlette" }, { name = "ty" }, ] lint = [ { name = "codespell" }, - { name = "ruff", specifier = "==0.15.20" }, + { name = "ruff", specifier = "==0.16.1" }, { name = "starlette" }, { name = "ty" }, ] diff --git a/libs/sdk-py/MIGRATION.md b/libs/sdk-py/MIGRATION.md index 9c9742ff0..f1a37c292 100644 --- a/libs/sdk-py/MIGRATION.md +++ b/libs/sdk-py/MIGRATION.md @@ -87,7 +87,9 @@ async with client.threads.stream(assistant_id="agent") as thread: ```python async with client.threads.stream(assistant_id="agent") as thread: - await thread.run.start(input={"messages": [{"role": "user", "content": "book a flight"}]}) + await thread.run.start( + input={"messages": [{"role": "user", "content": "book a flight"}]} + ) # Wait for the run to pause at an interrupt node. # thread.interrupted becomes True when input.requested arrives. diff --git a/libs/sdk-py/README.md b/libs/sdk-py/README.md index 53fae13a9..24bf82318 100644 --- a/libs/sdk-py/README.md +++ b/libs/sdk-py/README.md @@ -43,7 +43,9 @@ thread = await client.threads.create() # Start a streaming run input = {"messages": [{"role": "human", "content": "what's the weather in la"}]} -async for chunk in client.runs.stream(thread['thread_id'], agent['assistant_id'], input=input): +async for chunk in client.runs.stream( + thread["thread_id"], agent["assistant_id"], input=input +): print(chunk) ``` @@ -80,9 +82,9 @@ async with client.threads.stream( messages, tool_calls = await asyncio.gather(get_messages(), get_tool_calls()) for stream in messages: - print(await stream.text) # accumulated text + print(await stream.text) # accumulated text - final = await thread.output # terminal state values + final = await thread.output # terminal state values ``` ## 📕 Releases & Versioning diff --git a/libs/sdk-py/langgraph_sdk/__init__.py b/libs/sdk-py/langgraph_sdk/__init__.py index dbe4ffa45..a56e0b3b3 100644 --- a/libs/sdk-py/langgraph_sdk/__init__.py +++ b/libs/sdk-py/langgraph_sdk/__init__.py @@ -1,8 +1,15 @@ from langgraph_sdk.auth import Auth from langgraph_sdk.client import get_client, get_sync_client from langgraph_sdk.encryption import Encryption -from langgraph_sdk.encryption.types import EncryptionContext +from langgraph_sdk.encryption.types import DecryptResult, EncryptionContext -__version__ = "0.4.2" +__version__ = "0.4.3" -__all__ = ["Auth", "Encryption", "EncryptionContext", "get_client", "get_sync_client"] +__all__ = [ + "Auth", + "DecryptResult", + "Encryption", + "EncryptionContext", + "get_client", + "get_sync_client", +] diff --git a/libs/sdk-py/langgraph_sdk/encryption/__init__.py b/libs/sdk-py/langgraph_sdk/encryption/__init__.py index 79611de05..c2b3f8a03 100644 --- a/libs/sdk-py/langgraph_sdk/encryption/__init__.py +++ b/libs/sdk-py/langgraph_sdk/encryption/__init__.py @@ -18,6 +18,9 @@ import warnings from langgraph_sdk.encryption import types +_BlobDecryptorT = typing.TypeVar("_BlobDecryptorT", bound=types.BlobDecryptor) +_JsonDecryptorT = typing.TypeVar("_JsonDecryptorT", bound=types.JsonDecryptor) + class LangGraphBetaWarning(UserWarning): """Warning for beta features in LangGraph SDK.""" @@ -141,7 +144,7 @@ class _DecryptDecorators: def __init__(self, parent: Encryption): self._parent = parent - def blob(self, fn: types.BlobDecryptor) -> types.BlobDecryptor: + def blob(self, fn: _BlobDecryptorT) -> _BlobDecryptorT: """Register a blob decryption handler. The handler will be called to decrypt opaque data like checkpoint blobs. @@ -149,7 +152,9 @@ class _DecryptDecorators: Example: ```python @encryption.decrypt.blob - async def decrypt_blob(ctx: EncryptionContext, blob: bytes) -> bytes: + async def decrypt_blob( + ctx: EncryptionContext, blob: bytes + ) -> bytes | DecryptResult[bytes]: # Decrypt the blob using your encryption service return decrypted_blob ``` @@ -170,13 +175,15 @@ class _DecryptDecorators: self._parent._blob_decryptor = fn return fn - def json(self, fn: types.JsonDecryptor) -> types.JsonDecryptor: + def json(self, fn: _JsonDecryptorT) -> _JsonDecryptorT: """Register the JSON decryption handler. Example: ```python @encryption.decrypt.json - async def decrypt_json(ctx: EncryptionContext, data: dict) -> dict: + async def decrypt_json( + ctx: EncryptionContext, data: dict + ) -> dict | DecryptResult[dict]: # Decrypt the data return decrypt_data(data) ``` @@ -369,7 +376,7 @@ class Encryption: """Reference to encryption type definitions. Provides access to all type definitions used in the encryption system, - including EncryptionContext, BlobEncryptor, BlobDecryptor, + including EncryptionContext, DecryptResult, BlobEncryptor, BlobDecryptor, JsonEncryptor, and JsonDecryptor. """ diff --git a/libs/sdk-py/langgraph_sdk/encryption/types.py b/libs/sdk-py/langgraph_sdk/encryption/types.py index 92e65a8c6..fca78fa8e 100644 --- a/libs/sdk-py/langgraph_sdk/encryption/types.py +++ b/libs/sdk-py/langgraph_sdk/encryption/types.py @@ -9,10 +9,30 @@ from __future__ import annotations import typing from collections.abc import Awaitable, Callable +from dataclasses import dataclass Json = dict[str, typing.Any] """JSON-serializable dictionary type for structured data encryption.""" +T = typing.TypeVar("T") + + +@dataclass(frozen=True, slots=True) +class DecryptResult(typing.Generic[T]): + """Decrypted data and optional replacement ciphertext. + + Return this from a decrypt handler when encrypted data should be replaced, + such as after rotating its encryption key. Returning plaintext directly + remains supported when no replacement is needed. + + Attributes: + plaintext: Decrypted data returned to the caller + replacement: New encrypted data to persist in place of the input + """ + + plaintext: T + replacement: T | None = None + class EncryptionContext: """Context passed to encryption/decryption handlers. @@ -57,7 +77,9 @@ Returns: Awaitable that resolves to encrypted bytes """ -BlobDecryptor = Callable[[EncryptionContext, bytes], Awaitable[bytes]] +BlobDecryptor = Callable[ + [EncryptionContext, bytes], Awaitable[bytes | DecryptResult[bytes]] +] """Handler for decrypting opaque blob data like checkpoints. Note: Must be an async function. Decryption typically involves I/O operations @@ -68,7 +90,8 @@ Args: blob: The encrypted bytes to decrypt Returns: - Awaitable that resolves to decrypted bytes + Awaitable that resolves to decrypted bytes, or a DecryptResult containing + decrypted bytes and replacement ciphertext """ JsonEncryptor = Callable[[EncryptionContext, Json], Awaitable[Json]] @@ -101,7 +124,9 @@ Returns: Awaitable that resolves to encrypted JSON dictionary """ -JsonDecryptor = Callable[[EncryptionContext, Json], Awaitable[Json]] +JsonDecryptor = Callable[ + [EncryptionContext, Json], Awaitable[Json | DecryptResult[Json]] +] """Handler for decrypting structured JSON data. Note: Must be an async function. Decryption typically involves I/O operations @@ -115,7 +140,8 @@ Args: data: The encrypted JSON dictionary Returns: - Awaitable that resolves to decrypted JSON dictionary + Awaitable that resolves to a decrypted JSON dictionary, or a DecryptResult + containing decrypted JSON and replacement ciphertext """ if typing.TYPE_CHECKING: diff --git a/libs/sdk-py/langgraph_sdk/schema.py b/libs/sdk-py/langgraph_sdk/schema.py index 16b197db3..18b1b44f3 100644 --- a/libs/sdk-py/langgraph_sdk/schema.py +++ b/libs/sdk-py/langgraph_sdk/schema.py @@ -963,7 +963,7 @@ class _BaseModelLike(Protocol): ) -> dict[str, Any]: ... -_JSONLike: TypeAlias = None | str | int | float | bool +_JSONLike: TypeAlias = str | int | float | bool | None _JSONMap: TypeAlias = Mapping[ str, Union[_JSONLike, list[_JSONLike], "_JSONMap", list["_JSONMap"]] ] diff --git a/libs/sdk-py/pyproject.toml b/libs/sdk-py/pyproject.toml index b90676ea0..d2e8d9c84 100644 --- a/libs/sdk-py/pyproject.toml +++ b/libs/sdk-py/pyproject.toml @@ -36,7 +36,7 @@ test = [ "pytest-watch", ] lint = [ - "ruff==0.15.20", + "ruff==0.16.1", "codespell", "ty", "starlette", @@ -80,6 +80,7 @@ select = [ "SIM", # flake8-simplify (code simplification) "RUF", # ruff-specific rules "S101", # flake8-bandit: use of assert + "PLC0415", # import-outside-top-level ] ignore = [ "E501", # line too long (handled by formatter) @@ -87,7 +88,10 @@ ignore = [ "B904", # raise without from inside except (sometimes intentional) "SIM102", # nested if statements (sometimes clearer) ] -per-file-ignores = { "tests/**" = ["S101", "B017"], "integration/**" = ["S101", "T20", "B017", "ARG001", "ARG002"] } +# PLC0415 (import-outside-top-level) is enforced in tests only. Library code +# still has deferred imports that have not been reviewed, so it stays exempt +# for now. +per-file-ignores = { "tests/**" = ["S101", "B017"], "integration/**" = ["S101", "T20", "B017", "ARG001", "ARG002", "PLC0415"], "langgraph_sdk/**" = ["PLC0415"] } [tool.ty.src] # The `integration/` graphs run inside the docker image (with `deepagents` diff --git a/libs/sdk-py/tests/integration/conftest.py b/libs/sdk-py/tests/integration/conftest.py index 29eea134e..0fb8bf468 100644 --- a/libs/sdk-py/tests/integration/conftest.py +++ b/libs/sdk-py/tests/integration/conftest.py @@ -18,6 +18,11 @@ from collections.abc import AsyncIterator, Iterator import httpx import pytest +from langgraph_sdk._async.http import HttpClient +from langgraph_sdk._async.threads import ThreadsClient +from langgraph_sdk._sync.http import SyncHttpClient +from langgraph_sdk._sync.threads import SyncThreadsClient + BASE_URL = os.environ.get("LANGGRAPH_INTEGRATION_URL", "http://localhost:2024") ASSISTANT_ID = "agent" TOOLS_ASSISTANT_ID = "tools_agent" @@ -47,8 +52,6 @@ def _require_running_api() -> None: @pytest.fixture async def async_threads() -> AsyncIterator[tuple[object, httpx.AsyncClient]]: """Build an async ThreadsClient. Yields `(threads, raw_httpx)` so tests can close raw.""" - from langgraph_sdk._async.http import HttpClient - from langgraph_sdk._async.threads import ThreadsClient raw = httpx.AsyncClient(base_url=BASE_URL, timeout=30.0) try: @@ -60,8 +63,6 @@ async def async_threads() -> AsyncIterator[tuple[object, httpx.AsyncClient]]: @pytest.fixture def sync_threads() -> Iterator[tuple[object, httpx.Client]]: """Build a sync ThreadsClient. Yields `(threads, raw_httpx)` so tests can close raw.""" - from langgraph_sdk._sync.http import SyncHttpClient - from langgraph_sdk._sync.threads import SyncThreadsClient raw = httpx.Client(base_url=BASE_URL, timeout=30.0) try: diff --git a/libs/sdk-py/tests/integration/test_assistants.py b/libs/sdk-py/tests/integration/test_assistants.py index d71787732..430f65dff 100644 --- a/libs/sdk-py/tests/integration/test_assistants.py +++ b/libs/sdk-py/tests/integration/test_assistants.py @@ -9,21 +9,22 @@ from __future__ import annotations import pytest +from langgraph_sdk._async.assistants import AssistantsClient +from langgraph_sdk._async.http import HttpClient +from langgraph_sdk._sync.assistants import SyncAssistantsClient +from langgraph_sdk._sync.http import SyncHttpClient + from .conftest import ASSISTANT_ID pytestmark = pytest.mark.integration def _async_assistants(raw): - from langgraph_sdk._async.assistants import AssistantsClient - from langgraph_sdk._async.http import HttpClient return AssistantsClient(HttpClient(raw)) def _sync_assistants(raw): - from langgraph_sdk._sync.assistants import SyncAssistantsClient - from langgraph_sdk._sync.http import SyncHttpClient return SyncAssistantsClient(SyncHttpClient(raw)) diff --git a/libs/sdk-py/tests/integration/test_cancel.py b/libs/sdk-py/tests/integration/test_cancel.py index 995f1b26a..822897161 100644 --- a/libs/sdk-py/tests/integration/test_cancel.py +++ b/libs/sdk-py/tests/integration/test_cancel.py @@ -10,6 +10,11 @@ from typing import Any import pytest +from langgraph_sdk._async.http import HttpClient +from langgraph_sdk._async.runs import RunsClient +from langgraph_sdk._sync.http import SyncHttpClient +from langgraph_sdk._sync.runs import SyncRunsClient + from .conftest import ASSISTANT_ID pytestmark = pytest.mark.integration @@ -29,8 +34,6 @@ async def _cancel_after_first_event( async def test_cancel_async(async_threads) -> None: - from langgraph_sdk._async.http import HttpClient - from langgraph_sdk._async.runs import RunsClient threads, raw = async_threads runs_client = RunsClient(HttpClient(raw)) @@ -88,8 +91,6 @@ def _cancel_after_first_event_sync( def test_cancel_sync(sync_threads) -> None: - from langgraph_sdk._sync.http import SyncHttpClient - from langgraph_sdk._sync.runs import SyncRunsClient threads, raw = sync_threads runs_client = SyncRunsClient(SyncHttpClient(raw)) diff --git a/libs/sdk-py/tests/integration/test_crons.py b/libs/sdk-py/tests/integration/test_crons.py index 90d81b4ca..64b842634 100644 --- a/libs/sdk-py/tests/integration/test_crons.py +++ b/libs/sdk-py/tests/integration/test_crons.py @@ -10,21 +10,22 @@ from __future__ import annotations import pytest +from langgraph_sdk._async.cron import CronClient +from langgraph_sdk._async.http import HttpClient +from langgraph_sdk._sync.cron import SyncCronClient +from langgraph_sdk._sync.http import SyncHttpClient + from .conftest import ASSISTANT_ID pytestmark = pytest.mark.integration def _async_crons(raw): - from langgraph_sdk._async.cron import CronClient - from langgraph_sdk._async.http import HttpClient return CronClient(HttpClient(raw)) def _sync_crons(raw): - from langgraph_sdk._sync.cron import SyncCronClient - from langgraph_sdk._sync.http import SyncHttpClient return SyncCronClient(SyncHttpClient(raw)) diff --git a/libs/sdk-py/tests/integration/test_factory_graph.py b/libs/sdk-py/tests/integration/test_factory_graph.py index 5ffd0d957..c45933b9f 100644 --- a/libs/sdk-py/tests/integration/test_factory_graph.py +++ b/libs/sdk-py/tests/integration/test_factory_graph.py @@ -13,21 +13,22 @@ from __future__ import annotations import pytest +from langgraph_sdk._async.http import HttpClient +from langgraph_sdk._async.runs import RunsClient +from langgraph_sdk._sync.http import SyncHttpClient +from langgraph_sdk._sync.runs import SyncRunsClient + from .conftest import FACTORY_ASSISTANT_ID pytestmark = pytest.mark.integration def _async_runs(raw): - from langgraph_sdk._async.http import HttpClient - from langgraph_sdk._async.runs import RunsClient return RunsClient(HttpClient(raw)) def _sync_runs(raw): - from langgraph_sdk._sync.http import SyncHttpClient - from langgraph_sdk._sync.runs import SyncRunsClient return SyncRunsClient(SyncHttpClient(raw)) diff --git a/libs/sdk-py/tests/integration/test_runs.py b/libs/sdk-py/tests/integration/test_runs.py index 8a6404530..83dcc32ce 100644 --- a/libs/sdk-py/tests/integration/test_runs.py +++ b/libs/sdk-py/tests/integration/test_runs.py @@ -11,21 +11,22 @@ from __future__ import annotations import pytest +from langgraph_sdk._async.http import HttpClient +from langgraph_sdk._async.runs import RunsClient +from langgraph_sdk._sync.http import SyncHttpClient +from langgraph_sdk._sync.runs import SyncRunsClient + from .conftest import ASSISTANT_ID pytestmark = pytest.mark.integration def _async_runs(raw): - from langgraph_sdk._async.http import HttpClient - from langgraph_sdk._async.runs import RunsClient return RunsClient(HttpClient(raw)) def _sync_runs(raw): - from langgraph_sdk._sync.http import SyncHttpClient - from langgraph_sdk._sync.runs import SyncRunsClient return SyncRunsClient(SyncHttpClient(raw)) diff --git a/libs/sdk-py/tests/integration/test_store.py b/libs/sdk-py/tests/integration/test_store.py index bb80be63f..03493ab5a 100644 --- a/libs/sdk-py/tests/integration/test_store.py +++ b/libs/sdk-py/tests/integration/test_store.py @@ -10,19 +10,20 @@ import uuid import pytest +from langgraph_sdk._async.http import HttpClient +from langgraph_sdk._async.store import StoreClient +from langgraph_sdk._sync.http import SyncHttpClient +from langgraph_sdk._sync.store import SyncStoreClient + pytestmark = pytest.mark.integration def _async_store(raw): - from langgraph_sdk._async.http import HttpClient - from langgraph_sdk._async.store import StoreClient return StoreClient(HttpClient(raw)) def _sync_store(raw): - from langgraph_sdk._sync.http import SyncHttpClient - from langgraph_sdk._sync.store import SyncStoreClient return SyncStoreClient(SyncHttpClient(raw)) diff --git a/libs/sdk-py/tests/integration/test_websocket.py b/libs/sdk-py/tests/integration/test_websocket.py index 39d5dfa50..defeb21ed 100644 --- a/libs/sdk-py/tests/integration/test_websocket.py +++ b/libs/sdk-py/tests/integration/test_websocket.py @@ -4,6 +4,11 @@ from __future__ import annotations import pytest +from langgraph_sdk.stream.transport import ( + ProtocolWebSocketTransport, + SyncProtocolWebSocketTransport, +) + from .conftest import ASSISTANT_ID, EXPECTED_TERMINAL_ITEMS pytestmark = pytest.mark.integration @@ -14,8 +19,6 @@ async def test_websocket_async(async_threads) -> None: async with threads.stream( assistant_id=ASSISTANT_ID, transport="websocket" ) as thread: - from langgraph_sdk.stream.transport import ProtocolWebSocketTransport - assert isinstance(thread._transport, ProtocolWebSocketTransport) await thread.run.start(input={"messages": [], "value": "init", "items": []}) @@ -34,8 +37,6 @@ async def test_websocket_async(async_threads) -> None: def test_websocket_sync(sync_threads) -> None: threads, _ = sync_threads with threads.stream(assistant_id=ASSISTANT_ID, transport="websocket") as thread: - from langgraph_sdk.stream.transport import SyncProtocolWebSocketTransport - assert isinstance(thread._transport, SyncProtocolWebSocketTransport) thread.run.start(input={"messages": [], "value": "init", "items": []}) diff --git a/libs/sdk-py/tests/streaming/test_controller.py b/libs/sdk-py/tests/streaming/test_controller.py index e9c71670e..98ec1d35a 100644 --- a/libs/sdk-py/tests/streaming/test_controller.py +++ b/libs/sdk-py/tests/streaming/test_controller.py @@ -3,14 +3,21 @@ from __future__ import annotations import asyncio +import asyncio as _asyncio +import logging from collections.abc import AsyncIterator from typing import Any from unittest.mock import AsyncMock +import httpx import pytest -from langgraph_sdk.stream.controller import StreamController, _SeenEventIds -from langgraph_sdk.stream.transport.http import EventStreamHandle +from langgraph_sdk.stream.controller import ( + StreamController, + _close_after, + _SeenEventIds, +) +from langgraph_sdk.stream.transport.http import EventStreamHandle, ProtocolSseTransport # --------------------------------------------------------------------------- # Task 3.1: bounded subscription queues @@ -20,9 +27,6 @@ from langgraph_sdk.stream.transport.http import EventStreamHandle @pytest.mark.asyncio async def test_subscription_queue_bounded_by_max_queue_size(): """`StreamController` must create per-subscription queues bounded by `max_queue_size`.""" - import httpx - - from langgraph_sdk.stream.transport.http import ProtocolSseTransport transport = ProtocolSseTransport( client=httpx.AsyncClient(base_url="http://test"), @@ -36,9 +40,6 @@ async def test_subscription_queue_bounded_by_max_queue_size(): @pytest.mark.asyncio async def test_subscription_queue_default_max_queue_size_is_1024(): """`StreamController` default `max_queue_size` is 1024.""" - import httpx - - from langgraph_sdk.stream.transport.http import ProtocolSseTransport transport = ProtocolSseTransport( client=httpx.AsyncClient(base_url="http://test"), @@ -114,12 +115,6 @@ def test_seen_event_ids_iter_returns_keys(): async def test_close_awaits_pending_rotation_closes(): """When a rotation is mid-flight, controller.close() must await the old stream close before returning.""" - import asyncio as _asyncio - - import httpx - - from langgraph_sdk.stream.controller import _close_after - from langgraph_sdk.stream.transport.http import ProtocolSseTransport rotation_close_done = _asyncio.Event() @@ -269,7 +264,6 @@ async def test_reconnect_accepts_backoff_kwargs(): @pytest.mark.anyio async def test_transport_drop_exception_logged_with_type(monkeypatch, caplog): """Bare `pass` discarded exception types; the drop should at least log.""" - import logging monkeypatch.setattr("asyncio.sleep", AsyncMock()) diff --git a/libs/sdk-py/tests/streaming/test_decoders.py b/libs/sdk-py/tests/streaming/test_decoders.py index c1ebcd98d..7b706977d 100644 --- a/libs/sdk-py/tests/streaming/test_decoders.py +++ b/libs/sdk-py/tests/streaming/test_decoders.py @@ -8,6 +8,8 @@ from __future__ import annotations from typing import Any +import pytest + from langgraph_sdk.stream.decoders import ( DataDecoder, ExtensionsDecoder, @@ -454,7 +456,6 @@ def test_extensions_decoder_ignores_non_dict_data(): def test_extensions_decoder_rejects_empty_name(): - import pytest with pytest.raises(ValueError): ExtensionsDecoder(name="") diff --git a/libs/sdk-py/tests/streaming/test_extensions_projection.py b/libs/sdk-py/tests/streaming/test_extensions_projection.py index 16a8fd0c1..05bbb3ac5 100644 --- a/libs/sdk-py/tests/streaming/test_extensions_projection.py +++ b/libs/sdk-py/tests/streaming/test_extensions_projection.py @@ -3,6 +3,7 @@ from __future__ import annotations import httpx from langgraph_sdk._async.http import HttpClient +from langgraph_sdk._async.stream import ScopedStreamHandle from langgraph_sdk._async.threads import ThreadsClient from streaming._events import custom_event, lifecycle_completed_event from streaming._fake_server import FakeServer @@ -46,8 +47,6 @@ async def test_extension_projection_supports_namespace_scope_on_subgraph_handle( ) transport = httpx.ASGITransport(app=fake.app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as raw: - from langgraph_sdk._async.stream import ScopedStreamHandle - threads = ThreadsClient(HttpClient(raw)) async with threads.stream(thread_id="t-1", assistant_id="agent") as thread: await thread.run.start(input={}) diff --git a/libs/sdk-py/tests/streaming/test_lifecycle_watcher.py b/libs/sdk-py/tests/streaming/test_lifecycle_watcher.py index 2a6200406..2d759361a 100644 --- a/libs/sdk-py/tests/streaming/test_lifecycle_watcher.py +++ b/libs/sdk-py/tests/streaming/test_lifecycle_watcher.py @@ -7,9 +7,11 @@ import contextlib from typing import Any import httpx +import pytest from langgraph_sdk._async.http import HttpClient from langgraph_sdk._async.threads import ThreadsClient +from langgraph_sdk.stream.transport import EventStreamHandle, ProtocolSseTransport from streaming._events import ( input_requested_event, lifecycle_completed_event, @@ -153,7 +155,6 @@ async def test_lifecycle_clean_eof_resolves_run_done_with_errored(): """If the lifecycle SSE stream ends cleanly (server closes without a terminal `completed` or `errored` event), `_run_done` must resolve with an errored terminal so awaiters don't hang.""" - import pytest fake = FakeServer() # Emit a non-terminal lifecycle event, then close cleanly without @@ -179,7 +180,6 @@ async def test_lifecycle_mid_iteration_error_resolves_run_done_with_error( """If the transport reports an error via `handle.done` after iteration exits without a terminal lifecycle event, `_run_done` propagates the transport error rather than the generic clean-EOF message.""" - from langgraph_sdk.stream.transport import EventStreamHandle, ProtocolSseTransport def synthetic_handle() -> EventStreamHandle: loop = asyncio.get_running_loop() diff --git a/libs/sdk-py/tests/streaming/test_scoped_handles.py b/libs/sdk-py/tests/streaming/test_scoped_handles.py index 13955b225..8479315db 100644 --- a/libs/sdk-py/tests/streaming/test_scoped_handles.py +++ b/libs/sdk-py/tests/streaming/test_scoped_handles.py @@ -2,12 +2,16 @@ from __future__ import annotations +from unittest.mock import MagicMock + import httpx from langgraph_sdk._async.http import HttpClient +from langgraph_sdk._async.stream import ScopedStreamHandle from langgraph_sdk._async.threads import ThreadsClient from streaming._events import ( lifecycle_completed_event, + lifecycle_errored_event, lifecycle_started_event, message_finish_event, message_start_event, @@ -430,9 +434,6 @@ async def test_grandchild_events_dispatched_to_correct_sibling_not_first_match() def test_scoped_handle_inboxes_bounded_by_max_queue_size(): """ScopedStreamHandle with max_queue_size=N creates queues with maxsize=N.""" - from unittest.mock import MagicMock - - from langgraph_sdk._async.stream import ScopedStreamHandle fake_thread = MagicMock() handle = ScopedStreamHandle( @@ -484,7 +485,6 @@ async def test_force_complete_uses_failed_when_run_errored(): """If the lifecycle signals an errored run, scoped children that are still 'started' when the subgraphs projection's finally block runs must be force-finished as 'failed', not 'completed'.""" - from streaming._events import lifecycle_errored_event fake = FakeServer() fake.script( @@ -538,9 +538,6 @@ async def test_force_complete_uses_completed_when_run_completed(): def test_close_inboxes_does_not_enqueue_on_uniterated_inboxes(): """_close_inboxes must not push a sentinel on inboxes that had no consumer.""" - from unittest.mock import MagicMock - - from langgraph_sdk._async.stream import ScopedStreamHandle fake_thread = MagicMock() handle = ScopedStreamHandle( @@ -559,9 +556,6 @@ def test_close_inboxes_does_not_enqueue_on_uniterated_inboxes(): def test_close_inboxes_enqueues_sentinel_on_iterated_inboxes(): """_close_inboxes must push a None sentinel only on inboxes that had a consumer, so projection iterators see the EOF signal.""" - from unittest.mock import MagicMock - - from langgraph_sdk._async.stream import ScopedStreamHandle fake_thread = MagicMock() handle = ScopedStreamHandle( diff --git a/libs/sdk-py/tests/streaming/test_shared_stream.py b/libs/sdk-py/tests/streaming/test_shared_stream.py index 1a96a91bd..75835444c 100644 --- a/libs/sdk-py/tests/streaming/test_shared_stream.py +++ b/libs/sdk-py/tests/streaming/test_shared_stream.py @@ -3,14 +3,15 @@ from __future__ import annotations import asyncio from collections.abc import AsyncGenerator from typing import Any, cast +from unittest.mock import MagicMock import httpx from langgraph_sdk._async.http import HttpClient from langgraph_sdk._async.threads import ThreadsClient from langgraph_sdk.stream.controller import StreamController -from langgraph_sdk.stream.transport.http import EventStreamHandle -from streaming._events import lifecycle_event, values_event +from langgraph_sdk.stream.transport.http import EventStreamHandle, ProtocolSseTransport +from streaming._events import lifecycle_completed_event, lifecycle_event, values_event from streaming._fake_server import FakeServer, _StreamScript @@ -165,7 +166,6 @@ async def test_values_projection_registers_via_delegation_not_controller_directl directly — the subscription count seen through the thread wrapper equals the count inside the controller at the moment the subscription is live. """ - from streaming._events import lifecycle_completed_event fake = FakeServer() fake.script([lifecycle_completed_event(seq=0)]) @@ -255,10 +255,6 @@ async def test_shared_stream_reconnects_with_since_after_transport_drop(): handle2, _ = _make_handle([values_event(seq=2, values={"counter": 2})]) handles = [handle1, handle2] - from unittest.mock import MagicMock - - from langgraph_sdk.stream.transport.http import ProtocolSseTransport - transport = MagicMock(spec=ProtocolSseTransport) def _open(params: dict[str, Any]) -> EventStreamHandle: @@ -303,10 +299,6 @@ async def test_shared_stream_reconnect_dedupes_replayed_overlap(): ) handles = [handle1, handle2] - from unittest.mock import MagicMock - - from langgraph_sdk.stream.transport.http import ProtocolSseTransport - transport = MagicMock(spec=ProtocolSseTransport) transport.open_event_stream.side_effect = lambda _params: handles.pop(0) diff --git a/libs/sdk-py/tests/streaming/test_sync_extensions_projection.py b/libs/sdk-py/tests/streaming/test_sync_extensions_projection.py index 808846aee..926dc0a5d 100644 --- a/libs/sdk-py/tests/streaming/test_sync_extensions_projection.py +++ b/libs/sdk-py/tests/streaming/test_sync_extensions_projection.py @@ -3,6 +3,7 @@ from __future__ import annotations import httpx from langgraph_sdk._sync.http import SyncHttpClient +from langgraph_sdk._sync.stream import SyncScopedStreamHandle from langgraph_sdk._sync.threads import SyncThreadsClient from streaming._events import custom_event, lifecycle_completed_event from streaming._sync_fake_server import SyncFakeServer @@ -41,8 +42,6 @@ def test_sync_extension_projection_supports_namespace_scope_on_subgraph_handle() ] ) with httpx.Client(transport=fake.transport, base_url="http://test") as raw: - from langgraph_sdk._sync.stream import SyncScopedStreamHandle - threads = SyncThreadsClient(SyncHttpClient(raw)) with threads.stream(thread_id="t-1", assistant_id="agent") as thread: thread.run.start(input={}) diff --git a/libs/sdk-py/tests/streaming/test_sync_projections.py b/libs/sdk-py/tests/streaming/test_sync_projections.py index 07708793e..f22c529e4 100644 --- a/libs/sdk-py/tests/streaming/test_sync_projections.py +++ b/libs/sdk-py/tests/streaming/test_sync_projections.py @@ -2,6 +2,8 @@ from __future__ import annotations +import time +from collections.abc import Generator from typing import Any, cast import httpx @@ -10,6 +12,7 @@ from langchain_core.language_models.chat_model_stream import ChatModelStream from langchain_protocol import Event from langgraph_sdk._sync.http import SyncHttpClient +from langgraph_sdk._sync.stream import SyncToolCallHandle from langgraph_sdk._sync.threads import SyncThreadsClient from streaming._events import ( lifecycle_completed_event, @@ -459,11 +462,6 @@ def test_sync_tool_calls_explicit_close_does_not_block_1s(): tool_started_event(seq=1, tool_call_id="call-1"), ] ) - import time - from collections.abc import Generator - from typing import cast - - from langgraph_sdk._sync.stream import SyncToolCallHandle with httpx.Client(transport=fake.transport, base_url="http://test") as raw: threads = SyncThreadsClient(SyncHttpClient(raw)) @@ -528,7 +526,6 @@ def test_sync_tool_call_handle_deltas_queue_is_bounded(): Unbounded queues allow producers to enqueue indefinitely, causing memory growth when consumers are slow. """ - from langgraph_sdk._sync.stream import SyncToolCallHandle handle_default = SyncToolCallHandle(tool_call_id="tc1", name="foo") assert handle_default._deltas.maxsize > 0, ( @@ -552,7 +549,6 @@ def test_sync_tool_call_handle_deltas_single_consumer_guard(): The property must raise before returning the iterator so the caller sees the error even without iterating. """ - from langgraph_sdk._sync.stream import SyncToolCallHandle handle = SyncToolCallHandle(tool_call_id="tc1", name="foo") diff --git a/libs/sdk-py/tests/streaming/test_sync_scoped_handles.py b/libs/sdk-py/tests/streaming/test_sync_scoped_handles.py index 198ee2e8b..8c3fc04e6 100644 --- a/libs/sdk-py/tests/streaming/test_sync_scoped_handles.py +++ b/libs/sdk-py/tests/streaming/test_sync_scoped_handles.py @@ -6,12 +6,14 @@ import threading from concurrent.futures import ThreadPoolExecutor, wait import httpx +from langchain_protocol import Event from langgraph_sdk._sync.http import SyncHttpClient from langgraph_sdk._sync.stream import SyncScopedStreamHandle from langgraph_sdk._sync.threads import SyncThreadsClient from streaming._events import ( lifecycle_completed_event, + lifecycle_errored_event, lifecycle_started_event, message_finish_event, message_start_event, @@ -356,7 +358,6 @@ def test_sync_register_descendant_forwards_buffered_events_in_order(): """_register_descendant must drain already-buffered events whose namespace matches the new grandchild, push them into the grandchild, and preserve the original arrival order in the parent inbox.""" - from langchain_protocol import Event parent = SyncScopedStreamHandle( thread=None, # ty: ignore[invalid-argument-type] @@ -673,7 +674,6 @@ def test_sync_force_complete_uses_failed_when_run_errored(): """If the lifecycle signals an errored run, scoped children that are still 'started' when the subgraphs iterator's finally block runs must be force-finished as 'failed', not 'completed'.""" - from streaming._events import lifecycle_errored_event fake = SyncFakeServer() fake.script( diff --git a/libs/sdk-py/tests/streaming/test_sync_thread_stream.py b/libs/sdk-py/tests/streaming/test_sync_thread_stream.py index 72221d8d9..59a02da5d 100644 --- a/libs/sdk-py/tests/streaming/test_sync_thread_stream.py +++ b/libs/sdk-py/tests/streaming/test_sync_thread_stream.py @@ -2,21 +2,43 @@ from __future__ import annotations +import queue import re import threading import time import uuid from collections.abc import Iterator +from concurrent.futures import ThreadPoolExecutor +from typing import Any import httpx +import orjson import pytest +import langgraph_sdk.stream.sync_controller as _ctrl_mod from langgraph_sdk._sync.http import SyncHttpClient from langgraph_sdk._sync.threads import SyncThreadsClient +from langgraph_sdk.stream.sync_controller import SyncStreamController from langgraph_sdk.stream.transport.sync_http import ( SyncEventStreamHandle, SyncProtocolSseTransport, ) +from streaming._events import ( + checkpoints_event, + custom_event, + lifecycle_completed_event, + lifecycle_event, + lifecycle_started_event, + message_finish_event, + message_start_event, + message_text_delta_event, + message_text_finish_event, + tasks_start_event, + tool_finished_event, + tool_started_event, + updates_event, + values_event, +) from streaming._sync_fake_server import SyncFakeServer, SyncStreamScript # --------------------------------------------------------------------------- @@ -71,14 +93,10 @@ def test_sync_subscribe_before_run_start_waits_on_gate(): def test_sync_reconnect_uses_backoff_between_attempts(monkeypatch): """_reconnect_shared_stream sleeps between retry attempts with exp+jitter backoff, mirroring the async reconnect behavior.""" - import langgraph_sdk.stream.sync_controller as _ctrl_mod sleeps: list[float] = [] monkeypatch.setattr(_ctrl_mod.time, "sleep", lambda d: sleeps.append(d)) - from langgraph_sdk.stream.sync_controller import SyncStreamController - from langgraph_sdk.stream.transport.sync_http import SyncProtocolSseTransport - class _FailingTransport(SyncProtocolSseTransport): """Transport that always raises on open_event_stream.""" @@ -113,15 +131,6 @@ def test_sync_rotation_does_not_lose_buffered_events(): """When the shared stream rotates, old-stream events already in the queue are not dropped. _drain_and_close dispatches remaining events from the old handle to subscribers before closing it.""" - import queue - from typing import Any - - from langgraph_sdk.stream.sync_controller import SyncStreamController - from langgraph_sdk.stream.transport.sync_http import ( - SyncEventStreamHandle, - SyncProtocolSseTransport, - ) - from streaming._events import values_event event_a = values_event(seq=1, counter=1) @@ -188,8 +197,6 @@ def test_sync_rotation_does_not_lose_buffered_events(): def test_sync_concurrent_commands_do_not_share_command_id(): """50 concurrent threads calling _send_command must each get a unique id.""" - from concurrent.futures import ThreadPoolExecutor - from typing import Any captured_ids: list[int] = [] ids_lock = threading.Lock() @@ -246,7 +253,6 @@ def test_sync_events_returns_fresh_iterator_each_access(): """Two accesses of `thread.events` yield independent subscriptions, mirroring the async semantics where each access opens a new subscriber.""" fake = SyncFakeServer() - from streaming._events import values_event event_1 = values_event(seq=1, counter=1) fake.script_sequence( @@ -280,7 +286,6 @@ def test_close_unblocks_active_subscription_before_lifecycle_join(): """close() must send None to active subscriptions BEFORE joining the lifecycle watcher thread, so callers wake quickly even if the watcher thread blocks for up to 1s.""" - import queue # Gate that keeps the lifecycle watcher thread alive for 0.4s. lifecycle_block = threading.Event() @@ -293,8 +298,6 @@ def test_close_unblocks_active_subscription_before_lifecycle_join(): def _handle(self, request: httpx.Request) -> httpx.Response: path = request.url.path if path.endswith("/stream/events"): - import orjson - body = orjson.loads(request.content) channels = body.get("channels", []) if "lifecycle" in channels: @@ -417,7 +420,6 @@ def test_sync_threads_stream_mints_uuid4_when_thread_id_none(): def test_sync_run_start_sends_command(): - from streaming._events import lifecycle_completed_event fake = SyncFakeServer() fake.script([lifecycle_completed_event(seq=1)]) @@ -432,7 +434,6 @@ def test_sync_run_start_sends_command(): def test_sync_events_iterates_raw_events(): - from streaming._events import values_event fake = SyncFakeServer() fake.script([values_event(seq=1, counter=1)]) @@ -446,7 +447,6 @@ def test_sync_events_iterates_raw_events(): def test_sync_lifecycle_watcher_reconnects_with_since_after_transport_drop(): - from streaming._events import lifecycle_completed_event, lifecycle_event fake = SyncFakeServer() fake.set_state({"ok": True}) @@ -481,7 +481,6 @@ def test_sync_threads_stream_accepts_websocket_transport_option(): def test_sync_threads_stream_rejects_unknown_transport_option(): - import pytest with httpx.Client(base_url="http://test") as raw: threads = SyncThreadsClient(SyncHttpClient(raw)) @@ -494,17 +493,6 @@ def test_sync_threads_stream_rejects_unknown_transport_option(): def test_v3_streaming_sync_surface_smoke(): - from streaming._events import ( - custom_event, - lifecycle_completed_event, - message_finish_event, - message_start_event, - message_text_delta_event, - message_text_finish_event, - tool_finished_event, - tool_started_event, - values_event, - ) fake = SyncFakeServer() fake.set_state({"final": True}) @@ -610,11 +598,6 @@ def test_v3_streaming_sync_surface_smoke(): def test_interleave_projections_single_channel_values(): - from streaming._events import ( - lifecycle_completed_event, - lifecycle_started_event, - values_event, - ) fake = SyncFakeServer() fake.set_state({"counter": 0}) @@ -639,13 +622,6 @@ def test_interleave_projections_single_channel_values(): def test_interleave_projections_values_and_messages_arrival_order(): - from streaming._events import ( - lifecycle_completed_event, - lifecycle_started_event, - message_finish_event, - message_start_event, - values_event, - ) fake = SyncFakeServer() fake.set_state({"counter": 0}) @@ -672,12 +648,6 @@ def test_interleave_projections_values_and_messages_arrival_order(): def test_interleave_projections_mixes_builtin_and_extension(): - from streaming._events import ( - custom_event, - lifecycle_completed_event, - lifecycle_started_event, - values_event, - ) fake = SyncFakeServer() fake.set_state({"counter": 0}) @@ -701,12 +671,6 @@ def test_interleave_projections_mixes_builtin_and_extension(): def test_interleave_projections_tool_calls_uses_public_name(): - from streaming._events import ( - lifecycle_completed_event, - lifecycle_started_event, - tool_finished_event, - tool_started_event, - ) fake = SyncFakeServer() fake.set_state({}) @@ -735,10 +699,6 @@ def test_interleave_projections_tool_calls_uses_public_name(): def test_interleave_projections_subgraphs_discovers_child(): - from streaming._events import ( - lifecycle_completed_event, - lifecycle_started_event, - ) fake = SyncFakeServer() fake.set_state({}) @@ -761,11 +721,6 @@ def test_interleave_projections_subgraphs_discovers_child(): def test_interleave_projections_inflight_tool_call_failed_on_break(): """A tool handle held past an early break is failed in teardown, never left hanging.""" - from streaming._events import ( - lifecycle_completed_event, - lifecycle_started_event, - tool_started_event, - ) fake = SyncFakeServer() fake.set_state({}) @@ -794,10 +749,6 @@ def test_interleave_projections_inflight_tool_call_failed_on_break(): def test_interleave_projections_inflight_subgraph_finished_on_terminal(): """A discovered subgraph child with no terminal tasks-result is force-completed.""" - from streaming._events import ( - lifecycle_completed_event, - lifecycle_started_event, - ) fake = SyncFakeServer() fake.set_state({}) @@ -829,10 +780,6 @@ def test_interleave_projections_rejects_reserved_channel(channel): would subscribe to a channel that never matches and yield nothing. Fail closed. (`updates`/`checkpoints`/`tasks` are supported and tested below.) """ - from streaming._events import ( - lifecycle_completed_event, - lifecycle_started_event, - ) fake = SyncFakeServer() fake.set_state({}) @@ -849,13 +796,6 @@ def test_interleave_projections_rejects_reserved_channel(channel): def test_interleave_projections_data_channels_yield_payloads(): """`updates`/`checkpoints`/`tasks` yield their raw `params.data` payloads.""" - from streaming._events import ( - checkpoints_event, - lifecycle_completed_event, - lifecycle_started_event, - tasks_start_event, - updates_event, - ) fake = SyncFakeServer() fake.set_state({}) @@ -882,11 +822,6 @@ def test_interleave_projections_data_channels_yield_payloads(): def test_interleave_projections_data_channel_scoped_to_root_namespace(): """A child-namespace checkpoint must not leak into a root interleave.""" - from streaming._events import ( - checkpoints_event, - lifecycle_completed_event, - lifecycle_started_event, - ) fake = SyncFakeServer() fake.set_state({"counter": 0}) diff --git a/libs/sdk-py/tests/streaming/test_sync_transport_ws.py b/libs/sdk-py/tests/streaming/test_sync_transport_ws.py index 72584b325..bf06abbfb 100644 --- a/libs/sdk-py/tests/streaming/test_sync_transport_ws.py +++ b/libs/sdk-py/tests/streaming/test_sync_transport_ws.py @@ -6,8 +6,10 @@ import httpx import orjson import pytest +from langgraph_sdk.stream.sync_controller import SyncStreamController from langgraph_sdk.stream.transport.sync_ws import SyncProtocolWebSocketTransport from streaming._events import values_event +from streaming._sync_fake_server import SyncFakeServer class _FakeSyncWebSocket: @@ -102,7 +104,6 @@ def test_sync_websocket_records_post_ready_error(): def test_sync_websocket_send_command_uses_http_commands_endpoint(): - from streaming._sync_fake_server import SyncFakeServer fake = SyncFakeServer() with httpx.Client(transport=fake.transport, base_url="http://test") as client: @@ -124,7 +125,6 @@ def test_sync_websocket_open_event_stream_raises_when_closed(): def test_sync_websocket_transport_feeds_sync_stream_controller(): - from langgraph_sdk.stream.sync_controller import SyncStreamController socket = _FakeSyncWebSocket( [ @@ -164,7 +164,6 @@ def test_sync_websocket_transport_feeds_sync_stream_controller(): def test_sync_websocket_controller_reconnects_with_since_after_drop(): - from langgraph_sdk.stream.sync_controller import SyncStreamController first_socket = _FakeSyncWebSocket( [values_event(seq=1, values={"counter": 1})], diff --git a/libs/sdk-py/tests/streaming/test_thread_stream.py b/libs/sdk-py/tests/streaming/test_thread_stream.py index eb71b3dbc..4617f4690 100644 --- a/libs/sdk-py/tests/streaming/test_thread_stream.py +++ b/libs/sdk-py/tests/streaming/test_thread_stream.py @@ -4,10 +4,14 @@ import asyncio import contextlib import re import uuid -from typing import Any +from typing import Any, cast import httpx import pytest +from langchain_protocol import Event +from starlette.applications import Starlette +from starlette.responses import JSONResponse +from starlette.routing import Route from langgraph_sdk._async.http import HttpClient from langgraph_sdk._async.stream import AsyncThreadStream @@ -24,6 +28,8 @@ from streaming._events import ( lifecycle_started_event, message_finish_event, message_start_event, + message_text_delta_event, + message_text_finish_event, tasks_start_event, tool_finished_event, tool_started_event, @@ -225,9 +231,6 @@ async def test_aenter_constructs_transport_with_thread_id(): fake = FakeServer() transport = httpx.ASGITransport(app=fake.app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as raw: - from langgraph_sdk._async.http import HttpClient - from langgraph_sdk._async.threads import ThreadsClient - threads = ThreadsClient(HttpClient(raw)) stream = threads.stream(thread_id="t-1", assistant_id="agent") async with stream: @@ -237,9 +240,6 @@ async def test_aenter_constructs_transport_with_thread_id(): async def test_aenter_selects_websocket_transport(): async with httpx.AsyncClient(base_url="http://test") as raw: - from langgraph_sdk._async.http import HttpClient - from langgraph_sdk._async.threads import ThreadsClient - threads = ThreadsClient(HttpClient(raw)) stream = threads.stream( thread_id="t-1", assistant_id="agent", transport="websocket" @@ -252,9 +252,6 @@ async def test_aexit_closes_transport(): fake = FakeServer() transport = httpx.ASGITransport(app=fake.app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as raw: - from langgraph_sdk._async.http import HttpClient - from langgraph_sdk._async.threads import ThreadsClient - threads = ThreadsClient(HttpClient(raw)) stream = threads.stream(thread_id="t-1", assistant_id="agent") async with stream: @@ -268,9 +265,6 @@ async def test_run_start_sends_command_with_assistant_id(): fake = FakeServer() transport = httpx.ASGITransport(app=fake.app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as raw: - from langgraph_sdk._async.http import HttpClient - from langgraph_sdk._async.threads import ThreadsClient - threads = ThreadsClient(HttpClient(raw)) async with threads.stream(thread_id="t-1", assistant_id="agent") as thread: result = await thread.run.start(input={"x": 1}) @@ -286,9 +280,6 @@ async def test_command_ids_are_monotonic(): fake = FakeServer() transport = httpx.ASGITransport(app=fake.app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as raw: - from langgraph_sdk._async.http import HttpClient - from langgraph_sdk._async.threads import ThreadsClient - threads = ThreadsClient(HttpClient(raw)) async with threads.stream(thread_id="t-1", assistant_id="agent") as thread: await thread.run.start(input={"x": 1}) @@ -300,9 +291,6 @@ async def test_run_start_forwards_config_and_metadata(): fake = FakeServer() transport = httpx.ASGITransport(app=fake.app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as raw: - from langgraph_sdk._async.http import HttpClient - from langgraph_sdk._async.threads import ThreadsClient - threads = ThreadsClient(HttpClient(raw)) async with threads.stream(thread_id="t-1", assistant_id="agent") as thread: await thread.run.start( @@ -316,7 +304,6 @@ async def test_run_start_forwards_config_and_metadata(): async def test_run_start_raises_outside_context_manager(): - import pytest async with httpx.AsyncClient(base_url="http://test") as raw: stream = AsyncThreadStream( @@ -327,9 +314,6 @@ async def test_run_start_raises_outside_context_manager(): async def test_run_start_raises_on_error_envelope(): - from starlette.applications import Starlette - from starlette.responses import JSONResponse - from starlette.routing import Route async def commands(_request): return JSONResponse( @@ -346,11 +330,6 @@ async def test_run_start_raises_on_error_envelope(): ) transport = httpx.ASGITransport(app=app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as raw: - import pytest - - from langgraph_sdk._async.http import HttpClient - from langgraph_sdk._async.threads import ThreadsClient - threads = ThreadsClient(HttpClient(raw)) async with threads.stream(thread_id="t-1", assistant_id="agent") as thread: with pytest.raises(RuntimeError, match="invalid_argument"): @@ -367,9 +346,6 @@ async def test_events_yields_raw_events_after_run_start(): ) transport = httpx.ASGITransport(app=fake.app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as raw: - from langgraph_sdk._async.http import HttpClient - from langgraph_sdk._async.threads import ThreadsClient - threads = ThreadsClient(HttpClient(raw)) async with threads.stream(thread_id="t-1", assistant_id="agent") as thread: await thread.run.start(input={}) @@ -383,9 +359,6 @@ async def test_events_subscribes_to_all_channels(): fake.script([]) transport = httpx.ASGITransport(app=fake.app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as raw: - from langgraph_sdk._async.http import HttpClient - from langgraph_sdk._async.threads import ThreadsClient - threads = ThreadsClient(HttpClient(raw)) async with threads.stream(thread_id="t-1", assistant_id="agent") as thread: await thread.run.start(input={}) @@ -405,17 +378,11 @@ async def test_events_subscribes_to_all_channels(): async def test_events_terminates_on_aexit(): - import asyncio - - import pytest fake = FakeServer() fake.script([lifecycle_event(seq=i) for i in range(5)]) transport = httpx.ASGITransport(app=fake.app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as raw: - from langgraph_sdk._async.http import HttpClient - from langgraph_sdk._async.threads import ThreadsClient - threads = ThreadsClient(HttpClient(raw)) stream = threads.stream(thread_id="t-1", assistant_id="agent") async with stream as thread: @@ -462,9 +429,6 @@ async def test_events_property_returns_fresh_iterator_each_access(): fake.script([]) transport = httpx.ASGITransport(app=fake.app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as raw: - from langgraph_sdk._async.http import HttpClient - from langgraph_sdk._async.threads import ThreadsClient - threads = ThreadsClient(HttpClient(raw)) async with threads.stream(thread_id="t-1", assistant_id="agent") as thread: first_iter = thread.events @@ -549,7 +513,6 @@ async def test_unregister_subscription_removes_from_registry(): async def test_await_run_start_gate_honors_timeout(): """Gate must raise asyncio.TimeoutError if run.start never completes within the configured timeout.""" - import asyncio async with httpx.AsyncClient(base_url="http://test") as raw: threads = ThreadsClient(HttpClient(raw)) @@ -568,7 +531,6 @@ async def test_await_run_start_gate_honors_timeout(): async def test_await_run_start_gate_returns_when_gate_resolves_in_time(): """With a generous timeout and a gate that resolves promptly, the gate returns without raising.""" - import asyncio async with httpx.AsyncClient(base_url="http://test") as raw: threads = ThreadsClient(HttpClient(raw)) @@ -583,7 +545,6 @@ async def test_await_run_start_gate_returns_when_gate_resolves_in_time(): async def test_run_start_timeout_constructor_kwarg_forwarded_to_gate(): """`run_start_timeout` constructor kwarg is stored and consulted by `_reconcile_stream` via `_await_run_start_gate`.""" - import asyncio async with httpx.AsyncClient(base_url="http://test") as raw: stream = AsyncThreadStream( @@ -608,7 +569,6 @@ async def test_subscribe_waits_for_run_start_to_commit(): their SSE. Without it, a fast subscribe would 404 against a thread the server hasn't created yet. """ - import asyncio fake = FakeServer() fake.script([]) @@ -699,7 +659,6 @@ async def test_run_respond_snapshots_interrupts_under_lock(): `respond()` blocks until the lock is released — proving it serializes with the terminal-clear path that takes the same lock. """ - import asyncio fake = FakeServer() asgi = httpx.ASGITransport(app=fake.app) @@ -737,7 +696,6 @@ async def test_terminal_lifecycle_clear_acquires_interrupts_lock(): """Terminal lifecycle event clears `interrupts` under the same lock that `respond()` uses, preventing TOCTOU between snapshot and dispatch.""" - import asyncio fake = FakeServer() # No scripted events; we exercise `_apply_lifecycle_event` directly. @@ -754,10 +712,6 @@ async def test_terminal_lifecycle_clear_acquires_interrupts_lock(): # clearing interrupts. await thread._interrupts_lock.acquire() try: - from typing import cast - - from langchain_protocol import Event - terminal_event = cast( Event, { @@ -909,19 +863,6 @@ async def test_threads_stream_rejects_unknown_transport_option(): async def test_v3_streaming_async_surface_smoke(): - import asyncio - - from streaming._events import ( - custom_event, - lifecycle_completed_event, - message_finish_event, - message_start_event, - message_text_delta_event, - message_text_finish_event, - tool_finished_event, - tool_started_event, - values_event, - ) fake = FakeServer() fake.set_state({"final": True}) diff --git a/libs/sdk-py/tests/streaming/test_tool_calls_projection.py b/libs/sdk-py/tests/streaming/test_tool_calls_projection.py index f01e03d81..226eacc39 100644 --- a/libs/sdk-py/tests/streaming/test_tool_calls_projection.py +++ b/libs/sdk-py/tests/streaming/test_tool_calls_projection.py @@ -2,12 +2,15 @@ from __future__ import annotations +import asyncio import time +from collections.abc import AsyncGenerator import httpx import pytest from langgraph_sdk._async.http import HttpClient +from langgraph_sdk._async.stream import ToolCallHandle from langgraph_sdk._async.threads import ThreadsClient from streaming._events import ( lifecycle_completed_event, @@ -214,7 +217,6 @@ async def test_tool_calls_explicit_aclose_does_not_block_1s(): await thread.run.start(input={}) # _tool_calls_iter() is an AsyncGenerator; cast so the type checker # knows aclose() is available without a bare AsyncIterator protocol. - from collections.abc import AsyncGenerator gen: AsyncGenerator = thread.tool_calls._tool_calls_iter() _call = await gen.__anext__() # receive the one tool-started handle @@ -230,11 +232,9 @@ def test_tool_call_handle_deltas_queue_is_bounded(): Unbounded queues allow producers to enqueue indefinitely, causing memory growth when consumers are slow. """ - import asyncio # We need a running loop to create the Future inside ToolCallHandle.__init__. async def _make() -> None: - from langgraph_sdk._async.stream import ToolCallHandle handle_default = ToolCallHandle(tool_call_id="tc1", name="foo") assert handle_default._deltas.maxsize > 0, ( @@ -255,10 +255,8 @@ def test_tool_call_handle_deltas_single_consumer_guard(): The property must raise before returning the iterator so the caller sees the error even without iterating. """ - import asyncio async def _run() -> None: - from langgraph_sdk._async.stream import ToolCallHandle handle = ToolCallHandle(tool_call_id="tc1", name="foo") diff --git a/libs/sdk-py/tests/streaming/test_transport_http.py b/libs/sdk-py/tests/streaming/test_transport_http.py index 597136bc8..e97d66d67 100644 --- a/libs/sdk-py/tests/streaming/test_transport_http.py +++ b/libs/sdk-py/tests/streaming/test_transport_http.py @@ -6,8 +6,17 @@ import contextlib import httpx import orjson import pytest +from starlette.applications import Starlette +from starlette.responses import JSONResponse, Response +from starlette.routing import Route -from langgraph_sdk.stream.transport.http import EventStreamHandle, ProtocolSseTransport +from langgraph_sdk.stream.transport.http import ( + EventStreamHandle, + ProtocolSseTransport, + _build_event_stream_body, +) +from streaming._events import lifecycle_event, values_event +from streaming._fake_server import FakeServer async def test_event_stream_handle_constructs_with_open_state(): @@ -35,7 +44,6 @@ async def test_event_stream_handle_constructs_with_open_state(): async def test_send_command_posts_json_and_returns_response(): - from streaming._fake_server import FakeServer fake = FakeServer() transport = httpx.ASGITransport(app=fake.app) @@ -53,9 +61,6 @@ async def test_send_command_posts_json_and_returns_response(): async def test_send_command_returns_none_on_202(): - from starlette.applications import Starlette - from starlette.responses import Response - from starlette.routing import Route received: list[dict] = [] @@ -75,7 +80,6 @@ async def test_send_command_returns_none_on_202(): async def test_send_command_raises_when_closed(): - from streaming._fake_server import FakeServer fake = FakeServer() transport = httpx.ASGITransport(app=fake.app) @@ -87,9 +91,6 @@ async def test_send_command_raises_when_closed(): async def test_send_command_raises_http_error_on_4xx(): - from starlette.applications import Starlette - from starlette.responses import JSONResponse - from starlette.routing import Route async def commands(_request): return JSONResponse({"error": "bad request"}, status_code=400) @@ -105,8 +106,6 @@ async def test_send_command_raises_http_error_on_4xx(): async def test_open_event_stream_yields_scripted_events(): - from streaming._events import lifecycle_event, values_event - from streaming._fake_server import FakeServer fake = FakeServer() fake.script( @@ -129,7 +128,6 @@ async def test_open_event_stream_yields_scripted_events(): async def test_open_event_stream_passes_since_in_body(): - from streaming._fake_server import FakeServer fake = FakeServer() fake.script([]) @@ -145,8 +143,6 @@ async def test_open_event_stream_passes_since_in_body(): async def test_open_event_stream_close_cancels_in_flight_iteration(): - from streaming._events import lifecycle_event - from streaming._fake_server import FakeServer fake = FakeServer() fake.script( @@ -219,9 +215,6 @@ async def test_mid_stream_error_after_ready_surfaces_on_done(): """If the SSE response body iteration raises after headers/ready, the error must be exposed on handle.done so callers can distinguish a clean end from a transport failure.""" - import httpx - - from langgraph_sdk.stream.transport.http import ProtocolSseTransport def handler(_request: httpx.Request) -> httpx.Response: async def body(): @@ -250,9 +243,6 @@ async def test_mid_stream_error_after_ready_surfaces_on_done(): @pytest.mark.anyio async def test_clean_stream_end_done_resolves_with_none(): """A stream that ends without error must resolve `done` with None.""" - import httpx - - from langgraph_sdk.stream.transport.http import ProtocolSseTransport def handler(_request: httpx.Request) -> httpx.Response: async def body(): @@ -280,9 +270,6 @@ async def test_clean_stream_end_done_resolves_with_none(): async def test_send_command_empty_200_body_raises_runtime_error_not_decoder_error(): """A 200 response with empty body must raise RuntimeError matching the 'did not return a valid response' contract, not orjson.JSONDecodeError.""" - import httpx - - from langgraph_sdk.stream.transport.http import ProtocolSseTransport def handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(200, content=b"") @@ -305,9 +292,6 @@ async def test_send_command_empty_200_body_raises_runtime_error_not_decoder_erro async def test_cancel_event_prevents_post_cancel_flush(): """When the consumer cancels the handle mid-stream, the pump's decoder flush MUST NOT emit additional events after the cancel point.""" - import httpx - - from langgraph_sdk.stream.transport.http import ProtocolSseTransport received: list = [] @@ -342,9 +326,6 @@ async def test_cancel_event_prevents_post_cancel_flush(): @pytest.mark.anyio async def test_open_event_stream_ready_rejects_on_5xx(): - from starlette.applications import Starlette - from starlette.responses import JSONResponse - from starlette.routing import Route async def stream_events(_request): return JSONResponse({"error": "boom"}, status_code=500) @@ -371,14 +352,12 @@ async def test_open_event_stream_ready_rejects_on_5xx(): def test_build_event_stream_body_minimal_channels_only(): - from langgraph_sdk.stream.transport.http import _build_event_stream_body body = _build_event_stream_body({"channels": ["values"]}) assert body == {"channels": ["values"]} def test_build_event_stream_body_includes_all_optional_fields(): - from langgraph_sdk.stream.transport.http import _build_event_stream_body body = _build_event_stream_body( { @@ -397,14 +376,12 @@ def test_build_event_stream_body_includes_all_optional_fields(): def test_build_event_stream_body_omits_since_when_not_int(): - from langgraph_sdk.stream.transport.http import _build_event_stream_body body = _build_event_stream_body({"channels": ["values"], "since": None}) assert "since" not in body async def test_open_event_stream_raises_when_closed(): - from streaming._fake_server import FakeServer fake = FakeServer() transport = httpx.ASGITransport(app=fake.app) @@ -416,8 +393,6 @@ async def test_open_event_stream_raises_when_closed(): async def test_transport_close_cancels_open_event_streams(): - from streaming._events import lifecycle_event - from streaming._fake_server import FakeServer fake = FakeServer() fake.script([lifecycle_event(seq=i) for i in range(5)], delay=0.05) @@ -439,7 +414,6 @@ async def test_transport_close_cancels_open_event_streams(): async def test_default_headers_forwarded_to_send_command(): """Headers passed at construction are sent on every command request.""" - from streaming._fake_server import FakeServer fake = FakeServer() transport = httpx.ASGITransport(app=fake.app) @@ -457,7 +431,6 @@ async def test_default_headers_forwarded_to_send_command(): async def test_default_headers_forwarded_to_open_event_stream(): """Headers passed at construction are sent on every SSE stream request.""" - from streaming._fake_server import FakeServer fake = FakeServer() fake.script([]) @@ -479,7 +452,6 @@ async def test_default_headers_forwarded_to_open_event_stream(): async def test_default_headers_cannot_override_sse_fixed_headers(): """Caller-supplied default headers must not override content-type or accept.""" - from streaming._fake_server import FakeServer fake = FakeServer() fake.script([]) @@ -506,7 +478,6 @@ async def test_default_headers_cannot_override_sse_fixed_headers(): async def test_fake_server_state_endpoint(): """State endpoint returns the set state and increments the counter.""" - from streaming._fake_server import FakeServer fake = FakeServer() fake.set_state({"foo": "bar"}, next=["node_a"]) @@ -527,7 +498,6 @@ async def test_fake_server_state_endpoint(): def test_values_event_builder_shape(): """values_event produces the expected shape with params.data as the snapshot.""" - from streaming._events import values_event evt = values_event(seq=1, values={"foo": 1}) assert evt["event_id"] == "evt-1" @@ -537,13 +507,11 @@ def test_values_event_builder_shape(): async def test_open_event_stream_done_records_post_ready_error(): - from streaming._events import values_event event_data = values_event(seq=1) class _FailAfterOneStream(httpx.AsyncByteStream): async def __aiter__(self): - import orjson payload = orjson.dumps(event_data).decode() yield f"id: {event_data.get('event_id', '')}\n".encode() diff --git a/libs/sdk-py/tests/streaming/test_transport_ws.py b/libs/sdk-py/tests/streaming/test_transport_ws.py index 8844b75e0..391284d20 100644 --- a/libs/sdk-py/tests/streaming/test_transport_ws.py +++ b/libs/sdk-py/tests/streaming/test_transport_ws.py @@ -10,8 +10,10 @@ import pytest from websockets.exceptions import ConnectionClosedError, ConnectionClosedOK from websockets.frames import Close +from langgraph_sdk.stream.controller import StreamController from langgraph_sdk.stream.transport.ws import ProtocolWebSocketTransport from streaming._events import values_event +from streaming._fake_server import FakeServer class _FakeAsyncWebSocket: @@ -334,7 +336,6 @@ async def test_websocket_done_records_post_ready_error(): async def test_websocket_send_command_uses_http_commands_endpoint(): - from streaming._fake_server import FakeServer fake = FakeServer() transport = httpx.ASGITransport(app=fake.app) @@ -357,7 +358,6 @@ async def test_websocket_open_event_stream_raises_when_closed(): async def test_websocket_transport_feeds_async_stream_controller(): - from langgraph_sdk.stream.controller import StreamController socket = _FakeAsyncWebSocket( [ @@ -418,7 +418,6 @@ async def test_ws_transport_default_max_queue_size_is_1024(): async def test_websocket_controller_reconnects_with_since_after_drop(): - from langgraph_sdk.stream.controller import StreamController first_socket = _FakeAsyncWebSocket( [values_event(seq=1, values={"counter": 1})], @@ -465,7 +464,6 @@ async def test_websocket_controller_reconnects_with_since_after_drop(): async def test_async_close_sends_normal_close_frame(): """`handle.close()` sends a WebSocket close frame with code 1000 explicitly.""" - import asyncio # Use an event to distinguish an explicit close(code=1000) call from # the implicit one in __aexit__ when the task is cancelled. diff --git a/libs/sdk-py/tests/test_client_stream.py b/libs/sdk-py/tests/test_client_stream.py index d80d299b5..d2b4c18d5 100644 --- a/libs/sdk-py/tests/test_client_stream.py +++ b/libs/sdk-py/tests/test_client_stream.py @@ -8,7 +8,9 @@ import httpx import pytest from typing_extensions import assert_type +from langgraph_sdk._async.runs import _wrap_stream_v2 from langgraph_sdk._shared.utilities import _sse_to_v2_dict +from langgraph_sdk._sync.runs import _wrap_stream_v2_sync from langgraph_sdk.client import HttpClient, SyncHttpClient from langgraph_sdk.schema import ( CheckpointPayload, @@ -380,7 +382,6 @@ def test_sse_to_v2_dict_values_with_interrupts() -> None: @pytest.mark.asyncio async def test_async_stream_v2_client_side_conversion() -> None: - from langgraph_sdk._async.runs import _wrap_stream_v2 async def mock_stream() -> Any: yield StreamPart(event="metadata", data={"run_id": "r1"}) @@ -415,7 +416,6 @@ async def test_async_stream_v2_client_side_conversion() -> None: def test_sync_stream_v2_client_side_conversion() -> None: - from langgraph_sdk._sync.runs import _wrap_stream_v2_sync def mock_stream() -> Any: yield StreamPart(event="metadata", data={"run_id": "r1"}) diff --git a/libs/sdk-py/tests/test_encryption.py b/libs/sdk-py/tests/test_encryption.py index 90804c2a1..5fabbe952 100644 --- a/libs/sdk-py/tests/test_encryption.py +++ b/libs/sdk-py/tests/test_encryption.py @@ -1,8 +1,40 @@ +from collections.abc import Awaitable, Callable + import pytest +from langgraph_sdk import DecryptResult, EncryptionContext from langgraph_sdk.encryption import DuplicateHandlerError, Encryption +def test_decrypt_result(): + result = DecryptResult(plaintext=b"plain", replacement=b"rotated") + + assert result.plaintext == b"plain" + assert result.replacement == b"rotated" + assert DecryptResult(plaintext={"plain": True}).replacement is None + + +def test_decrypt_decorators_preserve_return_types(): + encryption = Encryption() + + @encryption.decrypt.blob + async def blob_dec(_ctx: EncryptionContext, data: bytes) -> bytes: + return data + + @encryption.decrypt.json + async def json_dec( + _ctx: EncryptionContext, data: dict[str, object] + ) -> dict[str, object]: + return data + + blob_handler: Callable[[EncryptionContext, bytes], Awaitable[bytes]] = blob_dec + json_handler: Callable[ + [EncryptionContext, dict[str, object]], Awaitable[dict[str, object]] + ] = json_dec + assert blob_handler is blob_dec + assert json_handler is json_dec + + class TestHandlerValidation: """Test duplicate handler and signature validation.""" diff --git a/libs/sdk-py/tests/test_langsmith_tracing.py b/libs/sdk-py/tests/test_langsmith_tracing.py index db0fc2205..0720039fc 100644 --- a/libs/sdk-py/tests/test_langsmith_tracing.py +++ b/libs/sdk-py/tests/test_langsmith_tracing.py @@ -7,6 +7,8 @@ from unittest.mock import AsyncMock, MagicMock import pytest +from langgraph_sdk._async.runs import RunsClient +from langgraph_sdk._sync.runs import SyncRunsClient from langgraph_sdk.schema import LangSmithTracing @@ -24,7 +26,6 @@ class TestLangSmithTracingPayload: @pytest.mark.asyncio async def test_async_create_includes_langsmith_tracer(self, tracing_config): """Test that async create sends langsmith_tracer in payload.""" - from langgraph_sdk._async.runs import RunsClient captured: dict[str, Any] = {} @@ -50,7 +51,6 @@ class TestLangSmithTracingPayload: def test_sync_create_includes_langsmith_tracer(self, tracing_config): """Test that sync create sends langsmith_tracer in payload.""" - from langgraph_sdk._sync.runs import SyncRunsClient captured: dict[str, Any] = {} @@ -76,7 +76,6 @@ class TestLangSmithTracingPayload: def test_sync_wait_includes_langsmith_tracer(self, tracing_config): """Test that sync wait sends langsmith_tracer in payload.""" - from langgraph_sdk._sync.runs import SyncRunsClient captured: dict[str, Any] = {} @@ -102,7 +101,6 @@ class TestLangSmithTracingPayload: def test_create_without_langsmith_tracing_excludes_key(self): """Test that langsmith_tracer is not in payload when not provided.""" - from langgraph_sdk._sync.runs import SyncRunsClient captured: dict[str, Any] = {} @@ -123,7 +121,6 @@ class TestLangSmithTracingPayload: def test_langsmith_tracing_project_name_only(self): """Test that langsmith_tracing works with only project_name.""" - from langgraph_sdk._sync.runs import SyncRunsClient captured: dict[str, Any] = {} diff --git a/libs/sdk-py/tests/test_path_encoding.py b/libs/sdk-py/tests/test_path_encoding.py index 4c5fc0d18..8bdf3860c 100644 --- a/libs/sdk-py/tests/test_path_encoding.py +++ b/libs/sdk-py/tests/test_path_encoding.py @@ -8,6 +8,8 @@ URL paths. from __future__ import annotations +import uuid + import httpx import pytest @@ -60,7 +62,6 @@ class TestQuotePathParam: assert "/" not in encoded def test_non_string_values_are_coerced_to_str(self) -> None: - import uuid uid = uuid.UUID("550e8400-e29b-41d4-a716-446655440000") assert _quote_path_param(uid) == str(uid) diff --git a/libs/sdk-py/tests/test_serde.py b/libs/sdk-py/tests/test_serde.py index 5678e5285..1b8d7a7ef 100644 --- a/libs/sdk-py/tests/test_serde.py +++ b/libs/sdk-py/tests/test_serde.py @@ -1,3 +1,4 @@ +from dataclasses import dataclass from typing import Any import orjson @@ -36,7 +37,6 @@ async def test_serde_pydantic(): async def test_serde_dataclass(): - from dataclasses import dataclass @dataclass class TestDataClass: diff --git a/libs/sdk-py/uv.lock b/libs/sdk-py/uv.lock index 215467427..1006e6c46 100644 --- a/libs/sdk-py/uv.lock +++ b/libs/sdk-py/uv.lock @@ -154,11 +154,11 @@ wheels = [ [[package]] name = "codespell" -version = "2.4.2" +version = "2.4.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2d/9d/1d0903dff693160f893ca6abcabad545088e7a2ee0a6deae7c24e958be69/codespell-2.4.2.tar.gz", hash = "sha256:3c33be9ae34543807f088aeb4832dfad8cb2dae38da61cac0a7045dd376cfdf3", size = 352058, upload-time = "2026-03-05T18:10:42.936Z" } +sdist = { url = "https://files.pythonhosted.org/packages/80/19/45e941380f69c042b43423513d201e6592346f992394347f5e7174c31407/codespell-2.4.3.tar.gz", hash = "sha256:cbe085e331227b37bb86ef8bddd08dc768c704ee9a07ca869852c093fa2793e2", size = 352773, upload-time = "2026-07-15T11:51:54.159Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/a1/52fa05533e95fe45bcc09bcf8a503874b1c08f221a4e35608017e0938f55/codespell-2.4.2-py3-none-any.whl", hash = "sha256:97e0c1060cf46bd1d5db89a936c98db8c2b804e1fdd4b5c645e82a1ec6b1f886", size = 353715, upload-time = "2026-03-05T18:10:41.398Z" }, + { url = "https://files.pythonhosted.org/packages/8b/bf/bdb951d34eb169140b546f44be9ec4525d1acefb9eb5572071f5492b19fc/codespell-2.4.3-py3-none-any.whl", hash = "sha256:af2505b335e8573dbd2d384d1c4ef498f4006f4ba2d6fceca01e55b91f52628a", size = 340736, upload-time = "2026-07-15T11:51:52.925Z" }, ] [[package]] @@ -266,7 +266,7 @@ wheels = [ [[package]] name = "langchain-core" -version = "1.4.8" +version = "1.5.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonpatch" }, @@ -279,9 +279,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "uuid-utils" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/12/e3/bea6d0080acf183332f24dcd74c208aee5857cf8f783c3fb0bd86027d8fb/langchain_core-1.4.8.tar.gz", hash = "sha256:5bf1f8411077c904182ad8f975943d36adcbf579c4e017b3a118b719229ebf9a", size = 957974, upload-time = "2026-06-18T19:39:23.636Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/3e/63af6b9d76d9be907c7c524d6ec18a2efed7e0e2d123fea0230d78dbd73f/langchain_core-1.5.3.tar.gz", hash = "sha256:a56457ac444fef41e9404443c187f0ecea708d36e816ea4ba9573c027f7d1a2d", size = 972461, upload-time = "2026-07-30T14:55:55.833Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/d6/bdf6f0481cc57ef300d6b1eb48cf1400c0409be715d6eb3cabadd1142a09/langchain_core-1.4.8-py3-none-any.whl", hash = "sha256:d84c28b05e3ba8d4271d0827aad5b592ccdaaf986e76768c23503f0a2045e8aa", size = 557416, upload-time = "2026-06-18T19:39:21.902Z" }, + { url = "https://files.pythonhosted.org/packages/36/e6/c7c39efe0bc7e1b7c3d8f54f85846e04c901913c3d3e99068b218558c6f1/langchain_core-1.5.3-py3-none-any.whl", hash = "sha256:48b56fa580277209594dd7baf837f5b9a2a3651613f34ff9fb1728b429df015f", size = 561687, upload-time = "2026-07-30T14:55:54.419Z" }, ] [[package]] @@ -298,7 +298,7 @@ wheels = [ [[package]] name = "langgraph" -version = "1.2.10" +version = "1.2.11" source = { editable = "../langgraph" } dependencies = [ { name = "langchain-core" }, @@ -382,7 +382,7 @@ test = [ [[package]] name = "langgraph-checkpoint" -version = "4.1.1" +version = "4.2.0" source = { editable = "../checkpoint" } dependencies = [ { name = "langchain-core" }, @@ -534,13 +534,13 @@ dev = [ { name = "pytest-asyncio" }, { name = "pytest-mock" }, { name = "pytest-watch" }, - { name = "ruff", specifier = "==0.15.20" }, + { name = "ruff", specifier = "==0.16.1" }, { name = "starlette" }, { name = "ty" }, ] lint = [ { name = "codespell" }, - { name = "ruff", specifier = "==0.15.20" }, + { name = "ruff", specifier = "==0.16.1" }, { name = "starlette" }, { name = "ty" }, ] @@ -1016,27 +1016,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.20" +version = "0.16.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/dc/35b341fc554ba02f217fc10da57d1a75168cfbcf75b0ef2202176d4c4f2d/ruff-0.15.20.tar.gz", hash = "sha256:1416eb04349192646b54de98f146c4f59afe37d0decfc02c3cbbf396f3a28566", size = 4755489, upload-time = "2026-06-25T17:20:37.578Z" } +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/94/d9/2d5014f0253ba541d2061d9fa7193f48e941c8b21bb88a7ff9bbe0bd0596/ruff-0.15.20-py3-none-linux_armv6l.whl", hash = "sha256:00e188c53e499c3c1637f73c91dcf2fb56d576cab76ce1be50a27c4e80e37078", size = 10839665, upload-time = "2026-06-25T17:19:44.702Z" }, - { url = "https://files.pythonhosted.org/packages/c6/d3/ac1798ba64f670698867fcfc591d50e7e421bef137db564858f619a30fcf/ruff-0.15.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9ebd1fd9b9c95fc0bd7b2761aebec1f030013d2e193a2901b224af68fe47251b", size = 11208649, upload-time = "2026-06-25T17:19:48.787Z" }, - { url = "https://files.pythonhosted.org/packages/47/47/d3ac899991202095dfcf3d5176be4272642be3cf981a2f1a30f72a2afb95/ruff-0.15.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5b16cdd67ca108185cd36dce98c576350c03b1660a751de725fb049193a0632", size = 10622638, upload-time = "2026-06-25T17:19:51.354Z" }, - { url = "https://files.pythonhosted.org/packages/33/13/4e043fe30aa94d4ff5213a9881fc296d12960f5971b234a5263fdc225312/ruff-0.15.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3413bb3c3d2ca6a8208f1f4809cd2dca3c6de6d0b491c0e70847672bde6e6efd", size = 10984227, upload-time = "2026-06-25T17:19:54.044Z" }, - { url = "https://files.pythonhosted.org/packages/76/e6/92e7bf40388bc5800073b96564f56264f7e48bfd1a498f5ced6ae6d5a769/ruff-0.15.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd7ec42b3bb3da066488db093308a69c4ac5ee6d2af333a86ba6e2eb2e7dd44b", size = 10622882, upload-time = "2026-06-25T17:19:57.037Z" }, - { url = "https://files.pythonhosted.org/packages/13/7a/43460be3f24495a3aa46d4b16873e2c4941b3b5f0b00cf88c03b7b94b339/ruff-0.15.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1a36ad0eb77fba9aabfb69ede54de6f376d04ac18ebea022847046d340a8267", size = 11474808, upload-time = "2026-06-25T17:20:00.357Z" }, - { url = "https://files.pythonhosted.org/packages/27/a0/f37077884873221c6b33b4ab49eb18f9f88e54a16a25a5bca59bef46dd66/ruff-0.15.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6df3b1e4610432f0386dba04d853b5f08cbbc903410c6fcc02f620f05aff53c", size = 12293094, upload-time = "2026-06-25T17:20:03.446Z" }, - { url = "https://files.pythonhosted.org/packages/a6/74/165545b60256a9704c21ac0ec4a0d07933b320812f9584836c9f4aca4292/ruff-0.15.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e89f198a1ea6ef0d727c1cf16088bc91a6cb0ab947dedc966715691647186eae", size = 11526176, upload-time = "2026-06-25T17:20:06.301Z" }, - { url = "https://files.pythonhosted.org/packages/86/b1/a976a136d40ade83ce743578399865f57001003a409acadc0ecbb3051082/ruff-0.15.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309809086c2acb67624950a3c8133e80f32d0d3e27106c0cd60ff26657c9f24b", size = 11520767, upload-time = "2026-06-25T17:20:09.191Z" }, - { url = "https://files.pythonhosted.org/packages/19/0f/f032696cb01c9b54c0263fa393474d7758f1cdc021a01b04e3cbc2500999/ruff-0.15.20-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2d2374caa2f2c2f9e2b7da0a50802cfb8b79f55a9b5e49379f564544fbf56487", size = 11500132, upload-time = "2026-06-25T17:20:13.602Z" }, - { url = "https://files.pythonhosted.org/packages/4b/f4/51b1a14bc69e8c224b15dab9cce8e99b425e0455d462caa2b3c9be2b6a8e/ruff-0.15.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a1ed17b65293e0c2f22fc387bc13198a5de94bf4429589b0ff6946b0feaf21a3", size = 10943828, upload-time = "2026-06-25T17:20:16.635Z" }, - { url = "https://files.pythonhosted.org/packages/71/4b/fe267640783cd02bf6c5cc290b1df1051be2ec294c678b5c15fe19e52343/ruff-0.15.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f701305e66b38ea6c91882490eb73459796808e4c6362a1b765255e0cdcd4053", size = 10645418, upload-time = "2026-06-25T17:20:19.4Z" }, - { url = "https://files.pythonhosted.org/packages/b0/c0/a65aa4ec2f5e87a1df32dc3ec1fede434fe3dfd5cbcf3b503cafc676ab54/ruff-0.15.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b9c0c367ad8e5d0d5b5b8537864c469a0a0e55417aadfbeca41fa61333be9f4", size = 11211770, upload-time = "2026-06-25T17:20:22.033Z" }, - { url = "https://files.pythonhosted.org/packages/5a/a4/0caa331d954ae2723d729d351c989cb4ca8b6077d5c6c2cb6de75e98c041/ruff-0.15.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:01cc00dd58f0df339d0e902219dd53990ea99996a0344e5d9cc8d45d5307e460", size = 11618698, upload-time = "2026-06-25T17:20:25.259Z" }, - { url = "https://files.pythonhosted.org/packages/10/9b/5f14927848d2fd4aa891fd88d883788c5a7baba561c7874732364045708c/ruff-0.15.20-py3-none-win32.whl", hash = "sha256:ed65ef510e43a137207e0f01cfcf998aeddb1aeeda5c9d35023e910284d7cf21", size = 10857322, upload-time = "2026-06-25T17:20:28.612Z" }, - { url = "https://files.pythonhosted.org/packages/fa/f0/fe47c501f9dea92a26d788ff98bb5d92ed4cb4c88792c5c88af6b697dc8e/ruff-0.15.20-py3-none-win_amd64.whl", hash = "sha256:a525c81c70fb0380344dd1d8745d8cc1c890b7fc94a58d5a07bd8eb9557b8415", size = 11993274, upload-time = "2026-06-25T17:20:31.871Z" }, - { url = "https://files.pythonhosted.org/packages/d7/2b/9555445e1201d92b3195f45cdb153a0b68f24e0a4273f6e3d5ab46e212bb/ruff-0.15.20-py3-none-win_arm64.whl", hash = "sha256:2f5b2a6d614e8700388806a14996c40fab2c47b819ef57d790a34878858ed9ca", size = 11343498, upload-time = "2026-06-25T17:20:35.03Z" }, + { 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" }, ] [[package]] @@ -1117,27 +1117,27 @@ wheels = [ [[package]] name = "ty" -version = "0.0.55" +version = "0.0.66" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/08/48/f687c8d268e3581f2f104d1f2ac5944d5b5e841b3695c613b3f263e5bbf7/ty-0.0.55.tar.gz", hash = "sha256:88ca87073825a79a8327c550efcc86cec94344890244c5946f84c9e44a969f31", size = 6040230, upload-time = "2026-06-27T00:27:29.385Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/58/4f6ab2a86589e422a3cf840bcf6114c565e4c39ddf4d0b7cd328af5b52b4/ty-0.0.66.tar.gz", hash = "sha256:24bddd4479ce445b51ac015410dd2d34af1cadd62a77f5b3cb269149ed83f9b5", size = 6520402, upload-time = "2026-08-04T01:09:47.714Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/a3/1a90ba7e5a61c6d09adb92346ddba97668095fc257b577af433e5ac4f404/ty-0.0.55-py3-none-linux_armv6l.whl", hash = "sha256:31e83eef512d066542fe990fe1a3b814423abd1616376c54e48af7045b3e1749", size = 11677249, upload-time = "2026-06-27T00:26:52.18Z" }, - { url = "https://files.pythonhosted.org/packages/82/3a/669f9aa478c38243e213a2684db1502086026cfadc15bb1b29b7cbde030d/ty-0.0.55-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ab4bca857950608fea73e269e2da369d43e6467131de85160d68e2fa466fa248", size = 11444180, upload-time = "2026-06-27T00:26:54.576Z" }, - { url = "https://files.pythonhosted.org/packages/15/a4/6a4b2507a53ce6530c66c5b4fe0d58551eb1748ffa9e0696c32fdd55bbd4/ty-0.0.55-py3-none-macosx_11_0_arm64.whl", hash = "sha256:55032bfd31bf2c5355ee81bdc6407b144a1cc7ee41e5681dd1368e4cef2ba327", size = 10963134, upload-time = "2026-06-27T00:26:57.348Z" }, - { url = "https://files.pythonhosted.org/packages/ce/ae/a3b1a0f1cc83b7d258662cb98aa80a720c2e671d0e8fa0d17a4d5d057a7a/ty-0.0.55-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1e049f69ce65b3c269af67624607f435e1c32319786c1e453ef9611502f295", size = 11493517, upload-time = "2026-06-27T00:26:59.26Z" }, - { url = "https://files.pythonhosted.org/packages/0d/9f/311ce39065a979ef40a9b847f685c8e02464e53adf1671e081eea90640ca/ty-0.0.55-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:631409975c681d5a280fc5a99b7b32e9e801f33be7567c6b42ec331362f59d7d", size = 11460590, upload-time = "2026-06-27T00:27:01.425Z" }, - { url = "https://files.pythonhosted.org/packages/cd/8f/3bf29aa77bd78aae48275153135a2052fa7d3ccdf1ecabeb99c8773abd66/ty-0.0.55-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e08cb0436e68b9351555ae8f2697138c9009b4d5b4ae4272232988b2a431a98f", size = 12098430, upload-time = "2026-06-27T00:27:03.596Z" }, - { url = "https://files.pythonhosted.org/packages/bc/6e/e88411a88240b94640bba06fb6d0d92b247fbeef47ee2bc71f39e58c2558/ty-0.0.55-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16c215ad9f823829409b94ee188cfaa4563f6e1384f6ce3fecb1db75f6c7cf7c", size = 12673086, upload-time = "2026-06-27T00:27:05.589Z" }, - { url = "https://files.pythonhosted.org/packages/6c/7e/8f1762fb7f9245a68ba5ae338d73c59403ce57554e5d311b8bb55027b0ec/ty-0.0.55-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b510eb8f4032baf11b7aee2f1d53babc3b4ca03939b9cdcf6a9d15761d575188", size = 12242559, upload-time = "2026-06-27T00:27:07.714Z" }, - { url = "https://files.pythonhosted.org/packages/72/1f/143657daf2670d977dac83435f1fe03d4843efb798d8e1e75950e541aadd/ty-0.0.55-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ddc05e7959709c3b9b83aa627128a80446865e3c1a4882638dcff6d776dc34a", size = 12021409, upload-time = "2026-06-27T00:27:09.881Z" }, - { url = "https://files.pythonhosted.org/packages/6d/30/69487c439dd1fad3a4a3d96f0a472193de297eaba6fc4b8ea687ce434ac2/ty-0.0.55-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:636e8e5078787b8c6916c94e1406719f10189a4ca6b37b813a5922ce5857a8c7", size = 12303807, upload-time = "2026-06-27T00:27:11.986Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ca/cd88b6493dafc7db077f5e17c0438eb3af6e2d6d08f616dbb52a8ddfd567/ty-0.0.55-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ef7d6deaacb73fec603666b5471f1dc5a5699aa84e11a6d4d644dd07ca72121e", size = 11441263, upload-time = "2026-06-27T00:27:14.087Z" }, - { url = "https://files.pythonhosted.org/packages/aa/fe/66b6915671653ab739f71e4f1b0528e69da64429b7ebf3840c625b6e43f2/ty-0.0.55-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9aeea0fe5875d3cf37faf0e44d0fdf9669335467749741b8fc0103916fb5cd32", size = 11484584, upload-time = "2026-06-27T00:27:16.311Z" }, - { url = "https://files.pythonhosted.org/packages/4a/4f/7a9c0bbac8b899e9f6c0ec110c6612f52e4db35f6bb17ddc0ef60384fa3e/ty-0.0.55-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0b699c01310dbd2705a07c97c5f4aaeedef61bd9adeea2e7c46aed32401d3576", size = 11759309, upload-time = "2026-06-27T00:27:18.471Z" }, - { url = "https://files.pythonhosted.org/packages/ca/de/b6f8b1b69aa631b5716ef3f985c3b56de0e46c2499cc00d30c402b41f714/ty-0.0.55-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:32cbeba543e46de2a983ec6d525d8b56514f7422bd1e1b57c44ccf7bfa72c38a", size = 12128755, upload-time = "2026-06-27T00:27:20.55Z" }, - { url = "https://files.pythonhosted.org/packages/7d/90/a912531e51ee7e076b42972479290fa687c0f5e747b7e773f3033164acaa/ty-0.0.55-py3-none-win32.whl", hash = "sha256:52b968e24eb4f7a5c3bd251db1f99f60dd385890356d38fc619d84f1b423446a", size = 11117501, upload-time = "2026-06-27T00:27:22.714Z" }, - { url = "https://files.pythonhosted.org/packages/4c/7a/99d59843bf8908a7f9f4d13fda107dbad07b7faa28ecd7860eacf363fb1c/ty-0.0.55-py3-none-win_amd64.whl", hash = "sha256:bf39cbfdc0add44d94bd3fff1f53c351418d134b6a66b87efdb7876d7b7a2224", size = 12150106, upload-time = "2026-06-27T00:27:24.881Z" }, - { url = "https://files.pythonhosted.org/packages/b3/44/20987505cedf2a865b08482f0eabc181fd9599b062964057ec8a128a4296/ty-0.0.55-py3-none-win_arm64.whl", hash = "sha256:f7f3700a9a060e8f1af11e4fb63fafcaf272b041781f4ccdfda2b3b5c6c1e439", size = 11560157, upload-time = "2026-06-27T00:27:27.332Z" }, + { url = "https://files.pythonhosted.org/packages/45/4d/bbc28310d6d887ef73e5800f062c4bf54caa35a1b47d70c7b03d0515ecf1/ty-0.0.66-py3-none-linux_armv6l.whl", hash = "sha256:8b46450438b54b732338e4d7a78a7d2f5e1a012a13d77d121aacaca20fb814e2", size = 12409743, upload-time = "2026-08-04T01:09:01.229Z" }, + { url = "https://files.pythonhosted.org/packages/73/4e/c3d2eb2242fa0a2ef445ca2c7009dc9118e5b3dcb8b8a8bec70d58c8e4bc/ty-0.0.66-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8e4adbe662bc3c62b52b83d46b07f703fdc3c123bb72601606c58be5ea017ed3", size = 12078362, upload-time = "2026-08-04T01:09:04.233Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/7a4b5e45d701a8de6b714dacf6ffca91b0411baaceaa781d494037623a18/ty-0.0.66-py3-none-macosx_11_0_arm64.whl", hash = "sha256:776814351735847eb934f9a3cbea21d2278ba14aa0fc099f683da11bc2d5c90a", size = 11583289, upload-time = "2026-08-04T01:09:06.973Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/fbb1f71ee2999f981f8c4b3b139231e4bcff4ede0c3b37e858fd1334ed26/ty-0.0.66-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cca1da877f613965b954bfd22d495386e260ec767c5536c8022cca98a260ea5d", size = 12137274, upload-time = "2026-08-04T01:09:09.592Z" }, + { url = "https://files.pythonhosted.org/packages/d5/07/9e0662f6603a5ac171ae6b314396e677ead4b45695fc948efb0a4837051f/ty-0.0.66-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aa523e777bc36c0fbf8ded5844096f46cbfc3712fe5a003351a94259b7e86cf4", size = 12207047, upload-time = "2026-08-04T01:09:12.533Z" }, + { url = "https://files.pythonhosted.org/packages/bb/cc/12e01bc2ea47fa7bf20fc6cdd3249d6a340be9be4edc52b1d77256245dd0/ty-0.0.66-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f7993c1f95f80a4e44056e6aaf8e46e608d778db131ae5ce59262ab59358f9a", size = 12919304, upload-time = "2026-08-04T01:09:15.175Z" }, + { url = "https://files.pythonhosted.org/packages/75/88/c6c0d3a8e71c9cdb560cc5c627a4bba6c47b5eec460b2f8c449b57c6d03a/ty-0.0.66-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f9b29354bcbac9f53b6952d8f46789bd81eeb9bdd7a68df7d26e654ca7498c3c", size = 13470963, upload-time = "2026-08-04T01:09:17.849Z" }, + { url = "https://files.pythonhosted.org/packages/27/c2/2f8c18063412ad80e1b3f87afce7bd50982b4a048166607b67f80660a9fb/ty-0.0.66-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbf4e5325f7f584d9c346946e7c415b2e3cd3b8f1119d468082a90a6afd020ce", size = 13244773, upload-time = "2026-08-04T01:09:20.59Z" }, + { url = "https://files.pythonhosted.org/packages/ed/f6/fdae2b95831116dffc055ad53a99a8f21437263651047b3ad46def4950a3/ty-0.0.66-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bd304363764bd723c22fb20b17035c345420fdf66fee856934b158ebde08a91", size = 12751343, upload-time = "2026-08-04T01:09:23.225Z" }, + { url = "https://files.pythonhosted.org/packages/14/1e/7e75f0371d11463a256dc2bee98b0df401e0fa06021e200b8b601d21949d/ty-0.0.66-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:205d8589bd957ea9718d488b731b2fbdd0d1b1cefd37c79f52c0deb7cafddfef", size = 13068057, upload-time = "2026-08-04T01:09:26.052Z" }, + { url = "https://files.pythonhosted.org/packages/b4/84/f20f24518f6f0bea936e2e668ede250b9ce0774624559931599bd1f42772/ty-0.0.66-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7304a1df54741a2343801354f41d7ad87974acb541ee3e93c26cb6a1a0b863af", size = 12082318, upload-time = "2026-08-04T01:09:28.877Z" }, + { url = "https://files.pythonhosted.org/packages/08/20/70ca0eac2427d4a58a81a3a9426b20e46fb4a5a13aefc9edc2e5172e1243/ty-0.0.66-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:cf2c062863e5da0f588b0b120fec0da2e81c0913ee4cd07b50d77aa60ffd8deb", size = 12228978, upload-time = "2026-08-04T01:09:31.905Z" }, + { url = "https://files.pythonhosted.org/packages/7c/01/5a461ab34456d788248780830aed673c9e0e796f6e9dd92d3dbf02e04d7f/ty-0.0.66-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a58d32c879d86428978332adf21e85009ec269314a23d330c3483c65b57aedc7", size = 12471917, upload-time = "2026-08-04T01:09:34.418Z" }, + { url = "https://files.pythonhosted.org/packages/46/ed/f8eb5eff7c9ee644490c6d2626425935c097acc54871adf659ea70624a2c/ty-0.0.66-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b2f810fa3516c977630d78dbe9161592b3c27029fa9bb81074366624d9b5e4b6", size = 12858086, upload-time = "2026-08-04T01:09:37.602Z" }, + { url = "https://files.pythonhosted.org/packages/02/7f/8a78361f752274a550a7f6f07f127b246ece7d7fdde31121d22074e46eab/ty-0.0.66-py3-none-win32.whl", hash = "sha256:d28c3df565a387c1c5ea359aa452a46d19163616ef71be819058a424a62f1ae1", size = 11782494, upload-time = "2026-08-04T01:09:40.136Z" }, + { url = "https://files.pythonhosted.org/packages/7d/e2/deed22b823ce309b8410d50414fd15afe9e90aee8b8ca04789ba55c21231/ty-0.0.66-py3-none-win_amd64.whl", hash = "sha256:e3a457f3312c078f24c47d0da6e4f73de34d0a77ed2de22571c066c80b2fd5e7", size = 12893338, upload-time = "2026-08-04T01:09:42.825Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ce/6c828f42ef1ed39f53f57f9c0cdcdf03e23666fa7a29d799042a2c34bcb4/ty-0.0.66-py3-none-win_arm64.whl", hash = "sha256:2f62ae247b9c75674fcc060635f9f00210357da7681de59234d97b50fb9e9e94", size = 12227381, upload-time = "2026-08-04T01:09:45.365Z" }, ] [[package]] @@ -1301,159 +1301,159 @@ wheels = [ [[package]] name = "xxhash" -version = "3.8.0" +version = "3.8.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/78/ed/07e560876a4458987511461187b285071f53cde49dd5b25cd8c51091522b/xxhash-3.8.0.tar.gz", hash = "sha256:d72b2204f37840b0f16f34192c09b994b97bd25823d723d47a1eddfacf06eb43", size = 86107, upload-time = "2026-06-27T08:17:28.798Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/63/71aa56b151a1b28770037a61bd4e461c2619cfc8866a4fcaf1548605e325/xxhash-3.8.1.tar.gz", hash = "sha256:b0de4bf3aa66363552d52c6a89003c479911f12098cd48a53d44a0f7a25f7c46", size = 86223, upload-time = "2026-07-06T10:49:58.937Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/d1/36cdfc7d9a5cdcebb2ff3eeeaebae2c51a7aca50de27a44520af4d6923fa/xxhash-3.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2289857ab90ebb2408d4ac2b7cf7e9ff29bba9d2cb21020c9d11fbbaef78eea", size = 34638, upload-time = "2026-06-27T08:12:17.753Z" }, - { url = "https://files.pythonhosted.org/packages/8e/37/f3439475537ca4c59e9b8cbc2b934672d1965b13b6e5fb32b1796c76e517/xxhash-3.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d211cfa927a107df09359d1f31070883a11121ddc88fd6dd27eda3a497a88f3d", size = 32316, upload-time = "2026-06-27T08:12:19.332Z" }, - { url = "https://files.pythonhosted.org/packages/a7/3e/3878d943d9169fd8f5ad8d2bffa7dfec14430f8240ef20213772a7ef3dce/xxhash-3.8.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ba02f4cc4e71e1315ecac0468189b49bf3970da05ddf0b6965b4a9b1fe147e62", size = 217379, upload-time = "2026-06-27T08:12:20.742Z" }, - { url = "https://files.pythonhosted.org/packages/af/8a/096f0bf4e4d33b5afcb27e7907d54f84ae3c581509188dca1083995aefd9/xxhash-3.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:342d1a6f161741f8612dc38d940ec0019ae3362c0ede2d16554c1b4e3f1d5444", size = 237734, upload-time = "2026-06-27T08:12:22.312Z" }, - { url = "https://files.pythonhosted.org/packages/0e/5b/811939d5d3fdf9b4a9cad7591759cc82c3c4734afb0138917ec3b3fc4fd5/xxhash-3.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:75feec84a48cafd3b2446cb41910bebaf9a8150e2313c1f42887435818fb7b4c", size = 262522, upload-time = "2026-06-27T08:12:23.869Z" }, - { url = "https://files.pythonhosted.org/packages/3e/e4/50e2b55b1390895214bdd9dc6a75d4c31e0283d646d2cae424962585427a/xxhash-3.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:53e8f6cc0cc24283d98e9c742a0f0a5ded7a810abc4038b9e885e419fcd44e43", size = 238441, upload-time = "2026-06-27T08:12:25.95Z" }, - { url = "https://files.pythonhosted.org/packages/4b/fa/725fbd70cc69b2738599c3e1b499941663b6ccef92aec7c78a4c9968f2d0/xxhash-3.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:73d04a4520cc7313acf4ff2122f783056d0592c71fc3a59e90fe0baeb499d124", size = 469833, upload-time = "2026-06-27T08:12:27.678Z" }, - { url = "https://files.pythonhosted.org/packages/86/e6/d80a2fcbd80f024d8e74a579aad538c5a24c6b672e6ce8180a9a8bfc2231/xxhash-3.8.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5a7fdfde5022f5000c8e6565db954580d19a8aa497ef80875f461e4546ed182", size = 217094, upload-time = "2026-06-27T08:12:29.221Z" }, - { url = "https://files.pythonhosted.org/packages/a9/c0/6d85ebdc1e488df9e37c3a2267a8b98a936a36d968560cfb0389307fd19f/xxhash-3.8.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6a667f0dd160ec0ff6dddf42f2d75ad82660074285855f6037d6ecb57d40d0f8", size = 307502, upload-time = "2026-06-27T08:12:30.782Z" }, - { url = "https://files.pythonhosted.org/packages/ca/a2/4b97a5e4fb3450fe0c4b361399f74679a491b3b0bed914bff6d00e70425f/xxhash-3.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aaaf53eb633205f01bb5fb807f6244bd34af121bfb1e21eedc925374aff5723e", size = 234622, upload-time = "2026-06-27T08:12:32.075Z" }, - { url = "https://files.pythonhosted.org/packages/e7/7e/5a227460f92ec7309219730ddfb7451e09e8aa3e0704cfb0f24746686a0f/xxhash-3.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:71b2e99a02fd5275b7ecab0b01130395beed4c6f027b6ce9f0730025634e7091", size = 265697, upload-time = "2026-06-27T08:12:33.559Z" }, - { url = "https://files.pythonhosted.org/packages/4e/31/56327a7b39dd3c605034f9b51c89d66aad022aacbe12aabeb6e335652d48/xxhash-3.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b25437ffd781d4cb98acef87f4bc32e27682f603ffd27ed5962948b516e777ff", size = 221932, upload-time = "2026-06-27T08:12:34.997Z" }, - { url = "https://files.pythonhosted.org/packages/41/a0/312504d1851969c62e3f2836eec5b16f3682edfae19aa60e6d69ee80d111/xxhash-3.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fe0ee773fd6c211b3b0134ee5d6fd6348411bd7bd79cdb4151d0aaf732179571", size = 236819, upload-time = "2026-06-27T08:12:36.66Z" }, - { url = "https://files.pythonhosted.org/packages/5d/23/d8f80cb1b1acede29ce76a39e013e5782712ab895bbffb32fe2e42b8eadd/xxhash-3.8.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:06c74e537f45c2f71010738d4d20741186cac29a035ec5c1c621c723d656c2fd", size = 297860, upload-time = "2026-06-27T08:12:38.103Z" }, - { url = "https://files.pythonhosted.org/packages/34/e9/4fdc697dcff5a73157ee34331e37849ada645448d4e47a38cb8a4044eafd/xxhash-3.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:718162a608eb85a22470725f95d63d834b1d7db98a2008b10309cd5a552d91ad", size = 439263, upload-time = "2026-06-27T08:12:39.808Z" }, - { url = "https://files.pythonhosted.org/packages/60/07/41a5144d7fd1c1f2b380de36521f7f34d624eef0374736515087ead7b925/xxhash-3.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:934cd5008d86e201818ca4416a4202039ea29edd89047166fea5c49999677bea", size = 213953, upload-time = "2026-06-27T08:12:41.528Z" }, - { url = "https://files.pythonhosted.org/packages/44/e4/7bc12b2fc9f340c446054b6f0e90e5b54c8021a4f9f6b1650054796009e9/xxhash-3.8.0-cp310-cp310-win32.whl", hash = "sha256:1f2c243a385e2c2ce72f5b7d68f3a621cc7d2ee2d0f35e0ca6bf5427ef1922a4", size = 31858, upload-time = "2026-06-27T08:12:43.139Z" }, - { url = "https://files.pythonhosted.org/packages/46/6a/3a61102925bf65ad81827a4586553a357f8a5316a25b938ef435e0bfabf8/xxhash-3.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:cb4996d43a42d825e2aa6f2b6a978b2a7779397b6a28e4fab5eb9505457023e4", size = 32659, upload-time = "2026-06-27T08:12:45.029Z" }, - { url = "https://files.pythonhosted.org/packages/06/c6/39d915926f45f72059519688b538a068efbea0307a294eba1ddb18887c0e/xxhash-3.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:b3a79d694adcfd70d118c73d244eaece7f5f5ab424feb44573bd1d377e1bf0ea", size = 29128, upload-time = "2026-06-27T08:12:46.447Z" }, - { url = "https://files.pythonhosted.org/packages/e3/1b/73aaae7755372ff0cd5788c9955abb64b34d519dd84f2f4f081e2082119b/xxhash-3.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:08c34553cd7ceb3bfcfca344dc70305a45430429b5d58a67750f2a58364f638f", size = 34641, upload-time = "2026-06-27T08:12:47.579Z" }, - { url = "https://files.pythonhosted.org/packages/53/08/fdb1cb1001ed15b1f74a8eb70457dbdcd6df8375e27e3fe0d0225dbab170/xxhash-3.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:842d147983110e5a4f533f98f4f5bc851a08c7ca00aaa30649e8d5f9a6d4e47a", size = 32316, upload-time = "2026-06-27T08:12:48.695Z" }, - { url = "https://files.pythonhosted.org/packages/d7/05/c004e99c4292a9dde76c9157e8e51c73c6db2dd7e4a876712e6a6113e3b0/xxhash-3.8.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:37c9943e18f569f76a8b7d5d01bfe0716f7762c396096ceb42a47eb3d5ecf641", size = 220196, upload-time = "2026-06-27T08:12:49.964Z" }, - { url = "https://files.pythonhosted.org/packages/a0/b2/8696a2008d59c3dc9346b26f7d64f5ec342cacc4051664e3b0201354fe58/xxhash-3.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21f6797afdc7abb0ffae059a0d1619c84a5368115bc0abd48f9803ab56a5d35e", size = 240908, upload-time = "2026-06-27T08:12:51.544Z" }, - { url = "https://files.pythonhosted.org/packages/da/ee/2415c55a17f525bcfa38b5b51d69381d6485b1c320eff373b263403b5e6b/xxhash-3.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5875d99d3540367d43779551dd22c813420b84a103e418d791095b9808fdca57", size = 264445, upload-time = "2026-06-27T08:12:53.08Z" }, - { url = "https://files.pythonhosted.org/packages/23/25/056d30ed2e500d0a993e4589da8cdbe50cbf4809c1b1ac84f6f9559d99ba/xxhash-3.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1a54ad5a2a96cdf1ee7a935d38bc63daa6095530095a916f644f1ab76604ced5", size = 241295, upload-time = "2026-06-27T08:12:54.703Z" }, - { url = "https://files.pythonhosted.org/packages/7e/70/5d8c9b65ae05725c2ea8f331705e1382fc4817911eb159450aecb2905c6b/xxhash-3.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b32e50dd85f0b67b2b95eb59cd3242052f6b27b70e9e73b27629686c592e3ea3", size = 473113, upload-time = "2026-06-27T08:12:56.159Z" }, - { url = "https://files.pythonhosted.org/packages/6f/d4/734dd8e6eaa03b0c4e3044127755221ebf153260a3c5de0382430486fcaf/xxhash-3.8.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4208fb85c950ddf7118b040bca15179c3bf9b7eb8bebe5e6ef067fc8af16a7", size = 220001, upload-time = "2026-06-27T08:12:57.869Z" }, - { url = "https://files.pythonhosted.org/packages/5b/cc/a0d92359d499db55f83fe6de13188125515319b968bd627b591a0984c454/xxhash-3.8.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9f17e09b035f2a0139536da53deb392b62ee259dc2a2189be12b06a7dd50489b", size = 309757, upload-time = "2026-06-27T08:12:59.438Z" }, - { url = "https://files.pythonhosted.org/packages/bc/dd/a20949401cfb9c940ef858d93b41ded90382ff4be0f7e8a5249edd95ff18/xxhash-3.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7d6dbb976d6e3b3be51bad16b13de7f4980e6aebd0aa51c5a14dfcc0fedd495e", size = 237596, upload-time = "2026-06-27T08:13:00.992Z" }, - { url = "https://files.pythonhosted.org/packages/99/5d/6963ee0c245a69d9c4a2583da603915f9288f1df23700a0ec705239ef014/xxhash-3.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:281897e5c516769694c999f5c50fd1e9acb27acbff187282a8ac77c38b6a9be5", size = 268683, upload-time = "2026-06-27T08:13:02.577Z" }, - { url = "https://files.pythonhosted.org/packages/db/ea/3489cde91ccd91230efbb2351a6d9358e8a63a9954cb8f071fa9c32a2558/xxhash-3.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8fba3d08c246201a1a0a6cece53a0b3b0890fc16adbe1edb245fcfcbf4eb0ce2", size = 224882, upload-time = "2026-06-27T08:13:04.21Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f6/179847064c92a07bba7381e9cd7132c380a17aad31e176a2d6f6e73eed48/xxhash-3.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:14ebc1559e8a9a481d0d5506b87678942fcdfa794d4aa55cdd2a0fb175d4245a", size = 239563, upload-time = "2026-06-27T08:13:05.96Z" }, - { url = "https://files.pythonhosted.org/packages/2d/83/dd599670efd161d31fba4149e20694f140ae5707068d38ac480dac1c8cd5/xxhash-3.8.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5e7a3e3bbe3a56bff70acc9b72576670e793b0184de3d1b9cda2bf697d17f630", size = 300148, upload-time = "2026-06-27T08:13:07.495Z" }, - { url = "https://files.pythonhosted.org/packages/e8/a8/a474f136610594b464ad813f6badf00b931211a69fc86542c21daf5d2a4d/xxhash-3.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9c71e3755a8320d29c351126d550930349be22b44bac1a559caf12ab78b53e9f", size = 442448, upload-time = "2026-06-27T08:13:09.467Z" }, - { url = "https://files.pythonhosted.org/packages/75/86/054032919fc73b72917054cf731be76be3a984e8f53b1d0ba6f22fb9cffc/xxhash-3.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:715c611582004e75010517b919776c5dbc00aae03054dc9fd72484a23fd1862f", size = 216755, upload-time = "2026-06-27T08:13:10.902Z" }, - { url = "https://files.pythonhosted.org/packages/3c/16/2eb382a78f12e3fde1c735b57607498c0efe897e8859484d69d9446bba55/xxhash-3.8.0-cp311-cp311-win32.whl", hash = "sha256:41a30a1d0ba978238742a374875c15979e0faed0a65294f3ff4d9410057ee8b6", size = 31851, upload-time = "2026-06-27T08:13:12.281Z" }, - { url = "https://files.pythonhosted.org/packages/dd/53/a07ad4dbdc32118b3bd190f5d54ee2ed28c1a0a994b52ae493435cfb4de7/xxhash-3.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:43705f917b8b817d6994851bf3725b98b4c95e64186404d9a6dbc1acf12fd140", size = 32655, upload-time = "2026-06-27T08:13:13.394Z" }, - { url = "https://files.pythonhosted.org/packages/e1/87/d76bef62a288a1f2441404b33cb757047cf555cd5956b36ed718a38b81e9/xxhash-3.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:35c5d843bb7ac1dfdb125ef4181fe4c2e01c2275856e6b699de89e9eb5c69c8d", size = 29128, upload-time = "2026-06-27T08:13:15.371Z" }, - { url = "https://files.pythonhosted.org/packages/17/2e/4b7c3ab28b7a54ac17eae7e02471c49609d6fc5900856a455feeb847a2a3/xxhash-3.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fc4bd14f873cd0b420f6f1ff5b5cd0dbfeb05b044a11bb9345bcbbf9749636e3", size = 34623, upload-time = "2026-06-27T08:13:16.696Z" }, - { url = "https://files.pythonhosted.org/packages/43/e4/09eea3e1bba6a59d64599cb8fba39f2a0872d06e85420eae989a4da61a9d/xxhash-3.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:31904979198e913239cb61b49f5b849696aeb3b03340da815d1491ec74dcc602", size = 32318, upload-time = "2026-06-27T08:13:18.036Z" }, - { url = "https://files.pythonhosted.org/packages/01/59/688bbae31e4e2d6d6eb92acbd3837c0e44ff8c7d435e6da922844ff6efda/xxhash-3.8.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7338ad13f2b273a1ef0ea97b2db0a059fdb3a1a29298bfa145937c0e4152d341", size = 220461, upload-time = "2026-06-27T08:13:19.311Z" }, - { url = "https://files.pythonhosted.org/packages/2d/de/71484ce0dab2fa4a475705d1ebc37a17ff02d40e5df6767b3255cc53120e/xxhash-3.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54e80e803cb34c8a1d278b491e543af40a588d288589c3e6becc991d5328b46b", size = 241110, upload-time = "2026-06-27T08:13:20.844Z" }, - { url = "https://files.pythonhosted.org/packages/bf/f9/1ac88f02e7df7898541490260b21f2b7f7bd2b233038a0cbd3a3b1bffdc2/xxhash-3.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:353953ea18f5c3fbdd13936fb536aacfb47d5bc06eef0919b1a355df61f7cc31", size = 264779, upload-time = "2026-06-27T08:13:22.485Z" }, - { url = "https://files.pythonhosted.org/packages/25/49/7ea1f128d2fe948ed679020f97a0896cdc6c975da5cc69b53a4a9c4a5def/xxhash-3.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d761f983a315630eff18c2fec7360c6b6946f82748026e779336eb8141ef3eba", size = 242609, upload-time = "2026-06-27T08:13:24.277Z" }, - { url = "https://files.pythonhosted.org/packages/a0/da/7d237278dfa1c48722c31010c84a328a317b8885429c8cb6ae4a8fa3e3db/xxhash-3.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f3786a9beb9a3b76241cb7db5f5388b460682c12204236389e3221963fc626a6", size = 473472, upload-time = "2026-06-27T08:13:25.877Z" }, - { url = "https://files.pythonhosted.org/packages/9b/5f/980fda82620a07d80026b4df371cbca12fca0fd94d7087c4ec5d898da76f/xxhash-3.8.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c94f5a9a775f36cc522fa2a7e8e2cec512e252d2ac056759f753dc68a79ffc", size = 220374, upload-time = "2026-06-27T08:13:27.366Z" }, - { url = "https://files.pythonhosted.org/packages/14/71/efa37bc3e91e1c801972bcef99eab877fcbd17ec10aca16c550ee2951107/xxhash-3.8.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:55ce59f9af37ac861947b43ea3ce7b294b5de77a1234b558d0f07ffad0197624", size = 310220, upload-time = "2026-06-27T08:13:28.804Z" }, - { url = "https://files.pythonhosted.org/packages/9d/48/19e40320044dc7051e8446505f18557d5661853b87a8770ad399325bb3c8/xxhash-3.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3afa1422a32c7c8e79ad5121dc21eaa5cee9e9e67bffca3f15d15d220d371908", size = 238100, upload-time = "2026-06-27T08:13:30.378Z" }, - { url = "https://files.pythonhosted.org/packages/d3/0d/588499f4d7cd064864ada7adfb9e8785f88a988f1332ed4c1be73d249c15/xxhash-3.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:551fda694938be910529452a89175137c58b4739e41fadff3c047e24b1d74a3b", size = 268937, upload-time = "2026-06-27T08:13:31.867Z" }, - { url = "https://files.pythonhosted.org/packages/54/18/fb2ad593572a33d1b6864b33047b8ca7269273a3c56107b5fd33e0b9c8fb/xxhash-3.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:512eb937c9457e6057e230e005c4709dd2ab63a5989f854d69f31db905750a62", size = 224910, upload-time = "2026-06-27T08:13:33.659Z" }, - { url = "https://files.pythonhosted.org/packages/63/9e/b880f9ed61b73492e24bb962d76aeb63f18ccb895f0edfb52e20d45ed6f2/xxhash-3.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4931ea93840f750a908efebaf23c71004feacc1a4649ef601b96d400a505c9a9", size = 240742, upload-time = "2026-06-27T08:13:35.237Z" }, - { url = "https://files.pythonhosted.org/packages/3f/89/fc682f93e54e486fc338b26a7d6d0d5cb0ab366269273c2608ac62b51afb/xxhash-3.8.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2fd4b60e8d9fc3923f39079f185b3425e6d76636fcb66d82a33dd7eba7c30f2f", size = 300527, upload-time = "2026-06-27T08:13:36.997Z" }, - { url = "https://files.pythonhosted.org/packages/80/71/a4b4122afb2d17ad69e0922cfeddb5ad5c25b02f37eed3dd3819d42e5f55/xxhash-3.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1da00075f1605794298878cb587f7533329693e2a0c45bbd25d6353644add675", size = 443195, upload-time = "2026-06-27T08:13:38.719Z" }, - { url = "https://files.pythonhosted.org/packages/d5/e5/ed3930f5dc90f4b1bab5ac3be099e8b2e81c1262d85e4adb5f2758e30d23/xxhash-3.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba73801c87d44fa37b2a5feab3004f0a654506027bf032ceb154d94bb74ea772", size = 217252, upload-time = "2026-06-27T08:13:41.179Z" }, - { url = "https://files.pythonhosted.org/packages/44/ae/128ea5794387ca54bb4084566db20dbdfc9c21cb17b67d3fcb403927b5ba/xxhash-3.8.0-cp312-cp312-win32.whl", hash = "sha256:0b0836dee6022e22ba516ebfa8f76c6e4bda08d6c166c553e40867bac89e4a54", size = 31890, upload-time = "2026-06-27T08:13:42.568Z" }, - { url = "https://files.pythonhosted.org/packages/4f/04/a6c182dc566c88e8d1a497d22cc4ffdcfcc0a9fa80325efa6cd4b9002c54/xxhash-3.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:3bc2a09b98b8f85c75208cd2b2d2aecf40c77ecb2d72f6bf9757db51a98d3499", size = 32677, upload-time = "2026-06-27T08:13:43.705Z" }, - { url = "https://files.pythonhosted.org/packages/93/b5/aeda4e79f962c8d58ec60cb20a5abfe91c9f7d62e626f69f6659bc0bd0c4/xxhash-3.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:208e6a8b93426896d803224e9fabe26f8b9c651e8381a80b1fa31812faa091e3", size = 29155, upload-time = "2026-06-27T08:13:44.903Z" }, - { url = "https://files.pythonhosted.org/packages/ec/1f/96f43c5c7c7c4d44721f8d2e5d74698c667a30283c4b10a7e50a56804ee3/xxhash-3.8.0-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:36434c1d1b0a4729df1fa26ab11bffed1ba52666c0beb605c98a995b470cd143", size = 38508, upload-time = "2026-06-27T08:13:46.152Z" }, - { url = "https://files.pythonhosted.org/packages/1c/d9/7d5d6af4876c6481f2e0acb2dda64dd5209574bf7ba1ad4f6af7a1f8d473/xxhash-3.8.0-cp313-cp313-android_21_x86_64.whl", hash = "sha256:a5e6497cefcb2d67f1745c66df9718a99112583af6cc2b70da0312a2eb939f1e", size = 36542, upload-time = "2026-06-27T08:13:47.497Z" }, - { url = "https://files.pythonhosted.org/packages/32/ff/66fed439d78c5a09a1491a85af29bf8923b516530116731a9ac6b14dee2b/xxhash-3.8.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:5b00b82f1be708da9404fefd658cf5cf3be5ee3be2aae4bfe3b874255badd342", size = 31102, upload-time = "2026-06-27T08:13:48.721Z" }, - { url = "https://files.pythonhosted.org/packages/56/b8/9fae0399281095f8aca1f32b21947b3c3c75ad6021b255c5c6e4b11d3866/xxhash-3.8.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:38b0cb0ab7f283413b7cace2bf710d7cf8f702ea82cbc683908691d52028a89b", size = 32096, upload-time = "2026-06-27T08:13:50.138Z" }, - { url = "https://files.pythonhosted.org/packages/61/a4/e53d162c74a8a2950dc063969914387b0680da4c7c20ad17744ec03a3b0a/xxhash-3.8.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:084312171a9798dea85e924b2674f5e1a44933050a1ea1cb1c6b1364e004c66c", size = 34585, upload-time = "2026-06-27T08:13:51.572Z" }, - { url = "https://files.pythonhosted.org/packages/69/f5/e12397e3f2c4917b6572e103a3277cd27cc56330e304bba61d195d7e5224/xxhash-3.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6a1a9e845bd3bbc57d9356819e0d198fe23282e0576b398a6282a0f8fdc75aef", size = 34622, upload-time = "2026-06-27T08:13:52.818Z" }, - { url = "https://files.pythonhosted.org/packages/70/80/c053dc51af5c942229689a0e9cb66fdc999bbd840f645e761f5ab73cbb17/xxhash-3.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9ffbde09743ebaf8957b8426948fbe85eab5e5de0d29eec407fcff5a2812a3cc", size = 32320, upload-time = "2026-06-27T08:13:54.04Z" }, - { url = "https://files.pythonhosted.org/packages/f8/a3/294171b67dfe770e1293edcf2a3f7e41302cdb8aefb258585312191b3ffe/xxhash-3.8.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a6dee3952c2b6e82e7f1dbc5dbc6167f9c84126851def7926e32827c2816169c", size = 220532, upload-time = "2026-06-27T08:13:55.448Z" }, - { url = "https://files.pythonhosted.org/packages/80/c3/d141bfdeca785c8c680abf867d4b52a5e64a55d90df242c3141a3e58c4b2/xxhash-3.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf8ff8e12416c9fa05b43c7509b9332d6ffc4090413c4e7a1dee8599763b6d59", size = 241215, upload-time = "2026-06-27T08:13:57.047Z" }, - { url = "https://files.pythonhosted.org/packages/09/5a/aeaf35143a6f3d44db73298e861405bdd9c9dacaedfc369cb43d9fd65282/xxhash-3.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:cebbb322df4d97d8ef2704f49ed2f6f21f6702fafa0dc0c2a6ae70e904205689", size = 264615, upload-time = "2026-06-27T08:13:58.912Z" }, - { url = "https://files.pythonhosted.org/packages/bf/3e/f8ca782bb34f99693faab70a7989bcc84f62ffe93c9a4cca464a33507a4b/xxhash-3.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9a8d08707b4100ebce598fc59fadf04b42d79b855818d6994f8f0fffd1df8edb", size = 242682, upload-time = "2026-06-27T08:14:00.483Z" }, - { url = "https://files.pythonhosted.org/packages/d4/fa/ddbee4ff1542c2e88e72269a5a6bd18c3f26a80c2514e0918f5d1f3e9ec5/xxhash-3.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cf5427602dda15d8ce3c6d870d29bf07d43975f59c9d6d3f7f6f93a901b28b12", size = 473551, upload-time = "2026-06-27T08:14:02.17Z" }, - { url = "https://files.pythonhosted.org/packages/25/f5/a680d48dddab37ab2fd9189ca03f775e29e3627122e30790816d7eb365af/xxhash-3.8.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97d7bd715ea5050b6c9638b52c62adf3055b648ef6eee6892a4cd9697b530191", size = 220485, upload-time = "2026-06-27T08:14:03.765Z" }, - { url = "https://files.pythonhosted.org/packages/22/b1/7ac129b74981c07f1ff9c649f204465e86f83f9f29b2ebdc70d91514c365/xxhash-3.8.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0cd25bbbab37d898f6e5a90905ce6ae2c1f8bd6668c07cef406fb3e8c8c570dd", size = 310307, upload-time = "2026-06-27T08:14:05.366Z" }, - { url = "https://files.pythonhosted.org/packages/67/e6/43e673411249dd63f6cd974523a1b32fad75cf5453e363bc8f44af215fb9/xxhash-3.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3e30e5c057f483c3c53a11b53eba091a737cb19dfead36c8b23bf5beb4a169cd", size = 238164, upload-time = "2026-06-27T08:14:07.149Z" }, - { url = "https://files.pythonhosted.org/packages/e5/95/87f8baf41f63130f3637104b7a610f82b20106332fc6e289c8dbf7955d0e/xxhash-3.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:07dd44d992ebd456752bc25b1c42cd172d94bd8cb24049300449ad0716081c3a", size = 269062, upload-time = "2026-06-27T08:14:08.834Z" }, - { url = "https://files.pythonhosted.org/packages/38/c9/3369b497cd1f926b930c52fd2400606f177790d887b49f9e86bddcc24562/xxhash-3.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3118600a3102d4707dc1c485dbc3acbbbf37819069ad3e7854e77b923745d76b", size = 225007, upload-time = "2026-06-27T08:14:10.689Z" }, - { url = "https://files.pythonhosted.org/packages/34/c8/03dceb86a8128858ac105bd6e282d62b3db6fd421a79bd8a9f6b8cdc47a7/xxhash-3.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7ed37b0c95d8fb3fbaad5e13cc0a9727eb8739d1d54b2adef28108c250cada3a", size = 240815, upload-time = "2026-06-27T08:14:12.195Z" }, - { url = "https://files.pythonhosted.org/packages/47/a5/ebd43eeb1af1dd8f0201943688b20958e99d3f6eb36481fb8c37b55ef139/xxhash-3.8.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bb043da412e478e7b1db3407051124b85b133803794d3809ad6d92870b304fc7", size = 300632, upload-time = "2026-06-27T08:14:13.916Z" }, - { url = "https://files.pythonhosted.org/packages/df/24/c873e41a3c00dacc385c8ff08c007723f6a528922c1cea7fd9684e86dae7/xxhash-3.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:196fc132683d9311a0bdce8388ee52bfa07fdc1987cc428a27956e47ccd7b50d", size = 443293, upload-time = "2026-06-27T08:14:15.446Z" }, - { url = "https://files.pythonhosted.org/packages/4f/1b/c671272fe28f70574e3c574d58465f26460154bcc68876121872afa1c14d/xxhash-3.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfb5411af3b77c75e99db100aa15c5ba623c85d72c565e4d7a0ed1a986ff766e", size = 217327, upload-time = "2026-06-27T08:14:17.28Z" }, - { url = "https://files.pythonhosted.org/packages/57/43/b45a52f795812cb769b6ac159e69b605d18b1c067749e63dcac159e90064/xxhash-3.8.0-cp313-cp313-win32.whl", hash = "sha256:6d1d6179e26830c6690fac63f76d372f69714b977e12ca9c42188a60f51c59f5", size = 31898, upload-time = "2026-06-27T08:14:18.952Z" }, - { url = "https://files.pythonhosted.org/packages/a1/42/2bd70e4eec25dc5990652979d708d4d7c999793d7d5af5d0e48ab4374dc1/xxhash-3.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:7c92427a56a12f4d5c7bb26dbb9e9a4658c313ecb6c2f1dca349902e3822df07", size = 32680, upload-time = "2026-06-27T08:14:20.277Z" }, - { url = "https://files.pythonhosted.org/packages/d5/c8/2fe61edb6144183cf094035a8c5354c65a073127acf6379655ed1e705b70/xxhash-3.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:9fc8453642c1c6d38b4fbac8901c2452ce1fa88b27f003bfee6703cbfae9bd63", size = 29157, upload-time = "2026-06-27T08:14:21.674Z" }, - { url = "https://files.pythonhosted.org/packages/b9/b8/81d17a993b9a4750ba426ce966421681bb4b8e82a460cd346756491b8cc2/xxhash-3.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:efcacb644a915f010dc477447b045e5dcde1afaa40d16b2f0f8e7cd99c9e1635", size = 34897, upload-time = "2026-06-27T08:14:23.044Z" }, - { url = "https://files.pythonhosted.org/packages/bb/3b/f5a368e3273440b3ea58fbd3f0b08c19f552b25ca59f43f5732ca96d2126/xxhash-3.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d1e0dbc510cff94c5efbcc2b82c28b41519fad09b5b1f9f3d99c63e3940e49a0", size = 32630, upload-time = "2026-06-27T08:14:24.603Z" }, - { url = "https://files.pythonhosted.org/packages/a6/ab/f424359c91c55f564fbbe4e454a126eb522471109f67376f20ad19c5e663/xxhash-3.8.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ff19d016a41c90d1f519005887191896b6da1274e1d5d48b347e17eb798ffc5a", size = 225874, upload-time = "2026-06-27T08:14:25.992Z" }, - { url = "https://files.pythonhosted.org/packages/ac/c2/434579ef9235123b6c9bfa89c5614e0001e988613b91557b24aa326d9faa/xxhash-3.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aafc3eab99c50508852e34307e9565933bf128cad084cac7d2471b7ab1743de0", size = 249705, upload-time = "2026-06-27T08:14:27.607Z" }, - { url = "https://files.pythonhosted.org/packages/c0/6c/3c0c917331ca3c71f826cedce2127f230624e2b49b992472dd5e9e72101c/xxhash-3.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5e521368ed79ae6c4d31e1e417726643c49d7d6e286f4fdabf9a8330ed8a8ff7", size = 274716, upload-time = "2026-06-27T08:14:29.495Z" }, - { url = "https://files.pythonhosted.org/packages/c1/f3/a8bb98d3307c67e88be9642dff52854c3de3f488f95989b60ff69c8dcc42/xxhash-3.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6a0127688d116ec0c225e7e1f744e3f206de2b8822ffeb31a9ab5cc6384f92c5", size = 252019, upload-time = "2026-06-27T08:14:31.247Z" }, - { url = "https://files.pythonhosted.org/packages/f7/73/fab69a2e5b6353dde643209fe9b6adf4fbd64c888e531deffc476bfb2635/xxhash-3.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:22c0b17da2f9fea0f8836538512249871b359141616bad44c58d238b5f011f40", size = 482024, upload-time = "2026-06-27T08:14:32.973Z" }, - { url = "https://files.pythonhosted.org/packages/a3/5b/ba34099b5278097ec9c68c0b740719813553bfd11ca17e7353de6d2a41e3/xxhash-3.8.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3d49465646b1a5e3b1729c5f636e05676a2fb52e203e3b22a5411c416c4c5302", size = 226655, upload-time = "2026-06-27T08:14:34.608Z" }, - { url = "https://files.pythonhosted.org/packages/76/0c/90aba4708a37fe752b324a7cbf10058eaa33e892cdd62751ff17a5137b93/xxhash-3.8.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c2853dea1e30ed00ca87dd87d76da5da063d302b823b3fb80ccd18421de0f251", size = 319583, upload-time = "2026-06-27T08:14:36.419Z" }, - { url = "https://files.pythonhosted.org/packages/38/46/42e349e2d3017b2688f4cb301742c37c438e77963e3fef711edce2fc5c65/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:82f0102a2a3760287b7cd7f9e0a30edd4c3b18762ed1a242208d43c8e2bcf30b", size = 246000, upload-time = "2026-06-27T08:14:38.104Z" }, - { url = "https://files.pythonhosted.org/packages/ee/15/741b947ae3c768e82018c46846f8616f6aa9b5042649f318a1a6897defe3/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:b8414a66a7524596d841cad5dc1adab6ce76848db5ab2b83db911fbdab1417af", size = 275455, upload-time = "2026-06-27T08:14:39.841Z" }, - { url = "https://files.pythonhosted.org/packages/c6/b4/a9db84c9458fc8f53eaf0051377d1e9eecd9f330fb1225640027417a309d/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0dbaa73df10414ea1e41b98691a9d8241d4c47ad8d02c726587a3cda05278e53", size = 231209, upload-time = "2026-06-27T08:14:41.543Z" }, - { url = "https://files.pythonhosted.org/packages/20/92/60a868cd34851746d0b0d95dced0f42867c7c00606f6e5dba85b70b232ce/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:43fc9aaba10ab4267c90793601f60d35c3c9caa1544eceb483618a71ad9ce7da", size = 250416, upload-time = "2026-06-27T08:14:43.193Z" }, - { url = "https://files.pythonhosted.org/packages/7a/6a/168ca46a4679c32aae9246caa1fddf35981d6304487e45e992b3d4530324/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ec5eb3d28fbb9802c6d2526f772133a06c91d6f03756fcc67c834b642ffdd51d", size = 309764, upload-time = "2026-06-27T08:14:44.79Z" }, - { url = "https://files.pythonhosted.org/packages/18/0b/13646b348c07679c818791ab2d35415db5cb20f3bc77daaa255909a401b4/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:2b77c301b644cd9b4d0749a3291081ec2048a6bef7fe0487c993bbba3efb9ce0", size = 448650, upload-time = "2026-06-27T08:14:46.562Z" }, - { url = "https://files.pythonhosted.org/packages/59/9a/3d244b2acf6bbd86a363817ee09084b4684e8e11840663e19869e9e0d952/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d7ece11a132325353890a144c30119073617a1299c593ca29b96c315b07e1edd", size = 223572, upload-time = "2026-06-27T08:14:48.294Z" }, - { url = "https://files.pythonhosted.org/packages/6f/c7/143410d026a6e0d86dc69037ec2a3b8db810a54e7f443b340ac17612be2e/xxhash-3.8.0-cp313-cp313t-win32.whl", hash = "sha256:b21db84df7b9d54d9e4195a964243c1b32d745c6fbc0cfcfffee1d4bd297196a", size = 32301, upload-time = "2026-06-27T08:14:49.687Z" }, - { url = "https://files.pythonhosted.org/packages/6c/db/2240b0638161637b2f310231748a7a6a06c79fb43a3adb34c96f359762bf/xxhash-3.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:0643b7d9f598f6da6f1f6b899f4358250d0fb853242e2d712cbde27bf5a99d29", size = 33221, upload-time = "2026-06-27T08:14:51.404Z" }, - { url = "https://files.pythonhosted.org/packages/e0/d8/52038e4fa5baf4f00654a225516168d02908edfec7ca104fbefc58af394f/xxhash-3.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:4bbacf2e938526969f8ab3334d4ac3da14ea059e1dfd1339a92f9091467e750f", size = 29294, upload-time = "2026-06-27T08:14:52.778Z" }, - { url = "https://files.pythonhosted.org/packages/e5/ef/a09907aa28bdcdf6810d5c26656b154c60c0f06bb8db8442a1192d9c227a/xxhash-3.8.0-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:557e2a7cc0b6a634cf9c8e5c975d96b7da796fdeb1824569d760cf0f25b6f33f", size = 38365, upload-time = "2026-06-27T08:14:54.166Z" }, - { url = "https://files.pythonhosted.org/packages/d2/4d/d991ff77bc489c2231025e64e570502156d573c7bff69c917589cc307089/xxhash-3.8.0-cp314-cp314-android_24_x86_64.whl", hash = "sha256:dad744d1613cbfddb844dad93adbffbd51c3e9f53ceea9568f7c3b94bedc19a4", size = 36477, upload-time = "2026-06-27T08:14:55.427Z" }, - { url = "https://files.pythonhosted.org/packages/cc/0e/553eab001f1e274da73da074968cdc8be8cacfb318937ab9871b8e1909cb/xxhash-3.8.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:953f29b22c04b123cf3cd2e08bccde3a73184aeda5a1038e0054cb3355644120", size = 31116, upload-time = "2026-06-27T08:14:56.897Z" }, - { url = "https://files.pythonhosted.org/packages/55/d5/d0f4dbe7b4d9ce0125f16e45ec0be5e04f6a172edb4e2fa551c4f2eb5d7a/xxhash-3.8.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:aa699e0253ceffecf41cae858d0a11f2439d6874a0890b556387bffe11dc1c08", size = 32112, upload-time = "2026-06-27T08:14:58.126Z" }, - { url = "https://files.pythonhosted.org/packages/2e/2f/b332c7bede6a676343f2c9c8dea233c8c82753eaeda6f7a2c321d8c58ca3/xxhash-3.8.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e232c82466babc13e956d53aa84d0149660ed6886bc195248bb4d03bf2eca301", size = 34618, upload-time = "2026-06-27T08:14:59.458Z" }, - { url = "https://files.pythonhosted.org/packages/b3/5b/2bf3c9e61c7cf8f53bce937af45e22b72bb1f224d5afb20352beba0d628d/xxhash-3.8.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7f75fd1c6a5028f345cd4a8c52f4774d2e5b7809fa58111c60a5502b528914a4", size = 34739, upload-time = "2026-06-27T08:15:00.863Z" }, - { url = "https://files.pythonhosted.org/packages/64/b6/e88521f5736c181b89bfb7ab756f0ca658a8a1ecece7277b75e167717614/xxhash-3.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b49d7e09b211a1ad658dbe2dbf6561eb92f2e6926bd1101e2d023178371f2d6f", size = 32332, upload-time = "2026-06-27T08:15:02.383Z" }, - { url = "https://files.pythonhosted.org/packages/ed/a2/fba440739fa5f86d2c28738c202e88d3dd063290c8bbb20e183c5334456a/xxhash-3.8.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ceb702bc8e56b7f1f1413d42aa294045b9a0e4c9888e07edc5cd153e8c4c948f", size = 220479, upload-time = "2026-06-27T08:15:03.785Z" }, - { url = "https://files.pythonhosted.org/packages/2c/1c/4a1639efec16416695d6c7bc6b224d3f607e0b8cbe2409fa81081a849d1c/xxhash-3.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f3c96e06bdb122e8cc84f5c7088579f3102b828efd62e9dc964a9d17c7b89e", size = 241409, upload-time = "2026-06-27T08:15:05.439Z" }, - { url = "https://files.pythonhosted.org/packages/92/d1/8ce471f8d6752384f972fd5f6363f2e8d8b867a89fbd724c6dbd91d2bb98/xxhash-3.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:415a8d06ac9bea36b1e06b603a347e0f62401042a97d7bfccec8ae2da12ad784", size = 264433, upload-time = "2026-06-27T08:15:07.027Z" }, - { url = "https://files.pythonhosted.org/packages/95/77/400a281683fd39c54e2ac497fa67bdf886baaadb8c0ba58f7e1ea1d7692e/xxhash-3.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7f5ccdd2deb5dce31201cc0eec94388cce97e681429073db50903fab0a0a8a0d", size = 242835, upload-time = "2026-06-27T08:15:08.703Z" }, - { url = "https://files.pythonhosted.org/packages/aa/a6/edda651cfa0ba8e921791e93468fae655b63894d89730fcbfe46704f0d0a/xxhash-3.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1a6cf81bc699d3a5ebfcf2fdb2a7bd2e096708d7de193f6f322944a02ba00953", size = 473800, upload-time = "2026-06-27T08:15:10.503Z" }, - { url = "https://files.pythonhosted.org/packages/dd/da/50f764ec6a93d3961fce294567e41bfca0e66d168deed354a3dc90ebeba6/xxhash-3.8.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e4d12a04d7ffc0359f0eadc4535a53cab113044c8d2f262c7e9a56950a5ed50e", size = 220677, upload-time = "2026-06-27T08:15:12.622Z" }, - { url = "https://files.pythonhosted.org/packages/bb/49/9fe4ed5aac6f38629cc83b34f84748b83ad8295a578ec6a49d8bf896cafb/xxhash-3.8.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d209373fcb66138c652cf843385ee60866e50158a7869bbbf8b322d9a822b765", size = 310385, upload-time = "2026-06-27T08:15:14.384Z" }, - { url = "https://files.pythonhosted.org/packages/83/f5/1147e03c0553ed22bbae9ce47503c37ee0c5f95592aae10f339c25f61de9/xxhash-3.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b88a3fe28277811e599efa6e1c96abce8a77d60dd79c94da7a9b5c377c172b7b", size = 238330, upload-time = "2026-06-27T08:15:16.201Z" }, - { url = "https://files.pythonhosted.org/packages/b8/d8/92daf66c1966c84da5c97a06ced1480208d3a3bd465cb0630565ec00d1b9/xxhash-3.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5d5a888a5ef997cb35f1aad346eb861cd87ecfe24f5e25d5aa4c9fd1bd3950c2", size = 268667, upload-time = "2026-06-27T08:15:18.064Z" }, - { url = "https://files.pythonhosted.org/packages/bd/c0/080c1a92972667e183c04b03f33c877f8ec61cfa3570e61731077286648d/xxhash-3.8.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:de2836e0329c01555957a603dcd113c337c577081153d691c12a51c5be3282b0", size = 224934, upload-time = "2026-06-27T08:15:19.972Z" }, - { url = "https://files.pythonhosted.org/packages/d9/d5/cbc4e5b2bee10c94cba05b5bb2b8033e7ef44ae742583fdafcd9188e33ed/xxhash-3.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:4bc74eedb0dd5827b3be748bacf9fdb50004037a3e16c7ddb5defae2682cef71", size = 240870, upload-time = "2026-06-27T08:15:22.04Z" }, - { url = "https://files.pythonhosted.org/packages/76/f7/09679b00e192b741b65c230440c4f7e6df3251a9ad427a518ddf262ec71a/xxhash-3.8.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:c571b03d59e339b010dc84f15a6f1cff80212f3a3116c2a71e2303c95065b1f6", size = 300683, upload-time = "2026-06-27T08:15:23.647Z" }, - { url = "https://files.pythonhosted.org/packages/5f/1b/f43ec36e8c6a20c77be0bcca23f0b133ed8a0312681500d1676eebd71924/xxhash-3.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:87626acdd6e2d762c588a4ffe94258c5ef34fb6049a4a3b25019bdb7f9267a9b", size = 443407, upload-time = "2026-06-27T08:15:25.504Z" }, - { url = "https://files.pythonhosted.org/packages/45/2e/a3e3a779c5e4789daf975e05cc1c7f11bae724a03855120029d4592c8e63/xxhash-3.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:076d8a4fb290af952826922aa42a46bfc64caa31662ce4e2925a445d0e6ce57f", size = 217559, upload-time = "2026-06-27T08:15:27.234Z" }, - { url = "https://files.pythonhosted.org/packages/44/da/1c1e078ac290afff304a541a2a60965beb369ad65b4f30ec93ea1e0b7210/xxhash-3.8.0-cp314-cp314-win32.whl", hash = "sha256:52f8c7c9833d947e60df830671f6eca810d7c667051243985a561c79f1a3d545", size = 32602, upload-time = "2026-06-27T08:15:28.809Z" }, - { url = "https://files.pythonhosted.org/packages/d7/7a/d455cb83d5e3c94046234294fb5dbbe5da600d1bbdf76b9527756920cce9/xxhash-3.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:4fbfcb7dd307e23189a71050f6e27746926590330f37d5fd2ffcb8ea78de1f42", size = 33393, upload-time = "2026-06-27T08:15:30.166Z" }, - { url = "https://files.pythonhosted.org/packages/89/8f/1b14471f617bc96edbb9566099a162d918a981381c398114726cc600b76c/xxhash-3.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:ecef1e65b4715c7326002073763fe94cc44c756a0698508abb915ab3d6be6e3d", size = 30007, upload-time = "2026-06-27T08:15:31.634Z" }, - { url = "https://files.pythonhosted.org/packages/11/8d/51ad2f9f784121c8057ef1ba36362f58d4595cbcad16322941f5b73eb53d/xxhash-3.8.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:02ed856a765cb6e006168595d9455ac8c3c4d60cc04cd47a158a1ac677d68f0f", size = 34957, upload-time = "2026-06-27T08:15:33.292Z" }, - { url = "https://files.pythonhosted.org/packages/1b/14/175c573ae4fac48bf21a82e5b9ceec75d64c520c51ca08de3105de539438/xxhash-3.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:eec30461a7b457611098ba7ab09363e36c8b2645b4687fb6f3d405bb646e3410", size = 32635, upload-time = "2026-06-27T08:15:34.766Z" }, - { url = "https://files.pythonhosted.org/packages/96/08/f83efabd350a50c31c851b88891e318a6f07bdbf40a43d0f7bb6cedade7f/xxhash-3.8.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b471744912d1ce5dd6d3975b7525e77518359ebf3aa1bd7d501e199f5ae488ea", size = 225969, upload-time = "2026-06-27T08:15:36.35Z" }, - { url = "https://files.pythonhosted.org/packages/7c/78/2b6d12da9cf572c84d93b88ecbf9bf6539a7c5219bde128b214396b97c8b/xxhash-3.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3748d71202bf3f279e77cb8b273b6d0f29d1bcaefb6ce6cb03b95f358863ba37", size = 249851, upload-time = "2026-06-27T08:15:38.087Z" }, - { url = "https://files.pythonhosted.org/packages/d0/0a/755eeb1882634983b24e6375a95ed233228dc48f0ef12655388bf3c7eeaf/xxhash-3.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3b3bf59ea94b2a23b0f992769804ab9401d5cdcd9df0062fe2cd78a491ae8851", size = 274842, upload-time = "2026-06-27T08:15:39.808Z" }, - { url = "https://files.pythonhosted.org/packages/77/f2/09b1231cad17c314e51664c4a004c919108ec59aba10f9a28fa061e7b8be/xxhash-3.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:40f061aa5379eba249e9367b179515571e632be6d1b6f55ac139e6fe3d08463c", size = 252218, upload-time = "2026-06-27T08:15:42.105Z" }, - { url = "https://files.pythonhosted.org/packages/b2/24/de756d55547953494eb6775aea92e258035647b3ecb8547618cd549001e1/xxhash-3.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:680d70896a61fc920cc717a0a8fe8a9fb5858c563184666e31874caa54a16d9e", size = 482135, upload-time = "2026-06-27T08:15:44.476Z" }, - { url = "https://files.pythonhosted.org/packages/e5/63/b8147633e32f98ef2b4bb0dfca82f0f63e2b02ff179f20664af64c4216a7/xxhash-3.8.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14973fbdee136588e57447401b521f466a42faca41eecdf35123c73103512ca8", size = 226776, upload-time = "2026-06-27T08:15:46.597Z" }, - { url = "https://files.pythonhosted.org/packages/29/37/ba051d8f0380d3cf845b23ba058a17d32025846463eb6bf885887fc8effe/xxhash-3.8.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:96c6bca2486cdc58b125966817a92a6abe6ef1fab86b2f8798a7e93488782540", size = 319738, upload-time = "2026-06-27T08:15:48.394Z" }, - { url = "https://files.pythonhosted.org/packages/4f/6f/36e0a27dd27ffa3f7b521650cbcd52a00fb86b71343ffadb642374e8263c/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0b1109ae238e932d8482f9cb568b56a405cc73bc7a36b837844087f1298dd218", size = 246136, upload-time = "2026-06-27T08:15:50.981Z" }, - { url = "https://files.pythonhosted.org/packages/fe/73/2663dbf4c09386a9dcc8a94d7a14b4609ed4bad8180ced5b848e60a9b660/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:1da5db0863400eade7c5a31969754d1392189f26b4105f6631da2c6c7ea3bccc", size = 275568, upload-time = "2026-06-27T08:15:52.735Z" }, - { url = "https://files.pythonhosted.org/packages/d6/58/f3ce1bc3bb3971191f6521273ddae98d3c610bcefbbed5327c3b3627c12f/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:c61b5a0f21ace5e886f177cce43826d85a7c84e35a9e17cb6d1b4ac0b7a7d833", size = 231314, upload-time = "2026-06-27T08:15:54.73Z" }, - { url = "https://files.pythonhosted.org/packages/4d/51/835706a36cdc00e5b638fba9b22218b3d40d23a7677c923feca8a3f55b98/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:1db4f27835a450c7e729bc9330c6e702113711cea1f873d646e3a31fe96a9732", size = 250521, upload-time = "2026-06-27T08:15:56.853Z" }, - { url = "https://files.pythonhosted.org/packages/c1/47/b0b62caa3caee58ab9de8969f66aef1c3729886f3ff60e173fda3f2762be/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4788a470f946df34383abc6cd345088c13f897a5ee580c4cdd12b1d32ad218ef", size = 309926, upload-time = "2026-06-27T08:15:58.704Z" }, - { url = "https://files.pythonhosted.org/packages/69/c4/60e6d18a0e131c7af622374af9deede15d3c47d8e5e7221933481b57b319/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3b6dfa83096cb1e54d082acebaf67f0c42667c56dc48ba536a76cac08d46391e", size = 448812, upload-time = "2026-06-27T08:16:00.619Z" }, - { url = "https://files.pythonhosted.org/packages/12/9f/c9627daa052be39a932d0e17c6bf6a9041d2cde3afacbded9196acf70261/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:57ec0ba5299a9a7df376063c139f5826ff0c89b438703939af3d252c31ca96a4", size = 223639, upload-time = "2026-06-27T08:16:02.784Z" }, - { url = "https://files.pythonhosted.org/packages/a9/38/92916e008a84c1f1a9aef82e4363cdc478a722ff69e59c6afbf93d3d1fda/xxhash-3.8.0-cp314-cp314t-win32.whl", hash = "sha256:d9a61f23b999baeb84102aba767b1b3e94958eab94e6c11b08927e7dc4200795", size = 33078, upload-time = "2026-06-27T08:16:04.639Z" }, - { url = "https://files.pythonhosted.org/packages/31/7c/e413bc75121d9628bf023b2ed251411ca3a447cf00cd9aa3438ab17f6c67/xxhash-3.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:61069b260fff84116235bb93845f319284dc6b42527c215af59264f4c2ee3468", size = 33953, upload-time = "2026-06-27T08:16:06.347Z" }, - { url = "https://files.pythonhosted.org/packages/f6/eb/21a96e218375bd8b6ecd6d07cf60c8ff1a046e93cdedc3cf7bc3309edf7b/xxhash-3.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:73cecd431b4f572d38fcf1a7fe85b30eb987778ef9e7a70bc9ffcf2d64810e6f", size = 30164, upload-time = "2026-06-27T08:16:08.009Z" }, - { url = "https://files.pythonhosted.org/packages/96/84/9bb3cc67475ac7678476b30eed2f1140431f06386d637534194037c0624f/xxhash-3.8.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ba14843f20df2dce6ff6684411a56ae53da44336546c55f8947e70aebb8cdd21", size = 32604, upload-time = "2026-06-27T08:17:19.291Z" }, - { url = "https://files.pythonhosted.org/packages/42/6d/e98f9dd62c89e8895e4f3b525b6dbc3efcf27e2b99800e51388c59eb96dd/xxhash-3.8.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ec6666a5311beae3f6cb5f2fd28c2b77e2df32702c8206f45c786a6ef81b3751", size = 29787, upload-time = "2026-06-27T08:17:21.001Z" }, - { url = "https://files.pythonhosted.org/packages/db/51/e7844a65c62d6d78747e4d149508d65a3df6fb65d72322c2526789e9f600/xxhash-3.8.0-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1ec9afdd53ac5f4fd1d8918807ba6c35ba62269086af794884b9f168a73331ea", size = 43155, upload-time = "2026-06-27T08:17:22.721Z" }, - { url = "https://files.pythonhosted.org/packages/3f/5d/652c47481053fabc33ea229540bd330a45f68d7a5277f45e6cf879c29965/xxhash-3.8.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68594a54be2eb5992d9b0d0a0ec7c32a7a8e930f06d6cb951d69708055680994", size = 38137, upload-time = "2026-06-27T08:17:24.295Z" }, - { url = "https://files.pythonhosted.org/packages/7d/a5/7b6e961a03ee713cbdbaa3d2cf3ddd33453a4d4112bbde58f2f607ab64d2/xxhash-3.8.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:591d5eb256abf59438800ace2730ac33f77bc6ab8c3623fab1ea24d9d8b28f3a", size = 34376, upload-time = "2026-06-27T08:17:25.688Z" }, - { url = "https://files.pythonhosted.org/packages/da/aa/95d36393bf732df516a2dcf4fd7e9e851bc033a5970e30774b972137f4da/xxhash-3.8.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:7f4eecf800275e62b6bcb41e65f361f2277cc886c2bff4e299959d701e5fcf93", size = 32798, upload-time = "2026-06-27T08:17:27.188Z" }, + { url = "https://files.pythonhosted.org/packages/55/97/1a8cebf0a6650417f08a18231590e2515aacd5ce39c3ad8b9e013ebd437d/xxhash-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:27a9e475157f7315826118e3f3127909a0fe25f1b43d3d3be9c584f9d265f937", size = 34695, upload-time = "2026-07-06T10:43:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/2f/cf/745b9bc0dd9c341bc074b5fc700db7bbef0f3b69ab21446492296ab37e50/xxhash-3.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b2ce44bf8f4a1d01f418b3110ff8dff32fd3f3e836c0e06333c3725f243fa6c", size = 32376, upload-time = "2026-07-06T10:43:41.97Z" }, + { url = "https://files.pythonhosted.org/packages/65/a4/8512a901b1d6ad4a9838d1b40385907a879d7e005a5afbec5d39526b69f6/xxhash-3.8.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:942bc86e9be6fdd6e1175048f5fe8f8fdaaf2309dd1323ef1e155a69cd346780", size = 217470, upload-time = "2026-07-06T10:43:43.572Z" }, + { url = "https://files.pythonhosted.org/packages/a0/ad/0ffd8094ea29579bb2dc42fa74d08570e9ea3d95db561e6b1105e69b9ca6/xxhash-3.8.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0204701e6d01f64254e0e5ff4255812b1febe027ddd7dda63372e27f98b5e91f", size = 237799, upload-time = "2026-07-06T10:43:45.248Z" }, + { url = "https://files.pythonhosted.org/packages/b3/90/783c6b3f9336bd07449fe672be32cef6833633936bbfda8d3b23ee18d202/xxhash-3.8.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7dc4bdf008f77c88d544849c48c1a40faf25a5eff6cc466de2e8edc37c191fce", size = 262587, upload-time = "2026-07-06T10:43:46.733Z" }, + { url = "https://files.pythonhosted.org/packages/c4/77/ba0316a7c3e661b86830a47ae4987798616ce1b15af8d2a6358e2d89ef60/xxhash-3.8.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c566b123dce7e4867ca518434cdfb9f84e5023771235b2e3107a26c9a41cbd8", size = 238484, upload-time = "2026-07-06T10:43:48.453Z" }, + { url = "https://files.pythonhosted.org/packages/09/79/33001037c1cba90f4ced38b257161c13452024c0db44208f883e2e47f3fc/xxhash-3.8.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9f23083e1bd9d901f844af7a126727c486e7eada9a1a6791c8f7e73f94fac656", size = 469909, upload-time = "2026-07-06T10:43:50.188Z" }, + { url = "https://files.pythonhosted.org/packages/45/90/237eded9dd6ae638083294e5a9f77b317aaebd480a330806b39c192a0de1/xxhash-3.8.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64af54dd1c3a45a27c04942f9a1a4683322bdd127f4745cca4e02549c1d2d2bb", size = 217166, upload-time = "2026-07-06T10:43:51.816Z" }, + { url = "https://files.pythonhosted.org/packages/0b/6a/8cb439dc9920e1468e1c2d69ef77cbeb4be3b1ae9f4b5344c07a2b59af18/xxhash-3.8.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8ea8a141eeced4f6262ab6dd71c681ac546a558c30bb586abe087d814b5f85ea", size = 307593, upload-time = "2026-07-06T10:43:53.436Z" }, + { url = "https://files.pythonhosted.org/packages/ec/c6/c0607d373c8affea92101a3926c4fc8b026bcf8983e05fd58f3a0380ebf8/xxhash-3.8.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a98b2f95cab589e0f5e92c48431afb4d56238b8bf6668edcc66166180e9b509b", size = 234702, upload-time = "2026-07-06T10:43:55.042Z" }, + { url = "https://files.pythonhosted.org/packages/5b/cb/f4cfd456624c1f017858168b7ba9443dad810da8aac779a612658450e827/xxhash-3.8.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1b86ae798a976ccbc1d02af6ccb98f5b4d24756b1f65e995f11d10fe071f486f", size = 265749, upload-time = "2026-07-06T10:43:56.749Z" }, + { url = "https://files.pythonhosted.org/packages/33/f3/9006669c04b01206e21b2177425c649461ba188930a052c2f1728d6ec6a8/xxhash-3.8.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:81f4ed9ca9644bc95cd976bfe10f7a4cafab8ffdc3aed52877d4600e445be7ef", size = 221992, upload-time = "2026-07-06T10:43:58.12Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0b/7e6f3eaa05df5e0b6c94aa452b0672801f7031e602081f07fd441aaaaed5/xxhash-3.8.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:cb3fe820c27593f170770d6c8d791936cf6275d9269405fbb7b30a55363c10c8", size = 236899, upload-time = "2026-07-06T10:43:59.562Z" }, + { url = "https://files.pythonhosted.org/packages/da/cc/bbaee4987f3aab1d7b33bb430bb49e940646160af448b9167431c931126d/xxhash-3.8.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:7345007c12780985de4fd740148776d1eee18c0d41407c6fa1e48c5450304fe5", size = 297934, upload-time = "2026-07-06T10:44:01.132Z" }, + { url = "https://files.pythonhosted.org/packages/a7/97/6bee358660eb8b4f73c00b00b00bc616ebde00e1ab4b67c63486ce360648/xxhash-3.8.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:12eaeaa9ab8b9e6033a1fa5f6b338aaf55ff4df4bee11b59fd6ee03b19186ee4", size = 439315, upload-time = "2026-07-06T10:44:02.878Z" }, + { url = "https://files.pythonhosted.org/packages/c6/50/7e35275f39256bedace0c3cd5be3c72d4ac9d5aecf5e5fdc3530337cd263/xxhash-3.8.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2a845687219ba3214126f14a8a5861f97c9e065a7d0b8252adb6df13eea86fb", size = 214038, upload-time = "2026-07-06T10:44:04.504Z" }, + { url = "https://files.pythonhosted.org/packages/59/2d/69d02d096ee50bdf3ef0d208d874f52c71b1aa6906066bce3c52fedb8bc6/xxhash-3.8.1-cp310-cp310-win32.whl", hash = "sha256:656256c9f9303e47f07d5cb8ae4468285370adfafd7ba48aea33a458e7697626", size = 31939, upload-time = "2026-07-06T10:44:06.213Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1d/e06fca9844919ca91c6587d530cfa1e745830ec73ad38f44f04b25d1bfb7/xxhash-3.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:27cfc2f1ed76f956f36dfe0c56e5f5a3e94cd91eb78b893f63e2ef2ae404fcdf", size = 32729, upload-time = "2026-07-06T10:44:07.621Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c2/800648d99039927b5a86d8ae02cd86a556a5ee1678d388216f6b44c8966c/xxhash-3.8.1-cp310-cp310-win_arm64.whl", hash = "sha256:c85949d02c85adf6d786eb94858e124989a632a4e65739835b2fc5761827fac3", size = 29215, upload-time = "2026-07-06T10:44:08.916Z" }, + { url = "https://files.pythonhosted.org/packages/8a/5a/05eaa129555f85476a3e16ff869e95f81a78bbe4647eef9d0229f515a317/xxhash-3.8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602efcad4a42c184e81d43a2b7e6e4f524d619878f2b6ee2ba469011f47c8147", size = 34699, upload-time = "2026-07-06T10:44:10.14Z" }, + { url = "https://files.pythonhosted.org/packages/80/59/0df1133958b2228929355e022aab1e958c7b2c43e27bf7f59bc9edfa8a54/xxhash-3.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:131324f719957b988861714de7d6ddf57b47abec3b0cc691302ffeaba0e05e10", size = 32373, upload-time = "2026-07-06T10:44:11.353Z" }, + { url = "https://files.pythonhosted.org/packages/3e/bf/1cfda5b5e6bf26617812b4a31662ef2220d2ad04e0a55b8ff9eb36e56a5c/xxhash-3.8.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:db77278a6eddadbf44ce5aae2fee5ebb4d061f026b1ce2130d058cd4d7a7b670", size = 220284, upload-time = "2026-07-06T10:44:12.683Z" }, + { url = "https://files.pythonhosted.org/packages/70/93/45dc0ad7913b69e5b08bd039236cf628380e4c9cc76a8a4c6625a328e058/xxhash-3.8.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c332dd48b8cb050da2bb2a3c96d72b1664168650a250ef9718e423df7989e05", size = 240980, upload-time = "2026-07-06T10:44:14.297Z" }, + { url = "https://files.pythonhosted.org/packages/e9/02/f28ba7d17f2c1410ee397982c817ab1bd5b2701070c2d2c373539aad000a/xxhash-3.8.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a5cd96f6dcdf4fa657b2d95668d71d58455248f98712ecffaa9c528edf40ccae", size = 264526, upload-time = "2026-07-06T10:44:16.017Z" }, + { url = "https://files.pythonhosted.org/packages/5c/d0/f10651cec2c7981b20d693deae6bdfc438427d92be2db4ccabb6181f0021/xxhash-3.8.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c959f88160b13b4e730b0d75b459b7929fc0d2225c284c9683ac95d6feeeac6a", size = 241369, upload-time = "2026-07-06T10:44:17.698Z" }, + { url = "https://files.pythonhosted.org/packages/ff/40/136e0cbaf5db51e191423b1c98643593189f02b6cd90837bf64b19113d70/xxhash-3.8.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:027dee4355f3fcc41481650d846cf6cfc895c85a1ab7acd063063821a0df5b4c", size = 473186, upload-time = "2026-07-06T10:44:19.354Z" }, + { url = "https://files.pythonhosted.org/packages/4b/3f/6aa808a96bdc43dba9a740dec56c744526ee3c0019e32c75e810fa90ae4d/xxhash-3.8.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad52a0e4bcc0ba956a953a169d1feec2734a64981d689e4fc8f490f7bf91af60", size = 220092, upload-time = "2026-07-06T10:44:20.956Z" }, + { url = "https://files.pythonhosted.org/packages/47/28/a8675e78a9ced96dab853416162268e10e05b452e95db7888cf69f58ac5f/xxhash-3.8.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5d3dfb1f0ff146da7952867a9414f0c7a29762f8825a84879592612fd6139342", size = 309846, upload-time = "2026-07-06T10:44:22.543Z" }, + { url = "https://files.pythonhosted.org/packages/89/0f/7fe4d4ef4e69f0033e012396ee2a115886bca7b10b7e45ce398626436bfc/xxhash-3.8.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4482380b462ca9e59994d072a877ecadd1cf51102daeeab2db696f96ab763723", size = 237659, upload-time = "2026-07-06T10:44:24.135Z" }, + { url = "https://files.pythonhosted.org/packages/38/8f/83e9e31d4ed57fe963b99cb5b13a23e3e0f0dad1885aa0ebd2a7819dd423/xxhash-3.8.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:950ac754d16daea42038f38e7465eb84cda4d08d7343c1c915771b29470f065a", size = 268737, upload-time = "2026-07-06T10:44:25.875Z" }, + { url = "https://files.pythonhosted.org/packages/57/79/7e7de46dbe5d1f49afc96a0bc42e6b8df24eae3d6bad6007b99e42f48430/xxhash-3.8.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0418ec8b2331b9d4d575fc9284427e8e69449d7172e99e1a86fcdd1f51a0a937", size = 224955, upload-time = "2026-07-06T10:44:27.777Z" }, + { url = "https://files.pythonhosted.org/packages/ec/34/b8540839e958d5ef5c6101af6f16032109e7099698ae8edbc8dcefe4d8f4/xxhash-3.8.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:32a94ad2763e0263d9102037d349002c3d3c401e42770542c3eeb4801f311661", size = 239653, upload-time = "2026-07-06T10:44:29.422Z" }, + { url = "https://files.pythonhosted.org/packages/ce/87/a735d05f7f859354acadabe470ff40e2c46672275f96dcf096a761904def/xxhash-3.8.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:89b11a5cdd441aa463f6d34ca0241602bc09b001a76994b6059828494108c673", size = 300213, upload-time = "2026-07-06T10:44:31.401Z" }, + { url = "https://files.pythonhosted.org/packages/98/31/3e1cb020237b68117fc212dc5f9753b87f865b4dfee7c1ce62d0836955b5/xxhash-3.8.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:09a204dd4bb0823daf938cdd0dc8057d5f1e14fe3cbde929424255f23f9de872", size = 442508, upload-time = "2026-07-06T10:44:33.023Z" }, + { url = "https://files.pythonhosted.org/packages/23/bf/f80090622141cc734b039ce1d15ce3ff6dced375e9680249bf5b9b8c6bf9/xxhash-3.8.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e710ad822c493fb80a4fbc1e3d0a807b1422cb90adbe64378f98291b7fa48fef", size = 216853, upload-time = "2026-07-06T10:44:34.983Z" }, + { url = "https://files.pythonhosted.org/packages/a6/a3/60157acecc307b238d3651c2483168e224b48b23a36ae6d6903588341d80/xxhash-3.8.1-cp311-cp311-win32.whl", hash = "sha256:5013be3bea7612852c62a7437f3302c1cfb91ca7e703b194459db0b2b2e0d792", size = 31936, upload-time = "2026-07-06T10:44:36.542Z" }, + { url = "https://files.pythonhosted.org/packages/59/5c/ef70c418d878d187b8da56d4cdc06aea6cf5e456b301e96e51e1d2cc8625/xxhash-3.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:f377012b86c0a23a1df0cf5a1b05aa7187649e472f71c7892e5f2c2815bbe74f", size = 32724, upload-time = "2026-07-06T10:44:38.177Z" }, + { url = "https://files.pythonhosted.org/packages/2c/25/f008db952cec6b2a26445b456eeed2ebebd65e08e848ebe09ed6ac0634e6/xxhash-3.8.1-cp311-cp311-win_arm64.whl", hash = "sha256:836f11d4474d3228e9909d97216faa4f7505df41cfaf3927eb29809de785a78d", size = 29212, upload-time = "2026-07-06T10:44:39.577Z" }, + { url = "https://files.pythonhosted.org/packages/42/91/f65c34a7aa7b4e7cf4854f8e6ef3f7ee32ceac41d4f008da0780db0612f6/xxhash-3.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e6e49370822c1f4d8d90e678b06dbcb08b51a026a7c4b55479e7d467f2e813bc", size = 34680, upload-time = "2026-07-06T10:44:40.932Z" }, + { url = "https://files.pythonhosted.org/packages/57/04/b10a245a4c09a9cfa88f8e9ae755029413ad1ac17047f9a61906e5ae0799/xxhash-3.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:220d68130f83f7cc86d6edfdeab176adc73d7200bf3a8ec10c629e8cf605c215", size = 32397, upload-time = "2026-07-06T10:44:42.196Z" }, + { url = "https://files.pythonhosted.org/packages/3a/75/45ab795b5945b6388583bd75202106af505537935566c15a1577797a0e08/xxhash-3.8.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4d365ee1892c1fa803536f8c6ce21d24b29c9718ec75eb856095c07830f8c478", size = 220549, upload-time = "2026-07-06T10:44:43.603Z" }, + { url = "https://files.pythonhosted.org/packages/13/44/5ba2bd0a14ddf4193fc7d8ec29625f659f22c06d60b28f04bf46305d8330/xxhash-3.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:852bfe059720632e2f16a6a4745e41d20937b2bf2a42a401e2412046bb6971cc", size = 241186, upload-time = "2026-07-06T10:44:45.534Z" }, + { url = "https://files.pythonhosted.org/packages/23/32/c4147def4d1e4538b906f82731e0ba23424377fc50a7cddd03cd284c8f63/xxhash-3.8.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2f8c25a7061d952de589bd0ea0eaadee32378ff83dd6a677b267f9cd86f401f8", size = 264852, upload-time = "2026-07-06T10:44:47.199Z" }, + { url = "https://files.pythonhosted.org/packages/6c/bd/71ed14f4f0318bb7fd7b2ec51999413487fa8da8d41208e84d50d1ef0f98/xxhash-3.8.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:868a8dcaff1a84ba78038e1cef14fc88ccf84d9b4d12ea604696e0693296aa56", size = 242663, upload-time = "2026-07-06T10:44:48.846Z" }, + { url = "https://files.pythonhosted.org/packages/91/09/70af22c565a8473b3f2ae73f88e7721af281bc4a575236dbd1970c9f76f6/xxhash-3.8.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6536d8677d2fff7e64cd0b98b976df9de7aee0e69590044c2af5f51b76b7a170", size = 473510, upload-time = "2026-07-06T10:44:50.695Z" }, + { url = "https://files.pythonhosted.org/packages/18/96/34db781c8f0cf99c544ca1f2bc2e5bf55426e1eb4ca6de8ea5da56a9f352/xxhash-3.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:82c0cedd280eab2e8291270e6c04894dbc096f8159a39dcf1807429f026ca3cc", size = 220469, upload-time = "2026-07-06T10:44:52.422Z" }, + { url = "https://files.pythonhosted.org/packages/93/5f/9a184f615fa5a4dce30c01534f62946ce5a11ce40f73785cbd356ccabaa9/xxhash-3.8.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:daa86e4b68221d38e669bb236ba112d0335353829fb627c82e5909e4bbe8694c", size = 310290, upload-time = "2026-07-06T10:44:54.142Z" }, + { url = "https://files.pythonhosted.org/packages/a9/dc/9b9a9789011ee153723a5eb9e7dd7fcbae2ba9b3fe7a729249ca7c252056/xxhash-3.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2bc7113e6f2b6b3922dd61796ca9f36af09da3773898e7003038dc992fc83b8d", size = 238173, upload-time = "2026-07-06T10:44:55.693Z" }, + { url = "https://files.pythonhosted.org/packages/ec/4d/71c6005ada9dcb608a4e1902e8475ecadb5f3fbfa04e1e244d276a2d0c43/xxhash-3.8.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5eed32dad81d6ba8e62dc7b9ffa0500199385d7810a8dd9d4eafaceb8c6e20bb", size = 269026, upload-time = "2026-07-06T10:44:57.424Z" }, + { url = "https://files.pythonhosted.org/packages/2f/87/d6c036ba25dfbd9c8633be5aa86fc9474bbb9e2c68212a841d090abe7344/xxhash-3.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:83697b0ea1f10e7f5d8b26a4906fa851393c61546c63839643a2b7fe2d868061", size = 224970, upload-time = "2026-07-06T10:44:59.085Z" }, + { url = "https://files.pythonhosted.org/packages/48/62/4c1f035a41c5752aa05e195b6c904c07b94fe9061a16de61e72a6e6b135f/xxhash-3.8.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:36fc69160465ae75c6ec4ac9f781bb2aa16ae7ff869e73c26fee85fbb11b9887", size = 240820, upload-time = "2026-07-06T10:45:00.746Z" }, + { url = "https://files.pythonhosted.org/packages/da/14/d39d565069b87e86d21a2af2a31d04db79249d25aa8d5b62959056a89857/xxhash-3.8.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:445e0f5a31f2f3546ae0895d4811e159518cdc9d824c11419898d40cfadb677e", size = 300619, upload-time = "2026-07-06T10:45:02.716Z" }, + { url = "https://files.pythonhosted.org/packages/13/22/75467acc887edc8cf71c97ab1708feb3df7a88bda589b9f399765c6387d2/xxhash-3.8.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:dfe0580fbfd5e4af87d0cc52d2044f155d55ebd8c8a93568758a2ea7d8e15975", size = 443267, upload-time = "2026-07-06T10:45:04.653Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b6/1da3baa5fa6ef705e3425fddd382be7dfc4dfba2686df90a20f16e9c7b1b/xxhash-3.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:095e1323fa108be1292c54c86da3ef3c7a7dc015b105a52133973bc07a6ad11a", size = 217338, upload-time = "2026-07-06T10:45:06.304Z" }, + { url = "https://files.pythonhosted.org/packages/78/dd/b5295a9f97484e7a1c2b283a742ca45e3104991c55a1ef670dde161829ba/xxhash-3.8.1-cp312-cp312-win32.whl", hash = "sha256:bf28f55e427e0483acb1f666bd0d869b6d5e5a716680c216ad7befe3d4cfba2e", size = 31970, upload-time = "2026-07-06T10:45:07.823Z" }, + { url = "https://files.pythonhosted.org/packages/ec/31/3fa0b807d7e21515cd975e7fe5c039d52ac3e9401a96d6ad68dae6305215/xxhash-3.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:2256e80e4960ee282f63428adb349cb7f8bd8efe4db770d88eb815f4b9860724", size = 32741, upload-time = "2026-07-06T10:45:09.42Z" }, + { url = "https://files.pythonhosted.org/packages/b8/05/86feada74e239600e6875aa507afb40482a89b92700aa74a92da83bdcb77/xxhash-3.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:9df56e6df96a60590935e22373041cccc91fd55858763dcffb55bf63b3a2b396", size = 29234, upload-time = "2026-07-06T10:45:10.809Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8c/446bb782cd0d27007a917b5569a08dd73219c3e8d6e459014db104b27bdb/xxhash-3.8.1-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:3c682fcd96eb4bf64be32a4d95f96107e1588005831bd8a741b324fdda01b913", size = 38562, upload-time = "2026-07-06T10:45:12.425Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ec/c0c45627eaa6be7a5d6117423adf8f7a15b17ee74b4b17072cca5959a225/xxhash-3.8.1-cp313-cp313-android_21_x86_64.whl", hash = "sha256:036a024d8b9c01f70782e09ed98d532e76fd23f950ae7154bd950fe94e90ebec", size = 36656, upload-time = "2026-07-06T10:45:13.932Z" }, + { url = "https://files.pythonhosted.org/packages/f6/94/8324c04cc7597154caaeba6c094e01fbd2e7601d01e7a13eea9f5420e77b/xxhash-3.8.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d6a5c0bce213b23b0166fe0d35bcbbe23ce4b968f257cc7eb6fd57cb8e1e6297", size = 31169, upload-time = "2026-07-06T10:45:15.687Z" }, + { url = "https://files.pythonhosted.org/packages/40/a4/beb6bb26e1184e126dbe7a5682330214ef54dcfbf882078aa9f4b5428d42/xxhash-3.8.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:5177aa44eddaa97c6ef0cc00c6d540edb64d51781d2f8fb941612ec61a92c9ed", size = 32177, upload-time = "2026-07-06T10:45:17.035Z" }, + { url = "https://files.pythonhosted.org/packages/56/0f/fc4c92a5a528f839b34b6419b2e53c8597f2a629d5a1f5d721f65bfa1fd6/xxhash-3.8.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7801b7223db017b9c0c9ccf37e44524edb35a1544a1c032add22c061c6af0276", size = 34642, upload-time = "2026-07-06T10:45:18.39Z" }, + { url = "https://files.pythonhosted.org/packages/d4/58/edbfb141d4000767ac6a9694f8ac0763e2c2e983e65c9e31620ba56e2667/xxhash-3.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9e80238259655bf69d7bcd08226a970d7f42605f3157786bfa76dd13472d7fa0", size = 34684, upload-time = "2026-07-06T10:45:20.033Z" }, + { url = "https://files.pythonhosted.org/packages/07/3f/5072f1f0f5714186f0ac2a0b5a4929ce30d4b845e94886b6c01b6ebda0be/xxhash-3.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bcab50a389cc04d87f90092af78a6adba2ab3deca63175a3344ca83514045315", size = 32401, upload-time = "2026-07-06T10:45:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/49/c7/802ea2f9c2ed59219934d6d65c470d502b1788043eae277a52af8658bda6/xxhash-3.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a2489d3a776fa380cb8e71f54c7fda268a9baf3de9b1395093fd280f95735907", size = 220617, upload-time = "2026-07-06T10:45:23.234Z" }, + { url = "https://files.pythonhosted.org/packages/99/a8/e10488efd31fcb13fcd6acbc6e788f10c6f8e3a0cc4ae3eb89dc19c55a12/xxhash-3.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32ab1e5432690276e71192be7401b55f96db2d0eedea5d44eb1f164505669cc0", size = 241295, upload-time = "2026-07-06T10:45:25.364Z" }, + { url = "https://files.pythonhosted.org/packages/18/cc/14180b17d44892a631f8ae7323c30bfbb1328efc8209e528a480293528ac/xxhash-3.8.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b30e01a0b97a4bc3f519a4d7a82da3dc53251fb0de5eeea8660dcd4ff094c0c2", size = 264688, upload-time = "2026-07-06T10:45:27.09Z" }, + { url = "https://files.pythonhosted.org/packages/a9/72/a14019d0c5f6c41ee407a503036ae32787c91325ca218a96a9b5627be651/xxhash-3.8.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1f44275ddb0978b67a58a951501903f04d49335a91f7681c9ce122ecb8ccb329", size = 242740, upload-time = "2026-07-06T10:45:28.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/08/92550e556c6fcfcb96c6a336945eb53a431ed43120ed749636debb16c5cf/xxhash-3.8.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e3b87cbd974512c0c5fc7b469c36b2cdc9ee6d76e4ec78bccb2c7184611c49b0", size = 473599, upload-time = "2026-07-06T10:45:30.524Z" }, + { url = "https://files.pythonhosted.org/packages/29/83/e361d3c1acd1b21e1d489616de6fa4aaf843365d8179f612e3743eac20a9/xxhash-3.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98ee81b4b7f3023c9cb04a78cc67610baffcb5812d92f2096cb5a5efc6f19437", size = 220559, upload-time = "2026-07-06T10:45:32.979Z" }, + { url = "https://files.pythonhosted.org/packages/05/01/006a4243c2c2a6831827f9999f6d1c23feeef100eb023c1f886022a00bf3/xxhash-3.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2666f059a1588a99267e33605365ed89cea92f424b3522806a9f4bd8ad2e3d62", size = 310383, upload-time = "2026-07-06T10:45:35.875Z" }, + { url = "https://files.pythonhosted.org/packages/d8/20/af388e8bf9f9a0f89eeef7d2a1935d176ee1c20bc6adeda05035879379cf/xxhash-3.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0093cf7eeb91b84776e8742113afa4bdf47533d36cf719179aaaf1f56f6f8bf", size = 238228, upload-time = "2026-07-06T10:45:38.02Z" }, + { url = "https://files.pythonhosted.org/packages/63/6b/4666579a87eebd1744663c404297355fa0658617b015cedfa58810ee7036/xxhash-3.8.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3a800912a2e5e975d4128969d645c4a2a80aa886ccd6c9b1c6f44529e327e8cf", size = 269137, upload-time = "2026-07-06T10:45:39.954Z" }, + { url = "https://files.pythonhosted.org/packages/de/d3/e963a8a46f900a137d91b02144d8ea07a8f812971b138204a3b2f8b8e55c/xxhash-3.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0fe37f72a207223d22a4eddc3149d4298993385aa9daef25c039246ca5a309f3", size = 225068, upload-time = "2026-07-06T10:45:41.718Z" }, + { url = "https://files.pythonhosted.org/packages/aa/80/9d181dbcde4b0fe48375f48833a5832d4b8cd2b349b15110c92ee472d874/xxhash-3.8.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5db43f249b4be9f99ef4b967863f37094fb40e67effafb78ba4f0356b6396104", size = 240874, upload-time = "2026-07-06T10:45:43.414Z" }, + { url = "https://files.pythonhosted.org/packages/39/15/ce3ab5a1cd27ead25a5196e55a7284220f6ad6e316da494ffd900b2b600f/xxhash-3.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c4ed42965c2cd9081f011be22f69d0e65d3b6165fe7734072fd0c232840bbd4e", size = 300702, upload-time = "2026-07-06T10:45:45.135Z" }, + { url = "https://files.pythonhosted.org/packages/96/c0/2281a8ab5f2a62dbf57a23c58a01ccc1d98abf40f71193c8a81f59e759b5/xxhash-3.8.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3557bec8fcb11738a8920eeb68974bc76b75262f6947998d3147954ce0a4b893", size = 443351, upload-time = "2026-07-06T10:45:47.188Z" }, + { url = "https://files.pythonhosted.org/packages/81/2e/071a58c1a53a52d4f7a3aa0987be0c396dffd40da8204805fe1b130a81f4/xxhash-3.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:00de40f3b42240db23a82a5c682b55d7263d84a26a953240c1aee463409660e3", size = 217396, upload-time = "2026-07-06T10:45:48.925Z" }, + { url = "https://files.pythonhosted.org/packages/68/44/36ab58134badd9d3433fc7b53c4ca8d113d8e807782885628640f8297a4d/xxhash-3.8.1-cp313-cp313-win32.whl", hash = "sha256:b5196cc2574cfec572a5f3fb7cfa5ade27305ae3d06516a082132441aff4c83a", size = 31974, upload-time = "2026-07-06T10:45:50.591Z" }, + { url = "https://files.pythonhosted.org/packages/96/2a/2a0b84798448e766f7b89ceed073cb0cb5a43fc9ebbacbdea74a38de18e3/xxhash-3.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:538f5f865df6cd8c32dd63158a0e5b4f5dd08d732a7da8b7228a5a0776c8ce55", size = 32739, upload-time = "2026-07-06T10:45:52.221Z" }, + { url = "https://files.pythonhosted.org/packages/d4/60/bb51dbf7c363ff88a7cbd50b7959718219577ef44d7cf255929ffc4a2194/xxhash-3.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:a6617f30641ba0d8baa1635fbefb1dffc5165ec36d26921bd5cee13497cd937a", size = 29239, upload-time = "2026-07-06T10:45:53.714Z" }, + { url = "https://files.pythonhosted.org/packages/56/d3/827ca123c2ee5443a6aaed3c5dd199237dc2f010e2bebd7ec09ef36f3a5f/xxhash-3.8.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:bfcd82852c62a60e314670a9602de354c4460f8adad916e2e42a20860c7870bc", size = 34964, upload-time = "2026-07-06T10:45:55.535Z" }, + { url = "https://files.pythonhosted.org/packages/05/67/67ae2a3ccdeb8b8ef025d35aee9edd1d26c3abe5051d47da9286232afbf8/xxhash-3.8.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:08ea2081f5e88615fec8622a9f87fbe21b8ea58d88cfc02163ca11026ee62a92", size = 32697, upload-time = "2026-07-06T10:45:57.288Z" }, + { url = "https://files.pythonhosted.org/packages/38/5a/3d3994346e1f45493679cb5c1ffc2bf454e410e9d1e8a662d253becee91e/xxhash-3.8.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2e32855b6f9e5b18f449e59d45e3d5778bdeb660632ef2693cca267a11246c75", size = 225954, upload-time = "2026-07-06T10:45:58.897Z" }, + { url = "https://files.pythonhosted.org/packages/3f/2c/53169270309b7cd8e05504e07fe123bac053b89d00ac63617faacf0a2ec0/xxhash-3.8.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6e088bd7870775624256a0d84c2a6714afd223b2eeb56b0ca58398e52a32fda", size = 249776, upload-time = "2026-07-06T10:46:00.977Z" }, + { url = "https://files.pythonhosted.org/packages/70/e0/5c551d8d592f944506f7c5185e210255c15e672a3c6008c156a1bd9b775e/xxhash-3.8.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:72eb5ae575cc7ae2b23f6f8064a8b10f638c7149819ae9cc6d20ebd4d37a1629", size = 274776, upload-time = "2026-07-06T10:46:02.869Z" }, + { url = "https://files.pythonhosted.org/packages/a0/2a/d3a762270cee2d7bcd0e25e28c623e5f3f5c0dc637b66e3e47dd5b0bb3f0/xxhash-3.8.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d0b48cdf690a64cedf7258c3dc9506cc41fc86edd7739c40e3098952265dc068", size = 252056, upload-time = "2026-07-06T10:46:04.688Z" }, + { url = "https://files.pythonhosted.org/packages/c1/8f/b78e4373b2cb6d1c42af60ea2d7e9146ad0710b239ac7f706d5d31d5bb98/xxhash-3.8.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb9e256a357dfcede7818c6d34e70db2d6b664394803d1de4b6984d2de76c0f1", size = 482108, upload-time = "2026-07-06T10:46:06.498Z" }, + { url = "https://files.pythonhosted.org/packages/e6/0d/642d923336ea61a15f8ce64fc7e078729e6e06c3a026e517fa79b2c23b7a/xxhash-3.8.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51f71a6e2ad071e70c937e41fcb6c19f82c3f9f49831eba850ed4a106ffbb647", size = 226739, upload-time = "2026-07-06T10:46:08.598Z" }, + { url = "https://files.pythonhosted.org/packages/a6/0a/a37d6da6427d45a8d23e3ee3a0ca9c9d4a90364849c6637fe2963a755f9b/xxhash-3.8.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e4a6443968c4e8dc69967e12776776a5952c119cc1bd94168ad1c5ad667c2be1", size = 319658, upload-time = "2026-07-06T10:46:10.504Z" }, + { url = "https://files.pythonhosted.org/packages/4a/51/ebbd40da8a3f1bc53b4b7a9a87f8e28bd95c5f21bc14b8a57860cf367d1b/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:714503083a1f2065c9ad15340dd49ac8a8e948a505a705ffa1750cb951519113", size = 246059, upload-time = "2026-07-06T10:46:12.634Z" }, + { url = "https://files.pythonhosted.org/packages/24/4c/d9014030147e1f0bb26e7da47aa240dd9ec61c763c573e558111d869f8e1/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:77f74e45a1e5574bbbf80181c8027b3a4c65c2248fffbd557bd596fff13102f9", size = 275535, upload-time = "2026-07-06T10:46:14.614Z" }, + { url = "https://files.pythonhosted.org/packages/84/86/caee2db41fadcd5a25aa4323213f9afec5a8586d4e419241e3d659362bd7/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:4e0e1b0fb0259c1b75d1251ac0bb4d7ab675d36f7a6bf4ba6aa630dae94f9ffa", size = 231292, upload-time = "2026-07-06T10:46:16.452Z" }, + { url = "https://files.pythonhosted.org/packages/0b/60/f52f08bcdc904c4514ea5c25caa19e9f3214144434a6ff96dc82dc1cbddd/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:10e4393ec33633c2f05ad01869e546ad080b1a18f2650503731f153774608b31", size = 250490, upload-time = "2026-07-06T10:46:18.318Z" }, + { url = "https://files.pythonhosted.org/packages/24/a0/94dc7ae310838f250669c6ad7168e6d6fca17d49dac1053f06dc232c4a56/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:b3ba794c3d885803db6c3116686923f1ec13bc86e621e169a375282b63ea1cc6", size = 309861, upload-time = "2026-07-06T10:46:20.503Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f9/adeead7d0eb28cdfc2832544ea639ffbc6749ccde47a8e228d667459182e/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:57189a69c0891e4818853feaa521c972d22c880a001453addea015f48e3c3398", size = 448739, upload-time = "2026-07-06T10:46:22.79Z" }, + { url = "https://files.pythonhosted.org/packages/04/a4/22ec0e07db57d901c9298ae98aa3cf2be45bafded6f07c13131e85b89032/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d59e71153fe9ff85648d00e18649b07e9b22c797291abb7e27274fa06df8b838", size = 223657, upload-time = "2026-07-06T10:46:24.831Z" }, + { url = "https://files.pythonhosted.org/packages/94/32/8a9531f37b59e5a013003db7cb7414baf4ce7e0e1268e0d5947cd3d6a2df/xxhash-3.8.1-cp313-cp313t-win32.whl", hash = "sha256:5b96f0024e9840f449bd91b2d005c921a4b666055a0d1b6492463799f32aae22", size = 32377, upload-time = "2026-07-06T10:46:26.86Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ab/2ca45fd7f671de5f81fc297ef1c95080b40c86ec6be0cc6034b8f7707ac8/xxhash-3.8.1-cp313-cp313t-win_amd64.whl", hash = "sha256:37d5a56c36dcc0b9a87b814cd992598d33863ff683749de6c86081f278d5e629", size = 33274, upload-time = "2026-07-06T10:46:28.39Z" }, + { url = "https://files.pythonhosted.org/packages/5a/54/20d7163463ddb6438b73a427d1655a77a502cf9b9b0c3ada3599629d9c0a/xxhash-3.8.1-cp313-cp313t-win_arm64.whl", hash = "sha256:6696c8752aded28ff3b16f33ef28ce28fb5d209b80c206746f943199fcf5fd65", size = 29375, upload-time = "2026-07-06T10:46:29.962Z" }, + { url = "https://files.pythonhosted.org/packages/c2/8b/df2ba04f22a6cd6b39f96a6577329a8471a55c90ef8d8e2f7c102363613f/xxhash-3.8.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:9db455cb649dcfe4504d6d68a6d83a7315a99a3ca59871dc3ff840671f99adba", size = 38430, upload-time = "2026-07-06T10:46:31.496Z" }, + { url = "https://files.pythonhosted.org/packages/b2/4f/6a059e8ad3ca8deedc91dfe335b211204900895152212c03ebbe721de68b/xxhash-3.8.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:affb37f152e55b5e4494bb9d0107f7bb08515c6704fbed82d9f61214d74adc17", size = 36558, upload-time = "2026-07-06T10:46:33.078Z" }, + { url = "https://files.pythonhosted.org/packages/cb/95/40be178205acce092ae418feb20ac737b32a02c7b864926ed0717354c9f8/xxhash-3.8.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:460261045936975193bfd20549a0de1cd52a33b405cbb972f0d80940c42266cd", size = 31181, upload-time = "2026-07-06T10:46:34.793Z" }, + { url = "https://files.pythonhosted.org/packages/3f/89/2da4dbf051bafa156c0e3f12012db2b0ac3b84ff37ca1f021f6bfffcdfbb/xxhash-3.8.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:38c887aedb696ef8bca19983206d270848558cfae4a91afa6a2fb05dde58ffc5", size = 32192, upload-time = "2026-07-06T10:46:36.393Z" }, + { url = "https://files.pythonhosted.org/packages/7c/4e/e000bbae3566bc8e0be771a8a0f294aa99075e3f0bc4ef43922ebffdebc8/xxhash-3.8.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:594131ce1aad18db3689781f806db1b065cdaa04f4df36b4c038d2013aefd0bf", size = 34691, upload-time = "2026-07-06T10:46:38.1Z" }, + { url = "https://files.pythonhosted.org/packages/b4/4a/ea954aacc7d1c8711880ac2b55da94429a9b4296b151c4fc0966549ca1ee/xxhash-3.8.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:78c794b643d214f1522e7a288bcf5a2de120d26cd170516749a4009dc92722c9", size = 34807, upload-time = "2026-07-06T10:46:39.647Z" }, + { url = "https://files.pythonhosted.org/packages/ca/29/df598e738ff37558ac627264deb2e560902d9bf7f46d3bd5175c9eee593e/xxhash-3.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:af0c9fedc4a2c24e8664953882fe8185f3790b8338c9c700f76f5ad660817711", size = 32410, upload-time = "2026-07-06T10:46:41.359Z" }, + { url = "https://files.pythonhosted.org/packages/59/9c/81ab40e7d33ada0b3df5d1bc884894d15dbf4f805cd645b685e4606bb8e0/xxhash-3.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:115772daeb71b2f3b9381177017f53e6cf3f3439c840737fdabd21aba6e54920", size = 220564, upload-time = "2026-07-06T10:46:43.463Z" }, + { url = "https://files.pythonhosted.org/packages/fd/6f/62ae6f5c8606320a0e2a41c2dc8c6d91cc5d63d0f84dd9582e9543779dd8/xxhash-3.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:000435984a0469b0f822fe76f35bddea0f96a4d6521b3339a60a6428cdee1edc", size = 241462, upload-time = "2026-07-06T10:46:45.509Z" }, + { url = "https://files.pythonhosted.org/packages/15/a1/9c3a0ec6cb524396f551eddd102a76690a795494eb9784fc67542b0daa37/xxhash-3.8.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2f1c68394818e0595569c2ff3cbc1e6d5a36a434e796f5c526b987b80c8a8c62", size = 264491, upload-time = "2026-07-06T10:46:47.655Z" }, + { url = "https://files.pythonhosted.org/packages/64/f2/700a4674e4308eb59d2fdb973977e82eae231bea5044753fee5c9eec0e0c/xxhash-3.8.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:46b39976d008e2a845758650f0ff7136bca004f40da0c8798bd37ac37860154f", size = 242905, upload-time = "2026-07-06T10:46:49.857Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8a/72d9874375c8d4cbc64a8cd1d659d5695a8765c3db82efa82dc5bd9f14d0/xxhash-3.8.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d5006c65ec507a333479e76e00e2c368781f16c24ededa764763956b32a0e93e", size = 473873, upload-time = "2026-07-06T10:46:51.953Z" }, + { url = "https://files.pythonhosted.org/packages/03/f0/6db07590ed7e0a77f186ef0bcea8d52553bf1ba57833e09467a2411f0f2d/xxhash-3.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31a2649bcf1fe97cf11c79848d761df33ac46b3896942d31b640557b486ff6b", size = 220765, upload-time = "2026-07-06T10:46:55.41Z" }, + { url = "https://files.pythonhosted.org/packages/8f/10/00d12d8b8beabbf49a8bbc626fb9f40445145a8887eb41a6acfb69149ac4/xxhash-3.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8f759eed402448c2bdbb492e4fba1f20668ffe29688605ea61f0f67f9e4e386d", size = 310478, upload-time = "2026-07-06T10:46:57.729Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f9/12a82394eefb0f185d15a7f7b9f627c61c475a72dd83718436a5b84b42ac/xxhash-3.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7b5f97ecfede10d5b2870383620e2d25c8561e217c7bf9081073802b54248d2b", size = 238393, upload-time = "2026-07-06T10:46:59.87Z" }, + { url = "https://files.pythonhosted.org/packages/20/f3/53f963e320b9ce678337aa7273f39ce692ded8b99e3d22a866ec722159ab/xxhash-3.8.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1da930bbcac3e8fbe2191850e2abb57977a99348c12c4b385e1058ac1b0a9ecc", size = 268704, upload-time = "2026-07-06T10:47:01.806Z" }, + { url = "https://files.pythonhosted.org/packages/0a/50/5b5badbd87c82d9f9b5f58ac74a3f29ef08f6fc387b324b8fd482450b862/xxhash-3.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:747476436f6891b9773374ce8d48edcc8b12cb5b61b67c6fb6289633747d088f", size = 225015, upload-time = "2026-07-06T10:47:03.784Z" }, + { url = "https://files.pythonhosted.org/packages/30/93/3ca68265afe7b4e69435e08a7b6a1d9d0f2a071e889da1f8041ed00fe878/xxhash-3.8.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef09bbc2519a93cd0f95f2ceb5f7b85919dffea643278e02362bf40e3c4bed1", size = 240951, upload-time = "2026-07-06T10:47:05.816Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a6/27e19670c40f46b5e76e11f2f4713d21054804568425d870670e757172ad/xxhash-3.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:a5eed9d41995a83f3332b4e3396abb7f433cac584222bd7e305b606d8353861e", size = 300751, upload-time = "2026-07-06T10:47:07.95Z" }, + { url = "https://files.pythonhosted.org/packages/bc/fb/b33e27689959fe7ed2ae0b830af41560d65213943983afa9db3a8d481bce/xxhash-3.8.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:53f3ed9118397074ff63a79b66b7fec1c84c782eecde35c5bc94e420a971c231", size = 443480, upload-time = "2026-07-06T10:47:10Z" }, + { url = "https://files.pythonhosted.org/packages/26/60/0e0d973be5fe280753ef02fbc89349492ad6e903bf1dcb870b668f94b662/xxhash-3.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d247b34bf433c92b41689318fd25d246313cab2275a6a47e2efac178b80d6efe", size = 217657, upload-time = "2026-07-06T10:47:12.196Z" }, + { url = "https://files.pythonhosted.org/packages/ad/68/c9e3ecef4a9a417d464cb5bd200aa12f73192dee677901b9e08e0ad0d1bb/xxhash-3.8.1-cp314-cp314-win32.whl", hash = "sha256:d58ce8b6cfa9c4d2f230557f69caf7c06369e318015d0b19485095bc2c5963ab", size = 32690, upload-time = "2026-07-06T10:47:14.204Z" }, + { url = "https://files.pythonhosted.org/packages/d7/99/e9e44588c0b62837bbec5ba7927816de0afa03406b1a0b6c7a7e1d1a30a0/xxhash-3.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:6cee733fe4ccb1737e0997135283c82341e5cfa9cf214b165f9087fb663aaf4f", size = 33460, upload-time = "2026-07-06T10:47:16.021Z" }, + { url = "https://files.pythonhosted.org/packages/45/2b/64f36d86380b3657ad9031967ab814f3ef31307174650853f69c18932ebc/xxhash-3.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:58346024d47e84f7d8b3e7f5d6faa1d58acbbe49a8771497872059f58c1d8ea5", size = 30092, upload-time = "2026-07-06T10:47:17.81Z" }, + { url = "https://files.pythonhosted.org/packages/92/cb/18b64bff88c58a0ca209dc533e63cf02d7ae5aa6b1b9a9fd14e81b5dbd60/xxhash-3.8.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:01cab782f8a0a05ecad2c63d7ef10f7ab475f660e0d6419d069418c14d88de7c", size = 35024, upload-time = "2026-07-06T10:47:19.821Z" }, + { url = "https://files.pythonhosted.org/packages/af/1d/72d8a70520e5dcddb472ea0486d299da3240745a10658290cd7b5690ede2/xxhash-3.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:717b12fdc51819833704e85e6926d76981ffa3f780ef92e33ebb8b26d46bb230", size = 32697, upload-time = "2026-07-06T10:47:21.649Z" }, + { url = "https://files.pythonhosted.org/packages/9c/b8/e041f555903c56db3d0a731b3d72a6575d75e0ed868b1bd2e5176111ca44/xxhash-3.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ec55d80e9b8a519d742669e0b49e8ce9e6747be42bf3c138158b6543a9c8e489", size = 226044, upload-time = "2026-07-06T10:47:23.612Z" }, + { url = "https://files.pythonhosted.org/packages/3a/7e/5cdcf06bf6ec4b5d2ac073feb23432ec1d603fd438864cbd2c09c7cb45e1/xxhash-3.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98d8ac1129b4dd39098cffed94d1284aceb61c3aa396757ccc736ac392e4cee5", size = 249899, upload-time = "2026-07-06T10:47:25.812Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c0/eb7e059cb5e1dba11fd30d2fdf882f56e5a417a3eaa43669d43623767f45/xxhash-3.8.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3bc0fa90830df1e1277f33cc6e55de9990b83c0319fd8c7412866cfde38b025e", size = 274892, upload-time = "2026-07-06T10:47:27.931Z" }, + { url = "https://files.pythonhosted.org/packages/66/74/a600aaf7cd39957fd1510adeedb1749c1e7eb82bd632a1153d9c664c3135/xxhash-3.8.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c73b6f652f0745425aa6378319c331293b5341756262e9408ed3d45f183375e6", size = 252243, upload-time = "2026-07-06T10:47:30.288Z" }, + { url = "https://files.pythonhosted.org/packages/ad/04/78d88fa75a6763e5d09bf1b947a392a27988903381b219006f92f3c68fc8/xxhash-3.8.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6114692261eff4266386cdec0f7d87eee24e317ab397c218b7ae6a76b4c6339", size = 482191, upload-time = "2026-07-06T10:47:32.45Z" }, + { url = "https://files.pythonhosted.org/packages/7f/06/07a8aea1108d682de8791ce608cdf367d75ff4e7e57cd3c154bdc6f47b23/xxhash-3.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4df57c0b161ec1b3ed0526a67b0db0914b557e86ee8aae51887aec941b261542", size = 226877, upload-time = "2026-07-06T10:47:34.705Z" }, + { url = "https://files.pythonhosted.org/packages/ed/b5/86bade5618a524d2c06c4041aa2fe8e5749ce16e88afba60d67c1684a21f/xxhash-3.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9043877a917be88ccf230aa5667c1bd059bce80f4c2727e4defa1b29b7f48b08", size = 319794, upload-time = "2026-07-06T10:47:37.08Z" }, + { url = "https://files.pythonhosted.org/packages/23/69/9b1a2b89b1621bb740fbcb7beb512f60f99480c1bdc680c0c90e1f56ff75/xxhash-3.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:559e3cabe522231909f9de98ef06929edbd53782046bd21aae0c72db6f2a0775", size = 246202, upload-time = "2026-07-06T10:47:39.676Z" }, + { url = "https://files.pythonhosted.org/packages/08/ea/662ed6cb49f1d34078b6a3a3e0f3d29ff93fd7b5a03c0bc9ecfd9b2159c3/xxhash-3.8.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:264710bd335016f303763ce1275c6486df30bb57c2245c91b224c983d7ac39b8", size = 275628, upload-time = "2026-07-06T10:47:41.99Z" }, + { url = "https://files.pythonhosted.org/packages/13/f5/49fc9e4c6728a5a3bd8fe639199d2fa67609b3a84f938aff6e8568dd3e4f/xxhash-3.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e14800b9b10bb39d7a60ad4a310e403164d7b8988a27ae933d4e40618a44088e", size = 231390, upload-time = "2026-07-06T10:47:44.233Z" }, + { url = "https://files.pythonhosted.org/packages/64/9d/3acaf8f599c0e0b30e910a3a11ba32929da53c86dc73c7c55fe6a010b4e9/xxhash-3.8.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:ea6a3e734b0fd41b82784a400be946821900daebe610c050a5e0760838a34f99", size = 250600, upload-time = "2026-07-06T10:47:47.611Z" }, + { url = "https://files.pythonhosted.org/packages/23/64/8acab4c5ec60dbe664b5b9858fd44c2413b07e535b09556a0a5022e78aa6/xxhash-3.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:cf399fac542a1c7a4734a435b93df2c55e858c7d31abf6c1bdf46f9ae67fbfd0", size = 310032, upload-time = "2026-07-06T10:47:49.88Z" }, + { url = "https://files.pythonhosted.org/packages/56/47/a0288d7329b1fe63e2734a32d19d444a96ae2b4810f545bc61e561224917/xxhash-3.8.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:44c89d915a75c11d2547eaee9098fcd80398987c4bff2974a0497a925bf92c07", size = 448882, upload-time = "2026-07-06T10:47:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/01/e7/3071dfd3beb5c38204ce1cf56bf7749fce08de900fa92714b81d1d8ca1f2/xxhash-3.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:358650d5bda9c635da699c53adf4e8134af492ecc79c960f917eebf088bb6799", size = 223728, upload-time = "2026-07-06T10:47:55.093Z" }, + { url = "https://files.pythonhosted.org/packages/12/11/b99949f0ba2b07e9f9ffe83b9c86faa685f9080725dc21a916a607313be5/xxhash-3.8.1-cp314-cp314t-win32.whl", hash = "sha256:c240939e963653054fc7e4a17c382829cda4aa88a7daf0af841715dbded1b497", size = 33150, upload-time = "2026-07-06T10:47:57.274Z" }, + { url = "https://files.pythonhosted.org/packages/54/1c/09703eb341f8416e74e58d6c6732d4b5c46de59c942363203cb237cc95b0/xxhash-3.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:7258ee276e8772599bc19e14b36f6260306e21b637190cd7cb489a2449d48684", size = 34005, upload-time = "2026-07-06T10:47:59.434Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f9/6ed7251bb6a8af10ac73b1821c60583d2826e5b2064e45a979c935287c98/xxhash-3.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:8f454166c2ffed45636c8d501741e649851ba2f346c4eb73a64c07ac00428f20", size = 30239, upload-time = "2026-07-06T10:48:01.874Z" }, + { url = "https://files.pythonhosted.org/packages/99/e4/4d8040435aeac814fc69ba63621565fbeb19229a138e2568324a26b2a45c/xxhash-3.8.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:39c9d5b61508b0bb68f29e54546de0ed2a74943c6a18585535a7e37356f1dd12", size = 32687, upload-time = "2026-07-06T10:49:42.803Z" }, + { url = "https://files.pythonhosted.org/packages/da/6a/975f1f2318c760e5bcec109ed379713ae645d8d856c2a3b9ec5d26857087/xxhash-3.8.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:83b9130b80b216d56fdf9e87131946b353c9627930c061955a101ea82b09fed9", size = 29879, upload-time = "2026-07-06T10:49:45.172Z" }, + { url = "https://files.pythonhosted.org/packages/08/0b/40a2a55ff52cf635bfdc5eae67a772bec85b4f44c6c737f73f6f528d51d1/xxhash-3.8.1-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8304be0982130954b7fd3aad18e2c6f8ee40254bc3d2e635991c16d77c91e2bd", size = 43246, upload-time = "2026-07-06T10:49:47.905Z" }, + { url = "https://files.pythonhosted.org/packages/9c/6d/56ed2b6b200f26fb474f3fd387d95d0601efcd5bb33430c90c68924bdd77/xxhash-3.8.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b512261801b1e5fde7b6ebf2fef7977339c620cbbca88a0040ad9ad134f4d02", size = 38202, upload-time = "2026-07-06T10:49:50.59Z" }, + { url = "https://files.pythonhosted.org/packages/0d/a3/56864d895d1161a9f17502088e9c1fb7c06bde2c2efdde620d22bb7a9c43/xxhash-3.8.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49aa8692507835dcc1e8ad8021f20c74c2dc13d83b5112e87877faa2a0035b20", size = 34448, upload-time = "2026-07-06T10:49:53.242Z" }, + { url = "https://files.pythonhosted.org/packages/6b/57/5c6e0908a47f61dca96d01c8ee6fce01ed1050611eb779083ba8758fed81/xxhash-3.8.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:345b07b78e2bf583d71682aa34ae5b5fab575f7a1cb31e10263ebbc6f89f8c42", size = 32869, upload-time = "2026-07-06T10:49:55.972Z" }, ] [[package]]