diff --git a/.gitignore b/.gitignore index 36a8381c..344e62b1 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,8 @@ target/ .claude/ .cursor/ .zed/ - +.gemini +.coverage* # files **/*.so **/*.sqlite diff --git a/pyproject.toml b/pyproject.toml index 19fd2da3..b99a8f60 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -105,6 +105,7 @@ test = [ "requests", "anyio", "coverage>=7.6.1", + "covdefaults", "pytest>=8.0.0", "pytest-asyncio>=0.23.8", "pytest-cov>=5.0.0", @@ -168,6 +169,14 @@ include = [ "sqlspec/utils/type_guards.py", # Type guard utilities "sqlspec/utils/fixtures.py", # File fixture loading "sqlspec/utils/data_transformation.py", # Data transformation utilities + + # === STORAGE LAYER === + "sqlspec/storage/_utils.py", + "sqlspec/storage/registry.py", + "sqlspec/storage/backends/base.py", + "sqlspec/storage/backends/obstore.py", + "sqlspec/storage/backends/fsspec.py", + "sqlspec/storage/backends/local.py", ] mypy-args = [ "--ignore-missing-imports", diff --git a/sqlspec/storage/_utils.py b/sqlspec/storage/_utils.py new file mode 100644 index 00000000..8e9b44ae --- /dev/null +++ b/sqlspec/storage/_utils.py @@ -0,0 +1,98 @@ +"""Shared utilities for storage backends.""" + +from typing import TYPE_CHECKING + +from sqlspec.exceptions import MissingDependencyError +from sqlspec.typing import PYARROW_INSTALLED + +if TYPE_CHECKING: + from pathlib import Path + +__all__ = ("ensure_pyarrow", "resolve_storage_path") + + +def ensure_pyarrow() -> None: + """Ensure PyArrow is available for Arrow operations. + + Raises: + MissingDependencyError: If pyarrow is not installed. + """ + if not PYARROW_INSTALLED: + raise MissingDependencyError(package="pyarrow", install_package="pyarrow") + + +def resolve_storage_path( + path: "str | Path", base_path: str = "", protocol: str = "file", strip_file_scheme: bool = True +) -> str: + """Resolve path relative to base_path with protocol-specific handling. + + Args: + path: Path to resolve (may include file:// scheme). + base_path: Base path to prepend if path is relative. + protocol: Storage protocol (file, s3, gs, etc.). + strip_file_scheme: Whether to strip file:// prefix. + + Returns: + Resolved path string suitable for the storage backend. + + Examples: + >>> resolve_storage_path("/data/file.txt", protocol="file") + 'data/file.txt' + + >>> resolve_storage_path( + ... "file.txt", base_path="/base", protocol="file" + ... ) + 'base/file.txt' + + >>> resolve_storage_path( + ... "file:///data/file.txt", strip_file_scheme=True + ... ) + 'data/file.txt' + + >>> resolve_storage_path( + ... "/data/subdir/file.txt", + ... base_path="/data", + ... protocol="file", + ... ) + 'subdir/file.txt' + """ + from pathlib import Path as PathlibPath + + path_str = str(path) + + if strip_file_scheme and path_str.startswith("file://"): + path_str = path_str.removeprefix("file://") + + # For local file protocol + if protocol == "file": + path_obj = PathlibPath(path_str) + + # Absolute path handling + if path_obj.is_absolute(): + if base_path: + base_obj = PathlibPath(base_path) + # Try to make path relative to base_path + try: + relative = path_obj.relative_to(base_obj) + # Return joined path for FSSpec-style backends + return f"{base_path.rstrip('/')}/{relative}" + except ValueError: + # Path is outside base_path + return path_str.lstrip("/") + # No base_path - strip leading / + return path_str.lstrip("/") + + # Relative path with base_path - join them + if base_path: + return f"{base_path.rstrip('/')}/{path_str}" + + # Relative path without base_path + return path_str + + # For cloud storage protocols (s3, gs, etc.), join with base_path + if not base_path: + return path_str + + clean_base = base_path.rstrip("/") + clean_path = path_str.lstrip("/") + return f"{clean_base}/{clean_path}" diff --git a/sqlspec/storage/backends/fsspec.py b/sqlspec/storage/backends/fsspec.py index 5174a87d..34433ebc 100644 --- a/sqlspec/storage/backends/fsspec.py +++ b/sqlspec/storage/backends/fsspec.py @@ -3,8 +3,11 @@ from pathlib import Path from typing import TYPE_CHECKING, Any +from mypy_extensions import mypyc_attr + from sqlspec.exceptions import MissingDependencyError -from sqlspec.typing import FSSPEC_INSTALLED, PYARROW_INSTALLED +from sqlspec.storage._utils import ensure_pyarrow, resolve_storage_path +from sqlspec.typing import FSSPEC_INSTALLED from sqlspec.utils.sync_tools import async_ if TYPE_CHECKING: @@ -18,41 +21,80 @@ class _ArrowStreamer: + """Async iterator for streaming Arrow batches from FSSpec backend. + + Uses async_() to offload blocking operations to thread pool, + preventing event loop blocking during file I/O and iteration. + + CRITICAL: Creates generators on main thread, offloads only next() calls. + """ + + __slots__ = ("_initialized", "backend", "batch_iterator", "kwargs", "paths_iterator", "pattern") + def __init__(self, backend: "FSSpecBackend", pattern: str, **kwargs: Any) -> None: self.backend = backend self.pattern = pattern self.kwargs = kwargs self.paths_iterator: Iterator[str] | None = None self.batch_iterator: Iterator[ArrowRecordBatch] | None = None + self._initialized = False def __aiter__(self) -> "_ArrowStreamer": return self async def _initialize(self) -> None: - """Initialize paths iterator.""" - if self.paths_iterator is None: + """Initialize paths iterator asynchronously.""" + if not self._initialized: paths = await async_(self.backend.glob)(self.pattern, **self.kwargs) self.paths_iterator = iter(paths) + self._initialized = True async def __anext__(self) -> "ArrowRecordBatch": + """Get next Arrow batch asynchronously. + + Iterative state machine that avoids recursion and blocking calls. + + Returns: + Arrow record batches from matching files. + + Raises: + StopAsyncIteration: When no more batches available. + """ await self._initialize() - if self.batch_iterator: + while True: + if self.batch_iterator is not None: + + def _safe_next_batch() -> "ArrowRecordBatch": + try: + return next(self.batch_iterator) # type: ignore[arg-type] + except StopIteration as e: + raise StopAsyncIteration from e + + try: + return await async_(_safe_next_batch)() + except StopAsyncIteration: + self.batch_iterator = None + continue + try: - return next(self.batch_iterator) - except StopIteration: - self.batch_iterator = None + path = next(self.paths_iterator) # type: ignore[arg-type] + except StopIteration as e: + raise StopAsyncIteration from e + + self.batch_iterator = self.backend._stream_file_batches(path) - if self.paths_iterator: + async def aclose(self) -> None: + """Close underlying batch iterator.""" + if self.batch_iterator is not None: try: - path = next(self.paths_iterator) - self.batch_iterator = await async_(self.backend._stream_file_batches)(path) - return await self.__anext__() - except StopIteration: - raise StopAsyncIteration - raise StopAsyncIteration + close_method = self.batch_iterator.close # type: ignore[attr-defined] + await async_(close_method)() + except AttributeError: + pass +@mypyc_attr(allow_interpreted_subclasses=True) class FSSpecBackend: """Storage backend using fsspec. @@ -60,19 +102,37 @@ class FSSpecBackend: including HTTP, HTTPS, FTP, and cloud storage services. """ + __slots__ = ("_fs_uri", "backend_type", "base_path", "fs", "protocol") + def __init__(self, uri: str, **kwargs: Any) -> None: - self._ensure_fsspec() + if not FSSPEC_INSTALLED: + raise MissingDependencyError(package="fsspec", install_package="fsspec") base_path = kwargs.pop("base_path", "") - self.base_path = base_path.rstrip("/") if base_path else "" if "://" in uri: self.protocol = uri.split("://", maxsplit=1)[0] self._fs_uri = uri + + # For S3/cloud URIs, extract bucket/path from URI as base_path + if self.protocol in {"s3", "gs", "az", "gcs"}: + from urllib.parse import urlparse + + parsed = urlparse(uri) + # Combine netloc (bucket) and path for base_path + if parsed.netloc: + uri_base_path = parsed.netloc + if parsed.path and parsed.path != "/": + uri_base_path = f"{uri_base_path}{parsed.path}" + # Only use URI base_path if no explicit base_path provided + if not base_path: + base_path = uri_base_path else: self.protocol = uri self._fs_uri = f"{uri}://" + self.base_path = base_path.rstrip("/") if base_path else "" + import fsspec self.fs = fsspec.filesystem(self.protocol, **kwargs) @@ -93,48 +153,19 @@ def from_config(cls, config: "dict[str, Any]") -> "FSSpecBackend": return cls(uri=uri, **kwargs) - def _ensure_fsspec(self) -> None: - """Ensure fsspec is available for operations.""" - if not FSSPEC_INSTALLED: - raise MissingDependencyError(package="fsspec", install_package="fsspec") - - def _ensure_pyarrow(self) -> None: - """Ensure PyArrow is available for Arrow operations.""" - if not PYARROW_INSTALLED: - raise MissingDependencyError(package="pyarrow", install_package="pyarrow") - - def _resolve_path(self, path: str | Path) -> str: - """Resolve path relative to base_path.""" - path_str = str(path) - if self.base_path: - clean_base = self.base_path.rstrip("/") - clean_path = path_str.lstrip("/") - return f"{clean_base}/{clean_path}" - if self.protocol == "s3" and "://" in self._fs_uri: - # For S3, we need to include the bucket from the URI - # Extract bucket and path from URI like s3://bucket/path - uri_parts = self._fs_uri.split("://", 1)[1] # Remove s3:// - if "/" in uri_parts: - # URI has bucket and base path - return f"{uri_parts.rstrip('/')}/{path_str.lstrip('/')}" - # URI has only bucket - return f"{uri_parts}/{path_str.lstrip('/')}" - return path_str - @property def base_uri(self) -> str: return self._fs_uri def read_bytes(self, path: str | Path, **kwargs: Any) -> bytes: """Read bytes from an object.""" - resolved_path = self._resolve_path(path) + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=False) return self.fs.cat(resolved_path, **kwargs) # type: ignore[no-any-return] # pyright: ignore def write_bytes(self, path: str | Path, data: bytes, **kwargs: Any) -> None: """Write bytes to an object.""" - resolved_path = self._resolve_path(path) + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=False) - # Only create directories for local file systems, not for cloud storage if self.protocol == "file": parent_dir = str(Path(resolved_path).parent) if parent_dir and not self.fs.exists(parent_dir): @@ -154,73 +185,74 @@ def write_text(self, path: str | Path, data: str, encoding: str = "utf-8", **kwa def exists(self, path: str | Path, **kwargs: Any) -> bool: """Check if an object exists.""" - resolved_path = self._resolve_path(path) + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=False) return self.fs.exists(resolved_path, **kwargs) # type: ignore[no-any-return] def delete(self, path: str | Path, **kwargs: Any) -> None: """Delete an object.""" - resolved_path = self._resolve_path(path) + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=False) self.fs.rm(resolved_path, **kwargs) def copy(self, source: str | Path, destination: str | Path, **kwargs: Any) -> None: """Copy an object.""" - source_path = self._resolve_path(source) - dest_path = self._resolve_path(destination) + source_path = resolve_storage_path(source, self.base_path, self.protocol, strip_file_scheme=False) + dest_path = resolve_storage_path(destination, self.base_path, self.protocol, strip_file_scheme=False) self.fs.copy(source_path, dest_path, **kwargs) def move(self, source: str | Path, destination: str | Path, **kwargs: Any) -> None: """Move an object.""" - source_path = self._resolve_path(source) - dest_path = self._resolve_path(destination) + source_path = resolve_storage_path(source, self.base_path, self.protocol, strip_file_scheme=False) + dest_path = resolve_storage_path(destination, self.base_path, self.protocol, strip_file_scheme=False) self.fs.mv(source_path, dest_path, **kwargs) def read_arrow(self, path: str | Path, **kwargs: Any) -> "ArrowTable": """Read an Arrow table from storage.""" - if not PYARROW_INSTALLED: - raise MissingDependencyError(package="pyarrow", install_package="pyarrow") + ensure_pyarrow() import pyarrow.parquet as pq - resolved_path = self._resolve_path(path) + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=False) with self.fs.open(resolved_path, mode="rb", **kwargs) as f: return pq.read_table(f) def write_arrow(self, path: str | Path, table: "ArrowTable", **kwargs: Any) -> None: """Write an Arrow table to storage.""" - if not PYARROW_INSTALLED: - raise MissingDependencyError(package="pyarrow", install_package="pyarrow") + ensure_pyarrow() import pyarrow.parquet as pq - resolved_path = self._resolve_path(path) + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=False) with self.fs.open(resolved_path, mode="wb") as f: pq.write_table(table, f, **kwargs) # pyright: ignore def list_objects(self, prefix: str = "", recursive: bool = True, **kwargs: Any) -> list[str]: """List objects with optional prefix.""" - resolved_prefix = self._resolve_path(prefix) + resolved_prefix = resolve_storage_path(prefix, self.base_path, self.protocol, strip_file_scheme=False) if recursive: return sorted(self.fs.find(resolved_prefix, **kwargs)) return sorted(self.fs.ls(resolved_prefix, detail=False, **kwargs)) def glob(self, pattern: str, **kwargs: Any) -> list[str]: """Find objects matching a glob pattern.""" - resolved_pattern = self._resolve_path(pattern) + resolved_pattern = resolve_storage_path(pattern, self.base_path, self.protocol, strip_file_scheme=False) return sorted(self.fs.glob(resolved_pattern, **kwargs)) # pyright: ignore def is_object(self, path: str | Path) -> bool: """Check if path points to an object.""" - resolved_path = self._resolve_path(path) + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=False) return self.fs.exists(resolved_path) and not self.fs.isdir(resolved_path) def is_path(self, path: str | Path) -> bool: """Check if path points to a prefix (directory-like).""" - resolved_path = self._resolve_path(path) + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=False) return self.fs.isdir(resolved_path) # type: ignore[no-any-return] def get_metadata(self, path: str | Path, **kwargs: Any) -> dict[str, Any]: """Get object metadata.""" + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=False) try: - resolved_path = self._resolve_path(path) info = self.fs.info(resolved_path, **kwargs) + except FileNotFoundError: + return {"path": resolved_path, "exists": False} + else: if isinstance(info, dict): return { "path": resolved_path, @@ -229,20 +261,17 @@ def get_metadata(self, path: str | Path, **kwargs: Any) -> dict[str, Any]: "last_modified": info.get("mtime"), "type": info.get("type", "file"), } - - except FileNotFoundError: - return {"path": self._resolve_path(path), "exists": False} - return { - "path": resolved_path, - "exists": True, - "size": info.size, - "last_modified": info.mtime, - "type": info.type, - } + return { + "path": resolved_path, + "exists": True, + "size": info.size, + "last_modified": info.mtime, + "type": info.type, + } def sign(self, path: str, expires_in: int = 3600, for_upload: bool = False) -> str: """Generate a signed URL for the file.""" - resolved_path = self._resolve_path(path) + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=False) return f"{self._fs_uri}{resolved_path}" def _stream_file_batches(self, obj_path: str | Path) -> "Iterator[ArrowRecordBatch]": @@ -253,8 +282,7 @@ def _stream_file_batches(self, obj_path: str | Path) -> "Iterator[ArrowRecordBat yield from parquet_file.iter_batches() def stream_arrow(self, pattern: str, **kwargs: Any) -> "Iterator[ArrowRecordBatch]": - self._ensure_fsspec() - self._ensure_pyarrow() + ensure_pyarrow() for obj_path in self.glob(pattern, **kwargs): yield from self._stream_file_batches(obj_path) @@ -277,7 +305,7 @@ def stream_arrow_async(self, pattern: str, **kwargs: Any) -> "AsyncIterator[Arro Returns: AsyncIterator of Arrow record batches """ - self._ensure_pyarrow() + ensure_pyarrow() return _ArrowStreamer(self, pattern, **kwargs) diff --git a/sqlspec/storage/backends/local.py b/sqlspec/storage/backends/local.py index 624ca7b4..3f010753 100644 --- a/sqlspec/storage/backends/local.py +++ b/sqlspec/storage/backends/local.py @@ -10,8 +10,9 @@ from typing import TYPE_CHECKING, Any from urllib.parse import unquote, urlparse -from sqlspec.exceptions import MissingDependencyError -from sqlspec.typing import PYARROW_INSTALLED +from mypy_extensions import mypyc_attr + +from sqlspec.storage._utils import ensure_pyarrow from sqlspec.utils.sync_tools import async_ if TYPE_CHECKING: @@ -22,6 +23,28 @@ __all__ = ("LocalStore",) +class _LocalArrowIterator: + """Async iterator for LocalStore Arrow streaming.""" + + __slots__ = ("_sync_iter",) + + def __init__(self, sync_iter: "Iterator[ArrowRecordBatch]") -> None: + self._sync_iter = sync_iter + + def __aiter__(self) -> "_LocalArrowIterator": + return self + + async def __anext__(self) -> "ArrowRecordBatch": + def _safe_next() -> "ArrowRecordBatch": + try: + return next(self._sync_iter) + except StopIteration as e: + raise StopAsyncIteration from e + + return await async_(_safe_next)() + + +@mypyc_attr(allow_interpreted_subclasses=True) class LocalStore: """Simple local file system storage backend. @@ -67,13 +90,15 @@ def __init__(self, uri: str = "", **kwargs: Any) -> None: self.protocol = "file" self.backend_type = "local" - def _ensure_pyarrow(self) -> None: - """Ensure PyArrow is available for Arrow operations.""" - if not PYARROW_INSTALLED: - raise MissingDependencyError(package="pyarrow", install_package="pyarrow") - def _resolve_path(self, path: "str | Path") -> Path: - """Resolve path relative to base_path.""" + """Resolve path relative to base_path. + + Args: + path: Path to resolve (absolute or relative). + + Returns: + Resolved Path object. + """ p = Path(path) if p.is_absolute(): return p @@ -208,19 +233,19 @@ def is_path(self, path: "str | Path") -> bool: def read_arrow(self, path: "str | Path", **kwargs: Any) -> "ArrowTable": """Read Arrow table from file.""" - self._ensure_pyarrow() + ensure_pyarrow() import pyarrow.parquet as pq - return pq.read_table(str(self._resolve_path(path))) + return pq.read_table(str(self._resolve_path(path))) # pyright: ignore def write_arrow(self, path: "str | Path", table: "ArrowTable", **kwargs: Any) -> None: """Write Arrow table to file.""" - self._ensure_pyarrow() + ensure_pyarrow() import pyarrow.parquet as pq resolved = self._resolve_path(path) resolved.parent.mkdir(parents=True, exist_ok=True) - pq.write_table(table, str(resolved)) + pq.write_table(table, str(resolved)) # pyright: ignore def stream_arrow(self, pattern: str, **kwargs: Any) -> Iterator["ArrowRecordBatch"]: """Stream Arrow record batches from files matching pattern. @@ -228,15 +253,14 @@ def stream_arrow(self, pattern: str, **kwargs: Any) -> Iterator["ArrowRecordBatc Yields: Arrow record batches from matching files. """ - if not PYARROW_INSTALLED: - raise MissingDependencyError(package="pyarrow", install_package="pyarrow") + ensure_pyarrow() import pyarrow.parquet as pq files = self.glob(pattern) for file_path in files: resolved = self._resolve_path(file_path) parquet_file = pq.ParquetFile(str(resolved)) - yield from parquet_file.iter_batches() + yield from parquet_file.iter_batches() # pyright: ignore def sign(self, path: "str | Path", expires_in: int = 3600, for_upload: bool = False) -> str: """Generate a signed URL (returns file:// URI for local files).""" @@ -294,14 +318,19 @@ async def write_arrow_async(self, path: "str | Path", table: "ArrowTable", **kwa await async_(self.write_arrow)(path, table, **kwargs) def stream_arrow_async(self, pattern: str, **kwargs: Any) -> AsyncIterator["ArrowRecordBatch"]: - """Stream Arrow record batches asynchronously.""" + """Stream Arrow record batches asynchronously. - # Convert sync iterator to async - async def _stream() -> AsyncIterator["ArrowRecordBatch"]: - for batch in self.stream_arrow(pattern, **kwargs): - yield batch + Offloads blocking file I/O operations to thread pool for + non-blocking event loop execution. - return _stream() + Args: + pattern: Glob pattern to match files. + **kwargs: Additional arguments passed to stream_arrow(). + + Returns: + Arrow record batches from matching files. + """ + return _LocalArrowIterator(self.stream_arrow(pattern, **kwargs)) async def sign_async(self, path: "str | Path", expires_in: int = 3600, for_upload: bool = False) -> str: """Generate a signed URL asynchronously (returns file:// URI for local files).""" diff --git a/sqlspec/storage/backends/obstore.py b/sqlspec/storage/backends/obstore.py index bfc51ad4..aaf53ab9 100644 --- a/sqlspec/storage/backends/obstore.py +++ b/sqlspec/storage/backends/obstore.py @@ -5,18 +5,22 @@ """ import fnmatch +import io import logging from collections.abc import AsyncIterator, Iterator from typing import TYPE_CHECKING, Any, Final, cast from urllib.parse import urlparse +from sqlspec.utils.sync_tools import async_ + if TYPE_CHECKING: from pathlib import Path from mypy_extensions import mypyc_attr from sqlspec.exceptions import MissingDependencyError, StorageOperationFailedError -from sqlspec.typing import OBSTORE_INSTALLED, PYARROW_INSTALLED, ArrowRecordBatch, ArrowTable +from sqlspec.storage._utils import ensure_pyarrow, resolve_storage_path +from sqlspec.typing import OBSTORE_INSTALLED, ArrowRecordBatch, ArrowTable __all__ = ("ObStoreBackend",) @@ -24,7 +28,14 @@ class _AsyncArrowIterator: - """Helper class to work around mypyc's lack of async generator support.""" + """Helper class to work around mypyc's lack of async generator support. + + Uses hybrid async/sync pattern: + - Native async I/O for network operations (S3, GCS, Azure) + - Thread pool for CPU-bound PyArrow parsing + """ + + __slots__ = ("_current_file_iterator", "_files_iterator", "backend", "kwargs", "pattern") def __init__(self, backend: "ObStoreBackend", pattern: str, **kwargs: Any) -> None: self.backend = backend @@ -37,24 +48,44 @@ def __aiter__(self) -> "_AsyncArrowIterator": return self async def __anext__(self) -> ArrowRecordBatch: + import pyarrow.parquet as pq + if self._files_iterator is None: files = self.backend.glob(self.pattern, **self.kwargs) self._files_iterator = iter(files) while True: if self._current_file_iterator is not None: + + def _safe_next_batch() -> ArrowRecordBatch: + try: + return next(self._current_file_iterator) # type: ignore[arg-type] + except StopIteration as e: + raise StopAsyncIteration from e + try: - return next(self._current_file_iterator) - except StopIteration: + return await async_(_safe_next_batch)() + except StopAsyncIteration: self._current_file_iterator = None + continue try: next_file = next(self._files_iterator) - # Stream from this file - file_batches = self.backend.stream_arrow(next_file) - self._current_file_iterator = iter(file_batches) - except StopIteration: - raise StopAsyncIteration + except StopIteration as e: + raise StopAsyncIteration from e + + data = await self.backend.read_bytes_async(next_file) + parquet_file = pq.ParquetFile(io.BytesIO(data)) + self._current_file_iterator = parquet_file.iter_batches() + + async def aclose(self) -> None: + """Close underlying file iterator.""" + if self._current_file_iterator is not None: + try: + close_method = self._current_file_iterator.close # type: ignore[attr-defined] + await async_(close_method)() # pyright: ignore + except AttributeError: + pass DEFAULT_OPTIONS: Final[dict[str, Any]] = {"connect_timeout": "30s", "request_timeout": "60s"} @@ -69,17 +100,17 @@ class ObStoreBackend: local filesystem, and HTTP endpoints. """ - __slots__ = ("_path_cache", "backend_type", "base_path", "protocol", "store", "store_options", "store_uri") - - def _ensure_obstore(self) -> None: - """Ensure obstore is available for operations.""" - if not OBSTORE_INSTALLED: - raise MissingDependencyError(package="obstore", install_package="obstore") - - def _ensure_pyarrow(self) -> None: - """Ensure PyArrow is available for Arrow operations.""" - if not PYARROW_INSTALLED: - raise MissingDependencyError(package="pyarrow", install_package="pyarrow") + __slots__ = ( + "_is_local_store", + "_local_store_root", + "_path_cache", + "backend_type", + "base_path", + "protocol", + "store", + "store_options", + "store_uri", + ) def __init__(self, uri: str, **kwargs: Any) -> None: """Initialize obstore backend. @@ -87,9 +118,12 @@ def __init__(self, uri: str, **kwargs: Any) -> None: Args: uri: Storage URI (e.g., 's3://bucket', 'file:///path', 'gs://bucket') **kwargs: Additional options including base_path and obstore configuration - """ - self._ensure_obstore() + Raises: + MissingDependencyError: If obstore is not installed. + """ + if not OBSTORE_INSTALLED: + raise MissingDependencyError(package="obstore", install_package="obstore") try: # Extract base_path from kwargs @@ -100,6 +134,8 @@ def __init__(self, uri: str, **kwargs: Any) -> None: self.store_options = kwargs self.store: Any self._path_cache: dict[str, str] = {} + self._is_local_store = False + self._local_store_root = "" self.protocol = uri.split("://", 1)[0] if "://" in uri else "file" self.backend_type = "obstore" @@ -112,11 +148,26 @@ def __init__(self, uri: str, **kwargs: Any) -> None: from obstore.store import LocalStore + # Parse URI to extract path + # Note: urlparse splits on '#', so we need to reconstruct the full path parsed = urlparse(uri) - path = parsed.path or "/" - # Create directory if it doesn't exist (ObStore LocalStore requires it) - PathlibPath(path).mkdir(parents=True, exist_ok=True) - self.store = LocalStore(path) + path_str = parsed.path or "/" + # Append fragment if present (handles paths with '#' character) + if parsed.fragment: + path_str = f"{path_str}#{parsed.fragment}" + path_obj = PathlibPath(path_str) + + # If path points to a file, use its parent as the base directory + if path_obj.is_file(): + path_str = str(path_obj.parent) + + # If base_path provided via kwargs, use it as LocalStore root + # Otherwise use the URI path + local_store_root = self.base_path or path_str + + self._is_local_store = True + self._local_store_root = local_store_root + self.store = LocalStore(local_store_root, mkdir=True) else: from obstore.store import from_url @@ -141,27 +192,44 @@ def from_config(cls, config: dict[str, Any]) -> "ObStoreBackend": return cls(uri=store_uri, **kwargs) - def _resolve_path(self, path: "str | Path") -> str: - """Resolve path relative to base_path.""" - path_str = str(path) - if path_str.startswith("file://"): - path_str = path_str.removeprefix("file://") - if self.store_uri.startswith("file://") and path_str.startswith("/"): - return path_str.lstrip("/") - if self.base_path: - clean_base = self.base_path.rstrip("/") - clean_path = path_str.lstrip("/") - return f"{clean_base}/{clean_path}" - return path_str + def _resolve_path_for_local_store(self, path: "str | Path") -> str: + """Resolve path for LocalStore which expects relative paths from its root.""" + from pathlib import Path as PathlibPath + + path_obj = PathlibPath(str(path)) + + # If absolute path, try to make it relative to LocalStore root + if path_obj.is_absolute() and self._local_store_root: + try: + return str(path_obj.relative_to(self._local_store_root)) + except ValueError: + # Path is outside LocalStore root - strip leading / as fallback + return str(path).lstrip("/") + + # Relative path - return as-is (already relative to LocalStore root) + return str(path) def read_bytes(self, path: "str | Path", **kwargs: Any) -> bytes: # pyright: ignore[reportUnusedParameter] """Read bytes using obstore.""" - result = self.store.get(self._resolve_path(path)) + # For LocalStore, use special path resolution (relative to LocalStore root) + if self._is_local_store: + resolved_path = self._resolve_path_for_local_store(path) + else: + # For cloud storage, use standard resolution + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True) + + result = self.store.get(resolved_path) return cast("bytes", result.bytes().to_bytes()) def write_bytes(self, path: "str | Path", data: bytes, **kwargs: Any) -> None: # pyright: ignore[reportUnusedParameter] """Write bytes using obstore.""" - self.store.put(self._resolve_path(path), data) + # For LocalStore, use special path resolution (relative to LocalStore root) + if self._is_local_store: + resolved_path = self._resolve_path_for_local_store(path) + else: + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True) + + self.store.put(resolved_path, data) def read_text(self, path: "str | Path", encoding: str = "utf-8", **kwargs: Any) -> str: """Read text using obstore.""" @@ -173,7 +241,11 @@ def write_text(self, path: "str | Path", data: str, encoding: str = "utf-8", **k def list_objects(self, prefix: str = "", recursive: bool = True, **kwargs: Any) -> list[str]: # pyright: ignore[reportUnusedParameter] """List objects using obstore.""" - resolved_prefix = self._resolve_path(prefix) if prefix else self.base_path or "" + resolved_prefix = ( + resolve_storage_path(prefix, self.base_path, self.protocol, strip_file_scheme=True) + if prefix + else self.base_path or "" + ) items = self.store.list_with_delimiter(resolved_prefix) if not recursive else self.store.list(resolved_prefix) paths: list[str] = [] for batch in items: @@ -183,22 +255,28 @@ def list_objects(self, prefix: str = "", recursive: bool = True, **kwargs: Any) def exists(self, path: "str | Path", **kwargs: Any) -> bool: # pyright: ignore[reportUnusedParameter] """Check if object exists using obstore.""" try: - self.store.head(self._resolve_path(path)) + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True) + self.store.head(resolved_path) except Exception: return False return True def delete(self, path: "str | Path", **kwargs: Any) -> None: # pyright: ignore[reportUnusedParameter] """Delete object using obstore.""" - self.store.delete(self._resolve_path(path)) + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True) + self.store.delete(resolved_path) def copy(self, source: "str | Path", destination: "str | Path", **kwargs: Any) -> None: # pyright: ignore[reportUnusedParameter] """Copy object using obstore.""" - self.store.copy(self._resolve_path(source), self._resolve_path(destination)) + source_path = resolve_storage_path(source, self.base_path, self.protocol, strip_file_scheme=True) + dest_path = resolve_storage_path(destination, self.base_path, self.protocol, strip_file_scheme=True) + self.store.copy(source_path, dest_path) def move(self, source: "str | Path", destination: "str | Path", **kwargs: Any) -> None: # pyright: ignore[reportUnusedParameter] """Move object using obstore.""" - self.store.rename(self._resolve_path(source), self._resolve_path(destination)) + source_path = resolve_storage_path(source, self.base_path, self.protocol, strip_file_scheme=True) + dest_path = resolve_storage_path(destination, self.base_path, self.protocol, strip_file_scheme=True) + self.store.rename(source_path, dest_path) def glob(self, pattern: str, **kwargs: Any) -> list[str]: """Find objects matching pattern. @@ -207,7 +285,7 @@ def glob(self, pattern: str, **kwargs: Any) -> list[str]: """ from pathlib import PurePosixPath - resolved_pattern = self._resolve_path(pattern) + resolved_pattern = resolve_storage_path(pattern, self.base_path, self.protocol, strip_file_scheme=True) all_objects = self.list_objects(recursive=True, **kwargs) if "**" in pattern: @@ -231,36 +309,48 @@ def glob(self, pattern: str, **kwargs: Any) -> list[str]: def get_metadata(self, path: "str | Path", **kwargs: Any) -> dict[str, Any]: # pyright: ignore[reportUnusedParameter] """Get object metadata using obstore.""" - resolved_path = self._resolve_path(path) - result: dict[str, Any] = {} + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True) + try: metadata = self.store.head(resolved_path) - result.update( - { + except Exception: + return {"path": resolved_path, "exists": False} + else: + if isinstance(metadata, dict): + result = { "path": resolved_path, "exists": True, - "size": getattr(metadata, "size", None), - "last_modified": getattr(metadata, "last_modified", None), - "e_tag": getattr(metadata, "e_tag", None), - "version": getattr(metadata, "version", None), + "size": metadata.get("size"), + "last_modified": metadata.get("last_modified"), + "e_tag": metadata.get("e_tag"), + "version": metadata.get("version"), } - ) - if hasattr(metadata, "metadata") and metadata.metadata: + if metadata.get("metadata"): + result["custom_metadata"] = metadata["metadata"] + return result + + result = { + "path": resolved_path, + "exists": True, + "size": metadata.size, + "last_modified": metadata.last_modified, + "e_tag": metadata.e_tag, + "version": metadata.version, + } + + if metadata.metadata: result["custom_metadata"] = metadata.metadata - except Exception: - return {"path": resolved_path, "exists": False} - else: return result def is_object(self, path: "str | Path") -> bool: """Check if path is an object using obstore.""" - resolved_path = self._resolve_path(path) + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True) return self.exists(path) and not resolved_path.endswith("/") def is_path(self, path: "str | Path") -> bool: """Check if path is a prefix/directory using obstore.""" - resolved_path = self._resolve_path(path) + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True) if resolved_path.endswith("/"): return True @@ -273,51 +363,46 @@ def is_path(self, path: "str | Path") -> bool: def read_arrow(self, path: "str | Path", **kwargs: Any) -> ArrowTable: """Read Arrow table using obstore.""" - resolved_path = self._resolve_path(path) - if hasattr(self.store, "read_arrow"): - return self.store.read_arrow(resolved_path, **kwargs) # type: ignore[no-any-return] # pyright: ignore[reportAttributeAccessIssue] - - self._ensure_pyarrow() + ensure_pyarrow() import io import pyarrow.parquet as pq - return pq.read_table(io.BytesIO(self.read_bytes(resolved_path)), **kwargs) + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True) + data = self.read_bytes(resolved_path) + return pq.read_table(io.BytesIO(data), **kwargs) def write_arrow(self, path: "str | Path", table: ArrowTable, **kwargs: Any) -> None: """Write Arrow table using obstore.""" - resolved_path = self._resolve_path(path) - if hasattr(self.store, "write_arrow"): - self.store.write_arrow(resolved_path, table, **kwargs) # pyright: ignore[reportAttributeAccessIssue] - else: - self._ensure_pyarrow() - import io - - import pyarrow as pa - import pyarrow.parquet as pq - - buffer = io.BytesIO() - - schema = table.schema - if any(str(f.type).startswith("decimal64") for f in schema): - new_fields = [] - for field in schema: - if str(field.type).startswith("decimal64"): - import re - - match = re.match(r"decimal64\((\d+),\s*(\d+)\)", str(field.type)) - if match: - precision, scale = int(match.group(1)), int(match.group(2)) - new_fields.append(pa.field(field.name, pa.decimal128(precision, scale))) - else: - new_fields.append(field) # pragma: no cover + ensure_pyarrow() + import io + + import pyarrow as pa + import pyarrow.parquet as pq + + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True) + + schema = table.schema + if any(str(f.type).startswith("decimal64") for f in schema): + new_fields = [] + for field in schema: + if str(field.type).startswith("decimal64"): + import re + + match = re.match(r"decimal64\((\d+),\s*(\d+)\)", str(field.type)) + if match: + precision, scale = int(match.group(1)), int(match.group(2)) + new_fields.append(pa.field(field.name, pa.decimal128(precision, scale))) else: new_fields.append(field) - table = table.cast(pa.schema(new_fields)) + else: + new_fields.append(field) + table = table.cast(pa.schema(new_fields)) - pq.write_table(table, buffer, **kwargs) - buffer.seek(0) - self.write_bytes(resolved_path, buffer.read()) + buffer = io.BytesIO() + pq.write_table(table, buffer, **kwargs) + buffer.seek(0) + self.write_bytes(resolved_path, buffer.read()) def stream_arrow(self, pattern: str, **kwargs: Any) -> Iterator[ArrowRecordBatch]: """Stream Arrow record batches. @@ -325,13 +410,14 @@ def stream_arrow(self, pattern: str, **kwargs: Any) -> Iterator[ArrowRecordBatch Yields: Iterator of Arrow record batches from matching objects. """ - self._ensure_pyarrow() + ensure_pyarrow() from io import BytesIO import pyarrow.parquet as pq for obj_path in self.glob(pattern, **kwargs): - result = self.store.get(self._resolve_path(obj_path)) + resolved_path = resolve_storage_path(obj_path, self.base_path, self.protocol, strip_file_scheme=True) + result = self.store.get(resolved_path) bytes_obj = result.bytes() data = bytes_obj.to_bytes() buffer = BytesIO(data) @@ -340,26 +426,38 @@ def stream_arrow(self, pattern: str, **kwargs: Any) -> Iterator[ArrowRecordBatch def sign(self, path: str, expires_in: int = 3600, for_upload: bool = False) -> str: """Generate a signed URL for the object.""" - resolved_path = self._resolve_path(path) - if hasattr(self.store, "sign_url") and callable(self.store.sign_url): - return self.store.sign_url(resolved_path, expires_in=expires_in) # type: ignore[no-any-return] + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True) return f"{self.store_uri}/{resolved_path}" async def read_bytes_async(self, path: "str | Path", **kwargs: Any) -> bytes: # pyright: ignore[reportUnusedParameter] """Read bytes from storage asynchronously.""" - resolved_path = self._resolve_path(path) + # For LocalStore (file protocol with base_path), use special resolution + if self._is_local_store: + resolved_path = self._resolve_path_for_local_store(path) + else: + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True) + result = await self.store.get_async(resolved_path) bytes_obj = await result.bytes_async() return bytes_obj.to_bytes() # type: ignore[no-any-return] # pyright: ignore[reportAttributeAccessIssue] async def write_bytes_async(self, path: "str | Path", data: bytes, **kwargs: Any) -> None: # pyright: ignore[reportUnusedParameter] """Write bytes to storage asynchronously.""" - resolved_path = self._resolve_path(path) + # For LocalStore (file protocol with base_path), use special resolution + if self._is_local_store: + resolved_path = self._resolve_path_for_local_store(path) + else: + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True) + await self.store.put_async(resolved_path, data) async def list_objects_async(self, prefix: str = "", recursive: bool = True, **kwargs: Any) -> list[str]: # pyright: ignore[reportUnusedParameter] """List objects in storage asynchronously.""" - resolved_prefix = self._resolve_path(prefix) if prefix else self.base_path or "" + resolved_prefix = ( + resolve_storage_path(prefix, self.base_path, self.protocol, strip_file_scheme=True) + if prefix + else self.base_path or "" + ) objects: list[str] = [] async for batch in self.store.list_async(resolved_prefix): # pyright: ignore[reportAttributeAccessIssue] @@ -383,7 +481,12 @@ async def write_text_async(self, path: "str | Path", data: str, encoding: str = async def exists_async(self, path: "str | Path", **kwargs: Any) -> bool: # pyright: ignore[reportUnusedParameter] """Check if object exists in storage asynchronously.""" - resolved_path = self._resolve_path(path) + # For LocalStore (file protocol with base_path), use special resolution + if self._is_local_store: + resolved_path = self._resolve_path_for_local_store(path) + else: + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True) + try: await self.store.head_async(resolved_path) except Exception: @@ -392,24 +495,46 @@ async def exists_async(self, path: "str | Path", **kwargs: Any) -> bool: # pyri async def delete_async(self, path: "str | Path", **kwargs: Any) -> None: # pyright: ignore[reportUnusedParameter] """Delete object from storage asynchronously.""" - resolved_path = self._resolve_path(path) + # For LocalStore (file protocol with base_path), use special resolution + if self._is_local_store: + resolved_path = self._resolve_path_for_local_store(path) + else: + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True) + await self.store.delete_async(resolved_path) async def copy_async(self, source: "str | Path", destination: "str | Path", **kwargs: Any) -> None: # pyright: ignore[reportUnusedParameter] """Copy object in storage asynchronously.""" - source_path = self._resolve_path(source) - dest_path = self._resolve_path(destination) + # For LocalStore (file protocol with base_path), use special resolution + if self._is_local_store: + source_path = self._resolve_path_for_local_store(source) + dest_path = self._resolve_path_for_local_store(destination) + else: + source_path = resolve_storage_path(source, self.base_path, self.protocol, strip_file_scheme=True) + dest_path = resolve_storage_path(destination, self.base_path, self.protocol, strip_file_scheme=True) + await self.store.copy_async(source_path, dest_path) async def move_async(self, source: "str | Path", destination: "str | Path", **kwargs: Any) -> None: # pyright: ignore[reportUnusedParameter] """Move object in storage asynchronously.""" - source_path = self._resolve_path(source) - dest_path = self._resolve_path(destination) + # For LocalStore (file protocol with base_path), use special resolution + if self._is_local_store: + source_path = self._resolve_path_for_local_store(source) + dest_path = self._resolve_path_for_local_store(destination) + else: + source_path = resolve_storage_path(source, self.base_path, self.protocol, strip_file_scheme=True) + dest_path = resolve_storage_path(destination, self.base_path, self.protocol, strip_file_scheme=True) + await self.store.rename_async(source_path, dest_path) async def get_metadata_async(self, path: "str | Path", **kwargs: Any) -> dict[str, Any]: # pyright: ignore[reportUnusedParameter] """Get object metadata from storage asynchronously.""" - resolved_path = self._resolve_path(path) + # For LocalStore (file protocol with base_path), use special resolution + if self._is_local_store: + resolved_path = self._resolve_path_for_local_store(path) + else: + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True) + result: dict[str, Any] = {} try: metadata = await self.store.head_async(resolved_path) @@ -433,40 +558,33 @@ async def get_metadata_async(self, path: "str | Path", **kwargs: Any) -> dict[st async def read_arrow_async(self, path: "str | Path", **kwargs: Any) -> ArrowTable: """Read Arrow table from storage asynchronously.""" - resolved_path = self._resolve_path(path) - if hasattr(self.store, "read_arrow_async"): - return await self.store.read_arrow_async(resolved_path, **kwargs) # type: ignore[no-any-return] # pyright: ignore[reportAttributeAccessIssue] - - self._ensure_pyarrow() + ensure_pyarrow() import io import pyarrow.parquet as pq - return pq.read_table(io.BytesIO(await self.read_bytes_async(resolved_path)), **kwargs) + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True) + data = await self.read_bytes_async(resolved_path) + return pq.read_table(io.BytesIO(data), **kwargs) async def write_arrow_async(self, path: "str | Path", table: ArrowTable, **kwargs: Any) -> None: """Write Arrow table to storage asynchronously.""" - resolved_path = self._resolve_path(path) - if hasattr(self.store, "write_arrow_async"): - await self.store.write_arrow_async(resolved_path, table, **kwargs) # pyright: ignore[reportAttributeAccessIssue] - else: - self._ensure_pyarrow() - import io + ensure_pyarrow() + import io - import pyarrow.parquet as pq + import pyarrow.parquet as pq - buffer = io.BytesIO() - pq.write_table(table, buffer, **kwargs) - buffer.seek(0) - await self.write_bytes_async(resolved_path, buffer.read()) + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True) + buffer = io.BytesIO() + pq.write_table(table, buffer, **kwargs) + buffer.seek(0) + await self.write_bytes_async(resolved_path, buffer.read()) def stream_arrow_async(self, pattern: str, **kwargs: Any) -> AsyncIterator[ArrowRecordBatch]: - resolved_pattern = self._resolve_path(pattern) + resolved_pattern = resolve_storage_path(pattern, self.base_path, self.protocol, strip_file_scheme=True) return _AsyncArrowIterator(self, resolved_pattern, **kwargs) async def sign_async(self, path: str, expires_in: int = 3600, for_upload: bool = False) -> str: """Generate a signed URL asynchronously.""" - resolved_path = self._resolve_path(path) - if hasattr(self.store, "sign_url_async") and callable(self.store.sign_url_async): - return await self.store.sign_url_async(resolved_path, expires_in=expires_in) # type: ignore[no-any-return] + resolved_path = resolve_storage_path(path, self.base_path, self.protocol, strip_file_scheme=True) return f"{self.store_uri}/{resolved_path}" diff --git a/sqlspec/storage/registry.py b/sqlspec/storage/registry.py index a3946e2f..adb57c51 100644 --- a/sqlspec/storage/registry.py +++ b/sqlspec/storage/registry.py @@ -15,26 +15,12 @@ from sqlspec.exceptions import ImproperConfigurationError, MissingDependencyError from sqlspec.protocols import ObjectStoreProtocol from sqlspec.typing import FSSPEC_INSTALLED, OBSTORE_INSTALLED +from sqlspec.utils.type_guards import is_local_path __all__ = ("StorageRegistry", "storage_registry") logger = logging.getLogger(__name__) - -def _is_local_uri(uri: str) -> bool: - """Check if URI represents a local filesystem path.""" - if "://" in uri and not uri.startswith("file://"): - return False - windows_drive_min_length = 3 - return ( - Path(uri).exists() - or Path(uri).is_absolute() - or uri.startswith(("~", ".", "/")) - or (len(uri) >= windows_drive_min_length and uri[1:3] == ":\\") - or "/" in uri - ) - - SCHEME_REGEX: Final = re.compile(r"([a-zA-Z0-9+.-]+)://") @@ -131,11 +117,15 @@ def get(self, uri_or_alias: str | Path, *, backend: str | None = None, **kwargs: if isinstance(uri_or_alias, Path): uri_or_alias = f"file://{uri_or_alias.resolve()}" - cache_key = (uri_or_alias, self._make_hashable(kwargs)) if kwargs else uri_or_alias + # Include backend in cache key to ensure different backends for same URI are cached separately + cache_params = dict(kwargs) + if backend: + cache_params["__backend__"] = backend + cache_key = (uri_or_alias, self._make_hashable(cache_params)) if cache_params else uri_or_alias if cache_key in self._instances: return self._instances[cache_key] scheme = self._get_scheme(uri_or_alias) - if not scheme and _is_local_uri(uri_or_alias): + if not scheme and is_local_path(uri_or_alias): scheme = "file" uri_or_alias = f"file://{uri_or_alias}" @@ -153,54 +143,104 @@ def get(self, uri_or_alias: str | Path, *, backend: str | None = None, **kwargs: return instance def _resolve_from_uri(self, uri: str, *, backend_override: str | None = None, **kwargs: Any) -> ObjectStoreProtocol: - """Resolve backend from URI with optional backend override.""" + """Resolve backend from URI with optional backend override. + + Backend selection priority for local files (file:// or bare paths): + 1. obstore (if installed) - provides async I/O performance + 2. fsspec (if installed) - async wrapper fallback + 3. local (always available) - zero-dependency sync backend + + For cloud storage, prefer obstore over fsspec when available. + + Args: + uri: Storage URI to resolve. + backend_override: Force specific backend type. + **kwargs: Additional backend configuration. + + Returns: + Configured backend instance. + + Raises: + MissingDependencyError: No backend available for URI scheme. + """ if backend_override: return self._create_backend(backend_override, uri, **kwargs) + scheme = self._get_scheme(uri) - # For local files, prefer LocalStore first + # NEW: Prefer obstore for local files when available if scheme in {None, "file"}: + # Try obstore first for async performance + if OBSTORE_INSTALLED: + try: + return self._create_backend("obstore", uri, **kwargs) + except (ValueError, ImportError, NotImplementedError): + pass + + # Fallback to fsspec if available + if FSSPEC_INSTALLED: + try: + return self._create_backend("fsspec", uri, **kwargs) + except (ValueError, ImportError, NotImplementedError): + pass + + # Final fallback: local zero-dependency backend return self._create_backend("local", uri, **kwargs) - # Try ObStore first if available and appropriate + # For cloud schemes, prefer obstore over fsspec if scheme not in FSSPEC_ONLY_SCHEMES and OBSTORE_INSTALLED: try: return self._create_backend("obstore", uri, **kwargs) except (ValueError, ImportError, NotImplementedError): pass - # Try FSSpec if available + # Try fsspec if available if FSSPEC_INSTALLED: try: return self._create_backend("fsspec", uri, **kwargs) except (ValueError, ImportError, NotImplementedError): pass - # For cloud schemes without backends, provide helpful error + # No backend available msg = f"No backend available for URI scheme '{scheme}'. Install obstore or fsspec for cloud storage support." raise MissingDependencyError(msg) def _determine_backend_class(self, uri: str) -> type[ObjectStoreProtocol]: - """Determine the backend class for a URI based on availability.""" + """Determine the backend class for a URI based on availability. + + Args: + uri: Storage URI to analyze. + + Returns: + Backend class type to use. + + Raises: + MissingDependencyError: No backend available for URI scheme. + """ scheme = self._get_scheme(uri) - # For local files, always use LocalStore + # NEW: For local files, prefer obstore > fsspec > local if scheme in {None, "file"}: + if OBSTORE_INSTALLED: + return self._get_backend_class("obstore") + if FSSPEC_INSTALLED: + return self._get_backend_class("fsspec") return self._get_backend_class("local") # FSSpec-only schemes require FSSpec - if scheme in FSSPEC_ONLY_SCHEMES and FSSPEC_INSTALLED: + if scheme in FSSPEC_ONLY_SCHEMES: + if not FSSPEC_INSTALLED: + msg = f"Scheme '{scheme}' requires fsspec. Install with: pip install fsspec" + raise MissingDependencyError(msg) return self._get_backend_class("fsspec") - # Prefer ObStore for cloud storage if available + # For cloud schemes, prefer obstore if OBSTORE_INSTALLED: return self._get_backend_class("obstore") - # Fall back to FSSpec if available if FSSPEC_INSTALLED: return self._get_backend_class("fsspec") - # For cloud schemes without backends, provide helpful error msg = f"No backend available for URI scheme '{scheme}'. Install obstore or fsspec for cloud storage support." raise MissingDependencyError(msg) diff --git a/sqlspec/utils/type_guards.py b/sqlspec/utils/type_guards.py index 02c5fb50..bed19c62 100644 --- a/sqlspec/utils/type_guards.py +++ b/sqlspec/utils/type_guards.py @@ -102,6 +102,7 @@ "is_indexable_row", "is_iterable_parameters", "is_limit_offset_filter", + "is_local_path", "is_msgspec_struct", "is_msgspec_struct_with_field", "is_msgspec_struct_without_field", @@ -122,6 +123,7 @@ "is_typed_dict", "is_typed_parameter", "schema_dump", + "supports_arrow_native", "supports_limit", "supports_offset", "supports_order_by", @@ -1255,3 +1257,79 @@ def has_expression_and_parameters(obj: Any) -> bool: True if the object has both attributes, False otherwise """ return hasattr(obj, "expression") and hasattr(obj, "parameters") + + +WINDOWS_DRIVE_PATTERN_LENGTH = 3 + + +def is_local_path(uri: str) -> bool: + r"""Check if URI represents a local filesystem path. + + Detects local paths including: + - file:// URIs + - Absolute paths (Unix: /, Windows: C:\\) + - Relative paths (., .., ~) + + Args: + uri: URI or path string to check. + + Returns: + True if uri is a local path, False for remote URIs. + + Examples: + >>> is_local_path("file:///data/file.txt") + True + >>> is_local_path("/absolute/path") + True + >>> is_local_path("s3://bucket/key") + False + """ + if not uri: + return False + + if "://" in uri and not uri.startswith("file://"): + return False + + if uri.startswith("file://"): + return True + + if uri.startswith("/"): + return True + + if uri.startswith((".", "~")): + return True + + if len(uri) >= WINDOWS_DRIVE_PATTERN_LENGTH and uri[1:3] == ":\\": + return True + + return "/" in uri or "\\" in uri + + +def supports_arrow_native(backend: Any) -> bool: + """Check if storage backend supports native Arrow operations. + + Some storage backends (like certain obstore stores) have native + Arrow read/write support, which is faster than going through bytes. + + Args: + backend: Storage backend instance to check. + + Returns: + True if backend has native read_arrow/write_arrow methods. + + Examples: + >>> from sqlspec.storage.backends.obstore import ObStoreBackend + >>> backend = ObStoreBackend("file:///tmp") + >>> supports_arrow_native(backend) + False + """ + from sqlspec.protocols import ObjectStoreProtocol + + if not isinstance(backend, ObjectStoreProtocol): + return False + + try: + store = backend.store # type: ignore[attr-defined] + return callable(getattr(store, "read_arrow", None)) + except AttributeError: + return False diff --git a/tests/integration/test_storage/test_storage_integration.py b/tests/integration/test_storage/test_storage_integration.py index c8a4a438..20459a75 100644 --- a/tests/integration/test_storage/test_storage_integration.py +++ b/tests/integration/test_storage/test_storage_integration.py @@ -49,7 +49,7 @@ def fsspec_s3_backend(minio_service: "MinioService", minio_default_bucket_name: from sqlspec.storage.backends.fsspec import FSSpecBackend return FSSpecBackend( - uri=f"s3://{minio_default_bucket_name}", + uri=f"s3://{minio_default_bucket_name}/", endpoint_url=f"http://{minio_service.endpoint}", key=minio_service.access_key, secret=minio_service.secret_key, @@ -331,6 +331,7 @@ def test_obstore_s3_listing_operations(obstore_s3_backend: "ObjectStoreProtocol" def test_registry_uri_resolution_local(tmp_path: Path) -> None: """Test storage registry URI resolution for local files.""" from sqlspec.storage.backends.local import LocalStore + from sqlspec.storage.backends.obstore import ObStoreBackend # Test file URI resolution test_file = tmp_path / "registry_test.txt" @@ -339,7 +340,8 @@ def test_registry_uri_resolution_local(tmp_path: Path) -> None: # Test file:// URI file_uri = f"file://{test_file}" backend = storage_registry.get(file_uri) - assert isinstance(backend, LocalStore) + # Registry prefers obstore for file:// URIs when available, otherwise LocalStore + assert isinstance(backend, (ObStoreBackend, LocalStore)) content = backend.read_text("registry_test.txt") assert content == TEST_TEXT_CONTENT @@ -349,13 +351,15 @@ def test_registry_uri_resolution_local(tmp_path: Path) -> None: def test_registry_path_resolution(tmp_path: Path) -> None: """Test storage registry resolution for raw paths.""" from sqlspec.storage.backends.local import LocalStore + from sqlspec.storage.backends.obstore import ObStoreBackend # Test Path object resolution test_file = tmp_path / "path_test.txt" test_file.write_text(TEST_TEXT_CONTENT) backend = storage_registry.get(tmp_path) - assert isinstance(backend, LocalStore) + # Registry prefers obstore for local paths when available, otherwise LocalStore + assert isinstance(backend, (ObStoreBackend, LocalStore)) content = backend.read_text("path_test.txt") assert content == TEST_TEXT_CONTENT @@ -367,7 +371,7 @@ def test_registry_s3_fsspec_resolution(minio_service: "MinioService", minio_defa """Test storage registry S3 resolution with FSSpec backend.""" from sqlspec.storage.backends.fsspec import FSSpecBackend - s3_uri = f"s3://{minio_default_bucket_name}/registry_test" + s3_uri = f"s3://{minio_default_bucket_name}/registry_test/" backend = storage_registry.get( s3_uri, @@ -395,6 +399,7 @@ def test_registry_alias_registration( ) -> None: """Test storage registry alias registration and usage.""" from sqlspec.storage.backends.local import LocalStore + from sqlspec.storage.backends.obstore import ObStoreBackend # Clear registry to avoid test interference storage_registry.clear() @@ -405,7 +410,8 @@ def test_registry_alias_registration( # Test local alias backend = storage_registry.get("test-local") - assert isinstance(backend, LocalStore) + # Registry prefers obstore for local paths when available, otherwise LocalStore + assert isinstance(backend, (ObStoreBackend, LocalStore)) # Create test data backend.write_text("alias_test.txt", TEST_TEXT_CONTENT) @@ -418,7 +424,7 @@ def test_registry_alias_registration( storage_registry.register_alias( "test-s3", - uri=f"s3://{minio_default_bucket_name}", + uri=f"s3://{minio_default_bucket_name}/", backend="fsspec", endpoint_url=f"http://{minio_service.endpoint}", key=minio_service.access_key, @@ -652,14 +658,16 @@ def test_registry_backend_fallback_order( ) -> None: """Test that registry follows correct backend fallback order.""" from sqlspec.storage.backends.local import LocalStore + from sqlspec.storage.backends.obstore import ObStoreBackend storage_registry.clear() try: - # Test local file resolution (should always use LocalStore) + # Test local file resolution (prefers ObStore > FSSpec > LocalStore) local_uri = f"file://{tmp_path}" local_backend = storage_registry.get(local_uri) - assert isinstance(local_backend, LocalStore) + # Should get ObStore if available, else FSSpec, else LocalStore + assert isinstance(local_backend, (ObStoreBackend, LocalStore)) # Test S3 resolution (should prefer ObStore > FSSpec if available) s3_uri = f"s3://{minio_default_bucket_name}" diff --git a/tests/unit/test_storage/test_storage_registry.py b/tests/unit/test_storage/test_storage_registry.py index 3f311c3c..08f068f8 100644 --- a/tests/unit/test_storage/test_storage_registry.py +++ b/tests/unit/test_storage/test_storage_registry.py @@ -7,26 +7,28 @@ import pytest from sqlspec.exceptions import ImproperConfigurationError, MissingDependencyError -from sqlspec.storage.registry import StorageRegistry, _is_local_uri +from sqlspec.storage.registry import StorageRegistry from sqlspec.typing import FSSPEC_INSTALLED, OBSTORE_INSTALLED +from sqlspec.utils.type_guards import is_local_path -def test_is_local_uri() -> None: - """Test _is_local_uri type guard function.""" - # Absolute paths - assert _is_local_uri("/absolute/path") - assert _is_local_uri("C:\\Windows\\path") +def test_is_local_path_type_guard() -> None: + """Test is_local_path type guard function.""" + assert is_local_path("file:///absolute/path") + assert is_local_path("file://C:/Windows/path") - # Relative paths - assert _is_local_uri("./relative/path") - assert _is_local_uri("../parent/path") - assert _is_local_uri("~/home/path") - assert _is_local_uri("relative/path") + assert is_local_path("/absolute/path") + assert is_local_path("C:\\Windows\\path") - # URIs should return False - assert not _is_local_uri("s3://bucket/key") - assert not _is_local_uri("https://example.com") - assert not _is_local_uri("gs://bucket") + assert is_local_path("./relative/path") + assert is_local_path("../parent/path") + assert is_local_path("~/home/path") + assert is_local_path("relative/path") + + assert not is_local_path("s3://bucket/key") + assert not is_local_path("https://example.com") + assert not is_local_path("gs://bucket") + assert not is_local_path("") def test_registry_init() -> None: @@ -45,19 +47,55 @@ def test_register_alias() -> None: def test_get_local_backend() -> None: - """Test getting local backend.""" + """Test getting local backend (when explicitly requested).""" with tempfile.TemporaryDirectory() as temp_dir: registry = StorageRegistry() - # Test direct path - backend = registry.get(temp_dir) + # Force local backend with override + backend = registry.get(temp_dir, backend="local") assert backend.backend_type == "local" - # Test file:// URI + # Force local backend for file:// URI + backend = registry.get(f"file://{temp_dir}", backend="local") + assert backend.backend_type == "local" + + +@pytest.mark.skipif(not OBSTORE_INSTALLED, reason="obstore not installed") +def test_get_local_backend_prefers_obstore() -> None: + """Test that local paths prefer obstore when available.""" + with tempfile.TemporaryDirectory() as temp_dir: + registry = StorageRegistry() + + # Without backend override, should get obstore for file:// backend = registry.get(f"file://{temp_dir}") + assert backend.backend_type == "obstore" + + # Direct path should also prefer obstore + backend = registry.get(temp_dir) + assert backend.backend_type == "obstore" + + # Can still force local backend with override + backend = registry.get(f"file://{temp_dir}", backend="local") assert backend.backend_type == "local" +def test_get_local_backend_fallback_priority() -> None: + """Test backend fallback priority for local paths.""" + with tempfile.TemporaryDirectory() as temp_dir: + registry = StorageRegistry() + + # Get whatever backend is available + backend = registry.get(f"file://{temp_dir}") + + # Should be one of: obstore > fsspec > local (in that priority order) + if OBSTORE_INSTALLED: + assert backend.backend_type == "obstore" + elif FSSPEC_INSTALLED: + assert backend.backend_type == "fsspec" + else: + assert backend.backend_type == "local" + + def test_get_alias() -> None: """Test getting backend by alias.""" with tempfile.TemporaryDirectory() as temp_dir: @@ -65,7 +103,8 @@ def test_get_alias() -> None: registry.register_alias("my_store", f"file://{temp_dir}") backend = registry.get("my_store") - assert backend.backend_type == "local" + # Backend type depends on what's installed + assert backend.backend_type in ("obstore", "fsspec", "local") def test_get_with_backend_override() -> None: @@ -222,7 +261,8 @@ def test_path_object_conversion() -> None: path_obj = Path(temp_dir) backend = registry.get(path_obj) - assert backend.backend_type == "local" + # Backend type depends on what's installed (obstore > fsspec > local) + assert backend.backend_type in ("obstore", "fsspec", "local") def test_cloud_storage_without_backends() -> None: diff --git a/tests/unit/test_storage/test_storage_utils.py b/tests/unit/test_storage/test_storage_utils.py new file mode 100644 index 00000000..4208dd1b --- /dev/null +++ b/tests/unit/test_storage/test_storage_utils.py @@ -0,0 +1,74 @@ +"""Unit tests for storage utilities.""" + +import pytest + +from sqlspec.exceptions import MissingDependencyError +from sqlspec.storage._utils import ensure_pyarrow, resolve_storage_path +from sqlspec.typing import PYARROW_INSTALLED + + +def test_ensure_pyarrow_succeeds_when_installed() -> None: + """Test ensure_pyarrow succeeds when pyarrow is available.""" + if not PYARROW_INSTALLED: + pytest.skip("pyarrow not installed") + + ensure_pyarrow() + + +def test_ensure_pyarrow_raises_when_not_installed() -> None: + """Test ensure_pyarrow raises error when pyarrow not available.""" + if PYARROW_INSTALLED: + pytest.skip("pyarrow is installed") + + with pytest.raises(MissingDependencyError, match="pyarrow"): + ensure_pyarrow() + + +def test_resolve_storage_path_file_protocol_absolute() -> None: + """Test path resolution for file protocol with absolute path.""" + result = resolve_storage_path("/data/file.txt", base_path="", protocol="file") + assert result == "data/file.txt" + + +def test_resolve_storage_path_file_protocol_with_base() -> None: + """Test path resolution for file protocol with base_path.""" + result = resolve_storage_path("file.txt", base_path="/base", protocol="file") + assert result == "/base/file.txt" + + +def test_resolve_storage_path_file_scheme_stripping() -> None: + """Test file:// scheme stripping.""" + result = resolve_storage_path("file:///data/file.txt", base_path="", protocol="file", strip_file_scheme=True) + assert result == "data/file.txt" + + +def test_resolve_storage_path_file_scheme_preserved() -> None: + """Test file:// scheme preserved when strip_file_scheme=False.""" + result = resolve_storage_path("file:///data/file.txt", base_path="", protocol="file", strip_file_scheme=False) + assert result == "file:///data/file.txt" + + +def test_resolve_storage_path_cloud_protocol() -> None: + """Test path resolution for cloud protocols.""" + result = resolve_storage_path("data/file.txt", base_path="bucket/prefix", protocol="s3") + assert result == "bucket/prefix/data/file.txt" + + +def test_resolve_storage_path_cloud_absolute() -> None: + """Test absolute path handling for cloud protocols.""" + result = resolve_storage_path("/data/file.txt", base_path="", protocol="s3") + assert result == "/data/file.txt" + + +def test_resolve_storage_path_no_base_path() -> None: + """Test path resolution without base_path.""" + result = resolve_storage_path("data/file.txt", base_path="", protocol="s3") + assert result == "data/file.txt" + + +def test_resolve_storage_path_pathlib_input() -> None: + """Test path resolution with pathlib.Path input.""" + from pathlib import Path + + result = resolve_storage_path(Path("data") / "file.txt", base_path="", protocol="file") + assert result == "data/file.txt" diff --git a/uv.lock b/uv.lock index 26e7f784..1d1c435a 100644 --- a/uv.lock +++ b/uv.lock @@ -661,11 +661,11 @@ wheels = [ [[package]] name = "certifi" -version = "2025.8.3" +version = "2025.10.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, + { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" }, ] [[package]] @@ -844,6 +844,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "covdefaults" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/ee/9a6f2611f72e4c5657ae5542a510cf4164d2c673687c0ea73bb1cbd85b4d/covdefaults-2.3.0.tar.gz", hash = "sha256:4e99f679f12d792bc62e5510fa3eb59546ed47bd569e36e4fddc4081c9c3ebf7", size = 4835, upload-time = "2023-03-05T16:43:34.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/4c/823bc951445aa97e5a1b7e337690db3abf85212c8d138e170922e7916ac8/covdefaults-2.3.0-py2.py3-none-any.whl", hash = "sha256:2832961f6ffcfe4b57c338bc3418a3526f495c26fb9c54565409c5532f7c41be", size = 5144, upload-time = "2023-03-05T16:43:33.245Z" }, +] + [[package]] name = "coverage" version = "7.10.7" @@ -1871,7 +1883,7 @@ wheels = [ [[package]] name = "litestar" -version = "2.17.0" +version = "2.18.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1888,9 +1900,9 @@ dependencies = [ { name = "rich-click" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3f/62/36619846adb94d24d31e1aad7a52632544f2db7b0df22c8839cfa1db0b2e/litestar-2.17.0.tar.gz", hash = "sha256:0f50c3b7063417c338b32902f9eb40b81a7b14c9802edbff921a24aec3b0ea5b", size = 372203, upload-time = "2025-08-10T13:37:41.401Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/75/e0c286719860bc63459dd3364616bb436aecfbd8ab26f212a3a7957a551f/litestar-2.18.0.tar.gz", hash = "sha256:be8f91813854722b7a2f37cbb57d76977050a96b2427d3c4455d406f0f4fcd50", size = 372890, upload-time = "2025-10-05T16:24:00.094Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/04/0935667c3cee2a26152102b9319fdfe987b0f765e14e0311c17ca6698820/litestar-2.17.0-py3-none-any.whl", hash = "sha256:2e62f01f852195362d69e3305cccddaf6371f5ab0ced30a081a66fb85e1df532", size = 564343, upload-time = "2025-08-10T13:37:39.462Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ec/76d3e07db8bac2f3248acb8879c7a8ba6b6c96e846c40d8474d1a0f79160/litestar-2.18.0-py3-none-any.whl", hash = "sha256:459ec993bafe47245c981d802a0a0c73f47c98313b3c4e47923eebe978f0e511", size = 564603, upload-time = "2025-10-05T16:23:58.439Z" }, ] [[package]] @@ -2941,91 +2953,116 @@ wheels = [ [[package]] name = "propcache" -version = "0.3.2" +version = "0.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139, upload-time = "2025-06-09T22:56:06.081Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/14/510deed325e262afeb8b360043c5d7c960da7d3ecd6d6f9496c9c56dc7f4/propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770", size = 73178, upload-time = "2025-06-09T22:53:40.126Z" }, - { url = "https://files.pythonhosted.org/packages/cd/4e/ad52a7925ff01c1325653a730c7ec3175a23f948f08626a534133427dcff/propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3", size = 43133, upload-time = "2025-06-09T22:53:41.965Z" }, - { url = "https://files.pythonhosted.org/packages/63/7c/e9399ba5da7780871db4eac178e9c2e204c23dd3e7d32df202092a1ed400/propcache-0.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3", size = 43039, upload-time = "2025-06-09T22:53:43.268Z" }, - { url = "https://files.pythonhosted.org/packages/22/e1/58da211eb8fdc6fc854002387d38f415a6ca5f5c67c1315b204a5d3e9d7a/propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e", size = 201903, upload-time = "2025-06-09T22:53:44.872Z" }, - { url = "https://files.pythonhosted.org/packages/c4/0a/550ea0f52aac455cb90111c8bab995208443e46d925e51e2f6ebdf869525/propcache-0.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220", size = 213362, upload-time = "2025-06-09T22:53:46.707Z" }, - { url = "https://files.pythonhosted.org/packages/5a/af/9893b7d878deda9bb69fcf54600b247fba7317761b7db11fede6e0f28bd0/propcache-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb", size = 210525, upload-time = "2025-06-09T22:53:48.547Z" }, - { url = "https://files.pythonhosted.org/packages/7c/bb/38fd08b278ca85cde36d848091ad2b45954bc5f15cce494bb300b9285831/propcache-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614", size = 198283, upload-time = "2025-06-09T22:53:50.067Z" }, - { url = "https://files.pythonhosted.org/packages/78/8c/9fe55bd01d362bafb413dfe508c48753111a1e269737fa143ba85693592c/propcache-0.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50", size = 191872, upload-time = "2025-06-09T22:53:51.438Z" }, - { url = "https://files.pythonhosted.org/packages/54/14/4701c33852937a22584e08abb531d654c8bcf7948a8f87ad0a4822394147/propcache-0.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339", size = 199452, upload-time = "2025-06-09T22:53:53.229Z" }, - { url = "https://files.pythonhosted.org/packages/16/44/447f2253d859602095356007657ee535e0093215ea0b3d1d6a41d16e5201/propcache-0.3.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0", size = 191567, upload-time = "2025-06-09T22:53:54.541Z" }, - { url = "https://files.pythonhosted.org/packages/f2/b3/e4756258749bb2d3b46defcff606a2f47410bab82be5824a67e84015b267/propcache-0.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2", size = 193015, upload-time = "2025-06-09T22:53:56.44Z" }, - { url = "https://files.pythonhosted.org/packages/1e/df/e6d3c7574233164b6330b9fd697beeac402afd367280e6dc377bb99b43d9/propcache-0.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7", size = 204660, upload-time = "2025-06-09T22:53:57.839Z" }, - { url = "https://files.pythonhosted.org/packages/b2/53/e4d31dd5170b4a0e2e6b730f2385a96410633b4833dc25fe5dffd1f73294/propcache-0.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b", size = 206105, upload-time = "2025-06-09T22:53:59.638Z" }, - { url = "https://files.pythonhosted.org/packages/7f/fe/74d54cf9fbe2a20ff786e5f7afcfde446588f0cf15fb2daacfbc267b866c/propcache-0.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c", size = 196980, upload-time = "2025-06-09T22:54:01.071Z" }, - { url = "https://files.pythonhosted.org/packages/22/ec/c469c9d59dada8a7679625e0440b544fe72e99311a4679c279562051f6fc/propcache-0.3.2-cp310-cp310-win32.whl", hash = "sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70", size = 37679, upload-time = "2025-06-09T22:54:03.003Z" }, - { url = "https://files.pythonhosted.org/packages/38/35/07a471371ac89d418f8d0b699c75ea6dca2041fbda360823de21f6a9ce0a/propcache-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9", size = 41459, upload-time = "2025-06-09T22:54:04.134Z" }, - { url = "https://files.pythonhosted.org/packages/80/8d/e8b436717ab9c2cfc23b116d2c297305aa4cd8339172a456d61ebf5669b8/propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be", size = 74207, upload-time = "2025-06-09T22:54:05.399Z" }, - { url = "https://files.pythonhosted.org/packages/d6/29/1e34000e9766d112171764b9fa3226fa0153ab565d0c242c70e9945318a7/propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f", size = 43648, upload-time = "2025-06-09T22:54:08.023Z" }, - { url = "https://files.pythonhosted.org/packages/46/92/1ad5af0df781e76988897da39b5f086c2bf0f028b7f9bd1f409bb05b6874/propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9", size = 43496, upload-time = "2025-06-09T22:54:09.228Z" }, - { url = "https://files.pythonhosted.org/packages/b3/ce/e96392460f9fb68461fabab3e095cb00c8ddf901205be4eae5ce246e5b7e/propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf", size = 217288, upload-time = "2025-06-09T22:54:10.466Z" }, - { url = "https://files.pythonhosted.org/packages/c5/2a/866726ea345299f7ceefc861a5e782b045545ae6940851930a6adaf1fca6/propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9", size = 227456, upload-time = "2025-06-09T22:54:11.828Z" }, - { url = "https://files.pythonhosted.org/packages/de/03/07d992ccb6d930398689187e1b3c718339a1c06b8b145a8d9650e4726166/propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66", size = 225429, upload-time = "2025-06-09T22:54:13.823Z" }, - { url = "https://files.pythonhosted.org/packages/5d/e6/116ba39448753b1330f48ab8ba927dcd6cf0baea8a0ccbc512dfb49ba670/propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df", size = 213472, upload-time = "2025-06-09T22:54:15.232Z" }, - { url = "https://files.pythonhosted.org/packages/a6/85/f01f5d97e54e428885a5497ccf7f54404cbb4f906688a1690cd51bf597dc/propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2", size = 204480, upload-time = "2025-06-09T22:54:17.104Z" }, - { url = "https://files.pythonhosted.org/packages/e3/79/7bf5ab9033b8b8194cc3f7cf1aaa0e9c3256320726f64a3e1f113a812dce/propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7", size = 214530, upload-time = "2025-06-09T22:54:18.512Z" }, - { url = "https://files.pythonhosted.org/packages/31/0b/bd3e0c00509b609317df4a18e6b05a450ef2d9a963e1d8bc9c9415d86f30/propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95", size = 205230, upload-time = "2025-06-09T22:54:19.947Z" }, - { url = "https://files.pythonhosted.org/packages/7a/23/fae0ff9b54b0de4e819bbe559508da132d5683c32d84d0dc2ccce3563ed4/propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e", size = 206754, upload-time = "2025-06-09T22:54:21.716Z" }, - { url = "https://files.pythonhosted.org/packages/b7/7f/ad6a3c22630aaa5f618b4dc3c3598974a72abb4c18e45a50b3cdd091eb2f/propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e", size = 218430, upload-time = "2025-06-09T22:54:23.17Z" }, - { url = "https://files.pythonhosted.org/packages/5b/2c/ba4f1c0e8a4b4c75910742f0d333759d441f65a1c7f34683b4a74c0ee015/propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf", size = 223884, upload-time = "2025-06-09T22:54:25.539Z" }, - { url = "https://files.pythonhosted.org/packages/88/e4/ebe30fc399e98572019eee82ad0caf512401661985cbd3da5e3140ffa1b0/propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e", size = 211480, upload-time = "2025-06-09T22:54:26.892Z" }, - { url = "https://files.pythonhosted.org/packages/96/0a/7d5260b914e01d1d0906f7f38af101f8d8ed0dc47426219eeaf05e8ea7c2/propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897", size = 37757, upload-time = "2025-06-09T22:54:28.241Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2d/89fe4489a884bc0da0c3278c552bd4ffe06a1ace559db5ef02ef24ab446b/propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39", size = 41500, upload-time = "2025-06-09T22:54:29.4Z" }, - { url = "https://files.pythonhosted.org/packages/a8/42/9ca01b0a6f48e81615dca4765a8f1dd2c057e0540f6116a27dc5ee01dfb6/propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10", size = 73674, upload-time = "2025-06-09T22:54:30.551Z" }, - { url = "https://files.pythonhosted.org/packages/af/6e/21293133beb550f9c901bbece755d582bfaf2176bee4774000bd4dd41884/propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154", size = 43570, upload-time = "2025-06-09T22:54:32.296Z" }, - { url = "https://files.pythonhosted.org/packages/0c/c8/0393a0a3a2b8760eb3bde3c147f62b20044f0ddac81e9d6ed7318ec0d852/propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615", size = 43094, upload-time = "2025-06-09T22:54:33.929Z" }, - { url = "https://files.pythonhosted.org/packages/37/2c/489afe311a690399d04a3e03b069225670c1d489eb7b044a566511c1c498/propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db", size = 226958, upload-time = "2025-06-09T22:54:35.186Z" }, - { url = "https://files.pythonhosted.org/packages/9d/ca/63b520d2f3d418c968bf596839ae26cf7f87bead026b6192d4da6a08c467/propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1", size = 234894, upload-time = "2025-06-09T22:54:36.708Z" }, - { url = "https://files.pythonhosted.org/packages/11/60/1d0ed6fff455a028d678df30cc28dcee7af77fa2b0e6962ce1df95c9a2a9/propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c", size = 233672, upload-time = "2025-06-09T22:54:38.062Z" }, - { url = "https://files.pythonhosted.org/packages/37/7c/54fd5301ef38505ab235d98827207176a5c9b2aa61939b10a460ca53e123/propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67", size = 224395, upload-time = "2025-06-09T22:54:39.634Z" }, - { url = "https://files.pythonhosted.org/packages/ee/1a/89a40e0846f5de05fdc6779883bf46ba980e6df4d2ff8fb02643de126592/propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b", size = 212510, upload-time = "2025-06-09T22:54:41.565Z" }, - { url = "https://files.pythonhosted.org/packages/5e/33/ca98368586c9566a6b8d5ef66e30484f8da84c0aac3f2d9aec6d31a11bd5/propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8", size = 222949, upload-time = "2025-06-09T22:54:43.038Z" }, - { url = "https://files.pythonhosted.org/packages/ba/11/ace870d0aafe443b33b2f0b7efdb872b7c3abd505bfb4890716ad7865e9d/propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251", size = 217258, upload-time = "2025-06-09T22:54:44.376Z" }, - { url = "https://files.pythonhosted.org/packages/5b/d2/86fd6f7adffcfc74b42c10a6b7db721d1d9ca1055c45d39a1a8f2a740a21/propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474", size = 213036, upload-time = "2025-06-09T22:54:46.243Z" }, - { url = "https://files.pythonhosted.org/packages/07/94/2d7d1e328f45ff34a0a284cf5a2847013701e24c2a53117e7c280a4316b3/propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535", size = 227684, upload-time = "2025-06-09T22:54:47.63Z" }, - { url = "https://files.pythonhosted.org/packages/b7/05/37ae63a0087677e90b1d14710e532ff104d44bc1efa3b3970fff99b891dc/propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06", size = 234562, upload-time = "2025-06-09T22:54:48.982Z" }, - { url = "https://files.pythonhosted.org/packages/a4/7c/3f539fcae630408d0bd8bf3208b9a647ccad10976eda62402a80adf8fc34/propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1", size = 222142, upload-time = "2025-06-09T22:54:50.424Z" }, - { url = "https://files.pythonhosted.org/packages/7c/d2/34b9eac8c35f79f8a962546b3e97e9d4b990c420ee66ac8255d5d9611648/propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1", size = 37711, upload-time = "2025-06-09T22:54:52.072Z" }, - { url = "https://files.pythonhosted.org/packages/19/61/d582be5d226cf79071681d1b46b848d6cb03d7b70af7063e33a2787eaa03/propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c", size = 41479, upload-time = "2025-06-09T22:54:53.234Z" }, - { url = "https://files.pythonhosted.org/packages/dc/d1/8c747fafa558c603c4ca19d8e20b288aa0c7cda74e9402f50f31eb65267e/propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945", size = 71286, upload-time = "2025-06-09T22:54:54.369Z" }, - { url = "https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252", size = 42425, upload-time = "2025-06-09T22:54:55.642Z" }, - { url = "https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f", size = 41846, upload-time = "2025-06-09T22:54:57.246Z" }, - { url = "https://files.pythonhosted.org/packages/5b/ad/3f0f9a705fb630d175146cd7b1d2bf5555c9beaed54e94132b21aac098a6/propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33", size = 208871, upload-time = "2025-06-09T22:54:58.975Z" }, - { url = "https://files.pythonhosted.org/packages/3a/38/2085cda93d2c8b6ec3e92af2c89489a36a5886b712a34ab25de9fbca7992/propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e", size = 215720, upload-time = "2025-06-09T22:55:00.471Z" }, - { url = "https://files.pythonhosted.org/packages/61/c1/d72ea2dc83ac7f2c8e182786ab0fc2c7bd123a1ff9b7975bee671866fe5f/propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1", size = 215203, upload-time = "2025-06-09T22:55:01.834Z" }, - { url = "https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3", size = 206365, upload-time = "2025-06-09T22:55:03.199Z" }, - { url = "https://files.pythonhosted.org/packages/09/73/88549128bb89e66d2aff242488f62869014ae092db63ccea53c1cc75a81d/propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1", size = 196016, upload-time = "2025-06-09T22:55:04.518Z" }, - { url = "https://files.pythonhosted.org/packages/b9/3f/3bdd14e737d145114a5eb83cb172903afba7242f67c5877f9909a20d948d/propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6", size = 205596, upload-time = "2025-06-09T22:55:05.942Z" }, - { url = "https://files.pythonhosted.org/packages/0f/ca/2f4aa819c357d3107c3763d7ef42c03980f9ed5c48c82e01e25945d437c1/propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387", size = 200977, upload-time = "2025-06-09T22:55:07.792Z" }, - { url = "https://files.pythonhosted.org/packages/cd/4a/e65276c7477533c59085251ae88505caf6831c0e85ff8b2e31ebcbb949b1/propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4", size = 197220, upload-time = "2025-06-09T22:55:09.173Z" }, - { url = "https://files.pythonhosted.org/packages/7c/54/fc7152e517cf5578278b242396ce4d4b36795423988ef39bb8cd5bf274c8/propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88", size = 210642, upload-time = "2025-06-09T22:55:10.62Z" }, - { url = "https://files.pythonhosted.org/packages/b9/80/abeb4a896d2767bf5f1ea7b92eb7be6a5330645bd7fb844049c0e4045d9d/propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206", size = 212789, upload-time = "2025-06-09T22:55:12.029Z" }, - { url = "https://files.pythonhosted.org/packages/b3/db/ea12a49aa7b2b6d68a5da8293dcf50068d48d088100ac016ad92a6a780e6/propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43", size = 205880, upload-time = "2025-06-09T22:55:13.45Z" }, - { url = "https://files.pythonhosted.org/packages/d1/e5/9076a0bbbfb65d1198007059c65639dfd56266cf8e477a9707e4b1999ff4/propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02", size = 37220, upload-time = "2025-06-09T22:55:15.284Z" }, - { url = "https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05", size = 40678, upload-time = "2025-06-09T22:55:16.445Z" }, - { url = "https://files.pythonhosted.org/packages/a4/3a/6ece377b55544941a08d03581c7bc400a3c8cd3c2865900a68d5de79e21f/propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b", size = 76560, upload-time = "2025-06-09T22:55:17.598Z" }, - { url = "https://files.pythonhosted.org/packages/0c/da/64a2bb16418740fa634b0e9c3d29edff1db07f56d3546ca2d86ddf0305e1/propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0", size = 44676, upload-time = "2025-06-09T22:55:18.922Z" }, - { url = "https://files.pythonhosted.org/packages/36/7b/f025e06ea51cb72c52fb87e9b395cced02786610b60a3ed51da8af017170/propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e", size = 44701, upload-time = "2025-06-09T22:55:20.106Z" }, - { url = "https://files.pythonhosted.org/packages/a4/00/faa1b1b7c3b74fc277f8642f32a4c72ba1d7b2de36d7cdfb676db7f4303e/propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28", size = 276934, upload-time = "2025-06-09T22:55:21.5Z" }, - { url = "https://files.pythonhosted.org/packages/74/ab/935beb6f1756e0476a4d5938ff44bf0d13a055fed880caf93859b4f1baf4/propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a", size = 278316, upload-time = "2025-06-09T22:55:22.918Z" }, - { url = "https://files.pythonhosted.org/packages/f8/9d/994a5c1ce4389610838d1caec74bdf0e98b306c70314d46dbe4fcf21a3e2/propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c", size = 282619, upload-time = "2025-06-09T22:55:24.651Z" }, - { url = "https://files.pythonhosted.org/packages/2b/00/a10afce3d1ed0287cef2e09506d3be9822513f2c1e96457ee369adb9a6cd/propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725", size = 265896, upload-time = "2025-06-09T22:55:26.049Z" }, - { url = "https://files.pythonhosted.org/packages/2e/a8/2aa6716ffa566ca57c749edb909ad27884680887d68517e4be41b02299f3/propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892", size = 252111, upload-time = "2025-06-09T22:55:27.381Z" }, - { url = "https://files.pythonhosted.org/packages/36/4f/345ca9183b85ac29c8694b0941f7484bf419c7f0fea2d1e386b4f7893eed/propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44", size = 268334, upload-time = "2025-06-09T22:55:28.747Z" }, - { url = "https://files.pythonhosted.org/packages/3e/ca/fcd54f78b59e3f97b3b9715501e3147f5340167733d27db423aa321e7148/propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe", size = 255026, upload-time = "2025-06-09T22:55:30.184Z" }, - { url = "https://files.pythonhosted.org/packages/8b/95/8e6a6bbbd78ac89c30c225210a5c687790e532ba4088afb8c0445b77ef37/propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81", size = 250724, upload-time = "2025-06-09T22:55:31.646Z" }, - { url = "https://files.pythonhosted.org/packages/ee/b0/0dd03616142baba28e8b2d14ce5df6631b4673850a3d4f9c0f9dd714a404/propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba", size = 268868, upload-time = "2025-06-09T22:55:33.209Z" }, - { url = "https://files.pythonhosted.org/packages/c5/98/2c12407a7e4fbacd94ddd32f3b1e3d5231e77c30ef7162b12a60e2dd5ce3/propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770", size = 271322, upload-time = "2025-06-09T22:55:35.065Z" }, - { url = "https://files.pythonhosted.org/packages/35/91/9cb56efbb428b006bb85db28591e40b7736847b8331d43fe335acf95f6c8/propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330", size = 265778, upload-time = "2025-06-09T22:55:36.45Z" }, - { url = "https://files.pythonhosted.org/packages/9a/4c/b0fe775a2bdd01e176b14b574be679d84fc83958335790f7c9a686c1f468/propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394", size = 41175, upload-time = "2025-06-09T22:55:38.436Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ff/47f08595e3d9b5e149c150f88d9714574f1a7cbd89fe2817158a952674bf/propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198", size = 44857, upload-time = "2025-06-09T22:55:39.687Z" }, - { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/ea/c8/d70cd26d845c6d85479d8f5a11a0fd7151e9bc4794cc5e6eb5a790f12df8/propcache-0.4.0.tar.gz", hash = "sha256:c1ad731253eb738f9cadd9fa1844e019576c70bca6a534252e97cf33a57da529", size = 45187, upload-time = "2025-10-04T21:57:39.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/7b/4bd85fea3dc58b6f246abf0e6c9e44adca26f6817e6c136780315d723b82/propcache-0.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:779aaae64089e2f4992e993faea801925395d26bb5de4a47df7ef7f942c14f80", size = 79437, upload-time = "2025-10-04T21:54:49.766Z" }, + { url = "https://files.pythonhosted.org/packages/e0/91/379ecc1ab37fe33648c7cb2d2252f58969adac1edcd6ec74682d7fb2d920/propcache-0.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566552ed9b003030745e5bc7b402b83cf3cecae1bade95262d78543741786db5", size = 45369, upload-time = "2025-10-04T21:54:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/de/8e/2e002e59e359bbc6ababbb7da168226f93e0533429ea1e93989a7eedcb2a/propcache-0.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:944de70384c62d16d4a00c686b422aa75efbc67c4addaebefbb56475d1c16034", size = 47191, upload-time = "2025-10-04T21:54:52.915Z" }, + { url = "https://files.pythonhosted.org/packages/b5/9a/f56eef9932dc3cbc63df4716f09fbaefec7a475608b643842784a01351b6/propcache-0.4.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e878553543ece1f8006d0ba4d096b40290580db173bfb18e16158045b9371335", size = 201000, upload-time = "2025-10-04T21:54:54.556Z" }, + { url = "https://files.pythonhosted.org/packages/6e/84/e7ad1e09c13f0574dbad261441f6a7f1fb8cc1e2fcb23ec4d4b3e4c7dc67/propcache-0.4.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8659f995b19185179474b18de8755689e1f71e1334d05c14e1895caa4e409cf7", size = 209175, upload-time = "2025-10-04T21:54:55.996Z" }, + { url = "https://files.pythonhosted.org/packages/4a/74/0b785ac0fbb44a5a7c267efc409b7a62d7a03b17c6442ecb52fd29152314/propcache-0.4.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7aa8cc5c94e682dce91cb4d12d7b81c01641f4ef5b3b3dc53325d43f0e3b9f2e", size = 214874, upload-time = "2025-10-04T21:54:57.831Z" }, + { url = "https://files.pythonhosted.org/packages/b4/fd/e8d795def2b1d8dc1dc4731d36da1f6111d7c73212909e79462172d0434c/propcache-0.4.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da584d917a1a17f690fc726617fd2c3f3006ea959dae5bb07a5630f7b16f9f5f", size = 196686, upload-time = "2025-10-04T21:54:59.218Z" }, + { url = "https://files.pythonhosted.org/packages/79/c2/dc992c712c3a1bfaa11d13ff177dbdf9b8b272e7bd443601e37f35728338/propcache-0.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:892a072e5b19c3f324a4f8543c9f7e8fc2b0aa08579e46f69bdf0cfc1b440454", size = 192000, upload-time = "2025-10-04T21:55:00.645Z" }, + { url = "https://files.pythonhosted.org/packages/b3/de/bb108dbdfae594148b033ff283d9fa6e4b0906a99f2c03b98b526883149d/propcache-0.4.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c20d796210720455086ef3f85adc413d1e41d374742f9b439354f122bbc3b528", size = 190310, upload-time = "2025-10-04T21:55:02.107Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7b/1bdb5d44ba4c87d270bcf11354950f8f7fbc9ace1fbe7745e683fcb57b5a/propcache-0.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:df7107a91126a495880576610ae989f19106e1900dd5218d08498391fa43b31d", size = 199646, upload-time = "2025-10-04T21:55:03.55Z" }, + { url = "https://files.pythonhosted.org/packages/e5/04/44beda877f779f49f5b8c0ff4817a62b5f90a2dfac1ec5311df15a9dfceb/propcache-0.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0b04ac2120c161416c866d0b6a4259e47e92231ff166b518cc0efb95777367c3", size = 200507, upload-time = "2025-10-04T21:55:04.914Z" }, + { url = "https://files.pythonhosted.org/packages/d4/62/a13ad0a63e06f3695fcaeaeeeb62e2cc685181a1248b23a2bc877c8b7111/propcache-0.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1e7fa29c71ffa8d6a37324258737d09475f84715a6e8c350f67f0bc8e5e44993", size = 192787, upload-time = "2025-10-04T21:55:06.385Z" }, + { url = "https://files.pythonhosted.org/packages/9b/07/386246b3b4a6b11208bcbf57580210fb8c923ab26759389fe594e5615cd7/propcache-0.4.0-cp310-cp310-win32.whl", hash = "sha256:01c0ebc172ca28e9d62876832befbf7f36080eee6ed9c9e00243de2a8089ad57", size = 38004, upload-time = "2025-10-04T21:55:07.692Z" }, + { url = "https://files.pythonhosted.org/packages/a4/f2/e1fcb9694f590bc443ae5044f982546bb01cbaa3cdf05286e9473a9874bf/propcache-0.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:84f847e64f4d1a232e50460eebc1196642ee9b4c983612f41cd2d44fd2fe7c71", size = 41516, upload-time = "2025-10-04T21:55:08.854Z" }, + { url = "https://files.pythonhosted.org/packages/15/f4/d211744d41d72fbb89d3ee53963c1dc26892c49f53ae3c49fbc15cfb2548/propcache-0.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:2166466a666a5bebc332cd209cad77d996fad925ca7e8a2a6310ba9e851ae641", size = 38122, upload-time = "2025-10-04T21:55:10.044Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c4/72b8d41bdbae8aea9c25b869d7cdc3ab5f281f979d8aea30f4646ad12743/propcache-0.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6a6a36b94c09711d6397d79006ca47901539fbc602c853d794c39abd6a326549", size = 80035, upload-time = "2025-10-04T21:55:11.266Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f8/f87115733e221408a363f3a9753419cf2d4be7a8a7ec9dc0788325cd23f1/propcache-0.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:da47070e1340a1639aca6b1c18fe1f1f3d8d64d3a1f9ddc67b94475f44cd40f3", size = 45622, upload-time = "2025-10-04T21:55:12.41Z" }, + { url = "https://files.pythonhosted.org/packages/5d/cc/391f883248faa2efdf6886bdb12ac8edf20eac0863770d8d925450d8cc76/propcache-0.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:de536cf796abc5b58d11c0ad56580215d231d9554ea4bb6b8b1b3bed80aa3234", size = 47517, upload-time = "2025-10-04T21:55:13.819Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/5593b59999f42d1044c5ab5f238be1f9d537ab91b0c910727986d520a6e9/propcache-0.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5c82af8e329c3cdc3e717dd3c7b2ff1a218b6de611f6ce76ee34967570a9de9", size = 214540, upload-time = "2025-10-04T21:55:15.206Z" }, + { url = "https://files.pythonhosted.org/packages/bb/5d/028cdc0eaa1a66ee2ec339a08b5e6ec15e7e71dac86103bebe53ba10dc0f/propcache-0.4.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:abe04e7aa5ab2e4056fcf3255ebee2071e4a427681f76d4729519e292c46ecc1", size = 221603, upload-time = "2025-10-04T21:55:16.704Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f8/e30aee5f59ea21647faef9c82bd67fa510295c34908a7a38571def555881/propcache-0.4.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:075ca32384294434344760fdcb95f7833e1d7cf7c4e55f0e726358140179da35", size = 227749, upload-time = "2025-10-04T21:55:18.082Z" }, + { url = "https://files.pythonhosted.org/packages/d7/85/0757dfc73931bea63b18d26b2c5e7bf13113ca60fe0e5f19905f104bcf6a/propcache-0.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:626ec13592928b677f48ff5861040b604b635e93d8e2162fb638397ea83d07e8", size = 209792, upload-time = "2025-10-04T21:55:19.475Z" }, + { url = "https://files.pythonhosted.org/packages/d2/45/35a6a6241f46948c0ac2418d5bf50cfbcd9735739f42028a1c11e9066a72/propcache-0.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:02e071548b6a376e173b0102c3f55dc16e7d055b5307d487e844c320e38cacf2", size = 207979, upload-time = "2025-10-04T21:55:21.164Z" }, + { url = "https://files.pythonhosted.org/packages/e3/d1/5930396e75c9ed477958eac1496e6fb08794d823e9b14a459f1c0e20f338/propcache-0.4.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2af6de831a26f42a3f94592964becd8d7f238551786d7525807f02e53defbd13", size = 201923, upload-time = "2025-10-04T21:55:22.5Z" }, + { url = "https://files.pythonhosted.org/packages/98/72/675455f22bcefeda16907461f9a9a4a93709ff2095e8cf799bdb6c78e030/propcache-0.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd6c6dba1a3b8949e08c4280071c86e38cb602f02e0ed6659234108c7a7cd710", size = 212117, upload-time = "2025-10-04T21:55:23.858Z" }, + { url = "https://files.pythonhosted.org/packages/13/27/c533302ff80a49a848c3dbd01bb18f87b06826602b3b37043ff00d6b5005/propcache-0.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:783e91595cf9b66c2deda17f2e8748ae8591aa9f7c65dcab038872bfe83c5bb1", size = 216594, upload-time = "2025-10-04T21:55:25.169Z" }, + { url = "https://files.pythonhosted.org/packages/63/91/8250fbb601fd16c427e5f469132f27e175c6692dbfa784ef1266dc652e55/propcache-0.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c3f4b125285d354a627eb37f3ea7c13b8842c7c0d47783581d0df0e272dbf5f0", size = 204863, upload-time = "2025-10-04T21:55:26.511Z" }, + { url = "https://files.pythonhosted.org/packages/34/c4/fd945a9a25845aafb6094b9fa6a88286e4e1c55686e60172c60fe669e0d1/propcache-0.4.0-cp311-cp311-win32.whl", hash = "sha256:71c45f02ffbb8a21040ae816ceff7f6cd749ffac29fc0f9daa42dc1a9652d577", size = 37948, upload-time = "2025-10-04T21:55:27.719Z" }, + { url = "https://files.pythonhosted.org/packages/42/02/f30e7304661ffe8d51ff4050e06765ac2df6d95cf23c999dfe5a0cd0eb4c/propcache-0.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:7d51f70f77950f8efafed4383865d3533eeee52d8a0dd1c35b65f24de41de4e0", size = 41511, upload-time = "2025-10-04T21:55:29.15Z" }, + { url = "https://files.pythonhosted.org/packages/a5/f2/edd329d86085438a1ba32cf4cf45fc982d18343bed1f16b218b516c3340d/propcache-0.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:858eaabd2191dd0da5272993ad08a748b5d3ae1aefabea8aee619b45c2af4a64", size = 37957, upload-time = "2025-10-04T21:55:30.31Z" }, + { url = "https://files.pythonhosted.org/packages/b3/cf/3f88344261d69f8021256f20e82e820c5df3aba96e5ba9b5fdd3685d3a9f/propcache-0.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:381c84a445efb8c9168f1393a5a7c566de22edc42bfe207a142fff919b37f5d9", size = 79846, upload-time = "2025-10-04T21:55:31.447Z" }, + { url = "https://files.pythonhosted.org/packages/be/fa/0286fc92764eead9dcfee639b67828daa32e61dd0f1618831547141eb28b/propcache-0.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5a531d29d7b873b12730972237c48b1a4e5980b98cf21b3f09fa4710abd3a8c3", size = 45850, upload-time = "2025-10-04T21:55:32.637Z" }, + { url = "https://files.pythonhosted.org/packages/c7/83/57840656f972f8a67992eee40781e4066657776dcb889f49df0e8eecb112/propcache-0.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cd6e22255ed73efeaaeb1765505a66a48a9ec9ebc919fce5ad490fe5e33b1555", size = 47171, upload-time = "2025-10-04T21:55:33.819Z" }, + { url = "https://files.pythonhosted.org/packages/9f/8e/e0a0bd376c3440476b924eca517589ee535bb4520420d178268bf88558ba/propcache-0.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9a8d277dc218ddf04ec243a53ac309b1afcebe297c0526a8f82320139b56289", size = 225306, upload-time = "2025-10-04T21:55:35.312Z" }, + { url = "https://files.pythonhosted.org/packages/84/fe/76884442da1bab6d4353ba1c43fdc4a770c3b3973f3ac7620a7205402fdd/propcache-0.4.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:399c73201d88c856a994916200d7cba41d7687096f8eb5139eb68f02785dc3f7", size = 230013, upload-time = "2025-10-04T21:55:37.005Z" }, + { url = "https://files.pythonhosted.org/packages/f4/b7/322af273bd1136bb7e13628821fb855c9f61d64651c73fea71dded68dda5/propcache-0.4.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a1d5e474d43c238035b74ecf997f655afa67f979bae591ac838bb3fbe3076392", size = 238331, upload-time = "2025-10-04T21:55:38.713Z" }, + { url = "https://files.pythonhosted.org/packages/84/5e/036d2b105927ae7f179346c9911d16c345f4dba5a19a063f23a8d28acfbd/propcache-0.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f589652ee38de96aa58dd219335604e09666092bc250c1d9c26a55bcef9932", size = 221461, upload-time = "2025-10-04T21:55:40.034Z" }, + { url = "https://files.pythonhosted.org/packages/63/0d/babd038efb12a87a46ab070438c52daeac6bed0a930693a418feef8cb8a6/propcache-0.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5227da556b2939da6125cda1d5eecf9e412e58bc97b41e2f192605c3ccbb7c2", size = 216707, upload-time = "2025-10-04T21:55:41.455Z" }, + { url = "https://files.pythonhosted.org/packages/ab/68/dd075a037381581f16e7e504a6da9c1d7e415e945dd8ed67905d608f0687/propcache-0.4.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:92bc43a1ab852310721ce856f40a3a352254aa6f5e26f0fad870b31be45bba2e", size = 212591, upload-time = "2025-10-04T21:55:42.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/43/22698f28fc8e04c32b109cb9cb81305a4873b77c907b17484566b6133aef/propcache-0.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:83ae2f5343f6f06f4c91ae530d95f56b415f768f9c401a5ee2a10459cf74370b", size = 220188, upload-time = "2025-10-04T21:55:44.53Z" }, + { url = "https://files.pythonhosted.org/packages/96/7a/27886e4a4c69598a38fbeeed64f9b8ddfa6f08fe3452035845a1fe90336f/propcache-0.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:077a32977399dc05299b16e793210341a0b511eb0a86d1796873e83ce47334cc", size = 226736, upload-time = "2025-10-04T21:55:46.348Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c7/313c632b5888db3c9f4cb262420dcd5e57cf858d939d6ad9c3b1b90c12af/propcache-0.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:94a278c45e6463031b5a8278e40a07edf2bcc3b5379510e22b6c1a6e6498c194", size = 216363, upload-time = "2025-10-04T21:55:47.768Z" }, + { url = "https://files.pythonhosted.org/packages/7a/5d/5aaf82bd1542aedb47d10483b84f49ee8f00d970a58e27534cd241e9c5ac/propcache-0.4.0-cp312-cp312-win32.whl", hash = "sha256:4c491462e1dc80f9deb93f428aad8d83bb286de212837f58eb48e75606e7726c", size = 37945, upload-time = "2025-10-04T21:55:49.104Z" }, + { url = "https://files.pythonhosted.org/packages/4c/67/47ffff6eb176f383f56319f31c0e1bcf7500cb94ffb7582efc600c6b3c73/propcache-0.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:cdb0cecafb528ab15ed89cdfed183074d15912d046d3e304955513b50a34b907", size = 41530, upload-time = "2025-10-04T21:55:50.261Z" }, + { url = "https://files.pythonhosted.org/packages/f3/7e/61b70306b9d7527286ce887a8ff28c304ab2514e5893eea36b5bdf7a21af/propcache-0.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:b2f29697d1110e8cdf7a39cc630498df0082d7898b79b731c1c863f77c6e8cfc", size = 37662, upload-time = "2025-10-04T21:55:51.35Z" }, + { url = "https://files.pythonhosted.org/packages/cd/dd/f405b0fe84d29d356895bc048404d3321a2df849281cf3f932158c9346ac/propcache-0.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e2d01fd53e89cb3d71d20b8c225a8c70d84660f2d223afc7ed7851a4086afe6d", size = 77565, upload-time = "2025-10-04T21:55:52.907Z" }, + { url = "https://files.pythonhosted.org/packages/c0/48/dfb2c45e1b0d92228c9c66fa929af7316c15cbe69a7e438786aaa60c1b3c/propcache-0.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7dfa60953169d2531dd8ae306e9c27c5d4e5efe7a2ba77049e8afdaece062937", size = 44602, upload-time = "2025-10-04T21:55:54.406Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/b15e88b4463df45a7793fb04e2b5497334f8fcc24e281c221150a0af9aff/propcache-0.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:227892597953611fce2601d49f1d1f39786a6aebc2f253c2de775407f725a3f6", size = 46168, upload-time = "2025-10-04T21:55:55.537Z" }, + { url = "https://files.pythonhosted.org/packages/40/ac/983e69cce8800251aab85858069cf9359b22222a9cda47591e03e2f24eec/propcache-0.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e0a5bc019014531308fb67d86066d235daa7551baf2e00e1ea7b00531f6ea85", size = 207997, upload-time = "2025-10-04T21:55:57.022Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9c/5586a7a54e7e0b9a87fdd8ba935961f398c0e6eaecd57baaa8eca468a236/propcache-0.4.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6ebc6e2e65c31356310ddb6519420eaa6bb8c30fbd809d0919129c89dcd70f4c", size = 210948, upload-time = "2025-10-04T21:55:58.397Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ba/644e367f8a86461d45bd023ace521180938e76515040550af9b44085e99a/propcache-0.4.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1927b78dd75fc31a7fdc76cc7039e39f3170cb1d0d9a271e60f0566ecb25211a", size = 217988, upload-time = "2025-10-04T21:56:00.251Z" }, + { url = "https://files.pythonhosted.org/packages/24/0e/1e21af74b4732d002b0452605bdf31d6bf990fd8b720cb44e27a97d80db5/propcache-0.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b113feeda47f908562d9a6d0e05798ad2f83d4473c0777dafa2bc7756473218", size = 204442, upload-time = "2025-10-04T21:56:01.93Z" }, + { url = "https://files.pythonhosted.org/packages/fd/30/ae2eec96995a8a760acb9a0b6c92b9815f1fc885c7d8481237ccb554eab0/propcache-0.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4596c12aa7e3bb2abf158ea8f79eb0fb4851606695d04ab846b2bb386f5690a1", size = 199371, upload-time = "2025-10-04T21:56:03.25Z" }, + { url = "https://files.pythonhosted.org/packages/45/1d/a18fac8cb04f8379ccb79cf15aac31f4167a270d1cd1111f33c0d38ce4fb/propcache-0.4.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6d1f67dad8cc36e8abc2207a77f3f952ac80be7404177830a7af4635a34cbc16", size = 196638, upload-time = "2025-10-04T21:56:04.619Z" }, + { url = "https://files.pythonhosted.org/packages/48/45/3549a2b6f74dce6f21b2664d078bd26ceb876aae9c58f3c017cf590f0ee3/propcache-0.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e6229ad15366cd8b6d6b4185c55dd48debf9ca546f91416ba2e5921ad6e210a6", size = 203651, upload-time = "2025-10-04T21:56:06.153Z" }, + { url = "https://files.pythonhosted.org/packages/7d/f0/90ea14d518c919fc154332742a9302db3004af4f1d3df688676959733283/propcache-0.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2a4bf309d057327f1f227a22ac6baf34a66f9af75e08c613e47c4d775b06d6c7", size = 205726, upload-time = "2025-10-04T21:56:07.955Z" }, + { url = "https://files.pythonhosted.org/packages/f6/de/8efc1dbafeb42108e7af744822cdca944b990869e9da70e79efb21569d6b/propcache-0.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c2e274f3d1cbb2ddcc7a55ce3739af0f8510edc68a7f37981b2258fa1eedc833", size = 199576, upload-time = "2025-10-04T21:56:09.43Z" }, + { url = "https://files.pythonhosted.org/packages/d7/38/4d79fe3477b050398fb8d8f59301ed116d8c6ea3c4dbf09498c679103f90/propcache-0.4.0-cp313-cp313-win32.whl", hash = "sha256:f114a3e1f8034e2957d34043b7a317a8a05d97dfe8fddb36d9a2252c0117dbbc", size = 37474, upload-time = "2025-10-04T21:56:10.74Z" }, + { url = "https://files.pythonhosted.org/packages/36/9b/a283daf665a1945cff1b03d1104e7c9ee92bb7b6bbcc6518b24fcdac8bd0/propcache-0.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ba68c57cde9c667f6b65b98bc342dfa7240b1272ffb2c24b32172ee61b6d281", size = 40685, upload-time = "2025-10-04T21:56:11.896Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f7/def8fc0b4d7a89f1628f337cb122bb9a946c5ed97760f2442b27b7fa5a69/propcache-0.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:eb77a85253174bf73e52c968b689d64be62d71e8ac33cabef4ca77b03fb4ef92", size = 37046, upload-time = "2025-10-04T21:56:13.021Z" }, + { url = "https://files.pythonhosted.org/packages/ca/6b/f6e8b36b58d17dfb6c505b9ae1163fcf7a4cf98825032fdc77bba4ab5c4a/propcache-0.4.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c0e1c218fff95a66ad9f2f83ad41a67cf4d0a3f527efe820f57bde5fda616de4", size = 81274, upload-time = "2025-10-04T21:56:14.206Z" }, + { url = "https://files.pythonhosted.org/packages/8e/c5/1fd0baa222b8faf53ba04dd4f34de33ea820b80e34f87c7960666bae5f4f/propcache-0.4.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:5710b1c01472542bb024366803812ca13e8774d21381bcfc1f7ae738eeb38acc", size = 46232, upload-time = "2025-10-04T21:56:15.337Z" }, + { url = "https://files.pythonhosted.org/packages/cb/6b/7aa5324983cab7666ed58fc32c68a0430468a18e02e3f04e7a879c002414/propcache-0.4.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d7f008799682e8826ce98f25e8bc43532d2cd26c187a1462499fa8d123ae054f", size = 48239, upload-time = "2025-10-04T21:56:16.768Z" }, + { url = "https://files.pythonhosted.org/packages/24/0f/58c192301c0436762ed5fed5a3edadb0ae399cb73528fb9c1b5cb8e53523/propcache-0.4.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0596d2ae99d74ca436553eb9ce11fe4163dc742fcf8724ebe07d7cb0db679bb1", size = 275804, upload-time = "2025-10-04T21:56:18.066Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b9/092ee32064ebfabedae4251952787e63e551075af1a1205e8061b3ed5838/propcache-0.4.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab9c1bd95ebd1689f0e24f2946c495808777e9e8df7bb3c1dfe3e9eb7f47fe0d", size = 273996, upload-time = "2025-10-04T21:56:19.801Z" }, + { url = "https://files.pythonhosted.org/packages/43/82/becf618ed28e732f3bba3df172cd290a1afbd99f291074f747fd5bd031bb/propcache-0.4.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a8ef2ea819549ae2e8698d2ec229ae948d7272feea1cb2878289f767b6c585a4", size = 280266, upload-time = "2025-10-04T21:56:21.136Z" }, + { url = "https://files.pythonhosted.org/packages/51/be/b370930249a9332a81b5c4c550dac614b7e11b6c160080777e903d57e197/propcache-0.4.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:71a400b2f0b079438cc24f9a27f02eff24d8ef78f2943f949abc518b844ade3d", size = 263186, upload-time = "2025-10-04T21:56:22.787Z" }, + { url = "https://files.pythonhosted.org/packages/33/b6/546fd3e31770aed3aed1c01b120944c689edb510aeb7a25472edc472ce23/propcache-0.4.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4c2735d3305e6cecab6e53546909edf407ad3da5b9eeaf483f4cf80142bb21be", size = 260721, upload-time = "2025-10-04T21:56:24.22Z" }, + { url = "https://files.pythonhosted.org/packages/80/70/3751930d16e5984490c73ca65b80777e4b26e7a0015f2d41f31d75959a71/propcache-0.4.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:72b51340047ac43b3cf388eebd362d052632260c9f73a50882edbb66e589fd44", size = 247516, upload-time = "2025-10-04T21:56:25.577Z" }, + { url = "https://files.pythonhosted.org/packages/59/90/4bc96ce6476f67e2e6b72469f328c92b53259a0e4d1d5386d71a36e9258c/propcache-0.4.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:184c779363740d6664982ad05699f378f7694220e2041996f12b7c2a4acdcad0", size = 262675, upload-time = "2025-10-04T21:56:27.065Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d1/f16d096869c5f1c93d67fc37488c0c814add0560574f6877653a10239cde/propcache-0.4.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a60634a9de41f363923c6adfb83105d39e49f7a3058511563ed3de6748661af6", size = 263379, upload-time = "2025-10-04T21:56:28.517Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2a/da5cd1bc1c6412939c457ea65bbe7e034045c395d98ff8ff880d06ec4553/propcache-0.4.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c9b8119244d122241a9c4566bce49bb20408a6827044155856735cf14189a7da", size = 257694, upload-time = "2025-10-04T21:56:30.051Z" }, + { url = "https://files.pythonhosted.org/packages/a5/11/938e67c07189b662a6c72551d48285a02496de885408392447c25657dd47/propcache-0.4.0-cp313-cp313t-win32.whl", hash = "sha256:515b610a364c8cdd2b72c734cc97dece85c416892ea8d5c305624ac8734e81db", size = 41321, upload-time = "2025-10-04T21:56:31.406Z" }, + { url = "https://files.pythonhosted.org/packages/f4/6e/72b11a4dcae68c728b15126cc5bc830bf275c84836da2633412b768d07e0/propcache-0.4.0-cp313-cp313t-win_amd64.whl", hash = "sha256:7ea86eb32e74f9902df57e8608e8ac66f1e1e1d24d1ed2ddeb849888413b924d", size = 44846, upload-time = "2025-10-04T21:56:32.5Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/0ef3c025e0621e703ef71b69e0085181a3124bcc1beef29e0ffef59ed7f4/propcache-0.4.0-cp313-cp313t-win_arm64.whl", hash = "sha256:c1443fa4bb306461a3a8a52b7de0932a2515b100ecb0ebc630cc3f87d451e0a9", size = 39689, upload-time = "2025-10-04T21:56:33.686Z" }, + { url = "https://files.pythonhosted.org/packages/60/89/7699d8e9f8c222bbef1fae26afd72d448353f164a52125d5f87dd9fec2c7/propcache-0.4.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:de8e310d24b5a61de08812dd70d5234da1458d41b059038ee7895a9e4c8cae79", size = 77977, upload-time = "2025-10-04T21:56:34.836Z" }, + { url = "https://files.pythonhosted.org/packages/77/c5/2758a498199ce46d6d500ba4391a8594df35400cc85738aa9f0c9b8366db/propcache-0.4.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:55a54de5266bc44aa274915cdf388584fa052db8748a869e5500ab5993bac3f4", size = 44715, upload-time = "2025-10-04T21:56:36.075Z" }, + { url = "https://files.pythonhosted.org/packages/0d/da/5a44e10282a28c2dd576e5e1a2c7bb8145587070ddab7375fb643f7129d7/propcache-0.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:88d50d662c917ec2c9d3858920aa7b9d5bfb74ab9c51424b775ccbe683cb1b4e", size = 46463, upload-time = "2025-10-04T21:56:37.227Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5a/b2c314f655f46c10c204dc0d69e19fadfb1cc4d40ab33f403698a35c3281/propcache-0.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae3adf88a66f5863cf79394bc359da523bb27a2ed6ba9898525a6a02b723bfc5", size = 206980, upload-time = "2025-10-04T21:56:38.828Z" }, + { url = "https://files.pythonhosted.org/packages/7c/4e/f6643ec2cd5527b92c93488f9b67a170494736bb1c5460136399d709ce5a/propcache-0.4.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7f088e21d15b3abdb9047e4b7b7a0acd79bf166893ac2b34a72ab1062feb219e", size = 211385, upload-time = "2025-10-04T21:56:40.2Z" }, + { url = "https://files.pythonhosted.org/packages/71/41/362766a346c3f8d3bbeb7899e1ff40f18844e0fe37e9f6f536553cf6b6be/propcache-0.4.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a4efbaf10793fd574c76a5732c75452f19d93df6e0f758c67dd60552ebd8614b", size = 215315, upload-time = "2025-10-04T21:56:41.574Z" }, + { url = "https://files.pythonhosted.org/packages/ff/98/17385d51816d56fa6acc035d8625fbf833b6a795d7ef7fb37ea3f62db6c9/propcache-0.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:681a168d06284602d56e97f09978057aa88bcc4177352b875b3d781df4efd4cb", size = 201416, upload-time = "2025-10-04T21:56:42.947Z" }, + { url = "https://files.pythonhosted.org/packages/7a/83/801178ca1c29e217564ee507ff2a49d3f24a4dd85c9b9d681fd1d62b15f2/propcache-0.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a7f06f077fc4ef37e8a37ca6bbb491b29e29db9fb28e29cf3896aad10dbd4137", size = 197726, upload-time = "2025-10-04T21:56:44.313Z" }, + { url = "https://files.pythonhosted.org/packages/d2/38/c8743917bca92b7e5474366b6b04c7b3982deac32a0fe4b705f2e92c09bb/propcache-0.4.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:082a643479f49a6778dcd68a80262fc324b14fd8e9b1a5380331fe41adde1738", size = 192819, upload-time = "2025-10-04T21:56:45.702Z" }, + { url = "https://files.pythonhosted.org/packages/0b/74/3de3ef483e8615aaaf62026fcdcb20cbfc4535ea14871b12f72d52c1d6dc/propcache-0.4.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:26692850120241a99bb4a4eec675cd7b4fdc431144f0d15ef69f7f8599f6165f", size = 202492, upload-time = "2025-10-04T21:56:47.388Z" }, + { url = "https://files.pythonhosted.org/packages/46/86/a130dd85199d651a6986ba6bf1ce297b7bbcafc01c8e139e6ba2b8218a20/propcache-0.4.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:33ad7d37b9a386f97582f5d042cc7b8d4b3591bb384cf50866b749a17e4dba90", size = 204106, upload-time = "2025-10-04T21:56:49.139Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f7/44eab58659d71d21995146c94139e63882bac280065b3a9ed10376897bcc/propcache-0.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1e7fd82d4a5b7583588f103b0771e43948532f1292105f13ee6f3b300933c4ca", size = 198043, upload-time = "2025-10-04T21:56:50.561Z" }, + { url = "https://files.pythonhosted.org/packages/96/14/df37be1bf1423d2dda201a4cdb1c5cb44048d34e31a97df227cc25b0a55c/propcache-0.4.0-cp314-cp314-win32.whl", hash = "sha256:213eb0d3bc695a70cffffe11a1c2e1c2698d89ffd8dba35a49bc44a035d45c93", size = 38036, upload-time = "2025-10-04T21:56:51.868Z" }, + { url = "https://files.pythonhosted.org/packages/99/96/9cea65d6c50224737e80c57a3f3db4ca81bc7b1b52bc73346df8c50db400/propcache-0.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:087e2d3d7613e1b59b2ffca0daabd500c1a032d189c65625ee05ea114afcad0b", size = 41156, upload-time = "2025-10-04T21:56:53.242Z" }, + { url = "https://files.pythonhosted.org/packages/52/4d/91523dcbe23cc127b097623a6ba177da51fca6b7c979082aa49745b527b7/propcache-0.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:94b0f7407d18001dbdcbb239512e753b1b36725a6e08a4983be1c948f5435f79", size = 37976, upload-time = "2025-10-04T21:56:54.351Z" }, + { url = "https://files.pythonhosted.org/packages/ec/f7/7118a944cb6cdb548c9333cf311bda120f9793ecca54b2ca4a3f7e58723e/propcache-0.4.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b730048ae8b875e2c0af1a09ca31b303fc7b5ed27652beec03fa22b29545aec9", size = 81270, upload-time = "2025-10-04T21:56:55.516Z" }, + { url = "https://files.pythonhosted.org/packages/ab/f9/04a8bc9977ea201783f3ccb04106f44697f635f70439a208852d4d08554d/propcache-0.4.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f495007ada16a4e16312b502636fafff42a9003adf1d4fb7541e0a0870bc056f", size = 46224, upload-time = "2025-10-04T21:56:56.695Z" }, + { url = "https://files.pythonhosted.org/packages/0f/3d/808b074034156f130a0047304d811a5a5df3bb0976c9adfb9383718fd888/propcache-0.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:659a0ea6d9017558ed7af00fb4028186f64d0ba9adfc70a4d2c85fcd3d026321", size = 48246, upload-time = "2025-10-04T21:56:57.926Z" }, + { url = "https://files.pythonhosted.org/packages/66/eb/e311f3a59ddc93078cb079b12699af9fd844142c4b4d382b386ee071d921/propcache-0.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d74aa60b1ec076d4d5dcde27c9a535fc0ebb12613f599681c438ca3daa68acac", size = 275562, upload-time = "2025-10-04T21:56:59.221Z" }, + { url = "https://files.pythonhosted.org/packages/f4/05/a146094d6a00bb2f2036dd2a2f4c2b2733ff9574b59ce53bd8513edfca5d/propcache-0.4.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34000e31795bdcda9826e0e70e783847a42e3dcd0d6416c5d3cb717905ebaec0", size = 273627, upload-time = "2025-10-04T21:57:00.582Z" }, + { url = "https://files.pythonhosted.org/packages/91/95/a6d138f6e3d5f6c9b34dbd336b964a1293f2f1a79cafbe70ae3403d7cc46/propcache-0.4.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bcb5bfac5b9635e6fc520c8af6efc7a0a56f12a1fe9e9d3eb4328537e316dd6a", size = 279778, upload-time = "2025-10-04T21:57:01.944Z" }, + { url = "https://files.pythonhosted.org/packages/ac/09/19594a20da0519bfa00deef8cf35dda6c9a5b51bba947f366e85ea59b3de/propcache-0.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ea11fceb31fa95b0fa2007037f19e922e2caceb7dc6c6cac4cb56e2d291f1a2", size = 262833, upload-time = "2025-10-04T21:57:03.326Z" }, + { url = "https://files.pythonhosted.org/packages/b5/92/60d2ddc7662f7b2720d3b628ad8ce888015f4ab5c335b7b1b50183194e68/propcache-0.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:cd8684f628fe285ea5c86f88e1c30716239dc9d6ac55e7851a4b7f555b628da3", size = 260456, upload-time = "2025-10-04T21:57:05.159Z" }, + { url = "https://files.pythonhosted.org/packages/6f/e2/4c2e25c77cf43add2e05a86c4fcf51107edc4d92318e5c593bbdc2515d57/propcache-0.4.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:790286d3d542c0ef9f6d0280d1049378e5e776dcba780d169298f664c39394db", size = 247284, upload-time = "2025-10-04T21:57:06.566Z" }, + { url = "https://files.pythonhosted.org/packages/dc/3e/c273ab8edc80683ec8b15b486e95c03096ef875d99e4b0ab0a36c1e42c94/propcache-0.4.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:009093c9b5dbae114a5958e6a649f8a5d94dd6866b0f82b60395eb92c58002d4", size = 262368, upload-time = "2025-10-04T21:57:08.231Z" }, + { url = "https://files.pythonhosted.org/packages/ac/a9/3fa231f65a9f78614c5aafa9cee788d7f55c22187cc2f33e86c7c16d0262/propcache-0.4.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:728d98179e92d77096937fdfecd2c555a3d613abe56c9909165c24196a3b5012", size = 263010, upload-time = "2025-10-04T21:57:09.641Z" }, + { url = "https://files.pythonhosted.org/packages/38/a0/f4f5d368e60c9dc04d3158eaf1ca0ad899b40ac3d29c015bf62735225a6f/propcache-0.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a9725d96a81e17e48a0fe82d0c3de2f5e623d7163fec70a6c7df90753edd1bec", size = 257298, upload-time = "2025-10-04T21:57:11.125Z" }, + { url = "https://files.pythonhosted.org/packages/c7/30/f78d6758dc36a98f1cddc39b3185cefde616cc58248715b7c65495491cb1/propcache-0.4.0-cp314-cp314t-win32.whl", hash = "sha256:0964c55c95625193defeb4fd85f8f28a9a754ed012cab71127d10e3dc66b1373", size = 42484, upload-time = "2025-10-04T21:57:12.652Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ad/de0640e9b56d2caa796c4266d7d1e6cc4544cc327c25b7ced5c59893b625/propcache-0.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:24403152e41abf09488d3ae9c0c3bf7ff93e2fb12b435390718f21810353db28", size = 46229, upload-time = "2025-10-04T21:57:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/da/bf/5aed62dddbf2bbe62a3564677436261909c9dd63a0fa1fb6cf0629daa13c/propcache-0.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0363a696a9f24b37a04ed5e34c2e07ccbe92798c998d37729551120a1bb744c4", size = 40329, upload-time = "2025-10-04T21:57:15.198Z" }, + { url = "https://files.pythonhosted.org/packages/c7/16/794c114f6041bbe2de23eb418ef58a0f45de27224d5540f5dbb266a73d72/propcache-0.4.0-py3-none-any.whl", hash = "sha256:015b2ca2f98ea9e08ac06eecc409d5d988f78c5fd5821b2ad42bc9afcd6b1557", size = 13183, upload-time = "2025-10-04T21:57:38.054Z" }, ] [[package]] @@ -3871,16 +3908,17 @@ wheels = [ [[package]] name = "rich-click" -version = "1.9.1" +version = "1.9.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "rich" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/29/c2/f08b5e7c1a33af8a115be640aa0796ba01c4732696da6d2254391376b314/rich_click-1.9.1.tar.gz", hash = "sha256:4f2620589d7287f86265432e6a909de4f281de909fe68d8c835fbba49265d268", size = 73109, upload-time = "2025-09-20T22:40:35.362Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/4d/e8fcbd785a93dc5d7aef38f8aa4ade1e31b0c820eb2e8ff267056eda70b1/rich_click-1.9.2.tar.gz", hash = "sha256:1c4212f05561be0cac6a9c1743e1ebcd4fe1fb1e311f9f672abfada3be649db6", size = 73533, upload-time = "2025-10-04T21:56:25.36Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/77/e9144dcf68a0b3f3f4386986f97255c3d9f7c659be58bb7a5fe8f26f3efa/rich_click-1.9.1-py3-none-any.whl", hash = "sha256:ea6114a9e081b7d68cc07b315070398f806f01bb0e0c49da56f129e672877817", size = 69759, upload-time = "2025-09-20T22:40:34.099Z" }, + { url = "https://files.pythonhosted.org/packages/a9/27/7a82106d69738aefb81e044d6dd278053c5263581c5e8e5330e1339b8444/rich_click-1.9.2-py3-none-any.whl", hash = "sha256:5079dad67ed7df434a9ec1f20b1d62d831e58c78740026f968ce3d3b861f01a0", size = 70153, upload-time = "2025-10-04T21:56:24.066Z" }, ] [[package]] @@ -4692,6 +4730,7 @@ dev = [ { name = "asyncpg-stubs" }, { name = "auto-pytabs", extra = ["sphinx"] }, { name = "bump-my-version" }, + { name = "covdefaults" }, { name = "coverage" }, { name = "dishka" }, { name = "duckdb" }, @@ -4786,6 +4825,7 @@ lint = [ test = [ { name = "aiohttp" }, { name = "anyio" }, + { name = "covdefaults" }, { name = "coverage" }, { name = "pytest" }, { name = "pytest-asyncio" }, @@ -4868,6 +4908,7 @@ dev = [ { name = "asyncpg-stubs" }, { name = "auto-pytabs", extras = ["sphinx"], specifier = ">=0.5.0" }, { name = "bump-my-version" }, + { name = "covdefaults" }, { name = "coverage", specifier = ">=7.6.1" }, { name = "dishka" }, { name = "duckdb" }, @@ -4956,6 +4997,7 @@ lint = [ test = [ { name = "aiohttp" }, { name = "anyio" }, + { name = "covdefaults" }, { name = "coverage", specifier = ">=7.6.1" }, { name = "pytest", specifier = ">=8.0.0" }, { name = "pytest-asyncio", specifier = ">=0.23.8" }, @@ -5509,101 +5551,121 @@ wheels = [ [[package]] name = "yarl" -version = "1.20.1" +version = "1.21.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "multidict" }, { name = "propcache" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428, upload-time = "2025-06-10T00:46:09.923Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/65/7fed0d774abf47487c64be14e9223749468922817b5e8792b8a64792a1bb/yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4", size = 132910, upload-time = "2025-06-10T00:42:31.108Z" }, - { url = "https://files.pythonhosted.org/packages/8a/7b/988f55a52da99df9e56dc733b8e4e5a6ae2090081dc2754fc8fd34e60aa0/yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a", size = 90644, upload-time = "2025-06-10T00:42:33.851Z" }, - { url = "https://files.pythonhosted.org/packages/f7/de/30d98f03e95d30c7e3cc093759982d038c8833ec2451001d45ef4854edc1/yarl-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed", size = 89322, upload-time = "2025-06-10T00:42:35.688Z" }, - { url = "https://files.pythonhosted.org/packages/e0/7a/f2f314f5ebfe9200724b0b748de2186b927acb334cf964fd312eb86fc286/yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e", size = 323786, upload-time = "2025-06-10T00:42:37.817Z" }, - { url = "https://files.pythonhosted.org/packages/15/3f/718d26f189db96d993d14b984ce91de52e76309d0fd1d4296f34039856aa/yarl-1.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73", size = 319627, upload-time = "2025-06-10T00:42:39.937Z" }, - { url = "https://files.pythonhosted.org/packages/a5/76/8fcfbf5fa2369157b9898962a4a7d96764b287b085b5b3d9ffae69cdefd1/yarl-1.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e", size = 339149, upload-time = "2025-06-10T00:42:42.627Z" }, - { url = "https://files.pythonhosted.org/packages/3c/95/d7fc301cc4661785967acc04f54a4a42d5124905e27db27bb578aac49b5c/yarl-1.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8", size = 333327, upload-time = "2025-06-10T00:42:44.842Z" }, - { url = "https://files.pythonhosted.org/packages/65/94/e21269718349582eee81efc5c1c08ee71c816bfc1585b77d0ec3f58089eb/yarl-1.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23", size = 326054, upload-time = "2025-06-10T00:42:47.149Z" }, - { url = "https://files.pythonhosted.org/packages/32/ae/8616d1f07853704523519f6131d21f092e567c5af93de7e3e94b38d7f065/yarl-1.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70", size = 315035, upload-time = "2025-06-10T00:42:48.852Z" }, - { url = "https://files.pythonhosted.org/packages/48/aa/0ace06280861ef055855333707db5e49c6e3a08840a7ce62682259d0a6c0/yarl-1.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb", size = 338962, upload-time = "2025-06-10T00:42:51.024Z" }, - { url = "https://files.pythonhosted.org/packages/20/52/1e9d0e6916f45a8fb50e6844f01cb34692455f1acd548606cbda8134cd1e/yarl-1.20.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2", size = 335399, upload-time = "2025-06-10T00:42:53.007Z" }, - { url = "https://files.pythonhosted.org/packages/f2/65/60452df742952c630e82f394cd409de10610481d9043aa14c61bf846b7b1/yarl-1.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30", size = 338649, upload-time = "2025-06-10T00:42:54.964Z" }, - { url = "https://files.pythonhosted.org/packages/7b/f5/6cd4ff38dcde57a70f23719a838665ee17079640c77087404c3d34da6727/yarl-1.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309", size = 358563, upload-time = "2025-06-10T00:42:57.28Z" }, - { url = "https://files.pythonhosted.org/packages/d1/90/c42eefd79d0d8222cb3227bdd51b640c0c1d0aa33fe4cc86c36eccba77d3/yarl-1.20.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24", size = 357609, upload-time = "2025-06-10T00:42:59.055Z" }, - { url = "https://files.pythonhosted.org/packages/03/c8/cea6b232cb4617514232e0f8a718153a95b5d82b5290711b201545825532/yarl-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13", size = 350224, upload-time = "2025-06-10T00:43:01.248Z" }, - { url = "https://files.pythonhosted.org/packages/ce/a3/eaa0ab9712f1f3d01faf43cf6f1f7210ce4ea4a7e9b28b489a2261ca8db9/yarl-1.20.1-cp310-cp310-win32.whl", hash = "sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8", size = 81753, upload-time = "2025-06-10T00:43:03.486Z" }, - { url = "https://files.pythonhosted.org/packages/8f/34/e4abde70a9256465fe31c88ed02c3f8502b7b5dead693a4f350a06413f28/yarl-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16", size = 86817, upload-time = "2025-06-10T00:43:05.231Z" }, - { url = "https://files.pythonhosted.org/packages/b1/18/893b50efc2350e47a874c5c2d67e55a0ea5df91186b2a6f5ac52eff887cd/yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e", size = 133833, upload-time = "2025-06-10T00:43:07.393Z" }, - { url = "https://files.pythonhosted.org/packages/89/ed/b8773448030e6fc47fa797f099ab9eab151a43a25717f9ac043844ad5ea3/yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b", size = 91070, upload-time = "2025-06-10T00:43:09.538Z" }, - { url = "https://files.pythonhosted.org/packages/e3/e3/409bd17b1e42619bf69f60e4f031ce1ccb29bd7380117a55529e76933464/yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b", size = 89818, upload-time = "2025-06-10T00:43:11.575Z" }, - { url = "https://files.pythonhosted.org/packages/f8/77/64d8431a4d77c856eb2d82aa3de2ad6741365245a29b3a9543cd598ed8c5/yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4", size = 347003, upload-time = "2025-06-10T00:43:14.088Z" }, - { url = "https://files.pythonhosted.org/packages/8d/d2/0c7e4def093dcef0bd9fa22d4d24b023788b0a33b8d0088b51aa51e21e99/yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1", size = 336537, upload-time = "2025-06-10T00:43:16.431Z" }, - { url = "https://files.pythonhosted.org/packages/f0/f3/fc514f4b2cf02cb59d10cbfe228691d25929ce8f72a38db07d3febc3f706/yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833", size = 362358, upload-time = "2025-06-10T00:43:18.704Z" }, - { url = "https://files.pythonhosted.org/packages/ea/6d/a313ac8d8391381ff9006ac05f1d4331cee3b1efaa833a53d12253733255/yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d", size = 357362, upload-time = "2025-06-10T00:43:20.888Z" }, - { url = "https://files.pythonhosted.org/packages/00/70/8f78a95d6935a70263d46caa3dd18e1f223cf2f2ff2037baa01a22bc5b22/yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8", size = 348979, upload-time = "2025-06-10T00:43:23.169Z" }, - { url = "https://files.pythonhosted.org/packages/cb/05/42773027968968f4f15143553970ee36ead27038d627f457cc44bbbeecf3/yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf", size = 337274, upload-time = "2025-06-10T00:43:27.111Z" }, - { url = "https://files.pythonhosted.org/packages/05/be/665634aa196954156741ea591d2f946f1b78ceee8bb8f28488bf28c0dd62/yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e", size = 363294, upload-time = "2025-06-10T00:43:28.96Z" }, - { url = "https://files.pythonhosted.org/packages/eb/90/73448401d36fa4e210ece5579895731f190d5119c4b66b43b52182e88cd5/yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389", size = 358169, upload-time = "2025-06-10T00:43:30.701Z" }, - { url = "https://files.pythonhosted.org/packages/c3/b0/fce922d46dc1eb43c811f1889f7daa6001b27a4005587e94878570300881/yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f", size = 362776, upload-time = "2025-06-10T00:43:32.51Z" }, - { url = "https://files.pythonhosted.org/packages/f1/0d/b172628fce039dae8977fd22caeff3eeebffd52e86060413f5673767c427/yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845", size = 381341, upload-time = "2025-06-10T00:43:34.543Z" }, - { url = "https://files.pythonhosted.org/packages/6b/9b/5b886d7671f4580209e855974fe1cecec409aa4a89ea58b8f0560dc529b1/yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1", size = 379988, upload-time = "2025-06-10T00:43:36.489Z" }, - { url = "https://files.pythonhosted.org/packages/73/be/75ef5fd0fcd8f083a5d13f78fd3f009528132a1f2a1d7c925c39fa20aa79/yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e", size = 371113, upload-time = "2025-06-10T00:43:38.592Z" }, - { url = "https://files.pythonhosted.org/packages/50/4f/62faab3b479dfdcb741fe9e3f0323e2a7d5cd1ab2edc73221d57ad4834b2/yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773", size = 81485, upload-time = "2025-06-10T00:43:41.038Z" }, - { url = "https://files.pythonhosted.org/packages/f0/09/d9c7942f8f05c32ec72cd5c8e041c8b29b5807328b68b4801ff2511d4d5e/yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e", size = 86686, upload-time = "2025-06-10T00:43:42.692Z" }, - { url = "https://files.pythonhosted.org/packages/5f/9a/cb7fad7d73c69f296eda6815e4a2c7ed53fc70c2f136479a91c8e5fbdb6d/yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9", size = 133667, upload-time = "2025-06-10T00:43:44.369Z" }, - { url = "https://files.pythonhosted.org/packages/67/38/688577a1cb1e656e3971fb66a3492501c5a5df56d99722e57c98249e5b8a/yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a", size = 91025, upload-time = "2025-06-10T00:43:46.295Z" }, - { url = "https://files.pythonhosted.org/packages/50/ec/72991ae51febeb11a42813fc259f0d4c8e0507f2b74b5514618d8b640365/yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2", size = 89709, upload-time = "2025-06-10T00:43:48.22Z" }, - { url = "https://files.pythonhosted.org/packages/99/da/4d798025490e89426e9f976702e5f9482005c548c579bdae792a4c37769e/yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee", size = 352287, upload-time = "2025-06-10T00:43:49.924Z" }, - { url = "https://files.pythonhosted.org/packages/1a/26/54a15c6a567aac1c61b18aa0f4b8aa2e285a52d547d1be8bf48abe2b3991/yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819", size = 345429, upload-time = "2025-06-10T00:43:51.7Z" }, - { url = "https://files.pythonhosted.org/packages/d6/95/9dcf2386cb875b234353b93ec43e40219e14900e046bf6ac118f94b1e353/yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16", size = 365429, upload-time = "2025-06-10T00:43:53.494Z" }, - { url = "https://files.pythonhosted.org/packages/91/b2/33a8750f6a4bc224242a635f5f2cff6d6ad5ba651f6edcccf721992c21a0/yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6", size = 363862, upload-time = "2025-06-10T00:43:55.766Z" }, - { url = "https://files.pythonhosted.org/packages/98/28/3ab7acc5b51f4434b181b0cee8f1f4b77a65919700a355fb3617f9488874/yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd", size = 355616, upload-time = "2025-06-10T00:43:58.056Z" }, - { url = "https://files.pythonhosted.org/packages/36/a3/f666894aa947a371724ec7cd2e5daa78ee8a777b21509b4252dd7bd15e29/yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a", size = 339954, upload-time = "2025-06-10T00:43:59.773Z" }, - { url = "https://files.pythonhosted.org/packages/f1/81/5f466427e09773c04219d3450d7a1256138a010b6c9f0af2d48565e9ad13/yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38", size = 365575, upload-time = "2025-06-10T00:44:02.051Z" }, - { url = "https://files.pythonhosted.org/packages/2e/e3/e4b0ad8403e97e6c9972dd587388940a032f030ebec196ab81a3b8e94d31/yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef", size = 365061, upload-time = "2025-06-10T00:44:04.196Z" }, - { url = "https://files.pythonhosted.org/packages/ac/99/b8a142e79eb86c926f9f06452eb13ecb1bb5713bd01dc0038faf5452e544/yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f", size = 364142, upload-time = "2025-06-10T00:44:06.527Z" }, - { url = "https://files.pythonhosted.org/packages/34/f2/08ed34a4a506d82a1a3e5bab99ccd930a040f9b6449e9fd050320e45845c/yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8", size = 381894, upload-time = "2025-06-10T00:44:08.379Z" }, - { url = "https://files.pythonhosted.org/packages/92/f8/9a3fbf0968eac704f681726eff595dce9b49c8a25cd92bf83df209668285/yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a", size = 383378, upload-time = "2025-06-10T00:44:10.51Z" }, - { url = "https://files.pythonhosted.org/packages/af/85/9363f77bdfa1e4d690957cd39d192c4cacd1c58965df0470a4905253b54f/yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004", size = 374069, upload-time = "2025-06-10T00:44:12.834Z" }, - { url = "https://files.pythonhosted.org/packages/35/99/9918c8739ba271dcd935400cff8b32e3cd319eaf02fcd023d5dcd487a7c8/yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5", size = 81249, upload-time = "2025-06-10T00:44:14.731Z" }, - { url = "https://files.pythonhosted.org/packages/eb/83/5d9092950565481b413b31a23e75dd3418ff0a277d6e0abf3729d4d1ce25/yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698", size = 86710, upload-time = "2025-06-10T00:44:16.716Z" }, - { url = "https://files.pythonhosted.org/packages/8a/e1/2411b6d7f769a07687acee88a062af5833cf1966b7266f3d8dfb3d3dc7d3/yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a", size = 131811, upload-time = "2025-06-10T00:44:18.933Z" }, - { url = "https://files.pythonhosted.org/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3", size = 90078, upload-time = "2025-06-10T00:44:20.635Z" }, - { url = "https://files.pythonhosted.org/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7", size = 88748, upload-time = "2025-06-10T00:44:22.34Z" }, - { url = "https://files.pythonhosted.org/packages/a3/25/35afe384e31115a1a801fbcf84012d7a066d89035befae7c5d4284df1e03/yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691", size = 349595, upload-time = "2025-06-10T00:44:24.314Z" }, - { url = "https://files.pythonhosted.org/packages/28/2d/8aca6cb2cabc8f12efcb82749b9cefecbccfc7b0384e56cd71058ccee433/yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31", size = 342616, upload-time = "2025-06-10T00:44:26.167Z" }, - { url = "https://files.pythonhosted.org/packages/0b/e9/1312633d16b31acf0098d30440ca855e3492d66623dafb8e25b03d00c3da/yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28", size = 361324, upload-time = "2025-06-10T00:44:27.915Z" }, - { url = "https://files.pythonhosted.org/packages/bc/a0/688cc99463f12f7669eec7c8acc71ef56a1521b99eab7cd3abb75af887b0/yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653", size = 359676, upload-time = "2025-06-10T00:44:30.041Z" }, - { url = "https://files.pythonhosted.org/packages/af/44/46407d7f7a56e9a85a4c207724c9f2c545c060380718eea9088f222ba697/yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5", size = 352614, upload-time = "2025-06-10T00:44:32.171Z" }, - { url = "https://files.pythonhosted.org/packages/b1/91/31163295e82b8d5485d31d9cf7754d973d41915cadce070491778d9c9825/yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02", size = 336766, upload-time = "2025-06-10T00:44:34.494Z" }, - { url = "https://files.pythonhosted.org/packages/b4/8e/c41a5bc482121f51c083c4c2bcd16b9e01e1cf8729e380273a952513a21f/yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53", size = 364615, upload-time = "2025-06-10T00:44:36.856Z" }, - { url = "https://files.pythonhosted.org/packages/e3/5b/61a3b054238d33d70ea06ebba7e58597891b71c699e247df35cc984ab393/yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc", size = 360982, upload-time = "2025-06-10T00:44:39.141Z" }, - { url = "https://files.pythonhosted.org/packages/df/a3/6a72fb83f8d478cb201d14927bc8040af901811a88e0ff2da7842dd0ed19/yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04", size = 369792, upload-time = "2025-06-10T00:44:40.934Z" }, - { url = "https://files.pythonhosted.org/packages/7c/af/4cc3c36dfc7c077f8dedb561eb21f69e1e9f2456b91b593882b0b18c19dc/yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4", size = 382049, upload-time = "2025-06-10T00:44:42.854Z" }, - { url = "https://files.pythonhosted.org/packages/19/3a/e54e2c4752160115183a66dc9ee75a153f81f3ab2ba4bf79c3c53b33de34/yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b", size = 384774, upload-time = "2025-06-10T00:44:45.275Z" }, - { url = "https://files.pythonhosted.org/packages/9c/20/200ae86dabfca89060ec6447649f219b4cbd94531e425e50d57e5f5ac330/yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1", size = 374252, upload-time = "2025-06-10T00:44:47.31Z" }, - { url = "https://files.pythonhosted.org/packages/83/75/11ee332f2f516b3d094e89448da73d557687f7d137d5a0f48c40ff211487/yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7", size = 81198, upload-time = "2025-06-10T00:44:49.164Z" }, - { url = "https://files.pythonhosted.org/packages/ba/ba/39b1ecbf51620b40ab402b0fc817f0ff750f6d92712b44689c2c215be89d/yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c", size = 86346, upload-time = "2025-06-10T00:44:51.182Z" }, - { url = "https://files.pythonhosted.org/packages/43/c7/669c52519dca4c95153c8ad96dd123c79f354a376346b198f438e56ffeb4/yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d", size = 138826, upload-time = "2025-06-10T00:44:52.883Z" }, - { url = "https://files.pythonhosted.org/packages/6a/42/fc0053719b44f6ad04a75d7f05e0e9674d45ef62f2d9ad2c1163e5c05827/yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf", size = 93217, upload-time = "2025-06-10T00:44:54.658Z" }, - { url = "https://files.pythonhosted.org/packages/4f/7f/fa59c4c27e2a076bba0d959386e26eba77eb52ea4a0aac48e3515c186b4c/yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3", size = 92700, upload-time = "2025-06-10T00:44:56.784Z" }, - { url = "https://files.pythonhosted.org/packages/2f/d4/062b2f48e7c93481e88eff97a6312dca15ea200e959f23e96d8ab898c5b8/yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d", size = 347644, upload-time = "2025-06-10T00:44:59.071Z" }, - { url = "https://files.pythonhosted.org/packages/89/47/78b7f40d13c8f62b499cc702fdf69e090455518ae544c00a3bf4afc9fc77/yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c", size = 323452, upload-time = "2025-06-10T00:45:01.605Z" }, - { url = "https://files.pythonhosted.org/packages/eb/2b/490d3b2dc66f52987d4ee0d3090a147ea67732ce6b4d61e362c1846d0d32/yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1", size = 346378, upload-time = "2025-06-10T00:45:03.946Z" }, - { url = "https://files.pythonhosted.org/packages/66/ad/775da9c8a94ce925d1537f939a4f17d782efef1f973039d821cbe4bcc211/yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce", size = 353261, upload-time = "2025-06-10T00:45:05.992Z" }, - { url = "https://files.pythonhosted.org/packages/4b/23/0ed0922b47a4f5c6eb9065d5ff1e459747226ddce5c6a4c111e728c9f701/yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3", size = 335987, upload-time = "2025-06-10T00:45:08.227Z" }, - { url = "https://files.pythonhosted.org/packages/3e/49/bc728a7fe7d0e9336e2b78f0958a2d6b288ba89f25a1762407a222bf53c3/yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be", size = 329361, upload-time = "2025-06-10T00:45:10.11Z" }, - { url = "https://files.pythonhosted.org/packages/93/8f/b811b9d1f617c83c907e7082a76e2b92b655400e61730cd61a1f67178393/yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16", size = 346460, upload-time = "2025-06-10T00:45:12.055Z" }, - { url = "https://files.pythonhosted.org/packages/70/fd/af94f04f275f95da2c3b8b5e1d49e3e79f1ed8b6ceb0f1664cbd902773ff/yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513", size = 334486, upload-time = "2025-06-10T00:45:13.995Z" }, - { url = "https://files.pythonhosted.org/packages/84/65/04c62e82704e7dd0a9b3f61dbaa8447f8507655fd16c51da0637b39b2910/yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f", size = 342219, upload-time = "2025-06-10T00:45:16.479Z" }, - { url = "https://files.pythonhosted.org/packages/91/95/459ca62eb958381b342d94ab9a4b6aec1ddec1f7057c487e926f03c06d30/yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390", size = 350693, upload-time = "2025-06-10T00:45:18.399Z" }, - { url = "https://files.pythonhosted.org/packages/a6/00/d393e82dd955ad20617abc546a8f1aee40534d599ff555ea053d0ec9bf03/yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458", size = 355803, upload-time = "2025-06-10T00:45:20.677Z" }, - { url = "https://files.pythonhosted.org/packages/9e/ed/c5fb04869b99b717985e244fd93029c7a8e8febdfcffa06093e32d7d44e7/yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e", size = 341709, upload-time = "2025-06-10T00:45:23.221Z" }, - { url = "https://files.pythonhosted.org/packages/24/fd/725b8e73ac2a50e78a4534ac43c6addf5c1c2d65380dd48a9169cc6739a9/yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d", size = 86591, upload-time = "2025-06-10T00:45:25.793Z" }, - { url = "https://files.pythonhosted.org/packages/94/c3/b2e9f38bc3e11191981d57ea08cab2166e74ea770024a646617c9cddd9f6/yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f", size = 93003, upload-time = "2025-06-10T00:45:27.752Z" }, - { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542, upload-time = "2025-06-10T00:46:07.521Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/2d/d1/a1ee68b513f31c6de9af56cdfafebb4939bf0d6528945a862e101699ae98/yarl-1.21.0.tar.gz", hash = "sha256:866c17223f7d734377a260a2800e14791cb5e55ec252de624e053a0b36b8568a", size = 187069, upload-time = "2025-10-05T17:22:46.946Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/78/5abe0da65addf428f26487f4f21496b04404637e6b1f24d019124bd4d066/yarl-1.21.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7d271fed8a4b46723db5001619c36192d94a3bd49d76ef186f13abb6897ff8e5", size = 140474, upload-time = "2025-10-05T17:18:40.239Z" }, + { url = "https://files.pythonhosted.org/packages/d3/e3/cea415910fae9bcafb1030537d30051bae7f5e9f0bd14aefdb8509bde8b0/yarl-1.21.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1bff86850033508af0a7f9973ced23a16de7ba4ce30521080e2330475b8711b5", size = 93456, upload-time = "2025-10-05T17:18:43.272Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e9/40f9f5c75a946a96d929b9ae4605cf265112d158e983dcece484106a800a/yarl-1.21.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:88ff0c0bea02ce78af8a91b173fb43aad5f1945221182f77ba7816fd01bcbc4c", size = 94357, upload-time = "2025-10-05T17:18:45.148Z" }, + { url = "https://files.pythonhosted.org/packages/0c/10/34ebdd10fa6a6ff66b03746d0b8807b868d3121843886ae3a813718a0575/yarl-1.21.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8da09e318a2916da7110d1147355056ee89d61b4ded49ba3ada717517f2fc71", size = 347504, upload-time = "2025-10-05T17:18:47.066Z" }, + { url = "https://files.pythonhosted.org/packages/96/bb/0a8496894b1e18709e1c81430cab9ca020f32b439434ac2800a64a755062/yarl-1.21.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0a9454d4c513a3aa2fd87471126e0d32b01f1bf58d49309a84431521488b30c4", size = 319542, upload-time = "2025-10-05T17:18:49.217Z" }, + { url = "https://files.pythonhosted.org/packages/ba/44/d827b88a12ef4ef882a8042c27b7eaff690128ab0338194ed42996cf5eb1/yarl-1.21.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:92a719bb1118f302f6fc3c7638e78e152de8bf279c0200325af831afa1b60f1a", size = 363844, upload-time = "2025-10-05T17:18:51.224Z" }, + { url = "https://files.pythonhosted.org/packages/53/da/e80beded6fbe10010c20575e85ad07fa3f396b91a9f8cdbf05bb6374be65/yarl-1.21.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:019c2798df9d74fe8fb9cc916702966dad7e2e3eef66b4c19f8084ba5e0b6ecd", size = 371204, upload-time = "2025-10-05T17:18:53.352Z" }, + { url = "https://files.pythonhosted.org/packages/b6/54/c85e753606df4c6c34ac5260d4d36e46c25c4634d70a6afb293b51d0d070/yarl-1.21.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5c35188fac7e448b52eb3916365fe5f59eb27fecec21ba757eea4f650584ca5", size = 346915, upload-time = "2025-10-05T17:18:55.874Z" }, + { url = "https://files.pythonhosted.org/packages/88/2c/c4e462f66e30e38464272a72590b18932b34863d4437d77da216714f5d5e/yarl-1.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8bfdb95a85404a943197264461b904a2e9e228fd28cb86e4e57321f5b4d5be07", size = 341987, upload-time = "2025-10-05T17:18:58.054Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3d/d9e9124b5d220d25848c5f326ff656279dbe8cb6fc8a78ec0d976fd755e4/yarl-1.21.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:ad6775f8bd57e2c4068246e03c00e212e01b27ea0e96a4b4f17f9d45d80cd5d8", size = 335599, upload-time = "2025-10-05T17:19:00.121Z" }, + { url = "https://files.pythonhosted.org/packages/87/73/2f70879e53fc3f297e50819bf87d128daea2edcdcfaabc7efeb89756a6a5/yarl-1.21.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c7fab0120e4ea5a2c170382bd27345b2b56e22b6270b40e4231a68f090ce17ed", size = 359028, upload-time = "2025-10-05T17:19:02.605Z" }, + { url = "https://files.pythonhosted.org/packages/89/49/e252940167fdcfd398f358c7a37228f845bf7038c460ba94a31aeed0b53c/yarl-1.21.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0b16c889a7168ecf7242946dec013c9fb82ade70ab8e6b5d3290383390083a2b", size = 357766, upload-time = "2025-10-05T17:19:04.565Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c6/13f7060718079576093069a5ccd3c0d5c67d8bea91b02cdafb8fe6254339/yarl-1.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0416fde6dc89866f4ff494a0ffcc4b2da984cf61aaa279c14a53495e8520c809", size = 351181, upload-time = "2025-10-05T17:19:06.522Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a3/c435560bf9152c32bfc3b9f42de2051d6ef6058343415a48d0f0ecb2acc0/yarl-1.21.0-cp310-cp310-win32.whl", hash = "sha256:ac487adb2e838d03aed0c1a9df4ba348ca2c215bf2afa2f6e1d9449c7029971f", size = 82075, upload-time = "2025-10-05T17:19:08.161Z" }, + { url = "https://files.pythonhosted.org/packages/bf/8f/f08048a1548170fab70a004dc1a4485541dbfd7d244d96a6270aaef17fea/yarl-1.21.0-cp310-cp310-win_amd64.whl", hash = "sha256:52a8b7541c5d8240ae32d12014f8448e29e1ae794f9443ea020b926cff8691e1", size = 86811, upload-time = "2025-10-05T17:19:10.061Z" }, + { url = "https://files.pythonhosted.org/packages/7e/d6/bff826fcd96e888fe9b80b5290edacd90f341a251edf23b1f93e57f13e01/yarl-1.21.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:56ead8d62b346c1ec67a6e8b2f66885180ea5bec05821d309ac1cb99ff4aacf5", size = 141556, upload-time = "2025-10-05T17:19:12.112Z" }, + { url = "https://files.pythonhosted.org/packages/c0/22/27ffacf5480948b013118d4f3c4f1f37b97badec1849330f14f1913c30e3/yarl-1.21.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:140402fef1f482840fcd4d2ee9bfd07f08bfb2c80dd215220bd47f6f3566b882", size = 93987, upload-time = "2025-10-05T17:19:13.905Z" }, + { url = "https://files.pythonhosted.org/packages/c0/60/8d69774dbce36d29f14b73780ce8a452793f8e72c46a23148324a31eb1a7/yarl-1.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2a05a5e018de23c4d2d75c8fbd8b58aba5199f752326f60a22aa37ef28d987bd", size = 94918, upload-time = "2025-10-05T17:19:15.915Z" }, + { url = "https://files.pythonhosted.org/packages/c0/9e/75bce89dae5bb42710252bab56d2b037e6bd208452b5f953cfc14739f60a/yarl-1.21.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60dcb45a3d762460ac5014755c190db36acf127f68d68643cde7d6d7ce0e5627", size = 368353, upload-time = "2025-10-05T17:19:17.927Z" }, + { url = "https://files.pythonhosted.org/packages/95/b6/5b62976cc105900fe2073208506ed994243d47f103b4fccd336f205c79d5/yarl-1.21.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2227fcc88bebdc90ed87d924bdf8a76a730fc91796641e41ca747aabd13a5074", size = 336558, upload-time = "2025-10-05T17:19:19.988Z" }, + { url = "https://files.pythonhosted.org/packages/7b/4d/244c3f5343f7f7d76e74f524e42f7d635336a2122c8167acaf44090e9b2e/yarl-1.21.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:20b2dca6588f65b5def8e8eae4a087d504eacf34b5b435c021cc233ce82f6c15", size = 388885, upload-time = "2025-10-05T17:19:21.7Z" }, + { url = "https://files.pythonhosted.org/packages/29/cf/08fb2d90646efc2b7194d1301c0bbeee17958d463d2c46e8261aa2c916b8/yarl-1.21.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c6dfa317e4b87052589253f50119211b801146ff7214b8684830e9084fa6eb0a", size = 392851, upload-time = "2025-10-05T17:19:23.976Z" }, + { url = "https://files.pythonhosted.org/packages/a3/9c/97ae635e6122edebab7e0c01be4df974b4e536d2bacfc5dada751eedb21c/yarl-1.21.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d957259a15e45e5fa5d51ce59ab7519cff8d3de0109d404627276ec68412c718", size = 365804, upload-time = "2025-10-05T17:19:26.086Z" }, + { url = "https://files.pythonhosted.org/packages/5f/c2/95c8dd8e5cc9064a3bab51387030a8884511e585d909e9f05e0af852d7c6/yarl-1.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4d5af10c9f580002c0ea6c8f345c8cadb2e0c53dce77d3f2639b9e31e5f24d3d", size = 363561, upload-time = "2025-10-05T17:19:28.201Z" }, + { url = "https://files.pythonhosted.org/packages/bb/32/dd78e626abc1cb60103594f44a9e612c2c62c444164ccaaf78a8c4db7f7a/yarl-1.21.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ececd833be7fd8390371c082103916702170e81a1b22beb989452f934def78d6", size = 358036, upload-time = "2025-10-05T17:19:30.031Z" }, + { url = "https://files.pythonhosted.org/packages/35/e6/3d58937bf031b6c952568c4978c6b4dca47ccd5e891a1fb4961e973731ac/yarl-1.21.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6614325ef69d8a53c731ed5e4bd55449ffc5fe86ad652789c0afc853099662ad", size = 385291, upload-time = "2025-10-05T17:19:32.247Z" }, + { url = "https://files.pythonhosted.org/packages/ea/50/1073a9969b40426520a2418b2701f164c53eeac69449b73aa9e6e4810d40/yarl-1.21.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:7331a7d2683e644b7830c924ac634fa3ec52257f5098f6415d8ad765d6bc29a8", size = 381048, upload-time = "2025-10-05T17:19:34.344Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a5/542a4529df6caea8c5e21daea7724b44e85cfa1e9e0e0df7835709fa9eed/yarl-1.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ee77d3c82576baae66a3281c9a6431fc84281443a7e36a8490a45b3dbbb60446", size = 370898, upload-time = "2025-10-05T17:19:36.268Z" }, + { url = "https://files.pythonhosted.org/packages/02/a0/d9ce91b514f5a24dea05f1b7c0df29f0c15d5abee18b9107f0ab39f72ffc/yarl-1.21.0-cp311-cp311-win32.whl", hash = "sha256:6378871775e0feb225693cbdad3d997327af0ab4c7e39d93849008c73b867134", size = 81763, upload-time = "2025-10-05T17:19:38.168Z" }, + { url = "https://files.pythonhosted.org/packages/aa/21/9cd2b53cc23f8d2e8c08d0f405fa4838ecfea56114b603b86b5afc023d38/yarl-1.21.0-cp311-cp311-win_amd64.whl", hash = "sha256:e6df05c2234786b15632cd154d60122c302fd860d89c3ee47c166ad92eb6ae55", size = 86815, upload-time = "2025-10-05T17:19:40.096Z" }, + { url = "https://files.pythonhosted.org/packages/10/a4/c87031092c8e4f488072d86d043b82b01f045866929eaf0b9c645cb9d756/yarl-1.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5110ebfe3cbf892b41590fcf4aa70a17ac0a5e9a73b4a8945010bdb970ff1b93", size = 141961, upload-time = "2025-10-05T17:19:42.532Z" }, + { url = "https://files.pythonhosted.org/packages/f2/03/b9265e1b7a8305bbc45fb6ed23dc78b6a6dfa31b9a3c6e850f47ee91c98d/yarl-1.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:973d630c00bbaf07045870d331c8596bf4fa07aa8eb10d69a02c542af714f128", size = 94294, upload-time = "2025-10-05T17:19:44.494Z" }, + { url = "https://files.pythonhosted.org/packages/dd/bb/bc7e99183403b8db8ddf4b3c5fe256f0e4ae0306f7c66d1539d754f03f3f/yarl-1.21.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aef7e9b60b371f4d3c3ea80c0ef2d841623dd64aad7718ab815a3205bd4bdf08", size = 94866, upload-time = "2025-10-05T17:19:46.431Z" }, + { url = "https://files.pythonhosted.org/packages/15/c1/ecd713a5d571fd27e42962b9e2d199d5db27bc786d8732717d3860104ef0/yarl-1.21.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:430e162d961af58f3dcac58aed038ba974ec7a73803ac6545db2338fbd0f4ed3", size = 372914, upload-time = "2025-10-05T17:19:48.489Z" }, + { url = "https://files.pythonhosted.org/packages/7e/94/b770cfe368b523a56b6bafbce705584f7fb42ee249a6d266b31f3d3a9560/yarl-1.21.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1754b3380ffef931b8eae3bbe6fc0b249db56294ffeb6e6124c2d031a82a3a92", size = 345783, upload-time = "2025-10-05T17:19:50.21Z" }, + { url = "https://files.pythonhosted.org/packages/5e/d3/46d217f9d743a5678eb52770875b521e87e9666fcc8a0ad1913e3b1e6cf2/yarl-1.21.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e7a8f70c7c283d0b4af90314ff8d969c9ab2c7ee522bfb612f42c542935f6e11", size = 386670, upload-time = "2025-10-05T17:19:52.058Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6d/140b358b50d65342f634a1577cf867fd9ac80147b16f7d17b14d734fa956/yarl-1.21.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd6ca6e66b4fee5e879207854f125b94f6ad77e98ddae4d7778d2e96be94ede4", size = 396495, upload-time = "2025-10-05T17:19:54.104Z" }, + { url = "https://files.pythonhosted.org/packages/50/00/8377df3c132041bc580235ad465f20a73f026210b0f0582dddb41125a2d3/yarl-1.21.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:45f17adf1b8bc56becb1bc38f293b1714866786c9d79e245fb3d3731788622a6", size = 377225, upload-time = "2025-10-05T17:19:56.165Z" }, + { url = "https://files.pythonhosted.org/packages/48/bb/ae6a99dbcf2f5db5484bcb61017bd8d59c8f9a6e81c3540a267f2e17355d/yarl-1.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4ee80f79c928ce7c18cf3ad18a5da7f3f0f1b08923e08d87143d628a6d5d2dba", size = 365752, upload-time = "2025-10-05T17:19:58.314Z" }, + { url = "https://files.pythonhosted.org/packages/5f/38/ed463a729e026a5608e443e7b7d9789c480d41fec967962ff9dcf9a77873/yarl-1.21.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:190356a39fed15109ab95600f8ff59c1a0665625f4cfe910388c82b965edaf87", size = 365479, upload-time = "2025-10-05T17:20:00.287Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ce/95e2b001c0672edfe68c7c8a59e95c3948c60ead779fb8cc384540cb4256/yarl-1.21.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:701cd0ee20fe9087c21229db579f2222a75c229b44840a7df7b2d795522068c3", size = 382289, upload-time = "2025-10-05T17:20:02.291Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e4/2cc150bccffa71f52b8e8354cc77ab8d653fdcf92ea729d428e005cf2f54/yarl-1.21.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:66248832212957d8bad28e8d9d307be1d987b94ffaf7e7cca658a349d52d3572", size = 383769, upload-time = "2025-10-05T17:20:04.165Z" }, + { url = "https://files.pythonhosted.org/packages/c3/00/c466a2e52d034f3e4f9b3f7090e345393ff76b34bda4559991e65d064775/yarl-1.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0cc3eeea8f527119aac1b0c874bbb8092675da85fd6d9d91946cf7be7d59477b", size = 374178, upload-time = "2025-10-05T17:20:06.335Z" }, + { url = "https://files.pythonhosted.org/packages/22/5d/68beb3107d2797e9e1be16de08f04454f846e6b8532adb28543a422375b2/yarl-1.21.0-cp312-cp312-win32.whl", hash = "sha256:0d37bf6f601c714b536159715d9ec6e69bf8a94dc593abe54c1b43ac339eb5e7", size = 81545, upload-time = "2025-10-05T17:20:08.572Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/c3c5311cb48ef949f4d00802082d42dd43e113f32f98742113c75f147d75/yarl-1.21.0-cp312-cp312-win_amd64.whl", hash = "sha256:7da21f0d9bebdc8ac1dde69b3c0951b339984883e2a751790f0f72cbfd1dd007", size = 87169, upload-time = "2025-10-05T17:20:10.225Z" }, + { url = "https://files.pythonhosted.org/packages/9d/b4/4fad5c66ad70f0e5d3b725c7ce72931d249891a4bec372c9181f9ba65f78/yarl-1.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bc8a06f7bc45219b2c191d68e779e6b3f62e32d09d2f8cf7b381ba1dcb7a68f9", size = 139931, upload-time = "2025-10-05T17:20:12.137Z" }, + { url = "https://files.pythonhosted.org/packages/b9/1a/684fcb0b57426b2f121d084a66cab6a3d8b60cf650d24bd0f18335111f11/yarl-1.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01ef0d7f1dd60d241529dc79a3fa647451056394f9a5ed05fbceeb5009de6122", size = 93384, upload-time = "2025-10-05T17:20:14.166Z" }, + { url = "https://files.pythonhosted.org/packages/ed/05/02f18b6b3ba344026d57796594a5630fc05816581c0d4aebfa00c26c6526/yarl-1.21.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:37b5e7bba1f6df45058cff626c83a0e8a1259363095e768046a3da40b24e9c4f", size = 93779, upload-time = "2025-10-05T17:20:16.062Z" }, + { url = "https://files.pythonhosted.org/packages/f0/08/c9af7d6535959ade95fcb7692bedb8788b8f802bb52996476f7c93949c29/yarl-1.21.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:221aa7c16055e8b9f2eba718cbbf10f174e47f02e659156804d9679654c5cbb0", size = 373176, upload-time = "2025-10-05T17:20:18.171Z" }, + { url = "https://files.pythonhosted.org/packages/29/82/bc05acdd003e7676b0f668fd06c41091b3656a46747e3d5ef2db56b961fd/yarl-1.21.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:863d7401d3a109f75c7a5ca0e33e8fb7704a61007f4bda03e08e05f3bf1af40f", size = 342306, upload-time = "2025-10-05T17:20:20.435Z" }, + { url = "https://files.pythonhosted.org/packages/62/98/e2eafd1596fc48cdc1e3204a6d25d13d0b927339145c46e4d0a1e55d8e1f/yarl-1.21.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:692603a8f82e7baa86bb3921d5002b711788cec547b626030f1f6cf017290ab7", size = 386976, upload-time = "2025-10-05T17:20:22.669Z" }, + { url = "https://files.pythonhosted.org/packages/d2/24/65726cc4a131442b4af140a94b12429ab5a39832e7abd58de189ef77764a/yarl-1.21.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c48477c6ff32032624aa122323adc343055bb7e347e01146a86e652b06281731", size = 397645, upload-time = "2025-10-05T17:20:24.873Z" }, + { url = "https://files.pythonhosted.org/packages/99/b9/deded0027a1bb174aeeec914899773a2db1ef83088cb25c435ab9b57e9ac/yarl-1.21.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1107b93c32cf7d7e2ece9bbb1b1820ecb923cfea24c8aa599a309434ed37d707", size = 377016, upload-time = "2025-10-05T17:20:26.699Z" }, + { url = "https://files.pythonhosted.org/packages/4a/c2/2bae5bd4e39c503738e8058659d68339f619d443129ea2d5375790a2b783/yarl-1.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0e485c4f9f5b5b9fc10b4bb0ba5baf145ed0a702756da126c9f62f8a89b391a8", size = 365316, upload-time = "2025-10-05T17:20:29.103Z" }, + { url = "https://files.pythonhosted.org/packages/14/d7/c20dc74713bccf5998babde260487d21b61497a9753200fdce887a715e24/yarl-1.21.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ec1f6129c1175d15da7b7c13ae5d4226acf6b5fe362c5b01ac9787fa88c64781", size = 361295, upload-time = "2025-10-05T17:20:31.296Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6f/fc3eee2f52f303f4b93b3d9b16842dd218bfb37b931f20c1e7b529f15395/yarl-1.21.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1e0b01fa225ec12e54c73be383326ae2a4a59a4a465a0e6cac679f314ed85d1f", size = 382546, upload-time = "2025-10-05T17:20:33.436Z" }, + { url = "https://files.pythonhosted.org/packages/51/37/80baf3548b6e910ba278ba0255177d091f0af66afd738bbd88857b3ef552/yarl-1.21.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7d8917677a64304db00ec46629aff335c935c788a10a164b29464b7e2d707463", size = 385276, upload-time = "2025-10-05T17:20:35.77Z" }, + { url = "https://files.pythonhosted.org/packages/39/8a/d1302e6e4454eabf1aa4034b2907439a43f7b5d5159b8f0237f54e5e0c86/yarl-1.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b846a17f810708f1beff6ad088121fd35334729df3e520412163c74ef49433f7", size = 374395, upload-time = "2025-10-05T17:20:37.555Z" }, + { url = "https://files.pythonhosted.org/packages/58/8b/a6fa48483fc60233e7a4225b80a0610ebed8dfd41404f1e5a4e6694654bd/yarl-1.21.0-cp313-cp313-win32.whl", hash = "sha256:1743d35529a8b9b2b6a9e5f00076c2c146726453051621b739b081dda382ee70", size = 81513, upload-time = "2025-10-05T17:20:39.282Z" }, + { url = "https://files.pythonhosted.org/packages/1c/50/9e921fee3f29fe75be1c20d7344dd943bad642430adee4eabb230dfd7c55/yarl-1.21.0-cp313-cp313-win_amd64.whl", hash = "sha256:13c9b91e2e1224a8d33addc1bd58bb097396519c4c49524843947776b8dd45da", size = 86922, upload-time = "2025-10-05T17:20:41.371Z" }, + { url = "https://files.pythonhosted.org/packages/5a/ff/d9af15a1e4c294c7a9b2a5063dbe866b6cda7236de609609b164a335e327/yarl-1.21.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c2c4da0802f6897f7fb766c4f0e7f55c96b103981265fcf12b648d088bee3744", size = 146171, upload-time = "2025-10-05T17:20:43.399Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f6/9c648fd2518821a0e8c80b9a96888e4d7ebe9e396d2aa4f5a804bd7e3903/yarl-1.21.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c115756cb1cad49862aa0c2687922ed10da6be7689cf35e3ab602c4a6da2d8fb", size = 95926, upload-time = "2025-10-05T17:20:45.274Z" }, + { url = "https://files.pythonhosted.org/packages/a2/72/528606b2d707e8d59ef905cc19a08c1265da4a535a99dbe813ccb56bed45/yarl-1.21.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfcca979b72f240bac7c73564026eae4c97639151a415e6ced6392d120022d2d", size = 97272, upload-time = "2025-10-05T17:20:46.947Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0f/441f882bda86de80cbd8c302b8f9bb1c449b0f4fc1ff7e9ea9e8161ed99e/yarl-1.21.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a999c5c50af0e564cab5bbbbbee97d494eb0e09f99481385108ddfd90049b3fe", size = 361934, upload-time = "2025-10-05T17:20:48.685Z" }, + { url = "https://files.pythonhosted.org/packages/38/b7/1af70aec3f4f0b60d3e94918adc1c38319120768e1b106b5c049bfc40838/yarl-1.21.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7d5d8eeb1051fac562d80aad7b6b496e2901f41fc2b0988c61016a1426996f66", size = 323909, upload-time = "2025-10-05T17:20:50.636Z" }, + { url = "https://files.pythonhosted.org/packages/65/89/b5d64607085bef4ef5319c1604e5e1f64604d7a4ed4efdfa12448dac5f37/yarl-1.21.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:61bf6233d04ccba7906f5261ff3628fa97a68fc526cda3d9dd092d2f49926933", size = 361785, upload-time = "2025-10-05T17:20:52.959Z" }, + { url = "https://files.pythonhosted.org/packages/56/b9/2544b2a6184b5e02736870c5919243da45cd105efd6285f2c7750cc0ea68/yarl-1.21.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:63157d66cf7682dec8b3117491cb87a5d8e1cd56df59156d5553ab9721895d19", size = 372589, upload-time = "2025-10-05T17:20:54.916Z" }, + { url = "https://files.pythonhosted.org/packages/ea/33/0cac77694b844e0e00aa2a5be679e47b62213d3ea2e6fe84396cb04183a1/yarl-1.21.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:45aa7711e1933bac1679f9534f112767f1fe64c97a8576294b760015d0fb65e7", size = 341082, upload-time = "2025-10-05T17:20:56.759Z" }, + { url = "https://files.pythonhosted.org/packages/41/e5/8527ca2fee44a519f659cb1e71182da8f4739032f26acd3cf1567afed081/yarl-1.21.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:74b2e94d3e410ed49c7a4cb2c3a5089a6632f7ab68e49bb612b972577e26e771", size = 346739, upload-time = "2025-10-05T17:20:58.647Z" }, + { url = "https://files.pythonhosted.org/packages/c5/98/d31449d293c4a400c5eea2835d38f3b86ab1a7eae73750b5e011c4faf0eb/yarl-1.21.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:178860382595f3b1fab2596b19570adc495c6211eee8b10a4112ce96342f6515", size = 334808, upload-time = "2025-10-05T17:21:00.607Z" }, + { url = "https://files.pythonhosted.org/packages/39/5f/605873225112f3bfd7b924fc00f9ac8f2d4a6b9e0a9abbca90ef566ffd92/yarl-1.21.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f0a6cd797394761692cc6f33b10f2ea46789ac0b7fba82b6df737f51e1297122", size = 350964, upload-time = "2025-10-05T17:21:02.694Z" }, + { url = "https://files.pythonhosted.org/packages/65/17/f40496a4bd7fb2047caaa4c2f3c573cf4ad1d1ab02549584a7930bd0ecea/yarl-1.21.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:8910f022242c0a15f6d77d781c6ba16bb88d9fed3bff8964de652ee2580029ac", size = 356007, upload-time = "2025-10-05T17:21:04.493Z" }, + { url = "https://files.pythonhosted.org/packages/ce/56/8965a790ad8007c6fa59d7a769e18a6b4451944c38e953f8acd620c98747/yarl-1.21.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1b5d29c1a86cc63e55f69253b8c817091884c4e1b79ee762a8643de834e70a64", size = 342913, upload-time = "2025-10-05T17:21:06.673Z" }, + { url = "https://files.pythonhosted.org/packages/2c/25/3e45f26e9204e4ad89c91d89b1e946a12bc79b0e4f84e39916a28058463e/yarl-1.21.0-cp313-cp313t-win32.whl", hash = "sha256:99febd7a9efab236d798d72ca878ae0d92fffadcc2e472636d6e093ce2677980", size = 86902, upload-time = "2025-10-05T17:21:08.69Z" }, + { url = "https://files.pythonhosted.org/packages/48/0f/4496e5506abf690100fc5d37f31c3216e5c1c5fc2a228b08d39e42d174e5/yarl-1.21.0-cp313-cp313t-win_amd64.whl", hash = "sha256:e00aaf1574075439ccb0b827ca822c5a97c0103351ead292c42a9f17bd2eae0a", size = 93676, upload-time = "2025-10-05T17:21:10.296Z" }, + { url = "https://files.pythonhosted.org/packages/cf/b9/01fc864ac6cc9bb1ae14ab852a7530d762254a27fe6c2c29e0c9c8dc6393/yarl-1.21.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9eaf0f28ed19919bdeb02cfa541daaee8a05c070227eaab8d9732f1eebfe2869", size = 140474, upload-time = "2025-10-05T17:21:11.976Z" }, + { url = "https://files.pythonhosted.org/packages/99/7a/142a173f148ea8a1b36ae498a961c0be26986a5fab86908210d0507e75a3/yarl-1.21.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:de9f7a51f828f73ea0ca2e856a7cac8766752f336241abdb6c5f45f402dd59ea", size = 93462, upload-time = "2025-10-05T17:21:13.628Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ba/71eeca357170115c28315ec1b1c015b44b10cadd801d28f5b25b754853f0/yarl-1.21.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b68c0c9deb2fcd183376600df99e88032a9c192d352b0f781e130b09220ef1cf", size = 94241, upload-time = "2025-10-05T17:21:15.552Z" }, + { url = "https://files.pythonhosted.org/packages/e6/7d/cdf516659244105b6eb78ee316b182f47d92ebdc33ce2b9cfe42e12c3cc7/yarl-1.21.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ee84156656d4a09010c280f41011f0a317c62e745f7a2cfafabd8035823fe2d", size = 372050, upload-time = "2025-10-05T17:21:17.696Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a7/2b8401a64d91828f6e18bbdec8beb761a221d7795f94e7a1b3083af5d001/yarl-1.21.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:780313d2a1877adef0e3839ef9596ad53ab640715e7f453e7304c121cd7f262d", size = 338623, upload-time = "2025-10-05T17:21:19.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/c9/7ab9b63e3ca31a8b104d183774de3eccfe1da9889d5fbf11aa7d6c90f7d3/yarl-1.21.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f9dae6ef584d3241571674ed7bcd1a28b003a5f0c3a6ca561ab42e5ce0c482e3", size = 387105, upload-time = "2025-10-05T17:21:21.83Z" }, + { url = "https://files.pythonhosted.org/packages/e4/29/0ae170810edb493591b5eced0b0a214e62df81ff9767282fd386282a9e12/yarl-1.21.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0a94664fe3c6dd44c36e875af0f338769dc9f80a1ccd58f53cf5f5b8341e8627", size = 394044, upload-time = "2025-10-05T17:21:23.775Z" }, + { url = "https://files.pythonhosted.org/packages/8a/d2/c134f3acd2797dacd631851d7b868fc0c5e2d6b8ed8879bcf42696383504/yarl-1.21.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:056fc431f10ae35aa2375c9de2b68176b34f54fb7de8bc2e830564e2a3d29efa", size = 372610, upload-time = "2025-10-05T17:21:25.998Z" }, + { url = "https://files.pythonhosted.org/packages/46/21/949def9a5369ba8a653a30de07b01be5813db1fb0b5e0f1c34606a7f84cc/yarl-1.21.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4fcce63c1117ef0630a92a0bda3028a96dc17feed2c78c713de4c963d13d1881", size = 364605, upload-time = "2025-10-05T17:21:27.859Z" }, + { url = "https://files.pythonhosted.org/packages/df/2e/9b1971c584f5ba0fde7f40b74f8d1b54e95c46fa39765189a1d696beb9af/yarl-1.21.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1a0ba7cd4eabb7433e69737f33333d9e79d8ab6dbaa2f4d7313ad6611200cc65", size = 355323, upload-time = "2025-10-05T17:21:29.856Z" }, + { url = "https://files.pythonhosted.org/packages/d5/11/4271403204e6f0cb46f63de249d1f552d23e26ad04a16e7cab686ab46256/yarl-1.21.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:3b539230fd64f283594a56633a9751d299cde5ab9c2791452ccb47a865842fa8", size = 381423, upload-time = "2025-10-05T17:21:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/72/92/7d00ecf66b56ae1509a1a82fdf671a0c60c50182092a5e08af638b896237/yarl-1.21.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:10580c7d9b50c883b93cc0ab5c91df5cc1e5b18713736471d622776b01c36810", size = 383767, upload-time = "2025-10-05T17:21:34.499Z" }, + { url = "https://files.pythonhosted.org/packages/d6/9e/c5ec1f51be336bdaac908219255318cb86074f1c403a72fd47ec0209b9b5/yarl-1.21.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5c0123db2d86d169554d5fb19421e8e455efcfe2e8e254328b85c77e712ab506", size = 370765, upload-time = "2025-10-05T17:21:36.395Z" }, + { url = "https://files.pythonhosted.org/packages/0b/4b/e3657b7069fb1e24f014e4351b311e522ae7a58afc76369e0f31cf65e9d8/yarl-1.21.0-cp314-cp314-win32.whl", hash = "sha256:5e7d24e9c3b638f046fcd9a5374818257a8c6d1c3fc7542887521b81a970fbc2", size = 82954, upload-time = "2025-10-05T17:21:38.414Z" }, + { url = "https://files.pythonhosted.org/packages/7e/2b/6447cbd3d43acc2ce2b6898fdaba7d517ee6269f5a278b5d09a1530cb645/yarl-1.21.0-cp314-cp314-win_amd64.whl", hash = "sha256:884d4f3509dfc810299d14faed24c0fbcac82ae2a9737b0cb1d8f7a5e8a291f8", size = 88253, upload-time = "2025-10-05T17:21:40.575Z" }, + { url = "https://files.pythonhosted.org/packages/2d/40/bd9d7894d4d14b6fc53e717536dd5e77e68fe278ce20ea6a04aa16dd413c/yarl-1.21.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3cbae81bff4014ca7745fa11f7015f784198fadba8935cf5a71e139b0b124ff0", size = 146189, upload-time = "2025-10-05T17:21:42.549Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f6/3054643d8187c0feb31db8da1abb73799a4d72f149bca3a928a171c6ecf3/yarl-1.21.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:de1ab4f48fbcb4c2e578951338cc1c8245e510be061d2773a2d47616fb0d6470", size = 95943, upload-time = "2025-10-05T17:21:44.224Z" }, + { url = "https://files.pythonhosted.org/packages/f5/19/fb9000892d04c500bad8f971cc2884cb986190ca606df9b4b41376d356af/yarl-1.21.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4b449296e2ba009481385349138130f209bb502c4f890b3298bf3ea13d43a6d5", size = 97264, upload-time = "2025-10-05T17:21:45.975Z" }, + { url = "https://files.pythonhosted.org/packages/6f/3b/dbe3af9b3c55463413938933b349b7221a16f052fcc132890c634fbde116/yarl-1.21.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19df967a905f2f9a09733dfb397baa6807772502931000f881943d7cfc6e9f47", size = 361753, upload-time = "2025-10-05T17:21:48.059Z" }, + { url = "https://files.pythonhosted.org/packages/5f/7d/9bf7c744ec1fdb2d97ecdf70775d61e5825859cf0eb42b6f05c454e6aea4/yarl-1.21.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:26940710eece6b5b08a108e81d6325b47610990cd8bb28886e27d4a0d6d60930", size = 323188, upload-time = "2025-10-05T17:21:50.284Z" }, + { url = "https://files.pythonhosted.org/packages/54/e5/2edd706871c7bdfe199f9a8ceba742929e1608400b4adfde872e0ff5977e/yarl-1.21.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0aaa36261a1279b03fa0655a9bd879cc42e06406adaae0150fde25c778393fcb", size = 363145, upload-time = "2025-10-05T17:21:52.168Z" }, + { url = "https://files.pythonhosted.org/packages/53/fa/1403e1d8d7fb5a19456731d55ce36cb7eead99a1d6b16a916a71f5295b6e/yarl-1.21.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d070756da822a538231d519ce290a1423ab108d6174ad1497cd020bee503d818", size = 373527, upload-time = "2025-10-05T17:21:54.179Z" }, + { url = "https://files.pythonhosted.org/packages/02/bb/f00f4e6f441e66db127c8a61d0371cdb5fea690cdc9a13ee2a84912f04a0/yarl-1.21.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:212a5c72d551f94b7799b5de1cc55ddcf3c69ac462f7c0df1beee7e47edb9fef", size = 339781, upload-time = "2025-10-05T17:21:56.215Z" }, + { url = "https://files.pythonhosted.org/packages/24/bb/0a9558f924c98875f96bfbf7e75ccc7a53da2f3b6e39065f039521a808d7/yarl-1.21.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b2f8e0bbdf49530ed09b2bc988082cab6ce24f4c49a0efd2ff5d9477cb29084", size = 347513, upload-time = "2025-10-05T17:21:59.078Z" }, + { url = "https://files.pythonhosted.org/packages/af/83/957137aef698100645922f96fb78dd66ffbce4dcdd5e6c6e50eae5087a91/yarl-1.21.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:9618070bb76a064c13020323b7fc23c332930604dfbc96b77e7ad7baca960c12", size = 334470, upload-time = "2025-10-05T17:22:01.233Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9b/549c3e2cb0cb7dda9a59ad35c5a1e26e35942953a7debee8a983529c95e1/yarl-1.21.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:7a9d0efd6ff6f4f55ff7a37852e4fcdc24b1feb3b09e204df3dda990171fe725", size = 352339, upload-time = "2025-10-05T17:22:03.432Z" }, + { url = "https://files.pythonhosted.org/packages/24/85/8cff7f713821578b6a7989af8d7226fe6119cd3d1884f7b67716290f9233/yarl-1.21.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f1b3930f0934057825227016a141ce16aad4b2a3805fb4e2de71064d042d72e9", size = 357107, upload-time = "2025-10-05T17:22:05.721Z" }, + { url = "https://files.pythonhosted.org/packages/fc/36/c124a3a2be46d051d693d5f0580be27b025f6bbf1d5dfeedcb933442dcd1/yarl-1.21.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fb09731156f54dfd8bb097ce80f9436c2a1a282061ba29e526c375c69086b764", size = 341432, upload-time = "2025-10-05T17:22:08.185Z" }, + { url = "https://files.pythonhosted.org/packages/74/d0/143a8b2bc5e19e4719a00fc453c0a2207ee8b3411e837a7a56d39b3cf60e/yarl-1.21.0-cp314-cp314t-win32.whl", hash = "sha256:2584651c047718ec4a863ee81a5432f6f68974e6f0c58975f0aab408ff839798", size = 89019, upload-time = "2025-10-05T17:22:10.623Z" }, + { url = "https://files.pythonhosted.org/packages/fe/3d/2520bb07955ba583e0c500a1223d7139da80e523160c52bea0f23927f76b/yarl-1.21.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b0e38cf49c17e35831ec38029854b772717d6071f0419b74b80be57571a83d0a", size = 96254, upload-time = "2025-10-05T17:22:12.366Z" }, + { url = "https://files.pythonhosted.org/packages/08/be/3ebe06c6903bb0a0e63c1f445124c6367f4080ef347703fe6cd806672a28/yarl-1.21.0-py3-none-any.whl", hash = "sha256:c464852c531e44abc5ba05d0c0c97a8fa63719106b3dca46fedae14daedf46ae", size = 46777, upload-time = "2025-10-05T17:22:45.175Z" }, ] [[package]]