mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-29 19:29:43 +02:00
Bumps the minor-and-patch group in /libs/sdk-py with 9 updates: | Package | From | To | | --- | --- | --- | | [orjson](https://github.com/ijl/orjson) | `3.11.8` | `3.11.9` | | [langchain-protocol](https://github.com/langchain-ai/agent-protocol) | `0.0.15` | `0.0.18` | | [langchain-core](https://github.com/langchain-ai/langchain) | `1.4.7` | `1.4.8` | | [pytest](https://github.com/pytest-dev/pytest) | `9.0.3` | `9.1.1` | | [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) | `1.3.0` | `1.4.0` | | [ruff](https://github.com/astral-sh/ruff) | `0.15.12` | `0.15.20` | | [ty](https://github.com/astral-sh/ty) | `0.0.43` | `0.0.55` | | [pydantic](https://github.com/pydantic/pydantic) | `2.13.3` | `2.13.4` | | [xxhash](https://github.com/ifduyue/python-xxhash) | `3.6.0` | `3.8.0` | Updates `orjson` from 3.11.8 to 3.11.9 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/ijl/orjson/releases">orjson's releases</a>.</em></p> <blockquote> <h2>3.11.9</h2> <h3>Changed</h3> <ul> <li>Build now depends on Rust 1.95 or later instead of 1.89.</li> </ul> <h3>Fixed</h3> <ul> <li>Fix building on Rust 1.95.</li> </ul> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/ijl/orjson/blob/master/CHANGELOG.md">orjson's changelog</a>.</em></p> <blockquote> <h2>3.11.9 - 2026-05-06</h2> <h3>Changed</h3> <ul> <li>Build now depends on Rust 1.95 or later instead of 1.89.</li> </ul> <h3>Fixed</h3> <ul> <li>Fix building on Rust 1.95.</li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/ijl/orjson/commit/705515d77b28429d0b7c30c3d781abe52e8a1e5a"><code>705515d</code></a> 3.11.9</li> <li><a href="https://github.com/ijl/orjson/commit/d19055d5bab432f98d53b71606a9c6c23fb21bf6"><code>d19055d</code></a> build update</li> <li><a href="https://github.com/ijl/orjson/commit/77e2d96c3febe099cde2447856fe2523d68c71b0"><code>77e2d96</code></a> MSRV 1.95, remove compiler feature detection</li> <li>See full diff in <a href="https://github.com/ijl/orjson/compare/3.11.8...3.11.9">compare view</a></li> </ul> </details> <br /> Updates `langchain-protocol` from 0.0.15 to 0.0.18 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/langchain-ai/agent-protocol/releases">langchain-protocol's releases</a>.</em></p> <blockquote> <h2>langchain-protocol==0.0.18</h2> <h2>What's Changed</h2> <ul> <li>feat(protocol): allow update and goto on input.respond by <a href="https://github.com/christian-bromann"><code>@christian-bromann</code></a> in <a href="https://redirect.github.com/langchain-ai/agent-protocol/pull/88">langchain-ai/agent-protocol#88</a></li> <li>v0.0.18 by <a href="https://github.com/christian-bromann"><code>@christian-bromann</code></a> in <a href="https://redirect.github.com/langchain-ai/agent-protocol/pull/89">langchain-ai/agent-protocol#89</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/langchain-ai/agent-protocol/compare/langchain-protocol==0.0.17...langchain-protocol==0.0.18">https://github.com/langchain-ai/agent-protocol/compare/langchain-protocol==0.0.17...langchain-protocol==0.0.18</a></p> <h2>langchain-protocol==0.0.17</h2> <h2>What's Changed</h2> <ul> <li>Bump idna from 3.11 to 3.15 in /tooling in the uv group across 1 directory by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/langchain-ai/agent-protocol/pull/85">langchain-ai/agent-protocol#85</a></li> <li>feat: add token-detail breakdowns to UsageInfo by <a href="https://github.com/nick-hollon-lc"><code>@nick-hollon-lc</code></a> in <a href="https://redirect.github.com/langchain-ai/agent-protocol/pull/87">langchain-ai/agent-protocol#87</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/langchain-ai/agent-protocol/compare/langchain-protocol==0.0.16...langchain-protocol==0.0.17">https://github.com/langchain-ai/agent-protocol/compare/langchain-protocol==0.0.16...langchain-protocol==0.0.17</a></p> <h2>langchain-protocol==0.0.16</h2> <h2>What's Changed</h2> <ul> <li>chore: bump <code>typing-extensions</code> floor from <code>>=4.7.0</code> to <code>>=4.13.0</code> by <a href="https://github.com/mdrxy"><code>@mdrxy</code></a> in <a href="https://redirect.github.com/langchain-ai/agent-protocol/pull/84">langchain-ai/agent-protocol#84</a></li> <li>feat(protocol): add batch resume for parallel interrupts by <a href="https://github.com/christian-bromann"><code>@christian-bromann</code></a> in <a href="https://redirect.github.com/langchain-ai/agent-protocol/pull/86">langchain-ai/agent-protocol#86</a></li> </ul> <h2>New Contributors</h2> <ul> <li><a href="https://github.com/mdrxy"><code>@mdrxy</code></a> made their first contribution in <a href="https://redirect.github.com/langchain-ai/agent-protocol/pull/84">langchain-ai/agent-protocol#84</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/langchain-ai/agent-protocol/compare/langchain-protocol==0.0.15...langchain-protocol==0.0.16">https://github.com/langchain-ai/agent-protocol/compare/langchain-protocol==0.0.15...langchain-protocol==0.0.16</a></p> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/langchain-ai/agent-protocol/commit/0ff7cd3962e8b4b3e347b76203be7dfeba003928"><code>0ff7cd3</code></a> v0.0.18 (<a href="https://redirect.github.com/langchain-ai/agent-protocol/issues/89">#89</a>)</li> <li><a href="https://github.com/langchain-ai/agent-protocol/commit/aae0e86f0daecac8867b90f2a68567641926ea3c"><code>aae0e86</code></a> feat(protocol): allow update and goto on input.respond (<a href="https://redirect.github.com/langchain-ai/agent-protocol/issues/88">#88</a>)</li> <li><a href="https://github.com/langchain-ai/agent-protocol/commit/c07051e7239028ee37c52b36641945c35022babc"><code>c07051e</code></a> Merge pull request <a href="https://redirect.github.com/langchain-ai/agent-protocol/issues/87">#87</a> from langchain-ai/nh/usage-token-details</li> <li><a href="https://github.com/langchain-ai/agent-protocol/commit/44d72d669bc35aad736cf557989136d93a0d297e"><code>44d72d6</code></a> chore: bump <code>@langchain/protocol</code> (js) to 0.0.17</li> <li><a href="https://github.com/langchain-ai/agent-protocol/commit/22ca3bfb68b8c7a98b3f6c56a455da111b49728e"><code>22ca3bf</code></a> feat: add token-detail breakdowns to UsageInfo</li> <li><a href="https://github.com/langchain-ai/agent-protocol/commit/0d511c82fc0e14f719b22da7b4905dd0e6b95788"><code>0d511c8</code></a> Merge pull request <a href="https://redirect.github.com/langchain-ai/agent-protocol/issues/85">#85</a> from langchain-ai/dependabot/uv/tooling/uv-d665ee01e3</li> <li><a href="https://github.com/langchain-ai/agent-protocol/commit/8246ac0b6c2db63fc3d9ffb257faa9325d49c8d2"><code>8246ac0</code></a> feat(protocol): add batch resume for parallel interrupts (<a href="https://redirect.github.com/langchain-ai/agent-protocol/issues/86">#86</a>)</li> <li><a href="https://github.com/langchain-ai/agent-protocol/commit/298e6c86564a84d847b70ba892aed2f045a85b2e"><code>298e6c8</code></a> Bump idna in /tooling in the uv group across 1 directory</li> <li><a href="https://github.com/langchain-ai/agent-protocol/commit/0f896ba72976145b0f98af1ddc25c3baaf652f1a"><code>0f896ba</code></a> Merge pull request <a href="https://redirect.github.com/langchain-ai/agent-protocol/issues/84">#84</a> from langchain-ai/mdrxy/bump-dep</li> <li><a href="https://github.com/langchain-ai/agent-protocol/commit/2e890bb0fb4887eb5f5f3e8ce21bc77f4cad2659"><code>2e890bb</code></a> chore: bump <code>typing-extensions</code> floor from <code>>=4.7.0</code> to <code>>=4.13.0</code></li> <li>See full diff in <a href="https://github.com/langchain-ai/agent-protocol/compare/langchain-protocol==0.0.15...langchain-protocol==0.0.18">compare view</a></li> </ul> </details> <br /> Updates `langchain-core` from 1.4.7 to 1.4.8 <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.4.8</h2> <p>Changes since langchain-core==1.4.7</p> <p>chore: bump jupyter-server from 2.18.0 to 2.20.0 in /libs/core (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38252">#38252</a>) chore: bump tornado from 6.5.6 to 6.5.7 in /libs/core (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38184">#38184</a>) chore: bump bleach from 6.3.0 to 6.4.0 in /libs/core (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38198">#38198</a>) release(core): 1.4.8 (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38254">#38254</a>) refactor(langchain-classic): remove code for Python < 3.10 (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38194">#38194</a>) perf(core): memoize <code>BaseTool.tool_call_schema</code> subset model and cache <code>model_json_schema</code> (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38073">#38073</a>) style(core): fix style in <code>langchain_core</code>/<code>_security</code> (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38189">#38189</a>) fix(core): preserve usage token details in v3 streaming events (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38021">#38021</a>) fix(core): <code>disallow_any_generics</code> (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38156">#38156</a>) chore(core): add mypy <code>warn_unreachable</code> (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38109">#38109</a>) docs: refresh <code>README</code> installation and resources (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38119">#38119</a>) test(core,langchain): update tests for explicit deserialization allowlists (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38118">#38118</a>)</p> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/langchain-ai/langchain/commit/15b0a4930b5306bb2174f16d92fe4b7837147d0a"><code>15b0a49</code></a> chore: bump jupyter-server from 2.18.0 to 2.20.0 in /libs/core (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38252">#38252</a>)</li> <li><a href="https://github.com/langchain-ai/langchain/commit/612139f3af5f55b6311695fe6b139f0b6a6f68a2"><code>612139f</code></a> chore: bump tornado from 6.5.6 to 6.5.7 in /libs/text-splitters (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38175">#38175</a>)</li> <li><a href="https://github.com/langchain-ai/langchain/commit/921e370a67af4254b948de94c4db4587f4716a84"><code>921e370</code></a> chore: bump cryptography from 46.0.7 to 48.0.1 in /libs/langchain_v1 (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38176">#38176</a>)</li> <li><a href="https://github.com/langchain-ai/langchain/commit/1aabc26836e0c1220121a1f37e3a2691e20f2f19"><code>1aabc26</code></a> chore: bump aiohttp from 3.14.0 to 3.14.1 in /libs/langchain_v1 (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38179">#38179</a>)</li> <li><a href="https://github.com/langchain-ai/langchain/commit/8d51355f1f81825b72cb55303a39825ecbdc6b44"><code>8d51355</code></a> chore: bump aiohttp from 3.14.0 to 3.14.1 in /libs/langchain (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38180">#38180</a>)</li> <li><a href="https://github.com/langchain-ai/langchain/commit/0b1b7bb77a7e8cf98071e088da777ff52558f771"><code>0b1b7bb</code></a> chore: bump cryptography from 46.0.7 to 48.0.1 in /libs/langchain (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38181">#38181</a>)</li> <li><a href="https://github.com/langchain-ai/langchain/commit/dfd062742271ad693c8bebe4ff1784cc4f408ade"><code>dfd0627</code></a> chore: bump starlette from 1.0.1 to 1.3.1 in /libs/langchain (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38182">#38182</a>)</li> <li><a href="https://github.com/langchain-ai/langchain/commit/0269392514c41e44bfa0413c3f550c06e46d2e1e"><code>0269392</code></a> chore: bump tornado from 6.5.6 to 6.5.7 in /libs/langchain (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38183">#38183</a>)</li> <li><a href="https://github.com/langchain-ai/langchain/commit/24d0b3791af52796b3fe5330f38e99b89afda4b1"><code>24d0b37</code></a> chore: bump tornado from 6.5.6 to 6.5.7 in /libs/core (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38184">#38184</a>)</li> <li><a href="https://github.com/langchain-ai/langchain/commit/f368854ec388f0a8e98b9e77aa8da83ebad1b978"><code>f368854</code></a> chore: bump bleach from 6.2.0 to 6.4.0 in /libs/text-splitters (<a href="https://redirect.github.com/langchain-ai/langchain/issues/38195">#38195</a>)</li> <li>Additional commits viewable in <a href="https://github.com/langchain-ai/langchain/compare/langchain-core==1.4.7...langchain-core==1.4.8">compare view</a></li> </ul> </details> <br /> Updates `pytest` from 9.0.3 to 9.1.1 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/pytest-dev/pytest/releases">pytest's releases</a>.</em></p> <blockquote> <h2>9.1.1</h2> <h1>pytest 9.1.1 (2026-06-19)</h1> <h2>Bug fixes</h2> <ul> <li><a href="https://redirect.github.com/pytest-dev/pytest/issues/14220">#14220</a>: Fixed a logic bug in <code>pytest.RaisesGroup</code> which would might cause it to display incorrect "It matches <!-- raw HTML omitted -->FooError()<!-- raw HTML omitted --> which was paired with <!-- raw HTML omitted -->BarError<!-- raw HTML omitted -->" messages.</li> <li><a href="https://redirect.github.com/pytest-dev/pytest/issues/14591">#14591</a>: Fixed a regression in pytest 9.1.0 which caused overriding a parametrized fixture with an indirect <!-- raw HTML omitted --><a href="https://github.com/pytest"><code>@pytest</code></a>.mark.parametrize<!-- raw HTML omitted --> to fail with "duplicate parametrization of '<fixture name>'".</li> <li><a href="https://redirect.github.com/pytest-dev/pytest/issues/14606">#14606</a>: Fixed <code>list-item</code> typing errors from mypy in <code>@pytest.mark.parametrize <pytest.mark.parametrize ref></code> <code>argvalues</code> parameter.</li> <li><a href="https://redirect.github.com/pytest-dev/pytest/issues/14608">#14608</a>: Fixed a regression in pytest 9.1.0 where <code>conftest.py</code> files located in <code><invocation dir>/test*</code> were no longer loaded as initial conftests when invoked without arguments. This could cause certain hooks (like <code>pytest_addoption</code>) in these files to not fire.</li> </ul> <h2>9.1.0</h2> <h1>pytest 9.1.0 (2026-06-13)</h1> <h2>Removals and backward incompatible breaking changes</h2> <ul> <li> <p><a href="https://redirect.github.com/pytest-dev/pytest/issues/14533">#14533</a>: When using <code>--doctest-modules</code>, autouse fixtures with <code>module</code>, <code>package</code> or <code>session</code> scope that are defined inline in Python test modules (not plugins or conftests) will now possibly execute twice.</p> <p>If this is undesirable, move the fixture definition to a <code>conftest.py</code> file if possible.</p> <p>Technical explanation for those interested: When using <!-- raw HTML omitted -->--doctest-modules<!-- raw HTML omitted -->, pytest possibly collects Python modules twice, once as <code>pytest.Module</code> and once as a <code>DoctestModule</code> (depending on the configuration). Due to improvements in pytest's fixture implementation, if e.g. the <code>DoctestModule</code> collects a fixture, it is now visible to it only, and not to the <code>Module</code>. This means that both need to register the fixtures independently.</p> </li> </ul> <h2>Deprecations (removal in next major release)</h2> <ul> <li> <p><a href="https://redirect.github.com/pytest-dev/pytest/issues/10819">#10819</a>: Added a deprecation warning for class-scoped fixtures defined as instance methods (without <code>@classmethod</code>). Such fixtures set attributes on a different instance than the test methods use, leading to unexpected behavior. Use <code>@classmethod</code> decorator instead -- by <code>yastcher</code>.</p> <p>See <code>10819</code> and <code>14011</code>.</p> </li> <li> <p><a href="https://redirect.github.com/pytest-dev/pytest/issues/12882">#12882</a>: Calling <code>request.getfixturevalue() <pytest.FixtureRequest.getfixturevalue></code> during teardown to request a fixture that was not already requested is now deprecated and will become an error in pytest 10.</p> <p>See <code>dynamic-fixture-request-during-teardown</code> for details.</p> </li> <li> <p><a href="https://redirect.github.com/pytest-dev/pytest/issues/13409">#13409</a>: Using non-<code>~collections.abc.Collection</code> iterables (such as generators, iterators, or custom iterable objects) for the <code>argvalues</code> parameter in <code>@pytest.mark.parametrize <pytest.mark.parametrize ref></code> and <code>metafunc.parametrize <pytest.Metafunc.parametrize></code> is now deprecated.</p> <p>These iterables get exhausted after the first iteration, leading to tests getting unexpectedly skipped in cases such as running <code>pytest.main()</code> multiple times, using class-level parametrize decorators, or collecting tests multiple times.</p> <p>See <code>parametrize-iterators</code> for details and suggestions.</p> </li> <li> <p><a href="https://redirect.github.com/pytest-dev/pytest/issues/13946">#13946</a>: The private <code>config.inicfg</code> attribute is now deprecated. Use <code>config.getini() <pytest.Config.getini></code> to access configuration values instead.</p> <p>See <code>config-inicfg</code> for more details.</p> </li> <li> <p><a href="https://redirect.github.com/pytest-dev/pytest/issues/14004">#14004</a>: Passing <code>baseid</code> to <code>~pytest.FixtureDef</code> or <code>nodeid</code> strings to fixture registration APIs is now deprecated. These are internal pytest APIs that are used by some plugins.</p> </li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/pytest-dev/pytest/commit/cf470ec0bf7eb89cd97dd56df4859eae5db46447"><code>cf470ec</code></a> Prepare release version 9.1.1</li> <li><a href="https://github.com/pytest-dev/pytest/commit/e0c8ce6cc5db1f08363be6f152c32e6838df2690"><code>e0c8ce6</code></a> Merge pull request <a href="https://redirect.github.com/pytest-dev/pytest/issues/14625">#14625</a> from pytest-dev/patchback/backports/9.1.x/a07c31a97...</li> <li><a href="https://github.com/pytest-dev/pytest/commit/1b82d1694fce22385ee7a4287917fbafbaf2e757"><code>1b82d16</code></a> Merge pull request <a href="https://redirect.github.com/pytest-dev/pytest/issues/14624">#14624</a> from pytest-dev/patchback/backports/9.1.x/b375b79ec...</li> <li><a href="https://github.com/pytest-dev/pytest/commit/501c4bc784da3b08bfcaa64858eba5d15dc59e53"><code>501c4bc</code></a> Merge pull request <a href="https://redirect.github.com/pytest-dev/pytest/issues/14596">#14596</a> from bluetech/doc-classmethod</li> <li><a href="https://github.com/pytest-dev/pytest/commit/b61f588e36e9377c3d1d3f06bece1da0fc31d9ca"><code>b61f588</code></a> Merge pull request <a href="https://redirect.github.com/pytest-dev/pytest/issues/14622">#14622</a> from chrisburr/fix-14608-initial-conftest-test-subdir</li> <li><a href="https://github.com/pytest-dev/pytest/commit/9a567e009f4d2da3ce1721c6db3109cb5744d40a"><code>9a567e0</code></a> [automated] Update plugin list (<a href="https://redirect.github.com/pytest-dev/pytest/issues/14617">#14617</a>) (<a href="https://redirect.github.com/pytest-dev/pytest/issues/14618">#14618</a>)</li> <li><a href="https://github.com/pytest-dev/pytest/commit/ef8b2993e5b48639e4a3d97d0525df9760781384"><code>ef8b299</code></a> Merge pull request <a href="https://redirect.github.com/pytest-dev/pytest/issues/14620">#14620</a> from pytest-dev/patchback/backports/9.1.x/680f9f3ed...</li> <li><a href="https://github.com/pytest-dev/pytest/commit/66abd0784d4cb7c1ba44ab9a8896506cd4985acc"><code>66abd07</code></a> Merge pull request <a href="https://redirect.github.com/pytest-dev/pytest/issues/14220">#14220</a> from bysiber/fix-stale-iexp-raisesgroup</li> <li><a href="https://github.com/pytest-dev/pytest/commit/79fbf93b666cac5f27c9dad047943d47b766c8d5"><code>79fbf93</code></a> Merge pull request <a href="https://redirect.github.com/pytest-dev/pytest/issues/14612">#14612</a> from pytest-dev/patchback/backports/9.1.x/974ed48b6...</li> <li><a href="https://github.com/pytest-dev/pytest/commit/0d312eb876177e9f1c04262b54060a41034ebf5c"><code>0d312eb</code></a> Merge pull request <a href="https://redirect.github.com/pytest-dev/pytest/issues/14611">#14611</a> from bluetech/parametrize-argvalues-typing</li> <li>Additional commits viewable in <a href="https://github.com/pytest-dev/pytest/compare/9.0.3...9.1.1">compare view</a></li> </ul> </details> <br /> Updates `pytest-asyncio` from 1.3.0 to 1.4.0 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/pytest-dev/pytest-asyncio/releases">pytest-asyncio's releases</a>.</em></p> <blockquote> <h2>pytest-asyncio v1.4.0</h2> <h1><a href="https://github.com/pytest-dev/pytest-asyncio/tree/1.4.0">1.4.0</a> - 2026-05-26</h1> <h2>Deprecated</h2> <ul> <li>Overriding the <em>event_loop_policy</em> fixture is deprecated. Use the <code>pytest_asyncio_loop_factories</code> hook instead. (<a href="https://redirect.github.com/pytest-dev/pytest-asyncio/issues/1419">#1419</a>)</li> </ul> <h2>Added</h2> <ul> <li> <p>Added the <code>pytest_asyncio_loop_factories</code> hook to parametrize asyncio tests with custom event loop factories.</p> <p>The hook returns a mapping of factory names to loop factories, and <code>pytest.mark.asyncio(loop_factories=[...])</code> selects a subset of configured factories per test. When a single factory is configured, test names are unchanged.</p> <p>Synchronous <code>@pytest_asyncio.fixture</code> functions now see the correct event loop when custom loop factories are configured, even when test code disrupts the current event loop (e.g., via <code>asyncio.run()</code> or <code>asyncio.set_event_loop(None)</code>). (<a href="https://redirect.github.com/pytest-dev/pytest-asyncio/issues/1164">#1164</a>)</p> </li> </ul> <h2>Changed</h2> <ul> <li>Improved the readability of the warning message that is displayed when <code>asyncio_default_fixture_loop_scope</code> is unset (<a href="https://redirect.github.com/pytest-dev/pytest-asyncio/issues/1298">#1298</a>)</li> <li>Only import <code>asyncio.AbstractEventLoopPolicy</code> for type checking to avoid raising a DeprecationWarning. (<a href="https://redirect.github.com/pytest-dev/pytest-asyncio/issues/1394">#1394</a>)</li> <li>Updated minimum supported pytest version to v8.4.0. (<a href="https://redirect.github.com/pytest-dev/pytest-asyncio/issues/1397">#1397</a>)</li> </ul> <h2>Fixed</h2> <ul> <li>Fixed a <code>ResourceWarning: unclosed event loop</code> warning that could occur when a synchronous test called <code>asyncio.run()</code> or otherwise unset the current event loop after pytest-asyncio had run an async test or fixture. (<a href="https://redirect.github.com/pytest-dev/pytest-asyncio/issues/724">#724</a>)</li> </ul> <h2>Notes for Downstream Packagers</h2> <ul> <li>Added dependency on <code>sphinx-tabs >= 3.5</code> to organize documentation examples into tabs. (<a href="https://redirect.github.com/pytest-dev/pytest-asyncio/issues/1395">#1395</a>)</li> </ul> <h2>pytest-asyncio v1.4.0a2</h2> <h1><a href="https://github.com/pytest-dev/pytest-asyncio/tree/1.4.0a2">1.4.0a2</a> - 2026-05-02</h1> <h2>Deprecated</h2> <ul> <li>Overriding the <em>event_loop_policy</em> fixture is deprecated. Use the <code>pytest_asyncio_loop_factories</code> hook instead. (<a href="https://redirect.github.com/pytest-dev/pytest-asyncio/issues/1419">#1419</a>)</li> </ul> <h2>Added</h2> <ul> <li> <p>Added the <code>pytest_asyncio_loop_factories</code> hook to parametrize asyncio tests with custom event loop factories.</p> <p>The hook returns a mapping of factory names to loop factories, and <code>pytest.mark.asyncio(loop_factories=[...])</code> selects a subset of configured factories per test. When a single factory is configured, test names are unchanged on pytest 8.4+.</p> <p>Synchronous <code>@pytest_asyncio.fixture</code> functions now see the correct event loop when custom loop factories are configured, even when test code disrupts the current event loop (e.g., via <code>asyncio.run()</code> or <code>asyncio.set_event_loop(None)</code>). (<a href="https://redirect.github.com/pytest-dev/pytest-asyncio/issues/1164">#1164</a>)</p> </li> </ul> <h2>Changed</h2> <ul> <li>Improved the readability of the warning message that is displayed when <code>asyncio_default_fixture_loop_scope</code> is unset (<a href="https://redirect.github.com/pytest-dev/pytest-asyncio/issues/1298">#1298</a>)</li> <li>Only import <code>asyncio.AbstractEventLoopPolicy</code> for type checking to avoid raising a DeprecationWarning. (<a href="https://redirect.github.com/pytest-dev/pytest-asyncio/issues/1394">#1394</a>)</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/pytest-dev/pytest-asyncio/commit/6e14cd2af9292dca1fa2b027a06bbc40b0e0e425"><code>6e14cd2</code></a> chore: Prepare release of v1.4.0.</li> <li><a href="https://github.com/pytest-dev/pytest-asyncio/commit/4b900fb5d0c30949c574e55dd904ee179f858a5e"><code>4b900fb</code></a> Build(deps): Bump codecov/codecov-action from 6.0.0 to 6.0.1</li> <li><a href="https://github.com/pytest-dev/pytest-asyncio/commit/ab9f63245094865c42c940a34af724b0dec1debf"><code>ab9f632</code></a> Build(deps): Bump zipp from 3.23.1 to 4.1.0</li> <li><a href="https://github.com/pytest-dev/pytest-asyncio/commit/a56fc77ecd59f781d8471b0f6a82bf58e08c95fa"><code>a56fc77</code></a> Build(deps): Bump hypothesis from 6.152.6 to 6.152.8</li> <li><a href="https://github.com/pytest-dev/pytest-asyncio/commit/e8bae9bc1f197731fc1a210c0da557af7b698e6d"><code>e8bae9b</code></a> Build(deps): Bump requests from 2.34.0 to 2.34.2</li> <li><a href="https://github.com/pytest-dev/pytest-asyncio/commit/fc433402c570fd36a7a227ef4bc3abd4579299de"><code>fc43340</code></a> Build(deps): Bump idna from 3.14 to 3.15</li> <li><a href="https://github.com/pytest-dev/pytest-asyncio/commit/762eaf5033b798b965c92afdbb2cebefa8fc3a8b"><code>762eaf5</code></a> Build(deps): Bump jaraco-functools from 4.4.0 to 4.5.0</li> <li><a href="https://github.com/pytest-dev/pytest-asyncio/commit/b62e2228c80070977baf6b77ba89d5c148af920f"><code>b62e222</code></a> Build(deps): Bump click from 8.3.3 to 8.4.0</li> <li><a href="https://github.com/pytest-dev/pytest-asyncio/commit/919044700627889d25ca63b6e7a3bc785f3137eb"><code>9190447</code></a> Build(deps): Bump pydantic from 2.13.3 to 2.13.4</li> <li><a href="https://github.com/pytest-dev/pytest-asyncio/commit/82a393c5e31b6ebbbd8ec2a8dafc5f35b9cf1236"><code>82a393c</code></a> ci: Remove unnecessary debug output.</li> <li>Additional commits viewable in <a href="https://github.com/pytest-dev/pytest-asyncio/compare/v1.3.0...v1.4.0">compare view</a></li> </ul> </details> <br /> Updates `ruff` from 0.15.12 to 0.15.20 <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.15.20</h2> <h2>Release Notes</h2> <p>Released on 2026-06-25.</p> <h3>Preview features</h3> <ul> <li>Allow human-readable names in rule selectors (<a href="https://redirect.github.com/astral-sh/ruff/pull/25887">#25887</a>)</li> <li>Emit a warning instead of an error for unknown rule selectors (<a href="https://redirect.github.com/astral-sh/ruff/pull/26113">#26113</a>)</li> <li>Match <code>noqa</code> shebang handling in <code>ruff:ignore</code> comments (<a href="https://redirect.github.com/astral-sh/ruff/pull/26286">#26286</a>)</li> <li>[<code>ruff</code>] Remove <code>pytest-fixture-autouse</code> (<code>RUF076</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/26240">#26240</a>, <a href="https://redirect.github.com/astral-sh/ruff/pull/26371">#26371</a>)</li> </ul> <h3>Documentation</h3> <ul> <li>Add versioning sections to custom crate READMEs (<a href="https://redirect.github.com/astral-sh/ruff/pull/26317">#26317</a>)</li> <li>Update <code>ruff_python_parser</code> README for crates.io (<a href="https://redirect.github.com/astral-sh/ruff/pull/26315">#26315</a>)</li> <li>[<code>perflint</code>] Clarify that <code>PERF402</code> applies to any iterable (<a href="https://redirect.github.com/astral-sh/ruff/pull/26242">#26242</a>)</li> </ul> <h3>Contributors</h3> <ul> <li><a href="https://github.com/dhruvmanila"><code>@dhruvmanila</code></a></li> <li><a href="https://github.com/MichaReiser"><code>@MichaReiser</code></a></li> <li><a href="https://github.com/ntBre"><code>@ntBre</code></a></li> <li><a href="https://github.com/trilamsr"><code>@trilamsr</code></a></li> </ul> <h2>Install ruff 0.15.20</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.15.20/ruff-installer.sh | sh </code></pre> <h3>Install prebuilt binaries via powershell script</h3> <pre lang="sh"><code>powershell -ExecutionPolicy Bypass -c "irm https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-installer.ps1 | iex" </code></pre> <h2>Download ruff 0.15.20</h2> <table> <thead> <tr> <th>File</th> <th>Platform</th> <th>Checksum</th> </tr> </thead> <tbody> <tr> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-aarch64-apple-darwin.tar.gz">ruff-aarch64-apple-darwin.tar.gz</a></td> <td>Apple Silicon macOS</td> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-aarch64-apple-darwin.tar.gz.sha256">checksum</a></td> </tr> <tr> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-x86_64-apple-darwin.tar.gz">ruff-x86_64-apple-darwin.tar.gz</a></td> <td>Intel macOS</td> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-x86_64-apple-darwin.tar.gz.sha256">checksum</a></td> </tr> <tr> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-aarch64-pc-windows-msvc.zip">ruff-aarch64-pc-windows-msvc.zip</a></td> <td>ARM64 Windows</td> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-aarch64-pc-windows-msvc.zip.sha256">checksum</a></td> </tr> <tr> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-i686-pc-windows-msvc.zip">ruff-i686-pc-windows-msvc.zip</a></td> <td>x86 Windows</td> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-i686-pc-windows-msvc.zip.sha256">checksum</a></td> </tr> <tr> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-x86_64-pc-windows-msvc.zip">ruff-x86_64-pc-windows-msvc.zip</a></td> <td>x64 Windows</td> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-x86_64-pc-windows-msvc.zip.sha256">checksum</a></td> </tr> <tr> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-aarch64-unknown-linux-gnu.tar.gz">ruff-aarch64-unknown-linux-gnu.tar.gz</a></td> <td>ARM64 Linux</td> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-aarch64-unknown-linux-gnu.tar.gz.sha256">checksum</a></td> </tr> <tr> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-i686-unknown-linux-gnu.tar.gz">ruff-i686-unknown-linux-gnu.tar.gz</a></td> <td>x86 Linux</td> <td><a href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-i686-unknown-linux-gnu.tar.gz.sha256">checksum</a></td> </tr> </tbody> </table> <!-- 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.15.20</h2> <p>Released on 2026-06-25.</p> <h3>Preview features</h3> <ul> <li>Allow human-readable names in rule selectors (<a href="https://redirect.github.com/astral-sh/ruff/pull/25887">#25887</a>)</li> <li>Emit a warning instead of an error for unknown rule selectors (<a href="https://redirect.github.com/astral-sh/ruff/pull/26113">#26113</a>)</li> <li>Match <code>noqa</code> shebang handling in <code>ruff:ignore</code> comments (<a href="https://redirect.github.com/astral-sh/ruff/pull/26286">#26286</a>)</li> <li>[<code>ruff</code>] Remove <code>pytest-fixture-autouse</code> (<code>RUF076</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/26240">#26240</a>, <a href="https://redirect.github.com/astral-sh/ruff/pull/26371">#26371</a>)</li> </ul> <h3>Documentation</h3> <ul> <li>Add versioning sections to custom crate READMEs (<a href="https://redirect.github.com/astral-sh/ruff/pull/26317">#26317</a>)</li> <li>Update <code>ruff_python_parser</code> README for crates.io (<a href="https://redirect.github.com/astral-sh/ruff/pull/26315">#26315</a>)</li> <li>[<code>perflint</code>] Clarify that <code>PERF402</code> applies to any iterable (<a href="https://redirect.github.com/astral-sh/ruff/pull/26242">#26242</a>)</li> </ul> <h3>Contributors</h3> <ul> <li><a href="https://github.com/dhruvmanila"><code>@dhruvmanila</code></a></li> <li><a href="https://github.com/MichaReiser"><code>@MichaReiser</code></a></li> <li><a href="https://github.com/ntBre"><code>@ntBre</code></a></li> <li><a href="https://github.com/trilamsr"><code>@trilamsr</code></a></li> </ul> <h2>0.15.19</h2> <p>Released on 2026-06-23.</p> <h3>Preview features</h3> <ul> <li>Support human-readable names when hovering suppression comments and in code actions (<a href="https://redirect.github.com/astral-sh/ruff/pull/26114">#26114</a>)</li> </ul> <h3>Bug fixes</h3> <ul> <li>Fall back to default settings when editor-only settings are invalid (<a href="https://redirect.github.com/astral-sh/ruff/pull/26244">#26244</a>)</li> <li>Fix panic when inserting text at a notebook cell boundary (<a href="https://redirect.github.com/astral-sh/ruff/pull/26111">#26111</a>)</li> </ul> <h3>Rule changes</h3> <ul> <li>[<code>pylint</code>] Update fix suggestions for <code>__floor__</code>, <code>__trunc__</code>, <code>__length_hint__</code>, and <code>__matmul__</code> variants (<code>PLC2801</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/26239">#26239</a>)</li> </ul> <h3>Performance</h3> <ul> <li>Avoid allocating when parsing single string literals (<a href="https://redirect.github.com/astral-sh/ruff/pull/26200">#26200</a>)</li> <li>Avoid reallocating singleton call arguments (<a href="https://redirect.github.com/astral-sh/ruff/pull/26223">#26223</a>)</li> <li>Lazily create source files for lint diagnostics (<a href="https://redirect.github.com/astral-sh/ruff/pull/26226">#26226</a>)</li> <li>Optimize formatter text width and indentation (<a href="https://redirect.github.com/astral-sh/ruff/pull/26236">#26236</a>)</li> <li>Reserve capacity for builtin bindings (<a href="https://redirect.github.com/astral-sh/ruff/pull/26229">#26229</a>)</li> <li>Skip repeated-key checks for singleton dictionaries (<a href="https://redirect.github.com/astral-sh/ruff/pull/26228">#26228</a>)</li> <li>Use ArrayVec for qualified name segments (<a href="https://redirect.github.com/astral-sh/ruff/pull/26224">#26224</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/f82a36b6baf8c0547a17bbde6c0d927ccd45d938"><code>f82a36b</code></a> Bump 0.15.20 (<a href="https://redirect.github.com/astral-sh/ruff/issues/26376">#26376</a>)</li> <li><a href="https://github.com/astral-sh/ruff/commit/af329438b55d5aa8ca28725f17abbae63b5c815d"><code>af32943</code></a> Improve the summarise-ecosystem-results skill (<a href="https://redirect.github.com/astral-sh/ruff/issues/26378">#26378</a>)</li> <li><a href="https://github.com/astral-sh/ruff/commit/485ebab5de692aad34720898cd80245b6092ba86"><code>485ebab</code></a> Remove <code>RUF076</code> name from schema (<a href="https://redirect.github.com/astral-sh/ruff/issues/26371">#26371</a>)</li> <li><a href="https://github.com/astral-sh/ruff/commit/ef81835ce3da615346584dc446de0fe26bfc154b"><code>ef81835</code></a> [ty] Implement rust-analyzer's "Click for full compiler diagnostic" feature (...</li> <li><a href="https://github.com/astral-sh/ruff/commit/572b31e2377a701a7365f069f6ef87002962cb2b"><code>572b31e</code></a> [<code>ruff</code>] Remove <code>pytest-fixture-autouse</code> (<code>RUF076</code>) (<a href="https://redirect.github.com/astral-sh/ruff/issues/26240">#26240</a>)</li> <li><a href="https://github.com/astral-sh/ruff/commit/f703f219d7fddd1a4abba9fa81e34773c185a4b9"><code>f703f21</code></a> Allow human-readable names in rule selectors (<a href="https://redirect.github.com/astral-sh/ruff/issues/25887">#25887</a>)</li> <li><a href="https://github.com/astral-sh/ruff/commit/0d726b28ee35413926145ffef4ac1143cae9ea1e"><code>0d726b2</code></a> [ty] Reuse equality semantics for membership compatibility (<a href="https://redirect.github.com/astral-sh/ruff/issues/25955">#25955</a>)</li> <li><a href="https://github.com/astral-sh/ruff/commit/dbe6e9848c3f78fdaf543cfc000939ebc73b5f00"><code>dbe6e98</code></a> [ty] Infer definite equality comparison results (<a href="https://redirect.github.com/astral-sh/ruff/issues/26337">#26337</a>)</li> <li><a href="https://github.com/astral-sh/ruff/commit/e700ea357a2196d76b58eb89b2031ddb5ca896e0"><code>e700ea3</code></a> [ty] Prove TypedDict structural patterns exhaustive (<a href="https://redirect.github.com/astral-sh/ruff/issues/26285">#26285</a>)</li> <li><a href="https://github.com/astral-sh/ruff/commit/6a0d2ec93959d966430b8c39d4de5c3e8a88e911"><code>6a0d2ec</code></a> [ty] Widen inferred class-valued instance attributes (<a href="https://redirect.github.com/astral-sh/ruff/issues/26338">#26338</a>)</li> <li>Additional commits viewable in <a href="https://github.com/astral-sh/ruff/compare/0.15.12...0.15.20">compare view</a></li> </ul> </details> <br /> Updates `ty` from 0.0.43 to 0.0.55 <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.55</h2> <h2>Release Notes</h2> <p>Released on 2026-06-26.</p> <h3>LSP server</h3> <ul> <li>Render full diagnostics in color (<a href="https://redirect.github.com/astral-sh/ruff/pull/26384">#26384</a>)</li> </ul> <h3>Documentation</h3> <ul> <li>Document colored diagnostic output (<a href="https://redirect.github.com/astral-sh/ty/pull/3858">#3858</a>)</li> </ul> <h3>Performance</h3> <ul> <li>Improve vendored filesystem concurrency (<a href="https://redirect.github.com/astral-sh/ruff/pull/26408">#26408</a>)</li> <li>Optimize enum comparisons in equality evaluation (<a href="https://redirect.github.com/astral-sh/ruff/pull/26340">#26340</a>)</li> <li>Remove redundant semantic index shrinks (<a href="https://redirect.github.com/astral-sh/ruff/pull/26392">#26392</a>)</li> <li>Use never-change durability for one-shot checks (<a href="https://redirect.github.com/astral-sh/ruff/pull/26359">#26359</a>)</li> </ul> <h3>Core type checking</h3> <ul> <li>Correct enum alias detection and scalar constructors (<a href="https://redirect.github.com/astral-sh/ruff/pull/26345">#26345</a>)</li> <li>Fix structural pattern binding inference (<a href="https://redirect.github.com/astral-sh/ruff/pull/26411">#26411</a>)</li> <li>Improve variable-length tuple slicing (<a href="https://redirect.github.com/astral-sh/ruff/pull/26151">#26151</a>)</li> <li>Infer class and mapping pattern bindings (<a href="https://redirect.github.com/astral-sh/ruff/pull/25941">#25941</a>)</li> <li>Infer empty collection constructors from later uses (<a href="https://redirect.github.com/astral-sh/ruff/pull/26389">#26389</a>)</li> <li>Skip shadowed submodule bindings during import analysis (<a href="https://redirect.github.com/astral-sh/ruff/pull/26385">#26385</a>)</li> <li>Sync vendored typeshed stubs (<a href="https://redirect.github.com/astral-sh/ruff/pull/26406">#26406</a>). <a href="https://github.com/python/typeshed/compare/8e6a886ca5b14742924be721d345a619762d6e93...3c6221722aac5bd9dbfc5fbf7b4f0bf64b2c5724">Typeshed diff</a></li> <li>Track literal iterable emptiness for reachability (<a href="https://redirect.github.com/astral-sh/ruff/pull/25222">#25222</a>)</li> <li>Validate positional class patterns against <code>__match_args__</code> (<a href="https://redirect.github.com/astral-sh/ruff/pull/26195">#26195</a>)</li> </ul> <h3>Contributors</h3> <ul> <li><a href="https://github.com/charliermarsh"><code>@charliermarsh</code></a></li> <li><a href="https://github.com/carljm"><code>@carljm</code></a></li> <li><a href="https://github.com/AlexWaygood"><code>@AlexWaygood</code></a></li> <li><a href="https://github.com/MichaReiser"><code>@MichaReiser</code></a></li> <li><a href="https://github.com/mtshiba"><code>@mtshiba</code></a></li> <li><a href="https://github.com/felixscherz"><code>@felixscherz</code></a></li> </ul> <h2>Install ty 0.0.55</h2> <h3>Install prebuilt binaries via shell script</h3> <pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf https://releases.astral.sh/github/ty/releases/download/0.0.55/ty-installer.sh | sh </code></pre> <h3>Install prebuilt binaries via powershell script</h3> <!-- 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.55</h2> <p>Released on 2026-06-26.</p> <h3>LSP server</h3> <ul> <li>Render full diagnostics in color (<a href="https://redirect.github.com/astral-sh/ruff/pull/26384">#26384</a>)</li> </ul> <h3>Documentation</h3> <ul> <li>Document colored diagnostic output (<a href="https://redirect.github.com/astral-sh/ty/pull/3858">#3858</a>)</li> </ul> <h3>Performance</h3> <ul> <li>Improve vendored filesystem concurrency (<a href="https://redirect.github.com/astral-sh/ruff/pull/26408">#26408</a>)</li> <li>Optimize enum comparisons in equality evaluation (<a href="https://redirect.github.com/astral-sh/ruff/pull/26340">#26340</a>)</li> <li>Remove redundant semantic index shrinks (<a href="https://redirect.github.com/astral-sh/ruff/pull/26392">#26392</a>)</li> <li>Use never-change durability for one-shot checks (<a href="https://redirect.github.com/astral-sh/ruff/pull/26359">#26359</a>)</li> </ul> <h3>Core type checking</h3> <ul> <li>Correct enum alias detection and scalar constructors (<a href="https://redirect.github.com/astral-sh/ruff/pull/26345">#26345</a>)</li> <li>Fix structural pattern binding inference (<a href="https://redirect.github.com/astral-sh/ruff/pull/26411">#26411</a>)</li> <li>Improve variable-length tuple slicing (<a href="https://redirect.github.com/astral-sh/ruff/pull/26151">#26151</a>)</li> <li>Infer class and mapping pattern bindings (<a href="https://redirect.github.com/astral-sh/ruff/pull/25941">#25941</a>)</li> <li>Infer empty collection constructors from later uses (<a href="https://redirect.github.com/astral-sh/ruff/pull/26389">#26389</a>)</li> <li>Skip shadowed submodule bindings during import analysis (<a href="https://redirect.github.com/astral-sh/ruff/pull/26385">#26385</a>)</li> <li>Sync vendored typeshed stubs (<a href="https://redirect.github.com/astral-sh/ruff/pull/26406">#26406</a>). <a href="https://github.com/python/typeshed/compare/8e6a886ca5b14742924be721d345a619762d6e93...3c6221722aac5bd9dbfc5fbf7b4f0bf64b2c5724">Typeshed diff</a></li> <li>Track literal iterable emptiness for reachability (<a href="https://redirect.github.com/astral-sh/ruff/pull/25222">#25222</a>)</li> <li>Validate positional class patterns against <code>__match_args__</code> (<a href="https://redirect.github.com/astral-sh/ruff/pull/26195">#26195</a>)</li> </ul> <h3>Contributors</h3> <ul> <li><a href="https://github.com/charliermarsh"><code>@charliermarsh</code></a></li> <li><a href="https://github.com/carljm"><code>@carljm</code></a></li> <li><a href="https://github.com/AlexWaygood"><code>@AlexWaygood</code></a></li> <li><a href="https://github.com/MichaReiser"><code>@MichaReiser</code></a></li> <li><a href="https://github.com/mtshiba"><code>@mtshiba</code></a></li> <li><a href="https://github.com/felixscherz"><code>@felixscherz</code></a></li> </ul> <h2>0.0.54</h2> <p>Released on 2026-06-25.</p> <h3>Bug fixes</h3> <ul> <li>Avoid duplicate configuration error output (<a href="https://redirect.github.com/astral-sh/ruff/pull/26375">#26375</a>)</li> <li>Avoid stack overflows in reachability analysis (<a href="https://redirect.github.com/astral-sh/ruff/pull/26272">#26272</a>)</li> <li>Fix divergent recursive tuple cycle handling in ty (<a href="https://redirect.github.com/astral-sh/ruff/pull/26316">#26316</a>)</li> <li>Fix panic from relation queries during cycle recovery (<a href="https://redirect.github.com/astral-sh/ruff/pull/26335">#26335</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/42869400da56f4ee7ce752141f81b50bb318e4e2"><code>4286940</code></a> Bump version to 0.0.55 (<a href="https://redirect.github.com/astral-sh/ty/issues/3866">#3866</a>)</li> <li><a href="https://github.com/astral-sh/ty/commit/5374b30ff462ef6e59d23dc9ba515007b02464a8"><code>5374b30</code></a> Update benchmarks for ty 0.0.54 (<a href="https://redirect.github.com/astral-sh/ty/issues/3865">#3865</a>)</li> <li><a href="https://github.com/astral-sh/ty/commit/3ed874d053ff9529c9fd0205a6d3bbe6a7407535"><code>3ed874d</code></a> Document colored diagnostic output (<a href="https://redirect.github.com/astral-sh/ty/issues/3858">#3858</a>)</li> <li><a href="https://github.com/astral-sh/ty/commit/bf8a7dd584f53d67d873c750afc1494331f93502"><code>bf8a7dd</code></a> Bump version to 0.0.54</li> <li><a href="https://github.com/astral-sh/ty/commit/78e094dfaf6245514f124b043b18c9a20edd8f08"><code>78e094d</code></a> Document the <code>fullDiagnosticOutput</code> extension to the Language Server Protocol...</li> <li><a href="https://github.com/astral-sh/ty/commit/7bda89e5069d747bd7f54513dcd0263157a8f694"><code>7bda89e</code></a> Document ty's <code>@Todo</code> type in the typing FAQ (<a href="https://redirect.github.com/astral-sh/ty/issues/3847">#3847</a>)</li> <li><a href="https://github.com/astral-sh/ty/commit/66a94cde4970b405a770e4c6ef4ac2a9bb6bf733"><code>66a94cd</code></a> Update maturin to v1.14.0 (<a href="https://redirect.github.com/astral-sh/ty/issues/3840">#3840</a>)</li> <li><a href="https://github.com/astral-sh/ty/commit/dc2b39d1e29e55a4a6d83486fffa936ba5121a72"><code>dc2b39d</code></a> Update prek dependencies (<a href="https://redirect.github.com/astral-sh/ty/issues/3839">#3839</a>)</li> <li><a href="https://github.com/astral-sh/ty/commit/23f26f98a22d3a0df412846506f3af6e76cf48e4"><code>23f26f9</code></a> Bump version to 0.0.53 (<a href="https://redirect.github.com/astral-sh/ty/issues/3841">#3841</a>)</li> <li><a href="https://github.com/astral-sh/ty/commit/13f91b093e004c76aa548a221c7b577dfbdd7ea6"><code>13f91b0</code></a> Bump version to 0.0.52 (<a href="https://redirect.github.com/astral-sh/ty/issues/3828">#3828</a>)</li> <li>Additional commits viewable in <a href="https://github.com/astral-sh/ty/compare/0.0.43...0.0.55">compare view</a></li> </ul> </details> <br /> Updates `pydantic` from 2.13.3 to 2.13.4 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/pydantic/pydantic/releases">pydantic's releases</a>.</em></p> <blockquote> <h2>v2.13.4 2026-05-06</h2> <h2>v2.13.4 (2026-05-06)</h2> <h3>What's Changed</h3> <h4>Packaging</h4> <ul> <li>Bump libc from 0.2.155 to 0.2.185 by <a href="https://github.com/Viicos"><code>@Viicos</code></a> in <a href="https://redirect.github.com/pydantic/pydantic/pull/13109">#13109</a></li> <li>Adapt <code>pydantic-core</code> linker flags on macOS by <a href="https://github.com/washingtoneg"><code>@washingtoneg</code></a> and <a href="https://github.com/Viicos"><code>@Viicos</code></a> in <a href="https://redirect.github.com/pydantic/pydantic/pull/13147">#13147</a></li> </ul> <h4>Fixes</h4> <ul> <li>Preserve <code>RootModel</code> core metadata by <a href="https://github.com/Viicos"><code>@Viicos</code></a> in <a href="https://redirect.github.com/pydantic/pydantic/pull/13129">#13129</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/pydantic/pydantic/compare/v2.13.3...v2.13.4">https://github.com/pydantic/pydantic/compare/v2.13.3...v2.13.4</a></p> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/pydantic/pydantic/blob/main/HISTORY.md">pydantic's changelog</a>.</em></p> <blockquote> <h2>v2.13.4 (2026-05-06)</h2> <p><a href="https://github.com/pydantic/pydantic/releases/tag/v2.13.4">GitHub release</a></p> <h3>What's Changed</h3> <h4>Packaging</h4> <ul> <li>Bump libc from 0.2.155 to 0.2.185 by <a href="https://github.com/Viicos"><code>@Viicos</code></a> in <a href="https://redirect.github.com/pydantic/pydantic/pull/13109">#13109</a></li> <li>Adapt <code>pydantic-core</code> linker flags on macOS by <a href="https://github.com/washingtoneg"><code>@washingtoneg</code></a> and <a href="https://github.com/Viicos"><code>@Viicos</code></a> in <a href="https://redirect.github.com/pydantic/pydantic/pull/13147">#13147</a></li> </ul> <h4>Fixes</h4> <ul> <li>Preserve <code>RootModel</code> core metadata by <a href="https://github.com/Viicos"><code>@Viicos</code></a> in <a href="https://redirect.github.com/pydantic/pydantic/pull/13129">#13129</a></li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/pydantic/pydantic/commit/cf67d4b3193c3fe43ede18612ed62785eee11382"><code>cf67d4b</code></a> Fix linting</li> <li><a href="https://github.com/pydantic/pydantic/commit/f0d8a214a5803036db46a56b1f62f1e56b81d662"><code>f0d8a21</code></a> Prepare release v2.13.4</li> <li><a href="https://github.com/pydantic/pydantic/commit/5e3fe1d41a00f441204241c66078003ae0391f9a"><code>5e3fe1d</code></a> Check for pydantic tag pattern in CI</li> <li><a href="https://github.com/pydantic/pydantic/commit/7f9edcc2a191d2eaa9751220eb910914e716a686"><code>7f9edcc</code></a> Document tagging conventions</li> <li><a href="https://github.com/pydantic/pydantic/commit/b46a0c9b8a4dd967fda8ec1a92f6437076bf262c"><code>b46a0c9</code></a> Adapt <code>pydantic-core</code> linker flags on macOS</li> <li><a href="https://github.com/pydantic/pydantic/commit/50629c851e61d887d5420452c311ec6203f1f400"><code>50629c8</code></a> Update to PyPy 7.3.22</li> <li><a href="https://github.com/pydantic/pydantic/commit/8522ebb71e5e9a6f7188af5f009f01785b8cf725"><code>8522ebb</code></a> Preserve <code>RootModel</code> core metadata</li> <li><a href="https://github.com/pydantic/pydantic/commit/a37f3aff090ca342dc5f48304889963530b993f8"><code>a37f3af</code></a> Adapt <code>MISSING</code> sentinel test to work with unreleased <code>typing_extensions</code> ver...</li> <li><a href="https://github.com/pydantic/pydantic/commit/909259a9df660518033aa686b689f045a6eaf9d2"><code>909259a</code></a> Remove Logfire example in documentation</li> <li><a href="https://github.com/pydantic/pydantic/commit/2c4174c366606fc2dc46cb806833a080aefa77df"><code>2c4174c</code></a> Bump libc from 0.2.155 to 0.2.185</li> <li>See full diff in <a href="https://github.com/pydantic/pydantic/compare/v2.13.3...v2.13.4">compare view</a></li> </ul> </details> <br /> Updates `xxhash` from 3.6.0 to 3.8.0 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/ifduyue/python-xxhash/releases">xxhash's releases</a>.</em></p> <blockquote> <h2>v3.8.0</h2> <ul> <li>Speed up module-level one-shot <code>digest()</code>, <code>intdigest()</code>, and <code>hexdigest()</code> functions by switching them to <code>METH_FASTCALL</code>.</li> <li>Keep one-shot argument handling consistent with hash constructors, including positional and keyword <code>input</code>/<code>seed</code> arguments, duplicate argument errors, and oversized seed wrapping.</li> <li>Fix error handling in the <code>xxh3_128</code> integer digest path so allocation failures are reported cleanly.</li> <li>Fix Python 3.8 builds by adding a <code>PyModule_AddType</code> compatibility fallback with correct reference counting.</li> <li>Correct type stubs for <code>xxh64_digest()</code>, <code>xxh64_hexdigest()</code>, and <code>xxh64_intdigest()</code>, they were incorrectly aliased to xxh3_64 functions.</li> </ul> <hr /> <p>Full list of changes: <a href="https://github.com/ifduyue/python-xxhash/compare/v3.7.1...v3.8.0">https://github.com/ifduyue/python-xxhash/compare/v3.7.1...v3.8.0</a></p> <h2>v3.7.1</h2> <ul> <li>Fix memory leak in copy() and new() when memory allocation fails (rare edge case)</li> <li>Fix seed/reset state initialization in xxh32 and xxh64 (unlikely to affect normal usage)</li> <li>Replace Py_BuildValue with PyLong_FromUnsignedLong/LongLong for performance</li> <li>Update README examples to use bytes literals</li> <li>Add CodSpeed performance benchmarks and CI workflow</li> <li>Build aarch64/armv7l on native Arm runners; test against Python 3.15.0-beta.2</li> </ul> <hr /> <p>Full list of changes: <a href="https://github.com/ifduyue/python-xxhash/compare/v3.7.0...v3.7.1">https://github.com/ifduyue/python-xxhash/compare/v3.7.0...v3.7.1</a></p> <h2>v3.7.0</h2> <ul> <li>Drop support for Python 3.7</li> <li>Build armv7l manylinux/musllinux wheels</li> <li>Build riscv64 manylinux/musllinux wheels</li> <li>Build android and ios wheels</li> </ul> <hr /> <p>Full list of changes: <a href="https://github.com/ifduyue/python-xxhash/compare/v3.6.0...v3.7.0">https://github.com/ifduyue/python-xxhash/compare/v3.6.0...v3.7.0</a></p> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/ifduyue/python-xxhash/blob/v3.8.0/CHANGELOG.rst">xxhash's changelog</a>.</em></p> <blockquote> <p>v3.8.0 2026-06-27</p> <pre><code> - Speed up module-level one-shot ``digest()``, ``intdigest()``, and ``hexdigest()`` functions by switching them to ``METH_FASTCALL``. - Keep one-shot argument handling consistent with hash constructors, including positional and keyword ``input``/``seed`` arguments, duplicate argument errors, and oversized seed wrapping. - Fix error handling in the ``xxh3_128`` integer digest path so allocation failures are reported cleanly. - Fix Python 3.8 builds by adding a ``PyModule_AddType`` compatibility fallback with correct reference counting. - Correct type stubs for ``xxh64_digest()``, ``xxh64_hexdigest()``, and ``xxh64_intdigest()``, they were incorrectly aliased to xxh3_64 functions. <p>v3.7.1 2026-06-24<br /> </code></pre></p> <ul> <li>Fix memory leak in copy() and new() when memory allocation fails (rare edge case)</li> <li>Fix seed/reset state initialization in xxh32 and xxh64 (unlikely to affect normal usage)</li> <li>Replace Py_BuildValue with PyLong_FromUnsignedLong/LongLong for performance</li> <li>Update README examples to use bytes literals</li> <li>Add CodSpeed performance benchmarks and CI workflow</li> <li>Build aarch64/armv7l on native Arm runners; test against Python 3.15.0-beta.2</li> </ul> <p>v3.7.0 2025-04-25</p> <pre><code> - Drop support for Python 3.7 - Build armv7l manylinux/musllinux wheels - Build riscv64 manylinux/musllinux wheels - Build android and ios wheels </code></pre> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/ifduyue/python-xxhash/commit/48c60f24fd50d7c2765fc3df099aa9006e763b9e"><code>48c60f2</code></a> chore: release v3.8.0</li> <li><a href="https://github.com/ifduyue/python-xxhash/commit/c7f239734a6683279bc8467024799fd35e007a61"><code>c7f2397</code></a> fix: restore PyLong_FromUnsignedLong/LongLong over Py_BuildValue</li> <li><a href="https://github.com/ifduyue/python-xxhash/commit/aea7a72706d1ed56a1386e71a2f62be9d7f65824"><code>aea7a72</code></a> test: fix positional seed tests in test_fastcall.py</li> <li><a href="https://github.com/ifduyue/python-xxhash/commit/e084433ec40f4347b7ade06a7cdb095512d8bfde"><code>e084433</code></a> fix: correct docstring typos in xxh3_64 copy() documentation</li> <li><a href="https://github.com/ifduyue/python-xxhash/commit/414b254e78e97b0d2000251451bd19b50d7e1462"><code>414b254</code></a> fix(pyi): xxh64_digest/hexdigest/intdigest are functions</li> <li><a href="https://github.com/ifduyue/python-xxhash/commit/9af991361c8ee702286ec6c9f64967e28b2184a2"><code>9af9913</code></a> fix: add PyModule_AddType fallback for Python 3.8 compatibility</li> <li><a href="https://github.com/ifduyue/python-xxhash/commit/1ecd5b4791b3871019423c8a11d023c0059f0864"><code>1ecd5b4</code></a> perf: mark _get_buffer_or_str and _parse_fastcall_args as inline</li> <li><a href="https://github.com/ifduyue/python-xxhash/commit/2b6f712b4bbab8ebdcd2f16435eae16dcd83adee"><code>2b6f712</code></a> fix: detect duplicate seed, expand fastcall tests</li> <li><a href="https://github.com/ifduyue/python-xxhash/commit/5d59623916536c0f67ba6fd2f818b899ab6a3dba"><code>5d59623</code></a> fix: add NULL checks in xxh3_128_intdigest 128-bit math</li> <li><a href="https://github.com/ifduyue/python-xxhash/commit/8fe3bf48b7767292d502362f57dc8e29d13d1786"><code>8fe3bf4</code></a> refactor: consolidate fastcall arg parsing into _parse_fastcall_args</li> <li>Additional commits viewable in <a href="https://github.com/ifduyue/python-xxhash/compare/v3.6.0...v3.8.0">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 <65985482+jkennedyvz@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
876 lines
30 KiB
Python
876 lines
30 KiB
Python
from __future__ import annotations
|
|
|
|
import inspect
|
|
import typing
|
|
from collections.abc import Callable, Sequence
|
|
|
|
from langgraph_sdk.auth import exceptions, types
|
|
|
|
TH = typing.TypeVar("TH", bound=types.Handler)
|
|
AH = typing.TypeVar("AH", bound=types.Authenticator)
|
|
|
|
|
|
class Auth:
|
|
"""Add custom authentication and authorization management to your LangGraph application.
|
|
|
|
The Auth class provides a unified system for handling authentication and
|
|
authorization in LangGraph applications. It supports custom user authentication
|
|
protocols and fine-grained authorization rules for different resources and
|
|
actions.
|
|
|
|
To use, create a separate python file and add the path to the file to your
|
|
LangGraph API configuration file (`langgraph.json`). Within that file, create
|
|
an instance of the Auth class and register authentication and authorization
|
|
handlers as needed.
|
|
|
|
Example `langgraph.json` file:
|
|
|
|
```json
|
|
{
|
|
"dependencies": ["."],
|
|
"graphs": {
|
|
"agent": "./my_agent/agent.py:graph"
|
|
},
|
|
"env": ".env",
|
|
"auth": {
|
|
"path": "./auth.py:my_auth"
|
|
}
|
|
```
|
|
|
|
Then the LangGraph server will load your auth file and run it server-side whenever a request comes in.
|
|
|
|
???+ example "Basic Usage"
|
|
|
|
```python
|
|
from langgraph_sdk import Auth
|
|
|
|
my_auth = Auth()
|
|
|
|
@my_auth.authenticate
|
|
async def authenticate(authorization: str) -> Auth.types.MinimalUserDict:
|
|
user = await verify_token(authorization) # Your token verification logic
|
|
if not user:
|
|
raise Auth.exceptions.HTTPException(
|
|
status_code=401, detail="Unauthorized"
|
|
)
|
|
return {
|
|
"identity": user["id"],
|
|
"permissions": user.get("permissions", []),
|
|
}
|
|
|
|
# Default deny: reject all requests that don't have a specific handler
|
|
@my_auth.on
|
|
async def deny_all(ctx: Auth.types.AuthContext, value: Any) -> False:
|
|
return False
|
|
|
|
# Allow users to create threads with their own identity as owner
|
|
@my_auth.on.threads.create
|
|
async def allow_thread_create(
|
|
ctx: Auth.types.AuthContext, value: Auth.types.on.threads.create.value
|
|
):
|
|
metadata = value.setdefault("metadata", {})
|
|
metadata["owner"] = ctx.user.identity
|
|
|
|
# Allow users to read and search their own threads
|
|
@my_auth.on.threads.read
|
|
async def allow_thread_read(
|
|
ctx: Auth.types.AuthContext, value: Auth.types.on.threads.read.value
|
|
) -> Auth.types.FilterType:
|
|
return {"owner": ctx.user.identity}
|
|
|
|
@my_auth.on.threads.search
|
|
async def allow_thread_search(
|
|
ctx: Auth.types.AuthContext, value: Auth.types.on.threads.search.value
|
|
) -> Auth.types.FilterType:
|
|
return {"owner": ctx.user.identity}
|
|
|
|
# Scope all store operations to the user's namespace
|
|
@my_auth.on.store
|
|
async def scope_store(ctx: Auth.types.AuthContext, value: Auth.types.on.store.value):
|
|
namespace = tuple(value["namespace"]) if value.get("namespace") else ()
|
|
if not namespace or namespace[0] != ctx.user.identity:
|
|
namespace = (ctx.user.identity, *namespace)
|
|
value["namespace"] = namespace
|
|
```
|
|
|
|
???+ note "Request Processing Flow"
|
|
|
|
1. Authentication (your `@auth.authenticate` handler) is performed first on **every request**
|
|
2. For authorization, the most specific matching handler is called:
|
|
* If a handler exists for the exact resource and action, it is used (e.g., `@auth.on.threads.create`)
|
|
* Otherwise, if a handler exists for the resource with any action, it is used (e.g., `@auth.on.threads`)
|
|
* Finally, if no specific handlers match, the global handler is used (e.g., `@auth.on`)
|
|
* If no global handler is set, the request is accepted
|
|
|
|
This allows you to set default behavior with a global handler while
|
|
overriding specific routes as needed.
|
|
"""
|
|
|
|
__slots__ = (
|
|
"_authenticate_handler",
|
|
"_global_handlers",
|
|
"_handler_cache",
|
|
"_handlers",
|
|
"on",
|
|
)
|
|
types = types
|
|
"""Reference to auth type definitions.
|
|
|
|
Provides access to all type definitions used in the auth system,
|
|
like ThreadsCreate, AssistantsRead, etc."""
|
|
|
|
exceptions = exceptions
|
|
"""Reference to auth exception definitions.
|
|
|
|
Provides access to all exception definitions used in the auth system,
|
|
like HTTPException, etc.
|
|
"""
|
|
|
|
def __init__(self) -> None:
|
|
self.on = _On(self)
|
|
"""Entry point for authorization handlers that control access to specific resources.
|
|
|
|
The on class provides a flexible way to define authorization rules for different
|
|
resources and actions in your application. It supports three main usage patterns:
|
|
|
|
1. Global handlers that run for all resources and actions
|
|
2. Resource-specific handlers that run for all actions on a resource
|
|
3. Resource and action specific handlers for fine-grained control
|
|
|
|
Each handler must be an async function that accepts two parameters:
|
|
- ctx (AuthContext): Contains request context and authenticated user info
|
|
- value: The data being authorized (type varies by endpoint)
|
|
|
|
The handler should return one of:
|
|
|
|
- None or True: Accept the request
|
|
- False: Reject with 403 error
|
|
- FilterType: Apply filtering rules to the response
|
|
|
|
???+ example "Examples"
|
|
|
|
Start by denying all requests by default, then add specific handlers
|
|
to allow access:
|
|
|
|
```python
|
|
# Default deny: reject all unhandled requests
|
|
@auth.on
|
|
async def deny_all(ctx: AuthContext, value: Any) -> False:
|
|
return False
|
|
```
|
|
|
|
Resource-specific handler. This takes precedence over the global handler
|
|
for all actions on the `threads` resource:
|
|
|
|
```python
|
|
@auth.on.threads
|
|
async def allow_thread_access(ctx: AuthContext, value: Any) -> Auth.types.FilterType:
|
|
# Only allow access to threads owned by the user
|
|
return {"owner": ctx.user.identity}
|
|
```
|
|
|
|
Resource and action specific handler:
|
|
|
|
```python
|
|
@auth.on.threads.delete
|
|
async def allow_admin_thread_deletion(ctx: AuthContext, value: Any) -> bool:
|
|
# Only admins can delete threads
|
|
return "admin" in ctx.user.permissions
|
|
```
|
|
|
|
Multiple resources or actions:
|
|
|
|
```python
|
|
@auth.on(resources=["threads", "assistants"], actions=["read", "search"])
|
|
async def allow_reads(ctx: AuthContext, value: Any) -> Auth.types.FilterType:
|
|
# Allow read/search access to resources owned by the user
|
|
return {"owner": ctx.user.identity}
|
|
```
|
|
|
|
Auth for the `store` resource is a bit different since its structure is developer defined.
|
|
You typically want to scope store operations by rewriting the namespace to include the user's identity.
|
|
The `value` dict is mutable — changes to `value["namespace"]` are used by the server for the actual operation.
|
|
|
|
```python
|
|
@auth.on.store
|
|
async def scope_store(ctx: AuthContext, value: Auth.types.on.store.value):
|
|
# Allow store access but scope to user's namespace
|
|
namespace = tuple(value["namespace"]) if value.get("namespace") else ()
|
|
if not namespace or namespace[0] != ctx.user.identity:
|
|
namespace = (ctx.user.identity, *namespace)
|
|
value["namespace"] = namespace
|
|
```
|
|
|
|
You can also register handlers for specific store actions:
|
|
|
|
```python
|
|
@auth.on.store.put
|
|
async def allow_put(ctx: AuthContext, value: Auth.types.on.store.put.value):
|
|
# Allow puts, scoped to user's namespace
|
|
value["namespace"] = (ctx.user.identity, *value["namespace"])
|
|
|
|
@auth.on.store.get
|
|
async def allow_get(ctx: AuthContext, value: Auth.types.on.store.get.value):
|
|
# Allow gets, scoped to user's namespace
|
|
value["namespace"] = (ctx.user.identity, *value["namespace"])
|
|
```
|
|
"""
|
|
# These are accessed by the API. Changes to their names or types is
|
|
# will be considered a breaking change.
|
|
self._handlers: dict[tuple[str, str], list[types.Handler]] = {}
|
|
self._global_handlers: list[types.Handler] = []
|
|
self._authenticate_handler: types.Authenticator | None = None
|
|
self._handler_cache: dict[tuple[str, str], types.Handler] = {}
|
|
|
|
def authenticate(self, fn: AH) -> AH:
|
|
"""Register an authentication handler function.
|
|
|
|
The authentication handler is responsible for verifying credentials
|
|
and returning user scopes. It can accept any of the following parameters
|
|
by name:
|
|
|
|
- request (Request): The raw ASGI request object
|
|
- path (str): The request path, e.g., "/threads/abcd-1234-abcd-1234/runs/abcd-1234-abcd-1234/stream"
|
|
- method (str): The HTTP method, e.g., "GET"
|
|
- path_params (dict[str, str]): URL path parameters, e.g., {"thread_id": "abcd-1234-abcd-1234", "run_id": "abcd-1234-abcd-1234"}
|
|
- query_params (dict[str, str]): URL query parameters, e.g., {"stream": "true"}
|
|
- headers (dict[bytes, bytes]): Request headers
|
|
- authorization (str | None): The Authorization header value (e.g., "Bearer <token>")
|
|
|
|
Args:
|
|
fn: The authentication handler function to register.
|
|
Must return a representation of the user. This could be a:
|
|
- string (the user id)
|
|
- dict containing {"identity": str, "permissions": list[str]}
|
|
- or an object with identity and permissions properties
|
|
Permissions can be optionally used by your handlers downstream.
|
|
|
|
Returns:
|
|
The registered handler function.
|
|
|
|
Raises:
|
|
ValueError: If an authentication handler is already registered.
|
|
|
|
???+ example "Examples"
|
|
|
|
Basic token authentication:
|
|
|
|
```python
|
|
@auth.authenticate
|
|
async def authenticate(authorization: str) -> str:
|
|
user_id = verify_token(authorization)
|
|
return user_id
|
|
```
|
|
|
|
Accept the full request context:
|
|
|
|
```python
|
|
@auth.authenticate
|
|
async def authenticate(
|
|
method: str,
|
|
path: str,
|
|
headers: dict[str, bytes]
|
|
) -> str:
|
|
user = await verify_request(method, path, headers)
|
|
return user
|
|
```
|
|
|
|
Return user name and permissions:
|
|
|
|
```python
|
|
@auth.authenticate
|
|
async def authenticate(
|
|
method: str,
|
|
path: str,
|
|
headers: dict[str, bytes]
|
|
) -> Auth.types.MinimalUserDict:
|
|
permissions, user = await verify_request(method, path, headers)
|
|
# Permissions could be things like ["runs:read", "runs:write", "threads:read", "threads:write"]
|
|
return {
|
|
"identity": user["id"],
|
|
"permissions": permissions,
|
|
"display_name": user["name"],
|
|
}
|
|
```
|
|
"""
|
|
if self._authenticate_handler is not None:
|
|
raise ValueError(
|
|
f"Authentication handler already set as {self._authenticate_handler}."
|
|
)
|
|
self._authenticate_handler = fn
|
|
return fn
|
|
|
|
|
|
## Helper types & utilities
|
|
|
|
V = typing.TypeVar("V", contravariant=True)
|
|
|
|
|
|
class _ActionHandler(typing.Protocol[V]):
|
|
async def __call__(
|
|
self, *, ctx: types.AuthContext, value: V
|
|
) -> types.HandlerResult: ...
|
|
|
|
|
|
T = typing.TypeVar("T", covariant=True)
|
|
|
|
|
|
class _ResourceActionOn(typing.Generic[T]):
|
|
def __init__(
|
|
self,
|
|
auth: Auth,
|
|
resource: typing.Literal["threads", "crons", "assistants"],
|
|
action: typing.Literal[
|
|
"create", "read", "update", "delete", "search", "create_run"
|
|
],
|
|
value: type[T],
|
|
) -> None:
|
|
self.auth = auth
|
|
self.resource = resource
|
|
self.action = action
|
|
self.value = value
|
|
|
|
def __call__(self, fn: _ActionHandler[T]) -> _ActionHandler[T]:
|
|
_validate_handler(fn)
|
|
_register_handler(self.auth, self.resource, self.action, fn)
|
|
return fn
|
|
|
|
|
|
VCreate = typing.TypeVar("VCreate", covariant=True)
|
|
VUpdate = typing.TypeVar("VUpdate", covariant=True)
|
|
VRead = typing.TypeVar("VRead", covariant=True)
|
|
VDelete = typing.TypeVar("VDelete", covariant=True)
|
|
VSearch = typing.TypeVar("VSearch", covariant=True)
|
|
|
|
|
|
class _ResourceOn(typing.Generic[VCreate, VRead, VUpdate, VDelete, VSearch]):
|
|
"""
|
|
Generic base class for resource-specific handlers.
|
|
"""
|
|
|
|
value: type[VCreate | VUpdate | VRead | VDelete | VSearch]
|
|
|
|
Create: type[VCreate]
|
|
Read: type[VRead]
|
|
Update: type[VUpdate]
|
|
Delete: type[VDelete]
|
|
Search: type[VSearch]
|
|
|
|
def __init__(
|
|
self,
|
|
auth: Auth,
|
|
resource: typing.Literal["threads", "crons", "assistants"],
|
|
) -> None:
|
|
self.auth = auth
|
|
self.resource = resource
|
|
self.create: _ResourceActionOn[VCreate] = _ResourceActionOn(
|
|
auth, resource, "create", self.Create
|
|
)
|
|
self.read: _ResourceActionOn[VRead] = _ResourceActionOn(
|
|
auth, resource, "read", self.Read
|
|
)
|
|
self.update: _ResourceActionOn[VUpdate] = _ResourceActionOn(
|
|
auth, resource, "update", self.Update
|
|
)
|
|
self.delete: _ResourceActionOn[VDelete] = _ResourceActionOn(
|
|
auth, resource, "delete", self.Delete
|
|
)
|
|
self.search: _ResourceActionOn[VSearch] = _ResourceActionOn(
|
|
auth, resource, "search", self.Search
|
|
)
|
|
|
|
@typing.overload
|
|
def __call__(
|
|
self,
|
|
fn: (
|
|
_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch]
|
|
| _ActionHandler[dict[str, typing.Any]]
|
|
),
|
|
) -> _ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch]: ...
|
|
|
|
@typing.overload
|
|
def __call__(
|
|
self,
|
|
*,
|
|
resources: str | Sequence[str],
|
|
actions: str | Sequence[str] | None = None,
|
|
) -> Callable[
|
|
[_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch]],
|
|
_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch],
|
|
]: ...
|
|
|
|
def __call__(
|
|
self,
|
|
fn: (
|
|
_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch]
|
|
| _ActionHandler[dict[str, typing.Any]]
|
|
| None
|
|
) = None,
|
|
*,
|
|
resources: str | Sequence[str] | None = None,
|
|
actions: str | Sequence[str] | None = None,
|
|
) -> (
|
|
_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch]
|
|
| Callable[
|
|
[_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch]],
|
|
_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch],
|
|
]
|
|
):
|
|
if fn is not None:
|
|
_validate_handler(fn)
|
|
return typing.cast(
|
|
"_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch]",
|
|
_register_handler(self.auth, self.resource, "*", fn),
|
|
)
|
|
|
|
def decorator(
|
|
handler: _ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch],
|
|
) -> _ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch]:
|
|
_validate_handler(handler)
|
|
return typing.cast(
|
|
"_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch]",
|
|
_register_handler(self.auth, self.resource, "*", handler),
|
|
)
|
|
|
|
# Accept keyword-only parameters for future filtering behavior; referenced to satisfy linters.
|
|
_ = resources, actions
|
|
return decorator
|
|
|
|
|
|
class _AssistantsOn(
|
|
_ResourceOn[
|
|
types.AssistantsCreate,
|
|
types.AssistantsRead,
|
|
types.AssistantsUpdate,
|
|
types.AssistantsDelete,
|
|
types.AssistantsSearch,
|
|
]
|
|
):
|
|
value = (
|
|
types.AssistantsCreate
|
|
| types.AssistantsRead
|
|
| types.AssistantsUpdate
|
|
| types.AssistantsDelete
|
|
| types.AssistantsSearch
|
|
)
|
|
Create = types.AssistantsCreate
|
|
Read = types.AssistantsRead
|
|
Update = types.AssistantsUpdate
|
|
Delete = types.AssistantsDelete
|
|
Search = types.AssistantsSearch
|
|
|
|
|
|
class _ThreadsOn(
|
|
_ResourceOn[
|
|
types.ThreadsCreate,
|
|
types.ThreadsRead,
|
|
types.ThreadsUpdate,
|
|
types.ThreadsDelete,
|
|
types.ThreadsSearch,
|
|
]
|
|
):
|
|
value = (
|
|
types.ThreadsCreate
|
|
| types.ThreadsRead
|
|
| types.ThreadsUpdate
|
|
| types.ThreadsDelete
|
|
| types.ThreadsSearch
|
|
| types.RunsCreate
|
|
)
|
|
Create = types.ThreadsCreate
|
|
Read = types.ThreadsRead
|
|
Update = types.ThreadsUpdate
|
|
Delete = types.ThreadsDelete
|
|
Search = types.ThreadsSearch
|
|
CreateRun = types.RunsCreate
|
|
|
|
def __init__(
|
|
self,
|
|
auth: Auth,
|
|
resource: typing.Literal["threads", "crons", "assistants"],
|
|
) -> None:
|
|
super().__init__(auth, resource)
|
|
self.create_run: _ResourceActionOn[types.RunsCreate] = _ResourceActionOn(
|
|
auth, resource, "create_run", self.CreateRun
|
|
)
|
|
|
|
|
|
class _CronsOn(
|
|
_ResourceOn[
|
|
types.CronsCreate,
|
|
types.CronsRead,
|
|
types.CronsUpdate,
|
|
types.CronsDelete,
|
|
types.CronsSearch,
|
|
]
|
|
):
|
|
value = type[
|
|
types.CronsCreate
|
|
| types.CronsRead
|
|
| types.CronsUpdate
|
|
| types.CronsDelete
|
|
| types.CronsSearch
|
|
]
|
|
|
|
Create = types.CronsCreate
|
|
Read = types.CronsRead
|
|
Update = types.CronsUpdate
|
|
Delete = types.CronsDelete
|
|
Search = types.CronsSearch
|
|
|
|
|
|
class _StoreActionOn(typing.Generic[T]):
|
|
"""Decorator for registering a handler for a specific store action."""
|
|
|
|
def __init__(
|
|
self,
|
|
auth: Auth,
|
|
action: typing.Literal["put", "get", "search", "delete", "list_namespaces"],
|
|
value: type[T],
|
|
) -> None:
|
|
self.auth = auth
|
|
self.action = action
|
|
self.value = value
|
|
|
|
def __call__(self, fn: _ActionHandler[T]) -> _ActionHandler[T]:
|
|
_validate_handler(fn)
|
|
_register_handler(self.auth, "store", self.action, fn)
|
|
return fn
|
|
|
|
|
|
class _StoreOn:
|
|
def __init__(self, auth: Auth) -> None:
|
|
self._auth = auth
|
|
self.put = _StoreActionOn(auth, "put", types.StorePut)
|
|
"""Register a handler for store put operations.
|
|
|
|
???+ example "Example"
|
|
If using `@auth.on` to deny by default, register this handler to allow
|
|
put operations (scoped to the user's namespace):
|
|
|
|
```python
|
|
@auth.on.store.put
|
|
async def allow_store_put(ctx: Auth.types.AuthContext, value: Auth.types.on.store.put.value):
|
|
# Allow puts, scoped to user's namespace
|
|
value["namespace"] = (ctx.user.identity, *value["namespace"])
|
|
```
|
|
"""
|
|
self.get = _StoreActionOn(auth, "get", types.StoreGet)
|
|
"""Register a handler for store get operations.
|
|
|
|
???+ example "Example"
|
|
If using `@auth.on` to deny by default, register this handler to allow
|
|
get operations (scoped to the user's namespace):
|
|
|
|
```python
|
|
@auth.on.store.get
|
|
async def allow_store_get(ctx: Auth.types.AuthContext, value: Auth.types.on.store.get.value):
|
|
# Allow gets, scoped to user's namespace
|
|
value["namespace"] = (ctx.user.identity, *value["namespace"])
|
|
```
|
|
"""
|
|
self.search = _StoreActionOn(auth, "search", types.StoreSearch)
|
|
"""Register a handler for store search operations.
|
|
|
|
???+ example "Example"
|
|
If using `@auth.on` to deny by default, register this handler to allow
|
|
search operations (scoped to the user's namespace):
|
|
|
|
```python
|
|
@auth.on.store.search
|
|
async def allow_store_search(ctx: Auth.types.AuthContext, value: Auth.types.on.store.search.value):
|
|
# Allow searches, scoped to user's namespace
|
|
value["namespace"] = (ctx.user.identity, *value["namespace"])
|
|
```
|
|
"""
|
|
self.delete = _StoreActionOn(auth, "delete", types.StoreDelete)
|
|
"""Register a handler for store delete operations.
|
|
|
|
???+ example "Example"
|
|
If using `@auth.on` to deny by default, register this handler to allow
|
|
delete operations (scoped to the user's namespace):
|
|
|
|
```python
|
|
@auth.on.store.delete
|
|
async def allow_store_delete(ctx: Auth.types.AuthContext, value: Auth.types.on.store.delete.value):
|
|
# Allow deletes, scoped to user's namespace
|
|
value["namespace"] = (ctx.user.identity, *value["namespace"])
|
|
```
|
|
"""
|
|
self.list_namespaces = _StoreActionOn(
|
|
auth, "list_namespaces", types.StoreListNamespaces
|
|
)
|
|
"""Register a handler for store list_namespaces operations.
|
|
|
|
???+ example "Example"
|
|
If using `@auth.on` to deny by default, register this handler to allow
|
|
namespace listing (scoped to the user's prefix):
|
|
|
|
```python
|
|
@auth.on.store.list_namespaces
|
|
async def allow_list_ns(ctx: Auth.types.AuthContext, value: Auth.types.on.store.list_namespaces.value):
|
|
# Allow listing, scoped to user's namespace prefix
|
|
value["namespace"] = (ctx.user.identity,)
|
|
```
|
|
"""
|
|
|
|
@typing.overload
|
|
def __call__(
|
|
self,
|
|
*,
|
|
actions: (
|
|
typing.Literal["put", "get", "search", "list_namespaces", "delete"]
|
|
| Sequence[
|
|
typing.Literal["put", "get", "search", "list_namespaces", "delete"]
|
|
]
|
|
| None
|
|
) = None,
|
|
) -> Callable[[AHO], AHO]: ...
|
|
|
|
@typing.overload
|
|
def __call__(self, fn: AHO) -> AHO: ...
|
|
|
|
def __call__(
|
|
self,
|
|
fn: AHO | None = None,
|
|
*,
|
|
actions: (
|
|
typing.Literal["put", "get", "search", "list_namespaces", "delete"]
|
|
| Sequence[
|
|
typing.Literal["put", "get", "search", "list_namespaces", "delete"]
|
|
]
|
|
| None
|
|
) = None,
|
|
) -> AHO | Callable[[AHO], AHO]:
|
|
"""Register a handler for specific resources and actions.
|
|
|
|
Can be used as a decorator or with explicit resource/action parameters:
|
|
|
|
@auth.on.store
|
|
async def handler(): ... # Handle all store ops
|
|
|
|
@auth.on.store(actions=("put", "get", "search", "delete"))
|
|
async def handler(): ... # Handle specific store ops
|
|
|
|
@auth.on.store.put
|
|
async def handler(): ... # Handle store.put ops
|
|
"""
|
|
if fn is not None:
|
|
# Used as a plain decorator
|
|
_register_handler(self._auth, "store", None, fn)
|
|
return fn
|
|
|
|
# Used with parameters, return a decorator
|
|
def decorator(
|
|
handler: AHO,
|
|
) -> AHO:
|
|
if isinstance(actions, str):
|
|
action_list = [actions]
|
|
else:
|
|
action_list = list(actions) if actions is not None else ["*"]
|
|
for action in action_list:
|
|
_register_handler(self._auth, "store", action, handler)
|
|
return handler
|
|
|
|
return decorator
|
|
|
|
|
|
AHO = typing.TypeVar("AHO", bound=_ActionHandler[dict[str, typing.Any]])
|
|
|
|
|
|
class _On:
|
|
"""Entry point for authorization handlers that control access to specific resources.
|
|
|
|
The _On class provides a flexible way to define authorization rules for different resources
|
|
and actions in your application. It supports three main usage patterns:
|
|
|
|
1. Global handlers that run for all resources and actions
|
|
2. Resource-specific handlers that run for all actions on a resource
|
|
3. Resource and action specific handlers for fine-grained control
|
|
|
|
Each handler must be an async function that accepts two parameters:
|
|
- ctx (AuthContext): Contains request context and authenticated user info
|
|
- value: The data being authorized (type varies by endpoint)
|
|
|
|
The handler should return one of:
|
|
- None or True: Accept the request
|
|
- False: Reject with 403 error
|
|
- FilterType: Apply filtering rules to the response
|
|
|
|
???+ example "Examples"
|
|
|
|
Start by denying all requests by default with a global handler,
|
|
then add specific handlers to allow access:
|
|
|
|
```python
|
|
# Default deny: reject all requests without a specific handler
|
|
@auth.on
|
|
async def deny_all(ctx: AuthContext, value: Any) -> False:
|
|
return False
|
|
```
|
|
|
|
Resource-specific handler to allow access (takes precedence
|
|
over the global deny handler):
|
|
|
|
```python
|
|
@auth.on.threads
|
|
async def allow_thread_access(ctx: AuthContext, value: Any) -> Auth.types.FilterType:
|
|
# Allow access only to threads owned by the user
|
|
return {"owner": ctx.user.identity}
|
|
```
|
|
|
|
Resource and action specific handler:
|
|
|
|
```python
|
|
@auth.on.threads.create
|
|
async def allow_thread_create(ctx: AuthContext, value: Any) -> None:
|
|
# Allow thread creation, stamping the owner
|
|
value.setdefault("metadata", {})["owner"] = ctx.user.identity
|
|
```
|
|
|
|
Multiple resources or actions:
|
|
|
|
```python
|
|
@auth.on(resources=["threads", "assistants"], actions=["read", "search"])
|
|
async def allow_reads(ctx: AuthContext, value: Any) -> Auth.types.FilterType:
|
|
# Allow read/search, scoped to user's resources
|
|
return {"owner": ctx.user.identity}
|
|
```
|
|
"""
|
|
|
|
__slots__ = (
|
|
"_auth",
|
|
"assistants",
|
|
"crons",
|
|
"runs",
|
|
"store",
|
|
"threads",
|
|
"value",
|
|
)
|
|
|
|
def __init__(self, auth: Auth) -> None:
|
|
self._auth = auth
|
|
self.assistants = _AssistantsOn(auth, "assistants")
|
|
self.threads = _ThreadsOn(auth, "threads")
|
|
self.crons = _CronsOn(auth, "crons")
|
|
self.store = _StoreOn(auth)
|
|
self.value = dict[str, typing.Any]
|
|
|
|
@typing.overload
|
|
def __call__(
|
|
self,
|
|
*,
|
|
resources: str | Sequence[str],
|
|
actions: str | Sequence[str] | None = None,
|
|
) -> Callable[[AHO], AHO]: ...
|
|
|
|
@typing.overload
|
|
def __call__(self, fn: AHO) -> AHO: ...
|
|
|
|
def __call__(
|
|
self,
|
|
fn: AHO | None = None,
|
|
*,
|
|
resources: str | Sequence[str] | None = None,
|
|
actions: str | Sequence[str] | None = None,
|
|
) -> AHO | Callable[[AHO], AHO]:
|
|
"""Register a handler for specific resources and actions.
|
|
|
|
Can be used as a decorator or with explicit resource/action parameters:
|
|
|
|
@auth.on
|
|
async def handler(): ... # Global handler
|
|
|
|
@auth.on(resources="threads")
|
|
async def handler(): ... # types.Handler for all thread actions
|
|
|
|
@auth.on(resources="threads", actions="create")
|
|
async def handler(): ... # types.Handler for thread creation
|
|
"""
|
|
if fn is not None:
|
|
# Used as a plain decorator
|
|
_register_handler(self._auth, None, None, fn)
|
|
return fn
|
|
|
|
# Used with parameters, return a decorator
|
|
def decorator(
|
|
handler: AHO,
|
|
) -> AHO:
|
|
if isinstance(resources, str):
|
|
resource_list = [resources]
|
|
else:
|
|
resource_list = list(resources) if resources is not None else ["*"]
|
|
|
|
if isinstance(actions, str):
|
|
action_list = [actions]
|
|
else:
|
|
action_list = list(actions) if actions is not None else ["*"]
|
|
for resource in resource_list:
|
|
for action in action_list:
|
|
_register_handler(self._auth, resource, action, handler)
|
|
return handler
|
|
|
|
return decorator
|
|
|
|
|
|
def _register_handler(
|
|
auth: Auth,
|
|
resource: str | None,
|
|
action: str | None,
|
|
fn: types.Handler,
|
|
) -> types.Handler:
|
|
_validate_handler(fn)
|
|
resource = resource or "*"
|
|
action = action or "*"
|
|
if resource == "*" and action == "*":
|
|
if auth._global_handlers:
|
|
raise ValueError("Global handler already set.")
|
|
auth._global_handlers.append(fn)
|
|
else:
|
|
r = resource if resource is not None else "*"
|
|
a = action if action is not None else "*"
|
|
if (r, a) in auth._handlers:
|
|
raise ValueError(f"types.Handler already set for {r}, {a}.")
|
|
auth._handlers[(r, a)] = [fn]
|
|
return fn
|
|
|
|
|
|
def _validate_handler(fn: Callable[..., typing.Any]) -> None:
|
|
"""Validates that an auth handler function meets the required signature.
|
|
|
|
Auth handlers must:
|
|
1. Be async functions
|
|
2. Accept a ctx parameter of type AuthContext
|
|
3. Accept a value parameter for the data being authorized
|
|
"""
|
|
if not inspect.iscoroutinefunction(fn):
|
|
raise ValueError(
|
|
f"Auth handler '{getattr(fn, '__name__', fn)}' must be an async function. "
|
|
"Add 'async' before 'def' to make it asynchronous and ensure"
|
|
" any IO operations are non-blocking."
|
|
)
|
|
|
|
sig = inspect.signature(fn)
|
|
if "ctx" not in sig.parameters:
|
|
raise ValueError(
|
|
f"Auth handler '{getattr(fn, '__name__', fn)}' must have a 'ctx: AuthContext' parameter. "
|
|
"Update the function signature to include this required parameter."
|
|
)
|
|
if "value" not in sig.parameters:
|
|
raise ValueError(
|
|
f"Auth handler '{getattr(fn, '__name__', fn)}' must have a 'value' parameter. "
|
|
" The value contains the mutable data being sent to the endpoint."
|
|
"Update the function signature to include this required parameter."
|
|
)
|
|
|
|
|
|
def is_studio_user(
|
|
user: types.MinimalUser | types.BaseUser | types.MinimalUserDict,
|
|
) -> bool:
|
|
return isinstance(user, types.StudioUser) or (
|
|
isinstance(user, dict) and user.get("kind") == "StudioUser"
|
|
)
|
|
|
|
|
|
__all__ = ["Auth", "exceptions", "types"]
|