From 3475333e5d04b498e91a26426a89c94afd5ce08b Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 6 Apr 2026 23:08:40 -0700 Subject: [PATCH 01/15] =?UTF-8?q?fix:=20harden=20NL2SQLTool=20=E2=80=94=20?= =?UTF-8?q?read-only=20by=20default,=20parameterized=20queries,=20query=20?= =?UTF-8?q?validation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../crewai_tools/tools/nl2sql/nl2sql_tool.py | 172 ++++++++++- .../tests/tools/test_nl2sql_security.py | 291 ++++++++++++++++++ 2 files changed, 452 insertions(+), 11 deletions(-) create mode 100644 lib/crewai-tools/tests/tools/test_nl2sql_security.py diff --git a/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py b/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py index bfb9c02dd2..84b4bd7728 100644 --- a/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py +++ b/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py @@ -1,7 +1,9 @@ +import logging +import os from typing import Any from crewai.tools import BaseTool -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, model_validator try: @@ -12,6 +14,29 @@ except ImportError: SQLALCHEMY_AVAILABLE = False +logger = logging.getLogger(__name__) + +# Commands allowed in read-only mode +_READ_ONLY_COMMANDS = {"SELECT", "SHOW", "DESCRIBE", "DESC", "EXPLAIN", "WITH"} + +# Commands that mutate state and are blocked by default +_WRITE_COMMANDS = { + "INSERT", + "UPDATE", + "DELETE", + "DROP", + "ALTER", + "CREATE", + "TRUNCATE", + "GRANT", + "REVOKE", + "EXEC", + "EXECUTE", + "CALL", + "MERGE", + "REPLACE", +} + class NL2SQLToolInput(BaseModel): sql_query: str = Field( @@ -21,20 +46,65 @@ class NL2SQLToolInput(BaseModel): class NL2SQLTool(BaseTool): + """Tool that converts natural language to SQL and executes it against a database. + + By default the tool operates in **read-only mode**: only SELECT, SHOW, + DESCRIBE, EXPLAIN, and WITH (CTE) statements are permitted. Write + operations (INSERT, UPDATE, DELETE, DROP, ALTER, CREATE, TRUNCATE, …) are + blocked unless ``allow_dml=True`` is set explicitly or the environment + variable ``CREWAI_NL2SQL_ALLOW_DML=true`` is present. + + The ``_fetch_all_available_columns`` helper uses parameterised queries so + that table names coming from the database catalogue cannot be used as an + injection vector. + """ + name: str = "NL2SQLTool" - description: str = "Converts natural language to SQL queries and executes them." + description: str = ( + "Converts natural language to SQL queries and executes them against a " + "database. Read-only by default — only SELECT/SHOW/DESCRIBE/EXPLAIN " + "queries are allowed unless the tool is configured with allow_dml=True." + ) db_uri: str = Field( title="Database URI", description="The URI of the database to connect to.", ) + allow_dml: bool = Field( + default=False, + title="Allow DML", + description=( + "When False (default) only read statements are permitted. " + "Set to True to allow INSERT/UPDATE/DELETE/DROP and other " + "write operations." + ), + ) tables: list[dict[str, Any]] = Field(default_factory=list) columns: dict[str, list[dict[str, Any]] | str] = Field(default_factory=dict) args_schema: type[BaseModel] = NL2SQLToolInput + @model_validator(mode="after") + def _apply_env_override(self) -> "NL2SQLTool": + """Allow CREWAI_NL2SQL_ALLOW_DML=true to override allow_dml at runtime.""" + if os.environ.get("CREWAI_NL2SQL_ALLOW_DML", "").strip().lower() == "true": + if not self.allow_dml: + logger.warning( + "NL2SQLTool: CREWAI_NL2SQL_ALLOW_DML env var is set — " + "DML/DDL operations are enabled. Ensure this is intentional." + ) + self.allow_dml = True + return self + def model_post_init(self, __context: Any) -> None: if not SQLALCHEMY_AVAILABLE: raise ImportError( - "sqlalchemy is not installed. Please install it with `pip install crewai-tools[sqlalchemy]`" + "sqlalchemy is not installed. Please install it with " + "`pip install crewai-tools[sqlalchemy]`" + ) + + if self.allow_dml: + logger.warning( + "NL2SQLTool: allow_dml=True — write operations (INSERT/UPDATE/" + "DELETE/DROP/…) are permitted. Use with caution." ) data: dict[str, list[dict[str, Any]] | str] = {} @@ -50,42 +120,122 @@ def model_post_init(self, __context: Any) -> None: self.tables = tables self.columns = data + # ------------------------------------------------------------------ + # Query validation + # ------------------------------------------------------------------ + + def _validate_query(self, sql_query: str) -> None: + """Raise ValueError if *sql_query* is not permitted under the current config. + + Parses the leading SQL command keyword and checks it against the + allowed set. When ``allow_dml=False`` (the default) only read + statements pass. When ``allow_dml=True`` all statements are allowed + but a warning is emitted for write operations. + """ + command = self._extract_command(sql_query) + + if command in _WRITE_COMMANDS: + if not self.allow_dml: + raise ValueError( + f"NL2SQLTool is configured in read-only mode and blocked a " + f"'{command}' statement. To allow write operations set " + f"allow_dml=True or CREWAI_NL2SQL_ALLOW_DML=true." + ) + logger.warning( + "NL2SQLTool: executing write statement '%s' because allow_dml=True.", + command, + ) + elif command not in _READ_ONLY_COMMANDS: + # Unknown command — block by default unless DML is explicitly enabled + if not self.allow_dml: + raise ValueError( + f"NL2SQLTool blocked an unrecognised SQL command '{command}'. " + f"Only {sorted(_READ_ONLY_COMMANDS)} are allowed in read-only " + f"mode." + ) + + @staticmethod + def _extract_command(sql_query: str) -> str: + """Return the uppercased first keyword of *sql_query*.""" + stripped = sql_query.strip().lstrip("(") + first_token = stripped.split()[0] if stripped.split() else "" + return first_token.upper().rstrip(";") + + # ------------------------------------------------------------------ + # Schema introspection helpers + # ------------------------------------------------------------------ + def _fetch_available_tables(self) -> list[dict[str, Any]] | str: return self.execute_sql( - "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';" + "SELECT table_name FROM information_schema.tables " + "WHERE table_schema = 'public';" ) def _fetch_all_available_columns( self, table_name: str ) -> list[dict[str, Any]] | str: + """Fetch columns for *table_name* using a parameterised query. + + The table name is bound via SQLAlchemy's ``:param`` syntax to prevent + SQL injection from catalogue values. + """ return self.execute_sql( - f"SELECT column_name, data_type FROM information_schema.columns WHERE table_name = '{table_name}';" # noqa: S608 + "SELECT column_name, data_type FROM information_schema.columns " + "WHERE table_name = :table_name", + params={"table_name": table_name}, ) + # ------------------------------------------------------------------ + # Core execution + # ------------------------------------------------------------------ + def _run(self, sql_query: str) -> list[dict[str, Any]] | str: try: + self._validate_query(sql_query) data = self.execute_sql(sql_query) + except ValueError: + raise except Exception as exc: data = ( f"Based on these tables {self.tables} and columns {self.columns}, " - "you can create SQL queries to retrieve data from the database." - f"Get the original request {sql_query} and the error {exc} and create the correct SQL query." + "you can create SQL queries to retrieve data from the database. " + f"Get the original request {sql_query} and the error {exc} and " + "create the correct SQL query." ) return data - def execute_sql(self, sql_query: str) -> list[dict[str, Any]] | str: + def execute_sql( + self, + sql_query: str, + params: dict[str, Any] | None = None, + ) -> list[dict[str, Any]] | str: + """Execute *sql_query* and return the results as a list of dicts. + + Parameters + ---------- + sql_query: + The SQL statement to run. + params: + Optional mapping of bind parameters (e.g. ``{"table_name": "users"}``). + """ if not SQLALCHEMY_AVAILABLE: raise ImportError( - "sqlalchemy is not installed. Please install it with `pip install crewai-tools[sqlalchemy]`" + "sqlalchemy is not installed. Please install it with " + "`pip install crewai-tools[sqlalchemy]`" ) + is_write = self._extract_command(sql_query) in _WRITE_COMMANDS + engine = create_engine(self.db_uri) Session = sessionmaker(bind=engine) # noqa: N806 session = Session() try: - result = session.execute(text(sql_query)) - session.commit() + result = session.execute(text(sql_query), params or {}) + + # Only commit when the operation actually mutates state + if self.allow_dml and is_write: + session.commit() if result.returns_rows: # type: ignore[attr-defined] columns = result.keys() diff --git a/lib/crewai-tools/tests/tools/test_nl2sql_security.py b/lib/crewai-tools/tests/tools/test_nl2sql_security.py new file mode 100644 index 0000000000..f13a7c8ea6 --- /dev/null +++ b/lib/crewai-tools/tests/tools/test_nl2sql_security.py @@ -0,0 +1,291 @@ +"""Security tests for NL2SQLTool. + +Uses an in-memory SQLite database so no external service is needed. +SQLite does not have information_schema, so we patch the schema-introspection +helpers to avoid bootstrap failures and focus purely on the security logic. +""" +import os +from unittest.mock import MagicMock, patch + +import pytest + +# Skip the entire module if SQLAlchemy is not installed +pytest.importorskip("sqlalchemy") + +from sqlalchemy import create_engine, text # noqa: E402 +from sqlalchemy.orm import sessionmaker # noqa: E402 + +from crewai_tools.tools.nl2sql.nl2sql_tool import NL2SQLTool # noqa: E402 + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +SQLITE_URI = "sqlite://" # in-memory + + +def _make_tool(allow_dml: bool = False, **kwargs) -> NL2SQLTool: + """Return a NL2SQLTool wired to an in-memory SQLite DB. + + Schema-introspection is patched out so we can create the tool without a + real PostgreSQL information_schema. + """ + with ( + patch.object(NL2SQLTool, "_fetch_available_tables", return_value=[]), + patch.object(NL2SQLTool, "_fetch_all_available_columns", return_value=[]), + ): + return NL2SQLTool(db_uri=SQLITE_URI, allow_dml=allow_dml, **kwargs) + + +def _seed_db(uri: str) -> None: + """Create a tiny table in the target database for DML tests.""" + engine = create_engine(uri) + with engine.connect() as conn: + conn.execute(text("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)")) + conn.execute(text("INSERT INTO users VALUES (1, 'alice')")) + conn.commit() + + +# --------------------------------------------------------------------------- +# Read-only enforcement (allow_dml=False) +# --------------------------------------------------------------------------- + + +class TestReadOnlyMode: + def test_select_allowed_by_default(self): + tool = _make_tool() + # SQLite supports SELECT without information_schema + result = tool.execute_sql("SELECT 1 AS val") + assert result == [{"val": 1}] + + @pytest.mark.parametrize( + "stmt", + [ + "INSERT INTO t VALUES (1)", + "UPDATE t SET col = 1", + "DELETE FROM t", + "DROP TABLE t", + "ALTER TABLE t ADD col TEXT", + "CREATE TABLE t (id INTEGER)", + "TRUNCATE TABLE t", + "GRANT SELECT ON t TO user1", + "REVOKE SELECT ON t FROM user1", + "EXEC sp_something", + "EXECUTE sp_something", + "CALL proc()", + ], + ) + def test_write_statements_blocked_by_default(self, stmt: str): + tool = _make_tool(allow_dml=False) + with pytest.raises(ValueError, match="read-only mode"): + tool._validate_query(stmt) + + def test_explain_allowed(self): + tool = _make_tool() + # Should not raise + tool._validate_query("EXPLAIN SELECT 1") + + def test_with_cte_allowed(self): + tool = _make_tool() + tool._validate_query("WITH cte AS (SELECT 1) SELECT * FROM cte") + + def test_show_allowed(self): + tool = _make_tool() + tool._validate_query("SHOW TABLES") + + def test_describe_allowed(self): + tool = _make_tool() + tool._validate_query("DESCRIBE users") + + +# --------------------------------------------------------------------------- +# DML enabled (allow_dml=True) +# --------------------------------------------------------------------------- + + +class TestDMLEnabled: + def test_insert_allowed_when_dml_enabled(self): + tool = _make_tool(allow_dml=True) + # Should not raise + tool._validate_query("INSERT INTO t VALUES (1)") + + def test_delete_allowed_when_dml_enabled(self): + tool = _make_tool(allow_dml=True) + tool._validate_query("DELETE FROM t WHERE id = 1") + + def test_drop_allowed_when_dml_enabled(self): + tool = _make_tool(allow_dml=True) + tool._validate_query("DROP TABLE t") + + def test_dml_actually_persists(self): + """End-to-end: INSERT commits when allow_dml=True.""" + # Use a file-based SQLite so we can verify persistence across sessions + import tempfile, os + with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f: + db_path = f.name + uri = f"sqlite:///{db_path}" + try: + tool = _make_tool(allow_dml=True) + tool.db_uri = uri + + engine = create_engine(uri) + with engine.connect() as conn: + conn.execute(text("CREATE TABLE items (id INTEGER PRIMARY KEY)")) + conn.commit() + + tool.execute_sql("INSERT INTO items VALUES (42)") + + with engine.connect() as conn: + rows = conn.execute(text("SELECT id FROM items")).fetchall() + assert (42,) in rows + finally: + os.unlink(db_path) + + +# --------------------------------------------------------------------------- +# Parameterised query — SQL injection prevention +# --------------------------------------------------------------------------- + + +class TestParameterisedQueries: + def test_table_name_is_parameterised(self): + """_fetch_all_available_columns must not interpolate table_name into SQL.""" + tool = _make_tool() + captured_calls = [] + + def recording_execute_sql(self_inner, sql_query, params=None): + captured_calls.append((sql_query, params)) + return [] + + with patch.object(NL2SQLTool, "execute_sql", recording_execute_sql): + tool._fetch_all_available_columns("users'; DROP TABLE users; --") + + assert len(captured_calls) == 1 + sql, params = captured_calls[0] + # The raw SQL must NOT contain the injected string + assert "DROP" not in sql + # The table name must be passed as a parameter + assert params is not None + assert params.get("table_name") == "users'; DROP TABLE users; --" + # The SQL template must use the :param syntax + assert ":table_name" in sql + + def test_injection_string_not_in_sql_template(self): + """The f-string vulnerability is gone — table name never lands in the SQL.""" + tool = _make_tool() + injection = "'; DROP TABLE users; --" + captured = {} + + def spy(self_inner, sql_query, params=None): + captured["sql"] = sql_query + captured["params"] = params + return [] + + with patch.object(NL2SQLTool, "execute_sql", spy): + tool._fetch_all_available_columns(injection) + + assert injection not in captured["sql"] + assert captured["params"]["table_name"] == injection + + +# --------------------------------------------------------------------------- +# session.commit() not called for read-only queries +# --------------------------------------------------------------------------- + + +class TestNoCommitForReadOnly: + def test_select_does_not_commit(self): + tool = _make_tool(allow_dml=False) + + mock_session = MagicMock() + mock_result = MagicMock() + mock_result.returns_rows = True + mock_result.keys.return_value = ["val"] + mock_result.fetchall.return_value = [(1,)] + mock_session.execute.return_value = mock_result + + mock_session_cls = MagicMock(return_value=mock_session) + + with ( + patch("crewai_tools.tools.nl2sql.nl2sql_tool.create_engine"), + patch( + "crewai_tools.tools.nl2sql.nl2sql_tool.sessionmaker", + return_value=mock_session_cls, + ), + ): + tool.execute_sql("SELECT 1") + + mock_session.commit.assert_not_called() + + def test_write_with_dml_enabled_does_commit(self): + tool = _make_tool(allow_dml=True) + + mock_session = MagicMock() + mock_result = MagicMock() + mock_result.returns_rows = False + mock_session.execute.return_value = mock_result + + mock_session_cls = MagicMock(return_value=mock_session) + + with ( + patch("crewai_tools.tools.nl2sql.nl2sql_tool.create_engine"), + patch( + "crewai_tools.tools.nl2sql.nl2sql_tool.sessionmaker", + return_value=mock_session_cls, + ), + ): + tool.execute_sql("INSERT INTO t VALUES (1)") + + mock_session.commit.assert_called_once() + + +# --------------------------------------------------------------------------- +# Environment-variable escape hatch +# --------------------------------------------------------------------------- + + +class TestEnvVarEscapeHatch: + def test_env_var_enables_dml(self): + with patch.dict(os.environ, {"CREWAI_NL2SQL_ALLOW_DML": "true"}): + tool = _make_tool(allow_dml=False) + assert tool.allow_dml is True + + def test_env_var_case_insensitive(self): + with patch.dict(os.environ, {"CREWAI_NL2SQL_ALLOW_DML": "TRUE"}): + tool = _make_tool(allow_dml=False) + assert tool.allow_dml is True + + def test_env_var_absent_keeps_default(self): + env = {k: v for k, v in os.environ.items() if k != "CREWAI_NL2SQL_ALLOW_DML"} + with patch.dict(os.environ, env, clear=True): + tool = _make_tool(allow_dml=False) + assert tool.allow_dml is False + + def test_env_var_false_does_not_enable_dml(self): + with patch.dict(os.environ, {"CREWAI_NL2SQL_ALLOW_DML": "false"}): + tool = _make_tool(allow_dml=False) + assert tool.allow_dml is False + + def test_dml_write_blocked_without_env_var(self): + env = {k: v for k, v in os.environ.items() if k != "CREWAI_NL2SQL_ALLOW_DML"} + with patch.dict(os.environ, env, clear=True): + tool = _make_tool(allow_dml=False) + with pytest.raises(ValueError, match="read-only mode"): + tool._validate_query("DROP TABLE sensitive_data") + + +# --------------------------------------------------------------------------- +# _run() propagates ValueError from _validate_query +# --------------------------------------------------------------------------- + + +class TestRunValidation: + def test_run_raises_on_blocked_query(self): + tool = _make_tool(allow_dml=False) + with pytest.raises(ValueError, match="read-only mode"): + tool._run("DELETE FROM users") + + def test_run_returns_results_for_select(self): + tool = _make_tool(allow_dml=False) + result = tool._run("SELECT 1 AS n") + assert result == [{"n": 1}] From da3f4b1cf226f72d56d8727a4e11df1a51364b9f Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 6 Apr 2026 23:16:15 -0700 Subject: [PATCH 02/15] fix: address CI lint failures and remove unused import - Remove unused `sessionmaker` import from test_nl2sql_security.py - Use `Self` return type on `_apply_env_override` (fixes UP037/F821) - Fix ruff errors auto-fixed in lib/crewai (UP007, etc.) Co-Authored-By: Claude Sonnet 4.6 --- lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py | 4 ++-- lib/crewai-tools/tests/tools/test_nl2sql_security.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py b/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py index 84b4bd7728..2566b78255 100644 --- a/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py +++ b/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py @@ -1,6 +1,6 @@ import logging import os -from typing import Any +from typing import Any, Self from crewai.tools import BaseTool from pydantic import BaseModel, Field, model_validator @@ -83,7 +83,7 @@ class NL2SQLTool(BaseTool): args_schema: type[BaseModel] = NL2SQLToolInput @model_validator(mode="after") - def _apply_env_override(self) -> "NL2SQLTool": + def _apply_env_override(self) -> Self: """Allow CREWAI_NL2SQL_ALLOW_DML=true to override allow_dml at runtime.""" if os.environ.get("CREWAI_NL2SQL_ALLOW_DML", "").strip().lower() == "true": if not self.allow_dml: diff --git a/lib/crewai-tools/tests/tools/test_nl2sql_security.py b/lib/crewai-tools/tests/tools/test_nl2sql_security.py index f13a7c8ea6..91838c2abf 100644 --- a/lib/crewai-tools/tests/tools/test_nl2sql_security.py +++ b/lib/crewai-tools/tests/tools/test_nl2sql_security.py @@ -13,7 +13,6 @@ pytest.importorskip("sqlalchemy") from sqlalchemy import create_engine, text # noqa: E402 -from sqlalchemy.orm import sessionmaker # noqa: E402 from crewai_tools.tools.nl2sql.nl2sql_tool import NL2SQLTool # noqa: E402 From e19ddb37ac6764d4b952b77eb6b926430d1e1348 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 6 Apr 2026 23:29:25 -0700 Subject: [PATCH 03/15] fix: expand _WRITE_COMMANDS and block multi-statement semicolon injection - Add missing write commands: UPSERT, LOAD, COPY, VACUUM, ANALYZE, ANALYSE, REINDEX, CLUSTER, REFRESH, COMMENT, SET, RESET - _validate_query() now splits on ';' and validates each statement independently; multi-statement queries are rejected outright in read-only mode to prevent 'SELECT 1; DROP TABLE users' bypass - Extract single-statement logic into _validate_statement() helper - Add TestSemicolonInjection and TestExtendedWriteCommands test classes Co-Authored-By: Claude Sonnet 4.6 --- .../crewai_tools/tools/nl2sql/nl2sql_tool.py | 39 +++++++++-- .../tests/tools/test_nl2sql_security.py | 66 +++++++++++++++++++ 2 files changed, 100 insertions(+), 5 deletions(-) diff --git a/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py b/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py index 2566b78255..436fb54715 100644 --- a/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py +++ b/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py @@ -35,6 +35,18 @@ "CALL", "MERGE", "REPLACE", + "UPSERT", + "LOAD", + "COPY", + "VACUUM", + "ANALYZE", + "ANALYSE", + "REINDEX", + "CLUSTER", + "REFRESH", + "COMMENT", + "SET", + "RESET", } @@ -127,12 +139,29 @@ def model_post_init(self, __context: Any) -> None: def _validate_query(self, sql_query: str) -> None: """Raise ValueError if *sql_query* is not permitted under the current config. - Parses the leading SQL command keyword and checks it against the - allowed set. When ``allow_dml=False`` (the default) only read - statements pass. When ``allow_dml=True`` all statements are allowed - but a warning is emitted for write operations. + Splits the query on semicolons and validates each statement + independently. When ``allow_dml=False`` (the default), multi-statement + queries are rejected outright to prevent ``SELECT 1; DROP TABLE users`` + style bypasses. When ``allow_dml=True`` every statement is checked and + a warning is emitted for write operations. """ - command = self._extract_command(sql_query) + statements = [s.strip() for s in sql_query.split(";") if s.strip()] + + if not statements: + raise ValueError("NL2SQLTool received an empty SQL query.") + + if not self.allow_dml and len(statements) > 1: + raise ValueError( + "NL2SQLTool blocked a multi-statement query in read-only mode. " + "Semicolons are not permitted when allow_dml=False." + ) + + for stmt in statements: + self._validate_statement(stmt) + + def _validate_statement(self, stmt: str) -> None: + """Validate a single SQL statement (no semicolons).""" + command = self._extract_command(stmt) if command in _WRITE_COMMANDS: if not self.allow_dml: diff --git a/lib/crewai-tools/tests/tools/test_nl2sql_security.py b/lib/crewai-tools/tests/tools/test_nl2sql_security.py index 91838c2abf..0c7a62112d 100644 --- a/lib/crewai-tools/tests/tools/test_nl2sql_security.py +++ b/lib/crewai-tools/tests/tools/test_nl2sql_security.py @@ -288,3 +288,69 @@ def test_run_returns_results_for_select(self): tool = _make_tool(allow_dml=False) result = tool._run("SELECT 1 AS n") assert result == [{"n": 1}] + + +# --------------------------------------------------------------------------- +# Multi-statement / semicolon injection prevention +# --------------------------------------------------------------------------- + + +class TestSemicolonInjection: + def test_multi_statement_blocked_in_read_only_mode(self): + """SELECT 1; DROP TABLE users must be rejected when allow_dml=False.""" + tool = _make_tool(allow_dml=False) + with pytest.raises(ValueError, match="multi-statement"): + tool._validate_query("SELECT 1; DROP TABLE users") + + def test_multi_statement_blocked_even_with_only_selects(self): + """Two SELECT statements are still rejected in read-only mode.""" + tool = _make_tool(allow_dml=False) + with pytest.raises(ValueError, match="multi-statement"): + tool._validate_query("SELECT 1; SELECT 2") + + def test_trailing_semicolon_allowed_single_statement(self): + """A single statement with a trailing semicolon should pass.""" + tool = _make_tool(allow_dml=False) + # Should not raise — the part after the semicolon is empty + tool._validate_query("SELECT 1;") + + def test_multi_statement_allowed_when_dml_enabled(self): + """Multiple statements are permitted when allow_dml=True.""" + tool = _make_tool(allow_dml=True) + # Should not raise + tool._validate_query("SELECT 1; INSERT INTO t VALUES (1)") + + def test_multi_statement_write_still_blocked_individually(self): + """Even with allow_dml=False, a single write statement is blocked.""" + tool = _make_tool(allow_dml=False) + with pytest.raises(ValueError, match="read-only mode"): + tool._validate_query("DROP TABLE users") + + +# --------------------------------------------------------------------------- +# Extended _WRITE_COMMANDS coverage +# --------------------------------------------------------------------------- + + +class TestExtendedWriteCommands: + @pytest.mark.parametrize( + "stmt", + [ + "UPSERT INTO t VALUES (1)", + "LOAD DATA INFILE 'f.csv' INTO TABLE t", + "COPY t FROM '/tmp/f.csv'", + "VACUUM ANALYZE t", + "ANALYZE t", + "ANALYSE t", + "REINDEX TABLE t", + "CLUSTER t USING idx", + "REFRESH MATERIALIZED VIEW v", + "COMMENT ON TABLE t IS 'desc'", + "SET search_path = myschema", + "RESET search_path", + ], + ) + def test_extended_write_commands_blocked_by_default(self, stmt: str): + tool = _make_tool(allow_dml=False) + with pytest.raises(ValueError, match="read-only mode"): + tool._validate_query(stmt) From d7ab9ee5a1fc2521630a94302712109be57aee04 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 6 Apr 2026 23:34:36 -0700 Subject: [PATCH 04/15] ci: retrigger From d9b86bb3b9217c2f2520a11aa7bab150de4f7625 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 6 Apr 2026 23:48:58 -0700 Subject: [PATCH 05/15] fix: use typing_extensions.Self for Python 3.10 compat --- .../src/crewai_tools/tools/nl2sql/nl2sql_tool.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py b/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py index 436fb54715..051f85ddb3 100644 --- a/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py +++ b/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py @@ -1,6 +1,12 @@ import logging import os -from typing import Any, Self +from typing import Any + + +try: + from typing import Self +except ImportError: + from typing_extensions import Self from crewai.tools import BaseTool from pydantic import BaseModel, Field, model_validator From 00219e6d3964ec141ced8e2bb9ef1ca47775a463 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 06:49:55 +0000 Subject: [PATCH 06/15] chore: update tool specifications --- lib/crewai-tools/tool.specs.json | 138 ++++++++++++++++++++++++++++++- 1 file changed, 137 insertions(+), 1 deletion(-) diff --git a/lib/crewai-tools/tool.specs.json b/lib/crewai-tools/tool.specs.json index 76ff76a4b4..a9f1ce3896 100644 --- a/lib/crewai-tools/tool.specs.json +++ b/lib/crewai-tools/tool.specs.json @@ -5010,6 +5010,135 @@ "type": "object" } }, + { + "description": "Interprets Python3 code strings with a final print statement.", + "env_vars": [], + "humanized_name": "Code Interpreter", + "init_params_schema": { + "$defs": { + "EnvVar": { + "properties": { + "default": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Default" + }, + "description": { + "title": "Description", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "required": { + "default": true, + "title": "Required", + "type": "boolean" + } + }, + "required": [ + "name", + "description" + ], + "title": "EnvVar", + "type": "object" + } + }, + "description": "A tool for executing Python code in isolated environments.\n\nThis tool provides functionality to run Python code either in a Docker container\nfor safe isolation or directly in a restricted sandbox. It can handle installing\nPython packages and executing arbitrary Python code.", + "properties": { + "code": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Code" + }, + "default_image_tag": { + "default": "code-interpreter:latest", + "title": "Default Image Tag", + "type": "string" + }, + "tool_type": { + "readOnly": true, + "title": "Tool Type", + "type": "string" + }, + "unsafe_mode": { + "default": false, + "title": "Unsafe Mode", + "type": "boolean" + }, + "user_docker_base_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "User Docker Base Url" + }, + "user_dockerfile_path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "User Dockerfile Path" + } + }, + "required": [ + "tool_type" + ], + "title": "CodeInterpreterTool", + "type": "object" + }, + "name": "CodeInterpreterTool", + "package_dependencies": [], + "run_params_schema": { + "description": "Schema for defining inputs to the CodeInterpreterTool.\n\nThis schema defines the required parameters for code execution,\nincluding the code to run and any libraries that need to be installed.", + "properties": { + "code": { + "description": "Python3 code used to be interpreted in the Docker container. ALWAYS PRINT the final result and the output of the code", + "title": "Code", + "type": "string" + }, + "libraries_used": { + "description": "List of libraries used in the code with proper installing names separated by commas. Example: numpy,pandas,beautifulsoup4", + "items": { + "type": "string" + }, + "title": "Libraries Used", + "type": "array" + } + }, + "required": [ + "code", + "libraries_used" + ], + "title": "CodeInterpreterSchema", + "type": "object" + } + }, { "description": "", "env_vars": [ @@ -14051,7 +14180,7 @@ } }, { - "description": "Converts natural language to SQL queries and executes them.", + "description": "Converts natural language to SQL queries and executes them against a database. Read-only by default \u2014 only SELECT/SHOW/DESCRIBE/EXPLAIN queries are allowed unless the tool is configured with allow_dml=True.", "env_vars": [], "humanized_name": "NL2SQLTool", "init_params_schema": { @@ -14092,7 +14221,14 @@ "type": "object" } }, + "description": "Tool that converts natural language to SQL and executes it against a database.\n\nBy default the tool operates in **read-only mode**: only SELECT, SHOW,\nDESCRIBE, EXPLAIN, and WITH (CTE) statements are permitted. Write\noperations (INSERT, UPDATE, DELETE, DROP, ALTER, CREATE, TRUNCATE, \u2026) are\nblocked unless ``allow_dml=True`` is set explicitly or the environment\nvariable ``CREWAI_NL2SQL_ALLOW_DML=true`` is present.\n\nThe ``_fetch_all_available_columns`` helper uses parameterised queries so\nthat table names coming from the database catalogue cannot be used as an\ninjection vector.", "properties": { + "allow_dml": { + "default": false, + "description": "When False (default) only read statements are permitted. Set to True to allow INSERT/UPDATE/DELETE/DROP and other write operations.", + "title": "Allow DML", + "type": "boolean" + }, "columns": { "additionalProperties": { "anyOf": [ From 5144daec08c8647bcf95d9f01b644613818b4641 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 7 Apr 2026 00:16:17 -0700 Subject: [PATCH 07/15] docs: document NL2SQLTool read-only default and DML configuration Co-Authored-By: Claude Sonnet 4.6 --- docs/ar/tools/database-data/nl2sqltool.mdx | 70 ++++++++++++++++++- docs/en/tools/database-data/nl2sqltool.mdx | 70 ++++++++++++++++++- docs/ko/tools/database-data/nl2sqltool.mdx | 70 ++++++++++++++++++- docs/pt-BR/tools/database-data/nl2sqltool.mdx | 70 ++++++++++++++++++- 4 files changed, 276 insertions(+), 4 deletions(-) diff --git a/docs/ar/tools/database-data/nl2sqltool.mdx b/docs/ar/tools/database-data/nl2sqltool.mdx index de52a5dd8d..6ddc6e058e 100644 --- a/docs/ar/tools/database-data/nl2sqltool.mdx +++ b/docs/ar/tools/database-data/nl2sqltool.mdx @@ -11,7 +11,7 @@ mode: "wide" يتيح ذلك سير عمل متعددة مثل أن يقوم وكيل بالوصول إلى قاعدة البيانات واسترجاع المعلومات بناءً على الهدف ثم استخدام تلك المعلومات لتوليد استجابة أو تقرير أو أي مخرجات أخرى. بالإضافة إلى ذلك، يوفر القدرة للوكيل على تحديث قاعدة البيانات بناءً على هدفه. -**تنبيه**: تأكد من أن الوكيل لديه وصول إلى نسخة قراءة فقط أو أنه من المقبول أن يقوم الوكيل بتنفيذ استعلامات إدراج/تحديث على قاعدة البيانات. +**تنبيه**: الأداة للقراءة فقط بشكل افتراضي (SELECT/SHOW/DESCRIBE/EXPLAIN فقط). تتطلب عمليات الكتابة تمرير `allow_dml=True` أو ضبط متغير البيئة `CREWAI_NL2SQL_ALLOW_DML=true`. عند تفعيل الكتابة، تأكد من أن الوكيل يستخدم مستخدم قاعدة بيانات محدود الصلاحيات أو نسخة قراءة كلما أمكن. ## نموذج الأمان @@ -36,6 +36,74 @@ mode: "wide" - أضف خطافات `before_tool_call` لفرض أنماط الاستعلام المسموح بها - فعّل تسجيل الاستعلامات والتنبيهات للعبارات التدميرية +## وضع القراءة فقط وتهيئة DML + +تعمل `NL2SQLTool` في **وضع القراءة فقط بشكل افتراضي**. لا يُسمح إلا بأنواع العبارات التالية دون تهيئة إضافية: + +- `SELECT` +- `SHOW` +- `DESCRIBE` +- `EXPLAIN` + +أي محاولة لتنفيذ عملية كتابة (`INSERT`، `UPDATE`، `DELETE`، `DROP`، `CREATE`، `ALTER`، `TRUNCATE`، إلخ) ستُسبب خطأً ما لم يتم تفعيل DML صراحةً. + +كما تُحظر الاستعلامات متعددة العبارات التي تحتوي على فاصلة منقوطة (مثل `SELECT 1; DROP TABLE users`) في وضع القراءة فقط لمنع هجمات الحقن. + +### تفعيل عمليات الكتابة + +يمكنك تفعيل DML (لغة معالجة البيانات) بطريقتين: + +**الخيار الأول — معامل المُنشئ:** + +```python +from crewai_tools import NL2SQLTool + +nl2sql = NL2SQLTool( + db_uri="postgresql://example@localhost:5432/test_db", + allow_dml=True, +) +``` + +**الخيار الثاني — متغير البيئة:** + +```bash +CREWAI_NL2SQL_ALLOW_DML=true +``` + +```python +from crewai_tools import NL2SQLTool + +# DML مفعّل عبر متغير البيئة +nl2sql = NL2SQLTool(db_uri="postgresql://example@localhost:5432/test_db") +``` + +### أمثلة الاستخدام + +**القراءة فقط (الافتراضي) — آمن للتحليلات والتقارير:** + +```python +from crewai_tools import NL2SQLTool + +# يُسمح فقط بـ SELECT/SHOW/DESCRIBE/EXPLAIN +nl2sql = NL2SQLTool(db_uri="postgresql://example@localhost:5432/test_db") +``` + +**مع تفعيل DML — مطلوب لأعباء عمل الكتابة:** + +```python +from crewai_tools import NL2SQLTool + +# يُسمح بـ INSERT وUPDATE وDELETE وDROP وغيرها +nl2sql = NL2SQLTool( + db_uri="postgresql://example@localhost:5432/test_db", + allow_dml=True, +) +``` + + +يمنح تفعيل DML للوكيل القدرة على تعديل البيانات أو حذفها. لا تفعّله إلا عندما يتطلب حالة الاستخدام صراحةً وصولاً للكتابة، وتأكد من أن بيانات اعتماد قاعدة البيانات محدودة بالحد الأدنى من الصلاحيات المطلوبة. + + ## المتطلبات - SqlAlchemy diff --git a/docs/en/tools/database-data/nl2sqltool.mdx b/docs/en/tools/database-data/nl2sqltool.mdx index ee423e7919..833a43cab8 100644 --- a/docs/en/tools/database-data/nl2sqltool.mdx +++ b/docs/en/tools/database-data/nl2sqltool.mdx @@ -13,7 +13,7 @@ This tool is used to convert natural language to SQL queries. When passed to the This enables multiple workflows like having an Agent to access the database fetch information based on the goal and then use the information to generate a response, report or any other output. Along with that provides the ability for the Agent to update the database based on its goal. -**Attention**: Make sure that the Agent has access to a Read-Replica or that is okay for the Agent to run insert/update queries on the database. +**Attention**: By default the tool is read-only (SELECT/SHOW/DESCRIBE/EXPLAIN only). Write operations require `allow_dml=True` or the `CREWAI_NL2SQL_ALLOW_DML=true` environment variable. When write access is enabled, make sure the Agent uses a scoped database user or a read replica where possible. ## Security Model @@ -38,6 +38,74 @@ Use all of the following in production: - Add `before_tool_call` hooks to enforce allowed query patterns - Enable query logging and alerting for destructive statements +## Read-Only Mode & DML Configuration + +`NL2SQLTool` operates in **read-only mode by default**. Only the following statement types are permitted without additional configuration: + +- `SELECT` +- `SHOW` +- `DESCRIBE` +- `EXPLAIN` + +Any attempt to execute a write operation (`INSERT`, `UPDATE`, `DELETE`, `DROP`, `CREATE`, `ALTER`, `TRUNCATE`, etc.) will raise an error unless DML is explicitly enabled. + +Multi-statement queries containing semicolons (e.g. `SELECT 1; DROP TABLE users`) are also blocked in read-only mode to prevent injection attacks. + +### Enabling Write Operations + +You can enable DML (Data Manipulation Language) in two ways: + +**Option 1 — constructor parameter:** + +```python +from crewai_tools import NL2SQLTool + +nl2sql = NL2SQLTool( + db_uri="postgresql://example@localhost:5432/test_db", + allow_dml=True, +) +``` + +**Option 2 — environment variable:** + +```bash +CREWAI_NL2SQL_ALLOW_DML=true +``` + +```python +from crewai_tools import NL2SQLTool + +# DML enabled via environment variable +nl2sql = NL2SQLTool(db_uri="postgresql://example@localhost:5432/test_db") +``` + +### Usage Examples + +**Read-only (default) — safe for analytics and reporting:** + +```python +from crewai_tools import NL2SQLTool + +# Only SELECT/SHOW/DESCRIBE/EXPLAIN are permitted +nl2sql = NL2SQLTool(db_uri="postgresql://example@localhost:5432/test_db") +``` + +**DML enabled — required for write workloads:** + +```python +from crewai_tools import NL2SQLTool + +# INSERT, UPDATE, DELETE, DROP, etc. are permitted +nl2sql = NL2SQLTool( + db_uri="postgresql://example@localhost:5432/test_db", + allow_dml=True, +) +``` + + +Enabling DML gives the agent the ability to modify or destroy data. Only enable this when your use case explicitly requires write access, and ensure the database credentials are scoped to the minimum required privileges. + + ## Requirements - SqlAlchemy diff --git a/docs/ko/tools/database-data/nl2sqltool.mdx b/docs/ko/tools/database-data/nl2sqltool.mdx index 5f65831552..32894f44c8 100644 --- a/docs/ko/tools/database-data/nl2sqltool.mdx +++ b/docs/ko/tools/database-data/nl2sqltool.mdx @@ -11,7 +11,75 @@ mode: "wide" 이를 통해 에이전트가 데이터베이스에 접근하여 목표에 따라 정보를 가져오고, 해당 정보를 사용해 응답, 보고서 또는 기타 출력물을 생성하는 다양한 워크플로우가 가능해집니다. 또한 에이전트가 자신의 목표에 맞춰 데이터베이스를 업데이트할 수 있는 기능도 제공합니다. -**주의**: 에이전트가 Read-Replica에 접근할 수 있거나, 에이전트가 데이터베이스에 insert/update 쿼리를 실행해도 괜찮은지 반드시 확인하십시오. +**주의**: 도구는 기본적으로 읽기 전용(SELECT/SHOW/DESCRIBE/EXPLAIN만 허용)으로 동작합니다. 쓰기 작업을 수행하려면 `allow_dml=True` 매개변수 또는 `CREWAI_NL2SQL_ALLOW_DML=true` 환경 변수가 필요합니다. 쓰기 접근이 활성화된 경우, 가능하면 권한이 제한된 데이터베이스 사용자나 읽기 복제본을 사용하십시오. + +## 읽기 전용 모드 및 DML 구성 + +`NL2SQLTool`은 기본적으로 **읽기 전용 모드**로 동작합니다. 추가 구성 없이 허용되는 구문 유형은 다음과 같습니다: + +- `SELECT` +- `SHOW` +- `DESCRIBE` +- `EXPLAIN` + +DML을 명시적으로 활성화하지 않으면 쓰기 작업(`INSERT`, `UPDATE`, `DELETE`, `DROP`, `CREATE`, `ALTER`, `TRUNCATE` 등)을 실행하려고 할 때 오류가 발생합니다. + +읽기 전용 모드에서는 세미콜론이 포함된 다중 구문 쿼리(예: `SELECT 1; DROP TABLE users`)도 인젝션 공격을 방지하기 위해 차단됩니다. + +### 쓰기 작업 활성화 + +DML(데이터 조작 언어)을 활성화하는 방법은 두 가지입니다: + +**옵션 1 — 생성자 매개변수:** + +```python +from crewai_tools import NL2SQLTool + +nl2sql = NL2SQLTool( + db_uri="postgresql://example@localhost:5432/test_db", + allow_dml=True, +) +``` + +**옵션 2 — 환경 변수:** + +```bash +CREWAI_NL2SQL_ALLOW_DML=true +``` + +```python +from crewai_tools import NL2SQLTool + +# 환경 변수를 통해 DML 활성화 +nl2sql = NL2SQLTool(db_uri="postgresql://example@localhost:5432/test_db") +``` + +### 사용 예시 + +**읽기 전용(기본값) — 분석 및 보고 워크로드에 안전:** + +```python +from crewai_tools import NL2SQLTool + +# SELECT/SHOW/DESCRIBE/EXPLAIN만 허용 +nl2sql = NL2SQLTool(db_uri="postgresql://example@localhost:5432/test_db") +``` + +**DML 활성화 — 쓰기 워크로드에 필요:** + +```python +from crewai_tools import NL2SQLTool + +# INSERT, UPDATE, DELETE, DROP 등이 허용됨 +nl2sql = NL2SQLTool( + db_uri="postgresql://example@localhost:5432/test_db", + allow_dml=True, +) +``` + + +DML을 활성화하면 에이전트가 데이터를 수정하거나 삭제할 수 있습니다. 사용 사례에서 명시적으로 쓰기 접근이 필요한 경우에만 활성화하고, 데이터베이스 자격 증명이 최소 필요 권한으로 제한되어 있는지 확인하십시오. + ## 요구 사항 diff --git a/docs/pt-BR/tools/database-data/nl2sqltool.mdx b/docs/pt-BR/tools/database-data/nl2sqltool.mdx index f414ab4e2c..8ef3cc160f 100644 --- a/docs/pt-BR/tools/database-data/nl2sqltool.mdx +++ b/docs/pt-BR/tools/database-data/nl2sqltool.mdx @@ -11,7 +11,75 @@ Esta ferramenta é utilizada para converter linguagem natural em consultas SQL. Isso possibilita múltiplos fluxos de trabalho, como por exemplo ter um Agente acessando o banco de dados para buscar informações com base em um objetivo e, então, usar essas informações para gerar uma resposta, relatório ou qualquer outro tipo de saída. Além disso, permite que o Agente atualize o banco de dados de acordo com seu objetivo. -**Atenção**: Certifique-se de que o Agente tenha acesso a um Read-Replica ou que seja permitido que o Agente execute consultas de inserção/atualização no banco de dados. +**Atenção**: Por padrão, a ferramenta opera em modo somente leitura (apenas SELECT/SHOW/DESCRIBE/EXPLAIN). Operações de escrita exigem `allow_dml=True` ou a variável de ambiente `CREWAI_NL2SQL_ALLOW_DML=true`. Quando o acesso de escrita estiver habilitado, certifique-se de que o Agente use um usuário de banco de dados com privilégios mínimos ou um Read-Replica sempre que possível. + +## Modo Somente Leitura e Configuração de DML + +O `NL2SQLTool` opera em **modo somente leitura por padrão**. Apenas os seguintes tipos de instrução são permitidos sem configuração adicional: + +- `SELECT` +- `SHOW` +- `DESCRIBE` +- `EXPLAIN` + +Qualquer tentativa de executar uma operação de escrita (`INSERT`, `UPDATE`, `DELETE`, `DROP`, `CREATE`, `ALTER`, `TRUNCATE`, etc.) resultará em erro, a menos que o DML seja habilitado explicitamente. + +Consultas com múltiplas instruções contendo ponto e vírgula (ex.: `SELECT 1; DROP TABLE users`) também são bloqueadas no modo somente leitura para prevenir ataques de injeção. + +### Habilitando Operações de Escrita + +Você pode habilitar DML (Linguagem de Manipulação de Dados) de duas formas: + +**Opção 1 — parâmetro do construtor:** + +```python +from crewai_tools import NL2SQLTool + +nl2sql = NL2SQLTool( + db_uri="postgresql://example@localhost:5432/test_db", + allow_dml=True, +) +``` + +**Opção 2 — variável de ambiente:** + +```bash +CREWAI_NL2SQL_ALLOW_DML=true +``` + +```python +from crewai_tools import NL2SQLTool + +# DML habilitado via variável de ambiente +nl2sql = NL2SQLTool(db_uri="postgresql://example@localhost:5432/test_db") +``` + +### Exemplos de Uso + +**Somente leitura (padrão) — seguro para análise e relatórios:** + +```python +from crewai_tools import NL2SQLTool + +# Apenas SELECT/SHOW/DESCRIBE/EXPLAIN são permitidos +nl2sql = NL2SQLTool(db_uri="postgresql://example@localhost:5432/test_db") +``` + +**Com DML habilitado — necessário para workloads de escrita:** + +```python +from crewai_tools import NL2SQLTool + +# INSERT, UPDATE, DELETE, DROP, etc. são permitidos +nl2sql = NL2SQLTool( + db_uri="postgresql://example@localhost:5432/test_db", + allow_dml=True, +) +``` + + +Habilitar DML concede ao agente a capacidade de modificar ou destruir dados. Ative apenas quando o seu caso de uso exigir explicitamente acesso de escrita e certifique-se de que as credenciais do banco de dados estejam limitadas aos privilégios mínimos necessários. + ## Requisitos From d42eb00f209b992cbdc4bc931fba59704f8b113c Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 7 Apr 2026 00:54:12 -0700 Subject: [PATCH 08/15] fix: close three NL2SQLTool security gaps (writable CTEs, EXPLAIN ANALYZE, multi-stmt commit) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove WITH from _READ_ONLY_COMMANDS; scan CTE body for write keywords so writable CTEs like `WITH d AS (DELETE …) SELECT …` are blocked in read-only mode. - EXPLAIN ANALYZE/ANALYSE now resolves the underlying command; EXPLAIN ANALYZE DELETE is treated as a write and blocked in read-only mode. - execute_sql commit decision now checks ALL semicolon-separated statements so a SELECT-first batch like `SELECT 1; DROP TABLE t` still triggers a commit when allow_dml=True. Co-Authored-By: Claude Sonnet 4.6 --- .../crewai_tools/tools/nl2sql/nl2sql_tool.py | 52 ++++++- .../tests/tools/test_nl2sql_security.py | 131 +++++++++++++++++- 2 files changed, 178 insertions(+), 5 deletions(-) diff --git a/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py b/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py index 051f85ddb3..c742c26b77 100644 --- a/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py +++ b/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py @@ -23,7 +23,9 @@ logger = logging.getLogger(__name__) # Commands allowed in read-only mode -_READ_ONLY_COMMANDS = {"SELECT", "SHOW", "DESCRIBE", "DESC", "EXPLAIN", "WITH"} +# NOTE: WITH is intentionally excluded — writable CTEs start with WITH, so the +# CTE body must be inspected separately (see _validate_statement). +_READ_ONLY_COMMANDS = {"SELECT", "SHOW", "DESCRIBE", "DESC", "EXPLAIN"} # Commands that mutate state and are blocked by default _WRITE_COMMANDS = { @@ -67,11 +69,15 @@ class NL2SQLTool(BaseTool): """Tool that converts natural language to SQL and executes it against a database. By default the tool operates in **read-only mode**: only SELECT, SHOW, - DESCRIBE, EXPLAIN, and WITH (CTE) statements are permitted. Write + DESCRIBE, EXPLAIN, and read-only CTEs (WITH … SELECT) are permitted. Write operations (INSERT, UPDATE, DELETE, DROP, ALTER, CREATE, TRUNCATE, …) are blocked unless ``allow_dml=True`` is set explicitly or the environment variable ``CREWAI_NL2SQL_ALLOW_DML=true`` is present. + Writable CTEs (``WITH d AS (DELETE …) SELECT …``) and + ``EXPLAIN ANALYZE `` are treated as write operations and are + blocked in read-only mode. + The ``_fetch_all_available_columns`` helper uses parameterised queries so that table names coming from the database catalogue cannot be used as an injection vector. @@ -81,7 +87,8 @@ class NL2SQLTool(BaseTool): description: str = ( "Converts natural language to SQL queries and executes them against a " "database. Read-only by default — only SELECT/SHOW/DESCRIBE/EXPLAIN " - "queries are allowed unless the tool is configured with allow_dml=True." + "queries (and read-only CTEs) are allowed unless configured with " + "allow_dml=True." ) db_uri: str = Field( title="Database URI", @@ -169,6 +176,40 @@ def _validate_statement(self, stmt: str) -> None: """Validate a single SQL statement (no semicolons).""" command = self._extract_command(stmt) + # EXPLAIN ANALYZE / EXPLAIN ANALYSE actually *executes* the underlying + # query. Resolve the real command so write operations are caught. + if command == "EXPLAIN": + tokens = stmt.strip().lstrip("(").split() + if len(tokens) >= 2 and tokens[1].upper().rstrip(";") in ( + "ANALYZE", + "ANALYSE", + ): + # The statement being explained starts at the third token. + if len(tokens) >= 3: + command = tokens[2].upper().rstrip(";") + # else: bare "EXPLAIN ANALYZE" with no query — treat as read-only. + + # WITH starts a CTE. Read-only CTEs are fine; writable CTEs + # (e.g. WITH d AS (DELETE …) SELECT …) must be blocked in read-only mode. + if command == "WITH": + tokens_upper = {t.upper().strip("();,") for t in stmt.split()} + write_found = tokens_upper & _WRITE_COMMANDS + if write_found: + found = next(iter(write_found)) + if not self.allow_dml: + raise ValueError( + f"NL2SQLTool is configured in read-only mode and blocked a " + f"writable CTE containing a '{found}' statement. To allow " + f"write operations set allow_dml=True or " + f"CREWAI_NL2SQL_ALLOW_DML=true." + ) + logger.warning( + "NL2SQLTool: executing writable CTE with '%s' because allow_dml=True.", + found, + ) + # Both read-only and writable-but-permitted CTEs need no further checks. + return + if command in _WRITE_COMMANDS: if not self.allow_dml: raise ValueError( @@ -260,7 +301,10 @@ def execute_sql( "`pip install crewai-tools[sqlalchemy]`" ) - is_write = self._extract_command(sql_query) in _WRITE_COMMANDS + # Check ALL statements so that e.g. "SELECT 1; DROP TABLE t" triggers a + # commit when allow_dml=True, regardless of statement order. + _stmts = [s.strip() for s in sql_query.split(";") if s.strip()] + is_write = any(self._extract_command(s) in _WRITE_COMMANDS for s in _stmts) engine = create_engine(self.db_uri) Session = sessionmaker(bind=engine) # noqa: N806 diff --git a/lib/crewai-tools/tests/tools/test_nl2sql_security.py b/lib/crewai-tools/tests/tools/test_nl2sql_security.py index 0c7a62112d..60d4f29596 100644 --- a/lib/crewai-tools/tests/tools/test_nl2sql_security.py +++ b/lib/crewai-tools/tests/tools/test_nl2sql_security.py @@ -84,7 +84,7 @@ def test_explain_allowed(self): # Should not raise tool._validate_query("EXPLAIN SELECT 1") - def test_with_cte_allowed(self): + def test_read_only_cte_allowed(self): tool = _make_tool() tool._validate_query("WITH cte AS (SELECT 1) SELECT * FROM cte") @@ -327,6 +327,135 @@ def test_multi_statement_write_still_blocked_individually(self): tool._validate_query("DROP TABLE users") +# --------------------------------------------------------------------------- +# Writable CTEs (WITH … DELETE/INSERT/UPDATE) +# --------------------------------------------------------------------------- + + +class TestWritableCTE: + def test_writable_cte_delete_blocked_in_read_only(self): + """WITH d AS (DELETE FROM users RETURNING *) SELECT * FROM d — blocked.""" + tool = _make_tool(allow_dml=False) + with pytest.raises(ValueError, match="read-only mode"): + tool._validate_query( + "WITH deleted AS (DELETE FROM users RETURNING *) SELECT * FROM deleted" + ) + + def test_writable_cte_insert_blocked_in_read_only(self): + tool = _make_tool(allow_dml=False) + with pytest.raises(ValueError, match="read-only mode"): + tool._validate_query( + "WITH ins AS (INSERT INTO t VALUES (1) RETURNING id) SELECT * FROM ins" + ) + + def test_writable_cte_update_blocked_in_read_only(self): + tool = _make_tool(allow_dml=False) + with pytest.raises(ValueError, match="read-only mode"): + tool._validate_query( + "WITH upd AS (UPDATE t SET x=1 RETURNING id) SELECT * FROM upd" + ) + + def test_writable_cte_allowed_when_dml_enabled(self): + tool = _make_tool(allow_dml=True) + # Should not raise + tool._validate_query( + "WITH deleted AS (DELETE FROM users RETURNING *) SELECT * FROM deleted" + ) + + def test_plain_read_only_cte_still_allowed(self): + tool = _make_tool(allow_dml=False) + # No write commands in the CTE body — must pass + tool._validate_query("WITH cte AS (SELECT id FROM users) SELECT * FROM cte") + + +# --------------------------------------------------------------------------- +# EXPLAIN ANALYZE executes the underlying query +# --------------------------------------------------------------------------- + + +class TestExplainAnalyze: + def test_explain_analyze_delete_blocked_in_read_only(self): + """EXPLAIN ANALYZE DELETE actually runs the delete — block it.""" + tool = _make_tool(allow_dml=False) + with pytest.raises(ValueError, match="read-only mode"): + tool._validate_query("EXPLAIN ANALYZE DELETE FROM users") + + def test_explain_analyse_delete_blocked_in_read_only(self): + """British spelling ANALYSE is also caught.""" + tool = _make_tool(allow_dml=False) + with pytest.raises(ValueError, match="read-only mode"): + tool._validate_query("EXPLAIN ANALYSE DELETE FROM users") + + def test_explain_analyze_drop_blocked_in_read_only(self): + tool = _make_tool(allow_dml=False) + with pytest.raises(ValueError, match="read-only mode"): + tool._validate_query("EXPLAIN ANALYZE DROP TABLE users") + + def test_explain_analyze_select_allowed_in_read_only(self): + """EXPLAIN ANALYZE on a SELECT is safe — must be permitted.""" + tool = _make_tool(allow_dml=False) + tool._validate_query("EXPLAIN ANALYZE SELECT * FROM users") + + def test_explain_without_analyze_allowed(self): + tool = _make_tool(allow_dml=False) + tool._validate_query("EXPLAIN SELECT * FROM users") + + def test_explain_analyze_delete_allowed_when_dml_enabled(self): + tool = _make_tool(allow_dml=True) + tool._validate_query("EXPLAIN ANALYZE DELETE FROM users") + + +# --------------------------------------------------------------------------- +# Multi-statement commit covers ALL statements (not just the first) +# --------------------------------------------------------------------------- + + +class TestMultiStatementCommit: + def test_select_then_insert_triggers_commit(self): + """SELECT 1; INSERT … — commit must happen because INSERT is a write.""" + tool = _make_tool(allow_dml=True) + + mock_session = MagicMock() + mock_result = MagicMock() + mock_result.returns_rows = False + mock_session.execute.return_value = mock_result + mock_session_cls = MagicMock(return_value=mock_session) + + with ( + patch("crewai_tools.tools.nl2sql.nl2sql_tool.create_engine"), + patch( + "crewai_tools.tools.nl2sql.nl2sql_tool.sessionmaker", + return_value=mock_session_cls, + ), + ): + tool.execute_sql("SELECT 1; INSERT INTO t VALUES (1)") + + mock_session.commit.assert_called_once() + + def test_select_only_multi_statement_does_not_commit(self): + """Two SELECTs must not trigger a commit even when allow_dml=True.""" + tool = _make_tool(allow_dml=True) + + mock_session = MagicMock() + mock_result = MagicMock() + mock_result.returns_rows = True + mock_result.keys.return_value = ["v"] + mock_result.fetchall.return_value = [(1,)] + mock_session.execute.return_value = mock_result + mock_session_cls = MagicMock(return_value=mock_session) + + with ( + patch("crewai_tools.tools.nl2sql.nl2sql_tool.create_engine"), + patch( + "crewai_tools.tools.nl2sql.nl2sql_tool.sessionmaker", + return_value=mock_session_cls, + ), + ): + tool.execute_sql("SELECT 1; SELECT 2") + + mock_session.commit.assert_not_called() + + # --------------------------------------------------------------------------- # Extended _WRITE_COMMANDS coverage # --------------------------------------------------------------------------- From ff8480884fc162b4390bae4ffd2f46f4c69e3dca Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 7 Apr 2026 08:42:25 -0700 Subject: [PATCH 09/15] fix: handle parenthesized EXPLAIN options syntax; remove unused _seed_db MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _validate_statement now strips parenthesized options from EXPLAIN (e.g. EXPLAIN (ANALYZE) DELETE, EXPLAIN (ANALYZE, VERBOSE) DELETE) before checking whether ANALYZE/ANALYSE is present — closing the bypass where the options-list form was silently allowed in read-only mode. Adds three new tests: - EXPLAIN (ANALYZE) DELETE → blocked - EXPLAIN (ANALYZE, VERBOSE) DELETE → blocked - EXPLAIN (VERBOSE) SELECT → allowed Also removes the unused _seed_db helper from test_nl2sql_security.py. Co-Authored-By: Claude Sonnet 4.6 --- .../crewai_tools/tools/nl2sql/nl2sql_tool.py | 33 ++++++++++++++----- .../tests/tools/test_nl2sql_security.py | 26 ++++++++++----- 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py b/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py index c742c26b77..54024ba719 100644 --- a/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py +++ b/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py @@ -178,16 +178,31 @@ def _validate_statement(self, stmt: str) -> None: # EXPLAIN ANALYZE / EXPLAIN ANALYSE actually *executes* the underlying # query. Resolve the real command so write operations are caught. + # Handles both space-separated ("EXPLAIN ANALYZE DELETE …") and + # parenthesized ("EXPLAIN (ANALYZE) DELETE …", "EXPLAIN (ANALYZE, VERBOSE) DELETE …"). if command == "EXPLAIN": - tokens = stmt.strip().lstrip("(").split() - if len(tokens) >= 2 and tokens[1].upper().rstrip(";") in ( - "ANALYZE", - "ANALYSE", - ): - # The statement being explained starts at the third token. - if len(tokens) >= 3: - command = tokens[2].upper().rstrip(";") - # else: bare "EXPLAIN ANALYZE" with no query — treat as read-only. + rest = stmt.strip()[len("EXPLAIN"):].strip() + analyze_found = False + + if rest.startswith("("): + # Parenthesized options: EXPLAIN (ANALYZE, VERBOSE, …) + close = rest.find(")") + if close != -1: + options_str = rest[1:close].upper() + analyze_found = any( + opt.strip() in ("ANALYZE", "ANALYSE") + for opt in options_str.split(",") + ) + rest = rest[close + 1:].strip() + else: + # Space-separated: EXPLAIN ANALYZE + first_opt = rest.split()[0].upper().rstrip(";") if rest.split() else "" + if first_opt in ("ANALYZE", "ANALYSE"): + analyze_found = True + rest = rest[len(first_opt):].strip() + + if analyze_found and rest: + command = rest.split()[0].upper().rstrip(";") # WITH starts a CTE. Read-only CTEs are fine; writable CTEs # (e.g. WITH d AS (DELETE …) SELECT …) must be blocked in read-only mode. diff --git a/lib/crewai-tools/tests/tools/test_nl2sql_security.py b/lib/crewai-tools/tests/tools/test_nl2sql_security.py index 60d4f29596..614c82d6f0 100644 --- a/lib/crewai-tools/tests/tools/test_nl2sql_security.py +++ b/lib/crewai-tools/tests/tools/test_nl2sql_security.py @@ -36,15 +36,6 @@ def _make_tool(allow_dml: bool = False, **kwargs) -> NL2SQLTool: return NL2SQLTool(db_uri=SQLITE_URI, allow_dml=allow_dml, **kwargs) -def _seed_db(uri: str) -> None: - """Create a tiny table in the target database for DML tests.""" - engine = create_engine(uri) - with engine.connect() as conn: - conn.execute(text("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)")) - conn.execute(text("INSERT INTO users VALUES (1, 'alice')")) - conn.commit() - - # --------------------------------------------------------------------------- # Read-only enforcement (allow_dml=False) # --------------------------------------------------------------------------- @@ -404,6 +395,23 @@ def test_explain_analyze_delete_allowed_when_dml_enabled(self): tool = _make_tool(allow_dml=True) tool._validate_query("EXPLAIN ANALYZE DELETE FROM users") + def test_explain_paren_analyze_delete_blocked_in_read_only(self): + """EXPLAIN (ANALYZE) DELETE actually runs the delete — block it.""" + tool = _make_tool(allow_dml=False) + with pytest.raises(ValueError, match="read-only mode"): + tool._validate_query("EXPLAIN (ANALYZE) DELETE FROM users") + + def test_explain_paren_analyze_verbose_delete_blocked_in_read_only(self): + """EXPLAIN (ANALYZE, VERBOSE) DELETE actually runs the delete — block it.""" + tool = _make_tool(allow_dml=False) + with pytest.raises(ValueError, match="read-only mode"): + tool._validate_query("EXPLAIN (ANALYZE, VERBOSE) DELETE FROM users") + + def test_explain_paren_verbose_select_allowed_in_read_only(self): + """EXPLAIN (VERBOSE) SELECT is safe — no ANALYZE means no execution.""" + tool = _make_tool(allow_dml=False) + tool._validate_query("EXPLAIN (VERBOSE) SELECT * FROM users") + # --------------------------------------------------------------------------- # Multi-statement commit covers ALL statements (not just the first) From b6f5ee1f409bbe4dcf8a97b72cfd2b874baf84ae Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 15:43:31 +0000 Subject: [PATCH 10/15] chore: update tool specifications --- lib/crewai-tools/tool.specs.json | 133 +------------------------------ 1 file changed, 2 insertions(+), 131 deletions(-) diff --git a/lib/crewai-tools/tool.specs.json b/lib/crewai-tools/tool.specs.json index a9f1ce3896..a005015031 100644 --- a/lib/crewai-tools/tool.specs.json +++ b/lib/crewai-tools/tool.specs.json @@ -5010,135 +5010,6 @@ "type": "object" } }, - { - "description": "Interprets Python3 code strings with a final print statement.", - "env_vars": [], - "humanized_name": "Code Interpreter", - "init_params_schema": { - "$defs": { - "EnvVar": { - "properties": { - "default": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Default" - }, - "description": { - "title": "Description", - "type": "string" - }, - "name": { - "title": "Name", - "type": "string" - }, - "required": { - "default": true, - "title": "Required", - "type": "boolean" - } - }, - "required": [ - "name", - "description" - ], - "title": "EnvVar", - "type": "object" - } - }, - "description": "A tool for executing Python code in isolated environments.\n\nThis tool provides functionality to run Python code either in a Docker container\nfor safe isolation or directly in a restricted sandbox. It can handle installing\nPython packages and executing arbitrary Python code.", - "properties": { - "code": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Code" - }, - "default_image_tag": { - "default": "code-interpreter:latest", - "title": "Default Image Tag", - "type": "string" - }, - "tool_type": { - "readOnly": true, - "title": "Tool Type", - "type": "string" - }, - "unsafe_mode": { - "default": false, - "title": "Unsafe Mode", - "type": "boolean" - }, - "user_docker_base_url": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "User Docker Base Url" - }, - "user_dockerfile_path": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "User Dockerfile Path" - } - }, - "required": [ - "tool_type" - ], - "title": "CodeInterpreterTool", - "type": "object" - }, - "name": "CodeInterpreterTool", - "package_dependencies": [], - "run_params_schema": { - "description": "Schema for defining inputs to the CodeInterpreterTool.\n\nThis schema defines the required parameters for code execution,\nincluding the code to run and any libraries that need to be installed.", - "properties": { - "code": { - "description": "Python3 code used to be interpreted in the Docker container. ALWAYS PRINT the final result and the output of the code", - "title": "Code", - "type": "string" - }, - "libraries_used": { - "description": "List of libraries used in the code with proper installing names separated by commas. Example: numpy,pandas,beautifulsoup4", - "items": { - "type": "string" - }, - "title": "Libraries Used", - "type": "array" - } - }, - "required": [ - "code", - "libraries_used" - ], - "title": "CodeInterpreterSchema", - "type": "object" - } - }, { "description": "", "env_vars": [ @@ -14180,7 +14051,7 @@ } }, { - "description": "Converts natural language to SQL queries and executes them against a database. Read-only by default \u2014 only SELECT/SHOW/DESCRIBE/EXPLAIN queries are allowed unless the tool is configured with allow_dml=True.", + "description": "Converts natural language to SQL queries and executes them against a database. Read-only by default \u2014 only SELECT/SHOW/DESCRIBE/EXPLAIN queries (and read-only CTEs) are allowed unless configured with allow_dml=True.", "env_vars": [], "humanized_name": "NL2SQLTool", "init_params_schema": { @@ -14221,7 +14092,7 @@ "type": "object" } }, - "description": "Tool that converts natural language to SQL and executes it against a database.\n\nBy default the tool operates in **read-only mode**: only SELECT, SHOW,\nDESCRIBE, EXPLAIN, and WITH (CTE) statements are permitted. Write\noperations (INSERT, UPDATE, DELETE, DROP, ALTER, CREATE, TRUNCATE, \u2026) are\nblocked unless ``allow_dml=True`` is set explicitly or the environment\nvariable ``CREWAI_NL2SQL_ALLOW_DML=true`` is present.\n\nThe ``_fetch_all_available_columns`` helper uses parameterised queries so\nthat table names coming from the database catalogue cannot be used as an\ninjection vector.", + "description": "Tool that converts natural language to SQL and executes it against a database.\n\nBy default the tool operates in **read-only mode**: only SELECT, SHOW,\nDESCRIBE, EXPLAIN, and read-only CTEs (WITH \u2026 SELECT) are permitted. Write\noperations (INSERT, UPDATE, DELETE, DROP, ALTER, CREATE, TRUNCATE, \u2026) are\nblocked unless ``allow_dml=True`` is set explicitly or the environment\nvariable ``CREWAI_NL2SQL_ALLOW_DML=true`` is present.\n\nWritable CTEs (``WITH d AS (DELETE \u2026) SELECT \u2026``) and\n``EXPLAIN ANALYZE `` are treated as write operations and are\nblocked in read-only mode.\n\nThe ``_fetch_all_available_columns`` helper uses parameterised queries so\nthat table names coming from the database catalogue cannot be used as an\ninjection vector.", "properties": { "allow_dml": { "default": false, From f49823731af68c4e90ee72ddbff9d9e6423b6b66 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 7 Apr 2026 09:14:58 -0700 Subject: [PATCH 11/15] fix: smarter CTE write detection, fix commit logic for writable CTEs - Replace naive token-set matching with positional AS() body inspection to avoid false positives on column names like 'comment', 'set', 'reset' - Fix execute_sql commit logic to detect writable CTEs (WITH + DELETE/INSERT) not just top-level write commands - Add tests for false positive cases and writable CTE commit behavior - Format nl2sql_tool.py to pass ruff format check --- .../crewai_tools/tools/nl2sql/nl2sql_tool.py | 59 ++++++++++++++++--- .../tests/tools/test_nl2sql_security.py | 39 +++++++++++- 2 files changed, 90 insertions(+), 8 deletions(-) diff --git a/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py b/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py index 54024ba719..3d6cc73591 100644 --- a/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py +++ b/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py @@ -58,6 +58,48 @@ } +# Subset of write commands that can realistically appear *inside* a CTE body. +# Narrower than _WRITE_COMMANDS to avoid false positives on identifiers like +# ``comment``, ``set``, or ``reset`` which are common column/table names. +_CTE_WRITE_INDICATORS = { + "INSERT", + "UPDATE", + "DELETE", + "DROP", + "ALTER", + "CREATE", + "TRUNCATE", + "MERGE", +} + + +def _detect_writable_cte(stmt: str) -> str | None: + """Return the first write command inside a CTE body, or None. + + Instead of tokenizing the whole statement (which falsely matches column + names like ``comment``), this walks through parenthesized CTE bodies and + checks only the *first keyword after* an opening ``AS (`` for a write + command. + """ + upper = stmt.upper() + search_start = 0 + while True: + # Find the next "AS (" which introduces a CTE body. + idx = upper.find("AS (", search_start) + if idx == -1: + idx = upper.find("AS(", search_start) + if idx == -1: + break + # Jump past "AS (" or "AS(" + paren_start = upper.index("(", idx) + 1 + body = upper[paren_start:].lstrip() + first_word = body.split()[0].strip("()") if body.split() else "" + if first_word in _CTE_WRITE_INDICATORS: + return first_word + search_start = paren_start + return None + + class NL2SQLToolInput(BaseModel): sql_query: str = Field( title="SQL Query", @@ -181,7 +223,7 @@ def _validate_statement(self, stmt: str) -> None: # Handles both space-separated ("EXPLAIN ANALYZE DELETE …") and # parenthesized ("EXPLAIN (ANALYZE) DELETE …", "EXPLAIN (ANALYZE, VERBOSE) DELETE …"). if command == "EXPLAIN": - rest = stmt.strip()[len("EXPLAIN"):].strip() + rest = stmt.strip()[len("EXPLAIN") :].strip() analyze_found = False if rest.startswith("("): @@ -193,13 +235,13 @@ def _validate_statement(self, stmt: str) -> None: opt.strip() in ("ANALYZE", "ANALYSE") for opt in options_str.split(",") ) - rest = rest[close + 1:].strip() + rest = rest[close + 1 :].strip() else: # Space-separated: EXPLAIN ANALYZE first_opt = rest.split()[0].upper().rstrip(";") if rest.split() else "" if first_opt in ("ANALYZE", "ANALYSE"): analyze_found = True - rest = rest[len(first_opt):].strip() + rest = rest[len(first_opt) :].strip() if analyze_found and rest: command = rest.split()[0].upper().rstrip(";") @@ -207,10 +249,9 @@ def _validate_statement(self, stmt: str) -> None: # WITH starts a CTE. Read-only CTEs are fine; writable CTEs # (e.g. WITH d AS (DELETE …) SELECT …) must be blocked in read-only mode. if command == "WITH": - tokens_upper = {t.upper().strip("();,") for t in stmt.split()} - write_found = tokens_upper & _WRITE_COMMANDS + write_found = _detect_writable_cte(stmt) if write_found: - found = next(iter(write_found)) + found = write_found if not self.allow_dml: raise ValueError( f"NL2SQLTool is configured in read-only mode and blocked a " @@ -319,7 +360,11 @@ def execute_sql( # Check ALL statements so that e.g. "SELECT 1; DROP TABLE t" triggers a # commit when allow_dml=True, regardless of statement order. _stmts = [s.strip() for s in sql_query.split(";") if s.strip()] - is_write = any(self._extract_command(s) in _WRITE_COMMANDS for s in _stmts) + is_write = any( + self._extract_command(s) in _WRITE_COMMANDS + or (self._extract_command(s) == "WITH" and _detect_writable_cte(s)) + for s in _stmts + ) engine = create_engine(self.db_uri) Session = sessionmaker(bind=engine) # noqa: N806 diff --git a/lib/crewai-tools/tests/tools/test_nl2sql_security.py b/lib/crewai-tools/tests/tools/test_nl2sql_security.py index 614c82d6f0..d5db86c91b 100644 --- a/lib/crewai-tools/tests/tools/test_nl2sql_security.py +++ b/lib/crewai-tools/tests/tools/test_nl2sql_security.py @@ -358,6 +358,21 @@ def test_plain_read_only_cte_still_allowed(self): # No write commands in the CTE body — must pass tool._validate_query("WITH cte AS (SELECT id FROM users) SELECT * FROM cte") + def test_cte_with_comment_column_not_false_positive(self): + """Column named 'comment' should NOT trigger writable CTE detection.""" + tool = _make_tool(allow_dml=False) + # 'comment' is a column name, not a SQL command + tool._validate_query( + "WITH cte AS (SELECT comment FROM posts) SELECT * FROM cte" + ) + + def test_cte_with_set_column_not_false_positive(self): + """Column named 'set' should NOT trigger writable CTE detection.""" + tool = _make_tool(allow_dml=False) + tool._validate_query( + "WITH cte AS (SELECT set, reset FROM config) SELECT * FROM cte" + ) + # --------------------------------------------------------------------------- # EXPLAIN ANALYZE executes the underlying query @@ -461,7 +476,29 @@ def test_select_only_multi_statement_does_not_commit(self): ): tool.execute_sql("SELECT 1; SELECT 2") - mock_session.commit.assert_not_called() + def test_writable_cte_triggers_commit(self): + """WITH d AS (DELETE ...) must trigger commit when allow_dml=True.""" + tool = _make_tool(allow_dml=True) + + mock_session = MagicMock() + mock_result = MagicMock() + mock_result.returns_rows = True + mock_result.keys.return_value = ["id"] + mock_result.fetchall.return_value = [(1,)] + mock_session.execute.return_value = mock_result + mock_session_cls = MagicMock(return_value=mock_session) + + with ( + patch("crewai_tools.tools.nl2sql.nl2sql_tool.create_engine"), + patch( + "crewai_tools.tools.nl2sql.nl2sql_tool.sessionmaker", + return_value=mock_session_cls, + ), + ): + tool.execute_sql( + "WITH d AS (DELETE FROM users RETURNING *) SELECT * FROM d" + ) + mock_session.commit.assert_called_once() # --------------------------------------------------------------------------- From 37e7a22cc5d36a9936f478aac45c257b43108c89 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 7 Apr 2026 09:27:48 -0700 Subject: [PATCH 12/15] fix: catch write commands in CTE main query + handle whitespace in AS() - WITH cte AS (SELECT 1) DELETE FROM users now correctly blocked - AS followed by newline/tab/multi-space before ( now detected - execute_sql commit logic updated for both cases - 4 new tests --- .../crewai_tools/tools/nl2sql/nl2sql_tool.py | 92 ++++++++++++++----- .../tests/tools/test_nl2sql_security.py | 32 +++++++ 2 files changed, 103 insertions(+), 21 deletions(-) diff --git a/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py b/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py index 3d6cc73591..5610424618 100644 --- a/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py +++ b/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py @@ -1,5 +1,6 @@ import logging import os +import re from typing import Any @@ -73,30 +74,52 @@ } +_AS_PAREN_RE = re.compile(r"\bAS\s*\(", re.IGNORECASE) + + def _detect_writable_cte(stmt: str) -> str | None: """Return the first write command inside a CTE body, or None. Instead of tokenizing the whole statement (which falsely matches column names like ``comment``), this walks through parenthesized CTE bodies and checks only the *first keyword after* an opening ``AS (`` for a write - command. + command. Uses a regex to handle any whitespace (spaces, tabs, newlines) + between ``AS`` and ``(``. """ - upper = stmt.upper() - search_start = 0 - while True: - # Find the next "AS (" which introduces a CTE body. - idx = upper.find("AS (", search_start) - if idx == -1: - idx = upper.find("AS(", search_start) - if idx == -1: - break - # Jump past "AS (" or "AS(" - paren_start = upper.index("(", idx) + 1 - body = upper[paren_start:].lstrip() - first_word = body.split()[0].strip("()") if body.split() else "" + for m in _AS_PAREN_RE.finditer(stmt): + body = stmt[m.end() :].lstrip() + first_word = body.split()[0].upper().strip("()") if body.split() else "" if first_word in _CTE_WRITE_INDICATORS: return first_word - search_start = paren_start + return None + + +def _extract_main_query_after_cte(stmt: str) -> str | None: + """Extract the main (outer) query that follows all CTE definitions. + + For ``WITH cte AS (SELECT 1) DELETE FROM users``, returns ``DELETE FROM users``. + Returns None if no main query is found after the last CTE body. + """ + # Walk through balanced parens after each AS( to find the end of CTE bodies. + last_cte_end = 0 + for m in _AS_PAREN_RE.finditer(stmt): + # Find the matching closing paren for this CTE body. + depth = 1 + i = m.end() + while i < len(stmt) and depth > 0: + if stmt[i] == "(": + depth += 1 + elif stmt[i] == ")": + depth -= 1 + i += 1 + last_cte_end = i + + if last_cte_end > 0: + remainder = stmt[last_cte_end:].strip().lstrip(",").strip() + # Skip additional CTE definitions (name AS (...)) + # The remainder after the last CTE closing paren is the main query + if remainder: + return remainder return None @@ -249,6 +272,7 @@ def _validate_statement(self, stmt: str) -> None: # WITH starts a CTE. Read-only CTEs are fine; writable CTEs # (e.g. WITH d AS (DELETE …) SELECT …) must be blocked in read-only mode. if command == "WITH": + # Check for write commands inside CTE bodies. write_found = _detect_writable_cte(stmt) if write_found: found = write_found @@ -263,7 +287,24 @@ def _validate_statement(self, stmt: str) -> None: "NL2SQLTool: executing writable CTE with '%s' because allow_dml=True.", found, ) - # Both read-only and writable-but-permitted CTEs need no further checks. + return + + # Check the main query after the CTE definitions. + main_query = _extract_main_query_after_cte(stmt) + if main_query: + main_cmd = main_query.split()[0].upper().rstrip(";") + if main_cmd in _WRITE_COMMANDS: + if not self.allow_dml: + raise ValueError( + f"NL2SQLTool is configured in read-only mode and blocked a " + f"'{main_cmd}' statement after a CTE. To allow write " + f"operations set allow_dml=True or " + f"CREWAI_NL2SQL_ALLOW_DML=true." + ) + logger.warning( + "NL2SQLTool: executing '%s' after CTE because allow_dml=True.", + main_cmd, + ) return if command in _WRITE_COMMANDS: @@ -360,11 +401,20 @@ def execute_sql( # Check ALL statements so that e.g. "SELECT 1; DROP TABLE t" triggers a # commit when allow_dml=True, regardless of statement order. _stmts = [s.strip() for s in sql_query.split(";") if s.strip()] - is_write = any( - self._extract_command(s) in _WRITE_COMMANDS - or (self._extract_command(s) == "WITH" and _detect_writable_cte(s)) - for s in _stmts - ) + + def _is_write_stmt(s: str) -> bool: + cmd = self._extract_command(s) + if cmd in _WRITE_COMMANDS: + return True + if cmd == "WITH": + if _detect_writable_cte(s): + return True + main_q = _extract_main_query_after_cte(s) + if main_q: + return main_q.split()[0].upper().rstrip(";") in _WRITE_COMMANDS + return False + + is_write = any(_is_write_stmt(s) for s in _stmts) engine = create_engine(self.db_uri) Session = sessionmaker(bind=engine) # noqa: N806 diff --git a/lib/crewai-tools/tests/tools/test_nl2sql_security.py b/lib/crewai-tools/tests/tools/test_nl2sql_security.py index d5db86c91b..0a2ea34a1d 100644 --- a/lib/crewai-tools/tests/tools/test_nl2sql_security.py +++ b/lib/crewai-tools/tests/tools/test_nl2sql_security.py @@ -379,6 +379,38 @@ def test_cte_with_set_column_not_false_positive(self): # --------------------------------------------------------------------------- + def test_cte_with_write_main_query_blocked(self): + """WITH cte AS (SELECT 1) DELETE FROM users — main query must be caught.""" + tool = _make_tool(allow_dml=False) + with pytest.raises(ValueError, match="read-only mode"): + tool._validate_query( + "WITH cte AS (SELECT 1) DELETE FROM users" + ) + + def test_cte_with_write_main_query_allowed_with_dml(self): + """Main query write after CTE should pass when allow_dml=True.""" + tool = _make_tool(allow_dml=True) + tool._validate_query( + "WITH cte AS (SELECT id FROM users) INSERT INTO archive SELECT * FROM cte" + ) + + def test_cte_with_newline_before_paren_blocked(self): + """AS followed by newline then ( should still detect writable CTE.""" + tool = _make_tool(allow_dml=False) + with pytest.raises(ValueError, match="read-only mode"): + tool._validate_query( + "WITH cte AS\n(DELETE FROM users RETURNING *) SELECT * FROM cte" + ) + + def test_cte_with_tab_before_paren_blocked(self): + """AS followed by tab then ( should still detect writable CTE.""" + tool = _make_tool(allow_dml=False) + with pytest.raises(ValueError, match="read-only mode"): + tool._validate_query( + "WITH cte AS\t(DELETE FROM users RETURNING *) SELECT * FROM cte" + ) + + class TestExplainAnalyze: def test_explain_analyze_delete_blocked_in_read_only(self): """EXPLAIN ANALYZE DELETE actually runs the delete — block it.""" From e2eca931eebef4c752f01f877beb9bdee93328c6 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 8 Apr 2026 22:42:19 -0700 Subject: [PATCH 13/15] fix: EXPLAIN ANALYZE VERBOSE handling, string literal paren bypass, commit logic for EXPLAIN ANALYZE - EXPLAIN handler now consumes all known options (ANALYZE, ANALYSE, VERBOSE) before extracting the real command, fixing 'EXPLAIN ANALYZE VERBOSE SELECT' being blocked - Paren walker in _extract_main_query_after_cte now skips string literals, preventing 'WITH cte AS (SELECT '\''('\'' FROM t) DELETE FROM users' from bypassing detection - _is_write_stmt in execute_sql now resolves EXPLAIN ANALYZE to underlying command via _resolve_explain_command, ensuring session.commit() fires for write operations - 10 new tests covering all three fixes --- .../crewai_tools/tools/nl2sql/nl2sql_tool.py | 106 +++++++++++++++--- .../tests/tools/test_nl2sql_security.py | 74 ++++++++++++ 2 files changed, 163 insertions(+), 17 deletions(-) diff --git a/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py b/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py index 5610424618..ee677377b6 100644 --- a/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py +++ b/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py @@ -94,35 +94,95 @@ def _detect_writable_cte(stmt: str) -> str | None: return None +def _skip_string_literal(stmt: str, pos: int) -> int: + """Skip past a string literal starting at pos (single-quoted). + + Handles escaped quotes ('') inside the literal. + Returns the index after the closing quote. + """ + quote_char = stmt[pos] + i = pos + 1 + while i < len(stmt): + if stmt[i] == quote_char: + # Check for escaped quote ('') + if i + 1 < len(stmt) and stmt[i + 1] == quote_char: + i += 2 + continue + return i + 1 + i += 1 + return i # Unterminated literal — return end + + +def _find_matching_close_paren(stmt: str, start: int) -> int: + """Find the matching close paren, skipping string literals.""" + depth = 1 + i = start + while i < len(stmt) and depth > 0: + ch = stmt[i] + if ch == "'": + i = _skip_string_literal(stmt, i) + continue + if ch == "(": + depth += 1 + elif ch == ")": + depth -= 1 + i += 1 + return i + + def _extract_main_query_after_cte(stmt: str) -> str | None: """Extract the main (outer) query that follows all CTE definitions. For ``WITH cte AS (SELECT 1) DELETE FROM users``, returns ``DELETE FROM users``. Returns None if no main query is found after the last CTE body. + Handles parentheses inside string literals (e.g., ``SELECT '(' FROM t``). """ - # Walk through balanced parens after each AS( to find the end of CTE bodies. last_cte_end = 0 for m in _AS_PAREN_RE.finditer(stmt): - # Find the matching closing paren for this CTE body. - depth = 1 - i = m.end() - while i < len(stmt) and depth > 0: - if stmt[i] == "(": - depth += 1 - elif stmt[i] == ")": - depth -= 1 - i += 1 - last_cte_end = i + last_cte_end = _find_matching_close_paren(stmt, m.end()) if last_cte_end > 0: remainder = stmt[last_cte_end:].strip().lstrip(",").strip() - # Skip additional CTE definitions (name AS (...)) - # The remainder after the last CTE closing paren is the main query if remainder: return remainder return None +def _resolve_explain_command(stmt: str) -> str | None: + """Resolve the underlying command from an EXPLAIN [ANALYZE] [VERBOSE] statement. + + Returns the real command (e.g., 'DELETE') if ANALYZE is present, else None. + Handles both space-separated and parenthesized syntax. + """ + rest = stmt.strip()[len("EXPLAIN") :].strip() + if not rest: + return None + + analyze_found = False + explain_opts = {"ANALYZE", "ANALYSE", "VERBOSE"} + + if rest.startswith("("): + close = rest.find(")") + if close != -1: + options_str = rest[1:close].upper() + analyze_found = any( + opt.strip() in ("ANALYZE", "ANALYSE") for opt in options_str.split(",") + ) + rest = rest[close + 1 :].strip() + else: + while rest: + first_opt = rest.split()[0].upper().rstrip(";") if rest.split() else "" + if first_opt in ("ANALYZE", "ANALYSE"): + analyze_found = True + if first_opt not in explain_opts: + break + rest = rest[len(first_opt) :].strip() + + if analyze_found and rest: + return rest.split()[0].upper().rstrip(";") + return None + + class NL2SQLToolInput(BaseModel): sql_query: str = Field( title="SQL Query", @@ -260,10 +320,17 @@ def _validate_statement(self, stmt: str) -> None: ) rest = rest[close + 1 :].strip() else: - # Space-separated: EXPLAIN ANALYZE - first_opt = rest.split()[0].upper().rstrip(";") if rest.split() else "" - if first_opt in ("ANALYZE", "ANALYSE"): - analyze_found = True + # Space-separated: EXPLAIN [ANALYZE] [VERBOSE] + # Consume all known EXPLAIN options before extracting the real command. + _explain_opts = {"ANALYZE", "ANALYSE", "VERBOSE"} + while rest: + first_opt = ( + rest.split()[0].upper().rstrip(";") if rest.split() else "" + ) + if first_opt in ("ANALYZE", "ANALYSE"): + analyze_found = True + if first_opt not in _explain_opts: + break rest = rest[len(first_opt) :].strip() if analyze_found and rest: @@ -406,6 +473,11 @@ def _is_write_stmt(s: str) -> bool: cmd = self._extract_command(s) if cmd in _WRITE_COMMANDS: return True + if cmd == "EXPLAIN": + # Resolve the underlying command for EXPLAIN ANALYZE + resolved = _resolve_explain_command(s) + if resolved and resolved in _WRITE_COMMANDS: + return True if cmd == "WITH": if _detect_writable_cte(s): return True diff --git a/lib/crewai-tools/tests/tools/test_nl2sql_security.py b/lib/crewai-tools/tests/tools/test_nl2sql_security.py index 0a2ea34a1d..f237261f6e 100644 --- a/lib/crewai-tools/tests/tools/test_nl2sql_security.py +++ b/lib/crewai-tools/tests/tools/test_nl2sql_security.py @@ -560,3 +560,77 @@ def test_extended_write_commands_blocked_by_default(self, stmt: str): tool = _make_tool(allow_dml=False) with pytest.raises(ValueError, match="read-only mode"): tool._validate_query(stmt) + + +# --------------------------------------------------------------------------- +# EXPLAIN ANALYZE VERBOSE handling +# --------------------------------------------------------------------------- + + +class TestExplainAnalyzeVerbose: + def test_explain_analyze_verbose_select_allowed(self): + """EXPLAIN ANALYZE VERBOSE SELECT should be allowed (read-only).""" + tool = _make_tool(allow_dml=False) + tool._validate_query("EXPLAIN ANALYZE VERBOSE SELECT * FROM users") + + def test_explain_analyze_verbose_delete_blocked(self): + """EXPLAIN ANALYZE VERBOSE DELETE should be blocked.""" + tool = _make_tool(allow_dml=False) + with pytest.raises(ValueError, match="read-only mode"): + tool._validate_query("EXPLAIN ANALYZE VERBOSE DELETE FROM users") + + def test_explain_verbose_select_allowed(self): + """EXPLAIN VERBOSE SELECT (no ANALYZE) should be allowed.""" + tool = _make_tool(allow_dml=False) + tool._validate_query("EXPLAIN VERBOSE SELECT * FROM users") + + +# --------------------------------------------------------------------------- +# CTE with string literal parens +# --------------------------------------------------------------------------- + + +class TestCTEStringLiteralParens: + def test_cte_string_paren_does_not_bypass(self): + """Parens inside string literals should not confuse the paren walker.""" + tool = _make_tool(allow_dml=False) + with pytest.raises(ValueError, match="read-only mode"): + tool._validate_query( + "WITH cte AS (SELECT '(' FROM t) DELETE FROM users" + ) + + def test_cte_string_paren_read_only_allowed(self): + """Read-only CTE with string literal parens should be allowed.""" + tool = _make_tool(allow_dml=False) + tool._validate_query( + "WITH cte AS (SELECT '(' FROM t) SELECT * FROM cte" + ) + + +# --------------------------------------------------------------------------- +# EXPLAIN ANALYZE commit logic +# --------------------------------------------------------------------------- + + +class TestExplainAnalyzeCommit: + def test_explain_analyze_delete_triggers_commit(self): + """EXPLAIN ANALYZE DELETE should trigger commit when allow_dml=True.""" + tool = _make_tool(allow_dml=True) + + mock_session = MagicMock() + mock_result = MagicMock() + mock_result.returns_rows = True + mock_result.keys.return_value = ["QUERY PLAN"] + mock_result.fetchall.return_value = [("Delete on users",)] + mock_session.execute.return_value = mock_result + mock_session_cls = MagicMock(return_value=mock_session) + + with ( + patch("crewai_tools.tools.nl2sql.nl2sql_tool.create_engine"), + patch( + "crewai_tools.tools.nl2sql.nl2sql_tool.sessionmaker", + return_value=mock_session_cls, + ), + ): + tool.execute_sql("EXPLAIN ANALYZE DELETE FROM users") + mock_session.commit.assert_called_once() From 69935e4b35199843d13a3ea74e3e17ce906baa19 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 8 Apr 2026 22:57:45 -0700 Subject: [PATCH 14/15] fix: deduplicate EXPLAIN parsing, fix AS( regex in strings, block unknown CTE commands, bump langchain-core - Refactor _validate_statement to use _resolve_explain_command (single source of truth) - _iter_as_paren_matches skips string literals so 'AS (' in data doesn't confuse CTE detection - Unknown commands after CTE definitions now blocked in read-only mode - Bump langchain-core override to >=1.2.28 (GHSA-926x-3r5x-gfhw) --- .../crewai_tools/tools/nl2sql/nl2sql_tool.py | 66 +- .../tests/tools/test_nl2sql_security.py | 35 + pyproject.toml | 4 +- uv.lock | 647 +++++++++++------- 4 files changed, 453 insertions(+), 299 deletions(-) diff --git a/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py b/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py index ee677377b6..fc0ae9e64b 100644 --- a/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py +++ b/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py @@ -77,6 +77,25 @@ _AS_PAREN_RE = re.compile(r"\bAS\s*\(", re.IGNORECASE) +def _iter_as_paren_matches(stmt: str): + """Yield regex matches for ``AS\\s*(`` outside of string literals.""" + # Build a set of character positions that are inside string literals. + in_string: set[int] = set() + i = 0 + while i < len(stmt): + if stmt[i] == "'": + start = i + end = _skip_string_literal(stmt, i) + in_string.update(range(start, end)) + i = end + else: + i += 1 + + for m in _AS_PAREN_RE.finditer(stmt): + if m.start() not in in_string: + yield m + + def _detect_writable_cte(stmt: str) -> str | None: """Return the first write command inside a CTE body, or None. @@ -84,9 +103,9 @@ def _detect_writable_cte(stmt: str) -> str | None: names like ``comment``), this walks through parenthesized CTE bodies and checks only the *first keyword after* an opening ``AS (`` for a write command. Uses a regex to handle any whitespace (spaces, tabs, newlines) - between ``AS`` and ``(``. + between ``AS`` and ``(``. Skips matches inside string literals. """ - for m in _AS_PAREN_RE.finditer(stmt): + for m in _iter_as_paren_matches(stmt): body = stmt[m.end() :].lstrip() first_word = body.split()[0].upper().strip("()") if body.split() else "" if first_word in _CTE_WRITE_INDICATORS: @@ -138,7 +157,7 @@ def _extract_main_query_after_cte(stmt: str) -> str | None: Handles parentheses inside string literals (e.g., ``SELECT '(' FROM t``). """ last_cte_end = 0 - for m in _AS_PAREN_RE.finditer(stmt): + for m in _iter_as_paren_matches(stmt): last_cte_end = _find_matching_close_paren(stmt, m.end()) if last_cte_end > 0: @@ -305,36 +324,12 @@ def _validate_statement(self, stmt: str) -> None: # query. Resolve the real command so write operations are caught. # Handles both space-separated ("EXPLAIN ANALYZE DELETE …") and # parenthesized ("EXPLAIN (ANALYZE) DELETE …", "EXPLAIN (ANALYZE, VERBOSE) DELETE …"). + # EXPLAIN ANALYZE actually executes the underlying query — resolve the + # real command so write operations are caught. if command == "EXPLAIN": - rest = stmt.strip()[len("EXPLAIN") :].strip() - analyze_found = False - - if rest.startswith("("): - # Parenthesized options: EXPLAIN (ANALYZE, VERBOSE, …) - close = rest.find(")") - if close != -1: - options_str = rest[1:close].upper() - analyze_found = any( - opt.strip() in ("ANALYZE", "ANALYSE") - for opt in options_str.split(",") - ) - rest = rest[close + 1 :].strip() - else: - # Space-separated: EXPLAIN [ANALYZE] [VERBOSE] - # Consume all known EXPLAIN options before extracting the real command. - _explain_opts = {"ANALYZE", "ANALYSE", "VERBOSE"} - while rest: - first_opt = ( - rest.split()[0].upper().rstrip(";") if rest.split() else "" - ) - if first_opt in ("ANALYZE", "ANALYSE"): - analyze_found = True - if first_opt not in _explain_opts: - break - rest = rest[len(first_opt) :].strip() - - if analyze_found and rest: - command = rest.split()[0].upper().rstrip(";") + resolved = _resolve_explain_command(stmt) + if resolved: + command = resolved # WITH starts a CTE. Read-only CTEs are fine; writable CTEs # (e.g. WITH d AS (DELETE …) SELECT …) must be blocked in read-only mode. @@ -372,6 +367,13 @@ def _validate_statement(self, stmt: str) -> None: "NL2SQLTool: executing '%s' after CTE because allow_dml=True.", main_cmd, ) + elif main_cmd not in _READ_ONLY_COMMANDS: + if not self.allow_dml: + raise ValueError( + f"NL2SQLTool blocked an unrecognised SQL command '{main_cmd}' " + f"after a CTE. Only {sorted(_READ_ONLY_COMMANDS)} are allowed " + f"in read-only mode." + ) return if command in _WRITE_COMMANDS: diff --git a/lib/crewai-tools/tests/tools/test_nl2sql_security.py b/lib/crewai-tools/tests/tools/test_nl2sql_security.py index f237261f6e..abef973ffc 100644 --- a/lib/crewai-tools/tests/tools/test_nl2sql_security.py +++ b/lib/crewai-tools/tests/tools/test_nl2sql_security.py @@ -634,3 +634,38 @@ def test_explain_analyze_delete_triggers_commit(self): ): tool.execute_sql("EXPLAIN ANALYZE DELETE FROM users") mock_session.commit.assert_called_once() + + +# --------------------------------------------------------------------------- +# AS( inside string literals must not confuse CTE detection +# --------------------------------------------------------------------------- + + +class TestCTEStringLiteralAS: + def test_as_paren_inside_string_does_not_bypass(self): + """'AS (' inside a string literal must not be treated as a CTE body.""" + tool = _make_tool(allow_dml=False) + with pytest.raises(ValueError, match="read-only mode"): + tool._validate_query( + "WITH cte AS (SELECT 'AS (' FROM t) DELETE FROM users" + ) + + def test_as_paren_inside_string_read_only_ok(self): + """Read-only CTE with 'AS (' in a string should be allowed.""" + tool = _make_tool(allow_dml=False) + tool._validate_query( + "WITH cte AS (SELECT 'AS (' FROM t) SELECT * FROM cte" + ) + + +# --------------------------------------------------------------------------- +# Unknown command after CTE should be blocked +# --------------------------------------------------------------------------- + + +class TestCTEUnknownCommand: + def test_unknown_command_after_cte_blocked(self): + """WITH cte AS (SELECT 1) FOOBAR should be blocked as unknown.""" + tool = _make_tool(allow_dml=False) + with pytest.raises(ValueError, match="unrecognised"): + tool._validate_query("WITH cte AS (SELECT 1) FOOBAR") diff --git a/pyproject.toml b/pyproject.toml index caf6c61c84..58faa76be2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -166,14 +166,14 @@ exclude-newer = "2026-04-10" # pinned for CVE-2026-39892; restore to "3 days" a # composio-core pins rich<14 but textual requires rich>=14. # onnxruntime 1.24+ dropped Python 3.10 wheels; cap it so qdrant[fastembed] resolves on 3.10. # fastembed 0.7.x and docling 2.63 cap pillow<12; the removed APIs don't affect them. -# langchain-core <1.2.11 has SSRF via image_url token counting (CVE-2026-26013). +# langchain-core <1.2.28 has GHSA-926x-3r5x-gfhw (incomplete f-string validation). # transformers 4.57.6 has CVE-2026-1839; force 5.4+ (docling 2.84 allows huggingface-hub>=1). # cryptography 46.0.6 has CVE-2026-39892; force 46.0.7+. override-dependencies = [ "rich>=13.7.1", "onnxruntime<1.24; python_version < '3.11'", "pillow>=12.1.1", - "langchain-core>=1.2.11,<2", + "langchain-core>=1.2.28,<2", "urllib3>=2.6.3", "transformers>=5.4.0; python_version >= '3.10'", "cryptography>=46.0.7", diff --git a/uv.lock b/uv.lock index 1bfe4937ea..2df4112744 100644 --- a/uv.lock +++ b/uv.lock @@ -13,7 +13,7 @@ resolution-markers = [ ] [options] -exclude-newer = "2026-04-10T16:00:00Z" +exclude-newer = "2026-04-11T07:00:00Z" [manifest] members = [ @@ -24,7 +24,7 @@ members = [ ] overrides = [ { name = "cryptography", specifier = ">=46.0.7" }, - { name = "langchain-core", specifier = ">=1.2.11,<2" }, + { name = "langchain-core", specifier = ">=1.2.28,<2" }, { name = "onnxruntime", marker = "python_full_version < '3.11'", specifier = "<1.24" }, { name = "pillow", specifier = ">=12.1.1" }, { name = "rich", specifier = ">=13.7.1" }, @@ -673,7 +673,7 @@ wheels = [ [[package]] name = "browserbase" -version = "1.7.0" +version = "1.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -683,9 +683,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d8/72/27d4ca6fec8d107f3ee905675ce7a48b47fcf7918a5ce17fdbe40846beef/browserbase-1.7.0.tar.gz", hash = "sha256:e5b7acd33fad07666c1b9c7a33acea14d46a1693adaf5620c52839a746a342b8", size = 143680, upload-time = "2026-03-16T21:01:26.837Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/07/4ab4b91921833d0fb1731940d74141396d83120821f4c85482ed80bb2457/browserbase-1.8.0.tar.gz", hash = "sha256:dc62910c2f1fab3e944f338af9fbf82f53bbffcb3aeb6382b4e435a752383011", size = 147213, upload-time = "2026-04-06T19:31:26.848Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/93/59/ae53543ca44b232f64f18413eaf5c3eb968d690ae6960ffb4b4d1a9449d9/browserbase-1.7.0-py3-none-any.whl", hash = "sha256:6ff0ad602f18a7b2034e9e564fbaee05f02954456f1709fc36061f53755356ce", size = 107840, upload-time = "2026-03-16T21:01:25.698Z" }, + { url = "https://files.pythonhosted.org/packages/6d/c3/a29e57566c52fdb24712dcbb93a9bc97937c0c75874d8880a41a651daa5c/browserbase-1.8.0-py3-none-any.whl", hash = "sha256:4c4215973cc99f2f6d34550ae105c3f1f83b5fe22df2845bea0920b10f809526", size = 110012, upload-time = "2026-04-06T19:31:25.765Z" }, ] [[package]] @@ -871,7 +871,8 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "bcrypt" }, { name = "build" }, - { name = "grpcio" }, + { name = "grpcio", version = "1.78.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and python_full_version < '3.13') or (python_full_version >= '3.11' and platform_machine == 's390x')" }, + { name = "grpcio", version = "1.80.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (python_full_version >= '3.13' and platform_machine != 's390x')" }, { name = "httpx" }, { name = "importlib-resources" }, { name = "jsonschema" }, @@ -1518,7 +1519,8 @@ tavily-python = [ { name = "tavily-python" }, ] weaviate-client = [ - { name = "weaviate-client" }, + { name = "weaviate-client", version = "4.16.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (python_full_version >= '3.13' and platform_machine != 's390x')" }, + { name = "weaviate-client", version = "4.18.3", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and python_full_version < '3.13') or (python_full_version >= '3.11' and platform_machine == 's390x')" }, ] xml = [ { name = "unstructured", extra = ["all-docs", "local-inference"] }, @@ -1647,10 +1649,10 @@ wheels = [ [[package]] name = "cuda-pathfinder" -version = "1.5.0" +version = "1.5.2" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/93/66/0c02bd330e7d976f83fa68583d6198d76f23581bcbb5c0e98a6148f326e5/cuda_pathfinder-1.5.0-py3-none-any.whl", hash = "sha256:498f90a9e9de36044a7924742aecce11c50c49f735f1bc53e05aa46de9ea4110", size = 49739, upload-time = "2026-03-24T21:14:30.869Z" }, + { url = "https://files.pythonhosted.org/packages/f2/f9/1b9b60a30fc463c14cdea7a77228131a0ccc89572e8df9cb86c9648271ab/cuda_pathfinder-1.5.2-py3-none-any.whl", hash = "sha256:0c5f160a7756c5b072723cbbd6d861e38917ef956c68150b02f0b6e9271c71fa", size = 49988, upload-time = "2026-04-06T23:01:05.17Z" }, ] [[package]] @@ -1864,7 +1866,7 @@ wheels = [ [[package]] name = "docling-core" -version = "2.71.0" +version = "2.72.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "defusedxml" }, @@ -1879,9 +1881,9 @@ dependencies = [ { name = "typer" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c4/5e/0e5463bcbb2de3ae8f35f76a1e98b201b373b71783120f57daa4d5bc4683/docling_core-2.71.0.tar.gz", hash = "sha256:4caa9f50c68b9dd332584ae16170b36db05d773532b14d7078b580d89d8bd2a4", size = 302901, upload-time = "2026-03-30T15:48:20.997Z" } +sdist = { url = "https://files.pythonhosted.org/packages/45/87/7b49ca0f4e39b051292694eb82e5ff3a7e6ae88a5bc11b8004747afb6e47/docling_core-2.72.0.tar.gz", hash = "sha256:981b789f7097c26b2fa84d0d28cdeaa58ddd8b49e277dce7e44b1b826b8f90f0", size = 304572, upload-time = "2026-04-07T12:35:55.736Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/5d/604cd8d076cacea11018e20c461bad6df1b769e1aa901b70d06bca33b0f6/docling_core-2.71.0-py3-none-any.whl", hash = "sha256:4761857816853b2b35263b5b4518e1ea6214e0565db0bbf1d929fb976665d1a0", size = 268049, upload-time = "2026-03-30T15:48:18.998Z" }, + { url = "https://files.pythonhosted.org/packages/6a/e5/dfbcbfb3d258d5c44043cc1cd314d0447c8f08563ff8fa5a2f77d34eab31/docling_core-2.72.0-py3-none-any.whl", hash = "sha256:3592c35a423093c7fe087416a43de7db0bd1539148f2fa9ac775c41e4ec015a4", size = 269342, upload-time = "2026-04-07T12:35:54.06Z" }, ] [package.optional-dependencies] @@ -1922,7 +1924,7 @@ wheels = [ [[package]] name = "docling-parse" -version = "5.7.0" +version = "5.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "docling-core" }, @@ -1931,24 +1933,24 @@ dependencies = [ { name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "tabulate" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/22/ce/2dff1c13dffd5557833b83697556126cbe78ad3d60adfbd9c915e6b8b464/docling_parse-5.7.0.tar.gz", hash = "sha256:c77209c2e093ca5f8266952bd13b95aef09dfa38e6995ecf855971819786c93d", size = 64359331, upload-time = "2026-04-01T08:46:45.447Z" } +sdist = { url = "https://files.pythonhosted.org/packages/be/57/7b98e3ccf1ed40977bf832f028c68c248b0df1c25a5a33a50c2b2943ea72/docling_parse-5.8.0.tar.gz", hash = "sha256:cbb1d591dd94edab4ab3b81e9e42a3e4c7fe9ab3c3e690dccd498602aae63c5a", size = 65990181, upload-time = "2026-04-08T09:41:39.651Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/7b/79a3aadb6b58b1e29660db833202d40a648a032475f52dadd994bc6a778e/docling_parse-5.7.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:e4d218e0983cdf447eb994b657fed7ba9b324ab2544b7a004ef97736b3b44b7c", size = 8531704, upload-time = "2026-04-01T08:46:04.047Z" }, - { url = "https://files.pythonhosted.org/packages/16/ff/08d6c25131e1dc8ab9cc745ea7b86168be9367c094389c98b29ed62152d0/docling_parse-5.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78631d7a9dafe716fb92af00199a585e9959454dd87d178d82ad583cc62af68c", size = 9303534, upload-time = "2026-04-01T08:46:06.096Z" }, - { url = "https://files.pythonhosted.org/packages/a8/20/ecd4da5492d6fafae8402d79251c389ac74e428bcab98c9c32a5d7439157/docling_parse-5.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4f78f8a570bb33e9557ec3c93e4939bec8bf4d9d96032e34616a877a3bda84f", size = 9544737, upload-time = "2026-04-01T08:46:08.458Z" }, - { url = "https://files.pythonhosted.org/packages/54/cb/175436f1fb29a5338bc6cc32a88ab319910dec55bf873f35cf4f8221cc2f/docling_parse-5.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:8acf03df37e475c523d3e2fd9101ec21f4f7de532adc4dd7b9394890dcc0547c", size = 10349252, upload-time = "2026-04-01T08:46:10.559Z" }, - { url = "https://files.pythonhosted.org/packages/61/90/164b10d24064e3186ba679b80f118a09644f67e938a90324d3a9b1294d64/docling_parse-5.7.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4a4df3a79b413e2fcaa9f4494c355045778b18fd71db070e6f9166e19d00b193", size = 8533116, upload-time = "2026-04-01T08:46:12.367Z" }, - { url = "https://files.pythonhosted.org/packages/44/c1/5181c34b2c6841222fff3a4a4ad082b4441c33a7e47227d21582021e7ed6/docling_parse-5.7.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fcab1f5c4a82925305897d198ad19a27e05a6859fe0c917c321040490d968dc", size = 9261386, upload-time = "2026-04-01T08:46:14.248Z" }, - { url = "https://files.pythonhosted.org/packages/62/1a/8dd86721b8dc653e750e1531359abb0548568a92c08d781348fafb17ff29/docling_parse-5.7.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:af12d1a011687cb46a0879d4b6dcb8534be393cb70de5d7428a335706af53dcc", size = 9592996, upload-time = "2026-04-01T08:46:15.881Z" }, - { url = "https://files.pythonhosted.org/packages/bf/c4/744e9f6150c7373d6ffa61ebed7957819f4c0e00c6794ea1473f9a11c799/docling_parse-5.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:660bbcc1fe7736289cb1e57ea8f770266e7095c3708e40b35b3c0e7d9ca08d81", size = 10350448, upload-time = "2026-04-01T08:46:17.948Z" }, - { url = "https://files.pythonhosted.org/packages/97/9d/14269974385ae0b1d6fb31df0224e0ae83aefb9931288282222f908fd704/docling_parse-5.7.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a645b47bc637a63e87b86b3995fe319b63be116e1b7bc9ec1fd44edb00356f6d", size = 8534658, upload-time = "2026-04-01T08:46:19.878Z" }, - { url = "https://files.pythonhosted.org/packages/1b/d1/f2a7815da9c8df51306fe941b4c829fa53bdaf866331caa0917508c1bade/docling_parse-5.7.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7503f5321ef94b455c4cd56e3d437699205d2150f2f3c93889dd64309b34d342", size = 9262244, upload-time = "2026-04-01T08:46:21.623Z" }, - { url = "https://files.pythonhosted.org/packages/3b/e6/17d7c19e4e4193aec5219ebbb4a8baf0afafa6d82c11df04a05e8483c759/docling_parse-5.7.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92e819292ab3ee2852a296b0189dfa972916446518fe977eefdfb2ea6823d86e", size = 9595224, upload-time = "2026-04-01T08:46:25.001Z" }, - { url = "https://files.pythonhosted.org/packages/e5/b1/9f9a1006de94e6775b2a332fd72a5d91478e4a9eda878a369d33e0ab23a6/docling_parse-5.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:763b53a30ea171e3a58f92d2892682692ae6a34001dfcad4f01806c18cbd021b", size = 10351618, upload-time = "2026-04-01T08:46:26.878Z" }, - { url = "https://files.pythonhosted.org/packages/c9/da/d781ee9da13b4d952e3baf5d7d01f429d60afe30ef90b1d70afc5960613c/docling_parse-5.7.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:d480fff217fc62183ca97259347c09f46e7539fcacedfb860ecdae628c0247a0", size = 8534712, upload-time = "2026-04-01T08:46:28.887Z" }, - { url = "https://files.pythonhosted.org/packages/a6/23/4205b2d8e0007d18d2bef7c67257272594f23a26882acdec06b13aabe858/docling_parse-5.7.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2247152e4438d01cc51bc9d5d6524a8da06362d3a80ec84397f6b3b414b577f", size = 9263031, upload-time = "2026-04-01T08:46:30.859Z" }, - { url = "https://files.pythonhosted.org/packages/01/61/8fbe76e34cd6715a5974f599ca1524f730847d6eebe73f7a230f391fab9b/docling_parse-5.7.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41785ee7b472d7a688f183e33c927c6b364ac8432898ff4616b99de1b1ae518d", size = 9595643, upload-time = "2026-04-01T08:46:32.819Z" }, - { url = "https://files.pythonhosted.org/packages/ee/62/6607673219fa157628f5c2ccb7e8bf1715f36c54cebaf46f031cc1bd6727/docling_parse-5.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:f122a81390e2869e03cf110de0ff4db6f5c57ce7d95def82fe0c5f1c3838fdf7", size = 10351630, upload-time = "2026-04-01T08:46:35.132Z" }, + { url = "https://files.pythonhosted.org/packages/06/38/02a686660fe89a6f6775618ae43f9d4b76f615edc7374a1e8e1bf648fb73/docling_parse-5.8.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:241d09a904d8e4b70a2c040252a75a088e971a7926a46973389cb3235a5cab74", size = 8539476, upload-time = "2026-04-08T09:40:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/f1/38/ebd2fd850eef60d9c201cfb28b24bc3c8a27efeb34e817c12f544453a3c2/docling_parse-5.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2e81da134baff612ea38ff0af3bf17deef196195d2415bfcf4f531bc7d0dd84", size = 9311993, upload-time = "2026-04-08T09:40:55.362Z" }, + { url = "https://files.pythonhosted.org/packages/c5/ba/c05c35a75b358ddaafdf0cd1e3f3737091722c6547b692cd66a99071159a/docling_parse-5.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b149bd7eeb91a5c6bdbc4a9bd87055a2a06d9ea959bf34d309580c1722d2e2b9", size = 9553650, upload-time = "2026-04-08T09:40:57.636Z" }, + { url = "https://files.pythonhosted.org/packages/63/7a/3670258908f6e5cf04251b9547967ebbf28211e29ede30eb5da41e0b509a/docling_parse-5.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:ac2c03347de9a0f02cdd46385ee4ae05f91eefc72aeac4749389d17f661dd7d5", size = 10357004, upload-time = "2026-04-08T09:40:59.921Z" }, + { url = "https://files.pythonhosted.org/packages/fc/09/57e47cc861f4e98201d6b881c6a7683e84f8ad20e2c1d619fe94c39ab7f2/docling_parse-5.8.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:fd1ae1cc22a96ccef76f82756ff7958d2a1eb38804e7cd9eed6ae951e2480c30", size = 8540650, upload-time = "2026-04-08T09:41:01.933Z" }, + { url = "https://files.pythonhosted.org/packages/5b/55/0265703d03377ad7ad3c4d482b00265275061ac15470dc815815944637cf/docling_parse-5.8.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3908496e6949d2e56e361fc743a8f9248cb0f76807a1860027dde02be14f854", size = 9269550, upload-time = "2026-04-08T09:41:04.454Z" }, + { url = "https://files.pythonhosted.org/packages/96/03/962449ed1b6692e16c3cae0cf00fd60145d620dd1886aedacd1636727dec/docling_parse-5.8.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:860fbd5f2d30774d1c739d373aec14b7e074fdace191e5ac16750e7b14f136f4", size = 9601965, upload-time = "2026-04-08T09:41:06.807Z" }, + { url = "https://files.pythonhosted.org/packages/eb/18/5bee07b6ef6451b71904e0d21d7721af964fd92f3465305ef791d7a3cf56/docling_parse-5.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:854630f6ef7889d1757611194330d88fbbe53c0b202b5a010a467bf059f715da", size = 10358059, upload-time = "2026-04-08T09:41:09.049Z" }, + { url = "https://files.pythonhosted.org/packages/f9/61/3038e3a759df3aff0f02628eaeb71f6068b428ddd62981e639c5acf1eca8/docling_parse-5.8.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a37c8c0aab730a9857c726420925cccc304a16abd91f054b25726394ee1ac836", size = 8541739, upload-time = "2026-04-08T09:41:11.525Z" }, + { url = "https://files.pythonhosted.org/packages/d1/98/b9307f84a7753cc369bbdd81f0183f308e8be1efeb2998193a494f8a8f44/docling_parse-5.8.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b2c7455b058525cdd46d4c6b7c429871f096aa7718ce1b8481dae426358cf29", size = 9269677, upload-time = "2026-04-08T09:41:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a6/686adf6ed39d9de9912b233b8d0bd4f5e8113023aef47630ffde12ff0ba4/docling_parse-5.8.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:987d8eacb0f515f53a860329acc5c826487a9d2ff4430f08bd37498854cdab42", size = 9604016, upload-time = "2026-04-08T09:41:15.762Z" }, + { url = "https://files.pythonhosted.org/packages/f0/1b/90c5447a00a652a81e2b4fea86b33a694b1e0fec3b9fb1862f9b6f48f54a/docling_parse-5.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6f72b0fdd370e825777f7a9989c390c630774870390c7277b7f016bfae395d6a", size = 10360133, upload-time = "2026-04-08T09:41:18.085Z" }, + { url = "https://files.pythonhosted.org/packages/33/c9/799cc497b71537bafb6b8bf66fcccf303f8a84684503e8783d489db03aab/docling_parse-5.8.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:292b82a9773c66a76e5ee376cfdde4a4d6a8edae6a4493aba4013d939e7a213f", size = 8541804, upload-time = "2026-04-08T09:41:20.358Z" }, + { url = "https://files.pythonhosted.org/packages/93/29/1030c13b257be7a4317bc7837c22366eff6d961ca6d6604b426dc8a9adcd/docling_parse-5.8.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:85c896983aaa7b95f409ed52014da59a945f2b914291c0782740e6a5b6d39028", size = 9269366, upload-time = "2026-04-08T09:41:22.437Z" }, + { url = "https://files.pythonhosted.org/packages/54/22/40990653103c2eb83b073d2aca47aa95b767f1360214fca4c6339df105c3/docling_parse-5.8.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2d9139f8da5e6553a36afb40dba614011ebd1bf97e5d17896ace07191a289c4b", size = 9604422, upload-time = "2026-04-08T09:41:24.619Z" }, + { url = "https://files.pythonhosted.org/packages/7e/9e/4ab1b16f6ba17f9695df79faa08a332b09a2d333d609036a7d0106538d57/docling_parse-5.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:7343ee48b0480593ed08b04ed0b09421724a6dec63d82c23fac436129b32c66a", size = 10360242, upload-time = "2026-04-08T09:41:27.132Z" }, ] [[package]] @@ -2014,7 +2016,7 @@ wheels = [ [[package]] name = "exa-py" -version = "2.10.2" +version = "2.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpcore" }, @@ -2025,9 +2027,9 @@ dependencies = [ { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fe/4f/f06a6f277d668f143e330fe503b0027cc5fed753b22c3e161f8cbbccdf65/exa_py-2.10.2.tar.gz", hash = "sha256:f781f30b199f1102333384728adae64bb15a6bbcabfa97e91fd705f90acffc45", size = 53792, upload-time = "2026-03-26T20:29:35.764Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/08/af21dace845b5cd67d728e9d7747e4d1024ec90bd83e007d78f969dc6e19/exa_py-2.11.0.tar.gz", hash = "sha256:989103cbd83aae6dbe88cb70e11522a4bb06026fdb54b8659e3a7922da41fc93", size = 54905, upload-time = "2026-04-04T00:04:32.455Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/bc/7a34e904a415040ba626948d0b0a36a08cd073f12b13342578a68331be3c/exa_py-2.10.2-py3-none-any.whl", hash = "sha256:ecb2a7581f4b7a8aeb6b434acce1bbc40f92ed1d4126b2aa6029913acd904a47", size = 72248, upload-time = "2026-03-26T20:29:37.306Z" }, + { url = "https://files.pythonhosted.org/packages/9b/c9/129dd486505e3c0dadda0d6c83c560060f76d4cf14ef4b7b93053846598a/exa_py-2.11.0-py3-none-any.whl", hash = "sha256:3b0070a6ce98e02895755f0f81752dff64e2e121cf9d9a82facf715a4b9a5238", size = 73424, upload-time = "2026-04-04T00:04:33.699Z" }, ] [[package]] @@ -2053,14 +2055,14 @@ wheels = [ [[package]] name = "faker" -version = "40.12.0" +version = "40.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/c1/f8224fe97fea2f98d455c22438c1b09b10e14ef2cb95ae4f7cec9aa59659/faker-40.12.0.tar.gz", hash = "sha256:58b5a9054c367bd5fb2e948634105364cc570e78a98a8e5161a74691c45f158f", size = 1962003, upload-time = "2026-03-30T18:00:56.596Z" } +sdist = { url = "https://files.pythonhosted.org/packages/89/95/4822ffe94723553789aef783104f4f18fc20d7c4c68e1bbd633e11d09758/faker-40.13.0.tar.gz", hash = "sha256:a0751c84c3abac17327d7bb4c98e8afe70ebf7821e01dd7d0b15cd8856415525", size = 1962043, upload-time = "2026-04-06T16:44:55.68Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/5c/39452a6b6aa76ffa518fa7308e1975b37e9ba77caa6172a69d61e7180221/faker-40.12.0-py3-none-any.whl", hash = "sha256:6238a4058a8b581892e3d78fe5fdfa7568739e1c8283e4ede83f1dde0bfc1a3b", size = 1994601, upload-time = "2026-03-30T18:00:54.804Z" }, + { url = "https://files.pythonhosted.org/packages/da/8a/708103325edff16a0b0e004de0d37db8ba216a32713948c64d71f6d4a4c2/faker-40.13.0-py3-none-any.whl", hash = "sha256:c1298fd0d819b3688fb5fd358c4ba8f56c7c8c740b411fd3dbd8e30bf2c05019", size = 1994597, upload-time = "2026-04-06T16:44:53.698Z" }, ] [[package]] @@ -2185,7 +2187,7 @@ wheels = [ [[package]] name = "firecrawl-py" -version = "4.21.1" +version = "4.22.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, @@ -2196,9 +2198,9 @@ dependencies = [ { name = "requests" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9c/3e/a2426b461e57f10327ba8ec56511a0ab0a817a433c933380c61b80e9b5c3/firecrawl_py-4.21.1.tar.gz", hash = "sha256:e82eab65ee4d46f38293c30d43e065a78d40ec9efd2872dd543c58e03ea58b54", size = 174335, upload-time = "2026-04-02T18:29:01.975Z" } +sdist = { url = "https://files.pythonhosted.org/packages/64/87/08cd440a3b942be5983c1a2db921d55697bdb91f7ead9a925b75715039a0/firecrawl_py-4.22.1.tar.gz", hash = "sha256:fb44d4c63ba91c076ae2f0b688f1556327c971baea45e7fb67d6ed5d393542a2", size = 174394, upload-time = "2026-04-07T01:54:19.682Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/ee/b1030a89dc28f0f54bb8bf387333bc67d905920b2165cb0fa94692f3c6b3/firecrawl_py-4.21.1-py3-none-any.whl", hash = "sha256:b54c645ae7cb73f2a683c4448cc0dfc195eea6948ef529be5ba52f0ec2210366", size = 217676, upload-time = "2026-04-02T18:29:00.433Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a7/54199470a5bf8e09bdf9511f80e766a11b20daafc3b0e1e638ec04e24fc9/firecrawl_py-4.22.1-py3-none-any.whl", hash = "sha256:3df92a7888f9d5907a6fbbe50ade330d2925f5bf51f8efa507c2ab9891df9a0a", size = 217741, upload-time = "2026-04-07T01:54:18.403Z" }, ] [[package]] @@ -2399,7 +2401,8 @@ wheels = [ [package.optional-dependencies] grpc = [ - { name = "grpcio" }, + { name = "grpcio", version = "1.78.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and python_full_version < '3.13') or (python_full_version >= '3.11' and platform_machine == 's390x')" }, + { name = "grpcio", version = "1.80.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (python_full_version >= '3.13' and platform_machine != 's390x')" }, { name = "grpcio-status" }, ] @@ -2428,7 +2431,8 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-api-core", extra = ["grpc"] }, { name = "google-auth" }, - { name = "grpcio" }, + { name = "grpcio", version = "1.78.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and python_full_version < '3.13') or (python_full_version >= '3.11' and platform_machine == 's390x')" }, + { name = "grpcio", version = "1.80.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (python_full_version >= '3.13' and platform_machine != 's390x')" }, { name = "proto-plus" }, { name = "protobuf" }, ] @@ -2472,53 +2476,64 @@ wheels = [ [[package]] name = "greenlet" -version = "3.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a3/51/1664f6b78fc6ebbd98019a1fd730e83fa78f2db7058f72b1463d3612b8db/greenlet-3.3.2.tar.gz", hash = "sha256:2eaf067fc6d886931c7962e8c6bede15d2f01965560f3359b27c80bde2d151f2", size = 188267, upload-time = "2026-02-20T20:54:15.531Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/38/3f/9859f655d11901e7b2996c6e3d33e0caa9a1d4572c3bc61ed0faa64b2f4c/greenlet-3.3.2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9bc885b89709d901859cf95179ec9f6bb67a3d2bb1f0e88456461bd4b7f8fd0d", size = 277747, upload-time = "2026-02-20T20:16:21.325Z" }, - { url = "https://files.pythonhosted.org/packages/fb/07/cb284a8b5c6498dbd7cba35d31380bb123d7dceaa7907f606c8ff5993cbf/greenlet-3.3.2-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b568183cf65b94919be4438dc28416b234b678c608cafac8874dfeeb2a9bbe13", size = 579202, upload-time = "2026-02-20T20:47:28.955Z" }, - { url = "https://files.pythonhosted.org/packages/ed/45/67922992b3a152f726163b19f890a85129a992f39607a2a53155de3448b8/greenlet-3.3.2-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:527fec58dc9f90efd594b9b700662ed3fb2493c2122067ac9c740d98080a620e", size = 590620, upload-time = "2026-02-20T20:55:55.581Z" }, - { url = "https://files.pythonhosted.org/packages/03/5f/6e2a7d80c353587751ef3d44bb947f0565ec008a2e0927821c007e96d3a7/greenlet-3.3.2-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:508c7f01f1791fbc8e011bd508f6794cb95397fdb198a46cb6635eb5b78d85a7", size = 602132, upload-time = "2026-02-20T21:02:43.261Z" }, - { url = "https://files.pythonhosted.org/packages/ad/55/9f1ebb5a825215fadcc0f7d5073f6e79e3007e3282b14b22d6aba7ca6cb8/greenlet-3.3.2-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad0c8917dd42a819fe77e6bdfcb84e3379c0de956469301d9fd36427a1ca501f", size = 591729, upload-time = "2026-02-20T20:20:58.395Z" }, - { url = "https://files.pythonhosted.org/packages/24/b4/21f5455773d37f94b866eb3cf5caed88d6cea6dd2c6e1f9c34f463cba3ec/greenlet-3.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:97245cc10e5515dbc8c3104b2928f7f02b6813002770cfaffaf9a6e0fc2b94ef", size = 1551946, upload-time = "2026-02-20T20:49:31.102Z" }, - { url = "https://files.pythonhosted.org/packages/00/68/91f061a926abead128fe1a87f0b453ccf07368666bd59ffa46016627a930/greenlet-3.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8c1fdd7d1b309ff0da81d60a9688a8bd044ac4e18b250320a96fc68d31c209ca", size = 1618494, upload-time = "2026-02-20T20:21:06.541Z" }, - { url = "https://files.pythonhosted.org/packages/ac/78/f93e840cbaef8becaf6adafbaf1319682a6c2d8c1c20224267a5c6c8c891/greenlet-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:5d0e35379f93a6d0222de929a25ab47b5eb35b5ef4721c2b9cbcc4036129ff1f", size = 230092, upload-time = "2026-02-20T20:17:09.379Z" }, - { url = "https://files.pythonhosted.org/packages/f3/47/16400cb42d18d7a6bb46f0626852c1718612e35dcb0dffa16bbaffdf5dd2/greenlet-3.3.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:c56692189a7d1c7606cb794be0a8381470d95c57ce5be03fb3d0ef57c7853b86", size = 278890, upload-time = "2026-02-20T20:19:39.263Z" }, - { url = "https://files.pythonhosted.org/packages/a3/90/42762b77a5b6aa96cd8c0e80612663d39211e8ae8a6cd47c7f1249a66262/greenlet-3.3.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ebd458fa8285960f382841da585e02201b53a5ec2bac6b156fc623b5ce4499f", size = 581120, upload-time = "2026-02-20T20:47:30.161Z" }, - { url = "https://files.pythonhosted.org/packages/bf/6f/f3d64f4fa0a9c7b5c5b3c810ff1df614540d5aa7d519261b53fba55d4df9/greenlet-3.3.2-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a443358b33c4ec7b05b79a7c8b466f5d275025e750298be7340f8fc63dff2a55", size = 594363, upload-time = "2026-02-20T20:55:56.965Z" }, - { url = "https://files.pythonhosted.org/packages/9c/8b/1430a04657735a3f23116c2e0d5eb10220928846e4537a938a41b350bed6/greenlet-3.3.2-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4375a58e49522698d3e70cc0b801c19433021b5c37686f7ce9c65b0d5c8677d2", size = 605046, upload-time = "2026-02-20T21:02:45.234Z" }, - { url = "https://files.pythonhosted.org/packages/72/83/3e06a52aca8128bdd4dcd67e932b809e76a96ab8c232a8b025b2850264c5/greenlet-3.3.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e2cd90d413acbf5e77ae41e5d3c9b3ac1d011a756d7284d7f3f2b806bbd6358", size = 594156, upload-time = "2026-02-20T20:20:59.955Z" }, - { url = "https://files.pythonhosted.org/packages/70/79/0de5e62b873e08fe3cef7dbe84e5c4bc0e8ed0c7ff131bccb8405cd107c8/greenlet-3.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:442b6057453c8cb29b4fb36a2ac689382fc71112273726e2423f7f17dc73bf99", size = 1554649, upload-time = "2026-02-20T20:49:32.293Z" }, - { url = "https://files.pythonhosted.org/packages/5a/00/32d30dee8389dc36d42170a9c66217757289e2afb0de59a3565260f38373/greenlet-3.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:45abe8eb6339518180d5a7fa47fa01945414d7cca5ecb745346fc6a87d2750be", size = 1619472, upload-time = "2026-02-20T20:21:07.966Z" }, - { url = "https://files.pythonhosted.org/packages/f1/3a/efb2cf697fbccdf75b24e2c18025e7dfa54c4f31fab75c51d0fe79942cef/greenlet-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e692b2dae4cc7077cbb11b47d258533b48c8fde69a33d0d8a82e2fe8d8531d5", size = 230389, upload-time = "2026-02-20T20:17:18.772Z" }, - { url = "https://files.pythonhosted.org/packages/e1/a1/65bbc059a43a7e2143ec4fc1f9e3f673e04f9c7b371a494a101422ac4fd5/greenlet-3.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:02b0a8682aecd4d3c6c18edf52bc8e51eacdd75c8eac52a790a210b06aa295fd", size = 229645, upload-time = "2026-02-20T20:18:18.695Z" }, - { url = "https://files.pythonhosted.org/packages/ea/ab/1608e5a7578e62113506740b88066bf09888322a311cff602105e619bd87/greenlet-3.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd", size = 280358, upload-time = "2026-02-20T20:17:43.971Z" }, - { url = "https://files.pythonhosted.org/packages/a5/23/0eae412a4ade4e6623ff7626e38998cb9b11e9ff1ebacaa021e4e108ec15/greenlet-3.3.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd", size = 601217, upload-time = "2026-02-20T20:47:31.462Z" }, - { url = "https://files.pythonhosted.org/packages/f8/16/5b1678a9c07098ecb9ab2dd159fafaf12e963293e61ee8d10ecb55273e5e/greenlet-3.3.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac", size = 611792, upload-time = "2026-02-20T20:55:58.423Z" }, - { url = "https://files.pythonhosted.org/packages/5c/c5/cc09412a29e43406eba18d61c70baa936e299bc27e074e2be3806ed29098/greenlet-3.3.2-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae9e21c84035c490506c17002f5c8ab25f980205c3e61ddb3a2a2a2e6c411fcb", size = 626250, upload-time = "2026-02-20T21:02:46.596Z" }, - { url = "https://files.pythonhosted.org/packages/50/1f/5155f55bd71cabd03765a4aac9ac446be129895271f73872c36ebd4b04b6/greenlet-3.3.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070", size = 613875, upload-time = "2026-02-20T20:21:01.102Z" }, - { url = "https://files.pythonhosted.org/packages/fc/dd/845f249c3fcd69e32df80cdab059b4be8b766ef5830a3d0aa9d6cad55beb/greenlet-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79", size = 1571467, upload-time = "2026-02-20T20:49:33.495Z" }, - { url = "https://files.pythonhosted.org/packages/2a/50/2649fe21fcc2b56659a452868e695634722a6655ba245d9f77f5656010bf/greenlet-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395", size = 1640001, upload-time = "2026-02-20T20:21:09.154Z" }, - { url = "https://files.pythonhosted.org/packages/9b/40/cc802e067d02af8b60b6771cea7d57e21ef5e6659912814babb42b864713/greenlet-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:34308836d8370bddadb41f5a7ce96879b72e2fdfb4e87729330c6ab52376409f", size = 231081, upload-time = "2026-02-20T20:17:28.121Z" }, - { url = "https://files.pythonhosted.org/packages/58/2e/fe7f36ff1982d6b10a60d5e0740c759259a7d6d2e1dc41da6d96de32fff6/greenlet-3.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:d3a62fa76a32b462a97198e4c9e99afb9ab375115e74e9a83ce180e7a496f643", size = 230331, upload-time = "2026-02-20T20:17:23.34Z" }, - { url = "https://files.pythonhosted.org/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4", size = 279120, upload-time = "2026-02-20T20:19:01.9Z" }, - { url = "https://files.pythonhosted.org/packages/49/8d/9771d03e7a8b1ee456511961e1b97a6d77ae1dea4a34a5b98eee706689d3/greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986", size = 603238, upload-time = "2026-02-20T20:47:32.873Z" }, - { url = "https://files.pythonhosted.org/packages/59/0e/4223c2bbb63cd5c97f28ffb2a8aee71bdfb30b323c35d409450f51b91e3e/greenlet-3.3.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d248d8c23c67d2291ffd47af766e2a3aa9fa1c6703155c099feb11f526c63a92", size = 614219, upload-time = "2026-02-20T20:55:59.817Z" }, - { url = "https://files.pythonhosted.org/packages/94/2b/4d012a69759ac9d77210b8bfb128bc621125f5b20fc398bce3940d036b1c/greenlet-3.3.2-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccd21bb86944ca9be6d967cf7691e658e43417782bce90b5d2faeda0ff78a7dd", size = 628268, upload-time = "2026-02-20T21:02:48.024Z" }, - { url = "https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab", size = 616774, upload-time = "2026-02-20T20:21:02.454Z" }, - { url = "https://files.pythonhosted.org/packages/0a/03/996c2d1689d486a6e199cb0f1cf9e4aa940c500e01bdf201299d7d61fa69/greenlet-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64970c33a50551c7c50491671265d8954046cb6e8e2999aacdd60e439b70418a", size = 1571277, upload-time = "2026-02-20T20:49:34.795Z" }, - { url = "https://files.pythonhosted.org/packages/d9/c4/2570fc07f34a39f2caf0bf9f24b0a1a0a47bc2e8e465b2c2424821389dfc/greenlet-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a9172f5bf6bd88e6ba5a84e0a68afeac9dc7b6b412b245dd64f52d83c81e55b", size = 1640455, upload-time = "2026-02-20T20:21:10.261Z" }, - { url = "https://files.pythonhosted.org/packages/91/39/5ef5aa23bc545aa0d31e1b9b55822b32c8da93ba657295840b6b34124009/greenlet-3.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:a7945dd0eab63ded0a48e4dcade82939783c172290a7903ebde9e184333ca124", size = 230961, upload-time = "2026-02-20T20:16:58.461Z" }, - { url = "https://files.pythonhosted.org/packages/62/6b/a89f8456dcb06becff288f563618e9f20deed8dd29beea14f9a168aef64b/greenlet-3.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:394ead29063ee3515b4e775216cb756b2e3b4a7e55ae8fd884f17fa579e6b327", size = 230221, upload-time = "2026-02-20T20:17:37.152Z" }, +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/86/94/a5935717b307d7c71fe877b52b884c6af707d2d2090db118a03fbd799369/greenlet-3.4.0.tar.gz", hash = "sha256:f50a96b64dafd6169e595a5c56c9146ef80333e67d4476a65a9c55f400fc22ff", size = 195913, upload-time = "2026-04-08T17:08:00.863Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/bc/e30e1e3d5e8860b0e0ce4d2b16b2681b77fd13542fc0d72f7e3c22d16eff/greenlet-3.4.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:d18eae9a7fb0f499efcd146b8c9750a2e1f6e0e93b5a382b3481875354a430e6", size = 284315, upload-time = "2026-04-08T17:02:52.322Z" }, + { url = "https://files.pythonhosted.org/packages/5b/cc/e023ae1967d2a26737387cac083e99e47f65f58868bd155c4c80c01ec4e0/greenlet-3.4.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:636d2f95c309e35f650e421c23297d5011716be15d966e6328b367c9fc513a82", size = 601916, upload-time = "2026-04-08T16:24:35.533Z" }, + { url = "https://files.pythonhosted.org/packages/67/32/5be1677954b6d8810b33abe94e3eb88726311c58fa777dc97e390f7caf5a/greenlet-3.4.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:234582c20af9742583c3b2ddfbdbb58a756cfff803763ffaae1ac7990a9fac31", size = 616399, upload-time = "2026-04-08T16:30:54.536Z" }, + { url = "https://files.pythonhosted.org/packages/82/0a/3a4af092b09ea02bcda30f33fd7db397619132fe52c6ece24b9363130d34/greenlet-3.4.0-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ac6a5f618be581e1e0713aecec8e54093c235e5fa17d6d8eb7ffc487e2300508", size = 621077, upload-time = "2026-04-08T16:40:34.946Z" }, + { url = "https://files.pythonhosted.org/packages/74/bf/2d58d5ea515704f83e34699128c9072a34bea27d2b6a556e102105fe62a5/greenlet-3.4.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:523677e69cd4711b5a014e37bc1fb3a29947c3e3a5bb6a527e1cc50312e5a398", size = 611978, upload-time = "2026-04-08T15:56:31.335Z" }, + { url = "https://files.pythonhosted.org/packages/8c/39/3786520a7d5e33ee87b3da2531f589a3882abf686a42a3773183a41ef010/greenlet-3.4.0-cp310-cp310-manylinux_2_39_riscv64.whl", hash = "sha256:d336d46878e486de7d9458653c722875547ac8d36a1cff9ffaf4a74a3c1f62eb", size = 416893, upload-time = "2026-04-08T16:43:02.392Z" }, + { url = "https://files.pythonhosted.org/packages/bd/69/6525049b6c179d8a923256304d8387b8bdd4acab1acf0407852463c6d514/greenlet-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b45e45fe47a19051a396abb22e19e7836a59ee6c5a90f3be427343c37908d65b", size = 1571957, upload-time = "2026-04-08T16:26:17.041Z" }, + { url = "https://files.pythonhosted.org/packages/4e/6c/bbfb798b05fec736a0d24dc23e81b45bcee87f45a83cfb39db031853bddc/greenlet-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5434271357be07f3ad0936c312645853b7e689e679e29310e2de09a9ea6c3adf", size = 1637223, upload-time = "2026-04-08T15:57:27.556Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7d/981fe0e7c07bd9d5e7eb18decb8590a11e3955878291f7a7de2e9c668eb7/greenlet-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:a19093fbad824ed7c0f355b5ff4214bffda5f1a7f35f29b31fcaa240cc0135ab", size = 237902, upload-time = "2026-04-08T17:03:14.16Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c6/dba32cab7e3a625b011aa5647486e2d28423a48845a2998c126dd69c85e1/greenlet-3.4.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:805bebb4945094acbab757d34d6e1098be6de8966009ab9ca54f06ff492def58", size = 285504, upload-time = "2026-04-08T15:52:14.071Z" }, + { url = "https://files.pythonhosted.org/packages/54/f4/7cb5c2b1feb9a1f50e038be79980dfa969aa91979e5e3a18fdbcfad2c517/greenlet-3.4.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:439fc2f12b9b512d9dfa681c5afe5f6b3232c708d13e6f02c845e0d9f4c2d8c6", size = 605476, upload-time = "2026-04-08T16:24:37.064Z" }, + { url = "https://files.pythonhosted.org/packages/d6/af/b66ab0b2f9a4c5a867c136bf66d9599f34f21a1bcca26a2884a29c450bd9/greenlet-3.4.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a70ed1cb0295bee1df57b63bf7f46b4e56a5c93709eea769c1fec1bb23a95875", size = 618336, upload-time = "2026-04-08T16:30:56.59Z" }, + { url = "https://files.pythonhosted.org/packages/6d/31/56c43d2b5de476f77d36ceeec436328533bff960a4cba9a07616e93063ab/greenlet-3.4.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c5696c42e6bb5cfb7c6ff4453789081c66b9b91f061e5e9367fa15792644e76", size = 625045, upload-time = "2026-04-08T16:40:37.111Z" }, + { url = "https://files.pythonhosted.org/packages/e5/5c/8c5633ece6ba611d64bf2770219a98dd439921d6424e4e8cf16b0ac74ea5/greenlet-3.4.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c660bce1940a1acae5f51f0a064f1bc785d07ea16efcb4bc708090afc4d69e83", size = 613515, upload-time = "2026-04-08T15:56:32.478Z" }, + { url = "https://files.pythonhosted.org/packages/80/ca/704d4e2c90acb8bdf7ae593f5cbc95f58e82de95cc540fb75631c1054533/greenlet-3.4.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:89995ce5ddcd2896d89615116dd39b9703bfa0c07b583b85b89bf1b5d6eddf81", size = 419745, upload-time = "2026-04-08T16:43:04.022Z" }, + { url = "https://files.pythonhosted.org/packages/a9/df/950d15bca0d90a0e7395eb777903060504cdb509b7b705631e8fb69ff415/greenlet-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ee407d4d1ca9dc632265aee1c8732c4a2d60adff848057cdebfe5fe94eb2c8a2", size = 1574623, upload-time = "2026-04-08T16:26:18.596Z" }, + { url = "https://files.pythonhosted.org/packages/1a/e7/0839afab829fcb7333c9ff6d80c040949510055d2d4d63251f0d1c7c804e/greenlet-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:956215d5e355fffa7c021d168728321fd4d31fd730ac609b1653b450f6a4bc71", size = 1639579, upload-time = "2026-04-08T15:57:29.231Z" }, + { url = "https://files.pythonhosted.org/packages/d9/2b/b4482401e9bcaf9f5c97f67ead38db89c19520ff6d0d6699979c6efcc200/greenlet-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:5cb614ace7c27571270354e9c9f696554d073f8aa9319079dcba466bbdead711", size = 238233, upload-time = "2026-04-08T17:02:54.286Z" }, + { url = "https://files.pythonhosted.org/packages/0c/4d/d8123a4e0bcd583d5cfc8ddae0bbe29c67aab96711be331a7cc935a35966/greenlet-3.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:04403ac74fe295a361f650818de93be11b5038a78f49ccfb64d3b1be8fbf1267", size = 235045, upload-time = "2026-04-08T17:04:05.072Z" }, + { url = "https://files.pythonhosted.org/packages/65/8b/3669ad3b3f247a791b2b4aceb3aa5a31f5f6817bf547e4e1ff712338145a/greenlet-3.4.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:1a54a921561dd9518d31d2d3db4d7f80e589083063ab4d3e2e950756ef809e1a", size = 286902, upload-time = "2026-04-08T15:52:12.138Z" }, + { url = "https://files.pythonhosted.org/packages/38/3e/3c0e19b82900873e2d8469b590a6c4b3dfd2b316d0591f1c26b38a4879a5/greenlet-3.4.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16dec271460a9a2b154e3b1c2fa1050ce6280878430320e85e08c166772e3f97", size = 606099, upload-time = "2026-04-08T16:24:38.408Z" }, + { url = "https://files.pythonhosted.org/packages/b5/33/99fef65e7754fc76a4ed14794074c38c9ed3394a5bd129d7f61b705f3168/greenlet-3.4.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:90036ce224ed6fe75508c1907a77e4540176dcf0744473627785dd519c6f9996", size = 618837, upload-time = "2026-04-08T16:30:58.298Z" }, + { url = "https://files.pythonhosted.org/packages/44/57/eae2cac10421feae6c0987e3dc106c6d86262b1cb379e171b017aba893a6/greenlet-3.4.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6f0def07ec9a71d72315cf26c061aceee53b306c36ed38c35caba952ea1b319d", size = 624901, upload-time = "2026-04-08T16:40:38.981Z" }, + { url = "https://files.pythonhosted.org/packages/36/f7/229f3aed6948faa20e0616a0b8568da22e365ede6a54d7d369058b128afd/greenlet-3.4.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a1c4f6b453006efb8310affb2d132832e9bbb4fc01ce6df6b70d810d38f1f6dc", size = 615062, upload-time = "2026-04-08T15:56:33.766Z" }, + { url = "https://files.pythonhosted.org/packages/6a/8a/0e73c9b94f31d1cc257fe79a0eff621674141cdae7d6d00f40de378a1e42/greenlet-3.4.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:0e1254cf0cbaa17b04320c3a78575f29f3c161ef38f59c977108f19ffddaf077", size = 423927, upload-time = "2026-04-08T16:43:05.293Z" }, + { url = "https://files.pythonhosted.org/packages/08/97/d988180011aa40135c46cd0d0cf01dd97f7162bae14139b4a3ef54889ba5/greenlet-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b2d9a138ffa0e306d0e2b72976d2fb10b97e690d40ab36a472acaab0838e2de", size = 1573511, upload-time = "2026-04-08T16:26:20.058Z" }, + { url = "https://files.pythonhosted.org/packages/d4/0f/a5a26fe152fb3d12e6a474181f6e9848283504d0afd095f353d85726374b/greenlet-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8424683caf46eb0eb6f626cb95e008e8cc30d0cb675bdfa48200925c79b38a08", size = 1640396, upload-time = "2026-04-08T15:57:30.88Z" }, + { url = "https://files.pythonhosted.org/packages/42/cf/bb2c32d9a100e36ee9f6e38fad6b1e082b8184010cb06259b49e1266ca01/greenlet-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0a53fb071531d003b075c444014ff8f8b1a9898d36bb88abd9ac7b3524648a2", size = 238892, upload-time = "2026-04-08T17:03:10.094Z" }, + { url = "https://files.pythonhosted.org/packages/b7/47/6c41314bac56e71436ce551c7fbe3cc830ed857e6aa9708dbb9c65142eb6/greenlet-3.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:f38b81880ba28f232f1f675893a39cf7b6db25b31cc0a09bb50787ecf957e85e", size = 235599, upload-time = "2026-04-08T15:52:54.3Z" }, + { url = "https://files.pythonhosted.org/packages/7a/75/7e9cd1126a1e1f0cd67b0eda02e5221b28488d352684704a78ed505bd719/greenlet-3.4.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:43748988b097f9c6f09364f260741aa73c80747f63389824435c7a50bfdfd5c1", size = 285856, upload-time = "2026-04-08T15:52:45.82Z" }, + { url = "https://files.pythonhosted.org/packages/9d/c4/3e2df392e5cb199527c4d9dbcaa75c14edcc394b45040f0189f649631e3c/greenlet-3.4.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5566e4e2cd7a880e8c27618e3eab20f3494452d12fd5129edef7b2f7aa9a36d1", size = 610208, upload-time = "2026-04-08T16:24:39.674Z" }, + { url = "https://files.pythonhosted.org/packages/da/af/750cdfda1d1bd30a6c28080245be8d0346e669a98fdbae7f4102aa95fff3/greenlet-3.4.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1054c5a3c78e2ab599d452f23f7adafef55062a783a8e241d24f3b633ba6ff82", size = 621269, upload-time = "2026-04-08T16:30:59.767Z" }, + { url = "https://files.pythonhosted.org/packages/e0/93/c8c508d68ba93232784bbc1b5474d92371f2897dfc6bc281b419f2e0d492/greenlet-3.4.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:98eedd1803353daf1cd9ef23eef23eda5a4d22f99b1f998d273a8b78b70dd47f", size = 628455, upload-time = "2026-04-08T16:40:40.698Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/0cbc693622cd54ebe25207efbb3a0eb07c2639cb8594f6e3aaaa0bb077a8/greenlet-3.4.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f82cb6cddc27dd81c96b1506f4aa7def15070c3b2a67d4e46fd19016aacce6cf", size = 617549, upload-time = "2026-04-08T15:56:34.893Z" }, + { url = "https://files.pythonhosted.org/packages/7f/46/cfaaa0ade435a60550fd83d07dfd5c41f873a01da17ede5c4cade0b9bab8/greenlet-3.4.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:b7857e2202aae67bc5725e0c1f6403c20a8ff46094ece015e7d474f5f7020b55", size = 426238, upload-time = "2026-04-08T16:43:06.865Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c0/8966767de01343c1ff47e8b855dc78e7d1a8ed2b7b9c83576a57e289f81d/greenlet-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:227a46251ecba4ff46ae742bc5ce95c91d5aceb4b02f885487aff269c127a729", size = 1575310, upload-time = "2026-04-08T16:26:21.671Z" }, + { url = "https://files.pythonhosted.org/packages/b8/38/bcdc71ba05e9a5fda87f63ffc2abcd1f15693b659346df994a48c968003d/greenlet-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5b99e87be7eba788dd5b75ba1cde5639edffdec5f91fe0d734a249535ec3408c", size = 1640435, upload-time = "2026-04-08T15:57:32.572Z" }, + { url = "https://files.pythonhosted.org/packages/a1/c2/19b664b7173b9e4ef5f77e8cef9f14c20ec7fce7920dc1ccd7afd955d093/greenlet-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:849f8bc17acd6295fcb5de8e46d55cc0e52381c56eaf50a2afd258e97bc65940", size = 238760, upload-time = "2026-04-08T17:04:03.878Z" }, + { url = "https://files.pythonhosted.org/packages/9b/96/795619651d39c7fbd809a522f881aa6f0ead504cc8201c3a5b789dfaef99/greenlet-3.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:9390ad88b652b1903814eaabd629ca184db15e0eeb6fe8a390bbf8b9106ae15a", size = 235498, upload-time = "2026-04-08T17:05:00.584Z" }, ] [[package]] name = "grpcio" version = "1.78.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13' and platform_machine == 's390x'", + "python_full_version == '3.12.*' and platform_machine != 's390x'", + "python_full_version == '3.12.*' and platform_machine == 's390x'", + "python_full_version == '3.11.*' and platform_machine != 's390x'", + "python_full_version == '3.11.*' and platform_machine == 's390x'", +] dependencies = [ - { name = "typing-extensions" }, + { name = "typing-extensions", marker = "(python_full_version >= '3.11' and python_full_version < '3.13') or (python_full_version >= '3.11' and platform_machine == 's390x')" }, ] sdist = { url = "https://files.pythonhosted.org/packages/06/8a/3d098f35c143a89520e568e6539cc098fcd294495910e359889ce8741c84/grpcio-1.78.0.tar.gz", hash = "sha256:7382b95189546f375c174f53a5fa873cef91c4b8005faa05cc5b3beea9c4f1c5", size = 12852416, upload-time = "2026-02-06T09:57:18.093Z" } wheels = [ @@ -2564,13 +2579,83 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4d/27/d86b89e36de8a951501fb06a0f38df19853210f341d0b28f83f4aa0ffa08/grpcio-1.78.0-cp313-cp313-win_amd64.whl", hash = "sha256:f2d4e43ee362adfc05994ed479334d5a451ab7bc3f3fee1b796b8ca66895acb4", size = 4797393, upload-time = "2026-02-06T09:56:17.882Z" }, ] +[[package]] +name = "grpcio" +version = "1.80.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13' and platform_machine != 's390x'", + "python_full_version < '3.11' and platform_machine != 's390x'", + "python_full_version < '3.11' and platform_machine == 's390x'", +] +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11' or (python_full_version >= '3.13' and platform_machine != 's390x')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b7/48/af6173dbca4454f4637a4678b67f52ca7e0c1ed7d5894d89d434fecede05/grpcio-1.80.0.tar.gz", hash = "sha256:29aca15edd0688c22ba01d7cc01cb000d72b2033f4a3c72a81a19b56fd143257", size = 12978905, upload-time = "2026-03-30T08:49:10.502Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/cd/bb7b7e54084a344c03d68144450da7ddd5564e51a298ae1662de65f48e2d/grpcio-1.80.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:886457a7768e408cdce226ad1ca67d2958917d306523a0e21e1a2fdaa75c9c9c", size = 6050363, upload-time = "2026-03-30T08:46:20.894Z" }, + { url = "https://files.pythonhosted.org/packages/16/02/1417f5c3460dea65f7a2e3c14e8b31e77f7ffb730e9bfadd89eda7a9f477/grpcio-1.80.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:7b641fc3f1dc647bfd80bd713addc68f6d145956f64677e56d9ebafc0bd72388", size = 12026037, upload-time = "2026-03-30T08:46:25.144Z" }, + { url = "https://files.pythonhosted.org/packages/43/98/c910254eedf2cae368d78336a2de0678e66a7317d27c02522392f949b5c6/grpcio-1.80.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:33eb763f18f006dc7fee1e69831d38d23f5eccd15b2e0f92a13ee1d9242e5e02", size = 6602306, upload-time = "2026-03-30T08:46:27.593Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f8/88ca4e78c077b2b2113d95da1e1ab43efd43d723c9a0397d26529c2c1a56/grpcio-1.80.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:52d143637e3872633fc7dd7c3c6a1c84e396b359f3a72e215f8bf69fd82084fc", size = 7301535, upload-time = "2026-03-30T08:46:29.556Z" }, + { url = "https://files.pythonhosted.org/packages/f9/96/f28660fe2fe0f153288bf4a04e4910b7309d442395135c88ed4f5b3b8b40/grpcio-1.80.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c51bf8ac4575af2e0678bccfb07e47321fc7acb5049b4482832c5c195e04e13a", size = 6808669, upload-time = "2026-03-30T08:46:31.984Z" }, + { url = "https://files.pythonhosted.org/packages/47/eb/3f68a5e955779c00aeef23850e019c1c1d0e032d90633ba49c01ad5a96e0/grpcio-1.80.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:50a9871536d71c4fba24ee856abc03a87764570f0c457dd8db0b4018f379fed9", size = 7409489, upload-time = "2026-03-30T08:46:34.684Z" }, + { url = "https://files.pythonhosted.org/packages/5b/a7/d2f681a4bfb881be40659a309771f3bdfbfdb1190619442816c3f0ffc079/grpcio-1.80.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a72d84ad0514db063e21887fbacd1fd7acb4d494a564cae22227cd45c7fbf199", size = 8423167, upload-time = "2026-03-30T08:46:36.833Z" }, + { url = "https://files.pythonhosted.org/packages/97/8a/29b4589c204959aa35ce5708400a05bba72181807c45c47b3ec000c39333/grpcio-1.80.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f7691a6788ad9196872f95716df5bc643ebba13c97140b7a5ee5c8e75d1dea81", size = 7846761, upload-time = "2026-03-30T08:46:40.091Z" }, + { url = "https://files.pythonhosted.org/packages/6b/d2/ed143e097230ee121ac5848f6ff14372dba91289b10b536d54fb1b7cbae7/grpcio-1.80.0-cp310-cp310-win32.whl", hash = "sha256:46c2390b59d67f84e882694d489f5b45707c657832d7934859ceb8c33f467069", size = 4156534, upload-time = "2026-03-30T08:46:42.026Z" }, + { url = "https://files.pythonhosted.org/packages/d5/c9/df8279bb49b29409995e95efa85b72973d62f8aeff89abee58c91f393710/grpcio-1.80.0-cp310-cp310-win_amd64.whl", hash = "sha256:dc053420fc75749c961e2a4c906398d7c15725d36ccc04ae6d16093167223b58", size = 4889869, upload-time = "2026-03-30T08:46:44.219Z" }, + { url = "https://files.pythonhosted.org/packages/5d/db/1d56e5f5823257b291962d6c0ce106146c6447f405b60b234c4f222a7cde/grpcio-1.80.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:dfab85db094068ff42e2a3563f60ab3dddcc9d6488a35abf0132daec13209c8a", size = 6055009, upload-time = "2026-03-30T08:46:46.265Z" }, + { url = "https://files.pythonhosted.org/packages/6e/18/c83f3cad64c5ca63bca7e91e5e46b0d026afc5af9d0a9972472ceba294b3/grpcio-1.80.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:5c07e82e822e1161354e32da2662f741a4944ea955f9f580ec8fb409dd6f6060", size = 12035295, upload-time = "2026-03-30T08:46:49.099Z" }, + { url = "https://files.pythonhosted.org/packages/0f/8e/e14966b435be2dda99fbe89db9525ea436edc79780431a1c2875a3582644/grpcio-1.80.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba0915d51fd4ced2db5ff719f84e270afe0e2d4c45a7bdb1e8d036e4502928c2", size = 6610297, upload-time = "2026-03-30T08:46:52.123Z" }, + { url = "https://files.pythonhosted.org/packages/cc/26/d5eb38f42ce0e3fdc8174ea4d52036ef8d58cc4426cb800f2610f625dd75/grpcio-1.80.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3cb8130ba457d2aa09fa6b7c3ed6b6e4e6a2685fce63cb803d479576c4d80e21", size = 7300208, upload-time = "2026-03-30T08:46:54.859Z" }, + { url = "https://files.pythonhosted.org/packages/25/51/bd267c989f85a17a5b3eea65a6feb4ff672af41ca614e5a0279cc0ea381c/grpcio-1.80.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:09e5e478b3d14afd23f12e49e8b44c8684ac3c5f08561c43a5b9691c54d136ab", size = 6813442, upload-time = "2026-03-30T08:46:57.056Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d9/d80eef735b19e9169e30164bbf889b46f9df9127598a83d174eb13a48b26/grpcio-1.80.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:00168469238b022500e486c1c33916acf2f2a9b2c022202cf8a1885d2e3073c1", size = 7414743, upload-time = "2026-03-30T08:46:59.682Z" }, + { url = "https://files.pythonhosted.org/packages/de/f2/567f5bd5054398ed6b0509b9a30900376dcf2786bd936812098808b49d8d/grpcio-1.80.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8502122a3cc1714038e39a0b071acb1207ca7844208d5ea0d091317555ee7106", size = 8426046, upload-time = "2026-03-30T08:47:02.474Z" }, + { url = "https://files.pythonhosted.org/packages/62/29/73ef0141b4732ff5eacd68430ff2512a65c004696997f70476a83e548e7e/grpcio-1.80.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ce1794f4ea6cc3ca29463f42d665c32ba1b964b48958a66497917fe9069f26e6", size = 7851641, upload-time = "2026-03-30T08:47:05.462Z" }, + { url = "https://files.pythonhosted.org/packages/46/69/abbfa360eb229a8623bab5f5a4f8105e445bd38ce81a89514ba55d281ad0/grpcio-1.80.0-cp311-cp311-win32.whl", hash = "sha256:51b4a7189b0bef2aa30adce3c78f09c83526cf3dddb24c6a96555e3b97340440", size = 4154368, upload-time = "2026-03-30T08:47:08.027Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d4/ae92206d01183b08613e846076115f5ac5991bae358d2a749fa864da5699/grpcio-1.80.0-cp311-cp311-win_amd64.whl", hash = "sha256:02e64bb0bb2da14d947a49e6f120a75e947250aebe65f9629b62bb1f5c14e6e9", size = 4894235, upload-time = "2026-03-30T08:47:10.839Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e8/a2b749265eb3415abc94f2e619bbd9e9707bebdda787e61c593004ec927a/grpcio-1.80.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:c624cc9f1008361014378c9d776de7182b11fe8b2e5a81bc69f23a295f2a1ad0", size = 6015616, upload-time = "2026-03-30T08:47:13.428Z" }, + { url = "https://files.pythonhosted.org/packages/3e/97/b1282161a15d699d1e90c360df18d19165a045ce1c343c7f313f5e8a0b77/grpcio-1.80.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:f49eddcac43c3bf350c0385366a58f36bed8cc2c0ec35ef7b74b49e56552c0c2", size = 12014204, upload-time = "2026-03-30T08:47:15.873Z" }, + { url = "https://files.pythonhosted.org/packages/6e/5e/d319c6e997b50c155ac5a8cb12f5173d5b42677510e886d250d50264949d/grpcio-1.80.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d334591df610ab94714048e0d5b4f3dd5ad1bee74dfec11eee344220077a79de", size = 6563866, upload-time = "2026-03-30T08:47:18.588Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f6/fdd975a2cb4d78eb67769a7b3b3830970bfa2e919f1decf724ae4445f42c/grpcio-1.80.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0cb517eb1d0d0aaf1d87af7cc5b801d686557c1d88b2619f5e31fab3c2315921", size = 7273060, upload-time = "2026-03-30T08:47:21.113Z" }, + { url = "https://files.pythonhosted.org/packages/db/f0/a3deb5feba60d9538a962913e37bd2e69a195f1c3376a3dd44fe0427e996/grpcio-1.80.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4e78c4ac0d97dc2e569b2f4bcbbb447491167cb358d1a389fc4af71ab6f70411", size = 6782121, upload-time = "2026-03-30T08:47:23.827Z" }, + { url = "https://files.pythonhosted.org/packages/ca/84/36c6dcfddc093e108141f757c407902a05085e0c328007cb090d56646cdf/grpcio-1.80.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2ed770b4c06984f3b47eb0517b1c69ad0b84ef3f40128f51448433be904634cd", size = 7383811, upload-time = "2026-03-30T08:47:26.517Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ef/f3a77e3dc5b471a0ec86c564c98d6adfa3510d38f8ee99010410858d591e/grpcio-1.80.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:256507e2f524092f1473071a05e65a5b10d84b82e3ff24c5b571513cfaa61e2f", size = 8393860, upload-time = "2026-03-30T08:47:29.439Z" }, + { url = "https://files.pythonhosted.org/packages/9b/8d/9d4d27ed7f33d109c50d6b5ce578a9914aa68edab75d65869a17e630a8d1/grpcio-1.80.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a6284a5d907c37db53350645567c522be314bac859a64a7a5ca63b77bb7958f", size = 7830132, upload-time = "2026-03-30T08:47:33.254Z" }, + { url = "https://files.pythonhosted.org/packages/14/e4/9990b41c6d7a44e1e9dee8ac11d7a9802ba1378b40d77468a7761d1ad288/grpcio-1.80.0-cp312-cp312-win32.whl", hash = "sha256:c71309cfce2f22be26aa4a847357c502db6c621f1a49825ae98aa0907595b193", size = 4140904, upload-time = "2026-03-30T08:47:35.319Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2c/296f6138caca1f4b92a31ace4ae1b87dab692fc16a7a3417af3bb3c805bf/grpcio-1.80.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe648599c0e37594c4809d81a9e77bd138cc82eb8baa71b6a86af65426723ff", size = 4880944, upload-time = "2026-03-30T08:47:37.831Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/7c3c25789e3f069e581dc342e03613c5b1cb012c4e8c7d9d5cf960a75856/grpcio-1.80.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:e9e408fc016dffd20661f0126c53d8a31c2821b5c13c5d67a0f5ed5de93319ad", size = 6017243, upload-time = "2026-03-30T08:47:40.075Z" }, + { url = "https://files.pythonhosted.org/packages/04/19/21a9806eb8240e174fd1ab0cd5b9aa948bb0e05c2f2f55f9d5d7405e6d08/grpcio-1.80.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:92d787312e613754d4d8b9ca6d3297e69994a7912a32fa38c4c4e01c272974b0", size = 12010840, upload-time = "2026-03-30T08:47:43.11Z" }, + { url = "https://files.pythonhosted.org/packages/18/3a/23347d35f76f639e807fb7a36fad3068aed100996849a33809591f26eca6/grpcio-1.80.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac393b58aa16991a2f1144ec578084d544038c12242da3a215966b512904d0f", size = 6567644, upload-time = "2026-03-30T08:47:46.806Z" }, + { url = "https://files.pythonhosted.org/packages/ff/40/96e07ecb604a6a67ae6ab151e3e35b132875d98bc68ec65f3e5ab3e781d7/grpcio-1.80.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:68e5851ac4b9afe07e7f84483803ad167852570d65326b34d54ca560bfa53fb6", size = 7277830, upload-time = "2026-03-30T08:47:49.643Z" }, + { url = "https://files.pythonhosted.org/packages/9b/e2/da1506ecea1f34a5e365964644b35edef53803052b763ca214ba3870c856/grpcio-1.80.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:873ff5d17d68992ef6605330127425d2fc4e77e612fa3c3e0ed4e668685e3140", size = 6783216, upload-time = "2026-03-30T08:47:52.817Z" }, + { url = "https://files.pythonhosted.org/packages/44/83/3b20ff58d0c3b7f6caaa3af9a4174d4023701df40a3f39f7f1c8e7c48f9d/grpcio-1.80.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2bea16af2750fd0a899bf1abd9022244418b55d1f37da2202249ba4ba673838d", size = 7385866, upload-time = "2026-03-30T08:47:55.687Z" }, + { url = "https://files.pythonhosted.org/packages/47/45/55c507599c5520416de5eefecc927d6a0d7af55e91cfffb2e410607e5744/grpcio-1.80.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba0db34f7e1d803a878284cd70e4c63cb6ae2510ba51937bf8f45ba997cefcf7", size = 8391602, upload-time = "2026-03-30T08:47:58.303Z" }, + { url = "https://files.pythonhosted.org/packages/10/bb/dd06f4c24c01db9cf11341b547d0a016b2c90ed7dbbb086a5710df7dd1d7/grpcio-1.80.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8eb613f02d34721f1acf3626dfdb3545bd3c8505b0e52bf8b5710a28d02e8aa7", size = 7826752, upload-time = "2026-03-30T08:48:01.311Z" }, + { url = "https://files.pythonhosted.org/packages/f9/1e/9d67992ba23371fd63d4527096eb8c6b76d74d52b500df992a3343fd7251/grpcio-1.80.0-cp313-cp313-win32.whl", hash = "sha256:93b6f823810720912fd131f561f91f5fed0fda372b6b7028a2681b8194d5d294", size = 4142310, upload-time = "2026-03-30T08:48:04.594Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e6/283326a27da9e2c3038bc93eeea36fb118ce0b2d03922a9cda6688f53c5b/grpcio-1.80.0-cp313-cp313-win_amd64.whl", hash = "sha256:e172cf795a3ba5246d3529e4d34c53db70e888fa582a8ffebd2e6e48bc0cba50", size = 4882833, upload-time = "2026-03-30T08:48:07.363Z" }, +] + +[[package]] +name = "grpcio-health-checking" +version = "1.71.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "grpcio", version = "1.80.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (python_full_version >= '3.13' and platform_machine != 's390x')" }, + { name = "protobuf", marker = "python_full_version < '3.11' or (python_full_version >= '3.13' and platform_machine != 's390x')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/86/20994347ef36b7626fb74539f13128100dd8b7eaac67efc063264e6cdc80/grpcio_health_checking-1.71.2.tar.gz", hash = "sha256:1c21ece88c641932f432b573ef504b20603bdf030ad4e1ec35dd7fdb4ea02637", size = 16770, upload-time = "2025-06-28T04:24:08.768Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/74/7bc6ab96bf1083cab2684f9c3ae434caa638de3d5c5574e8435e2c146598/grpcio_health_checking-1.71.2-py3-none-any.whl", hash = "sha256:f91db41410d6bd18a7828c5b6ac2bebd77a63483263cbe42bf3c0c9b86cece33", size = 18918, upload-time = "2025-06-28T04:23:56.923Z" }, +] + [[package]] name = "grpcio-status" version = "1.71.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, - { name = "grpcio" }, + { name = "grpcio", version = "1.78.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and python_full_version < '3.13') or (python_full_version >= '3.11' and platform_machine == 's390x')" }, + { name = "grpcio", version = "1.80.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (python_full_version >= '3.13' and platform_machine != 's390x')" }, { name = "protobuf" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fd/d1/b6e9877fedae3add1afdeae1f89d1927d296da9cf977eca0eb08fb8a460e/grpcio_status-1.71.2.tar.gz", hash = "sha256:c7a97e176df71cdc2c179cd1847d7fc86cca5832ad12e9798d7fed6b7a1aab50", size = 13677, upload-time = "2025-06-28T04:24:05.426Z" } @@ -2738,7 +2823,7 @@ wheels = [ [[package]] name = "huggingface-hub" -version = "1.9.0" +version = "1.9.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, @@ -2751,9 +2836,9 @@ dependencies = [ { name = "typer" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/88/bb/62c7aa86f63a05e2f9b96642fdef9b94526a23979820b09f5455deff4983/huggingface_hub-1.9.0.tar.gz", hash = "sha256:0ea5be7a56135c91797cae6ad726e38eaeb6eb4b77cefff5c9d38ba0ecf874f7", size = 750326, upload-time = "2026-04-03T08:35:55.888Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/65/fb800d327bf25bf31b798dd08935d326d064ecb9b359059fecd91b3a98e8/huggingface_hub-1.9.2.tar.gz", hash = "sha256:8d09d080a186bd950a361bfc04b862dfb04d6a2b41d48e9ba1b37507cfd3f1e1", size = 750284, upload-time = "2026-04-08T08:43:11.127Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/37/0d15d16150e1829f3e90962c99f28257f6de9e526a680b4c6f5acdb54fd2/huggingface_hub-1.9.0-py3-none-any.whl", hash = "sha256:2999328c058d39fd19ab748dd09bd4da2fbaa4f4c1ddea823eab103051e14a1f", size = 637355, upload-time = "2026-04-03T08:35:53.897Z" }, + { url = "https://files.pythonhosted.org/packages/57/d4/e33bf0b362810a9b96c5923e38908950d58ecb512db42e3730320c7f4a3a/huggingface_hub-1.9.2-py3-none-any.whl", hash = "sha256:e1e62ce237d4fbeca9f970aeb15176fbd503e04c25577bfd22f44aa7aa2b5243", size = 637349, upload-time = "2026-04-08T08:43:09.114Z" }, ] [[package]] @@ -2770,7 +2855,7 @@ wheels = [ [[package]] name = "hyperbrowser" -version = "0.89.2" +version = "0.90.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, @@ -2778,9 +2863,9 @@ dependencies = [ { name = "pydantic" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e0/45/b31a6cd1a7db3ca41b986174013653893a1947af348835c23f22f997aac6/hyperbrowser-0.89.2.tar.gz", hash = "sha256:3f97f392b5394124fd8424fcf274c69a37317fc4c773923ef8fdf78f5b5406e0", size = 64130, upload-time = "2026-03-30T17:13:53.581Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/60/b651865b7154feb571980c7f3341c75275a7330d3980c6a328bd875eb1dc/hyperbrowser-0.90.1.tar.gz", hash = "sha256:987259a99a8fe740274bc87b9cd64430476588fb5467313537d746881703fe4c", size = 65524, upload-time = "2026-04-07T23:56:44.951Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/65/abbc6cc3446e174ba17aa145de85dc740fc241296e5e0fd443f7bf263922/hyperbrowser-0.89.2-py3-none-any.whl", hash = "sha256:7ae4a9eb155b2d984748224ded0e33bf5bd910779a326021f3060a2024e9e82c", size = 109655, upload-time = "2026-03-30T17:13:52.397Z" }, + { url = "https://files.pythonhosted.org/packages/6c/49/cca92edcbace09135bf6c13a15c1856357c1cf68185d09088937b0bfe1f2/hyperbrowser-0.90.1-py3-none-any.whl", hash = "sha256:831c4e9b3143d713b64dd69034936763c5d92dfbf18f2936bc33d72c066b6551", size = 110792, upload-time = "2026-04-07T23:56:43.626Z" }, ] [[package]] @@ -3037,11 +3122,11 @@ wheels = [ [[package]] name = "invoke" -version = "2.2.1" +version = "3.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/de/bd/b461d3424a24c80490313fd77feeb666ca4f6a28c7e72713e3d9095719b4/invoke-2.2.1.tar.gz", hash = "sha256:515bf49b4a48932b79b024590348da22f39c4942dff991ad1fb8b8baea1be707", size = 304762, upload-time = "2025-10-11T00:36:35.172Z" } +sdist = { url = "https://files.pythonhosted.org/packages/33/f6/227c48c5fe47fa178ccf1fda8f047d16c97ba926567b661e9ce2045c600c/invoke-3.0.3.tar.gz", hash = "sha256:437b6a622223824380bfb4e64f612711a6b648c795f565efc8625af66fb57f0c", size = 343419, upload-time = "2026-04-07T15:17:48.307Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/4b/b99e37f88336009971405cbb7630610322ed6fbfa31e1d7ab3fbf3049a2d/invoke-2.2.1-py3-none-any.whl", hash = "sha256:2413bc441b376e5cd3f55bb5d364f973ad8bdd7bf87e53c79de3c11bf3feecc8", size = 160287, upload-time = "2025-10-11T00:36:33.703Z" }, + { url = "https://files.pythonhosted.org/packages/5a/de/bbc12563bbf979618d17625a4e753ff7a078523e28d870d3626daa97261a/invoke-3.0.3-py3-none-any.whl", hash = "sha256:f11327165e5cbb89b2ad1d88d3292b5113332c43b8553b494da435d6ec6f5053", size = 160958, upload-time = "2026-04-07T15:17:46.875Z" }, ] [[package]] @@ -3423,7 +3508,7 @@ wheels = [ [[package]] name = "langchain-core" -version = "1.2.25" +version = "1.2.28" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonpatch" }, @@ -3435,9 +3520,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "uuid-utils" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/86/2a/d65de24fc9b7989137253da8973f850f3e39b4ce3e0377bc8200d6b3c189/langchain_core-1.2.25.tar.gz", hash = "sha256:77e032b96509d0eb1f6875042fdf97b7e2334a815314700c6894d9d078909b9c", size = 842347, upload-time = "2026-04-02T22:39:11.528Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/a4/317a1a3ac1df33a64adb3670bf88bbe3b3d5baa274db6863a979db472897/langchain_core-1.2.28.tar.gz", hash = "sha256:271a3d8bd618f795fdeba112b0753980457fc90537c46a0c11998516a74dc2cb", size = 846119, upload-time = "2026-04-08T18:19:34.867Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/0e/7b31b0249f9b9b0fc7829d5b0ee484b8f8d43c78e376e9951e2ef3eac70c/langchain_core-1.2.25-py3-none-any.whl", hash = "sha256:0c05bf395aec6d2dfa14488fd006f7bcd0540e7e89287e04f92203532a82c828", size = 506866, upload-time = "2026-04-02T22:39:10.137Z" }, + { url = "https://files.pythonhosted.org/packages/a8/92/32f785f077c7e898da97064f113c73fbd9ad55d1e2169cf3a391b183dedb/langchain_core-1.2.28-py3-none-any.whl", hash = "sha256:80764232581eaf8057bcefa71dbf8adc1f6a28d257ebd8b95ba9b8b452e8c6ac", size = 508727, upload-time = "2026-04-08T18:19:32.823Z" }, ] [[package]] @@ -3463,7 +3548,7 @@ sdist = { url = "https://files.pythonhosted.org/packages/0e/72/a3add0e4eec4eb9e2 [[package]] name = "langsmith" -version = "0.7.25" +version = "0.7.29" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, @@ -3476,9 +3561,9 @@ dependencies = [ { name = "xxhash" }, { name = "zstandard" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7e/d7/21ffae5ccdc3c9b8de283e8f8bf48a92039681df0d39f15133d8ff8965bd/langsmith-0.7.25.tar.gz", hash = "sha256:d17da71f156ca69eafd28ac9627c8e0e93170260ec37cd27cedc83205a067598", size = 1145410, upload-time = "2026-04-03T13:11:42.36Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/b3/b9b2218483400c9c0f84ea781ec4fc92a9afb51c3f16d2b6369356990d47/langsmith-0.7.29.tar.gz", hash = "sha256:bcec464be00b35cdf0ed0087ef9b1f40889fe1017066f11136a02aa0276cedf5", size = 1094512, upload-time = "2026-04-09T03:17:12.961Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/13/67889d41baf7dbaf13ffd0b334a0f284e107fad1cc8782a1abb1e56e5eeb/langsmith-0.7.25-py3-none-any.whl", hash = "sha256:55ecc24c547f6c79b5a684ff8685c669eec34e52fcac5d2c0af7d613aef5a632", size = 359417, upload-time = "2026-04-03T13:11:40.729Z" }, + { url = "https://files.pythonhosted.org/packages/aa/11/8189be47b5d5a64ecd7e19c81ad3fd9cd9f0bf6778abc5ff177db90ebb3d/langsmith-0.7.29-py3-none-any.whl", hash = "sha256:ec61cdca1f2e2add48742f97a4ee1d6894c968ef3d5a50122289dac56170978c", size = 367655, upload-time = "2026-04-09T03:17:10.944Z" }, ] [[package]] @@ -4867,7 +4952,7 @@ wheels = [ [[package]] name = "openai" -version = "2.30.0" +version = "2.31.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -4879,9 +4964,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/88/15/52580c8fbc16d0675d516e8749806eda679b16de1e4434ea06fb6feaa610/openai-2.30.0.tar.gz", hash = "sha256:92f7661c990bda4b22a941806c83eabe4896c3094465030dd882a71abe80c885", size = 676084, upload-time = "2026-03-25T22:08:59.96Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/fe/64b3d035780b3188f86c4f6f1bc202e7bb74757ef028802112273b9dcacf/openai-2.31.0.tar.gz", hash = "sha256:43ca59a88fc973ad1848d86b98d7fac207e265ebbd1828b5e4bdfc85f79427a5", size = 684772, upload-time = "2026-04-08T21:01:41.797Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/9e/5bfa2270f902d5b92ab7d41ce0475b8630572e71e349b2a4996d14bdda93/openai-2.30.0-py3-none-any.whl", hash = "sha256:9a5ae616888eb2748ec5e0c5b955a51592e0b201a11f4262db920f2a78c5231d", size = 1146656, upload-time = "2026-03-25T22:08:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/66/bc/a8f7c3aa03452fedbb9af8be83e959adba96a6b4a35e416faffcc959c568/openai-2.31.0-py3-none-any.whl", hash = "sha256:44e1344d87e56a493d649b17e2fac519d1368cbb0745f59f1957c4c26de50a0a", size = 1153479, upload-time = "2026-04-08T21:01:39.217Z" }, ] [[package]] @@ -4959,7 +5044,8 @@ version = "1.34.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, - { name = "grpcio" }, + { name = "grpcio", version = "1.78.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and python_full_version < '3.13') or (python_full_version >= '3.11' and platform_machine == 's390x')" }, + { name = "grpcio", version = "1.80.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (python_full_version >= '3.13' and platform_machine != 's390x')" }, { name = "opentelemetry-api" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, @@ -5460,11 +5546,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.9.4" +version = "4.9.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737, upload-time = "2026-03-05T18:34:13.271Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216, upload-time = "2026-03-05T18:34:12.172Z" }, + { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, ] [[package]] @@ -6476,31 +6562,31 @@ wheels = [ [[package]] name = "pypdfium2" -version = "5.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3b/01/be763b9081c7eb823196e7d13d9c145bf75ac43f3c1466de81c21c24b381/pypdfium2-5.6.0.tar.gz", hash = "sha256:bcb9368acfe3547054698abbdae68ba0cbd2d3bda8e8ee437e061deef061976d", size = 270714, upload-time = "2026-03-08T01:05:06.5Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/b1/129ed0177521a93a892f8a6a215dd3260093e30e77ef7035004bb8af7b6c/pypdfium2-5.6.0-py3-none-android_23_arm64_v8a.whl", hash = "sha256:fb7858c9707708555b4a719b5548a6e7f5d26bc82aef55ae4eb085d7a2190b11", size = 3346059, upload-time = "2026-03-08T01:04:21.37Z" }, - { url = "https://files.pythonhosted.org/packages/86/34/cbdece6886012180a7f2c7b2c360c415cf5e1f83f1973d2c9201dae3506a/pypdfium2-5.6.0-py3-none-android_23_armeabi_v7a.whl", hash = "sha256:6a7e1f4597317786f994bfb947eef480e53933f804a990193ab89eef8243f805", size = 2804418, upload-time = "2026-03-08T01:04:23.384Z" }, - { url = "https://files.pythonhosted.org/packages/6e/f6/9f9e190fe0e5a6b86b82f83bd8b5d3490348766062381140ca5cad8e00b1/pypdfium2-5.6.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e468c38997573f0e86f03273c2c1fbdea999de52ba43fee96acaa2f6b2ad35f7", size = 3412541, upload-time = "2026-03-08T01:04:25.45Z" }, - { url = "https://files.pythonhosted.org/packages/ee/8d/e57492cb2228ba56ed57de1ff044c8ac114b46905f8b1445c33299ba0488/pypdfium2-5.6.0-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:ad3abddc5805424f962e383253ccad6a0d1d2ebd86afa9a9e1b9ca659773cd0d", size = 3592320, upload-time = "2026-03-08T01:04:27.509Z" }, - { url = "https://files.pythonhosted.org/packages/f9/8a/8ab82e33e9c551494cbe1526ea250ca8cc4e9e98d6a4fc6b6f8d959aa1d1/pypdfium2-5.6.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6b5eb9eae5c45076395454522ca26add72ba8bd1fe473e1e4721aa58521470c", size = 3596450, upload-time = "2026-03-08T01:04:29.183Z" }, - { url = "https://files.pythonhosted.org/packages/f5/b5/602a792282312ccb158cc63849528079d94b0a11efdc61f2a359edfb41e9/pypdfium2-5.6.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:258624da8ef45cdc426e11b33e9d83f9fb723c1c201c6e0f4ab5a85966c6b876", size = 3325442, upload-time = "2026-03-08T01:04:30.886Z" }, - { url = "https://files.pythonhosted.org/packages/81/1f/9e48ec05ed8d19d736c2d1f23c1bd0f20673f02ef846a2576c69e237f15d/pypdfium2-5.6.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9367451c8a00931d6612db0822525a18c06f649d562cd323a719e46ac19c9bb", size = 3727434, upload-time = "2026-03-08T01:04:33.619Z" }, - { url = "https://files.pythonhosted.org/packages/33/90/0efd020928b4edbd65f4f3c2af0c84e20b43a3ada8fa6d04f999a97afe7a/pypdfium2-5.6.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a757869f891eac1cc1372e38a4aa01adac8abc8fe2a8a4e2ebf50595e3bf5937", size = 4139029, upload-time = "2026-03-08T01:04:36.08Z" }, - { url = "https://files.pythonhosted.org/packages/ff/49/a640b288a48dab1752281dd9b72c0679fccea107874e80a65a606b00efa9/pypdfium2-5.6.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:515be355222cc57ae9e62cd5c7c350b8e0c863efc539f80c7d75e2811ba45cb6", size = 3646387, upload-time = "2026-03-08T01:04:38.151Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3b/a344c19c01021eeb5d830c102e4fc9b1602f19c04aa7d11abbe2d188fd8e/pypdfium2-5.6.0-py3-none-manylinux_2_27_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1c4753c7caf7d004211d7f57a21f10d127f5e0e5510a14d24bc073e7220a3ea", size = 3097212, upload-time = "2026-03-08T01:04:40.776Z" }, - { url = "https://files.pythonhosted.org/packages/50/96/e48e13789ace22aeb9b7510904a1b1493ec588196e11bbacc122da330b3d/pypdfium2-5.6.0-py3-none-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c49729090281fdd85775fb8912c10bd19e99178efaa98f145ab06e7ce68554d2", size = 2965026, upload-time = "2026-03-08T01:04:42.857Z" }, - { url = "https://files.pythonhosted.org/packages/cb/06/3100e44d4935f73af8f5d633d3bd40f0d36d606027085a0ef1f0566a6320/pypdfium2-5.6.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a4a1749a8d4afd62924a8d95cfa4f2e26fc32957ce34ac3b674be6f127ed252e", size = 4131431, upload-time = "2026-03-08T01:04:44.982Z" }, - { url = "https://files.pythonhosted.org/packages/64/ef/d8df63569ce9a66c8496057782eb8af78e0d28667922d62ec958434e3d4b/pypdfium2-5.6.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:36469ebd0fdffb7130ce45ed9c44f8232d91571c89eb851bd1633c64b6f6114f", size = 3747469, upload-time = "2026-03-08T01:04:46.702Z" }, - { url = "https://files.pythonhosted.org/packages/a6/47/fd2c6a67a49fade1acd719fbd11f7c375e7219912923ef2de0ea0ac1544e/pypdfium2-5.6.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9da900df09be3cf546b637a127a7b6428fb22d705951d731269e25fd3adef457", size = 4337578, upload-time = "2026-03-08T01:04:49.007Z" }, - { url = "https://files.pythonhosted.org/packages/6b/f5/836c83e54b01e09478c4d6bf4912651d6053c932250fcee953f5c72d8e4a/pypdfium2-5.6.0-py3-none-musllinux_1_2_ppc64le.whl", hash = "sha256:45fccd5622233c5ec91a885770ae7dd4004d4320ac05a4ad8fa03a66dea40244", size = 4376104, upload-time = "2026-03-08T01:04:51.04Z" }, - { url = "https://files.pythonhosted.org/packages/6e/7f/b940b6a1664daf8f9bad87c6c99b84effa3611615b8708d10392dc33036c/pypdfium2-5.6.0-py3-none-musllinux_1_2_riscv64.whl", hash = "sha256:282dc030e767cd61bd0299f9d581052b91188e2b87561489057a8e7963e7e0cb", size = 3929824, upload-time = "2026-03-08T01:04:53.544Z" }, - { url = "https://files.pythonhosted.org/packages/88/79/00267d92a6a58c229e364d474f5698efe446e0c7f4f152f58d0138715e99/pypdfium2-5.6.0-py3-none-musllinux_1_2_s390x.whl", hash = "sha256:a1c1dfe950382c76a7bba1ba160ec5e40df8dd26b04a1124ae268fda55bc4cbe", size = 4270201, upload-time = "2026-03-08T01:04:55.81Z" }, - { url = "https://files.pythonhosted.org/packages/e1/ab/b127f38aba41746bdf9ace15ba08411d7ef6ecba1326d529ba414eb1ed50/pypdfium2-5.6.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:43b0341ca6feb6c92e4b7a9eb4813e5466f5f5e8b6baeb14df0a94d5f312c00b", size = 4180793, upload-time = "2026-03-08T01:04:57.961Z" }, - { url = "https://files.pythonhosted.org/packages/0e/8c/a01c8e4302448b614d25a85c08298b0d3e9dfbdac5bd1b2f32c9b02e83d9/pypdfium2-5.6.0-py3-none-win32.whl", hash = "sha256:9dfcd4ff49a2b9260d00e38539ab28190d59e785e83030b30ffaf7a29c42155d", size = 3596753, upload-time = "2026-03-08T01:05:00.566Z" }, - { url = "https://files.pythonhosted.org/packages/9b/5f/2d871adf46761bb002a62686545da6348afe838d19af03df65d1ece786a2/pypdfium2-5.6.0-py3-none-win_amd64.whl", hash = "sha256:c6bc8dd63d0568f4b592f3e03de756afafc0e44aa1fe8878cc4aba1b11ae7374", size = 3716526, upload-time = "2026-03-08T01:05:02.433Z" }, - { url = "https://files.pythonhosted.org/packages/3a/80/0d9b162098597fbe3ac2b269b1682c0c3e8db9ba87679603fdd9b19afaa6/pypdfium2-5.6.0-py3-none-win_arm64.whl", hash = "sha256:5538417b199bdcb3207370c88df61f2ba3dac7a3253f82e1aa2708e6376b6f90", size = 3515049, upload-time = "2026-03-08T01:05:04.587Z" }, +version = "5.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/76/19aacfff78d328a700ca34b5b1dff891e587aac2fd6b928b035ed366cc37/pypdfium2-5.7.0.tar.gz", hash = "sha256:9febb09f532555485f064c1f6442f46d31e27be5981359cb06b5826695906a06", size = 265935, upload-time = "2026-04-08T19:58:16.831Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/a5/7e6d9532e7753a1dc439412b38dda5943c692d3ab3f1e01826f9b5527c67/pypdfium2-5.7.0-py3-none-android_23_arm64_v8a.whl", hash = "sha256:9e815e75498a03a3049baf68ff00b90459bead0d9eee65b1860142529faba81d", size = 3343748, upload-time = "2026-04-08T19:57:40.293Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ea/9d4a0b41f86d342dfb6529c31789e70d1123cc6521b29979e02ec2b267b6/pypdfium2-5.7.0-py3-none-android_23_armeabi_v7a.whl", hash = "sha256:405bb3c6d0e7a5a32e98eb45a3343da1ad847d6d6eef77bf6f285652a250e0b7", size = 2805480, upload-time = "2026-04-08T19:57:42.109Z" }, + { url = "https://files.pythonhosted.org/packages/34/dc/ce1c8e94082a84d1669606f90c4f694acbdcabd359d92db7302d16b5938b/pypdfium2-5.7.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:609b34d91871c185f399b1a503513c03a9de83597f55404de00c3d31a8037544", size = 3420156, upload-time = "2026-04-08T19:57:43.672Z" }, + { url = "https://files.pythonhosted.org/packages/51/84/6d859ce82a3723ba7cd70d88ad87eca3cb40553c68db182976fd2b0febe1/pypdfium2-5.7.0-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:6ae6c6bba0cde30c9293c3f525778c229466de7782e8f7d99e7c2a1b8f9c7a6f", size = 3601560, upload-time = "2026-04-08T19:57:45.148Z" }, + { url = "https://files.pythonhosted.org/packages/66/0c/8bc2258d1e7ba971d05241a049cd3100c75df6bcf930423de7d0c6265a30/pypdfium2-5.7.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b518d78211cb2912139d10d7f4e39669231eb155e8258159e3413e9e5e4baef", size = 3588134, upload-time = "2026-04-08T19:57:47.379Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f7/3248cc569a92ff25f1fe0a4a1790807e6e05df60563e39e74c9b723d5620/pypdfium2-5.7.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8aaa8e7681ebcaa042ac8adc152521fd5f16a4ceee1e9b9b582e148519528aa9", size = 3323100, upload-time = "2026-04-08T19:57:49.243Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ee/6f004509df77ce963ed5a0f2e090ea0c43036e49cc72c321ce90f3d328bf/pypdfium2-5.7.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c8d2284f799adbae755b66ce1a579834e487337d89bbb34ee749ecfa68322425", size = 3719217, upload-time = "2026-04-08T19:57:50.708Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f0/bb61601aa1c2990d4a5d194440281941781250f6a438813a13fe20eb95cf/pypdfium2-5.7.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:08e9e9576eefbc085ba9a63feede4bcaf93d9fa0d9b17cb549aba6f065a8750e", size = 4147676, upload-time = "2026-04-08T19:57:52.292Z" }, + { url = "https://files.pythonhosted.org/packages/bd/27/a119e0519049afcfca51e9834b67949ffaba5b9afe7e74ed04d6c39b0285/pypdfium2-5.7.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ace647320bae562903097977b83449f91d30e045dd19ce62939d3100869f180", size = 3635469, upload-time = "2026-04-08T19:57:53.948Z" }, + { url = "https://files.pythonhosted.org/packages/70/0b/4bcb67b039f057aca01ddbe692ae7666b630ad42b91a3aca3cb4d4f01222/pypdfium2-5.7.0-py3-none-manylinux_2_27_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7bb7555fe613cd76fff871a12299f902b80443f90b49e2001338718c758f6f4", size = 3091818, upload-time = "2026-04-08T19:57:55.471Z" }, + { url = "https://files.pythonhosted.org/packages/a6/c9/31490ab7cecaf433195683ff5c750f4111c7347f1fef9131d3d8704618eb/pypdfium2-5.7.0-py3-none-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e7c0ef5ae35d40daa1883f3993b3b7ecf3fb06993bcc46651e28cf058d9da992", size = 2959579, upload-time = "2026-04-08T19:57:57.238Z" }, + { url = "https://files.pythonhosted.org/packages/f9/1e/bf5fe52f007130c0b1b38786ef82c98b4ac06f77e7ca001a17cda6ce76b6/pypdfium2-5.7.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:423c749e8cab22ddaf833041498ec5ad477c1c2abbff0a8ec00b99663c284592", size = 4126033, upload-time = "2026-04-08T19:57:59.111Z" }, + { url = "https://files.pythonhosted.org/packages/18/7d/46dcebf4eb9ccf9b5fafe79702c31863b4c127e9c3140c0f335c375d3818/pypdfium2-5.7.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f48f453f848a90ec7786bcc84a4c0ee42eb84c2d8af3ca9004f7c18648939838", size = 3742063, upload-time = "2026-04-08T19:58:00.643Z" }, + { url = "https://files.pythonhosted.org/packages/4d/29/cfec37942f13a1dfe3ab059cf8d130609143d33ca1dd554b017a30bffe97/pypdfium2-5.7.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e84bfa61f0243ed4b33bfe2492946ba761007b7feb5e7e0a086c635436d47906", size = 4332177, upload-time = "2026-04-08T19:58:02.425Z" }, + { url = "https://files.pythonhosted.org/packages/3f/da/07812153eff746bbc548d50129ada699765036674ff94065d538015c9556/pypdfium2-5.7.0-py3-none-musllinux_1_2_ppc64le.whl", hash = "sha256:e3f4d7f4473b5ef762560cd5971cad3b51a77da3a25af479ef5aae4611709bb8", size = 4370704, upload-time = "2026-04-08T19:58:04.379Z" }, + { url = "https://files.pythonhosted.org/packages/9b/df/07a6a038ccb6fae6a1a06708c98d00aa03f2ca720b02cd3b75248dc5da70/pypdfium2-5.7.0-py3-none-musllinux_1_2_riscv64.whl", hash = "sha256:9e0b6c9be8c92b63ce0a00a94f6635eec22831e253811d6692824a1244e21780", size = 3924428, upload-time = "2026-04-08T19:58:06.406Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a8/70ce4f997fef4186098c032fb3dd2c39193027a92a23b5a94d7a4c85e068/pypdfium2-5.7.0-py3-none-musllinux_1_2_s390x.whl", hash = "sha256:3e4974a8545f726fc97a7443507713007e177f22058cd1ca0b28cb0e8e2d7dc2", size = 4264817, upload-time = "2026-04-08T19:58:08.003Z" }, + { url = "https://files.pythonhosted.org/packages/02/42/03779e61ca40120f87839b4693899c72031b7a9e23676dcd8914d92e460c/pypdfium2-5.7.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2fe12d57a0b413d42bdba435a608b2435a921a5f6a9d78fd8091b6266b63901a", size = 4175393, upload-time = "2026-04-08T19:58:09.858Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f1/19bea36b354f2407c6ffdc60ad8564d95eb515badec457043ff57ad636f0/pypdfium2-5.7.0-py3-none-win32.whl", hash = "sha256:23958aec5c28c52e71f183a647fcc9fcec96ef703cc60a3ade44e55f4701678f", size = 3606308, upload-time = "2026-04-08T19:58:11.672Z" }, + { url = "https://files.pythonhosted.org/packages/70/aa/fb333c1912a019de26e2395afd3dbef09e8118a59d70f1e5886fc90aa565/pypdfium2-5.7.0-py3-none-win_amd64.whl", hash = "sha256:a33d2c190042ae09c5512f599a540f88b07be956f18c4bb49c027e8c5118ce44", size = 3726429, upload-time = "2026-04-08T19:58:13.374Z" }, + { url = "https://files.pythonhosted.org/packages/86/cf/6d4bc1ae4466a1f223abfe27210dce218da307e921961cd687f6e5a795a0/pypdfium2-5.7.0-py3-none-win_arm64.whl", hash = "sha256:8233fd06b0b8c22a5ea0bccbd7c4f73d6e9d0388040ea51909a5b2b1f63157e8", size = 3519317, upload-time = "2026-04-08T19:58:15.261Z" }, ] [[package]] @@ -6672,15 +6758,15 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.2.1" +version = "1.2.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/88/815e53084c5079a59df912825a279f41dd2e0df82281770eadc732f5352c/python_discovery-1.2.1.tar.gz", hash = "sha256:180c4d114bff1c32462537eac5d6a332b768242b76b69c0259c7d14b1b680c9e", size = 58457, upload-time = "2026-03-26T22:30:44.496Z" } +sdist = { url = "https://files.pythonhosted.org/packages/de/ef/3bae0e537cfe91e8431efcba4434463d2c5a65f5a89edd47c6cf2f03c55f/python_discovery-1.2.2.tar.gz", hash = "sha256:876e9c57139eb757cb5878cbdd9ae5379e5d96266c99ef731119e04fffe533bb", size = 58872, upload-time = "2026-04-07T17:28:49.249Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl", hash = "sha256:b6a957b24c1cd79252484d3566d1b49527581d46e789aaf43181005e56201502", size = 31674, upload-time = "2026-03-26T22:30:43.396Z" }, + { url = "https://files.pythonhosted.org/packages/d8/db/795879cc3ddfe338599bddea6388cc5100b088db0a4caf6e6c1af1c27e04/python_discovery-1.2.2-py3-none-any.whl", hash = "sha256:e1ae95d9af875e78f15e19aed0c6137ab1bb49c200f21f5061786490c9585c7a", size = 31894, upload-time = "2026-04-07T17:28:48.09Z" }, ] [[package]] @@ -6725,11 +6811,11 @@ wheels = [ [[package]] name = "python-multipart" -version = "0.0.22" +version = "0.0.24" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/01/979e98d542a70714b0cb2b6728ed0b7c46792b695e3eaec3e20711271ca3/python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58", size = 37612, upload-time = "2026-01-25T10:15:56.219Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/45/e23b5dc14ddb9918ae4a625379506b17b6f8fc56ca1d82db62462f59aea6/python_multipart-0.0.24.tar.gz", hash = "sha256:9574c97e1c026e00bc30340ef7c7d76739512ab4dfd428fec8c330fa6a5cc3c8", size = 37695, upload-time = "2026-04-05T20:49:13.829Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579, upload-time = "2026-01-25T10:15:54.811Z" }, + { url = "https://files.pythonhosted.org/packages/a3/73/89930efabd4da63cea44a3f438aeb753d600123570e6d6264e763617a9ce/python_multipart-0.0.24-py3-none-any.whl", hash = "sha256:9b110a98db707df01a53c194f0af075e736a770dc5058089650d70b4a182f950", size = 24420, upload-time = "2026-04-05T20:49:12.555Z" }, ] [[package]] @@ -6849,7 +6935,8 @@ name = "qdrant-client" version = "1.14.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "grpcio" }, + { name = "grpcio", version = "1.78.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and python_full_version < '3.13') or (python_full_version >= '3.11' and platform_machine == 's390x')" }, + { name = "grpcio", version = "1.80.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (python_full_version >= '3.13' and platform_machine != 's390x')" }, { name = "httpx", extra = ["http2"] }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, @@ -6903,75 +6990,75 @@ wheels = [ [[package]] name = "rapidfuzz" -version = "3.14.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d3/28/9d808fe62375b9aab5ba92fa9b29371297b067c2790b2d7cda648b1e2f8d/rapidfuzz-3.14.3.tar.gz", hash = "sha256:2491937177868bc4b1e469087601d53f925e8d270ccc21e07404b4b5814b7b5f", size = 57863900, upload-time = "2025-11-01T11:54:52.321Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/d1/0efa42a602ed466d3ca1c462eed5d62015c3fd2a402199e2c4b87aa5aa25/rapidfuzz-3.14.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9fcd4d751a4fffa17aed1dde41647923c72c74af02459ad1222e3b0022da3a1", size = 1952376, upload-time = "2025-11-01T11:52:29.175Z" }, - { url = "https://files.pythonhosted.org/packages/be/00/37a169bb28b23850a164e6624b1eb299e1ad73c9e7c218ee15744e68d628/rapidfuzz-3.14.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ad73afb688b36864a8d9b7344a9cf6da186c471e5790cbf541a635ee0f457f2", size = 1390903, upload-time = "2025-11-01T11:52:31.239Z" }, - { url = "https://files.pythonhosted.org/packages/3c/91/b37207cbbdb6eaafac3da3f55ea85287b27745cb416e75e15769b7d8abe8/rapidfuzz-3.14.3-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5fb2d978a601820d2cfd111e2c221a9a7bfdf84b41a3ccbb96ceef29f2f1ac7", size = 1385655, upload-time = "2025-11-01T11:52:32.852Z" }, - { url = "https://files.pythonhosted.org/packages/f2/bb/ca53e518acf43430be61f23b9c5987bd1e01e74fcb7a9ee63e00f597aefb/rapidfuzz-3.14.3-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d83b8b712fa37e06d59f29a4b49e2e9e8635e908fbc21552fe4d1163db9d2a1", size = 3164708, upload-time = "2025-11-01T11:52:34.618Z" }, - { url = "https://files.pythonhosted.org/packages/df/e1/7667bf2db3e52adb13cb933dd4a6a2efc66045d26fa150fc0feb64c26d61/rapidfuzz-3.14.3-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:dc8c07801df5206b81ed6bd6c35cb520cf9b6c64b9b0d19d699f8633dc942897", size = 1221106, upload-time = "2025-11-01T11:52:36.069Z" }, - { url = "https://files.pythonhosted.org/packages/05/8a/84d9f2d46a2c8eb2ccae81747c4901fa10fe4010aade2d57ce7b4b8e02ec/rapidfuzz-3.14.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c71ce6d4231e5ef2e33caa952bfe671cb9fd42e2afb11952df9fad41d5c821f9", size = 2406048, upload-time = "2025-11-01T11:52:37.936Z" }, - { url = "https://files.pythonhosted.org/packages/3c/a9/a0b7b7a1b81a020c034eb67c8e23b7e49f920004e295378de3046b0d99e1/rapidfuzz-3.14.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:0e38828d1381a0cceb8a4831212b2f673d46f5129a1897b0451c883eaf4a1747", size = 2527020, upload-time = "2025-11-01T11:52:39.657Z" }, - { url = "https://files.pythonhosted.org/packages/b4/bc/416df7d108b99b4942ba04dd4cf73c45c3aadb3ef003d95cad78b1d12eb9/rapidfuzz-3.14.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da2a007434323904719158e50f3076a4dadb176ce43df28ed14610c773cc9825", size = 4273958, upload-time = "2025-11-01T11:52:41.017Z" }, - { url = "https://files.pythonhosted.org/packages/81/d0/b81e041c17cd475002114e0ab8800e4305e60837882cb376a621e520d70f/rapidfuzz-3.14.3-cp310-cp310-win32.whl", hash = "sha256:fce3152f94afcfd12f3dd8cf51e48fa606e3cb56719bccebe3b401f43d0714f9", size = 1725043, upload-time = "2025-11-01T11:52:42.465Z" }, - { url = "https://files.pythonhosted.org/packages/09/6b/64ad573337d81d64bc78a6a1df53a72a71d54d43d276ce0662c2e95a1f35/rapidfuzz-3.14.3-cp310-cp310-win_amd64.whl", hash = "sha256:37d3c653af15cd88592633e942f5407cb4c64184efab163c40fcebad05f25141", size = 1542273, upload-time = "2025-11-01T11:52:44.005Z" }, - { url = "https://files.pythonhosted.org/packages/f4/5e/faf76e259bc15808bc0b86028f510215c3d755b6c3a3911113079485e561/rapidfuzz-3.14.3-cp310-cp310-win_arm64.whl", hash = "sha256:cc594bbcd3c62f647dfac66800f307beaee56b22aaba1c005e9c4c40ed733923", size = 814875, upload-time = "2025-11-01T11:52:45.405Z" }, - { url = "https://files.pythonhosted.org/packages/76/25/5b0a33ad3332ee1213068c66f7c14e9e221be90bab434f0cb4defa9d6660/rapidfuzz-3.14.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dea2d113e260a5da0c4003e0a5e9fdf24a9dc2bb9eaa43abd030a1e46ce7837d", size = 1953885, upload-time = "2025-11-01T11:52:47.75Z" }, - { url = "https://files.pythonhosted.org/packages/2d/ab/f1181f500c32c8fcf7c966f5920c7e56b9b1d03193386d19c956505c312d/rapidfuzz-3.14.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e6c31a4aa68cfa75d7eede8b0ed24b9e458447db604c2db53f358be9843d81d3", size = 1390200, upload-time = "2025-11-01T11:52:49.491Z" }, - { url = "https://files.pythonhosted.org/packages/14/2a/0f2de974ececad873865c6bb3ea3ad07c976ac293d5025b2d73325aac1d4/rapidfuzz-3.14.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02821366d928e68ddcb567fed8723dad7ea3a979fada6283e6914d5858674850", size = 1389319, upload-time = "2025-11-01T11:52:51.224Z" }, - { url = "https://files.pythonhosted.org/packages/ed/69/309d8f3a0bb3031fd9b667174cc4af56000645298af7c2931be5c3d14bb4/rapidfuzz-3.14.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cfe8df315ab4e6db4e1be72c5170f8e66021acde22cd2f9d04d2058a9fd8162e", size = 3178495, upload-time = "2025-11-01T11:52:53.005Z" }, - { url = "https://files.pythonhosted.org/packages/10/b7/f9c44a99269ea5bf6fd6a40b84e858414b6e241288b9f2b74af470d222b1/rapidfuzz-3.14.3-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:769f31c60cd79420188fcdb3c823227fc4a6deb35cafec9d14045c7f6743acae", size = 1228443, upload-time = "2025-11-01T11:52:54.991Z" }, - { url = "https://files.pythonhosted.org/packages/f2/0a/3b3137abac7f19c9220e14cd7ce993e35071a7655e7ef697785a3edfea1a/rapidfuzz-3.14.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:54fa03062124e73086dae66a3451c553c1e20a39c077fd704dc7154092c34c63", size = 2411998, upload-time = "2025-11-01T11:52:56.629Z" }, - { url = "https://files.pythonhosted.org/packages/f3/b6/983805a844d44670eaae63831024cdc97ada4e9c62abc6b20703e81e7f9b/rapidfuzz-3.14.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:834d1e818005ed0d4ae38f6b87b86fad9b0a74085467ece0727d20e15077c094", size = 2530120, upload-time = "2025-11-01T11:52:58.298Z" }, - { url = "https://files.pythonhosted.org/packages/b4/cc/2c97beb2b1be2d7595d805682472f1b1b844111027d5ad89b65e16bdbaaa/rapidfuzz-3.14.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:948b00e8476a91f510dd1ec07272efc7d78c275d83b630455559671d4e33b678", size = 4283129, upload-time = "2025-11-01T11:53:00.188Z" }, - { url = "https://files.pythonhosted.org/packages/4d/03/2f0e5e94941045aefe7eafab72320e61285c07b752df9884ce88d6b8b835/rapidfuzz-3.14.3-cp311-cp311-win32.whl", hash = "sha256:43d0305c36f504232f18ea04e55f2059bb89f169d3119c4ea96a0e15b59e2a91", size = 1724224, upload-time = "2025-11-01T11:53:02.149Z" }, - { url = "https://files.pythonhosted.org/packages/cf/99/5fa23e204435803875daefda73fd61baeabc3c36b8fc0e34c1705aab8c7b/rapidfuzz-3.14.3-cp311-cp311-win_amd64.whl", hash = "sha256:ef6bf930b947bd0735c550683939a032090f1d688dfd8861d6b45307b96fd5c5", size = 1544259, upload-time = "2025-11-01T11:53:03.66Z" }, - { url = "https://files.pythonhosted.org/packages/48/35/d657b85fcc615a42661b98ac90ce8e95bd32af474603a105643963749886/rapidfuzz-3.14.3-cp311-cp311-win_arm64.whl", hash = "sha256:f3eb0ff3b75d6fdccd40b55e7414bb859a1cda77c52762c9c82b85569f5088e7", size = 814734, upload-time = "2025-11-01T11:53:05.008Z" }, - { url = "https://files.pythonhosted.org/packages/fa/8e/3c215e860b458cfbedb3ed73bc72e98eb7e0ed72f6b48099604a7a3260c2/rapidfuzz-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:685c93ea961d135893b5984a5a9851637d23767feabe414ec974f43babbd8226", size = 1945306, upload-time = "2025-11-01T11:53:06.452Z" }, - { url = "https://files.pythonhosted.org/packages/36/d9/31b33512015c899f4a6e6af64df8dfe8acddf4c8b40a4b3e0e6e1bcd00e5/rapidfuzz-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fa7c8f26f009f8c673fbfb443792f0cf8cf50c4e18121ff1e285b5e08a94fbdb", size = 1390788, upload-time = "2025-11-01T11:53:08.721Z" }, - { url = "https://files.pythonhosted.org/packages/a9/67/2ee6f8de6e2081ccd560a571d9c9063184fe467f484a17fa90311a7f4a2e/rapidfuzz-3.14.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57f878330c8d361b2ce76cebb8e3e1dc827293b6abf404e67d53260d27b5d941", size = 1374580, upload-time = "2025-11-01T11:53:10.164Z" }, - { url = "https://files.pythonhosted.org/packages/30/83/80d22997acd928eda7deadc19ccd15883904622396d6571e935993e0453a/rapidfuzz-3.14.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c5f545f454871e6af05753a0172849c82feaf0f521c5ca62ba09e1b382d6382", size = 3154947, upload-time = "2025-11-01T11:53:12.093Z" }, - { url = "https://files.pythonhosted.org/packages/5b/cf/9f49831085a16384695f9fb096b99662f589e30b89b4a589a1ebc1a19d34/rapidfuzz-3.14.3-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:07aa0b5d8863e3151e05026a28e0d924accf0a7a3b605da978f0359bb804df43", size = 1223872, upload-time = "2025-11-01T11:53:13.664Z" }, - { url = "https://files.pythonhosted.org/packages/c8/0f/41ee8034e744b871c2e071ef0d360686f5ccfe5659f4fd96c3ec406b3c8b/rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73b07566bc7e010e7b5bd490fb04bb312e820970180df6b5655e9e6224c137db", size = 2392512, upload-time = "2025-11-01T11:53:15.109Z" }, - { url = "https://files.pythonhosted.org/packages/da/86/280038b6b0c2ccec54fb957c732ad6b41cc1fd03b288d76545b9cf98343f/rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6de00eb84c71476af7d3110cf25d8fe7c792d7f5fa86764ef0b4ca97e78ca3ed", size = 2521398, upload-time = "2025-11-01T11:53:17.146Z" }, - { url = "https://files.pythonhosted.org/packages/fa/7b/05c26f939607dca0006505e3216248ae2de631e39ef94dd63dbbf0860021/rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d7843a1abf0091773a530636fdd2a49a41bcae22f9910b86b4f903e76ddc82dc", size = 4259416, upload-time = "2025-11-01T11:53:19.34Z" }, - { url = "https://files.pythonhosted.org/packages/40/eb/9e3af4103d91788f81111af1b54a28de347cdbed8eaa6c91d5e98a889aab/rapidfuzz-3.14.3-cp312-cp312-win32.whl", hash = "sha256:dea97ac3ca18cd3ba8f3d04b5c1fe4aa60e58e8d9b7793d3bd595fdb04128d7a", size = 1709527, upload-time = "2025-11-01T11:53:20.949Z" }, - { url = "https://files.pythonhosted.org/packages/b8/63/d06ecce90e2cf1747e29aeab9f823d21e5877a4c51b79720b2d3be7848f8/rapidfuzz-3.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:b5100fd6bcee4d27f28f4e0a1c6b5127bc8ba7c2a9959cad9eab0bf4a7ab3329", size = 1538989, upload-time = "2025-11-01T11:53:22.428Z" }, - { url = "https://files.pythonhosted.org/packages/fc/6d/beee32dcda64af8128aab3ace2ccb33d797ed58c434c6419eea015fec779/rapidfuzz-3.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:4e49c9e992bc5fc873bd0fff7ef16a4405130ec42f2ce3d2b735ba5d3d4eb70f", size = 811161, upload-time = "2025-11-01T11:53:23.811Z" }, - { url = "https://files.pythonhosted.org/packages/e4/4f/0d94d09646853bd26978cb3a7541b6233c5760687777fa97da8de0d9a6ac/rapidfuzz-3.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dbcb726064b12f356bf10fffdb6db4b6dce5390b23627c08652b3f6e49aa56ae", size = 1939646, upload-time = "2025-11-01T11:53:25.292Z" }, - { url = "https://files.pythonhosted.org/packages/b6/eb/f96aefc00f3bbdbab9c0657363ea8437a207d7545ac1c3789673e05d80bd/rapidfuzz-3.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1704fc70d214294e554a2421b473779bcdeef715881c5e927dc0f11e1692a0ff", size = 1385512, upload-time = "2025-11-01T11:53:27.594Z" }, - { url = "https://files.pythonhosted.org/packages/26/34/71c4f7749c12ee223dba90017a5947e8f03731a7cc9f489b662a8e9e643d/rapidfuzz-3.14.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc65e72790ddfd310c2c8912b45106e3800fefe160b0c2ef4d6b6fec4e826457", size = 1373571, upload-time = "2025-11-01T11:53:29.096Z" }, - { url = "https://files.pythonhosted.org/packages/32/00/ec8597a64f2be301ce1ee3290d067f49f6a7afb226b67d5f15b56d772ba5/rapidfuzz-3.14.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e38c1305cffae8472572a0584d4ffc2f130865586a81038ca3965301f7c97c", size = 3156759, upload-time = "2025-11-01T11:53:30.777Z" }, - { url = "https://files.pythonhosted.org/packages/61/d5/b41eeb4930501cc899d5a9a7b5c9a33d85a670200d7e81658626dcc0ecc0/rapidfuzz-3.14.3-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:e195a77d06c03c98b3fc06b8a28576ba824392ce40de8c708f96ce04849a052e", size = 1222067, upload-time = "2025-11-01T11:53:32.334Z" }, - { url = "https://files.pythonhosted.org/packages/2a/7d/6d9abb4ffd1027c6ed837b425834f3bed8344472eb3a503ab55b3407c721/rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b7ef2f4b8583a744338a18f12c69693c194fb6777c0e9ada98cd4d9e8f09d10", size = 2394775, upload-time = "2025-11-01T11:53:34.24Z" }, - { url = "https://files.pythonhosted.org/packages/15/ce/4f3ab4c401c5a55364da1ffff8cc879fc97b4e5f4fa96033827da491a973/rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a2135b138bcdcb4c3742d417f215ac2d8c2b87bde15b0feede231ae95f09ec41", size = 2526123, upload-time = "2025-11-01T11:53:35.779Z" }, - { url = "https://files.pythonhosted.org/packages/c1/4b/54f804975376a328f57293bd817c12c9036171d15cf7292032e3f5820b2d/rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33a325ed0e8e1aa20c3e75f8ab057a7b248fdea7843c2a19ade0008906c14af0", size = 4262874, upload-time = "2025-11-01T11:53:37.866Z" }, - { url = "https://files.pythonhosted.org/packages/e9/b6/958db27d8a29a50ee6edd45d33debd3ce732e7209183a72f57544cd5fe22/rapidfuzz-3.14.3-cp313-cp313-win32.whl", hash = "sha256:8383b6d0d92f6cd008f3c9216535be215a064b2cc890398a678b56e6d280cb63", size = 1707972, upload-time = "2025-11-01T11:53:39.442Z" }, - { url = "https://files.pythonhosted.org/packages/07/75/fde1f334b0cec15b5946d9f84d73250fbfcc73c236b4bc1b25129d90876b/rapidfuzz-3.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:e6b5e3036976f0fde888687d91be86d81f9ac5f7b02e218913c38285b756be6c", size = 1537011, upload-time = "2025-11-01T11:53:40.92Z" }, - { url = "https://files.pythonhosted.org/packages/2e/d7/d83fe001ce599dc7ead57ba1debf923dc961b6bdce522b741e6b8c82f55c/rapidfuzz-3.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:7ba009977601d8b0828bfac9a110b195b3e4e79b350dcfa48c11269a9f1918a0", size = 810744, upload-time = "2025-11-01T11:53:42.723Z" }, - { url = "https://files.pythonhosted.org/packages/92/13/a486369e63ff3c1a58444d16b15c5feb943edd0e6c28a1d7d67cb8946b8f/rapidfuzz-3.14.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0a28add871425c2fe94358c6300bbeb0bc2ed828ca003420ac6825408f5a424", size = 1967702, upload-time = "2025-11-01T11:53:44.554Z" }, - { url = "https://files.pythonhosted.org/packages/f1/82/efad25e260b7810f01d6b69122685e355bed78c94a12784bac4e0beb2afb/rapidfuzz-3.14.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:010e12e2411a4854b0434f920e72b717c43f8ec48d57e7affe5c42ecfa05dd0e", size = 1410702, upload-time = "2025-11-01T11:53:46.066Z" }, - { url = "https://files.pythonhosted.org/packages/ba/1a/34c977b860cde91082eae4a97ae503f43e0d84d4af301d857679b66f9869/rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cfc3d57abd83c734d1714ec39c88a34dd69c85474918ebc21296f1e61eb5ca8", size = 1382337, upload-time = "2025-11-01T11:53:47.62Z" }, - { url = "https://files.pythonhosted.org/packages/88/74/f50ea0e24a5880a9159e8fd256b84d8f4634c2f6b4f98028bdd31891d907/rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89acb8cbb52904f763e5ac238083b9fc193bed8d1f03c80568b20e4cef43a519", size = 3165563, upload-time = "2025-11-01T11:53:49.216Z" }, - { url = "https://files.pythonhosted.org/packages/e8/7a/e744359404d7737049c26099423fc54bcbf303de5d870d07d2fb1410f567/rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_31_armv7l.whl", hash = "sha256:7d9af908c2f371bfb9c985bd134e295038e3031e666e4b2ade1e7cb7f5af2f1a", size = 1214727, upload-time = "2025-11-01T11:53:50.883Z" }, - { url = "https://files.pythonhosted.org/packages/d3/2e/87adfe14ce75768ec6c2b8acd0e05e85e84be4be5e3d283cdae360afc4fe/rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1f1925619627f8798f8c3a391d81071336942e5fe8467bc3c567f982e7ce2897", size = 2403349, upload-time = "2025-11-01T11:53:52.322Z" }, - { url = "https://files.pythonhosted.org/packages/70/17/6c0b2b2bff9c8b12e12624c07aa22e922b0c72a490f180fa9183d1ef2c75/rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:152555187360978119e98ce3e8263d70dd0c40c7541193fc302e9b7125cf8f58", size = 2507596, upload-time = "2025-11-01T11:53:53.835Z" }, - { url = "https://files.pythonhosted.org/packages/c3/d1/87852a7cbe4da7b962174c749a47433881a63a817d04f3e385ea9babcd9e/rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52619d25a09546b8db078981ca88939d72caa6b8701edd8b22e16482a38e799f", size = 4273595, upload-time = "2025-11-01T11:53:55.961Z" }, - { url = "https://files.pythonhosted.org/packages/c1/ab/1d0354b7d1771a28fa7fe089bc23acec2bdd3756efa2419f463e3ed80e16/rapidfuzz-3.14.3-cp313-cp313t-win32.whl", hash = "sha256:489ce98a895c98cad284f0a47960c3e264c724cb4cfd47a1430fa091c0c25204", size = 1757773, upload-time = "2025-11-01T11:53:57.628Z" }, - { url = "https://files.pythonhosted.org/packages/0b/0c/71ef356adc29e2bdf74cd284317b34a16b80258fa0e7e242dd92cc1e6d10/rapidfuzz-3.14.3-cp313-cp313t-win_amd64.whl", hash = "sha256:656e52b054d5b5c2524169240e50cfa080b04b1c613c5f90a2465e84888d6f15", size = 1576797, upload-time = "2025-11-01T11:53:59.455Z" }, - { url = "https://files.pythonhosted.org/packages/fe/d2/0e64fc27bb08d4304aa3d11154eb5480bcf5d62d60140a7ee984dc07468a/rapidfuzz-3.14.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c7e40c0a0af02ad6e57e89f62bef8604f55a04ecae90b0ceeda591bbf5923317", size = 829940, upload-time = "2025-11-01T11:54:01.1Z" }, - { url = "https://files.pythonhosted.org/packages/c9/33/b5bd6475c7c27164b5becc9b0e3eb978f1e3640fea590dd3dced6006ee83/rapidfuzz-3.14.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7cf174b52cb3ef5d49e45d0a1133b7e7d0ecf770ed01f97ae9962c5c91d97d23", size = 1888499, upload-time = "2025-11-01T11:54:42.094Z" }, - { url = "https://files.pythonhosted.org/packages/30/d2/89d65d4db4bb931beade9121bc71ad916b5fa9396e807d11b33731494e8e/rapidfuzz-3.14.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:442cba39957a008dfc5bdef21a9c3f4379e30ffb4e41b8555dbaf4887eca9300", size = 1336747, upload-time = "2025-11-01T11:54:43.957Z" }, - { url = "https://files.pythonhosted.org/packages/85/33/cd87d92b23f0b06e8914a61cea6850c6d495ca027f669fab7a379041827a/rapidfuzz-3.14.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1faa0f8f76ba75fd7b142c984947c280ef6558b5067af2ae9b8729b0a0f99ede", size = 1352187, upload-time = "2025-11-01T11:54:45.518Z" }, - { url = "https://files.pythonhosted.org/packages/22/20/9d30b4a1ab26aac22fff17d21dec7e9089ccddfe25151d0a8bb57001dc3d/rapidfuzz-3.14.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e6eefec45625c634926a9fd46c9e4f31118ac8f3156fff9494422cee45207e6", size = 3101472, upload-time = "2025-11-01T11:54:47.255Z" }, - { url = "https://files.pythonhosted.org/packages/b1/ad/fa2d3e5c29a04ead7eaa731c7cd1f30f9ec3c77b3a578fdf90280797cbcb/rapidfuzz-3.14.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56fefb4382bb12250f164250240b9dd7772e41c5c8ae976fd598a32292449cc5", size = 1511361, upload-time = "2025-11-01T11:54:49.057Z" }, +version = "3.14.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/21/ef6157213316e85790041254259907eb722e00b03480256c0545d98acd33/rapidfuzz-3.14.5.tar.gz", hash = "sha256:ba10ac57884ce82112f7ed910b67e7fb6072d8ef2c06e30dc63c0f604a112e0e", size = 57901753, upload-time = "2026-04-07T11:16:31.931Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/b1/d6d6e7737fe3d0eb2ac2ac337686420d538f83f28495acc3cc32201c0dbf/rapidfuzz-3.14.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:071d96b957a33b9296b9284b6350a0fb6d030b154a04efd7c15e56b98b79a517", size = 1953508, upload-time = "2026-04-07T11:13:37.733Z" }, + { url = "https://files.pythonhosted.org/packages/2b/7b/94c1c953ac818bdd88b43213a9d38e4a41e953b786af3c3b2444d4a8f96d/rapidfuzz-3.14.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:667f40fe9c81ad129b198d236881b00dd9e8314d9cc72d03c3e16bdfe5879051", size = 1160895, upload-time = "2026-04-07T11:13:39.278Z" }, + { url = "https://files.pythonhosted.org/packages/7f/60/a67a7ca7c2532c6c1a4b5cd797917780eed43798b82c98b6df734a086c95/rapidfuzz-3.14.5-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9fff308486bbd2c8c24f25e8e152c7594d3fe8db265a2d6a1ce24d58671127f", size = 1382245, upload-time = "2026-04-07T11:13:41.054Z" }, + { url = "https://files.pythonhosted.org/packages/95/ff/a42c9ce9f9e90ceb5b51136e0b8e8e6e5113ba0b45d986effbd671e7dddf/rapidfuzz-3.14.5-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dfa552338f51aec280f17b02d28bace1e162d1a84ccd80e3339a57f98aedb56b", size = 3163974, upload-time = "2026-04-07T11:13:42.662Z" }, + { url = "https://files.pythonhosted.org/packages/e3/3c/11e2d41075e6e48b7dad373631b379b7e40491f71d5412c5a98d3c58f60f/rapidfuzz-3.14.5-cp310-cp310-manylinux_2_39_riscv64.whl", hash = "sha256:068b3e965ca9d9ee4debe40001ae7c3938ba646308afd33cf0c66618147db65c", size = 1475540, upload-time = "2026-04-07T11:13:44.687Z" }, + { url = "https://files.pythonhosted.org/packages/29/fa/09be143dcc22c79f09cf90168a574725dbda49f02cbbd55d0447da8bec86/rapidfuzz-3.14.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:88b7d31ff1cc5e9bc0e4406e6b1fa00b6d37163d50bb58091e9b976ff1129faa", size = 2404128, upload-time = "2026-04-07T11:13:46.641Z" }, + { url = "https://files.pythonhosted.org/packages/32/f9/1aeb504cdcfde42881825e9c86f48238d4e01ba8a1530491e82eb17e5689/rapidfuzz-3.14.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:eacb434410b8d9ca99a8d42352ef085cf423e3c76c1f0b86be2fcba3bff2952c", size = 2508455, upload-time = "2026-04-07T11:13:48.726Z" }, + { url = "https://files.pythonhosted.org/packages/10/8e/b1b5eed8d887a29b0e18fd3222c46ca60fddfb528e7e1c41267ce42d5522/rapidfuzz-3.14.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:649712823f3abcdc48427147a5384fac15623ba435d0013959b52e6462521397", size = 4274060, upload-time = "2026-04-07T11:13:50.805Z" }, + { url = "https://files.pythonhosted.org/packages/e3/c4/7e5b0353693d4f47b8b0f96e941efc377cfb2034b67ef92d082ac4441a0f/rapidfuzz-3.14.5-cp310-cp310-win32.whl", hash = "sha256:13cb79c23ef5516e4c4e3830877be8b19aa75203636be1163d690d37803f6504", size = 1727457, upload-time = "2026-04-07T11:13:52.45Z" }, + { url = "https://files.pythonhosted.org/packages/d9/6e/f530a39b946fa71c009bc9c81fdb6b48a77bbc57ee8572ac0302b3bf6308/rapidfuzz-3.14.5-cp310-cp310-win_amd64.whl", hash = "sha256:f2073495a7f9b75e57e600747ac09510d67683fd64d3228e009740b7ef88f9fe", size = 1544657, upload-time = "2026-04-07T11:13:54.952Z" }, + { url = "https://files.pythonhosted.org/packages/bc/01/02fa075f9f59ff766d374fecbd042b3ac9782dcd5abc52d909a54f587eeb/rapidfuzz-3.14.5-cp310-cp310-win_arm64.whl", hash = "sha256:8166efddea49fdbc61185559f47593239e4794fd7c9044dd5a789d1a90af852d", size = 816587, upload-time = "2026-04-07T11:13:56.418Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f9/3c41a7be8855803f4f6c713b472226a98d31d41869d98f64f4ca790510d6/rapidfuzz-3.14.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e251126d48615e1f02b4a178f2cd0cd4f0332b8a019c01a2e10480f7552554b4", size = 1952372, upload-time = "2026-04-07T11:13:58.32Z" }, + { url = "https://files.pythonhosted.org/packages/9e/89/c2557e37531d03465193bff0ab9de70b468420a807d71a26a65100635459/rapidfuzz-3.14.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ab449c9abd0d4e1f8145dce0798a4c822a1a1933d613c764a641bea88b8bdab", size = 1159782, upload-time = "2026-04-07T11:14:00.127Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b2/ffeeb7eca1a897d51b998f4c0ef0281696c3b06abcca4f88f9def708ffe1/rapidfuzz-3.14.5-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb2829fedd672dd7107267189dabe2bbe07972801d636014417c6861eb89e358", size = 1383677, upload-time = "2026-04-07T11:14:01.696Z" }, + { url = "https://files.pythonhosted.org/packages/6b/d0/4539e42a2d596e068f7738f279638a4a74edd1fbb6f8594e2458058979c6/rapidfuzz-3.14.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3d50e5861872935fece391351cbb5ba21d1bced277cf5e1143d207a0a35f1925", size = 3168906, upload-time = "2026-04-07T11:14:03.29Z" }, + { url = "https://files.pythonhosted.org/packages/5e/1c/3ec897eb9d8b05308aa8ef6ae4ed64b088ad521a3f9d8ff469e7e97bc2b0/rapidfuzz-3.14.5-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:7092a216728f80c960bd6b3807275d1ee318b168986bd5dc523349581d4890b8", size = 1478176, upload-time = "2026-04-07T11:14:04.94Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ba/970c03a12ce20a5399e22afe9f8932fd4cd1265b8a8461d0e63b00eb4eae/rapidfuzz-3.14.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9669753caef7fdc6529f6adcc5883ed98d65976445d9322e7dbdb6b697feee13", size = 2402441, upload-time = "2026-04-07T11:14:07.228Z" }, + { url = "https://files.pythonhosted.org/packages/81/93/61d351cae60c1d0e21ba5ff1a1015ad045539ed215da9d6e302204ed887a/rapidfuzz-3.14.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:823b1b9d9230809d8edcc18872770764bfe8ef4357995e16744047c8ccf0e489", size = 2511628, upload-time = "2026-04-07T11:14:09.234Z" }, + { url = "https://files.pythonhosted.org/packages/87/52/374d2d4f60fd98155142a869323aa221e30868cfa1f15171a0f64070c247/rapidfuzz-3.14.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f0b2af76b7e7060c09e1a0dfa9410eb19369cbe6164509bff2ef94094b54d2b6", size = 4275480, upload-time = "2026-04-07T11:14:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/d8/04/82e7989bc9ec20a15b720a335c5cb6b0724bf6582013898f90a3280cfccd/rapidfuzz-3.14.5-cp311-cp311-win32.whl", hash = "sha256:c5801a89604c65ab4cc9e91b23bc4076d0ca80efd8c976fb63843d7879a85d7f", size = 1725627, upload-time = "2026-04-07T11:14:13.217Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b5/eca8ac5609bc9bcb02bb6ff87fa5983cc92b8772d66a431556ab8a8c178f/rapidfuzz-3.14.5-cp311-cp311-win_amd64.whl", hash = "sha256:d7ca16637c0ede8243f84074044bd0b2335a0341421f8227c85756de2d18c819", size = 1545977, upload-time = "2026-04-07T11:14:14.766Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e1/dbf318de28f65fa2cdd0a9dfbdee380f8199eb83b19259bc4f8592551b4e/rapidfuzz-3.14.5-cp311-cp311-win_arm64.whl", hash = "sha256:8c90cdf8516d9057e502aa6003cea71cf5ec27cc44699ca52412b502a04761bb", size = 816827, upload-time = "2026-04-07T11:14:16.788Z" }, + { url = "https://files.pythonhosted.org/packages/d3/e3/574435c6aafb80254c191ef40d7aca2cb2bb97a095ec9395e9fa59ac307a/rapidfuzz-3.14.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0d3378f471ef440473a396ce2f8e97ee12f89a78b495540e0a5617bbfe895638", size = 1944601, upload-time = "2026-04-07T11:14:18.771Z" }, + { url = "https://files.pythonhosted.org/packages/d0/1f/fbad3102a255ecc112ce9a7e779bacab7fd14398217be8868dc9082ba363/rapidfuzz-3.14.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e910eebca9fd0eba245c0555e764597e8a0cccb673a92da2dc2397050725f48", size = 1164293, upload-time = "2026-04-07T11:14:20.534Z" }, + { url = "https://files.pythonhosted.org/packages/88/37/a3eb7ff6121ed3a5f199a8c38cc86c8e481816f879cb0e0b738b078c9a7e/rapidfuzz-3.14.5-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:01550fe5f60fd176aa66b7611289d46dc4aa4b1b904874c7b6d1d54e581c5ec1", size = 1371999, upload-time = "2026-04-07T11:14:22.63Z" }, + { url = "https://files.pythonhosted.org/packages/79/72/97a9728c711c7c1b06e107d3f0623880fb4ef90e147ed13c551a1730e7cc/rapidfuzz-3.14.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48bee0b91bebfaec41e1081e351000659ab7570cc4598d617aa04d5bf827f9e6", size = 3145715, upload-time = "2026-04-07T11:14:24.508Z" }, + { url = "https://files.pythonhosted.org/packages/ed/54/d5caabbea233ac90c286c87c260e49d7641467e87438a18d858e41c82e91/rapidfuzz-3.14.5-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:7e580cb04ad849ae9b786fa21383c6b994b6e6c1444ad1cb9f22392759d72741", size = 1456304, upload-time = "2026-04-07T11:14:26.515Z" }, + { url = "https://files.pythonhosted.org/packages/fc/a7/2d1a81250ac8c01a0100c026018e76f0e7a097ff63e4c553e02a6938c6fb/rapidfuzz-3.14.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:09d6c9ba091854f07817055d795d604179c12a8f308ba4c7d56f3719dfea1646", size = 2389089, upload-time = "2026-04-07T11:14:28.635Z" }, + { url = "https://files.pythonhosted.org/packages/65/0d/c47c3872203ae88e6506997c0b576ad731f5261daa25d559be09c9756658/rapidfuzz-3.14.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1e989f86113be66574113b9c7bdf4793f3f863d248e47d911b355e05ca6b6b10", size = 2493404, upload-time = "2026-04-07T11:14:30.577Z" }, + { url = "https://files.pythonhosted.org/packages/8f/2f/71e0a5a3130792146c8a200a2dd1e52aa16f7c1074012e17f2601eea9a90/rapidfuzz-3.14.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ebd1a18e2e47bc0b292a07e6ed9c3642f8aaa672d12253885f599b50807a4f9", size = 4251709, upload-time = "2026-04-07T11:14:32.451Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/d39874901abacef325adb5b34ae416817c8486dfb4fb87c7a9b74ec5b072/rapidfuzz-3.14.5-cp312-cp312-win32.whl", hash = "sha256:9981d38a703b86f0e315a3cd229fd1906fe1d91c989ed121fb975b3c849f89f5", size = 1710069, upload-time = "2026-04-07T11:14:34.37Z" }, + { url = "https://files.pythonhosted.org/packages/85/0b/f65572c53de8a1c704bda707f63a447b67bdbe95d7cdc70d18885e191df5/rapidfuzz-3.14.5-cp312-cp312-win_amd64.whl", hash = "sha256:d8375e3da319593389727c3187ccaf3e0e84199accc530866b8e0f2b79af05e9", size = 1540630, upload-time = "2026-04-07T11:14:36.287Z" }, + { url = "https://files.pythonhosted.org/packages/5e/c3/143be3a578f989758cae516f3270d5cbb49783a7bfdf57cc27a670e00456/rapidfuzz-3.14.5-cp312-cp312-win_arm64.whl", hash = "sha256:478b59bb018a6780d73f33e38d0b3ec5e968a6c1ed42876b993dd456b7aa20e8", size = 813137, upload-time = "2026-04-07T11:14:38.289Z" }, + { url = "https://files.pythonhosted.org/packages/11/66/252803f2010ba699618cdc048b6e1f7cc1f433c08b4a9a17579b92ab0142/rapidfuzz-3.14.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ebd8fd343bf8492a1e60bcb6dc99f90f74f65d98d8241a6b3e1fed225b76ecd6", size = 1940205, upload-time = "2026-04-07T11:14:40.319Z" }, + { url = "https://files.pythonhosted.org/packages/ea/59/b2afd98e41af9cd54554a4c1c423d84cdd60e6b1c0a09496f033b55f60ec/rapidfuzz-3.14.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6737b35d5af7479c5bf9710f7b17edd9d2c43128d974d25fb4ea653e42c64609", size = 1159639, upload-time = "2026-04-07T11:14:42.52Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/7aa7e62c4c516a7af322ed0c4f0774208b72d457d0cfec808bad0df12f4a/rapidfuzz-3.14.5-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b002c7994cc9f2bc9d9856f0fbaee6e8072c983873846c92f25cefba5b2a925f", size = 1367194, upload-time = "2026-04-07T11:14:44.25Z" }, + { url = "https://files.pythonhosted.org/packages/90/79/2fc252a63bc91d3c3b234d0a3a6ad4ebc460037a23cdcdaf9285f986e6c9/rapidfuzz-3.14.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17a34330cd2a538c1ce5d400b61ba358c5b72c654b928ff87b362e88f8b864c7", size = 3151805, upload-time = "2026-04-07T11:14:46.21Z" }, + { url = "https://files.pythonhosted.org/packages/17/54/0c83508f2683ea70e2d05f8527eb07328acf7bb1e9d97a3bece5702378e7/rapidfuzz-3.14.5-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:95d937e74c1a7a1287dfb03b62a827be08ede10a155cf1af73bbf47f2b73ee6e", size = 1455667, upload-time = "2026-04-07T11:14:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/71/1b/070175e873177814d58850a01ebe80e20ae11e93eb4da894d563988660fa/rapidfuzz-3.14.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:46b92a9970dcc34f0096901c792644094cab49554ac3547f35e3aebbdf0a3610", size = 2388246, upload-time = "2026-04-07T11:14:50.098Z" }, + { url = "https://files.pythonhosted.org/packages/c9/dd/77caf7aaf9c2be050ad1f128d7c24ff0f59079aa62c5f62f9df41c0af45e/rapidfuzz-3.14.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e012177c8e8a8a0754ae0d6027d63042aa5ff036d9f40f07cb3466a6082e21b8", size = 2494333, upload-time = "2026-04-07T11:14:52.303Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e2/dd7e1f2aa31a8fbbfc16b0610af1d770ffaf1287490f3c8c5b1c52da264f/rapidfuzz-3.14.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a2ae6f53f99c9a0eca7a0afc5b4e45fc73bc1dd4ac74c00509031d76df80ed98", size = 4258579, upload-time = "2026-04-07T11:14:54.538Z" }, + { url = "https://files.pythonhosted.org/packages/9c/0a/ac99e1ba347ba0e85e0bb60b74231d55fb93c0eff43f2920ccb413d0be08/rapidfuzz-3.14.5-cp313-cp313-win32.whl", hash = "sha256:4a60f0057231188e3bd30216f7b4e0f279b11fa4ec818bb6c1d9f014d1562fbc", size = 1709231, upload-time = "2026-04-07T11:14:56.524Z" }, + { url = "https://files.pythonhosted.org/packages/cf/cb/0e251d731b3166378644238e8f0cf9e89858c024e19f75ca9f7e3ae83fd5/rapidfuzz-3.14.5-cp313-cp313-win_amd64.whl", hash = "sha256:11bfc2ed8fbe4ab86bd516fadefab126f90e6dcadffa761739fcb304707dfd35", size = 1538519, upload-time = "2026-04-07T11:14:58.635Z" }, + { url = "https://files.pythonhosted.org/packages/30/6f/4548132acc947db6d5346a248e44a8b3a22d608ef30e770fb578caaf2d00/rapidfuzz-3.14.5-cp313-cp313-win_arm64.whl", hash = "sha256:b486b5218808f6f4dc471b114b1054e63553db69705c97da0271f47bd706aedd", size = 812628, upload-time = "2026-04-07T11:15:00.552Z" }, + { url = "https://files.pythonhosted.org/packages/00/60/69b177577290c5eab892c6f75fe89c3aff3f9ae80298a78d9372b1cecb9a/rapidfuzz-3.14.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:39ef8658aaf67d51667e7bdaf7096f432333377d8302ac43c70b5df8a4cf89b8", size = 1970231, upload-time = "2026-04-07T11:15:02.603Z" }, + { url = "https://files.pythonhosted.org/packages/48/38/2fd790052659cc4e2907b63c25433f0987864b445c1aeec1a302ef5ad948/rapidfuzz-3.14.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ad37a0be705b544af6296da8edddc260d10a8ae5462530fc9991f66498bb1f9", size = 1194394, upload-time = "2026-04-07T11:15:04.572Z" }, + { url = "https://files.pythonhosted.org/packages/80/f4/28430ad8472fc3536e8ebd51a864a226e979cfe924c6e3f83d111373aa74/rapidfuzz-3.14.5-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d45e06f60729e07d9b20c205f7e5cff90b6ef2584e852eecf46e045aea69627d", size = 1377051, upload-time = "2026-04-07T11:15:06.728Z" }, + { url = "https://files.pythonhosted.org/packages/77/7e/9aeacabcfd1e77397968362e5b98fe14248b8307011136b17daf99752a8e/rapidfuzz-3.14.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e52da10236aa6212de71b9e170bace65b64b129c0dea7fc243d6c9ce976f5074", size = 3160565, upload-time = "2026-04-07T11:15:08.667Z" }, + { url = "https://files.pythonhosted.org/packages/56/f4/db4dd7be0cd2f2022117ac5407d905f435d60e48baaea313a567ad27e865/rapidfuzz-3.14.5-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:440d30faaf682ca496170a7f0cc5453ec942e3e079f0fd802c9a7f938dfb50a3", size = 1442113, upload-time = "2026-04-07T11:15:11.138Z" }, + { url = "https://files.pythonhosted.org/packages/a4/99/0e9f6aa57f3e32a767216f797e56dc96b720fcecfb9d8ee907ecc82f8d66/rapidfuzz-3.14.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:56227a61fd3d17b0cd9793132431f3a3d07c8654be96794ba9f89fe0fc8b2d09", size = 2396618, upload-time = "2026-04-07T11:15:13.154Z" }, + { url = "https://files.pythonhosted.org/packages/60/94/44a78e39ffce17cbdd3e2b53b696acc751d5d153be0f499d052b07a4d904/rapidfuzz-3.14.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:2e83cd2e25bb4edd97b689d9979d9c3acccdaaf26ceac08212ceece202febcfa", size = 2478220, upload-time = "2026-04-07T11:15:15.193Z" }, + { url = "https://files.pythonhosted.org/packages/dd/df/454311469a09a507e9d784a35796742bec22e4cebe75551e2da4e0e290fd/rapidfuzz-3.14.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:af3b859726cd3374287e405e14b9634563c078c5531a4f62375508addebddad1", size = 4265027, upload-time = "2026-04-07T11:15:17.28Z" }, + { url = "https://files.pythonhosted.org/packages/fc/01/175465a9ab3e3b70ba669058372f009d1d49c1746e2dcd56b69df188d3a5/rapidfuzz-3.14.5-cp313-cp313t-win32.whl", hash = "sha256:8ce1d850b3c0178440efde9e884d98421b5e87ff925f364d6d79e23910d7593f", size = 1766814, upload-time = "2026-04-07T11:15:19.687Z" }, + { url = "https://files.pythonhosted.org/packages/1b/a0/a9b84a47af06ebed94a1439eb2f02adebfb8628bcd30af1fe3e02f5ef56c/rapidfuzz-3.14.5-cp313-cp313t-win_amd64.whl", hash = "sha256:c84af70bcf34e99aee894e46a0f1ac77f17d0ef828179c387407642e2466d28a", size = 1582448, upload-time = "2026-04-07T11:15:21.98Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f1/5937800238b3f8248e70860d79f69ba8f73e764fff47e36bc9e2f26dbcc6/rapidfuzz-3.14.5-cp313-cp313t-win_arm64.whl", hash = "sha256:aac0ad28c686a5e72b81668b906c030ee28050b244544b8af68e12fb32543895", size = 832932, upload-time = "2026-04-07T11:15:24.358Z" }, + { url = "https://files.pythonhosted.org/packages/d9/ee/e71853bf82846c5c2174b924b71d8e8099fb05ff87c958a720380b434ba3/rapidfuzz-3.14.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:578e6051f6d5e6200c259b47a103cf06bb875ab5814d17333fc0b5c290b22f4c", size = 1888603, upload-time = "2026-04-07T11:16:18.223Z" }, + { url = "https://files.pythonhosted.org/packages/36/82/40f67b730f32be2ebad9f62add1571c754f52249254b2e88af094b907eee/rapidfuzz-3.14.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fbf1b8bb2695415b347f3727da1addca2acb82c9b97ac86bebf8b1bead1eb12d", size = 1120599, upload-time = "2026-04-07T11:16:20.682Z" }, + { url = "https://files.pythonhosted.org/packages/ef/9f/a3635cc4ec8fc6e14b46e7db1f7f8763d8c4bef33dcc124eea2e6cb2c8f3/rapidfuzz-3.14.5-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f4a8f5cc84c7ad6bffa0e9947b33eb343ad66e6b53e94fe54378a5508c5ed53", size = 1348524, upload-time = "2026-04-07T11:16:23.451Z" }, + { url = "https://files.pythonhosted.org/packages/cc/1b/2b229520f0b48464cfcd7aa758f74551d12c9bc4ab544022a60210aab064/rapidfuzz-3.14.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97c6d85283629646fa87acc22c66b30ea9d4de7f6fdf887daa2e30fa041829b5", size = 3099302, upload-time = "2026-04-07T11:16:25.858Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b5/363906b1064fc6fe611783a61764927bbd91919aaaabe8cba82151ca93ef/rapidfuzz-3.14.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:dfef96543ced67d9513a422755db422ae1dc34dade0a1485e0b43e7342ed3ebf", size = 1509889, upload-time = "2026-04-07T11:16:28.487Z" }, ] [[package]] name = "rapidocr" -version = "3.7.0" +version = "3.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorlog" }, @@ -6988,7 +7075,7 @@ dependencies = [ { name = "tqdm" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/b8/011338eec8aea40cf9b82da7481f3e65e100537cff4c866b3c1b1e719b97/rapidocr-3.7.0-py3-none-any.whl", hash = "sha256:ace47f037956fa3780875f8556a0f27ab20d91962d36a9a2816aa367bb48718f", size = 15080131, upload-time = "2026-03-04T15:38:20.339Z" }, + { url = "https://files.pythonhosted.org/packages/49/1f/5f815e17c0b02b8f937b5b680b85d0ec5f34b195314dfa8f11ed14a6de03/rapidocr-3.8.0-py3-none-any.whl", hash = "sha256:54abb10883d588120a3390bc447566f1590aea641e127f63a4ca44415fecd18a", size = 15082360, upload-time = "2026-04-08T13:42:15.89Z" }, ] [[package]] @@ -7767,49 +7854,49 @@ wheels = [ [[package]] name = "sqlalchemy" -version = "2.0.48" +version = "2.0.49" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1f/73/b4a9737255583b5fa858e0bb8e116eb94b88c910164ed2ed719147bde3de/sqlalchemy-2.0.48.tar.gz", hash = "sha256:5ca74f37f3369b45e1f6b7b06afb182af1fd5dde009e4ffd831830d98cbe5fe7", size = 9886075, upload-time = "2026-03-02T15:28:51.474Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/67/1235676e93dd3b742a4a8eddfae49eea46c85e3eed29f0da446a8dd57500/sqlalchemy-2.0.48-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7001dc9d5f6bb4deb756d5928eaefe1930f6f4179da3924cbd95ee0e9f4dce89", size = 2157384, upload-time = "2026-03-02T15:38:26.781Z" }, - { url = "https://files.pythonhosted.org/packages/4d/d7/fa728b856daa18c10e1390e76f26f64ac890c947008284387451d56ca3d0/sqlalchemy-2.0.48-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a89ce07ad2d4b8cfc30bd5889ec40613e028ed80ef47da7d9dd2ce969ad30e0", size = 3236981, upload-time = "2026-03-02T15:58:53.53Z" }, - { url = "https://files.pythonhosted.org/packages/5c/ad/6c4395649a212a6c603a72c5b9ab5dce3135a1546cfdffa3c427e71fd535/sqlalchemy-2.0.48-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10853a53a4a00417a00913d270dddda75815fcb80675874285f41051c094d7dd", size = 3235232, upload-time = "2026-03-02T15:52:25.654Z" }, - { url = "https://files.pythonhosted.org/packages/01/f4/58f845e511ac0509765a6f85eb24924c1ef0d54fb50de9d15b28c3601458/sqlalchemy-2.0.48-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fac0fa4e4f55f118fd87177dacb1c6522fe39c28d498d259014020fec9164c29", size = 3188106, upload-time = "2026-03-02T15:58:55.193Z" }, - { url = "https://files.pythonhosted.org/packages/3f/f9/6dcc7bfa5f5794c3a095e78cd1de8269dfb5584dfd4c2c00a50d3c1ade44/sqlalchemy-2.0.48-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3713e21ea67bca727eecd4a24bf68bcd414c403faae4989442be60994301ded0", size = 3209522, upload-time = "2026-03-02T15:52:27.407Z" }, - { url = "https://files.pythonhosted.org/packages/d7/5a/b632875ab35874d42657f079529f0745410604645c269a8c21fb4272ff7a/sqlalchemy-2.0.48-cp310-cp310-win32.whl", hash = "sha256:d404dc897ce10e565d647795861762aa2d06ca3f4a728c5e9a835096c7059018", size = 2117695, upload-time = "2026-03-02T15:46:51.389Z" }, - { url = "https://files.pythonhosted.org/packages/de/03/9752eb2a41afdd8568e41ac3c3128e32a0a73eada5ab80483083604a56d1/sqlalchemy-2.0.48-cp310-cp310-win_amd64.whl", hash = "sha256:841a94c66577661c1f088ac958cd767d7c9bf507698f45afffe7a4017049de76", size = 2140928, upload-time = "2026-03-02T15:46:52.992Z" }, - { url = "https://files.pythonhosted.org/packages/d7/6d/b8b78b5b80f3c3ab3f7fa90faa195ec3401f6d884b60221260fd4d51864c/sqlalchemy-2.0.48-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b4c575df7368b3b13e0cebf01d4679f9a28ed2ae6c1cd0b1d5beffb6b2007dc", size = 2157184, upload-time = "2026-03-02T15:38:28.161Z" }, - { url = "https://files.pythonhosted.org/packages/21/4b/4f3d4a43743ab58b95b9ddf5580a265b593d017693df9e08bd55780af5bb/sqlalchemy-2.0.48-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e83e3f959aaa1c9df95c22c528096d94848a1bc819f5d0ebf7ee3df0ca63db6c", size = 3313555, upload-time = "2026-03-02T15:58:57.21Z" }, - { url = "https://files.pythonhosted.org/packages/21/dd/3b7c53f1dbbf736fd27041aee68f8ac52226b610f914085b1652c2323442/sqlalchemy-2.0.48-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f7b7243850edd0b8b97043f04748f31de50cf426e939def5c16bedb540698f7", size = 3313057, upload-time = "2026-03-02T15:52:29.366Z" }, - { url = "https://files.pythonhosted.org/packages/d9/cc/3e600a90ae64047f33313d7d32e5ad025417f09d2ded487e8284b5e21a15/sqlalchemy-2.0.48-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:82745b03b4043e04600a6b665cb98697c4339b24e34d74b0a2ac0a2488b6f94d", size = 3265431, upload-time = "2026-03-02T15:58:59.096Z" }, - { url = "https://files.pythonhosted.org/packages/8b/19/780138dacfe3f5024f4cf96e4005e91edf6653d53d3673be4844578faf1d/sqlalchemy-2.0.48-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e5e088bf43f6ee6fec7dbf1ef7ff7774a616c236b5c0cb3e00662dd71a56b571", size = 3287646, upload-time = "2026-03-02T15:52:31.569Z" }, - { url = "https://files.pythonhosted.org/packages/40/fd/f32ced124f01a23151f4777e4c705f3a470adc7bd241d9f36a7c941a33bf/sqlalchemy-2.0.48-cp311-cp311-win32.whl", hash = "sha256:9c7d0a77e36b5f4b01ca398482230ab792061d243d715299b44a0b55c89fe617", size = 2116956, upload-time = "2026-03-02T15:46:54.535Z" }, - { url = "https://files.pythonhosted.org/packages/58/d5/dd767277f6feef12d05651538f280277e661698f617fa4d086cce6055416/sqlalchemy-2.0.48-cp311-cp311-win_amd64.whl", hash = "sha256:583849c743e0e3c9bb7446f5b5addeacedc168d657a69b418063dfdb2d90081c", size = 2141627, upload-time = "2026-03-02T15:46:55.849Z" }, - { url = "https://files.pythonhosted.org/packages/ef/91/a42ae716f8925e9659df2da21ba941f158686856107a61cc97a95e7647a3/sqlalchemy-2.0.48-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:348174f228b99f33ca1f773e85510e08927620caa59ffe7803b37170df30332b", size = 2155737, upload-time = "2026-03-02T15:49:13.207Z" }, - { url = "https://files.pythonhosted.org/packages/b9/52/f75f516a1f3888f027c1cfb5d22d4376f4b46236f2e8669dcb0cddc60275/sqlalchemy-2.0.48-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53667b5f668991e279d21f94ccfa6e45b4e3f4500e7591ae59a8012d0f010dcb", size = 3337020, upload-time = "2026-03-02T15:50:34.547Z" }, - { url = "https://files.pythonhosted.org/packages/37/9a/0c28b6371e0cdcb14f8f1930778cb3123acfcbd2c95bb9cf6b4a2ba0cce3/sqlalchemy-2.0.48-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34634e196f620c7a61d18d5cf7dc841ca6daa7961aed75d532b7e58b309ac894", size = 3349983, upload-time = "2026-03-02T15:53:25.542Z" }, - { url = "https://files.pythonhosted.org/packages/1c/46/0aee8f3ff20b1dcbceb46ca2d87fcc3d48b407925a383ff668218509d132/sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:546572a1793cc35857a2ffa1fe0e58571af1779bcc1ffa7c9fb0839885ed69a9", size = 3279690, upload-time = "2026-03-02T15:50:36.277Z" }, - { url = "https://files.pythonhosted.org/packages/ce/8c/a957bc91293b49181350bfd55e6dfc6e30b7f7d83dc6792d72043274a390/sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:07edba08061bc277bfdc772dd2a1a43978f5a45994dd3ede26391b405c15221e", size = 3314738, upload-time = "2026-03-02T15:53:27.519Z" }, - { url = "https://files.pythonhosted.org/packages/4b/44/1d257d9f9556661e7bdc83667cc414ba210acfc110c82938cb3611eea58f/sqlalchemy-2.0.48-cp312-cp312-win32.whl", hash = "sha256:908a3fa6908716f803b86896a09a2c4dde5f5ce2bb07aacc71ffebb57986ce99", size = 2115546, upload-time = "2026-03-02T15:54:31.591Z" }, - { url = "https://files.pythonhosted.org/packages/f2/af/c3c7e1f3a2b383155a16454df62ae8c62a30dd238e42e68c24cebebbfae6/sqlalchemy-2.0.48-cp312-cp312-win_amd64.whl", hash = "sha256:68549c403f79a8e25984376480959975212a670405e3913830614432b5daa07a", size = 2142484, upload-time = "2026-03-02T15:54:34.072Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c6/569dc8bf3cd375abc5907e82235923e986799f301cd79a903f784b996fca/sqlalchemy-2.0.48-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e3070c03701037aa418b55d36532ecb8f8446ed0135acb71c678dbdf12f5b6e4", size = 2152599, upload-time = "2026-03-02T15:49:14.41Z" }, - { url = "https://files.pythonhosted.org/packages/6d/ff/f4e04a4bd5a24304f38cb0d4aa2ad4c0fb34999f8b884c656535e1b2b74c/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2645b7d8a738763b664a12a1542c89c940daa55196e8d73e55b169cc5c99f65f", size = 3278825, upload-time = "2026-03-02T15:50:38.269Z" }, - { url = "https://files.pythonhosted.org/packages/fe/88/cb59509e4668d8001818d7355d9995be90c321313078c912420603a7cb95/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b19151e76620a412c2ac1c6f977ab1b9fa7ad43140178345136456d5265b32ed", size = 3295200, upload-time = "2026-03-02T15:53:29.366Z" }, - { url = "https://files.pythonhosted.org/packages/87/dc/1609a4442aefd750ea2f32629559394ec92e89ac1d621a7f462b70f736ff/sqlalchemy-2.0.48-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b193a7e29fd9fa56e502920dca47dffe60f97c863494946bd698c6058a55658", size = 3226876, upload-time = "2026-03-02T15:50:39.802Z" }, - { url = "https://files.pythonhosted.org/packages/37/c3/6ae2ab5ea2fa989fbac4e674de01224b7a9d744becaf59bb967d62e99bed/sqlalchemy-2.0.48-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:36ac4ddc3d33e852da9cb00ffb08cea62ca05c39711dc67062ca2bb1fae35fd8", size = 3265045, upload-time = "2026-03-02T15:53:31.421Z" }, - { url = "https://files.pythonhosted.org/packages/6f/82/ea4665d1bb98c50c19666e672f21b81356bd6077c4574e3d2bbb84541f53/sqlalchemy-2.0.48-cp313-cp313-win32.whl", hash = "sha256:389b984139278f97757ea9b08993e7b9d1142912e046ab7d82b3fbaeb0209131", size = 2113700, upload-time = "2026-03-02T15:54:35.825Z" }, - { url = "https://files.pythonhosted.org/packages/b7/2b/b9040bec58c58225f073f5b0c1870defe1940835549dafec680cbd58c3c3/sqlalchemy-2.0.48-cp313-cp313-win_amd64.whl", hash = "sha256:d612c976cbc2d17edfcc4c006874b764e85e990c29ce9bd411f926bbfb02b9a2", size = 2139487, upload-time = "2026-03-02T15:54:37.079Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f4/7b17bd50244b78a49d22cc63c969d71dc4de54567dc152a9b46f6fae40ce/sqlalchemy-2.0.48-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69f5bc24904d3bc3640961cddd2523e361257ef68585d6e364166dfbe8c78fae", size = 3558851, upload-time = "2026-03-02T15:57:48.607Z" }, - { url = "https://files.pythonhosted.org/packages/20/0d/213668e9aca61d370f7d2a6449ea4ec699747fac67d4bda1bb3d129025be/sqlalchemy-2.0.48-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd08b90d211c086181caed76931ecfa2bdfc83eea3cfccdb0f82abc6c4b876cb", size = 3525525, upload-time = "2026-03-02T16:04:38.058Z" }, - { url = "https://files.pythonhosted.org/packages/85/d7/a84edf412979e7d59c69b89a5871f90a49228360594680e667cb2c46a828/sqlalchemy-2.0.48-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1ccd42229aaac2df431562117ac7e667d702e8e44afdb6cf0e50fa3f18160f0b", size = 3466611, upload-time = "2026-03-02T15:57:50.759Z" }, - { url = "https://files.pythonhosted.org/packages/86/55/42404ce5770f6be26a2b0607e7866c31b9a4176c819e9a7a5e0a055770be/sqlalchemy-2.0.48-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0dcbc588cd5b725162c076eb9119342f6579c7f7f55057bb7e3c6ff27e13121", size = 3475812, upload-time = "2026-03-02T16:04:40.092Z" }, - { url = "https://files.pythonhosted.org/packages/ae/ae/29b87775fadc43e627cf582fe3bda4d02e300f6b8f2747c764950d13784c/sqlalchemy-2.0.48-cp313-cp313t-win32.whl", hash = "sha256:9764014ef5e58aab76220c5664abb5d47d5bc858d9debf821e55cfdd0f128485", size = 2141335, upload-time = "2026-03-02T15:52:51.518Z" }, - { url = "https://files.pythonhosted.org/packages/91/44/f39d063c90f2443e5b46ec4819abd3d8de653893aae92df42a5c4f5843de/sqlalchemy-2.0.48-cp313-cp313t-win_amd64.whl", hash = "sha256:e2f35b4cccd9ed286ad62e0a3c3ac21e06c02abc60e20aa51a3e305a30f5fa79", size = 2173095, upload-time = "2026-03-02T15:52:52.79Z" }, - { url = "https://files.pythonhosted.org/packages/46/2c/9664130905f03db57961b8980b05cab624afd114bf2be2576628a9f22da4/sqlalchemy-2.0.48-py3-none-any.whl", hash = "sha256:a66fe406437dd65cacd96a72689a3aaaecaebbcd62d81c5ac1c0fdbeac835096", size = 1940202, upload-time = "2026-03-02T15:52:43.285Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/09/45/461788f35e0364a8da7bda51a1fe1b09762d0c32f12f63727998d85a873b/sqlalchemy-2.0.49.tar.gz", hash = "sha256:d15950a57a210e36dd4cec1aac22787e2a4d57ba9318233e2ef8b2daf9ff2d5f", size = 9898221, upload-time = "2026-04-03T16:38:11.704Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/76/f908955139842c362aa877848f42f9249642d5b69e06cee9eae5111da1bd/sqlalchemy-2.0.49-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:42e8804962f9e6f4be2cbaedc0c3718f08f60a16910fa3d86da5a1e3b1bfe60f", size = 2159321, upload-time = "2026-04-03T16:50:11.8Z" }, + { url = "https://files.pythonhosted.org/packages/24/e2/17ba0b7bfbd8de67196889b6d951de269e8a46057d92baca162889beb16d/sqlalchemy-2.0.49-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc992c6ed024c8c3c592c5fc9846a03dd68a425674900c70122c77ea16c5fb0b", size = 3238937, upload-time = "2026-04-03T16:54:45.731Z" }, + { url = "https://files.pythonhosted.org/packages/90/1e/410dd499c039deacff395eec01a9da057125fcd0c97e3badc252c6a2d6a7/sqlalchemy-2.0.49-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6eb188b84269f357669b62cb576b5b918de10fb7c728a005fa0ebb0b758adce1", size = 3237188, upload-time = "2026-04-03T16:56:53.217Z" }, + { url = "https://files.pythonhosted.org/packages/ab/06/e797a8b98a3993ac4bc785309b9b6d005457fc70238ee6cefa7c8867a92e/sqlalchemy-2.0.49-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:62557958002b69699bdb7f5137c6714ca1133f045f97b3903964f47db97ea339", size = 3190061, upload-time = "2026-04-03T16:54:47.489Z" }, + { url = "https://files.pythonhosted.org/packages/44/d3/5a9f7ef580af1031184b38235da6ac58c3b571df01c9ec061c44b2b0c5a6/sqlalchemy-2.0.49-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da9b91bca419dc9b9267ffadde24eae9b1a6bffcd09d0a207e5e3af99a03ce0d", size = 3211477, upload-time = "2026-04-03T16:56:55.056Z" }, + { url = "https://files.pythonhosted.org/packages/69/ec/7be8c8cb35f038e963a203e4fe5a028989167cc7299927b7cf297c271e37/sqlalchemy-2.0.49-cp310-cp310-win32.whl", hash = "sha256:5e61abbec255be7b122aa461021daa7c3f310f3e743411a67079f9b3cc91ece3", size = 2119965, upload-time = "2026-04-03T17:00:50.009Z" }, + { url = "https://files.pythonhosted.org/packages/b5/31/0defb93e3a10b0cf7d1271aedd87251a08c3a597ee4f353281769b547b5a/sqlalchemy-2.0.49-cp310-cp310-win_amd64.whl", hash = "sha256:0c98c59075b890df8abfcc6ad632879540f5791c68baebacb4f833713b510e75", size = 2142935, upload-time = "2026-04-03T17:00:51.675Z" }, + { url = "https://files.pythonhosted.org/packages/60/b5/e3617cc67420f8f403efebd7b043128f94775e57e5b84e7255203390ceae/sqlalchemy-2.0.49-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5070135e1b7409c4161133aa525419b0062088ed77c92b1da95366ec5cbebbe", size = 2159126, upload-time = "2026-04-03T16:50:13.242Z" }, + { url = "https://files.pythonhosted.org/packages/20/9b/91ca80403b17cd389622a642699e5f6564096b698e7cdcbcbb6409898bc4/sqlalchemy-2.0.49-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ac7a3e245fd0310fd31495eb61af772e637bdf7d88ee81e7f10a3f271bff014", size = 3315509, upload-time = "2026-04-03T16:54:49.332Z" }, + { url = "https://files.pythonhosted.org/packages/b1/61/0722511d98c54de95acb327824cb759e8653789af2b1944ab1cc69d32565/sqlalchemy-2.0.49-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d4e5a0ceba319942fa6b585cf82539288a61e314ef006c1209f734551ab9536", size = 3315014, upload-time = "2026-04-03T16:56:56.376Z" }, + { url = "https://files.pythonhosted.org/packages/46/55/d514a653ffeb4cebf4b54c47bec32ee28ad89d39fafba16eeed1d81dccd5/sqlalchemy-2.0.49-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3ddcb27fb39171de36e207600116ac9dfd4ae46f86c82a9bf3934043e80ebb88", size = 3267388, upload-time = "2026-04-03T16:54:51.272Z" }, + { url = "https://files.pythonhosted.org/packages/2f/16/0dcc56cb6d3335c1671a2258f5d2cb8267c9a2260e27fde53cbfb1b3540a/sqlalchemy-2.0.49-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:32fe6a41ad97302db2931f05bb91abbcc65b5ce4c675cd44b972428dd2947700", size = 3289602, upload-time = "2026-04-03T16:56:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/51/6c/f8ab6fb04470a133cd80608db40aa292e6bae5f162c3a3d4ab19544a67af/sqlalchemy-2.0.49-cp311-cp311-win32.whl", hash = "sha256:46d51518d53edfbe0563662c96954dc8fcace9832332b914375f45a99b77cc9a", size = 2119044, upload-time = "2026-04-03T17:00:53.455Z" }, + { url = "https://files.pythonhosted.org/packages/c4/59/55a6d627d04b6ebb290693681d7683c7da001eddf90b60cfcc41ee907978/sqlalchemy-2.0.49-cp311-cp311-win_amd64.whl", hash = "sha256:951d4a210744813be63019f3df343bf233b7432aadf0db54c75802247330d3af", size = 2143642, upload-time = "2026-04-03T17:00:54.769Z" }, + { url = "https://files.pythonhosted.org/packages/49/b3/2de412451330756aaaa72d27131db6dde23995efe62c941184e15242a5fa/sqlalchemy-2.0.49-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4bbccb45260e4ff1b7db0be80a9025bb1e6698bdb808b83fff0000f7a90b2c0b", size = 2157681, upload-time = "2026-04-03T16:53:07.132Z" }, + { url = "https://files.pythonhosted.org/packages/50/84/b2a56e2105bd11ebf9f0b93abddd748e1a78d592819099359aa98134a8bf/sqlalchemy-2.0.49-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb37f15714ec2652d574f021d479e78cd4eb9d04396dca36568fdfffb3487982", size = 3338976, upload-time = "2026-04-03T17:07:40Z" }, + { url = "https://files.pythonhosted.org/packages/2c/fa/65fcae2ed62f84ab72cf89536c7c3217a156e71a2c111b1305ab6f0690e2/sqlalchemy-2.0.49-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb9ec6436a820a4c006aad1ac351f12de2f2dbdaad171692ee457a02429b672", size = 3351937, upload-time = "2026-04-03T17:12:23.374Z" }, + { url = "https://files.pythonhosted.org/packages/f8/2f/6fd118563572a7fe475925742eb6b3443b2250e346a0cc27d8d408e73773/sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8d6efc136f44a7e8bc8088507eaabbb8c2b55b3dbb63fe102c690da0ddebe55e", size = 3281646, upload-time = "2026-04-03T17:07:41.949Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d7/410f4a007c65275b9cf82354adb4bb8ba587b176d0a6ee99caa16fe638f8/sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e06e617e3d4fd9e51d385dfe45b077a41e9d1b033a7702551e3278ac597dc750", size = 3316695, upload-time = "2026-04-03T17:12:25.642Z" }, + { url = "https://files.pythonhosted.org/packages/d9/95/81f594aa60ded13273a844539041ccf1e66c5a7bed0a8e27810a3b52d522/sqlalchemy-2.0.49-cp312-cp312-win32.whl", hash = "sha256:83101a6930332b87653886c01d1ee7e294b1fe46a07dd9a2d2b4f91bcc88eec0", size = 2117483, upload-time = "2026-04-03T17:05:40.896Z" }, + { url = "https://files.pythonhosted.org/packages/47/9e/fd90114059175cac64e4fafa9bf3ac20584384d66de40793ae2e2f26f3bb/sqlalchemy-2.0.49-cp312-cp312-win_amd64.whl", hash = "sha256:618a308215b6cececb6240b9abde545e3acdabac7ae3e1d4e666896bf5ba44b4", size = 2144494, upload-time = "2026-04-03T17:05:42.282Z" }, + { url = "https://files.pythonhosted.org/packages/ae/81/81755f50eb2478eaf2049728491d4ea4f416c1eb013338682173259efa09/sqlalchemy-2.0.49-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df2d441bacf97022e81ad047e1597552eb3f83ca8a8f1a1fdd43cd7fe3898120", size = 2154547, upload-time = "2026-04-03T16:53:08.64Z" }, + { url = "https://files.pythonhosted.org/packages/a2/bc/3494270da80811d08bcfa247404292428c4fe16294932bce5593f215cad9/sqlalchemy-2.0.49-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8e20e511dc15265fb433571391ba313e10dd8ea7e509d51686a51313b4ac01a2", size = 3280782, upload-time = "2026-04-03T17:07:43.508Z" }, + { url = "https://files.pythonhosted.org/packages/cd/f5/038741f5e747a5f6ea3e72487211579d8cbea5eb9827a9cbd61d0108c4bd/sqlalchemy-2.0.49-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47604cb2159f8bbd5a1ab48a714557156320f20871ee64d550d8bf2683d980d3", size = 3297156, upload-time = "2026-04-03T17:12:27.697Z" }, + { url = "https://files.pythonhosted.org/packages/88/50/a6af0ff9dc954b43a65ca9b5367334e45d99684c90a3d3413fc19a02d43c/sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:22d8798819f86720bc646ab015baff5ea4c971d68121cb36e2ebc2ee43ead2b7", size = 3228832, upload-time = "2026-04-03T17:07:45.38Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d1/5f6bdad8de0bf546fc74370939621396515e0cdb9067402d6ba1b8afbe9a/sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9b1c058c171b739e7c330760044803099c7fff11511e3ab3573e5327116a9c33", size = 3267000, upload-time = "2026-04-03T17:12:29.657Z" }, + { url = "https://files.pythonhosted.org/packages/f7/30/ad62227b4a9819a5e1c6abff77c0f614fa7c9326e5a3bdbee90f7139382b/sqlalchemy-2.0.49-cp313-cp313-win32.whl", hash = "sha256:a143af2ea6672f2af3f44ed8f9cd020e9cc34c56f0e8db12019d5d9ecf41cb3b", size = 2115641, upload-time = "2026-04-03T17:05:43.989Z" }, + { url = "https://files.pythonhosted.org/packages/17/3a/7215b1b7d6d49dc9a87211be44562077f5f04f9bb5a59552c1c8e2d98173/sqlalchemy-2.0.49-cp313-cp313-win_amd64.whl", hash = "sha256:12b04d1db2663b421fe072d638a138460a51d5a862403295671c4f3987fb9148", size = 2141498, upload-time = "2026-04-03T17:05:45.7Z" }, + { url = "https://files.pythonhosted.org/packages/28/4b/52a0cb2687a9cd1648252bb257be5a1ba2c2ded20ba695c65756a55a15a4/sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24bd94bb301ec672d8f0623eba9226cc90d775d25a0c92b5f8e4965d7f3a1518", size = 3560807, upload-time = "2026-04-03T16:58:31.666Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d8/fda95459204877eed0458550d6c7c64c98cc50c2d8d618026737de9ed41a/sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a51d3db74ba489266ef55c7a4534eb0b8db9a326553df481c11e5d7660c8364d", size = 3527481, upload-time = "2026-04-03T17:06:00.155Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0a/2aac8b78ac6487240cf7afef8f203ca783e8796002dc0cf65c4ee99ff8bb/sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:55250fe61d6ebfd6934a272ee16ef1244e0f16b7af6cd18ab5b1fc9f08631db0", size = 3468565, upload-time = "2026-04-03T16:58:33.414Z" }, + { url = "https://files.pythonhosted.org/packages/a5/3d/ce71cfa82c50a373fd2148b3c870be05027155ce791dc9a5dcf439790b8b/sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:46796877b47034b559a593d7e4b549aba151dae73f9e78212a3478161c12ab08", size = 3477769, upload-time = "2026-04-03T17:06:02.787Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e8/0a9f5c1f7c6f9ca480319bf57c2d7423f08d31445974167a27d14483c948/sqlalchemy-2.0.49-cp313-cp313t-win32.whl", hash = "sha256:9c4969a86e41454f2858256c39bdfb966a20961e9b58bf8749b65abf447e9a8d", size = 2143319, upload-time = "2026-04-03T17:02:04.328Z" }, + { url = "https://files.pythonhosted.org/packages/0e/51/fb5240729fbec73006e137c4f7a7918ffd583ab08921e6ff81a999d6517a/sqlalchemy-2.0.49-cp313-cp313t-win_amd64.whl", hash = "sha256:b9870d15ef00e4d0559ae10ee5bc71b654d1f20076dbe8bc7ed19b4c0625ceba", size = 2175104, upload-time = "2026-04-03T17:02:05.989Z" }, + { url = "https://files.pythonhosted.org/packages/e5/30/8519fdde58a7bdf155b714359791ad1dc018b47d60269d5d160d311fdc36/sqlalchemy-2.0.49-py3-none-any.whl", hash = "sha256:ec44cfa7ef1a728e88ad41674de50f6db8cfdb3e2af84af86e0041aaf02d43d0", size = 1942158, upload-time = "2026-04-03T16:53:44.135Z" }, ] [[package]] @@ -7836,7 +7923,7 @@ wheels = [ [[package]] name = "stagehand" -version = "3.19.1" +version = "3.19.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -7846,12 +7933,12 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/86/81/da1fc0e559708f6d3c722e2a231209e4f0bcd235e5f7864467598a046b94/stagehand-3.19.1.tar.gz", hash = "sha256:79f90149617c66b52f3d5ef98eec670084576ced21adfc5047f0287f1825bd0a", size = 279625, upload-time = "2026-03-31T22:05:48.01Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/f8/ccd2bb2758a4eaf0af3846e097ff206e0aa76c8d3b5aa2bded77fb47825e/stagehand-3.19.5.tar.gz", hash = "sha256:3cb8279ac82051e584b34d26e87dc764f0ccad766a01625198ca578eb35f0b6c", size = 281033, upload-time = "2026-04-03T20:21:09.792Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/f5/691c3e050b059fdb949dcb34e6e692e22bf2bb5913d595a7142afa33fa9d/stagehand-3.19.1-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:ec7e2f0ed5a33c8374ce29cc0be96d5bd79f3b0912c862268df9a87cba3abee0", size = 34492500, upload-time = "2026-03-31T22:05:49.574Z" }, - { url = "https://files.pythonhosted.org/packages/42/c8/4d40169828de0ed9f9d108aa8c8a5a4e2ee42d13c4a5f2612cec6acec63e/stagehand-3.19.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:002989a7730c91c51ea38c1b11b0b07b25610ccf823779e709951568ae0e65de", size = 33190159, upload-time = "2026-03-31T22:05:55.079Z" }, - { url = "https://files.pythonhosted.org/packages/25/4d/787a3a5a4a0a0661dba24d8904734d48934c28b20ba9af842c8c84892487/stagehand-3.19.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:baea3a6cff498e096c3d1933751aa1879838d5e308b479d50e60cdd4fe104d99", size = 37269772, upload-time = "2026-03-31T22:05:45.856Z" }, - { url = "https://files.pythonhosted.org/packages/f1/65/03072d2d5e8178fd4cfa7cbb51088ea4248c1a29ad0cb84c4ab813e5e416/stagehand-3.19.1-py3-none-win_amd64.whl", hash = "sha256:1cd73285d80517a674aaaa40a3bf40cf9b85da50e8f29a01d7f5f2ef2f11d70e", size = 30754952, upload-time = "2026-03-31T22:05:52.481Z" }, + { url = "https://files.pythonhosted.org/packages/d1/6f/a47bad258bfafc193ebb8e0e8c440e8028c9ab28b54a333b46aa3c0cff53/stagehand-3.19.5-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:14f39a4f8d30d77c089166185c705f66aade25432b903a663a937b3747439c26", size = 34495874, upload-time = "2026-04-03T20:21:07.366Z" }, + { url = "https://files.pythonhosted.org/packages/72/f7/e39868903121f1a80ae6eda088383362cd2d3a578c04493a2f83c1aac1da/stagehand-3.19.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:80ed0d732cb9c3e952ad851e071dad5775a9ea88d2787c006289d61097fd2609", size = 33193535, upload-time = "2026-04-03T20:21:18.536Z" }, + { url = "https://files.pythonhosted.org/packages/c8/0b/35cb92bb53e9539c0147892dbd0a227b43bf0d8adcd0a8e867dc5f2bf7fd/stagehand-3.19.5-py3-none-manylinux2014_x86_64.whl", hash = "sha256:aa947a5f6241f5953ac238cd9b0ab72e0cb87f559f97e5ee875f83dbc0c351d1", size = 37273148, upload-time = "2026-04-03T20:21:11.939Z" }, + { url = "https://files.pythonhosted.org/packages/7c/c7/dccf63cba1941b5710dc9968218e2883a937cf6534d644bb0c5222d3f40a/stagehand-3.19.5-py3-none-win_amd64.whl", hash = "sha256:e37bf630b99b4a9b7d95f151c56b296940db88b3049b68f0abb56f9e31cc6095", size = 30758357, upload-time = "2026-04-03T20:21:15.121Z" }, ] [[package]] @@ -7931,7 +8018,7 @@ wheels = [ [[package]] name = "textual" -version = "8.2.2" +version = "8.2.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py", extra = ["linkify"] }, @@ -7941,9 +8028,9 @@ dependencies = [ { name = "rich" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/69/b0/a9aedf13af1bfb1bf01cbc645ea5d5a4151b5d77ac1748b85c4f0d777d7d/textual-8.2.2.tar.gz", hash = "sha256:94e85267650cf679ac16ade5ac929055e836dc00798a0e6e3925926a5beee303", size = 1848623, upload-time = "2026-04-03T13:19:06.057Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/2f/d44f0f12b3ddb1f0b88f7775652e99c6b5a43fd733badf4ce064bdbfef4a/textual-8.2.3.tar.gz", hash = "sha256:beea7b86b03b03558a2224f0cc35252e60ef8b0c4353b117b2f40972902d976a", size = 1848738, upload-time = "2026-04-05T09:12:45.338Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/18/4d59eb3f2241db6d346a90f2452fc47a19d61090a38b9cf331afe23e8431/textual-8.2.2-py3-none-any.whl", hash = "sha256:35a8f439875dc6e5b4dc7ee72dc9698a40bd13091c2de5bd5b2d4318522af8df", size = 724078, upload-time = "2026-04-03T13:19:08.115Z" }, + { url = "https://files.pythonhosted.org/packages/0e/28/a81d6ce9f4804818bd1231a9a6e4d56ea84ebbe8385c49591444f0234fa2/textual-8.2.3-py3-none-any.whl", hash = "sha256:5008ac581bebf1f6fa0520404261844a231e5715fdbddd10ca73916a3af48ca2", size = 724231, upload-time = "2026-04-05T09:12:48.747Z" }, ] [[package]] @@ -8351,11 +8438,11 @@ wheels = [ [[package]] name = "types-aiofiles" -version = "25.1.0.20251011" +version = "25.1.0.20260409" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/84/6c/6d23908a8217e36704aa9c79d99a620f2fdd388b66a4b7f72fbc6b6ff6c6/types_aiofiles-25.1.0.20251011.tar.gz", hash = "sha256:1c2b8ab260cb3cd40c15f9d10efdc05a6e1e6b02899304d80dfa0410e028d3ff", size = 14535, upload-time = "2025-10-11T02:44:51.237Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/66/9e62a2692792bc96c0f423f478149f4a7b84720704c546c8960b0a047c89/types_aiofiles-25.1.0.20260409.tar.gz", hash = "sha256:49e67d72bdcf9fe406f5815758a78dc34a1249bb5aa2adba78a80aec0a775435", size = 14812, upload-time = "2026-04-09T04:22:35.308Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/71/0f/76917bab27e270bb6c32addd5968d69e558e5b6f7fb4ac4cbfa282996a96/types_aiofiles-25.1.0.20251011-py3-none-any.whl", hash = "sha256:8ff8de7f9d42739d8f0dadcceeb781ce27cd8d8c4152d4a7c52f6b20edb8149c", size = 14338, upload-time = "2025-10-11T02:44:50.054Z" }, + { url = "https://files.pythonhosted.org/packages/27/d0/28236f869ba4dfb223ecdbc267eb2bdb634b81a561dd992230a4f9ec48fa/types_aiofiles-25.1.0.20260409-py3-none-any.whl", hash = "sha256:923fedb532c772cc0f62e0ce4282725afa82ca5b41cabd9857f06b55e5eee8de", size = 14372, upload-time = "2026-04-09T04:22:34.328Z" }, ] [[package]] @@ -8396,11 +8483,11 @@ wheels = [ [[package]] name = "types-pyyaml" -version = "6.0.12.20250915" +version = "6.0.12.20260408" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/69/3c51b36d04da19b92f9e815be12753125bd8bc247ba0470a982e6979e71c/types_pyyaml-6.0.12.20250915.tar.gz", hash = "sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3", size = 17522, upload-time = "2025-09-15T03:01:00.728Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/73/b759b1e413c31034cc01ecdfb96b38115d0ab4db55a752a3929f0cd449fd/types_pyyaml-6.0.12.20260408.tar.gz", hash = "sha256:92a73f2b8d7f39ef392a38131f76b970f8c66e4c42b3125ae872b7c93b556307", size = 17735, upload-time = "2026-04-08T04:30:50.974Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl", hash = "sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6", size = 20338, upload-time = "2025-09-15T03:00:59.218Z" }, + { url = "https://files.pythonhosted.org/packages/1c/f0/c391068b86abb708882c6d75a08cd7d25b2c7227dab527b3a3685a3c635b/types_pyyaml-6.0.12.20260408-py3-none-any.whl", hash = "sha256:fbc42037d12159d9c801ebfcc79ebd28335a7c13b08a4cfbc6916df78fee9384", size = 20339, upload-time = "2026-04-08T04:30:50.113Z" }, ] [[package]] @@ -8694,16 +8781,16 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.42.0" +version = "0.44.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e3/ad/4a96c425be6fb67e0621e62d86c402b4a17ab2be7f7c055d9bd2f638b9e2/uvicorn-0.42.0.tar.gz", hash = "sha256:9b1f190ce15a2dd22e7758651d9b6d12df09a13d51ba5bf4fc33c383a48e1775", size = 85393, upload-time = "2026-03-16T06:19:50.077Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/da/6eee1ff8b6cbeed47eeb5229749168e81eb4b7b999a1a15a7176e51410c9/uvicorn-0.44.0.tar.gz", hash = "sha256:6c942071b68f07e178264b9152f1f16dfac5da85880c4ce06366a96d70d4f31e", size = 86947, upload-time = "2026-04-06T09:23:22.826Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/89/f8827ccff89c1586027a105e5630ff6139a64da2515e24dafe860bd9ae4d/uvicorn-0.42.0-py3-none-any.whl", hash = "sha256:96c30f5c7abe6f74ae8900a70e92b85ad6613b745d4879eb9b16ccad15645359", size = 68830, upload-time = "2026-03-16T06:19:48.325Z" }, + { url = "https://files.pythonhosted.org/packages/b7/23/a5bbd9600dd607411fa644c06ff4951bec3a4d82c4b852374024359c19c0/uvicorn-0.44.0-py3-none-any.whl", hash = "sha256:ce937c99a2cc70279556967274414c087888e8cec9f9c94644dfca11bd3ced89", size = 69425, upload-time = "2026-04-06T09:23:21.524Z" }, ] [package.optional-dependencies] @@ -8900,18 +8987,48 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189, upload-time = "2026-02-06T19:19:39.646Z" }, ] +[[package]] +name = "weaviate-client" +version = "4.16.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13' and platform_machine != 's390x'", + "python_full_version < '3.11' and platform_machine != 's390x'", + "python_full_version < '3.11' and platform_machine == 's390x'", +] +dependencies = [ + { name = "authlib", marker = "python_full_version < '3.11' or (python_full_version >= '3.13' and platform_machine != 's390x')" }, + { name = "deprecation", marker = "python_full_version < '3.11' or (python_full_version >= '3.13' and platform_machine != 's390x')" }, + { name = "grpcio", version = "1.80.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (python_full_version >= '3.13' and platform_machine != 's390x')" }, + { name = "grpcio-health-checking", marker = "python_full_version < '3.11' or (python_full_version >= '3.13' and platform_machine != 's390x')" }, + { name = "httpx", marker = "python_full_version < '3.11' or (python_full_version >= '3.13' and platform_machine != 's390x')" }, + { name = "pydantic", marker = "python_full_version < '3.11' or (python_full_version >= '3.13' and platform_machine != 's390x')" }, + { name = "validators", marker = "python_full_version < '3.11' or (python_full_version >= '3.13' and platform_machine != 's390x')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/b9/7b9e05cf923743aa1479afcd85c48ebca82d031c3c3a5d02b1b3fcb52eb9/weaviate_client-4.16.2.tar.gz", hash = "sha256:eb7107a3221a5ad68d604cafc65195bd925a9709512ea0b6fe0dd212b0678fab", size = 681321, upload-time = "2025-07-22T09:10:48.79Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/c8/8a8c7ddbdd2c7fc73782056310666736a36a7d860f9935ce1d21f5f6c02e/weaviate_client-4.16.2-py3-none-any.whl", hash = "sha256:c236adca30d18667943544ad89fcd9157947af95dfc6de4a8ecf9e7619f1c979", size = 451475, upload-time = "2025-07-22T09:10:46.941Z" }, +] + [[package]] name = "weaviate-client" version = "4.18.3" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13' and platform_machine == 's390x'", + "python_full_version == '3.12.*' and platform_machine != 's390x'", + "python_full_version == '3.12.*' and platform_machine == 's390x'", + "python_full_version == '3.11.*' and platform_machine != 's390x'", + "python_full_version == '3.11.*' and platform_machine == 's390x'", +] dependencies = [ - { name = "authlib" }, - { name = "deprecation" }, - { name = "grpcio" }, - { name = "httpx" }, - { name = "protobuf" }, - { name = "pydantic" }, - { name = "validators" }, + { name = "authlib", marker = "(python_full_version >= '3.11' and python_full_version < '3.13') or (python_full_version >= '3.11' and platform_machine == 's390x')" }, + { name = "deprecation", marker = "(python_full_version >= '3.11' and python_full_version < '3.13') or (python_full_version >= '3.11' and platform_machine == 's390x')" }, + { name = "grpcio", version = "1.78.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and python_full_version < '3.13') or (python_full_version >= '3.11' and platform_machine == 's390x')" }, + { name = "httpx", marker = "(python_full_version >= '3.11' and python_full_version < '3.13') or (python_full_version >= '3.11' and platform_machine == 's390x')" }, + { name = "protobuf", marker = "(python_full_version >= '3.11' and python_full_version < '3.13') or (python_full_version >= '3.11' and platform_machine == 's390x')" }, + { name = "pydantic", marker = "(python_full_version >= '3.11' and python_full_version < '3.13') or (python_full_version >= '3.11' and platform_machine == 's390x')" }, + { name = "validators", marker = "(python_full_version >= '3.11' and python_full_version < '3.13') or (python_full_version >= '3.11' and platform_machine == 's390x')" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a8/76/14e07761c5fb7e8573e3cff562e2d9073c65f266db0e67511403d10435b1/weaviate_client-4.18.3.tar.gz", hash = "sha256:9d889246d62be36641a7f2b8cedf5fb665b804d46f7a53ae37e02d297a11f119", size = 783634, upload-time = "2025-12-03T09:38:28.261Z" } wheels = [ From eb9227a5e2f5d44554d0927a7e5bd9cd300c98ae Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 8 Apr 2026 23:03:15 -0700 Subject: [PATCH 15/15] fix: add return type annotation to _iter_as_paren_matches --- lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py b/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py index fc0ae9e64b..4e20b43543 100644 --- a/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py +++ b/lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py @@ -1,3 +1,4 @@ +from collections.abc import Iterator import logging import os import re @@ -77,7 +78,7 @@ _AS_PAREN_RE = re.compile(r"\bAS\s*\(", re.IGNORECASE) -def _iter_as_paren_matches(stmt: str): +def _iter_as_paren_matches(stmt: str) -> Iterator[re.Match[str]]: """Yield regex matches for ``AS\\s*(`` outside of string literals.""" # Build a set of character positions that are inside string literals. in_string: set[int] = set()