Compare commits

..
Author SHA1 Message Date
Elior Nataf LackritzandGitHub 1400d25b7b release(cli): 0.4.37 (#9351)
Bumps the CLI to 0.4.37 to cut a release including #9333 (`langgraph
deploy` defaults to serverless) and #9350 (`--verbose` no longer prints
subprocess stdin), the two CLI changes merged since 0.4.36.

Merging doesn't publish by itself: after it merges, run the `release`
workflow with `working-directory: libs/cli`.
2026-10-19 10:03:29 -04:00
Elior Nataf LackritzandGitHub 7b23a10846 fix(cli): stop printing subprocess stdin in verbose output (#9350)
`--verbose` printed each subprocess command followed by everything piped
to its stdin, which can hold credentials. It now prints only the command
line, so `langgraph up --verbose`, `langgraph build` and `langgraph
deploy --verbose` no longer show the generated compose file or
Dockerfile (`langgraph dockerfile` still writes the Dockerfile out).
2026-10-19 13:45:42 +00:00
Vladislav NechakhinGitHubVladislav Nechakhinopen-swe[bot] <open-swe@users.noreply.github.com>
8b9c1a1301 Merge PR #9349
Update brace-expansion from 5.0.9 to 5.0.12 in both CLI JavaScript
example lockfiles to address CVE-2026-102277. Existing dependency ranges
already allow the patched version; no manifests or runtime code change.

Verification: generated the replacement entries with the declared Yarn
1.22.22 in an isolated dependency fixture, then checked basic
brace/range expansion and `git diff --check`. Full example installs were
attempted but blocked by registry.yarnpkg.com DNS/timeouts, including
with npm selected as the registry; only the authentic generated package
entries were transferred, preserving unrelated resolutions. Full package
lint/tests were not run and CI is not claimed.

## References
- [Slack
thread](https://langchain.slack.com/archives/D0C00MN8BR8/p1792399312164349)

Made by [Open SWE](https://github.com/langchain-ai/open-swe) · [view
thread](https://openswe.langchain.dev/agents/07fb6bc4-65cc-5511-a329-5db31424d846)
· openai:gpt-6.1-sol (medium)

Co-authored-by: Vladislav Nechakhin <vladislav-nechakhin@users.noreply.github.com>
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-10-19 21:47:25 +09:00
hari-dhanushkodiGitHubHari Dhanushkodiopen-swe[bot] <open-swe@users.noreply.github.com>
98043c37ab fix(cli): default deploy to serverless (#9333)
- Default new deployments to serverless, preserving explicit
deployment-type overrides.
- Let the backend determine AWS serverless availability; preserve its
rejection and append guidance to use --deployment-type dev. Do not
detect AWS hostnames, block attempts locally, or silently fall back.
- Serverless deployment on AWS works without another CLI change once the
backend supports it.

## References
- [Slack
thread](https://langchain.slack.com/archives/C0BHM1C5R5L/p1792175194359579)

Made by [Open SWE](https://github.com/langchain-ai/open-swe) · [view
thread](https://openswe.langchain.dev/agents/c71525bc-e760-503e-b187-6997859056c1)
· openai:gpt-6.1-sol (medium)

---------

Co-authored-by: Hari Dhanushkodi <hari-dhanushkodi@users.noreply.github.com>
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-10-18 15:18:50 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>John Kennedyopen-swe[bot] <open-swe@users.noreply.github.com>John Kennedy
0870f5efce chore(deps): bump the minor-and-patch group in /libs/cli/js-examples with 9 updates (#9152)
Bumps the minor-and-patch group in /libs/cli/js-examples with 9 updates:

| Package | From | To |
| --- | --- | --- |
| [@langchain/core](https://github.com/langchain-ai/langchainjs) |
`1.2.9` | `1.2.13` |
|
[@langchain/langgraph](https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core)
| `1.4.13` | `1.4.18` |
| [@eslint/eslintrc](https://github.com/eslint/eslintrc) | `3.3.6` |
`3.3.7` |
|
[@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin)
| `8.68.0` | `8.70.1` |
|
[@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser)
| `8.68.0` | `8.70.1` |
| [eslint](https://github.com/eslint/eslint) | `10.9.1` | `10.11.0` |
| [jest](https://github.com/jestjs/jest/tree/HEAD/packages/jest) |
`30.5.0` | `30.5.2` |
| [prettier](https://github.com/prettier/prettier) | `3.9.6` | `3.9.9` |
| [ts-jest](https://github.com/kulshekhar/ts-jest) | `29.4.12` |
`29.4.14` |

Updates `@langchain/core` from 1.2.9 to 1.2.13
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langchainjs/releases">@​langchain/core's
releases</a>.</em></p>
<blockquote>
<h2><code>@​langchain/core</code><a
href="https://github.com/1"><code>@​1</code></a>.2.13</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/langchain-ai/langchainjs/pull/11714">#11714</a>
<a
href="https://github.com/langchain-ai/langchainjs/commit/a83dfb14f8e17fcb66dc7a915f0cf8ed7b0dffa1"><code>a83dfb1</code></a>
Thanks <a href="https://github.com/ccurme"><code>@​ccurme</code></a>! -
Abbreviate long tool-call IDs when formatting chat messages as strings,
keeping original messages unchanged.</li>
</ul>
<h2><code>@​langchain/core</code><a
href="https://github.com/1"><code>@​1</code></a>.2.12</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/langchain-ai/langchainjs/pull/11675">#11675</a>
<a
href="https://github.com/langchain-ai/langchainjs/commit/030a726639e0147488bc8c23c063a2e72b004c2e"><code>030a726</code></a>
Thanks <a href="https://github.com/hntrl"><code>@​hntrl</code></a>! -
Preserve structured tool arguments in tracer run inputs.</li>
</ul>
<h2><code>@​langchain/core</code><a
href="https://github.com/1"><code>@​1</code></a>.2.11</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/langchain-ai/langchainjs/pull/11603">#11603</a>
<a
href="https://github.com/langchain-ai/langchainjs/commit/fec9cd87b01976014dd549bd2cf7849aee89a566"><code>fec9cd8</code></a>
Thanks <a
href="https://github.com/thushanth-bengre-langchain"><code>@​thushanth-bengre-langchain</code></a>!
- fix(core): build streaming <code>llmOutput.tokenUsage</code> from the
fully-accumulated chunk instead of whichever individual chunk's
<code>usage_metadata</code> arrived last</p>
<p>Affects both core streaming paths —
<code>.stream()</code>/<code>.streamEvents()</code>
(<code>_streamIterator</code>) and
<code>.invoke()</code>/<code>.generate()</code> when a
streaming-preferring callback is attached
(<code>_generateWithCache</code>'s <code>hasStreamingHandler</code>
branch). Previously, <code>llmOutput.tokenUsage</code> was overwritten
by each chunk in turn, so only the last chunk carrying
<code>usage_metadata</code> won — correct for providers that emit one
cumulative total on a final chunk, but wrong for providers (e.g.
<code>@langchain/google</code>, <code>@langchain/anthropic</code>) that
emit <code>usage_metadata</code> as a per-chunk delta across multiple
chunks, where the values must be summed.</p>
<p>Note for provider authors: this assumes each streamed chunk's
<code>usage_metadata</code> is either a per-chunk delta or appears only
on a single final chunk. A provider that instead repeats a cumulative
total on every chunk will now see it summed (and inflated) in
<code>llmOutput.tokenUsage</code>, matching the existing behavior of the
correctly-working <code>message.usage_metadata</code> field.</p>
<p>Also fixes <code>@langchain/google</code>'s <code>invoke({streaming:
true})</code> path (no streaming-preferring callback attached), where
<code>llmOutput</code> was never populated at all.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/langchain-ai/langchainjs/pull/11590">#11590</a>
<a
href="https://github.com/langchain-ai/langchainjs/commit/ffebdc2f00f3290d19f85e5afd6a297920ae584c"><code>ffebdc2</code></a>
Thanks <a
href="https://github.com/thushanth-bengre-langchain"><code>@​thushanth-bengre-langchain</code></a>!
- Fix OpenAI Responses API replay under Zero Data Retention when a
response contains more than one reasoning item, for both v0 and v1. In
v0, the default replay path now reuses
<code>response_metadata.output</code> directly, preserving every
reasoning item's <code>id</code>/<code>encrypted_content</code> in
original order. In v1, <code>AIMessage.contentBlocks</code>
(<code>outputVersion: &quot;v1&quot;</code>) is fixed the same way.
<code>additional_kwargs.reasoning</code> is unchanged.</p>
</li>
</ul>
<h2><code>@​langchain/core</code><a
href="https://github.com/1"><code>@​1</code></a>.2.10</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/langchain-ai/langchainjs/pull/10047">#10047</a>
<a
href="https://github.com/langchain-ai/langchainjs/commit/ff1248fd49dacdb35f5da128278cd777068481d7"><code>ff1248f</code></a>
Thanks <a
href="https://github.com/afirstenberg"><code>@​afirstenberg</code></a>!
- fix(core): BaseMessage.text use contentBlocks</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langchainjs/commit/c8d12783ecc31a0816f3a47da1040fbd3c08fdbc"><code>c8d1278</code></a>
chore: version packages (<a
href="https://redirect.github.com/langchain-ai/langchainjs/issues/11729">#11729</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchainjs/commit/5d509ad4b8a5ed9dd4b83020b4c103ac8a69859c"><code>5d509ad</code></a>
fix(openai): send in-memory prompt cache retention as in_memory (<a
href="https://redirect.github.com/langchain-ai/langchainjs/issues/11735">#11735</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchainjs/commit/663b9a2076c5b274435c77524ad7730450b34f34"><code>663b9a2</code></a>
feat(openai): support explicit prompt caching (<a
href="https://redirect.github.com/langchain-ai/langchainjs/issues/11232">#11232</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchainjs/commit/7735b219221d6ae63eb5445fb67d26c18fce9c46"><code>7735b21</code></a>
chore(infra): drop held mcp-adapters 2.0 changeset to unblock main
releases (...</li>
<li><a
href="https://github.com/langchain-ai/langchainjs/commit/34edee1153f0fa40ba6997c8ff7bd45834ca8ce8"><code>34edee1</code></a>
fix(langchain): return failed returnDirect tool calls to the model (<a
href="https://redirect.github.com/langchain-ai/langchainjs/issues/11712">#11712</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchainjs/commit/0fcb98b4306a771c940e78d6881d0be9f88507c1"><code>0fcb98b</code></a>
fix(textsplitters): use char length for loc.lines with custom
lengthFunction ...</li>
<li><a
href="https://github.com/langchain-ai/langchainjs/commit/e4a3d1bd0c6753d4e1739a7f10064f48e6bc49ec"><code>e4a3d1b</code></a>
chore: version packages (<a
href="https://redirect.github.com/langchain-ai/langchainjs/issues/11683">#11683</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchainjs/commit/a9ada857f22c4b746801e7723ffaa4c4d59db771"><code>a9ada85</code></a>
fix(openai): send tool result images so the model can see them (<a
href="https://redirect.github.com/langchain-ai/langchainjs/issues/11690">#11690</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchainjs/commit/485d030054ff599beea7d1070d87cac22bcef3cc"><code>485d030</code></a>
chore(mcp-adapters): hold the adapter out of releases until 2.0 is ready
(<a
href="https://redirect.github.com/langchain-ai/langchainjs/issues/11">#11</a>...</li>
<li><a
href="https://github.com/langchain-ai/langchainjs/commit/a83dfb14f8e17fcb66dc7a915f0cf8ed7b0dffa1"><code>a83dfb1</code></a>
fix(core): abbreviate long tool IDs in buffer strings (<a
href="https://redirect.github.com/langchain-ai/langchainjs/issues/11714">#11714</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langchainjs/compare/@langchain/core@1.2.9...@langchain/core@1.2.13">compare
view</a></li>
</ul>
</details>
<br />

Updates `@langchain/langgraph` from 1.4.13 to 1.4.18
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langgraphjs/releases">@​langchain/langgraph's
releases</a>.</em></p>
<blockquote>
<h2><code>@​langchain/langgraph</code><a
href="https://github.com/1"><code>@​1</code></a>.4.18</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies [<a
href="https://github.com/langchain-ai/langgraphjs/commit/7a12289df4ed6441171ec393da4ccfccd98d69e8"><code>7a12289</code></a>]:
<ul>
<li><code>@​langchain/langgraph-sdk</code><a
href="https://github.com/1"><code>@​1</code></a>.12.0</li>
</ul>
</li>
</ul>
<h2><code>@​langchain/langgraph</code><a
href="https://github.com/1"><code>@​1</code></a>.4.18-rc.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies [<a
href="https://github.com/langchain-ai/langgraphjs/commit/7a12289df4ed6441171ec393da4ccfccd98d69e8"><code>7a12289</code></a>]:
<ul>
<li><code>@​langchain/langgraph-sdk</code><a
href="https://github.com/1"><code>@​1</code></a>.12.0-rc.0</li>
</ul>
</li>
</ul>
<h2><code>@​langchain/langgraph</code><a
href="https://github.com/1"><code>@​1</code></a>.4.17</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/langchain-ai/langgraphjs/pull/2861">#2861</a>
<a
href="https://github.com/langchain-ai/langgraphjs/commit/7639085f4d8d998ea4f9cfc0de8440c77bc2f78d"><code>7639085</code></a>
Thanks <a href="https://github.com/casparb"><code>@​casparb</code></a>!
- Add <code>GraphCallbackHandler</code> with typed
<code>handleInterrupt</code> and <code>handleResume</code> events
through normal callback configuration, matching Python graph lifecycle
behavior. Lifecycle callbacks are awaited before terminal chain
callbacks and work independently of stream mode.</li>
</ul>
<h2><code>@​langchain/langgraph</code><a
href="https://github.com/1"><code>@​1</code></a>.4.16</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/langchain-ai/langgraphjs/pull/2824">#2824</a>
<a
href="https://github.com/langchain-ai/langgraphjs/commit/e75f6a08e96f65d201a0bb501a97e00bcffe83a2"><code>e75f6a0</code></a>
Thanks <a href="https://github.com/eliornl"><code>@​eliornl</code></a>!
- feat(langgraph): add <code>responseSchema</code> option to
<code>interrupt()</code></p>
<p><code>interrupt(value, { responseSchema })</code> lets a graph
declare the shape of the
value it expects on resume. A Zod schema validates the resume value and
the
parsed result is what <code>interrupt()</code> returns; a raw JSON
Schema object is passed
through as-is. The schema is surfaced on
<code>Interrupt.response_schema</code> so clients
such as Studio can render a typed form instead of a free-form JSON
editor.
Omitting the option keeps today's behavior.</p>
</li>
<li>
<p>Updated dependencies [<a
href="https://github.com/langchain-ai/langgraphjs/commit/e75f6a08e96f65d201a0bb501a97e00bcffe83a2"><code>e75f6a0</code></a>]:</p>
<ul>
<li><code>@​langchain/langgraph-sdk</code><a
href="https://github.com/1"><code>@​1</code></a>.11.1</li>
</ul>
</li>
</ul>
<h2><code>@​langchain/langgraph</code><a
href="https://github.com/1"><code>@​1</code></a>.4.15</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/langchain-ai/langgraphjs/pull/2794">#2794</a>
<a
href="https://github.com/langchain-ai/langgraphjs/commit/83a4b62be99488f17f3d7370a064f635e7c05a2e"><code>83a4b62</code></a>
Thanks <a href="https://github.com/hntrl"><code>@​hntrl</code></a>! -
feat(langgraph): add per-node <code>tracePolicy</code> input/output
processors and <code>omitPayload</code></p>
<p>Transform the payloads recorded on a node's own trace run while
retaining its span and timing. Processors receive raw values and fall
back to the original payload if they throw. Graph state, root runs, and
child runs remain unchanged when processors do not mutate their
arguments.</p>
<p>Matches Python's callback-level behavior: transforms also affect
chain events and message streaming, so omitting outputs can suppress
messages returned directly by nodes and omitting inputs can affect
message deduplication.</p>
</li>
<li>
<p>Updated dependencies [<a
href="https://github.com/langchain-ai/langgraphjs/commit/3234c69530416e2709bf54c53472e640864e7de4"><code>3234c69</code></a>,
<a
href="https://github.com/langchain-ai/langgraphjs/commit/3234c69530416e2709bf54c53472e640864e7de4"><code>3234c69</code></a>,
<a
href="https://github.com/langchain-ai/langgraphjs/commit/11a4535762b04f8f28cc98eb7b1e4b682b69e91a"><code>11a4535</code></a>,
<a
href="https://github.com/langchain-ai/langgraphjs/commit/2fab6fda74714cd792fed24e5416cec66fdbc105"><code>2fab6fd</code></a>,
<a
href="https://github.com/langchain-ai/langgraphjs/commit/4fc118fcde8fd6d977c03c8a0a7071912df1873a"><code>4fc118f</code></a>,
<a
href="https://github.com/langchain-ai/langgraphjs/commit/db4bdad61ddfc6c1113269b131ac2efde3eecf69"><code>db4bdad</code></a>,
<a
href="https://github.com/langchain-ai/langgraphjs/commit/55fa26be9290fbd89a6e0acb232f04cbc6dedb22"><code>55fa26b</code></a>]:</p>
<ul>
<li><code>@​langchain/langgraph-sdk</code><a
href="https://github.com/1"><code>@​1</code></a>.11.0</li>
</ul>
</li>
</ul>
<h2><code>@​langchain/langgraph</code><a
href="https://github.com/1"><code>@​1</code></a>.4.15-rc.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/langchain-ai/langgraphjs/pull/2794">#2794</a>
<a
href="https://github.com/langchain-ai/langgraphjs/commit/83a4b62be99488f17f3d7370a064f635e7c05a2e"><code>83a4b62</code></a>
Thanks <a href="https://github.com/hntrl"><code>@​hntrl</code></a>! -
feat(langgraph): add per-node <code>tracePolicy</code> input/output
processors and <code>omitPayload</code></p>
<p>Transform the payloads recorded on a node's own trace run while
retaining its span and timing. Processors receive raw values and fall
back to the original payload if they throw. Graph state, root runs, and
child runs remain unchanged when processors do not mutate their
arguments.</p>
</li>
<li>
<p>Updated dependencies [<a
href="https://github.com/langchain-ai/langgraphjs/commit/2fab6fda74714cd792fed24e5416cec66fdbc105"><code>2fab6fd</code></a>]:</p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langgraphjs/blob/main/libs/langgraph-core/CHANGELOG.md">@​langchain/langgraph's
changelog</a>.</em></p>
<blockquote>
<h2>1.4.18</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies [<a
href="https://github.com/langchain-ai/langgraphjs/commit/7a12289df4ed6441171ec393da4ccfccd98d69e8"><code>7a12289</code></a>]:
<ul>
<li><code>@​langchain/langgraph-sdk</code><a
href="https://github.com/1"><code>@​1</code></a>.12.0</li>
</ul>
</li>
</ul>
<h2>1.4.18-rc.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies [<a
href="https://github.com/langchain-ai/langgraphjs/commit/7a12289df4ed6441171ec393da4ccfccd98d69e8"><code>7a12289</code></a>]:
<ul>
<li><code>@​langchain/langgraph-sdk</code><a
href="https://github.com/1"><code>@​1</code></a>.12.0-rc.0</li>
</ul>
</li>
</ul>
<h2>1.4.17</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/langchain-ai/langgraphjs/pull/2861">#2861</a>
<a
href="https://github.com/langchain-ai/langgraphjs/commit/7639085f4d8d998ea4f9cfc0de8440c77bc2f78d"><code>7639085</code></a>
Thanks <a href="https://github.com/casparb"><code>@​casparb</code></a>!
- Add <code>GraphCallbackHandler</code> with typed
<code>handleInterrupt</code> and <code>handleResume</code> events
through normal callback configuration, matching Python graph lifecycle
behavior. Lifecycle callbacks are awaited before terminal chain
callbacks and work independently of stream mode.</li>
</ul>
<h2>1.4.16</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/langchain-ai/langgraphjs/pull/2824">#2824</a>
<a
href="https://github.com/langchain-ai/langgraphjs/commit/e75f6a08e96f65d201a0bb501a97e00bcffe83a2"><code>e75f6a0</code></a>
Thanks <a href="https://github.com/eliornl"><code>@​eliornl</code></a>!
- feat(langgraph): add <code>responseSchema</code> option to
<code>interrupt()</code></p>
<p><code>interrupt(value, { responseSchema })</code> lets a graph
declare the shape of the
value it expects on resume. A Zod schema validates the resume value and
the
parsed result is what <code>interrupt()</code> returns; a raw JSON
Schema object is passed
through as-is. The schema is surfaced on
<code>Interrupt.response_schema</code> so clients
such as Studio can render a typed form instead of a free-form JSON
editor.
Omitting the option keeps today's behavior.</p>
</li>
<li>
<p>Updated dependencies [<a
href="https://github.com/langchain-ai/langgraphjs/commit/e75f6a08e96f65d201a0bb501a97e00bcffe83a2"><code>e75f6a0</code></a>]:</p>
<ul>
<li><code>@​langchain/langgraph-sdk</code><a
href="https://github.com/1"><code>@​1</code></a>.11.1</li>
</ul>
</li>
</ul>
<h2>1.4.15</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/langchain-ai/langgraphjs/pull/2794">#2794</a>
<a
href="https://github.com/langchain-ai/langgraphjs/commit/83a4b62be99488f17f3d7370a064f635e7c05a2e"><code>83a4b62</code></a>
Thanks <a href="https://github.com/hntrl"><code>@​hntrl</code></a>! -
feat(langgraph): add per-node <code>tracePolicy</code> input/output
processors and <code>omitPayload</code></p>
<p>Transform the payloads recorded on a node's own trace run while
retaining its span and timing. Processors receive raw values and fall
back to the original payload if they throw. Graph state, root runs, and
child runs remain unchanged when processors do not mutate their
arguments.</p>
<p>Matches Python's callback-level behavior: transforms also affect
chain events and message streaming, so omitting outputs can suppress
messages returned directly by nodes and omitting inputs can affect
message deduplication.</p>
</li>
<li>
<p>Updated dependencies [<a
href="https://github.com/langchain-ai/langgraphjs/commit/3234c69530416e2709bf54c53472e640864e7de4"><code>3234c69</code></a>,
<a
href="https://github.com/langchain-ai/langgraphjs/commit/3234c69530416e2709bf54c53472e640864e7de4"><code>3234c69</code></a>,
<a
href="https://github.com/langchain-ai/langgraphjs/commit/11a4535762b04f8f28cc98eb7b1e4b682b69e91a"><code>11a4535</code></a>,
<a
href="https://github.com/langchain-ai/langgraphjs/commit/2fab6fda74714cd792fed24e5416cec66fdbc105"><code>2fab6fd</code></a>,
<a
href="https://github.com/langchain-ai/langgraphjs/commit/4fc118fcde8fd6d977c03c8a0a7071912df1873a"><code>4fc118f</code></a>,
<a
href="https://github.com/langchain-ai/langgraphjs/commit/db4bdad61ddfc6c1113269b131ac2efde3eecf69"><code>db4bdad</code></a>,
<a
href="https://github.com/langchain-ai/langgraphjs/commit/55fa26be9290fbd89a6e0acb232f04cbc6dedb22"><code>55fa26b</code></a>]:</p>
<ul>
<li><code>@​langchain/langgraph-sdk</code><a
href="https://github.com/1"><code>@​1</code></a>.11.0</li>
</ul>
</li>
</ul>
<h2>1.4.15-rc.0</h2>
<h3>Patch Changes</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/ec8cb378e3c7846b2a4aea31dfc4aff8f1cbf2fb"><code>ec8cb37</code></a>
chore: version packages (<a
href="https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core/issues/2870">#2870</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/285ed3d60c51a90c2d5698f617d6ad7ce3048ef2"><code>285ed3d</code></a>
chore: version packages (rc) (<a
href="https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core/issues/2868">#2868</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/3dd35b6d0c4d7e5d2499ce44d85ecb0c0a34e1fd"><code>3dd35b6</code></a>
chore: version packages (<a
href="https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core/issues/2862">#2862</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/7639085f4d8d998ea4f9cfc0de8440c77bc2f78d"><code>7639085</code></a>
feat(langgraph): add graph lifecycle callbacks (<a
href="https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core/issues/2861">#2861</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/ec67d5d70dc26341e92a0962d9d2f4018c310b39"><code>ec67d5d</code></a>
chore: version packages (<a
href="https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core/issues/2852">#2852</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/e75f6a08e96f65d201a0bb501a97e00bcffe83a2"><code>e75f6a0</code></a>
feat(langgraph): add responseSchema option to interrupt() (<a
href="https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core/issues/2824">#2824</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/592fd0cab0fd9287fab1b7c7fcfd4abc44675700"><code>592fd0c</code></a>
chore: version packages (<a
href="https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core/issues/2832">#2832</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/a3172e045b36b00244512260b041e472b915c39b"><code>a3172e0</code></a>
revert(langgraph): <code>tracePolicy</code> (<a
href="https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core/issues/2834">#2834</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/a97e6f4eaf8683cd67002bc42cf8dddf5255e29d"><code>a97e6f4</code></a>
chore(deps): patch vulnerable browser and example dependencies (<a
href="https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core/issues/2822">#2822</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/6fb9a3fe43d0e4d600fbf413392343d7242ccb63"><code>6fb9a3f</code></a>
chore(deps): bump vitest from 4.1.10 to 4.1.11 (<a
href="https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core/issues/2818">#2818</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langgraphjs/commits/@langchain/langgraph@1.4.18/libs/langgraph-core">compare
view</a></li>
</ul>
</details>
<br />

Updates `@eslint/eslintrc` from 3.3.6 to 3.3.7
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/eslint/eslintrc/releases">@​eslint/eslintrc's
releases</a>.</em></p>
<blockquote>
<h2>eslintrc: v3.3.7</h2>
<h2><a
href="https://github.com/eslint/eslintrc/compare/eslintrc-v3.3.6...eslintrc-v3.3.7">3.3.7</a>
(2026-09-01)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>Bump js-yaml to 4.3.1 (<a
href="https://redirect.github.com/eslint/eslintrc/issues/239">#239</a>)
(<a
href="https://github.com/eslint/eslintrc/commit/f27e7c94e6d9438bd51cb483d8d5da768e1cc0b9">f27e7c9</a>)</li>
<li>update js-yaml to 4.3.2 to address security vulnerability (<a
href="https://redirect.github.com/eslint/eslintrc/issues/243">#243</a>)
(<a
href="https://github.com/eslint/eslintrc/commit/bb0d97a338937b88fa99d6bbc0a904e34eda3d5f">bb0d97a</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/eslint/eslintrc/blob/main/CHANGELOG.md">@​eslint/eslintrc's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/eslint/eslintrc/compare/eslintrc-v3.3.6...eslintrc-v3.3.7">3.3.7</a>
(2026-09-01)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>Bump js-yaml to 4.3.1 (<a
href="https://redirect.github.com/eslint/eslintrc/issues/239">#239</a>)
(<a
href="https://github.com/eslint/eslintrc/commit/f27e7c94e6d9438bd51cb483d8d5da768e1cc0b9">f27e7c9</a>)</li>
<li>update js-yaml to 4.3.2 to address security vulnerability (<a
href="https://redirect.github.com/eslint/eslintrc/issues/243">#243</a>)
(<a
href="https://github.com/eslint/eslintrc/commit/bb0d97a338937b88fa99d6bbc0a904e34eda3d5f">bb0d97a</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/eslint/eslintrc/commit/7943fa6dd55b236a539f658b88c763a4f9fbb38d"><code>7943fa6</code></a>
chore: release 3.3.7 🚀 (<a
href="https://redirect.github.com/eslint/eslintrc/issues/240">#240</a>)</li>
<li><a
href="https://github.com/eslint/eslintrc/commit/bb0d97a338937b88fa99d6bbc0a904e34eda3d5f"><code>bb0d97a</code></a>
fix: update js-yaml to 4.3.2 to address security vulnerability (<a
href="https://redirect.github.com/eslint/eslintrc/issues/243">#243</a>)</li>
<li><a
href="https://github.com/eslint/eslintrc/commit/5b4abc9c74dd92b9eb782ca81d559a88906509e9"><code>5b4abc9</code></a>
docs: Update README sponsors</li>
<li><a
href="https://github.com/eslint/eslintrc/commit/f27e7c94e6d9438bd51cb483d8d5da768e1cc0b9"><code>f27e7c9</code></a>
fix: Bump js-yaml to 4.3.1 (<a
href="https://redirect.github.com/eslint/eslintrc/issues/239">#239</a>)</li>
<li><a
href="https://github.com/eslint/eslintrc/commit/b75e1d2425deebfd1ec7f952dda7d0c4f4d65d5c"><code>b75e1d2</code></a>
docs: Update README sponsors</li>
<li><a
href="https://github.com/eslint/eslintrc/commit/302c13310e148268f990df3edbfd10dab44f2678"><code>302c133</code></a>
docs: Update README sponsors</li>
<li><a
href="https://github.com/eslint/eslintrc/commit/e62e7661b0d9063e42a37af5551bbcb1897e188d"><code>e62e766</code></a>
docs: Update README sponsors</li>
<li><a
href="https://github.com/eslint/eslintrc/commit/cf27f9fd8f775d94500f2569a5bfb6a7de9cdb33"><code>cf27f9f</code></a>
docs: Update README sponsors</li>
<li><a
href="https://github.com/eslint/eslintrc/commit/c7f4cdf1ffa86e28b5b8bf09f9e8bc7fadbf87ff"><code>c7f4cdf</code></a>
docs: Update README sponsors</li>
<li><a
href="https://github.com/eslint/eslintrc/commit/3319822ac925e018c47fcafa351b20ea0bf6eeff"><code>3319822</code></a>
docs: Update README sponsors</li>
<li>Additional commits viewable in <a
href="https://github.com/eslint/eslintrc/compare/eslintrc-v3.3.6...eslintrc-v3.3.7">compare
view</a></li>
</ul>
</details>
<br />

Updates `@typescript-eslint/eslint-plugin` from 8.68.0 to 8.70.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases">@​typescript-eslint/eslint-plugin's
releases</a>.</em></p>
<blockquote>
<h2>v8.70.1</h2>
<h2>8.70.1 (2026-09-21)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>ast-spec:</strong> narrow import attribute keys to
identifiers and strings (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12879">#12879</a>)</li>
<li><strong>eslint-plugin:</strong> [no-useless-default-assignment]
avoid false positives on tuples with a rest element (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12768">#12768</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-parameters]
handle type precedence in the suggestion fixer (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12637">#12637</a>)</li>
<li><strong>eslint-plugin:</strong> [no-explicit-any] use unknown[] for
bare any rest parameters (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12818">#12818</a>)</li>
<li><strong>eslint-plugin:</strong> [no-generated-empty-object-type]
don't report a mapped type whose keys are not resolved yet (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12854">#12854</a>)</li>
<li><strong>eslint-plugin:</strong> [no-misused-spread] omit WeakMap
spread suggestions (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12850">#12850</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
false positive for empty object asserted to a type alias of Record (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12869">#12869</a>)</li>
<li><strong>eslint-plugin:</strong> [no-meaningless-void-operator] allow
void on assignment expressions (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12873">#12873</a>)</li>
<li><strong>eslint-plugin:</strong> [await-thenable] prevent autofix
from breaking code when removing <code>await</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12716">#12716</a>)</li>
<li><strong>eslint-plugin:</strong>
[no-unnecessary-parameter-property-assignment] account for parameter
reassignment (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12880">#12880</a>)</li>
<li><strong>eslint-plugin:</strong> [unbound-method] treat
Intl.Collator.prototype.compare as spec-bound (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12845">#12845</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-condition] handle
union-keyed index access on the left-hand side of nullish assignment (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12747">#12747</a>)</li>
<li><strong>eslint-plugin:</strong> [no-useless-default-assignment]
convert the fixer to a suggestion fixer (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12826">#12826</a>)</li>
<li><strong>eslint-plugin:</strong> [no-misused-promises] handle
multiple Promise constituents (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12904">#12904</a>)</li>
<li><strong>rule-tester:</strong> test the final autofix output instead
of the first pass (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12867">#12867</a>)</li>
<li><strong>scope-manager:</strong> merge implicit global definitions
(<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12809">#12809</a>)</li>
<li><strong>type-utils:</strong> match package specifiers on whole path
components (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12838">#12838</a>)</li>
<li><strong>typescript-estree:</strong> resolve symlinked paths when
matching files to projects (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12725">#12725</a>)</li>
<li><strong>typescript-estree:</strong> add missing <code>&lt;</code>
token opening type arguments (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12821">#12821</a>)</li>
<li><strong>typescript-estree:</strong> require string literal import
attribute values (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12894">#12894</a>)</li>
<li><strong>website:</strong> prevent playground from breaking down
after opening link with the .js file type (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12777">#12777</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Amin <a
href="https://github.com/amiin-dev"><code>@​amiin-dev</code></a></li>
<li>Brad Zacher <a
href="https://github.com/bradzacher"><code>@​bradzacher</code></a></li>
<li>Cameron</li>
<li>Diptajoy Mistry <a
href="https://github.com/diptomistry"><code>@​diptomistry</code></a></li>
<li>Evyatar Daud <a
href="https://github.com/StyleShit"><code>@​StyleShit</code></a></li>
<li>Grit <a
href="https://github.com/Grit03"><code>@​Grit03</code></a></li>
<li>Hugo <a
href="https://github.com/hugop95"><code>@​hugop95</code></a></li>
<li>Josh Goldberg ✨</li>
<li>Michael Naumov <a
href="https://github.com/mnaoumov"><code>@​mnaoumov</code></a></li>
<li>Mikhail Baev <a
href="https://github.com/baevm"><code>@​baevm</code></a></li>
<li>Om Rawat</li>
<li>overlookmotel</li>
<li>Sanath <a
href="https://github.com/sansynx"><code>@​sansynx</code></a></li>
<li>Shinji</li>
<li>stoicism <a
href="https://github.com/stoicism02"><code>@​stoicism02</code></a></li>
<li>Vinccool96</li>
<li>Younsang Na <a
href="https://github.com/nayounsang"><code>@​nayounsang</code></a></li>
<li>김채영 <a
href="https://github.com/cchaeyoung"><code>@​cchaeyoung</code></a></li>
<li>송재욱</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.70.1">GitHub
Releases</a> for more information.</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md">@​typescript-eslint/eslint-plugin's
changelog</a>.</em></p>
<blockquote>
<h2>8.70.1 (2026-09-21)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-misused-promises] handle
multiple Promise constituents (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12904">#12904</a>)</li>
<li><strong>eslint-plugin:</strong> [no-useless-default-assignment]
convert the fixer to a suggestion fixer (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12826">#12826</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-condition] handle
union-keyed index access on the left-hand side of nullish assignment (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12747">#12747</a>)</li>
<li><strong>eslint-plugin:</strong> [unbound-method] treat
Intl.Collator.prototype.compare as spec-bound (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12845">#12845</a>)</li>
<li><strong>eslint-plugin:</strong>
[no-unnecessary-parameter-property-assignment] account for parameter
reassignment (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12880">#12880</a>)</li>
<li><strong>eslint-plugin:</strong> [await-thenable] prevent autofix
from breaking code when removing <code>await</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12716">#12716</a>)</li>
<li><strong>eslint-plugin:</strong> [no-meaningless-void-operator] allow
void on assignment expressions (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12873">#12873</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
false positive for empty object asserted to a type alias of Record (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12869">#12869</a>)</li>
<li><strong>eslint-plugin:</strong> [no-misused-spread] omit WeakMap
spread suggestions (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12850">#12850</a>)</li>
<li><strong>eslint-plugin:</strong> [no-generated-empty-object-type]
don't report a mapped type whose keys are not resolved yet (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12854">#12854</a>)</li>
<li><strong>eslint-plugin:</strong> [no-explicit-any] use unknown[] for
bare any rest parameters (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12818">#12818</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-parameters]
handle type precedence in the suggestion fixer (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12637">#12637</a>)</li>
<li><strong>eslint-plugin:</strong> [no-useless-default-assignment]
avoid false positives on tuples with a rest element (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12768">#12768</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Brad Zacher <a
href="https://github.com/bradzacher"><code>@​bradzacher</code></a></li>
<li>Diptajoy Mistry <a
href="https://github.com/diptomistry"><code>@​diptomistry</code></a></li>
<li>Grit <a
href="https://github.com/Grit03"><code>@​Grit03</code></a></li>
<li>Hugo <a
href="https://github.com/hugop95"><code>@​hugop95</code></a></li>
<li>Michael Naumov <a
href="https://github.com/mnaoumov"><code>@​mnaoumov</code></a></li>
<li>Mikhail Baev <a
href="https://github.com/baevm"><code>@​baevm</code></a></li>
<li>Om Rawat</li>
<li>Sanath <a
href="https://github.com/sansynx"><code>@​sansynx</code></a></li>
<li>Shinji</li>
<li>Vinccool96</li>
<li>김채영 <a
href="https://github.com/cchaeyoung"><code>@​cchaeyoung</code></a></li>
<li>송재욱</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.70.1">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
<h2>8.70.0 (2026-09-07)</h2>
<h3>🚀 Features</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-generated-empty-object-type] add
rule (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12730">#12730</a>)</li>
</ul>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-deprecated] report deprecated
imported values used in object shorthand properties (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12780">#12780</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-condition] no false
positive on RHS of a nested logical expression (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12728">#12728</a>)</li>
<li><strong>eslint-plugin:</strong> [member-ordering] don't report
fields that read fields declared before them (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12729">#12729</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/23d38ceeb8fb1237f55cb62fc2b54419e1fa1ed7"><code>23d38ce</code></a>
chore(release): publish 8.70.1</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/9d82e4b1d9011da31d62d14387bb5a539853fafa"><code>9d82e4b</code></a>
chore: expand eslint-plugin rules test files glob (<a
href="https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin/issues/12878">#12878</a>)</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/f7482f636b89b8eab966b4191ce4a53a3f4b4d9c"><code>f7482f6</code></a>
fix(eslint-plugin): [no-misused-promises] handle multiple Promise
constituent...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/267304477e04538614fe72ad71e7e13082eb7a90"><code>2673044</code></a>
docs(eslint-plugin): [prefer-promise-reject-errors] add option sections
and e...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/8f141a2ba63d539465eb4e4604f7d79f6f50ce14"><code>8f141a2</code></a>
fix(eslint-plugin): [no-useless-default-assignment] convert the fixer to
a su...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/479cd85ff82b81dbec9989621681257ebc317a69"><code>479cd85</code></a>
chore: enable assertion option <code>requireData</code> for all rule
tests (<a
href="https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin/issues/12816">#12816</a>)</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/f3c190c5b5cfa9edaba15fa3f944a76a7364b3ed"><code>f3c190c</code></a>
fix(eslint-plugin): [no-unnecessary-condition] handle union-keyed index
acces...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/b1993dfd3c45b2c2d83058627499f1dfec5d6d2c"><code>b1993df</code></a>
fix(eslint-plugin): [unbound-method] treat
Intl.Collator.prototype.compare as...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/da394bc9c21afe97f456040da723d2c558f99658"><code>da394bc</code></a>
fix(eslint-plugin): [no-unnecessary-parameter-property-assignment]
account fo...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/4a742ff890b7cca8cbf79e78a9a3b345913094c6"><code>4a742ff</code></a>
fix(eslint-plugin): [await-thenable] prevent autofix from breaking code
when ...</li>
<li>Additional commits viewable in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.70.1/packages/eslint-plugin">compare
view</a></li>
</ul>
</details>
<br />

Updates `@typescript-eslint/parser` from 8.68.0 to 8.70.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases">@​typescript-eslint/parser's
releases</a>.</em></p>
<blockquote>
<h2>v8.70.1</h2>
<h2>8.70.1 (2026-09-21)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>ast-spec:</strong> narrow import attribute keys to
identifiers and strings (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12879">#12879</a>)</li>
<li><strong>eslint-plugin:</strong> [no-useless-default-assignment]
avoid false positives on tuples with a rest element (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12768">#12768</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-parameters]
handle type precedence in the suggestion fixer (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12637">#12637</a>)</li>
<li><strong>eslint-plugin:</strong> [no-explicit-any] use unknown[] for
bare any rest parameters (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12818">#12818</a>)</li>
<li><strong>eslint-plugin:</strong> [no-generated-empty-object-type]
don't report a mapped type whose keys are not resolved yet (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12854">#12854</a>)</li>
<li><strong>eslint-plugin:</strong> [no-misused-spread] omit WeakMap
spread suggestions (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12850">#12850</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
false positive for empty object asserted to a type alias of Record (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12869">#12869</a>)</li>
<li><strong>eslint-plugin:</strong> [no-meaningless-void-operator] allow
void on assignment expressions (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12873">#12873</a>)</li>
<li><strong>eslint-plugin:</strong> [await-thenable] prevent autofix
from breaking code when removing <code>await</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12716">#12716</a>)</li>
<li><strong>eslint-plugin:</strong>
[no-unnecessary-parameter-property-assignment] account for parameter
reassignment (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12880">#12880</a>)</li>
<li><strong>eslint-plugin:</strong> [unbound-method] treat
Intl.Collator.prototype.compare as spec-bound (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12845">#12845</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-condition] handle
union-keyed index access on the left-hand side of nullish assignment (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12747">#12747</a>)</li>
<li><strong>eslint-plugin:</strong> [no-useless-default-assignment]
convert the fixer to a suggestion fixer (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12826">#12826</a>)</li>
<li><strong>eslint-plugin:</strong> [no-misused-promises] handle
multiple Promise constituents (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12904">#12904</a>)</li>
<li><strong>rule-tester:</strong> test the final autofix output instead
of the first pass (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12867">#12867</a>)</li>
<li><strong>scope-manager:</strong> merge implicit global definitions
(<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12809">#12809</a>)</li>
<li><strong>type-utils:</strong> match package specifiers on whole path
components (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12838">#12838</a>)</li>
<li><strong>typescript-estree:</strong> resolve symlinked paths when
matching files to projects (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12725">#12725</a>)</li>
<li><strong>typescript-estree:</strong> add missing <code>&lt;</code>
token opening type arguments (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12821">#12821</a>)</li>
<li><strong>typescript-estree:</strong> require string literal import
attribute values (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12894">#12894</a>)</li>
<li><strong>website:</strong> prevent playground from breaking down
after opening link with the .js file type (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12777">#12777</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Amin <a
href="https://github.com/amiin-dev"><code>@​amiin-dev</code></a></li>
<li>Brad Zacher <a
href="https://github.com/bradzacher"><code>@​bradzacher</code></a></li>
<li>Cameron</li>
<li>Diptajoy Mistry <a
href="https://github.com/diptomistry"><code>@​diptomistry</code></a></li>
<li>Evyatar Daud <a
href="https://github.com/StyleShit"><code>@​StyleShit</code></a></li>
<li>Grit <a
href="https://github.com/Grit03"><code>@​Grit03</code></a></li>
<li>Hugo <a
href="https://github.com/hugop95"><code>@​hugop95</code></a></li>
<li>Josh Goldberg ✨</li>
<li>Michael Naumov <a
href="https://github.com/mnaoumov"><code>@​mnaoumov</code></a></li>
<li>Mikhail Baev <a
href="https://github.com/baevm"><code>@​baevm</code></a></li>
<li>Om Rawat</li>
<li>overlookmotel</li>
<li>Sanath <a
href="https://github.com/sansynx"><code>@​sansynx</code></a></li>
<li>Shinji</li>
<li>stoicism <a
href="https://github.com/stoicism02"><code>@​stoicism02</code></a></li>
<li>Vinccool96</li>
<li>Younsang Na <a
href="https://github.com/nayounsang"><code>@​nayounsang</code></a></li>
<li>김채영 <a
href="https://github.com/cchaeyoung"><code>@​cchaeyoung</code></a></li>
<li>송재욱</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.70.1">GitHub
Releases</a> for more information.</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md">@​typescript-eslint/parser's
changelog</a>.</em></p>
<blockquote>
<h2>8.70.1 (2026-09-21)</h2>
<p>This was a version bump only for parser to align it with other
projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.70.1">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
<h2>8.70.0 (2026-09-07)</h2>
<p>This was a version bump only for parser to align it with other
projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.70.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
<h2>8.69.0 (2026-08-31)</h2>
<p>This was a version bump only for parser to align it with other
projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.69.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/23d38ceeb8fb1237f55cb62fc2b54419e1fa1ed7"><code>23d38ce</code></a>
chore(release): publish 8.70.1</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/7ee76085c22e923c0036b8e0733a3ca7dfd82b60"><code>7ee7608</code></a>
chore(release): publish 8.70.0</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/9a6e546823e5d8f2dc015df2aa66c0230615e209"><code>9a6e546</code></a>
chore(release): publish 8.69.0</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.70.1/packages/parser">compare
view</a></li>
</ul>
</details>
<br />

Updates `eslint` from 10.9.1 to 10.11.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/eslint/eslint/releases">eslint's
releases</a>.</em></p>
<blockquote>
<h2>v10.11.0</h2>
<h2>Features</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/d136fa4b0d2dd4a9e738ca1c012cc674d1441127"><code>d136fa4</code></a>
feat: object-shorthand handle quoted properties for
<code>ignoreConstructors</code> (<a
href="https://redirect.github.com/eslint/eslint/issues/21271">#21271</a>)
(Pavel)</li>
<li><a
href="https://github.com/eslint/eslint/commit/397b3b8134b8ce1a61cbf414d4c29c945ba25690"><code>397b3b8</code></a>
feat: report unsafe labeled <code>continue</code> in
<code>no-unsafe-finally</code> rule (<a
href="https://redirect.github.com/eslint/eslint/issues/21316">#21316</a>)
(electrohyun)</li>
<li><a
href="https://github.com/eslint/eslint/commit/d3dd47f42e4cb5f9da27e3a2e741aa21e02ea1a4"><code>d3dd47f</code></a>
feat: only exempt <code>new-cap</code> built-ins that reference the
global (<a
href="https://redirect.github.com/eslint/eslint/issues/21290">#21290</a>)
(sethamus)</li>
</ul>
<h2>Bug Fixes</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/22b09f54f67a9512d4cea39ddbab9b6973d5c476"><code>22b09f5</code></a>
fix: ignore <code>__proto__</code> properties in
<code>prefer-object-spread</code> (<a
href="https://redirect.github.com/eslint/eslint/issues/21311">#21311</a>)
(xbinaryx)</li>
<li><a
href="https://github.com/eslint/eslint/commit/b684bb1cd7e6be03ad1b7a951baead936d9c2166"><code>b684bb1</code></a>
fix: make TimePass.parse optional in types and docs (<a
href="https://redirect.github.com/eslint/eslint/issues/21313">#21313</a>)
(ntnyq)</li>
<li><a
href="https://github.com/eslint/eslint/commit/26d11bce3e0e21f223613d4bb4be3423839b229e"><code>26d11bc</code></a>
fix: don't report <code>__proto__</code> properties in
<code>object-shorthand</code> (<a
href="https://redirect.github.com/eslint/eslint/issues/21310">#21310</a>)
(xbinaryx)</li>
</ul>
<h2>Documentation</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/9ecfdc5319f83580cf3b81beab7e936015fef2fa"><code>9ecfdc5</code></a>
docs: note that --cache can serve stale results for cross-file rules (<a
href="https://redirect.github.com/eslint/eslint/issues/21312">#21312</a>)
(bytedoe)</li>
<li><a
href="https://github.com/eslint/eslint/commit/6c789ff39bc5420e94e8dafeb328bf2b7e3d35ab"><code>6c789ff</code></a>
docs: Update README (GitHub Actions Bot)</li>
<li><a
href="https://github.com/eslint/eslint/commit/5997825635dc9c4e81434894607ef2e3887e0ea3"><code>5997825</code></a>
docs: clarify preserve-caught-error known limitation (<a
href="https://redirect.github.com/eslint/eslint/issues/21294">#21294</a>)
(Akinyemi Toluwalase)</li>
</ul>
<h2>Chores</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/520dd77a35922fb537e2dbfb3c839d047acbdd68"><code>520dd77</code></a>
perf: Implement fast paths in critical areas (<a
href="https://redirect.github.com/eslint/eslint/issues/21210">#21210</a>)
(Nicholas C. Zakas)</li>
<li><a
href="https://github.com/eslint/eslint/commit/92086c87e042413a3d0363d8fc3fbb11db98d06a"><code>92086c8</code></a>
test: update <code>EMFILE</code> error generation for Node.js 26.9.0
compatibility (<a
href="https://redirect.github.com/eslint/eslint/issues/21330">#21330</a>)
(Francesco Trotta)</li>
<li><a
href="https://github.com/eslint/eslint/commit/9ac7eb60525ec768c2fdc6699e5a292aa913ce9e"><code>9ac7eb6</code></a>
chore: update github/codeql-action action to v4.38.0 (<a
href="https://redirect.github.com/eslint/eslint/issues/21331">#21331</a>)
(renovate[bot])</li>
<li><a
href="https://github.com/eslint/eslint/commit/24310e3a0e22b3c086ca402f88448676f2e1cfcd"><code>24310e3</code></a>
chore: update ecosystem plugins (<a
href="https://redirect.github.com/eslint/eslint/issues/21324">#21324</a>)
(ESLint Bot)</li>
<li><a
href="https://github.com/eslint/eslint/commit/45ad79e54a39b54b4ce8eb47e612bd2f72a7a651"><code>45ad79e</code></a>
ci: bump pnpm/action-setup from 6.0.10 to 6.1.0 (<a
href="https://redirect.github.com/eslint/eslint/issues/21318">#21318</a>)
(dependabot[bot])</li>
<li><a
href="https://github.com/eslint/eslint/commit/ac74e37322ebf122ada676f153dc55c81f3caab0"><code>ac74e37</code></a>
chore: Add AGENTS.md with AI disclosure requirements (<a
href="https://redirect.github.com/eslint/eslint/issues/21221">#21221</a>)
(Nicholas C. Zakas)</li>
<li><a
href="https://github.com/eslint/eslint/commit/c8326608e710e670beaf982501aed80ea104919a"><code>c832660</code></a>
chore: Upgrade Stylelint to the latest version in docs (<a
href="https://redirect.github.com/eslint/eslint/issues/21245">#21245</a>)
(Jung Hyeon Jun)</li>
<li><a
href="https://github.com/eslint/eslint/commit/f9f88fcccd965fdc162b89c4615dd4018e3cabdf"><code>f9f88fc</code></a>
chore: update ecosystem plugins (<a
href="https://redirect.github.com/eslint/eslint/issues/21308">#21308</a>)
(ESLint Bot)</li>
<li><a
href="https://github.com/eslint/eslint/commit/fc81076a5b8145360654d81cbc130cf7d25dca77"><code>fc81076</code></a>
ci: add more types integration tests (<a
href="https://redirect.github.com/eslint/eslint/issues/20395">#20395</a>)
(Nitin Kumar)</li>
</ul>
<h2>v10.10.0</h2>
<h2>Features</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/264b4346d1963701df0c398b4aeb2f6e8b2af93e"><code>264b434</code></a>
feat: add <code>d</code> and <code>v</code> flags to
<code>no-unexpected-multiline</code> (<a
href="https://redirect.github.com/eslint/eslint/issues/21305">#21305</a>)
(Gihyeon Jeong / 정기현)</li>
<li><a
href="https://github.com/eslint/eslint/commit/c6cc6c592f30901345d94ef75e0d42c1894fae6c"><code>c6cc6c5</code></a>
feat: check <code>Object.prototype</code> property names in
<code>new-cap</code> (<a
href="https://redirect.github.com/eslint/eslint/issues/21269">#21269</a>)
(crimsonjay0)</li>
<li><a
href="https://github.com/eslint/eslint/commit/5661fa65fde9fd4c14f0b730e3cee6a42fc657c1"><code>5661fa6</code></a>
feat: no-extra-bind false negatives with class fields and static blocks
(<a
href="https://redirect.github.com/eslint/eslint/issues/21260">#21260</a>)
(synthex-byte)</li>
</ul>
<h2>Bug Fixes</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/bb47dc6da2399a8f76c0c0c3273e6bc314c480e5"><code>bb47dc6</code></a>
fix: update dependency file-entry-cache to v11 (<a
href="https://redirect.github.com/eslint/eslint/issues/20801">#20801</a>)
(Milos Djermanovic)</li>
<li><a
href="https://github.com/eslint/eslint/commit/427ac0a014066c36aa57fa8fa9af20fd9fb591e1"><code>427ac0a</code></a>
fix: use format strings in debug calls (<a
href="https://redirect.github.com/eslint/eslint/issues/21247">#21247</a>)
(Francesco Trotta)</li>
<li><a
href="https://github.com/eslint/eslint/commit/9d8153223dbf47b9aecdc1474202aaee4845f146"><code>9d81532</code></a>
fix: support <code>__proto__</code> in <code>/* exported */</code>
comments (<a
href="https://redirect.github.com/eslint/eslint/issues/21261">#21261</a>)
(sethamus)</li>
<li><a
href="https://github.com/eslint/eslint/commit/87e0a082438264ad90b87fd74165ab4fd90f63ef"><code>87e0a08</code></a>
fix: prefer-object-has-own autofix breaks when Object is shadowed (<a
href="https://redirect.github.com/eslint/eslint/issues/21282">#21282</a>)
(김채영)</li>
<li><a
href="https://github.com/eslint/eslint/commit/8e2cb142217f2efee1d10dcc02bfb75145ae775d"><code>8e2cb14</code></a>
fix: <code>new-cap</code> false positive for <code>UTC</code> calls with
<code>properties: false</code> (<a
href="https://redirect.github.com/eslint/eslint/issues/21275">#21275</a>)
(Pixel)</li>
<li><a
href="https://github.com/eslint/eslint/commit/9f4a364ab0ade048dfce1f37792b1d461d866e55"><code>9f4a364</code></a>
fix: Ignore static imports in no-unreachable (<a
href="https://redirect.github.com/eslint/eslint/issues/21276">#21276</a>)
(Taha Kotil)</li>
</ul>
<h2>Documentation</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/2417cad57d7d1bc4cf3ecf0f0575cfb10ff2011c"><code>2417cad</code></a>
docs: Update README (GitHub Actions Bot)</li>
<li><a
href="https://github.com/eslint/eslint/commit/9cecb8a0a2348070abf72321965d41919c7cc626"><code>9cecb8a</code></a>
docs: document <code>\c</code> control letter escapes in
no-control-regex (<a
href="https://redirect.github.com/eslint/eslint/issues/21286">#21286</a>)
(한국)</li>
<li><a
href="https://github.com/eslint/eslint/commit/8724829f69f8ed80c876e3a5a017da199ce78739"><code>8724829</code></a>
docs: update compat table links (<a
href="https://redirect.github.com/eslint/eslint/issues/21263">#21263</a>)
(fnx)</li>
<li><a
href="https://github.com/eslint/eslint/commit/5634542be580750ffb1a5766470f9e9c72719696"><code>5634542</code></a>
docs: Clarify eqeqeq suggestion behavior (<a
href="https://redirect.github.com/eslint/eslint/issues/21256">#21256</a>)
(Müslüm Yılmaz)</li>
</ul>
<h2>Chores</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/b3d876b46083d67899eb1d9613118c1c583632a2"><code>b3d876b</code></a>
chore: disable npm audit in ecosystem tests (<a
href="https://redirect.github.com/eslint/eslint/issues/21306">#21306</a>)
(Francesco Trotta)</li>
<li><a
href="https://github.com/eslint/eslint/commit/1696682791661c13167eb905da2f38d1b8f4a3bf"><code>1696682</code></a>
ci: restore EMFILE test on Node.js 26 (<a
href="https://redirect.github.com/eslint/eslint/issues/21297">#21297</a>)
(Marry (Subin Yang))</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/3c0b7c6e1fe2dec778b97a996018126f22d437ae"><code>3c0b7c6</code></a>
10.11.0</li>
<li><a
href="https://github.com/eslint/eslint/commit/321f0a70dd908ed461b11142e17fbdc0c0f1f198"><code>321f0a7</code></a>
Build: changelog update for 10.11.0</li>
<li><a
href="https://github.com/eslint/eslint/commit/520dd77a35922fb537e2dbfb3c839d047acbdd68"><code>520dd77</code></a>
perf: Implement fast paths in critical areas (<a
href="https://redirect.github.com/eslint/eslint/issues/21210">#21210</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/9ecfdc5319f83580cf3b81beab7e936015fef2fa"><code>9ecfdc5</code></a>
docs: note that --cache can serve stale results for cross-file rules (<a
href="https://redirect.github.com/eslint/eslint/issues/21312">#21312</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/92086c87e042413a3d0363d8fc3fbb11db98d06a"><code>92086c8</code></a>
test: update <code>EMFILE</code> error generation for Node.js 26.9.0
compatibility (<a
href="https://redirect.github.com/eslint/eslint/issues/21330">#21330</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/9ac7eb60525ec768c2fdc6699e5a292aa913ce9e"><code>9ac7eb6</code></a>
chore: update github/codeql-action action to v4.38.0 (<a
href="https://redirect.github.com/eslint/eslint/issues/21331">#21331</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/22b09f54f67a9512d4cea39ddbab9b6973d5c476"><code>22b09f5</code></a>
fix: ignore <code>__proto__</code> properties in
<code>prefer-object-spread</code> (<a
href="https://redirect.github.com/eslint/eslint/issues/21311">#21311</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/24310e3a0e22b3c086ca402f88448676f2e1cfcd"><code>24310e3</code></a>
chore: update ecosystem plugins (<a
href="https://redirect.github.com/eslint/eslint/issues/21324">#21324</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/d136fa4b0d2dd4a9e738ca1c012cc674d1441127"><code>d136fa4</code></a>
feat: object-shorthand handle quoted properties for
<code>ignoreConstructors</code> (<a
href="https://redirect.github.com/eslint/eslint/issues/21">#21</a>...</li>
<li><a
href="https://github.com/eslint/eslint/commit/45ad79e54a39b54b4ce8eb47e612bd2f72a7a651"><code>45ad79e</code></a>
ci: bump pnpm/action-setup from 6.0.10 to 6.1.0 (<a
href="https://redirect.github.com/eslint/eslint/issues/21318">#21318</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/eslint/eslint/compare/v10.9.1...v10.11.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `jest` from 30.5.0 to 30.5.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/jestjs/jest/releases">jest's
releases</a>.</em></p>
<blockquote>
<h2>v30.5.2</h2>
<h2>Features</h2>
<ul>
<li><code>[@jest/transform]</code> Strip TypeScript types with Node when
no transformer claims a <code>.ts</code>, <code>.mts</code> or
<code>.cts</code> file (<a
href="https://redirect.github.com/jestjs/jest/pull/16421">#16421</a>)</li>
</ul>
<h2>Fixes</h2>
<ul>
<li><code>[jest-core, jest-haste-map, jest-transform]</code> Keep
<code>require('../package.json')</code> external when bundling, so
<code>jest --version</code> and the transform and haste-map cache keys
report the released version instead of the previous one (<a
href="https://redirect.github.com/jestjs/jest/pull/16422">#16422</a>)</li>
<li><code>[jest-each]</code> Escape a table row's keys before building
the <code>$variable</code> interpolation <code>RegExp</code>, so a
column name such as <code>count(*)</code> no longer fails the whole
table with <code>Invalid regular expression</code>, and a <code>.</code>
or <code>|</code> in a column name is matched literally (<a
href="https://redirect.github.com/jestjs/jest/pull/16345">#16345</a>)</li>
<li><code>[@jest/source-map]</code> Resolve absolute Windows paths in a
source map's <code>sources</code> and <code>sourceRoot</code> again,
instead of appending them to the transformed file's directory (<a
href="https://redirect.github.com/jestjs/jest/pull/16439">#16439</a>)</li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/winklemad"><code>@​winklemad</code></a>
made their first contribution in <a
href="https://redirect.github.com/jestjs/jest/pull/16345">jestjs/jest#16345</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/jestjs/jest/compare/v30.5.1...v30.5.2">https://github.com/jestjs/jest/compare/v30.5.1...v30.5.2</a></p>
<h2>v30.5.1</h2>
<h2>Fixes</h2>
<ul>
<li><code>[jest-config]</code> Don't warn about global-only options in
the config that supplies the global config - the root config a project
resolves to, or the first entry of <code>--projects</code> when no root
config is passed (<a
href="https://redirect.github.com/jestjs/jest/pull/16411">#16411</a>)</li>
<li><code>[jest-config, jest-types]</code> Stop accepting
<code>reporters</code>, <code>coverageReporters</code>,
<code>workerIdleMemoryLimit</code>, <code>cwd</code> and
<code>runnerOptions</code> in a project config - they were silently
ignored, and now warn like the other global-only options (<a
href="https://redirect.github.com/jestjs/jest/pull/16411">#16411</a>)</li>
<li><code>[jest-config, jest-validate]</code> Warn about
<code>maxWorkers</code> and <code>coverageThreshold</code> in a project
config instead of dropping them without a word (<a
href="https://redirect.github.com/jestjs/jest/pull/16411">#16411</a>)</li>
<li><code>[jest-resolve]</code> Match <code>moduleNameMapper</code>
patterns against the specifier as written again (reverting <a
href="https://redirect.github.com/jestjs/jest/pull/16390">#16390</a>)
(<a
href="https://redirect.github.com/jestjs/jest/pull/16417">#16417</a>)</li>
<li><code>[jest-runtime]</code> Resolve package <code>imports</code>
specifiers like <code>#dep</code> under ESM again (<a
href="https://redirect.github.com/jestjs/jest/pull/16413">#16413</a>)</li>
</ul>
<h2>Chore &amp; Maintenance</h2>
<ul>
<li><code>[jest-util]</code> Name the
<code>testEnvironmentOptions.globalsCleanup</code> option and link the
docs from the <code>JEST-01</code> deprecation warning, and document the
option's modes (<a
href="https://redirect.github.com/jestjs/jest/pull/16404">#16404</a>)</li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/HuzaifaChaudary"><code>@​HuzaifaChaudary</code></a>
made their first contribution in <a
href="https://redirect.github.com/jestjs/jest/pull/16413">jestjs/jest#16413</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/jestjs/jest/compare/v30.5.0...v30.5.1">https://github.com/jestjs/jest/compare/v30.5.0...v30.5.1</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/jestjs/jest/blob/main/CHANGELOG.md">jest's
changelog</a>.</em></p>
<blockquote>
<h2>30.5.2</h2>
<h3>Features</h3>
<ul>
<li><code>[@jest/transform]</code> Strip TypeScript types with Node when
no transformer claims a <code>.ts</code>, <code>.mts</code> or
<code>.cts...

_Description has been truncated_

---------

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 <jkennedyvz@users.noreply.github.com>
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: John Kennedy <65985482+jkennedyvz@users.noreply.github.com>
2026-10-17 18:27:10 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>John Kennedyopen-swe[bot] <open-swe@users.noreply.github.com>
beb6fe2ffb chore(deps-dev): bump dotenv from 17.4.2 to 18.0.4 in /libs/cli/js-examples in the major group (#9154)
Bumps the major group in /libs/cli/js-examples with 1 update:
[dotenv](https://github.com/motdotla/dotenv).

Updates `dotenv` from 17.4.2 to 18.0.4
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md">dotenv's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/motdotla/dotenv/compare/v18.0.3...v18.0.4">18.0.4</a>
(2026-09-25)</h2>
<h3>Changed</h3>
<ul>
<li><code>import dotenv/config</code> should default quiet: true (<a
href="https://redirect.github.com/motdotla/dotenv/pull/1063">#1063</a>)</li>
</ul>
<h2><a
href="https://github.com/motdotla/dotenv/compare/v18.0.2...v18.0.3">18.0.3</a>
(2026-09-22)</h2>
<h3>Changed</h3>
<ul>
<li>Patch <code>DOTENV_QUIET</code> setting when inside .env file (<a
href="https://redirect.github.com/motdotla/dotenv/pull/1059">#1059</a>)</li>
</ul>
<h2><a
href="https://github.com/motdotla/dotenv/compare/v18.0.1...v18.0.2">18.0.2</a>
(2026-09-21)</h2>
<h3>Changed</h3>
<ul>
<li>Patch additional edge cases for the fast parser (<a
href="https://redirect.github.com/motdotla/dotenv/pull/1056">#1056</a>)</li>
</ul>
<h2><a
href="https://github.com/motdotla/dotenv/compare/v18.0.0...v18.0.1">18.0.1</a>
(2026-09-18)</h2>
<h3>Changed</h3>
<ul>
<li>Handle file urls in config logging (<a
href="https://redirect.github.com/motdotla/dotenv/pull/1054">#1054</a>)</li>
</ul>
<h2><a
href="https://github.com/motdotla/dotenv/compare/v17.4.2...v18.0.0">18.0.0</a>
(2026-09-17)</h2>
<h3>Added</h3>
<ul>
<li>NEW: Dotenv now has a CLI. (<a
href="https://redirect.github.com/motdotla/dotenv/pull/1022">#1022</a>)</li>
</ul>
<pre lang="sh"><code>$ dotenv run -- node index.js
◇ injected env (2) from .env
Hello Dotenv
</code></pre>
<ul>
<li>NEW: Dotenv now has a fast parser thanks to <a
href="https://github.com/homanp"><code>@​homanp</code></a> of <a
href="https://superagent.sh">superagent.sh</a>. Pass <code>config({
fast: true })</code>, flag <code>--fast</code>, or set
<code>DOTENV_FAST=true</code> to opt-in to ~2x faster character-scanner
parser. (<a
href="https://redirect.github.com/motdotla/dotenv/pull/1010">#1010</a>)</li>
</ul>
<pre lang="sh"><code>$ dotenv run --fast -- node index.js
◇ injected env (2) from .env
Hello Dotenv
</code></pre>
<p><a href="https://dotenvx.com/blog/2026/09/28/dotenv-fast.html">faster
than Node native parseEnv!</a></p>
<h3>Changed</h3>
<ul>
<li>Injecting message sent to stderr rather than stdout and tips removed
(<a
href="https://redirect.github.com/motdotla/dotenv/pull/1037">#1037</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/motdotla/dotenv/commit/86804c0507aff98d3a82204759729af3bc2ce1a9"><code>86804c0</code></a>
18.0.4</li>
<li><a
href="https://github.com/motdotla/dotenv/commit/2403db57c3f8b01123d69a8b441a5f8b828fab3c"><code>2403db5</code></a>
changelog</li>
<li><a
href="https://github.com/motdotla/dotenv/commit/75b775eda45af3085fb7a7de6ae2613eb646807e"><code>75b775e</code></a>
npm audit</li>
<li><a
href="https://github.com/motdotla/dotenv/commit/7e1750ce8034b3c9485f1e1722587a40890449e1"><code>7e1750c</code></a>
Merge pull request <a
href="https://redirect.github.com/motdotla/dotenv/issues/1064">#1064</a>
from motdotla/dependabot/npm_and_yarn/ip-address-10.7.2</li>
<li><a
href="https://github.com/motdotla/dotenv/commit/a6ade63b6fa94a78fdc68f815c4e88cfe8781774"><code>a6ade63</code></a>
Merge pull request <a
href="https://redirect.github.com/motdotla/dotenv/issues/1063">#1063</a>
from motdotla/import</li>
<li><a
href="https://github.com/motdotla/dotenv/commit/288b0d7cd85d8deba3f91a995abd033786626a8d"><code>288b0d7</code></a>
patch test</li>
<li><a
href="https://github.com/motdotla/dotenv/commit/00da9125dd7633a121d169a3a55b62a686b64ef8"><code>00da912</code></a>
Bump ip-address from 10.2.0 to 10.7.2</li>
<li><a
href="https://github.com/motdotla/dotenv/commit/e6b58161d2f2b1448c735b1f6555bde7a20443b7"><code>e6b5816</code></a>
import 'dotenv/config' should still be defaulting to quiet</li>
<li><a
href="https://github.com/motdotla/dotenv/commit/3646ac5e5d000f5f68fe83db52399010f9cb781f"><code>3646ac5</code></a>
Merge pull request <a
href="https://redirect.github.com/motdotla/dotenv/issues/1061">#1061</a>
from motdotla/fast</li>
<li><a
href="https://github.com/motdotla/dotenv/commit/83fc961f92f6c330833da4a5f5dc7093d539501e"><code>83fc961</code></a>
report dotenv vs native vs dotenv.fast</li>
<li>Additional commits viewable in <a
href="https://github.com/motdotla/dotenv/compare/v17.4.2...v18.0.4">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dotenv&package-manager=npm_and_yarn&previous-version=17.4.2&new-version=18.0.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

---------

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 <jkennedyvz@users.noreply.github.com>
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-10-18 00:49:00 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>John Kennedyopen-swe[bot] <open-swe@users.noreply.github.com>
0eb194fe60 chore(deps-dev): bump the minor-and-patch group in /libs/prebuilt with 6 updates (#9150)
Bumps the minor-and-patch group in /libs/prebuilt with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [langchain-core](https://github.com/langchain-ai/langchain) | `1.6.1`
| `1.6.5` |
| [pytest-mock](https://github.com/pytest-dev/pytest-mock) | `3.15.1` |
`3.16.0` |
| [syrupy](https://github.com/syrupy-project/syrupy) | `6.0.0` | `6.1.1`
|
| [psycopg-binary](https://github.com/psycopg/psycopg) | `3.3.4` |
`3.3.6` |
| [ruff](https://github.com/astral-sh/ruff) | `0.16.5` | `0.16.9` |
| [ty](https://github.com/astral-sh/ty) | `0.0.75` | `0.0.84` |

Updates `langchain-core` from 1.6.1 to 1.6.5
<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.6.5</h2>
<p>Changes since langchain-core==1.6.4</p>
<p>release(core): 1.6.5 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/40816">#40816</a>)
fix(core): abbreviate long tool IDs in XML buffer strings (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/40792">#40792</a>)</p>
<h2>langchain-core==1.6.4</h2>
<p>Changes since langchain-core==1.6.3</p>
<p>release(core): 1.6.4 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/40718">#40718</a>)
chore(core): deprecate chat message history (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/40711">#40711</a>)
chore(deps): bump anyio from 4.12.0 to 4.14.2 in /libs/core (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/40634">#40634</a>)
chore(deps): bump soupsieve from 2.8.4 to 2.9 in /libs/core (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/40574">#40574</a>)</p>
<h2>langchain-core==1.6.3</h2>
<p>Changes since langchain-core==1.6.2</p>
<p>release(core): 1.6.3 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/40407">#40407</a>)
feat(core): Allow model name and provider tracing metadata override
based on gateway response (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/40406">#40406</a>)
test(core): cover the deprecated <code>.text()</code> access path (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/40243">#40243</a>)
docs(core): remove stale Args/Raises entries from
FileCallbackHandler._write and ChatGeneration.set_text (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/40211">#40211</a>)</p>
<h2>langchain-core==1.6.2</h2>
<p>Changes since langchain-core==1.6.1</p>
<p>release(core): 1.6.2 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/40209">#40209</a>)
feat(openai): support async tools (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/40208">#40208</a>)
chore(deps): bump mistune from 3.3.0 to 3.3.3 in /libs/core (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/40150">#40150</a>)
chore(deps): bump tornado from 6.5.7 to 6.5.8 in /libs/core (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/40113">#40113</a>)
fix(core): avoid mutation in google-genai standard content (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/40023">#40023</a>)
fix(core): avoid mutation in bedrock converse standard content (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/40022">#40022</a>)</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langchain/commit/c5ab14d42a3e22865c9def909de0b11d70b0bbf0"><code>c5ab14d</code></a>
release(core): 1.6.5 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/40816">#40816</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/5704d9d4813f8c0527f04e2662cf313e1350f96f"><code>5704d9d</code></a>
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/40804">#40804</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/7622d3dce760ac4be6d9aef4c653277e06064aea"><code>7622d3d</code></a>
release(openai): 1.6.6 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/40800">#40800</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/2dd956b8add667dac4f97605ae441d75b8ae228e"><code>2dd956b</code></a>
docs(infra): fix AGENTS.md root setup guidance and package doc accuracy
(<a
href="https://redirect.github.com/langchain-ai/langchain/issues/40794">#40794</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/49f4b4016b82513c3dc1623bf3daa10453861621"><code>49f4b40</code></a>
fix(openai): raise on error events in stream path (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/40791">#40791</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/19cadaa1a1e0cac71f3148e2f18b414dcab17426"><code>19cadaa</code></a>
fix(core): abbreviate long tool IDs in XML buffer strings (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/40792">#40792</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/798441e8b07e88e79db355db452cd9492f9628f2"><code>798441e</code></a>
chore(anthropic): fix integration test cassette (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/40790">#40790</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/a476942bac3fe5e521676d0f53fabafcb7ba71b1"><code>a476942</code></a>
release(openai): 1.6.5 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/40787">#40787</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/46c6bdf1b4e23028a5acc060d8b2797dbf200291"><code>46c6bdf</code></a>
release(anthropic): 1.7.4 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/40786">#40786</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/290dabaff27ee379920d55d155d5055d2fe29cc8"><code>290daba</code></a>
fix(anthropic): add Opus 5.5 and GPT-6 profile augmentations (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/40785">#40785</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langchain/compare/langchain-core==1.6.1...langchain-core==1.6.5">compare
view</a></li>
</ul>
</details>
<br />

Updates `pytest-mock` from 3.15.1 to 3.16.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pytest-dev/pytest-mock/releases">pytest-mock's
releases</a>.</em></p>
<blockquote>
<h2>v3.16.0</h2>
<p><em>2026-09-27</em></p>
<ul>
<li><a
href="https://redirect.github.com/pytest-dev/pytest-mock/pull/604">#604</a>:
Fixed <code>duplicate_iterators=True</code> for async functions spied
with <code>mocker.spy</code>.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest-mock/pull/611">#611</a>:
Fixed async mock assertion introspection to use awaited arguments
instead of the latest call's arguments.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest-mock/pull/606">#606</a>:
<code>mocker.resetall(return_value=True, side_effect=True)</code> now
also applies to non-callable mocks, such as those returned by
<code>mocker.create_autospec(SomeClass, instance=True)</code>.
Previously both arguments were silently ignored for them.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest-mock/issues/547">#547</a>:
Added <code>SpyType</code> for annotating <code>mocker.spy</code>
results.</li>
<li>Dropped support for EOL Python 3.9.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest-mock/issues/147">#147</a>:
Removed handling of <code>RuntimeError: stop called on unstarted
patcher</code>, which can no longer occur in the supported Python
versions.</li>
<li>Added support for Python 3.15.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/pytest-dev/pytest-mock/blob/main/CHANGELOG.rst">pytest-mock's
changelog</a>.</em></p>
<blockquote>
<h2>3.16.0</h2>
<p><em>2026-09-27</em></p>
<ul>
<li><code>[#604](https://github.com/pytest-dev/pytest-mock/issues/604)
&lt;https://github.com/pytest-dev/pytest-mock/pull/604&gt;</code>_:
Fixed <code>duplicate_iterators=True</code> for async functions spied
with <code>mocker.spy</code>.</li>
<li><code>[#611](https://github.com/pytest-dev/pytest-mock/issues/611)
&lt;https://github.com/pytest-dev/pytest-mock/pull/611&gt;</code>_:
Fixed async mock assertion introspection to use awaited arguments
instead of the latest call's arguments.</li>
<li><code>[#606](https://github.com/pytest-dev/pytest-mock/issues/606)
&lt;https://github.com/pytest-dev/pytest-mock/pull/606&gt;</code>_:
<code>mocker.resetall(return_value=True, side_effect=True)</code> now
also applies to non-callable mocks, such as those returned by
<code>mocker.create_autospec(SomeClass, instance=True)</code>.
Previously both arguments were silently ignored for them.</li>
<li><code>[#547](https://github.com/pytest-dev/pytest-mock/issues/547)
&lt;https://github.com/pytest-dev/pytest-mock/issues/547&gt;</code>_:
Added <code>SpyType</code> for annotating <code>mocker.spy</code>
results.</li>
<li>Dropped support for EOL Python 3.9.</li>
<li><code>[#147](https://github.com/pytest-dev/pytest-mock/issues/147)
&lt;https://github.com/pytest-dev/pytest-mock/issues/147&gt;</code>_:
Removed handling of <code>RuntimeError: stop called on unstarted
patcher</code>, which can no longer occur in the supported Python
versions.</li>
<li>Added support for Python 3.15.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pytest-dev/pytest-mock/commit/f3b531f93c6d9837edc87ea1a6f4324dc9a2cdf3"><code>f3b531f</code></a>
Release 3.16.0</li>
<li><a
href="https://github.com/pytest-dev/pytest-mock/commit/71ed67309520be160f3e100368705b58758d237a"><code>71ed673</code></a>
Fix duplicate_iterators for async spies (<a
href="https://redirect.github.com/pytest-dev/pytest-mock/issues/604">#604</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest-mock/commit/0797e3d303cc5bbe0a3514b0625f7aeb3fa350b0"><code>0797e3d</code></a>
Fix async assertion argument introspection (<a
href="https://redirect.github.com/pytest-dev/pytest-mock/issues/611">#611</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest-mock/commit/e24c334d573cfbf6989a31f4e3346676d9d31598"><code>e24c334</code></a>
docs: import os in the opening usage example (<a
href="https://redirect.github.com/pytest-dev/pytest-mock/issues/612">#612</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest-mock/commit/9e7eb7b59cde362c1c8ace2d18ca00fe2d7a1cd1"><code>9e7eb7b</code></a>
[pre-commit.ci] pre-commit autoupdate (<a
href="https://redirect.github.com/pytest-dev/pytest-mock/issues/613">#613</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest-mock/commit/445fd4d3daa8ed4c6d5428a77880da2e03d2f721"><code>445fd4d</code></a>
[pre-commit.ci] pre-commit autoupdate (<a
href="https://redirect.github.com/pytest-dev/pytest-mock/issues/610">#610</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest-mock/commit/a8bd0b147afdf6b4bb1a1a3c3c088f32b0240091"><code>a8bd0b1</code></a>
Honour resetall() arguments for non-callable mocks (<a
href="https://redirect.github.com/pytest-dev/pytest-mock/issues/606">#606</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest-mock/commit/6559fdfd5b67f096423e604a3a5a7045db2486a0"><code>6559fdf</code></a>
[pre-commit.ci] pre-commit autoupdate (<a
href="https://redirect.github.com/pytest-dev/pytest-mock/issues/608">#608</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest-mock/commit/466d026254c949a8fbba8626b894308827be81cf"><code>466d026</code></a>
[pre-commit.ci] pre-commit autoupdate (<a
href="https://redirect.github.com/pytest-dev/pytest-mock/issues/605">#605</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest-mock/commit/164defebde25bc31d53a2a64943c5c9aac43b57e"><code>164defe</code></a>
[pre-commit.ci] pre-commit autoupdate (<a
href="https://redirect.github.com/pytest-dev/pytest-mock/issues/603">#603</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/pytest-dev/pytest-mock/compare/v3.15.1...v3.16.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `syrupy` from 6.0.0 to 6.1.1
<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>v6.1.1</h2>
<blockquote>
<p><strong>Note:</strong> 6.1.0 was skipped due to a CI issue. Use
<strong>6.1.1</strong>.</p>
</blockquote>
<h3>Experimental <code>--snapshot-file-lock</code> for pytest-xdist</h3>
<p><code>--snapshot-update</code> under pytest-xdist can race when
multiple workers rewrite the same multi-entry amber (<code>.ambr</code>)
file, silently dropping snapshots (<a
href="https://redirect.github.com/syrupy-project/syrupy/issues/1237">#1237</a>).</p>
<p>This release adds an <strong>experimental</strong> opt-in:</p>
<ul>
<li><code>--snapshot-file-lock</code> — exclusive file lock + atomic
replace around amber writes</li>
<li><code>--snapshot-file-lock-timeout</code> — max wait for the lock
(default <strong>60s</strong>)</li>
</ul>
<p>Same-machine workers only; not reliable across remote workers / NFS.
See <a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1239">#1239</a>.</p>
<blockquote>
<p><strong>Callout for pytest-xdist users:</strong> please try
<code>--snapshot-file-lock</code> with <code>--snapshot-update</code>
and <a href="https://github.com/syrupy-project/syrupy/issues">open an
issue</a> if you hit problems. Locking is expected to become the default
in a future minor release.</p>
</blockquote>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/shipbyaeron"><code>@​shipbyaeron</code></a>
made their first contribution in <a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1236">syrupy-project/syrupy#1236</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/syrupy-project/syrupy/compare/v6.0.0...v6.1.1">https://github.com/syrupy-project/syrupy/compare/v6.0.0...v6.1.1</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/v6.1.1">v6.1.1</a>
(2026-09-13)</h2>
<h2>What's Changed</h2>
<ul>
<li>chore(deps): update dependency ruff to v0.16.4 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1225">syrupy-project/syrupy#1225</a></li>
<li>chore(deps): update dependency pytest-benchmark to v5.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/1226">syrupy-project/syrupy#1226</a></li>
<li>chore(deps): update dependency ruff to v0.16.5 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1227">syrupy-project/syrupy#1227</a></li>
<li>chore(deps): update dependency pydantic to v2.13.5 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1228">syrupy-project/syrupy#1228</a></li>
<li>chore(deps): update dependency coverage to v7.16.0 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1229">syrupy-project/syrupy#1229</a></li>
<li>chore(deps): update dependency hypothesis to v6.166.0 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1230">syrupy-project/syrupy#1230</a></li>
<li>chore(deps): update dependency hypothesis to v6.167.0 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1231">syrupy-project/syrupy#1231</a></li>
<li>chore(deps): update dependency hypothesis to v6.167.1 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1232">syrupy-project/syrupy#1232</a></li>
<li>chore(deps): update actions/deploy-pages action to v5.0.1 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1234">syrupy-project/syrupy#1234</a></li>
<li>chore(deps): update dependency ruff to v0.16.6 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1235">syrupy-project/syrupy#1235</a></li>
<li>test: add coverage for read_snapshot_data_from_location returning
None by <a
href="https://github.com/shipbyaeron"><code>@​shipbyaeron</code></a> in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1236">syrupy-project/syrupy#1236</a></li>
<li>chore(deps): update dependency hypothesis to v6.168.0 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1238">syrupy-project/syrupy#1238</a></li>
<li>fix: opt-in file lock for concurrent amber writes under xdist by <a
href="https://github.com/noahnu"><code>@​noahnu</code></a> in <a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1239">syrupy-project/syrupy#1239</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/shipbyaeron"><code>@​shipbyaeron</code></a>
made their first contribution in <a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1236">syrupy-project/syrupy#1236</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/syrupy-project/syrupy/compare/v6.0.0...v6.1.1">https://github.com/syrupy-project/syrupy/compare/v6.0.0...v6.1.1</a></p>
<h2><a
href="https://github.com/syrupy-project/syrupy/releases/tag/v6.1.0">v6.1.0</a>
(2026-09-13)</h2>
<h2>What's Changed</h2>
<ul>
<li>chore(deps): update dependency ruff to v0.16.4 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1225">syrupy-project/syrupy#1225</a></li>
<li>chore(deps): update dependency pytest-benchmark to v5.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/1226">syrupy-project/syrupy#1226</a></li>
<li>chore(deps): update dependency ruff to v0.16.5 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1227">syrupy-project/syrupy#1227</a></li>
<li>chore(deps): update dependency pydantic to v2.13.5 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1228">syrupy-project/syrupy#1228</a></li>
<li>chore(deps): update dependency coverage to v7.16.0 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1229">syrupy-project/syrupy#1229</a></li>
<li>chore(deps): update dependency hypothesis to v6.166.0 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1230">syrupy-project/syrupy#1230</a></li>
<li>chore(deps): update dependency hypothesis to v6.167.0 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1231">syrupy-project/syrupy#1231</a></li>
<li>chore(deps): update dependency hypothesis to v6.167.1 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1232">syrupy-project/syrupy#1232</a></li>
<li>chore(deps): update actions/deploy-pages action to v5.0.1 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1234">syrupy-project/syrupy#1234</a></li>
<li>chore(deps): update dependency ruff to v0.16.6 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1235">syrupy-project/syrupy#1235</a></li>
<li>test: add coverage for read_snapshot_data_from_location returning
None by <a
href="https://github.com/shipbyaeron"><code>@​shipbyaeron</code></a> in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1236">syrupy-project/syrupy#1236</a></li>
<li>chore(deps): update dependency hypothesis to v6.168.0 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1238">syrupy-project/syrupy#1238</a></li>
<li>fix: opt-in file lock for concurrent amber writes under xdist by <a
href="https://github.com/noahnu"><code>@​noahnu</code></a> in <a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1239">syrupy-project/syrupy#1239</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/shipbyaeron"><code>@​shipbyaeron</code></a>
made their first contribution in <a
href="https://redirect.github.com/syrupy-project/syrupy/pull/1236">syrupy-project/syrupy#1236</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/syrupy-project/syrupy/compare/v6.0.0...v6.1.0">https://github.com/syrupy-project/syrupy/compare/v6.0.0...v6.1.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/syrupy-project/syrupy/commit/2d251e73f1ebe47fe83ed71d835d3ae8580e8313"><code>2d251e7</code></a>
chore(release): 6.1.1 [skip ci]</li>
<li><a
href="https://github.com/syrupy-project/syrupy/commit/9b64ec05752586c45d1097e9d25f9ee1d4e93219"><code>9b64ec0</code></a>
chore(release): 6.1.0 [skip ci]</li>
<li><a
href="https://github.com/syrupy-project/syrupy/commit/5b4256eac8720075f25b0fa8491f75948001b983"><code>5b4256e</code></a>
fix: opt-in file lock for concurrent amber writes under xdist (<a
href="https://redirect.github.com/syrupy-project/syrupy/issues/1239">#1239</a>)</li>
<li><a
href="https://github.com/syrupy-project/syrupy/commit/d4cb1513b5d2b1b18c3b407150db22d2d8604ddd"><code>d4cb151</code></a>
chore(deps): update dependency hypothesis to v6.168.0 (<a
href="https://redirect.github.com/syrupy-project/syrupy/issues/1238">#1238</a>)</li>
<li><a
href="https://github.com/syrupy-project/syrupy/commit/16c4cb5b8785d89e49b8076a99f58d810e02fcce"><code>16c4cb5</code></a>
test: add coverage for read_snapshot_data_from_location returning None
(<a
href="https://redirect.github.com/syrupy-project/syrupy/issues/1236">#1236</a>)</li>
<li><a
href="https://github.com/syrupy-project/syrupy/commit/2e1d3ba0d39a1d87735213a32de9dd9cdb929690"><code>2e1d3ba</code></a>
chore(deps): update dependency ruff to v0.16.6 (<a
href="https://redirect.github.com/syrupy-project/syrupy/issues/1235">#1235</a>)</li>
<li><a
href="https://github.com/syrupy-project/syrupy/commit/fcc929d4deb2302f2d37e6511015ef43a0841bc2"><code>fcc929d</code></a>
chore(deps): update actions/deploy-pages action to v5.0.1 (<a
href="https://redirect.github.com/syrupy-project/syrupy/issues/1234">#1234</a>)</li>
<li><a
href="https://github.com/syrupy-project/syrupy/commit/4f1bad75777ecc9d916a648dd3669b2844d08f7d"><code>4f1bad7</code></a>
chore(deps): update dependency hypothesis to v6.167.1 (<a
href="https://redirect.github.com/syrupy-project/syrupy/issues/1232">#1232</a>)</li>
<li><a
href="https://github.com/syrupy-project/syrupy/commit/24eda28579f5761cbc64713ffcee41cc73602010"><code>24eda28</code></a>
chore(deps): update dependency hypothesis to v6.167.0 (<a
href="https://redirect.github.com/syrupy-project/syrupy/issues/1231">#1231</a>)</li>
<li><a
href="https://github.com/syrupy-project/syrupy/commit/0d7e46f9431e07810da4ef90dbe861b85fbc8469"><code>0d7e46f</code></a>
chore(deps): update dependency hypothesis to v6.166.0 (<a
href="https://redirect.github.com/syrupy-project/syrupy/issues/1230">#1230</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/syrupy-project/syrupy/compare/v6.0.0...v6.1.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `psycopg-binary` from 3.3.4 to 3.3.6
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/psycopg/psycopg/blob/master/docs/news.rst">psycopg-binary's
changelog</a>.</em></p>
<blockquote>
<p>.. currentmodule:: psycopg</p>
<p>.. index::
single: Release notes
single: News</p>
<h1><code>psycopg</code> release notes</h1>
<h2>Future releases</h2>
<p>Psycopg 3.3.7 (unreleased)
^^^^^^^^^^^^^^^^^^^^^^^^^^</p>
<ul>
<li>Fix segfault fetching arrays of strings or timestamps after closing
the
connection
(🎫<code>[#1428](https://github.com/psycopg/psycopg/issues/1428)</code>).</li>
</ul>
<h2>Current release</h2>
<p>Psycopg 3.3.6
^^^^^^^^^^^^^</p>
<ul>
<li>Add support for Python 3.15
(🎫<code>[#1245](https://github.com/psycopg/psycopg/issues/1245)</code>).</li>
<li>Improve performance of async queries by reducing the overhead of the
<code>!wait_async()</code> function
(🎫<code>[#1331](https://github.com/psycopg/psycopg/issues/1331)</code>).</li>
<li>Don't wait forever for a query to terminate after interrupting it,
for
instance if the server is unresponsive. The fix requires libpq 17 or
newer

(🎫<code>[#1371](https://github.com/psycopg/psycopg/issues/1371)</code>).</li>
<li>Cancel a running query upon receiving <code>!SystemExit</code>
(🎫<code>[#1384](https://github.com/psycopg/psycopg/issues/1384)</code>).</li>
<li>Report <code>!None</code> instead of <code>65535</code> as the
<code>Column.precision</code> of an
:sql:<code>interval</code> column declared with a fields restriction and
no explicit
precision, such as e.g. :sql:<code>interval day to second</code>
(🎫<code>[#1397](https://github.com/psycopg/psycopg/issues/1397)</code>).</li>
<li>Fix dumping of nested subclasses of lists as arrays
(🎫<code>[#1398](https://github.com/psycopg/psycopg/issues/1398)</code>).</li>
<li>Discard prepared statements upon :sql:<code>DEALLOCATE ALL</code>
(🎫<code>[#1408](https://github.com/psycopg/psycopg/issues/1408)</code>).</li>
<li>Better guards dumping large Python <code>!int</code> to binary
numeric
(🎫<code>[#1414](https://github.com/psycopg/psycopg/issues/1414)</code>).</li>
</ul>
<p>Psycopg 3.3.5
^^^^^^^^^^^^^</p>
<ul>
<li>Discard prepared statements upon :sql:<code>ALTER *</code> or
<code>DISCARD *</code>

(🎫<code>[#1307](https://github.com/psycopg/psycopg/issues/1307)</code>).</li>
<li>Fix <code>!ProgrammingError</code> when dumping
non-<code>!None</code> values with
no <code>!NoneType</code> dumper registered in python implementation
(🎫<code>[#1325](https://github.com/psycopg/psycopg/issues/1325)</code>).</li>
<li>Fix <code>!wait_selector</code> wait function to not raise
<code>!KeyError</code>

(🎫<code>[#1327](https://github.com/psycopg/psycopg/issues/1327)</code>).</li>
<li>Fix <code>!DataError</code> messages leaking the literal
<code>{...}</code> placeholder instead</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/psycopg/psycopg/commit/a67654d1e7afbf9b3a619557838f62de1c790e7c"><code>a67654d</code></a>
chore: bump psycopg package version to 3.3.6</li>
<li><a
href="https://github.com/psycopg/psycopg/commit/443814b3b111ac678a39fd523c1a826266fb69b5"><code>443814b</code></a>
Merge pull request <a
href="https://redirect.github.com/psycopg/psycopg/issues/1416">#1416</a>
from dvarrazzo/wait-async-perf</li>
<li><a
href="https://github.com/psycopg/psycopg/commit/42966e9ebbda3b9c69b15c5588543c15f2aae5dd"><code>42966e9</code></a>
test: add helpful comments to some tests</li>
<li><a
href="https://github.com/psycopg/psycopg/commit/5e8797f0c8003d8cd12a1e5c13f05fbb94c1c1d2"><code>5e8797f</code></a>
test: add reasonable connect_timeout to most tests</li>
<li><a
href="https://github.com/psycopg/psycopg/commit/c81ba62442dfbd69e207d6914e6ae2aa27e56886"><code>c81ba62</code></a>
perf: reduce the overhead of wait_async()</li>
<li><a
href="https://github.com/psycopg/psycopg/commit/d2bbfe4e2b1e36a20e72a18097f35a3db27296e3"><code>d2bbfe4</code></a>
refactor: use get_running_loop() in the async wait functions</li>
<li><a
href="https://github.com/psycopg/psycopg/commit/2b1484a550e01c88fda077099678f0abb9217ecb"><code>2b1484a</code></a>
test: add a script to measure the async wait functions</li>
<li><a
href="https://github.com/psycopg/psycopg/commit/573cf4aa70d8fdefc9d5f3eb69d5419cd6d3cd51"><code>573cf4a</code></a>
test: fix incorrect wait timing test</li>
<li><a
href="https://github.com/psycopg/psycopg/commit/2b68990874175b8d97269218d4963b2d5c8656f3"><code>2b68990</code></a>
refactor: drop leftovers of waiting with inf interval in
wait_conn_async</li>
<li><a
href="https://github.com/psycopg/psycopg/commit/60765dd8fc7d8df03cd75efcff485bfb3c83a0ed"><code>60765dd</code></a>
Merge pull request <a
href="https://redirect.github.com/psycopg/psycopg/issues/1414">#1414</a>
from dvarrazzo/fix-decimal-overflow</li>
<li>Additional commits viewable in <a
href="https://github.com/psycopg/psycopg/compare/3.3.4...3.3.6">compare
view</a></li>
</ul>
</details>
<br />

Updates `ruff` from 0.16.5 to 0.16.9
<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.9</h2>
<h2>Release Notes</h2>
<p>Released on 2026-09-24.</p>
<h3>Preview features</h3>
<ul>
<li>[<code>ruff</code>] Avoid false positives for overloaded division
(<code>RUF069</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28309">#28309</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>[<code>flake8-bugbear</code>] Avoid false positives for calls with
keyword arguments (<code>B009</code>, <code>B010</code>,
<code>B043</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28776">#28776</a>)</li>
<li>[<code>flake8-tidy-imports</code>] Allow lazy imports to be used in
deferred annotations (<code>TID255</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28767">#28767</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>Update LibCST-based fixes for Python 3.15 (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28616">#28616</a>)</li>
<li>[<code>flake8-pyi</code>] Mention stubs in the diagnostic message
(<code>PYI002</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28542">#28542</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Fix horizontal overflow on the rules documentation page (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28699">#28699</a>)</li>
<li>Update rules table with category information (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28651">#28651</a>)</li>
<li>[<code>flake8-annotations</code>] Clarify that <code>ANN401</code>
checks return types in addition to arguments (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28334">#28334</a>)</li>
<li>[<code>flake8-bugbear</code>] Document type-checker interaction
(<code>B010</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28509">#28509</a>)</li>
<li>[<code>flake8-comprehensions</code>] Document
<code>map</code>/generator exception behavior (<code>C417</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27794">#27794</a>)</li>
<li>[<code>ruff</code>] Mention related isort settings
(<code>RUF022</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28719">#28719</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/qinpei-dev"><code>@​qinpei-dev</code></a></li>
<li><a
href="https://github.com/sanjayrohith"><code>@​sanjayrohith</code></a></li>
<li><a href="https://github.com/ntBre"><code>@​ntBre</code></a></li>
<li><a
href="https://github.com/webdevsamran"><code>@​webdevsamran</code></a></li>
<li><a
href="https://github.com/zaniebot"><code>@​zaniebot</code></a></li>
<li><a
href="https://github.com/ewdurbin"><code>@​ewdurbin</code></a></li>
<li><a
href="https://github.com/MichaReiser"><code>@​MichaReiser</code></a></li>
<li><a
href="https://github.com/spaceone"><code>@​spaceone</code></a></li>
<li><a
href="https://github.com/IbrahimKhan12"><code>@​IbrahimKhan12</code></a></li>
<li><a
href="https://github.com/devtechedge"><code>@​devtechedge</code></a></li>
<li><a
href="https://github.com/GruffElixir"><code>@​GruffElixir</code></a></li>
</ul>
<h2>Install ruff 0.16.9</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://releases.astral.sh/github/ruff/releases/download/0.16.9/ruff-installer.sh
| sh
</code></pre>
<!-- 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.9</h2>
<p>Released on 2026-09-24.</p>
<h3>Preview features</h3>
<ul>
<li>[<code>ruff</code>] Avoid false positives for overloaded division
(<code>RUF069</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28309">#28309</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>[<code>flake8-bugbear</code>] Avoid false positives for calls with
keyword arguments (<code>B009</code>, <code>B010</code>,
<code>B043</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28776">#28776</a>)</li>
<li>[<code>flake8-tidy-imports</code>] Allow lazy imports to be used in
deferred annotations (<code>TID255</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28767">#28767</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>Update LibCST-based fixes for Python 3.15 (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28616">#28616</a>)</li>
<li>[<code>flake8-pyi</code>] Mention stubs in the diagnostic message
(<code>PYI002</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28542">#28542</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Fix horizontal overflow on the rules documentation page (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28699">#28699</a>)</li>
<li>Update rules table with category information (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28651">#28651</a>)</li>
<li>[<code>flake8-annotations</code>] Clarify that <code>ANN401</code>
checks return types in addition to arguments (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28334">#28334</a>)</li>
<li>[<code>flake8-bugbear</code>] Document type-checker interaction
(<code>B010</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28509">#28509</a>)</li>
<li>[<code>flake8-comprehensions</code>] Document
<code>map</code>/generator exception behavior (<code>C417</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27794">#27794</a>)</li>
<li>[<code>ruff</code>] Mention related isort settings
(<code>RUF022</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28719">#28719</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/qinpei-dev"><code>@​qinpei-dev</code></a></li>
<li><a
href="https://github.com/sanjayrohith"><code>@​sanjayrohith</code></a></li>
<li><a href="https://github.com/ntBre"><code>@​ntBre</code></a></li>
<li><a
href="https://github.com/webdevsamran"><code>@​webdevsamran</code></a></li>
<li><a
href="https://github.com/zaniebot"><code>@​zaniebot</code></a></li>
<li><a
href="https://github.com/ewdurbin"><code>@​ewdurbin</code></a></li>
<li><a
href="https://github.com/MichaReiser"><code>@​MichaReiser</code></a></li>
<li><a
href="https://github.com/spaceone"><code>@​spaceone</code></a></li>
<li><a
href="https://github.com/IbrahimKhan12"><code>@​IbrahimKhan12</code></a></li>
<li><a
href="https://github.com/devtechedge"><code>@​devtechedge</code></a></li>
<li><a
href="https://github.com/GruffElixir"><code>@​GruffElixir</code></a></li>
</ul>
<h2>0.16.8</h2>
<p>Released on 2026-09-16.</p>
<h3>Bug fixes</h3>
<ul>
<li>Visit functional <code>TypedDict</code> keyword arguments correctly
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/28584">#28584</a>)</li>
<li>[<code>flake8-simplify</code>] Detect nested <code>async with</code>
under sync parent (<code>SIM117</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27821">#27821</a>)</li>
<li>[<code>flake8-simplify</code>] Preserve operand order in
<code>SIM109</code> fix (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27824">#27824</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/0be08a206f9c3180afd3e93bcc792ed5cb1f4db1"><code>0be08a2</code></a>
Bump version to 0.16.9 (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28882">#28882</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/b4920b72b354e7c715ab861ae23458874683bb02"><code>b4920b7</code></a>
Rename <code>ruff_cli</code> to <code>ruff_command_line</code> (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28881">#28881</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/47c751b95908a4d1f95f9ef8723036aae9da0b18"><code>47c751b</code></a>
Update dependency astral-sh/uv to v0.12.18 (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28880">#28880</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/8c244e56a1aeac31c26d2371ef26588e0632235c"><code>8c244e5</code></a>
[<code>flake8-comprehensions</code>] Document <code>map</code>/generator
exception behavior (<code>C417</code>...</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/5edf5a1d0a84663079e46983216059f06acea87d"><code>5edf5a1</code></a>
Use <code>target</code> form in <code>rooster.version_files</code> (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28876">#28876</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/915bb2b4bf9ae7eee47cf55646bbfebae254a23b"><code>915bb2b</code></a>
[ty] Prefer existing @ paths over response files in Ruff and ty (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28877">#28877</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/4710e1aa962b13720cf64aa84eb279c5333896d7"><code>4710e1a</code></a>
ci(github): update version number in placeholder of issue template (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28871">#28871</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/eedfc62a75bf1ba86d48959b00eea75ae87eadca"><code>eedfc62</code></a>
[ty] Propagate outer type context through cast calls (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28855">#28855</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/ceaa6a00830e1e350b8a23977a1a10ac467920a1"><code>ceaa6a0</code></a>
[ty] Contain rendered code within Markdown fences (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28869">#28869</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/dba0f30615424b94f94a174bba6ce6cce4bf11ff"><code>dba0f30</code></a>
authorize ruff-pre-commit dispatch via OIDC (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28867">#28867</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/ruff/compare/0.16.5...0.16.9">compare
view</a></li>
</ul>
</details>
<br />

Updates `ty` from 0.0.75 to 0.0.84
<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.84</h2>
<h2>Release Notes</h2>
<p>Released on 2026-09-24.</p>
<p>This release addresses <a
href="https://github.com/astral-sh/ty/security/advisories/GHSA-vxvm-j4xq-q7m4">GHSA-vxvm-j4xq-q7m4</a>,
a use-after-free vulnerability during incremental type checking that can
result in arbitrary code execution when analyzing a specially crafted
Python project. Users who run ty on untrusted code should upgrade to
0.0.84 or newer.</p>
<h3>Bug fixes</h3>
<ul>
<li>Fix stale diagnostics from the LSP server after toggling
<code>showSyntaxErrors</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28759">#28759</a>)</li>
</ul>
<h3>LSP server</h3>
<ul>
<li>Complete string keys from dictionary initializers (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28820">#28820</a>)</li>
<li>Support LSP requests against closed documents (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28595">#28595</a>)</li>
<li>Select projects for external files using import search paths (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28594">#28594</a>)</li>
<li>Use workspace editor settings for external files (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28639">#28639</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Avoid repeated subtyping checks for materialized recursive protocols
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/28774">#28774</a>)</li>
<li>Skip reading notebooks when discovering scripts (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28781">#28781</a>)</li>
</ul>
<h3>Core type checking</h3>
<ul>
<li>Avoid incorrect simplification of <code>TypeIs</code>
materializations (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28817">#28817</a>)</li>
<li>Fix disjointness of generic class types (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28787">#28787</a>)</li>
<li>Fix staticmethod shadowing through generic receivers and unions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28766">#28766</a>)</li>
<li>Infer callable signatures from bounded type variables (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28599">#28599</a>)</li>
<li>Infer constant membership in inline list and set literals (e.g.
<code>&quot;foo&quot; in [&quot;foo&quot;]</code> is now inferred as
<code>Literal[True]</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28676">#28676</a>)</li>
<li>Infer through optional generic containers in the legacy solver (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28791">#28791</a>)</li>
<li>Preserve call narrowing during cyclic inference (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28708">#28708</a>)</li>
<li>Preserve intersections of type guard return types (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28796">#28796</a>)</li>
<li>Use subtyping for constraint-set implication (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28657">#28657</a>)</li>
</ul>
<h3>Configuration</h3>
<ul>
<li>Disable <code>invalid-legacy-positional-parameter</code> by default
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/28834">#28834</a>)</li>
</ul>
<h3>Other changes</h3>
<ul>
<li>Only consider reachable definitions when determining whether a
condition should be exempted from
<code>redundant-condition(-strict)</code> due to the condition being
defined relative to <code>sys.version_info</code>,
<code>sys.platform</code>, <code>os.name</code> or
<code>typing.TYPE_CHECKING</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28788">#28788</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/ibraheemdev"><code>@​ibraheemdev</code></a></li>
<li><a href="https://github.com/zsol"><code>@​zsol</code></a></li>
<li><a
href="https://github.com/charliermarsh"><code>@​charliermarsh</code></a></li>
<li><a
href="https://github.com/lerebear"><code>@​lerebear</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>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.84</h2>
<p>Released on 2026-09-24.</p>
<p>This release addresses <a
href="https://github.com/astral-sh/ty/security/advisories/GHSA-vxvm-j4xq-q7m4">GHSA-vxvm-j4xq-q7m4</a>,
a use-after-free vulnerability during incremental type checking that can
result in arbitrary code execution when analyzing a specially crafted
Python project. Users who run ty on untrusted code should upgrade to
0.0.84 or newer.</p>
<h3>Bug fixes</h3>
<ul>
<li>Fix stale diagnostics from the LSP server after toggling
<code>showSyntaxErrors</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28759">#28759</a>)</li>
</ul>
<h3>LSP server</h3>
<ul>
<li>Complete string keys from dictionary initializers (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28820">#28820</a>)</li>
<li>Support LSP requests against closed documents (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28595">#28595</a>)</li>
<li>Select projects for external files using import search paths (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28594">#28594</a>)</li>
<li>Use workspace editor settings for external files (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28639">#28639</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Avoid repeated subtyping checks for materialized recursive protocols
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/28774">#28774</a>)</li>
<li>Skip reading notebooks when discovering scripts (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28781">#28781</a>)</li>
</ul>
<h3>Core type checking</h3>
<ul>
<li>Avoid incorrect simplification of <code>TypeIs</code>
materializations (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28817">#28817</a>)</li>
<li>Fix disjointness of generic class types (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28787">#28787</a>)</li>
<li>Fix staticmethod shadowing through generic receivers and unions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28766">#28766</a>)</li>
<li>Infer callable signatures from bounded type variables (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28599">#28599</a>)</li>
<li>Infer constant membership in inline list and set literals (e.g.
<code>&quot;foo&quot; in [&quot;foo&quot;]</code> is now inferred as
<code>Literal[True]</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28676">#28676</a>)</li>
<li>Infer through optional generic containers in the legacy solver (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28791">#28791</a>)</li>
<li>Preserve call narrowing during cyclic inference (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28708">#28708</a>)</li>
<li>Preserve intersections of type guard return types (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28796">#28796</a>)</li>
<li>Use subtyping for constraint-set implication (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28657">#28657</a>)</li>
</ul>
<h3>Configuration</h3>
<ul>
<li>Disable <code>invalid-legacy-positional-parameter</code> by default
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/28834">#28834</a>)</li>
</ul>
<h3>Other changes</h3>
<ul>
<li>Only consider reachable definitions when determining whether a
condition should be exempted from
<code>redundant-condition(-strict)</code> due to the condition being
defined relative to <code>sys.version_info</code>,
<code>sys.platform</code>, <code>os.name</code> or
<code>typing.TYPE_CHECKING</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28788">#28788</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/ibraheemdev"><code>@​ibraheemdev</code></a></li>
<li><a href="https://github.com/zsol"><code>@​zsol</code></a></li>
<li><a
href="https://github.com/charliermarsh"><code>@​charliermarsh</code></a></li>
<li><a
href="https://github.com/lerebear"><code>@​lerebear</code></a></li>
<li><a
href="https://github.com/MichaReiser"><code>@​MichaReiser</code></a></li>
<li><a
href="https://github.com/ewdurbin"><code>@​ewdurbin</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/ty/commit/8dd9a7f7fa35a18275d82117e6593ba45507065f"><code>8dd9a7f</code></a>
Bump version to 0.0.84 (<a
href="https://redirect.github.com/astral-sh/ty/issues/4585">#4585</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/d4e4bf90906c7677049bc3a29d1735de7d22052d"><code>d4e4bf9</code></a>
publish to astral-sh/versions via OIDC (<a
href="https://redirect.github.com/astral-sh/ty/issues/4581">#4581</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/94ce7333abb356b2068dc98d37a68c993da6d09b"><code>94ce733</code></a>
use oidc issued token for docs publication (<a
href="https://redirect.github.com/astral-sh/ty/issues/4579">#4579</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/901eeaa9cd9ac06f34e85e4f721f76a0dc36e217"><code>901eeaa</code></a>
Update prek dependencies (<a
href="https://redirect.github.com/astral-sh/ty/issues/4576">#4576</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/09bf0b1b2114a628f3e2296d3d9d28245066c98b"><code>09bf0b1</code></a>
Update docker/setup-buildx-action action to v4.3.0 (<a
href="https://redirect.github.com/astral-sh/ty/issues/4575">#4575</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/89d6ffe529dd44f64adc0d28f80744b7c7ab217f"><code>89d6ffe</code></a>
Update astral-sh/setup-uv action to v10.2.0 (<a
href="https://redirect.github.com/astral-sh/ty/issues/4574">#4574</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/9c214798cfcc59069e986876564638fadf32d774"><code>9c21479</code></a>
Bump version to 0.0.83 (<a
href="https://redirect.github.com/astral-sh/ty/issues/4569">#4569</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/04c69110b391e96310e6892cbed9936c4f31d7aa"><code>04c6911</code></a>
Sync the ty security mirror (<a
href="https://redirect.github.com/astral-sh/ty/issues/4549">#4549</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/3fa57a25767c619dbeba697c2acfbb8eb291519a"><code>3fa57a2</code></a>
Grant the versions workflow repository read access (<a
href="https://redirect.github.com/astral-sh/ty/issues/4550">#4550</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/a15b35389b44db57f718fd4e19e093481747c53e"><code>a15b353</code></a>
use scoped token for release workflow (<a
href="https://redirect.github.com/astral-sh/ty/issues/4506">#4506</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/ty/compare/0.0.75...0.0.84">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

---------

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 <jkennedyvz@users.noreply.github.com>
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-10-17 16:17:52 +00:00
Elior Nataf LackritzandGitHub 41c9c22fdc release(langgraph): 1.2.16 (#9334)
Bumps langgraph from 1.2.15 to 1.2.16.

Includes:

- Keep a DeltaChannel's value when its field changes to a plain reducer
(#9302)
- Save a resumed exit run's pending writes once (#9314)
- Stop a subgraph from rerunning its finished tasks on resume (#9315)
- Apply a resumed `Command`'s update once (#9322)
- Let other tasks finish when a resume value is invalid (#9265)
2026-10-16 18:02:08 -04:00
Elior Nataf LackritzandGitHub 51018652a6 release(sdk-py): 0.4.8 (#9335)
Bumps the Python SDK version from 0.4.7 to 0.4.8. Thread streams now
report why they ended: a server's error frame becomes the error
`thread.output` raises, the sync client no longer blocks forever when
the server closes the stream, and a stream the server rejects with a 4xx
ends at once instead of being retried (#9317).
2026-10-16 18:01:53 -04:00
Elior Nataf LackritzandGitHub 9e6fc45c6f fix(sdk-py): surface thread stream errors and stop retrying a rejected stream (#9317)
## Summary

Thread streams in `langgraph_sdk` now report why they ended. An `id:
null` error frame becomes the error `thread.output` raises, the sync
client no longer blocks forever when the server closes the stream, and a
stream the server rejects with a 4xx ends at once instead of being
retried.

## Problem

- A server sends `{"type": "error", "id": null, ...}` just before it
closes a thread stream whose read failed. The SDK dropped the frame, so
`thread.output` raised a generic "lifecycle stream ended before terminal
event" (async) or blocked forever (sync: the lifecycle watcher returned
without settling the run).
- A 403 or 404 on the stream request was retried like a network error,
then reported as `RuntimeError("Lifecycle transport failed: ...")` with
no status. A server that rejects a WebSocket after accepting it closes
with 4000 plus the HTTP status (4403, 4404, 4422), since a browser can't
read a refused upgrade's status. The shared stream counted each such
reconnect as a success and reconnected forever.

## Fix

- The lifecycle watcher fails the run with the frame's error on a clean
close (both clients). The shared stream logs the frame instead of
dropping it: projections still end when the stream closes, as before,
and `thread.output` carries the error.
- A 4xx other than 408 and 429, as a status or as a 4000+status close
code, ends the stream without retries. `thread.output` now raises that
`httpx.HTTPStatusError` or `websockets.exceptions.ConnectionClosedError`
instead of the generic `RuntimeError`.
- For a minted `thread_id`, the lifecycle watcher starts after
`run.start` creates the thread. Without this, the 4xx stop would turn a
stream opened under custom auth for a not-yet-created thread from
retried into failed. The sync client starts it under a lock, since
`run.start` can be called from several threads.
- Drops the first of two `SyncStreamController._reconnect_shared_stream`
definitions. It was shadowed by the second and never ran.

Not in this PR: over WebSocket the server closes with 1011 after an
error frame, which is still retried, while over SSE the close is clean
and fails the stream. Reconnecting after a clean close, as
`langchain-ai/langgraphjs#2812` does for the JS SDK, is a separate
change.

## Test plan

- [x] The 10 new behavior tests (async and sync: error frame, clean
close, 404, 4404 close, reconnect on 404, minted `thread_id`) fail on
`main` and pass here; a race test checks that two concurrent `run.start`
calls start one watcher.
- [x] `make test` in `libs/sdk-py`: 541 passed. `make lint` clean,
`check_sdk_methods.py` passes.
- [x] `RemoteGraph` v3 tests in `libs/langgraph`
(`test_remote_graph_v3.py`,
`test_stream_events_v3_kwarg_forwarding.py`): 94 passed against this
SDK.
- [x] Against a local server with custom auth: a thread the caller can't
read fails at once with 404 (SSE) or 4404 (WebSocket), and a new thread
works with a 1 s pause before `run.start`, which fails on `main`.
2026-10-16 15:29:34 -04:00
Baihao WangandGitHub b0cabd93d0 fix(langgraph): let other tasks finish when a resume value is invalid (#9265)
Found while testing per-call human-in-the-loop
(langchain-ai/langchain#41062). The code depends only on #9232, which is
already on `main`.

When one answer in a multi-answer resume fails `response_schema`
validation, the runner cancelled the other tasks in the step, so in
async runs a task cut off after its side effect kept its saved answer
and ran again on the next resume. Invalid resume values (already tagged
by `is_invalid_resume` from #9232) no longer stop the other tasks: the
error is raised once they finish, so their results are saved.

Hit by per-call human-in-the-loop: approving one tool call while giving
another an invalid edit, in one resume, sent the approved call's email
twice once the second answer was fixed.

## How to verify

- `test_invalid_resume_lets_other_answered_tasks_finish` fails on `main`
(the approved task is still pending after its side effect) and passes
with this change. Like the other async interrupt tests, it is skipped
before Python 3.11, where async nodes can't call `interrupt()`.
- The per-call HITL reproduction against this branch: the approved async
tool runs once (twice on `main` whenever it awaits after its side
effect).
- `make lint` passes. With `NO_DOCKER=true`, the full `libs/langgraph`
suite has the same failures as `main` locally (`test_prebuilt_tool_chat`
and `test_stream_mode_messages_command`, sync and async), and nothing
new.
2026-10-16 11:33:53 -07:00
raghavverma-langchainandGitHub 4ac46ba186 release(cli): 0.4.36 (#9330)
## Summary

<!-- Write the summary yourself -->

## Notes for reviewers
- Includes [push through the active Docker
context](https://github.com/langchain-ai/langgraph/pull/9321) and
[accept serverless and dedicated deployment
aliases](https://github.com/langchain-ai/langgraph/pull/9325), the two
CLI changes merged since 0.4.35.
- Merging doesn't publish by itself: after it merges, run the `release`
workflow with `working-directory: libs/cli`, as was done for 0.4.35.

## Test Plan
- [x] Only `__version__` in `libs/cli/langgraph_cli/__init__.py` changes
(0.4.35 → 0.4.36), like the 0.4.35 release PR
- [ ] After merge, the `release` workflow for `libs/cli` succeeds and
PyPI shows `langgraph-cli` 0.4.36
- [ ] `uvx --from langgraph-cli==0.4.36 langgraph deploy` pushes on
Docker Desktop without `DOCKER_HOST` set

## Demo

<!-- Drag a screen recording or screenshot here -->
2026-10-16 12:29:42 -04:00
Elior Nataf LackritzandGitHub e14f4277dc fix(langgraph): apply a resumed Command's update once (#9322)
`Command(resume=..., update=...)` applied its update twice once the
resumed node called `interrupt()` again, with sync or async durability
(`invoke`'s default). A node that asks three times, resumed with
`Command(resume=str(i), update={"log": [f"u{i}"]})`, ends with `["u1",
"u1", "u2", "u3", "a:123"]`.

## Cause

The null task's writes are saved as one list, and savers key a regular
write by its position in that list. The Command's resume came first, so
its update was stored at index 1. The next Command saved the null task's
earlier writes again without the resume, which put the update at index
0, so it was stored twice and every later resume applied both copies.
`durability="exit"` saves after the resume is consumed, so it wasn't
affected.

## Fix

The null task's special writes (a resume) now go after its regular ones,
so each regular write keeps its index as the list grows.

`test_resume_with_update_applies_the_update_once` fails on `main` for
sync and async durability.

Found by @hntrl, and flagged by @soarez on #9314.
2026-10-16 12:07:59 -04:00
hari-dhanushkodiandGitHub 4e2dfe4a24 fix(cli): accept serverless and dedicated deployment aliases (#9325) 2026-10-16 08:02:43 -04:00
raghavverma-langchainandGitHub e77a5cb08c fix(cli): push through the active Docker context in langgraph deploy (#9321)
## Summary

<!-- Write the summary yourself -->

## Notes for reviewers
- Known gap: `--host` carries only the context's endpoint, not its TLS
settings, so a remote context that uses client certificates still can't
push. That case was broken before this change too, and it's rare for
local builds.
- A context with no Docker endpoint (Docker prints `<no value>`) or a
missing `docker` binary falls back to today's behavior.

## Test Plan
- [x] `make lint` and `make test` pass in `libs/cli` (556 tests)
- [x] New tests: `login` and `push` get the active context's endpoint as
`--host`; with `DOCKER_HOST` set there's no lookup and no `--host`; when
the context lookup fails, or the context has no Docker endpoint, there's
no `--host` and the deploy still succeeds. The first fails without the
change.
- [x] On a Mac running Docker Desktop with no `/var/run/docker.sock`:
`docker --config <temp dir> version` fails with `failed to connect to
the docker API at unix:///var/run/docker.sock`, and adding `--host
<active context endpoint>` reaches the daemon
- [x] Reproduced end to end on LangSmith AWS: `langgraph deploy`
(0.4.35) failed at the push with that error, then passed with
`DOCKER_HOST` set to the context's endpoint, which this change does
automatically
- [ ] After release, `langgraph deploy` pushes on Docker Desktop without
`DOCKER_HOST` set

## Demo

<!-- Drag a screen recording or screenshot here -->
2026-10-15 16:14:01 -04:00
b0482d0627 fix(langgraph): stop a subgraph from rerunning its finished tasks on resume (#9315)
Fixes #9106

When a subgraph pauses in several parallel interrupts and they're answered one at a time, each resume ran the nodes that were already answered again. Their saved answers carried them straight through `interrupt()`, so whatever they do after it (an API write, say) ran once more per later answer, while the final state still looked right. A `@task` that finished before an interrupt in a nested graph reran the same way, which is the rerun reported in #6792.

## Cause

Every task config sets `checkpoint_id` to None, and `PregelLoop` treats a config with that key as a replay, so a nested graph replayed on every resume. A replaying loop doesn't hand saved writes back to the tasks that made them, so finished tasks ran again.

## Fix

`PregelLoop` now notes whether the run points it at a checkpoint (a truthy `checkpoint_id`, its own `checkpoint_map` entry, or a parent that is replaying), the test `__enter__` already uses to pick the checkpoint to load. `_first` then decides once whether finished tasks rerun: only on time travel, or on a resume at a checkpoint the thread has moved past. A plain resume of a subgraph is neither, so it hands finished tasks their writes back the way a top-level resume does.

The same rule fixes the top level, which @hntrl pointed out: a resume sent with the head's `checkpoint_id`, as Studio sends it, or with `checkpoint_id: None` reran finished siblings too. A resume at an older checkpoint still reruns them, as the checkpoint migration tests expect. Telling the two apart costs one more checkpoint read for a run sent with a `checkpoint_id`. `test_addressed_resume_keeps_a_rerun_tasks_write_to_a_new_channel` relied on a resume at the head rerunning a task, so it now resumes a checkpoint the thread has moved past.

Most subgraph time travel forks first, which gives tasks new ids, so saved writes can't match them anyway. `test_subgraph_replay_reasks_answered_parallel_interrupt` replays an update checkpoint, which runs in place: it passes on `main` and fails if subgraphs never replay.

The counters added to `test_parallel_interrupts_double` and the three new tests in `test_interrupt_state.py` fail on `main`.

## Not in this PR

- With `durability="exit"` and three or more parallel interrupts, a subgraph now hits a bug top-level graphs already have: a finished task's output is applied twice. #9314 fixes it.
- #6792 also reports the interrupt of a `@task` inside a nested `@entrypoint` coming back twice. That's a separate bug.

Thanks @connorimes for the report, the root cause and the fix, which this applies as proposed. The same change was opened as #9113 by @AnvitDevadiga, and @morehardy found the root cause for #6792 in #6793.

JS: langchain-ai/langgraphjs#3028

Co-authored-by: Connor Imes <5701850+connorimes@users.noreply.github.com>
2026-10-15 15:51:21 -04:00
Elior Nataf LackritzandGitHub 889a5ff16f fix(langgraph): save a resumed exit run's pending writes once (#9314)
With `durability="exit"`, resuming three or more parallel interrupts one
at a time applied the earlier answers twice. Nodes `a`, `b` and `c` each
return `{"log": [f"{name}:{interrupt(name)}"]}`, and the run ends with
`{"log": ["a:yes", "a:yes", "b:yes", "c:yes"]}`.

## Cause

An exit-mode run saves its pending writes when it ends, and that save
took all of them, including the ones loaded with the checkpoint. Savers
key a task's writes by their position in the list they're given. A task
that finished in an earlier run comes back with its old interrupt next
to its output, so saving its list again put the output under a new
index, and the next load applied both copies.

## Fix

The save at exit keeps only the writes of tasks that wrote during this
run. The rest are already stored on the checkpoint, which is what sync
and async durability do.

`test_finished_task_output_applies_once` fails on `main` in exit mode
for every saver and passes for sync and async.

The #9106 fix needs this too: once a subgraph stops rerunning its
finished tasks, it hits this bug in exit mode.

JS: langchain-ai/langgraphjs#3027
2026-10-15 15:26:01 -04:00
Baihao WangandGitHub 5eb60aef6f release(prebuilt): 1.1.1 (#9316)
Bumps langgraph-prebuilt from 1.1.0 to 1.1.1.

Includes:

- Stop `ToolNode` from swallowing a resume value that fails an
interrupt's `response_schema` (#9232). This needs langgraph 1.2.15 or
later, which marks those errors; with an older langgraph, `ToolNode`
keeps today's behavior.
- Keep tool results out of v3 messages (#7838)

Managed Deep Agents (langchain-ai/managed-deepagents-sdk#710) will raise
its `langgraph-prebuilt` minimum to this release, so an invalid answer
to an interrupt raised inside a tool, such as a subagent, fails the
resume instead of reaching the model.

After merge, run the Release workflow with
`working-directory=libs/prebuilt`.

---

This PR was written with help from an AI coding agent (Claude Code); a
human reviewed and directed the changes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-14 18:31:09 -07:00
35 changed files with 1972 additions and 729 deletions
+10 -10
View File
@@ -21,8 +21,8 @@
"test:all": "yarn test && yarn test:int && yarn lint:langgraph"
},
"dependencies": {
"@langchain/core": "^1.2.9",
"@langchain/langgraph": "^1.4.13"
"@langchain/core": "^1.2.13",
"@langchain/langgraph": "^1.4.18"
},
"resolutions": {
"@langchain/langgraph-checkpoint": "1.0.4",
@@ -30,21 +30,21 @@
"@eslint/eslintrc/js-yaml": "4.3.2"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.6",
"@eslint/eslintrc": "^3.3.7",
"@eslint/js": "^10.0.1",
"@tsconfig/recommended": "^1.0.13",
"@types/jest": "^30.0.0",
"@typescript-eslint/eslint-plugin": "^8.68.0",
"@typescript-eslint/parser": "^8.68.0",
"dotenv": "^17.4.2",
"eslint": "^10.9.1",
"@typescript-eslint/eslint-plugin": "^8.70.1",
"@typescript-eslint/parser": "^8.70.1",
"dotenv": "^18.0.4",
"eslint": "^10.11.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-no-instanceof": "^1.0.1",
"eslint-plugin-prettier": "^5.5.6",
"jest": "^30.5.0",
"prettier": "^3.9.6",
"ts-jest": "^29.4.12",
"jest": "^30.5.2",
"prettier": "^3.9.9",
"ts-jest": "^29.4.14",
"typescript": "^7.0.2"
}
}
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -589,9 +589,9 @@ brace-expansion@^1.1.7:
concat-map "0.0.1"
brace-expansion@^5.0.8:
version "5.0.9"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.9.tgz#7c72438809b5fa5babf54199a1f1c281a6984fcf"
integrity sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==
version "5.0.12"
resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz#995fbb4750a77c4d16a7dc942ff2ca6ef8e675ec"
integrity sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==
dependencies:
balanced-match "^4.0.2"
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.4.35"
__version__ = "0.4.37"
+53 -13
View File
@@ -1209,6 +1209,33 @@ class DockerBuildCommand:
return cls(("docker", "buildx", "build"), flags)
def _active_docker_host(runner: CommandRunner) -> str | None:
"""Return the daemon endpoint of the user's active Docker context.
None when `DOCKER_HOST` is set, which Docker honors with any config
directory. Also None when the lookup fails (for example, `DOCKER_CONTEXT`
names a context that doesn't exist) or the context has no Docker endpoint
(Docker prints `<no value>`); that keeps Docker's own default in place.
"""
if os.environ.get("DOCKER_HOST"):
return None
try:
stdout, _ = runner.run(
subp_exec(
"docker",
"context",
"inspect",
"--format",
"{{.Endpoints.docker.Host}}",
collect=True,
)
)
except (click.exceptions.Exit, OSError):
return None
host = (stdout or "").strip()
return host if "://" in host else None
def _docker_argv(docker_config_dir: str | None) -> tuple[str, ...]:
if docker_config_dir is None:
return ("docker",)
@@ -1242,17 +1269,13 @@ def _push_image(
runner: CommandRunner,
image: str,
*,
docker_config_dir: str | None,
docker: tuple[str, ...],
verbose: bool,
) -> None:
for attempt in range(1, _PUSH_ATTEMPTS + 1):
try:
with Progress(message="Pushing...", elapsed=not verbose):
runner.run(
subp_exec(
*_docker_argv(docker_config_dir), "push", image, verbose=verbose
)
)
runner.run(subp_exec(*docker, "push", image, verbose=verbose))
return
except click.exceptions.Exit:
if attempt == _PUSH_ATTEMPTS:
@@ -1337,6 +1360,11 @@ def _run_local_build(
registry_host = repository.split("/")[0]
with _docker_config_for_token(registry_host, deployment_token, username) as cfg:
# A config directory has no current context, so Docker would fall
# back to /var/run/docker.sock; keep using the daemon the build used.
docker_host = _active_docker_host(runner)
host_args = ("--host", docker_host) if docker_host else ()
docker = (*_docker_argv(cfg), *host_args)
_log_deploy_step(step, f"Logging into {registry_host}")
token_input = (
deployment_token
@@ -1345,7 +1373,7 @@ def _run_local_build(
)
runner.run(
subp_exec(
*_docker_argv(cfg),
*docker,
"login",
"-u",
username,
@@ -1361,7 +1389,7 @@ def _run_local_build(
runner.run(
subp_exec("docker", "tag", image_to_push, remote_image, verbose=verbose)
)
_push_image(runner, remote_image, docker_config_dir=cfg, verbose=verbose)
_push_image(runner, remote_image, docker=docker, verbose=verbose)
step += 1
resolved_image = _resolve_pushed_image_digest(
@@ -1532,6 +1560,14 @@ def _resolve_or_create(
secrets=ctx.secrets,
)
except HostBackendError as err:
if err.status_code == 400 and err.detail == (
"Scale to zero deployments are not supported in AWS environments"
):
raise HostBackendError(
f"{err.message}\nUse --deployment-type dev instead.",
status_code=err.status_code,
detail=err.detail,
) from None
if _needs_a_listener(err):
raise ListenerRequiredError(
"The image has to come from a registry you manage, so re-run with "
@@ -1826,7 +1862,7 @@ class CustomerRegistrySource:
_build_image(runner, ctx.spec, image, verbose=ctx.verbose)
step += 1
_log_deploy_step(step, f"Pushing image {image}")
_push_image(runner, image, docker_config_dir=None, verbose=ctx.verbose)
_push_image(runner, image, docker=_docker_argv(None), verbose=ctx.verbose)
step += 1
digest = _resolve_pushed_image_digest(
runner, remote_image=image, docker_config_dir=None, verbose=ctx.verbose
@@ -2185,11 +2221,13 @@ def _deploy_base_options(
),
click.option(
"--deployment-type",
type=click.Choice(["dev", "prod"]),
default="dev",
type=click.Choice(["serverless", "dedicated", "dev", "prod"]),
default="serverless",
show_default=True,
help=(
"Deployment type (used when creating a new deployment). "
"Deployment type (used when creating a new deployment): "
"serverless (scale to zero) or dedicated (prod); "
"legacy dev and prod are also accepted. "
"Ignored with --push-to."
),
),
@@ -2480,7 +2518,9 @@ def _deploy_cmd(
),
verbose=verbose,
selector=selector,
deployment_type=deployment_type,
deployment_type={"serverless": "dev_zero", "dedicated": "prod"}.get(
deployment_type, deployment_type
),
secrets=secrets,
tracked_packages=tracked_packages,
)
+2 -5
View File
@@ -44,11 +44,8 @@ async def subp_exec(
on_stdout: Callable[[str], bool | None] | None = None,
) -> tuple[str | None, str | None]:
if verbose:
cmd_str = f"+ {cmd} {' '.join(map(str, args))}"
if input:
print(cmd_str, " <\n", "\n".join(filter(None, input.splitlines())), sep="")
else:
print(cmd_str)
# Never echo stdin: it can carry secrets (docker login --password-stdin).
print(f"+ {cmd} {' '.join(map(str, args))}")
if wait:
await asyncio.sleep(wait)
@@ -52,6 +52,8 @@ ARCHIVE = ("/tmp/src.tgz", 2048, "langgraph.json")
OBJECT_PATH = "tarballs/src.tgz"
PLATFORM_FORMAT = "{{.Os}}/{{.Architecture}}"
DIGESTS_FORMAT = "{{json .RepoDigests}}"
CONTEXT_HOST_FORMAT = "{{.Endpoints.docker.Host}}"
CONTEXT_HOST = "unix:///Users/dev/.docker/run/docker.sock"
NOT_A_CLI_DEPLOYMENT = (
"push token is only available for 'internal_docker' source deployments"
)
@@ -192,6 +194,8 @@ class DockerCommand:
class DockerDouble:
timeline: list[str]
failing_pushes: int = 0
context_lookup_fails: bool = False
context_host: str = CONTEXT_HOST
builds: list[dict] = field(default_factory=list)
commands: list[DockerCommand] = field(default_factory=list)
@@ -235,6 +239,10 @@ class DockerDouble:
if "push" in args and self.failing_pushes > 0:
self.failing_pushes -= 1
raise click.exceptions.Exit(1)
if CONTEXT_HOST_FORMAT in args:
if self.context_lookup_fails:
raise click.exceptions.Exit(1)
return f"{self.context_host}\n", None
if PLATFORM_FORMAT in args:
return "linux/amd64\n", None
if DIGESTS_FORMAT in args:
@@ -244,6 +252,8 @@ class DockerDouble:
@staticmethod
def _verb(args: tuple[str, ...]) -> str:
if CONTEXT_HOST_FORMAT in args:
return "inspect-context"
if PLATFORM_FORMAT in args:
return "inspect-platform"
if DIGESTS_FORMAT in args:
@@ -292,7 +302,12 @@ def deploy_project(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> DeployPro
json.dumps({"graphs": {"agent": "agent.py:graph"}, "dependencies": ["."]})
)
monkeypatch.chdir(tmp_path)
for name in ("LANGSMITH_TENANT_ID", "LANGSMITH_ENDPOINT", "LANGGRAPH_HOST_URL"):
for name in (
"LANGSMITH_TENANT_ID",
"LANGSMITH_ENDPOINT",
"LANGGRAPH_HOST_URL",
"DOCKER_HOST",
):
monkeypatch.delenv(name, raising=False)
timeline: list[str] = []
@@ -329,6 +344,44 @@ def deploy_project(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> DeployPro
return DeployProject(control_plane, docker, timeline, uploads)
@pytest.mark.parametrize("build_mode", ["--no-remote", "--remote"])
@pytest.mark.parametrize(
("detail", "expects_dev_hint"),
[
("Scale to zero deployments are not supported in AWS environments", True),
("No eligible cluster is currently available to host this deployment.", False),
],
)
def test_create_failure_preserves_backend_error_and_only_hints_for_aws_rejection(
deploy_project: DeployProject, build_mode: str, detail: str, expects_dev_hint: bool
) -> None:
deploy_project.control_plane.create_error = detail
result = deploy_project.run(build_mode)
assert result.exit_code == 1, result.output
assert detail in result.output
assert ("Use --deployment-type dev instead." in result.output) is expects_dev_hint
assert deploy_project.control_plane.bodies[CREATE_DEPLOYMENT]["source_config"] == {
"deployment_type": "dev_zero"
}
assert deploy_project.timeline == [LIST_DEPLOYMENTS, CREATE_DEPLOYMENT]
@pytest.mark.parametrize("build_mode", ["--no-remote", "--remote"])
def test_aws_serverless_creation_succeeds_when_backend_accepts_it(
deploy_project: DeployProject, build_mode: str
) -> None:
result = deploy_project.run(
build_mode, host_url="https://aws.api.host.langchain.com"
)
assert result.exit_code == 0, result.output
assert deploy_project.control_plane.bodies[CREATE_DEPLOYMENT]["source_config"] == {
"deployment_type": "dev_zero"
}
def test_first_local_deploy_creates_then_builds_pushes_and_updates_in_order(
deploy_project: DeployProject,
) -> None:
@@ -340,6 +393,7 @@ def test_first_local_deploy_creates_then_builds_pushes_and_updates_in_order(
CREATE_DEPLOYMENT,
"docker build",
_push_token(CREATED_ID),
"docker inspect-context",
"docker login",
"docker tag",
"docker push",
@@ -349,15 +403,41 @@ def test_first_local_deploy_creates_then_builds_pushes_and_updates_in_order(
assert "Deployment updated" in result.output
def test_first_local_deploy_creates_an_internal_docker_deployment(
deploy_project: DeployProject,
@pytest.mark.parametrize(
("deployment_type", "wire_type"),
[
("serverless", "dev_zero"),
("dedicated", "prod"),
("dev", "dev"),
("prod", "prod"),
],
)
@pytest.mark.parametrize("build_mode", ["--no-remote", "--remote"])
def test_deployment_type_aliases(
deploy_project: DeployProject, deployment_type: str, wire_type: str, build_mode: str
) -> None:
deploy_project.run("--no-remote")
result = deploy_project.run(build_mode, "--deployment-type", deployment_type)
assert result.exit_code == 0, result.output
assert deploy_project.control_plane.bodies[CREATE_DEPLOYMENT]["source_config"] == {
"deployment_type": wire_type
}
@pytest.mark.parametrize(
("build_mode", "source"),
[("--no-remote", "internal_docker"), ("--remote", "internal_source")],
)
def test_first_deploy_defaults_to_serverless(
deploy_project: DeployProject, build_mode: str, source: str
) -> None:
result = deploy_project.run(build_mode)
assert result.exit_code == 0, result.output
assert deploy_project.control_plane.bodies[CREATE_DEPLOYMENT] == {
"name": "my-app",
"source": "internal_docker",
"source_config": {"deployment_type": "dev"},
"source": source,
"source_config": {"deployment_type": "dev_zero"},
"source_revision_config": {},
"secrets": [],
}
@@ -406,7 +486,7 @@ def test_local_deploy_logs_in_with_the_control_plane_push_token(
login = deploy_project.docker.command("login")
assert login.args[:2] == ("docker", "--config")
assert login.args[3:] == (
assert login.args[-5:] == (
"login",
"-u",
"oauth2accesstoken",
@@ -416,6 +496,53 @@ def test_local_deploy_logs_in_with_the_control_plane_push_token(
assert login.kwargs["input"] == f"{PUSH_TOKEN}\n"
def test_local_deploy_logs_in_and_pushes_through_the_active_docker_context(
deploy_project: DeployProject,
) -> None:
"""The temporary config has no current context, so without the active
context's endpoint Docker would push through /var/run/docker.sock."""
result = deploy_project.run("--no-remote")
assert result.exit_code == 0, result.output
for verb in ("login", "push"):
args = deploy_project.docker.command(verb).args
assert args[3:5] == ("--host", CONTEXT_HOST), verb
def test_local_deploy_leaves_an_explicit_docker_host_to_docker(
deploy_project: DeployProject, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("DOCKER_HOST", "tcp://127.0.0.1:2375")
result = deploy_project.run("--no-remote")
assert result.exit_code == 0, result.output
for verb in ("login", "push"):
assert "--host" not in deploy_project.docker.command(verb).args, verb
def test_local_deploy_keeps_dockers_default_when_the_context_lookup_fails(
deploy_project: DeployProject,
) -> None:
deploy_project.docker.context_lookup_fails = True
result = deploy_project.run("--no-remote")
assert result.exit_code == 0, result.output
assert "--host" not in deploy_project.docker.command("push").args
def test_local_deploy_ignores_a_context_without_a_docker_endpoint(
deploy_project: DeployProject,
) -> None:
deploy_project.docker.context_host = "<no value>"
result = deploy_project.run("--no-remote")
assert result.exit_code == 0, result.output
assert "--host" not in deploy_project.docker.command("push").args
def test_local_deploy_tells_the_control_plane_it_honors_registry_usernames(
deploy_project: DeployProject,
) -> None:
@@ -438,7 +565,7 @@ def test_local_deploy_logs_in_with_the_username_the_control_plane_returns(
deploy_project.run("--no-remote")
login = deploy_project.docker.command("login")
assert login.args[3:] == (
assert login.args[-5:] == (
"login",
"-u",
"AWS",
@@ -506,6 +633,7 @@ def test_prebuilt_image_is_validated_and_pushed_without_a_build(
assert deploy_project.docker.builds == []
assert deploy_project.docker.verbs() == [
"docker inspect-platform",
"docker inspect-context",
"docker login",
"docker tag",
"docker push",
+17
View File
@@ -0,0 +1,17 @@
import sys
from langgraph_cli.exec import subp_exec
async def test_verbose_prints_command_but_not_stdin(capsys):
await subp_exec(
sys.executable,
"-c",
"import sys; sys.stdin.read()",
input="registry-push-token\n",
verbose=True,
)
out = capsys.readouterr().out
assert out.startswith(f"+ {sys.executable} -c"), out
assert "registry-push-token" not in out, out
+33 -18
View File
@@ -43,14 +43,31 @@ start-services:
stop-services:
docker compose -f tests/compose-postgres.yml -f tests/compose-redis.yml down -v
DEV_SERVER_PYTHON ?= 3.12
DEV_SERVER_ENV = $(CURDIR)/.venvs/dev-server
start-dev-server:
LOG_LEVEL=warning uv run langgraph dev --config tests/example_app/langgraph.json --no-browser & echo "$$!" > .devserver.pid
@echo "Dev server started."
uv sync --frozen --group test --no-dev
UV_PROJECT_ENVIRONMENT=$(DEV_SERVER_ENV) uv sync --frozen --group test --no-dev --python $(DEV_SERVER_PYTHON)
@LOG_LEVEL=warning UV_PROJECT_ENVIRONMENT=$(DEV_SERVER_ENV) uv run --no-sync --python $(DEV_SERVER_PYTHON) langgraph dev --config tests/example_app/langgraph.json --no-browser --no-reload & \
PID=$$!; echo "$$PID" > .devserver.pid; \
for ATTEMPT in $$(seq 1 60); do \
if ! kill -0 $$PID 2>/dev/null; then break; fi; \
if curl --fail --silent --max-time 1 http://localhost:2024/ok >/dev/null; then \
echo "Dev server ready."; exit 0; \
fi; \
sleep 1; \
done; \
echo "Dev server failed to become ready." >&2; \
kill $$PID 2>/dev/null || true; \
wait $$PID 2>/dev/null || true; \
rm -f .devserver.pid; exit 1
stop-dev-server:
@if [ -f .devserver.pid ]; then \
kill `cat .devserver.pid` && rm .devserver.pid; \
echo "Dev server stopped."; \
kill $$(cat .devserver.pid) 2>/dev/null || true; \
rm -f .devserver.pid; \
echo "Dev server stopped."; \
else \
echo "No dev server PID file found."; \
fi
@@ -60,13 +77,12 @@ NO_DOCKER ?= $(sh command -v docker >/dev/null 2>&1 && echo "false" || echo "tru
test:
if [ "$(NO_DOCKER)" = "false" ]; then \
make start-services &&\
make start-dev-server &&\
uv run pytest $(TEST); \
EXIT_CODE=$$?; \
make stop-services; \
make stop-dev-server; \
exit $$EXIT_CODE; \
trap '$(MAKE) stop-dev-server; $(MAKE) stop-services' EXIT; \
trap 'exit 130' INT; trap 'exit 143' TERM; \
$(MAKE) start-services &&\
$(MAKE) start-dev-server &&\
uv run --no-sync pytest $(TEST); \
exit $$?; \
else \
NO_DOCKER=true uv run pytest $(TEST) ; \
EXIT_CODE=$$?; \
@@ -74,13 +90,12 @@ test:
fi
test_parallel:
make start-services &&\
make start-dev-server &&\
uv run pytest -n auto --dist worksteal $(TEST) -vv --lf; \
EXIT_CODE=$$?; \
make stop-services; \
make stop-dev-server; \
exit $$EXIT_CODE
trap '$(MAKE) stop-dev-server; $(MAKE) stop-services' EXIT; \
trap 'exit 130' INT; trap 'exit 143' TERM; \
$(MAKE) start-services &&\
$(MAKE) start-dev-server &&\
uv run --no-sync pytest -n auto --dist worksteal $(TEST) -vv --lf; \
exit $$?
integration_tests:
uv run pytest integration_tests
+32 -10
View File
@@ -257,8 +257,10 @@ class PregelLoop:
_resume_loaded_writes: Sequence[PendingWrite] = ()
# Tasks `_reapply_writes_to_succeeded_nodes` handed loaded writes back to.
_reapplied_task_ids: set[str]
# An update or fork checkpoint addressed by `checkpoint_id` that the thread
# has moved past, so it may already have children.
# Tasks that wrote during this run, the only ones exit mode saves at exit.
_written_task_ids: set[str]
# A checkpoint addressed by `checkpoint_id` that the thread has moved past,
# so it may already have children.
_addressed_checkpoint_superseded: bool = False
# The checkpoint_config that points at the parent loaded at `__enter__`
@@ -349,7 +351,6 @@ class PregelLoop:
self.interrupt_before = interrupt_before
self.manager = manager
self.is_nested = CONFIG_KEY_TASK_ID in self.config.get(CONF, {})
self.is_replaying = CONFIG_KEY_CHECKPOINT_ID in config[CONF]
self._migrate_checkpoint = migrate_checkpoint
self.trigger_to_nodes = trigger_to_nodes
self.retry_policy = retry_policy
@@ -407,6 +408,14 @@ class PregelLoop:
else ()
)
self.prev_checkpoint_config = None
# Whether the run points this loop at a checkpoint: a truthy
# `checkpoint_id`, this graph's `checkpoint_map` entry, or a replaying
# parent. Every task config carries `checkpoint_id: None`, so a key
# check would count every subgraph.
self._addressed = bool(
self.checkpoint_config[CONF].get(CONFIG_KEY_CHECKPOINT_ID)
or self.config[CONF].get(CONFIG_KEY_REPLAY_STATE)
)
runtime = self.config[CONF].get(CONFIG_KEY_RUNTIME)
self.control = runtime.control if isinstance(runtime, Runtime) else None
@@ -455,6 +464,7 @@ class PregelLoop:
"""Put writes for a task, to be read by the next tick."""
if not writes:
return
self._written_task_ids.add(task_id)
# deduplicate writes to special channels, last write wins
if all(w[0] in WRITES_IDX_MAP for w in writes):
writes = list({w[0]: w for w in writes}.values())
@@ -468,6 +478,11 @@ class PregelLoop:
for w in self.checkpoint_pending_writes
if w[0] != task_id or w[1] not in WRITES_IDX_MAP
]
# Savers key a regular write by its position in the list, and the
# special ones dropped above won't be there next time, so they go
# last: a resume saved ahead of an update would move it to a new
# slot once the list is saved again, and it would apply twice.
writes = sorted(writes, key=lambda w: w[0] in WRITES_IDX_MAP)
writes_to_save: WritesT = [
w[1:] for w in self.checkpoint_pending_writes if w[0] == task_id
] + list(writes)
@@ -556,10 +571,12 @@ class PregelLoop:
CONFIG_KEY_CHECKPOINT_ID: self.checkpoint["id"],
},
)
# group by task id
# Writes loaded with the checkpoint are stored on it already. Saving them
# again can file them under new indexes, and then they apply twice.
by_task = defaultdict(list)
for task_id, channel, value in self.checkpoint_pending_writes:
by_task[task_id].append((channel, value))
if task_id in self._written_task_ids:
by_task[task_id].append((channel, value))
# submit writes to checkpointer
for task_id, writes in by_task.items():
if self.checkpointer_put_writes_accepts_task_path and hasattr(
@@ -904,6 +921,7 @@ class PregelLoop:
if tid != NULL_TASK_ID and isinstance(self.specs.get(ch), DeltaChannel)
}
self._exit_command_writes = []
self._written_task_ids = set()
# Resuming from a previous checkpoint requires two things:
# 1. A prior checkpoint exists (channel_versions is non-empty)
# 2. The input signals continuation (not a fresh run with new input)
@@ -931,7 +949,7 @@ class PregelLoop:
# writes so that interrupt() calls re-fire instead of returning
# stale values. But if we're actively resuming, keep them —
# multi-interrupt scenarios need previously resolved values preserved.
is_time_traveling = self.is_replaying and (
is_time_traveling = self._addressed and (
# Time-travel to a subgraph checkpoint: the parent sets
# RESUMING=True (it can't distinguish time-travel from resume),
# so we check if this subgraph's own ns is in checkpoint_map.
@@ -950,6 +968,10 @@ class PregelLoop:
or configurable.get(CONFIG_KEY_RESUMING, False)
)
)
# Finished tasks rerun only on time travel, or on a resume at a
# checkpoint the thread has moved past. A resume addressed at the
# thread's latest checkpoint hands their writes back, as without an id.
self.is_replaying = is_time_traveling or self._addressed_checkpoint_superseded
if is_time_traveling:
self.checkpoint_pending_writes = [
w for w in self.checkpoint_pending_writes if w[1] != RESUME
@@ -1165,11 +1187,11 @@ class PregelLoop:
# checkpoint ID since the fork was created after the subgraph's
# checkpoints from the original execution.
#
# Only gate on is_time_traveling (not is_replaying). When the
# Only gate on is_time_traveling (not _addressed). When the
# client resumes with an explicit checkpoint_id that happens to
# point at the current head (e.g. LangGraph Studio sending
# `checkpoint: {checkpoint_id}` alongside Command(resume=...)),
# is_replaying is True but is_time_traveling is False. In that
# the run is addressed but not time traveling. In that
# case subgraphs should load their latest checkpoint normally,
# not go through ReplayState's before-bound lookup which would
# miss subgraph checkpoints created during processing of the
@@ -1789,7 +1811,7 @@ class SyncPregelLoop(PregelLoop, AbstractContextManager):
# This covers both normal replay and subgraphs resolved via
# checkpoint_map during time-travel.
saved = self.checkpointer.get_tuple(self.checkpoint_config)
if saved is not None and saved.metadata.get("source") in ("update", "fork"):
if saved is not None:
self._addressed_checkpoint_superseded = checkpoint_superseded(
self.checkpointer, self.checkpoint_config, saved
)
@@ -2045,7 +2067,7 @@ class AsyncPregelLoop(PregelLoop, AbstractAsyncContextManager):
# This covers both normal replay and subgraphs resolved via
# checkpoint_map during time-travel.
saved = await self.checkpointer.aget_tuple(self.checkpoint_config)
if saved is not None and saved.metadata.get("source") in ("update", "fork"):
if saved is not None:
self._addressed_checkpoint_superseded = await acheckpoint_superseded(
self.checkpointer, self.checkpoint_config, saved
)
+7 -2
View File
@@ -41,7 +41,7 @@ from langgraph._internal._future import chain_future, run_coroutine_threadsafe
from langgraph._internal._scratchpad import PregelScratchpad
from langgraph._internal._typing import MISSING
from langgraph.constants import TAG_HIDDEN
from langgraph.errors import GraphBubbleUp, GraphInterrupt
from langgraph.errors import GraphBubbleUp, GraphInterrupt, is_invalid_resume
from langgraph.pregel._algo import Call
from langgraph.pregel._executor import Submit
from langgraph.pregel._retry import arun_with_retry, run_with_retry
@@ -621,7 +621,11 @@ def _should_stop_others(
handled_exception_ids: set[int] | None = None,
) -> bool:
"""Check if any task failed, if so, cancel all other tasks.
GraphInterrupts are not considered failures."""
GraphInterrupts are not considered failures.
Neither is an invalid resume value: it's raised once the other tasks finish, so a
task answered in the same resume isn't cut off after its side effects and then run
again with its saved answer."""
for fut in done:
if fut.cancelled():
continue
@@ -629,6 +633,7 @@ def _should_stop_others(
if (
id(exc) not in (handled_exception_ids or set())
and not isinstance(exc, GraphBubbleUp)
and not is_invalid_resume(exc)
and fut not in SKIP_RERAISE_SET
):
return True
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "langgraph"
version = "1.2.15"
version = "1.2.16"
description = "Building stateful, multi-actor applications with LLMs"
authors = []
requires-python = ">=3.10"
@@ -521,10 +521,11 @@ def test_addressed_resume_keeps_a_rerun_tasks_write_to_a_new_channel(
graph = builder.compile(checkpointer=sync_checkpointer)
config = {"configurable": {"thread_id": "t"}}
graph.invoke(_both("in"), config, durability=durability)
head = graph.get_state(config).config
paused = graph.get_state(config).config
graph.update_state(config, None)
live = graph.invoke(
Command(resume="yes", update={"flag": True}), head, durability=durability
Command(resume="yes", update={"flag": True}), paused, durability=durability
)
state = graph.get_state(config)
@@ -152,6 +152,83 @@ def test_finished_task_does_not_report_answered_interrupt(
assert asked == [["A1", "B"]]
def test_finished_task_output_applies_once(
sync_checkpointer: BaseCheckpointSaver, durability: Durability
) -> None:
builder = StateGraph(State)
builder.add_node("a", lambda s: {"log": [f"a:{interrupt('A')}"]})
builder.add_node("b", lambda s: {"log": [f"b:{interrupt('B')}"]})
builder.add_node("c", lambda s: {"log": [f"c:{interrupt('C')}"]})
builder.add_edge(START, "a")
builder.add_edge(START, "b")
builder.add_edge(START, "c")
graph = builder.compile(checkpointer=sync_checkpointer)
config = _config()
result = graph.invoke({"log": []}, config, durability=durability)
while pending := result.get("__interrupt__"):
result = graph.invoke(
Command(resume={pending[0].id: "yes"}), config, durability=durability
)
assert sorted(result["log"]) == ["a:yes", "b:yes", "c:yes"]
def test_resume_with_update_applies_the_update_once(
sync_checkpointer: BaseCheckpointSaver, durability: Durability
) -> None:
def ask_three_times(state: State) -> State:
answers = [interrupt(str(question)) for question in range(1, 4)]
return {"log": ["a:" + "".join(answers)]}
graph = (
StateGraph(State)
.add_node("a", ask_three_times)
.add_edge(START, "a")
.compile(checkpointer=sync_checkpointer)
)
config = _config()
result = graph.invoke({"log": []}, config, durability=durability)
for i in range(1, 4):
result = graph.invoke(
Command(resume=str(i), update={"log": [f"u{i}"]}),
config,
durability=durability,
)
assert sorted(result["log"]) == ["a:123", "u1", "u2", "u3"]
assert sorted(graph.get_state(config).values["log"]) == sorted(result["log"])
@pytest.mark.parametrize("checkpoint_id", ["none", "head"])
def test_resume_at_the_latest_checkpoint_does_not_rerun_finished_task(
sync_checkpointer: BaseCheckpointSaver, checkpoint_id: str
) -> None:
calls: Counter[str] = Counter()
graph = _build_parallel(sync_checkpointer, calls)
config = _config()
graph.invoke({"log": []}, config)
def resume(question: str, answer: str) -> Any:
snapshot = graph.get_state(config)
target = (
snapshot.config
if checkpoint_id == "head"
else {"configurable": {**config["configurable"], "checkpoint_id": None}}
)
return graph.invoke(
Command(resume={_interrupt_by_value(snapshot, question).id: answer}),
target,
)
resume("A1", "yes")
result = resume("B", "ok")
assert sorted(result["log"]) == ["a:yes", "b:ok"]
assert calls == {"a": 2, "b": 3}, "resuming B ran the finished A again"
@NEEDS_CONTEXTVARS
async def test_finished_task_does_not_report_answered_interrupt_async(
async_checkpointer: BaseCheckpointSaver,
@@ -474,6 +551,64 @@ def test_functional_task_finished_with_none_is_not_rerun(
assert calls == {"a": 2, "b": 3}
def test_subgraph_finished_task_is_not_rerun(
sync_checkpointer: BaseCheckpointSaver, durability: Durability
) -> None:
calls: Counter[str] = Counter()
builder = StateGraph(State)
builder.add_node("child", _build_parallel(None, calls))
builder.add_edge(START, "child")
graph = builder.compile(checkpointer=sync_checkpointer)
config = _config()
graph.invoke({"log": []}, config, durability=durability)
snapshot = graph.get_state(config)
graph.invoke(
Command(resume={_interrupt_by_value(snapshot, "A1").id: "yes"}),
config,
durability=durability,
)
snapshot = graph.get_state(config)
result = graph.invoke(
Command(resume={_interrupt_by_value(snapshot, "B").id: "ok"}),
config,
durability=durability,
)
assert sorted(result["log"]) == ["a:yes", "b:ok"]
assert calls == {"a": 2, "b": 3}, "resuming B ran the finished A again"
def test_nested_entrypoint_finished_task_is_not_rerun(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
calls: Counter[str] = Counter()
@task
def step_1(query: str) -> str:
calls["step_1"] += 1
return f"{query} bar"
@task
def human_feedback(query: str) -> str:
calls["human_feedback"] += 1
return f"{query} {interrupt('feedback?')}"
@entrypoint()
def child(query: str) -> str:
return human_feedback(step_1(query).result()).result()
@entrypoint(checkpointer=sync_checkpointer)
def parent(query: str) -> str:
return child.invoke(query)
config = _config()
parent.invoke("foo", config)
assert parent.invoke(Command(resume="baz"), config) == "foo bar baz"
assert calls == {"step_1": 1, "human_feedback": 2}
# --- Reading task status from recorded writes ---
+62 -1
View File
@@ -1,5 +1,8 @@
import asyncio
import operator
import sys
from dataclasses import dataclass
from typing import Any
from typing import Annotated, Any
import pytest
from langgraph.checkpoint.base import BaseCheckpointSaver
@@ -251,3 +254,61 @@ def test_interrupt_response_schema_invalid_resume_after_earlier_interrupt(
assert graph.invoke(resume({"approved": True}), config) == {
"answer": ["ok", Decision(approved=True)]
}
@pytest.mark.skipif(
sys.version_info < (3, 11),
reason="Python 3.11+ is required for async contextvars support",
)
async def test_invalid_resume_lets_other_answered_tasks_finish(
async_checkpointer: BaseCheckpointSaver,
) -> None:
"""An invalid answer fails the resume without cutting off the other answered tasks.
A task cancelled after its side effect keeps its answer and runs again on the next
resume, so the side effect would happen twice.
"""
sent: list[str] = []
class State(TypedDict):
log: Annotated[list[str], operator.add]
async def send(state: State) -> State:
interrupt("send?", response_schema=Decision)
sent.append("email")
await asyncio.sleep(0.05) # waiting on the reply to a request already sent
return {"log": ["sent"]}
async def check(state: State) -> State:
interrupt("check?", response_schema=Decision)
return {"log": ["checked"]}
graph = (
StateGraph(State)
.add_node("send", send)
.add_node("check", check)
.add_edge(START, "send")
.add_edge(START, "check")
.compile(checkpointer=async_checkpointer)
)
config = {"configurable": {"thread_id": "1"}}
await graph.ainvoke({"log": []}, config)
ids = {i.value: i.id for i in (await graph.aget_state(config)).interrupts}
with pytest.raises(ValidationError, match="approved"):
await graph.ainvoke(
Command(
resume={
ids["send?"]: {"approved": True},
ids["check?"]: {"approved": "nope"},
}
),
config,
)
pending = [i.value for i in (await graph.aget_state(config)).interrupts]
assert (pending, sent) == (["check?"], ["email"])
result = await graph.ainvoke(
Command(resume={ids["check?"]: {"approved": True}}), config
)
assert (sorted(result["log"]), sent) == (["checked", "sent"], ["email"])
+5
View File
@@ -7737,6 +7737,8 @@ def test_parallel_interrupts(sync_checkpointer: BaseCheckpointSaver) -> None:
def test_parallel_interrupts_double(sync_checkpointer: BaseCheckpointSaver) -> None:
# --- CHILD GRAPH ---
answered: Counter[str] = Counter()
class ChildState(BaseModel):
prompt: str = Field(..., description="What is going to be asked to the user?")
human_input: str | None = Field(None, description="What the human said")
@@ -7746,6 +7748,7 @@ def test_parallel_interrupts_double(sync_checkpointer: BaseCheckpointSaver) -> N
def get_human_input(state: ChildState):
human_input = interrupt(state.prompt)
answered["human"] += 1
return dict(
human_inputs=[human_input], # update parent state
@@ -7753,6 +7756,7 @@ def test_parallel_interrupts_double(sync_checkpointer: BaseCheckpointSaver) -> N
def get_dolphin_input(state: ChildState):
human_input = interrupt(state.prompt)
answered["dolphin"] += 1
return dict(
human_inputs=[human_input], # update parent state
@@ -7846,6 +7850,7 @@ def test_parallel_interrupts_double(sync_checkpointer: BaseCheckpointSaver) -> N
assert invokes == 5
assert len(events) == 5
assert answered == {"human": 2, "dolphin": 2}
def test_pregel_loop_refcount():
+25 -2
View File
@@ -2,6 +2,7 @@ import operator
import sys
from collections.abc import Callable
from contextlib import contextmanager
from threading import Event
from typing import Annotated, Any
import pytest
@@ -72,12 +73,15 @@ def _graph(
ask: bool = False,
fail: set[str] | None = None,
calls: dict[str, int] | None = None,
before_fail: Callable[[], None] | None = None,
) -> Any:
def note(name: str):
def node(state: State) -> dict:
if calls is not None:
calls[name] = calls.get(name, 0) + 1
if fail is not None and name in fail:
if before_fail is not None:
before_fail()
fail.discard(name)
raise ValueError(f"{name} failed")
return {"notes": [name]}
@@ -234,18 +238,37 @@ def test_keep_latest_keeps_a_pending_interrupt_resumable(
@pytest.mark.parametrize("make_checkpointer", SYNC)
def test_keep_latest_keeps_writes_of_tasks_that_finished_before_a_failure(
make_checkpointer: Any,
make_checkpointer: Any, monkeypatch: pytest.MonkeyPatch
) -> None:
with make_checkpointer() as checkpointer:
calls: dict[str, int] = {}
fail: set[str] = set()
graph = _graph(checkpointer, fail=fail, calls=calls)
left_persisted = Event()
put_writes = checkpointer.put_writes
def persist(config, writes, task_id, task_path=""):
put_writes(config, writes, task_id, task_path)
if ("notes", ["left"]) in writes:
left_persisted.set()
def wait_for_left() -> None:
assert left_persisted.wait(5), "left task writes were not persisted"
monkeypatch.setattr(checkpointer, "put_writes", persist)
graph = _graph(checkpointer, fail=fail, calls=calls, before_fail=wait_for_left)
for thread_id in ("pruned", "twin"):
for i in range(2):
graph.invoke(_say(i), _config(thread_id))
left_persisted.clear()
fail.add("right")
with pytest.raises(ValueError, match="right failed"):
graph.invoke(_say(2), _config(thread_id))
checkpoint = checkpointer.get_tuple(_config(thread_id))
assert checkpoint is not None
assert any(
channel == "notes" and value == ["left"]
for _, channel, value in checkpoint.pending_writes
)
before = graph.get_state(_config("pruned"))
checkpointer.prune(["pruned"])
+37
View File
@@ -1177,6 +1177,43 @@ def test_subgraph_interrupt_resume_with_explicit_head_checkpoint_id(
assert result["value"] == ["sub_a", "human:answer", "sub_b"]
def test_subgraph_replay_reasks_answered_parallel_interrupt(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
"""Replaying a subgraph checkpoint asks its step's questions again, including
one already answered. An update checkpoint replays in place instead of
forking, so its tasks keep their ids and its saved answers could be reused."""
subgraph = (
StateGraph(State)
.add_node("a", lambda s: {"value": [f"a:{interrupt('a')}"]})
.add_node("b", lambda s: {"value": [f"b:{interrupt('b')}"]})
.add_edge(START, "a")
.add_edge(START, "b")
.compile(checkpointer=True)
)
graph = (
StateGraph(State)
.add_node("subgraph_node", subgraph)
.add_edge(START, "subgraph_node")
.compile(checkpointer=sync_checkpointer)
)
config = {"configurable": {"thread_id": "1"}}
def subgraph_head() -> RunnableConfig:
return graph.get_state(config, subgraphs=True).tasks[0].state.config
graph.invoke({"value": []}, config)
graph.update_state(subgraph_head(), {"value": ["u"]}, as_node="__start__")
result = graph.invoke(None, config)
question_a = next(i for i in result["__interrupt__"] if i.value == "a")
graph.invoke(Command(resume={question_a.id: "yes"}), config)
result = graph.invoke(None, subgraph_head())
assert sorted(i.value for i in result["__interrupt__"]) == ["a", "b"]
def test_subgraph_replay_loads_accumulated_state_then_resume(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
+4 -4
View File
@@ -1451,7 +1451,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "1.2.15"
version = "1.2.16"
source = { editable = "." }
dependencies = [
{ name = "langchain-core" },
@@ -1835,7 +1835,7 @@ test = [
[[package]]
name = "langgraph-prebuilt"
version = "1.1.0"
version = "1.1.1"
source = { editable = "../prebuilt" }
dependencies = [
{ name = "langchain-core" },
@@ -1856,7 +1856,7 @@ dev = [
{ name = "langgraph-checkpoint", editable = "../checkpoint" },
{ name = "langgraph-checkpoint-postgres", editable = "../checkpoint-postgres" },
{ name = "langgraph-checkpoint-sqlite", editable = "../checkpoint-sqlite" },
{ name = "psycopg-binary" },
{ name = "psycopg", extras = ["binary"] },
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "pytest-mock" },
@@ -1876,7 +1876,7 @@ test = [
{ name = "langgraph-checkpoint", editable = "../checkpoint" },
{ name = "langgraph-checkpoint-postgres", editable = "../checkpoint-postgres" },
{ name = "langgraph-checkpoint-sqlite", editable = "../checkpoint-sqlite" },
{ name = "psycopg-binary" },
{ name = "psycopg", extras = ["binary"] },
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "pytest-mock" },
+2 -2
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "langgraph-prebuilt"
version = "1.1.0"
version = "1.1.1"
description = "Library with high-level APIs for creating and executing LangGraph agents and tools."
authors = []
requires-python = ">=3.10"
@@ -46,7 +46,7 @@ test = [
"langgraph-checkpoint-sqlite",
"langgraph-checkpoint-postgres",
"syrupy",
"psycopg-binary",
"psycopg[binary]",
]
lint = [
"ruff",
+128 -112
View File
@@ -253,7 +253,7 @@ wheels = [
[[package]]
name = "langchain-core"
version = "1.6.1"
version = "1.6.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx" },
@@ -267,9 +267,9 @@ dependencies = [
{ name = "typing-extensions" },
{ name = "uuid-utils" },
]
sdist = { url = "https://files.pythonhosted.org/packages/90/12/aff76ca89c219ebe6f9dd3c5dbc4e3b1cf5450e9fc7037dccad23d45cd7a/langchain_core-1.6.1.tar.gz", hash = "sha256:1b156cb395aac4f009a8a1b38a574c7d948fe2d5f74c96e0d8a5017b4149e04f", size = 1003359, upload-time = "2026-08-27T19:31:14.956Z" }
sdist = { url = "https://files.pythonhosted.org/packages/ed/ed/ceecd3bfcfc77a94cf30a9f1506e907fdea447458825e01914a72d2d1a6f/langchain_core-1.6.5.tar.gz", hash = "sha256:bdb059fef65473fd444558dbb18f50901657402c43ceed9dc0fdc7b76a393c6b", size = 1007041, upload-time = "2026-09-24T18:11:06.487Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8e/25/f50dd65673c819aa33d3c34df58c115dbb6ec627d19f93e6e401dd0fc8d7/langchain_core-1.6.1-py3-none-any.whl", hash = "sha256:954a84132a5cb0435d27b910e336347b6744ecc18fbeef1e2de7029a0959841a", size = 571478, upload-time = "2026-08-27T19:31:13.34Z" },
{ url = "https://files.pythonhosted.org/packages/59/68/447ac7e734990b7ed9dcfc3b6dee9a4cd4d4169e296429fa71a9a47c1c7b/langchain_core-1.6.5-py3-none-any.whl", hash = "sha256:54c7b0e9314b9084fb04405bb33dc5d32986d512d92b7b8863899cd5f6267556", size = 572136, upload-time = "2026-09-24T18:11:04.837Z" },
]
[[package]]
@@ -286,7 +286,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "1.2.15"
version = "1.2.16"
source = { editable = "../langgraph" }
dependencies = [
{ name = "langchain-core" },
@@ -512,7 +512,7 @@ test = [
[[package]]
name = "langgraph-prebuilt"
version = "1.1.0"
version = "1.1.1"
source = { editable = "." }
dependencies = [
{ name = "langchain-core" },
@@ -527,7 +527,7 @@ dev = [
{ name = "langgraph-checkpoint" },
{ name = "langgraph-checkpoint-postgres" },
{ name = "langgraph-checkpoint-sqlite" },
{ name = "psycopg-binary" },
{ name = "psycopg", extra = ["binary"] },
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "pytest-mock" },
@@ -547,7 +547,7 @@ test = [
{ name = "langgraph-checkpoint" },
{ name = "langgraph-checkpoint-postgres" },
{ name = "langgraph-checkpoint-sqlite" },
{ name = "psycopg-binary" },
{ name = "psycopg", extra = ["binary"] },
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "pytest-mock" },
@@ -569,7 +569,7 @@ dev = [
{ name = "langgraph-checkpoint", editable = "../checkpoint" },
{ name = "langgraph-checkpoint-postgres", editable = "../checkpoint-postgres" },
{ name = "langgraph-checkpoint-sqlite", editable = "../checkpoint-sqlite" },
{ name = "psycopg-binary" },
{ name = "psycopg", extras = ["binary"] },
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "pytest-mock" },
@@ -589,7 +589,7 @@ test = [
{ name = "langgraph-checkpoint", editable = "../checkpoint" },
{ name = "langgraph-checkpoint-postgres", editable = "../checkpoint-postgres" },
{ name = "langgraph-checkpoint-sqlite", editable = "../checkpoint-sqlite" },
{ name = "psycopg-binary" },
{ name = "psycopg", extras = ["binary"] },
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "pytest-mock" },
@@ -821,77 +821,93 @@ wheels = [
[[package]]
name = "psycopg"
version = "3.3.4"
version = "3.3.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
{ name = "tzdata", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/db/2f/cb91e5502ec9de1de6f1b76cfbf69531932725361168bb06963620c77e2e/psycopg-3.3.4.tar.gz", hash = "sha256:e21207764952cff81b6b8bdacad9a3939f2793367fdac2987b3aac36a651b5bc", size = 165799, upload-time = "2026-05-01T23:31:55.179Z" }
sdist = { url = "https://files.pythonhosted.org/packages/76/26/3ea4ca5eaea1c0debcdf7ee7c1613fbe721dc27a03c461c0817ffd8a0601/psycopg-3.3.6.tar.gz", hash = "sha256:c081f2250df751a943036e42db6df4571c66cd0aabe8291a7a506512b12007d2", size = 168171, upload-time = "2026-09-18T13:22:55.152Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5c/e0/7b3dee031daae7743609ce3c746565d4a3ed7c2c186479eb48e34e838c64/psycopg-3.3.4-py3-none-any.whl", hash = "sha256:b6bbc25ccf05c8fad3b061d9db2ef0909a555171b84b07f29458a447253d679a", size = 213001, upload-time = "2026-05-01T23:20:50.816Z" },
{ url = "https://files.pythonhosted.org/packages/4e/de/748bd7609c71cae5d737f0ba9192f19329f70180ecda8fff3cac02c5abe3/psycopg-3.3.6-py3-none-any.whl", hash = "sha256:a1db9f7148b06a28606767efaca51fa6f9398c5c0a3810519be69d7000bdb631", size = 215490, upload-time = "2026-09-18T13:15:29.374Z" },
]
[package.optional-dependencies]
binary = [
{ name = "psycopg-binary", marker = "implementation_name != 'pypy'" },
]
[[package]]
name = "psycopg-binary"
version = "3.3.4"
version = "3.3.6"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/bf/70d8a60488f9955cbbcd538beae44d56bb2f1d19e673b72788f2d343ff55/psycopg_binary-3.3.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b7bfff1ca23732b488cbca3076fc11bc98d520ee122514fdb17a8e20d3338f5a", size = 4609750, upload-time = "2026-05-01T23:24:20.06Z" },
{ url = "https://files.pythonhosted.org/packages/db/b0/29e98ba210c9dbc75a6dc91e3f99b9e06ea901a62ca95804e02a1ae13e6b/psycopg_binary-3.3.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32a6fbf8481e3a370d0d72b860d35948a693cb01281da217f7b2f307636e591a", size = 4676700, upload-time = "2026-05-01T23:25:21.727Z" },
{ url = "https://files.pythonhosted.org/packages/8e/ab/3df087b3c12bf74e47c08204172b2fabb5a144679110d5c7ad12d9201323/psycopg_binary-3.3.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bdef84570ebbce1d42b4e7ea952d21c414c5f118ad02fee00c5625f35e134429", size = 5496319, upload-time = "2026-05-01T23:25:28.271Z" },
{ url = "https://files.pythonhosted.org/packages/87/9a/f088207b4cd6772f9e0d8a91807e79fa2458d4eb9eb1ae406c68415f2bec/psycopg_binary-3.3.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fa1cbc10768a796c96d3243656016bf4e337c81c71097270bb7b0ad6210d9765", size = 5171906, upload-time = "2026-05-01T23:25:34.004Z" },
{ url = "https://files.pythonhosted.org/packages/48/45/4523a857f253871d75c22e1c2e79fd47e599e736bcba1bad58d83e24be02/psycopg_binary-3.3.4-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cf7f73a4a792bc5db58a4b385d8a1467e8d468f7548702fb0ed1e9b7501b1c13", size = 6762621, upload-time = "2026-05-01T23:25:41.392Z" },
{ url = "https://files.pythonhosted.org/packages/7c/d1/925bf776503345bef428e6c45fb017d0139ddbe0e211814b585c4253dca8/psycopg_binary-3.3.4-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d7b4d40c153fa352ab3cca530f3a0baedf7621b2ebcbd7f084009522c21788fc", size = 5006319, upload-time = "2026-05-01T23:25:51.419Z" },
{ url = "https://files.pythonhosted.org/packages/6f/aa/99727337206fbba357ca084bf4ea8b29dc986f61842a2685859af61416db/psycopg_binary-3.3.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f9b1c2533af01cd7648378599f82b0b8ae32f293296e6eec5753a625bc97ef28", size = 4535388, upload-time = "2026-05-01T23:25:57.957Z" },
{ url = "https://files.pythonhosted.org/packages/0b/a4/567ba2c37d19d8c2f63d836385dfd2495aa5897bbee6cfab104d9ee58624/psycopg_binary-3.3.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ad3bc94054876155549fdaedf4a46d1ec69d39a5bcee377148afe498e84c4b8e", size = 4224544, upload-time = "2026-05-01T23:26:03.832Z" },
{ url = "https://files.pythonhosted.org/packages/b7/23/86457f5a82731685d7701de7bfaa5eb783dd1fecbf875321897d9d9ce33a/psycopg_binary-3.3.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:eb4eed2079c01a4850bf467deacfab56d356d4225040170af03dc9958321242d", size = 3956282, upload-time = "2026-05-01T23:26:09.983Z" },
{ url = "https://files.pythonhosted.org/packages/a7/d8/249456df16d47de082abd9b73bce8ccdeb0293eb12e590f9150c7cbdb788/psycopg_binary-3.3.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f80e3f2b5331dbbf0901bcb658056c03eeb2c1ef31d774afb0d61598b242e744", size = 4261736, upload-time = "2026-05-01T23:26:16.798Z" },
{ url = "https://files.pythonhosted.org/packages/15/6b/c4abe228acafd8a385c1fb615d4f1e3c9b8ad7a4e4f0e84118ba3ffeed9c/psycopg_binary-3.3.4-cp310-cp310-win_amd64.whl", hash = "sha256:574ea21a9651958f1535c5a1c649c7409e9168bcbffa29a3f2f961f58b322949", size = 3570620, upload-time = "2026-05-01T23:26:22.655Z" },
{ url = "https://files.pythonhosted.org/packages/b6/82/df3312c0ca083d5b43b352f27d4dd8b1e614bd334473074715d9e0000da4/psycopg_binary-3.3.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:612a627d733f695b1de1f9b4bd511c15f999a5d8b915d444bbd7dd71cf3370da", size = 4609813, upload-time = "2026-05-01T23:26:30.612Z" },
{ url = "https://files.pythonhosted.org/packages/1f/b5/d74d542458d3e8ac0571d8a88f57ca369999b9a82f4fa528052d0d7d3e4c/psycopg_binary-3.3.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:13a7f380824c35896dcac7fe0f61440f7ca49d6dc73f3c13a9a4471e6a3b302e", size = 4676799, upload-time = "2026-05-01T23:26:38.475Z" },
{ url = "https://files.pythonhosted.org/packages/09/67/06bab9c60671999f4c6ceff1b334f3ac1f9fc5789eb467c714623ea21de9/psycopg_binary-3.3.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:276904e3452d6a23d474ef9a21eee19f20eed3d53ddd2576af033827e0ba0992", size = 5497050, upload-time = "2026-05-01T23:26:47.061Z" },
{ url = "https://files.pythonhosted.org/packages/72/9b/023433e2b20f970de1e22d29132a95281277646da0b2e2879dd4ee94b8c1/psycopg_binary-3.3.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ab8cca8ef8fb1ccf5b048ae5bd78ba55b9e4b5d472e3ce5ca39ff4d2a9c249e4", size = 5172428, upload-time = "2026-05-01T23:26:56.708Z" },
{ url = "https://files.pythonhosted.org/packages/08/cd/ae16da8fde228a38b2fe9269bbc13cf89e0186173f2265600f02d6a71e64/psycopg_binary-3.3.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7465bfe6087d2d5b42d4c53b9b11ca9f218e477317a4a162a10e3c19e984ba8e", size = 6762746, upload-time = "2026-05-01T23:27:07.023Z" },
{ url = "https://files.pythonhosted.org/packages/4f/81/0ba09fa5f5f88779093a2541a8e02489825721f258ab88058b11d68b3eb5/psycopg_binary-3.3.4-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22cdbf5f91ef7bb91fe0c5757e1962d3127a8010256eefd9c61fcaf441802097", size = 5006033, upload-time = "2026-05-01T23:27:12.221Z" },
{ url = "https://files.pythonhosted.org/packages/73/6a/629136040cc3497adb442a305710b5913f2a754d4630fc3d3717c4c0df65/psycopg_binary-3.3.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e2631da29253a98bd496e6c4813b24e09a4fe3fb2a9e88513305d6f8747cce95", size = 4534175, upload-time = "2026-05-01T23:27:18.248Z" },
{ url = "https://files.pythonhosted.org/packages/7c/32/1027f843c6dc2d5d51960ee62cc0c2cf755a4c39455aff1371173edbef7d/psycopg_binary-3.3.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7f7668f30b9dd5163197e5cbf4e0efd54e00f0a859cc566ce56cfc31f4054839", size = 4224203, upload-time = "2026-05-01T23:27:24.3Z" },
{ url = "https://files.pythonhosted.org/packages/0b/e1/380a724d9093c74adb14d4fce920ea8327838abb61f760b1448586b14a8e/psycopg_binary-3.3.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:cffc3408d77a27973f33e5d909b624cce683db5fc25964b02fe0aae7886c1007", size = 3954509, upload-time = "2026-05-01T23:27:30.815Z" },
{ url = "https://files.pythonhosted.org/packages/db/cd/895893ae575a09c97ccfd5def070d88993d955ef34df45a881fd5ff506d6/psycopg_binary-3.3.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0579252a1202cd73e4da137a1426e2dae993ae44e757605344282af3a082848c", size = 4259551, upload-time = "2026-05-01T23:27:38.828Z" },
{ url = "https://files.pythonhosted.org/packages/dd/c6/2330a20794e37a3ec609ef2fd8522919ec7a4395a1abf979a8e2d1775cd5/psycopg_binary-3.3.4-cp311-cp311-win_amd64.whl", hash = "sha256:41f2ec0fea529832982bcb6c9415de3c86264ebe562b77a467c0fbcd7efbba8d", size = 3572054, upload-time = "2026-05-01T23:27:45.455Z" },
{ url = "https://files.pythonhosted.org/packages/95/7d/03818e13ba7f36de93573c93ee3482006d3dfa8b0f8d28df511bad0a1a92/psycopg_binary-3.3.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5ab28a2a7649df3b72e6b674b4c190e448e8e77cf496a65bd846472048de2089", size = 4591122, upload-time = "2026-05-01T23:27:56.162Z" },
{ url = "https://files.pythonhosted.org/packages/a5/b9/11b341edf8d54e2694726b273fe9652b254d989f4f63e3ac6816ad6b55f4/psycopg_binary-3.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6402a9d8146cf4b3974ded3fd28a971e83dc6a0333eb7822524a3aa20b546578", size = 4669943, upload-time = "2026-05-01T23:28:04.522Z" },
{ url = "https://files.pythonhosted.org/packages/8b/18/4665bacd65e7865b4372fcd8abb8b9186ada4b0025f8c2ca691b364a556c/psycopg_binary-3.3.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:580ae30a5f95ccd90008ec697d3ed6a4a2047a516407ad904283fa42086936e9", size = 5469697, upload-time = "2026-05-01T23:28:11.337Z" },
{ url = "https://files.pythonhosted.org/packages/7c/b1/b83136c6e510593d9b0c759ba5384337bc4ad82d19fda675adc4b2703c84/psycopg_binary-3.3.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7510c37550f91a187e3660a8cc50d4b760f8c3b8b2f89ebc5698cd2c7f2c85d", size = 5152995, upload-time = "2026-05-01T23:28:20.529Z" },
{ url = "https://files.pythonhosted.org/packages/67/8d/a9821e2a648afe6091989929982a3b0f00b2631a859cb81379728f08fb75/psycopg_binary-3.3.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77df19583501ea288eaf15ac0fe7ad01e6d8091a91d5c41df5c718f307d8e31b", size = 6738180, upload-time = "2026-05-01T23:28:30.654Z" },
{ url = "https://files.pythonhosted.org/packages/7e/58/2e349e8d23905dc2317b80ac65f48fb6f821a4777a4e994a60da91c4850f/psycopg_binary-3.3.4-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:018fbed325936da502feb546642c982dcc4b9ffdea32dfef78dbf3b7f7ad4070", size = 4978828, upload-time = "2026-05-01T23:28:37.277Z" },
{ url = "https://files.pythonhosted.org/packages/45/48/57b00d03b4721878326122a1f1e6b0a90b85bcaec56b5b2f8ea6cfa45235/psycopg_binary-3.3.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:17a21953a9e5ff3a16dab692625a3676e2f101db5e40072f39dbee2250194d68", size = 4509757, upload-time = "2026-05-01T23:28:43.078Z" },
{ url = "https://files.pythonhosted.org/packages/25/37/33b47d8c007df69aec500df5889767c4d313748e8e9e27a2fef8a6dabcee/psycopg_binary-3.3.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:eb05ee1c2b817d27c537333224c9e83c7afb86fe7296ba970990068baf819b16", size = 4190546, upload-time = "2026-05-01T23:28:50.016Z" },
{ url = "https://files.pythonhosted.org/packages/ca/c6/32b0835dbc2122617902b649d76a91c1e75406e76bf3d595b0c3bb5ffad6/psycopg_binary-3.3.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:773d573e11f437ce0bdb95b7c18dc58390494f96d43f8b45b9760436114f7652", size = 3926197, upload-time = "2026-05-01T23:28:55.55Z" },
{ url = "https://files.pythonhosted.org/packages/cd/68/d190ef0c0c5b16ded07831dabc8ddd412f4cdab07ec6e30ed38d9bda0e1f/psycopg_binary-3.3.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:71e55ccbdfae79a2ed9c6369c3008a3025817ff9d7e27b32a2d84e2a4267e66e", size = 4236627, upload-time = "2026-05-01T23:29:05.336Z" },
{ url = "https://files.pythonhosted.org/packages/25/8f/81dcbc2e8454b74d14881275ea45f00791052dac531a9fa8be1730d1685b/psycopg_binary-3.3.4-cp312-cp312-win_amd64.whl", hash = "sha256:494ca54901be8cf9eb7e02c25b731f2317c378efa44f43e8f9bd0e1184ae7be4", size = 3560782, upload-time = "2026-05-01T23:29:11.967Z" },
{ url = "https://files.pythonhosted.org/packages/09/43/13e9c406fbbf354580476e248a16b64802a376873ebe6339e30bb655572d/psycopg_binary-3.3.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fbd1d4ed566895ad2d3bf4ddfd8bae90026930ddf29df3b9d91d32c8c47866a7", size = 4590377, upload-time = "2026-05-01T23:29:18.782Z" },
{ url = "https://files.pythonhosted.org/packages/22/be/2923cd7c3683e7afdecf4f10796a18de02f5c5ddc0969aa2ad0a8cdd3bbd/psycopg_binary-3.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:75a9067e236f9b9ae3535b66fe99bddb33d39c0de10112e49b9ab11eee53dc31", size = 4669023, upload-time = "2026-05-01T23:29:25.884Z" },
{ url = "https://files.pythonhosted.org/packages/96/a0/2c913d6fe13d6a8bd13597d36739bf47af063ad9399e402cfecab16f3c1e/psycopg_binary-3.3.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:b56b603ebcea8aa10b46228b8410ba7f13e7c2ee54389d4d9be0927fd8ce2a70", size = 5467423, upload-time = "2026-05-01T23:29:33.416Z" },
{ url = "https://files.pythonhosted.org/packages/e7/38/205d10bc1ad0df4a21c5c51659126bd3ea0ef98fcad1e852f78c249bb9c3/psycopg_binary-3.3.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c677c4ad433cb7150c8cd304a0769ae3bcfbe5ea0676eb53faa7b1443b16d0d3", size = 5151137, upload-time = "2026-05-01T23:29:42.013Z" },
{ url = "https://files.pythonhosted.org/packages/36/fc/f0381ddcd45eff3bb70dbca6823a996048d7f507b2ec3fc92c6fabc0fe87/psycopg_binary-3.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:26df2717e59c0473e4465a97dfb1b7afebaa479277870fd5784d1436470db47c", size = 6736671, upload-time = "2026-05-01T23:29:51.626Z" },
{ url = "https://files.pythonhosted.org/packages/95/40/fa545ae152c24327651e5624e4902121e808270be36c10b12e9939be09bc/psycopg_binary-3.3.4-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1dc1f79fd16bb1f3f4421417a514607539f17804d95c7ed617265369d1981cae", size = 4979601, upload-time = "2026-05-01T23:29:56.961Z" },
{ url = "https://files.pythonhosted.org/packages/86/e4/2f8a47ee97f90cd2b933d0463081d35631ff419de2b8c984a5f369857de0/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:136f199a407b5348b9b857c504aff60c77622a28482e7195839ce1b51238c4cc", size = 4510513, upload-time = "2026-05-01T23:30:07.243Z" },
{ url = "https://files.pythonhosted.org/packages/0e/0e/94e842ff4a7f98ed162580ca2e8b8864b28c1e0350f2443f8ee47f821167/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b6f5a29e9c775b9f12a1a717aa7a2c80f9e1db6f27ba44a5b59c80ac61d2ffcf", size = 4187243, upload-time = "2026-05-01T23:30:15.352Z" },
{ url = "https://files.pythonhosted.org/packages/d0/83/fc6c174b672e29b7de996ea77b6cbddf46c891751c3355f6974292baa6b4/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ee17a2cf4943cde261adfad1bbc5bf38d6b3776d7afff74c7cabcbeaeb08c260", size = 3927347, upload-time = "2026-05-01T23:30:21.186Z" },
{ url = "https://files.pythonhosted.org/packages/e9/65/768364d4a97a15b1a7f47ba52688c1686f22941d8332a8398cefc468e25f/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5c4ab71be17bdca30cb34c34c4e1496e2f5d6f20c199c12bad226070b22ef9bf", size = 4236393, upload-time = "2026-05-01T23:30:26.211Z" },
{ url = "https://files.pythonhosted.org/packages/bd/3b/218efbc9e645becd80cdf651acda05f85cfe546b7a9c0458c7cbc8fe1f74/psycopg_binary-3.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:dbfdb9b6cc79f31104a7b162a2b921b765fcc62af6c00540a167a8de47e4ed38", size = 3564592, upload-time = "2026-05-01T23:30:31.764Z" },
{ url = "https://files.pythonhosted.org/packages/48/a6/828c9185701dab71b234c2a76c38a08b098ebfec5020716b4e93807492b5/psycopg_binary-3.3.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:28b7398fdd19db3232c884fb24550bdfe951221f510e195e233299e4c9b78f97", size = 4607292, upload-time = "2026-05-01T23:30:38.962Z" },
{ url = "https://files.pythonhosted.org/packages/92/58/5b40dbc9d839045c9dae956960e4fb6d20bcabe6c59a2aa34fc3a371913f/psycopg_binary-3.3.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1fbaa292a3c8bb61b45df1ad3da1908ccee7cb889db9425e3557d9e34e2a4829", size = 4687023, upload-time = "2026-05-01T23:30:47.227Z" },
{ url = "https://files.pythonhosted.org/packages/85/a9/793f0ac107a9003b48441d0d1f9f616d96e0f37458dd8dc12528ceff55fb/psycopg_binary-3.3.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94596f9e7633ee3f6440711d43bb70aa31cc0a46a900ab8b4201a366ace5c9e7", size = 5486985, upload-time = "2026-05-01T23:30:55.517Z" },
{ url = "https://files.pythonhosted.org/packages/8f/26/42e8533497e2592334f68ec529cf5f840f7fa4e99575a4bb61aa184dbfbf/psycopg_binary-3.3.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8c0056529e68dbe9184cd4019a1f3d8f3a4ead2f6fc7a5afcf27d3314edd1277", size = 5168745, upload-time = "2026-05-01T23:31:01.904Z" },
{ url = "https://files.pythonhosted.org/packages/15/af/b7151776cc08d5935d45c833ec818a9beb417cf7c08239af1aafbdae78ee/psycopg_binary-3.3.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c09aad7051326e7603c14e50636db9c01f78272dc54b3accff03d46370461e6", size = 6761486, upload-time = "2026-05-01T23:31:14.511Z" },
{ url = "https://files.pythonhosted.org/packages/d0/ed/c92533b9124712d592cbf1cd6c76da933a2e0acea81dfe1fbe7e735f0cff/psycopg_binary-3.3.4-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:514404ed543efd620c85602b747df2a23cf1241b4067199e1a66f2d2757aaa41", size = 4997427, upload-time = "2026-05-01T23:31:20.901Z" },
{ url = "https://files.pythonhosted.org/packages/a2/23/ccadfd0de416aa188356daa199453af24087b042e296088706d190ae0295/psycopg_binary-3.3.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:46893c26858be12cc49ca4226ed6a60b4bfccadd946b3bebb783a60b38788228", size = 4533549, upload-time = "2026-05-01T23:31:26.204Z" },
{ url = "https://files.pythonhosted.org/packages/fd/a0/c8f43cee36386f7bc891ab41a9d31ea07cf9826038e732da79f26b1e5f34/psycopg_binary-3.3.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:df1d567fc430f6df15c9fcf67d87685fc49bdb325adc0db5af1adfb2f44eb5c9", size = 4210256, upload-time = "2026-05-01T23:31:33.884Z" },
{ url = "https://files.pythonhosted.org/packages/4e/2c/c1547871be3790676e8868b38655496422f94f0978dfb66b74bdba2f1676/psycopg_binary-3.3.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:6b9016b1714da4dd5ecaaa75b82098aa5a0b87854ce9b092e21c27c4ae23e014", size = 3946204, upload-time = "2026-05-01T23:31:39.626Z" },
{ url = "https://files.pythonhosted.org/packages/c4/b1/f6670f00fa7ea601584623f6c11602ab92117d83eaff885e0210f6de7418/psycopg_binary-3.3.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:47c656a8a7ba6eb0cff1801a4caaa9c8bdc12d03080e273aff1c8ac39971a77e", size = 4255811, upload-time = "2026-05-01T23:31:44.986Z" },
{ url = "https://files.pythonhosted.org/packages/eb/e6/5fff07a70d1f945ed90ae131c3bd76cab32beff7c58c6db15ad5820b6d1f/psycopg_binary-3.3.4-cp314-cp314-win_amd64.whl", hash = "sha256:c37e024c07308cd06cf3ec51bfd0e7f6157585a4d84d1bce4a7f5f7913719bf8", size = 3666849, upload-time = "2026-05-01T23:31:51.165Z" },
{ url = "https://files.pythonhosted.org/packages/52/92/00350a66de0af05e41d01aa3134e3970045e816afed3f99d58ec1abe15b2/psycopg_binary-3.3.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7beb3e41c9a1e509f3ed85263386588cbe3e975aa67be21f79f44fd35ffaeefc", size = 4728682, upload-time = "2026-09-18T13:15:36.605Z" },
{ url = "https://files.pythonhosted.org/packages/91/fc/afa9c7fd316a469af7ede6ebb020eac482f5d827fae57d5310c9bc0c41ae/psycopg_binary-3.3.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa73160077345ec21b3f51e8e24b3de2e99586217e497629326eb9b2ea88c52e", size = 4777111, upload-time = "2026-09-18T13:15:46.566Z" },
{ url = "https://files.pythonhosted.org/packages/f2/44/7c1e015f1bc56b36ff1369f09e852b2d83ccefd5a669a42633a916cdedc4/psycopg_binary-3.3.6-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f87dbdc42e78ee0f7ea180c03f8c78e80a949e373066629bd90fefff10552dff", size = 5592755, upload-time = "2026-09-18T13:15:52.886Z" },
{ url = "https://files.pythonhosted.org/packages/3b/ae/314a251ca918cdac380bce1b87839ade9355382ea749e6ef3ba75ba0c09f/psycopg_binary-3.3.6-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a9348c5b43a3bb5ef8c2e89d5237c9c87eeafb01d338c84a7aebbc5cd0313299", size = 5267949, upload-time = "2026-09-18T13:16:00.53Z" },
{ url = "https://files.pythonhosted.org/packages/b6/9f/3bb0cfe9bb0f31ca57cf486ddc8c9ac51251aed8181bf88ff870b2623105/psycopg_binary-3.3.6-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a52991594ac4db888c7d39bccef331797e30cb31a95cae02cf2607f83a42dc2", size = 6862403, upload-time = "2026-09-18T13:16:10.385Z" },
{ url = "https://files.pythonhosted.org/packages/c4/d6/7032c10309c3155e9b24300fdcc9a1afa539cfd20ce52fdef74a46f10161/psycopg_binary-3.3.6-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5ea8beeb5541780b4b50b462eeacbc4f594ce3b911dc20c81c75f267876f71d2", size = 5107271, upload-time = "2026-09-18T13:16:16.843Z" },
{ url = "https://files.pythonhosted.org/packages/61/cc/79add2cf92684cf1a81da134b32caa662c25c72d0cc905d181ef4455f834/psycopg_binary-3.3.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:198a48e68cc99ccac03ba95ac857e73aa66f3bf6be77019fafb0832a05f7ad03", size = 4627984, upload-time = "2026-09-18T13:16:23.889Z" },
{ url = "https://files.pythonhosted.org/packages/c9/48/6dfb14f9350c14af6a2edb3c31262051b8cd94e2186e4b831e46dbbe8cd9/psycopg_binary-3.3.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fa34eb47969297471db7b7f193622c7e3ee839ec05abd05f1fe104d5b1b1dcf4", size = 4322054, upload-time = "2026-09-18T13:16:29.33Z" },
{ url = "https://files.pythonhosted.org/packages/29/35/2982338716a91cbb4dfc866be015be4457ee8106a445aabf3d1fb6a270e0/psycopg_binary-3.3.6-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:b979a42815410432420275412633960807178b1ce26591a16ce06e78a5bd4bb2", size = 4048471, upload-time = "2026-09-18T13:16:34.119Z" },
{ url = "https://files.pythonhosted.org/packages/24/e1/171b1db1542c5f76a678b7ee0a7800bebc9735a0a03417c76cf948bfd63c/psycopg_binary-3.3.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:889e42acec10450185e0cdfb396f375e2c1a8d7737c114830a7fde4654f59e30", size = 4353931, upload-time = "2026-09-18T13:16:38.692Z" },
{ url = "https://files.pythonhosted.org/packages/08/89/4424e62a944eef40bd9326ada4ae23802b28eab6502af91e84ef7bba74fb/psycopg_binary-3.3.6-cp310-cp310-win_amd64.whl", hash = "sha256:cbd5f73073ed19c378d4c35499db1e3e703a5b1a324e521204065967bfaa7a18", size = 3674362, upload-time = "2026-09-18T13:16:44.454Z" },
{ url = "https://files.pythonhosted.org/packages/70/86/b71166048974d49c6d136b2ed1c0e5bec0b974d8c4de5cbce7e86a9e412a/psycopg_binary-3.3.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:be4f9b3c9338ac5dd217c5847e21521b396c8117f78dc420d495a5c49bbef874", size = 4728002, upload-time = "2026-09-18T13:16:53.393Z" },
{ url = "https://files.pythonhosted.org/packages/12/1d/1e06c0de7ed5aed898acb87544eac6ef0bc7d752a67ec6e5d6b835e9b40c/psycopg_binary-3.3.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f0535693ce476a722b718b002d5d2c27d47e71ca945276ac194409c98e74c492", size = 4775403, upload-time = "2026-09-18T13:16:58.939Z" },
{ url = "https://files.pythonhosted.org/packages/84/02/2ffcbc43f8e4bbc38e5286a22013bcac01898d13cd38325f60dd5428a8af/psycopg_binary-3.3.6-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3c9e663b2e800e3218994cf948c11bcc2844e6491b34aa80d089baf6531827bf", size = 5594145, upload-time = "2026-09-18T13:17:08.515Z" },
{ url = "https://files.pythonhosted.org/packages/e1/25/031dae2c7d2e7e77dcf5b1962c1e0684fa548d7af0ff6707b6b5e6054ca7/psycopg_binary-3.3.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a2e44a342d2aee40508e28a563d8961c39d9bbd8cae36d8578f0a3c6658aab0f", size = 5267985, upload-time = "2026-09-18T13:17:16.24Z" },
{ url = "https://files.pythonhosted.org/packages/8c/e5/94c89ada3c003a4d858178f3bba49a35e0297ef2aad659b80eb5e380e690/psycopg_binary-3.3.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f598f19fa9a91540b5cee17932ffd227b7b53a481605bcc4573c0eafa647300", size = 6860986, upload-time = "2026-09-18T13:17:23.348Z" },
{ url = "https://files.pythonhosted.org/packages/9d/a0/81bf499d095adee8413bd19822a6872fbfa21663ec78014a68d83a8db83c/psycopg_binary-3.3.6-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6ff05561e4a067d35507dc5c90f1deb2ec1c9703ac5cccc1bc26e08a197f9c5a", size = 5106702, upload-time = "2026-09-18T13:17:28.847Z" },
{ url = "https://files.pythonhosted.org/packages/00/75/99d56da64c27bd985fd82c6ecbf7976b724ac638fdd1654ef995323a1a26/psycopg_binary-3.3.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:566dd827f17728efdf7d88a5b066f815170f6fdad13967ae952842d90e6aaa9f", size = 4627090, upload-time = "2026-09-18T13:17:36.668Z" },
{ url = "https://files.pythonhosted.org/packages/3e/0c/0222171d11233332c6a24b1cef1578215f0ffddf3642eb8dd8c4448ad69f/psycopg_binary-3.3.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9b2f11794e017ce340934e35de46181c46ef71ec75ea3d85dd75cd836761c01e", size = 4320353, upload-time = "2026-09-18T13:17:42.526Z" },
{ url = "https://files.pythonhosted.org/packages/62/6f/e1cc2a28dd1228c67c969ba6fd37cd8726b312e2ff51380f847ddb38ccde/psycopg_binary-3.3.6-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:910ace140e3e7b7596898d083f37a8fe90c5c40684252ad4e682364b2cd3deba", size = 4047253, upload-time = "2026-09-18T13:17:47.068Z" },
{ url = "https://files.pythonhosted.org/packages/d8/fd/38b64790ce7a515b1dbd2bab3d119637a858aeb22c380cf4859bc4ce0e42/psycopg_binary-3.3.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37e517c146b185f9c0c6e8d0a0ebbdeeeb67896af28466e032bc810d0c7dc7a7", size = 4353208, upload-time = "2026-09-18T13:17:52.41Z" },
{ url = "https://files.pythonhosted.org/packages/f7/dc/45386530ceb2a8c789a226de9b9b34eca8fccf1feba2e4ef68a6aca50c56/psycopg_binary-3.3.6-cp311-cp311-win_amd64.whl", hash = "sha256:c7f92daa0d2a1c76f07264abddf8cbabd30152a2f09c3270e50f0c7efdf5dcac", size = 3675638, upload-time = "2026-09-18T13:17:58.112Z" },
{ url = "https://files.pythonhosted.org/packages/e6/01/2cdd1824e58b4467ee0b9498664cd28c42d8794db6b1e35b6bcb834f0044/psycopg_binary-3.3.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3f84dab25e0385692ee13274c68678377e0b1a70ab9d14e56264cbf61f60c62d", size = 4707086, upload-time = "2026-09-18T13:18:05.138Z" },
{ url = "https://files.pythonhosted.org/packages/f6/76/de9948ac06895261c84d5b9fbe283d8f3c5bc9f070691b8d9eaa1b51e322/psycopg_binary-3.3.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:612382ac3ed13651c7fa44b5fee9fbf7baaa2ddbc6f500391672682c5f1df9e0", size = 4769607, upload-time = "2026-09-18T13:18:12.83Z" },
{ url = "https://files.pythonhosted.org/packages/76/a9/72436c9915ee4905964689e7f0e182ce7767cc0a0390b3ce703be8177625/psycopg_binary-3.3.6-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:366db6e97e66b37211475f20c4c1324a2dc0dd825e46d4e87f9d599304d276f9", size = 5554134, upload-time = "2026-09-18T13:18:21.175Z" },
{ url = "https://files.pythonhosted.org/packages/0a/42/948bb3d2617795093512613fd96ba380e922992c7908fbc073858147d196/psycopg_binary-3.3.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1679a1cb93fbe5a6d1fd58d82cbddcc6fcb8c61446ba7cae6eb2a7b19bc585de", size = 5235723, upload-time = "2026-09-18T13:18:27.071Z" },
{ url = "https://files.pythonhosted.org/packages/99/47/93e823ff1b0088400703410939c9bda3e63ed9c850b3ee088e8769f4c10b/psycopg_binary-3.3.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:37d40450659401600e6d043ff586c89a71a69f33cbb8bcdba6cdb2569beecdbe", size = 6833587, upload-time = "2026-09-18T13:18:33.794Z" },
{ url = "https://files.pythonhosted.org/packages/5e/2d/ecc69c847795aa704041a9f5667a6b0938a088cf1853636d762a6938e493/psycopg_binary-3.3.6-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a5165300324efd5a772c48a88ab3a928513ab3979fca76553e62ee815f7b2b9c", size = 5070013, upload-time = "2026-09-18T13:18:39.628Z" },
{ url = "https://files.pythonhosted.org/packages/92/36/6126f0dac21713dcae91404f2a76da18598a6252339a8c669c46370d43b2/psycopg_binary-3.3.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d636338c8f21b0df2f84657b00bc34f9313f826ef93f1155bc743607e4a0c5eb", size = 4597367, upload-time = "2026-09-18T13:18:45.023Z" },
{ url = "https://files.pythonhosted.org/packages/4d/29/7ecfc04243b46c89ffd49924e9c5634ea904ef96c7d0f37e4073623584c1/psycopg_binary-3.3.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a4ee3bdd5468a725f2a4d9aab8a74b6d0279f768c8b5d3aeb102c5307ff3d59c", size = 4275419, upload-time = "2026-09-18T13:18:49.299Z" },
{ url = "https://files.pythonhosted.org/packages/6e/90/2f46d2e0de79706ac170df0a3637fe63c4498fc04f131f6049520b78b806/psycopg_binary-3.3.6-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:289aadd6a00e151203c081f708348ec89f1e483c9b510ef4ac3981f847f01f79", size = 4007358, upload-time = "2026-09-18T13:18:53.944Z" },
{ url = "https://files.pythonhosted.org/packages/03/48/6744e91291b751a8cf12d63d719977974bb94c84ceba913e7ddb2e478e51/psycopg_binary-3.3.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f21d057f3e5f5491067e5b292498073b73847d48799b099803fef100775fcc52", size = 4320156, upload-time = "2026-09-18T13:18:59.258Z" },
{ url = "https://files.pythonhosted.org/packages/1a/9b/94ff7fce53a64d5b286e2ec454e0a025cf3d6e6b4a9189bef16aa5de98b2/psycopg_binary-3.3.6-cp312-cp312-win_amd64.whl", hash = "sha256:e23a66a763fbe83fcc210bc77c27e5a5ea380ebf091c06f34d8561b695e5a40f", size = 3658864, upload-time = "2026-09-18T13:19:06.503Z" },
{ url = "https://files.pythonhosted.org/packages/b4/c3/c072584b69ad44a747b448cfc9766fecb8aae56e372a017e2ef668790057/psycopg_binary-3.3.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ad8f35e67cc16d1fad1fa8c88972dc9b3a3141ea67897399904edab96a301b6", size = 4712284, upload-time = "2026-09-18T13:19:13.451Z" },
{ url = "https://files.pythonhosted.org/packages/0a/b9/4283b785339e8e2318d03048994b093d650ea6289fabaa806b765dc0d449/psycopg_binary-3.3.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:373704aea331d3f3e3402c125a1543f5875e2986ebb54f97d1647942161f803f", size = 4772031, upload-time = "2026-09-18T13:19:18.524Z" },
{ url = "https://files.pythonhosted.org/packages/6f/72/7a1321d359246769fff1affffbd0132785a28f7f63c18524c15a502398f4/psycopg_binary-3.3.6-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:b82491019b884d62318b5f30706c3d7e6d4e5a6cb7eabcb3edc0c1b0fdaceae9", size = 5556392, upload-time = "2026-09-18T13:19:24.418Z" },
{ url = "https://files.pythonhosted.org/packages/de/b0/c6f8a0585a5dacbea74e130bcfc66629390e8f5bbc79d2a8e806e8952150/psycopg_binary-3.3.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cec5ea900390897d0b46130f60bc2883bf19c314f9044235217c8be88b0ef269", size = 5237855, upload-time = "2026-09-18T13:19:31.257Z" },
{ url = "https://files.pythonhosted.org/packages/e2/fc/c3a7a8bbef7e945ec584ac61d460a612363ea398511cd0e220242b1d69f1/psycopg_binary-3.3.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98c02090d88f2ebc0ec1e8da538f77d225ce0fffecf372aa39262e62a1b054ef", size = 6833856, upload-time = "2026-09-18T13:19:43.622Z" },
{ url = "https://files.pythonhosted.org/packages/a9/f2/8e80b921db728ebb68fc105bd7c4277f908210ad755bd6481d5ea7add740/psycopg_binary-3.3.6-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ee2c4728c691245e24501fcd7a97b5b381236b9985bc445bba88cdce7d1b5784", size = 5070730, upload-time = "2026-09-18T13:19:49.968Z" },
{ url = "https://files.pythonhosted.org/packages/54/6a/5b313e0c5348244f0e973aff3258bf86766656256d5ece8d541a53e35b4a/psycopg_binary-3.3.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f19cc87343eaa55255e76b31259a570072ac95d6ae82c92dd34b97691f5e49dc", size = 4598089, upload-time = "2026-09-18T13:19:56.426Z" },
{ url = "https://files.pythonhosted.org/packages/32/e9/db7f76ec24bf6699e92bf604e5c4bae10664a681a8999ef42aa0faf0f2c6/psycopg_binary-3.3.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdccb3a0e184b03e9baa673b15a809cf36c339c85dbda0ebc25a698846dfbee8", size = 4278481, upload-time = "2026-09-18T13:20:04.681Z" },
{ url = "https://files.pythonhosted.org/packages/61/83/72c67013656f4d6b547caabffb193e91d57e63f90eefdcc6d045c400e97d/psycopg_binary-3.3.6-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9892188bb15e5803beb51afe8a25add6b56be391a53058e8bca03b74e1e6bf22", size = 4009229, upload-time = "2026-09-18T13:20:11.905Z" },
{ url = "https://files.pythonhosted.org/packages/82/35/5e4500df2c999eb0faed8b184e6958b834172128274f06167a5deef4c19c/psycopg_binary-3.3.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3af90f92769d8cc10f94515ee7a0aef36ea85ca733a0ce22858f6e0953f41138", size = 4321467, upload-time = "2026-09-18T13:20:17.949Z" },
{ url = "https://files.pythonhosted.org/packages/55/7f/e350e1cf498ba2565c3f87b12f429d2012eb86b76c2b3845a19ee5fbb4d6/psycopg_binary-3.3.6-cp313-cp313-win_amd64.whl", hash = "sha256:0ebfad5d131de9f892ae9e70cc7616207768b6714b66a52d4612b8ceaf78b372", size = 3658179, upload-time = "2026-09-18T13:20:22.691Z" },
{ url = "https://files.pythonhosted.org/packages/6d/b9/60711317c284a442511644ea7185b56ebe627606d6741e732cd16108c47b/psycopg_binary-3.3.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:b3f75dee0f9afafabe4edc52c4842f1e1878ed2069bd05b22d6fe961e97e4dba", size = 4720512, upload-time = "2026-09-18T13:20:29.278Z" },
{ url = "https://files.pythonhosted.org/packages/63/da/28befc84454cbc6374550de7746f591f8fe1b6165c1fce249652cc8291c4/psycopg_binary-3.3.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5927b7ba63153cd8e9862987290a2b783a5c590daf2a4ef981700cc3569166d4", size = 4782318, upload-time = "2026-09-18T13:20:35.401Z" },
{ url = "https://files.pythonhosted.org/packages/a4/8a/0d21c2c833cdc0d4244c77e858e0ed37fa2abec2623be4fd686f617109ce/psycopg_binary-3.3.6-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:0bf08b749cc144f33b44a91b78e3f71c60eb07963746a0df5a100b36ce3d7475", size = 5567460, upload-time = "2026-09-18T13:20:41.902Z" },
{ url = "https://files.pythonhosted.org/packages/49/6d/7692d0d4e656b6cc9868d8acc2e3b42f17a0db4a625400a6d093cb0533a1/psycopg_binary-3.3.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:31cd942c23f613276b81a6e6598cefa12960058b0f46e1e874b540c793f6aca5", size = 5246902, upload-time = "2026-09-18T13:20:47.661Z" },
{ url = "https://files.pythonhosted.org/packages/d4/c1/b8a1f18fb1b7558a17f57f7cb3fc8bc93189feea2958925950b3acb15743/psycopg_binary-3.3.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4690cf67738f0e0e49a32aeec99bf0e4595cc2b4f1af984a4345394b1dcff91a", size = 6847192, upload-time = "2026-09-18T13:20:56.874Z" },
{ url = "https://files.pythonhosted.org/packages/a5/76/404f33519167c65cca88ec4998776f1dbebccc301ee977f0e62c47fb0826/psycopg_binary-3.3.6-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ad1c785e784cfd87e8436c6b7702f2d321fc39601bbaf29bc63a41a867091638", size = 5079573, upload-time = "2026-09-18T13:21:04.155Z" },
{ url = "https://files.pythonhosted.org/packages/f0/d9/79e8fbc8f37262a415f3550f0bcc5f98037442bf3d12ef6cbae2056655ae/psycopg_binary-3.3.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:79a2a1c3449f6c3409427078ed1cec10de79f3023cb5f2504f0597d350ad46c7", size = 4613633, upload-time = "2026-09-18T13:21:10.664Z" },
{ url = "https://files.pythonhosted.org/packages/d4/47/96225db74be7d2ce04b3a58678b53cda610225055edf5faa775c9f501d8b/psycopg_binary-3.3.6-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:86147cb5d140341c3363fb5bacce31f8d5543902a46699d3c536b101bbceaf9e", size = 4293375, upload-time = "2026-09-18T13:21:16.027Z" },
{ url = "https://files.pythonhosted.org/packages/2a/d2/18e9c779a5efd565250329adaf529ecc2b8b2ed5be5cb0f6ccee208cbfd9/psycopg_binary-3.3.6-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7308c93cf0b19bbaf8e6ff0a6ad50d3c442385739245fe15a8d593bf841734a6", size = 4019883, upload-time = "2026-09-18T13:21:21.587Z" },
{ url = "https://files.pythonhosted.org/packages/ef/28/0cc654afc6c2cda982767f5679d3646b30b1ec86545bdaa9402202d6776c/psycopg_binary-3.3.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:05a83ac9fd52b9bca7cb5ab04b3691163170bd16f53defa27216ea3aa07ee781", size = 4332607, upload-time = "2026-09-18T13:21:27.63Z" },
{ url = "https://files.pythonhosted.org/packages/f1/3e/0a753a74fbd7aef120f286c016e09d3cc3f1daf7688f4a145d27281260b2/psycopg_binary-3.3.6-cp314-cp314-win_amd64.whl", hash = "sha256:1fbd30e537dab22cafdf080608f10148fe2a5f3a61294ddb5113caac8a623840", size = 3755671, upload-time = "2026-09-18T13:21:33.855Z" },
{ url = "https://files.pythonhosted.org/packages/0e/b1/a372b9c02aea50148e71c9853e19efca8fa5ae2010a8e27243b9b8f790c0/psycopg_binary-3.3.6-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:bf8c8481d026b85dd70c5fa7dde85b2333aed0b32a2602bcd38a900cbd78a49c", size = 4719571, upload-time = "2026-09-18T13:21:41.437Z" },
{ url = "https://files.pythonhosted.org/packages/65/7c/811e3828c6b82e2f10c6c9cdd963cfc66f3e024026e5a69ac18530bad984/psycopg_binary-3.3.6-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:b599defe9190b17e9907c8b4d114c181e702c87efcd1b8a0ad40971cdcc4634a", size = 4781230, upload-time = "2026-09-18T13:21:49.516Z" },
{ url = "https://files.pythonhosted.org/packages/3e/15/9a784eed813ea9e97c294af3ead63d02b7b203502c66380336c50065e441/psycopg_binary-3.3.6-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:b8ece331509f7a975b90501f41e83ad905e4141753fedf3f2711b2bc70a8efbc", size = 5566111, upload-time = "2026-09-18T13:21:58.089Z" },
{ url = "https://files.pythonhosted.org/packages/68/16/47194e002007c27337b11e49bf459c4b19727463f9aff2e1a90917bcc806/psycopg_binary-3.3.6-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c61617eaae0112ca154da87ffb99b73af2c74067acac28dfb9a4455b019dff2e", size = 5249963, upload-time = "2026-09-18T13:22:06.695Z" },
{ url = "https://files.pythonhosted.org/packages/53/84/5dcf9f310b11f0675cd860c6b2c70f58ce61798a3ee3f6f962b53fa358ca/psycopg_binary-3.3.6-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c6d19cb4999d03231e8730a5f66c8f5068bc3b532677eb39dab0f600bff3e312", size = 6847925, upload-time = "2026-09-18T13:22:13.088Z" },
{ url = "https://files.pythonhosted.org/packages/f3/06/1957a06dc22963c418c27b284929579de84f29c37ad1abe6dc6ee9e8cf25/psycopg_binary-3.3.6-cp315-cp315-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8cbb54454dbf1bbf2ff08dd7693e8d94ac94b1a20f70f4b3b813d52ecb5cbc1", size = 5087720, upload-time = "2026-09-18T13:22:17.959Z" },
{ url = "https://files.pythonhosted.org/packages/21/43/ac07d042bae99b57bf123bb473632f29af544008094da0ffd285ab8011e2/psycopg_binary-3.3.6-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:dc75da5a20951049f7b773145f998f69d181adad9c58a0ff36e0cf1d73c10e10", size = 4613412, upload-time = "2026-09-18T13:22:26.719Z" },
{ url = "https://files.pythonhosted.org/packages/aa/b1/019156fbeafcefb4cccc9d109de4699493bceb8313c7545c8349e089dfbc/psycopg_binary-3.3.6-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:955e3dd94da361e052d2e49acf591017158dc8f8ed2c8a42c2e3943403c39dc2", size = 4292618, upload-time = "2026-09-18T13:22:33.042Z" },
{ url = "https://files.pythonhosted.org/packages/5d/0f/62113dc6b1df65983a1f2fc816c04b1edfa22f2ae9d4abee74ed267f4a96/psycopg_binary-3.3.6-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:c7753871eb57e6a5f4646f6168590c6653073dea5e9e720b201c8875332df4c8", size = 4027121, upload-time = "2026-09-18T13:22:38.334Z" },
{ url = "https://files.pythonhosted.org/packages/5d/d5/cf0cbd1ea5a7d8167fe2c6953efde19101f7b193bd61a23e6d622ad6854c/psycopg_binary-3.3.6-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:303732e798fe6729f8e12021b9c96107df8e95ecec4dd487c67b98ec2a59435e", size = 4336388, upload-time = "2026-09-18T13:22:45.576Z" },
{ url = "https://files.pythonhosted.org/packages/98/33/e2a5b36edf8aa422f6fa4b894756eb33dc93b36df5f65121280bb8b929c4/psycopg_binary-3.3.6-cp315-cp315-win_amd64.whl", hash = "sha256:2f122603f36050937982abf9668d8bc4769a79f7c93a65013b1c49f1cab7b56b", size = 3756154, upload-time = "2026-09-18T13:22:51.283Z" },
]
[[package]]
@@ -1080,14 +1096,14 @@ wheels = [
[[package]]
name = "pytest-mock"
version = "3.15.1"
version = "3.16.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pytest" },
]
sdist = { url = "https://files.pythonhosted.org/packages/68/14/eb014d26be205d38ad5ad20d9a80f7d201472e08167f0bb4361e251084a9/pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f", size = 34036, upload-time = "2025-09-16T16:37:27.081Z" }
sdist = { url = "https://files.pythonhosted.org/packages/7a/7f/6ed29931d5c8cd396e7c0a55412e6cc88020373365c8685985dea53d26d7/pytest_mock-3.16.0.tar.gz", hash = "sha256:5a8395528b8f498205f3718f575228d0edaed7425fff638f87d1a6c3e0383636", size = 35362, upload-time = "2026-09-27T14:57:55.46Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" },
{ url = "https://files.pythonhosted.org/packages/db/5b/b83a9bf1a3b4ec222f9fa083147ff6816245223da0ab92370e7e056f113f/pytest_mock-3.16.0-py3-none-any.whl", hash = "sha256:007cfeb257801d88d9c0b2a7b5a15a15e73b71968dfd72e7bf8c4a2f8393aec8", size = 10016, upload-time = "2026-09-27T14:57:54.283Z" },
]
[[package]]
@@ -1196,27 +1212,27 @@ wheels = [
[[package]]
name = "ruff"
version = "0.16.5"
version = "0.16.9"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f3/85/c8e12473c93018f92d19dd988a294202e1c27426c47ec4de53ffb847b8d8/ruff-0.16.5.tar.gz", hash = "sha256:1b88500f9ffbcab3dedb0082c9f9492e91ec3d618aac1236a3e0189938f7040b", size = 4912003, upload-time = "2026-08-27T16:34:18.258Z" }
sdist = { url = "https://files.pythonhosted.org/packages/96/bf/c935ca98e73fe8ce65b87ef08a280c0c1e85295d569228c15e87d8fdfaf1/ruff-0.16.9.tar.gz", hash = "sha256:12b625c6cfba78d285d9f48eda5f053374f1e53cb10ef17342a383750db99161", size = 4948764, upload-time = "2026-09-24T20:37:49.416Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c6/b6/77c90a970fe2dae17a723acbd011043ea97c98d7deacccefdc4ba74ec512/ruff-0.16.5-py3-none-linux_armv6l.whl", hash = "sha256:12e5f673e774c35fbb62f288809c7653b73445f8ecec6b6063fd6ea3521aa14b", size = 10011941, upload-time = "2026-08-27T16:33:41.287Z" },
{ url = "https://files.pythonhosted.org/packages/4b/46/6cf67cf6411885a1d6f7f6d801682f155536a85176d10b605e2ceffed8bd/ruff-0.16.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:eda58a5802de40e7ed5b32b64e0b32539338cc6fcd2c78f61e3ad6a0d79f51c3", size = 10204049, upload-time = "2026-08-27T16:33:44.056Z" },
{ url = "https://files.pythonhosted.org/packages/46/fd/c8720ca7a090abf0c2fef4abe8a5ef6e5127ed15196d8886ff75a2b370e2/ruff-0.16.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5ae9a7b9a8875131f40f8fe967cc86abf899779efd663cb7ce3d572d01da7eb", size = 9809037, upload-time = "2026-08-27T16:33:46.257Z" },
{ url = "https://files.pythonhosted.org/packages/43/45/a684caacdedaca180f52bacccc40bf0789d2c5a7c75f25324853e9eaedb5/ruff-0.16.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b719b0a1f4d59710d283ab2965f621684a108a9e41da622e3b23f0326cd0025", size = 9964129, upload-time = "2026-08-27T16:33:48.352Z" },
{ url = "https://files.pythonhosted.org/packages/9e/f2/5d2bcdaca6b5b93d1b4dfc166cd2aebf7680143a1b38a28759df13a94d31/ruff-0.16.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2298f2780ed1be0c5cb1361e32ab7b1467f3cce7dabe101d2210a314f2fe42e9", size = 9821518, upload-time = "2026-08-27T16:33:50.57Z" },
{ url = "https://files.pythonhosted.org/packages/aa/ff/011cce29accf9257d5974145b733fc653a37985ed6825413a3987cefbfe0/ruff-0.16.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:258f29035a2dd021e7861e631b227a5b3f14e50c1184c9a6a122c5f4576154d7", size = 10534835, upload-time = "2026-08-27T16:33:52.522Z" },
{ url = "https://files.pythonhosted.org/packages/d7/5a/f0cf109bada9bba0e96c90c21c9f9251803f57225c32d293327a03c710d6/ruff-0.16.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9a4f0432966834019c74d1b7e5c51224305d7713f3d7faf3e7451f1a3be3cde", size = 11252550, upload-time = "2026-08-27T16:33:54.521Z" },
{ url = "https://files.pythonhosted.org/packages/63/4d/1d481aaea2046c6a7ed7c291f9004c669cce3c087b6b376ed5b08271e3fe/ruff-0.16.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5eb3a8c3d0ade9cea42b591fd530368e8798380e30e0a308b85a5cf718f09ea", size = 10777949, upload-time = "2026-08-27T16:33:56.88Z" },
{ url = "https://files.pythonhosted.org/packages/ee/34/ee245ca55f64443233034b3d02b03236b19242004281247c079390b7facd/ruff-0.16.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef0f69e191a13a3c9816f63163c88790cb12cd157bbbb384e9c44745702ab105", size = 10311656, upload-time = "2026-08-27T16:33:59.12Z" },
{ url = "https://files.pythonhosted.org/packages/a7/4d/c33a333e341c0a2b96c715b52d89a606f5a34cd4ac493cd9b8d0187186b8/ruff-0.16.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:0eeab41fbea2c42f98dfb9822cdccda9d24ba38d49f6dc945b5c236d48f0ef29", size = 10532125, upload-time = "2026-08-27T16:34:01.166Z" },
{ url = "https://files.pythonhosted.org/packages/30/e1/a64cef78b40192497bb98a27a8aa8f2c98ee9ee15bc97f7712d94ef32937/ruff-0.16.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f0768e9df4300713fff30733c87575f68b6f1d8de41184e505b7fdd9c0c95eaf", size = 10097648, upload-time = "2026-08-27T16:34:03.16Z" },
{ url = "https://files.pythonhosted.org/packages/cc/4e/4cdc9ed3c3e109d2f71e62572a37457298d7bc7501ec3138babb7ed32bbd/ruff-0.16.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:95cc70cdc7aa80c338de356279d2adbeb2de0f520b9ecd8aba75b94e95e02f91", size = 9829344, upload-time = "2026-08-27T16:34:05.134Z" },
{ url = "https://files.pythonhosted.org/packages/39/4a/31ed35ce31729955fc583ee0d176d6e784c1290cb0b0a75cb2134c1ab72a/ruff-0.16.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d185c8398ded1bfd91c0c2cb258346307571eccc473a8490af8c3977399c384a", size = 10277117, upload-time = "2026-08-27T16:34:07.425Z" },
{ url = "https://files.pythonhosted.org/packages/a8/a0/60356d86687b4b666d593df213f4dc3041750d024cb7bf2cfa81cfd65c2e/ruff-0.16.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:fb8e3a3c4c6a784150a7ced53b015f4b253fc2bf97a610886419ead64b4756ef", size = 10711653, upload-time = "2026-08-27T16:34:09.712Z" },
{ url = "https://files.pythonhosted.org/packages/ed/20/656d67f5b25ca9bda4e02b1de25867b2954e1d19e03648060f167ad0f4cc/ruff-0.16.5-py3-none-win32.whl", hash = "sha256:288b0a5f080492fe5635db849f9e2e84aa3cce7b7f0e955997d416c507c76a26", size = 10034250, upload-time = "2026-08-27T16:34:11.8Z" },
{ url = "https://files.pythonhosted.org/packages/5b/42/ee8e68a207b9127fcde6c3d7e197def432f346cb1af159e1fa14ca0d1cdc/ruff-0.16.5-py3-none-win_amd64.whl", hash = "sha256:ddc6385fb2137f616357ca03d6c74f4be987f80fed4008566b754f6032b8546f", size = 10516714, upload-time = "2026-08-27T16:34:13.963Z" },
{ url = "https://files.pythonhosted.org/packages/73/e3/7df5a396e445b9ba49ce9a9437439a4d80042c61c0ade199abf8d16de1ac/ruff-0.16.5-py3-none-win_arm64.whl", hash = "sha256:a64abe90968719b851bb7cedffaa8753fbdbdadab483089682db623f3edc587e", size = 10391564, upload-time = "2026-08-27T16:34:16.064Z" },
{ url = "https://files.pythonhosted.org/packages/0d/26/df51322b52ee1ada7eff2d071ea09d11d5c2d1dcc9f02594f5785c4d1635/ruff-0.16.9-py3-none-linux_armv6l.whl", hash = "sha256:95e6f022090368ab3b824c36276839c53b2adf1a3f4c09fefc33dfc400f6da96", size = 10082922, upload-time = "2026-09-24T20:37:13.045Z" },
{ url = "https://files.pythonhosted.org/packages/a5/27/7bf51f5a7aa375e9f339280a303aab44ca75dc1525f1cdc5991761685b0f/ruff-0.16.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a5f27be168556594a86d2f415db0cf43f5291917849318f873c7e2791f7a8c67", size = 10236360, upload-time = "2026-09-24T20:37:16.049Z" },
{ url = "https://files.pythonhosted.org/packages/b6/63/09659283f92f02dff45809da194a70da2f688d87c55d8875c4fae3536072/ruff-0.16.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1632eb1d6197f33bd00b1acbc5b71009e89a8895c158e2d2b03a834fac964ab6", size = 9892940, upload-time = "2026-09-24T20:37:17.957Z" },
{ url = "https://files.pythonhosted.org/packages/24/58/98de1b72ec172f5f8f1731236fe21585b3998bf7dfe9fcc44ae9ba626012/ruff-0.16.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3f951b14d865d5952c89d40a5ca07e87abe24fa5453299878411e127748fb1c", size = 10032114, upload-time = "2026-09-24T20:37:19.942Z" },
{ url = "https://files.pythonhosted.org/packages/c8/7d/f1e17c54ab59d4bad1dce8ee3e22a7a1d0ef4745240decacdcf3832b5bb2/ruff-0.16.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:447fc07e1573afff7cb02803462b12b6c8ece7cf10e2cd78565fa6d7a1c0bf8d", size = 9910227, upload-time = "2026-09-24T20:37:21.872Z" },
{ url = "https://files.pythonhosted.org/packages/34/19/436f647a65075bbd3bab2668b3bdaa5120559b294694018cdcefabbbf30b/ruff-0.16.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a3e039a6a40ed976c491722b60e0ae4a4aa1a86057f540ee7a37a5d19ae9120", size = 10547484, upload-time = "2026-09-24T20:37:24.229Z" },
{ url = "https://files.pythonhosted.org/packages/03/59/38430a6bc2f6d8095447ac39625cf8b6e9344a47e6c26225c8ba1bff3ffb/ruff-0.16.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4684dded7db60aa57cb118fa158630f5feade4af5782903b6053484bdf9bd129", size = 11412367, upload-time = "2026-09-24T20:37:26.307Z" },
{ url = "https://files.pythonhosted.org/packages/c8/bd/bbb6d7fc7f208c8b8c50dd5dc8206e4cfdb1e7a8fb852606a3adf370c880/ruff-0.16.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d29c934357e45642fda2f34c0b1f4025b4a6c01e15e4bf0016879d60078a142c", size = 10869787, upload-time = "2026-09-24T20:37:28.35Z" },
{ url = "https://files.pythonhosted.org/packages/bc/b8/9c543074918061abbefc3bd139bee22de35abedb00dde0f2d27288838962/ruff-0.16.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a21713e629d3e5bdb2f5c2def1cc7f04f47fa8e1a7eb0571b4a28e1da64bc728", size = 10406494, upload-time = "2026-09-24T20:37:30.624Z" },
{ url = "https://files.pythonhosted.org/packages/35/7a/5a8851bd146e7ccf8fd4b003f6c75c11f8fbdb0e60673b45097986b6bf41/ruff-0.16.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:7baa24ef5fc8e77aa93879e1d3f43754a01ae488e869f1ae30cf431afd4d2452", size = 10590083, upload-time = "2026-09-24T20:37:32.439Z" },
{ url = "https://files.pythonhosted.org/packages/87/f0/4c3467188f23f806960b46fa76575a7cd0514c9ba90562650b71efc96980/ruff-0.16.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a41aac6230aadfaa133bdfa1614488531ffa3e0837567ae04c0da2058a9c0f9e", size = 10119151, upload-time = "2026-09-24T20:37:34.581Z" },
{ url = "https://files.pythonhosted.org/packages/15/34/5a4def5adea572ce6aea0bb64f21f928ee317b80e0db747d7979b01d7261/ruff-0.16.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:c2529fb5896d49115b0e9aa8f887490b34bbe76baf879ec2264ac59406869ce7", size = 9911544, upload-time = "2026-09-24T20:37:36.796Z" },
{ url = "https://files.pythonhosted.org/packages/62/5d/d15ebea7499eef9373318c0ee6ca127832927c6529731f6d48e18dce7ca9/ruff-0.16.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:41e3870277694177429b56406d65dfbdb2c2802c52b715edaf6a0b829c69d4ee", size = 10269884, upload-time = "2026-09-24T20:37:38.857Z" },
{ url = "https://files.pythonhosted.org/packages/d1/56/c5d3cd119ded7a3c7aba0e961b69cb3df701c91662c98ad694467d060ce1/ruff-0.16.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8adbe4e58af167f767d7b2ba5e83c42e878350796cf78c2f5e14ab9903a92588", size = 10749366, upload-time = "2026-09-24T20:37:41.042Z" },
{ url = "https://files.pythonhosted.org/packages/ac/fe/734ec7527029ac757ecf821f143c9f3fcf69149c21f53a044a899b430f5a/ruff-0.16.9-py3-none-win32.whl", hash = "sha256:0e1dbc2073624dee6618d41d0098690a7244654af746704b64759e12b6b6b385", size = 10152355, upload-time = "2026-09-24T20:37:43.025Z" },
{ url = "https://files.pythonhosted.org/packages/14/21/26e4643629b3ebb44f0a06f9c9a53058d63d989415f63a9a3c28e2ee7f22/ruff-0.16.9-py3-none-win_amd64.whl", hash = "sha256:6bd40fec8cd4c8a3d4dd589bd8ad4e6320c13c29234159bfd959a40d529d597b", size = 10592965, upload-time = "2026-09-24T20:37:44.944Z" },
{ url = "https://files.pythonhosted.org/packages/51/60/5fb1a39dbb5ae314d5f59bc7348a63c1d5c20f3cd83914c4b5cb0be31d2d/ruff-0.16.9-py3-none-win_arm64.whl", hash = "sha256:ed1a252039200f57a59eebc063b54beabea67bfbaaca0eeaa7f54b5fbcda2284", size = 10458649, upload-time = "2026-09-24T20:37:46.882Z" },
]
[[package]]
@@ -1233,14 +1249,14 @@ wheels = [
[[package]]
name = "syrupy"
version = "6.0.0"
version = "6.1.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pytest" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d2/a7/3c797c76994dc817582abdda90ee31867a9883567b0add47dbc0ccb12ce4/syrupy-6.0.0.tar.gz", hash = "sha256:bf4f5f662a7b23a9e5711bd5597e301cd09c00f31be5fa4095f79da7b0585f3d", size = 99435, upload-time = "2026-08-22T20:48:39.493Z" }
sdist = { url = "https://files.pythonhosted.org/packages/74/2c/8ffc1414d1951895d0d5b7dc83c0b079ebc48c3bf27ffbb0064646367500/syrupy-6.1.1.tar.gz", hash = "sha256:7e584a8df4ede6dd3c1c44a77f9edc46186b6767a73dea6f0083044946839a59", size = 109705, upload-time = "2026-09-13T20:25:21.599Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7c/ff/4d8ae6d6070ea73ee9d466701584a75db9586ce09de98f8f1b3276efad5f/syrupy-6.0.0-py3-none-any.whl", hash = "sha256:3efa16d767bd8614413cc20057552e4aec14428c6e5ea788c1f7e03047e8a5e4", size = 58047, upload-time = "2026-08-22T20:48:38.213Z" },
{ url = "https://files.pythonhosted.org/packages/2c/b5/bf2a8a335a6a813869dd482cedbcde252f582b5afbfe50eb78ec09962800/syrupy-6.1.1-py3-none-any.whl", hash = "sha256:6ac9ac6a42d70ae44156f5f157a9563b9bb8cc96dcf9364327f330463bff889a", size = 63436, upload-time = "2026-09-13T20:25:20.294Z" },
]
[[package]]
@@ -1308,27 +1324,27 @@ wheels = [
[[package]]
name = "ty"
version = "0.0.75"
version = "0.0.84"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/81/d0/d0c96f898d6974a4a3569ab3efdf9512c04ad99f9203effb55f72497fe97/ty-0.0.75.tar.gz", hash = "sha256:4c5eead33dfbf6e2ebb4f400f74b51ffc9bab702a6f23ddb648a1cbb740387e3", size = 6868326, upload-time = "2026-08-26T20:23:40.399Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e1/f6/34f8869c45ea87fe6a931ffa824b1fe4428167173543d92988d631add355/ty-0.0.84.tar.gz", hash = "sha256:0cefdb0cd5d399418dbe83c349ba605047b7dff815ae6f460c80e8522b40be67", size = 7409537, upload-time = "2026-09-24T13:16:24.367Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/6c/b12d03505f17581f0cfa3c12273fe34c1d67b36dfda1bc561a6bdc16512b/ty-0.0.75-py3-none-linux_armv6l.whl", hash = "sha256:e5409f50db2246fd4bd039d93d261e0cfa1daa554a4fb77256f91072c570349a", size = 12972606, upload-time = "2026-08-26T20:22:59.716Z" },
{ url = "https://files.pythonhosted.org/packages/d1/aa/30f11eecd9215a9f87e8fe8baaf48f3ce905f5d75b8e4aac70f0091f130c/ty-0.0.75-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5e7b8b3472fb9bb2eeab314984b265df08a7a9d518867a9e6020eebc06570be2", size = 12527158, upload-time = "2026-08-26T20:23:02.767Z" },
{ url = "https://files.pythonhosted.org/packages/f2/11/7fd7001b0b5c6610bfbad7357e47d5fe6f82d4e84e94c53776a478f5e9f8/ty-0.0.75-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c6ccf34169821fe0d23e3360deeef981d217963412f1d087b9bdd32ec57f7a57", size = 12400533, upload-time = "2026-08-26T20:23:04.965Z" },
{ url = "https://files.pythonhosted.org/packages/fd/7f/1e284ea3d348d7be02f12d83bc22ed9ef193033f863f05b64db99027f141/ty-0.0.75-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:842ebb41e9c6c334b40768704e20b1a69d5c6b08805b289d5e0e2565f49f2de1", size = 12420592, upload-time = "2026-08-26T20:23:07.427Z" },
{ url = "https://files.pythonhosted.org/packages/2d/ab/d813271543370c47fd74b5118f2066ab32b0983e907b1821f3f9a6d0fa7f/ty-0.0.75-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf7a5a723c5f1e0fab4ffbfe9bd95123a526ed48f206e5f25cb2161ca294007a", size = 12739219, upload-time = "2026-08-26T20:23:09.809Z" },
{ url = "https://files.pythonhosted.org/packages/31/5b/95b49cc5570fd92a7bf63732f649b31906158721e03c7fcb1b5be74ee3bf/ty-0.0.75-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54382f98e5da292fcd7104391afef5105c35bb2f312e29bea6f5fa419935255c", size = 13494046, upload-time = "2026-08-26T20:23:12.191Z" },
{ url = "https://files.pythonhosted.org/packages/2c/0d/502d2dd68173cf020e1ad2bdbab9544c86776de0b0e2ed15f8c2fe006e3d/ty-0.0.75-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac13b180dc2aade2cd243f56b01650e78bf091a2e522ad3bc947245d7837c613", size = 13938899, upload-time = "2026-08-26T20:23:14.764Z" },
{ url = "https://files.pythonhosted.org/packages/20/5b/f3b12a25c07224456219fc2bd20db0ad7e40b304be0ff6aad728da0135f9/ty-0.0.75-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:752df7951a443219d7f1ff817e3723c85d428565ff449e08a7a93ba821661526", size = 13656711, upload-time = "2026-08-26T20:23:17.145Z" },
{ url = "https://files.pythonhosted.org/packages/51/7b/f090ad306e2b15a07b332d647138c5264b89d9758855ecce8b8a10bcb153/ty-0.0.75-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fd399feedf7cee816563c1baec45fc1c0b3c89f1ea42364920b688004b5b7da", size = 13093499, upload-time = "2026-08-26T20:23:19.489Z" },
{ url = "https://files.pythonhosted.org/packages/f1/4b/f69b99aaaca0c7c65d5f114b186b26b21666f767b0c69eec99a2bdccc061/ty-0.0.75-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:d7625f6f56c7dc1e873579fdc9e432a0e21e302afe847ab60704d2303442a92e", size = 13520580, upload-time = "2026-08-26T20:23:21.789Z" },
{ url = "https://files.pythonhosted.org/packages/b6/e7/692c5f905c0345a15d2255fc74066d660f030254ae8dcdaf33f5a5c2f279/ty-0.0.75-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:89e7d527e95a2534b70cae29e94c104b84082760ea05927d23bb87280969c104", size = 12524095, upload-time = "2026-08-26T20:23:24.026Z" },
{ url = "https://files.pythonhosted.org/packages/ba/9a/f42b12cf265ea95344bf554764c4791cfb273bdd628aadd7c209af7cadc3/ty-0.0.75-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0843f134440740706e01bee5f88f4cfc10e9b018bddb9e4ef4c12dc9fc0c9aef", size = 12756591, upload-time = "2026-08-26T20:23:26.126Z" },
{ url = "https://files.pythonhosted.org/packages/7c/5e/9b180c133cb9cce48179a7d2bf9e1802d992aa8176a918e0e05205760b42/ty-0.0.75-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1bd0ec0e50ee1376875c88891efe6f549c3560fa5b2ddad79a425cd5a6218b9c", size = 12998754, upload-time = "2026-08-26T20:23:28.353Z" },
{ url = "https://files.pythonhosted.org/packages/39/f6/3c6ef5dd550103e29905121c67fb96a374564f31a2f44c6faa1af98c2d61/ty-0.0.75-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1f9eafd561f90110d5e29f589ec3e956c4686e2f6631348d99276436f5cbe4d1", size = 13316474, upload-time = "2026-08-26T20:23:30.857Z" },
{ url = "https://files.pythonhosted.org/packages/bb/52/12776337874c821076bd5368e352ccd9e67174790abe3b856f749cb3524b/ty-0.0.75-py3-none-win32.whl", hash = "sha256:05063a6fafe2154b794a7f964515d148e51acd186d72d4a3acd347ee9fa19336", size = 12316315, upload-time = "2026-08-26T20:23:33.528Z" },
{ url = "https://files.pythonhosted.org/packages/53/e6/bb51e16af5c7138c9f52f8f3d0a401a371c6798d092e3b74926f186a9814/ty-0.0.75-py3-none-win_amd64.whl", hash = "sha256:81cf1ba5f6b7536ad56747865214255d9bc8e80533a689dbb9ddeaad464b09f1", size = 12917267, upload-time = "2026-08-26T20:23:35.978Z" },
{ url = "https://files.pythonhosted.org/packages/39/73/4542f829107468b5de4231af67f29927c093bfad11f3c1e5b2c08fb1206b/ty-0.0.75-py3-none-win_arm64.whl", hash = "sha256:541c9af5b7a0ad23d15ec315a7da81150833c359f48124ed3789ff25eacd6f42", size = 12711024, upload-time = "2026-08-26T20:23:38.159Z" },
{ url = "https://files.pythonhosted.org/packages/aa/0c/600648778e1c79133ce6b6a5c4c2531ae16b90c9b30f5041b38b574f61b8/ty-0.0.84-py3-none-linux_armv6l.whl", hash = "sha256:868438e2b9abfc835dd5b5b07ed693db95d8348ac40e3228252511d63ae87695", size = 13935333, upload-time = "2026-09-24T13:15:31.167Z" },
{ url = "https://files.pythonhosted.org/packages/c5/3b/27f06f49945c36170765fad82076898039ae3873338e9865f876dd62f12c/ty-0.0.84-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b00f39b521f0b559cde0f6abf8dd47213b513b04f33d58efeb9b98486a16bb17", size = 13511328, upload-time = "2026-09-24T13:15:34.446Z" },
{ url = "https://files.pythonhosted.org/packages/fc/b6/b12dd130e7f30141f0645614f454c925991cd389b1cc6c8284fd6bf32326/ty-0.0.84-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8f78c2915567f2ee62ce0c24d86091bc76f81577677755e242d09951db588b9a", size = 13445445, upload-time = "2026-09-24T13:15:37.476Z" },
{ url = "https://files.pythonhosted.org/packages/44/96/f012de4b3c1d9a4773326621380d768b4d48c47e620dc012775c5e01c7d7/ty-0.0.84-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61155cad0921e890d86d1d911a40b3a0e924ef4bdc3b4f3865a6c89c7e3012dc", size = 13471235, upload-time = "2026-09-24T13:15:40.64Z" },
{ url = "https://files.pythonhosted.org/packages/05/32/dca630f5f5353fa7bf67eebccf1549df99b16656d21071d0df6677c826a5/ty-0.0.84-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:417c3bf14947ed16b0b92976ab333c27915590354e96d4f581709068d3811355", size = 13680704, upload-time = "2026-09-24T13:15:43.817Z" },
{ url = "https://files.pythonhosted.org/packages/44/8d/33702c8f62f05ad45b33644fb914d483de6a0a7bc8e51884e9b8773e6d69/ty-0.0.84-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e06ef117759f654f5379efe9ae79bda8a186016c016f1023d05c34f8e15c1546", size = 14518923, upload-time = "2026-09-24T13:15:46.802Z" },
{ url = "https://files.pythonhosted.org/packages/c8/a1/da3246e6ef2ae0e842bca4058b25c54bee55ab058ffff4ee54e72517ec01/ty-0.0.84-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a05d1d53db8b64410ab38c6e32c5898510584869d1d5dd9c0605db81c9aef7df", size = 15027731, upload-time = "2026-09-24T13:15:49.755Z" },
{ url = "https://files.pythonhosted.org/packages/20/17/b0099098a560aeb4f9fde6c912f9a81622c3e44e6555e1fbe322b368ec31/ty-0.0.84-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b92400494ab855cfb1204ac1101e3f53b2d09a8e21d171758b2cf9acdab1809e", size = 14748260, upload-time = "2026-09-24T13:15:52.713Z" },
{ url = "https://files.pythonhosted.org/packages/60/d5/32cd67aedb271f006e716ad093806c96c5d7e2af86be8255469dbd3d76c8/ty-0.0.84-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af17eb391ae3027fed9be1780bea555c2b8a25ba68e184c627b4981b2c9eaab7", size = 14205858, upload-time = "2026-09-24T13:15:55.677Z" },
{ url = "https://files.pythonhosted.org/packages/43/c4/638ac62ca2e8eca1b92e3e16273085744fada728b993500819b5c258ca15/ty-0.0.84-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:44cf06af0e7ec78ea6d8ff33a3450fc19627756a4fba681f7410d4e3ef63047a", size = 14573196, upload-time = "2026-09-24T13:15:59.165Z" },
{ url = "https://files.pythonhosted.org/packages/a6/f6/9ee81c5d8921e5fd5f10889ae563c95cd3272b31df2ada528919ca1cef6b/ty-0.0.84-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c6e56b443ff9ba462e34bfbc1e582004c2719ed35f8f93a433744b0ba0afb2d9", size = 13480760, upload-time = "2026-09-24T13:16:02.313Z" },
{ url = "https://files.pythonhosted.org/packages/f6/83/fbe5d1177667ae16d84c3242846b143809c9cc09568b333e5a9b4d5908f6/ty-0.0.84-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8b6562fcc48820c1030e2a4509ef2c14ef93043ca4860280074314ac3a825b95", size = 13696346, upload-time = "2026-09-24T13:16:05.391Z" },
{ url = "https://files.pythonhosted.org/packages/86/9a/1b4ac9ddc34cc0ab4624bc219825d66af52f1ad87f4778ad08ad03fa06d5/ty-0.0.84-py3-none-musllinux_1_2_i686.whl", hash = "sha256:dee7421451efcbd70f03e95a8f2c8d9e5ec3d35edcd27b4095ee00ee062abfc4", size = 13959410, upload-time = "2026-09-24T13:16:08.661Z" },
{ url = "https://files.pythonhosted.org/packages/c9/04/70e289437a67dfd686056ab4fd91880261a8233033a6a95d29393741fb97/ty-0.0.84-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:31e96307cf621babe969307dd657783d672e2d8bdbd06493710da635bf63fa07", size = 14350294, upload-time = "2026-09-24T13:16:11.865Z" },
{ url = "https://files.pythonhosted.org/packages/9c/f4/ae7dbd118425c850981d23efb850f0d9d7f6b8d2b852bde2767434f668b4/ty-0.0.84-py3-none-win32.whl", hash = "sha256:4bdc85f91cbaae35825f2b17bb4ac5de0563367875cb0f42b2bf07a1bf7ff2ba", size = 13176439, upload-time = "2026-09-24T13:16:15.049Z" },
{ url = "https://files.pythonhosted.org/packages/29/ec/994d87252f065dda5a500bc8afc726d18f6582d81d6f4ff1feb8a267c800/ty-0.0.84-py3-none-win_amd64.whl", hash = "sha256:71cc9aa7a7c89d4e12c6a814e9c0c3310ad5bee44efedbd6f5a42d91e9ac0f0d", size = 13962879, upload-time = "2026-09-24T13:16:18.191Z" },
{ url = "https://files.pythonhosted.org/packages/6d/1c/e6e91852b72566b1ab9aca504712857dfb89324c12092a5bd61d42d34427/ty-0.0.84-py3-none-win_arm64.whl", hash = "sha256:6c9d3718c4a0d318bc9c92af9b2aa85c3294e7dff54cf1d69ad79568f56516da", size = 13656453, upload-time = "2026-09-24T13:16:21.51Z" },
]
[[package]]
+1 -1
View File
@@ -3,7 +3,7 @@ from langgraph_sdk.client import get_client, get_sync_client
from langgraph_sdk.encryption import Encryption
from langgraph_sdk.encryption.types import DecryptResult, EncryptionContext
__version__ = "0.4.7"
__version__ = "0.4.8"
__all__ = [
"Auth",
+65 -26
View File
@@ -15,6 +15,7 @@ from __future__ import annotations
import asyncio
import contextlib
import logging
import random
from collections.abc import AsyncGenerator, AsyncIterator, Generator, Mapping
from dataclasses import dataclass, field
@@ -36,6 +37,11 @@ from langgraph_sdk.stream.decoders import (
ToolCallsDecoder,
validate_interleave_channels,
)
from langgraph_sdk.stream.errors import (
is_non_retryable_stream_error,
protocol_error,
stream_error_frame,
)
from langgraph_sdk.stream.subscription import compute_union_filter, infer_channel
from langgraph_sdk.stream.transport import (
AsyncProtocolTransport,
@@ -44,6 +50,8 @@ from langgraph_sdk.stream.transport import (
ProtocolWebSocketTransport,
)
logger = logging.getLogger(__name__)
class InterruptPayload(TypedDict):
"""Payload surfaced when the server requests human input for a thread."""
@@ -193,6 +201,7 @@ class RunModule:
if not gate.done():
gate.set_result(None)
self._owner._run_seen = True
self._owner._ensure_lifecycle_watcher_running()
return result
except BaseException as err:
# Why: gate MUST reject on any exit type, including CancelledError,
@@ -1203,6 +1212,7 @@ class AsyncThreadStream:
# observes a `completed`/`errored` event.
self._interrupts_lock = asyncio.Lock()
self._lifecycle_watcher_task: asyncio.Task[None] | None = None
self._lifecycle_watcher_rejected = False
self._lifecycle_watcher_handle: EventStreamHandle | None = None
self._lifecycle_cursor: int | None = None
self._lifecycle_max_reconnect_attempts = 5
@@ -1261,8 +1271,11 @@ class AsyncThreadStream:
# Create the run-done future here (async context guarantees a running loop).
self._run_done = asyncio.get_running_loop().create_future()
# Start the lifecycle watcher immediately so reattach and thread.output
# work without a preceding run.start call.
self._ensure_lifecycle_watcher_running()
# work without a preceding run.start call. A minted thread_id names no
# thread until run.start creates it, and with custom auth a stream for a
# thread that doesn't exist yet fails, so run.start starts the watcher.
if self._explicit_thread_id:
self._ensure_lifecycle_watcher_running()
return self
async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
@@ -1630,6 +1643,13 @@ class AsyncThreadStream:
async for event in self._dedup_iter(shared.events):
if self._closed:
break
if (frame_error := stream_error_frame(event)) is not None:
logger.warning(
"Thread %s event stream failed: %s",
self.thread_id,
frame_error,
)
continue
self._observe_event(event)
for sub in list(self._subscriptions.values()):
if matches_subscription(event, sub.params):
@@ -1655,6 +1675,7 @@ class AsyncThreadStream:
if (
err is not None
and not isinstance(err, asyncio.CancelledError)
and not is_non_retryable_stream_error(err)
and not self._closed
):
with contextlib.suppress(Exception):
@@ -1681,7 +1702,8 @@ class AsyncThreadStream:
Returns:
`True` if a new stream was opened (caller should resume fanout),
`False` if all reconnect attempts were exhausted or the controller
`False` if all reconnect attempts were exhausted, the server
rejected the stream with a non-retryable 4xx, or the controller
was closed in the meantime.
"""
if self._transport is None:
@@ -1702,7 +1724,9 @@ class AsyncThreadStream:
await new_stream.ready
except asyncio.CancelledError:
raise
except Exception:
except Exception as err:
if is_non_retryable_stream_error(err):
return False
await self._reconnect_sleep(attempt)
continue
self._shared_stream = new_stream
@@ -1800,9 +1824,7 @@ class AsyncThreadStream:
# 202/204 — no body. Caller gets an empty result.
return {}
if response.get("type") == "error":
code = response.get("error", "unknown")
message = response.get("message", "")
raise RuntimeError(f"Protocol error [{code}]: {message}")
raise protocol_error(response)
meta = response.get("meta")
if isinstance(meta, dict):
applied_through_seq = meta.get("applied_through_seq")
@@ -1826,8 +1848,15 @@ class AsyncThreadStream:
await asyncio.wait_for(asyncio.shield(gate), timeout=timeout)
def _ensure_lifecycle_watcher_running(self) -> None:
if self._lifecycle_watcher_task is not None:
task = self._lifecycle_watcher_task
if task is not None and not self._lifecycle_watcher_rejected:
return
if task is not None:
# A 4xx stopped the watcher before this run started, typically the
# 404 for a thread this run.start just created, so the result it
# left isn't this run's.
self._run_done = asyncio.get_running_loop().create_future()
self._lifecycle_watcher_rejected = False
self._lifecycle_watcher_task = asyncio.create_task(
self._run_lifecycle_watcher()
)
@@ -1850,7 +1879,11 @@ class AsyncThreadStream:
if self._transport is None:
return
reconnect_attempts = 0
frame_error: RuntimeError | None = None
while not self._closed:
# A stream opened before run.start can 404 for the thread that
# run.start creates, so it gets one more try once a run has started.
opened_before_run = not self._run_seen
try:
handle = self._transport.open_event_stream(
self._lifecycle_stream_params()
@@ -1860,6 +1893,7 @@ class AsyncThreadStream:
async for event in handle.events:
if self._closed:
return
frame_error = stream_error_frame(event) or frame_error
self._observe_lifecycle_event(event)
await self._apply_lifecycle_event(event)
err = await handle.done
@@ -1868,38 +1902,43 @@ class AsyncThreadStream:
# event. Resolve `_run_done` as errored so awaiters of
# `thread.output` don't hang.
if err is None:
run_done = self._run_done
if run_done is not None and not run_done.done():
run_done.set_result(
_RunTerminal(
status="errored",
error=RuntimeError(
"lifecycle stream ended before terminal event"
),
)
self._fail_run_done(
frame_error
or RuntimeError(
"lifecycle stream ended before terminal event"
)
)
return
reconnect_attempts += 1
if reconnect_attempts > self._lifecycle_max_reconnect_attempts:
if (
is_non_retryable_stream_error(err)
or reconnect_attempts > self._lifecycle_max_reconnect_attempts
):
raise err
await asyncio.sleep(0.05)
except asyncio.CancelledError:
raise
except Exception as exc:
if is_non_retryable_stream_error(exc):
if opened_before_run and self._run_seen:
continue
self._lifecycle_watcher_rejected = True
self._fail_run_done(exc)
return
reconnect_attempts += 1
if reconnect_attempts <= self._lifecycle_max_reconnect_attempts:
await asyncio.sleep(0.05)
continue
run_done = self._run_done
if run_done is not None and not run_done.done():
run_done.set_result(
_RunTerminal(
status="errored",
error=RuntimeError(f"Lifecycle transport failed: {exc}"),
)
)
self._fail_run_done(
frame_error or RuntimeError(f"Lifecycle transport failed: {exc}")
)
return
def _fail_run_done(self, error: BaseException) -> None:
run_done = self._run_done
if run_done is not None and not run_done.done():
run_done.set_result(_RunTerminal(status="errored", error=error))
async def _fetch_state(self) -> dict[str, Any]:
"""Fetch the current thread state from the REST endpoint."""
return await self._http.get(
+76 -21
View File
@@ -34,6 +34,11 @@ from langgraph_sdk.stream.decoders import (
ToolCallsDecoder,
validate_interleave_channels,
)
from langgraph_sdk.stream.errors import (
is_non_retryable_stream_error,
protocol_error,
stream_error_frame,
)
from langgraph_sdk.stream.subscription import compute_union_filter, infer_channel
from langgraph_sdk.stream.sync_controller import (
SyncStreamController,
@@ -237,6 +242,7 @@ class SyncRunModule:
controller = self._owner._controller
if controller is not None and controller._run_start_gate is not None:
controller._run_start_gate.set()
self._owner._ensure_lifecycle_watcher_running()
return result
def respond(
@@ -1111,6 +1117,7 @@ class SyncThreadStream:
self.interrupted: bool = False
self.interrupts: list[InterruptPayload] = []
self._lifecycle_watcher_thread: threading.Thread | None = None
self._lifecycle_watcher_lock = threading.Lock()
self._lifecycle_watcher_handle: SyncEventStreamHandle | None = None
self._lifecycle_cursor: int | None = None
self._lifecycle_max_reconnect_attempts = 5
@@ -1149,7 +1156,11 @@ class SyncThreadStream:
self._transport, run_start_gate=run_start_gate
)
self._run_done = _BlockingResult()
self._ensure_lifecycle_watcher_running()
# A minted thread_id names no thread until run.start creates it, and
# with custom auth a stream for a thread that doesn't exist yet fails,
# so run.start starts the watcher instead.
if self._explicit_thread_id:
self._ensure_lifecycle_watcher_running()
return self
def __exit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
@@ -1485,9 +1496,7 @@ class SyncThreadStream:
if response is None:
return {}
if response.get("type") == "error":
code = response.get("error", "unknown")
message = response.get("message", "")
raise RuntimeError(f"Protocol error [{code}]: {message}")
raise protocol_error(response)
meta = response.get("meta")
if isinstance(meta, dict):
applied_through_seq = meta.get("applied_through_seq")
@@ -1496,14 +1505,20 @@ class SyncThreadStream:
return response.get("result", {})
def _ensure_lifecycle_watcher_running(self) -> None:
if self._lifecycle_watcher_thread is not None:
return
self._lifecycle_watcher_thread = threading.Thread(
target=self._run_lifecycle_watcher,
name="langgraph-sdk-sync-lifecycle",
daemon=True,
)
self._lifecycle_watcher_thread.start()
with self._lifecycle_watcher_lock:
if self._lifecycle_watcher_thread is not None:
return
if self._run_done is not None and self._run_done.done():
# A 4xx stopped the watcher before this run started, typically
# the 404 for a thread this run.start just created, so the
# result it left isn't this run's.
self._run_done = _BlockingResult()
self._lifecycle_watcher_thread = threading.Thread(
target=self._run_lifecycle_watcher,
name="langgraph-sdk-sync-lifecycle",
daemon=True,
)
self._lifecycle_watcher_thread.start()
def _observe_lifecycle_event(self, event: Event) -> None:
seq = event.get("seq")
@@ -1523,7 +1538,11 @@ class SyncThreadStream:
if self._transport is None:
return
reconnect_attempts = 0
frame_error: RuntimeError | None = None
while not self._closed:
# A stream opened before run.start can 404 for the thread that
# run.start creates, so it gets one more try once a run has started.
opened_before_run = not self._run_seen
try:
handle = self._transport.open_event_stream(
self._lifecycle_stream_params()
@@ -1532,28 +1551,64 @@ class SyncThreadStream:
for event in handle.events:
if self._closed:
return
frame_error = stream_error_frame(event) or frame_error
self._observe_lifecycle_event(event)
self._apply_lifecycle_event(event)
err = handle.error()
if err is None:
# Clean EOF: stream ended without a terminal lifecycle
# event. Resolve `_run_done` as errored so `thread.output`
# doesn't block forever.
if not self._closed:
self._fail_run_done(
frame_error
or RuntimeError(
"lifecycle stream ended before terminal event"
)
)
return
reconnect_attempts += 1
if reconnect_attempts > self._lifecycle_max_reconnect_attempts:
if (
is_non_retryable_stream_error(err)
or reconnect_attempts > self._lifecycle_max_reconnect_attempts
):
raise err
except Exception as exc:
if is_non_retryable_stream_error(exc):
if self._stop_rejected_lifecycle_watcher(
exc, unless_run_started=opened_before_run
):
return
continue
reconnect_attempts += 1
if reconnect_attempts <= self._lifecycle_max_reconnect_attempts:
continue
run_done = self._run_done
if run_done is not None and not run_done.done():
run_done.set_result(
_RunTerminal(
status="errored",
error=RuntimeError(f"Lifecycle transport failed: {exc}"),
)
)
self._fail_run_done(
frame_error or RuntimeError(f"Lifecycle transport failed: {exc}")
)
return
def _stop_rejected_lifecycle_watcher(
self, error: BaseException, *, unless_run_started: bool = False
) -> bool:
"""Fail the run with a 4xx `error` and clear the watcher, so run.start starts another.
Returns False, changing nothing, when `unless_run_started` is set and a
run has started. Holding the lock keeps a run.start from seeing this
watcher as still running once it has decided to stop.
"""
with self._lifecycle_watcher_lock:
if unless_run_started and self._run_seen:
return False
self._fail_run_done(error)
self._lifecycle_watcher_thread = None
return True
def _fail_run_done(self, error: BaseException) -> None:
run_done = self._run_done
if run_done is not None and not run_done.done():
run_done.set_result(_RunTerminal(status="errored", error=error))
def _fetch_state(self) -> dict[str, Any]:
"""Fetch the current thread state from the REST endpoint."""
return self._http.get(
@@ -0,0 +1,46 @@
"""Thread-stream failures, shared by the async and sync clients."""
from __future__ import annotations
from typing import Any
import httpx
from websockets.exceptions import ConnectionClosed, InvalidStatus
def protocol_error(response: dict[str, Any]) -> RuntimeError:
"""Build the error for a protocol `{"type": "error"}` envelope."""
code = response.get("error", "unknown")
message = response.get("message", "")
return RuntimeError(f"Protocol error [{code}]: {message}")
def stream_error_frame(event: Any) -> RuntimeError | None:
"""Return the error in an unsolicited `{"type": "error", "id": null}` frame.
The server sends one just before it closes a thread stream whose read failed.
"""
if (
isinstance(event, dict)
and event.get("type") == "error"
and event.get("id") is None
):
return protocol_error(event)
return None
def is_non_retryable_stream_error(error: BaseException | None) -> bool:
"""Whether reopening the stream would fail the same way.
True for a 4xx other than 408 and 429: the status of a refused stream request
or WebSocket upgrade, or a WebSocket close code of 4000 plus that status. A
server that rejects a WebSocket after accepting it closes with such a code,
because a browser can't read the status of a refused upgrade.
"""
if isinstance(error, (httpx.HTTPStatusError, InvalidStatus)):
status = error.response.status_code
elif isinstance(error, ConnectionClosed) and error.rcvd is not None:
status = error.rcvd.code - 4000
else:
return False
return 400 <= status < 500 and status not in (408, 429)
@@ -13,6 +13,10 @@ from typing import Any
from langchain_protocol import Event, SubscribeParams
from langgraph_sdk.stream.errors import (
is_non_retryable_stream_error,
stream_error_frame,
)
from langgraph_sdk.stream.subscription import compute_union_filter, filter_covers
from langgraph_sdk.stream.transport import (
SyncEventStreamHandle,
@@ -167,6 +171,13 @@ class SyncStreamController:
return
try:
for event in self._dedup_iter(shared.events):
if (frame_error := stream_error_frame(event)) is not None:
_logger.warning(
"Thread %s event stream failed: %s",
self._transport.thread_id,
frame_error,
)
continue
with self._lock:
if self._closed:
break
@@ -190,7 +201,11 @@ class SyncStreamController:
continue # rotation happened; pick up new stream
# No rotation — check if this was a transport drop
err = shared.error()
if err is not None and not self._closed:
if (
err is not None
and not is_non_retryable_stream_error(err)
and not self._closed
):
if self._reconnect_shared_stream():
continue
break
@@ -199,25 +214,6 @@ class SyncStreamController:
for sub in self._subscriptions.values():
sub.queue.put(None)
def _reconnect_shared_stream(self) -> bool:
with self._lock:
base_filter = self._shared_stream_filter
if base_filter is None:
return False
for _ in range(self._max_reconnect_attempts):
with self._lock:
if self._closed:
return False
params = self._filter_with_since(base_filter)
try:
new_stream = self._transport.open_event_stream(params)
except Exception:
continue
with self._lock:
self._shared_stream = new_stream
return True
return False
def _compute_current_union(
self, extra: SubscribeParams | None = None
) -> dict[str, Any]:
@@ -296,7 +292,8 @@ class SyncStreamController:
"""Attempt to reopen the shared stream after a transport drop.
Returns True if a new stream was successfully opened, False if all
reconnect attempts were exhausted or the controller was closed.
reconnect attempts were exhausted, the server rejected the stream with a
non-retryable 4xx, or the controller was closed.
"""
base_filter = self._shared_stream_filter
if base_filter is None:
@@ -317,6 +314,8 @@ class SyncStreamController:
old.close()
return True
except Exception as err:
if is_non_retryable_stream_error(err):
return False
_logger.debug("sync reconnect attempt %d failed: %r", attempt, err)
return False
+10
View File
@@ -69,6 +69,16 @@ def lifecycle_errored_event(
return _base(seq, "lifecycle", namespace or [], {"event": "failed", "error": error})
def thread_stream_failed_frame() -> dict[str, Any]:
"""Unsolicited error frame the server sends before closing a failed stream."""
return {
"type": "error",
"id": None,
"error": "unknown_error",
"message": "Thread event stream failed.",
}
def values_event(
seq: int = 0, namespace: list[str] | None = None, **data: Any
) -> dict[str, Any]:
@@ -60,6 +60,7 @@ class _StreamScript:
events: list[dict[str, Any]]
delay: float = 0.0
fail_after: int | None = None
status: int = 200
class FakeServer:
@@ -263,6 +264,8 @@ class FakeServer:
events=list(self.scripted_events), delay=self._stream_delay
)
)
if script.status != 200:
return httpx.Response(script.status, json={"detail": "scripted"})
self.open_event_streams += 1
self._open_event_streams_max = max(
self._open_event_streams_max, self.open_event_streams
@@ -14,6 +14,7 @@ import orjson
class SyncStreamScript:
events: list[dict[str, Any]]
fail_after: int | None = None
status: int = 200
class _SseByteStream(httpx.SyncByteStream):
@@ -113,6 +114,8 @@ class SyncFakeServer:
if self._stream_scripts
else SyncStreamScript(events=list(self.scripted_events))
)
if script.status != 200:
return httpx.Response(script.status, json={"detail": "scripted"})
return httpx.Response(
200,
headers={"content-type": "text/event-stream"},
@@ -0,0 +1,67 @@
"""Child lifecycle completion must not finish the parent thread's output."""
from __future__ import annotations
import asyncio
from typing import cast
import httpx
import pytest
from langchain_protocol import Event
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.stream import _BlockingResult
from langgraph_sdk._sync.threads import SyncThreadsClient
from streaming._events import input_requested_event, lifecycle_event
@pytest.mark.parametrize("phase", ["completed", "failed"])
async def test_async_child_terminal_does_not_finish_root(phase):
async with httpx.AsyncClient(base_url="http://test") as raw:
thread = ThreadsClient(HttpClient(raw)).stream(
thread_id="t-1", assistant_id="agent"
)
thread._run_done = asyncio.get_running_loop().create_future()
await thread._apply_lifecycle_event(
cast(Event, input_requested_event(namespace=["sub:1"]))
)
await thread._apply_lifecycle_event(
cast(Event, lifecycle_event(namespace=["sub:1"], event=phase))
)
assert not thread._run_done.done()
assert thread.interrupted
assert thread.interrupts[0]["namespace"] == ["sub:1"]
await thread._apply_lifecycle_event(
cast(Event, lifecycle_event(event="completed"))
)
assert thread._run_done.done()
assert (await thread._run_done).error is None
assert not thread.interrupted
assert thread.interrupts == []
@pytest.mark.parametrize("phase", ["completed", "failed"])
def test_sync_child_terminal_does_not_finish_root(phase):
with httpx.Client(base_url="http://test") as raw:
thread = SyncThreadsClient(SyncHttpClient(raw)).stream(
thread_id="t-1", assistant_id="agent"
)
thread._run_done = _BlockingResult()
thread._apply_lifecycle_event(
cast(Event, input_requested_event(namespace=["sub:1"]))
)
thread._apply_lifecycle_event(
cast(Event, lifecycle_event(namespace=["sub:1"], event=phase))
)
assert not thread._run_done.done()
assert thread.interrupted
assert thread.interrupts[0]["namespace"] == ["sub:1"]
thread._apply_lifecycle_event(cast(Event, lifecycle_event(event="completed")))
assert thread._run_done.done()
assert thread._run_done.result().error is None
assert not thread.interrupted
assert thread.interrupts == []
@@ -17,6 +17,7 @@ from streaming._events import (
lifecycle_completed_event,
lifecycle_errored_event,
lifecycle_event,
thread_stream_failed_frame,
)
from streaming._fake_server import FakeServer, _StreamScript
@@ -48,7 +49,7 @@ async def test_interrupts_populated_from_input_requested_event():
async def test_aenter_starts_lifecycle_watcher():
"""Entering AsyncThreadStream opens lifecycle/input SSE before run.start."""
"""Entering with an explicit thread_id opens the lifecycle/input SSE before run.start."""
fake = FakeServer()
fake.script([lifecycle_event(seq=0, phase="started")])
asgi = httpx.ASGITransport(app=fake.app)
@@ -274,3 +275,137 @@ async def test_lifecycle_watcher_reconnects_with_since_after_transport_drop():
assert fake.stream_request_bodies[0]["channels"] == ["lifecycle", "input"]
assert fake.stream_request_bodies[1]["channels"] == ["lifecycle", "input"]
assert fake.stream_request_bodies[1]["since"] == 1
async def test_lifecycle_error_frame_becomes_the_run_error():
fake = FakeServer()
fake.script([lifecycle_event(seq=0, phase="running"), thread_stream_failed_frame()])
async with httpx.AsyncClient(
transport=fake.transport, base_url="http://test"
) as raw:
threads = ThreadsClient(HttpClient(raw))
async with threads.stream(thread_id="t-1", assistant_id="agent") as thread:
run_done = thread._run_done
assert run_done is not None
terminal = await asyncio.wait_for(run_done, timeout=2.0)
assert terminal.status == "errored"
assert (
str(terminal.error)
== "Protocol error [unknown_error]: Thread event stream failed."
)
async def test_lifecycle_watcher_stops_on_a_404_without_retrying():
fake = FakeServer()
fake.script_sequence([_StreamScript(events=[], status=404)])
async with httpx.AsyncClient(
transport=fake.transport, base_url="http://test"
) as raw:
threads = ThreadsClient(HttpClient(raw))
async with threads.stream(thread_id="t-1", assistant_id="agent") as thread:
run_done = thread._run_done
assert run_done is not None
terminal = await asyncio.wait_for(run_done, timeout=2.0)
assert isinstance(terminal.error, httpx.HTTPStatusError)
assert terminal.error.response.status_code == 404
assert len(fake.stream_request_bodies) == 1
async def test_minted_thread_opens_the_lifecycle_stream_after_run_start():
fake = FakeServer()
fake.script([lifecycle_completed_event(seq=1)])
async with httpx.AsyncClient(
transport=fake.transport, base_url="http://test"
) as raw:
threads = ThreadsClient(HttpClient(raw))
async with threads.stream(assistant_id="agent") as thread:
assert thread._lifecycle_watcher_task is None
await thread.run.start(input={})
run_done = thread._run_done
assert run_done is not None
terminal = await asyncio.wait_for(run_done, timeout=2.0)
assert terminal.status == "completed"
def _new_thread_scripts() -> list[_StreamScript]:
return [
_StreamScript(events=[], status=404),
_StreamScript(events=[lifecycle_completed_event(seq=1)]),
]
async def test_run_start_restarts_a_watcher_stopped_by_a_404_for_a_new_thread():
fake = FakeServer()
fake.script_sequence(_new_thread_scripts())
fake.set_state({"ok": True})
async with httpx.AsyncClient(
transport=fake.transport, base_url="http://test"
) as raw:
threads = ThreadsClient(HttpClient(raw))
async with threads.stream(thread_id="t-new", assistant_id="agent") as thread:
rejected = thread._run_done
assert rejected is not None
await asyncio.wait_for(rejected, timeout=2.0)
await thread.run.start(input={})
output = await thread.output.with_timeout(2.0)
assert output == {"ok": True}
assert len(fake.stream_request_bodies) == 2
async def test_lifecycle_watcher_retries_a_404_that_arrives_after_run_start():
fake = FakeServer()
fake.script_sequence(_new_thread_scripts())
fake.set_state({"ok": True})
stream_opened = asyncio.Event()
run_started = asyncio.Event()
async def handle(request: httpx.Request) -> httpx.Response:
if (
request.url.path.endswith("/stream/events")
and not fake.stream_request_bodies
):
stream_opened.set()
await run_started.wait()
return await fake._handle_request(request)
async with httpx.AsyncClient(
transport=httpx.MockTransport(handle), base_url="http://test"
) as raw:
threads = ThreadsClient(HttpClient(raw))
async with threads.stream(thread_id="t-new", assistant_id="agent") as thread:
await asyncio.wait_for(stream_opened.wait(), timeout=2.0)
await thread.run.start(input={})
run_started.set()
output = await thread.output.with_timeout(2.0)
assert output == {"ok": True}
assert len(fake.stream_request_bodies) == 2
async def test_lifecycle_watcher_reports_the_error_frame_when_reconnects_run_out():
fake = FakeServer()
async with httpx.AsyncClient(
transport=fake.transport, base_url="http://test"
) as raw:
threads = ThreadsClient(HttpClient(raw))
stream = threads.stream(thread_id="t-1", assistant_id="agent")
stream._lifecycle_max_reconnect_attempts = 1
fake.script_sequence(
[
_StreamScript(events=[thread_stream_failed_frame()], fail_after=1)
for _ in range(stream._lifecycle_max_reconnect_attempts + 1)
]
)
async with stream as thread:
run_done = thread._run_done
assert run_done is not None
terminal = await asyncio.wait_for(run_done, timeout=2.0)
assert (
str(terminal.error)
== "Protocol error [unknown_error]: Thread event stream failed."
)
@@ -1,11 +1,13 @@
from __future__ import annotations
import asyncio
from collections.abc import AsyncGenerator
from collections.abc import AsyncGenerator, AsyncIterator
from typing import Any, cast
from unittest.mock import MagicMock
import httpx
from websockets.exceptions import ConnectionClosedError
from websockets.frames import Close
from langgraph_sdk._async.http import HttpClient
from langgraph_sdk._async.threads import ThreadsClient
@@ -348,3 +350,66 @@ async def test_send_command_applied_through_seq_seeds_shared_stream_since():
]
assert len(values_requests) == 1
assert values_requests[0]["since"] == 17
async def _drain(events: AsyncIterator[Any]) -> list[Any]:
return [event async for event in events]
async def test_shared_stream_stops_reconnecting_on_a_404():
fake = FakeServer()
fake.script_sequence(
[
_StreamScript(events=[values_event(seq=1)], fail_after=1),
_StreamScript(events=[], status=404),
]
)
async with httpx.AsyncClient(
transport=fake.transport, base_url="http://test"
) as raw:
threads = ThreadsClient(HttpClient(raw))
async with threads.stream(assistant_id="agent") as thread:
received = await asyncio.wait_for(
_drain(thread.subscribe(["values"])), timeout=5.0
)
assert [event["seq"] for event in received] == [1]
assert len(fake.stream_request_bodies) == 2
async def test_shared_stream_does_not_reconnect_after_a_4404_close(
monkeypatch: Any,
) -> None:
opened: list[dict[str, Any]] = []
def open_then_close_with_4404(
_self: ProtocolSseTransport, params: dict[str, Any]
) -> EventStreamHandle:
opened.append(params)
loop = asyncio.get_running_loop()
ready: asyncio.Future[None] = loop.create_future()
ready.set_result(None)
done: asyncio.Future[BaseException | None] = loop.create_future()
done.set_result(ConnectionClosedError(Close(4404, "not found"), None))
async def events() -> AsyncGenerator[Any, None]:
await asyncio.sleep(0)
yield values_event(seq=1)
async def close() -> None:
return None
return EventStreamHandle(events=events(), ready=ready, done=done, close=close)
monkeypatch.setattr(
ProtocolSseTransport, "open_event_stream", open_then_close_with_4404
)
async with httpx.AsyncClient(base_url="http://test") as raw:
threads = ThreadsClient(HttpClient(raw))
async with threads.stream(assistant_id="agent") as thread:
received = await asyncio.wait_for(
_drain(thread.subscribe(["values"])), timeout=5.0
)
assert [event["seq"] for event in received] == [1]
assert len(opened) == 1
@@ -0,0 +1,67 @@
from __future__ import annotations
import httpx
import pytest
from websockets.datastructures import Headers
from websockets.exceptions import ConnectionClosedError, InvalidStatus
from websockets.frames import Close
from websockets.http11 import Response
from langgraph_sdk.stream.errors import (
is_non_retryable_stream_error,
stream_error_frame,
)
def _http_error(status: int) -> httpx.HTTPStatusError:
request = httpx.Request("POST", "http://test/threads/t-1/stream/events")
response = httpx.Response(status, request=request)
return httpx.HTTPStatusError("scripted", request=request, response=response)
def _ws_close(code: int) -> ConnectionClosedError:
return ConnectionClosedError(Close(code, "scripted"), None)
@pytest.mark.parametrize(
("error", "expected"),
[
pytest.param(_http_error(403), True, id="http-403"),
pytest.param(_http_error(404), True, id="http-404"),
pytest.param(_http_error(408), False, id="http-408"),
pytest.param(_http_error(429), False, id="http-429"),
pytest.param(_http_error(503), False, id="http-503"),
pytest.param(_ws_close(4404), True, id="ws-close-4404"),
pytest.param(_ws_close(4422), True, id="ws-close-4422"),
pytest.param(_ws_close(4429), False, id="ws-close-4429"),
pytest.param(_ws_close(4503), False, id="ws-close-4503"),
pytest.param(_ws_close(1011), False, id="ws-close-1011"),
pytest.param(
InvalidStatus(Response(403, "Forbidden", Headers())),
True,
id="ws-upgrade-403",
),
pytest.param(RuntimeError("network down"), False, id="no-status"),
pytest.param(None, False, id="none"),
],
)
def test_is_non_retryable_stream_error(
error: BaseException | None, expected: bool
) -> None:
assert is_non_retryable_stream_error(error) is expected
def test_stream_error_frame_reads_only_unsolicited_error_frames() -> None:
frame = {
"type": "error",
"id": None,
"error": "unknown_error",
"message": "Thread event stream failed.",
}
assert (
str(stream_error_frame(frame))
== "Protocol error [unknown_error]: Thread event stream failed."
)
assert stream_error_frame({**frame, "id": 3}) is None
assert stream_error_frame({"type": "event", "method": "values"}) is None
@@ -35,6 +35,7 @@ from streaming._events import (
message_text_delta_event,
message_text_finish_event,
tasks_start_event,
thread_stream_failed_frame,
tool_finished_event,
tool_started_event,
updates_event,
@@ -868,3 +869,196 @@ def test_interleave_projections_data_channel_scoped_to_root_namespace():
]
assert {"scope": "root"} in checkpoints
assert {"scope": "child"} not in checkpoints
def test_sync_lifecycle_clean_eof_fails_the_run_instead_of_blocking():
fake = SyncFakeServer()
fake.script([lifecycle_started_event(seq=0)])
with httpx.Client(transport=fake.transport, base_url="http://test") as raw:
threads = SyncThreadsClient(SyncHttpClient(raw))
with threads.stream(thread_id="t-1", assistant_id="agent") as thread:
run_done = thread._run_done
assert run_done is not None
terminal = run_done.result(timeout=2.0)
assert terminal.status == "errored"
assert "ended before terminal" in str(terminal.error)
def test_sync_lifecycle_error_frame_becomes_the_run_error():
fake = SyncFakeServer()
fake.script([lifecycle_event(seq=0, phase="running"), thread_stream_failed_frame()])
with httpx.Client(transport=fake.transport, base_url="http://test") as raw:
threads = SyncThreadsClient(SyncHttpClient(raw))
with threads.stream(thread_id="t-1", assistant_id="agent") as thread:
run_done = thread._run_done
assert run_done is not None
terminal = run_done.result(timeout=2.0)
assert terminal.status == "errored"
assert (
str(terminal.error)
== "Protocol error [unknown_error]: Thread event stream failed."
)
def test_sync_lifecycle_watcher_stops_on_a_404_without_retrying():
fake = SyncFakeServer()
fake.script_sequence([SyncStreamScript(events=[], status=404)])
with httpx.Client(transport=fake.transport, base_url="http://test") as raw:
threads = SyncThreadsClient(SyncHttpClient(raw))
with threads.stream(thread_id="t-1", assistant_id="agent") as thread:
run_done = thread._run_done
assert run_done is not None
terminal = run_done.result(timeout=2.0)
assert isinstance(terminal.error, httpx.HTTPStatusError)
assert terminal.error.response.status_code == 404
assert len(fake.stream_request_bodies) == 1
def test_sync_minted_thread_opens_the_lifecycle_stream_after_run_start():
fake = SyncFakeServer()
fake.script([lifecycle_completed_event(seq=1)])
with httpx.Client(transport=fake.transport, base_url="http://test") as raw:
threads = SyncThreadsClient(SyncHttpClient(raw))
with threads.stream(assistant_id="agent") as thread:
assert thread._lifecycle_watcher_thread is None
thread.run.start(input={})
run_done = thread._run_done
assert run_done is not None
terminal = run_done.result(timeout=2.0)
assert terminal.status == "completed"
def _sync_new_thread_scripts() -> list[SyncStreamScript]:
return [
SyncStreamScript(events=[], status=404),
SyncStreamScript(events=[lifecycle_completed_event(seq=1)]),
]
def test_sync_run_start_restarts_a_watcher_stopped_by_a_404_for_a_new_thread():
fake = SyncFakeServer()
fake.script_sequence(_sync_new_thread_scripts())
fake.set_state({"ok": True})
with httpx.Client(transport=fake.transport, base_url="http://test") as raw:
threads = SyncThreadsClient(SyncHttpClient(raw))
with threads.stream(thread_id="t-new", assistant_id="agent") as thread:
rejected = thread._run_done
assert rejected is not None
rejected.result(timeout=2.0)
thread.run.start(input={})
output = thread.output
assert output == {"ok": True}
assert len(fake.stream_request_bodies) == 2
def test_sync_lifecycle_watcher_retries_a_404_that_arrives_after_run_start():
fake = SyncFakeServer()
fake.script_sequence(_sync_new_thread_scripts())
fake.set_state({"ok": True})
stream_opened = threading.Event()
run_started = threading.Event()
def handle(request: httpx.Request) -> httpx.Response:
if (
request.url.path.endswith("/stream/events")
and not fake.stream_request_bodies
):
stream_opened.set()
run_started.wait(timeout=2.0)
return fake._handle(request)
with httpx.Client(
transport=httpx.MockTransport(handle), base_url="http://test"
) as raw:
threads = SyncThreadsClient(SyncHttpClient(raw))
with threads.stream(thread_id="t-new", assistant_id="agent") as thread:
assert stream_opened.wait(timeout=2.0)
thread.run.start(input={})
run_started.set()
output = thread.output
assert output == {"ok": True}
assert len(fake.stream_request_bodies) == 2
def test_sync_lifecycle_watcher_reports_the_error_frame_when_reconnects_run_out():
fake = SyncFakeServer()
with httpx.Client(transport=fake.transport, base_url="http://test") as raw:
threads = SyncThreadsClient(SyncHttpClient(raw))
stream = threads.stream(thread_id="t-1", assistant_id="agent")
stream._lifecycle_max_reconnect_attempts = 1
fake.script_sequence(
[
SyncStreamScript(events=[thread_stream_failed_frame()], fail_after=1)
for _ in range(stream._lifecycle_max_reconnect_attempts + 1)
]
)
with stream as thread:
run_done = thread._run_done
assert run_done is not None
terminal = run_done.result(timeout=2.0)
assert (
str(terminal.error)
== "Protocol error [unknown_error]: Thread event stream failed."
)
def test_sync_concurrent_run_starts_start_one_lifecycle_watcher(monkeypatch):
real_thread = threading.Thread
created_watchers: list[str] = []
class _SlowToCreateThread(real_thread):
def __init__(self, *args: Any, **kwargs: Any) -> None:
if kwargs.get("name") == "langgraph-sdk-sync-lifecycle":
created_watchers.append(kwargs["name"])
time.sleep(0.05)
super().__init__(*args, **kwargs)
monkeypatch.setattr(threading, "Thread", _SlowToCreateThread)
fake = SyncFakeServer()
fake.script([lifecycle_completed_event(seq=1)])
with httpx.Client(transport=fake.transport, base_url="http://test") as raw:
threads = SyncThreadsClient(SyncHttpClient(raw))
with threads.stream(assistant_id="agent") as thread:
barrier = threading.Barrier(2)
def start_run() -> None:
barrier.wait()
thread.run.start(input={})
runners = [real_thread(target=start_run) for _ in range(2)]
for runner in runners:
runner.start()
for runner in runners:
runner.join()
assert len(created_watchers) == 1
def test_sync_reconnect_stops_on_a_404(monkeypatch):
monkeypatch.setattr(_ctrl_mod.time, "sleep", lambda _delay: None)
opened: list[dict[str, Any]] = []
class _NotFoundTransport(SyncProtocolSseTransport):
def open_event_stream(self, params: dict) -> SyncEventStreamHandle:
opened.append(params)
request = httpx.Request("POST", "http://test/threads/t-1/stream/events")
raise httpx.HTTPStatusError(
"not found",
request=request,
response=httpx.Response(404, request=request),
)
with httpx.Client(base_url="http://test") as raw:
transport = _NotFoundTransport(client=raw, thread_id="t-1")
controller = SyncStreamController(transport, max_reconnect_attempts=5)
controller._shared_stream_filter = {"channels": ["values"]}
assert controller._reconnect_shared_stream() is False
assert len(opened) == 1
+5 -5
View File
@@ -1,5 +1,5 @@
version = 1
revision = 3
revision = 5
requires-python = ">=3.10"
resolution-markers = [
"python_full_version >= '3.15'",
@@ -301,7 +301,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "1.2.15"
version = "1.2.16"
source = { editable = "../langgraph" }
dependencies = [
{ name = "langchain-core" },
@@ -433,7 +433,7 @@ test = [
[[package]]
name = "langgraph-prebuilt"
version = "1.1.0"
version = "1.1.1"
source = { editable = "../prebuilt" }
dependencies = [
{ name = "langchain-core" },
@@ -454,7 +454,7 @@ dev = [
{ name = "langgraph-checkpoint", editable = "../checkpoint" },
{ name = "langgraph-checkpoint-postgres", editable = "../checkpoint-postgres" },
{ name = "langgraph-checkpoint-sqlite", editable = "../checkpoint-sqlite" },
{ name = "psycopg-binary" },
{ name = "psycopg", extras = ["binary"] },
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "pytest-mock" },
@@ -474,7 +474,7 @@ test = [
{ name = "langgraph-checkpoint", editable = "../checkpoint" },
{ name = "langgraph-checkpoint-postgres", editable = "../checkpoint-postgres" },
{ name = "langgraph-checkpoint-sqlite", editable = "../checkpoint-sqlite" },
{ name = "psycopg-binary" },
{ name = "psycopg", extras = ["binary"] },
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "pytest-mock" },