mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-10-14 12:15:25 +02:00
Bumps the minor-and-patch group in /libs/checkpoint-sqlite with 3 updates: [pytest-mock](https://github.com/pytest-dev/pytest-mock), [ruff](https://github.com/astral-sh/ruff) and [ty](https://github.com/astral-sh/ty). 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) <https://github.com/pytest-dev/pytest-mock/pull/604></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) <https://github.com/pytest-dev/pytest-mock/pull/611></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) <https://github.com/pytest-dev/pytest-mock/pull/606></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) <https://github.com/pytest-dev/pytest-mock/issues/547></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) <https://github.com/pytest-dev/pytest-mock/issues/147></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 `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>"foo" in ["foo"]</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>"foo" in ["foo"]</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>
633 lines
23 KiB
Python
633 lines
23 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import logging
|
|
from collections import defaultdict
|
|
from collections.abc import AsyncIterator, Callable, Iterable, Sequence
|
|
from contextlib import asynccontextmanager
|
|
from types import TracebackType
|
|
from typing import Any, cast
|
|
|
|
import aiosqlite
|
|
import orjson
|
|
import sqlite_vec # type: ignore[import-untyped]
|
|
from langgraph.store.base import (
|
|
GetOp,
|
|
ListNamespacesOp,
|
|
Op,
|
|
PutOp,
|
|
Result,
|
|
SearchOp,
|
|
TTLConfig,
|
|
)
|
|
from langgraph.store.base.batch import AsyncBatchedBaseStore
|
|
|
|
from langgraph.store.sqlite.base import (
|
|
_PLACEHOLDER,
|
|
NS_MATCH_FUNCTION,
|
|
BaseSqliteStore,
|
|
SqliteIndexConfig,
|
|
_decode_ns_text,
|
|
_ensure_index_config,
|
|
_group_ops,
|
|
_namespace_match,
|
|
_row_to_item,
|
|
_row_to_search_item,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class AsyncSqliteStore(AsyncBatchedBaseStore, BaseSqliteStore):
|
|
"""Asynchronous SQLite-backed store with optional vector search.
|
|
|
|
This class provides an asynchronous interface for storing and retrieving data
|
|
using a SQLite database with support for vector search capabilities.
|
|
|
|
Examples:
|
|
Basic setup and usage:
|
|
```python
|
|
from langgraph.store.sqlite import AsyncSqliteStore
|
|
|
|
async with AsyncSqliteStore.from_conn_string(":memory:") as store:
|
|
await store.setup() # Run migrations
|
|
|
|
# Store and retrieve data
|
|
await store.aput(("users", "123"), "prefs", {"theme": "dark"})
|
|
item = await store.aget(("users", "123"), "prefs")
|
|
```
|
|
|
|
Vector search using LangChain embeddings:
|
|
```python
|
|
from langchain_openai import OpenAIEmbeddings
|
|
from langgraph.store.sqlite import AsyncSqliteStore
|
|
|
|
async with AsyncSqliteStore.from_conn_string(
|
|
":memory:",
|
|
index={
|
|
"dims": 1536,
|
|
"embed": OpenAIEmbeddings(),
|
|
"fields": ["text"] # specify which fields to embed
|
|
}
|
|
) as store:
|
|
await store.setup() # Run migrations once
|
|
|
|
# Store documents
|
|
await store.aput(("docs",), "doc1", {"text": "Python tutorial"})
|
|
await store.aput(("docs",), "doc2", {"text": "TypeScript guide"})
|
|
await store.aput(("docs",), "doc3", {"text": "Other guide"}, index=False) # don't index
|
|
|
|
# Search by similarity
|
|
results = await store.asearch(("docs",), query="programming guides", limit=2)
|
|
```
|
|
|
|
Warning:
|
|
Make sure to call `setup()` before first use to create necessary tables and indexes.
|
|
|
|
Note:
|
|
This class requires the aiosqlite package. Install with `pip install aiosqlite`.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
conn: aiosqlite.Connection,
|
|
*,
|
|
deserializer: Callable[[bytes | str | orjson.Fragment], dict[str, Any]]
|
|
| None = None,
|
|
index: SqliteIndexConfig | None = None,
|
|
ttl: TTLConfig | None = None,
|
|
):
|
|
"""Initialize the async SQLite store.
|
|
|
|
Args:
|
|
conn: The SQLite database connection.
|
|
deserializer: Optional custom deserializer function for values.
|
|
index: Optional vector search configuration.
|
|
ttl: Optional time-to-live configuration.
|
|
"""
|
|
super().__init__()
|
|
self._deserializer = deserializer
|
|
self.conn = conn
|
|
self.lock = asyncio.Lock()
|
|
self.loop = asyncio.get_running_loop()
|
|
self.is_setup = False
|
|
self.index_config = index
|
|
if self.index_config:
|
|
self.embeddings, self.index_config = _ensure_index_config(self.index_config)
|
|
else:
|
|
self.embeddings = None
|
|
self.ttl_config = ttl
|
|
self._ttl_sweeper_task: asyncio.Task[None] | None = None
|
|
self._ttl_stop_event = asyncio.Event()
|
|
|
|
@classmethod
|
|
@asynccontextmanager
|
|
async def from_conn_string(
|
|
cls,
|
|
conn_string: str,
|
|
*,
|
|
index: SqliteIndexConfig | None = None,
|
|
ttl: TTLConfig | None = None,
|
|
) -> AsyncIterator[AsyncSqliteStore]:
|
|
"""Create a new AsyncSqliteStore instance from a connection string.
|
|
|
|
Args:
|
|
conn_string: The SQLite connection string.
|
|
index: Optional vector search configuration.
|
|
ttl: Optional time-to-live configuration.
|
|
|
|
Returns:
|
|
An AsyncSqliteStore instance wrapped in an async context manager.
|
|
"""
|
|
async with aiosqlite.connect(conn_string, isolation_level=None) as conn:
|
|
yield cls(conn, index=index, ttl=ttl)
|
|
|
|
async def setup(self) -> None:
|
|
"""Set up the store database.
|
|
|
|
This method creates the necessary tables in the SQLite database if they don't
|
|
already exist and runs database migrations. It should be called before first use.
|
|
"""
|
|
async with self.lock:
|
|
if self.is_setup:
|
|
return
|
|
|
|
# list_namespaces needs segment-aware matching, which SQLite cannot
|
|
# express in LIKE or GLOB. Registered here rather than in __init__
|
|
# because aiosqlite's create_function is a coroutine.
|
|
await self.conn.create_function(
|
|
NS_MATCH_FUNCTION, 2, _namespace_match, deterministic=True
|
|
)
|
|
|
|
# Create migrations table if it doesn't exist
|
|
await self.conn.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS store_migrations (
|
|
v INTEGER PRIMARY KEY
|
|
)
|
|
"""
|
|
)
|
|
|
|
# Check current migration version
|
|
async with self.conn.execute(
|
|
"SELECT v FROM store_migrations ORDER BY v DESC LIMIT 1"
|
|
) as cur:
|
|
row = await cur.fetchone()
|
|
if row is None:
|
|
version = -1
|
|
else:
|
|
version = row[0]
|
|
|
|
# Apply migrations
|
|
for v, sql in enumerate(self.MIGRATIONS[version + 1 :], start=version + 1):
|
|
await self.conn.executescript(sql)
|
|
await self.conn.execute(
|
|
"INSERT INTO store_migrations (v) VALUES (?)", (v,)
|
|
)
|
|
|
|
# Apply vector migrations if index config is provided
|
|
if self.index_config:
|
|
# Create vector migrations table if it doesn't exist
|
|
await self.conn.enable_load_extension(True)
|
|
await self.conn.load_extension(sqlite_vec.loadable_path())
|
|
await self.conn.enable_load_extension(False)
|
|
await self.conn.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS vector_migrations (
|
|
v INTEGER PRIMARY KEY
|
|
)
|
|
"""
|
|
)
|
|
|
|
# Check current vector migration version
|
|
async with self.conn.execute(
|
|
"SELECT v FROM vector_migrations ORDER BY v DESC LIMIT 1"
|
|
) as cur:
|
|
row = await cur.fetchone()
|
|
if row is None:
|
|
version = -1
|
|
else:
|
|
version = row[0]
|
|
|
|
# Apply vector migrations
|
|
for v, sql in enumerate(
|
|
self.VECTOR_MIGRATIONS[version + 1 :], start=version + 1
|
|
):
|
|
await self.conn.executescript(sql)
|
|
await self.conn.execute(
|
|
"INSERT INTO vector_migrations (v) VALUES (?)", (v,)
|
|
)
|
|
|
|
self.is_setup = True
|
|
|
|
@asynccontextmanager
|
|
async def _cursor(
|
|
self, *, transaction: bool = True
|
|
) -> AsyncIterator[aiosqlite.Cursor]:
|
|
"""Get a cursor for the SQLite database.
|
|
|
|
Args:
|
|
transaction: Whether to use a transaction for database operations.
|
|
|
|
Yields:
|
|
An SQLite cursor object.
|
|
"""
|
|
if not self.is_setup:
|
|
await self.setup()
|
|
async with self.lock:
|
|
if transaction:
|
|
await self.conn.execute("BEGIN")
|
|
|
|
async with self.conn.cursor() as cur:
|
|
try:
|
|
yield cur
|
|
finally:
|
|
if transaction:
|
|
await self.conn.execute("COMMIT")
|
|
|
|
async def sweep_ttl(self) -> int:
|
|
"""Delete expired store items based on TTL.
|
|
|
|
Returns:
|
|
int: The number of deleted items.
|
|
"""
|
|
async with self._cursor() as cur:
|
|
await cur.execute(
|
|
"""
|
|
DELETE FROM store
|
|
WHERE expires_at IS NOT NULL AND expires_at < CURRENT_TIMESTAMP
|
|
"""
|
|
)
|
|
deleted_count = cur.rowcount
|
|
return deleted_count
|
|
|
|
async def start_ttl_sweeper(
|
|
self, sweep_interval_minutes: int | None = None
|
|
) -> asyncio.Task[None]:
|
|
"""Periodically delete expired store items based on TTL.
|
|
|
|
Returns:
|
|
Task that can be awaited or cancelled.
|
|
"""
|
|
if not self.ttl_config:
|
|
return asyncio.create_task(asyncio.sleep(0))
|
|
|
|
if self._ttl_sweeper_task is not None and not self._ttl_sweeper_task.done():
|
|
return self._ttl_sweeper_task
|
|
|
|
self._ttl_stop_event.clear()
|
|
|
|
interval = float(
|
|
sweep_interval_minutes or self.ttl_config.get("sweep_interval_minutes") or 5
|
|
)
|
|
logger.info(f"Starting store TTL sweeper with interval {interval} minutes")
|
|
|
|
async def _sweep_loop() -> None:
|
|
while not self._ttl_stop_event.is_set():
|
|
try:
|
|
try:
|
|
await asyncio.wait_for(
|
|
self._ttl_stop_event.wait(),
|
|
timeout=interval * 60,
|
|
)
|
|
break
|
|
except asyncio.TimeoutError:
|
|
pass
|
|
|
|
expired_items = await self.sweep_ttl()
|
|
if expired_items > 0:
|
|
logger.info(f"Store swept {expired_items} expired items")
|
|
except asyncio.CancelledError:
|
|
break
|
|
except Exception as exc:
|
|
logger.exception("Store TTL sweep iteration failed", exc_info=exc)
|
|
|
|
task = asyncio.create_task(_sweep_loop())
|
|
task.set_name("ttl_sweeper")
|
|
self._ttl_sweeper_task = task
|
|
return task
|
|
|
|
async def stop_ttl_sweeper(self, timeout: float | None = None) -> bool:
|
|
"""Stop the TTL sweeper task if it's running.
|
|
|
|
Args:
|
|
timeout: Maximum time to wait for the task to stop, in seconds.
|
|
If `None`, wait indefinitely.
|
|
|
|
Returns:
|
|
bool: True if the task was successfully stopped or wasn't running,
|
|
False if the timeout was reached before the task stopped.
|
|
"""
|
|
if self._ttl_sweeper_task is None or self._ttl_sweeper_task.done():
|
|
return True
|
|
|
|
logger.info("Stopping TTL sweeper task")
|
|
self._ttl_stop_event.set()
|
|
|
|
if timeout is not None:
|
|
try:
|
|
await asyncio.wait_for(self._ttl_sweeper_task, timeout=timeout)
|
|
success = True
|
|
except asyncio.TimeoutError:
|
|
success = False
|
|
else:
|
|
await self._ttl_sweeper_task
|
|
success = True
|
|
|
|
if success:
|
|
self._ttl_sweeper_task = None
|
|
logger.info("TTL sweeper task stopped")
|
|
else:
|
|
logger.warning("Timed out waiting for TTL sweeper task to stop")
|
|
|
|
return success
|
|
|
|
async def __aenter__(self) -> AsyncSqliteStore:
|
|
return self
|
|
|
|
async def __aexit__(
|
|
self,
|
|
exc_type: type[BaseException] | None,
|
|
exc_val: BaseException | None,
|
|
exc_tb: TracebackType | None,
|
|
) -> None:
|
|
# Ensure the TTL sweeper task is stopped when exiting the context
|
|
if hasattr(self, "_ttl_sweeper_task") and self._ttl_sweeper_task is not None:
|
|
# Set the event to signal the task to stop
|
|
self._ttl_stop_event.set()
|
|
# We don't wait for the task to complete here to avoid blocking
|
|
# The task will clean up itself gracefully
|
|
|
|
async def abatch(self, ops: Iterable[Op]) -> list[Result]:
|
|
"""Execute a batch of operations asynchronously.
|
|
|
|
Args:
|
|
ops: Iterable of operations to execute.
|
|
|
|
Returns:
|
|
List of operation results.
|
|
"""
|
|
grouped_ops, num_ops = _group_ops(ops)
|
|
results: list[Result] = [None] * num_ops
|
|
|
|
async with self._cursor(transaction=True) as cur:
|
|
if GetOp in grouped_ops:
|
|
await self._batch_get_ops(
|
|
cast(Sequence[tuple[int, GetOp]], grouped_ops[GetOp]), results, cur
|
|
)
|
|
|
|
if SearchOp in grouped_ops:
|
|
await self._batch_search_ops(
|
|
cast(Sequence[tuple[int, SearchOp]], grouped_ops[SearchOp]),
|
|
results,
|
|
cur,
|
|
)
|
|
|
|
if ListNamespacesOp in grouped_ops:
|
|
await self._batch_list_namespaces_ops(
|
|
cast(
|
|
Sequence[tuple[int, ListNamespacesOp]],
|
|
grouped_ops[ListNamespacesOp],
|
|
),
|
|
results,
|
|
cur,
|
|
)
|
|
|
|
if PutOp in grouped_ops:
|
|
await self._batch_put_ops(
|
|
cast(Sequence[tuple[int, PutOp]], grouped_ops[PutOp]), cur
|
|
)
|
|
|
|
return results
|
|
|
|
async def _batch_get_ops(
|
|
self,
|
|
get_ops: Sequence[tuple[int, GetOp]],
|
|
results: list[Result],
|
|
cur: aiosqlite.Cursor,
|
|
) -> None:
|
|
"""Process batch GET operations.
|
|
|
|
Args:
|
|
get_ops: Sequence of GET operations.
|
|
results: List to store results in.
|
|
cur: Database cursor.
|
|
"""
|
|
# Group all queries by namespace to execute all operations for each namespace together
|
|
namespace_queries = defaultdict(list)
|
|
for prepared_query in self._get_batch_GET_ops_queries(get_ops):
|
|
namespace_queries[prepared_query.namespace].append(prepared_query)
|
|
|
|
# Process each namespace's operations
|
|
for namespace, queries in namespace_queries.items():
|
|
# Execute TTL refresh queries first
|
|
for query in queries:
|
|
if query.kind == "refresh":
|
|
try:
|
|
await cur.execute(query.query, query.params)
|
|
except Exception as e:
|
|
raise ValueError(
|
|
f"Error executing TTL refresh: \n{query.query}\n{query.params}\n{e}"
|
|
) from e
|
|
|
|
# Then execute GET queries and process results
|
|
for query in queries:
|
|
if query.kind == "get":
|
|
try:
|
|
await cur.execute(query.query, query.params)
|
|
except Exception as e:
|
|
raise ValueError(
|
|
f"Error executing GET query: \n{query.query}\n{query.params}\n{e}"
|
|
) from e
|
|
|
|
rows = await cur.fetchall()
|
|
key_to_row = {
|
|
row[0]: {
|
|
"key": row[0],
|
|
"value": row[1],
|
|
"created_at": row[2],
|
|
"updated_at": row[3],
|
|
"expires_at": row[4] if len(row) > 4 else None,
|
|
"ttl_minutes": row[5] if len(row) > 5 else None,
|
|
}
|
|
for row in rows
|
|
}
|
|
|
|
# Process results for this query
|
|
for idx, key in query.items:
|
|
row = key_to_row.get(key)
|
|
if row:
|
|
results[idx] = _row_to_item(
|
|
namespace, row, loader=self._deserializer
|
|
)
|
|
else:
|
|
results[idx] = None
|
|
|
|
async def _batch_put_ops(
|
|
self,
|
|
put_ops: Sequence[tuple[int, PutOp]],
|
|
cur: aiosqlite.Cursor,
|
|
) -> None:
|
|
"""Process batch PUT operations.
|
|
|
|
Args:
|
|
put_ops: Sequence of PUT operations.
|
|
cur: Database cursor.
|
|
"""
|
|
queries, embedding_request = self._prepare_batch_PUT_queries(put_ops)
|
|
if embedding_request:
|
|
if self.embeddings is None:
|
|
# Should not get here since the embedding config is required
|
|
# to return an embedding_request above
|
|
raise ValueError(
|
|
"Embedding configuration is required for vector operations "
|
|
f"(for semantic search). "
|
|
f"Please provide an Embeddings when initializing the {self.__class__.__name__}."
|
|
)
|
|
|
|
query, txt_params = embedding_request
|
|
# Update the params to replace the raw text with the vectors
|
|
vectors = await self.embeddings.aembed_documents(
|
|
[param[-1] for param in txt_params]
|
|
)
|
|
|
|
# Convert vectors to SQLite-friendly format
|
|
vector_params = []
|
|
for (ns, k, pathname, _), vector in zip(txt_params, vectors, strict=False):
|
|
vector_params.extend(
|
|
[ns, k, pathname, sqlite_vec.serialize_float32(vector)]
|
|
)
|
|
|
|
queries.append((query, vector_params))
|
|
|
|
for query, params in queries:
|
|
await cur.execute(query, params)
|
|
|
|
async def _batch_search_ops(
|
|
self,
|
|
search_ops: Sequence[tuple[int, SearchOp]],
|
|
results: list[Result],
|
|
cur: aiosqlite.Cursor,
|
|
) -> None:
|
|
"""Process batch SEARCH operations.
|
|
|
|
Args:
|
|
search_ops: Sequence of SEARCH operations.
|
|
results: List to store results in.
|
|
cur: Database cursor.
|
|
"""
|
|
prepared_queries, embedding_requests = self._prepare_batch_search_queries(
|
|
search_ops
|
|
)
|
|
|
|
# Setup dot_product function if it doesn't exist
|
|
if embedding_requests and self.embeddings:
|
|
vectors = await self.embeddings.aembed_documents(
|
|
[query for _, query in embedding_requests]
|
|
)
|
|
|
|
for (embed_req_idx, _), embedding in zip(
|
|
embedding_requests, vectors, strict=False
|
|
):
|
|
# Find the corresponding query in prepared_queries
|
|
# The embed_req_idx is the original index in search_ops, which should map to prepared_queries
|
|
if embed_req_idx < len(prepared_queries):
|
|
_params_list: list = prepared_queries[embed_req_idx][1]
|
|
for i, param in enumerate(_params_list):
|
|
if param is _PLACEHOLDER:
|
|
_params_list[i] = sqlite_vec.serialize_float32(embedding)
|
|
else:
|
|
logger.warning(
|
|
f"Embedding request index {embed_req_idx} out of bounds for prepared_queries."
|
|
)
|
|
|
|
for (original_op_idx, _), (query, params, needs_refresh) in zip(
|
|
search_ops, prepared_queries, strict=False
|
|
):
|
|
await cur.execute(query, params)
|
|
rows = await cur.fetchall()
|
|
|
|
if needs_refresh and self.ttl_config:
|
|
keys_to_refresh = []
|
|
for row_data in rows:
|
|
# Assuming row_data[0] is prefix (text), row_data[1] is key (text)
|
|
# These are raw text values directly from the DB.
|
|
keys_to_refresh.append((row_data[0], row_data[1]))
|
|
|
|
if keys_to_refresh:
|
|
updates_by_prefix = defaultdict(list)
|
|
for prefix_text, key_text in keys_to_refresh:
|
|
updates_by_prefix[prefix_text].append(key_text)
|
|
|
|
for prefix_text, key_list in updates_by_prefix.items():
|
|
placeholders = ",".join(["?"] * len(key_list))
|
|
update_query = f"""
|
|
UPDATE store
|
|
SET expires_at = DATETIME(CURRENT_TIMESTAMP, '+' || ttl_minutes || ' minutes')
|
|
WHERE prefix = ? AND key IN ({placeholders}) AND ttl_minutes IS NOT NULL
|
|
"""
|
|
update_params = (prefix_text, *key_list)
|
|
try:
|
|
await cur.execute(update_query, update_params)
|
|
except Exception as e:
|
|
logger.error(
|
|
f"Error during TTL refresh update for search: {e}"
|
|
)
|
|
|
|
# Process rows into items
|
|
if "score" in query: # Vector search query
|
|
items = [
|
|
_row_to_search_item(
|
|
_decode_ns_text(row[0]), # prefix
|
|
{
|
|
"key": row[1], # key
|
|
"value": row[2], # value
|
|
"created_at": row[3],
|
|
"updated_at": row[4],
|
|
"expires_at": row[5] if len(row) > 5 else None,
|
|
"ttl_minutes": row[6] if len(row) > 6 else None,
|
|
"score": row[7] if len(row) > 7 else None,
|
|
},
|
|
loader=self._deserializer,
|
|
)
|
|
for row in rows
|
|
]
|
|
else: # Regular search query
|
|
items = [
|
|
_row_to_search_item(
|
|
_decode_ns_text(row[0]), # prefix
|
|
{
|
|
"key": row[1], # key
|
|
"value": row[2], # value
|
|
"created_at": row[3],
|
|
"updated_at": row[4],
|
|
"expires_at": row[5] if len(row) > 5 else None,
|
|
"ttl_minutes": row[6] if len(row) > 6 else None,
|
|
},
|
|
loader=self._deserializer,
|
|
)
|
|
for row in rows
|
|
]
|
|
|
|
results[original_op_idx] = items
|
|
|
|
async def _batch_list_namespaces_ops(
|
|
self,
|
|
list_ops: Sequence[tuple[int, ListNamespacesOp]],
|
|
results: list[Result],
|
|
cur: aiosqlite.Cursor,
|
|
) -> None:
|
|
"""Process batch LIST NAMESPACES operations.
|
|
|
|
Args:
|
|
list_ops: Sequence of LIST NAMESPACES operations.
|
|
results: List to store results in.
|
|
cur: Database cursor.
|
|
"""
|
|
queries = self._get_batch_list_namespaces_queries(list_ops)
|
|
for (query, params), (idx, _) in zip(queries, list_ops, strict=False):
|
|
await cur.execute(query, params)
|
|
|
|
rows = await cur.fetchall()
|
|
results[idx] = [_decode_ns_text(row[0]) for row in rows]
|