diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 75925037..4868c2e9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: - name: Run checks run: make check - unit-test: + build: runs-on: ubuntu-latest steps: - name: Check out code @@ -45,6 +45,37 @@ jobs: with: python-version: '3.11' + - name: Build package + run: make build + + - name: Verify build artifacts + run: | + ls -la dist/ + # Verify both sdist and wheel are created + test -f dist/*.tar.gz || (echo "Source distribution not found" && exit 1) + test -f dist/*.whl || (echo "Wheel not found" && exit 1) + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + + unit-test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ['3.11', '3.14'] + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Set up environment + uses: ./.github/actions/setup-python-env + with: + python-version: ${{ matrix.python-version }} + - name: Install depencies for unit tests run: | uv run pip install openai sentence-transformers torch torchvision torchaudio boto3 litellm voyageai @@ -56,8 +87,9 @@ jobs: else log_level=INFO fi + set -o pipefail uv run pytest tests/unit_tests/ -v --log-cli-level=${log_level} | tee pytest.log - tail -n 1 pytest.log | grep -q 'failed' && exit 1 || exit 0 + tail -n 1 pytest.log | grep '=======' | grep 'passed' |grep -q 'failed' && exit 1 || exit 0 - name: Run integration tests without any test mode run: | @@ -66,8 +98,9 @@ jobs: else log_level=INFO fi + set -o pipefail uv run pytest tests/integration_tests/ -v --log-cli-level=${log_level} -k "not server and not embedded and not oceanbase" | tee pytest.log - tail -n 1 pytest.log | grep -q 'failed' && exit 1 || exit 0 + tail -n 1 pytest.log | grep '=======' | grep 'passed' | grep -q 'failed' && exit 1 || exit 0 integration-test: runs-on: ubuntu-latest @@ -128,5 +161,6 @@ jobs: else log_level=INFO fi + set -o pipefail uv run pytest tests/integration_tests/ -v --log-cli-level=${log_level} -k "${{ matrix.test_mode }}" | tee pytest.log - tail -n 1 pytest.log | grep -q 'failed' && exit 1 || exit 0 + tail -n 1 pytest.log | grep '=======' | grep 'passed' | grep -q 'failed' && exit 1 || exit 0 diff --git a/Makefile b/Makefile index 6b6e5ae4..0885a3c6 100644 --- a/Makefile +++ b/Makefile @@ -34,10 +34,20 @@ test-integration-embedded: ## Run embedded integration tests @$(UV) run pytest tests/integration_tests/ -v --log-cli-level=INFO -k embedded .PHONY: docs -docs: ## Build documentation +docs: ## Build documentation (single version) @echo ">> Building documentation" @$(UV) run sphinx-build -b html docs docs/_build/html +.PHONY: docs-multiversion +docs-multiversion: ## Build multi-version documentation + @echo ">> Building multi-version documentation" + @bash docs/build_multiversion.sh + +.PHONY: docs-serve +docs-serve: ## Serve documentation with auto-reload + @echo ">> Starting documentation server" + @$(UV) run sphinx-autobuild docs docs/_build/html --host 127.0.0.1 --port 8000 + .PHONY: build build: ## Build package @echo ">> Building package" diff --git a/README.md b/README.md index 5a2a6fb4..dc2edd6f 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,8 @@ To achieve the above design goals, this SDK follows the following design princip 6. [DQL Operations](#5-dql-operations) 7. [Embedding Functions](#6-embedding-functions) 8. [RAG Demo](#rag-demo) -9. [Testing](#testing) +9. [Development](#development) +10. [Testing](#testing) ## Installation @@ -1156,31 +1157,75 @@ The demo supports three embedding modes: For detailed instructions, see [demo/rag/README.md](demo/rag/README.md). -## Testing +## Development + +This project uses [uv](https://docs.astral.sh/uv/) as the package manager with [pdm-backend](https://pdm-backend.fming.dev/) as the build backend. All common development tasks are unified through the `Makefile`. + +### Prerequisites + +Install uv: ```bash -# Run all tests (unit + integration) -python3 -m pytest -v +# macOS/Linux +curl -LsSf https://astral.sh/uv/install.sh | sh + +# Windows +powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" -# Run tests with log output -python3 -m pytest -v -s +# Or via pip +pip install uv +``` + +### Setup Development Environment + +```bash +# Clone the repository +git clone https://github.com/oceanbase/pyseekdb.git +cd pyseekdb + +# Install dependencies and pre-commit hooks +make install +``` -# Run unit tests only -python3 -m pytest tests/unit_tests/ -v +### Make Targets + +Run `make help` to see all available targets: + +```bash +make help # Show all available targets +make install # Install dependencies and pre-commit hooks +make check # Run code quality tools (lint, format check) +make test # Run unit tests +make test-integration-embedded # Run embedded integration tests +make build # Build the package +make docs # Build documentation +make clean # Clean build artifacts +``` + +### Build Artifacts + +After running `make build`, the distribution files will be in the `dist/` directory: +- `pyseekdb-.tar.gz` - Source distribution +- `pyseekdb--py3-none-any.whl` - Wheel distribution + +## Testing + +```bash +# Run unit tests +make test -# Run integration tests only -python3 -m pytest tests/integration_tests/ -v +# Run embedded integration tests +make test-integration-embedded -# Run integration tests for specific mode -python3 -m pytest tests/integration_tests/ -v -k "embedded" # embedded mode -python3 -m pytest tests/integration_tests/ -v -k "server" # server mode (requires seekdb server) -python3 -m pytest tests/integration_tests/ -v -k "oceanbase" # oceanbase mode (requires OceanBase) +# Run specific tests with uv run +uv run pytest tests/integration_tests/ -v -k "server" # server mode (requires seekdb server) +uv run pytest tests/integration_tests/ -v -k "oceanbase" # oceanbase mode (requires OceanBase) # Run specific test file -python3 -m pytest tests/integration_tests/test_collection_query.py -v +uv run pytest tests/integration_tests/test_collection_query.py -v # Run specific test function -python3 -m pytest tests/integration_tests/test_collection_query.py::TestCollectionQuery::test_collection_query -v +uv run pytest tests/integration_tests/test_collection_query.py::TestCollectionQuery::test_collection_query -v ``` ## License diff --git a/docs/_static/custom.css b/docs/_static/custom.css index e69de29b..853148d2 100644 --- a/docs/_static/custom.css +++ b/docs/_static/custom.css @@ -0,0 +1,66 @@ +/* 版本选择器下拉框样式 - 放在左侧导航栏搜索框下方 */ +.version-selector-dropdown { + display: flex; + align-items: center; + gap: 8px; + margin-top: 8px; + border-radius: 50px; +} + +.version-selector-dropdown label { + font-size: 13px; + font-weight: 600; + color: #fff; + margin: 0; + white-space: nowrap; +} + +.version-selector-dropdown select { + flex: 1; + padding: 6px 10px; + font-size: 13px; + color: #404040; + background-color: #fff; + border: 1px solid #d0d0d0; + border-radius: 4px; + cursor: pointer; + outline: none; + transition: all 0.2s ease; +} + +.version-selector-dropdown select:hover { + border-color: #2980b9; +} + +.version-selector-dropdown select:focus { + border-color: #2980b9; + box-shadow: 0 0 0 2px rgba(41, 128, 185, 0.1); +} + +/* RTD 主题版本横幅样式 - 隐藏左下角的版本选择器 */ +.rst-versions { + display: none !important; +} + +/* 版本警告横幅 */ +.version-warning { + padding: 10px 15px; + margin-bottom: 20px; + background-color: #fff3cd; + border: 1px solid #ffeaa7; + border-radius: 4px; + color: #856404; +} + +.version-warning strong { + font-weight: bold; +} + +.version-warning a { + color: #856404; + text-decoration: underline; +} + +.version-warning a:hover { + color: #533f03; +} diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html new file mode 100644 index 00000000..2bdb8d8c --- /dev/null +++ b/docs/_templates/layout.html @@ -0,0 +1,18 @@ +{% extends "!layout.html" %} + +{# 在左侧导航栏的搜索框下方插入版本选择器 #} +{% block sidebartitle %} + {{ super() }} + {% if versions %} +
+ + +
+ {% endif %} +{% endblock %} diff --git a/docs/build_multiversion.sh b/docs/build_multiversion.sh new file mode 100755 index 00000000..9abd9c90 --- /dev/null +++ b/docs/build_multiversion.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# 构建多版本文档的脚本 + +set -e + +# 颜色输出 +GREEN='\033[0;32m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${BLUE}开始构建多版本文档...${NC}" + +# 确保在项目根目录 +cd "$(dirname "$0")/.." + +# 激活虚拟环境(如果存在) +if [ -d ".venv" ]; then + echo -e "${GREEN}激活虚拟环境...${NC}" + source .venv/bin/activate +fi + +# 清理旧的构建文件 +echo -e "${GREEN}清理旧的构建文件...${NC}" +rm -rf docs/_build/html + +# 使用 sphinx-multiversion 构建文档 +echo -e "${GREEN}使用 sphinx-multiversion 构建文档...${NC}" +sphinx-multiversion docs docs/_build/html + +# 创建重定向到最新版本的 index.html +echo -e "${GREEN}创建重定向页面...${NC}" +cat > docs/_build/html/index.html << 'EOF' + + + + Redirecting to latest version + + + + + +

Redirecting to latest version...

