From cee8d916f544440f95e65158cc573d95bd0d0826 Mon Sep 17 00:00:00 2001 From: huanghaoyuanhhy Date: Tue, 7 Jul 2026 14:41:08 +0800 Subject: [PATCH] enhance: resolve global cluster topology via DNS SRV records Discover ha-manager seed servers through the _grpc._tcp. SRV record set instead of hitting the global endpoint directly, then concurrently probe the nearest seeds (spanning priorities) and use the topology with the highest version. The topology request now carries the raw endpoint hostname verbatim via the ?endpoint= query parameter; the server derives the global cluster id from it (its first DNS label), so the SDK does no gcid parsing. Endpoints without SRV records raise instead of falling back to the legacy beta path. Adds a dnspython dependency for SRV resolution. Signed-off-by: huanghaoyuanhhy --- pymilvus/client/connection_manager.py | 14 +- pymilvus/client/global_topology.py | 395 ++++++++++++++++++--- pyproject.toml | 1 + tests/unit/test_connection_manager.py | 6 +- tests/unit/test_global_topology.py | 275 ++++++++++++--- uv.lock | 476 ++++++++++++++------------ 6 files changed, 844 insertions(+), 323 deletions(-) diff --git a/pymilvus/client/connection_manager.py b/pymilvus/client/connection_manager.py index 46f994612..8f893479b 100644 --- a/pymilvus/client/connection_manager.py +++ b/pymilvus/client/connection_manager.py @@ -327,7 +327,9 @@ def _fetch_and_start_refresher(self, config: ConnectionConfig) -> GlobalTopology Returns: The fetched GlobalTopology (caller uses it to connect to primary). """ - topology = fetch_topology(config.uri, config.token) + topology = fetch_topology( + config.uri, config.token, on_topology_change=self._on_topology_change + ) with self._lock: self._topology = topology @@ -338,6 +340,7 @@ def _fetch_and_start_refresher(self, config: ConnectionConfig) -> GlobalTopology token=config.token, topology=topology, on_topology_change=self._on_topology_change, + get_current=self.get_topology, ) self._refresher.start() return topology @@ -353,11 +356,16 @@ def on_unavailable(self, managed: ManagedConnection) -> bool: return True # No config, trigger recovery with self._lock: - old_primary = self._topology.primary.endpoint if self._topology else None + old_topology = self._topology + old_primary = old_topology.primary.endpoint if old_topology else None # Fetch fresh topology try: - new_topology = fetch_topology(self._config.uri, self._config.token) + new_topology = fetch_topology( + self._config.uri, + self._config.token, + cached_version=old_topology.version if old_topology else None, + ) except Exception: logger.warning("Failed to refresh topology on UNAVAILABLE", exc_info=True) return True # Error fetching, trigger recovery anyway diff --git a/pymilvus/client/global_topology.py b/pymilvus/client/global_topology.py index 33ae40f75..94acafcb0 100644 --- a/pymilvus/client/global_topology.py +++ b/pymilvus/client/global_topology.py @@ -2,9 +2,13 @@ import random import threading import time +from concurrent.futures import Future, ThreadPoolExecutor, as_completed from dataclasses import dataclass -from typing import Callable, List, Optional +from typing import Callable, Dict, List, Optional, Tuple +from urllib.parse import urlparse +import dns.exception +import dns.resolver import requests from pymilvus.exceptions import MilvusException @@ -66,6 +70,22 @@ def primary(self) -> ClusterInfo: MAX_DELAY = 10.0 # seconds REQUEST_TIMEOUT = 10 # seconds +# SRV-based topology discovery. +# +# The SDK never talks to the global endpoint directly. It derives an SRV query +# name from the endpoint hostname, resolves it to a set of ha-manager seed +# servers, and fetches topology from them. The global cluster id is NOT parsed +# here: the endpoint hostname is passed verbatim to the server via the +# ``?endpoint=`` query parameter, and the server derives the id from it (its +# first DNS label). Keeping the parse server-side lets the endpoint format +# evolve without touching every SDK. +SRV_SERVICE_PREFIX = "_grpc._tcp." +# Query parameter carrying the raw endpoint hostname to the topology API. +ENDPOINT_QUERY_PARAM = "endpoint" +# Minimum number of seeds to probe concurrently, spanning priorities so a slow +# or dead nearest region does not stall discovery. +DEFAULT_PROBE_COUNT = 2 + def _parse_topology_response(data: dict) -> GlobalTopology: """Parse the topology response from the REST API.""" @@ -80,56 +100,303 @@ def _parse_topology_response(data: dict) -> GlobalTopology: return GlobalTopology(version=int(data["version"]), clusters=clusters) -def fetch_topology(global_endpoint: str, token: str) -> GlobalTopology: - """Fetch the global cluster topology from the REST API. +@dataclass +class SrvTarget: + """A single ha-manager seed server resolved from an SRV record.""" - Args: - global_endpoint: The global cluster endpoint URL - token: Authentication token + priority: int + weight: int + port: int + target: str # ha-manager seed server hostname - Returns: - GlobalTopology object containing cluster information - Raises: - MilvusException: If topology cannot be fetched after retries +def _endpoint_hostname(global_endpoint: str) -> str: + """Extract the bare hostname from a global cluster endpoint. + + Strips scheme, credentials, port and path so the result can be used both to + derive the SRV query name and as the verbatim ``?endpoint=`` value. """ - # Build the topology URL - endpoint = global_endpoint.rstrip("/") + endpoint = global_endpoint.strip() if not endpoint.startswith(("http://", "https://")): endpoint = f"https://{endpoint}" - url = f"{endpoint}/{GLOBAL_CLUSTER_IDENTIFIER}/topology" + hostname = urlparse(endpoint).hostname + if not hostname: + raise MilvusException(message=f"Invalid global cluster endpoint: {global_endpoint}") + return hostname + + +def _resolve_srv(hostname: str) -> List[SrvTarget]: + """Resolve the ``_grpc._tcp.`` SRV record set. + + Returns an empty list when the name resolves but carries no SRV records + (NXDOMAIN / empty answer) so the caller can raise a clear "not provisioned" + error. Transient DNS failures (timeout, no reachable nameserver) are raised. + """ + srv_name = f"{SRV_SERVICE_PREFIX}{hostname}" + try: + answers = dns.resolver.resolve(srv_name, "SRV", lifetime=REQUEST_TIMEOUT) + except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): + return [] + except dns.exception.DNSException as e: + raise MilvusException(message=f"SRV resolution failed for {srv_name}: {e}") from e + + targets = [ + SrvTarget( + priority=rr.priority, + weight=rr.weight, + port=rr.port, + target=str(rr.target).rstrip("."), + ) + for rr in answers + ] + # Nearest first: lower priority wins; higher weight breaks ties. + targets.sort(key=lambda t: (t.priority, -t.weight)) + return targets + + +def _weighted_choice(group: List[SrvTarget]) -> SrvTarget: + """Pick one target from a same-priority group, weighted by SRV weight.""" + weights = [max(t.weight, 0) for t in group] + if sum(weights) == 0: + return random.choice(group) # noqa: S311 - all-zero weights: pick uniformly + return random.choices(group, weights=weights, k=1)[0] # noqa: S311 + + +def _pick_candidates(targets: List[SrvTarget], count: int = DEFAULT_PROBE_COUNT) -> List[SrvTarget]: + """Select seeds to probe: one weighted pick per priority, lowest first. + + Spans priorities (so a dead nearest region does not stall discovery) and + returns at least ``count`` seeds when that many exist. + """ + by_priority = {} + for t in targets: + by_priority.setdefault(t.priority, []).append(t) + + picked: List[SrvTarget] = [] + for priority in sorted(by_priority): + picked.append(_weighted_choice(by_priority[priority])) + if len(picked) >= count: + return picked + + # Few priority groups: top up from the remaining nearest targets. + for t in targets: + if len(picked) >= count: + break + if t not in picked: + picked.append(t) + return picked + + +def _probe_seed(base_url: str, hostname: str, token: str) -> GlobalTopology: + """Fetch topology from a single seed. Raises on any failure. + + The global cluster is identified by the raw ``hostname``, passed verbatim via + the ``?endpoint=`` query parameter; the server derives the id from it. + """ + url = f"{base_url}/{GLOBAL_CLUSTER_IDENTIFIER}/topology" + response = requests.get( + url, + headers={"Authorization": f"Bearer {token}"}, + params={ENDPOINT_QUERY_PARAM: hostname}, + timeout=REQUEST_TIMEOUT, + ) + if response.status_code != 200: + msg = f"Topology request failed with status {response.status_code}: {response.text}" + raise RuntimeError(msg) + + result = response.json() + if result.get("code", 0) != 0: + raise MilvusException(message=result.get("message", "Unknown API error")) + return _parse_topology_response(result["data"]) + + +def _watch_remaining( + executor: ThreadPoolExecutor, + futures: Dict[Future, SrvTarget], + answered: Future, + topology: GlobalTopology, + on_higher_version: Callable[[GlobalTopology], None], +) -> None: + """Watch the seeds still in flight and trigger replacement on newer answers. + + Runs in a daemon thread after the first answer is handed over, so a slow + seed does not stall the initial connection while a newer version from the + remaining seeds can still win the replacement flow. + """ + + def watch() -> None: + try: + for future in as_completed(futures): + if future is answered: + continue + try: + new_topology = future.result() + except Exception as e: # one bad seed must not fail the rest + logger.warning(f"Topology probe failed: {e}") + continue + if new_topology.version > topology.version: + try: + on_higher_version(new_topology) + except Exception: + logger.warning("Topology replacement callback failed", exc_info=True) + return # one replacement is enough; the refresher keeps polling + finally: + executor.shutdown(wait=False, cancel_futures=True) + + threading.Thread(target=watch, daemon=True).start() + + +def _probe_candidates( + candidates: List[SrvTarget], + hostname: str, + token: str, + wait_all: bool, + on_higher_version: Optional[Callable[[GlobalTopology], None]] = None, +) -> Tuple[Optional[GlobalTopology], Optional[MilvusException]]: + """Probe seeds concurrently; return (best topology, sticky API error). + + With ``wait_all`` (a topology version is already cached locally) every seed + is polled and the highest version wins, guarding the cache against stale + answers. Without it (first fetch, nothing to guard) the first seed that + answers wins so a slow seed does not stall the initial connection; the + seeds still in flight keep being polled in the background and + ``on_higher_version`` triggers the replacement flow when one returns a + newer version. An API-level error (e.g. auth failure) is the same across + seeds, so it is captured and surfaced when no seed succeeds instead of a + generic "unreachable". + """ + best: Optional[GlobalTopology] = None + api_error: Optional[MilvusException] = None + executor = ThreadPoolExecutor(max_workers=len(candidates)) + futures = { + executor.submit(_probe_seed, f"https://{t.target}:{t.port}", hostname, token): t + for t in candidates + } + answered: Optional[Future] = None + try: + for future in as_completed(futures): + target = futures[future] + try: + topology = future.result() + except MilvusException as e: + api_error = e + continue + except Exception as e: # one bad seed must not fail the rest + logger.warning(f"Topology probe failed for {target.target}: {e}") + continue + if best is None or topology.version > best.version: + best = topology + if not wait_all: + answered = future + break + except BaseException: + executor.shutdown(wait=False, cancel_futures=True) + raise + + if wait_all: + # Every seed was polled; the highest version seen wins. + executor.shutdown(wait=False, cancel_futures=True) + elif best is not None and on_higher_version is not None and answered is not None: + # First fetch: hand the seeds still in flight to a background watcher. + _watch_remaining(executor, futures, answered, best, on_higher_version) + else: + executor.shutdown(wait=False, cancel_futures=True) + return best, api_error + + +def _backoff_sleep(attempt: int) -> float: + """Sleep with exponential backoff (+jitter) before the next attempt. + + Returns the computed delay so callers can include it in log messages. + """ + delay = min(BASE_DELAY * (2**attempt), MAX_DELAY) + delay += random.uniform(0, delay * 0.1) # noqa: S311 + time.sleep(delay) + return delay + + +def fetch_topology( + global_endpoint: str, + token: str, + cached_version: Optional[int] = None, + on_topology_change: Optional[Callable[[GlobalTopology], None]] = None, +) -> GlobalTopology: + """Fetch the global cluster topology via SRV-based seed discovery. + + Resolves ``_grpc._tcp.`` to ha-manager seeds, then concurrently + probes the nearest ones and returns the topology with the highest version. + + Args: + global_endpoint: The global cluster endpoint (URL or bare hostname). + token: Authentication token. + cached_version: Version of the topology already cached locally, if any. + When set, every seed is polled and the highest version wins; when + None (first fetch) the first seed that answers wins, and the rest + keep being polled in the background. + on_topology_change: Replacement flow invoked with a topology whose + version is higher than the first answer (only used when + ``cached_version`` is None). + + Returns: + GlobalTopology object containing cluster information. - headers = {"Authorization": f"Bearer {token}"} + Raises: + MilvusException: If the endpoint has no SRV records, the server rejects + the request, or DNS / seed probing fails after retries. + """ + hostname = _endpoint_hostname(global_endpoint) + candidates: List[SrvTarget] = [] - last_error = None for attempt in range(MAX_RETRIES): + # Resolve inside the retry loop: DNS timeouts / unreachable nameservers + # are transient and deserve the same backoff as an unreachable seed. + # Only NXDOMAIN / empty answers (a deterministic "not provisioned" + # state) fail fast below. try: - response = requests.get(url, headers=headers, timeout=REQUEST_TIMEOUT) - - if response.status_code != 200: - msg = f"Topology request failed with status {response.status_code}: {response.text}" - raise RuntimeError(msg) # Retryable HTTP error - - result = response.json() - if result.get("code", 0) != 0: - raise MilvusException(message=result.get("message", "Unknown API error")) - - return _parse_topology_response(result["data"]) - - except MilvusException: - raise - except (requests.exceptions.RequestException, RuntimeError) as e: - last_error = e - if attempt < MAX_RETRIES - 1: - delay = min(BASE_DELAY * (2**attempt), MAX_DELAY) - delay += random.uniform(0, delay * 0.1) # noqa: S311 - logger.warning( - f"Topology fetch attempt {attempt + 1} failed: {e}. Retrying in {delay:.1f}s" + targets = _resolve_srv(hostname) + except MilvusException as e: + if attempt == MAX_RETRIES - 1: + raise + delay = _backoff_sleep(attempt) + logger.warning( + f"SRV resolution failed for '{hostname}' (attempt {attempt + 1}): " + f"{e}; retrying in {delay:.1f}s" + ) + continue + + if not targets: + raise MilvusException( + message=( + f"No SRV records found for global cluster endpoint " + f"'{hostname}' ({SRV_SERVICE_PREFIX}{hostname})" ) - time.sleep(delay) + ) + candidates = _pick_candidates(targets) + + best, api_error = _probe_candidates( + candidates, + hostname, + token, + wait_all=cached_version is not None, + on_higher_version=on_topology_change if cached_version is None else None, + ) + if best is not None: + return best + if api_error is not None: + # Deterministic server-side rejection (e.g. auth): do not retry. + raise api_error + if attempt < MAX_RETRIES - 1: + delay = _backoff_sleep(attempt) + logger.warning( + f"All {len(candidates)} topology seeds unreachable " + f"(attempt {attempt + 1}); retrying in {delay:.1f}s" + ) raise MilvusException( - message=f"Failed to fetch global topology after {MAX_RETRIES} attempts: {last_error}" + message=( + f"Failed to fetch global topology from {len(candidates)} seed(s) " + f"for '{hostname}' after {MAX_RETRIES} attempts" + ) ) @@ -147,12 +414,18 @@ def __init__( topology: GlobalTopology, refresh_interval: float = DEFAULT_REFRESH_INTERVAL, on_topology_change: Optional[Callable] = None, + get_current: Optional[Callable[[], Optional[GlobalTopology]]] = None, ): self._global_endpoint = global_endpoint self._token = token self._topology = topology self._refresh_interval = refresh_interval self._on_topology_change = on_topology_change + # Optional provider of the authoritative current topology (the shared + # copy owned by the connection strategy). When set, version comparisons + # and cached_version derive from it so the refresher's private snapshot + # cannot diverge and trigger a version rollback. + self._get_current = get_current self._lock = threading.Lock() self._stop_event = threading.Event() @@ -199,24 +472,40 @@ def _refresh_loop(self) -> None: while not self._stop_event.wait(self._refresh_interval): self._try_refresh() + def _current_topology(self) -> GlobalTopology: + """The authoritative current topology for version comparisons. + + Uses the provider's shared copy when set, so refreshes never regress + the version the connection strategy is actually routing on. + """ + if self._get_current is not None: + current = self._get_current() + if current is not None: + return current + with self._lock: + return self._topology + def _try_refresh(self) -> None: """Attempt to refresh the topology.""" try: - new_topology = fetch_topology(self._global_endpoint, self._token) - - with self._lock: - if new_topology.version > self._topology.version: - old_topology = self._topology - self._topology = new_topology - logger.info( - f"Topology updated: version {old_topology.version} -> {new_topology.version}" - ) - - if self._on_topology_change: - try: - self._on_topology_change(new_topology) - except Exception: - logger.warning("Topology change callback failed", exc_info=True) + current = self._current_topology() + new_topology = fetch_topology( + self._global_endpoint, self._token, cached_version=current.version + ) + + if new_topology.version > current.version: + logger.info( + f"Topology updated: version {current.version} -> {new_topology.version}" + ) + if self._get_current is None: + with self._lock: + self._topology = new_topology + + if self._on_topology_change: + try: + self._on_topology_change(new_topology) + except Exception: + logger.warning("Topology change callback failed", exc_info=True) except Exception: logger.warning("Topology refresh failed", exc_info=True) diff --git a/pyproject.toml b/pyproject.toml index af695158a..b4bc1760b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ dependencies=[ "pandas>=1.2.4", "cachetools>=5.0.0", "requests>=2.20.0", + "dnspython>=2.0.0", # SRV record resolution for global cluster topology discovery ] classifiers=[ diff --git a/tests/unit/test_connection_manager.py b/tests/unit/test_connection_manager.py index a9101ca1a..8d0a4e3cb 100644 --- a/tests/unit/test_connection_manager.py +++ b/tests/unit/test_connection_manager.py @@ -4,7 +4,7 @@ import asyncio import threading import time -from unittest.mock import AsyncMock, Mock, patch +from unittest.mock import ANY, AsyncMock, Mock, patch import grpc import pytest @@ -467,7 +467,9 @@ def test_create_handler_fetches_topology( strategy = GlobalStrategy() handler = strategy.create_handler(config) - mock_fetch.assert_called_once_with("https://global-cluster.example.com:19530", "mytoken") + mock_fetch.assert_called_once_with( + "https://global-cluster.example.com:19530", "mytoken", on_topology_change=ANY + ) mock_handler_cls.assert_called_once_with( uri="https://primary:19530", token="mytoken", diff --git a/tests/unit/test_global_topology.py b/tests/unit/test_global_topology.py index d202e91b9..fc8b45433 100644 --- a/tests/unit/test_global_topology.py +++ b/tests/unit/test_global_topology.py @@ -1,13 +1,21 @@ import threading from unittest.mock import MagicMock, patch +import dns.exception +import dns.resolver import pytest +import requests from pymilvus.client.global_topology import ( GLOBAL_CLUSTER_IDENTIFIER, ClusterCapability, ClusterInfo, GlobalTopology, + SrvTarget, TopologyRefresher, + _endpoint_hostname, + _pick_candidates, + _resolve_srv, + _weighted_choice, fetch_topology, is_global_endpoint, ) @@ -19,7 +27,7 @@ def _make_topology(version=1, clusters=None): if clusters is None: clusters = [ - ClusterInfo(cluster_id="in01", endpoint="https://in01.zilliz.com", capability=3) + ClusterInfo(cluster_id="in01", endpoint="https://in01.example.com", capability=3) ] return GlobalTopology(version=version, clusters=clusters) @@ -27,7 +35,7 @@ def _make_topology(version=1, clusters=None): def _mock_response( version="1", cluster_id="in01-xxx", - endpoint="https://in01-xxx.zilliz.com", + endpoint="https://in01-xxx.example.com", capability=3, code=0, message=None, @@ -53,13 +61,13 @@ def _mock_response( class TestIsGlobalEndpoint: def test_detects_global_cluster_in_url(self): - assert is_global_endpoint("https://glo-xxx.global-cluster.vectordb.zilliz.com") is True + assert is_global_endpoint("https://glo-xxx.global-cluster.vectordb.example.com") is True def test_detects_global_cluster_case_insensitive(self): - assert is_global_endpoint("https://glo-xxx.GLOBAL-CLUSTER.vectordb.zilliz.com") is True + assert is_global_endpoint("https://glo-xxx.GLOBAL-CLUSTER.vectordb.example.com") is True def test_rejects_regular_endpoint(self): - assert is_global_endpoint("https://in01-xxx.zilliz.com") is False + assert is_global_endpoint("https://in01-xxx.example.com") is False def test_rejects_empty_string(self): assert is_global_endpoint("") is False @@ -81,13 +89,13 @@ def test_primary_capability(self): class TestClusterInfo: def test_primary_cluster(self): cluster = ClusterInfo( - cluster_id="in01-xxx", endpoint="https://in01-xxx.zilliz.com", capability=3 + cluster_id="in01-xxx", endpoint="https://in01-xxx.example.com", capability=3 ) assert cluster.is_primary is True def test_secondary_cluster(self): cluster = ClusterInfo( - cluster_id="in02-xxx", endpoint="https://in02-xxx.zilliz.com", capability=1 + cluster_id="in02-xxx", endpoint="https://in02-xxx.example.com", capability=1 ) assert cluster.is_primary is False @@ -101,10 +109,10 @@ def test_finds_primary_cluster(self): version=1, clusters=[ ClusterInfo( - cluster_id="in01-xxx", endpoint="https://in01-xxx.zilliz.com", capability=3 + cluster_id="in01-xxx", endpoint="https://in01-xxx.example.com", capability=3 ), ClusterInfo( - cluster_id="in02-xxx", endpoint="https://in02-xxx.zilliz.com", capability=1 + cluster_id="in02-xxx", endpoint="https://in02-xxx.example.com", capability=1 ), ], ) @@ -117,7 +125,7 @@ def test_raises_when_no_primary(self): version=1, clusters=[ ClusterInfo( - cluster_id="in02-xxx", endpoint="https://in02-xxx.zilliz.com", capability=1 + cluster_id="in02-xxx", endpoint="https://in02-xxx.example.com", capability=1 ), ], ) @@ -127,65 +135,238 @@ def test_raises_when_no_primary(self): # ── TestFetchTopology ───────────────────────────────────────────────────────── -_GLOBAL_URL = "https://glo-xxx.global-cluster.vectordb.zilliz.com" +_GLOBAL_URL = "https://glo-xxx.global-cluster.vectordb.example.com" +_GLOBAL_HOST = "glo-xxx.global-cluster.vectordb.example.com" _TOKEN = "test-token" +class _FakeSrvRecord: + """Mimics a dnspython SRV rdata item (target carries a trailing dot).""" + + def __init__(self, target, priority=10, weight=50, port=443): + self.priority = priority + self.weight = weight + self.port = port + self.target = f"{target}." + + +def _patch_srv(*records): + """Patch DNS resolution to return the given fake SRV records.""" + return patch( + "pymilvus.client.global_topology.dns.resolver.resolve", + return_value=list(records), + ) + + +class TestEndpointHostname: + def test_strips_scheme_port_and_path(self): + assert _endpoint_hostname(f"https://{_GLOBAL_HOST}:443/mydb") == _GLOBAL_HOST + + def test_bare_hostname(self): + assert _endpoint_hostname(_GLOBAL_HOST) == _GLOBAL_HOST + + def test_strips_credentials(self): + assert _endpoint_hostname(f"https://user:pass@{_GLOBAL_HOST}") == _GLOBAL_HOST + + +class TestResolveSrv: + def test_returns_targets_sorted_by_priority_then_weight(self): + with _patch_srv( + _FakeSrvRecord("t-us.example.com", priority=20, weight=50), + _FakeSrvRecord("t-ap-low.example.com", priority=10, weight=10), + _FakeSrvRecord("t-ap-high.example.com", priority=10, weight=90), + ): + targets = _resolve_srv(_GLOBAL_HOST) + # priority ascending, then weight descending; trailing dot stripped + assert [t.target for t in targets] == [ + "t-ap-high.example.com", + "t-ap-low.example.com", + "t-us.example.com", + ] + assert targets[0].port == 443 + + def test_nxdomain_returns_empty(self): + with patch( + "pymilvus.client.global_topology.dns.resolver.resolve", + side_effect=dns.resolver.NXDOMAIN, + ): + assert _resolve_srv(_GLOBAL_HOST) == [] + + def test_no_answer_returns_empty(self): + with patch( + "pymilvus.client.global_topology.dns.resolver.resolve", + side_effect=dns.resolver.NoAnswer, + ): + assert _resolve_srv(_GLOBAL_HOST) == [] + + def test_transient_dns_error_raises(self): + with patch( + "pymilvus.client.global_topology.dns.resolver.resolve", + side_effect=dns.exception.Timeout, + ): + with pytest.raises(MilvusException, match="SRV resolution failed"): + _resolve_srv(_GLOBAL_HOST) + + +class TestPickCandidates: + def test_spans_priorities(self): + targets = [ + SrvTarget(priority=10, weight=50, port=443, target="ap1"), + SrvTarget(priority=10, weight=50, port=443, target="ap2"), + SrvTarget(priority=20, weight=50, port=443, target="us1"), + ] + picked = _pick_candidates(targets, count=2) + assert len(picked) == 2 + assert {t.priority for t in picked} == {10, 20} # one per priority + + def test_single_priority_group_tops_up(self): + targets = [ + SrvTarget(priority=10, weight=50, port=443, target="ap1"), + SrvTarget(priority=10, weight=50, port=443, target="ap2"), + ] + picked = _pick_candidates(targets, count=2) + assert len(picked) == 2 + assert {t.target for t in picked} == {"ap1", "ap2"} + + def test_fewer_targets_than_count(self): + targets = [SrvTarget(priority=10, weight=50, port=443, target="only")] + assert _pick_candidates(targets, count=2) == targets + + def test_all_zero_weights_picks_randomly(self): + targets = [ + SrvTarget(priority=0, weight=0, port=443, target="a"), + SrvTarget(priority=0, weight=0, port=443, target="b"), + ] + with patch("pymilvus.client.global_topology.random.choice", return_value=targets[1]): + assert _weighted_choice(targets) is targets[1] + + class TestFetchTopology: def test_fetches_topology_successfully(self): mock_response = _mock_response(version="123") - with patch( + with _patch_srv(_FakeSrvRecord("ha-mgr-ap1.global-cluster.vectordb.example.com")), patch( "pymilvus.client.global_topology.requests.get", return_value=mock_response ) as mock_get: topology = fetch_topology(_GLOBAL_URL, _TOKEN) - mock_get.assert_called_once() + assert mock_get.called call_args = mock_get.call_args - assert "global-cluster/topology" in call_args[0][0] + url = call_args[0][0] + # connects to the SRV target, not the global endpoint + assert "ha-mgr-ap1.global-cluster.vectordb.example.com" in url + assert f"/{GLOBAL_CLUSTER_IDENTIFIER}/topology" in url assert call_args[1]["headers"]["Authorization"] == f"Bearer {_TOKEN}" + # the raw endpoint hostname is passed verbatim; server derives the gcid + assert call_args[1]["params"]["endpoint"] == _GLOBAL_HOST assert topology.version == 123 - assert len(topology.clusters) == 1 assert topology.primary.cluster_id == "in01-xxx" - def test_retries_on_failure(self): - mock_fail = MagicMock() - mock_fail.status_code = 500 - mock_success = _mock_response(version="1") + def test_raises_when_no_srv_records(self): with patch( + "pymilvus.client.global_topology.dns.resolver.resolve", + side_effect=dns.resolver.NXDOMAIN, + ) as mock_resolve: + with pytest.raises(MilvusException, match="No SRV records"): + fetch_topology(_GLOBAL_URL, _TOKEN) + # Deterministic "not provisioned" state: fail fast, no retries. + assert mock_resolve.call_count == 1 + + def test_selects_highest_version_across_seeds(self): + # A version is cached locally: every seed is polled, highest wins. + with _patch_srv( + _FakeSrvRecord("ha-mgr-ap1.global-cluster.example.com", priority=10), + _FakeSrvRecord("ha-mgr-us1.global-cluster.example.com", priority=20), + ), patch( + "pymilvus.client.global_topology.requests.get", + side_effect=[_mock_response(version="5"), _mock_response(version="9")], + ): + topology = fetch_topology(_GLOBAL_URL, _TOKEN, cached_version=1) + assert topology.version == 9 + + def test_first_answer_wins_without_cached_version(self): + # First fetch, nothing cached: the first seed that answers wins. + with _patch_srv( + _FakeSrvRecord("ha-mgr-ap1.global-cluster.example.com", priority=10), + _FakeSrvRecord("ha-mgr-us1.global-cluster.example.com", priority=20), + ), patch( + "pymilvus.client.global_topology.requests.get", + side_effect=[_mock_response(version="5"), _mock_response(version="9")], + ): + topology = fetch_topology(_GLOBAL_URL, _TOKEN) + assert topology.version == 5 + + def test_background_higher_version_triggers_replacement(self): + # The first answer is handed over immediately, but a newer answer from + # a remaining seed still runs the replacement flow. + replacement = threading.Event() + received = [] + + def on_topology_change(topology): + received.append(topology) + replacement.set() + + with _patch_srv( + _FakeSrvRecord("ha-mgr-ap1.global-cluster.example.com", priority=10), + _FakeSrvRecord("ha-mgr-us1.global-cluster.example.com", priority=20), + ), patch( "pymilvus.client.global_topology.requests.get", - side_effect=[mock_fail, mock_success], + side_effect=[_mock_response(version="5"), _mock_response(version="9")], + ): + topology = fetch_topology(_GLOBAL_URL, _TOKEN, on_topology_change=on_topology_change) + assert topology.version == 5 + assert replacement.wait(timeout=2.0) + assert received[0].version == 9 + + def test_retries_when_all_seeds_unreachable_then_succeeds(self): + with _patch_srv(_FakeSrvRecord("ha-mgr-ap1.global-cluster.example.com")), patch( + "pymilvus.client.global_topology.requests.get", + side_effect=[requests.exceptions.ConnectionError("boom"), _mock_response(version="7")], ), patch("pymilvus.client.global_topology.time.sleep"): topology = fetch_topology(_GLOBAL_URL, _TOKEN) - assert topology.version == 1 + assert topology.version == 7 - def test_raises_after_max_retries(self): - mock_response = MagicMock() - mock_response.status_code = 500 - mock_response.text = "Internal Server Error" - with patch( - "pymilvus.client.global_topology.requests.get", return_value=mock_response + def test_raises_after_all_retries_fail(self): + with _patch_srv(_FakeSrvRecord("ha-mgr-ap1.global-cluster.example.com")), patch( + "pymilvus.client.global_topology.requests.get", + side_effect=requests.exceptions.ConnectionError("down"), ), patch("pymilvus.client.global_topology.time.sleep"): with pytest.raises(MilvusException, match="Failed to fetch global topology"): fetch_topology(_GLOBAL_URL, _TOKEN) - def test_raises_on_api_error_code(self): + def test_raises_api_error_without_retry(self): mock_response = _mock_response(code=1, message="Invalid token") - with patch( + with _patch_srv(_FakeSrvRecord("ha-mgr-ap1.global-cluster.example.com")), patch( "pymilvus.client.global_topology.requests.get", return_value=mock_response - ), patch("pymilvus.client.global_topology.time.sleep"): + ) as mock_get, patch("pymilvus.client.global_topology.time.sleep") as mock_sleep: with pytest.raises(MilvusException, match="Invalid token"): fetch_topology(_GLOBAL_URL, _TOKEN) + assert mock_get.call_count == 1 # single seed, one round + mock_sleep.assert_not_called() # deterministic rejection, no retry - def test_adds_https_prefix_when_missing(self): - mock_response = _mock_response() + def test_srv_resolution_failure_raises(self): with patch( - "pymilvus.client.global_topology.requests.get", return_value=mock_response - ) as mock_get: - fetch_topology("glo-xxx.global-cluster.vectordb.zilliz.com", _TOKEN) - call_args = mock_get.call_args - assert ( - call_args[0][0] - == "https://glo-xxx.global-cluster.vectordb.zilliz.com/global-cluster/topology" - ) + "pymilvus.client.global_topology.dns.resolver.resolve", + side_effect=dns.exception.Timeout, + ) as mock_resolve, patch("pymilvus.client.global_topology.time.sleep"): + with pytest.raises(MilvusException, match="SRV resolution failed"): + fetch_topology(_GLOBAL_URL, _TOKEN) + # Transient DNS failure is retried like an unreachable seed. + assert mock_resolve.call_count == 3 + + def test_retries_transient_dns_failure_then_succeeds(self): + with patch( + "pymilvus.client.global_topology.dns.resolver.resolve", + side_effect=[ + dns.exception.Timeout, + [_FakeSrvRecord("ha-mgr-ap1.global-cluster.example.com")], + ], + ), patch( + "pymilvus.client.global_topology.requests.get", + return_value=_mock_response(version="7"), + ), patch( + "pymilvus.client.global_topology.time.sleep" + ): + topology = fetch_topology(_GLOBAL_URL, _TOKEN) + assert topology.version == 7 # ── TestTopologyRefresher ───────────────────────────────────────────────────── @@ -195,7 +376,7 @@ class TestTopologyRefresher: def test_starts_and_stops(self): topology = _make_topology() refresher = TopologyRefresher( - global_endpoint="https://glo.global-cluster.zilliz.com", + global_endpoint="https://glo.global-cluster.example.com", token="test-token", topology=topology, refresh_interval=0.1, @@ -210,7 +391,7 @@ def test_updates_topology_on_version_change(self): new_topology = _make_topology( version=2, clusters=[ - ClusterInfo(cluster_id="in02", endpoint="https://in02.zilliz.com", capability=3) + ClusterInfo(cluster_id="in02", endpoint="https://in02.example.com", capability=3) ], ) @@ -222,7 +403,7 @@ def on_topology_change(topo): callback_called.set() refresher = TopologyRefresher( - global_endpoint="https://glo.global-cluster.zilliz.com", + global_endpoint="https://glo.global-cluster.example.com", token="test-token", topology=initial_topology, refresh_interval=0.05, @@ -254,7 +435,7 @@ def on_topology_change(topo): callback_called.append(topo) refresher = TopologyRefresher( - global_endpoint="https://glo.global-cluster.zilliz.com", + global_endpoint="https://glo.global-cluster.example.com", token="test-token", topology=topology, refresh_interval=0.05, @@ -273,7 +454,7 @@ def test_trigger_refresh_immediate(self): new_topology = _make_topology( version=2, clusters=[ - ClusterInfo(cluster_id="in02", endpoint="https://in02.zilliz.com", capability=3) + ClusterInfo(cluster_id="in02", endpoint="https://in02.example.com", capability=3) ], ) @@ -283,7 +464,7 @@ def on_topology_change(topo): callback_called.set() refresher = TopologyRefresher( - global_endpoint="https://glo.global-cluster.zilliz.com", + global_endpoint="https://glo.global-cluster.example.com", token="test-token", topology=initial_topology, refresh_interval=300, # Long interval - shouldn't trigger automatically @@ -307,7 +488,7 @@ def failing_fetch(*args, **kwargs): raise Exception("Network error") refresher = TopologyRefresher( - global_endpoint="https://glo.global-cluster.zilliz.com", + global_endpoint="https://glo.global-cluster.example.com", token="test-token", topology=topology, refresh_interval=0.05, diff --git a/uv.lock b/uv.lock index 25a9c02c2..786be9299 100644 --- a/uv.lock +++ b/uv.lock @@ -42,9 +42,9 @@ name = "anyio" version = "4.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, - { name = "idna", marker = "python_full_version >= '3.10'" }, - { name = "typing-extensions", marker = "python_full_version >= '3.10' and python_full_version < '3.13'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } wheels = [ @@ -117,8 +117,8 @@ resolution-markers = [ "python_full_version <= '3.9'", ] dependencies = [ - { name = "requests", version = "2.32.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "typing-extensions", marker = "python_full_version < '3.10'" }, + { name = "requests", version = "2.32.5", source = { registry = "https://pypi.org/simple" } }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/34/83/bbde3faa84ddcb8eb0eca4b3ffb3221252281db4ce351300fe248c5c70b1/azure_core-1.39.0.tar.gz", hash = "sha256:8a90a562998dd44ce84597590fff6249701b98c0e8797c95fcdd695b54c35d74", size = 367531, upload-time = "2026-03-19T01:31:29.461Z" } wheels = [ @@ -145,8 +145,8 @@ resolution-markers = [ "python_full_version == '3.10.*'", ] dependencies = [ - { name = "requests", version = "2.34.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, + { name = "requests", version = "2.34.2", source = { registry = "https://pypi.org/simple" } }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a6/f3/b416179e408990df5db0d516283022dde0f5d0111d98c1a848e41853e81c/azure_core-1.41.0.tar.gz", hash = "sha256:f46ff5dfcd230f25cf1c19e8a34b8dc08a337b2503e268bb600a16c00db8ad5a", size = 381042, upload-time = "2026-05-07T23:30:54.302Z" } wheels = [ @@ -197,14 +197,14 @@ resolution-markers = [ "python_full_version <= '3.9'", ] dependencies = [ - { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "mypy-extensions", marker = "python_full_version < '3.10'" }, - { name = "packaging", marker = "python_full_version < '3.10'" }, - { name = "pathspec", marker = "python_full_version < '3.10'" }, - { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pytokens", marker = "python_full_version < '3.10'" }, - { name = "tomli", marker = "python_full_version < '3.10'" }, - { name = "typing-extensions", marker = "python_full_version < '3.10'" }, + { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" } }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" } }, + { name = "pytokens" }, + { name = "tomli" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8c/ad/33adf4708633d047950ff2dfdea2e215d84ac50ef95aff14a614e4b6e9b2/black-25.11.0.tar.gz", hash = "sha256:9a323ac32f5dc75ce7470501b887250be5005a01602e931a15e45593f70f6e08", size = 655669, upload-time = "2025-11-10T01:53:50.558Z" } wheels = [ @@ -255,14 +255,14 @@ resolution-markers = [ "python_full_version == '3.10.*'", ] dependencies = [ - { name = "click", version = "8.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "mypy-extensions", marker = "python_full_version >= '3.10'" }, - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "pathspec", marker = "python_full_version >= '3.10'" }, - { name = "platformdirs", version = "4.9.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "pytokens", marker = "python_full_version >= '3.10'" }, - { name = "tomli", marker = "python_full_version == '3.10.*'" }, - { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, + { name = "click", version = "8.4.1", source = { registry = "https://pypi.org/simple" } }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs", version = "4.9.6", source = { registry = "https://pypi.org/simple" } }, + { name = "pytokens" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c0/37/5628dd55bf2b34257fc7603f0fe97c40e3aaf24265f416a9c85c95ca1436/black-26.5.1.tar.gz", hash = "sha256:dd321f668053961824bcc1be1cc1df748b2d7e4fa28086b08331e577b0100a73", size = 679439, upload-time = "2026-05-18T16:53:36.107Z" } wheels = [ @@ -565,12 +565,12 @@ resolution-markers = [ "python_full_version <= '3.9'", ] dependencies = [ - { name = "attrs", marker = "python_full_version < '3.10'" }, - { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "packaging", marker = "python_full_version < '3.10'" }, - { name = "pydantic", marker = "python_full_version < '3.10'" }, - { name = "tomli", marker = "python_full_version < '3.10'" }, - { name = "wheel-filename", marker = "python_full_version < '3.10'" }, + { name = "attrs" }, + { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "tomli" }, + { name = "wheel-filename" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9b/42/ecf9001d6a0eb3bd9b893299399c779457a178f82c52630d77cc834758ee/check_wheel_contents-0.6.1.tar.gz", hash = "sha256:d1261166c24a7a0f202016e38f2601452580674e9bb35667530c2b1a1c36f690", size = 585959, upload-time = "2024-12-01T12:28:09.461Z" } wheels = [ @@ -597,12 +597,12 @@ resolution-markers = [ "python_full_version == '3.10.*'", ] dependencies = [ - { name = "attrs", marker = "python_full_version >= '3.10'" }, - { name = "click", version = "8.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "pydantic", marker = "python_full_version >= '3.10'" }, - { name = "tomli", marker = "python_full_version == '3.10.*'" }, - { name = "wheel-filename", marker = "python_full_version >= '3.10'" }, + { name = "attrs" }, + { name = "click", version = "8.4.1", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "wheel-filename" }, ] sdist = { url = "https://files.pythonhosted.org/packages/95/37/2b9e0d38c2f668791ffe8b97711185c364d91c027e47f189c371c2348d18/check_wheel_contents-0.6.3.tar.gz", hash = "sha256:10e6939e2fe4e6ce1edf2ff6ec6157808677e80782e78021ae139dd88473a442", size = 586023, upload-time = "2025-08-02T14:01:45.546Z" } wheels = [ @@ -618,7 +618,7 @@ resolution-markers = [ "python_full_version <= '3.9'", ] dependencies = [ - { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } wheels = [ @@ -645,7 +645,7 @@ resolution-markers = [ "python_full_version == '3.10.*'", ] dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9b/98/518d8e5081007684232226f475082b30087d0f585e8457db087298259f49/click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96", size = 353007, upload-time = "2026-05-22T04:08:37.769Z" } wheels = [ @@ -666,7 +666,7 @@ name = "coloredlogs" version = "15.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "humanfriendly", marker = "python_full_version < '3.10'" }, + { name = "humanfriendly" }, ] 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 = [ @@ -790,7 +790,7 @@ wheels = [ [package.optional-dependencies] toml = [ - { name = "tomli", marker = "python_full_version < '3.10'" }, + { name = "tomli" }, ] [[package]] @@ -923,7 +923,7 @@ wheels = [ [package.optional-dependencies] toml = [ - { name = "tomli", marker = "python_full_version >= '3.10' and python_full_version <= '3.11'" }, + { name = "tomli", marker = "python_full_version <= '3.11'" }, ] [[package]] @@ -934,8 +934,8 @@ resolution-markers = [ "python_full_version <= '3.9'", ] dependencies = [ - { name = "cffi", marker = "python_full_version <= '3.9' and platform_python_implementation != 'PyPy'" }, - { name = "typing-extensions", marker = "python_full_version <= '3.9'" }, + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ef/b2/7ffa7fe8207a8c42147ffe70c3e360b228160c1d85dc3faff16aaa3244c0/cryptography-47.0.0.tar.gz", hash = "sha256:9f8e55fe4e63613a5e1cc5819030f27b97742d720203a087802ce4ce9ceb52bb", size = 830863, upload-time = "2026-04-24T19:54:57.056Z" } wheels = [ @@ -1010,8 +1010,8 @@ resolution-markers = [ "python_full_version > '3.9' and python_full_version < '3.10'", ] dependencies = [ - { name = "cffi", marker = "python_full_version > '3.9' and platform_python_implementation != 'PyPy'" }, - { name = "typing-extensions", marker = "python_full_version > '3.9' and python_full_version < '3.11'" }, + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9f/a9/db8f313fdcd85d767d4973515e1db101f9c71f95fced83233de224673757/cryptography-48.0.0.tar.gz", hash = "sha256:5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920", size = 832984, upload-time = "2026-05-04T22:59:38.133Z" } wheels = [ @@ -1107,6 +1107,43 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ff/fa/d3c15189f7c52aaefbaea76fb012119b04b9013f4bf446cb4eb4c26c4e6b/cython-3.2.4-py3-none-any.whl", hash = "sha256:732fc93bc33ae4b14f6afaca663b916c2fdd5dcbfad7114e17fb2434eeaea45c", size = 1257078, upload-time = "2026-01-04T14:14:12.373Z" }, ] +[[package]] +name = "dnspython" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197, upload-time = "2024-10-05T20:14:59.362Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload-time = "2024-10-05T20:14:57.687Z" }, +] + +[[package]] +name = "dnspython" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, +] + [[package]] name = "docutils" version = "0.22.4" @@ -1121,7 +1158,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -1133,9 +1170,9 @@ name = "faiss-cpu" version = "1.13.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and sys_platform != 'win32'" }, - { name = "packaging", marker = "(python_full_version >= '3.10' and sys_platform != 'win32') or (python_full_version == '3.10.*' and sys_platform == 'win32')" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "packaging" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/07/c9/671f66f6b31ec48e5825d36435f0cb91189fa8bb6b50724029dbff4ca83c/faiss_cpu-1.13.2-cp310-abi3-macosx_14_0_arm64.whl", hash = "sha256:a9064eb34f8f64438dd5b95c8f03a780b1a3f0b99c46eeacb1f0b5d15fc02dc1", size = 3452776, upload-time = "2025-12-24T10:27:01.419Z" }, @@ -1305,7 +1342,7 @@ resolution-markers = [ "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "typing-extensions", marker = "python_full_version >= '3.14'" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b7/48/af6173dbca4454f4637a4678b67f52ca7e0c1ed7d5894d89d434fecede05/grpcio-1.80.0.tar.gz", hash = "sha256:29aca15edd0688c22ba01d7cc01cb000d72b2033f4a3c72a81a19b56fd143257", size = 12978905, upload-time = "2026-03-30T08:49:10.502Z" } wheels = [ @@ -1376,10 +1413,9 @@ name = "grpcio-tools" version = "1.66.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "grpcio", version = "1.66.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" }, - { name = "protobuf", marker = "python_full_version < '3.14'" }, - { name = "setuptools", version = "81.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "setuptools", version = "82.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and python_full_version < '3.14'" }, + { name = "grpcio", version = "1.66.2", source = { registry = "https://pypi.org/simple" } }, + { name = "protobuf" }, + { name = "setuptools", version = "75.3.2", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/3c/31/bc5b5cc2552a58690eb90a6dcfa64d4ff4bd5dcce03711e4975ea683a3e3/grpcio_tools-1.66.2.tar.gz", hash = "sha256:4a36e07913d26ba5ccfd2685ba63ca97f26b08c249d2cc9e74dda37efa49d7e4", size = 5158759, upload-time = "2024-09-28T12:44:18.31Z" } wheels = [ @@ -1476,8 +1512,8 @@ name = "httpcore" version = "1.0.9" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "certifi", marker = "python_full_version >= '3.10'" }, - { name = "h11", marker = "python_full_version >= '3.10'" }, + { name = "certifi" }, + { name = "h11" }, ] sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } wheels = [ @@ -1489,10 +1525,10 @@ name = "httpx" version = "0.28.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "anyio", marker = "python_full_version >= '3.10'" }, - { name = "certifi", marker = "python_full_version >= '3.10'" }, - { name = "httpcore", marker = "python_full_version >= '3.10'" }, - { name = "idna", marker = "python_full_version >= '3.10'" }, + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } wheels = [ @@ -1508,14 +1544,14 @@ resolution-markers = [ "python_full_version <= '3.9'", ] dependencies = [ - { name = "filelock", version = "3.19.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "fsspec", version = "2025.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "hf-xet", marker = "(python_full_version < '3.10' and platform_machine == 'aarch64') or (python_full_version < '3.10' and platform_machine == 'amd64') or (python_full_version < '3.10' and platform_machine == 'arm64') or (python_full_version < '3.10' and platform_machine == 'x86_64')" }, - { name = "packaging", marker = "python_full_version < '3.10'" }, - { name = "pyyaml", marker = "python_full_version < '3.10'" }, - { name = "requests", version = "2.32.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "tqdm", marker = "python_full_version < '3.10'" }, - { name = "typing-extensions", marker = "python_full_version < '3.10'" }, + { name = "filelock", version = "3.19.1", source = { registry = "https://pypi.org/simple" } }, + { name = "fsspec", version = "2025.10.0", source = { registry = "https://pypi.org/simple" } }, + { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "requests", version = "2.32.5", source = { registry = "https://pypi.org/simple" } }, + { name = "tqdm" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7c/b7/8cb61d2eece5fb05a83271da168186721c450eb74e3c31f7ef3169fa475b/huggingface_hub-0.36.2.tar.gz", hash = "sha256:1934304d2fb224f8afa3b87007d58501acfda9215b334eed53072dd5e815ff7a", size = 649782, upload-time = "2026-02-06T09:24:13.098Z" } wheels = [ @@ -1542,15 +1578,15 @@ resolution-markers = [ "python_full_version == '3.10.*'", ] dependencies = [ - { name = "filelock", version = "3.29.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "fsspec", version = "2026.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "hf-xet", marker = "(python_full_version >= '3.10' and platform_machine == 'AMD64') or (python_full_version >= '3.10' and platform_machine == 'aarch64') or (python_full_version >= '3.10' and platform_machine == 'amd64') or (python_full_version >= '3.10' and platform_machine == 'arm64') or (python_full_version >= '3.10' and platform_machine == 'x86_64')" }, - { name = "httpx", marker = "python_full_version >= '3.10'" }, - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "pyyaml", marker = "python_full_version >= '3.10'" }, - { name = "tqdm", marker = "python_full_version >= '3.10'" }, - { name = "typer", marker = "python_full_version >= '3.10'" }, - { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, + { name = "filelock", version = "3.29.0", source = { registry = "https://pypi.org/simple" } }, + { name = "fsspec", version = "2026.4.0", source = { registry = "https://pypi.org/simple" } }, + { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "httpx" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "tqdm" }, + { name = "typer" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/48/0f/ed994dbade67a54407c28cab96ef845e0e6d25500be56aca6394f8bfc9dd/huggingface_hub-1.16.1.tar.gz", hash = "sha256:7f1dc4c5ec21aed69be630ad0c3378616be16f3de1a47b141c0e812965d9c832", size = 792534, upload-time = "2026-05-21T18:40:00.908Z" } wheels = [ @@ -1562,7 +1598,7 @@ name = "humanfriendly" version = "10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyreadline3", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, + { name = "pyreadline3", marker = "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 = [ @@ -1600,7 +1636,7 @@ resolution-markers = [ "python_full_version <= '3.9'", ] dependencies = [ - { name = "zipp", version = "3.23.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "zipp", version = "3.23.1", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } wheels = [ @@ -1627,7 +1663,7 @@ resolution-markers = [ "python_full_version == '3.10.*'", ] dependencies = [ - { name = "zipp", version = "4.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "zipp", version = "4.1.0", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/a9/01/15bb152d77b21318514a96f43af312635eb2500c96b55398d020c93d86ea/importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc", size = 56405, upload-time = "2026-03-20T06:42:56.999Z" } wheels = [ @@ -1702,7 +1738,7 @@ resolution-markers = [ "python_full_version <= '3.9'", ] dependencies = [ - { name = "backports-tarfile", marker = "python_full_version < '3.10'" }, + { name = "backports-tarfile" }, ] sdist = { url = "https://files.pythonhosted.org/packages/27/7b/c3081ff1af947915503121c649f26a778e1a2101fd525f74aef997d75b7e/jaraco_context-6.1.1.tar.gz", hash = "sha256:bc046b2dc94f1e5532bd02402684414575cc11f565d929b6563125deb0a6e581", size = 15832, upload-time = "2026-03-07T15:46:04.63Z" } wheels = [ @@ -1729,7 +1765,7 @@ resolution-markers = [ "python_full_version == '3.10.*'", ] dependencies = [ - { name = "backports-tarfile", marker = "python_full_version >= '3.10' and python_full_version < '3.12'" }, + { name = "backports-tarfile", marker = "python_full_version < '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/af/50/4763cd07e722bb6285316d390a164bc7e479db9d90daa769f22578f698b4/jaraco_context-6.1.2.tar.gz", hash = "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3", size = 16801, upload-time = "2026-03-20T22:13:33.922Z" } wheels = [ @@ -1745,7 +1781,7 @@ resolution-markers = [ "python_full_version <= '3.9'", ] dependencies = [ - { name = "more-itertools", version = "10.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "more-itertools", version = "10.8.0", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/0f/27/056e0638a86749374d6f57d0b0db39f29509cce9313cf91bdc0ac4d91084/jaraco_functools-4.4.0.tar.gz", hash = "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb", size = 19943, upload-time = "2025-12-21T09:29:43.6Z" } wheels = [ @@ -1772,7 +1808,7 @@ resolution-markers = [ "python_full_version == '3.10.*'", ] dependencies = [ - { name = "more-itertools", version = "11.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "more-itertools", version = "11.1.0", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/36/cf/ea4ef2920830dea3f5ab2ea4da6fb67724e6dca80ee2553788c3607243d0/jaraco_functools-4.5.0.tar.gz", hash = "sha256:3bb5665ea4a020cf78a7040e89154c77edadb3ca74f366479669c5999aa70b03", size = 20272, upload-time = "2026-05-15T21:34:10.025Z" } wheels = [ @@ -1793,7 +1829,7 @@ name = "jinja2" version = "3.1.6" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markupsafe", marker = "python_full_version < '3.11' or sys_platform != 'win32'" }, + { name = "markupsafe" }, ] sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } wheels = [ @@ -1831,7 +1867,7 @@ resolution-markers = [ "python_full_version <= '3.9'", ] dependencies = [ - { name = "uc-micro-py", version = "1.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "uc-micro-py", version = "1.0.3", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/2a/ae/bb56c6828e4797ba5a4821eec7c43b8bf40f69cda4d4f5f8c8a2810ec96a/linkify-it-py-2.0.3.tar.gz", hash = "sha256:68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048", size = 27946, upload-time = "2024-02-04T14:48:04.179Z" } wheels = [ @@ -1854,7 +1890,7 @@ resolution-markers = [ "python_full_version == '3.10.*'", ] dependencies = [ - { name = "uc-micro-py", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.10' and sys_platform != 'win32') or (python_full_version == '3.10.*' and sys_platform == 'win32')" }, + { name = "uc-micro-py", version = "2.0.0", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/2e/c9/06ea13676ef354f0af6169587ae292d3e2406e212876a413bf9eece4eb23/linkify_it_py-2.1.0.tar.gz", hash = "sha256:43360231720999c10e9328dc3691160e27a718e280673d444c38d7d3aaa3b98b", size = 29158, upload-time = "2026-03-01T07:48:47.683Z" } wheels = [ @@ -1870,7 +1906,7 @@ resolution-markers = [ "python_full_version <= '3.9'", ] dependencies = [ - { name = "mdurl", marker = "python_full_version < '3.10'" }, + { name = "mdurl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } wheels = [ @@ -1879,7 +1915,7 @@ wheels = [ [package.optional-dependencies] linkify = [ - { name = "linkify-it-py", version = "2.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "linkify-it-py", version = "2.0.3", source = { registry = "https://pypi.org/simple" } }, ] [[package]] @@ -1902,7 +1938,7 @@ resolution-markers = [ "python_full_version == '3.10.*'", ] dependencies = [ - { name = "mdurl", marker = "python_full_version >= '3.10'" }, + { name = "mdurl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } wheels = [ @@ -1911,7 +1947,7 @@ wheels = [ [package.optional-dependencies] linkify = [ - { name = "linkify-it-py", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.10' and sys_platform != 'win32') or (python_full_version == '3.10.*' and sys_platform == 'win32')" }, + { name = "linkify-it-py", version = "2.1.0", source = { registry = "https://pypi.org/simple" } }, ] [[package]] @@ -1995,7 +2031,7 @@ resolution-markers = [ "python_full_version <= '3.9'", ] dependencies = [ - { name = "markdown-it-py", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "markdown-it-py", version = "3.0.0", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/19/03/a2ecab526543b152300717cf232bb4bb8605b6edb946c845016fa9c9c9fd/mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5", size = 43542, upload-time = "2024-09-09T20:27:49.564Z" } wheels = [ @@ -2018,7 +2054,7 @@ resolution-markers = [ "python_full_version == '3.10.*'", ] dependencies = [ - { name = "markdown-it-py", version = "4.2.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.10' and sys_platform != 'win32') or (python_full_version == '3.10.*' and sys_platform == 'win32')" }, + { name = "markdown-it-py", version = "4.2.0", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/59/fc/f8d0863f8862f25602c0404d75568e89fb6b4109804645e5cdfb1be5cf56/mdit_py_plugins-0.6.1.tar.gz", hash = "sha256:a2bca0f039f39dbd35fb74ae1b5f998608c437463371f0ff7f49a19a17a114d0", size = 56114, upload-time = "2026-05-13T09:03:38.91Z" } wheels = [ @@ -2039,9 +2075,9 @@ name = "memray" version = "1.19.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "jinja2", marker = "python_full_version < '3.11' or sys_platform != 'win32'" }, - { name = "rich", marker = "python_full_version < '3.11' or sys_platform != 'win32'" }, - { name = "textual", marker = "python_full_version < '3.11' or sys_platform != 'win32'" }, + { name = "jinja2" }, + { name = "rich" }, + { name = "textual" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/04/5b886a36df947599e0f37cd46e6e44e565299815f044e2303ab2ae9f8870/memray-1.19.3.tar.gz", hash = "sha256:4e0fb29ff0a50c0ec9dc84294d8f2c83419feba561a37628b304c2ae4fe73d03", size = 2417089, upload-time = "2026-04-08T18:49:32.409Z" } wheels = [ @@ -2098,7 +2134,7 @@ resolution-markers = [ "python_full_version <= '3.9'", ] dependencies = [ - { name = "tqdm", marker = "python_full_version < '3.10'" }, + { name = "tqdm" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/a9/b2/acc5024c8e8b6a0b034670b8e8af306ebd633ede777dcbf557eac4785937/milvus_lite-2.5.1-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:6b014453200ba977be37ba660cb2d021030375fa6a35bc53c2e1d92980a0c512", size = 27934713, upload-time = "2025-06-30T04:23:37.028Z" }, @@ -2123,13 +2159,13 @@ resolution-markers = [ "python_full_version == '3.10.*'", ] dependencies = [ - { name = "faiss-cpu", marker = "(python_full_version >= '3.10' and sys_platform != 'win32') or (python_full_version == '3.10.*' and sys_platform == 'win32')" }, - { name = "grpcio", version = "1.66.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.10' and python_full_version < '3.14' and sys_platform != 'win32') or (python_full_version == '3.10.*' and sys_platform == 'win32')" }, - { name = "grpcio", version = "1.80.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14' and sys_platform != 'win32'" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and sys_platform != 'win32'" }, - { name = "pyarrow", version = "24.0.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.10' and sys_platform != 'win32') or (python_full_version == '3.10.*' and sys_platform == 'win32')" }, - { name = "tomli", marker = "python_full_version == '3.10.*'" }, + { name = "faiss-cpu" }, + { name = "grpcio", version = "1.66.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" }, + { name = "grpcio", version = "1.80.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pyarrow", version = "24.0.0", source = { registry = "https://pypi.org/simple" } }, + { name = "tomli", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a3/9a/d80d260e6fe1246818a8ef782c374ba9c6ca46ca3b987c14eabe914ef805/milvus_lite-3.0.tar.gz", hash = "sha256:2c35d0d046b1faae3402cde1fb73d65f51ee8c6aba65f54de1dda46f7bb18b9b", size = 589749, upload-time = "2026-05-13T07:14:05.827Z" } wheels = [ @@ -2516,12 +2552,12 @@ resolution-markers = [ "python_full_version <= '3.9'", ] dependencies = [ - { name = "coloredlogs", marker = "python_full_version < '3.10'" }, - { name = "flatbuffers", marker = "python_full_version < '3.10'" }, - { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "packaging", marker = "python_full_version < '3.10'" }, - { name = "protobuf", marker = "python_full_version < '3.10'" }, - { name = "sympy", marker = "python_full_version < '3.10'" }, + { name = "coloredlogs" }, + { name = "flatbuffers" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "sympy" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/4e/28/99f903b0eb1cd6f3faa0e343217d9fb9f47b84bca98bd9859884631336ee/onnxruntime-1.20.1-cp310-cp310-macosx_13_0_universal2.whl", hash = "sha256:e50ba5ff7fed4f7d9253a6baf801ca2883cc08491f9d32d78a80da57256a5439", size = 30996314, upload-time = "2024-11-21T00:48:31.43Z" }, @@ -2555,11 +2591,11 @@ resolution-markers = [ "python_full_version == '3.10.*'", ] dependencies = [ - { name = "flatbuffers", marker = "python_full_version == '3.10.*'" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "packaging", marker = "python_full_version == '3.10.*'" }, - { name = "protobuf", marker = "python_full_version == '3.10.*'" }, - { name = "sympy", marker = "python_full_version == '3.10.*'" }, + { name = "flatbuffers" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "sympy" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/15/41/3253db975a90c3ce1d475e2a230773a21cd7998537f0657947df6fb79861/onnxruntime-1.24.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3e6456801c66b095c5cd68e690ca25db970ea5202bd0c5b84a2c3ef7731c5a3c", size = 17332766, upload-time = "2026-03-05T17:18:59.714Z" }, @@ -2607,10 +2643,10 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "flatbuffers", marker = "python_full_version >= '3.11'" }, - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "packaging", marker = "python_full_version >= '3.11'" }, - { name = "protobuf", marker = "python_full_version >= '3.11'" }, + { name = "flatbuffers" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "protobuf" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/d4/81/29a9eb470994a75eb7b3ccf32be314d7c66675a00ac7b50294816cc2db27/onnxruntime-1.26.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ee1109ef4ef27cad90e823399e61e03b3c6c7bfe0fb820b4baf3678c15be8b3c", size = 18005108, upload-time = "2026-05-08T19:08:11.728Z" }, @@ -2852,11 +2888,11 @@ resolution-markers = [ "python_full_version <= '3.9'", ] dependencies = [ - { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version != '3.10.*'" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "python-dateutil", marker = "python_full_version < '3.11'" }, - { name = "pytz", marker = "python_full_version < '3.11'" }, - { name = "tzdata", marker = "python_full_version < '3.11'" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, ] sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } wheels = [ @@ -2935,9 +2971,9 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "python-dateutil", marker = "python_full_version >= '3.11'" }, - { name = "tzdata", marker = "(python_full_version >= '3.11' and sys_platform == 'emscripten') or (python_full_version >= '3.11' and sys_platform == 'win32')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" } }, + { name = "python-dateutil" }, + { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f8/87/4341c6252d1c47b08768c3d25ac487362bf403f0313ddae4a2a26c9b1b4c/pandas-3.0.3.tar.gz", hash = "sha256:696a4a00a2a2a35d4e5deb3fc946641b96c944f02230e4f76137fe35d806c4fc", size = 4651414, upload-time = "2026-05-11T18:54:29.21Z" } wheels = [ @@ -3470,6 +3506,8 @@ source = { editable = "." } dependencies = [ { name = "cachetools", version = "6.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "cachetools", version = "7.1.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "dnspython", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "dnspython", version = "2.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "grpcio", version = "1.66.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" }, { name = "grpcio", version = "1.80.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" }, { name = "orjson", version = "3.11.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, @@ -3501,7 +3539,7 @@ bulk-writer = [ milvus-lite = [ { name = "milvus-lite", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' and sys_platform != 'win32'" }, { name = "milvus-lite", version = "3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and sys_platform != 'win32'" }, - { name = "setuptools", version = "81.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "setuptools", version = "75.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, ] model = [ { name = "pymilvus-model" }, @@ -3554,6 +3592,7 @@ release = [ requires-dist = [ { name = "azure-storage-blob", marker = "extra == 'bulk-writer'" }, { name = "cachetools", specifier = ">=5.0.0" }, + { name = "dnspython", specifier = ">=2.0.0" }, { name = "grpcio", marker = "python_full_version < '3.14'", specifier = ">=1.66.2,!=1.68.0,!=1.68.1,!=1.69.0,!=1.70.0,!=1.70.1,!=1.71.0,!=1.72.1,!=1.73.0" }, { name = "grpcio", marker = "python_full_version >= '3.14'", specifier = ">=1.75.1" }, { name = "milvus-lite", marker = "sys_platform != 'win32' and extra == 'milvus-lite'", specifier = ">=2.4.0" }, @@ -3646,13 +3685,13 @@ resolution-markers = [ "python_full_version <= '3.9'", ] dependencies = [ - { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.10'" }, - { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "packaging", marker = "python_full_version < '3.10'" }, - { name = "pluggy", marker = "python_full_version < '3.10'" }, - { name = "pygments", marker = "python_full_version < '3.10'" }, - { name = "tomli", marker = "python_full_version < '3.10'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } wheels = [ @@ -3679,13 +3718,13 @@ resolution-markers = [ "python_full_version == '3.10.*'", ] dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, - { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "pluggy", marker = "python_full_version >= '3.10'" }, - { name = "pygments", marker = "python_full_version >= '3.10'" }, - { name = "tomli", marker = "python_full_version == '3.10.*'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } wheels = [ @@ -3701,9 +3740,9 @@ resolution-markers = [ "python_full_version <= '3.9'", ] dependencies = [ - { name = "backports-asyncio-runner", marker = "python_full_version < '3.10'" }, - { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "typing-extensions", marker = "python_full_version < '3.10'" }, + { name = "backports-asyncio-runner" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" } }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/86/9e3c5f48f7b7b638b216e4b9e645f54d199d7abbbab7a64a13b4e12ba10f/pytest_asyncio-1.2.0.tar.gz", hash = "sha256:c609a64a2a8768462d0c99811ddb8bd2583c33fd33cf7f21af1c142e824ffb57", size = 50119, upload-time = "2025-09-12T07:33:53.816Z" } wheels = [ @@ -3730,9 +3769,9 @@ resolution-markers = [ "python_full_version == '3.10.*'", ] dependencies = [ - { name = "backports-asyncio-runner", marker = "python_full_version == '3.10.*'" }, - { name = "pytest", version = "9.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "typing-extensions", marker = "python_full_version >= '3.10' and python_full_version < '3.13'" }, + { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" }, + { name = "pytest", version = "9.0.3", source = { registry = "https://pypi.org/simple" } }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } wheels = [ @@ -3757,8 +3796,8 @@ wheels = [ histogram = [ { name = "pygal" }, { name = "pygaljs" }, - { name = "setuptools", version = "81.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "setuptools", version = "82.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "setuptools", version = "75.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" }, + { name = "setuptools", version = "82.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" }, ] [[package]] @@ -4275,10 +4314,10 @@ resolution-markers = [ "python_full_version <= '3.9'", ] dependencies = [ - { name = "certifi", marker = "python_full_version < '3.10'" }, - { name = "charset-normalizer", marker = "python_full_version < '3.10'" }, - { name = "idna", marker = "python_full_version < '3.10'" }, - { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } wheels = [ @@ -4305,10 +4344,10 @@ resolution-markers = [ "python_full_version == '3.10.*'", ] dependencies = [ - { name = "certifi", marker = "python_full_version >= '3.10'" }, - { name = "charset-normalizer", marker = "python_full_version >= '3.10'" }, - { name = "idna", marker = "python_full_version >= '3.10'" }, - { name = "urllib3", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3", version = "2.7.0", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } wheels = [ @@ -4415,7 +4454,7 @@ resolution-markers = [ "python_full_version <= '3.9'", ] dependencies = [ - { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/ae/00/48c2f661e2816ccf2ecd77982f6605b2950afe60f60a52b4cbbc2504aa8f/scipy-1.13.1.tar.gz", hash = "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c", size = 57210720, upload-time = "2024-05-23T03:29:26.079Z" } wheels = [ @@ -4453,7 +4492,7 @@ resolution-markers = [ "python_full_version == '3.10.*'", ] dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } wheels = [ @@ -4523,7 +4562,7 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } wheels = [ @@ -4598,9 +4637,9 @@ resolution-markers = [ "python_full_version <= '3.9'", ] dependencies = [ - { name = "cryptography", version = "47.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version <= '3.9'" }, + { name = "cryptography", version = "47.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version <= '3.9' or python_full_version >= '3.10'" }, { name = "cryptography", version = "48.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version > '3.9' and python_full_version < '3.10'" }, - { name = "jeepney", marker = "python_full_version < '3.10'" }, + { name = "jeepney" }, ] sdist = { url = "https://files.pythonhosted.org/packages/53/a4/f48c9d79cb507ed1373477dbceaba7401fd8a23af63b837fa61f1dcd3691/SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77", size = 19739, upload-time = "2022-08-13T16:22:46.976Z" } wheels = [ @@ -4619,8 +4658,8 @@ resolution-markers = [ "python_full_version == '3.10.*'", ] dependencies = [ - { name = "cryptography", version = "48.0.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version == '3.10.*' and sys_platform == 'emscripten') or (python_full_version == '3.10.*' and sys_platform == 'win32') or (python_full_version >= '3.10' and sys_platform != 'emscripten' and sys_platform != 'win32')" }, - { name = "jeepney", marker = "(python_full_version == '3.10.*' and sys_platform == 'emscripten') or (python_full_version == '3.10.*' and sys_platform == 'win32') or (python_full_version >= '3.10' and sys_platform != 'emscripten' and sys_platform != 'win32')" }, + { name = "cryptography", version = "48.0.0", source = { registry = "https://pypi.org/simple" } }, + { name = "jeepney" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } wheels = [ @@ -4629,15 +4668,25 @@ wheels = [ [[package]] name = "setuptools" -version = "81.0.0" +version = "75.3.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", "python_full_version > '3.9' and python_full_version < '3.10'", "python_full_version <= '3.9'", ] -sdist = { url = "https://files.pythonhosted.org/packages/0d/1c/73e719955c59b8e424d015ab450f51c0af856ae46ea2da83eba51cc88de1/setuptools-81.0.0.tar.gz", hash = "sha256:487b53915f52501f0a79ccfd0c02c165ffe06631443a886740b91af4b7a5845a", size = 1198299, upload-time = "2026-02-06T21:10:39.601Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/01/771ea46cce201dd42cff043a5eea929d1c030fb3d1c2ee2729d02ca7814c/setuptools-75.3.2.tar.gz", hash = "sha256:3c1383e1038b68556a382c1e8ded8887cd20141b0eb5708a6c8d277de49364f5", size = 1354489, upload-time = "2025-03-12T00:02:19.004Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl", hash = "sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6", size = 1062021, upload-time = "2026-02-06T21:10:37.175Z" }, + { url = "https://files.pythonhosted.org/packages/15/65/3f0dba35760d902849d39d38c0a72767794b1963227b69a587f8a336d08c/setuptools-75.3.2-py3-none-any.whl", hash = "sha256:90ab613b6583fc02d5369cbca13ea26ea0e182d1df2d943ee9cbe81d4c61add9", size = 1251198, upload-time = "2025-03-12T00:02:17.554Z" }, ] [[package]] @@ -4648,16 +4697,6 @@ resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", "python_full_version >= '3.14' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", - "python_full_version == '3.11.*' and sys_platform == 'win32'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", - "python_full_version == '3.11.*' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version == '3.10.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } wheels = [ @@ -4673,10 +4712,10 @@ resolution-markers = [ "python_full_version <= '3.9'", ] dependencies = [ - { name = "packaging", marker = "python_full_version < '3.10'" }, - { name = "setuptools", version = "81.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "tomli", marker = "python_full_version < '3.10'" }, - { name = "typing-extensions", marker = "python_full_version < '3.10'" }, + { name = "packaging" }, + { name = "setuptools", version = "75.3.2", source = { registry = "https://pypi.org/simple" } }, + { name = "tomli" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7b/b1/19587742aad604f1988a8a362e660e8c3ac03adccdb71c96d86526e5eb62/setuptools_scm-9.2.2.tar.gz", hash = "sha256:1c674ab4665686a0887d7e24c03ab25f24201c213e82ea689d2f3e169ef7ef57", size = 203385, upload-time = "2025-10-19T22:08:05.608Z" } wheels = [ @@ -4703,11 +4742,12 @@ resolution-markers = [ "python_full_version == '3.10.*'", ] dependencies = [ - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "setuptools", version = "82.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "tomli", marker = "python_full_version == '3.10.*'" }, - { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, - { name = "vcs-versioning", version = "1.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "packaging" }, + { name = "setuptools", version = "75.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" }, + { name = "setuptools", version = "82.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "vcs-versioning", version = "1.1.1", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/a5/b1/2a6a8ecd6f9e263754036a0b573360bdbd6873b595725e49e11139722041/setuptools_scm-10.0.5.tar.gz", hash = "sha256:bbba8fe754516cdefd017f4456721775e6ef9662bd7887fb52ae26813d4838c3", size = 56748, upload-time = "2026-03-27T15:57:05.751Z" } wheels = [ @@ -4737,7 +4777,7 @@ name = "sympy" version = "1.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "mpmath", marker = "python_full_version < '3.11'" }, + { name = "mpmath" }, ] sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } wheels = [ @@ -4750,14 +4790,14 @@ version = "8.2.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, extra = ["linkify"], marker = "python_full_version < '3.10'" }, - { name = "markdown-it-py", version = "4.2.0", source = { registry = "https://pypi.org/simple" }, extra = ["linkify"], marker = "(python_full_version >= '3.10' and sys_platform != 'win32') or (python_full_version == '3.10.*' and sys_platform == 'win32')" }, + { name = "markdown-it-py", version = "4.2.0", source = { registry = "https://pypi.org/simple" }, extra = ["linkify"], marker = "python_full_version >= '3.10'" }, { name = "mdit-py-plugins", version = "0.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "mdit-py-plugins", version = "0.6.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.10' and sys_platform != 'win32') or (python_full_version == '3.10.*' and sys_platform == 'win32')" }, + { name = "mdit-py-plugins", version = "0.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "platformdirs", version = "4.9.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.10' and sys_platform != 'win32') or (python_full_version == '3.10.*' and sys_platform == 'win32')" }, - { name = "pygments", marker = "python_full_version < '3.11' or sys_platform != 'win32'" }, - { name = "rich", marker = "python_full_version < '3.11' or sys_platform != 'win32'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11' or sys_platform != 'win32'" }, + { name = "platformdirs", version = "4.9.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pygments" }, + { name = "rich" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9b/7a/c519db0aba5024f86e71e9631810bfdd6866ed2c8695bd7fa34b90e7ef59/textual-8.2.7.tar.gz", hash = "sha256:658f568ff81e30ed43890c3e07520390e5cf1b4763822006e060656b0a88f105", size = 1859249, upload-time = "2026-05-19T10:52:49.531Z" } wheels = [ @@ -4874,16 +4914,16 @@ resolution-markers = [ "python_full_version <= '3.9'", ] dependencies = [ - { name = "filelock", version = "3.19.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "huggingface-hub", version = "0.36.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "packaging", marker = "python_full_version < '3.10'" }, - { name = "pyyaml", marker = "python_full_version < '3.10'" }, - { name = "regex", version = "2026.1.15", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "requests", version = "2.32.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "safetensors", marker = "python_full_version < '3.10'" }, - { name = "tokenizers", marker = "python_full_version < '3.10'" }, - { name = "tqdm", marker = "python_full_version < '3.10'" }, + { name = "filelock", version = "3.19.1", source = { registry = "https://pypi.org/simple" } }, + { name = "huggingface-hub", version = "0.36.2", source = { registry = "https://pypi.org/simple" } }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "regex", version = "2026.1.15", source = { registry = "https://pypi.org/simple" } }, + { name = "requests", version = "2.32.5", source = { registry = "https://pypi.org/simple" } }, + { name = "safetensors" }, + { name = "tokenizers" }, + { name = "tqdm" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c4/35/67252acc1b929dc88b6602e8c4a982e64f31e733b804c14bc24b47da35e6/transformers-4.57.6.tar.gz", hash = "sha256:55e44126ece9dc0a291521b7e5492b572e6ef2766338a610b9ab5afbb70689d3", size = 10134912, upload-time = "2026-01-16T10:38:39.284Z" } wheels = [ @@ -4910,16 +4950,16 @@ resolution-markers = [ "python_full_version == '3.10.*'", ] dependencies = [ - { name = "huggingface-hub", version = "1.16.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "huggingface-hub", version = "1.16.1", source = { registry = "https://pypi.org/simple" } }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "pyyaml", marker = "python_full_version >= '3.10'" }, - { name = "regex", version = "2026.5.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "safetensors", marker = "python_full_version >= '3.10'" }, - { name = "tokenizers", marker = "python_full_version >= '3.10'" }, - { name = "tqdm", marker = "python_full_version >= '3.10'" }, - { name = "typer", marker = "python_full_version >= '3.10'" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "regex", version = "2026.5.9", source = { registry = "https://pypi.org/simple" } }, + { name = "safetensors" }, + { name = "tokenizers" }, + { name = "tqdm" }, + { name = "typer" }, ] sdist = { url = "https://files.pythonhosted.org/packages/51/58/7f843608f2e8421f86bb97060b54649be6239ec612b82bf9d41e65c26c00/transformers-5.9.0.tar.gz", hash = "sha256:25997cb8fa6053533171634b6162d7df54346530ec2aa9b42bb834e63668c842", size = 8642240, upload-time = "2026-05-20T14:50:49.278Z" } wheels = [ @@ -4954,10 +4994,10 @@ name = "typer" version = "0.25.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "annotated-doc", marker = "python_full_version >= '3.10'" }, - { name = "click", version = "8.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "rich", marker = "python_full_version >= '3.10'" }, - { name = "shellingham", marker = "python_full_version >= '3.10'" }, + { name = "annotated-doc" }, + { name = "click", version = "8.4.1", source = { registry = "https://pypi.org/simple" } }, + { name = "rich" }, + { name = "shellingham" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e4/51/9aed62104cea109b820bbd6c14245af756112017d309da813ef107d42e7e/typer-0.25.1.tar.gz", hash = "sha256:9616eb8853a09ffeabab1698952f33c6f29ffdbceb4eaeecf571880e8d7664cc", size = 122276, upload-time = "2026-04-30T19:32:16.964Z" } wheels = [ @@ -5096,9 +5136,9 @@ resolution-markers = [ "python_full_version == '3.10.*'", ] dependencies = [ - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "tomli", marker = "python_full_version == '3.10.*'" }, - { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, + { name = "packaging" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/49/42/d97a7795055677961c63a1eef8e7b19d5968ed992ed3a70ab8eb012efad8/vcs_versioning-1.1.1.tar.gz", hash = "sha256:fabd75a3cab7dd8ac02fe24a3a9ba936bf258667b5a62ed468c9a1da0f5775bc", size = 97575, upload-time = "2026-03-27T20:42:41.613Z" } wheels = [