Compare commits

...
Author SHA1 Message Date
ccurmeandGitHub 644815f9e5 release(langgraph): 1.2.11 (#8595) 2026-08-11 09:55:59 -04:00
ccurmeandGitHub 7d6b5790ba feat(langgraph): expose trace_policy on add_node (#8523) 2026-08-11 09:19:43 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>John Kennedyopen-swe[bot] <open-swe@users.noreply.github.com>
d56666f7fb chore(deps): bump the minor-and-patch group across 1 directory with 7 updates (#8533)
Bumps the minor-and-patch group with 7 updates in the /libs/langgraph
directory:

| Package | From | To |
| --- | --- | --- |
| [langchain-core](https://github.com/langchain-ai/langchain) | `1.4.8`
| `1.5.3` |
| [xxhash](https://github.com/ifduyue/python-xxhash) | `3.8.0` | `3.8.1`
|
| [syrupy](https://github.com/syrupy-project/syrupy) | `5.3.4` | `5.5.3`
|
| [redis](https://github.com/redis/redis-py) | `8.0.1` | `8.1.0` |
| [ruff](https://github.com/astral-sh/ruff) | `0.15.20` | `0.16.1` |
| [ty](https://github.com/astral-sh/ty) | `0.0.55` | `0.0.66` |
| [anyio](https://github.com/agronholm/anyio) | `4.14.1` | `4.14.2` |


Updates `langchain-core` from 1.4.8 to 1.5.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langchain/releases">langchain-core's
releases</a>.</em></p>
<blockquote>
<h2>langchain-core==1.5.3</h2>
<p>Changes since langchain-core==1.5.2</p>
<p>release(core): 1.5.3 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39145">#39145</a>)
fix(core): fall back to <code>LANGSMITH_API_KEY</code> for gateway (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39115">#39115</a>)</p>
<h2>langchain-core==1.5.2</h2>
<p>Changes since langchain-core==1.5.1</p>
<p>release(core): 1.5.2 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39108">#39108</a>)
fix(core): handle empty string in gateway env vars (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39107">#39107</a>)
chore: bump setuptools from 82.0.0 to 83.0.0 in /libs/core (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39032">#39032</a>)
chore: bump jupyterlab from 4.5.9 to 4.5.10 in /libs/core (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39033">#39033</a>)
chore: bump setuptools from 80.9.0 to 83.0.0 in /libs/text-splitters (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39029">#39029</a>)</p>
<h2>langchain-core==1.5.1</h2>
<p>Changes since langchain-core==1.5.0</p>
<p>release(core): 1.5.1 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39042">#39042</a>)
feat(anthropic,fireworks,openai): support langsmith gateway through env
var (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/38742">#38742</a>)
fix(core): use <code>tool_call_schema</code> cache for
<code>BaseTool</code> token counting in
<code>count_tokens_approximately</code> (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39020">#39020</a>)</p>
<h2>langchain-core==1.5.0</h2>
<p>Changes since langchain-core==1.4.9</p>
<p>release(core): 1.5.0 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/38978">#38978</a>)
feat(core): add <code>reasoning_effort</code> as a standard chat model
parameter (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/38887">#38887</a>)
chore: bump soupsieve from 2.8 to 2.8.4 in /libs/core (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/38750">#38750</a>)
chore: bump mistune from 3.2.1 to 3.3.0 in /libs/core (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/38783">#38783</a>)</p>
<h2>langchain-core==1.4.9</h2>
<p>Changes since langchain-core==1.4.8</p>
<p>release(core): 1.4.9 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/38728">#38728</a>)
fix(core): improve langsmith loader error messages (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35648">#35648</a>)
fix(core): output parser bugs in xml.py and pydantic.py (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35641">#35641</a>)
style(core): fix some ruff preview rules (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/38656">#38656</a>)
fix(core): avoid <code>dict</code> shadowing in language models (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/38480">#38480</a>)
fix(core): <code>_parse_google_docstring</code> mishandling continuation
lines with colons (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35680">#35680</a>)
fix(core): add messages to bare <code>raise ValueError</code> calls (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/38158">#38158</a>)
fix(core): use <code>asyncio.get_running_loop()</code> in async contexts
(<a
href="https://redirect.github.com/langchain-ai/langchain/issues/38157">#38157</a>)
chore: bump langsmith from 0.8.0 to 0.8.18 in /libs/core (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/38319">#38319</a>)
chore: bump jupyterlab from 4.5.7 to 4.5.9 in /libs/core (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/38326">#38326</a>)
chore: bump vcrpy from 8.1.1 to 8.2.1 in /libs/core (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/38327">#38327</a>)</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langchain/commit/01d8481ae3d457ec3751cbb0331ea3a4b720fa90"><code>01d8481</code></a>
release(core): 1.5.3 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39145">#39145</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/b8a6e36d3f810302c66065ea1c2892da000de550"><code>b8a6e36</code></a>
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39141">#39141</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/cdca311de2958e497cfd823a45ddb770d0ef94c0"><code>cdca311</code></a>
fix(core): fall back to <code>LANGSMITH_API_KEY</code> for gateway (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39115">#39115</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/a5a8762436658b218dbfc5efff9e8ab8fabbe62f"><code>a5a8762</code></a>
ci: annotate uv bump workflow with PR links (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39112">#39112</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/557c0a986d5068085a945dcb3c9ce27257c4add8"><code>557c0a9</code></a>
chore(deps): bump uv to 0.12.0 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39111">#39111</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/835c85ae236a2d33cd11de4c98d28cbc6ebbd72f"><code>835c85a</code></a>
release(anthropic): 1.5.3 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39109">#39109</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/c1ab807b1f62ad04274c28f2b7d8c3141d8ba1f2"><code>c1ab807</code></a>
release(core): 1.5.2 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39108">#39108</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/313223347f842274432d8a2968b4be407e2722d8"><code>3132233</code></a>
fix(anthropic): preserve empty thinking field in signature_delta
streaming (#...</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/b3a6d9a012681df8a8e33345c8255ca69ec0e437"><code>b3a6d9a</code></a>
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39105">#39105</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/447b51abbccde236838524ee717682a0dd2a6c09"><code>447b51a</code></a>
fix(anthropic): strip unsupported fields from system message content
blocks (...</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langchain/compare/langchain-core==1.4.8...langchain-core==1.5.3">compare
view</a></li>
</ul>
</details>
<br />

Updates `xxhash` from 3.8.0 to 3.8.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/ifduyue/python-xxhash/releases">xxhash's
releases</a>.</em></p>
<blockquote>
<h2>v3.8.1</h2>
<ul>
<li>Register the &quot;benchmark&quot; pytest mark to avoid
PytestUnknownMarkWarning</li>
<li>Update C extension docstrings and remove stale comments</li>
</ul>
<hr />
<p>Full list of changes: <a
href="https://github.com/ifduyue/python-xxhash/compare/v3.8.0...v3.8.1">https://github.com/ifduyue/python-xxhash/compare/v3.8.0...v3.8.1</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/ifduyue/python-xxhash/blob/master/CHANGELOG.rst">xxhash's
changelog</a>.</em></p>
<blockquote>
<p>v3.8.1 2026-07-06</p>
<pre><code>
- Register the &quot;benchmark&quot; pytest mark to avoid
PytestUnknownMarkWarning
- Update C extension docstrings and remove stale comments
</code></pre>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/ifduyue/python-xxhash/commit/0a1adf7b6a5f26b821272985a0a987cca5446c23"><code>0a1adf7</code></a>
Release v3.8.1</li>
<li><a
href="https://github.com/ifduyue/python-xxhash/commit/aa419ca1478219b7ba2ad18a8fe05ca9d29288bc"><code>aa419ca</code></a>
Prepare 3.8.1</li>
<li><a
href="https://github.com/ifduyue/python-xxhash/commit/e26b770f19b5eabd1c58d098206e5c9adfcef5d1"><code>e26b770</code></a>
docs: update C extension docstrings and remove stale comments</li>
<li><a
href="https://github.com/ifduyue/python-xxhash/commit/aa69707513c4ba8c52a4bebc23f0aada7844decc"><code>aa69707</code></a>
Register the “benchmark” pytest mark</li>
<li>See full diff in <a
href="https://github.com/ifduyue/python-xxhash/compare/v3.8.0...v3.8.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `syrupy` from 5.3.4 to 5.5.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/syrupy-project/syrupy/releases">syrupy's
releases</a>.</em></p>
<blockquote>
<h2>v5.5.3</h2>
<h2>What's Changed</h2>
<p>This release has no functional changes. It just fixes an issue where
the release tag pointed to the previous commit instead of the commit
with the updated pyproject.toml (see <a
href="https://redirect.github.com/syrupy-project/syrupy/issues/1144">syrupy-project/syrupy#1144</a>).</p>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/syrupy-project/syrupy/compare/v5.5.2...v5.5.3">https://github.com/syrupy-project/syrupy/compare/v5.5.2...v5.5.3</a></p>
<h2>v5.5.2</h2>
<h2>What's Changed</h2>
<ul>
<li>fix: Fix detecting <code>pytest-xdist</code> when loaded via
<code>PYTEST_PLUGINS</code> by <a
href="https://github.com/mgorny"><code>@​mgorny</code></a> in <a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1142">syrupy-project/syrupy#1142</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/mgorny"><code>@​mgorny</code></a> made
their first contribution in <a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1142">syrupy-project/syrupy#1142</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/syrupy-project/syrupy/compare/v5.5.1...v5.5.2">https://github.com/syrupy-project/syrupy/compare/v5.5.1...v5.5.2</a></p>
<h2>v5.5.1</h2>
<h2>What's Changed</h2>
<ul>
<li>fix: Defer registering <code>pytest-xdist</code> hook
<code>pytest_testnodedown</code> to avoid &quot;unknown hook&quot; error
by <a
href="https://github.com/edgarrmondragon"><code>@​edgarrmondragon</code></a>
in <a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1140">syrupy-project/syrupy#1140</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/edgarrmondragon"><code>@​edgarrmondragon</code></a>
made their first contribution in <a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1140">syrupy-project/syrupy#1140</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/syrupy-project/syrupy/compare/v5.5.0...v5.5.1">https://github.com/syrupy-project/syrupy/compare/v5.5.0...v5.5.1</a></p>
<h2>v5.5.0</h2>
<h2>What's Changed</h2>
<ul>
<li>feat: detect unused snapshots under pytest-xdist by <a
href="https://github.com/frenck"><code>@​frenck</code></a> in <a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1132">syrupy-project/syrupy#1132</a></li>
</ul>
<blockquote>
<p><strong>NOTE</strong>: Test suites using pytest-xdist that previously
passed due to incomplete snapshot support may now fail if unused
snapshots are detected. You can use <code>--snapshot-warn-unused</code>
to downgrade unused snapshot detection from an error to a warning though
it's not recommended.</p>
</blockquote>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/syrupy-project/syrupy/compare/v5.4.0...v5.5.0">https://github.com/syrupy-project/syrupy/compare/v5.4.0...v5.5.0</a></p>
<h2>v5.4.0</h2>
<h2>What's Changed</h2>
<ul>
<li>feat: add --snapshot-no-cleanup to keep unused snapshots on update
by <a href="https://github.com/frenck"><code>@​frenck</code></a> in <a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1126">syrupy-project/syrupy#1126</a></li>
<li>fix: honor boolean operators in -k snapshot selection by <a
href="https://github.com/frenck"><code>@​frenck</code></a> in <a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1127">syrupy-project/syrupy#1127</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/syrupy-project/syrupy/compare/v5.3.4...v5.4.0">https://github.com/syrupy-project/syrupy/compare/v5.3.4...v5.4.0</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/syrupy-project/syrupy/blob/main/CHANGELOG.md">syrupy's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/syrupy-project/syrupy/releases/tag/v5.5.3">v5.5.3</a>
(2026-07-11)</h2>
<h2>What's Changed</h2>
<ul>
<li>chore(deps): update astral-sh/setup-uv action to v8.3.0 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1143">syrupy-project/syrupy#1143</a></li>
<li>chore(deps): update astral-sh/setup-uv action to v8.3.1 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1145">syrupy-project/syrupy#1145</a></li>
<li>chore(deps): update dependency hypothesis to v6.156.2 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1147">syrupy-project/syrupy#1147</a></li>
<li>chore(deps): update dependency mypy to v2.2.0 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1148">syrupy-project/syrupy#1148</a></li>
<li>chore(deps): update dependency hypothesis to v6.156.3 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1150">syrupy-project/syrupy#1150</a></li>
<li>chore: closes <a
href="https://redirect.github.com/syrupy-project/syrupy/issues/1144">#1144</a>
by <a href="https://github.com/noahnu"><code>@​noahnu</code></a> in <a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1146">syrupy-project/syrupy#1146</a></li>
<li>chore(deps): update astral-sh/setup-uv action to v8.3.2 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1149">syrupy-project/syrupy#1149</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/syrupy-project/syrupy/compare/v5.5.2...v5.5.3">https://github.com/syrupy-project/syrupy/compare/v5.5.2...v5.5.3</a></p>
<h2><a
href="https://github.com/syrupy-project/syrupy/releases/tag/v5.5.2">v5.5.2</a>
(2026-07-08)</h2>
<h2>What's Changed</h2>
<ul>
<li>fix: Fix detecting <code>pytest-xdist</code> when loaded via
<code>PYTEST_PLUGINS</code> by <a
href="https://github.com/mgorny"><code>@​mgorny</code></a> in <a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1142">syrupy-project/syrupy#1142</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/mgorny"><code>@​mgorny</code></a> made
their first contribution in <a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1142">syrupy-project/syrupy#1142</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/syrupy-project/syrupy/compare/v5.5.1...v5.5.2">https://github.com/syrupy-project/syrupy/compare/v5.5.1...v5.5.2</a></p>
<h2><a
href="https://github.com/syrupy-project/syrupy/releases/tag/v5.5.1">v5.5.1</a>
(2026-07-06)</h2>
<h2>What's Changed</h2>
<ul>
<li>fix: Defer registering <code>pytest-xdist</code> hook
<code>pytest_testnodedown</code> to avoid &quot;unknown hook&quot; error
by <a
href="https://github.com/edgarrmondragon"><code>@​edgarrmondragon</code></a>
in <a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1140">syrupy-project/syrupy#1140</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/edgarrmondragon"><code>@​edgarrmondragon</code></a>
made their first contribution in <a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1140">syrupy-project/syrupy#1140</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/syrupy-project/syrupy/compare/v5.5.0...v5.5.1">https://github.com/syrupy-project/syrupy/compare/v5.5.0...v5.5.1</a></p>
<h2><a
href="https://github.com/syrupy-project/syrupy/releases/tag/v5.5.0">v5.5.0</a>
(2026-07-06)</h2>
<h2>What's Changed</h2>
<ul>
<li>feat: detect unused snapshots under pytest-xdist by <a
href="https://github.com/frenck"><code>@​frenck</code></a> in <a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1132">syrupy-project/syrupy#1132</a></li>
</ul>
<blockquote>
<p><strong>NOTE</strong>: Test suites using pytest-xdist that previously
passed due to incomplete snapshot support may now fail if unused
snapshots are detected. You can use <code>--snapshot-warn-unused</code>
to downgrade unused snapshot detection from an error to a warning though
it's not recommended.</p>
</blockquote>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/syrupy-project/syrupy/compare/v5.4.0...v5.5.0">https://github.com/syrupy-project/syrupy/compare/v5.4.0...v5.5.0</a></p>
<h2><a
href="https://github.com/syrupy-project/syrupy/releases/tag/v5.4.0">v5.4.0</a>
(2026-07-01)</h2>
<h2>What's Changed</h2>
<ul>
<li>feat: add --snapshot-no-cleanup to keep unused snapshots on update
by <a href="https://github.com/frenck"><code>@​frenck</code></a> in <a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1126">syrupy-project/syrupy#1126</a></li>
<li>fix: honor boolean operators in -k snapshot selection by <a
href="https://github.com/frenck"><code>@​frenck</code></a> in <a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1127">syrupy-project/syrupy#1127</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/syrupy-project/syrupy/commit/3cd347306cf44db076d2e4f50f7dfd325d9b5a92"><code>3cd3473</code></a>
chore(release): 5.5.3 [skip ci]</li>
<li><a
href="https://github.com/syrupy-project/syrupy/commit/fed7e9f69f4dd14b39360e626236c868147588d5"><code>fed7e9f</code></a>
chore(deps): update astral-sh/setup-uv action to v8.3.2 (<a
href="https://redirect.github.com/syrupy-project/syrupy/issues/1149">#1149</a>)</li>
<li><a
href="https://github.com/syrupy-project/syrupy/commit/443fc11bf67ce588753cd4518ef056463cfe651b"><code>443fc11</code></a>
chore: closes <a
href="https://redirect.github.com/syrupy-project/syrupy/issues/1144">#1144</a>
(<a
href="https://redirect.github.com/syrupy-project/syrupy/issues/1146">#1146</a>)</li>
<li><a
href="https://github.com/syrupy-project/syrupy/commit/56bf157ce4135bde78ecc49cfa9532ffa41659e8"><code>56bf157</code></a>
chore(deps): update dependency hypothesis to v6.156.3 (<a
href="https://redirect.github.com/syrupy-project/syrupy/issues/1150">#1150</a>)</li>
<li><a
href="https://github.com/syrupy-project/syrupy/commit/f9c725a5653677bfcafb53645c9a078f3aa79d87"><code>f9c725a</code></a>
chore(deps): update dependency mypy to v2.2.0 (<a
href="https://redirect.github.com/syrupy-project/syrupy/issues/1148">#1148</a>)</li>
<li><a
href="https://github.com/syrupy-project/syrupy/commit/3af2ccf2feeadb3a0d4e3f418d22ad3395725c80"><code>3af2ccf</code></a>
chore(deps): update dependency hypothesis to v6.156.2 (<a
href="https://redirect.github.com/syrupy-project/syrupy/issues/1147">#1147</a>)</li>
<li><a
href="https://github.com/syrupy-project/syrupy/commit/feaa22ca4c211dfcdd8d0d048112531fd6cb344c"><code>feaa22c</code></a>
chore(deps): update astral-sh/setup-uv action to v8.3.1 (<a
href="https://redirect.github.com/syrupy-project/syrupy/issues/1145">#1145</a>)</li>
<li><a
href="https://github.com/syrupy-project/syrupy/commit/5c59701e411922a464497398f5df25d76f2f9dcd"><code>5c59701</code></a>
chore(deps): update astral-sh/setup-uv action to v8.3.0 (<a
href="https://redirect.github.com/syrupy-project/syrupy/issues/1143">#1143</a>)</li>
<li><a
href="https://github.com/syrupy-project/syrupy/commit/1cbf7167ff114e3012b1c139661777c1a0b692ee"><code>1cbf716</code></a>
chore(release): 5.5.2 [skip ci]</li>
<li><a
href="https://github.com/syrupy-project/syrupy/commit/4f83490d2ebf4ae22aeba6d9946979640151aa34"><code>4f83490</code></a>
fix: Fix detecting <code>pytest-xdist</code> when loaded via
<code>PYTEST_PLUGINS</code> (<a
href="https://redirect.github.com/syrupy-project/syrupy/issues/1142">#1142</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/syrupy-project/syrupy/compare/v5.3.4...v5.5.3">compare
view</a></li>
</ul>
</details>
<br />

Updates `redis` from 8.0.1 to 8.1.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/redis/redis-py/releases">redis's
releases</a>.</em></p>
<blockquote>
<h2>8.1.0</h2>
<h1>Changes</h1>
<h2> Highlights</h2>
<h3>Async maintenance notifications</h3>
<p>redis-py now supports server-pushed <strong>maintenance
notifications</strong> in the asyncio stack for both
standalone and cluster clients, bringing the async client to parity with
the sync implementation.
When a Redis deployment signals maintenance events (such as node
migration or failover windows), the
async client reacts through the maintenance-notifications handler
(<code>redis/maint_notifications.py</code> and
its async integration under <code>redis/asyncio/</code>), allowing
applications to adapt connection handling
during maintenance without downtime. (<a
href="https://redirect.github.com/redis/redis-py/issues/4177">#4177</a>)</p>
<h3>Expanded command coverage</h3>
<p>This release adds a batch of new command surfaces across core data
types and modules:</p>
<ul>
<li><strong>Lists</strong> — <code>LMOVEM</code> / <code>BLMOVEM</code>
for moving multiple elements between lists (<a
href="https://redirect.github.com/redis/redis-py/issues/4174">#4174</a>).</li>
<li><strong>Sets</strong> — <code>SDIFFCARD</code> and
<code>SUNIONCARD</code> cardinality commands (<a
href="https://redirect.github.com/redis/redis-py/issues/4171">#4171</a>).</li>
<li><strong>Streams</strong> — <code>MAXCOUNT</code> /
<code>MAXSIZE</code> options for <code>XREAD</code> and
<code>XREADGROUP</code> (<a
href="https://redirect.github.com/redis/redis-py/issues/4173">#4173</a>).</li>
<li><strong>Time Series</strong> — <code>TS.READ</code> (<a
href="https://redirect.github.com/redis/redis-py/issues/4170">#4170</a>),
<code>TS.QUERYLABELS</code> (<a
href="https://redirect.github.com/redis/redis-py/issues/4197">#4197</a>),
<code>TS.NRANGE</code> / <code>TS.NREVRANGE</code> (<a
href="https://redirect.github.com/redis/redis-py/issues/4163">#4163</a>),
and an <code>exclude_empty</code> (<code>EXCLUDEEMPTY</code>) option for
<code>TS.MRANGE</code> / <code>TS.MREVRANGE</code> (<a
href="https://redirect.github.com/redis/redis-py/issues/4188">#4188</a>).</li>
<li><strong>Search</strong> — <code>FT.ALIASLIST</code> (<a
href="https://redirect.github.com/redis/redis-py/issues/4198">#4198</a>)
and a <code>COLLECT</code> reducer for aggregations (<a
href="https://redirect.github.com/redis/redis-py/issues/4179">#4179</a>).</li>
<li><strong>Sentinel</strong> — replica sentinel aliases (<a
href="https://redirect.github.com/redis/redis-py/issues/4127">#4127</a>).</li>
</ul>
<h2>🚀 New Features</h2>
<ul>
<li>Add replica sentinel aliases (<a
href="https://redirect.github.com/redis/redis-py/issues/4127">#4127</a>)</li>
<li>Add TS.NRANGE and TS.NREVRANGE support to the timeseries command
surface (<a
href="https://redirect.github.com/redis/redis-py/issues/4163">#4163</a>)</li>
<li>feat: add TS.READ command support to the timeseries module (<a
href="https://redirect.github.com/redis/redis-py/issues/4170">#4170</a>)</li>
<li>feat: add SDIFFCARD and SUNIONCARD command support (<a
href="https://redirect.github.com/redis/redis-py/issues/4171">#4171</a>)</li>
<li>feat: add MAXCOUNT/MAXSIZE support to XREAD and XREADGROUP (<a
href="https://redirect.github.com/redis/redis-py/issues/4173">#4173</a>)</li>
<li>feat: add LMOVEM and BLMOVEM commands for moving multiple list
elements (<a
href="https://redirect.github.com/redis/redis-py/issues/4174">#4174</a>)</li>
<li>feat: add async maintenance-notifications support for standalone and
cluster clients (<a
href="https://redirect.github.com/redis/redis-py/issues/4177">#4177</a>)</li>
<li>feat: add COLLECT reducer support to search aggregations (<a
href="https://redirect.github.com/redis/redis-py/issues/4179">#4179</a>)</li>
<li>feat: add exclude_empty (EXCLUDEEMPTY) option to TS.MRANGE and
TS.MREVRANGE (<a
href="https://redirect.github.com/redis/redis-py/issues/4188">#4188</a>)</li>
<li>feat: add TS.QUERYLABELS support to the timeseries command surface
(<a
href="https://redirect.github.com/redis/redis-py/issues/4197">#4197</a>)</li>
<li>feat: add FT.ALIASLIST support (<a
href="https://redirect.github.com/redis/redis-py/issues/4198">#4198</a>)</li>
</ul>
<h2>⚠️ Experimental</h2>
<ul>
<li>feat: add client-side HIMPORT fieldset support for standalone and
cluster clients (<a
href="https://redirect.github.com/redis/redis-py/issues/4205">#4205</a>)</li>
</ul>
<p>The feature considered unstable and <strong>public API might be
changed in the future minor version</strong></p>
<h2>🐛 Bug Fixes</h2>
<ul>
<li>fix: detect closed pooled connection without consuming pending push
data (RESP3 + hiredis) (<a
href="https://redirect.github.com/redis/redis-py/issues/4156">#4156</a>)</li>
<li>Fix Sentinel pool capacity loss after failover (<a
href="https://redirect.github.com/redis/redis-py/issues/4193">#4193</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/redis/redis-py/commit/e013126df18e476fbb8b8215f040f39bd3242683"><code>e013126</code></a>
Testing with 8.10 GA (<a
href="https://redirect.github.com/redis/redis-py/issues/4227">#4227</a>)</li>
<li><a
href="https://github.com/redis/redis-py/commit/059d1e97d88dd7ed516628e0a9765ab8e0e299f1"><code>059d1e9</code></a>
Fixed TS.NRANGE commands to correctly apply aggregators (<a
href="https://redirect.github.com/redis/redis-py/issues/4225">#4225</a>)</li>
<li><a
href="https://github.com/redis/redis-py/commit/d486a0a982dcdf316006474dc286ca65cbec5a9f"><code>d486a0a</code></a>
Fix FIELDNAME alias dropping first character of un-prefixed fields (<a
href="https://redirect.github.com/redis/redis-py/issues/4224">#4224</a>)</li>
<li><a
href="https://github.com/redis/redis-py/commit/fb931042420baaa5fbb9021955e444f7afa66dc2"><code>fb93104</code></a>
feat: add client-side HIMPORT fieldset support for standalone and
cluster cli...</li>
<li><a
href="https://github.com/redis/redis-py/commit/9197609f48eb9713411a50534ca48210486a4bf9"><code>9197609</code></a>
Fix lat/lon swap in search querystring geo() helper (<a
href="https://redirect.github.com/redis/redis-py/issues/4223">#4223</a>)</li>
<li><a
href="https://github.com/redis/redis-py/commit/14714a0812d3ff03ad34c89ce6f79e287a04e9fb"><code>14714a0</code></a>
fix: Fixed double decoding issue with unquote() (<a
href="https://redirect.github.com/redis/redis-py/issues/4222">#4222</a>)</li>
<li><a
href="https://github.com/redis/redis-py/commit/227280a939718d09ff9d7c41f9785ffdc21bdc2a"><code>227280a</code></a>
Update lib version to 8.1.0</li>
<li><a
href="https://github.com/redis/redis-py/commit/88d16d0db10c852d1d292628b2c832f1ed117056"><code>88d16d0</code></a>
Decode ACL LOG string values on the default RESP3 legacy callback (<a
href="https://redirect.github.com/redis/redis-py/issues/4201">#4201</a>)</li>
<li><a
href="https://github.com/redis/redis-py/commit/599fd75ca7d0745928280ff3a64ccd224dbf2b76"><code>599fd75</code></a>
feat: add FT.ALIASLIST support (<a
href="https://redirect.github.com/redis/redis-py/issues/4198">#4198</a>)</li>
<li><a
href="https://github.com/redis/redis-py/commit/71e275e6eae94d202b49bccce10ef0d2a59050e7"><code>71e275e</code></a>
test: add VectorField RERANK serialization tests for sync and async
search (#...</li>
<li>Additional commits viewable in <a
href="https://github.com/redis/redis-py/compare/v8.0.1...v8.1.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `ruff` from 0.15.20 to 0.16.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/releases">ruff's
releases</a>.</em></p>
<blockquote>
<h2>0.16.1</h2>
<h2>Release Notes</h2>
<p>Released on 2026-07-30.</p>
<h3>Preview features</h3>
<ul>
<li>Add an option to opt out of human-readable names (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27160">#27160</a>)</li>
<li>[<code>flake8-pytest-style</code>] Make fixes safe by default and
unsafe only when comments are present (<code>PT018</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27201">#27201</a>)</li>
<li>[<code>pyupgrade</code>] Skip fix when a defaulted
<code>TypeVar</code> precedes a non-defaulted one (<code>UP040</code>,
<code>UP046</code>, <code>UP047</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27133">#27133</a>)</li>
<li>[<code>ruff</code>] Fix false positive with unpacked arguments
(<code>RUF065</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26959">#26959</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Bump <code>gen-lsp-types</code> to gracefully handle unknown
enumeration values in LSP messages (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27230">#27230</a>)</li>
<li>[<code>flake8-bugbear</code>] Mark <code>range</code> as immutable
(<code>B008</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27247">#27247</a>)</li>
<li>[<code>flake8-comprehensions</code>] NFKC-normalize keyword names in
<code>C408</code> fix (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26813">#26813</a>)</li>
<li>[<code>flake8-return</code>] Fix false positive when variable is
read in <code>finally</code> clause (<code>RET504</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25441">#25441</a>)</li>
<li>[<code>pydocstyle</code>] Skip section detection inside RST
directive bodies (<code>D214</code>, <code>D405</code>,
<code>D413</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23635">#23635</a>)</li>
<li>[<code>refurb</code>] Parenthesize <code>yield</code> arguments in
the <code>FURB192</code> fix (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27192">#27192</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>flake8-pytest-style</code>] Mark <code>PT022</code> fixes as
unsafe (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26440">#26440</a>)</li>
<li>[<code>refurb</code>] Mark fixes that remove unknown separators as
unsafe (<code>FURB105</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27200">#27200</a>)</li>
</ul>
<h3>Server</h3>
<ul>
<li>Fix indexing of excluded nested Ruff workspaces (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27303">#27303</a>)</li>
<li>Lint TOML files in the LSP (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26862">#26862</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Cover <code>pycon</code> Markdown formatting (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27153">#27153</a>)</li>
<li>[<code>flake8-bandit</code>] Document <code>TYPE_CHECKING</code>
exception (<code>S101</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27004">#27004</a>)</li>
<li>[<code>flake8-import-conventions</code>] Document that
<code>extend-aliases</code> can override default aliases (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27191">#27191</a>)</li>
<li>[<code>pylint</code>] Add missing fix safety gotchas for
<code>non-augmented-assignment</code> (<code>PLR6104</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27250">#27250</a>)</li>
</ul>
<h3>Other changes</h3>
<ul>
<li>Reduce syntax error noise by swallowing dedents like indents (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27170">#27170</a>)</li>
<li>Vendor latest annotate-snippets (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27033">#27033</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/bxff"><code>@​bxff</code></a></li>
<li><a
href="https://github.com/anishgirianish"><code>@​anishgirianish</code></a></li>
<li><a href="https://github.com/Avasam"><code>@​Avasam</code></a></li>
<li><a href="https://github.com/epage"><code>@​epage</code></a></li>
<li><a href="https://github.com/LHMQ878"><code>@​LHMQ878</code></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md">ruff's
changelog</a>.</em></p>
<blockquote>
<h2>0.16.1</h2>
<p>Released on 2026-07-30.</p>
<h3>Preview features</h3>
<ul>
<li>Add an option to opt out of human-readable names (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27160">#27160</a>)</li>
<li>[<code>flake8-pytest-style</code>] Make fixes safe by default and
unsafe only when comments are present (<code>PT018</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27201">#27201</a>)</li>
<li>[<code>pyupgrade</code>] Skip fix when a defaulted
<code>TypeVar</code> precedes a non-defaulted one (<code>UP040</code>,
<code>UP046</code>, <code>UP047</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27133">#27133</a>)</li>
<li>[<code>ruff</code>] Fix false positive with unpacked arguments
(<code>RUF065</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26959">#26959</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Bump <code>gen-lsp-types</code> to gracefully handle unknown
enumeration values in LSP messages (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27230">#27230</a>)</li>
<li>[<code>flake8-bugbear</code>] Mark <code>range</code> as immutable
(<code>B008</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27247">#27247</a>)</li>
<li>[<code>flake8-comprehensions</code>] NFKC-normalize keyword names in
<code>C408</code> fix (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26813">#26813</a>)</li>
<li>[<code>flake8-return</code>] Fix false positive when variable is
read in <code>finally</code> clause (<code>RET504</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25441">#25441</a>)</li>
<li>[<code>pydocstyle</code>] Skip section detection inside RST
directive bodies (<code>D214</code>, <code>D405</code>,
<code>D413</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23635">#23635</a>)</li>
<li>[<code>refurb</code>] Parenthesize <code>yield</code> arguments in
the <code>FURB192</code> fix (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27192">#27192</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>flake8-pytest-style</code>] Mark <code>PT022</code> fixes as
unsafe (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26440">#26440</a>)</li>
<li>[<code>refurb</code>] Mark fixes that remove unknown separators as
unsafe (<code>FURB105</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27200">#27200</a>)</li>
</ul>
<h3>Server</h3>
<ul>
<li>Fix indexing of excluded nested Ruff workspaces (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27303">#27303</a>)</li>
<li>Lint TOML files in the LSP (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26862">#26862</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Cover <code>pycon</code> Markdown formatting (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27153">#27153</a>)</li>
<li>[<code>flake8-bandit</code>] Document <code>TYPE_CHECKING</code>
exception (<code>S101</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27004">#27004</a>)</li>
<li>[<code>flake8-import-conventions</code>] Document that
<code>extend-aliases</code> can override default aliases (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27191">#27191</a>)</li>
<li>[<code>pylint</code>] Add missing fix safety gotchas for
<code>non-augmented-assignment</code> (<code>PLR6104</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27250">#27250</a>)</li>
</ul>
<h3>Other changes</h3>
<ul>
<li>Reduce syntax error noise by swallowing dedents like indents (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27170">#27170</a>)</li>
<li>Vendor latest annotate-snippets (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27033">#27033</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/bxff"><code>@​bxff</code></a></li>
<li><a
href="https://github.com/anishgirianish"><code>@​anishgirianish</code></a></li>
<li><a href="https://github.com/Avasam"><code>@​Avasam</code></a></li>
<li><a href="https://github.com/epage"><code>@​epage</code></a></li>
<li><a href="https://github.com/LHMQ878"><code>@​LHMQ878</code></a></li>
<li><a
href="https://github.com/MichaReiser"><code>@​MichaReiser</code></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/ruff/commit/80790b348b5188e7fc253665540f442c6ec7dd05"><code>80790b3</code></a>
Bump 0.16.1 (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27330">#27330</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/63830f3e97b56ca3be0dd8f1092f76c4acc63213"><code>63830f3</code></a>
[ty] Borrow from constraint set storage less often (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27328">#27328</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/f40dca98a7f1b6fff0cee5ecf1e6364d5a8bdc15"><code>f40dca9</code></a>
[ty] Preserve forwarded expanded-variadic diagnostic sources (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27266">#27266</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/0d804975a2645d6e4a795cf501fd764b88aa47e3"><code>0d80497</code></a>
Lint TOML files in the LSP (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26862">#26862</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/d91586bd5b30f77f6614d57b73fc7dbea9051a0e"><code>d91586b</code></a>
Update prek dependencies (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27293">#27293</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/7da4b8b8d78fd6df2b3e06d8466d9cd49822900d"><code>7da4b8b</code></a>
[ty] Respect bounds and constraints in generic materializations (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27228">#27228</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/b20daf741241a6829ff8963a203f7847561deb62"><code>b20daf7</code></a>
[ty] refactor: add helper function to send partial results (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27249">#27249</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/4d4c8fa1c75b00561ea24eefefb13eb5ff80e01f"><code>4d4c8fa</code></a>
[ty] Emit diagnostic when specializing a non-generic class (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26883">#26883</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/7c3e2db97deb5c5edd6e4303403ef6caf2bde8be"><code>7c3e2db</code></a>
[ty] Fix enum class container assignability (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27318">#27318</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/d5ef97fcd03e108f7510f84b8ed85bb4051311fe"><code>d5ef97f</code></a>
[<code>flake8-return</code>] Fix false positive when variable is read in
<code>finally</code> claus...</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/ruff/compare/0.15.20...0.16.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `ty` from 0.0.55 to 0.0.66
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ty/releases">ty's
releases</a>.</em></p>
<blockquote>
<h2>0.0.66</h2>
<h2>Release Notes</h2>
<p>Released on 2026-08-03.</p>
<h3>LSP server</h3>
<ul>
<li>Index <code>with</code>-statement targets as symbols (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27256">#27256</a>)</li>
<li>Trigger signature help after completing callable with parentheses
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/27084">#27084</a>)</li>
</ul>
<h3>Library support</h3>
<ul>
<li>Pydantic: Recognize models installed on extra search paths (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27429">#27429</a>)</li>
</ul>
<h3>Diagnostics</h3>
<ul>
<li>Detect <code>__aenter__</code> and <code>__aexit__</code> that do
not return awaitables (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27414">#27414</a>)</li>
<li>Diagnose dataclass fields after inherited defaults (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27327">#27327</a>)</li>
<li>Emit diagnostic when specializing a non-generic class (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26883">#26883</a>)</li>
<li>Improve error context for incompatible callable signatures (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27422">#27422</a>)</li>
<li>Preserve forwarded expanded-variadic diagnostic sources (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27266">#27266</a>)</li>
</ul>
<h3>Core type checking</h3>
<ul>
<li>Avoid over-materializing user-defined <code>TypeIs</code> return
types (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26864">#26864</a>)</li>
<li>Fix enum class container assignability (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27318">#27318</a>)</li>
<li>Hide stub-only helpers from implicit builtin lookup (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27423">#27423</a>)</li>
<li>Move <code>Unknown</code> out of the experimental
<code>ty_extensions</code> API (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27430">#27430</a>)</li>
<li>Preserve <code>TypeVarTuple</code> context during
<code>Generic</code> recovery (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27381">#27381</a>)</li>
<li>Preserve exact numeric types in covariant collections (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27311">#27311</a>)</li>
<li>Reject <code>ClassVar</code> and <code>Final</code> qualifiers in
<code>NamedTuple</code> fields (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27380">#27380</a>)</li>
<li>Reject out-of-scope <code>ParamSpec</code> components (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27378">#27378</a>)</li>
<li>Reject specializing non-generic subclasses (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27377">#27377</a>)</li>
<li>Respect bounds and constraints in generic materializations (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27228">#27228</a>)</li>
<li>Sync vendored typeshed stubs (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27401">#27401</a>).
<a
href="https://github.com/python/typeshed/compare/b00c387c669cb50d5d388d77b74c2e832e147fe8...1b116673774d062a4af7b0a0b3d05533a6be55d0">Typeshed
diff</a></li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/dhruvmanila"><code>@​dhruvmanila</code></a></li>
<li><a href="https://github.com/carljm"><code>@​carljm</code></a></li>
<li><a
href="https://github.com/Punisheroot"><code>@​Punisheroot</code></a></li>
<li><a
href="https://github.com/AlexWaygood"><code>@​AlexWaygood</code></a></li>
<li><a
href="https://github.com/cheparity"><code>@​cheparity</code></a></li>
<li><a
href="https://github.com/BitWeaverDev"><code>@​BitWeaverDev</code></a></li>
<li><a
href="https://github.com/charliermarsh"><code>@​charliermarsh</code></a></li>
<li><a href="https://github.com/ribru17"><code>@​ribru17</code></a></li>
<li><a href="https://github.com/sharkdp"><code>@​sharkdp</code></a></li>
</ul>
<h2>Install ty 0.0.66</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ty/blob/main/CHANGELOG.md">ty's
changelog</a>.</em></p>
<blockquote>
<h2>0.0.66</h2>
<p>Released on 2026-08-03.</p>
<h3>LSP server</h3>
<ul>
<li>Index <code>with</code>-statement targets as symbols (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27256">#27256</a>)</li>
<li>Trigger signature help after completing callable with parentheses
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/27084">#27084</a>)</li>
</ul>
<h3>Library support</h3>
<ul>
<li>Pydantic: Recognize models installed on extra search paths (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27429">#27429</a>)</li>
</ul>
<h3>Diagnostics</h3>
<ul>
<li>Detect <code>__aenter__</code> and <code>__aexit__</code> that do
not return awaitables (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27414">#27414</a>)</li>
<li>Diagnose dataclass fields after inherited defaults (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27327">#27327</a>)</li>
<li>Emit diagnostic when specializing a non-generic class (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26883">#26883</a>)</li>
<li>Improve error context for incompatible callable signatures (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27422">#27422</a>)</li>
<li>Preserve forwarded expanded-variadic diagnostic sources (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27266">#27266</a>)</li>
</ul>
<h3>Core type checking</h3>
<ul>
<li>Avoid over-materializing user-defined <code>TypeIs</code> return
types (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26864">#26864</a>)</li>
<li>Fix enum class container assignability (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27318">#27318</a>)</li>
<li>Hide stub-only helpers from implicit builtin lookup (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27423">#27423</a>)</li>
<li>Move <code>Unknown</code> out of the experimental
<code>ty_extensions</code> API (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27430">#27430</a>)</li>
<li>Preserve <code>TypeVarTuple</code> context during
<code>Generic</code> recovery (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27381">#27381</a>)</li>
<li>Preserve exact numeric types in covariant collections (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27311">#27311</a>)</li>
<li>Reject <code>ClassVar</code> and <code>Final</code> qualifiers in
<code>NamedTuple</code> fields (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27380">#27380</a>)</li>
<li>Reject out-of-scope <code>ParamSpec</code> components (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27378">#27378</a>)</li>
<li>Reject specializing non-generic subclasses (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27377">#27377</a>)</li>
<li>Respect bounds and constraints in generic materializations (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27228">#27228</a>)</li>
<li>Sync vendored typeshed stubs (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27401">#27401</a>).
<a
href="https://github.com/python/typeshed/compare/b00c387c669cb50d5d388d77b74c2e832e147fe8...1b116673774d062a4af7b0a0b3d05533a6be55d0">Typeshed
diff</a></li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/dhruvmanila"><code>@​dhruvmanila</code></a></li>
<li><a href="https://github.com/carljm"><code>@​carljm</code></a></li>
<li><a
href="https://github.com/Punisheroot"><code>@​Punisheroot</code></a></li>
<li><a
href="https://github.com/AlexWaygood"><code>@​AlexWaygood</code></a></li>
<li><a
href="https://github.com/cheparity"><code>@​cheparity</code></a></li>
<li><a
href="https://github.com/BitWeaverDev"><code>@​BitWeaverDev</code></a></li>
<li><a
href="https://github.com/charliermarsh"><code>@​charliermarsh</code></a></li>
<li><a href="https://github.com/ribru17"><code>@​ribru17</code></a></li>
<li><a href="https://github.com/sharkdp"><code>@​sharkdp</code></a></li>
</ul>
<h2>0.0.65</h2>
<p>Released on 2026-07-29.</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/ty/commit/3e28e58af75c3b16403c05ab401ae7347b65c5e2"><code>3e28e58</code></a>
Bump version to 0.0.66 (<a
href="https://redirect.github.com/astral-sh/ty/issues/4166">#4166</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/87de836dfef2d3e1990440752e30e24cfb572f4e"><code>87de836</code></a>
Bump version to 0.0.65 (<a
href="https://redirect.github.com/astral-sh/ty/issues/4113">#4113</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/d784bad352f4640e7f788eac4dfb0353ca199902"><code>d784bad</code></a>
README: Add link to Stable milestone (<a
href="https://redirect.github.com/astral-sh/ty/issues/4112">#4112</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/b4c1978dbdf18501ed03139e33ee8afaef251ec3"><code>b4c1978</code></a>
Update actions/setup-python action to v7 (<a
href="https://redirect.github.com/astral-sh/ty/issues/4110">#4110</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/8d216a39dad99c4443396a08a77da00502cd7e58"><code>8d216a3</code></a>
Update actions/checkout action to v7.0.1 (<a
href="https://redirect.github.com/astral-sh/ty/issues/4107">#4107</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/06f44e9039cb2764346c4bceaff4021ac143de9e"><code>06f44e9</code></a>
Update dependency prek to v0.4.10 (<a
href="https://redirect.github.com/astral-sh/ty/issues/4108">#4108</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/b3665fde4804424576a30078bc0b7c101c5e2242"><code>b3665fd</code></a>
Update prek dependencies (<a
href="https://redirect.github.com/astral-sh/ty/issues/4109">#4109</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/5e64a131b436a4e1f40e6317c526f4fc73fab38b"><code>5e64a13</code></a>
Bump version to 0.0.64 (<a
href="https://redirect.github.com/astral-sh/ty/issues/4097">#4097</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/46f4915e67aad965b1b4e7478a75378e8e52ff6d"><code>46f4915</code></a>
Bump version to 0.0.63 (<a
href="https://redirect.github.com/astral-sh/ty/issues/4071">#4071</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/df75992e7fd0cc73cb0f66e864df3f6e4fc4e1a6"><code>df75992</code></a>
Bump version to 0.0.62 (<a
href="https://redirect.github.com/astral-sh/ty/issues/4057">#4057</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/ty/compare/0.0.55...0.0.66">compare
view</a></li>
</ul>
</details>
<br />

Updates `anyio` from 4.14.1 to 4.14.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/agronholm/anyio/releases">anyio's
releases</a>.</em></p>
<blockquote>
<h2>4.14.2</h2>
<ul>
<li>Changed <code>ByteReceiveStream.receive()</code> implementations to
raise a <code>ValueError</code> when <code>max_bytes</code> is not a
positive integer (<a
href="https://redirect.github.com/agronholm/anyio/pull/1191">#1191</a>)</li>
<li>Fixed <code>CapacityLimiter.total_tokens</code> rejecting
<code>float(&quot;inf&quot;)</code> when the limiter was instantiated
outside of an event loop. The adapter setter checked for infinity by
identity (<code>value is math.inf</code>), so only the exact
<code>math.inf</code> singleton was accepted, while every backend setter
(using <code>math.isinf()</code>) accepts any positive infinity (<a
href="https://redirect.github.com/agronholm/anyio/pull/1189">#1189</a>;
PR by <a
href="https://github.com/greymoth-jp"><code>@​greymoth-jp</code></a>).</li>
<li>Fixed <code>to_process.run_sync()</code> deadlocking when the worker
function writes enough data to <code>sys.stderr</code> to fill the
(undrained) pipe buffer. The worker process now redirects
<code>sys.stderr</code> to <code>os.devnull</code> as well, matching the
documented behavior</li>
<li>Fixed <code>TLSStream.wrap()</code> matching an internationalized
(unicode) host name against the peer certificate using IDNA 2003 (via
the standard library) instead of IDNA 2008, which could cause the host
name to be matched against the wrong certificate (<a
href="https://redirect.github.com/agronholm/anyio/pull/1208">#1208</a>)</li>
<li>Fixed <code>anyio.open_process()</code> (and
<code>run_process()</code>) ignoring the <code>extra_groups</code>
argument, as it mistakenly passed the value of the <code>group</code>
argument instead (<a
href="https://redirect.github.com/agronholm/anyio/pull/1209">#1209</a>)</li>
<li>Fixed <code>CapacityLimiter.acquire_nowait()</code> and
<code>CapacityLimiter.acquire_nowait_on_behalf_of()</code> raising
<code>trio.WouldBlock</code> instead of <code>anyio.WouldBlock</code> on
the <code>trio</code> backend when there are no tokens available (<a
href="https://redirect.github.com/agronholm/anyio/pull/1218">#1218</a>)</li>
<li>Fixed <code>CapacityLimiter</code> on the asyncio backend
over-granting tokens (<code>borrowed_tokens</code> exceeding
<code>total_tokens</code> and <code>available_tokens</code> going
negative) when a non-blocking acquire was made in the window between a
token being released and the notified waiter resuming. The freed token
is now reserved for the woken waiter right away, so the non-blocking
acquire correctly raises <code>WouldBlock</code> (<a
href="https://redirect.github.com/agronholm/anyio/issues/1170">#1170</a>;
PR by <a
href="https://github.com/gaoflow"><code>@​gaoflow</code></a>)</li>
<li>Fixed unnecessary CPU spin when delivering cancellation from
<code>CancelScope</code> on asyncio under certain conditions, including
improper cancel scope nesting (<a
href="https://redirect.github.com/agronholm/anyio/issues/1111">#1111</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/agronholm/anyio/commit/c384f99687c64c59ed8a11c3a0f11a2d57daff71"><code>c384f99</code></a>
Bumped up the version</li>
<li><a
href="https://github.com/agronholm/anyio/commit/dbba29d1ade7936f18fb71ba24aa92978673482a"><code>dbba29d</code></a>
Fixed 100% CPU spin on cancel scope misuse (<a
href="https://redirect.github.com/agronholm/anyio/issues/1217">#1217</a>)</li>
<li><a
href="https://github.com/agronholm/anyio/commit/6bbc6c33caabc13af5bc4256f745027cf8d5d7b8"><code>6bbc6c3</code></a>
Fix CapacityLimiter over-granting tokens on asyncio (<a
href="https://redirect.github.com/agronholm/anyio/issues/1172">#1172</a>)</li>
<li><a
href="https://github.com/agronholm/anyio/commit/6f82b2537cbbe98f3df3f295499056ab7de0b15b"><code>6f82b25</code></a>
Refactored TestTLSStream.test_receive_invalid_max_bytes() to be less
flaky</li>
<li><a
href="https://github.com/agronholm/anyio/commit/be24b0414f67f604bcbdd5ea3bcc56ab920d872e"><code>be24b04</code></a>
Relaxed timeouts to fix test flakiness</li>
<li><a
href="https://github.com/agronholm/anyio/commit/81135065749b4f60c06619b9caaf0a11871c1ddf"><code>8113506</code></a>
Fix test flakiness caused by slow callback duration logging</li>
<li><a
href="https://github.com/agronholm/anyio/commit/1e988b617b69588e33fecb75e36a9837245f562f"><code>1e988b6</code></a>
Fixed CapacityLimiter raising trio.WouldBlock instead of
anyio.WouldBlock (<a
href="https://redirect.github.com/agronholm/anyio/issues/1">#1</a>...</li>
<li><a
href="https://github.com/agronholm/anyio/commit/44713f345cd29dd4e7d76553c134543a1296cc62"><code>44713f3</code></a>
Pin setup-uv to a commit sha across downstream jobs (<a
href="https://redirect.github.com/agronholm/anyio/issues/1213">#1213</a>)</li>
<li><a
href="https://github.com/agronholm/anyio/commit/f1b7301c8264b0d2e8d24a5788fd29e93dea4040"><code>f1b7301</code></a>
Fixed stderr writes in a worker subprocess causing a deadlock (<a
href="https://redirect.github.com/agronholm/anyio/issues/1207">#1207</a>)</li>
<li><a
href="https://github.com/agronholm/anyio/commit/212be93c2cf2c841e753e95e5e2c543ee7feca90"><code>212be93</code></a>
Fix flaky test_tcp_listener_same_port using a hardcoded port (<a
href="https://redirect.github.com/agronholm/anyio/issues/1206">#1206</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/agronholm/anyio/compare/4.14.1...4.14.2">compare
view</a></li>
</ul>
</details>
<br />

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: John Kennedy <65985482+jkennedyvz@users.noreply.github.com>
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-08-08 12:02:52 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>John Kennedyopen-swe[bot] <open-swe@users.noreply.github.com>
6a2822d3c7 chore(deps): bump the minor-and-patch group across 1 directory with 5 updates (#8532)
Bumps the minor-and-patch group with 5 updates in the /libs/sdk-py
directory:

| Package | From | To |
| --- | --- | --- |
| [langchain-core](https://github.com/langchain-ai/langchain) | `1.4.8`
| `1.5.3` |
| [ruff](https://github.com/astral-sh/ruff) | `0.15.20` | `0.16.1` |
| [codespell](https://github.com/codespell-project/codespell) | `2.4.2`
| `2.4.3` |
| [ty](https://github.com/astral-sh/ty) | `0.0.55` | `0.0.66` |
| [xxhash](https://github.com/ifduyue/python-xxhash) | `3.8.0` | `3.8.1`
|


Updates `langchain-core` from 1.4.8 to 1.5.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langchain/releases">langchain-core's
releases</a>.</em></p>
<blockquote>
<h2>langchain-core==1.5.3</h2>
<p>Changes since langchain-core==1.5.2</p>
<p>release(core): 1.5.3 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39145">#39145</a>)
fix(core): fall back to <code>LANGSMITH_API_KEY</code> for gateway (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39115">#39115</a>)</p>
<h2>langchain-core==1.5.2</h2>
<p>Changes since langchain-core==1.5.1</p>
<p>release(core): 1.5.2 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39108">#39108</a>)
fix(core): handle empty string in gateway env vars (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39107">#39107</a>)
chore: bump setuptools from 82.0.0 to 83.0.0 in /libs/core (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39032">#39032</a>)
chore: bump jupyterlab from 4.5.9 to 4.5.10 in /libs/core (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39033">#39033</a>)
chore: bump setuptools from 80.9.0 to 83.0.0 in /libs/text-splitters (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39029">#39029</a>)</p>
<h2>langchain-core==1.5.1</h2>
<p>Changes since langchain-core==1.5.0</p>
<p>release(core): 1.5.1 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39042">#39042</a>)
feat(anthropic,fireworks,openai): support langsmith gateway through env
var (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/38742">#38742</a>)
fix(core): use <code>tool_call_schema</code> cache for
<code>BaseTool</code> token counting in
<code>count_tokens_approximately</code> (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39020">#39020</a>)</p>
<h2>langchain-core==1.5.0</h2>
<p>Changes since langchain-core==1.4.9</p>
<p>release(core): 1.5.0 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/38978">#38978</a>)
feat(core): add <code>reasoning_effort</code> as a standard chat model
parameter (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/38887">#38887</a>)
chore: bump soupsieve from 2.8 to 2.8.4 in /libs/core (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/38750">#38750</a>)
chore: bump mistune from 3.2.1 to 3.3.0 in /libs/core (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/38783">#38783</a>)</p>
<h2>langchain-core==1.4.9</h2>
<p>Changes since langchain-core==1.4.8</p>
<p>release(core): 1.4.9 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/38728">#38728</a>)
fix(core): improve langsmith loader error messages (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35648">#35648</a>)
fix(core): output parser bugs in xml.py and pydantic.py (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35641">#35641</a>)
style(core): fix some ruff preview rules (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/38656">#38656</a>)
fix(core): avoid <code>dict</code> shadowing in language models (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/38480">#38480</a>)
fix(core): <code>_parse_google_docstring</code> mishandling continuation
lines with colons (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35680">#35680</a>)
fix(core): add messages to bare <code>raise ValueError</code> calls (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/38158">#38158</a>)
fix(core): use <code>asyncio.get_running_loop()</code> in async contexts
(<a
href="https://redirect.github.com/langchain-ai/langchain/issues/38157">#38157</a>)
chore: bump langsmith from 0.8.0 to 0.8.18 in /libs/core (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/38319">#38319</a>)
chore: bump jupyterlab from 4.5.7 to 4.5.9 in /libs/core (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/38326">#38326</a>)
chore: bump vcrpy from 8.1.1 to 8.2.1 in /libs/core (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/38327">#38327</a>)</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langchain/commit/01d8481ae3d457ec3751cbb0331ea3a4b720fa90"><code>01d8481</code></a>
release(core): 1.5.3 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39145">#39145</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/b8a6e36d3f810302c66065ea1c2892da000de550"><code>b8a6e36</code></a>
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39141">#39141</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/cdca311de2958e497cfd823a45ddb770d0ef94c0"><code>cdca311</code></a>
fix(core): fall back to <code>LANGSMITH_API_KEY</code> for gateway (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39115">#39115</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/a5a8762436658b218dbfc5efff9e8ab8fabbe62f"><code>a5a8762</code></a>
ci: annotate uv bump workflow with PR links (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39112">#39112</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/557c0a986d5068085a945dcb3c9ce27257c4add8"><code>557c0a9</code></a>
chore(deps): bump uv to 0.12.0 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39111">#39111</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/835c85ae236a2d33cd11de4c98d28cbc6ebbd72f"><code>835c85a</code></a>
release(anthropic): 1.5.3 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39109">#39109</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/c1ab807b1f62ad04274c28f2b7d8c3141d8ba1f2"><code>c1ab807</code></a>
release(core): 1.5.2 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39108">#39108</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/313223347f842274432d8a2968b4be407e2722d8"><code>3132233</code></a>
fix(anthropic): preserve empty thinking field in signature_delta
streaming (#...</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/b3a6d9a012681df8a8e33345c8255ca69ec0e437"><code>b3a6d9a</code></a>
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/39105">#39105</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/447b51abbccde236838524ee717682a0dd2a6c09"><code>447b51a</code></a>
fix(anthropic): strip unsupported fields from system message content
blocks (...</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langchain/compare/langchain-core==1.4.8...langchain-core==1.5.3">compare
view</a></li>
</ul>
</details>
<br />

Updates `ruff` from 0.15.20 to 0.16.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/releases">ruff's
releases</a>.</em></p>
<blockquote>
<h2>0.16.1</h2>
<h2>Release Notes</h2>
<p>Released on 2026-07-30.</p>
<h3>Preview features</h3>
<ul>
<li>Add an option to opt out of human-readable names (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27160">#27160</a>)</li>
<li>[<code>flake8-pytest-style</code>] Make fixes safe by default and
unsafe only when comments are present (<code>PT018</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27201">#27201</a>)</li>
<li>[<code>pyupgrade</code>] Skip fix when a defaulted
<code>TypeVar</code> precedes a non-defaulted one (<code>UP040</code>,
<code>UP046</code>, <code>UP047</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27133">#27133</a>)</li>
<li>[<code>ruff</code>] Fix false positive with unpacked arguments
(<code>RUF065</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26959">#26959</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Bump <code>gen-lsp-types</code> to gracefully handle unknown
enumeration values in LSP messages (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27230">#27230</a>)</li>
<li>[<code>flake8-bugbear</code>] Mark <code>range</code> as immutable
(<code>B008</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27247">#27247</a>)</li>
<li>[<code>flake8-comprehensions</code>] NFKC-normalize keyword names in
<code>C408</code> fix (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26813">#26813</a>)</li>
<li>[<code>flake8-return</code>] Fix false positive when variable is
read in <code>finally</code> clause (<code>RET504</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25441">#25441</a>)</li>
<li>[<code>pydocstyle</code>] Skip section detection inside RST
directive bodies (<code>D214</code>, <code>D405</code>,
<code>D413</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23635">#23635</a>)</li>
<li>[<code>refurb</code>] Parenthesize <code>yield</code> arguments in
the <code>FURB192</code> fix (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27192">#27192</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>flake8-pytest-style</code>] Mark <code>PT022</code> fixes as
unsafe (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26440">#26440</a>)</li>
<li>[<code>refurb</code>] Mark fixes that remove unknown separators as
unsafe (<code>FURB105</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27200">#27200</a>)</li>
</ul>
<h3>Server</h3>
<ul>
<li>Fix indexing of excluded nested Ruff workspaces (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27303">#27303</a>)</li>
<li>Lint TOML files in the LSP (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26862">#26862</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Cover <code>pycon</code> Markdown formatting (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27153">#27153</a>)</li>
<li>[<code>flake8-bandit</code>] Document <code>TYPE_CHECKING</code>
exception (<code>S101</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27004">#27004</a>)</li>
<li>[<code>flake8-import-conventions</code>] Document that
<code>extend-aliases</code> can override default aliases (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27191">#27191</a>)</li>
<li>[<code>pylint</code>] Add missing fix safety gotchas for
<code>non-augmented-assignment</code> (<code>PLR6104</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27250">#27250</a>)</li>
</ul>
<h3>Other changes</h3>
<ul>
<li>Reduce syntax error noise by swallowing dedents like indents (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27170">#27170</a>)</li>
<li>Vendor latest annotate-snippets (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27033">#27033</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/bxff"><code>@​bxff</code></a></li>
<li><a
href="https://github.com/anishgirianish"><code>@​anishgirianish</code></a></li>
<li><a href="https://github.com/Avasam"><code>@​Avasam</code></a></li>
<li><a href="https://github.com/epage"><code>@​epage</code></a></li>
<li><a href="https://github.com/LHMQ878"><code>@​LHMQ878</code></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md">ruff's
changelog</a>.</em></p>
<blockquote>
<h2>0.16.1</h2>
<p>Released on 2026-07-30.</p>
<h3>Preview features</h3>
<ul>
<li>Add an option to opt out of human-readable names (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27160">#27160</a>)</li>
<li>[<code>flake8-pytest-style</code>] Make fixes safe by default and
unsafe only when comments are present (<code>PT018</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27201">#27201</a>)</li>
<li>[<code>pyupgrade</code>] Skip fix when a defaulted
<code>TypeVar</code> precedes a non-defaulted one (<code>UP040</code>,
<code>UP046</code>, <code>UP047</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27133">#27133</a>)</li>
<li>[<code>ruff</code>] Fix false positive with unpacked arguments
(<code>RUF065</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26959">#26959</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Bump <code>gen-lsp-types</code> to gracefully handle unknown
enumeration values in LSP messages (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27230">#27230</a>)</li>
<li>[<code>flake8-bugbear</code>] Mark <code>range</code> as immutable
(<code>B008</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27247">#27247</a>)</li>
<li>[<code>flake8-comprehensions</code>] NFKC-normalize keyword names in
<code>C408</code> fix (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26813">#26813</a>)</li>
<li>[<code>flake8-return</code>] Fix false positive when variable is
read in <code>finally</code> clause (<code>RET504</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25441">#25441</a>)</li>
<li>[<code>pydocstyle</code>] Skip section detection inside RST
directive bodies (<code>D214</code>, <code>D405</code>,
<code>D413</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23635">#23635</a>)</li>
<li>[<code>refurb</code>] Parenthesize <code>yield</code> arguments in
the <code>FURB192</code> fix (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27192">#27192</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>flake8-pytest-style</code>] Mark <code>PT022</code> fixes as
unsafe (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26440">#26440</a>)</li>
<li>[<code>refurb</code>] Mark fixes that remove unknown separators as
unsafe (<code>FURB105</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27200">#27200</a>)</li>
</ul>
<h3>Server</h3>
<ul>
<li>Fix indexing of excluded nested Ruff workspaces (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27303">#27303</a>)</li>
<li>Lint TOML files in the LSP (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26862">#26862</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Cover <code>pycon</code> Markdown formatting (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27153">#27153</a>)</li>
<li>[<code>flake8-bandit</code>] Document <code>TYPE_CHECKING</code>
exception (<code>S101</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27004">#27004</a>)</li>
<li>[<code>flake8-import-conventions</code>] Document that
<code>extend-aliases</code> can override default aliases (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27191">#27191</a>)</li>
<li>[<code>pylint</code>] Add missing fix safety gotchas for
<code>non-augmented-assignment</code> (<code>PLR6104</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27250">#27250</a>)</li>
</ul>
<h3>Other changes</h3>
<ul>
<li>Reduce syntax error noise by swallowing dedents like indents (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27170">#27170</a>)</li>
<li>Vendor latest annotate-snippets (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27033">#27033</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/bxff"><code>@​bxff</code></a></li>
<li><a
href="https://github.com/anishgirianish"><code>@​anishgirianish</code></a></li>
<li><a href="https://github.com/Avasam"><code>@​Avasam</code></a></li>
<li><a href="https://github.com/epage"><code>@​epage</code></a></li>
<li><a href="https://github.com/LHMQ878"><code>@​LHMQ878</code></a></li>
<li><a
href="https://github.com/MichaReiser"><code>@​MichaReiser</code></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/ruff/commit/80790b348b5188e7fc253665540f442c6ec7dd05"><code>80790b3</code></a>
Bump 0.16.1 (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27330">#27330</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/63830f3e97b56ca3be0dd8f1092f76c4acc63213"><code>63830f3</code></a>
[ty] Borrow from constraint set storage less often (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27328">#27328</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/f40dca98a7f1b6fff0cee5ecf1e6364d5a8bdc15"><code>f40dca9</code></a>
[ty] Preserve forwarded expanded-variadic diagnostic sources (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27266">#27266</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/0d804975a2645d6e4a795cf501fd764b88aa47e3"><code>0d80497</code></a>
Lint TOML files in the LSP (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26862">#26862</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/d91586bd5b30f77f6614d57b73fc7dbea9051a0e"><code>d91586b</code></a>
Update prek dependencies (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27293">#27293</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/7da4b8b8d78fd6df2b3e06d8466d9cd49822900d"><code>7da4b8b</code></a>
[ty] Respect bounds and constraints in generic materializations (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27228">#27228</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/b20daf741241a6829ff8963a203f7847561deb62"><code>b20daf7</code></a>
[ty] refactor: add helper function to send partial results (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27249">#27249</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/4d4c8fa1c75b00561ea24eefefb13eb5ff80e01f"><code>4d4c8fa</code></a>
[ty] Emit diagnostic when specializing a non-generic class (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26883">#26883</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/7c3e2db97deb5c5edd6e4303403ef6caf2bde8be"><code>7c3e2db</code></a>
[ty] Fix enum class container assignability (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27318">#27318</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/d5ef97fcd03e108f7510f84b8ed85bb4051311fe"><code>d5ef97f</code></a>
[<code>flake8-return</code>] Fix false positive when variable is read in
<code>finally</code> claus...</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/ruff/compare/0.15.20...0.16.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `codespell` from 2.4.2 to 2.4.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/codespell-project/codespell/releases">codespell's
releases</a>.</em></p>
<blockquote>
<h2>v2.4.3</h2>
<!-- raw HTML omitted -->
<h2>What's Changed</h2>
<ul>
<li>Add 'radback' to dictionary with correction by <a
href="https://github.com/Flo3561"><code>@​Flo3561</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3883">codespell-project/codespell#3883</a></li>
<li>Add 'repetirion' to dictionary corrections by <a
href="https://github.com/Flo3561"><code>@​Flo3561</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3885">codespell-project/codespell#3885</a></li>
<li>Need to specify a version of Python version after all by <a
href="https://github.com/DimitriPapadopoulos"><code>@​DimitriPapadopoulos</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3887">codespell-project/codespell#3887</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3889">codespell-project/codespell#3889</a></li>
<li>Add cases for &quot;modulle&quot; -&gt; &quot;module&quot; by <a
href="https://github.com/utzcoz"><code>@​utzcoz</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3888">codespell-project/codespell#3888</a></li>
<li>Add case &quot;auido&quot; -&gt; &quot;audio&quot; by <a
href="https://github.com/utzcoz"><code>@​utzcoz</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3890">codespell-project/codespell#3890</a></li>
<li>Add credentilas-&gt;credentials and friends by <a
href="https://github.com/peternewman"><code>@​peternewman</code></a> in
<a
href="https://redirect.github.com/codespell-project/codespell/pull/3895">codespell-project/codespell#3895</a></li>
<li>Add the case &quot;cubid&quot; -&gt; &quot;cubic&quot; by <a
href="https://github.com/utzcoz"><code>@​utzcoz</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3891">codespell-project/codespell#3891</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3897">codespell-project/codespell#3897</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3900">codespell-project/codespell#3900</a></li>
<li>Bump codecov/codecov-action from 5 to 6 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3902">codespell-project/codespell#3902</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3904">codespell-project/codespell#3904</a></li>
<li>Add <code>magntiude-&gt;magnitude</code> by <a
href="https://github.com/nathanjmcdougall"><code>@​nathanjmcdougall</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3899">codespell-project/codespell#3899</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3909">codespell-project/codespell#3909</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3912">codespell-project/codespell#3912</a></li>
<li>Add the case &quot;instanc&quot; -&gt; &quot;instance&quot; by <a
href="https://github.com/utzcoz"><code>@​utzcoz</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3896">codespell-project/codespell#3896</a></li>
<li>gampad -&gt; gamepad (and plural) by <a
href="https://github.com/julianstirling"><code>@​julianstirling</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3906">codespell-project/codespell#3906</a></li>
<li>Add typos of <code>monotonic</code> and <code>monotonicity</code> by
<a
href="https://github.com/nathanjmcdougall"><code>@​nathanjmcdougall</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3898">codespell-project/codespell#3898</a></li>
<li>Add spelling correction for multipile(s)/vulnerabities. by <a
href="https://github.com/cfi-gb"><code>@​cfi-gb</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3905">codespell-project/codespell#3905</a></li>
<li>Add 'simpilfy -&gt; simplify' by <a
href="https://github.com/alexreinking"><code>@​alexreinking</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3913">codespell-project/codespell#3913</a></li>
<li>fix(packaging): prevent unwanted files and tests from being
installed by <a
href="https://github.com/mikelolasagasti"><code>@​mikelolasagasti</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3911">codespell-project/codespell#3911</a></li>
<li>Add skarhoj-&gt;SKAARHOJ to dictionary corrections by <a
href="https://github.com/peternewman"><code>@​peternewman</code></a> in
<a
href="https://redirect.github.com/codespell-project/codespell/pull/3908">codespell-project/codespell#3908</a></li>
<li>Improve the dictionary by <a
href="https://github.com/algonell"><code>@​algonell</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3914">codespell-project/codespell#3914</a></li>
<li>Add spelling correction for accorss/accors. by <a
href="https://github.com/cfi-gb"><code>@​cfi-gb</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3916">codespell-project/codespell#3916</a></li>
<li>Bump autofix-ci/action from 1.3.3 to 1.3.4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3921">codespell-project/codespell#3921</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3923">codespell-project/codespell#3923</a></li>
<li>Add <code>influecer-&gt;influencer</code> and <code>influnce*</code>
typos to dictionary by <a
href="https://github.com/nathanjmcdougall"><code>@​nathanjmcdougall</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3925">codespell-project/codespell#3925</a></li>
<li>Add typos for <code>excavate</code> and variants by <a
href="https://github.com/nathanjmcdougall"><code>@​nathanjmcdougall</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3926">codespell-project/codespell#3926</a></li>
<li>Dict: Add corrections for memoy by <a
href="https://github.com/mdeweerd"><code>@​mdeweerd</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3924">codespell-project/codespell#3924</a></li>
<li><code>overheda -&gt; overhead</code> by <a
href="https://github.com/George-Ogden"><code>@​George-Ogden</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3919">codespell-project/codespell#3919</a></li>
<li><code>inclusize-&gt;inclusive</code> and variants by <a
href="https://github.com/George-Ogden"><code>@​George-Ogden</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3918">codespell-project/codespell#3918</a></li>
<li>Add spelling corrections for authorization by <a
href="https://github.com/cfi-gb"><code>@​cfi-gb</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3922">codespell-project/codespell#3922</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3927">codespell-project/codespell#3927</a></li>
<li>Don't fix Voight by <a
href="https://github.com/DimitriPapadopoulos"><code>@​DimitriPapadopoulos</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3929">codespell-project/codespell#3929</a></li>
<li>Add spelling corrections for interstect and interstection by <a
href="https://github.com/korli"><code>@​korli</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3928">codespell-project/codespell#3928</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3930">codespell-project/codespell#3930</a></li>
<li>Improve output in interactive mode by <a
href="https://github.com/darkmattercoder"><code>@​darkmattercoder</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3884">codespell-project/codespell#3884</a></li>
<li>feat: support codespell:ignore-next-line directive by <a
href="https://github.com/SAY-5"><code>@​SAY-5</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3931">codespell-project/codespell#3931</a></li>
<li>Add woork-&gt;work and formace-&gt;format and friends by <a
href="https://github.com/peternewman"><code>@​peternewman</code></a> in
<a
href="https://redirect.github.com/codespell-project/codespell/pull/3828">codespell-project/codespell#3828</a></li>
<li>shortctu -&gt; shortcut by <a
href="https://github.com/George-Ogden"><code>@​George-Ogden</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3934">codespell-project/codespell#3934</a></li>
<li>A couple typos by <a
href="https://github.com/DimitriPapadopoulos"><code>@​DimitriPapadopoulos</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3935">codespell-project/codespell#3935</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3937">codespell-project/codespell#3937</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3942">codespell-project/codespell#3942</a></li>
<li>reclaculate-&gt;recalculate by <a
href="https://github.com/adamgann"><code>@​adamgann</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3936">codespell-project/codespell#3936</a></li>
<li>Add spelling correction for improprt. by <a
href="https://github.com/cfi-gb"><code>@​cfi-gb</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3939">codespell-project/codespell#3939</a></li>
<li>Dictionary plasic-plastic by <a
href="https://github.com/julianstirling"><code>@​julianstirling</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3938">codespell-project/codespell#3938</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/codespell-project/codespell/commit/57b21406f092110c18776e39b0bda50d37c945c8"><code>57b2140</code></a>
Read only [tool.codespell] from TOML config (<a
href="https://redirect.github.com/codespell-project/codespell/issues/3975">#3975</a>)</li>
<li><a
href="https://github.com/codespell-project/codespell/commit/23b8d940332bcff6369872b53964d4137c586728"><code>23b8d94</code></a>
[pre-commit.ci] pre-commit autoupdate</li>
<li><a
href="https://github.com/codespell-project/codespell/commit/926c4d6ff083acc62bc04c102e0a9b2091d754f1"><code>926c4d6</code></a>
[pre-commit.ci] pre-commit autoupdate</li>
<li><a
href="https://github.com/codespell-project/codespell/commit/820a3011e0bfe3f90789ba41e751a26656bea7ab"><code>820a301</code></a>
Merge pull request <a
href="https://redirect.github.com/codespell-project/codespell/issues/3967">#3967</a>
from codespell-project/peternewman-patch-1</li>
<li><a
href="https://github.com/codespell-project/codespell/commit/9fe42c964c90f65a9a2841847af4e093c67ec817"><code>9fe42c9</code></a>
Add common misspellings for reseeve-&gt;reserve to dictionary</li>
<li><a
href="https://github.com/codespell-project/codespell/commit/725173c815cee1b781b85ec51eb43dc4f51dcd0f"><code>725173c</code></a>
[pre-commit.ci] pre-commit autoupdate</li>
<li><a
href="https://github.com/codespell-project/codespell/commit/86c13ee4dce0010e8d8119ded33694806e37ca8f"><code>86c13ee</code></a>
[pre-commit.ci] pre-commit autoupdate</li>
<li><a
href="https://github.com/codespell-project/codespell/commit/7b9f36ce634c551107a3a4a5c25782a2245d7658"><code>7b9f36c</code></a>
Bump actions/checkout from 6 to 7</li>
<li><a
href="https://github.com/codespell-project/codespell/commit/bcc1b804a83b8a146586101431b063c07cfcc699"><code>bcc1b80</code></a>
Add spelling corrections for simpe and variants.</li>
<li><a
href="https://github.com/codespell-project/codespell/commit/89584cc1479d0f8dbfd501925673b6be6d460fe1"><code>89584cc</code></a>
[pre-commit.ci] pre-commit autoupdate</li>
<li>Additional commits viewable in <a
href="https://github.com/codespell-project/codespell/compare/v2.4.2...v2.4.3">compare
view</a></li>
</ul>
</details>
<br />

Updates `ty` from 0.0.55 to 0.0.66
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ty/releases">ty's
releases</a>.</em></p>
<blockquote>
<h2>0.0.66</h2>
<h2>Release Notes</h2>
<p>Released on 2026-08-03.</p>
<h3>LSP server</h3>
<ul>
<li>Index <code>with</code>-statement targets as symbols (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27256">#27256</a>)</li>
<li>Trigger signature help after completing callable with parentheses
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/27084">#27084</a>)</li>
</ul>
<h3>Library support</h3>
<ul>
<li>Pydantic: Recognize models installed on extra search paths (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27429">#27429</a>)</li>
</ul>
<h3>Diagnostics</h3>
<ul>
<li>Detect <code>__aenter__</code> and <code>__aexit__</code> that do
not return awaitables (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27414">#27414</a>)</li>
<li>Diagnose dataclass fields after inherited defaults (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27327">#27327</a>)</li>
<li>Emit diagnostic when specializing a non-generic class (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26883">#26883</a>)</li>
<li>Improve error context for incompatible callable signatures (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27422">#27422</a>)</li>
<li>Preserve forwarded expanded-variadic diagnostic sources (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27266">#27266</a>)</li>
</ul>
<h3>Core type checking</h3>
<ul>
<li>Avoid over-materializing user-defined <code>TypeIs</code> return
types (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26864">#26864</a>)</li>
<li>Fix enum class container assignability (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27318">#27318</a>)</li>
<li>Hide stub-only helpers from implicit builtin lookup (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27423">#27423</a>)</li>
<li>Move <code>Unknown</code> out of the experimental
<code>ty_extensions</code> API (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27430">#27430</a>)</li>
<li>Preserve <code>TypeVarTuple</code> context during
<code>Generic</code> recovery (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27381">#27381</a>)</li>
<li>Preserve exact numeric types in covariant collections (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27311">#27311</a>)</li>
<li>Reject <code>ClassVar</code> and <code>Final</code> qualifiers in
<code>NamedTuple</code> fields (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27380">#27380</a>)</li>
<li>Reject out-of-scope <code>ParamSpec</code> components (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27378">#27378</a>)</li>
<li>Reject specializing non-generic subclasses (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27377">#27377</a>)</li>
<li>Respect bounds and constraints in generic materializations (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27228">#27228</a>)</li>
<li>Sync vendored typeshed stubs (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27401">#27401</a>).
<a
href="https://github.com/python/typeshed/compare/b00c387c669cb50d5d388d77b74c2e832e147fe8...1b116673774d062a4af7b0a0b3d05533a6be55d0">Typeshed
diff</a></li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/dhruvmanila"><code>@​dhruvmanila</code></a></li>
<li><a href="https://github.com/carljm"><code>@​carljm</code></a></li>
<li><a
href="https://github.com/Punisheroot"><code>@​Punisheroot</code></a></li>
<li><a
href="https://github.com/AlexWaygood"><code>@​AlexWaygood</code></a></li>
<li><a
href="https://github.com/cheparity"><code>@​cheparity</code></a></li>
<li><a
href="https://github.com/BitWeaverDev"><code>@​BitWeaverDev</code></a></li>
<li><a
href="https://github.com/charliermarsh"><code>@​charliermarsh</code></a></li>
<li><a href="https://github.com/ribru17"><code>@​ribru17</code></a></li>
<li><a href="https://github.com/sharkdp"><code>@​sharkdp</code></a></li>
</ul>
<h2>Install ty 0.0.66</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ty/blob/main/CHANGELOG.md">ty's
changelog</a>.</em></p>
<blockquote>
<h2>0.0.66</h2>
<p>Released on 2026-08-03.</p>
<h3>LSP server</h3>
<ul>
<li>Index <code>with</code>-statement targets as symbols (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27256">#27256</a>)</li>
<li>Trigger signature help after completing callable with parentheses
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/27084">#27084</a>)</li>
</ul>
<h3>Library support</h3>
<ul>
<li>Pydantic: Recognize models installed on extra search paths (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27429">#27429</a>)</li>
</ul>
<h3>Diagnostics</h3>
<ul>
<li>Detect <code>__aenter__</code> and <code>__aexit__</code> that do
not return awaitables (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27414">#27414</a>)</li>
<li>Diagnose dataclass fields after inherited defaults (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27327">#27327</a>)</li>
<li>Emit diagnostic when specializing a non-generic class (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26883">#26883</a>)</li>
<li>Improve error context for incompatible callable signatures (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27422">#27422</a>)</li>
<li>Preserve forwarded expanded-variadic diagnostic sources (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27266">#27266</a>)</li>
</ul>
<h3>Core type checking</h3>
<ul>
<li>Avoid over-materializing user-defined <code>TypeIs</code> return
types (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26864">#26864</a>)</li>
<li>Fix enum class container assignability (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27318">#27318</a>)</li>
<li>Hide stub-only helpers from implicit builtin lookup (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27423">#27423</a>)</li>
<li>Move <code>Unknown</code> out of the experimental
<code>ty_extensions</code> API (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27430">#27430</a>)</li>
<li>Preserve <code>TypeVarTuple</code> context during
<code>Generic</code> recovery (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27381">#27381</a>)</li>
<li>Preserve exact numeric types in covariant collections (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27311">#27311</a>)</li>
<li>Reject <code>ClassVar</code> and <code>Final</code> qualifiers in
<code>NamedTuple</code> fields (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27380">#27380</a>)</li>
<li>Reject out-of-scope <code>ParamSpec</code> components (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27378">#27378</a>)</li>
<li>Reject specializing non-generic subclasses (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27377">#27377</a>)</li>
<li>Respect bounds and constraints in generic materializations (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27228">#27228</a>)</li>
<li>Sync vendored typeshed stubs (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27401">#27401</a>).
<a
href="https://github.com/python/typeshed/compare/b00c387c669cb50d5d388d77b74c2e832e147fe8...1b116673774d062a4af7b0a0b3d05533a6be55d0">Typeshed
diff</a></li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/dhruvmanila"><code>@​dhruvmanila</code></a></li>
<li><a href="https://github.com/carljm"><code>@​carljm</code></a></li>
<li><a
href="https://github.com/Punisheroot"><code>@​Punisheroot</code></a></li>
<li><a
href="https://github.com/AlexWaygood"><code>@​AlexWaygood</code></a></li>
<li><a
href="https://github.com/cheparity"><code>@​cheparity</code></a></li>
<li><a
href="https://github.com/BitWeaverDev"><code>@​BitWeaverDev</code></a></li>
<li><a
href="https://github.com/charliermarsh"><code>@​charliermarsh</code></a></li>
<li><a href="https://github.com/ribru17"><code>@​ribru17</code></a></li>
<li><a href="https://github.com/sharkdp"><code>@​sharkdp</code></a></li>
</ul>
<h2>0.0.65</h2>
<p>Released on 2026-07-29.</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/ty/commit/3e28e58af75c3b16403c05ab401ae7347b65c5e2"><code>3e28e58</code></a>
Bump version to 0.0.66 (<a
href="https://redirect.github.com/astral-sh/ty/issues/4166">#4166</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/87de836dfef2d3e1990440752e30e24cfb572f4e"><code>87de836</code></a>
Bump version to 0.0.65 (<a
href="https://redirect.github.com/astral-sh/ty/issues/4113">#4113</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/d784bad352f4640e7f788eac4dfb0353ca199902"><code>d784bad</code></a>
README: Add link to Stable milestone (<a
href="https://redirect.github.com/astral-sh/ty/issues/4112">#4112</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/b4c1978dbdf18501ed03139e33ee8afaef251ec3"><code>b4c1978</code></a>
Update actions/setup-python action to v7 (<a
href="https://redirect.github.com/astral-sh/ty/issues/4110">#4110</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/8d216a39dad99c4443396a08a77da00502cd7e58"><code>8d216a3</code></a>
Update actions/checkout action to v7.0.1 (<a
href="https://redirect.github.com/astral-sh/ty/issues/4107">#4107</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/06f44e9039cb2764346c4bceaff4021ac143de9e"><code>06f44e9</code></a>
Update dependency prek to v0.4.10 (<a
href="https://redirect.github.com/astral-sh/ty/issues/4108">#4108</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/b3665fde4804424576a30078bc0b7c101c5e2242"><code>b3665fd</code></a>
Update prek dependencies (<a
href="https://redirect.github.com/astral-sh/ty/issues/4109">#4109</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/5e64a131b436a4e1f40e6317c526f4fc73fab38b"><code>5e64a13</code></a>
Bump version to 0.0.64 (<a
href="https://redirect.github.com/astral-sh/ty/issues/4097">#4097</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/46f4915e67aad965b1b4e7478a75378e8e52ff6d"><code>46f4915</code></a>
Bump version to 0.0.63 (<a
href="https://redirect.github.com/astral-sh/ty/issues/4071">#4071</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/df75992e7fd0cc73cb0f66e864df3f6e4fc4e1a6"><code>df75992</code></a>
Bump version to 0.0.62 (<a
href="https://redirect.github.com/astral-sh/ty/issues/4057">#4057</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/ty/compare/0.0.55...0.0.66">compare
view</a></li>
</ul>
</details>
<br />

Updates `xxhash` from 3.8.0 to 3.8.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/ifduyue/python-xxhash/releases">xxhash's
releases</a>.</em></p>
<blockquote>
<h2>v3.8.1</h2>
<ul>
<li>Register the &quot;benchmark&quot; pytest mark to avoid
PytestUnknownMarkWarning</li>
<li>Update C extension docstrings and remove stale comments</li>
</ul>
<hr />
<p>Full list of changes: <a
href="https://github.com/ifduyue/python-xxhash/compare/v3.8.0...v3.8.1">https://github.com/ifduyue/python-xxhash/compare/v3.8.0...v3.8.1</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/ifduyue/python-xxhash/blob/master/CHANGELOG.rst">xxhash's
changelog</a>.</em></p>
<blockquote>
<p>v3.8.1 2026-07-06</p>
<pre><code>
- Register the &quot;benchmark&quot; pytest mark to avoid
PytestUnknownMarkWarning
- Update C extension docstrings and remove stale comments
</code></pre>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/ifduyue/python-xxhash/commit/0a1adf7b6a5f26b821272985a0a987cca5446c23"><code>0a1adf7</code></a>
Release v3.8.1</li>
<li><a
href="https://github.com/ifduyue/python-xxhash/commit/aa419ca1478219b7ba2ad18a8fe05ca9d29288bc"><code>aa419ca</code></a>
Prepare 3.8.1</li>
<li><a
href="https://github.com/ifduyue/python-xxhash/commit/e26b770f19b5eabd1c58d098206e5c9adfcef5d1"><code>e26b770</code></a>
docs: update C extension docstrings and remove stale comments</li>
<li><a
href="https://github.com/ifduyue/python-xxhash/commit/aa69707513c4ba8c52a4bebc23f0aada7844decc"><code>aa69707</code></a>
Register the “benchmark” pytest mark</li>
<li>See full diff in <a
href="https://github.com/ifduyue/python-xxhash/compare/v3.8.0...v3.8.1">compare
view</a></li>
</ul>
</details>
<br />

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: John Kennedy <65985482+jkennedyvz@users.noreply.github.com>
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-08-08 12:01:48 -07:00
Elior Nataf LackritzandGitHub fde3068970 release(checkpoint-postgres): 3.1.2 (#8565)
Version bump only. No library code changes in this PR.

Unreleased since `checkpointpostgres==3.1.1`:

- #8535 - find plain-value seeds when walking delta history

Follows `checkpoint==4.2.0`, which is already live on PyPI. No
dependency change needed here, since
`langgraph-checkpoint>=4.1.0,<5.0.0` already admits 4.2.0.
2026-08-07 16:32:21 -04:00
Elior Nataf LackritzandGitHub f55e77274d release(checkpoint): 4.2.0 (#8563)
Version bump only. No library code changes in this PR.

Unreleased since `checkpoint==4.1.1`:

- #8526 - collect writes at plain-value seed in delta channel history
- #8354 - add opt-in `TTLConfig.omit_expired` to skip expired rows on
read

Minor rather than patch because #8354 adds a new public `TTLConfig` key.
Note that the checkpoint-postgres half of #8354 already shipped in
`checkpointpostgres==3.1.1`, so the currently released Postgres saver
honors an option that no released `langgraph-checkpoint` declares. This
closes that gap.

`langgraph-checkpoint-postgres` 3.1.2 (#8535) follows once this is live
on PyPI.
2026-08-07 15:59:52 -04:00
a90ab44358 fix(checkpoint): collect writes at plain-value seed in delta channel history (#8526)
Fixes langchain-ai/langgraph#8384

`InMemorySaver.get_delta_channel_history` skipped the writes stored at
the ancestor it seeded from whenever that ancestor's blob was a plain
value rather than a `_DeltaSnapshot`, silently dropping the first write
made after migrating a thread to `DeltaChannel`.

### Why the old rule was wrong

A stored blob is the value *entering* its checkpoint; the writes stored
under that same checkpoint are what produce its child. That's true for
`_DeltaSnapshot` blobs and pre-delta plain values alike, so there was
never a reason to treat them differently.

Writes at ancestors *older* than the seed genuinely are subsumed by the
seed value — but that's already guaranteed by terminating the walk,
since the channel leaves `remaining` once its seed is found. The removed
check re-solved that and overreached by one checkpoint.

`BaseCheckpointSaver`, `SqliteSaver` and `PostgresSaver` never had this
check. `InMemorySaver` was the only outlier.

### How I verified it

Built a differential harness running the same migration scenarios
through `InMemorySaver`, the `BaseCheckpointSaver` reference walk, and
`SqliteSaver`. **4 of 11 scenarios agreed before this change; 11 of 11
after.** The loss is wider than one write — on the `add_messages` →
`DeltaChannel` path it drops a real user message.

Suites: `libs/checkpoint` 156 passed, `libs/langgraph` 1972 passed,
`libs/checkpoint-sqlite` 117 passed, `libs/checkpoint-postgres` passed
against PG 16. `make format`, `make lint` clean in each.

### Two things worth a closer look in review

**1. I inverted two existing assertions** in
`TestPreDeltaBlobTerminator` (`libs/checkpoint/tests/test_memory.py`).
They encoded the old rule. Their fixture is the real migration shape — a
plain-value blob carrying pending writes, with a delta-era child — which
I confirmed against a dumped checkpoint chain from the issue's repro, so
the assertions were wrong rather than the fixture being unrealistic. I
added an ancestor *older* than the seed so the terminator still guards
what it legitimately should: older writes stay excluded, the seed's own
writes replay.

**2. The new conformance test fails against Postgres**, for a reason
unrelated to this change. Postgres `aput` leaves an inline `True` marker
in `channel_values` only for `_DeltaSnapshot`; plain non-primitive
values are popped with no marker, and seed detection is `(checkpoint ->
'channel_values' -> ch) IS NOT NULL`. So Postgres can't locate a
plain-value seed at all:

```
seed stored as plain list:      InMemorySaver -> [10, 20]    AsyncPostgresSaver -> no seed key
seed stored as _DeltaSnapshot:  InMemorySaver -> found       AsyncPostgresSaver -> found
```

The pre-existing `test_history_migration_plain_value_as_seed` already
fails there too — conformance CI only validates `InMemorySaver`, so
nobody was watching. Values still come out correct today (with no seed
the walk runs to the root and replays everything), but early termination
is lost: 1 write replayed on `InMemorySaver` vs 7 on Postgres for the
same 6-turn thread. Filing separately rather than folding a
write-path/format decision into this PR.

### Note on scope

This touches three packages: the fix in `libs/checkpoint`, graph-level
regression tests in `libs/langgraph` (the bug is only observable through
a graph read), and the contract test in `libs/checkpoint-conformance` so
third-party savers are covered too.

---------

Co-authored-by: PiedPiper911 <32931126+PiedPiper911@users.noreply.github.com>
2026-08-07 12:29:41 -04:00
Elior Nataf LackritzandGitHub ea5f9cc9fb chore: enforce PLC0415 in tests for the remaining packages (#8547)
Follow-up to #8540, which turned on `PLC0415` (import-outside-top-level)
for checkpoint-postgres and checkpoint-sqlite. This does the remaining
six packages: checkpoint, checkpoint-conformance, langgraph, prebuilt,
cli, sdk-py.

Scoped to tests, per @sydney-runkle's call on #8540: library code is
exempted with `per-file-ignores`, since it still has deferred imports
nobody has reviewed and mixing that in would make this hard to read.

## What changed

Function-level imports across 56 test files moved to module level. Nine
could not move and carry an explicit `# noqa: PLC0415` with a reason:

| File | Why it stays local |
|---|---|
| `libs/langgraph/tests/test_deprecation.py` (4) | the import has to run
inside `pytest.warns` for the warning to be observed |
| `libs/langgraph/tests/test_serde_allowlist.py` | try/except guard,
skips when langchain_core is absent |
| `libs/langgraph/tests/test_delta_channel_benchmark.py` | optional
psycopg probe |
| `libs/checkpoint/tests/test_conformance_delta.py` (3) | protected by a
module-level `pytest.importorskip`; hoisting past the guard turns a skip
into a collection error |

That last one is the trap: an import moved above `pytest.importorskip`
silently defeats the guard. I hit it locally and it turned the skip into
a `ModuleNotFoundError` at collection. Every file with an `importorskip`
or `except ImportError` was checked by hand for this.

## Verification

`make lint` and `make test` in each of the six:

| Package | Tests |
|---|---|
| checkpoint | 156 passed, 17 skipped |
| checkpoint-conformance | 1 passed |
| langgraph | 1968 passed, 4 skipped |
| prebuilt | 284 passed |
| cli | 336 passed |
| sdk-py | 493 passed |

Also confirmed the rule actually fires: a throwaway test file with a
function-level import is flagged in all six packages, and the source
exemption holds.
2026-08-07 09:40:18 -04:00
Elior Nataf LackritzandGitHub 36a505ac65 test(checkpoint-postgres,checkpoint-sqlite): run the conformance suite (#8537)
Depends on #8535

`libs/checkpoint-conformance/tests/` only validates `InMemorySaver`.
`checkpoint-sqlite` has had a `test_conformance_delta.py` for a while,
but it guards on `importorskip("langgraph.checkpoint.conformance")` and
the package was never in its test environment — so it has been skipping
silently every run. `checkpoint-postgres` had no runner at all.

Net effect: the shared checkpointer contract was effectively unenforced
everywhere except in-memory.

### Change

Adds `langgraph-checkpoint-conformance` to the `test` dependency group
of both packages, with a path source like the existing
`langgraph-checkpoint` entry. That alone is what makes sqlite's runner
start executing. Postgres gets the equivalent runner.

Both pass the `delta_channel_history` capability.

### Why it's stacked

Against `main`'s Postgres, the new runner fails:

```
Capability delta_channel_history failed:
  test_history_migration_plain_value_as_seed
```

That is exactly the bug #8535 fixes, and it had been failing unnoticed
precisely because nothing ran the suite there. So this is based on that
branch rather than `main` — the diff here is the one conformance commit,
and it will retarget once #8535 lands.

Reasonable to read that as the change justifying itself: the first thing
turning the suite on did was catch a real bug that had been sitting in
`main`.

### Verified

`checkpoint-postgres` 270 passed on PG 15 and 16, `checkpoint-sqlite`
118 passed, lint and `ty` clean in both. The `uv.lock` updates are the
conformance package entry only.

### Note

The sync `PostgresSaver` and `SqliteSaver` aren't covered — the
conformance harness reports every capability as `detected=False` for
them, so only the async savers are exercised. Pre-existing and not
addressed here, but worth knowing the coverage isn't total.
2026-08-07 09:39:20 -04:00
Elior Nataf LackritzandGitHub d569e18f4b fix(checkpoint-postgres): find plain-value seeds when walking delta history (#8535)
Fixes langchain-ai/langgraph#8534

`put` splits stored values in two: primitives stay inline in the
checkpoint's `channel_values`, everything else moves to
`checkpoint_blobs`, and only `_DeltaSnapshot` leaves an inline marker
behind when it moves. Stage-1 seed detection tested for that marker, so
a plain value — what a thread migrated from `BinaryOperatorAggregate`
leaves behind — was invisible to the walk.

### Effect

Migrated threads found no seed, walked to the root, and replayed every
write on every read. Values still came out correct, because replaying an
additive reducer from empty rebuilds the same list, which is why nothing
looked wrong. What was lost is early termination — the entire point of
`DeltaChannel`:

<!-- linear:table-colwidths:266,266,266 -->
| thread length | writes replayed, before | after |
| -- | -- | -- |
| 2 turns | 3 | 1 |
| 6 turns | 7 | 1 |
| 20 turns | 21 | 1 |

Read latency is flat at \~0.6ms across all three after the change.

### Approach

Stage 1 now checks both places a value can live rather than trusting the
marker. It probes `checkpoint_blobs`:

```sql
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
```

and selects the inline value alongside it, since `None`, `str`, `int`,
`float` and `bool` stay in `channel_values` with no blob row:

```sql
checkpoint -> 'channel_values' -> %s AS inline_0
```

The blob predicate matches `checkpoint_blobs`' primary key `(thread_id,
checkpoint_ns, channel, version)` exactly, so it is one index lookup per
row per channel, bounded by the 1024-row page.

I picked reading storage over the cheaper alternative — also writing the
marker for plain values — because **that would not fix any thread
already on disk.** Existing checkpoints have no marker and there is
nowhere to add one retroactively.

The seed resolves to the blob when one exists and the inline value
otherwise. That ordering is also what keeps a genuine inline `true` — a
`bool` channel holding `True` — distinguishable from the literal `true`
marker `put` inlines for a `_DeltaSnapshot`: only the snapshot has a
blob.

`None` is deliberately not treated as a seed; a JSON null is
indistinguishable from "nothing stored" at this layer, so the walk
continues and replay from empty is correct.

Params go from two to four per channel; both callers updated.

The inline half came out of review on this PR — a blob-only probe would
have left scalar-aggregate migrations (an integer sum, say) still
replaying their full history.

### On the `type <> 'empty'` predicate

Being upfront since it isn't demonstrable with a test: `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. I confirmed there are no `empty` rows in
a populated test database.

I kept it because stage 2 already applies the same check when resolving
the seed blob. Without it the two stages could disagree: stage 1
terminates the walk on a row stage 2 then discards, producing no seed
*and* a truncated write chain — the same failure shape this function
exists to avoid. Rationale is in the docstring so the next reader
doesn't have to ask. Happy to drop it if you'd rather not carry an
unexercised predicate.

### Tests

`libs/checkpoint-postgres/tests/test_delta_plain_value_seed.py` —
blob-stored plain-value seed, `_DeltaSnapshot` seed, a version bump with
nothing stored (which must not stop the walk short of an older real
value), inline primitives (`int`, `str`, `float`, `None`), and inline
`True` versus the snapshot marker. Each fails against the behaviour it
fixes.

Verified: postgres suite 269 passed on PG 15 and 16; delta-channel
conformance against `AsyncPostgresSaver` went from 6 of 8 to 8 of 8,
including the pre-existing `test_history_migration_plain_value_as_seed`
failure this was causing; `make lint` clean.

### Not included

I wanted a Postgres conformance runner alongside `checkpoint-sqlite`'s,
but it needs `langgraph-checkpoint-conformance` as a dev dependency and
the contributing guide asks for maintainer sign-off before adding one.
The direct tests above cover the same ground without it.

Worth flagging separately: **conformance effectively runs against**
`InMemorySaver` **only today.** `libs/checkpoint-conformance/tests/`
contains just `test_validate_memory.py`, and `checkpoint-sqlite`'s
`test_conformance_delta.py` silently skips because the package isn't
installed in its test environment (`importorskip`). Wiring it up for
sqlite and postgres is what would have caught this bug, and
langchain-ai/langgraph#8534 notes it.

Sqlite is unaffected by the bug itself — it stores `channel_values`
inline and inspects them directly. `langgraph-api` already resolves
seeds by version rather than by marker.
2026-08-07 09:07:04 -04:00
Elior Nataf LackritzandGitHub f22af6248c chore: enable RUF100 and clear unused noqa directives (#8546)
Follow-up to review on #8540, where a stale `# noqa: E402` slipped past
me and Sydney spotted it by eye. This turns on the rule that catches
that automatically.

`RUF100` flags a `noqa` that suppresses nothing. `sdk-py` already had it
through its blanket `RUF` selection; this adds it to the other seven
packages and clears what it finds.

### The 33 it flags, all autofixed

**Blanket `# noqa` on docstring-closing lines** (4, in
`checkpoint-postgres` and `checkpoint-sqlite`). `E501` is in
`lint.ignore` for those packages, so nothing was being suppressed:

```diff
-        """  # noqa
+        """
```

**`# noqa: F821` on `anext(aiter_)`** (2). Left over from Python 3.9
support. `anext` became a builtin in 3.10, which is the floor now, so
`F821` no longer fires:

```diff
-                    anext(aiter_),  # type: ignore[arg-type]  # noqa: F821
+                    anext(aiter_),  # type: ignore[arg-type]
```

**Suppressions naming rules the package does not enable** (27), across
`langgraph`, `prebuilt` and `checkpoint-sqlite`: `FBT001`, `FBT002`,
`TC002`, `BLE001`, `ANN001`, `ANN002`, `ANN003`, `E501`, `F401`. Mostly
copied between packages whose rule sets differ.

### One measurement note

If you check these numbers yourself, use `--extend-select`:

```
ruff check --select RUF100 .          # 81, misleading
ruff check --extend-select RUF100 .   # 33, real
```

With a bare `--select`, ruff treats every other rule as disabled, so
every suppression for another rule looks unused. I quoted 81 before
catching that.

### Verified

`checkpoint-sqlite` 118 passed, `prebuilt` 284 passed, `langgraph` 1968
passed, `checkpoint-postgres` 264 passed on PG 15 and 16. `make lint`
clean in every package.

Independent of #8540 and #8537, so it can land in any order.
2026-08-06 17:38:31 -04:00
Elior Nataf LackritzandGitHub 658541c496 chore(checkpoint-postgres,checkpoint-sqlite): enable PLC0415 lint rule (#8540)
Follow-up to review on #8537: turn on ruff's `PLC0415`
(`import-outside-top-level`) so deferred imports in tests stop
accumulating.

Scoped to `checkpoint-postgres` and `checkpoint-sqlite` rather than
repo-wide, because the sweep turns up three different things and only
one of them is a style problem.

### What the rule finds today

```
package                 tests   src    files
checkpoint                13     10      11
checkpoint-conformance     0     10       4
checkpoint-postgres        6      0       2
checkpoint-sqlite          9      0       3
langgraph                130     23      32
prebuilt                  14      3       7
cli                        9     14      10
sdk-py                   189     23      38
                        ────────────────────
                         370     83     107
```

453 violations across 107 files, and ruff has no autofix for this rule.

### Three categories, not one

**Style — hoist.** `checkpoint-sqlite/tests/test_store.py` deferred
`math`, `random`, `time`, `Counter` and `defaultdict` inside methods for
no reason.

**Deliberate — keep, annotate.**
`checkpoint-postgres/tests/test_async.py` defers behind
`pytest.importorskip("langgraph.channels.delta")` because langgraph core
is *not* a test dependency of that package. Hoisting would break the
skip. Those get `# noqa: PLC0415` and a comment.

**Redundant guard — hoist.**
`checkpoint-sqlite/tests/test_conformance_delta.py` deferred imports
only to get past its own `importorskip`. Imports move up; the
`aiosqlite` guard stays, since that dependency genuinely can be absent.

The second category is why I did not enable this everywhere in one go.
Most of the 83 source-level violations look like the same pattern —
optional-dependency handling and circular-import avoidance in
`jsonplus.py`, `embed.py`, `encrypted.py` and friends. Blanket-enabling
would mean `# noqa` on a lot of correct code, and each one wants an
owner's eye rather than a mechanical pass.

These two packages are clean to enforce today because both have **zero**
source-level violations.

### Suggested rollout for the rest

Either extend package by package as owners confirm which deferrals are
intentional, or enable everywhere at once with `per-file-ignores`
grandfathering the current 107 files so new code is blocked immediately
and the debt burns down. Happy to do either — the second is a smaller
diff but leaves a long ignore list.

### Verified

`checkpoint-sqlite` 118 passed, `checkpoint-postgres` 264 passed on PG
15 and 16, `make lint` clean in both.

One overlap worth flagging:
`checkpoint-sqlite/tests/test_conformance_delta.py` is also touched by
#8537. The change is identical in both, so it should merge cleanly
either way.
2026-08-05 21:23:28 -04:00
103 changed files with 1818 additions and 1250 deletions
@@ -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,
]
@@ -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]
+1 -1
View File
@@ -279,7 +279,7 @@ wheels = [
[[package]]
name = "langgraph-checkpoint"
version = "4.1.1"
version = "4.2.0"
source = { editable = "../checkpoint" }
dependencies = [
{ name = "langchain-core" },
+8 -32
View File
@@ -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},
},
}
@@ -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),
)
@@ -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:
@@ -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
@@ -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:
+5 -1
View File
@@ -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"]
+8 -6
View File
@@ -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)]
@@ -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}")
@@ -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', '<missing>')}"
)
# 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', '<missing>')}"
)
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', '<missing>')!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', '<missing>')!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
+34 -3
View File
@@ -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" },
@@ -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
@@ -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:
+4
View File
@@ -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"]
@@ -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:
@@ -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
@@ -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
+4 -9
View File
@@ -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!"}
+33 -2
View File
@@ -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" },
@@ -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))
)
+7 -1
View File
@@ -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]
@@ -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():
-2
View File
@@ -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
+4 -9
View File
@@ -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
+48 -19
View File
@@ -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
+1 -1
View File
@@ -300,7 +300,7 @@ wheels = [
[[package]]
name = "langgraph-checkpoint"
version = "4.1.1"
version = "4.2.0"
source = { editable = "." }
dependencies = [
{ name = "langchain-core" },
+6
View File
@@ -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]
+1 -6
View File
@@ -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()
@@ -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")
@@ -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",
@@ -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:
+1 -1
View File
@@ -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:
+1 -1
View File
@@ -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
+9 -1
View File
@@ -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."""
+15 -1
View File
@@ -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
+17 -3
View File
@@ -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
+40
View File
@@ -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"
+6 -2
View File
@@ -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"]
+1 -2
View File
@@ -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"]
+3 -8
View File
@@ -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)
+4 -4
View File
@@ -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
@@ -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"))
@@ -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}"
@@ -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)
+4 -4
View File
@@ -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(
+1 -1
View File
@@ -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()
+16 -45
View File
@@ -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
+16 -29
View File
@@ -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
+18 -27
View File
@@ -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)
+18 -29
View File
@@ -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]
+3 -4
View File
@@ -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
+3 -5
View File
@@ -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")
+1 -1
View File
@@ -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()
+3 -5
View File
@@ -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": ""})
+2 -3
View File
@@ -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"}
+1 -1
View File
@@ -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()
@@ -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)
@@ -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)
+149
View File
@@ -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}
+4 -10
View File
@@ -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"}}
+317 -313
View File
@@ -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]]
+17 -4
View File
@@ -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
@@ -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"
+5 -1
View File
@@ -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]
+1 -2
View File
@@ -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"]
@@ -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 {},
+2 -2
View File
@@ -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}
+4 -10
View File
@@ -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",
+9 -5
View File
@@ -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" },
]
+3 -1
View File
@@ -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.
+5 -3
View File
@@ -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
+1 -1
View File
@@ -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"]]
]
+6 -2
View File
@@ -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`
+5 -4
View File
@@ -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:
@@ -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))
+5 -4
View File
@@ -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))
+5 -4
View File
@@ -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))
@@ -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))
+5 -4
View File
@@ -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))
+5 -4
View File
@@ -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))
@@ -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": []})
+9 -15
View File
@@ -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())
+2 -1
View File
@@ -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="")
@@ -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={})
@@ -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()
@@ -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(
@@ -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)
@@ -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={})
@@ -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")
@@ -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(
@@ -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})
@@ -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})],
@@ -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})
@@ -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")
@@ -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()
@@ -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.
+2 -2
View File
@@ -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"})
+2 -5
View File
@@ -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] = {}

Some files were not shown because too many files have changed in this diff Show More