+ + +EOF + +echo -e "${BLUE}多版本文档构建完成!${NC}" +echo -e "${GREEN}文档位置: docs/_build/html${NC}" +echo -e "${GREEN}可以使用以下命令启动本地服务器查看:${NC}" +echo -e " cd docs/_build/html && python -m http.server 8000" diff --git a/docs/conf.py b/docs/conf.py index 1b1be534..75897341 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -26,6 +26,7 @@ "sphinx.ext.viewcode", "sphinx.ext.napoleon", "myst_parser", + "sphinx_multiversion", ] # Enable Markdown in docstrings @@ -59,3 +60,25 @@ # HTML theme html_theme = "sphinx_rtd_theme" html_static_path = ["_static"] +html_css_files = [ + "custom.css", +] + +# Sphinx-multiversion configuration +# 配置要包含的分支和标签 +smv_tag_whitelist = r"^v\d+\.\d+\.\d+$" # 匹配 v1.0.0 格式的标签 +smv_branch_whitelist = r"^(main|develop)$" # 包含 main 和 develop 分支 +smv_remote_whitelist = r"^origin$" # 只使用 origin 远程仓库 +smv_released_pattern = r"^refs/tags/.*$" # 标记已发布的版本 + +# 自定义模板路径 +templates_path = ["_templates"] + +# 版本横幅配置 +html_context = { + "display_github": True, + "github_user": "oceanbase", + "github_repo": "pyseekdb", + "github_version": "develop", + "conf_py_path": "/docs/", +} diff --git a/pyproject.toml b/pyproject.toml index 38406b6a..f519f93d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,17 +5,18 @@ description = "A unified Python client for seekdb that supports embedded, server readme = "README.md" license = { text = "Apache-2.0" } authors = [{ name = "OceanBase", email = "open_oceanbase@oceanbase.com" }] -requires-python = ">=3.11,<3.14" +requires-python = ">=3.11,<4" classifiers = [ "Development Status :: 4 - Beta", ] dependencies = [ "pymysql>=1.1.1", "pylibseekdb; sys_platform == \"linux\"", - "onnxruntime>=1.19.0", - "tokenizers>=0.15.0", - "httpx", - "tqdm", + "onnxruntime>=1.19.0; python_version < \"3.14\"", + "tokenizers>=0.15.0; python_version < \"3.14\"", + "httpx; python_version < \"3.14\"", + "tqdm; python_version < \"3.14\"", + "sentence-transformers; python_version >= \"3.14\"", "tenacity", "numpy>=1.26", ] @@ -100,6 +101,9 @@ ignore = [ [tool.ruff.format] preview = true +[project.scripts] +pyseekdb = "pyseekdb.cli:main" + [build-system] requires = ["pdm-backend"] build-backend = "pdm.backend" diff --git a/src/pyseekdb/__init__.py b/src/pyseekdb/__init__.py index fabcaf6b..86c10492 100644 --- a/src/pyseekdb/__init__.py +++ b/src/pyseekdb/__init__.py @@ -60,10 +60,11 @@ import importlib.metadata -# Note: pylibseekdb built with ABI=0 and onnxruntime built with ABI=1, so there's a conflict between the two libraries. +# Note: pylibseekdb built with ABI=0 and torch built with ABI=1, so there's a conflict between the two libraries. # pylibseekdb is built both with ABI=0 and the -Bsymbolic flag, so we can load libraries with ABI=1 first # and then pylibseekdb to avoid these conflicts. -import onnxruntime # noqa: F401 +if importlib.util.find_spec("torch"): + import torch # noqa: F401 from .client import ( AdminAPI, diff --git a/src/pyseekdb/cli/__init__.py b/src/pyseekdb/cli/__init__.py new file mode 100644 index 00000000..f9062630 --- /dev/null +++ b/src/pyseekdb/cli/__init__.py @@ -0,0 +1,14 @@ +""" +pyseekdb CLI - Debug and manage seekdb collections/databases. + +Usage: + pyseekdb [OPTIONS] db list|create|delete ... + pyseekdb [OPTIONS] collections list|create|delete|info ... + pyseekdb [OPTIONS] sql "SELECT ..." + pyseekdb [OPTIONS] query --text "..." [--n 5] + pyseekdb [OPTIONS] get [--limit 10] [--ids id1,id2] +""" + +from .main import main + +__all__ = ["main"] diff --git a/src/pyseekdb/cli/main.py b/src/pyseekdb/cli/main.py new file mode 100644 index 00000000..39a7b993 --- /dev/null +++ b/src/pyseekdb/cli/main.py @@ -0,0 +1,400 @@ +""" +CLI main: argument parsing, connection factory, and subcommand dispatch. +""" + +import argparse +import json +import os +import sys +from typing import Any + +from .. import AdminClient, Client +from ..client.admin_client import _AdminClientProxy, _ClientProxy +from ..client.configuration import HNSWConfiguration + + +def _add_connection_args(parser: argparse.ArgumentParser) -> None: + """Add global connection options (embedded vs server).""" + g = parser.add_argument_group("connection (choose one)") + g.add_argument( + "--path", + metavar="DIR", + default=None, + help="Embedded mode: path to seekdb data directory (default: seekdb.db in cwd)", + ) + g.add_argument("--host", default=None, help="Server mode: host (e.g. localhost)") + g.add_argument("--port", type=int, default=2881, help="Server mode: port (default: 2881)") + g.add_argument("--tenant", default="sys", help="Server mode: tenant (default: sys)") + g.add_argument("--database", "-d", default="test", help="Database name (default: test)") + g.add_argument("--user", default="root", help="Server mode: user (default: root)") + g.add_argument( + "--password", + "-p", + default=None, + help="Server mode: password (or set SEEKDB_PASSWORD)", + ) + parser.add_argument( + "-o", + "--output", + choices=["table", "json"], + default="table", + help="Output format (default: table)", + ) + + +def _make_client(args: argparse.Namespace) -> _ClientProxy: + """Build Client from parsed args (for collection operations).""" + password = args.password or os.environ.get("SEEKDB_PASSWORD", "") + return Client( + path=args.path, + host=args.host, + port=args.port, + tenant=args.tenant, + database=args.database, + user=args.user, + password=password, + ) + + +def _make_admin(args: argparse.Namespace) -> _AdminClientProxy: + """Build AdminClient from parsed args (for db operations).""" + password = args.password or os.environ.get("SEEKDB_PASSWORD", "") + return AdminClient( + path=args.path, + host=args.host, + port=args.port, + tenant=args.tenant, + user=args.user, + password=password, + ) + + +def _execute_sql(client: _ClientProxy, sql: str) -> Any: + """Run raw SQL using the underlying server (for debug).""" + server = client._server + return server._execute(sql) + + +def _print_table(rows: list[dict[str, Any]] | None, columns: list[str] | None = None) -> None: + """Print rows as a simple text table.""" + if not rows: + print("(0 rows)") + return + if columns is None: + columns = list(rows[0].keys()) if rows else [] + col_widths = [max(len(str(c)), 3) for c in columns] + for r in rows: + for i, c in enumerate(columns): + val = r.get(c, "") + if isinstance(val, (dict, list)): + val = json.dumps(val, ensure_ascii=False)[:40] + col_widths[i] = max(col_widths[i], min(len(str(val)), 50)) + fmt = " ".join(f"%-{w}s" for w in col_widths) + print(fmt % tuple(columns)) + print("-" * (sum(col_widths) + 2 * (len(columns) - 1))) + for r in rows: + cells = [] + for c in columns: + v = r.get(c, "") + if isinstance(v, (dict, list)): + v = json.dumps(v, ensure_ascii=False)[:40] + s = str(v) + if len(s) > 50: + s = s[:47] + "..." + cells.append(s) + print(fmt % tuple(cells)) + + +def _print_json(obj: Any) -> None: + """Print object as JSON.""" + if hasattr(obj, "__iter__") and not isinstance(obj, (str, dict)): + obj = list(obj) + print(json.dumps(obj, indent=2, ensure_ascii=False, default=str)) + + +# ---------- Commands ---------- + + +def cmd_db_list(args: argparse.Namespace) -> int: + """List databases.""" + admin = _make_admin(args) + try: + dbs = admin.list_databases() + rows = [{"name": d.name, "tenant": d.tenant or ""} for d in dbs] + if args.output == "json": + _print_json(rows) + else: + _print_table(rows, ["name", "tenant"]) + return 0 + finally: + admin._server._cleanup() + + +def cmd_db_create(args: argparse.Namespace) -> int: + """Create database.""" + admin = _make_admin(args) + try: + admin.create_database(args.name, tenant=args.tenant) + print(f"Created database: {args.name}") + return 0 + finally: + admin._server._cleanup() + + +def cmd_db_delete(args: argparse.Namespace) -> int: + """Delete database.""" + admin = _make_admin(args) + try: + admin.delete_database(args.name, tenant=args.tenant) + print(f"Deleted database: {args.name}") + return 0 + finally: + admin._server._cleanup() + + +def cmd_collections_list(args: argparse.Namespace) -> int: + """List collections.""" + client = _make_client(args) + try: + colls = client.list_collections() + rows = [ + { + "name": c.name, + "dimension": c.dimension or "", + "distance": c.distance or "", + } + for c in colls + ] + if args.output == "json": + _print_json(rows) + else: + _print_table(rows, ["name", "dimension", "distance"]) + return 0 + finally: + client._server._cleanup() + + +def cmd_collections_create(args: argparse.Namespace) -> int: + """Create collection.""" + client = _make_client(args) + try: + config = HNSWConfiguration(dimension=args.dimension) if args.dimension else None + client.create_collection(args.name, configuration=config) + print(f"Created collection: {args.name}") + return 0 + finally: + client._server._cleanup() + + +def cmd_collections_delete(args: argparse.Namespace) -> int: + """Delete collection.""" + client = _make_client(args) + try: + client.delete_collection(args.name) + print(f"Deleted collection: {args.name}") + return 0 + finally: + client._server._cleanup() + + +def cmd_collections_info(args: argparse.Namespace) -> int: + """Show collection info (schema, count, sample).""" + client = _make_client(args) + try: + if not client.has_collection(args.name): + print(f"Collection not found: {args.name}", file=sys.stderr) + return 1 + coll = client.get_collection(args.name) + info = { + "name": coll.name, + "dimension": coll.dimension, + "distance": coll.distance, + "metadata": coll.metadata, + "count": coll.count(), + } + if args.output == "json": + sample = coll.peek(limit=args.sample) if args.sample and info["count"] > 0 else {} + payload = {"info": info, "sample": sample} + _print_json(payload) + else: + print(f"Name: {info['name']}") + print(f"Dimension: {info['dimension']}") + print(f"Distance: {info['distance']}") + print(f"Count: {info['count']}") + if info.get("metadata"): + print(f"Metadata: {info['metadata']}") + if args.sample and info["count"] > 0: + sample = coll.peek(limit=args.sample) + print("\nSample (peek):") + for i in range(len(sample["ids"])): + print(f" id={sample['ids'][i]}") + if sample.get("documents"): + doc = sample["documents"][i] + print(f" document: {str(doc)[:80]}...") + if sample.get("metadatas"): + print(f" metadata: {sample['metadatas'][i]}") + return 0 + finally: + client._server._cleanup() + + +def cmd_sql(args: argparse.Namespace) -> int: + """Execute raw SQL (debug).""" + client = _make_client(args) + try: + result = _execute_sql(client, args.sql) + if result is None: + print("OK") + return 0 + if args.output == "json": + # result may be list of tuples or list of dicts + if not result: + _print_json([]) + elif hasattr(result[0], "keys"): + _print_json(result) + else: + _print_json([list(r) for r in result]) + else: + if not result: + print("(0 rows)") + elif hasattr(result[0], "keys"): + _print_table(result) + else: + cols = [f"col_{i}" for i in range(len(result[0]))] + _print_table([dict(zip(cols, r, strict=True)) for r in result], cols) + return 0 + finally: + client._server._cleanup() + + +def cmd_query(args: argparse.Namespace) -> int: + """Query collection by text or embedding.""" + client = _make_client(args) + try: + if not client.has_collection(args.collection): + print(f"Collection not found: {args.collection}", file=sys.stderr) + return 1 + coll = client.get_collection(args.collection) + if args.text: + res = coll.query( + query_texts=[args.text], n_results=args.n, include=args.include or ["documents", "metadatas"] + ) + else: + print("Specify --text for query by text (embedding not supported in CLI)", file=sys.stderr) + return 1 + if args.output == "json": + _print_json(res) + else: + ids = res.get("ids", [[]])[0] + docs = res.get("documents", [[]])[0] if res.get("documents") else [] + metas = res.get("metadatas", [[]])[0] if res.get("metadatas") else [] + dists = res.get("distances", [[]])[0] if res.get("distances") else [] + for i in range(len(ids)): + print(f"id={ids[i]} distance={dists[i] if i < len(dists) else ''}") + if i < len(docs): + print(f" document: {str(docs[i])[:80]}...") + if i < len(metas): + print(f" metadata: {metas[i]}") + return 0 + finally: + client._server._cleanup() + + +def cmd_get(args: argparse.Namespace) -> int: + """Get documents from collection by ids or limit.""" + client = _make_client(args) + try: + if not client.has_collection(args.collection): + print(f"Collection not found: {args.collection}", file=sys.stderr) + return 1 + coll = client.get_collection(args.collection) + ids = None + if args.ids: + ids = [x.strip() for x in args.ids.split(",") if x.strip()] + res = coll.get( + ids=ids, + limit=args.limit, + include=args.include or ["documents", "metadatas"], + ) + if args.output == "json": + _print_json(res) + else: + for i in range(len(res.get("ids", []))): + print(f"id={res['ids'][i]}") + if res.get("documents") and i < len(res["documents"]): + print(f" document: {str(res['documents'][i])[:80]}...") + if res.get("metadatas") and i < len(res["metadatas"]): + print(f" metadata: {res['metadatas'][i]}") + return 0 + finally: + client._server._cleanup() + + +def main(argv: list[str] | None = None) -> int: + """CLI entry point.""" + parser = argparse.ArgumentParser( + prog="pyseekdb", + description="seekdb CLI: debug and manage collections/databases (see spec.md).", + ) + _add_connection_args(parser) + sub = parser.add_subparsers(dest="command", required=True, help="command") + + # db + db = sub.add_parser("db", help="Database (admin) operations") + db_sub = db.add_subparsers(dest="db_command", required=True) + db_list = db_sub.add_parser("list", help="List databases") + db_list.set_defaults(func=cmd_db_list) + db_create = db_sub.add_parser("create", help="Create database") + db_create.add_argument("name", help="Database name") + db_create.set_defaults(func=cmd_db_create) + db_delete = db_sub.add_parser("delete", help="Delete database") + db_delete.add_argument("name", help="Database name") + db_delete.set_defaults(func=cmd_db_delete) + + # collections (and alias coll) + def _add_collection_subparsers(parent: argparse.ArgumentParser) -> None: + csub = parent.add_subparsers(dest="coll_command", required=True) + csub.add_parser("list", help="List collections").set_defaults(func=cmd_collections_list) + create = csub.add_parser("create", help="Create collection") + create.add_argument("name", help="Collection name") + create.add_argument("--dimension", type=int, default=None, help="Vector dimension") + create.set_defaults(func=cmd_collections_create) + delete = csub.add_parser("delete", help="Delete collection") + delete.add_argument("name", help="Collection name") + delete.set_defaults(func=cmd_collections_delete) + info = csub.add_parser("info", help="Show collection info and optional sample") + info.add_argument("name", help="Collection name") + info.add_argument("--sample", type=int, default=0, metavar="N", help="Peek first N rows") + info.set_defaults(func=cmd_collections_info) + + _add_collection_subparsers(sub.add_parser("collections", help="Collection operations")) + _add_collection_subparsers(sub.add_parser("coll", help="Alias for collections")) + + # sql + sql_p = sub.add_parser("sql", help="Execute raw SQL (debug)") + _add_connection_args(sql_p) + sql_p.add_argument("sql", help="SQL statement") + sql_p.set_defaults(func=cmd_sql) + + # query + query_p = sub.add_parser("query", help="Query collection by text") + _add_connection_args(query_p) + query_p.add_argument("collection", help="Collection name") + query_p.add_argument( + "--text", "-t", required=True, help="Query text (will be embedded if collection has embedding)" + ) + query_p.add_argument("--n", type=int, default=10, help="Number of results (default: 10)") + query_p.add_argument("--include", nargs="+", default=None, help="Include fields: documents, metadatas, embeddings") + query_p.set_defaults(func=cmd_query) + + # get + get_p = sub.add_parser("get", help="Get documents from collection") + _add_connection_args(get_p) + get_p.add_argument("collection", help="Collection name") + get_p.add_argument("--ids", default=None, help="Comma-separated IDs") + get_p.add_argument("--limit", type=int, default=10, help="Max rows (default: 10)") + get_p.add_argument("--include", nargs="+", default=None, help="Include fields") + get_p.set_defaults(func=cmd_get) + + args = parser.parse_args(argv) + return args.func(args) diff --git a/src/pyseekdb/client/client_base.py b/src/pyseekdb/client/client_base.py index bd0aa89d..18cd4166 100644 --- a/src/pyseekdb/client/client_base.py +++ b/src/pyseekdb/client/client_base.py @@ -210,14 +210,22 @@ def create_collection( @abstractmethod def get_collection(self, name: str, embedding_function: EmbeddingFunctionParam = _NOT_PROVIDED) -> "Collection": - """ - Get collection object + """Get an existing collection. Args: - name: Collection name - embedding_function: Embedding function to convert documents to embeddings. - Defaults to DefaultEmbeddingFunction. - If explicitly set to None, collection will not have an embedding function. + name: The name of the collection to retrieve. + embedding_function: The embedding function to use. If not provided, + it will try to load the function used when creating the collection. + If explicitly set to None, no embedding function will be used. + + Returns: + The ``Collection`` object. + + Raises: + ValueError: If the collection does not exist. + + Examples: + >>> collection = client.get_collection("my_collection") """ pass @@ -512,85 +520,48 @@ def create_collection( # noqa: C901 embedding_function: EmbeddingFunctionParam = _NOT_PROVIDED, **kwargs, ) -> "Collection": - """ - Create a collection (user-facing API) + """Create a new collection. Args: - name: Collection name - configuration: Index configuration (Configuration or HNSWConfiguration). - If not provided, uses default configuration (dimension=384, distance='cosine', analyzer='ik'). - If explicitly set to None, will try to calculate dimension from embedding_function. - If embedding_function is also None, will raise an error. - For backward compatibility, HNSWConfiguration is still accepted. - Configuration can include fulltext index configuration. - embedding_function: Embedding function to convert documents to embeddings. - Defaults to DefaultEmbeddingFunction. - If explicitly set to None, collection will not have an embedding function. - If provided, the actual dimension will be calculated by calling - embedding_function.__call__("seekdb"), and this dimension will be used - to create the table. If configuration.dimension is set and doesn't match - the calculated dimension, a ValueError will be raised. - **kwargs: Additional parameters + name: The name of the collection to create. Must contain only alphanumeric + characters or underscores. + configuration: Index configuration. Defaults to None (uses HNSW with + Cosine distance and dimension 384). Can be a ``Configuration`` or + ``HNSWConfiguration`` object. If set to None, the dimension will be + inferred from the embedding function. + embedding_function: The embedding function to use for this collection. + Defaults to ``DefaultEmbeddingFunction`` (all-MiniLM-L6-v2). If set to None, + no embedding function will be used (embeddings must be provided manually). + **kwargs: Additional parameters for collection creation. Returns: - Collection object + The created ``Collection`` object. Raises: - ValueError: If configuration is explicitly set to None and embedding_function is also None - (cannot determine dimension), or if embedding_function is provided and - configuration.dimension doesn't match the calculated dimension from embedding_function - TypeError: If configuration is not None, Configuration, or HNSWConfiguration + ValueError: If the collection name is invalid, already exists, or if the + configuration/embedding function combination is invalid (e.g., dimension mismatch). + TypeError: If the configuration object is of an invalid type. Examples: - .. code-block:: python - # Using default configuration and default embedding function (defaults to IK parser) - collection = client.create_collection('my_collection') - - .. code-block:: python - # Using custom embedding function (dimension will be calculated automatically) - from pyseekdb import DefaultEmbeddingFunction - ef = DefaultEmbeddingFunction(model_name='all-MiniLM-L6-v2') - config = HNSWConfiguration(dimension=384, distance='cosine') # Must match EF dimension - collection = client.create_collection( - 'my_collection', - configuration=config, - embedding_function=ef - ) + Create a collection with default settings: - .. code-block:: python - # Using Configuration wrapper with IK parser (default) - from pyseekdb import Configuration, HNSWConfiguration, FulltextIndexConfig - config = Configuration( - hnsw=HNSWConfiguration(dimension=384, distance='cosine'), - fulltext_config=FulltextIndexConfig(analyzer='ik') - ) - collection = client.create_collection('my_collection', configuration=config, embedding_function=ef) - - .. code-block:: python - # Using Space parser - config = Configuration( - hnsw=HNSWConfiguration(dimension=384, distance='cosine'), - fulltext_config=FulltextIndexConfig(analyzer='space') - ) - collection = client.create_collection('my_collection', configuration=config, embedding_function=ef) + >>> client.create_collection("my_collection") - .. code-block:: python - # Using Ngram parser with parameters - config = Configuration( - hnsw=HNSWConfiguration(dimension=384, distance='cosine'), - fulltext_config=FulltextIndexConfig(analyzer='ngram', properties={'size': 2}) - ) - collection = client.create_collection('my_collection', configuration=config, embedding_function=ef) + Create a collection with a custom embedding function: - .. code-block:: python - # Explicitly set configuration=None, use embedding function to determine dimension - collection = client.create_collection('my_collection', configuration=None, embedding_function=ef) + >>> from pyseekdb import DefaultEmbeddingFunction + >>> ef = DefaultEmbeddingFunction(model_name="all-MiniLM-L6-v2") + >>> collection = client.create_collection("my_docs", embedding_function=ef) - .. code-block:: python - # Explicitly disable embedding function (use configuration dimension) - config = HNSWConfiguration(dimension=128, distance='cosine') - collection = client.create_collection('my_collection', configuration=config, embedding_function=None) + Create a collection with specific configuration: + >>> from pyseekdb import HNSWConfiguration + >>> config = HNSWConfiguration(dimension=128, distance="l2") + >>> collection = client.create_collection( + ... "custom_config", + ... configuration=config, + ... embedding_function=None + ... ) """ _validate_collection_name(name) if self.has_collection(name): @@ -982,11 +953,16 @@ def _get_collection_v1(self, name: str, embedding_function: EmbeddingFunctionPar return Collection(client=self, name=name, embedding_function=embedding_function, **metadata) def delete_collection(self, name: str) -> None: - """ - Delete a collection (user-facing API) + """Delete a collection. Args: - name: Collection name + name: The name of the collection to delete. + + Raises: + ValueError: If the collection does not exist. + + Examples: + >>> client.delete_collection("my_collection") """ try: self._delete_collection_v2(name) @@ -1032,11 +1008,15 @@ def _delete_collection_v1(self, name: str) -> None: self._execute(f"DROP TABLE IF EXISTS `{table_name}`") def list_collections(self) -> list["Collection"]: - """ - List all collections (user-facing API) + """List all collections in the database. Returns: - List of Collection objects + A list of ``Collection`` objects. + + Examples: + >>> collections = client.list_collections() + >>> for col in collections: + ... print(col.name) """ collections = self._list_collections_v1() collections.extend(self._list_collections_v2()) @@ -1142,25 +1122,30 @@ def _list_collections_v1(self) -> list["Collection"]: return collections def count_collection(self) -> int: - """ - Count the number of collections in the current database + """Count the total number of collections. Returns: - Number of collections + The number of collections. Examples: - count = client.count_collection() - print(f"Database has {count} collections") + >>> count = client.count_collection() + >>> print(f"Database has {count} collections") """ collections = self.list_collections() return len(collections) def has_collection(self, name: str) -> bool: - """ - Check if a collection exists (user-facing API) + """Check if a collection exists. Args: - name: Collection name + name: The name of the collection to check. + + Returns: + True if the collection exists, False otherwise. + + Examples: + >>> if client.has_collection("my_collection"): + ... print("Collection exists!") """ return self._has_collection_v2(name) or self._has_collection_v1(name) @@ -1211,29 +1196,27 @@ def get_or_create_collection( embedding_function: EmbeddingFunctionParam = _NOT_PROVIDED, **kwargs, ) -> "Collection": - """ - Get an existing collection or create it if it doesn't exist (user-facing API) + """Get a collection if it exists, otherwise create it. Args: - name: Collection name - configuration: Configuration (HNSWConfiguration is accepted for backward compatibility) - Please refer to create_collection for more details. - embedding_function: Embedding function to convert documents to embeddings. - Defaults to DefaultEmbeddingFunction. - If explicitly set to None, collection will not have an embedding function. - If provided when creating a new collection, the actual dimension will be - calculated by calling embedding_function.__call__("seekdb"), and this - dimension will be used to create the table. If configuration.dimension is - set and doesn't match the calculated dimension, a ValueError will be raised. - **kwargs: Additional parameters for create_collection + name: The name of the collection. + configuration: Index configuration. Defaults to None (uses HNSW with + Cosine distance and dimension 384). Can be a ``Configuration`` or + ``HNSWConfiguration`` object. If set to None, the dimension will be + inferred from the embedding function. + embedding_function: The embedding function to use for this collection. + Defaults to ``DefaultEmbeddingFunction`` (all-MiniLM-L6-v2). If set to None, + no embedding function will be used (embeddings must be provided manually). + **kwargs: Additional parameters passed to ``create_collection`` if the collection is created. Returns: - Collection object + The existing or newly created ``Collection`` object. Raises: - ValueError: If creating a new collection and configuration is explicitly set to None and - embedding_function is also None (cannot determine dimension), or if embedding_function - is provided and configuration.dimension doesn't match the calculated dimension + ValueError: If the configuration/embedding function combination is invalid (e.g., dimension mismatch). + + Examples: + >>> collection = client.get_or_create_collection("my_collection") """ # Validate collection name before any database interaction _validate_collection_name(name) @@ -1393,10 +1376,10 @@ def _collection_add( # noqa: C901 elif documents: # embeddings not provided but documents are provided, check for embedding_function if embedding_function is not None: - logger.info(f"Generating embeddings for {len(documents)} documents using embedding function") + logger.debug(f"Generating embeddings for {len(documents)} documents using embedding function") try: embeddings = embedding_function(documents) - logger.info(f"✅ Successfully generated {len(embeddings)} embeddings") + logger.debug(f"✅ Successfully generated {len(embeddings)} embeddings") except Exception as e: logger.exception("Failed to generate embeddings") raise ValueError(f"Failed to generate embeddings from documents: {e}") from e @@ -1489,7 +1472,7 @@ def _collection_add( # noqa: C901 logger.debug(f"Executing SQL: {sql}") self._execute(sql) - logger.info(f"✅ Successfully added {num_items} item(s) to collection '{collection_name}'") + logger.debug(f"✅ Successfully added {num_items} item(s) to collection '{collection_name}'") def _collection_update( # noqa: C901 self, @@ -1550,10 +1533,10 @@ def _collection_update( # noqa: C901 elif documents: # embeddings not provided but documents are provided, check for embedding_function if embedding_function is not None: - logger.info(f"Generating embeddings for {len(documents)} documents using embedding function") + logger.debug(f"Generating embeddings for {len(documents)} documents using embedding function") try: embeddings = embedding_function(documents) - logger.info(f"✅ Successfully generated {len(embeddings)} embeddings") + logger.debug(f"✅ Successfully generated {len(embeddings)} embeddings") except Exception as e: logger.exception("Failed to generate embeddings") raise ValueError(f"Failed to generate embeddings from documents: {e}") from e @@ -1692,7 +1675,7 @@ def _collection_upsert( # noqa: C901 elif documents: # embeddings not provided but documents are provided, check for embedding_function if embedding_function is not None: - logger.info(f"Generating embeddings for {len(documents)} documents using embedding function") + logger.debug(f"Generating embeddings for {len(documents)} documents using embedding function") try: embeddings = embedding_function(documents) logger.info(f"✅ Successfully generated {len(embeddings)} embeddings") @@ -1835,7 +1818,7 @@ def _collection_delete( where_document: Filter condition on documents (optional) **kwargs: Additional parameters """ - logger.info(f"Deleting data from collection '{collection_name}'") + logger.debug(f"Deleting data from collection '{collection_name}'") # Validate that at least one filter is provided if not ids and not where and not where_document: @@ -1866,7 +1849,7 @@ def _collection_delete( use_context_manager = self._use_context_manager_for_cursor() self._execute_query_with_cursor(conn, sql, params, use_context_manager) - logger.info(f"✅ Successfully deleted data from collection '{collection_name}'") + logger.debug(f"✅ Successfully deleted data from collection '{collection_name}'") # -------------------- DQL Operations -------------------- # Note: _collection_query() and _collection_get() are implemented below with common SQL-based logic @@ -2631,7 +2614,15 @@ def _collection_hybrid_search( table_name = CollectionNames.table_name(collection_name) # Build search_parm JSON - search_parm = self._build_search_parm(query, knn, rank, n_results, dimension=dimension, **kwargs) + search_parm = self._build_search_parm( + query, + knn, + rank, + n_results, + include=include, + dimension=dimension, + **kwargs, + ) # Convert search_parm to JSON string search_parm_json = json.dumps(search_parm, ensure_ascii=False) @@ -2684,6 +2675,7 @@ def _build_search_parm( # noqa: C901 knn: dict[str, Any] | list[dict[str, Any]] | None, rank: dict[str, Any] | None, n_results: int, + include: list[str] | None = None, dimension: int | None = None, **kwargs, ) -> dict[str, Any]: @@ -2695,6 +2687,8 @@ def _build_search_parm( # noqa: C901 knn: Vector search configuration dict or list of dicts rank: Ranking configuration dict n_results: Final number of results to return + include: Fields requested by the SDK caller. Used to infer the minimal OceanBase GET_SQL + `_source` allowlist to avoid returning large unused columns (e.g. `embedding`). dimension: Collection dimension for validating query_embeddings (optional) **kwargs: Additional parameters, including: embedding_function: EmbeddingFunction instance to convert query_texts in knn to embeddings. @@ -2739,6 +2733,9 @@ def _build_search_parm( # noqa: C901 if rank: search_parm["rank"] = rank + # Always infer a minimal `_source` allowlist from include to reduce response payload. + search_parm["_source"] = self._build_source_fields(include) + return search_parm def _build_query_expression(self, query: dict[str, Any]) -> dict[str, Any] | None: @@ -3092,23 +3089,26 @@ def _normalize_vectors(raw_embeddings: Any) -> list[list[float]]: return knn_exprs if len(knn_exprs) > 1 else knn_exprs[0] def _build_source_fields(self, include: list[str] | None) -> list[str]: - """Build _source fields list from include parameter""" - if not include: - return ["document", "metadata", "embedding"] - - source_fields = [] - field_mapping = { - "documents": "document", - "metadatas": "metadata", - "embeddings": "embedding", - } + """ + Infer OceanBase GET_SQL `_source` allowlist from include. + """ + if include is None: + requested = {"documents", "metadatas"} + else: + if not isinstance(include, list) or not all(isinstance(item, str) for item in include): + raise TypeError("include must be a List[str] or None") + requested = {item.lower() for item in include} - for field in include: - mapped = field_mapping.get(field.lower(), field) - if mapped not in source_fields: - source_fields.append(mapped) + source = ["_id"] + + if {"documents", "document"} & requested: + source.append("document") + if {"metadatas", "metadata"} & requested: + source.append("metadata") + if {"embeddings", "embedding"} & requested: + source.append("embedding") - return source_fields if source_fields else ["document", "metadata", "embedding"] + return source def _transform_sql_result( # noqa: C901 self, result_rows: list[dict[str, Any]], include: list[str] | None @@ -3267,7 +3267,7 @@ def _collection_count(self, collection_id: str | None, collection_name: str) -> Returns: Item count """ - logger.info(f"Counting items in collection '{collection_name}'") + logger.debug(f"Counting items in collection '{collection_name}'") conn = self._ensure_connection() # Convert collection name to table name diff --git a/src/pyseekdb/client/client_seekdb_embedded.py b/src/pyseekdb/client/client_seekdb_embedded.py index 156656a6..48532580 100644 --- a/src/pyseekdb/client/client_seekdb_embedded.py +++ b/src/pyseekdb/client/client_seekdb_embedded.py @@ -93,7 +93,7 @@ def _cleanup(self): if self._connection is not None: self._connection.close() self._connection = None - logger.info("Connection closed") + logger.info(f"Connection closed: path={self.path}, database={self.database}") def is_connected(self) -> bool: """Check connection status""" diff --git a/src/pyseekdb/client/embedding_function.py b/src/pyseekdb/client/embedding_function.py index 58347fc3..0c7f249c 100644 --- a/src/pyseekdb/client/embedding_function.py +++ b/src/pyseekdb/client/embedding_function.py @@ -5,12 +5,10 @@ for converting text documents to vector embeddings. """ -import contextlib import logging -import os +import sys +import warnings from abc import abstractmethod -from functools import cached_property -from pathlib import Path from typing import ( Any, ClassVar, @@ -20,15 +18,6 @@ runtime_checkable, ) -import httpx -import numpy as np -import numpy.typing as npt - -# Set Hugging Face mirror endpoint for better download speed in China -# Users can override this by setting HF_ENDPOINT environment variable -if "HF_ENDPOINT" not in os.environ: - os.environ["HF_ENDPOINT"] = "https://hf-mirror.com" - logger = logging.getLogger(__name__) # Type variable for input types @@ -152,11 +141,8 @@ class DefaultEmbeddingFunction(EmbeddingFunction[Documents]): >>> print(len(embeddings[0])) # 384 """ - MODEL_NAME = "all-MiniLM-L6-v2" - HF_MODEL_ID = "sentence-transformers/all-MiniLM-L6-v2" # Hugging Face model ID - DOWNLOAD_PATH = Path.home() / ".cache" / "pyseekdb" / "onnx_models" / MODEL_NAME - EXTRACTED_FOLDER_NAME = "onnx" - ARCHIVE_FILENAME = "onnx.tar.gz" + _MODEL_NAME = "all-MiniLM-L6-v2" + _HF_MODEL_ID = "sentence-transformers/all-MiniLM-L6-v2" # Hugging Face model ID _DIMENSION = 384 # all-MiniLM-L6-v2 produces 384-dimensional embeddings def __init__( @@ -168,344 +154,43 @@ def __init__( Initialize the default embedding function. Args: - model_name: Name of the model (currently only 'all-MiniLM-L6-v2' is supported). - Default is 'all-MiniLM-L6-v2' (384 dimensions). - preferred_providers: The preferred ONNX runtime providers. - Defaults to None (uses available providers). + model_name: str = "all-MiniLM-L6-v2", # Deprecated. Will be removed in a future version. + preferred_providers: list[str] | None = None, # Deprecated. Will be removed in a future version. + # The preferred ONNX runtime providers. Defaults to None (uses available providers). """ - if model_name != "all-MiniLM-L6-v2": - raise ValueError(f"Currently only 'all-MiniLM-L6-v2' is supported, got '{model_name}'") - self.model_name = model_name - - # Validate preferred_providers - if preferred_providers and not all(isinstance(i, str) for i in preferred_providers): - raise ValueError("Preferred providers must be a list of strings") - if preferred_providers and len(preferred_providers) != len(set(preferred_providers)): - raise ValueError("Preferred providers must be unique") - - self._preferred_providers = preferred_providers + if model_name != self._MODEL_NAME: + raise ValueError(f"Currently only '{self._MODEL_NAME}' is supported, got '{model_name}'") + if preferred_providers: + warnings.warn( + "preferred_providers is deprecated and will be removed in a future version. " + "Use the preferred_providers argument of OnnxEmbeddingFunction instead.", + DeprecationWarning, + stacklevel=2, + ) + self.model_name = self._MODEL_NAME + if sys.version_info >= (3, 14): + from pyseekdb.utils.embedding_functions.sentence_transformer_embedding_function import ( + SentenceTransformerEmbeddingFunction, + ) - # Import required modules - import onnxruntime as ort_module - import tokenizers - import tqdm + self._backend = SentenceTransformerEmbeddingFunction(model_name=self._MODEL_NAME) + else: + from pyseekdb.utils.embedding_functions import OnnxEmbeddingFunction - self.ort = ort_module - self.tokenizers = tokenizers # Store the module - self.tqdm = tqdm.tqdm + self._backend = OnnxEmbeddingFunction( + model_name=self._MODEL_NAME, + hf_model_id=self._HF_MODEL_ID, + dimension=self._DIMENSION, + preferred_providers=preferred_providers, + ) @property def dimension(self) -> int: - """Get the dimension of embeddings produced by this function""" + """Get the dimension of embeddings produced by this function.""" return self._DIMENSION - def _download(self, url: str, fname: str, chunk_size: int = 8192) -> None: - """ - Download a file from the URL and save it to the file path. - - Args: - url: The URL to download the file from. - fname: The path to save the file to. - chunk_size: The chunk size to use when downloading (default: 8192 for better speed). - """ - logger.info(f"Downloading from {url}") - # Use Client to ensure correct handling of redirects - with httpx.Client(timeout=600.0, follow_redirects=True) as client, client.stream("GET", url) as resp: - resp.raise_for_status() - total = int(resp.headers.get("content-length", 0)) - with ( - open(fname, "wb") as file, - self.tqdm( - desc=os.path.basename(fname), - total=total, - unit="iB", - unit_scale=True, - unit_divisor=1024, - ) as bar, - ): - for data in resp.iter_bytes(chunk_size=chunk_size): - size = file.write(data) - bar.update(size) - - def _get_hf_endpoint(self) -> str: - """Get Hugging Face endpoint URL, using HF_ENDPOINT environment variable if set.""" - return os.environ.get("HF_ENDPOINT", "https://huggingface.co") - - def _download_from_huggingface(self) -> bool: # noqa: C901 - """ - Download model files from Hugging Face (supports mirror acceleration). - - Returns: - True if download successful, False otherwise. - """ - try: - hf_endpoint = self._get_hf_endpoint() - # Remove trailing slash - hf_endpoint = hf_endpoint.rstrip("/") - - # List of files to download - # ONNX model files are in the onnx/ subdirectory, other files in the root directory - files_to_download = { - "onnx/model.onnx": "model.onnx", # ONNX file in onnx subdirectory - "tokenizer.json": "tokenizer.json", - "config.json": "config.json", - "special_tokens_map.json": "special_tokens_map.json", - "tokenizer_config.json": "tokenizer_config.json", - "vocab.txt": "vocab.txt", - } - - extracted_folder = os.path.join(self.DOWNLOAD_PATH, self.EXTRACTED_FOLDER_NAME) - os.makedirs(extracted_folder, exist_ok=True) - - logger.info(f"Downloading model from Hugging Face (endpoint: {hf_endpoint})") - - # Download each file - for hf_filename, local_filename in files_to_download.items(): - local_path = os.path.join(extracted_folder, local_filename) - - # Skip if file already exists - if os.path.exists(local_path): - continue - - # Construct Hugging Face download URL - # Format: https://hf-mirror.com/sentence-transformers/all-MiniLM-L6-v2/resolve/main/onnx/model.onnx - # Or: https://hf-mirror.com/sentence-transformers/all-MiniLM-L6-v2/resolve/main/tokenizer.json - url = f"{hf_endpoint}/{self.HF_MODEL_ID}/resolve/main/{hf_filename}" - - try: - # First check if file exists (HEAD request) - with contextlib.suppress(Exception): - head_resp = httpx.head(url, timeout=10.0, follow_redirects=True) - if head_resp.status_code == 404: - logger.warning(f"File {hf_filename} not found on Hugging Face (404), will try fallback") - return False - - self._download(url, local_path, chunk_size=8192) - logger.info(f"Successfully downloaded {local_filename}") - except httpx.HTTPStatusError as e: - if e.response.status_code == 404: - logger.warning(f"File {hf_filename} not found on Hugging Face (404), will try fallback") - return False - logger.warning(f"HTTP error downloading {hf_filename} from Hugging Face: {e}") - if os.path.exists(local_path): - os.remove(local_path) - return False - except Exception as e: - logger.warning(f"Failed to download {hf_filename} from Hugging Face: {e}") - # If download fails, try to delete partially downloaded file - if os.path.exists(local_path): - os.remove(local_path) - return False - - # Verify critical files exist - if not os.path.exists(os.path.join(extracted_folder, "model.onnx")): - logger.error("model.onnx not found after download") - return False - if not os.path.exists(os.path.join(extracted_folder, "tokenizer.json")): - logger.error("tokenizer.json not found after download") - return False - - logger.info("Successfully downloaded all model files from Hugging Face") - return True # noqa: TRY300 - - except Exception: - logger.exception("Error downloading from Hugging Face") - return False - - def _forward(self, documents: list[str], batch_size: int = 32) -> npt.NDArray[np.float32]: - """ - Generate embeddings for a list of documents. - - Args: - documents: The documents to generate embeddings for. - batch_size: The batch size to use when generating embeddings. - - Returns: - The embeddings for the documents. - """ - all_embeddings = [] - for i in range(0, len(documents), batch_size): - batch = documents[i : i + batch_size] - - # Encode each document separately - encoded = [self.tokenizer.encode(d) for d in batch] - - # Check if any document exceeds the max tokens - for doc_tokens in encoded: - if len(doc_tokens.ids) > self.max_tokens(): - raise ValueError( - f"Document length {len(doc_tokens.ids)} is greater than the max tokens {self.max_tokens()}" - ) - - # Create input arrays exactly like the working standalone script - # Create input arrays, ensuring int64 type - input_ids = np.array([e.ids for e in encoded], dtype=np.int64) - attention_mask = np.array([e.attention_mask for e in encoded], dtype=np.int64) - - # Ensure 2D arrays (batch_size, seq_length) - if input_ids.ndim == 1: - input_ids = input_ids.reshape(1, -1) - if attention_mask.ndim == 1: - attention_mask = attention_mask.reshape(1, -1) - - # Use zeros_like to create token_type_ids, ensuring exact shape match - token_type_ids = np.zeros_like(input_ids, dtype=np.int64) - - # Ensure all arrays are contiguous, which is important for onnxruntime 1.19.0 - input_ids = np.ascontiguousarray(input_ids, dtype=np.int64) - attention_mask = np.ascontiguousarray(attention_mask, dtype=np.int64) - token_type_ids = np.ascontiguousarray(token_type_ids, dtype=np.int64) - - onnx_input = { - "input_ids": input_ids, - "attention_mask": attention_mask, - "token_type_ids": token_type_ids, - } - - model_output = self.model.run(None, onnx_input) - last_hidden_state = model_output[0] - - # Mean pooling (exactly as in the code) - # Note: attention_mask needs to be converted to float type for floating point operations - attention_mask_float = attention_mask.astype(np.float32) - input_mask_expanded = np.broadcast_to(np.expand_dims(attention_mask_float, -1), last_hidden_state.shape) - embeddings = np.sum(last_hidden_state * input_mask_expanded, 1) / np.clip( - input_mask_expanded.sum(1), a_min=1e-9, a_max=None - ) - - embeddings = embeddings.astype(np.float32) - all_embeddings.append(embeddings) - - return np.concatenate(all_embeddings) - - @cached_property - def tokenizer(self) -> Any: - """ - Get the tokenizer for the model. - - Returns: - The tokenizer for the model. - """ - tokenizer = self.tokenizers.Tokenizer.from_file( - os.path.join(self.DOWNLOAD_PATH, self.EXTRACTED_FOLDER_NAME, "tokenizer.json") - ) - # max_seq_length = 256, for some reason sentence-transformers uses 256 - # even though the HF config has a max length of 128 - tokenizer.enable_truncation(max_length=256) - tokenizer.enable_padding(pad_id=0, pad_token="[PAD]", length=256) # noqa: S106 - return tokenizer - - @cached_property - def model(self) -> Any: - """ - Get the model. - - Returns: - The model. - """ - if self._preferred_providers is None or len(self._preferred_providers) == 0: - if len(self.ort.get_available_providers()) > 0: - logger.debug( - f"WARNING: No ONNX providers provided, defaulting to available providers: " - f"{self.ort.get_available_providers()}" - ) - self._preferred_providers = self.ort.get_available_providers() - elif not set(self._preferred_providers).issubset(set(self.ort.get_available_providers())): - raise ValueError( - f"Preferred providers must be subset of available providers: {self.ort.get_available_providers()}" - ) - - # Create minimal session options to avoid issues - so = self.ort.SessionOptions() - so.log_severity_level = 3 - # Disable all optimizations that might cause issues - so.graph_optimization_level = self.ort.GraphOptimizationLevel.ORT_DISABLE_ALL - so.execution_mode = self.ort.ExecutionMode.ORT_SEQUENTIAL - so.inter_op_num_threads = 1 - so.intra_op_num_threads = 1 - - if self._preferred_providers and "CoreMLExecutionProvider" in self._preferred_providers: - # remove CoreMLExecutionProvider from the list, it is not as well optimized as CPU. - self._preferred_providers.remove("CoreMLExecutionProvider") - - return self.ort.InferenceSession( - os.path.join(self.DOWNLOAD_PATH, self.EXTRACTED_FOLDER_NAME, "model.onnx"), - # Force CPU execution provider to avoid provider issues - providers=["CPUExecutionProvider"], - sess_options=so, - ) - - def _download_model_if_not_exists(self) -> None: - """ - Download from Hugging Face with image mirror if the model doesn't exist. - """ - onnx_files = [ - "config.json", - "model.onnx", - "special_tokens_map.json", - "tokenizer_config.json", - "tokenizer.json", - "vocab.txt", - ] - extracted_folder = os.path.join(self.DOWNLOAD_PATH, self.EXTRACTED_FOLDER_NAME) - onnx_files_exist = True - for f in onnx_files: - if not os.path.exists(os.path.join(extracted_folder, f)): - onnx_files_exist = False - break - - # Model is not downloaded yet - if not onnx_files_exist: - os.makedirs(self.DOWNLOAD_PATH, exist_ok=True) - - logger.info("Attempting to download model from Hugging Face...") - hf_endpoint = self._get_hf_endpoint() - if not self._download_from_huggingface(): - raise RuntimeError( - f"Failed to download model from Hugging Face (endpoint: {hf_endpoint}). " - f"Please check your network connection or set HF_ENDPOINT environment variable " - f"to use a mirror site (e.g., export HF_ENDPOINT='https://hf-mirror.com'). " - f"Model ID: {self.HF_MODEL_ID}" - ) - logger.info("Model downloaded successfully from Hugging Face") - - def max_tokens(self) -> int: - """Get the maximum number of tokens supported by the model.""" - return 256 - def __call__(self, documents: Documents) -> Embeddings: - """ - Generate embeddings for the given documents. - - Args: - documents: Single document (str) or list of documents (List[str]) - - Returns: - List of embedding vectors - - Example: - >>> ef = DefaultEmbeddingFunction() - >>> # Single document - >>> embedding = ef("Hello world") - >>> # Multiple documents - >>> embeddings = ef(["Hello", "World"]) - """ - # Handle single string input - if isinstance(documents, str): - documents = [documents] - - # Handle empty input - if not documents: - return [] - - # Only download the model when it is actually used - self._download_model_if_not_exists() - - # Generate embeddings - embeddings = self._forward(documents) - - # Convert numpy arrays to lists - return [embedding.tolist() for embedding in embeddings] + return self._backend(documents) @staticmethod def name() -> str: @@ -634,6 +319,8 @@ def _initialize(cls) -> None: CohereEmbeddingFunction, GoogleVertexEmbeddingFunction, JinaEmbeddingFunction, + MistralEmbeddingFunction, + MorphEmbeddingFunction, OllamaEmbeddingFunction, OpenAIEmbeddingFunction, QwenEmbeddingFunction, @@ -646,6 +333,8 @@ def _initialize(cls) -> None: cls._registry["sentence_transformer"] = SentenceTransformerEmbeddingFunction cls._registry["openai"] = OpenAIEmbeddingFunction cls._registry["qwen"] = QwenEmbeddingFunction + cls._registry["mistral"] = MistralEmbeddingFunction + cls._registry["morph"] = MorphEmbeddingFunction cls._registry["siliconflow"] = SiliconflowEmbeddingFunction cls._registry["tencent_hunyuan"] = TencentHunyuanEmbeddingFunction cls._registry["ollama"] = OllamaEmbeddingFunction diff --git a/src/pyseekdb/utils/embedding_functions/__init__.py b/src/pyseekdb/utils/embedding_functions/__init__.py index f589b70a..de32a65b 100644 --- a/src/pyseekdb/utils/embedding_functions/__init__.py +++ b/src/pyseekdb/utils/embedding_functions/__init__.py @@ -10,7 +10,10 @@ from .google_vertex_embedding_function import GoogleVertexEmbeddingFunction from .jina_embedding_function import JinaEmbeddingFunction from .litellm_base_embedding_function import LiteLLMBaseEmbeddingFunction +from .mistral_embedding_function import MistralEmbeddingFunction +from .morph_embedding_function import MorphEmbeddingFunction from .ollama_embedding_function import OllamaEmbeddingFunction +from .onnx_embedding_function import OnnxEmbeddingFunction from .openai_base_embedding_function import OpenAIBaseEmbeddingFunction from .openai_embedding_function import OpenAIEmbeddingFunction from .qwen_embedding_function import QwenEmbeddingFunction @@ -27,7 +30,10 @@ "GoogleVertexEmbeddingFunction", "JinaEmbeddingFunction", "LiteLLMBaseEmbeddingFunction", + "MistralEmbeddingFunction", + "MorphEmbeddingFunction", "OllamaEmbeddingFunction", + "OnnxEmbeddingFunction", "OpenAIBaseEmbeddingFunction", "OpenAIEmbeddingFunction", "QwenEmbeddingFunction", diff --git a/src/pyseekdb/utils/embedding_functions/mistral_embedding_function.py b/src/pyseekdb/utils/embedding_functions/mistral_embedding_function.py new file mode 100644 index 00000000..e390ddde --- /dev/null +++ b/src/pyseekdb/utils/embedding_functions/mistral_embedding_function.py @@ -0,0 +1,153 @@ +import warnings +from typing import Any + +from pyseekdb.client.embedding_function import Documents, Embeddings +from pyseekdb.utils.embedding_functions.openai_base_embedding_function import ( + OpenAIBaseEmbeddingFunction, +) + +# Known Mistral embedding model dimensions +# Source: https://docs.mistral.ai/capabilities/embeddings/text_embeddings +_MISTRAL_MODEL_DIMENSIONS = { + "mistral-embed": 1024, +} + + +class MistralEmbeddingFunction(OpenAIBaseEmbeddingFunction): + """ + A convenient embedding function for Mistral text embedding models. + + This class provides a simplified interface to Mistral text embeddings using the + OpenAI-compatible API. + + Note: The embeddings API only accepts the model name and input texts. + + For more information about Mistral embeddings, see: + https://docs.mistral.ai/capabilities/embeddings/text_embeddings + + Example: + pip install pyseekdb openai + + .. code-block:: python + import pyseekdb + from pyseekdb.utils.embedding_functions import MistralEmbeddingFunction + + # Using Mistral text embedding model + # Set MISTRAL_API_KEY environment variable first + ef = MistralEmbeddingFunction(model_name="mistral-embed") + + # Using with additional parameters + ef = MistralEmbeddingFunction( + model_name="mistral-embed", + timeout=30, + max_retries=3 + ) + + db = pyseekdb.Client(path="./seekdb.db") + collection = db.create_collection(name="my_collection", embedding_function=ef) + # Add documents + collection.add(ids=["1", "2"], documents=["Hello world", "How are you?"], metadatas=[{"id": 1}, {"id": 2}]) + # Query using semantic search + results = collection.query("How are you?", n_results=1) + print(results) + + """ + + def __init__( + self, + model_name: str = "mistral-embed", + api_key_env: str | None = None, + api_base: str | None = None, + dimensions: int | None = None, + **kwargs: Any, + ): + """Initialize MistralEmbeddingFunction. + + Args: + model_name (str, optional): Name of the Mistral embedding model. + Defaults to "mistral-embed". + api_key_env (str, optional): Name of the environment variable containing the Mistral API key. + Defaults to "MISTRAL_API_KEY" if not provided. + api_base (str, optional): Base URL for the Mistral API endpoint. + Defaults to "https://api.mistral.ai/v1" if not provided. + dimensions (int, optional): This parameter is not supported by the Mistral embeddings API. + If provided, a warning will be issued and the parameter will be ignored. + **kwargs: Additional arguments to pass to the OpenAI client. + Common options include: + - timeout: Request timeout in seconds + - max_retries: Maximum number of retries + - See https://github.com/openai/openai-python for more options + """ + if dimensions is not None: + warnings.warn( + "The dimensions parameter is not supported by Mistral embeddings. " + "The provided dimensions parameter will be ignored.", + UserWarning, + stacklevel=2, + ) + + super().__init__( + model_name=model_name, + api_key_env=api_key_env, + api_base=api_base, + dimensions=None, + **kwargs, + ) + + def _get_default_api_base(self) -> str: + return "https://api.mistral.ai/v1" + + def _get_default_api_key_env(self) -> str: + return "MISTRAL_API_KEY" + + def _get_model_dimensions(self) -> dict[str, int]: + return _MISTRAL_MODEL_DIMENSIONS + + @staticmethod + def name() -> str: + return "mistral" + + def __call__(self, documents: Documents) -> Embeddings: + """Generate embeddings for the given documents using Mistral's input parameter.""" + if isinstance(documents, str): + documents = [documents] + + if not documents: + return [] + + request_params = { + "model": self.model_name, + "input": documents, + } + + response = self._client.embeddings.create(**request_params) + embeddings = [item.embedding for item in response.data] + + if len(embeddings) != len(documents): + raise ValueError(f"Expected {len(documents)} embeddings but got {len(embeddings)} from API") + + return embeddings + + def get_config(self) -> dict[str, Any]: + return super().get_config() + + @staticmethod + def build_from_config(config: dict[str, Any]) -> "MistralEmbeddingFunction": + model_name = config.get("model_name") + if model_name is None: + raise ValueError("Missing required field 'model_name' in configuration") + + api_key_env = config.get("api_key_env") + api_base = config.get("api_base") + dimensions = config.get("dimensions") + client_kwargs = config.get("client_kwargs", {}) + if not isinstance(client_kwargs, dict): + raise TypeError(f"client_kwargs must be a dictionary, but got {client_kwargs}") + + return MistralEmbeddingFunction( + model_name=model_name, + api_key_env=api_key_env, + api_base=api_base, + dimensions=dimensions, + **client_kwargs, + ) diff --git a/src/pyseekdb/utils/embedding_functions/morph_embedding_function.py b/src/pyseekdb/utils/embedding_functions/morph_embedding_function.py new file mode 100644 index 00000000..4d4661ef --- /dev/null +++ b/src/pyseekdb/utils/embedding_functions/morph_embedding_function.py @@ -0,0 +1,137 @@ +import logging +from typing import Any + +from pyseekdb.utils.embedding_functions.openai_base_embedding_function import ( + OpenAIBaseEmbeddingFunction, +) + +# Known Morph embedding model dimensions +# Source: https://docs.morphllm.com/api-reference/endpoint/embedding +_MORPH_MODEL_DIMENSIONS = { + "morph-embedding-v4": 1536, +} + +logger = logging.getLogger(__name__) + + +class MorphEmbeddingFunction(OpenAIBaseEmbeddingFunction): + """ + A convenient embedding function for Morph embedding models. + + This class provides a simplified interface to Morph embedding models using the + OpenAI-compatible API. + + Example: + pip install pyseekdb openai + + .. code-block:: python + import pyseekdb + from pyseekdb.utils.embedding_functions import MorphEmbeddingFunction + + # Using Morph embedding model + # Set MORPH_API_KEY environment variable first + ef = MorphEmbeddingFunction( + model_name="morph-embedding-v4" + ) + + # Using with custom api_key_env + ef = MorphEmbeddingFunction( + model_name="morph-embedding-v4", + api_key_env="MORPH_API_KEY" + ) + + db = pyseekdb.Client(path="./seekdb.db") + collection = db.create_collection(name="my_collection", embedding_function=ef) + # Add documents + collection.add(ids=["1", "2"], documents=["Hello world", "How are you?"], metadatas=[{"id": 1}, {"id": 2}]) + # Query using semantic search + results = collection.query("How are you?", n_results=1) + print(results) + """ + + def __init__( + self, + model_name: str, + api_key_env: str | None = None, + api_base: str | None = None, + **kwargs: Any, + ): + """Initialize MorphEmbeddingFunction. + + Args: + model_name (str): Name of the Morph embedding model. + api_key_env (str, optional): Name of the environment variable containing the Morph API key. + Defaults to "MORPH_API_KEY" if not provided. + api_base (str, optional): Base URL for the Morph API endpoint. + Defaults to "https://api.morphllm.com/v1" if not provided. + **kwargs: Additional arguments to pass to the OpenAI client. + Common options include: + - timeout: Request timeout in seconds + - max_retries: Maximum number of retries + - See https://github.com/openai/openai-python for more options + """ + super().__init__( + model_name=model_name, + api_key_env=api_key_env, + api_base=api_base, + dimensions=None, + **kwargs, + ) + + def _get_default_api_base(self) -> str: + """Get the default API base URL for Morph. + + Returns: + str: Default Morph API base URL + """ + return "https://api.morphllm.com/v1" + + def _get_default_api_key_env(self) -> str: + """Get the default API key environment variable name for Morph. + + Returns: + str: Default Morph API key environment variable name + """ + return "MORPH_API_KEY" + + def _get_model_dimensions(self) -> dict[str, int]: + """Get a dictionary mapping Morph model names to their default dimensions. + + Returns: + dict[str, int]: Dictionary mapping model names to dimensions + """ + return _MORPH_MODEL_DIMENSIONS + + @staticmethod + def name() -> str: + """Get the unique name identifier for MorphEmbeddingFunction. + + Returns: + The name identifier for this embedding function type + """ + return "morph" + + def get_config(self) -> dict[str, Any]: + return super().get_config() + + @staticmethod + def build_from_config(config: dict[str, Any]) -> "MorphEmbeddingFunction": + model_name = config.get("model_name") + if model_name is None: + raise ValueError("Missing required field 'model_name' in configuration") + + api_key_env = config.get("api_key_env") + api_base = config.get("api_base") + dimensions = config.get("dimensions") + if dimensions is not None: + logger.warning("Ignoring unsupported 'dimensions' for MorphEmbeddingFunction") + client_kwargs = config.get("client_kwargs", {}) + if not isinstance(client_kwargs, dict): + raise TypeError(f"client_kwargs must be a dictionary, but got {client_kwargs}") + + return MorphEmbeddingFunction( + model_name=model_name, + api_key_env=api_key_env, + api_base=api_base, + **client_kwargs, + ) diff --git a/src/pyseekdb/utils/embedding_functions/onnx_embedding_function.py b/src/pyseekdb/utils/embedding_functions/onnx_embedding_function.py new file mode 100644 index 00000000..6fad2f99 --- /dev/null +++ b/src/pyseekdb/utils/embedding_functions/onnx_embedding_function.py @@ -0,0 +1,394 @@ +""" +ONNX-based embedding function implementation. + +This module provides a generic ONNX embedding function that can run +sentence-transformer style models via onnxruntime. +""" + +import contextlib +import logging +import os +from functools import cached_property +from pathlib import Path +from typing import Any + +import numpy as np +import numpy.typing as npt + +logger = logging.getLogger(__name__) + +Documents = str | list[str] +Embeddings = list[list[float]] + + +class OnnxEmbeddingFunction: + """ + Generic ONNX runtime embedding function. + + This class handles model download, tokenizer/model loading, and embedding + generation using onnxruntime. + """ + + EXTRACTED_FOLDER_NAME = "onnx" + ARCHIVE_FILENAME = "onnx.tar.gz" + + def __init__( + self, + model_name: str, + hf_model_id: str, + dimension: int, + download_path: Path | None = None, + preferred_providers: list[str] | None = None, + ): + """ + Initialize an ONNX embedding function. + + Args: + model_name: Name of the model (used for cache directory naming). + hf_model_id: Hugging Face model ID. + dimension: Output embedding dimension. + download_path: Optional cache path override. + preferred_providers: Preferred ONNX runtime providers. + """ + if not model_name: + raise ValueError("model_name must be a non-empty string") + if not hf_model_id: + raise ValueError("hf_model_id must be a non-empty string") + if dimension <= 0: + raise ValueError("dimension must be a positive integer") + + self.model_name = model_name + self.hf_model_id = hf_model_id + self._dimension = dimension + self.download_path = ( + download_path + if download_path is not None + else Path.home() / ".cache" / "pyseekdb" / "onnx_models" / model_name + ) + + # Validate preferred_providers + if preferred_providers and not all(isinstance(i, str) for i in preferred_providers): + raise ValueError("Preferred providers must be a list of strings") + if preferred_providers and len(preferred_providers) != len(set(preferred_providers)): + raise ValueError("Preferred providers must be unique") + + self._preferred_providers = preferred_providers + + # Import required modules lazily to avoid hard dependencies at import time + import onnxruntime as ort_module + import tokenizers + import tqdm + + self.ort = ort_module + self.tokenizers = tokenizers # Store the module + self.tqdm = tqdm.tqdm + + @property + def dimension(self) -> int: + """Get the dimension of embeddings produced by this function.""" + return self._dimension + + def _download(self, url: str, fname: str, chunk_size: int = 8192) -> None: + """ + Download a file from the URL and save it to the file path. + + Args: + url: The URL to download the file from. + fname: The path to save the file to. + chunk_size: The chunk size to use when downloading. + """ + logger.info(f"Downloading from {url}") + # Use Client to ensure correct handling of redirects + import httpx + + with httpx.Client(timeout=600.0, follow_redirects=True) as client, client.stream("GET", url) as resp: + resp.raise_for_status() + total = int(resp.headers.get("content-length", 0)) + with ( + open(fname, "wb") as file, + self.tqdm( + desc=os.path.basename(fname), + total=total, + unit="iB", + unit_scale=True, + unit_divisor=1024, + ) as bar, + ): + for data in resp.iter_bytes(chunk_size=chunk_size): + size = file.write(data) + bar.update(size) + + def _get_hf_endpoint(self) -> str: + """Get Hugging Face endpoint URL, using HF_ENDPOINT environment variable if set.""" + return os.environ.get("HF_ENDPOINT", "https://hf-mirror.com") + + def _download_from_huggingface(self) -> bool: # noqa: C901 + """ + Download model files from Hugging Face (supports mirror acceleration). + + Returns: + True if download successful, False otherwise. + """ + try: + hf_endpoint = self._get_hf_endpoint() + # Remove trailing slash + hf_endpoint = hf_endpoint.rstrip("/") + + # List of files to download + # ONNX model files are in the onnx/ subdirectory, other files in the root directory + files_to_download = { + "onnx/model.onnx": "model.onnx", # ONNX file in onnx subdirectory + "tokenizer.json": "tokenizer.json", + "config.json": "config.json", + "special_tokens_map.json": "special_tokens_map.json", + "tokenizer_config.json": "tokenizer_config.json", + "vocab.txt": "vocab.txt", + } + + extracted_folder = os.path.join(self.download_path, self.EXTRACTED_FOLDER_NAME) + os.makedirs(extracted_folder, exist_ok=True) + + logger.info(f"Downloading model from Hugging Face (endpoint: {hf_endpoint})") + import httpx + + # Download each file + for hf_filename, local_filename in files_to_download.items(): + local_path = os.path.join(extracted_folder, local_filename) + + # Skip if file already exists + if os.path.exists(local_path): + continue + + # Construct Hugging Face download URL + url = f"{hf_endpoint}/{self.hf_model_id}/resolve/main/{hf_filename}" + + try: + # First check if file exists (HEAD request) + with contextlib.suppress(Exception): + head_resp = httpx.head(url, timeout=10.0, follow_redirects=True) + if head_resp.status_code == 404: + logger.warning(f"File {hf_filename} not found on Hugging Face (404), will try fallback") + return False + + self._download(url, local_path, chunk_size=8192) + logger.info(f"Successfully downloaded {local_filename}") + except httpx.HTTPStatusError as e: + if e.response.status_code == 404: + logger.warning(f"File {hf_filename} not found on Hugging Face (404), will try fallback") + return False + logger.warning(f"HTTP error downloading {hf_filename} from Hugging Face: {e}") + if os.path.exists(local_path): + os.remove(local_path) + return False + except Exception as e: + logger.warning(f"Failed to download {hf_filename} from Hugging Face: {e}") + # If download fails, try to delete partially downloaded file + if os.path.exists(local_path): + os.remove(local_path) + return False + + # Verify critical files exist + if not os.path.exists(os.path.join(extracted_folder, "model.onnx")): + logger.error("model.onnx not found after download") + return False + if not os.path.exists(os.path.join(extracted_folder, "tokenizer.json")): + logger.error("tokenizer.json not found after download") + return False + + logger.info("Successfully downloaded all model files from Hugging Face") + return True # noqa: TRY300 + + except Exception: + logger.exception("Error downloading from Hugging Face") + return False + + def _forward(self, documents: list[str], batch_size: int = 32) -> npt.NDArray[np.float32]: + """ + Generate embeddings for a list of documents. + + Args: + documents: The documents to generate embeddings for. + batch_size: The batch size to use when generating embeddings. + + Returns: + The embeddings for the documents. + """ + all_embeddings = [] + for i in range(0, len(documents), batch_size): + batch = documents[i : i + batch_size] + + # Encode each document separately + encoded = [self.tokenizer.encode(d) for d in batch] + + # Check if any document exceeds the max tokens + for doc_tokens in encoded: + if len(doc_tokens.ids) > self.max_tokens(): + raise ValueError( + f"Document length {len(doc_tokens.ids)} is greater than the max tokens {self.max_tokens()}" + ) + + # Create input arrays exactly like the working standalone script + # Create input arrays, ensuring int64 type + input_ids = np.array([e.ids for e in encoded], dtype=np.int64) + attention_mask = np.array([e.attention_mask for e in encoded], dtype=np.int64) + + # Ensure 2D arrays (batch_size, seq_length) + if input_ids.ndim == 1: + input_ids = input_ids.reshape(1, -1) + if attention_mask.ndim == 1: + attention_mask = attention_mask.reshape(1, -1) + + # Use zeros_like to create token_type_ids, ensuring exact shape match + token_type_ids = np.zeros_like(input_ids, dtype=np.int64) + + # Ensure all arrays are contiguous, which is important for onnxruntime 1.19.0 + input_ids = np.ascontiguousarray(input_ids, dtype=np.int64) + attention_mask = np.ascontiguousarray(attention_mask, dtype=np.int64) + token_type_ids = np.ascontiguousarray(token_type_ids, dtype=np.int64) + + onnx_input = { + "input_ids": input_ids, + "attention_mask": attention_mask, + "token_type_ids": token_type_ids, + } + + model_output = self.model.run(None, onnx_input) + last_hidden_state = model_output[0] + + # Mean pooling (exactly as in the code) + # Note: attention_mask needs to be converted to float type for floating point operations + attention_mask_float = attention_mask.astype(np.float32) + input_mask_expanded = np.broadcast_to(np.expand_dims(attention_mask_float, -1), last_hidden_state.shape) + embeddings = np.sum(last_hidden_state * input_mask_expanded, 1) / np.clip( + input_mask_expanded.sum(1), a_min=1e-9, a_max=None + ) + + embeddings = embeddings.astype(np.float32) + all_embeddings.append(embeddings) + + return np.concatenate(all_embeddings) + + @cached_property + def tokenizer(self) -> Any: + """ + Get the tokenizer for the model. + + Returns: + The tokenizer for the model. + """ + tokenizer = self.tokenizers.Tokenizer.from_file( + os.path.join(self.download_path, self.EXTRACTED_FOLDER_NAME, "tokenizer.json") + ) + # max_seq_length = 256, for some reason sentence-transformers uses 256 + # even though the HF config has a max length of 128 + tokenizer.enable_truncation(max_length=256) + tokenizer.enable_padding(pad_id=0, pad_token="[PAD]", length=256) # noqa: S106 + return tokenizer + + @cached_property + def model(self) -> Any: + """ + Get the model. + + Returns: + The model. + """ + if self._preferred_providers is None or len(self._preferred_providers) == 0: + if len(self.ort.get_available_providers()) > 0: + logger.debug( + f"WARNING: No ONNX providers provided, defaulting to available providers: " + f"{self.ort.get_available_providers()}" + ) + self._preferred_providers = self.ort.get_available_providers() + elif not set(self._preferred_providers).issubset(set(self.ort.get_available_providers())): + raise ValueError( + f"Preferred providers must be subset of available providers: {self.ort.get_available_providers()}" + ) + + # Create minimal session options to avoid issues + so = self.ort.SessionOptions() + so.log_severity_level = 3 + # Disable all optimizations that might cause issues + so.graph_optimization_level = self.ort.GraphOptimizationLevel.ORT_DISABLE_ALL + so.execution_mode = self.ort.ExecutionMode.ORT_SEQUENTIAL + so.inter_op_num_threads = 1 + so.intra_op_num_threads = 1 + + if self._preferred_providers and "CoreMLExecutionProvider" in self._preferred_providers: + # remove CoreMLExecutionProvider from the list, it is not as well optimized as CPU. + self._preferred_providers.remove("CoreMLExecutionProvider") + + return self.ort.InferenceSession( + os.path.join(self.download_path, self.EXTRACTED_FOLDER_NAME, "model.onnx"), + # Force CPU execution provider to avoid provider issues + providers=["CPUExecutionProvider"], + sess_options=so, + ) + + def _download_model_if_not_exists(self) -> None: + """ + Download from Hugging Face with image mirror if the model doesn't exist. + """ + onnx_files = [ + "config.json", + "model.onnx", + "special_tokens_map.json", + "tokenizer_config.json", + "tokenizer.json", + "vocab.txt", + ] + extracted_folder = os.path.join(self.download_path, self.EXTRACTED_FOLDER_NAME) + onnx_files_exist = True + for f in onnx_files: + if not os.path.exists(os.path.join(extracted_folder, f)): + onnx_files_exist = False + break + + # Model is not downloaded yet + if not onnx_files_exist: + os.makedirs(self.download_path, exist_ok=True) + + logger.info("Attempting to download model from Hugging Face...") + hf_endpoint = self._get_hf_endpoint() + if not self._download_from_huggingface(): + raise RuntimeError( + f"Failed to download model from Hugging Face (endpoint: {hf_endpoint}). " + f"Please check your network connection or set HF_ENDPOINT environment variable " + f"to use a mirror site (e.g., export HF_ENDPOINT='https://hf-mirror.com'). " + f"Model ID: {self.hf_model_id}" + ) + logger.info("Model downloaded successfully from Hugging Face") + + def max_tokens(self) -> int: + """Get the maximum number of tokens supported by the model.""" + return 256 + + def __call__(self, documents: Documents) -> Embeddings: + """ + Generate embeddings for the given documents. + + Args: + documents: Single document (str) or list of documents (List[str]) + + Returns: + List of embedding vectors + """ + # Handle single string input + if isinstance(documents, str): + documents = [documents] + + # Handle empty input + if not documents: + return [] + + # Only download the model when it is actually used + self._download_model_if_not_exists() + + # Generate embeddings + embeddings = self._forward(documents) + + # Convert numpy arrays to lists + return [embedding.tolist() for embedding in embeddings] + + def __repr__(self) -> str: + return f"OnnxEmbeddingFunction(model_name='{self.model_name}')" diff --git a/tests/integration_tests/conftest.py b/tests/integration_tests/conftest.py index 3739e7dc..257c24df 100644 --- a/tests/integration_tests/conftest.py +++ b/tests/integration_tests/conftest.py @@ -10,15 +10,16 @@ import pytest -# Add project path -project_root = Path(__file__).parent.parent -sys.path.insert(0, str(project_root)) +# Add project path (repo root + src) +repo_root = Path(__file__).resolve().parents[2] +src_root = repo_root / "src" +sys.path.insert(0, str(src_root)) import pyseekdb # noqa: E402 # ==================== Environment Variable Configuration ==================== # Embedded mode -SEEKDB_PATH = os.environ.get("SEEKDB_PATH", os.path.join(project_root, "seekdb.db")) +SEEKDB_PATH = os.environ.get("SEEKDB_PATH", os.path.join(repo_root, "seekdb.db")) SEEKDB_DATABASE = os.environ.get("SEEKDB_DATABASE", "test") # Server mode diff --git a/tests/integration_tests/test_collection_hybrid_search_source_inference.py b/tests/integration_tests/test_collection_hybrid_search_source_inference.py new file mode 100644 index 00000000..8ee21afb --- /dev/null +++ b/tests/integration_tests/test_collection_hybrid_search_source_inference.py @@ -0,0 +1,133 @@ +""" +Integration tests for hybrid_search `_source` inference from include against a real database. + +These tests require a running OceanBase/MySQL-compatible endpoint that supports +`DBMS_HYBRID_SEARCH.GET_SQL`. +""" + +import contextlib +import json +import uuid + +from pymysql.converters import escape_string + +from pyseekdb import HNSWConfiguration + + +class TestCollectionHybridSearchSourceInferenceRealDB: + def _unique_collection_name(self, prefix: str) -> str: + # Keep names short to avoid MySQL/OceanBase identifier length limits after + # internal table-name prefixing (e.g. "c$v1$..."). + return f"{prefix}_{uuid.uuid4().hex[:8]}" + + def _create_test_collection(self, client, collection_name: str, dimension: int = 3): + config = HNSWConfiguration(dimension=dimension, distance="l2") + collection = client.create_collection(name=collection_name, configuration=config, embedding_function=None) + return collection, collection.dimension + + def _generate_query_vector(self, dimension: int) -> list[float]: + base = [1.0, 2.0, 3.0] + if dimension <= len(base): + return base[:dimension] + extended = base * ((dimension // len(base)) + 1) + return extended[:dimension] + + def _insert_test_data(self, collection, dimension: int): + test_data = [ + ( + "Machine learning is a subset of artificial intelligence", + [1.0, 2.0, 3.0], + {"category": "AI", "tag": "ml"}, + ), + ( + "Python programming language is widely used in data science", + [2.0, 3.0, 4.0], + {"category": "Programming", "tag": "python"}, + ), + ] + + ids: list[str] = [] + documents: list[str] = [] + embeddings: list[list[float]] = [] + metadatas: list[dict] = [] + + for document, base_vec, metadata in test_data: + record_id = str(uuid.uuid4()) + ids.append(record_id) + documents.append(document) + metadatas.append(metadata) + + if dimension <= len(base_vec): + embedding = base_vec[:dimension] + else: + embedding = (base_vec * ((dimension // len(base_vec)) + 1))[:dimension] + + embeddings.append(embedding) + + collection.add(ids=ids, documents=documents, embeddings=embeddings, metadatas=metadatas) + return ids + + def _get_sql_query(self, client, table_name: str, search_parm: dict) -> str: + search_parm_json = json.dumps(search_parm, ensure_ascii=False) + client._server._execute(f"SET @search_parm = '{escape_string(search_parm_json)}'") + get_sql_query = f"SELECT DBMS_HYBRID_SEARCH.GET_SQL('{table_name}', @search_parm) as query_sql FROM dual" # noqa: S608 + rows = client._server._execute(get_sql_query) + assert rows and rows[0].get("query_sql") + query_sql = rows[0]["query_sql"] + if isinstance(query_sql, str): + return query_sql.strip().strip("'\"") + return str(query_sql) + + def test_include_infers_source_result_shape_matrix(self, db_client): + """ + Verify `_source` inference end-to-end: + 1) GET_SQL result columns match requested include (avoid returning large unused columns like embedding) + 2) SDK return shape matches include + """ + collection_name = self._unique_collection_name("hs_include_matrix") + collection = None + try: + collection, dimension = self._create_test_collection(db_client, collection_name) + self._insert_test_data(collection, dimension=dimension) + + query_vector = self._generate_query_vector(dimension) + knn = {"query_embeddings": query_vector, "n_results": 2} + + # 1) include=None: default returns documents+metadatas; should not return embedding column + default_include = collection.hybrid_search(knn=knn, n_results=2) + assert set(default_include.keys()) == {"ids", "distances", "documents", "metadatas"} + assert all(isinstance(d, str) for d in default_include["documents"][0]) + assert all(isinstance(m, dict) and m for m in default_include["metadatas"][0]) + + # 2) include=[]: ids/distances only; should not return document/metadata/embedding columns + ids_only = collection.hybrid_search(knn=knn, n_results=2, include=[]) + assert set(ids_only.keys()) == {"ids", "distances"} + + # 3) include=["documents"]: only document column + docs_only = collection.hybrid_search(knn=knn, n_results=2, include=["documents"]) + assert set(docs_only.keys()) == {"ids", "distances", "documents"} + assert all(isinstance(d, str) for d in docs_only["documents"][0]) + + # 4) include=["metadatas"]: only metadata column + metadatas_only = collection.hybrid_search(knn=knn, n_results=2, include=["metadatas"]) + assert set(metadatas_only.keys()) == {"ids", "distances", "metadatas"} + assert all(isinstance(m, dict) and m for m in metadatas_only["metadatas"][0]) + + # 5) include=["embeddings"]: only embedding column + embeddings_only = collection.hybrid_search(knn=knn, n_results=2, include=["embeddings"]) + assert set(embeddings_only.keys()) == {"ids", "distances", "embeddings"} + first_embedding = embeddings_only["embeddings"][0][0] + assert isinstance(first_embedding, list) + assert len(first_embedding) == dimension + + # 6) include=["documents","embeddings"]: document+embedding columns + docs_and_embeddings = collection.hybrid_search(knn=knn, n_results=2, include=["documents", "embeddings"]) + assert set(docs_and_embeddings.keys()) == {"ids", "distances", "documents", "embeddings"} + assert all(isinstance(d, str) for d in docs_and_embeddings["documents"][0]) + assert all(isinstance(e, list) and len(e) == dimension for e in docs_and_embeddings["embeddings"][0]) + finally: + with contextlib.suppress(Exception): + db_client.delete_collection(name=collection_name) + + # NOTE: `HybridSearch` fluent builder was removed on `develop` (rollback enhanced hybrid search). + # Keep this file focused on verifying OceanBase GET_SQL `_source` inference and result shapes. diff --git a/tests/unit_tests/test_default_embedding_function.py b/tests/unit_tests/test_default_embedding_function.py index 37c3f410..0d0969e4 100644 --- a/tests/unit_tests/test_default_embedding_function.py +++ b/tests/unit_tests/test_default_embedding_function.py @@ -2,6 +2,7 @@ Unit tests for DefaultEmbeddingFunction. """ +import sys from typing import Any import pytest @@ -59,5 +60,21 @@ def test_persistence_roundtrip(self): assert restored_ef.model_name == original_ef.model_name +def test_default_embedding_function_on_py314(): + if sys.version_info < (3, 14): + pytest.skip("Python < 3.14") + embedding_function = DefaultEmbeddingFunction() + assert embedding_function.dimension == 384 + assert len(embedding_function("hello")[0]) == 384 + + +def test_default_embedding_function_uses_onnx_on_pre314(): + if sys.version_info >= (3, 14): + pytest.skip("Python >= 3.14") + embedding_function = DefaultEmbeddingFunction() + assert embedding_function.dimension == 384 + assert len(embedding_function("hello")[0]) == 384 + + if __name__ == "__main__": pytest.main([__file__, "-v", "-s"]) diff --git a/tests/unit_tests/test_hybrid_search_source_inference.py b/tests/unit_tests/test_hybrid_search_source_inference.py new file mode 100644 index 00000000..1ea019e3 --- /dev/null +++ b/tests/unit_tests/test_hybrid_search_source_inference.py @@ -0,0 +1,89 @@ +""" +Unit tests for hybrid_search `_source` inference from include. + +Public API exposes only `include`. The SDK infers a minimal OceanBase GET_SQL `_source` +allowlist from `include` to reduce response payload size. +""" + +from typing import Any + +from pyseekdb.client.client_base import BaseClient +from pyseekdb.client.collection import Collection + + +class _CapturingClient: + mode = "dummy" + + def __init__(self) -> None: + self.captured: dict[str, Any] | None = None + + def _collection_hybrid_search(self, **kwargs: Any) -> dict[str, Any]: + self.captured = kwargs + return {"captured": kwargs} + + +class _DummyClient: + def _build_source_fields(self, include: list[str] | None) -> list[str]: + return BaseClient._build_source_fields(self, include) + + +class TestHybridSearchPublicSurfaceUnit: + def test_collection_forwards_include_only(self) -> None: + client = _CapturingClient() + collection = Collection(client=client, name="test", dimension=3) + collection.hybrid_search(query={"where_document": {"$contains": "hi"}}, include=["documents"]) + assert client.captured is not None + assert client.captured["include"] == ["documents"] + assert "return_fields" not in client.captured + + +class TestBuildSearchParmSourceInferenceUnit: + def test_build_search_parm_sets_source_from_default_include(self) -> None: + dummy = _DummyClient() + result = BaseClient._build_search_parm( + dummy, + query=None, + knn=None, + rank=None, + n_results=10, + include=None, + ) + assert result["_source"] == ["_id", "document", "metadata"] + + def test_build_search_parm_sets_source_from_empty_include(self) -> None: + dummy = _DummyClient() + result = BaseClient._build_search_parm( + dummy, + query=None, + knn=None, + rank=None, + n_results=10, + include=[], + ) + assert result["_source"] == ["_id"] + + +class TestBuildSourceFieldsUnit: + def test_build_source_fields_defaults_to_documents_and_metadatas(self) -> None: + dummy = _DummyClient() + assert BaseClient._build_source_fields(dummy, include=None) == ["_id", "document", "metadata"] + + def test_build_source_fields_empty_include_is_id_only(self) -> None: + dummy = _DummyClient() + assert BaseClient._build_source_fields(dummy, include=[]) == ["_id"] + + def test_build_source_fields_includes_embedding_only_when_requested(self) -> None: + dummy = _DummyClient() + assert BaseClient._build_source_fields(dummy, include=["embeddings"]) == ["_id", "embedding"] + + def test_build_source_fields_accepts_singular_aliases(self) -> None: + dummy = _DummyClient() + assert BaseClient._build_source_fields(dummy, include=["document"]) == ["_id", "document"] + assert BaseClient._build_source_fields(dummy, include=["metadata"]) == ["_id", "metadata"] + assert BaseClient._build_source_fields(dummy, include=["embedding"]) == ["_id", "embedding"] + + def test_build_source_fields_ignores_non_source_include_items(self) -> None: + dummy = _DummyClient() + assert BaseClient._build_source_fields(dummy, include=["ids"]) == ["_id"] + assert BaseClient._build_source_fields(dummy, include=["distances"]) == ["_id"] + assert BaseClient._build_source_fields(dummy, include=["documents", "ids", "distances"]) == ["_id", "document"] diff --git a/tests/unit_tests/test_mistral_embedding_function.py b/tests/unit_tests/test_mistral_embedding_function.py new file mode 100644 index 00000000..9077f1fa --- /dev/null +++ b/tests/unit_tests/test_mistral_embedding_function.py @@ -0,0 +1,188 @@ +""" +Unit tests for MistralEmbeddingFunction. + +Tests Mistral embedding function initialization, embedding generation, and dimension detection. +Uses real API calls - requires MISTRAL_API_KEY environment variable to be set. + +To run this test manually: + pytest tests/unit_tests/test_mistral_embedding_function.py -v -s + # Or with environment variable: + MISTRAL_API_KEY=your-key pytest tests/unit_tests/test_mistral_embedding_function.py -v -s +""" + +import importlib.util +import os + +import pytest + +from pyseekdb.client.embedding_function import dimension_of +from pyseekdb.utils.embedding_functions import MistralEmbeddingFunction + +from .test_utils import env_guard + + +def is_openai_available() -> bool: + """ + Check if openai is available for testing. + + Returns: + True if openai is available, False otherwise. + """ + return importlib.util.find_spec("openai") is not None + + +# Skip this test by default - it requires external API access and API keys +@pytest.mark.skipif( + not os.environ.get("MISTRAL_API_KEY") or not is_openai_available(), + reason="MISTRAL_API_KEY environment variable must be set", +) +class TestMistralEmbeddingFunction: + """Test MistralEmbeddingFunction - skipped by default, requires manual execution""" + + def test_mistral_env(self): + """Test if openai package is installed and required environment variables are set.""" + if not is_openai_available(): + print("openai package is not installed") + raise AssertionError("openai package is not installed") + + if not os.environ.get("MISTRAL_API_KEY"): + print("MISTRAL_API_KEY environment variable is not set") + raise AssertionError("MISTRAL_API_KEY environment variable is not set") + + def test_initialization_with_defaults(self): + """Test MistralEmbeddingFunction initialization with default values""" + print("\nTesting MistralEmbeddingFunction initialization with defaults") + + # Check if openai is available and env vars are set + self.test_mistral_env() + + ef = MistralEmbeddingFunction() + + assert ef is not None + assert ef.model_name == "mistral-embed" + assert ef.api_key_env == "MISTRAL_API_KEY" + assert ef.api_base == "https://api.mistral.ai/v1" + assert ef._dimensions_param is None + print(f" Model name: {ef.model_name}") + print(f" API key env: {ef.api_key_env}") + print(f" API base: {ef.api_base}") + + def test_initialization_with_custom_api_key_env(self): + """Test MistralEmbeddingFunction initialization with custom API key env""" + print("\nTesting MistralEmbeddingFunction initialization with custom API key env") + + self.test_mistral_env() + + custom_key_env = "CUSTOM_MISTRAL_KEY" + if not os.environ.get(custom_key_env): + os.environ[custom_key_env] = "your-custom-key" + + ef = MistralEmbeddingFunction(model_name="mistral-embed", api_key_env=custom_key_env) + assert ef.api_key_env == custom_key_env + print(f" Custom API key env: {ef.api_key_env}") + + def test_initialization_with_custom_api_base(self): + """Test MistralEmbeddingFunction initialization with custom API base""" + print("\nTesting MistralEmbeddingFunction initialization with custom API base") + + self.test_mistral_env() + + custom_base = "https://api.mistral.ai/v1" + ef = MistralEmbeddingFunction(model_name="mistral-embed", api_base=custom_base) + assert ef.api_base == custom_base + print(f" Custom API base: {ef.api_base}") + + def test_initialization_with_additional_kwargs(self): + """Test MistralEmbeddingFunction initialization with additional kwargs""" + print("\nTesting MistralEmbeddingFunction initialization with kwargs") + + self.test_mistral_env() + + ef = MistralEmbeddingFunction(model_name="mistral-embed", timeout=30, max_retries=3) + assert ef._client_kwargs.get("timeout") == 30 + assert ef._client_kwargs.get("max_retries") == 3 + print(f" Client kwargs: {ef._client_kwargs}") + + def test_initialization_with_missing_api_key(self): + """Test MistralEmbeddingFunction initialization with missing API key""" + print("\nTesting MistralEmbeddingFunction initialization with missing API key") + + with env_guard(MISTRAL_API_KEY=None): + try: + MistralEmbeddingFunction(model_name="mistral-embed") + raise AssertionError("Expected ValueError for missing API key") + except ValueError as e: + print(f" Caught expected error: {e}") + + def test_dimension_property_for_known_model(self): + """Test MistralEmbeddingFunction dimension property for known model""" + print("\nTesting MistralEmbeddingFunction dimension property for known model") + + self.test_mistral_env() + + ef = MistralEmbeddingFunction(model_name="mistral-embed") + assert ef.dimension == 1024 + print(f" Model mistral-embed dimension: {ef.dimension}") + + def test_embedding_generation_single_document(self): + """Test MistralEmbeddingFunction embedding generation (single document)""" + print("\nTesting MistralEmbeddingFunction embedding generation (single document)") + + self.test_mistral_env() + + ef = MistralEmbeddingFunction(model_name="mistral-embed") + embeddings = ef("Hello world") + assert len(embeddings) == 1 + assert len(embeddings[0]) > 0 + print(f" Embedding dimension: {len(embeddings[0])}") + + def test_embedding_generation_multiple_documents(self): + """Test MistralEmbeddingFunction embedding generation (multiple documents)""" + print("\nTesting MistralEmbeddingFunction embedding generation (multiple documents)") + + self.test_mistral_env() + + ef = MistralEmbeddingFunction(model_name="mistral-embed") + embeddings = ef(["Hello world", "How are you?"]) + assert len(embeddings) == 2 + assert len(embeddings[0]) > 0 + print(f" Embedding dimension: {len(embeddings[0])}") + + def test_embedding_with_empty_input(self): + """Test MistralEmbeddingFunction with empty input""" + print("\nTesting MistralEmbeddingFunction with empty input") + + self.test_mistral_env() + + ef = MistralEmbeddingFunction(model_name="mistral-embed") + embeddings = ef([]) + assert embeddings == [] + print(" Empty input returned empty embeddings") + + def test_dimension_of_function(self): + """Test dimension_of function with MistralEmbeddingFunction""" + print("\nTesting dimension_of function with MistralEmbeddingFunction") + + self.test_mistral_env() + + ef = MistralEmbeddingFunction(model_name="mistral-embed") + dim = dimension_of(ef) + assert dim == 1024 + print(f" dimension_of returned: {dim}") + + def test_persistence(self): + """Test persistence for MistralEmbeddingFunction""" + print("\nTesting MistralEmbeddingFunction persistence") + + self.test_mistral_env() + + assert MistralEmbeddingFunction.name() == "mistral" + + ef = MistralEmbeddingFunction(model_name="mistral-embed") + config = ef.get_config() + + restored_ef = MistralEmbeddingFunction.build_from_config(config) + assert isinstance(restored_ef, MistralEmbeddingFunction) + assert restored_ef.model_name == ef.model_name + assert restored_ef.api_key_env == ef.api_key_env + assert restored_ef.api_base == ef.api_base diff --git a/tests/unit_tests/test_morph_embedding_function.py b/tests/unit_tests/test_morph_embedding_function.py new file mode 100644 index 00000000..402b025f --- /dev/null +++ b/tests/unit_tests/test_morph_embedding_function.py @@ -0,0 +1,343 @@ +""" +Unit tests for MorphEmbeddingFunction. + +Tests Morph embedding function initialization, embedding generation, and config handling. +Uses real API calls - requires MORPH_API_KEY environment variable to be set. + +To run this test manually: + pytest tests/unit_tests/test_morph_embedding_function.py -v -s + # Or with environment variable: + MORPH_API_KEY=your-key pytest tests/unit_tests/test_morph_embedding_function.py -v -s +""" + +import importlib.util +import os + +import pytest + +from pyseekdb.client.embedding_function import dimension_of +from pyseekdb.utils.embedding_functions import MorphEmbeddingFunction + +from .test_utils import env_guard + + +def is_openai_available() -> bool: + """ + Check if openai is available for testing. + + Returns: + True if openai is available, False otherwise. + """ + return importlib.util.find_spec("openai") is not None + + +# Skip this test by default - it requires external API access and API keys +@pytest.mark.skipif( + not os.environ.get("MORPH_API_KEY") or not is_openai_available(), + reason="MORPH_API_KEY environment variable must be set", +) +class TestMorphEmbeddingFunction: + """Test MorphEmbeddingFunction - skipped by default, requires manual execution""" + + def test_morph_env(self): + """Test if openai package is installed and required environment variables are set.""" + if not is_openai_available(): + print("openai package is not installed") + raise AssertionError("openai package is not installed") + + if not os.environ.get("MORPH_API_KEY"): + print("MORPH_API_KEY environment variable is not set") + raise AssertionError("MORPH_API_KEY environment variable is not set") + + def test_initialization_with_model_name(self): + """Test MorphEmbeddingFunction initialization with required model_name""" + print("\nTesting MorphEmbeddingFunction initialization with required model_name") + + self.test_morph_env() + + ef = MorphEmbeddingFunction(model_name="morph-embedding-v4") + + assert ef is not None + assert ef.model_name == "morph-embedding-v4" + assert ef.api_key_env == "MORPH_API_KEY" + assert ef.api_base == "https://api.morphllm.com/v1" + assert ef._dimensions_param is None + print(f" Model name: {ef.model_name}") + print(f" API key env: {ef.api_key_env}") + print(f" API base: {ef.api_base}") + + def test_initialization_with_custom_api_key_env(self): + """Test MorphEmbeddingFunction initialization with custom API key env""" + print("\nTesting MorphEmbeddingFunction initialization with custom API key env") + + self.test_morph_env() + + custom_key_env = "CUSTOM_MORPH_KEY" + if not os.environ.get(custom_key_env): + os.environ[custom_key_env] = "your-custom-key" + + ef = MorphEmbeddingFunction(model_name="morph-embedding-v4", api_key_env=custom_key_env) + assert ef.api_key_env == custom_key_env + print(f" Custom API key env: {ef.api_key_env}") + + def test_initialization_with_custom_api_base(self): + """Test MorphEmbeddingFunction initialization with custom API base""" + print("\nTesting MorphEmbeddingFunction initialization with custom API base") + + self.test_morph_env() + + custom_base = "https://api.morphllm.com/v1" + ef = MorphEmbeddingFunction(model_name="morph-embedding-v4", api_base=custom_base) + assert ef.api_base == custom_base + print(f" Custom API base: {ef.api_base}") + + def test_initialization_with_kwargs(self): + """Test MorphEmbeddingFunction initialization with additional kwargs""" + print("\nTesting MorphEmbeddingFunction initialization with kwargs") + + self.test_morph_env() + + ef = MorphEmbeddingFunction(model_name="morph-embedding-v4", timeout=30, max_retries=3) + assert ef is not None + print(" Initialized with timeout and max_retries") + + def test_initialization_missing_api_key(self): + """Test that missing API key raises ValueError""" + print("\nTesting MorphEmbeddingFunction initialization with missing API key") + + original_key = os.environ.pop("MORPH_API_KEY", None) + try: + with pytest.raises(ValueError, match="API key environment variable"): + MorphEmbeddingFunction(model_name="morph-embedding-v4") + finally: + if original_key: + os.environ["MORPH_API_KEY"] = original_key + + def test_initialization_missing_model_name(self): + """Test that missing model_name raises TypeError""" + print("\nTesting MorphEmbeddingFunction initialization with missing model_name") + + self.test_morph_env() + + with pytest.raises(TypeError): + MorphEmbeddingFunction() + + def test_dimension_property_known_model(self): + """Test dimension property for known Morph model""" + print("\nTesting MorphEmbeddingFunction dimension property for known model") + + self.test_morph_env() + + ef = MorphEmbeddingFunction(model_name="morph-embedding-v4") + dim = ef.dimension + assert dim == 1536, f"Expected dimension 1536 for morph-embedding-v4, got {dim}" + print(f" morph-embedding-v4 dimension: {dim}") + + def test_call_single_document(self): + """Test __call__ with single document""" + print("\nTesting MorphEmbeddingFunction embedding generation (single document)") + + self.test_morph_env() + + ef = MorphEmbeddingFunction(model_name="morph-embedding-v4") + single_doc = "def add(a, b): return a + b" + embeddings = ef(single_doc) + + assert isinstance(embeddings, list) + assert len(embeddings) == 1 + assert isinstance(embeddings[0], list) + assert len(embeddings[0]) > 0 + print(f" Single document embedding dimension: {len(embeddings[0])}") + + def test_call_multiple_documents(self): + """Test __call__ with multiple documents""" + print("\nTesting MorphEmbeddingFunction embedding generation (multiple documents)") + + self.test_morph_env() + + ef = MorphEmbeddingFunction(model_name="morph-embedding-v4") + multiple_docs = [ + "def foo():\n return 1", + "class Bar:\n pass", + "print('hello')", + ] + embeddings = ef(multiple_docs) + + assert isinstance(embeddings, list) + assert len(embeddings) == len(multiple_docs) + for emb in embeddings: + assert isinstance(emb, list) + assert len(emb) == len(embeddings[0]), "All embeddings should have same dimension" + print(f" Multiple documents embedding dimension: {len(embeddings[0])}") + print(f" Number of embeddings: {len(embeddings)}") + + def test_call_empty_input(self): + """Test __call__ with empty input""" + print("\nTesting MorphEmbeddingFunction with empty input") + + self.test_morph_env() + + ef = MorphEmbeddingFunction(model_name="morph-embedding-v4") + empty_embeddings = ef([]) + assert empty_embeddings == [] + print(" Empty input correctly returns empty list") + + def test_dimension_of_function(self): + """Test dimension_of function with MorphEmbeddingFunction""" + print("\nTesting dimension_of function with MorphEmbeddingFunction") + + self.test_morph_env() + + ef = MorphEmbeddingFunction(model_name="morph-embedding-v4") + dim = dimension_of(ef) + assert dim == 1536 + print(f" dimension_of result: {dim}") + + def test_get_default_api_base(self): + """Test _get_default_api_base method""" + print("\nTesting _get_default_api_base method") + + self.test_morph_env() + + ef = MorphEmbeddingFunction(model_name="morph-embedding-v4") + api_base = ef._get_default_api_base() + assert api_base == "https://api.morphllm.com/v1" + print(f" Default API base: {api_base}") + + def test_get_default_api_key_env(self): + """Test _get_default_api_key_env method""" + print("\nTesting _get_default_api_key_env method") + + self.test_morph_env() + + ef = MorphEmbeddingFunction(model_name="morph-embedding-v4") + api_key_env = ef._get_default_api_key_env() + assert api_key_env == "MORPH_API_KEY" + print(f" Default API key env: {api_key_env}") + + def test_get_model_dimensions(self): + """Test _get_model_dimensions method""" + print("\nTesting _get_model_dimensions method") + + self.test_morph_env() + + ef = MorphEmbeddingFunction(model_name="morph-embedding-v4") + dimensions = ef._get_model_dimensions() + + assert isinstance(dimensions, dict) + assert "morph-embedding-v4" in dimensions + assert dimensions["morph-embedding-v4"] == 1536 + print(f" Model dimensions: {dimensions}") + + +@pytest.mark.skipif(not is_openai_available(), reason="openai is not available on this system") +class TestMorphEmbeddingFunctionPersistence: + """Test persistence for MorphEmbeddingFunction""" + + def test_name(self): + """Test that name() returns the correct identifier""" + assert MorphEmbeddingFunction.name() == "morph" + + def test_get_config_with_defaults(self): + """Test that get_config() returns correct config with default values""" + with env_guard(MORPH_API_KEY="test-key"): + ef = MorphEmbeddingFunction(model_name="morph-embedding-v4") + config = ef.get_config() + + assert isinstance(config, dict) + assert config["model_name"] == "morph-embedding-v4" + assert config["api_key_env"] == "MORPH_API_KEY" + assert config["api_base"] == "https://api.morphllm.com/v1" + assert config["dimensions"] is None + assert isinstance(config["client_kwargs"], dict) + assert "name" not in config + + def test_get_config_with_custom_values(self): + """Test that get_config() returns correct config with custom values""" + with env_guard(CUSTOM_MORPH_KEY="test-key"): + ef = MorphEmbeddingFunction( + model_name="morph-embedding-v4", + api_key_env="CUSTOM_MORPH_KEY", + api_base="https://custom-morph.example.com/v1", + timeout=60, + ) + config = ef.get_config() + + assert config["model_name"] == "morph-embedding-v4" + assert config["api_key_env"] == "CUSTOM_MORPH_KEY" + assert config["api_base"] == "https://custom-morph.example.com/v1" + assert config["dimensions"] is None + assert config["client_kwargs"]["timeout"] == 60 + + def test_build_from_config_with_defaults(self): + """Test that build_from_config() restores instance with default values""" + config = { + "model_name": "morph-embedding-v4", + "api_key_env": "MORPH_API_KEY", + "api_base": "https://api.morphllm.com/v1", + "dimensions": None, + "client_kwargs": {}, + } + + with env_guard(MORPH_API_KEY="test-key"): + restored_ef = MorphEmbeddingFunction.build_from_config(config) + + assert isinstance(restored_ef, MorphEmbeddingFunction) + assert restored_ef.model_name == "morph-embedding-v4" + assert restored_ef.api_key_env == "MORPH_API_KEY" + assert restored_ef.api_base == "https://api.morphllm.com/v1" + assert restored_ef._dimensions_param is None + + def test_build_from_config_with_custom_values(self): + """Test that build_from_config() restores instance with custom values""" + config = { + "model_name": "morph-embedding-v4", + "api_key_env": "CUSTOM_MORPH_KEY", + "api_base": "https://custom-morph.example.com/v1", + "dimensions": None, + "client_kwargs": {"timeout": 60}, + } + + with env_guard(CUSTOM_MORPH_KEY="test-key"): + restored_ef = MorphEmbeddingFunction.build_from_config(config) + + assert isinstance(restored_ef, MorphEmbeddingFunction) + assert restored_ef.model_name == "morph-embedding-v4" + assert restored_ef.api_key_env == "CUSTOM_MORPH_KEY" + assert restored_ef.api_base == "https://custom-morph.example.com/v1" + assert restored_ef._dimensions_param is None + assert restored_ef._client_kwargs["timeout"] == 60 + + def test_build_from_config_with_dimensions_ignored(self, caplog): + """Test that build_from_config() ignores dimensions parameter""" + config = { + "model_name": "morph-embedding-v4", + "api_key_env": "MORPH_API_KEY", + "api_base": "https://api.morphllm.com/v1", + "dimensions": 1536, + "client_kwargs": {}, + } + + with env_guard(MORPH_API_KEY="test-key"): + restored_ef = MorphEmbeddingFunction.build_from_config(config) + + assert isinstance(restored_ef, MorphEmbeddingFunction) + assert restored_ef._dimensions_param is None + + def test_persistence_roundtrip(self): + """Test complete roundtrip: get_config -> build_from_config""" + with env_guard(MORPH_API_KEY="test-key"): + original_ef = MorphEmbeddingFunction(model_name="morph-embedding-v4") + + config = original_ef.get_config() + restored_ef = MorphEmbeddingFunction.build_from_config(config) + + assert isinstance(restored_ef, MorphEmbeddingFunction) + assert restored_ef.model_name == original_ef.model_name + assert restored_ef.api_key_env == original_ef.api_key_env + assert restored_ef.api_base == original_ef.api_base + assert restored_ef._dimensions_param == original_ef._dimensions_param + + +if __name__ == "__main__": + pytest.main([__file__, "-v", "-s"]) diff --git a/tests/unit_tests/test_onnx_embedding_function.py b/tests/unit_tests/test_onnx_embedding_function.py new file mode 100644 index 00000000..f1825e6e --- /dev/null +++ b/tests/unit_tests/test_onnx_embedding_function.py @@ -0,0 +1,78 @@ +""" +Unit tests for OnnxEmbeddingFunction. +""" + +from __future__ import annotations + +import importlib + +import pytest + +from pyseekdb.utils.embedding_functions.onnx_embedding_function import OnnxEmbeddingFunction + + +def is_onnx_available() -> bool: + """Check if onnxruntime is available for testing.""" + return importlib.util.find_spec("onnxruntime") is not None + + +# Skip this test by default - it requires external API access and API keys +@pytest.mark.skipif( + not is_onnx_available(), + reason="onnxruntime is not available", +) +class TestOnnxEmbeddingFunction: + def _make_onnx(self) -> OnnxEmbeddingFunction: + return OnnxEmbeddingFunction( + model_name="all-MiniLM-L6-v2", + hf_model_id="sentence-transformers/all-MiniLM-L6-v2", + dimension=384, + preferred_providers=None, + ) + + def test_init_validates_model_name(self) -> None: + with pytest.raises(ValueError, match="model_name must be a non-empty string"): + OnnxEmbeddingFunction(model_name="", hf_model_id="org/test", dimension=3) + + def test_init_validates_hf_model_id(self) -> None: + with pytest.raises(ValueError, match="hf_model_id must be a non-empty string"): + OnnxEmbeddingFunction(model_name="test", hf_model_id="", dimension=3) + + def test_init_validates_dimension(self) -> None: + with pytest.raises(ValueError, match="dimension must be a positive integer"): + OnnxEmbeddingFunction(model_name="test", hf_model_id="org/test", dimension=0) + + def test_init_validates_preferred_providers(self) -> None: + with pytest.raises(ValueError, match="Preferred providers must be a list of strings"): + OnnxEmbeddingFunction(model_name="test", hf_model_id="org/test", dimension=3, preferred_providers=[1]) # type: ignore[list-item] + with pytest.raises(ValueError, match="Preferred providers must be unique"): + OnnxEmbeddingFunction( + model_name="test", + hf_model_id="org/test", + dimension=3, + preferred_providers=["CPUExecutionProvider", "CPUExecutionProvider"], + ) + + def test_dimension_property(self) -> None: + ef = self._make_onnx() + assert ef.dimension == 384 + + def test_call_empty_returns_empty(self) -> None: + ef = self._make_onnx() + assert ef([]) == [] + + def test_call_generates_embeddings(self) -> None: + pytest.importorskip("onnxruntime") + pytest.importorskip("tokenizers") + pytest.importorskip("tqdm") + + ef = self._make_onnx() + embeddings = ef("hello world") + + assert isinstance(embeddings, list) + assert len(embeddings) == 1 + assert len(embeddings[0]) == ef.dimension + + +if __name__ == "__main__": + pytest.main([__file__, "-v", "-s"]) diff --git a/uv.lock b/uv.lock index dc335b1d..101dd5b0 100644 --- a/uv.lock +++ b/uv.lock @@ -1,8 +1,9 @@ version = 1 revision = 3 -requires-python = ">=3.11, <3.14" +requires-python = ">=3.11, <4" resolution-markers = [ - "python_full_version >= '3.12'", + "python_full_version >= '3.14'", + "python_full_version >= '3.12' and python_full_version < '3.14'", "python_full_version < '3.12'", ] @@ -30,7 +31,7 @@ dependencies = [ { name = "jsonschema" }, { name = "narwhals" }, { name = "packaging" }, - { name = "typing-extensions" }, + { name = "typing-extensions", marker = "python_full_version < '3.15'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f7/c0/184a89bd5feba14ff3c41cfaf1dd8a82c05f5ceedbc92145e17042eb08a4/altair-6.0.0.tar.gz", hash = "sha256:614bf5ecbe2337347b590afb111929aa9c16c9527c4887d96c9bc7f6640756b4", size = 763834, upload-time = "2025-11-12T08:59:11.519Z" } wheels = [ @@ -167,6 +168,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, ] @@ -196,7 +213,7 @@ name = "coloredlogs" version = "15.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "humanfriendly" }, + { name = "humanfriendly", marker = "python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520, upload-time = "2021-06-11T10:22:45.202Z" } wheels = [ @@ -302,6 +319,13 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4a/1d/04513e3cab8f29ab8c109d309ddd21a2705afab9d52f2ba1151e0c14f086/hf_xet-1.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6de1fc44f58f6dd937956c8d304d8c2dea264c80680bcfa61ca4a15e7b76780f", size = 3408448, upload-time = "2025-10-24T19:04:20.951Z" }, { url = "https://files.pythonhosted.org/packages/f0/7c/60a2756d7feec7387db3a1176c632357632fbe7849fce576c5559d4520c7/hf_xet-1.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f182f264ed2acd566c514e45da9f2119110e48a87a327ca271027904c70c5832", size = 3503401, upload-time = "2025-10-24T19:04:22.549Z" }, { url = "https://files.pythonhosted.org/packages/4e/64/48fffbd67fb418ab07451e4ce641a70de1c40c10a13e25325e24858ebe5a/hf_xet-1.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:293a7a3787e5c95d7be1857358a9130694a9c6021de3f27fa233f37267174382", size = 2900866, upload-time = "2025-10-24T19:04:33.461Z" }, + { url = "https://files.pythonhosted.org/packages/e2/51/f7e2caae42f80af886db414d4e9885fac959330509089f97cccb339c6b87/hf_xet-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:10bfab528b968c70e062607f663e21e34e2bba349e8038db546646875495179e", size = 2861861, upload-time = "2025-10-24T19:04:19.01Z" }, + { url = "https://files.pythonhosted.org/packages/6e/1d/a641a88b69994f9371bd347f1dd35e5d1e2e2460a2e350c8d5165fc62005/hf_xet-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a212e842647b02eb6a911187dc878e79c4aa0aa397e88dd3b26761676e8c1f8", size = 2717699, upload-time = "2025-10-24T19:04:17.306Z" }, + { url = "https://files.pythonhosted.org/packages/df/e0/e5e9bba7d15f0318955f7ec3f4af13f92e773fbb368c0b8008a5acbcb12f/hf_xet-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30e06daccb3a7d4c065f34fc26c14c74f4653069bb2b194e7f18f17cbe9939c0", size = 3314885, upload-time = "2025-10-24T19:04:07.642Z" }, + { url = "https://files.pythonhosted.org/packages/21/90/b7fe5ff6f2b7b8cbdf1bd56145f863c90a5807d9758a549bf3d916aa4dec/hf_xet-1.2.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:29c8fc913a529ec0a91867ce3d119ac1aac966e098cf49501800c870328cc090", size = 3221550, upload-time = "2025-10-24T19:04:05.55Z" }, + { url = "https://files.pythonhosted.org/packages/6f/cb/73f276f0a7ce46cc6a6ec7d6c7d61cbfe5f2e107123d9bbd0193c355f106/hf_xet-1.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e159cbfcfbb29f920db2c09ed8b660eb894640d284f102ada929b6e3dc410a", size = 3408010, upload-time = "2025-10-24T19:04:28.598Z" }, + { url = "https://files.pythonhosted.org/packages/b8/1e/d642a12caa78171f4be64f7cd9c40e3ca5279d055d0873188a58c0f5fbb9/hf_xet-1.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9c91d5ae931510107f148874e9e2de8a16052b6f1b3ca3c1b12f15ccb491390f", size = 3503264, upload-time = "2025-10-24T19:04:30.397Z" }, + { url = "https://files.pythonhosted.org/packages/17/b5/33764714923fa1ff922770f7ed18c2daae034d21ae6e10dbf4347c854154/hf_xet-1.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:210d577732b519ac6ede149d2f2f34049d44e8622bf14eb3d63bbcd2d4b332dc", size = 2901071, upload-time = "2025-10-24T19:04:37.463Z" }, { url = "https://files.pythonhosted.org/packages/96/2d/22338486473df5923a9ab7107d375dbef9173c338ebef5098ef593d2b560/hf_xet-1.2.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:46740d4ac024a7ca9b22bebf77460ff43332868b661186a8e46c227fdae01848", size = 2866099, upload-time = "2025-10-24T19:04:15.366Z" }, { url = "https://files.pythonhosted.org/packages/7f/8c/c5becfa53234299bc2210ba314eaaae36c2875e0045809b82e40a9544f0c/hf_xet-1.2.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:27df617a076420d8845bea087f59303da8be17ed7ec0cd7ee3b9b9f579dff0e4", size = 2722178, upload-time = "2025-10-24T19:04:13.695Z" }, { url = "https://files.pythonhosted.org/packages/9a/92/cf3ab0b652b082e66876d08da57fcc6fa2f0e6c70dfbbafbd470bb73eb47/hf_xet-1.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3651fd5bfe0281951b988c0facbe726aa5e347b103a675f49a3fa8144c7968fd", size = 3320214, upload-time = "2025-10-24T19:04:03.596Z" }, @@ -363,7 +387,7 @@ name = "humanfriendly" version = "10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyreadline3", marker = "sys_platform == 'win32'" }, + { name = "pyreadline3", marker = "python_full_version < '3.14' and sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702, upload-time = "2021-09-17T21:40:43.31Z" } wheels = [ @@ -468,6 +492,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/05/17/710bf8472d1dff0d3caf4ced6031060091c1320f84ee7d5dcbed1f352417/jiter-0.12.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:351d54f2b09a41600ffea43d081522d792e81dcfb915f6d2d242744c1cc48beb", size = 361272, upload-time = "2025-11-09T20:48:06.951Z" }, { url = "https://files.pythonhosted.org/packages/fb/f1/1dcc4618b59761fef92d10bcbb0b038b5160be653b003651566a185f1a5c/jiter-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2a5e90604620f94bf62264e7c2c038704d38217b7465b863896c6d7c902b06c7", size = 204604, upload-time = "2025-11-09T20:48:08.328Z" }, { url = "https://files.pythonhosted.org/packages/d9/32/63cb1d9f1c5c6632a783c0052cde9ef7ba82688f7065e2f0d5f10a7e3edb/jiter-0.12.0-cp313-cp313t-win_arm64.whl", hash = "sha256:88ef757017e78d2860f96250f9393b7b577b06a956ad102c29c8237554380db3", size = 185628, upload-time = "2025-11-09T20:48:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/a8/99/45c9f0dbe4a1416b2b9a8a6d1236459540f43d7fb8883cff769a8db0612d/jiter-0.12.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c46d927acd09c67a9fb1416df45c5a04c27e83aae969267e98fba35b74e99525", size = 312478, upload-time = "2025-11-09T20:48:10.898Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a7/54ae75613ba9e0f55fcb0bc5d1f807823b5167cc944e9333ff322e9f07dd/jiter-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:774ff60b27a84a85b27b88cd5583899c59940bcc126caca97eb2a9df6aa00c49", size = 318706, upload-time = "2025-11-09T20:48:12.266Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/2aa241ad2c10774baf6c37f8b8e1f39c07db358f1329f4eb40eba179c2a2/jiter-0.12.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5433fab222fb072237df3f637d01b81f040a07dcac1cb4a5c75c7aa9ed0bef1", size = 351894, upload-time = "2025-11-09T20:48:13.673Z" }, + { url = "https://files.pythonhosted.org/packages/54/4f/0f2759522719133a9042781b18cc94e335b6d290f5e2d3e6899d6af933e3/jiter-0.12.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8c593c6e71c07866ec6bfb790e202a833eeec885022296aff6b9e0b92d6a70e", size = 365714, upload-time = "2025-11-09T20:48:15.083Z" }, + { url = "https://files.pythonhosted.org/packages/dc/6f/806b895f476582c62a2f52c453151edd8a0fde5411b0497baaa41018e878/jiter-0.12.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:90d32894d4c6877a87ae00c6b915b609406819dce8bc0d4e962e4de2784e567e", size = 478989, upload-time = "2025-11-09T20:48:16.706Z" }, + { url = "https://files.pythonhosted.org/packages/86/6c/012d894dc6e1033acd8db2b8346add33e413ec1c7c002598915278a37f79/jiter-0.12.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:798e46eed9eb10c3adbbacbd3bdb5ecd4cf7064e453d00dbef08802dae6937ff", size = 378615, upload-time = "2025-11-09T20:48:18.614Z" }, + { url = "https://files.pythonhosted.org/packages/87/30/d718d599f6700163e28e2c71c0bbaf6dace692e7df2592fd793ac9276717/jiter-0.12.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3f1368f0a6719ea80013a4eb90ba72e75d7ea67cfc7846db2ca504f3df0169a", size = 364745, upload-time = "2025-11-09T20:48:20.117Z" }, + { url = "https://files.pythonhosted.org/packages/8f/85/315b45ce4b6ddc7d7fceca24068543b02bdc8782942f4ee49d652e2cc89f/jiter-0.12.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65f04a9d0b4406f7e51279710b27484af411896246200e461d80d3ba0caa901a", size = 386502, upload-time = "2025-11-09T20:48:21.543Z" }, + { url = "https://files.pythonhosted.org/packages/74/0b/ce0434fb40c5b24b368fe81b17074d2840748b4952256bab451b72290a49/jiter-0.12.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:fd990541982a24281d12b67a335e44f117e4c6cbad3c3b75c7dea68bf4ce3a67", size = 519845, upload-time = "2025-11-09T20:48:22.964Z" }, + { url = "https://files.pythonhosted.org/packages/e8/a3/7a7a4488ba052767846b9c916d208b3ed114e3eb670ee984e4c565b9cf0d/jiter-0.12.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:b111b0e9152fa7df870ecaebb0bd30240d9f7fff1f2003bcb4ed0f519941820b", size = 510701, upload-time = "2025-11-09T20:48:24.483Z" }, + { url = "https://files.pythonhosted.org/packages/c3/16/052ffbf9d0467b70af24e30f91e0579e13ded0c17bb4a8eb2aed3cb60131/jiter-0.12.0-cp314-cp314-win32.whl", hash = "sha256:a78befb9cc0a45b5a5a0d537b06f8544c2ebb60d19d02c41ff15da28a9e22d42", size = 205029, upload-time = "2025-11-09T20:48:25.749Z" }, + { url = "https://files.pythonhosted.org/packages/e4/18/3cf1f3f0ccc789f76b9a754bdb7a6977e5d1d671ee97a9e14f7eb728d80e/jiter-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:e1fe01c082f6aafbe5c8faf0ff074f38dfb911d53f07ec333ca03f8f6226debf", size = 204960, upload-time = "2025-11-09T20:48:27.415Z" }, + { url = "https://files.pythonhosted.org/packages/02/68/736821e52ecfdeeb0f024b8ab01b5a229f6b9293bbdb444c27efade50b0f/jiter-0.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:d72f3b5a432a4c546ea4bedc84cce0c3404874f1d1676260b9c7f048a9855451", size = 185529, upload-time = "2025-11-09T20:48:29.125Z" }, + { url = "https://files.pythonhosted.org/packages/30/61/12ed8ee7a643cce29ac97c2281f9ce3956eb76b037e88d290f4ed0d41480/jiter-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e6ded41aeba3603f9728ed2b6196e4df875348ab97b28fc8afff115ed42ba7a7", size = 318974, upload-time = "2025-11-09T20:48:30.87Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c6/f3041ede6d0ed5e0e79ff0de4c8f14f401bbf196f2ef3971cdbe5fd08d1d/jiter-0.12.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a947920902420a6ada6ad51892082521978e9dd44a802663b001436e4b771684", size = 345932, upload-time = "2025-11-09T20:48:32.658Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5d/4d94835889edd01ad0e2dbfc05f7bdfaed46292e7b504a6ac7839aa00edb/jiter-0.12.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:add5e227e0554d3a52cf390a7635edaffdf4f8fce4fdbcef3cc2055bb396a30c", size = 367243, upload-time = "2025-11-09T20:48:34.093Z" }, + { url = "https://files.pythonhosted.org/packages/fd/76/0051b0ac2816253a99d27baf3dda198663aff882fa6ea7deeb94046da24e/jiter-0.12.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f9b1cda8fcb736250d7e8711d4580ebf004a46771432be0ae4796944b5dfa5d", size = 479315, upload-time = "2025-11-09T20:48:35.507Z" }, + { url = "https://files.pythonhosted.org/packages/70/ae/83f793acd68e5cb24e483f44f482a1a15601848b9b6f199dacb970098f77/jiter-0.12.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:deeb12a2223fe0135c7ff1356a143d57f95bbf1f4a66584f1fc74df21d86b993", size = 380714, upload-time = "2025-11-09T20:48:40.014Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/4808a88338ad2c228b1126b93fcd8ba145e919e886fe910d578230dabe3b/jiter-0.12.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c596cc0f4cb574877550ce4ecd51f8037469146addd676d7c1a30ebe6391923f", size = 365168, upload-time = "2025-11-09T20:48:41.462Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d4/04619a9e8095b42aef436b5aeb4c0282b4ff1b27d1db1508df9f5dc82750/jiter-0.12.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ab4c823b216a4aeab3fdbf579c5843165756bd9ad87cc6b1c65919c4715f783", size = 387893, upload-time = "2025-11-09T20:48:42.921Z" }, + { url = "https://files.pythonhosted.org/packages/17/ea/d3c7e62e4546fdc39197fa4a4315a563a89b95b6d54c0d25373842a59cbe/jiter-0.12.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e427eee51149edf962203ff8db75a7514ab89be5cb623fb9cea1f20b54f1107b", size = 520828, upload-time = "2025-11-09T20:48:44.278Z" }, + { url = "https://files.pythonhosted.org/packages/cc/0b/c6d3562a03fd767e31cb119d9041ea7958c3c80cb3d753eafb19b3b18349/jiter-0.12.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:edb868841f84c111255ba5e80339d386d937ec1fdce419518ce1bd9370fac5b6", size = 511009, upload-time = "2025-11-09T20:48:45.726Z" }, + { url = "https://files.pythonhosted.org/packages/aa/51/2cb4468b3448a8385ebcd15059d325c9ce67df4e2758d133ab9442b19834/jiter-0.12.0-cp314-cp314t-win32.whl", hash = "sha256:8bbcfe2791dfdb7c5e48baf646d37a6a3dcb5a97a032017741dea9f817dca183", size = 205110, upload-time = "2025-11-09T20:48:47.033Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c5/ae5ec83dec9c2d1af805fd5fe8f74ebded9c8670c5210ec7820ce0dbeb1e/jiter-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2fa940963bf02e1d8226027ef461e36af472dea85d36054ff835aeed944dd873", size = 205223, upload-time = "2025-11-09T20:48:49.076Z" }, + { url = "https://files.pythonhosted.org/packages/97/9a/3c5391907277f0e55195550cf3fa8e293ae9ee0c00fb402fec1e38c0c82f/jiter-0.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:506c9708dd29b27288f9f8f1140c3cb0e3d8ddb045956d7757b1fa0e0f39a473", size = 185564, upload-time = "2025-11-09T20:48:50.376Z" }, { url = "https://files.pythonhosted.org/packages/fe/54/5339ef1ecaa881c6948669956567a64d2670941925f245c434f494ffb0e5/jiter-0.12.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:4739a4657179ebf08f85914ce50332495811004cc1747852e8b2041ed2aab9b8", size = 311144, upload-time = "2025-11-09T20:49:10.503Z" }, { url = "https://files.pythonhosted.org/packages/27/74/3446c652bffbd5e81ab354e388b1b5fc1d20daac34ee0ed11ff096b1b01a/jiter-0.12.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:41da8def934bf7bec16cb24bd33c0ca62126d2d45d81d17b864bd5ad721393c3", size = 305877, upload-time = "2025-11-09T20:49:12.269Z" }, { url = "https://files.pythonhosted.org/packages/a1/f4/ed76ef9043450f57aac2d4fbeb27175aa0eb9c38f833be6ef6379b3b9a86/jiter-0.12.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c44ee814f499c082e69872d426b624987dbc5943ab06e9bbaa4f81989fdb79e", size = 340419, upload-time = "2025-11-09T20:49:13.803Z" }, @@ -576,6 +625,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, ] [[package]] @@ -701,6 +772,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ac/52/41f3d71296a3dcaa4f456aaa3c6fc8e745b43d0552b6bde56571bb4b4a0f/numpy-2.4.0-cp313-cp313t-win32.whl", hash = "sha256:20c115517513831860c573996e395707aa9fb691eb179200125c250e895fcd93", size = 6076216, upload-time = "2025-12-20T16:17:11.437Z" }, { url = "https://files.pythonhosted.org/packages/35/ff/46fbfe60ab0710d2a2b16995f708750307d30eccbb4c38371ea9e986866e/numpy-2.4.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b48e35f4ab6f6a7597c46e301126ceba4c44cd3280e3750f85db48b082624fa4", size = 12444263, upload-time = "2025-12-20T16:17:13.182Z" }, { url = "https://files.pythonhosted.org/packages/a3/e3/9189ab319c01d2ed556c932ccf55064c5d75bb5850d1df7a482ce0badead/numpy-2.4.0-cp313-cp313t-win_arm64.whl", hash = "sha256:4d1cfce39e511069b11e67cd0bd78ceff31443b7c9e5c04db73c7a19f572967c", size = 10378265, upload-time = "2025-12-20T16:17:15.211Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ed/52eac27de39d5e5a6c9aadabe672bc06f55e24a3d9010cd1183948055d76/numpy-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c95eb6db2884917d86cde0b4d4cf31adf485c8ec36bf8696dd66fa70de96f36b", size = 16647476, upload-time = "2025-12-20T16:17:17.671Z" }, + { url = "https://files.pythonhosted.org/packages/77/c0/990ce1b7fcd4e09aeaa574e2a0a839589e4b08b2ca68070f1acb1fea6736/numpy-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:65167da969cd1ec3a1df31cb221ca3a19a8aaa25370ecb17d428415e93c1935e", size = 12374563, upload-time = "2025-12-20T16:17:20.216Z" }, + { url = "https://files.pythonhosted.org/packages/37/7c/8c5e389c6ae8f5fd2277a988600d79e9625db3fff011a2d87ac80b881a4c/numpy-2.4.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3de19cfecd1465d0dcf8a5b5ea8b3155b42ed0b639dba4b71e323d74f2a3be5e", size = 5203107, upload-time = "2025-12-20T16:17:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/e6/94/ca5b3bd6a8a70a5eec9a0b8dd7f980c1eff4b8a54970a9a7fef248ef564f/numpy-2.4.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6c05483c3136ac4c91b4e81903cb53a8707d316f488124d0398499a4f8e8ef51", size = 6538067, upload-time = "2025-12-20T16:17:24.001Z" }, + { url = "https://files.pythonhosted.org/packages/79/43/993eb7bb5be6761dde2b3a3a594d689cec83398e3f58f4758010f3b85727/numpy-2.4.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36667db4d6c1cea79c8930ab72fadfb4060feb4bfe724141cd4bd064d2e5f8ce", size = 14411926, upload-time = "2025-12-20T16:17:25.822Z" }, + { url = "https://files.pythonhosted.org/packages/03/75/d4c43b61de473912496317a854dac54f1efec3eeb158438da6884b70bb90/numpy-2.4.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9a818668b674047fd88c4cddada7ab8f1c298812783e8328e956b78dc4807f9f", size = 16354295, upload-time = "2025-12-20T16:17:28.308Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0a/b54615b47ee8736a6461a4bb6749128dd3435c5a759d5663f11f0e9af4ac/numpy-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1ee32359fb7543b7b7bd0b2f46294db27e29e7bbdf70541e81b190836cd83ded", size = 16190242, upload-time = "2025-12-20T16:17:30.993Z" }, + { url = "https://files.pythonhosted.org/packages/98/ce/ea207769aacad6246525ec6c6bbd66a2bf56c72443dc10e2f90feed29290/numpy-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e493962256a38f58283de033d8af176c5c91c084ea30f15834f7545451c42059", size = 18280875, upload-time = "2025-12-20T16:17:33.327Z" }, + { url = "https://files.pythonhosted.org/packages/17/ef/ec409437aa962ea372ed601c519a2b141701683ff028f894b7466f0ab42b/numpy-2.4.0-cp314-cp314-win32.whl", hash = "sha256:6bbaebf0d11567fa8926215ae731e1d58e6ec28a8a25235b8a47405d301332db", size = 6002530, upload-time = "2025-12-20T16:17:35.729Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4a/5cb94c787a3ed1ac65e1271b968686521169a7b3ec0b6544bb3ca32960b0/numpy-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d857f55e7fdf7c38ab96c4558c95b97d1c685be6b05c249f5fdafcbd6f9899e", size = 12435890, upload-time = "2025-12-20T16:17:37.599Z" }, + { url = "https://files.pythonhosted.org/packages/48/a0/04b89db963af9de1104975e2544f30de89adbf75b9e75f7dd2599be12c79/numpy-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:bb50ce5fb202a26fd5404620e7ef820ad1ab3558b444cb0b55beb7ef66cd2d63", size = 10591892, upload-time = "2025-12-20T16:17:39.649Z" }, + { url = "https://files.pythonhosted.org/packages/53/e5/d74b5ccf6712c06c7a545025a6a71bfa03bdc7e0568b405b0d655232fd92/numpy-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:355354388cba60f2132df297e2d53053d4063f79077b67b481d21276d61fc4df", size = 12494312, upload-time = "2025-12-20T16:17:41.714Z" }, + { url = "https://files.pythonhosted.org/packages/c2/08/3ca9cc2ddf54dfee7ae9a6479c071092a228c68aef08252aa08dac2af002/numpy-2.4.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:1d8f9fde5f6dc1b6fc34df8162f3b3079365468703fee7f31d4e0cc8c63baed9", size = 5322862, upload-time = "2025-12-20T16:17:44.145Z" }, + { url = "https://files.pythonhosted.org/packages/87/74/0bb63a68394c0c1e52670cfff2e309afa41edbe11b3327d9af29e4383f34/numpy-2.4.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e0434aa22c821f44eeb4c650b81c7fbdd8c0122c6c4b5a576a76d5a35625ecd9", size = 6644986, upload-time = "2025-12-20T16:17:46.203Z" }, + { url = "https://files.pythonhosted.org/packages/06/8f/9264d9bdbcf8236af2823623fe2f3981d740fc3461e2787e231d97c38c28/numpy-2.4.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40483b2f2d3ba7aad426443767ff5632ec3156ef09742b96913787d13c336471", size = 14457958, upload-time = "2025-12-20T16:17:48.017Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d9/f9a69ae564bbc7236a35aa883319364ef5fd41f72aa320cc1cbe66148fe2/numpy-2.4.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6a7664ddd9746e20b7325351fe1a8408d0a2bf9c63b5e898290ddc8f09544", size = 16398394, upload-time = "2025-12-20T16:17:50.409Z" }, + { url = "https://files.pythonhosted.org/packages/34/c7/39241501408dde7f885d241a98caba5421061a2c6d2b2197ac5e3aa842d8/numpy-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ecb0019d44f4cdb50b676c5d0cb4b1eae8e15d1ed3d3e6639f986fc92b2ec52c", size = 16241044, upload-time = "2025-12-20T16:17:52.661Z" }, + { url = "https://files.pythonhosted.org/packages/7c/95/cae7effd90e065a95e59fe710eeee05d7328ed169776dfdd9f789e032125/numpy-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d0ffd9e2e4441c96a9c91ec1783285d80bf835b677853fc2770a89d50c1e48ac", size = 18321772, upload-time = "2025-12-20T16:17:54.947Z" }, + { url = "https://files.pythonhosted.org/packages/96/df/3c6c279accd2bfb968a76298e5b276310bd55d243df4fa8ac5816d79347d/numpy-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:77f0d13fa87036d7553bf81f0e1fe3ce68d14c9976c9851744e4d3e91127e95f", size = 6148320, upload-time = "2025-12-20T16:17:57.249Z" }, + { url = "https://files.pythonhosted.org/packages/92/8d/f23033cce252e7a75cae853d17f582e86534c46404dea1c8ee094a9d6d84/numpy-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b1f5b45829ac1848893f0ddf5cb326110604d6df96cdc255b0bf9edd154104d4", size = 12623460, upload-time = "2025-12-20T16:17:58.963Z" }, + { url = "https://files.pythonhosted.org/packages/a4/4f/1f8475907d1a7c4ef9020edf7f39ea2422ec896849245f00688e4b268a71/numpy-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:23a3e9d1a6f360267e8fbb38ba5db355a6a7e9be71d7fce7ab3125e88bb646c8", size = 10661799, upload-time = "2025-12-20T16:18:01.078Z" }, { url = "https://files.pythonhosted.org/packages/4b/ef/088e7c7342f300aaf3ee5f2c821c4b9996a1bef2aaf6a49cc8ab4883758e/numpy-2.4.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b54c83f1c0c0f1d748dca0af516062b8829d53d1f0c402be24b4257a9c48ada6", size = 16819003, upload-time = "2025-12-20T16:18:03.41Z" }, { url = "https://files.pythonhosted.org/packages/ff/ce/a53017b5443b4b84517182d463fc7bcc2adb4faa8b20813f8e5f5aeb5faa/numpy-2.4.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:aabb081ca0ec5d39591fc33018cd4b3f96e1a2dd6756282029986d00a785fba4", size = 12567105, upload-time = "2025-12-20T16:18:05.594Z" }, { url = "https://files.pythonhosted.org/packages/77/58/5ff91b161f2ec650c88a626c3905d938c89aaadabd0431e6d9c1330c83e2/numpy-2.4.0-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:8eafe7c36c8430b7794edeab3087dec7bf31d634d92f2af9949434b9d1964cba", size = 5395590, upload-time = "2025-12-20T16:18:08.031Z" }, @@ -849,12 +941,12 @@ name = "onnxruntime" version = "1.23.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "coloredlogs" }, - { name = "flatbuffers" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "protobuf" }, - { name = "sympy" }, + { name = "coloredlogs", marker = "python_full_version < '3.14'" }, + { name = "flatbuffers", marker = "python_full_version < '3.14'" }, + { name = "numpy", marker = "python_full_version < '3.14'" }, + { name = "packaging", marker = "python_full_version < '3.14'" }, + { name = "protobuf", marker = "python_full_version < '3.14'" }, + { name = "sympy", marker = "python_full_version < '3.14'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/44/be/467b00f09061572f022ffd17e49e49e5a7a789056bad95b54dfd3bee73ff/onnxruntime-1.23.2-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:6f91d2c9b0965e86827a5ba01531d5b669770b01775b23199565d6c1f136616c", size = 17196113, upload-time = "2025-10-22T03:47:33.526Z" }, @@ -943,6 +1035,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, + { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, + { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, + { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, + { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, + { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, + { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, + { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, + { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, ] [[package]] @@ -998,6 +1103,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/86/77/eacc62356b4cf81abe99ff9dbc7402750044aed02cfd6a503f7c6fc11f3e/pillow-12.1.0-cp313-cp313t-win32.whl", hash = "sha256:7315f9137087c4e0ee73a761b163fc9aa3b19f5f606a7fc08d83fd3e4379af65", size = 6336445, upload-time = "2026-01-02T09:12:14.775Z" }, { url = "https://files.pythonhosted.org/packages/e7/3c/57d81d0b74d218706dafccb87a87ea44262c43eef98eb3b164fd000e0491/pillow-12.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:0ddedfaa8b5f0b4ffbc2fa87b556dc59f6bb4ecb14a53b33f9189713ae8053c0", size = 7045354, upload-time = "2026-01-02T09:12:16.599Z" }, { url = "https://files.pythonhosted.org/packages/ac/82/8b9b97bba2e3576a340f93b044a3a3a09841170ab4c1eb0d5c93469fd32f/pillow-12.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:80941e6d573197a0c28f394753de529bb436b1ca990ed6e765cf42426abc39f8", size = 2454547, upload-time = "2026-01-02T09:12:18.704Z" }, + { url = "https://files.pythonhosted.org/packages/8c/87/bdf971d8bbcf80a348cc3bacfcb239f5882100fe80534b0ce67a784181d8/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:5cb7bc1966d031aec37ddb9dcf15c2da5b2e9f7cc3ca7c54473a20a927e1eb91", size = 4062533, upload-time = "2026-01-02T09:12:20.791Z" }, + { url = "https://files.pythonhosted.org/packages/ff/4f/5eb37a681c68d605eb7034c004875c81f86ec9ef51f5be4a63eadd58859a/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:97e9993d5ed946aba26baf9c1e8cf18adbab584b99f452ee72f7ee8acb882796", size = 4138546, upload-time = "2026-01-02T09:12:23.664Z" }, + { url = "https://files.pythonhosted.org/packages/11/6d/19a95acb2edbace40dcd582d077b991646b7083c41b98da4ed7555b59733/pillow-12.1.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:414b9a78e14ffeb98128863314e62c3f24b8a86081066625700b7985b3f529bd", size = 3601163, upload-time = "2026-01-02T09:12:26.338Z" }, + { url = "https://files.pythonhosted.org/packages/fc/36/2b8138e51cb42e4cc39c3297713455548be855a50558c3ac2beebdc251dd/pillow-12.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e6bdb408f7c9dd2a5ff2b14a3b0bb6d4deb29fb9961e6eb3ae2031ae9a5cec13", size = 5266086, upload-time = "2026-01-02T09:12:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/649056e4d22e1caa90816bf99cef0884aed607ed38075bd75f091a607a38/pillow-12.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3413c2ae377550f5487991d444428f1a8ae92784aac79caa8b1e3b89b175f77e", size = 4657344, upload-time = "2026-01-02T09:12:31.117Z" }, + { url = "https://files.pythonhosted.org/packages/6c/6b/c5742cea0f1ade0cd61485dc3d81f05261fc2276f537fbdc00802de56779/pillow-12.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e5dcbe95016e88437ecf33544ba5db21ef1b8dd6e1b434a2cb2a3d605299e643", size = 6232114, upload-time = "2026-01-02T09:12:32.936Z" }, + { url = "https://files.pythonhosted.org/packages/bf/8f/9f521268ce22d63991601aafd3d48d5ff7280a246a1ef62d626d67b44064/pillow-12.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d0a7735df32ccbcc98b98a1ac785cc4b19b580be1bdf0aeb5c03223220ea09d5", size = 8042708, upload-time = "2026-01-02T09:12:34.78Z" }, + { url = "https://files.pythonhosted.org/packages/1a/eb/257f38542893f021502a1bbe0c2e883c90b5cff26cc33b1584a841a06d30/pillow-12.1.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c27407a2d1b96774cbc4a7594129cc027339fd800cd081e44497722ea1179de", size = 6347762, upload-time = "2026-01-02T09:12:36.748Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5a/8ba375025701c09b309e8d5163c5a4ce0102fa86bbf8800eb0d7ac87bc51/pillow-12.1.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15c794d74303828eaa957ff8070846d0efe8c630901a1c753fdc63850e19ecd9", size = 7039265, upload-time = "2026-01-02T09:12:39.082Z" }, + { url = "https://files.pythonhosted.org/packages/cf/dc/cf5e4cdb3db533f539e88a7bbf9f190c64ab8a08a9bc7a4ccf55067872e4/pillow-12.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c990547452ee2800d8506c4150280757f88532f3de2a58e3022e9b179107862a", size = 6462341, upload-time = "2026-01-02T09:12:40.946Z" }, + { url = "https://files.pythonhosted.org/packages/d0/47/0291a25ac9550677e22eda48510cfc4fa4b2ef0396448b7fbdc0a6946309/pillow-12.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b63e13dd27da389ed9475b3d28510f0f954bca0041e8e551b2a4eb1eab56a39a", size = 7165395, upload-time = "2026-01-02T09:12:42.706Z" }, + { url = "https://files.pythonhosted.org/packages/4f/4c/e005a59393ec4d9416be06e6b45820403bb946a778e39ecec62f5b2b991e/pillow-12.1.0-cp314-cp314-win32.whl", hash = "sha256:1a949604f73eb07a8adab38c4fe50791f9919344398bdc8ac6b307f755fc7030", size = 6431413, upload-time = "2026-01-02T09:12:44.944Z" }, + { url = "https://files.pythonhosted.org/packages/1c/af/f23697f587ac5f9095d67e31b81c95c0249cd461a9798a061ed6709b09b5/pillow-12.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f9f6a650743f0ddee5593ac9e954ba1bdbc5e150bc066586d4f26127853ab94", size = 7176779, upload-time = "2026-01-02T09:12:46.727Z" }, + { url = "https://files.pythonhosted.org/packages/b3/36/6a51abf8599232f3e9afbd16d52829376a68909fe14efe29084445db4b73/pillow-12.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:808b99604f7873c800c4840f55ff389936ef1948e4e87645eaf3fccbc8477ac4", size = 2543105, upload-time = "2026-01-02T09:12:49.243Z" }, + { url = "https://files.pythonhosted.org/packages/82/54/2e1dd20c8749ff225080d6ba465a0cab4387f5db0d1c5fb1439e2d99923f/pillow-12.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc11908616c8a283cf7d664f77411a5ed2a02009b0097ff8abbba5e79128ccf2", size = 5268571, upload-time = "2026-01-02T09:12:51.11Z" }, + { url = "https://files.pythonhosted.org/packages/57/61/571163a5ef86ec0cf30d265ac2a70ae6fc9e28413d1dc94fa37fae6bda89/pillow-12.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:896866d2d436563fa2a43a9d72f417874f16b5545955c54a64941e87c1376c61", size = 4660426, upload-time = "2026-01-02T09:12:52.865Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e1/53ee5163f794aef1bf84243f755ee6897a92c708505350dd1923f4afec48/pillow-12.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8e178e3e99d3c0ea8fc64b88447f7cac8ccf058af422a6cedc690d0eadd98c51", size = 6269908, upload-time = "2026-01-02T09:12:54.884Z" }, + { url = "https://files.pythonhosted.org/packages/bc/0b/b4b4106ff0ee1afa1dc599fde6ab230417f800279745124f6c50bcffed8e/pillow-12.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:079af2fb0c599c2ec144ba2c02766d1b55498e373b3ac64687e43849fbbef5bc", size = 8074733, upload-time = "2026-01-02T09:12:56.802Z" }, + { url = "https://files.pythonhosted.org/packages/19/9f/80b411cbac4a732439e629a26ad3ef11907a8c7fc5377b7602f04f6fe4e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdec5e43377761c5dbca620efb69a77f6855c5a379e32ac5b158f54c84212b14", size = 6381431, upload-time = "2026-01-02T09:12:58.823Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b7/d65c45db463b66ecb6abc17c6ba6917a911202a07662247e1355ce1789e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:565c986f4b45c020f5421a4cea13ef294dde9509a8577f29b2fc5edc7587fff8", size = 7068529, upload-time = "2026-01-02T09:13:00.885Z" }, + { url = "https://files.pythonhosted.org/packages/50/96/dfd4cd726b4a45ae6e3c669fc9e49deb2241312605d33aba50499e9d9bd1/pillow-12.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:43aca0a55ce1eefc0aefa6253661cb54571857b1a7b2964bd8a1e3ef4b729924", size = 6492981, upload-time = "2026-01-02T09:13:03.314Z" }, + { url = "https://files.pythonhosted.org/packages/4d/1c/b5dc52cf713ae46033359c5ca920444f18a6359ce1020dd3e9c553ea5bc6/pillow-12.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0deedf2ea233722476b3a81e8cdfbad786f7adbed5d848469fa59fe52396e4ef", size = 7191878, upload-time = "2026-01-02T09:13:05.276Z" }, + { url = "https://files.pythonhosted.org/packages/53/26/c4188248bd5edaf543864fe4834aebe9c9cb4968b6f573ce014cc42d0720/pillow-12.1.0-cp314-cp314t-win32.whl", hash = "sha256:b17fbdbe01c196e7e159aacb889e091f28e61020a8abeac07b68079b6e626988", size = 6438703, upload-time = "2026-01-02T09:13:07.491Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0e/69ed296de8ea05cb03ee139cee600f424ca166e632567b2d66727f08c7ed/pillow-12.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27b9baecb428899db6c0de572d6d305cfaf38ca1596b5c0542a5182e3e74e8c6", size = 7182927, upload-time = "2026-01-02T09:13:09.841Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f5/68334c015eed9b5cff77814258717dec591ded209ab5b6fb70e2ae873d1d/pillow-12.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f61333d817698bdcdd0f9d7793e365ac3d2a21c1f1eb02b32ad6aefb8d8ea831", size = 2545104, upload-time = "2026-01-02T09:13:12.068Z" }, { url = "https://files.pythonhosted.org/packages/8b/bc/224b1d98cffd7164b14707c91aac83c07b047fbd8f58eba4066a3e53746a/pillow-12.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ca94b6aac0d7af2a10ba08c0f888b3d5114439b6b3ef39968378723622fed377", size = 5228605, upload-time = "2026-01-02T09:13:14.084Z" }, { url = "https://files.pythonhosted.org/packages/0c/ca/49ca7769c4550107de049ed85208240ba0f330b3f2e316f24534795702ce/pillow-12.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:351889afef0f485b84078ea40fe33727a0492b9af3904661b0abbafee0355b72", size = 4622245, upload-time = "2026-01-02T09:13:15.964Z" }, { url = "https://files.pythonhosted.org/packages/73/48/fac807ce82e5955bcc2718642b94b1bd22a82a6d452aea31cbb678cddf12/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb0984b30e973f7e2884362b7d23d0a348c7143ee559f38ef3eaab640144204c", size = 5247593, upload-time = "2026-01-02T09:13:17.913Z" }, @@ -1051,17 +1181,17 @@ wheels = [ [[package]] name = "protobuf" -version = "6.33.2" +version = "6.33.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/34/44/e49ecff446afeec9d1a66d6bbf9adc21e3c7cea7803a920ca3773379d4f6/protobuf-6.33.2.tar.gz", hash = "sha256:56dc370c91fbb8ac85bc13582c9e373569668a290aa2e66a590c2a0d35ddb9e4", size = 444296, upload-time = "2025-12-06T00:17:53.311Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/91/1e3a34881a88697a7354ffd177e8746e97a722e5e8db101544b47e84afb1/protobuf-6.33.2-cp310-abi3-win32.whl", hash = "sha256:87eb388bd2d0f78febd8f4c8779c79247b26a5befad525008e49a6955787ff3d", size = 425603, upload-time = "2025-12-06T00:17:41.114Z" }, - { url = "https://files.pythonhosted.org/packages/64/20/4d50191997e917ae13ad0a235c8b42d8c1ab9c3e6fd455ca16d416944355/protobuf-6.33.2-cp310-abi3-win_amd64.whl", hash = "sha256:fc2a0e8b05b180e5fc0dd1559fe8ebdae21a27e81ac77728fb6c42b12c7419b4", size = 436930, upload-time = "2025-12-06T00:17:43.278Z" }, - { url = "https://files.pythonhosted.org/packages/b2/ca/7e485da88ba45c920fb3f50ae78de29ab925d9e54ef0de678306abfbb497/protobuf-6.33.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d9b19771ca75935b3a4422957bc518b0cecb978b31d1dd12037b088f6bcc0e43", size = 427621, upload-time = "2025-12-06T00:17:44.445Z" }, - { url = "https://files.pythonhosted.org/packages/7d/4f/f743761e41d3b2b2566748eb76bbff2b43e14d5fcab694f494a16458b05f/protobuf-6.33.2-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:b5d3b5625192214066d99b2b605f5783483575656784de223f00a8d00754fc0e", size = 324460, upload-time = "2025-12-06T00:17:45.678Z" }, - { url = "https://files.pythonhosted.org/packages/b1/fa/26468d00a92824020f6f2090d827078c09c9c587e34cbfd2d0c7911221f8/protobuf-6.33.2-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8cd7640aee0b7828b6d03ae518b5b4806fdfc1afe8de82f79c3454f8aef29872", size = 339168, upload-time = "2025-12-06T00:17:46.813Z" }, - { url = "https://files.pythonhosted.org/packages/56/13/333b8f421738f149d4fe5e49553bc2a2ab75235486259f689b4b91f96cec/protobuf-6.33.2-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:1f8017c48c07ec5859106533b682260ba3d7c5567b1ca1f24297ce03384d1b4f", size = 323270, upload-time = "2025-12-06T00:17:48.253Z" }, - { url = "https://files.pythonhosted.org/packages/0e/15/4f02896cc3df04fc465010a4c6a0cd89810f54617a32a70ef531ed75d61c/protobuf-6.33.2-py3-none-any.whl", hash = "sha256:7636aad9bb01768870266de5dc009de2d1b936771b38a793f73cbbf279c91c5c", size = 170501, upload-time = "2025-12-06T00:17:52.211Z" }, + { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, + { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, + { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, + { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, + { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, ] [[package]] @@ -1098,6 +1228,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b9/f0/77aa5198fd3943682b2e4faaf179a674f0edea0d55d326d83cb2277d9363/pyarrow-22.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a9d9ffdc2ab696f6b15b4d1f7cec6658e1d788124418cb30030afbae31c64746", size = 48066216, upload-time = "2025-10-24T10:07:43.528Z" }, { url = "https://files.pythonhosted.org/packages/79/87/a1937b6e78b2aff18b706d738c9e46ade5bfcf11b294e39c87706a0089ac/pyarrow-22.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ec1a15968a9d80da01e1d30349b2b0d7cc91e96588ee324ce1b5228175043e95", size = 50288552, upload-time = "2025-10-24T10:07:53.519Z" }, { url = "https://files.pythonhosted.org/packages/60/ae/b5a5811e11f25788ccfdaa8f26b6791c9807119dffcf80514505527c384c/pyarrow-22.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:bba208d9c7decf9961998edf5c65e3ea4355d5818dd6cd0f6809bec1afb951cc", size = 28262504, upload-time = "2025-10-24T10:08:00.932Z" }, + { url = "https://files.pythonhosted.org/packages/bd/b0/0fa4d28a8edb42b0a7144edd20befd04173ac79819547216f8a9f36f9e50/pyarrow-22.0.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:9bddc2cade6561f6820d4cd73f99a0243532ad506bc510a75a5a65a522b2d74d", size = 34224062, upload-time = "2025-10-24T10:08:14.101Z" }, + { url = "https://files.pythonhosted.org/packages/0f/a8/7a719076b3c1be0acef56a07220c586f25cd24de0e3f3102b438d18ae5df/pyarrow-22.0.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:e70ff90c64419709d38c8932ea9fe1cc98415c4f87ea8da81719e43f02534bc9", size = 35990057, upload-time = "2025-10-24T10:08:21.842Z" }, + { url = "https://files.pythonhosted.org/packages/89/3c/359ed54c93b47fb6fe30ed16cdf50e3f0e8b9ccfb11b86218c3619ae50a8/pyarrow-22.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:92843c305330aa94a36e706c16209cd4df274693e777ca47112617db7d0ef3d7", size = 45068002, upload-time = "2025-10-24T10:08:29.034Z" }, + { url = "https://files.pythonhosted.org/packages/55/fc/4945896cc8638536ee787a3bd6ce7cec8ec9acf452d78ec39ab328efa0a1/pyarrow-22.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:6dda1ddac033d27421c20d7a7943eec60be44e0db4e079f33cc5af3b8280ccde", size = 47737765, upload-time = "2025-10-24T10:08:38.559Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5e/7cb7edeb2abfaa1f79b5d5eb89432356155c8426f75d3753cbcb9592c0fd/pyarrow-22.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:84378110dd9a6c06323b41b56e129c504d157d1a983ce8f5443761eb5256bafc", size = 48048139, upload-time = "2025-10-24T10:08:46.784Z" }, + { url = "https://files.pythonhosted.org/packages/88/c6/546baa7c48185f5e9d6e59277c4b19f30f48c94d9dd938c2a80d4d6b067c/pyarrow-22.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:854794239111d2b88b40b6ef92aa478024d1e5074f364033e73e21e3f76b25e0", size = 50314244, upload-time = "2025-10-24T10:08:55.771Z" }, + { url = "https://files.pythonhosted.org/packages/3c/79/755ff2d145aafec8d347bf18f95e4e81c00127f06d080135dfc86aea417c/pyarrow-22.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:b883fe6fd85adad7932b3271c38ac289c65b7337c2c132e9569f9d3940620730", size = 28757501, upload-time = "2025-10-24T10:09:59.891Z" }, + { url = "https://files.pythonhosted.org/packages/0e/d2/237d75ac28ced3147912954e3c1a174df43a95f4f88e467809118a8165e0/pyarrow-22.0.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7a820d8ae11facf32585507c11f04e3f38343c1e784c9b5a8b1da5c930547fe2", size = 34355506, upload-time = "2025-10-24T10:09:02.953Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/733dfffe6d3069740f98e57ff81007809067d68626c5faef293434d11bd6/pyarrow-22.0.0-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:c6ec3675d98915bf1ec8b3c7986422682f7232ea76cad276f4c8abd5b7319b70", size = 36047312, upload-time = "2025-10-24T10:09:10.334Z" }, + { url = "https://files.pythonhosted.org/packages/7c/2b/29d6e3782dc1f299727462c1543af357a0f2c1d3c160ce199950d9ca51eb/pyarrow-22.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:3e739edd001b04f654b166204fc7a9de896cf6007eaff33409ee9e50ceaff754", size = 45081609, upload-time = "2025-10-24T10:09:18.61Z" }, + { url = "https://files.pythonhosted.org/packages/8d/42/aa9355ecc05997915af1b7b947a7f66c02dcaa927f3203b87871c114ba10/pyarrow-22.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:7388ac685cab5b279a41dfe0a6ccd99e4dbf322edfb63e02fc0443bf24134e91", size = 47703663, upload-time = "2025-10-24T10:09:27.369Z" }, + { url = "https://files.pythonhosted.org/packages/ee/62/45abedde480168e83a1de005b7b7043fd553321c1e8c5a9a114425f64842/pyarrow-22.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f633074f36dbc33d5c05b5dc75371e5660f1dbf9c8b1d95669def05e5425989c", size = 48066543, upload-time = "2025-10-24T10:09:34.908Z" }, + { url = "https://files.pythonhosted.org/packages/84/e9/7878940a5b072e4f3bf998770acafeae13b267f9893af5f6d4ab3904b67e/pyarrow-22.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4c19236ae2402a8663a2c8f21f1870a03cc57f0bef7e4b6eb3238cc82944de80", size = 50288838, upload-time = "2025-10-24T10:09:44.394Z" }, + { url = "https://files.pythonhosted.org/packages/7b/03/f335d6c52b4a4761bcc83499789a1e2e16d9d201a58c327a9b5cc9a41bd9/pyarrow-22.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0c34fe18094686194f204a3b1787a27456897d8a2d62caf84b61e8dfbc0252ae", size = 29185594, upload-time = "2025-10-24T10:09:53.111Z" }, ] [[package]] @@ -1166,6 +1310,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, @@ -1222,6 +1394,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8c/b6/a81291350f864f8c2bf58c1d413b2fdce24cc57cc0866f71b23e2f3406b3/pylibseekdb-1.0.0.post1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e12ac23e8409fdb7785d8054ff4361910e761540ab7eafede9aa464e7dbc1ac7", size = 80346064, upload-time = "2025-11-17T11:01:38.447Z" }, { url = "https://files.pythonhosted.org/packages/bb/f9/4b83f9118a2884be01e09362fa9963f6e7892fd4bed326059445072485c6/pylibseekdb-1.0.0.post1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:82d2c56dbce6d41f2059cd4285292f00bf801eb6f5c36df12ab30eb18c714070", size = 59927878, upload-time = "2025-11-17T11:31:28.833Z" }, { url = "https://files.pythonhosted.org/packages/b1/93/28c9ff560032b7e7fe2fb5e76979b482c75de516838775c956b3999b00c5/pylibseekdb-1.0.0.post1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:7249a82f55e1e8f4c13c0f6a1a773fa95902d8e2334c674491e601b2efbcc89b", size = 80349282, upload-time = "2025-11-17T11:01:43.298Z" }, + { url = "https://files.pythonhosted.org/packages/ea/0b/0084a0d78deb0dfc6ff36b4f6ae64c091398a2e3ef3ee7b3db0d8d78e115/pylibseekdb-1.0.0.post1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:c1113b6b6579a8743cb6dcceed3a21473713c988cab91650c85e01d58682a4b7", size = 59929351, upload-time = "2025-11-17T11:31:38.231Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f1/615f960f70f5b8caf512f8c32c93b4677fa638da2d8ed2bc792d1fb76264/pylibseekdb-1.0.0.post1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:fb8b195338f42a0c5d021b0ad67aad342348167adacdb500eadd3c649a93d485", size = 80353322, upload-time = "2025-11-17T11:01:47.772Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7f/a3c81ef9c4fab8f2c8d48d7a184100c59e70f384310ddbb12a135d6ec082/pylibseekdb-1.0.0.post1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:4cdb0b11e64b465c77dc52a76ec46ab9ff85eba2c5af7ef5f2a4001288e1c49a", size = 59935108, upload-time = "2025-11-17T11:31:46.795Z" }, + { url = "https://files.pythonhosted.org/packages/cc/24/2dfc7721493e492e50d29cda9fee99360a39334b6e63aef0f9895cb9be8c/pylibseekdb-1.0.0.post1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:9878fb337111d3c3367a936aee61fac34238f0d0901cedf7773d061855407d06", size = 80354154, upload-time = "2025-11-17T11:01:52.224Z" }, ] [[package]] @@ -1237,6 +1413,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/13/aa/ac67fe8376af4ecc6619d453692caac2bb3bd6473e3236767baee17c6bc9/pylibseekdb_runtime-1.0.0.post1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:d58a30a9ea32238ec7db72bc6c8e199c3e4a2c54ffda039c0fc69573c78afdf8", size = 86347710, upload-time = "2025-11-17T11:02:21.603Z" }, { url = "https://files.pythonhosted.org/packages/b7/7b/ad6e1b82f57f24e5bf6aedff61cae60a54ad0ce6994a94df9ab04378ff40/pylibseekdb_runtime-1.0.0.post1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:b491b37347d2e806c9359e6c73ad5c399cd6bf9c3fa7daa8ee0b75b513f486ad", size = 84222093, upload-time = "2025-11-17T11:32:55.587Z" }, { url = "https://files.pythonhosted.org/packages/82/e3/74d77fdc385c6b0b9c46ac36c1fe745438af6a23deecca881d5a49f35af0/pylibseekdb_runtime-1.0.0.post1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:82bc2201dde2fd94f60ba7b4e52406602d40feff4301a4f5af82188beb5d9a2c", size = 86365110, upload-time = "2025-11-17T11:02:26.111Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c4/9e351a8c52c508091c3af1b802b0a66043282669eb8f50007b71b70782ce/pylibseekdb_runtime-1.0.0.post1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:57e46c2dec66f255e9a1a34476f5e80348b2ddfaa8ee8b39c6c6509506e45446", size = 84224151, upload-time = "2025-11-17T11:33:05.546Z" }, + { url = "https://files.pythonhosted.org/packages/ea/23/aa13383e5fb18200dc2bc86c46f0158012f0cbabecd5cfbe3ac4bda064cb/pylibseekdb_runtime-1.0.0.post1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:eec0d00d824620b2f36635c12c920a3f3cd03a5509521d8a618a800b1b75ae04", size = 86354643, upload-time = "2025-11-17T11:02:32.552Z" }, + { url = "https://files.pythonhosted.org/packages/77/bd/73d0525048c30fa2ff18fe4516943095d452e77484a27def88fe8fa17438/pylibseekdb_runtime-1.0.0.post1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:aa2f6be0abdc0b4e35f2971cf96c26a72d1715901b85a7584ed4e395cf999027", size = 84222095, upload-time = "2025-11-17T11:33:15.46Z" }, + { url = "https://files.pythonhosted.org/packages/0f/3d/22d4bd6cba2fd8093e01cd1246dfee7f970306cf6c60520481135f305eed/pylibseekdb_runtime-1.0.0.post1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:916771f9fc6a613d0b8334f1688eb4519b314cd30a21c5e83096f84a039c7b06", size = 86352963, upload-time = "2025-11-17T11:02:37.896Z" }, ] [[package]] @@ -1280,14 +1460,15 @@ name = "pyseekdb" version = "1.0.0b4" source = { editable = "." } dependencies = [ - { name = "httpx" }, + { name = "httpx", marker = "python_full_version < '3.14'" }, { name = "numpy" }, - { name = "onnxruntime" }, + { name = "onnxruntime", marker = "python_full_version < '3.14'" }, { name = "pylibseekdb", marker = "sys_platform == 'linux'" }, { name = "pymysql" }, + { name = "sentence-transformers", marker = "python_full_version >= '3.14'" }, { name = "tenacity" }, - { name = "tokenizers" }, - { name = "tqdm" }, + { name = "tokenizers", marker = "python_full_version < '3.14'" }, + { name = "tqdm", marker = "python_full_version < '3.14'" }, ] [package.dev-dependencies] @@ -1303,14 +1484,15 @@ dev = [ [package.metadata] requires-dist = [ - { name = "httpx" }, + { name = "httpx", marker = "python_full_version < '3.14'" }, { name = "numpy", specifier = ">=1.26" }, - { name = "onnxruntime", specifier = ">=1.19.0" }, + { name = "onnxruntime", marker = "python_full_version < '3.14'", specifier = ">=1.19.0" }, { name = "pylibseekdb", marker = "sys_platform == 'linux'" }, { name = "pymysql", specifier = ">=1.1.1" }, + { name = "sentence-transformers", marker = "python_full_version >= '3.14'" }, { name = "tenacity" }, - { name = "tokenizers", specifier = ">=0.15.0" }, - { name = "tqdm" }, + { name = "tokenizers", marker = "python_full_version < '3.14'", specifier = ">=0.15.0" }, + { name = "tqdm", marker = "python_full_version < '3.14'" }, ] [package.metadata.requires-dev] @@ -1405,6 +1587,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, ] [[package]] @@ -1483,6 +1683,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4f/f9/8bd6b656592f925b6845fcbb4d57603a3ac2fb2373344ffa1ed70aa6820a/regex-2025.11.3-cp313-cp313t-win32.whl", hash = "sha256:9ddc42e68114e161e51e272f667d640f97e84a2b9ef14b7477c53aac20c2d59a", size = 268792, upload-time = "2025-11-03T21:32:44.13Z" }, { url = "https://files.pythonhosted.org/packages/e5/87/0e7d603467775ff65cd2aeabf1b5b50cc1c3708556a8b849a2fa4dd1542b/regex-2025.11.3-cp313-cp313t-win_amd64.whl", hash = "sha256:7a7c7fdf755032ffdd72c77e3d8096bdcb0eb92e89e17571a196f03d88b11b3c", size = 280214, upload-time = "2025-11-03T21:32:45.853Z" }, { url = "https://files.pythonhosted.org/packages/8d/d0/2afc6f8e94e2b64bfb738a7c2b6387ac1699f09f032d363ed9447fd2bb57/regex-2025.11.3-cp313-cp313t-win_arm64.whl", hash = "sha256:df9eb838c44f570283712e7cff14c16329a9f0fb19ca492d21d4b7528ee6821e", size = 271469, upload-time = "2025-11-03T21:32:48.026Z" }, + { url = "https://files.pythonhosted.org/packages/31/e9/f6e13de7e0983837f7b6d238ad9458800a874bf37c264f7923e63409944c/regex-2025.11.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9697a52e57576c83139d7c6f213d64485d3df5bf84807c35fa409e6c970801c6", size = 489089, upload-time = "2025-11-03T21:32:50.027Z" }, + { url = "https://files.pythonhosted.org/packages/a3/5c/261f4a262f1fa65141c1b74b255988bd2fa020cc599e53b080667d591cfc/regex-2025.11.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e18bc3f73bd41243c9b38a6d9f2366cd0e0137a9aebe2d8ff76c5b67d4c0a3f4", size = 291059, upload-time = "2025-11-03T21:32:51.682Z" }, + { url = "https://files.pythonhosted.org/packages/8e/57/f14eeb7f072b0e9a5a090d1712741fd8f214ec193dba773cf5410108bb7d/regex-2025.11.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:61a08bcb0ec14ff4e0ed2044aad948d0659604f824cbd50b55e30b0ec6f09c73", size = 288900, upload-time = "2025-11-03T21:32:53.569Z" }, + { url = "https://files.pythonhosted.org/packages/3c/6b/1d650c45e99a9b327586739d926a1cd4e94666b1bd4af90428b36af66dc7/regex-2025.11.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9c30003b9347c24bcc210958c5d167b9e4f9be786cb380a7d32f14f9b84674f", size = 799010, upload-time = "2025-11-03T21:32:55.222Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/d66dcbc6b628ce4e3f7f0cbbb84603aa2fc0ffc878babc857726b8aab2e9/regex-2025.11.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4e1e592789704459900728d88d41a46fe3969b82ab62945560a31732ffc19a6d", size = 864893, upload-time = "2025-11-03T21:32:57.239Z" }, + { url = "https://files.pythonhosted.org/packages/bf/2d/f238229f1caba7ac87a6c4153d79947fb0261415827ae0f77c304260c7d3/regex-2025.11.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6538241f45eb5a25aa575dbba1069ad786f68a4f2773a29a2bd3dd1f9de787be", size = 911522, upload-time = "2025-11-03T21:32:59.274Z" }, + { url = "https://files.pythonhosted.org/packages/bd/3d/22a4eaba214a917c80e04f6025d26143690f0419511e0116508e24b11c9b/regex-2025.11.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce22519c989bb72a7e6b36a199384c53db7722fe669ba891da75907fe3587db", size = 803272, upload-time = "2025-11-03T21:33:01.393Z" }, + { url = "https://files.pythonhosted.org/packages/84/b1/03188f634a409353a84b5ef49754b97dbcc0c0f6fd6c8ede505a8960a0a4/regex-2025.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:66d559b21d3640203ab9075797a55165d79017520685fb407b9234d72ab63c62", size = 787958, upload-time = "2025-11-03T21:33:03.379Z" }, + { url = "https://files.pythonhosted.org/packages/99/6a/27d072f7fbf6fadd59c64d210305e1ff865cc3b78b526fd147db768c553b/regex-2025.11.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:669dcfb2e38f9e8c69507bace46f4889e3abbfd9b0c29719202883c0a603598f", size = 859289, upload-time = "2025-11-03T21:33:05.374Z" }, + { url = "https://files.pythonhosted.org/packages/9a/70/1b3878f648e0b6abe023172dacb02157e685564853cc363d9961bcccde4e/regex-2025.11.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:32f74f35ff0f25a5021373ac61442edcb150731fbaa28286bbc8bb1582c89d02", size = 850026, upload-time = "2025-11-03T21:33:07.131Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d5/68e25559b526b8baab8e66839304ede68ff6727237a47727d240006bd0ff/regex-2025.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e6c7a21dffba883234baefe91bc3388e629779582038f75d2a5be918e250f0ed", size = 789499, upload-time = "2025-11-03T21:33:09.141Z" }, + { url = "https://files.pythonhosted.org/packages/fc/df/43971264857140a350910d4e33df725e8c94dd9dee8d2e4729fa0d63d49e/regex-2025.11.3-cp314-cp314-win32.whl", hash = "sha256:795ea137b1d809eb6836b43748b12634291c0ed55ad50a7d72d21edf1cd565c4", size = 271604, upload-time = "2025-11-03T21:33:10.9Z" }, + { url = "https://files.pythonhosted.org/packages/01/6f/9711b57dc6894a55faf80a4c1b5aa4f8649805cb9c7aef46f7d27e2b9206/regex-2025.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:9f95fbaa0ee1610ec0fc6b26668e9917a582ba80c52cc6d9ada15e30aa9ab9ad", size = 280320, upload-time = "2025-11-03T21:33:12.572Z" }, + { url = "https://files.pythonhosted.org/packages/f1/7e/f6eaa207d4377481f5e1775cdeb5a443b5a59b392d0065f3417d31d80f87/regex-2025.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:dfec44d532be4c07088c3de2876130ff0fbeeacaa89a137decbbb5f665855a0f", size = 273372, upload-time = "2025-11-03T21:33:14.219Z" }, + { url = "https://files.pythonhosted.org/packages/c3/06/49b198550ee0f5e4184271cee87ba4dfd9692c91ec55289e6282f0f86ccf/regex-2025.11.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ba0d8a5d7f04f73ee7d01d974d47c5834f8a1b0224390e4fe7c12a3a92a78ecc", size = 491985, upload-time = "2025-11-03T21:33:16.555Z" }, + { url = "https://files.pythonhosted.org/packages/ce/bf/abdafade008f0b1c9da10d934034cb670432d6cf6cbe38bbb53a1cfd6cf8/regex-2025.11.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:442d86cf1cfe4faabf97db7d901ef58347efd004934da045c745e7b5bd57ac49", size = 292669, upload-time = "2025-11-03T21:33:18.32Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ef/0c357bb8edbd2ad8e273fcb9e1761bc37b8acbc6e1be050bebd6475f19c1/regex-2025.11.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fd0a5e563c756de210bb964789b5abe4f114dacae9104a47e1a649b910361536", size = 291030, upload-time = "2025-11-03T21:33:20.048Z" }, + { url = "https://files.pythonhosted.org/packages/79/06/edbb67257596649b8fb088d6aeacbcb248ac195714b18a65e018bf4c0b50/regex-2025.11.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf3490bcbb985a1ae97b2ce9ad1c0f06a852d5b19dde9b07bdf25bf224248c95", size = 807674, upload-time = "2025-11-03T21:33:21.797Z" }, + { url = "https://files.pythonhosted.org/packages/f4/d9/ad4deccfce0ea336296bd087f1a191543bb99ee1c53093dcd4c64d951d00/regex-2025.11.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3809988f0a8b8c9dcc0f92478d6501fac7200b9ec56aecf0ec21f4a2ec4b6009", size = 873451, upload-time = "2025-11-03T21:33:23.741Z" }, + { url = "https://files.pythonhosted.org/packages/13/75/a55a4724c56ef13e3e04acaab29df26582f6978c000ac9cd6810ad1f341f/regex-2025.11.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f4ff94e58e84aedb9c9fce66d4ef9f27a190285b451420f297c9a09f2b9abee9", size = 914980, upload-time = "2025-11-03T21:33:25.999Z" }, + { url = "https://files.pythonhosted.org/packages/67/1e/a1657ee15bd9116f70d4a530c736983eed997b361e20ecd8f5ca3759d5c5/regex-2025.11.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eb542fd347ce61e1321b0a6b945d5701528dca0cd9759c2e3bb8bd57e47964d", size = 812852, upload-time = "2025-11-03T21:33:27.852Z" }, + { url = "https://files.pythonhosted.org/packages/b8/6f/f7516dde5506a588a561d296b2d0044839de06035bb486b326065b4c101e/regex-2025.11.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d6c2d5919075a1f2e413c00b056ea0c2f065b3f5fe83c3d07d325ab92dce51d6", size = 795566, upload-time = "2025-11-03T21:33:32.364Z" }, + { url = "https://files.pythonhosted.org/packages/d9/dd/3d10b9e170cc16fb34cb2cef91513cf3df65f440b3366030631b2984a264/regex-2025.11.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3f8bf11a4827cc7ce5a53d4ef6cddd5ad25595d3c1435ef08f76825851343154", size = 868463, upload-time = "2025-11-03T21:33:34.459Z" }, + { url = "https://files.pythonhosted.org/packages/f5/8e/935e6beff1695aa9085ff83195daccd72acc82c81793df480f34569330de/regex-2025.11.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:22c12d837298651e5550ac1d964e4ff57c3f56965fc1812c90c9fb2028eaf267", size = 854694, upload-time = "2025-11-03T21:33:36.793Z" }, + { url = "https://files.pythonhosted.org/packages/92/12/10650181a040978b2f5720a6a74d44f841371a3d984c2083fc1752e4acf6/regex-2025.11.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:62ba394a3dda9ad41c7c780f60f6e4a70988741415ae96f6d1bf6c239cf01379", size = 799691, upload-time = "2025-11-03T21:33:39.079Z" }, + { url = "https://files.pythonhosted.org/packages/67/90/8f37138181c9a7690e7e4cb388debbd389342db3c7381d636d2875940752/regex-2025.11.3-cp314-cp314t-win32.whl", hash = "sha256:4bf146dca15cdd53224a1bf46d628bd7590e4a07fbb69e720d561aea43a32b38", size = 274583, upload-time = "2025-11-03T21:33:41.302Z" }, + { url = "https://files.pythonhosted.org/packages/8f/cd/867f5ec442d56beb56f5f854f40abcfc75e11d10b11fdb1869dd39c63aaf/regex-2025.11.3-cp314-cp314t-win_amd64.whl", hash = "sha256:adad1a1bcf1c9e76346e091d22d23ac54ef28e1365117d99521631078dfec9de", size = 284286, upload-time = "2025-11-03T21:33:43.324Z" }, + { url = "https://files.pythonhosted.org/packages/20/31/32c0c4610cbc070362bf1d2e4ea86d1ea29014d400a6d6c2486fcfd57766/regex-2025.11.3-cp314-cp314t-win_arm64.whl", hash = "sha256:c54f768482cef41e219720013cd05933b6f971d9562544d691c68699bf2b6801", size = 274741, upload-time = "2025-11-03T21:33:45.557Z" }, ] [[package]] @@ -1586,6 +1814,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, + { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, + { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, + { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, + { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, + { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, + { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, + { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, + { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, + { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, + { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, + { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, + { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, + { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, + { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, + { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, + { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" }, { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" }, { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" }, @@ -1684,6 +1941,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fe/56/a85473cd75f200c9759e3a5f0bcab2d116c92a8a02ee08ccd73b870f8bb4/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:80832434a6cc114f5219211eec13dcbc16c2bac0e31ef64c6d346cde3cf054cb", size = 8925045, upload-time = "2025-12-10T07:08:22.11Z" }, { url = "https://files.pythonhosted.org/packages/cc/b7/64d8cfa896c64435ae57f4917a548d7ac7a44762ff9802f75a79b77cb633/scikit_learn-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ee787491dbfe082d9c3013f01f5991658b0f38aa8177e4cd4bf434c58f551702", size = 8507994, upload-time = "2025-12-10T07:08:23.943Z" }, { url = "https://files.pythonhosted.org/packages/5e/37/e192ea709551799379958b4c4771ec507347027bb7c942662c7fbeba31cb/scikit_learn-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf97c10a3f5a7543f9b88cbf488d33d175e9146115a451ae34568597ba33dcde", size = 7869518, upload-time = "2025-12-10T07:08:25.71Z" }, + { url = "https://files.pythonhosted.org/packages/24/05/1af2c186174cc92dcab2233f327336058c077d38f6fe2aceb08e6ab4d509/scikit_learn-1.8.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c22a2da7a198c28dd1a6e1136f19c830beab7fdca5b3e5c8bba8394f8a5c45b3", size = 8528667, upload-time = "2025-12-10T07:08:27.541Z" }, + { url = "https://files.pythonhosted.org/packages/a8/25/01c0af38fe969473fb292bba9dc2b8f9b451f3112ff242c647fee3d0dfe7/scikit_learn-1.8.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:6b595b07a03069a2b1740dc08c2299993850ea81cce4fe19b2421e0c970de6b7", size = 8066524, upload-time = "2025-12-10T07:08:29.822Z" }, + { url = "https://files.pythonhosted.org/packages/be/ce/a0623350aa0b68647333940ee46fe45086c6060ec604874e38e9ab7d8e6c/scikit_learn-1.8.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:29ffc74089f3d5e87dfca4c2c8450f88bdc61b0fc6ed5d267f3988f19a1309f6", size = 8657133, upload-time = "2025-12-10T07:08:31.865Z" }, + { url = "https://files.pythonhosted.org/packages/b8/cb/861b41341d6f1245e6ca80b1c1a8c4dfce43255b03df034429089ca2a2c5/scikit_learn-1.8.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb65db5d7531bccf3a4f6bec3462223bea71384e2cda41da0f10b7c292b9e7c4", size = 8923223, upload-time = "2025-12-10T07:08:34.166Z" }, + { url = "https://files.pythonhosted.org/packages/76/18/a8def8f91b18cd1ba6e05dbe02540168cb24d47e8dcf69e8d00b7da42a08/scikit_learn-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:56079a99c20d230e873ea40753102102734c5953366972a71d5cb39a32bc40c6", size = 8096518, upload-time = "2025-12-10T07:08:36.339Z" }, + { url = "https://files.pythonhosted.org/packages/d1/77/482076a678458307f0deb44e29891d6022617b2a64c840c725495bee343f/scikit_learn-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:3bad7565bc9cf37ce19a7c0d107742b320c1285df7aab1a6e2d28780df167242", size = 7754546, upload-time = "2025-12-10T07:08:38.128Z" }, + { url = "https://files.pythonhosted.org/packages/2d/d1/ef294ca754826daa043b2a104e59960abfab4cf653891037d19dd5b6f3cf/scikit_learn-1.8.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:4511be56637e46c25721e83d1a9cea9614e7badc7040c4d573d75fbe257d6fd7", size = 8848305, upload-time = "2025-12-10T07:08:41.013Z" }, + { url = "https://files.pythonhosted.org/packages/5b/e2/b1f8b05138ee813b8e1a4149f2f0d289547e60851fd1bb268886915adbda/scikit_learn-1.8.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:a69525355a641bf8ef136a7fa447672fb54fe8d60cab5538d9eb7c6438543fb9", size = 8432257, upload-time = "2025-12-10T07:08:42.873Z" }, + { url = "https://files.pythonhosted.org/packages/26/11/c32b2138a85dcb0c99f6afd13a70a951bfdff8a6ab42d8160522542fb647/scikit_learn-1.8.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c2656924ec73e5939c76ac4c8b026fc203b83d8900362eb2599d8aee80e4880f", size = 8678673, upload-time = "2025-12-10T07:08:45.362Z" }, + { url = "https://files.pythonhosted.org/packages/c7/57/51f2384575bdec454f4fe4e7a919d696c9ebce914590abf3e52d47607ab8/scikit_learn-1.8.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15fc3b5d19cc2be65404786857f2e13c70c83dd4782676dd6814e3b89dc8f5b9", size = 8922467, upload-time = "2025-12-10T07:08:47.408Z" }, + { url = "https://files.pythonhosted.org/packages/35/4d/748c9e2872637a57981a04adc038dacaa16ba8ca887b23e34953f0b3f742/scikit_learn-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:00d6f1d66fbcf4eba6e356e1420d33cc06c70a45bb1363cd6f6a8e4ebbbdece2", size = 8774395, upload-time = "2025-12-10T07:08:49.337Z" }, + { url = "https://files.pythonhosted.org/packages/60/22/d7b2ebe4704a5e50790ba089d5c2ae308ab6bb852719e6c3bd4f04c3a363/scikit_learn-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f28dd15c6bb0b66ba09728cf09fd8736c304be29409bd8445a080c1280619e8c", size = 8002647, upload-time = "2025-12-10T07:08:51.601Z" }, ] [[package]] @@ -1735,6 +2004,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/f2/b31d75cb9b5fa4dd39a0a931ee9b33e7f6f36f23be5ef560bf72e0f92f32/scipy-1.16.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e7efa2681ea410b10dde31a52b18b0154d66f2485328830e45fdf183af5aefc6", size = 38796678, upload-time = "2025-10-28T17:35:26.354Z" }, { url = "https://files.pythonhosted.org/packages/b4/1e/b3723d8ff64ab548c38d87055483714fefe6ee20e0189b62352b5e015bb1/scipy-1.16.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2d1ae2cf0c350e7705168ff2429962a89ad90c2d49d1dd300686d8b2a5af22fc", size = 38640178, upload-time = "2025-10-28T17:35:35.304Z" }, { url = "https://files.pythonhosted.org/packages/8e/f3/d854ff38789aca9b0cc23008d607ced9de4f7ab14fa1ca4329f86b3758ca/scipy-1.16.3-cp313-cp313t-win_arm64.whl", hash = "sha256:0c623a54f7b79dd88ef56da19bc2873afec9673a48f3b85b18e4d402bdd29a5a", size = 25803246, upload-time = "2025-10-28T17:35:42.155Z" }, + { url = "https://files.pythonhosted.org/packages/99/f6/99b10fd70f2d864c1e29a28bbcaa0c6340f9d8518396542d9ea3b4aaae15/scipy-1.16.3-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:875555ce62743e1d54f06cdf22c1e0bc47b91130ac40fe5d783b6dfa114beeb6", size = 36606469, upload-time = "2025-10-28T17:36:08.741Z" }, + { url = "https://files.pythonhosted.org/packages/4d/74/043b54f2319f48ea940dd025779fa28ee360e6b95acb7cd188fad4391c6b/scipy-1.16.3-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:bb61878c18a470021fb515a843dc7a76961a8daceaaaa8bad1332f1bf4b54657", size = 28872043, upload-time = "2025-10-28T17:36:16.599Z" }, + { url = "https://files.pythonhosted.org/packages/4d/e1/24b7e50cc1c4ee6ffbcb1f27fe9f4c8b40e7911675f6d2d20955f41c6348/scipy-1.16.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f2622206f5559784fa5c4b53a950c3c7c1cf3e84ca1b9c4b6c03f062f289ca26", size = 20862952, upload-time = "2025-10-28T17:36:22.966Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3a/3e8c01a4d742b730df368e063787c6808597ccb38636ed821d10b39ca51b/scipy-1.16.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7f68154688c515cdb541a31ef8eb66d8cd1050605be9dcd74199cbd22ac739bc", size = 23508512, upload-time = "2025-10-28T17:36:29.731Z" }, + { url = "https://files.pythonhosted.org/packages/1f/60/c45a12b98ad591536bfe5330cb3cfe1850d7570259303563b1721564d458/scipy-1.16.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8b3c820ddb80029fe9f43d61b81d8b488d3ef8ca010d15122b152db77dc94c22", size = 33413639, upload-time = "2025-10-28T17:36:37.982Z" }, + { url = "https://files.pythonhosted.org/packages/71/bc/35957d88645476307e4839712642896689df442f3e53b0fa016ecf8a3357/scipy-1.16.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d3837938ae715fc0fe3c39c0202de3a8853aff22ca66781ddc2ade7554b7e2cc", size = 35704729, upload-time = "2025-10-28T17:36:46.547Z" }, + { url = "https://files.pythonhosted.org/packages/3b/15/89105e659041b1ca11c386e9995aefacd513a78493656e57789f9d9eab61/scipy-1.16.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aadd23f98f9cb069b3bd64ddc900c4d277778242e961751f77a8cb5c4b946fb0", size = 36086251, upload-time = "2025-10-28T17:36:55.161Z" }, + { url = "https://files.pythonhosted.org/packages/1a/87/c0ea673ac9c6cc50b3da2196d860273bc7389aa69b64efa8493bdd25b093/scipy-1.16.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b7c5f1bda1354d6a19bc6af73a649f8285ca63ac6b52e64e658a5a11d4d69800", size = 38716681, upload-time = "2025-10-28T17:37:04.1Z" }, + { url = "https://files.pythonhosted.org/packages/91/06/837893227b043fb9b0d13e4bd7586982d8136cb249ffb3492930dab905b8/scipy-1.16.3-cp314-cp314-win_amd64.whl", hash = "sha256:e5d42a9472e7579e473879a1990327830493a7047506d58d73fc429b84c1d49d", size = 39358423, upload-time = "2025-10-28T17:38:20.005Z" }, + { url = "https://files.pythonhosted.org/packages/95/03/28bce0355e4d34a7c034727505a02d19548549e190bedd13a721e35380b7/scipy-1.16.3-cp314-cp314-win_arm64.whl", hash = "sha256:6020470b9d00245926f2d5bb93b119ca0340f0d564eb6fbaad843eaebf9d690f", size = 26135027, upload-time = "2025-10-28T17:38:24.966Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6f/69f1e2b682efe9de8fe9f91040f0cd32f13cfccba690512ba4c582b0bc29/scipy-1.16.3-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:e1d27cbcb4602680a49d787d90664fa4974063ac9d4134813332a8c53dbe667c", size = 37028379, upload-time = "2025-10-28T17:37:14.061Z" }, + { url = "https://files.pythonhosted.org/packages/7c/2d/e826f31624a5ebbab1cd93d30fd74349914753076ed0593e1d56a98c4fb4/scipy-1.16.3-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:9b9c9c07b6d56a35777a1b4cc8966118fb16cfd8daf6743867d17d36cfad2d40", size = 29400052, upload-time = "2025-10-28T17:37:21.709Z" }, + { url = "https://files.pythonhosted.org/packages/69/27/d24feb80155f41fd1f156bf144e7e049b4e2b9dd06261a242905e3bc7a03/scipy-1.16.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:3a4c460301fb2cffb7f88528f30b3127742cff583603aa7dc964a52c463b385d", size = 21391183, upload-time = "2025-10-28T17:37:29.559Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d3/1b229e433074c5738a24277eca520a2319aac7465eea7310ea6ae0e98ae2/scipy-1.16.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:f667a4542cc8917af1db06366d3f78a5c8e83badd56409f94d1eac8d8d9133fa", size = 23930174, upload-time = "2025-10-28T17:37:36.306Z" }, + { url = "https://files.pythonhosted.org/packages/16/9d/d9e148b0ec680c0f042581a2be79a28a7ab66c0c4946697f9e7553ead337/scipy-1.16.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f379b54b77a597aa7ee5e697df0d66903e41b9c85a6dd7946159e356319158e8", size = 33497852, upload-time = "2025-10-28T17:37:42.228Z" }, + { url = "https://files.pythonhosted.org/packages/2f/22/4e5f7561e4f98b7bea63cf3fd7934bff1e3182e9f1626b089a679914d5c8/scipy-1.16.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4aff59800a3b7f786b70bfd6ab551001cb553244988d7d6b8299cb1ea653b353", size = 35798595, upload-time = "2025-10-28T17:37:48.102Z" }, + { url = "https://files.pythonhosted.org/packages/83/42/6644d714c179429fc7196857866f219fef25238319b650bb32dde7bf7a48/scipy-1.16.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:da7763f55885045036fabcebd80144b757d3db06ab0861415d1c3b7c69042146", size = 36186269, upload-time = "2025-10-28T17:37:53.72Z" }, + { url = "https://files.pythonhosted.org/packages/ac/70/64b4d7ca92f9cf2e6fc6aaa2eecf80bb9b6b985043a9583f32f8177ea122/scipy-1.16.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ffa6eea95283b2b8079b821dc11f50a17d0571c92b43e2b5b12764dc5f9b285d", size = 38802779, upload-time = "2025-10-28T17:37:59.393Z" }, + { url = "https://files.pythonhosted.org/packages/61/82/8d0e39f62764cce5ffd5284131e109f07cf8955aef9ab8ed4e3aa5e30539/scipy-1.16.3-cp314-cp314t-win_amd64.whl", hash = "sha256:d9f48cafc7ce94cf9b15c6bffdc443a81a27bf7075cf2dcd5c8b40f85d10c4e7", size = 39471128, upload-time = "2025-10-28T17:38:05.259Z" }, + { url = "https://files.pythonhosted.org/packages/64/47/a494741db7280eae6dc033510c319e34d42dd41b7ac0c7ead39354d1a2b5/scipy-1.16.3-cp314-cp314t-win_arm64.whl", hash = "sha256:21d9d6b197227a12dcbf9633320a4e34c6b0e51c57268df255a0942983bac562", size = 26464127, upload-time = "2025-10-28T17:38:11.34Z" }, ] [[package]] @@ -2088,6 +2377,14 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f4/dd/f1c0d879f2863ef209e18823a988dc7a1bf40470750e3ebe927efdb9407f/torch-2.9.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:8301a7b431e51764629208d0edaa4f9e4c33e6df0f2f90b90e261d623df6a4e2", size = 899748978, upload-time = "2025-11-12T15:23:04.568Z" }, { url = "https://files.pythonhosted.org/packages/1f/9f/6986b83a53b4d043e36f3f898b798ab51f7f20fdf1a9b01a2720f445043d/torch-2.9.1-cp313-cp313t-win_amd64.whl", hash = "sha256:2e1c42c0ae92bf803a4b2409fdfed85e30f9027a66887f5e7dcdbc014c7531db", size = 111176995, upload-time = "2025-11-12T15:22:01.618Z" }, { url = "https://files.pythonhosted.org/packages/40/60/71c698b466dd01e65d0e9514b5405faae200c52a76901baf6906856f17e4/torch-2.9.1-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:2c14b3da5df416cf9cb5efab83aa3056f5b8cd8620b8fde81b4987ecab730587", size = 74480347, upload-time = "2025-11-12T15:21:57.648Z" }, + { url = "https://files.pythonhosted.org/packages/48/50/c4b5112546d0d13cc9eaa1c732b823d676a9f49ae8b6f97772f795874a03/torch-2.9.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1edee27a7c9897f4e0b7c14cfc2f3008c571921134522d5b9b5ec4ebbc69041a", size = 74433245, upload-time = "2025-11-12T15:22:39.027Z" }, + { url = "https://files.pythonhosted.org/packages/81/c9/2628f408f0518b3bae49c95f5af3728b6ab498c8624ab1e03a43dd53d650/torch-2.9.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:19d144d6b3e29921f1fc70503e9f2fc572cde6a5115c0c0de2f7ca8b1483e8b6", size = 104134804, upload-time = "2025-11-12T15:22:35.222Z" }, + { url = "https://files.pythonhosted.org/packages/28/fc/5bc91d6d831ae41bf6e9e6da6468f25330522e92347c9156eb3f1cb95956/torch-2.9.1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:c432d04376f6d9767a9852ea0def7b47a7bbc8e7af3b16ac9cf9ce02b12851c9", size = 899747132, upload-time = "2025-11-12T15:23:36.068Z" }, + { url = "https://files.pythonhosted.org/packages/63/5d/e8d4e009e52b6b2cf1684bde2a6be157b96fb873732542fb2a9a99e85a83/torch-2.9.1-cp314-cp314-win_amd64.whl", hash = "sha256:d187566a2cdc726fc80138c3cdb260970fab1c27e99f85452721f7759bbd554d", size = 110934845, upload-time = "2025-11-12T15:22:48.367Z" }, + { url = "https://files.pythonhosted.org/packages/bd/b2/2d15a52516b2ea3f414643b8de68fa4cb220d3877ac8b1028c83dc8ca1c4/torch-2.9.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cb10896a1f7fedaddbccc2017ce6ca9ecaaf990f0973bdfcf405439750118d2c", size = 74823558, upload-time = "2025-11-12T15:22:43.392Z" }, + { url = "https://files.pythonhosted.org/packages/86/5c/5b2e5d84f5b9850cd1e71af07524d8cbb74cba19379800f1f9f7c997fc70/torch-2.9.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:0a2bd769944991c74acf0c4ef23603b9c777fdf7637f115605a4b2d8023110c7", size = 104145788, upload-time = "2025-11-12T15:23:52.109Z" }, + { url = "https://files.pythonhosted.org/packages/a9/8c/3da60787bcf70add986c4ad485993026ac0ca74f2fc21410bc4eb1bb7695/torch-2.9.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:07c8a9660bc9414c39cac530ac83b1fb1b679d7155824144a40a54f4a47bfa73", size = 899735500, upload-time = "2025-11-12T15:24:08.788Z" }, + { url = "https://files.pythonhosted.org/packages/db/2b/f7818f6ec88758dfd21da46b6cd46af9d1b3433e53ddbb19ad1e0da17f9b/torch-2.9.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c88d3299ddeb2b35dcc31753305612db485ab6f1823e37fb29451c8b2732b87e", size = 111163659, upload-time = "2025-11-12T15:23:20.009Z" }, ] [[package]] @@ -2151,6 +2448,8 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f2/50/9a8358d3ef58162c0a415d173cfb45b67de60176e1024f71fbc4d24c0b6d/triton-3.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d2c6b915a03888ab931a9fd3e55ba36785e1fe70cbea0b40c6ef93b20fc85232", size = 170470207, upload-time = "2025-11-11T17:41:00.253Z" }, { url = "https://files.pythonhosted.org/packages/27/46/8c3bbb5b0a19313f50edcaa363b599e5a1a5ac9683ead82b9b80fe497c8d/triton-3.5.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3f4346b6ebbd4fad18773f5ba839114f4826037c9f2f34e0148894cd5dd3dba", size = 170470410, upload-time = "2025-11-11T17:41:06.319Z" }, { url = "https://files.pythonhosted.org/packages/37/92/e97fcc6b2c27cdb87ce5ee063d77f8f26f19f06916aa680464c8104ef0f6/triton-3.5.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0b4d2c70127fca6a23e247f9348b8adde979d2e7a20391bfbabaac6aebc7e6a8", size = 170579924, upload-time = "2025-11-11T17:41:12.455Z" }, + { url = "https://files.pythonhosted.org/packages/a4/e6/c595c35e5c50c4bc56a7bac96493dad321e9e29b953b526bbbe20f9911d0/triton-3.5.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0637b1efb1db599a8e9dc960d53ab6e4637db7d4ab6630a0974705d77b14b60", size = 170480488, upload-time = "2025-11-11T17:41:18.222Z" }, + { url = "https://files.pythonhosted.org/packages/16/b5/b0d3d8b901b6a04ca38df5e24c27e53afb15b93624d7fd7d658c7cd9352a/triton-3.5.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bac7f7d959ad0f48c0e97d6643a1cc0fd5786fe61cb1f83b537c6b2d54776478", size = 170582192, upload-time = "2025-11-11T17:41:23.963Z" }, ] [[package]]