diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3dd891f69..2424a8a7c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: - id: mixed-line-ending - id: trailing-whitespace - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: "v0.9.10" + rev: "v0.10.0" hooks: - id: ruff args: ["--fix"] diff --git a/docs/conf.py b/docs/conf.py index 009a19dcb..a76568013 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -45,7 +45,7 @@ PY_OBJ = "py:obj" nitpicky = True -nitpick_ignore = [] +nitpick_ignore: list[str] = [] nitpick_ignore_regex = [ (PY_RE, r"sqlspec.*\.T"), ] diff --git a/docs/examples/litestar_multi_db.py b/docs/examples/litestar_multi_db.py new file mode 100644 index 000000000..70e75903b --- /dev/null +++ b/docs/examples/litestar_multi_db.py @@ -0,0 +1,28 @@ +from aiosqlite import Connection +from duckdb import DuckDBPyConnection +from litestar import Litestar, get + +from sqlspec.adapters.aiosqlite import AiosqliteConfig +from sqlspec.adapters.duckdb import DuckDBConfig +from sqlspec.extensions.litestar import DatabaseConfig, SQLSpec + + +@get("/test", sync_to_thread=True) +def simple_select(etl_session: DuckDBPyConnection) -> dict[str, str]: + result = etl_session.execute("SELECT 'Hello, world!' AS greeting").fetchall() + return {"greeting": result[0][0]} + + +@get("/") +async def simple_sqlite(db_connection: Connection) -> dict[str, str]: + result = await db_connection.execute_fetchall("SELECT 'Hello, world!' AS greeting") + return {"greeting": result[0][0]} # type: ignore # noqa: PGH003 + + +sqlspec = SQLSpec( + config=[ + DatabaseConfig(config=AiosqliteConfig(), commit_mode="autocommit"), + DatabaseConfig(config=DuckDBConfig(), connection_key="etl_session"), + ], +) +app = Litestar(route_handlers=[simple_sqlite, simple_select], plugins=[sqlspec]) diff --git a/docs/examples/litestar_single_db.py b/docs/examples/litestar_single_db.py new file mode 100644 index 000000000..e73bc5b9f --- /dev/null +++ b/docs/examples/litestar_single_db.py @@ -0,0 +1,20 @@ +from aiosqlite import Connection +from litestar import Litestar, get + +from sqlspec.adapters.aiosqlite import AiosqliteConfig +from sqlspec.extensions.litestar import SQLSpec + + +@get("/") +async def simple_sqlite(db_session: Connection) -> dict[str, str]: + """Simple select statement. + + Returns: + dict[str, str]: The greeting. + """ + result = await db_session.execute_fetchall("SELECT 'Hello, world!' AS greeting") + return {"greeting": result[0][0]} # type: ignore # noqa: PGH003 + + +sqlspec = SQLSpec(config=AiosqliteConfig()) +app = Litestar(route_handlers=[simple_sqlite], plugins=[sqlspec]) diff --git a/pyproject.toml b/pyproject.toml index 3e05f2a82..5dbece5b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -267,6 +267,8 @@ ignore = [ "ARG002", # Unused method argument "ARG001", # Unused function argument "CPY001", # pycodestyle - Missing Copywrite notice at the top of the file + "RUF029", # Ruff - function is declared as async but has no awaitable calls + "COM812", # flake8-comma - Missing trailing comma ] select = ["ALL"] @@ -310,6 +312,8 @@ known-first-party = ["sqlspec", "tests"] "TRY", "PT012", "INP001", + "DOC", + "PLC", ] "tools/**/*.*" = ["D", "ARG", "EM", "TRY", "G", "FBT", "S603", "F811", "PLW0127", "PLR0911"] "tools/prepare_release.py" = ["S603", "S607"] diff --git a/sqlspec/adapters/adbc/config.py b/sqlspec/adapters/adbc/config.py index c87cf77d9..5712256b2 100644 --- a/sqlspec/adapters/adbc/config.py +++ b/sqlspec/adapters/adbc/config.py @@ -40,7 +40,11 @@ def connection_params(self) -> "dict[str, Any]": @contextmanager def provide_connection(self, *args: "Any", **kwargs: "Any") -> "Generator[Connection, None, None]": - """Create and provide a database connection.""" + """Create and provide a database connection. + + Yields: + Connection: A database connection instance. + """ from adbc_driver_manager.dbapi import connect with connect(**self.connection_params) as connection: diff --git a/sqlspec/adapters/aiosqlite/config.py b/sqlspec/adapters/aiosqlite/config.py index fe2a9905a..dab9011b9 100644 --- a/sqlspec/adapters/aiosqlite/config.py +++ b/sqlspec/adapters/aiosqlite/config.py @@ -50,7 +50,7 @@ def connection_config_dict(self) -> "dict[str, Any]": Returns: A string keyed dict of config kwargs for the aiosqlite.connect() function. """ - return dataclass_to_dict(self, exclude_empty=True, convert_nested=False) + return dataclass_to_dict(self, exclude_empty=True, convert_nested=False, exclude={"pool_instance"}) async def create_connection(self) -> "Connection": """Create and return a new database connection. @@ -76,8 +76,6 @@ async def provide_connection(self, *args: "Any", **kwargs: "Any") -> "AsyncGener Yields: An Aiosqlite connection instance. - Raises: - ImproperConfigurationError: If the connection could not be established. """ connection = await self.create_connection() try: diff --git a/sqlspec/adapters/asyncmy/config.py b/sqlspec/adapters/asyncmy/config.py index 50caf4df8..c0567d00d 100644 --- a/sqlspec/adapters/asyncmy/config.py +++ b/sqlspec/adapters/asyncmy/config.py @@ -130,7 +130,12 @@ def pool_config_dict(self) -> "dict[str, Any]": ImproperConfigurationError: If the pool configuration is not provided. """ if self.pool_config: - return dataclass_to_dict(self.pool_config, exclude_empty=True, convert_nested=False) + return dataclass_to_dict( + self.pool_config, + exclude_empty=True, + convert_nested=False, + exclude={"pool_instance"}, + ) msg = "'pool_config' methods can not be used when a 'pool_instance' is provided." raise ImproperConfigurationError(msg) @@ -179,3 +184,9 @@ async def provide_connection(self, *args: "Any", **kwargs: "Any") -> "AsyncGener pool = await self.provide_pool(*args, **kwargs) # pyright: ignore[reportUnknownVariableType,reportUnknownMemberType] async with pool.acquire() as connection: # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType] yield connection # pyright: ignore[reportUnknownMemberType] + + async def close_pool(self) -> None: + """Close the connection pool.""" + if self.pool_instance is not None: + await self.pool_instance.close() + self.pool_instance = None diff --git a/sqlspec/adapters/asyncpg/config.py b/sqlspec/adapters/asyncpg/config.py index a45241f0b..f262f7576 100644 --- a/sqlspec/adapters/asyncpg/config.py +++ b/sqlspec/adapters/asyncpg/config.py @@ -96,9 +96,14 @@ def pool_config_dict(self) -> "dict[str, Any]": Returns: A string keyed dict of config kwargs for the Asyncpg :func:`create_pool ` function. + + Raises: + ImproperConfigurationError: If no pool_config is provided but a pool_instance is set. """ if self.pool_config: - return dataclass_to_dict(self.pool_config, exclude_empty=True, convert_nested=False) + return dataclass_to_dict( + self.pool_config, exclude_empty=True, exclude={"pool_instance"}, convert_nested=False + ) msg = "'pool_config' methods can not be used when a 'pool_instance' is provided." raise ImproperConfigurationError(msg) @@ -107,6 +112,10 @@ async def create_pool(self) -> "Pool": # pyright: ignore[reportMissingTypeArgum Returns: Getter that returns the pool instance used by the plugin. + + Raises: + ImproperConfigurationError: If neither pool_config nor pool_instance are provided, + or if the pool could not be configured. """ if self.pool_instance is not None: return self.pool_instance @@ -136,9 +145,15 @@ def provide_pool(self, *args: "Any", **kwargs: "Any") -> "Awaitable[Pool]": # p async def provide_connection(self, *args: "Any", **kwargs: "Any") -> "AsyncGenerator[PoolConnectionProxy, None]": # pyright: ignore[reportMissingTypeArgument,reportUnknownParameterType] """Create a connection instance. - Returns: + Yields: A connection instance. """ db_pool = await self.provide_pool(*args, **kwargs) # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType] async with db_pool.acquire() as connection: # pyright: ignore[reportUnknownVariableType] yield connection + + async def close_pool(self) -> None: + """Close the pool.""" + if self.pool_instance is not None: + await self.pool_instance.close() + self.pool_instance = None diff --git a/sqlspec/adapters/duckdb/config.py b/sqlspec/adapters/duckdb/config.py index 1dfeac374..e525d89c6 100644 --- a/sqlspec/adapters/duckdb/config.py +++ b/sqlspec/adapters/duckdb/config.py @@ -1,5 +1,5 @@ from contextlib import contextmanager -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import TYPE_CHECKING, Any, Union, cast from duckdb import DuckDBPyConnection @@ -39,6 +39,27 @@ class ExtensionConfig(TypedDict): """Optional version of the extension to install""" +@dataclass +class SecretConfig: + """Configuration for a secret to store in a connection. + + This class provides configuration options for storing a secret in a connection for later retrieval. + + For details see: https://duckdb.org/docs/api/python/overview#connection-options + """ + + secret_type: str = field() + """The type of secret to store""" + name: str = field() + """The name of the secret to store""" + persist: bool = field(default=False) + """Whether to persist the secret""" + value: dict[str, Any] = field(default_factory=dict) + """The secret value to store""" + replace_if_exists: bool = field(default=True) + """Whether to replace the secret if it already exists""" + + @dataclass class DuckDBConfig(NoPoolSyncConfig[DuckDBPyConnection]): """Configuration for DuckDB database connections. @@ -63,6 +84,8 @@ class DuckDBConfig(NoPoolSyncConfig[DuckDBPyConnection]): extensions: "Union[Sequence[ExtensionConfig], ExtensionConfig, EmptyType]" = Empty """A sequence of extension configurations to install and configure upon connection creation.""" + secrets: "Union[Sequence[SecretConfig], SecretConfig , EmptyType]" = Empty + """A dictionary of secrets to store in the connection for later retrieval.""" def __post_init__(self) -> None: """Post-initialization validation and processing. @@ -73,9 +96,10 @@ def __post_init__(self) -> None: """ if self.config is Empty: self.config = {} - if self.extensions is Empty: self.extensions = [] + if self.secrets is Empty: + self.secrets = [] if isinstance(self.extensions, dict): self.extensions = [self.extensions] # this is purely for mypy @@ -120,6 +144,47 @@ def _configure_extensions(self, connection: "DuckDBPyConnection") -> None: for extension in cast("list[ExtensionConfig]", self.extensions): self._configure_extension(connection, extension) + @staticmethod + def _secret_exists(connection: "DuckDBPyConnection", name: "str") -> bool: + """Check if a secret exists in the connection. + + Args: + connection: The DuckDB connection to check for the secret. + name: The name of the secret to check for. + + Returns: + bool: True if the secret exists, False otherwise. + """ + results = connection.execute("select 1 from duckdb_secrets() where name=?", name).fetchone() + return results is not None + + @classmethod + def _configure_secrets( + cls, + connection: "DuckDBPyConnection", + secrets: "list[SecretConfig]", + ) -> None: + """Configure persistent secrets for the connection. + + Args: + connection: The DuckDB connection to configure secrets for. + secrets: The list of secrets to store in the connection. + + Raises: + ImproperConfigurationError: If a secret could not be stored in the connection. + """ + try: + for secret in secrets: + secret_exists = cls._secret_exists(connection, secret.name) + if not secret_exists or secret.replace_if_exists: + connection.execute(f"""create or replace {"persistent" if secret.persist else ""} secret {secret.name} ( + type {secret.secret_type}, + {" ,".join([f"{k} '{v}'" for k, v in secret.value.items()])} + ) """) + except Exception as e: + msg = f"Failed to store secret. Error: {e!s}" + raise ImproperConfigurationError(msg) from e + @staticmethod def _configure_extension(connection: "DuckDBPyConnection", extension: ExtensionConfig) -> None: """Configure a single extension for the connection. @@ -156,7 +221,12 @@ def connection_config_dict(self) -> "dict[str, Any]": Returns: A string keyed dict of config kwargs for the duckdb.connect() function. """ - config = dataclass_to_dict(self, exclude_empty=True, exclude={"extensions"}, convert_nested=False) + config = dataclass_to_dict( + self, + exclude_empty=True, + exclude={"extensions", "pool_instance", "secrets"}, + convert_nested=False, + ) if not config.get("database"): config["database"] = ":memory:" return config @@ -176,6 +246,8 @@ def create_connection(self) -> "DuckDBPyConnection": connection = duckdb.connect(**self.connection_config_dict) # pyright: ignore[reportUnknownMemberType] self._configure_extensions(connection) self._configure_connection(connection) + self._configure_secrets(connection, cast("list[SecretConfig]", self.secrets)) + except Exception as e: msg = f"Could not configure the DuckDB connection. Error: {e!s}" raise ImproperConfigurationError(msg) from e diff --git a/sqlspec/adapters/oracledb/config/_asyncio.py b/sqlspec/adapters/oracledb/config/_asyncio.py index d088af865..a88fbc9b9 100644 --- a/sqlspec/adapters/oracledb/config/_asyncio.py +++ b/sqlspec/adapters/oracledb/config/_asyncio.py @@ -54,18 +54,27 @@ class OracleAsyncDatabaseConfig(AsyncDatabaseConfig[AsyncConnection, AsyncConnec def pool_config_dict(self) -> "dict[str, Any]": """Return the pool configuration as a dict. + Raises: + ImproperConfigurationError: If no pool_config is provided but a pool_instance + Returns: A string keyed dict of config kwargs for the Asyncpg :func:`create_pool ` function. """ if self.pool_config is not None: - return dataclass_to_dict(self.pool_config, exclude_empty=True, convert_nested=False) + return dataclass_to_dict( + self.pool_config, exclude_empty=True, convert_nested=False, exclude={"pool_instance"} + ) msg = "'pool_config' methods can not be used when a 'pool_instance' is provided." raise ImproperConfigurationError(msg) async def create_pool(self) -> "AsyncConnectionPool": """Return a pool. If none exists yet, create one. + Raises: + ImproperConfigurationError: If neither pool_config nor pool_instance are provided, + or if the pool could not be configured. + Returns: Getter that returns the pool instance used by the plugin. """ @@ -95,8 +104,8 @@ def provide_pool(self, *args: "Any", **kwargs: "Any") -> "Awaitable[AsyncConnect async def provide_connection(self, *args: "Any", **kwargs: "Any") -> "AsyncGenerator[AsyncConnection, None]": """Create a connection instance. - Returns: - A connection instance. + Yields: + AsyncConnection: A connection instance. """ db_pool = await self.provide_pool(*args, **kwargs) async with db_pool.acquire() as connection: # pyright: ignore[reportUnknownMemberType] diff --git a/sqlspec/adapters/oracledb/config/_common.py b/sqlspec/adapters/oracledb/config/_common.py index ed684f4d9..12632dfd1 100644 --- a/sqlspec/adapters/oracledb/config/_common.py +++ b/sqlspec/adapters/oracledb/config/_common.py @@ -27,7 +27,7 @@ @dataclass -class OracleGenericPoolConfig(Generic[ConnectionT, PoolT], GenericPoolConfig): +class OracleGenericPoolConfig(GenericPoolConfig, Generic[ConnectionT, PoolT]): """Configuration for Oracle database connection pools. This class provides configuration options for both synchronous and asynchronous Oracle diff --git a/sqlspec/adapters/oracledb/config/_sync.py b/sqlspec/adapters/oracledb/config/_sync.py index dfdcbe26f..28fa32ed4 100644 --- a/sqlspec/adapters/oracledb/config/_sync.py +++ b/sqlspec/adapters/oracledb/config/_sync.py @@ -54,18 +54,27 @@ class OracleSyncDatabaseConfig(SyncDatabaseConfig[Connection, ConnectionPool]): def pool_config_dict(self) -> "dict[str, Any]": """Return the pool configuration as a dict. + Raises: + ImproperConfigurationError: If no pool_config is provided but a pool_instance + Returns: A string keyed dict of config kwargs for the Asyncpg :func:`create_pool ` function. """ if self.pool_config: - return dataclass_to_dict(self.pool_config, exclude_empty=True, convert_nested=False) + return dataclass_to_dict( + self.pool_config, exclude_empty=True, convert_nested=False, exclude={"pool_instance"} + ) msg = "'pool_config' methods can not be used when a 'pool_instance' is provided." raise ImproperConfigurationError(msg) def create_pool(self) -> "ConnectionPool": """Return a pool. If none exists yet, create one. + Raises: + ImproperConfigurationError: If neither pool_config nor pool_instance is provided, + or if the pool could not be configured. + Returns: Getter that returns the pool instance used by the plugin. """ @@ -95,8 +104,8 @@ def provide_pool(self, *args: "Any", **kwargs: "Any") -> "ConnectionPool": def provide_connection(self, *args: "Any", **kwargs: "Any") -> "Generator[Connection, None, None]": """Create a connection instance. - Returns: - A connection instance. + Yields: + Connection: A connection instance from the pool. """ db_pool = self.provide_pool(*args, **kwargs) with db_pool.acquire() as connection: # pyright: ignore[reportUnknownMemberType] diff --git a/sqlspec/adapters/psycopg/config/_async.py b/sqlspec/adapters/psycopg/config/_async.py index 85c8ce1ae..a6f4b5414 100644 --- a/sqlspec/adapters/psycopg/config/_async.py +++ b/sqlspec/adapters/psycopg/config/_async.py @@ -44,14 +44,31 @@ class PsycoPgAsyncDatabaseConfig(AsyncDatabaseConfig[AsyncConnection, AsyncConne @property def pool_config_dict(self) -> "dict[str, Any]": - """Return the pool configuration as a dict.""" + """Return the pool configuration as a dict. + + Raises: + ImproperConfigurationError: If pool_config is not set but pool_instance is provided. + """ if self.pool_config: - return dataclass_to_dict(self.pool_config, exclude_empty=True, convert_nested=False) + return dataclass_to_dict( + self.pool_config, + exclude_empty=True, + convert_nested=False, + exclude={"pool_instance"}, + ) msg = "'pool_config' methods can not be used when a 'pool_instance' is provided." raise ImproperConfigurationError(msg) async def create_pool(self) -> "AsyncConnectionPool": - """Create and return a connection pool.""" + """Create and return a connection pool. + + Returns: + AsyncConnectionPool: The configured connection pool. + + Raises: + ImproperConfigurationError: If neither pool_config nor pool_instance are provided + or if pool creation fails. + """ if self.pool_instance is not None: return self.pool_instance @@ -67,12 +84,20 @@ async def create_pool(self) -> "AsyncConnectionPool": return self.pool_instance def provide_pool(self, *args: "Any", **kwargs: "Any") -> "Awaitable[AsyncConnectionPool]": - """Create and return a connection pool.""" + """Create and return a connection pool. + + Returns: + Awaitable[AsyncConnectionPool]: The configured connection pool. + """ return self.create_pool() @asynccontextmanager async def provide_connection(self, *args: "Any", **kwargs: "Any") -> "AsyncGenerator[AsyncConnection, None]": - """Create and provide a database connection.""" + """Create and provide a database connection. + + Yields: + AsyncConnection: A database connection from the pool. + """ pool = await self.provide_pool(*args, **kwargs) async with pool.connection() as connection: yield connection diff --git a/sqlspec/adapters/psycopg/config/_common.py b/sqlspec/adapters/psycopg/config/_common.py index 45ba25ecc..03da7a3c1 100644 --- a/sqlspec/adapters/psycopg/config/_common.py +++ b/sqlspec/adapters/psycopg/config/_common.py @@ -22,7 +22,7 @@ @dataclass -class PsycoPgGenericPoolConfig(Generic[ConnectionT, PoolT], GenericPoolConfig): +class PsycoPgGenericPoolConfig(GenericPoolConfig, Generic[ConnectionT, PoolT]): """Configuration for Psycopg connection pools. This class provides configuration options for both synchronous and asynchronous Psycopg diff --git a/sqlspec/adapters/psycopg/config/_sync.py b/sqlspec/adapters/psycopg/config/_sync.py index fea1754ab..386781493 100644 --- a/sqlspec/adapters/psycopg/config/_sync.py +++ b/sqlspec/adapters/psycopg/config/_sync.py @@ -43,14 +43,31 @@ class PsycoPgSyncDatabaseConfig(SyncDatabaseConfig[Connection, ConnectionPool]): @property def pool_config_dict(self) -> "dict[str, Any]": - """Return the pool configuration as a dict.""" + """Return the pool configuration as a dict. + + Raises: + ImproperConfigurationError: If pool_config is not provided and instead pool_instance is used. + """ if self.pool_config: - return dataclass_to_dict(self.pool_config, exclude_empty=True, convert_nested=False) + return dataclass_to_dict( + self.pool_config, + exclude_empty=True, + convert_nested=False, + exclude={"pool_instance"}, + ) msg = "'pool_config' methods can not be used when a 'pool_instance' is provided." raise ImproperConfigurationError(msg) def create_pool(self) -> "ConnectionPool": - """Create and return a connection pool.""" + """Create and return a connection pool. + + Returns: + ConnectionPool: The configured connection pool instance. + + Raises: + ImproperConfigurationError: If neither pool_config nor pool_instance is provided, + or if the pool could not be configured. + """ if self.pool_instance is not None: return self.pool_instance @@ -66,12 +83,20 @@ def create_pool(self) -> "ConnectionPool": return self.pool_instance def provide_pool(self, *args: "Any", **kwargs: "Any") -> "ConnectionPool": - """Create and return a connection pool.""" + """Create and return a connection pool. + + Returns: + ConnectionPool: The configured connection pool instance. + """ return self.create_pool() @contextmanager def provide_connection(self, *args: "Any", **kwargs: "Any") -> "Generator[Connection, None, None]": - """Create and provide a database connection.""" + """Create and provide a database connection. + + Yields: + Connection: A database connection from the pool. + """ pool = self.provide_pool(*args, **kwargs) with pool.connection() as connection: yield connection diff --git a/sqlspec/adapters/sqlite/config.py b/sqlspec/adapters/sqlite/config.py index d6e0c13c5..6e31e4400 100644 --- a/sqlspec/adapters/sqlite/config.py +++ b/sqlspec/adapters/sqlite/config.py @@ -54,7 +54,7 @@ def connection_config_dict(self) -> "dict[str, Any]": Returns: A string keyed dict of config kwargs for the sqlite3.connect() function. """ - return dataclass_to_dict(self, exclude_empty=True, convert_nested=False) + return dataclass_to_dict(self, exclude_empty=True, convert_nested=False, exclude={"pool_instance"}) def create_connection(self) -> "Connection": """Create and return a new database connection. @@ -80,8 +80,6 @@ def provide_connection(self, *args: "Any", **kwargs: "Any") -> "Generator[Connec Yields: A SQLite connection instance. - Raises: - ImproperConfigurationError: If the connection could not be established. """ connection = self.create_connection() try: diff --git a/sqlspec/base.py b/sqlspec/base.py index 33b917b75..910ddcdf4 100644 --- a/sqlspec/base.py +++ b/sqlspec/base.py @@ -1,8 +1,9 @@ +# ruff: noqa: PLR6301 from abc import ABC, abstractmethod from collections.abc import AsyncGenerator, Awaitable, Generator from contextlib import AbstractAsyncContextManager, AbstractContextManager from dataclasses import dataclass -from typing import Annotated, Any, ClassVar, Generic, TypeVar, Union, cast, overload +from typing import Annotated, Any, ClassVar, Generic, Optional, TypeVar, Union, cast, overload __all__ = ( "AsyncDatabaseConfig", @@ -17,14 +18,19 @@ PoolT = TypeVar("PoolT") AsyncConfigT = TypeVar("AsyncConfigT", bound="Union[AsyncDatabaseConfig[Any, Any], NoPoolAsyncConfig[Any]]") SyncConfigT = TypeVar("SyncConfigT", bound="Union[SyncDatabaseConfig[Any, Any], NoPoolSyncConfig[Any]]") +ConfigT = TypeVar( + "ConfigT", + bound="Union[Union[AsyncDatabaseConfig[Any, Any], NoPoolAsyncConfig[Any]], SyncDatabaseConfig[Any, Any], NoPoolSyncConfig[Any]]", +) @dataclass -class DatabaseConfigProtocol(Generic[ConnectionT, PoolT], ABC): +class DatabaseConfigProtocol(ABC, Generic[ConnectionT, PoolT]): """Protocol defining the interface for database configurations.""" __is_async__: ClassVar[bool] = False __supports_connection_pooling__: ClassVar[bool] = False + pool_instance: Union[PoolT, None] = None def __hash__(self) -> int: return id(self) @@ -59,6 +65,11 @@ def create_pool(self) -> Union[PoolT, Awaitable[PoolT]]: """Create and return connection pool.""" raise NotImplementedError + @abstractmethod + def close_pool(self) -> Optional[Awaitable[None]]: + """Terminate the connection pool.""" + raise NotImplementedError + @abstractmethod def provide_pool( self, @@ -84,11 +95,15 @@ class NoPoolSyncConfig(DatabaseConfigProtocol[ConnectionT, None]): __is_async__ = False __supports_connection_pooling__ = False + pool_instance: None = None def create_pool(self) -> None: """This database backend has not implemented the pooling configurations.""" return + def close_pool(self) -> None: + return + def provide_pool(self, *args: Any, **kwargs: Any) -> None: """This database backend has not implemented the pooling configurations.""" return @@ -99,11 +114,15 @@ class NoPoolAsyncConfig(DatabaseConfigProtocol[ConnectionT, None]): __is_async__ = True __supports_connection_pooling__ = False + pool_instance: None = None async def create_pool(self) -> None: """This database backend has not implemented the pooling configurations.""" return + async def close_pool(self) -> None: + return + def provide_pool(self, *args: Any, **kwargs: Any) -> None: """This database backend has not implemented the pooling configurations.""" return @@ -133,6 +152,8 @@ class AsyncDatabaseConfig(DatabaseConfigProtocol[ConnectionT, PoolT]): class ConfigManager: """Type-safe configuration manager with literal inference.""" + __slots__ = ("_configs",) + def __init__(self) -> None: self._configs: dict[Any, DatabaseConfigProtocol[Any, Any]] = {} @@ -149,7 +170,11 @@ def add_config( AsyncConfigT, ], ) -> Union[Annotated[type[SyncConfigT], int], Annotated[type[AsyncConfigT], int]]: # pyright: ignore[reportInvalidTypeVarUse] - """Add a new configuration to the manager.""" + """Add a new configuration to the manager. + + Returns: + A unique type key that can be used to retrieve the configuration later. + """ key = Annotated[type(config), id(config)] # type: ignore[valid-type] self._configs[key] = config return key # type: ignore[return-value] # pyright: ignore[reportReturnType] @@ -164,7 +189,14 @@ def get_config( self, name: Union[type[DatabaseConfigProtocol[ConnectionT, PoolT]], Any], ) -> DatabaseConfigProtocol[ConnectionT, PoolT]: - """Retrieve a configuration by its type.""" + """Retrieve a configuration by its type. + + Returns: + DatabaseConfigProtocol: The configuration instance for the given type. + + Raises: + KeyError: If no configuration is found for the given type. + """ config = self._configs.get(name) if not config: msg = f"No configuration found for {name}" @@ -198,7 +230,15 @@ def get_connection( type[AsyncDatabaseConfig[ConnectionT, PoolT]], ], ) -> Union[ConnectionT, Awaitable[ConnectionT]]: - """Create and return a connection from the specified configuration.""" + """Create and return a connection from the specified configuration. + + Args: + name: The configuration type to use for creating the connection. + + Returns: + Either a connection instance or an awaitable that resolves to a connection, + depending on whether the configuration is sync or async. + """ config = self.get_config(name) return config.create_connection() @@ -220,8 +260,38 @@ def get_pool( type[AsyncDatabaseConfig[ConnectionT, PoolT]], ], ) -> Union[type[PoolT], Awaitable[type[PoolT]], None]: - """Create and return a connection pool from the specified configuration.""" + """Create and return a connection pool from the specified configuration. + + Args: + name: The configuration type to use for creating the pool. + + Returns: + Either a pool instance, an awaitable that resolves to a pool instance, or None + if the configuration does not support connection pooling. + """ + config = self.get_config(name) + if config.support_connection_pooling: + return cast("Union[type[PoolT], Awaitable[type[PoolT]]]", config.create_pool()) + return None + + def close_pool( + self, + name: Union[ + type[NoPoolSyncConfig[ConnectionT]], + type[NoPoolAsyncConfig[ConnectionT]], + type[SyncDatabaseConfig[ConnectionT, PoolT]], + type[AsyncDatabaseConfig[ConnectionT, PoolT]], + ], + ) -> Optional[Awaitable[None]]: + """Close the connection pool for the specified configuration. + + Args: + name: The configuration type whose pool to close. + + Returns: + An awaitable if the configuration is async, otherwise None. + """ config = self.get_config(name) - if isinstance(config, (NoPoolSyncConfig, NoPoolAsyncConfig)): - return None - return cast("Union[type[PoolT], Awaitable[type[PoolT]]]", config.create_pool()) + if config.support_connection_pooling: + return config.close_pool() + return None diff --git a/sqlspec/extensions/litestar/__init__.py b/sqlspec/extensions/litestar/__init__.py index e69de29bb..581d846d5 100644 --- a/sqlspec/extensions/litestar/__init__.py +++ b/sqlspec/extensions/litestar/__init__.py @@ -0,0 +1,19 @@ +from sqlspec.extensions.litestar.config import DatabaseConfig +from sqlspec.extensions.litestar.handlers import ( + autocommit_handler_maker, + connection_provider_maker, + lifespan_handler_maker, + manual_handler_maker, + pool_provider_maker, +) +from sqlspec.extensions.litestar.plugin import SQLSpec + +__all__ = ( + "DatabaseConfig", + "SQLSpec", + "autocommit_handler_maker", + "connection_provider_maker", + "lifespan_handler_maker", + "manual_handler_maker", + "pool_provider_maker", +) diff --git a/sqlspec/extensions/litestar/_utils.py b/sqlspec/extensions/litestar/_utils.py new file mode 100644 index 000000000..32c1cbeff --- /dev/null +++ b/sqlspec/extensions/litestar/_utils.py @@ -0,0 +1,56 @@ +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from litestar.types import Scope + +__all__ = ( + "delete_sqlspec_scope_state", + "get_sqlspec_scope_state", + "set_sqlspec_scope_state", +) + +_SCOPE_NAMESPACE = "_sqlspec" + + +def get_sqlspec_scope_state(scope: "Scope", key: str, default: Any = None, pop: bool = False) -> Any: + """Get an internal value from connection scope state. + + Note: + If called with a default value, this method behaves like to `dict.set_default()`, both setting the key in the + namespace to the default value, and returning it. + + If called without a default value, the method behaves like `dict.get()`, returning ``None`` if the key does not + exist. + + Args: + scope: The connection scope. + key: Key to get from internal namespace in scope state. + default: Default value to return. + pop: Boolean flag dictating whether the value should be deleted from the state. + + Returns: + Value mapped to ``key`` in internal connection scope namespace. + """ + namespace = scope.setdefault(_SCOPE_NAMESPACE, {}) # type: ignore[misc] + return namespace.pop(key, default) if pop else namespace.get(key, default) # pyright: ignore[reportUnknownVariableType,reportUnknownMemberType] + + +def set_sqlspec_scope_state(scope: "Scope", key: str, value: Any) -> None: + """Set an internal value in connection scope state. + + Args: + scope: The connection scope. + key: Key to set under internal namespace in scope state. + value: Value for key. + """ + scope.setdefault(_SCOPE_NAMESPACE, {})[key] = value # type: ignore[misc] + + +def delete_sqlspec_scope_state(scope: "Scope", key: str) -> None: + """Remove an internal value from connection scope state. + + Args: + scope: The connection scope. + key: Key to set under internal namespace in scope state. + """ + del scope.setdefault(_SCOPE_NAMESPACE, {})[key] # type: ignore[misc] diff --git a/sqlspec/extensions/litestar/config.py b/sqlspec/extensions/litestar/config.py index e69de29bb..ed717162d 100644 --- a/sqlspec/extensions/litestar/config.py +++ b/sqlspec/extensions/litestar/config.py @@ -0,0 +1,81 @@ +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Callable, Literal, Optional, Union + +from sqlspec.base import ( + ConnectionT, + PoolT, +) +from sqlspec.exceptions import ImproperConfigurationError +from sqlspec.extensions.litestar.handlers import ( + autocommit_handler_maker, + connection_provider_maker, + lifespan_handler_maker, + manual_handler_maker, + pool_provider_maker, +) + +if TYPE_CHECKING: + from collections.abc import Awaitable + from contextlib import _AsyncGeneratorContextManager + + from litestar import Litestar + from litestar.datastructures.state import State + from litestar.types import BeforeMessageSendHookHandler, Scope + + from sqlspec.base import ( + AsyncConfigT, + ConnectionT, + PoolT, + SyncConfigT, + ) + +CommitMode = Literal["manual", "autocommit", "autocommit_include_redirect"] +DEFAULT_COMMIT_MODE: CommitMode = "manual" +DEFAULT_CONNECTION_KEY = "db_connection" +DEFAULT_POOL_KEY = "db_pool" + + +@dataclass +class DatabaseConfig: + config: "Union[SyncConfigT, AsyncConfigT]" = field() # type: ignore[valid-type] # pyright: ignore[reportGeneralTypeIssues] + connection_key: str = field(default=DEFAULT_CONNECTION_KEY) + pool_key: str = field(default=DEFAULT_POOL_KEY) + commit_mode: "CommitMode" = field(default=DEFAULT_COMMIT_MODE) + extra_commit_statuses: "Optional[set[int]]" = field(default=None) + extra_rollback_statuses: "Optional[set[int]]" = field(default=None) + connection_provider: "Callable[[State,Scope], Awaitable[ConnectionT]]" = field(init=False, repr=False, hash=False) # pyright: ignore[reportGeneralTypeIssues] + pool_provider: "Callable[[State,Scope], Awaitable[PoolT]]" = field(init=False, repr=False, hash=False) # pyright: ignore[reportGeneralTypeIssues] + before_send_handler: "BeforeMessageSendHookHandler" = field(init=False, repr=False, hash=False) + lifespan_handler: "Callable[[Litestar], _AsyncGeneratorContextManager[None, None]]" = field( + init=False, + repr=False, + hash=False, + ) + annotation: "type[Union[SyncConfigT, AsyncConfigT]]" = field(init=False, repr=False, hash=False) # type: ignore[valid-type] # pyright: ignore[reportGeneralTypeIssues] + + def __post_init__(self) -> None: + if not self.config.support_connection_pooling and self.pool_key == DEFAULT_POOL_KEY: # type: ignore[union-attr,unused-ignore] + """If the database configuration does not support connection pooling, the pool key must be unique. We just automatically generate a unique identify so it won't conflict with other configs that may get added""" + self.pool_key = f"_{self.pool_key}_{id(self.config)}" + if self.commit_mode == "manual": + self.before_send_handler = manual_handler_maker(connection_scope_key=self.connection_key) + elif self.commit_mode == "autocommit": + self.before_send_handler = autocommit_handler_maker( + commit_on_redirect=False, + extra_commit_statuses=self.extra_commit_statuses, + extra_rollback_statuses=self.extra_rollback_statuses, + connection_scope_key=self.connection_key, + ) + elif self.commit_mode == "autocommit_include_redirect": + self.before_send_handler = autocommit_handler_maker( + commit_on_redirect=True, + extra_commit_statuses=self.extra_commit_statuses, + extra_rollback_statuses=self.extra_rollback_statuses, + connection_scope_key=self.connection_key, + ) + else: + msg = f"Invalid commit mode: {self.commit_mode}" # type: ignore[unreachable] + raise ImproperConfigurationError(detail=msg) + self.lifespan_handler = lifespan_handler_maker(config=self.config, pool_key=self.pool_key) + self.connection_provider = connection_provider_maker(connection_key=self.connection_key, config=self.config) + self.pool_provider = pool_provider_maker(pool_key=self.pool_key, config=self.config) diff --git a/sqlspec/extensions/litestar/handlers.py b/sqlspec/extensions/litestar/handlers.py new file mode 100644 index 000000000..eb385a1d2 --- /dev/null +++ b/sqlspec/extensions/litestar/handlers.py @@ -0,0 +1,187 @@ +import contextlib +from typing import TYPE_CHECKING, Any, Callable, Optional, cast + +from litestar.constants import HTTP_DISCONNECT, HTTP_RESPONSE_START, WEBSOCKET_CLOSE, WEBSOCKET_DISCONNECT + +from sqlspec.exceptions import ImproperConfigurationError +from sqlspec.extensions.litestar._utils import ( + delete_sqlspec_scope_state, + get_sqlspec_scope_state, + set_sqlspec_scope_state, +) +from sqlspec.utils.sync_tools import maybe_async_ + +if TYPE_CHECKING: + from collections.abc import AsyncGenerator, Awaitable, Coroutine + + from litestar import Litestar + from litestar.datastructures.state import State + from litestar.types import Message, Scope + + from sqlspec.base import ConnectionT, DatabaseConfigProtocol, PoolT + + +SESSION_TERMINUS_ASGI_EVENTS = {HTTP_RESPONSE_START, HTTP_DISCONNECT, WEBSOCKET_DISCONNECT, WEBSOCKET_CLOSE} +"""ASGI events that terminate a session scope.""" + + +def manual_handler_maker(connection_scope_key: str) -> "Callable[[Message, Scope], Coroutine[Any, Any, None]]": + """Set up the handler to issue a transaction commit or rollback based on specified status codes + Args: + connection_scope_key: The key to use within the application state + + Returns: + The handler callable + """ + + async def handler(message: "Message", scope: "Scope") -> None: + """Handle commit/rollback, closing and cleaning up sessions before sending. + + Args: + message: ASGI-``Message`` + scope: An ASGI-``Scope`` + + """ + connection = get_sqlspec_scope_state(scope, connection_scope_key) + if connection and message["type"] in SESSION_TERMINUS_ASGI_EVENTS: + with contextlib.suppress(Exception): + await maybe_async_(connection.close)() + delete_sqlspec_scope_state(scope, connection_scope_key) + + return handler + + +def autocommit_handler_maker( + connection_scope_key: str, + commit_on_redirect: bool = False, + extra_commit_statuses: "Optional[set[int]]" = None, + extra_rollback_statuses: "Optional[set[int]]" = None, +) -> "Callable[[Message, Scope], Coroutine[Any, Any, None]]": + """Set up the handler to issue a transaction commit or rollback based on specified status codes + Args: + commit_on_redirect: Issue a commit when the response status is a redirect (``3XX``) + extra_commit_statuses: A set of additional status codes that trigger a commit + extra_rollback_statuses: A set of additional status codes that trigger a rollback + connection_scope_key: The key to use within the application state + + Raises: + ImproperConfigurationError: If extra_commit_statuses and extra_rollback_statuses share any status codes + + Returns: + The handler callable + """ + if extra_commit_statuses is None: + extra_commit_statuses = set() + + if extra_rollback_statuses is None: + extra_rollback_statuses = set() + + if len(extra_commit_statuses & extra_rollback_statuses) > 0: + msg = "Extra rollback statuses and commit statuses must not share any status codes" + raise ImproperConfigurationError(msg) + + commit_range = range(200, 400 if commit_on_redirect else 300) + + async def handler(message: "Message", scope: "Scope") -> None: + """Handle commit/rollback, closing and cleaning up sessions before sending. + + Args: + message: ASGI-``litestar.types.Message`` + scope: An ASGI-``litestar.types.Scope`` + + """ + connection = get_sqlspec_scope_state(scope, connection_scope_key) + try: + if connection is not None and message["type"] == HTTP_RESPONSE_START: + if (message["status"] in commit_range or message["status"] in extra_commit_statuses) and message[ + "status" + ] not in extra_rollback_statuses: + await maybe_async_(connection.commit)() + else: + await maybe_async_(connection.rollback)() + finally: + if connection and message["type"] in SESSION_TERMINUS_ASGI_EVENTS: + with contextlib.suppress(Exception): + await maybe_async_(connection.close)() + delete_sqlspec_scope_state(scope, connection_scope_key) + + return handler + + +def lifespan_handler_maker( + config: "DatabaseConfigProtocol[Any, Any]", + pool_key: str, +) -> "Callable[[Litestar], contextlib._AsyncGeneratorContextManager[None, None]]": + """Build the lifespan handler for the database configuration. + + Args: + config: The database configuration. + pool_key: The key to use for the connection pool within Litestar. + + Returns: + The generated lifespan handler for the connection. + """ + + @contextlib.asynccontextmanager + async def lifespan_handler(app: "Litestar") -> "AsyncGenerator[None, None]": + db_pool = await maybe_async_(config.create_pool)() + app.state.update({pool_key: db_pool}) + try: + yield + finally: + app.state.pop(pool_key, None) + try: + await maybe_async_(config.close_pool)() + except Exception as e: # noqa: BLE001 + if app.logger: + app.logger.warning("Error closing database pool for %s. Error: %s", pool_key, e) + + return lifespan_handler + + +def connection_provider_maker( + connection_key: str, + config: "DatabaseConfigProtocol[ConnectionT, PoolT]", +) -> "Callable[[State,Scope], Awaitable[ConnectionT]]": + """Build the connection provider for the database configuration. + + Args: + connection_key: The dependency key to use for the session within Litestar. + config: The database configuration. + + Returns: + The generated connection provider for the connection. + """ + + async def provide_connection(state: "State", scope: "Scope") -> "ConnectionT": + connection = get_sqlspec_scope_state(scope, connection_key) + if connection is None: + connection = await maybe_async_(config.create_connection)() + set_sqlspec_scope_state(scope, connection_key, connection) + return cast("ConnectionT", connection) + + return provide_connection + + +def pool_provider_maker( + pool_key: str, + config: "DatabaseConfigProtocol[ConnectionT, PoolT]", +) -> "Callable[[State,Scope], Awaitable[PoolT]]": + """Build the pool provider for the database configuration. + + Args: + pool_key: The dependency key to use for the pool within Litestar. + config: The database configuration. + + Returns: + The generated connection pool for the database. + """ + + async def provide_pool(state: "State", scope: "Scope") -> "PoolT": + pool = get_sqlspec_scope_state(scope, pool_key) + if pool is None: + pool = await maybe_async_(config.create_pool)() + set_sqlspec_scope_state(scope, pool_key, pool) + return cast("PoolT", pool) + + return provide_pool diff --git a/sqlspec/extensions/litestar/plugin.py b/sqlspec/extensions/litestar/plugin.py index eaedc10fb..7031586c3 100644 --- a/sqlspec/extensions/litestar/plugin.py +++ b/sqlspec/extensions/litestar/plugin.py @@ -1,34 +1,63 @@ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Literal, Union +from litestar.di import Provide from litestar.plugins import InitPluginProtocol +from sqlspec.base import ( + AsyncConfigT, + ConfigManager, + ConnectionT, + DatabaseConfigProtocol, + PoolT, + SyncConfigT, +) +from sqlspec.exceptions import ImproperConfigurationError +from sqlspec.extensions.litestar.config import DatabaseConfig + if TYPE_CHECKING: + from click import Group from litestar.config.app import AppConfig - from sqlspec.base import ConfigManager + +CommitMode = Literal["manual", "autocommit", "autocommit_include_redirect"] +DEFAULT_COMMIT_MODE: CommitMode = "manual" +DEFAULT_CONNECTION_KEY = "db_connection" +DEFAULT_POOL_KEY = "db_pool" -class SQLSpecPlugin(InitPluginProtocol): +class SQLSpec(InitPluginProtocol, ConfigManager): """SQLSpec plugin.""" - __slots__ = ("_config",) + __slots__ = ("_config", "_plugin_configs") - def __init__(self, config: "ConfigManager") -> None: + def __init__( + self, + config: Union["SyncConfigT", "AsyncConfigT", "DatabaseConfig", list["DatabaseConfig"]], + ) -> None: """Initialize ``SQLSpecPlugin``. Args: config: configure SQLSpec plugin for use with Litestar. """ - self._config = config + self._configs: dict[Any, DatabaseConfigProtocol[Any, Any]] = {} + if isinstance(config, DatabaseConfigProtocol): + self._plugin_configs: list[DatabaseConfig] = [DatabaseConfig(config=config)] + elif isinstance(config, DatabaseConfig): + self._plugin_configs = [config] + else: + self._plugin_configs = config @property - def config(self) -> "ConfigManager": + def config(self) -> "list[DatabaseConfig]": # pyright: ignore[reportInvalidTypeVarUse] """Return the plugin config. Returns: ConfigManager. """ - return self._config + return self._plugin_configs + + def on_cli_init(self, cli: "Group") -> None: + """Configure the CLI for use with SQLSpec.""" def on_app_init(self, app_config: "AppConfig") -> "AppConfig": """Configure application for use with SQLSpec. @@ -39,8 +68,68 @@ def on_app_init(self, app_config: "AppConfig") -> "AppConfig": Returns: The updated :class:`AppConfig <.config.app.AppConfig>` instance. """ + self._validate_dependency_keys() + app_config.signature_types.extend( + [ + ConfigManager, + ConnectionT, + PoolT, + DatabaseConfig, + DatabaseConfigProtocol, + SyncConfigT, + AsyncConfigT, + ] + ) + for c in self._plugin_configs: + c.annotation = self.add_config(c.config) + app_config.before_send.append(c.before_send_handler) + app_config.lifespan.append(c.lifespan_handler) + app_config.dependencies.update( + {c.connection_key: Provide(c.connection_provider), c.pool_key: Provide(c.pool_provider)}, + ) - from sqlspec.base import ConfigManager - - app_config.signature_types.append(ConfigManager) return app_config + + def get_annotations(self) -> "list[type[Union[SyncConfigT, AsyncConfigT]]]": # pyright: ignore[reportInvalidTypeVarUse] + """Return the list of annotations. + + Returns: + List of annotations. + """ + return [c.annotation for c in self.config] + + def get_annotation( + self, + key: "Union[str, SyncConfigT, AsyncConfigT, type[Union[SyncConfigT, AsyncConfigT]]]", + ) -> "type[Union[SyncConfigT, AsyncConfigT]]": + """Return the annotation for the given configuration. + + Args: + key: The configuration instance or key to lookup + + Raises: + KeyError: If no configuration is found for the given key. + + Returns: + The annotation for the configuration. + """ + for c in self.config: + if key == c.config or key in {c.annotation, c.connection_key, c.pool_key}: + return c.annotation + msg = f"No configuration found for {key}" + raise KeyError(msg) + + def _validate_dependency_keys(self) -> None: + """Verify uniqueness of ``connection_key`` and ``pool_key``. + + Raises: + ImproperConfigurationError: If session keys or pool keys are not unique. + """ + connection_keys = [c.connection_key for c in self.config] + pool_keys = [c.pool_key for c in self.config] + if len(set(connection_keys)) != len(connection_keys): + msg = "When using multiple database configuration, each configuration must have a unique `connection_key`." + raise ImproperConfigurationError(detail=msg) + if len(set(pool_keys)) != len(pool_keys): + msg = "When using multiple database configuration, each configuration must have a unique `pool_key`." + raise ImproperConfigurationError(detail=msg) diff --git a/sqlspec/utils/sync_tools.py b/sqlspec/utils/sync_tools.py new file mode 100644 index 000000000..0fb0b91c6 --- /dev/null +++ b/sqlspec/utils/sync_tools.py @@ -0,0 +1,335 @@ +import asyncio +import functools +import inspect +import sys +from contextlib import AbstractAsyncContextManager, AbstractContextManager +from typing import ( + TYPE_CHECKING, + Any, + Generic, + Optional, + TypeVar, + Union, + cast, +) + +from typing_extensions import ParamSpec + +if TYPE_CHECKING: + from collections.abc import Awaitable, Callable, Coroutine + from types import TracebackType + +try: + import uvloop # pyright: ignore[reportMissingImports] +except ImportError: + uvloop = None + + +ReturnT = TypeVar("ReturnT") +ParamSpecT = ParamSpec("ParamSpecT") +T = TypeVar("T") + + +class PendingType: + def __repr__(self) -> str: + return "AsyncPending" + + +Pending = PendingType() + + +class PendingValueError(Exception): + """Exception raised when a value is accessed before it is ready.""" + + +class SoonValue(Generic[T]): + """Holds a value that will be available soon after an async operation.""" + + def __init__(self) -> None: + self._stored_value: Union[T, PendingType] = Pending + + @property + def value(self) -> "T": + if isinstance(self._stored_value, PendingType): + msg = "The return value of this task is still pending." + raise PendingValueError(msg) + return self._stored_value + + @property + def ready(self) -> bool: + return not isinstance(self._stored_value, PendingType) + + +class TaskGroup: + """Manages a group of asyncio tasks, allowing them to be run concurrently.""" + + def __init__(self) -> None: + self._tasks: set[asyncio.Task[Any]] = set() + self._exceptions: list[BaseException] = [] + self._closed = False + + async def __aenter__(self) -> "TaskGroup": + if self._closed: + msg = "Cannot enter a task group that has already been closed." + raise RuntimeError(msg) + return self + + async def __aexit__( + self, + exc_type: "Optional[type[BaseException]]", # noqa: PYI036 + exc_val: "Optional[BaseException]", # noqa: PYI036 + exc_tb: "Optional[TracebackType]", # noqa: PYI036 + ) -> None: + self._closed = True + if exc_val: + self._exceptions.append(exc_val) + + if self._tasks: + await asyncio.wait(self._tasks) + + if self._exceptions: + # Re-raise the first exception encountered. + raise self._exceptions[0] + + def create_task(self, coro: "Coroutine[Any, Any, Any]") -> "asyncio.Task[Any]": + """Create and add a coroutine as a task to the task group. + + Args: + coro (Coroutine): The coroutine to be added as a task. + + Returns: + asyncio.Task: The created asyncio task. + + Raises: + RuntimeError: If the task group has already been closed. + """ + if self._closed: + msg = "Cannot create a task in a task group that has already been closed." + raise RuntimeError(msg) + task = asyncio.create_task(coro) + self._tasks.add(task) + task.add_done_callback(self._tasks.discard) + task.add_done_callback(self._check_result) + return task + + def _check_result(self, task: "asyncio.Task[Any]") -> None: + """Check and store exceptions from a completed task. + + Args: + task (asyncio.Task): The task to check for exceptions. + """ + try: + task.result() # This will raise the exception if one occurred. + except Exception as e: # noqa: BLE001 + self._exceptions.append(e) + + def start_soon_( + self, + async_function: "Callable[ParamSpecT, Awaitable[T]]", + name: object = None, + ) -> "Callable[ParamSpecT, SoonValue[T]]": + """Create a function to start a new task in this task group. + + Args: + async_function (Callable): An async function to call soon. + name (object, optional): Name of the task for introspection and debugging. + + Returns: + Callable: A function that starts the task and returns a SoonValue object. + """ + + @functools.wraps(async_function) + def wrapper(*args: "ParamSpecT.args", **kwargs: "ParamSpecT.kwargs") -> "SoonValue[T]": + partial_f = functools.partial(async_function, *args, **kwargs) + soon_value: SoonValue[T] = SoonValue() + + @functools.wraps(partial_f) + async def value_wrapper(*args: "Any") -> None: + value = await partial_f() + soon_value._stored_value = value # pyright: ignore[reportPrivateUsage] # noqa: SLF001 + + self.create_task(value_wrapper) # type: ignore[arg-type] + return soon_value + + return wrapper + + +def create_task_group() -> "TaskGroup": + """Create a TaskGroup for managing multiple concurrent async tasks. + + Returns: + TaskGroup: A new TaskGroup instance. + """ + return TaskGroup() + + +class CapacityLimiter: + """Limits the number of concurrent operations using a semaphore.""" + + def __init__(self, total_tokens: int) -> None: + self._semaphore = asyncio.Semaphore(total_tokens) + + async def acquire(self) -> None: + await self._semaphore.acquire() + + def release(self) -> None: + self._semaphore.release() + + @property + def total_tokens(self) -> int: + return self._semaphore._value # noqa: SLF001 + + @total_tokens.setter + def total_tokens(self, value: int) -> None: + self._semaphore = asyncio.Semaphore(value) + + async def __aenter__(self) -> None: + await self.acquire() + + async def __aexit__( + self, + exc_type: "Optional[type[BaseException]]", # noqa: PYI036 + exc_val: "Optional[BaseException]", # noqa: PYI036 + exc_tb: "Optional[TracebackType]", # noqa: PYI036 + ) -> None: + self.release() + + +_default_limiter = CapacityLimiter(40) + + +def run_(async_function: "Callable[ParamSpecT, Coroutine[Any, Any, ReturnT]]") -> "Callable[ParamSpecT, ReturnT]": + """Convert an async function to a blocking function using asyncio.run(). + + Args: + async_function (Callable): The async function to convert. + + Returns: + Callable: A blocking function that runs the async function. + + """ + + @functools.wraps(async_function) + def wrapper(*args: "ParamSpecT.args", **kwargs: "ParamSpecT.kwargs") -> "ReturnT": + partial_f = functools.partial(async_function, *args, **kwargs) + try: + loop = asyncio.get_running_loop() + except RuntimeError: + loop = None + + if loop is not None: + # Running in an existing event loop + return asyncio.run(partial_f()) + # Create a new event loop and run the function + if uvloop and sys.platform != "win32": + uvloop.install() # pyright: ignore[reportUnknownMemberType] + return asyncio.run(partial_f()) + + return wrapper + + +def await_( + async_function: "Callable[ParamSpecT, Coroutine[Any, Any, ReturnT]]", + raise_sync_error: bool = True, +) -> "Callable[ParamSpecT, ReturnT]": + """Convert an async function to a blocking one, running in the main async loop. + + Args: + async_function (Callable): The async function to convert. + raise_sync_error (bool, optional): If False, runs in a new event loop if no loop is present. + + Returns: + Callable: A blocking function that runs the async function. + """ + + @functools.wraps(async_function) + def wrapper(*args: "ParamSpecT.args", **kwargs: "ParamSpecT.kwargs") -> "ReturnT": + partial_f = functools.partial(async_function, *args, **kwargs) + try: + loop = asyncio.get_running_loop() + except RuntimeError: + loop = None + + if loop is None and raise_sync_error is False: + return asyncio.run(partial_f()) + # Running in an existing event loop + return asyncio.run(partial_f()) + + return wrapper + + +def async_( + function: "Callable[ParamSpecT, ReturnT]", + *, + limiter: "Optional[CapacityLimiter]" = None, +) -> "Callable[ParamSpecT, Awaitable[ReturnT]]": + """Convert a blocking function to an async one using asyncio.to_thread(). + + Args: + function (Callable): The blocking function to convert. + cancellable (bool, optional): Allow cancellation of the operation. + limiter (CapacityLimiter, optional): Limit the total number of threads. + + Returns: + Callable: An async function that runs the original function in a thread. + """ + + async def wrapper( + *args: "ParamSpecT.args", + **kwargs: "ParamSpecT.kwargs", + ) -> "ReturnT": + partial_f = functools.partial(function, *args, **kwargs) + used_limiter = limiter or _default_limiter + async with used_limiter: + return await asyncio.to_thread(partial_f) + + return wrapper + + +def maybe_async_( + function: "Callable[ParamSpecT, Union[Awaitable[ReturnT], ReturnT]]", +) -> "Callable[ParamSpecT, Awaitable[ReturnT]]": + if inspect.iscoroutinefunction(function): + return function + + async def wrapper(*args: "ParamSpecT.args", **kwargs: "ParamSpecT.kwargs") -> "ReturnT": + result = function(*args, **kwargs) + if inspect.isawaitable(result): + return await result + return await async_(lambda: result)() + + return wrapper + + +def wrap_sync(fn: "Callable[ParamSpecT, ReturnT]") -> "Callable[ParamSpecT, Awaitable[ReturnT]]": + if inspect.iscoroutinefunction(fn): + return fn + + async def wrapped(*args: "ParamSpecT.args", **kwargs: "ParamSpecT.kwargs") -> ReturnT: + return await async_(functools.partial(fn, *args, **kwargs))() + + return wrapped + + +class _ContextManagerWrapper(Generic[T]): + def __init__(self, cm: AbstractContextManager[T]) -> None: + self._cm = cm + + async def __aenter__(self) -> T: + return self._cm.__enter__() + + async def __aexit__( + self, + exc_type: "Optional[type[BaseException]]", # noqa: PYI036 + exc_val: "Optional[BaseException]", # noqa: PYI036 + exc_tb: "Optional[TracebackType]", # noqa: PYI036 + ) -> "Optional[bool]": + return self._cm.__exit__(exc_type, exc_val, exc_tb) + + +def maybe_async_context( + obj: "Union[AbstractContextManager[T], AbstractAsyncContextManager[T]]", +) -> "AbstractAsyncContextManager[T]": + if isinstance(obj, AbstractContextManager): + return cast("AbstractAsyncContextManager[T]", _ContextManagerWrapper(obj)) + return obj diff --git a/tests/unit/test_adapters/test_aiosqlite/__init__.py b/tests/unit/test_adapters/test_aiosqlite/__init__.py new file mode 100644 index 000000000..0b00d8541 --- /dev/null +++ b/tests/unit/test_adapters/test_aiosqlite/__init__.py @@ -0,0 +1 @@ +"""Tests for OracleDB adapter.""" diff --git a/tests/unit/test_adapters/test_asyncpg/test_config.py b/tests/unit/test_adapters/test_asyncpg/test_config.py index fd49199a1..c9b817766 100644 --- a/tests/unit/test_adapters/test_asyncpg/test_config.py +++ b/tests/unit/test_adapters/test_asyncpg/test_config.py @@ -19,7 +19,11 @@ @pytest.fixture def mock_asyncpg_pool() -> Generator[MagicMock, None, None]: - """Create a mock AsyncPG pool.""" + """Create a mock AsyncPG pool. + + Yields: + MagicMock: A mock object that simulates an AsyncPG pool. + """ with patch("sqlspec.adapters.asyncpg.config.asyncpg_create_pool") as mock_create_pool: pool = MagicMock(spec=Pool) mock_create_pool.return_value = pool @@ -34,7 +38,11 @@ async def async_create_pool(*args: Any, **kwargs: Any) -> Pool: # pyright: igno @pytest.fixture def mock_asyncpg_connection() -> Generator[MagicMock, None, None]: - """Create a mock AsyncPG connection.""" + """Create a mock AsyncPG connection. + + Yields: + MagicMock: A mock object that simulates an AsyncPG connection. + """ return MagicMock(spec=PoolConnectionProxy) @@ -46,14 +54,14 @@ def test_default_values(self) -> None: config = AsyncPgPoolConfig(dsn="postgresql://localhost/test") assert config.dsn == "postgresql://localhost/test" assert config.connect_kwargs is Empty - assert config.connection_class is Empty + assert config.connection_class is Empty # pyright: ignore[reportUnknownMemberType] assert config.record_class is Empty assert config.min_size is Empty assert config.max_size is Empty assert config.max_queries is Empty assert config.max_inactive_connection_lifetime is Empty - assert config.setup is Empty - assert config.init is Empty + assert config.setup is Empty # pyright: ignore[reportUnknownMemberType] + assert config.init is Empty # pyright: ignore[reportUnknownMemberType] assert config.loop is Empty def test_with_all_values(self) -> None: @@ -89,6 +97,10 @@ async def create_connection(self, *args: Any, **kwargs: Any) -> PoolConnectionPr """Mock create_connection method.""" return MagicMock(spec=PoolConnectionProxy) + async def close_pool(self) -> None: + """Mock close_pool method.""" + pass + @property def connection_config_dict(self) -> dict[str, Any]: """Mock connection_config_dict property.""" @@ -124,7 +136,7 @@ async def test_create_pool_with_pool_config(self, mock_asyncpg_pool: MagicMock) """Test create_pool with pool configuration.""" pool_config = AsyncPgPoolConfig(dsn="postgresql://localhost/test") config = MockAsyncPgConfig(pool_config=pool_config) - pool = await config.create_pool() + pool = await config.create_pool() # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType] assert pool is mock_asyncpg_pool @pytest.mark.asyncio @@ -132,7 +144,7 @@ async def test_create_pool_with_existing_pool(self) -> None: """Test create_pool with existing pool instance.""" existing_pool = MagicMock(spec=Pool) config = MockAsyncPgConfig(pool_instance=existing_pool) - pool = await config.create_pool() + pool = await config.create_pool() # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType] assert pool is existing_pool @pytest.mark.asyncio @@ -143,7 +155,7 @@ async def test_create_pool_without_config_or_instance(self) -> None: ImproperConfigurationError, match="One of 'pool_config' or 'pool_instance' must be provided", ): - await config.create_pool() + await config.create_pool() # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType] @pytest.mark.asyncio async def test_provide_connection(self, mock_asyncpg_pool: MagicMock, mock_asyncpg_connection: MagicMock) -> None: @@ -155,5 +167,5 @@ async def test_provide_connection(self, mock_asyncpg_pool: MagicMock, mock_async config = MockAsyncPgConfig(pool_config=AsyncPgPoolConfig(dsn="postgresql://localhost/test")) - async with config.provide_connection() as conn: + async with config.provide_connection() as conn: # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType] assert conn is mock_asyncpg_connection diff --git a/tests/unit/test_adapters/test_oracledb/test_config.py b/tests/unit/test_adapters/test_oracledb/test_config.py index 935ca92be..e095376fb 100644 --- a/tests/unit/test_adapters/test_oracledb/test_config.py +++ b/tests/unit/test_adapters/test_oracledb/test_config.py @@ -214,6 +214,10 @@ async def create_connection(self, *args: Any, **kwargs: Any) -> Connection: """Mock create_connection method.""" return MagicMock(spec=Connection) + async def close_pool(self) -> None: + """Mock close_pool method.""" + pass + @property def connection_config_dict(self) -> dict[str, Any]: """Mock connection_config_dict property.""" diff --git a/tests/unit/test_adapters/test_psycopg/__init__.py b/tests/unit/test_adapters/test_psycopg/__init__.py new file mode 100644 index 000000000..0b00d8541 --- /dev/null +++ b/tests/unit/test_adapters/test_psycopg/__init__.py @@ -0,0 +1 @@ +"""Tests for OracleDB adapter.""" diff --git a/tests/unit/test_adapters/test_psycopg/test_async_config.py b/tests/unit/test_adapters/test_psycopg/test_async_config.py index dd8ff7061..4df57e482 100644 --- a/tests/unit/test_adapters/test_psycopg/test_async_config.py +++ b/tests/unit/test_adapters/test_psycopg/test_async_config.py @@ -29,6 +29,10 @@ def connection_config_dict(self) -> dict[str, Any]: """Mock connection_config_dict property.""" return {} + async def close_pool(self) -> None: + """Mock close_pool method.""" + pass + @pytest.fixture def mock_psycopg_pool() -> Generator[MagicMock, None, None]: diff --git a/tests/unit/test_adapters/test_psycopg/test_sync_config.py b/tests/unit/test_adapters/test_psycopg/test_sync_config.py index 12a8d2294..300447c07 100644 --- a/tests/unit/test_adapters/test_psycopg/test_sync_config.py +++ b/tests/unit/test_adapters/test_psycopg/test_sync_config.py @@ -29,6 +29,10 @@ def connection_config_dict(self) -> dict[str, Any]: """Mock connection_config_dict property.""" return {} + def close_pool(self) -> None: + """Mock close_pool method.""" + pass + @pytest.fixture def mock_psycopg_pool() -> Generator[MagicMock, None, None]: diff --git a/tests/unit/test_base.py b/tests/unit/test_base.py index fa2d46c87..f1aab71f4 100644 --- a/tests/unit/test_base.py +++ b/tests/unit/test_base.py @@ -58,6 +58,9 @@ def connection_config_dict(self) -> dict[str, Any]: def create_pool(self) -> MockPool: return MockPool() + def close_pool(self) -> None: + pass + def provide_pool(self, *args: Any, **kwargs: Any) -> AbstractContextManager[MockPool]: @contextmanager def _provide_pool() -> Generator[MockPool, None, None]: @@ -84,6 +87,9 @@ def provide_connection(self, *args: Any, **kwargs: Any) -> Generator[MockConnect finally: connection.close() + def close_pool(self) -> None: + pass + @property def connection_config_dict(self) -> dict[str, Any]: return {"host": "localhost", "port": 5432} @@ -103,6 +109,9 @@ async def provide_connection(self, *args: Any, **kwargs: Any) -> AsyncGenerator[ finally: await connection.close() + async def close_pool(self) -> None: + pass + @property def connection_config_dict(self) -> dict[str, Any]: return {"host": "localhost", "port": 5432} diff --git a/tests/unit/test_utils/test_sync_tools.py b/tests/unit/test_utils/test_sync_tools.py new file mode 100644 index 000000000..ec1145a15 --- /dev/null +++ b/tests/unit/test_utils/test_sync_tools.py @@ -0,0 +1,109 @@ +import asyncio +from collections.abc import AsyncIterator, Iterator +from contextlib import asynccontextmanager, contextmanager +from typing import TypeVar + +import pytest + +from sqlspec.utils.sync_tools import ( + CapacityLimiter, + PendingValueError, + SoonValue, + TaskGroup, + async_, + await_, + maybe_async_, + maybe_async_context, + run_, +) + +T = TypeVar("T") + + +@pytest.mark.asyncio +async def test_maybe_async() -> None: + @maybe_async_ + def sync_func(x: int) -> int: + return x * 2 + + @maybe_async_ # type: ignore[arg-type] + async def async_func(x: int) -> int: + return x * 2 + + assert await sync_func(21) == 42 + assert await async_func(21) == 42 + + +@pytest.mark.asyncio +async def test_maybe_async_context() -> None: + @contextmanager + def sync_cm() -> Iterator[int]: + yield 42 + + @asynccontextmanager + async def async_cm() -> AsyncIterator[int]: + yield 42 + + async with maybe_async_context(sync_cm()) as value: + assert value == 42 + + async with maybe_async_context(async_cm()) as value: + assert value == 42 + + +@pytest.mark.asyncio +async def test_soon_value() -> None: + soon_value = SoonValue[int]() + assert not soon_value.ready + with pytest.raises(PendingValueError): + _ = soon_value.value + + setattr(soon_value, "_stored_value", 42) + assert soon_value.ready + assert soon_value.value == 42 # type: ignore[unreachable] + + +@pytest.mark.asyncio +async def test_task_group() -> None: + async def sample_task(x: int) -> int: + return x * 2 + + async with TaskGroup() as tg: + task = tg.create_task(sample_task(21)) + await asyncio.wait([task]) + assert task.result() == 42 + + +@pytest.mark.asyncio +async def test_capacity_limiter() -> None: + limiter = CapacityLimiter(1) + + async with limiter: + assert limiter.total_tokens == 0 + + assert limiter.total_tokens == 1 + + +def test_run_() -> None: + async def async_func(x: int) -> int: + return x * 2 + + sync_func = run_(async_func) + assert sync_func(21) == 42 + + +def test_await_() -> None: + async def async_func(x: int) -> int: + return x * 2 + + sync_func = await_(async_func, raise_sync_error=False) + assert sync_func(21) == 42 + + +@pytest.mark.asyncio +async def test_async_() -> None: + def sync_func(x: int) -> int: + return x * 2 + + async_func = async_(sync_func) + assert await async_func(21) == 42 diff --git a/tools/sphinx_ext/__init__.py b/tools/sphinx_ext/__init__.py index ad8fb1aeb..849f9faad 100644 --- a/tools/sphinx_ext/__init__.py +++ b/tools/sphinx_ext/__init__.py @@ -10,7 +10,7 @@ def setup(app: Sphinx) -> dict[str, bool]: ext_config = {} - ext_config.update(missing_references.setup(app)) - ext_config.update(changelog.setup(app)) + ext_config.update(missing_references.setup(app)) # pyright: ignore[reportUnknownMemberType] + ext_config.update(changelog.setup(app)) # type: ignore[arg-type] # pyright: ignore[reportUnknownMemberType] - return ext_config + return ext_config # pyright: ignore[reportUnknownVariableType] diff --git a/tools/sphinx_ext/missing_references.py b/tools/sphinx_ext/missing_references.py index 3cd1bae12..5531d0f02 100644 --- a/tools/sphinx_ext/missing_references.py +++ b/tools/sphinx_ext/missing_references.py @@ -86,7 +86,7 @@ def on_warn_missing_reference(app: "Sphinx", domain: str, node: Node) -> "Option continue if isinstance(targets, set) and target in targets: return True - if targets.match(target): # pyright: ignore[reportAttributeAccessIssue,reportUnknownMemberType] + if targets.match(target): # type: ignore[union-attr] # pyright: ignore[reportAttributeAccessIssue,reportUnknownMemberType] return True return None @@ -118,7 +118,7 @@ def on_missing_reference( def on_env_before_read_docs(app: "Sphinx", env: "BuildEnvironment", docnames: "set[str]") -> None: tmp_examples_path = Path.cwd() / "docs/_build/_tmp_examples" tmp_examples_path.mkdir(exist_ok=True, parents=True) - env.tmp_examples_path = tmp_examples_path # pyright: ignore[reportAttributeAccessIssue] + env.tmp_examples_path = tmp_examples_path # type: ignore[attr-defined] # pyright: ignore[reportAttributeAccessIssue] def setup(app: "Sphinx") -> "dict[str, bool]": diff --git a/uv.lock b/uv.lock index ea42dc325..36b8b644e 100644 --- a/uv.lock +++ b/uv.lock @@ -1077,20 +1077,20 @@ wheels = [ [[package]] name = "google-cloud-core" -version = "2.4.2" +version = "2.4.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-api-core" }, { name = "google-auth" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8d/96/16cc0a34f75899ace6a42bb4ef242ac4aa263089b018d1c18c007d1fd8f2/google_cloud_core-2.4.2.tar.gz", hash = "sha256:a4fcb0e2fcfd4bfe963837fad6d10943754fd79c1a50097d68540b6eb3d67f35", size = 35854 } +sdist = { url = "https://files.pythonhosted.org/packages/d6/b8/2b53838d2acd6ec6168fd284a990c76695e84c65deee79c9f3a4276f6b4f/google_cloud_core-2.4.3.tar.gz", hash = "sha256:1fab62d7102844b278fe6dead3af32408b1df3eb06f5c7e8634cbd40edc4da53", size = 35861 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/0f/76e813cee7568ac467d929f4f0da7ab349596e7fc4ee837b990611e07d99/google_cloud_core-2.4.2-py2.py3-none-any.whl", hash = "sha256:7459c3e83de7cb8b9ecfec9babc910efb4314030c56dd798eaad12c426f7d180", size = 29343 }, + { url = "https://files.pythonhosted.org/packages/40/86/bda7241a8da2d28a754aad2ba0f6776e35b67e37c36ae0c45d49370f1014/google_cloud_core-2.4.3-py2.py3-none-any.whl", hash = "sha256:5130f9f4c14b4fafdff75c79448f9495cfade0d8775facf1b09c3bf67e027f6e", size = 29348 }, ] [[package]] name = "google-cloud-spanner" -version = "3.52.0" +version = "3.53.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-api-core", extra = ["grpc"] }, @@ -1101,9 +1101,9 @@ dependencies = [ { name = "protobuf" }, { name = "sqlparse" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ec/fc/8863aa062f73aeec5e30ab01133c7211b35e8ffe7b4b65f5616c24d31500/google_cloud_spanner-3.52.0.tar.gz", hash = "sha256:b18cc9b8d97866c80297c878175fa86af9244cd0c13455970192f8318d646e8a", size = 621356 } +sdist = { url = "https://files.pythonhosted.org/packages/27/eb/cef9263b4ac61a9e967d3d6846c4469151160a1cced891791ce1c6ccddee/google_cloud_spanner-3.53.0.tar.gz", hash = "sha256:0c7be3134b74928cf928d1f73b58c722fc2014346de1240a0cc8ffdd3222f606", size = 659445 } wheels = [ - { url = "https://files.pythonhosted.org/packages/55/be/84f99a510a4fd60dc2ef4b031f015b7a8ce0a68dc687fe133fb46794785b/google_cloud_spanner-3.52.0-py2.py3-none-any.whl", hash = "sha256:d6c30a7ad9742bbe93dc5fc11293f0b339714d1dbf395b541ca9c8942d5ecf3f", size = 449941 }, + { url = "https://files.pythonhosted.org/packages/d6/96/52997fc187611a2cf0c64df747fa70ffc0b469f0a367f39bdd078c43db52/google_cloud_spanner-3.53.0-py2.py3-none-any.whl", hash = "sha256:be863394521b44df3c5a118c00c4b7c978d4437adb49e359e39b3d76362a7e60", size = 483101 }, ] [[package]] @@ -1197,69 +1197,74 @@ wheels = [ [[package]] name = "grpcio" -version = "1.70.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/69/e1/4b21b5017c33f3600dcc32b802bb48fe44a4d36d6c066f52650c7c2690fa/grpcio-1.70.0.tar.gz", hash = "sha256:8d1584a68d5922330025881e63a6c1b54cc8117291d382e4fa69339b6d914c56", size = 12788932 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/10/e9/f72408bac1f7b05b25e4df569b02d6b200c8e7857193aa9f1df7a3744add/grpcio-1.70.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:95469d1977429f45fe7df441f586521361e235982a0b39e33841549143ae2851", size = 5229736 }, - { url = "https://files.pythonhosted.org/packages/b3/17/e65139ea76dac7bcd8a3f17cbd37e3d1a070c44db3098d0be5e14c5bd6a1/grpcio-1.70.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:ed9718f17fbdb472e33b869c77a16d0b55e166b100ec57b016dc7de9c8d236bf", size = 11432751 }, - { url = "https://files.pythonhosted.org/packages/a0/12/42de6082b4ab14a59d30b2fc7786882fdaa75813a4a4f3d4a8c4acd6ed59/grpcio-1.70.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:374d014f29f9dfdb40510b041792e0e2828a1389281eb590df066e1cc2b404e5", size = 5711439 }, - { url = "https://files.pythonhosted.org/packages/34/f8/b5a19524d273cbd119274a387bb72d6fbb74578e13927a473bc34369f079/grpcio-1.70.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2af68a6f5c8f78d56c145161544ad0febbd7479524a59c16b3e25053f39c87f", size = 6330777 }, - { url = "https://files.pythonhosted.org/packages/1a/67/3d6c0ad786238aac7fa93b79246fc452978fbfe9e5f86f70da8e8a2797d0/grpcio-1.70.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce7df14b2dcd1102a2ec32f621cc9fab6695effef516efbc6b063ad749867295", size = 5944639 }, - { url = "https://files.pythonhosted.org/packages/76/0d/d9f7cbc41c2743cf18236a29b6a582f41bd65572a7144d92b80bc1e68479/grpcio-1.70.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c78b339869f4dbf89881e0b6fbf376313e4f845a42840a7bdf42ee6caed4b11f", size = 6643543 }, - { url = "https://files.pythonhosted.org/packages/fc/24/bdd7e606b3400c14330e33a4698fa3a49e38a28c9e0a831441adbd3380d2/grpcio-1.70.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:58ad9ba575b39edef71f4798fdb5c7b6d02ad36d47949cd381d4392a5c9cbcd3", size = 6199897 }, - { url = "https://files.pythonhosted.org/packages/d1/33/8132eb370087960c82d01b89faeb28f3e58f5619ffe19889f57c58a19c18/grpcio-1.70.0-cp310-cp310-win32.whl", hash = "sha256:2b0d02e4b25a5c1f9b6c7745d4fa06efc9fd6a611af0fb38d3ba956786b95199", size = 3617513 }, - { url = "https://files.pythonhosted.org/packages/99/bc/0fce5cfc0ca969df66f5dca6cf8d2258abb88146bf9ab89d8cf48e970137/grpcio-1.70.0-cp310-cp310-win_amd64.whl", hash = "sha256:0de706c0a5bb9d841e353f6343a9defc9fc35ec61d6eb6111802f3aa9fef29e1", size = 4303342 }, - { url = "https://files.pythonhosted.org/packages/65/c4/1f67d23d6bcadd2fd61fb460e5969c52b3390b4a4e254b5e04a6d1009e5e/grpcio-1.70.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:17325b0be0c068f35770f944124e8839ea3185d6d54862800fc28cc2ffad205a", size = 5229017 }, - { url = "https://files.pythonhosted.org/packages/e4/bd/cc36811c582d663a740fb45edf9f99ddbd99a10b6ba38267dc925e1e193a/grpcio-1.70.0-cp311-cp311-macosx_10_14_universal2.whl", hash = "sha256:dbe41ad140df911e796d4463168e33ef80a24f5d21ef4d1e310553fcd2c4a386", size = 11472027 }, - { url = "https://files.pythonhosted.org/packages/7e/32/8538bb2ace5cd72da7126d1c9804bf80b4fe3be70e53e2d55675c24961a8/grpcio-1.70.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:5ea67c72101d687d44d9c56068328da39c9ccba634cabb336075fae2eab0d04b", size = 5707785 }, - { url = "https://files.pythonhosted.org/packages/ce/5c/a45f85f2a0dfe4a6429dee98717e0e8bd7bd3f604315493c39d9679ca065/grpcio-1.70.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb5277db254ab7586769e490b7b22f4ddab3876c490da0a1a9d7c695ccf0bf77", size = 6331599 }, - { url = "https://files.pythonhosted.org/packages/9f/e5/5316b239380b8b2ad30373eb5bb25d9fd36c0375e94a98a0a60ea357d254/grpcio-1.70.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7831a0fc1beeeb7759f737f5acd9fdcda520e955049512d68fda03d91186eea", size = 5940834 }, - { url = "https://files.pythonhosted.org/packages/05/33/dbf035bc6d167068b4a9f2929dfe0b03fb763f0f861ecb3bb1709a14cb65/grpcio-1.70.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:27cc75e22c5dba1fbaf5a66c778e36ca9b8ce850bf58a9db887754593080d839", size = 6641191 }, - { url = "https://files.pythonhosted.org/packages/4c/c4/684d877517e5bfd6232d79107e5a1151b835e9f99051faef51fed3359ec4/grpcio-1.70.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d63764963412e22f0491d0d32833d71087288f4e24cbcddbae82476bfa1d81fd", size = 6198744 }, - { url = "https://files.pythonhosted.org/packages/e9/43/92fe5eeaf340650a7020cfb037402c7b9209e7a0f3011ea1626402219034/grpcio-1.70.0-cp311-cp311-win32.whl", hash = "sha256:bb491125103c800ec209d84c9b51f1c60ea456038e4734688004f377cfacc113", size = 3617111 }, - { url = "https://files.pythonhosted.org/packages/55/15/b6cf2c9515c028aff9da6984761a3ab484a472b0dc6435fcd07ced42127d/grpcio-1.70.0-cp311-cp311-win_amd64.whl", hash = "sha256:d24035d49e026353eb042bf7b058fb831db3e06d52bee75c5f2f3ab453e71aca", size = 4304604 }, - { url = "https://files.pythonhosted.org/packages/4c/a4/ddbda79dd176211b518f0f3795af78b38727a31ad32bc149d6a7b910a731/grpcio-1.70.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:ef4c14508299b1406c32bdbb9fb7b47612ab979b04cf2b27686ea31882387cff", size = 5198135 }, - { url = "https://files.pythonhosted.org/packages/30/5c/60eb8a063ea4cb8d7670af8fac3f2033230fc4b75f62669d67c66ac4e4b0/grpcio-1.70.0-cp312-cp312-macosx_10_14_universal2.whl", hash = "sha256:aa47688a65643afd8b166928a1da6247d3f46a2784d301e48ca1cc394d2ffb40", size = 11447529 }, - { url = "https://files.pythonhosted.org/packages/fb/b9/1bf8ab66729f13b44e8f42c9de56417d3ee6ab2929591cfee78dce749b57/grpcio-1.70.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:880bfb43b1bb8905701b926274eafce5c70a105bc6b99e25f62e98ad59cb278e", size = 5664484 }, - { url = "https://files.pythonhosted.org/packages/d1/06/2f377d6906289bee066d96e9bdb91e5e96d605d173df9bb9856095cccb57/grpcio-1.70.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e654c4b17d07eab259d392e12b149c3a134ec52b11ecdc6a515b39aceeec898", size = 6303739 }, - { url = "https://files.pythonhosted.org/packages/ae/50/64c94cfc4db8d9ed07da71427a936b5a2bd2b27c66269b42fbda82c7c7a4/grpcio-1.70.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2394e3381071045a706ee2eeb6e08962dd87e8999b90ac15c55f56fa5a8c9597", size = 5910417 }, - { url = "https://files.pythonhosted.org/packages/53/89/8795dfc3db4389c15554eb1765e14cba8b4c88cc80ff828d02f5572965af/grpcio-1.70.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:b3c76701428d2df01964bc6479422f20e62fcbc0a37d82ebd58050b86926ef8c", size = 6626797 }, - { url = "https://files.pythonhosted.org/packages/9c/b2/6a97ac91042a2c59d18244c479ee3894e7fb6f8c3a90619bb5a7757fa30c/grpcio-1.70.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ac073fe1c4cd856ebcf49e9ed6240f4f84d7a4e6ee95baa5d66ea05d3dd0df7f", size = 6190055 }, - { url = "https://files.pythonhosted.org/packages/86/2b/28db55c8c4d156053a8c6f4683e559cd0a6636f55a860f87afba1ac49a51/grpcio-1.70.0-cp312-cp312-win32.whl", hash = "sha256:cd24d2d9d380fbbee7a5ac86afe9787813f285e684b0271599f95a51bce33528", size = 3600214 }, - { url = "https://files.pythonhosted.org/packages/17/c3/a7a225645a965029ed432e5b5e9ed959a574e62100afab553eef58be0e37/grpcio-1.70.0-cp312-cp312-win_amd64.whl", hash = "sha256:0495c86a55a04a874c7627fd33e5beaee771917d92c0e6d9d797628ac40e7655", size = 4292538 }, - { url = "https://files.pythonhosted.org/packages/68/38/66d0f32f88feaf7d83f8559cd87d899c970f91b1b8a8819b58226de0a496/grpcio-1.70.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:aa573896aeb7d7ce10b1fa425ba263e8dddd83d71530d1322fd3a16f31257b4a", size = 5199218 }, - { url = "https://files.pythonhosted.org/packages/c1/96/947df763a0b18efb5cc6c2ae348e56d97ca520dc5300c01617b234410173/grpcio-1.70.0-cp313-cp313-macosx_10_14_universal2.whl", hash = "sha256:d405b005018fd516c9ac529f4b4122342f60ec1cee181788249372524e6db429", size = 11445983 }, - { url = "https://files.pythonhosted.org/packages/fd/5b/f3d4b063e51b2454bedb828e41f3485800889a3609c49e60f2296cc8b8e5/grpcio-1.70.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:f32090238b720eb585248654db8e3afc87b48d26ac423c8dde8334a232ff53c9", size = 5663954 }, - { url = "https://files.pythonhosted.org/packages/bd/0b/dab54365fcedf63e9f358c1431885478e77d6f190d65668936b12dd38057/grpcio-1.70.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfa089a734f24ee5f6880c83d043e4f46bf812fcea5181dcb3a572db1e79e01c", size = 6304323 }, - { url = "https://files.pythonhosted.org/packages/76/a8/8f965a7171ddd336ce32946e22954aa1bbc6f23f095e15dadaa70604ba20/grpcio-1.70.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f19375f0300b96c0117aca118d400e76fede6db6e91f3c34b7b035822e06c35f", size = 5910939 }, - { url = "https://files.pythonhosted.org/packages/1b/05/0bbf68be8b17d1ed6f178435a3c0c12e665a1e6054470a64ce3cb7896596/grpcio-1.70.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:7c73c42102e4a5ec76608d9b60227d917cea46dff4d11d372f64cbeb56d259d0", size = 6631405 }, - { url = "https://files.pythonhosted.org/packages/79/6a/5df64b6df405a1ed1482cb6c10044b06ec47fd28e87c2232dbcf435ecb33/grpcio-1.70.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:0a5c78d5198a1f0aa60006cd6eb1c912b4a1520b6a3968e677dbcba215fabb40", size = 6190982 }, - { url = "https://files.pythonhosted.org/packages/42/aa/aeaac87737e6d25d1048c53b8ec408c056d3ed0c922e7c5efad65384250c/grpcio-1.70.0-cp313-cp313-win32.whl", hash = "sha256:fe9dbd916df3b60e865258a8c72ac98f3ac9e2a9542dcb72b7a34d236242a5ce", size = 3598359 }, - { url = "https://files.pythonhosted.org/packages/1f/79/8edd2442d2de1431b4a3de84ef91c37002f12de0f9b577fb07b452989dbc/grpcio-1.70.0-cp313-cp313-win_amd64.whl", hash = "sha256:4119fed8abb7ff6c32e3d2255301e59c316c22d31ab812b3fbcbaf3d0d87cc68", size = 4293938 }, - { url = "https://files.pythonhosted.org/packages/9d/0e/64061c9746a2dd6e07cb0a0f3829f0a431344add77ec36397cc452541ff6/grpcio-1.70.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:4f1937f47c77392ccd555728f564a49128b6a197a05a5cd527b796d36f3387d0", size = 5231123 }, - { url = "https://files.pythonhosted.org/packages/72/9f/c93501d5f361aecee0146ab19300d5acb1c2747b00217c641f06fffbcd62/grpcio-1.70.0-cp39-cp39-macosx_10_14_universal2.whl", hash = "sha256:0cd430b9215a15c10b0e7d78f51e8a39d6cf2ea819fd635a7214fae600b1da27", size = 11467217 }, - { url = "https://files.pythonhosted.org/packages/0a/1a/980d115b701023450a304881bf3f6309f6fb15787f9b78d2728074f3bf86/grpcio-1.70.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:e27585831aa6b57b9250abaf147003e126cd3a6c6ca0c531a01996f31709bed1", size = 5710913 }, - { url = "https://files.pythonhosted.org/packages/a0/84/af420067029808f9790e98143b3dd0f943bebba434a4706755051a520c91/grpcio-1.70.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1af8e15b0f0fe0eac75195992a63df17579553b0c4af9f8362cc7cc99ccddf4", size = 6330947 }, - { url = "https://files.pythonhosted.org/packages/24/1c/e1f06a7d29a1fa5053dcaf5352a50f8e1f04855fd194a65422a9d685d375/grpcio-1.70.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbce24409beaee911c574a3d75d12ffb8c3e3dd1b813321b1d7a96bbcac46bf4", size = 5943913 }, - { url = "https://files.pythonhosted.org/packages/41/8f/de13838e4467519a50cd0693e98b0b2bcc81d656013c38a1dd7dcb801526/grpcio-1.70.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ff4a8112a79464919bb21c18e956c54add43ec9a4850e3949da54f61c241a4a6", size = 6643236 }, - { url = "https://files.pythonhosted.org/packages/ac/73/d68c745d34e43a80440da4f3d79fa02c56cb118c2a26ba949f3cfd8316d7/grpcio-1.70.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5413549fdf0b14046c545e19cfc4eb1e37e9e1ebba0ca390a8d4e9963cab44d2", size = 6199038 }, - { url = "https://files.pythonhosted.org/packages/7e/dd/991f100b8c31636b4bb2a941dbbf54dbcc55d69c722cfa038c3d017eaa0c/grpcio-1.70.0-cp39-cp39-win32.whl", hash = "sha256:b745d2c41b27650095e81dea7091668c040457483c9bdb5d0d9de8f8eb25e59f", size = 3617512 }, - { url = "https://files.pythonhosted.org/packages/4d/80/1aa2ba791207a13e314067209b48e1a0893ed8d1f43ef012e194aaa6c2de/grpcio-1.70.0-cp39-cp39-win_amd64.whl", hash = "sha256:a31d7e3b529c94e930a117b2175b2efd179d96eb3c7a21ccb0289a8ab05b645c", size = 4303506 }, +version = "1.71.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/95/aa11fc09a85d91fbc7dd405dcb2a1e0256989d67bf89fa65ae24b3ba105a/grpcio-1.71.0.tar.gz", hash = "sha256:2b85f7820475ad3edec209d3d89a7909ada16caab05d3f2e08a7e8ae3200a55c", size = 12549828 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/c5/ef610b3f988cc0cc67b765f72b8e2db06a1db14e65acb5ae7810a6b7042e/grpcio-1.71.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:c200cb6f2393468142eb50ab19613229dcc7829b5ccee8b658a36005f6669fdd", size = 5210643 }, + { url = "https://files.pythonhosted.org/packages/bf/de/c84293c961622df302c0d5d07ec6e2d4cd3874ea42f602be2df09c4ad44f/grpcio-1.71.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:b2266862c5ad664a380fbbcdbdb8289d71464c42a8c29053820ee78ba0119e5d", size = 11308962 }, + { url = "https://files.pythonhosted.org/packages/7c/38/04c9e0dc8c904570c80faa1f1349b190b63e45d6b2782ec8567b050efa9d/grpcio-1.71.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:0ab8b2864396663a5b0b0d6d79495657ae85fa37dcb6498a2669d067c65c11ea", size = 5699236 }, + { url = "https://files.pythonhosted.org/packages/95/96/e7be331d1298fa605ea7c9ceafc931490edd3d5b33c4f695f1a0667f3491/grpcio-1.71.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c30f393f9d5ff00a71bb56de4aa75b8fe91b161aeb61d39528db6b768d7eac69", size = 6339767 }, + { url = "https://files.pythonhosted.org/packages/5d/b7/7e7b7bb6bb18baf156fd4f2f5b254150dcdd6cbf0def1ee427a2fb2bfc4d/grpcio-1.71.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f250ff44843d9a0615e350c77f890082102a0318d66a99540f54769c8766ab73", size = 5943028 }, + { url = "https://files.pythonhosted.org/packages/13/aa/5fb756175995aeb47238d706530772d9a7ac8e73bcca1b47dc145d02c95f/grpcio-1.71.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6d8de076528f7c43a2f576bc311799f89d795aa6c9b637377cc2b1616473804", size = 6031841 }, + { url = "https://files.pythonhosted.org/packages/54/93/172783e01eed61f7f180617b7fa4470f504e383e32af2587f664576a7101/grpcio-1.71.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9b91879d6da1605811ebc60d21ab6a7e4bae6c35f6b63a061d61eb818c8168f6", size = 6651039 }, + { url = "https://files.pythonhosted.org/packages/6f/99/62654b220a27ed46d3313252214f4bc66261143dc9b58004085cd0646753/grpcio-1.71.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f71574afdf944e6652203cd1badcda195b2a27d9c83e6d88dc1ce3cfb73b31a5", size = 6198465 }, + { url = "https://files.pythonhosted.org/packages/68/35/96116de833b330abe4412cc94edc68f99ed2fa3e39d8713ff307b3799e81/grpcio-1.71.0-cp310-cp310-win32.whl", hash = "sha256:8997d6785e93308f277884ee6899ba63baafa0dfb4729748200fcc537858a509", size = 3620382 }, + { url = "https://files.pythonhosted.org/packages/b7/09/f32ef637e386f3f2c02effac49699229fa560ce9007682d24e9e212d2eb4/grpcio-1.71.0-cp310-cp310-win_amd64.whl", hash = "sha256:7d6ac9481d9d0d129224f6d5934d5832c4b1cddb96b59e7eba8416868909786a", size = 4280302 }, + { url = "https://files.pythonhosted.org/packages/63/04/a085f3ad4133426f6da8c1becf0749872a49feb625a407a2e864ded3fb12/grpcio-1.71.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:d6aa986318c36508dc1d5001a3ff169a15b99b9f96ef5e98e13522c506b37eef", size = 5210453 }, + { url = "https://files.pythonhosted.org/packages/b4/d5/0bc53ed33ba458de95020970e2c22aa8027b26cc84f98bea7fcad5d695d1/grpcio-1.71.0-cp311-cp311-macosx_10_14_universal2.whl", hash = "sha256:d2c170247315f2d7e5798a22358e982ad6eeb68fa20cf7a820bb74c11f0736e7", size = 11347567 }, + { url = "https://files.pythonhosted.org/packages/e3/6d/ce334f7e7a58572335ccd61154d808fe681a4c5e951f8a1ff68f5a6e47ce/grpcio-1.71.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:e6f83a583ed0a5b08c5bc7a3fe860bb3c2eac1f03f1f63e0bc2091325605d2b7", size = 5696067 }, + { url = "https://files.pythonhosted.org/packages/05/4a/80befd0b8b1dc2b9ac5337e57473354d81be938f87132e147c4a24a581bd/grpcio-1.71.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4be74ddeeb92cc87190e0e376dbc8fc7736dbb6d3d454f2fa1f5be1dee26b9d7", size = 6348377 }, + { url = "https://files.pythonhosted.org/packages/c7/67/cbd63c485051eb78663355d9efd1b896cfb50d4a220581ec2cb9a15cd750/grpcio-1.71.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd0dfbe4d5eb1fcfec9490ca13f82b089a309dc3678e2edabc144051270a66e", size = 5940407 }, + { url = "https://files.pythonhosted.org/packages/98/4b/7a11aa4326d7faa499f764eaf8a9b5a0eb054ce0988ee7ca34897c2b02ae/grpcio-1.71.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a2242d6950dc892afdf9e951ed7ff89473aaf744b7d5727ad56bdaace363722b", size = 6030915 }, + { url = "https://files.pythonhosted.org/packages/eb/a2/cdae2d0e458b475213a011078b0090f7a1d87f9a68c678b76f6af7c6ac8c/grpcio-1.71.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0fa05ee31a20456b13ae49ad2e5d585265f71dd19fbd9ef983c28f926d45d0a7", size = 6648324 }, + { url = "https://files.pythonhosted.org/packages/27/df/f345c8daaa8d8574ce9869f9b36ca220c8845923eb3087e8f317eabfc2a8/grpcio-1.71.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3d081e859fb1ebe176de33fc3adb26c7d46b8812f906042705346b314bde32c3", size = 6197839 }, + { url = "https://files.pythonhosted.org/packages/f2/2c/cd488dc52a1d0ae1bad88b0d203bc302efbb88b82691039a6d85241c5781/grpcio-1.71.0-cp311-cp311-win32.whl", hash = "sha256:d6de81c9c00c8a23047136b11794b3584cdc1460ed7cbc10eada50614baa1444", size = 3619978 }, + { url = "https://files.pythonhosted.org/packages/ee/3f/cf92e7e62ccb8dbdf977499547dfc27133124d6467d3a7d23775bcecb0f9/grpcio-1.71.0-cp311-cp311-win_amd64.whl", hash = "sha256:24e867651fc67717b6f896d5f0cac0ec863a8b5fb7d6441c2ab428f52c651c6b", size = 4282279 }, + { url = "https://files.pythonhosted.org/packages/4c/83/bd4b6a9ba07825bd19c711d8b25874cd5de72c2a3fbf635c3c344ae65bd2/grpcio-1.71.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:0ff35c8d807c1c7531d3002be03221ff9ae15712b53ab46e2a0b4bb271f38537", size = 5184101 }, + { url = "https://files.pythonhosted.org/packages/31/ea/2e0d90c0853568bf714693447f5c73272ea95ee8dad107807fde740e595d/grpcio-1.71.0-cp312-cp312-macosx_10_14_universal2.whl", hash = "sha256:b78a99cd1ece4be92ab7c07765a0b038194ded2e0a26fd654591ee136088d8d7", size = 11310927 }, + { url = "https://files.pythonhosted.org/packages/ac/bc/07a3fd8af80467390af491d7dc66882db43884128cdb3cc8524915e0023c/grpcio-1.71.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:dc1a1231ed23caac1de9f943d031f1bc38d0f69d2a3b243ea0d664fc1fbd7fec", size = 5654280 }, + { url = "https://files.pythonhosted.org/packages/16/af/21f22ea3eed3d0538b6ef7889fce1878a8ba4164497f9e07385733391e2b/grpcio-1.71.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6beeea5566092c5e3c4896c6d1d307fb46b1d4bdf3e70c8340b190a69198594", size = 6312051 }, + { url = "https://files.pythonhosted.org/packages/49/9d/e12ddc726dc8bd1aa6cba67c85ce42a12ba5b9dd75d5042214a59ccf28ce/grpcio-1.71.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5170929109450a2c031cfe87d6716f2fae39695ad5335d9106ae88cc32dc84c", size = 5910666 }, + { url = "https://files.pythonhosted.org/packages/d9/e9/38713d6d67aedef738b815763c25f092e0454dc58e77b1d2a51c9d5b3325/grpcio-1.71.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5b08d03ace7aca7b2fadd4baf291139b4a5f058805a8327bfe9aece7253b6d67", size = 6012019 }, + { url = "https://files.pythonhosted.org/packages/80/da/4813cd7adbae6467724fa46c952d7aeac5e82e550b1c62ed2aeb78d444ae/grpcio-1.71.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f903017db76bf9cc2b2d8bdd37bf04b505bbccad6be8a81e1542206875d0e9db", size = 6637043 }, + { url = "https://files.pythonhosted.org/packages/52/ca/c0d767082e39dccb7985c73ab4cf1d23ce8613387149e9978c70c3bf3b07/grpcio-1.71.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:469f42a0b410883185eab4689060a20488a1a0a00f8bbb3cbc1061197b4c5a79", size = 6186143 }, + { url = "https://files.pythonhosted.org/packages/00/61/7b2c8ec13303f8fe36832c13d91ad4d4ba57204b1c723ada709c346b2271/grpcio-1.71.0-cp312-cp312-win32.whl", hash = "sha256:ad9f30838550695b5eb302add33f21f7301b882937460dd24f24b3cc5a95067a", size = 3604083 }, + { url = "https://files.pythonhosted.org/packages/fd/7c/1e429c5fb26122055d10ff9a1d754790fb067d83c633ff69eddcf8e3614b/grpcio-1.71.0-cp312-cp312-win_amd64.whl", hash = "sha256:652350609332de6dac4ece254e5d7e1ff834e203d6afb769601f286886f6f3a8", size = 4272191 }, + { url = "https://files.pythonhosted.org/packages/04/dd/b00cbb45400d06b26126dcfdbdb34bb6c4f28c3ebbd7aea8228679103ef6/grpcio-1.71.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:cebc1b34ba40a312ab480ccdb396ff3c529377a2fce72c45a741f7215bfe8379", size = 5184138 }, + { url = "https://files.pythonhosted.org/packages/ed/0a/4651215983d590ef53aac40ba0e29dda941a02b097892c44fa3357e706e5/grpcio-1.71.0-cp313-cp313-macosx_10_14_universal2.whl", hash = "sha256:85da336e3649a3d2171e82f696b5cad2c6231fdd5bad52616476235681bee5b3", size = 11310747 }, + { url = "https://files.pythonhosted.org/packages/57/a3/149615b247f321e13f60aa512d3509d4215173bdb982c9098d78484de216/grpcio-1.71.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:f9a412f55bb6e8f3bb000e020dbc1e709627dcb3a56f6431fa7076b4c1aab0db", size = 5653991 }, + { url = "https://files.pythonhosted.org/packages/ca/56/29432a3e8d951b5e4e520a40cd93bebaa824a14033ea8e65b0ece1da6167/grpcio-1.71.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47be9584729534660416f6d2a3108aaeac1122f6b5bdbf9fd823e11fe6fbaa29", size = 6312781 }, + { url = "https://files.pythonhosted.org/packages/a3/f8/286e81a62964ceb6ac10b10925261d4871a762d2a763fbf354115f9afc98/grpcio-1.71.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c9c80ac6091c916db81131d50926a93ab162a7e97e4428ffc186b6e80d6dda4", size = 5910479 }, + { url = "https://files.pythonhosted.org/packages/35/67/d1febb49ec0f599b9e6d4d0d44c2d4afdbed9c3e80deb7587ec788fcf252/grpcio-1.71.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:789d5e2a3a15419374b7b45cd680b1e83bbc1e52b9086e49308e2c0b5bbae6e3", size = 6013262 }, + { url = "https://files.pythonhosted.org/packages/a1/04/f9ceda11755f0104a075ad7163fc0d96e2e3a9fe25ef38adfc74c5790daf/grpcio-1.71.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:1be857615e26a86d7363e8a163fade914595c81fec962b3d514a4b1e8760467b", size = 6643356 }, + { url = "https://files.pythonhosted.org/packages/fb/ce/236dbc3dc77cf9a9242adcf1f62538734ad64727fabf39e1346ad4bd5c75/grpcio-1.71.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:a76d39b5fafd79ed604c4be0a869ec3581a172a707e2a8d7a4858cb05a5a7637", size = 6186564 }, + { url = "https://files.pythonhosted.org/packages/10/fd/b3348fce9dd4280e221f513dd54024e765b21c348bc475516672da4218e9/grpcio-1.71.0-cp313-cp313-win32.whl", hash = "sha256:74258dce215cb1995083daa17b379a1a5a87d275387b7ffe137f1d5131e2cfbb", size = 3601890 }, + { url = "https://files.pythonhosted.org/packages/be/f8/db5d5f3fc7e296166286c2a397836b8b042f7ad1e11028d82b061701f0f7/grpcio-1.71.0-cp313-cp313-win_amd64.whl", hash = "sha256:22c3bc8d488c039a199f7a003a38cb7635db6656fa96437a8accde8322ce2366", size = 4273308 }, + { url = "https://files.pythonhosted.org/packages/c8/e3/22cb31bbb42de95b35b8f0fb691d8da6e0579e658bb37b86efe2999c702b/grpcio-1.71.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:c6a0a28450c16809f94e0b5bfe52cabff63e7e4b97b44123ebf77f448534d07d", size = 5210667 }, + { url = "https://files.pythonhosted.org/packages/f6/5e/4970fb231e57aad8f41682292343551f58fec5c7a07e261294def3cb8bb6/grpcio-1.71.0-cp39-cp39-macosx_10_14_universal2.whl", hash = "sha256:a371e6b6a5379d3692cc4ea1cb92754d2a47bdddeee755d3203d1f84ae08e03e", size = 11336193 }, + { url = "https://files.pythonhosted.org/packages/7f/a4/dd71a5540d5e86526b39c23060b7d3195f3144af3fe291947b30c3fcbdad/grpcio-1.71.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:39983a9245d37394fd59de71e88c4b295eb510a3555e0a847d9965088cdbd033", size = 5699572 }, + { url = "https://files.pythonhosted.org/packages/d0/69/3e3522d7c2c525a60f4bbf811891925ac7594b768b1ac8e6c9d955a72c45/grpcio-1.71.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9182e0063112e55e74ee7584769ec5a0b4f18252c35787f48738627e23a62b97", size = 6339648 }, + { url = "https://files.pythonhosted.org/packages/32/f2/9d864ca8f3949bf507db9c6a18532c150fc03910dd3d3e17fd4bc5d3e462/grpcio-1.71.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693bc706c031aeb848849b9d1c6b63ae6bcc64057984bb91a542332b75aa4c3d", size = 5943469 }, + { url = "https://files.pythonhosted.org/packages/9b/58/aec6ce541b7fb2a9efa15d968db5897c2700bd2da6fb159c1d27515f120c/grpcio-1.71.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:20e8f653abd5ec606be69540f57289274c9ca503ed38388481e98fa396ed0b41", size = 6030255 }, + { url = "https://files.pythonhosted.org/packages/f7/4f/7356b7edd1f622d49e72faaea75a5d6ac7bdde8f4c14dd19bcfbafd56f4c/grpcio-1.71.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8700a2a57771cc43ea295296330daaddc0d93c088f0a35cc969292b6db959bf3", size = 6651120 }, + { url = "https://files.pythonhosted.org/packages/54/10/c1bb13137dc8d1637e2373a85904aa57991e65ef429791bfb8a64a60d5bd/grpcio-1.71.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d35a95f05a8a2cbe8e02be137740138b3b2ea5f80bd004444e4f9a1ffc511e32", size = 6197989 }, + { url = "https://files.pythonhosted.org/packages/0e/dc/0fd537831501df786bc2f9ec5ac1724528a344cd146f6335f7991763eb2b/grpcio-1.71.0-cp39-cp39-win32.whl", hash = "sha256:f9c30c464cb2ddfbc2ddf9400287701270fdc0f14be5f08a1e3939f1e749b455", size = 3620173 }, + { url = "https://files.pythonhosted.org/packages/97/22/b1535291aaa9c046c79a9dc4db125f6b9974d41de154221b72da4e8a005c/grpcio-1.71.0-cp39-cp39-win_amd64.whl", hash = "sha256:63e41b91032f298b3e973b3fa4093cbbc620c875e2da7b93e249d4728b54559a", size = 4280941 }, ] [[package]] name = "grpcio-status" -version = "1.70.0" +version = "1.71.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "grpcio" }, { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/d1/2397797c810020eac424e1aac10fbdc5edb6b9b4ad6617e0ed53ca907653/grpcio_status-1.70.0.tar.gz", hash = "sha256:0e7b42816512433b18b9d764285ff029bde059e9d41f8fe10a60631bd8348101", size = 13681 } +sdist = { url = "https://files.pythonhosted.org/packages/d7/53/a911467bece076020456401f55a27415d2d70d3bc2c37af06b44ea41fc5c/grpcio_status-1.71.0.tar.gz", hash = "sha256:11405fed67b68f406b3f3c7c5ae5104a79d2d309666d10d61b152e91d28fb968", size = 13669 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/34/49e558040e069feebac70cdd1b605f38738c0277ac5d38e2ce3d03e1b1ec/grpcio_status-1.70.0-py3-none-any.whl", hash = "sha256:fc5a2ae2b9b1c1969cc49f3262676e6854aa2398ec69cb5bd6c47cd501904a85", size = 14429 }, + { url = "https://files.pythonhosted.org/packages/ad/d6/31fbc43ff097d8c4c9fc3df741431b8018f67bf8dfbe6553a555f6e5f675/grpcio_status-1.71.0-py3-none-any.whl", hash = "sha256:843934ef8c09e3e858952887467f8256aac3910c55f077a359a65b2b3cde3e68", size = 14424 }, ] [[package]] @@ -2060,15 +2065,15 @@ wheels = [ [[package]] name = "psycopg" -version = "3.2.5" +version = "3.2.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.13'" }, { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0e/cf/dc1a4d45e3c6222fe272a245c5cea9a969a7157639da606ac7f2ab5de3a1/psycopg-3.2.5.tar.gz", hash = "sha256:f5f750611c67cb200e85b408882f29265c66d1de7f813add4f8125978bfd70e8", size = 156158 } +sdist = { url = "https://files.pythonhosted.org/packages/67/97/eea08f74f1c6dd2a02ee81b4ebfe5b558beb468ebbd11031adbf58d31be0/psycopg-3.2.6.tar.gz", hash = "sha256:16fa094efa2698f260f2af74f3710f781e4a6f226efe9d1fd0c37f384639ed8a", size = 156322 } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/f3/14a1370b1449ca875d5e353ef02cb9db6b70bd46ec361c236176837c0be1/psycopg-3.2.5-py3-none-any.whl", hash = "sha256:b782130983e5b3de30b4c529623d3687033b4dafa05bb661fc6bf45837ca5879", size = 198749 }, + { url = "https://files.pythonhosted.org/packages/d7/7d/0ba52deff71f65df8ec8038adad86ba09368c945424a9bd8145d679a2c6a/psycopg-3.2.6-py3-none-any.whl", hash = "sha256:f3ff5488525890abb0566c429146add66b329e20d6d4835662b920cbbf90ac58", size = 199077 }, ] [package.optional-dependencies] @@ -2081,64 +2086,64 @@ pool = [ [[package]] name = "psycopg-binary" -version = "3.2.5" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/30/af3806081adc75b5a8addde839d4c6b171a8c5d0d07dd92de20ca4dd6717/psycopg_binary-3.2.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a82211a43372cba9b1555a110e84e679deec2dc9463ae4c736977dad99dca5ed", size = 3868990 }, - { url = "https://files.pythonhosted.org/packages/31/77/31968655db2efe83c519e6296ff3a85a0c9e50432e0c11c8ffae1b404870/psycopg_binary-3.2.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e7d215a43343d91ba08301865f059d9518818d66a222a85fb425e4156716f5a6", size = 3938253 }, - { url = "https://files.pythonhosted.org/packages/b5/d7/c898cd7d5c672d1c16b10dfde6ab220a6d295ff136711bf8ebcd1bebe91e/psycopg_binary-3.2.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f893c0ed3d5c7b83b76b1f8f7d3ca5a03e38bcd3cab5d65b5c25a0d1064aca4", size = 4523098 }, - { url = "https://files.pythonhosted.org/packages/98/d7/84517d0f62ddb10ca15254b6a63596f0e47ebd462b3ed30473b191a2a57f/psycopg_binary-3.2.5-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d10ce4c39eb9631381a0c3792727946a4391e843625a7ee9579ac6bb11495a5", size = 4329658 }, - { url = "https://files.pythonhosted.org/packages/3d/65/9c6addcf00ba80d2355ffa825d6537d60313c24d4b6db438f631f9ff0ac7/psycopg_binary-3.2.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a602d9fdb567cca090ca19ac3ebf10219065be2a4f8cf9eb8356cffb5a7ab1d", size = 4575351 }, - { url = "https://files.pythonhosted.org/packages/a5/90/9f2c41b3b42d8cd8b9866f0bbd27a5796a1ca8042a1a019b39a6645df523/psycopg_binary-3.2.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c37eb3be7a6be93f4925ccf52bbfa60244da6c63201770a709dd81a3d2d08534", size = 4287136 }, - { url = "https://files.pythonhosted.org/packages/20/e6/2476e30ff4b02588799dc6d0cff244cea448f9a2a80e37b48c39a64a81be/psycopg_binary-3.2.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7d5f1bfc848a94e0d63fe693adee4f88bd9e5c415ecb4c9c17d2d44eba6795a6", size = 3872875 }, - { url = "https://files.pythonhosted.org/packages/ba/bc/93272521e571df3a6ce85553e2eba424c7abb2ded006b8d6643c2a3cc0f2/psycopg_binary-3.2.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b5e0acbc991472188c9df40eb56d8a97ad3ad00d4de560b8b74bdc2d94041a8f", size = 3341000 }, - { url = "https://files.pythonhosted.org/packages/a2/d7/930a127d2b4817445a08153a1b203655d3da52e79e4c66843d8bd7e3643f/psycopg_binary-3.2.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d4e0c1b1aa5283f6d9a384ffc7a8400d25386bb98fdb9bddae446e4ef4da7366", size = 3439711 }, - { url = "https://files.pythonhosted.org/packages/aa/4a/73ea25870d0b4cac60ad768e6cdf4014e7a44036ec29d3820876c62efea0/psycopg_binary-3.2.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c3c5fa3d4fa0a651cefab391b783f89bc5e331afa0a4e93c9b16141993fa05c8", size = 3464993 }, - { url = "https://files.pythonhosted.org/packages/55/1d/790223b15283904759ef48279dd7201dc4a9d088c5196f7b529a52c5b40d/psycopg_binary-3.2.5-cp310-cp310-win_amd64.whl", hash = "sha256:7efe6c732fd2d7e22d72dc4f7cf9b644020adacfff61b0a8a151343da8e661c0", size = 2791126 }, - { url = "https://files.pythonhosted.org/packages/27/ac/201a9bcfe4a2ae0cc1999c55dff9a2da8daf829e9baca103045ed1c41876/psycopg_binary-3.2.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:393ab353196d364858b47317d27804ecc58ab56dbde32217bd67f0f2f2980662", size = 3876607 }, - { url = "https://files.pythonhosted.org/packages/4a/ef/2d7722bee81c0a2619b8748070cea8ec299979f677479554e299a864d171/psycopg_binary-3.2.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71d82dbc7c6c7f5746468e7992e5483aa45b12250d78d220a2431ab88795825c", size = 3942789 }, - { url = "https://files.pythonhosted.org/packages/f6/dc/a1fe4b61d0f614ab6283a9c5a35747b8fd2b72d7c21f201d6772394c0c09/psycopg_binary-3.2.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39e2cd10bf15442d95c3f48376b25dc33360418ea6c3c05884d8bf42407768c0", size = 4519457 }, - { url = "https://files.pythonhosted.org/packages/2c/5a/bbf5ec9fea9cc81c77d37957777d9b15492884437929fc634fc6dc16aade/psycopg_binary-3.2.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7623659d44a6aa032be4a066c658ba45009d768c2481526fbef7c609702af116", size = 4324376 }, - { url = "https://files.pythonhosted.org/packages/4b/17/c785b4a795860bf67f0dc1e03129cb8e9a3be45d21049ccbffeae9c576e9/psycopg_binary-3.2.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8cd9ebf335262e864d740f9dad3f672f61162cc0d4825a5eb5cf50df334a688f", size = 4578729 }, - { url = "https://files.pythonhosted.org/packages/e8/bb/c7bcb17b60040777fb26efd2db5f61bc84453e380114be480ebbedc20829/psycopg_binary-3.2.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc8bc40d82d1ee8dec136e10707c7f3147a6322fd8014e174a0f3446fb793649", size = 4281876 }, - { url = "https://files.pythonhosted.org/packages/2c/a2/ea6d36644fbccd462f4e3bd79149e94b284d4f90f24671bd50ce5e9e9dc5/psycopg_binary-3.2.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:11e3ed8b94c750d54fc3e4502dd930fb0fd041629845b6a7ce089873ac9756b0", size = 3871313 }, - { url = "https://files.pythonhosted.org/packages/09/38/b32728e13d65bac03d556f730af02509310f451ee873f8662bfc40b3f6ef/psycopg_binary-3.2.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:48fcb12a0a72fdfe4102bdb1252a7366e8d73a2c89fe6ce5923be890de367c2f", size = 3334458 }, - { url = "https://files.pythonhosted.org/packages/ca/69/fcd3d845ff2a39fad7783249c8add4966cb12a50f40df3cbcd743fa24c10/psycopg_binary-3.2.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:51a96d9fe51f718912b4a0089784f1f32d800217499fd0f0095b888506aba4c5", size = 3432832 }, - { url = "https://files.pythonhosted.org/packages/f6/9c/90baa71833da03c08ff9d4e12a4bcebfb15c1b0259738f7d3970c2292ab9/psycopg_binary-3.2.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:eb8293d66c6a4ddc72fceb7ad0e111cb196cc394954ae0f9b63c251d97f1b00e", size = 3463280 }, - { url = "https://files.pythonhosted.org/packages/4f/42/f40ca24a89de58a47e54f82d7124d7dcf996781c89a5ed7bfe722e96da55/psycopg_binary-3.2.5-cp311-cp311-win_amd64.whl", hash = "sha256:5b81342e139ddccfa417832089cd213bd4beacd7a1462ca4019cafe71682d177", size = 2794275 }, - { url = "https://files.pythonhosted.org/packages/84/eb/175a81bfd26734eeaaa39b651bc44a3c5e3fce1190963ace21e428c4d2ee/psycopg_binary-3.2.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a4321ee8180982d70458d3e8378e31448901bf0ee40fe0d410a87413578f4098", size = 3857964 }, - { url = "https://files.pythonhosted.org/packages/ca/2e/0d57047372c3dd31becc1a48185862d7e6714ffbdc1401742a32f2294f79/psycopg_binary-3.2.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2cc86657c05e09c701e97f87132cd58e0d55381dd568520081ac1fe7580a9bbb", size = 3940056 }, - { url = "https://files.pythonhosted.org/packages/c5/2f/339a18b28787d33fe892d1ae1fbaa83739c6274327cbf9ada4158322ad9d/psycopg_binary-3.2.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5244bebaa9734a236b7157fb57c065b6c0f2344281916187bd73f951df1899e0", size = 4499081 }, - { url = "https://files.pythonhosted.org/packages/42/21/32d7115b2cbd87d043ad494254fd7c4c8652ac3c32f49bb571fd8111caf3/psycopg_binary-3.2.5-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:21b839f9bfd77ed074f7f71464a43f453400c57d038a0ba0716329a28e335897", size = 4307502 }, - { url = "https://files.pythonhosted.org/packages/00/67/e99b58f616dd02c5e52c179b3df047d9683a9f699993cb1795ee435db598/psycopg_binary-3.2.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7376b13504396da9678b646f5338462347da01286b2a688a0d8493ec764683a2", size = 4547821 }, - { url = "https://files.pythonhosted.org/packages/0d/64/9d13ee0fed78a47c506a93d1e67ee53cc7ffd75c1f5885b59d17810fe5cd/psycopg_binary-3.2.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:473f6827cf1faf3924eb77146d1e85126a1b5e48a88053b8d8b78dd29e971d78", size = 4259849 }, - { url = "https://files.pythonhosted.org/packages/ea/f2/172b6ebcd60a1a86f5ce1a539cfb93ffbe42fc9bc7ab2e1ed79e99a75d71/psycopg_binary-3.2.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:28bd5cb2324567e5e70f07fe1d646398d6b0e210e28b49be0e69593590a59980", size = 3847280 }, - { url = "https://files.pythonhosted.org/packages/0f/51/9cd26c6b862d499b4b25ea173ae6e21c9d460ddce6b09cbe9501dff66211/psycopg_binary-3.2.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:48f97936145cb7de18b95d85670b2d3e2c257277263272be05815b74fb0ef195", size = 3320262 }, - { url = "https://files.pythonhosted.org/packages/51/7d/2dac61ff16476e77c6ce0a49a30b130e2ba6ad08c83c4950591b4bc49cf2/psycopg_binary-3.2.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e6f2bef5aed021fbdf46323d3cd8847bf960efb56394698644a8ee2306f8892", size = 3400254 }, - { url = "https://files.pythonhosted.org/packages/45/67/bd36932c24f96dc1bc21fb18b1bdebcda7b9791067f7151a1c5dc1193e6b/psycopg_binary-3.2.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d2e57a1d06f3968e49e948ba374f21a7d8dcf44f37d582a4aeddeb7c85ce239", size = 3438916 }, - { url = "https://files.pythonhosted.org/packages/00/ab/882b861cfcf83d7faffe583e1e092117cd66eacc86fb4517d27973e52f35/psycopg_binary-3.2.5-cp312-cp312-win_amd64.whl", hash = "sha256:2cbb8649cfdacbd14e17f5ab78edc52d33350013888518c73e90c5d17d7bea55", size = 2782504 }, - { url = "https://files.pythonhosted.org/packages/81/3d/26483d75e1a5daa93cbb47ee7cde96fac07a9b026058b036b00a04f5c012/psycopg_binary-3.2.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2dbaf32c18c0d11c4480016b89c9c5cadb7b64c55de7f181d222b189bd13a558", size = 3852616 }, - { url = "https://files.pythonhosted.org/packages/90/cb/542bd0eab110ed2ddcc02cbe8f5df0afe3e86bd843c533fc6a795ffd7c0f/psycopg_binary-3.2.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ca5e36a3e7480a5c09aed99ecdb8e6554b21485c3b064297fe77f7b1b5806106", size = 3936563 }, - { url = "https://files.pythonhosted.org/packages/e1/43/2b347816983a5b0f1cc3e608eae4650422476187e047e574981081bcf9ec/psycopg_binary-3.2.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9abe093a303e25ac58774a11241150e2fe2947358d1ca12521ad03c90b131060", size = 4499166 }, - { url = "https://files.pythonhosted.org/packages/3f/0d/d7ac5289dfa1163b0fcce9aeb848a7f4499d7b3ef34f1de565d0ba9a51bd/psycopg_binary-3.2.5-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a91b0e096fdfeb52d86bb8f5ee25dc22483d6960af9b968e6b381a8ec5bfbf82", size = 4311647 }, - { url = "https://files.pythonhosted.org/packages/7b/a2/b238d91cbbc5953ff6910737b5a598cc4d5aad84453052005891cec329b3/psycopg_binary-3.2.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3eb71cfc35116e4a8e336b7e785f1fe06ca23b4516a48ea91facd577d1a1fdf6", size = 4547848 }, - { url = "https://files.pythonhosted.org/packages/d7/33/e78ae02d8f23753af2884303370b914a5d172f76fed13bfde380ec473f53/psycopg_binary-3.2.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98efaedf2bf79f4d563ca039a57a025b72847bd80568f54709cc39fc1404772c", size = 4261732 }, - { url = "https://files.pythonhosted.org/packages/44/9a/1745ff5c6e4c715aa71f3da3f393022ec0c7cc972fa0ee7296df8871d6d6/psycopg_binary-3.2.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ba4a610882171bdaae0779f14e0ff45f3ee271fd2dbf16cdadfc81bd67323232", size = 3850803 }, - { url = "https://files.pythonhosted.org/packages/7b/1c/933fb04560e7bcf5f24c632f9381e8700dcf8462adcd32eabd6192480d66/psycopg_binary-3.2.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1494827c43265820d5dcdc6f8086521bc7dd04b9da8831310978a788cdcd2e62", size = 3320315 }, - { url = "https://files.pythonhosted.org/packages/5d/36/111e2db9c3ff5123da4ce814aa9462d242a7c393f132a4005ec427e09903/psycopg_binary-3.2.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7a94020821723a6a210206ddb458001f3ed27e1e6a0555b9422bebf7ead8ff37", size = 3403225 }, - { url = "https://files.pythonhosted.org/packages/90/04/246efe587463d13b015202ab344e12e8e30ea9ba90ca952def0469b95a9e/psycopg_binary-3.2.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:659f2c675d478b1bc01b95a8d3ded74fa939b370e71ffbecd496f617b215eb05", size = 3440446 }, - { url = "https://files.pythonhosted.org/packages/92/75/5e15e7a6ad4c6a00fe1a28fe704310dc7f7b26dbd5e6e14c817e7899451b/psycopg_binary-3.2.5-cp313-cp313-win_amd64.whl", hash = "sha256:6b581da13126b8715c0c0585cd37ce934c9864d44b2a4019f5487c0b943275e6", size = 2783095 }, - { url = "https://files.pythonhosted.org/packages/bd/8b/12aa4b412fd2f3d522b9dc3170e198377dde3d542cd26dc1733355bff3ef/psycopg_binary-3.2.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d2253189aa4cca0a425e2ca896d1a29760cd3a2b10ab12194e4e827a566505c", size = 3870116 }, - { url = "https://files.pythonhosted.org/packages/87/f6/7e5a3ace6b0a2ffa4a3a4a4753048d73b63e4ce2c1863257068dc35b085f/psycopg_binary-3.2.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4914dc60f2fddf0884464985e31d775aa865b665471fa156ec2f56fa72a1a097", size = 3939148 }, - { url = "https://files.pythonhosted.org/packages/7f/04/477345fd694aeec1e74992c71c36427c0393372cdce65348736a15021959/psycopg_binary-3.2.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efb878d08dd49d7d9d18512e791b418a1171d08f935475eec98305f0886b7c14", size = 4525094 }, - { url = "https://files.pythonhosted.org/packages/37/41/97915cd1872b14b85a89fe4719ab44d483c0b836837a07357bc0513309e5/psycopg_binary-3.2.5-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62965045cc0fe3dc5dd55d39779620b225ef75962825c7b1b533033cb91810bd", size = 4330352 }, - { url = "https://files.pythonhosted.org/packages/e3/71/4d3a3a66b7cc4fee6356362bbee9cc0f36cdf20a76c6cf073a24341e3d4d/psycopg_binary-3.2.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d22a15e45f43d36ed35aed4d5261f8ef6ab7d9b84ee075576ca56ae03b9e0aa", size = 4577922 }, - { url = "https://files.pythonhosted.org/packages/c6/29/c8a81bab9b9335edeccdd1412290142fe38a32033b135f583ae2a03c9eb3/psycopg_binary-3.2.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:375149006e21d58ed8aba640e0295d8e636043064c433af94eb58057f9b96877", size = 4286333 }, - { url = "https://files.pythonhosted.org/packages/e0/f9/0b450b8ce0afdd913d2d413aea2e1ed9e6e42fdd9c8b25fa5ff6742df80f/psycopg_binary-3.2.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:60d0f36a42a822e43c4c7472df8a0c980c0f32e5d74ed871333c423a4e942f11", size = 3875267 }, - { url = "https://files.pythonhosted.org/packages/fa/75/29a613ea7f42771dbf5a5f0c15e5ccec7147b6ad038a96b19ba783e9f4d7/psycopg_binary-3.2.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:b6b5a4542aca4095ab35e184517cb0d18895ba4b6661c92865b431fa7b7974d8", size = 3339629 }, - { url = "https://files.pythonhosted.org/packages/d3/ce/40fbb009e695277bf71de8d80f672cd7e5f344eea779a8df1e6344151891/psycopg_binary-3.2.5-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:605f70e267222d567fc40de7813ee3fb29f8145a1a20aa6fd3dc62baba9312f1", size = 3438814 }, - { url = "https://files.pythonhosted.org/packages/f7/cc/bb396b7a4fd68039efc674c6425ae1636adc2a2f16711f6766f408f01725/psycopg_binary-3.2.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2b053eae21dd3a6828b516a1171e1274d1af5f7c07d2d9a8f597f2e19c732168", size = 3464814 }, - { url = "https://files.pythonhosted.org/packages/44/2a/e445be603db86abf2eca7641c634eb907a70c0c35e3c0202519897ca3db4/psycopg_binary-3.2.5-cp39-cp39-win_amd64.whl", hash = "sha256:23a1dc61abb8f7cc702472ab29554167a9421842f976c201ceb3b722c0299769", size = 2792655 }, +version = "3.2.6" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/7b/48afdcb14bf828c4006f573845fbbd98df701bff9043fbb0b8caab261b6f/psycopg_binary-3.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1b639acb3e24243c23f75700bf6e3af7b76da92523ec7c3196a13aaf0b578453", size = 3868985 }, + { url = "https://files.pythonhosted.org/packages/de/45/9e777c61ef3ac5e7fb42618afbd9f41464c1c396ec85c79c48086ace437a/psycopg_binary-3.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1b5c359173726b38d7acbb9f73270f269591d8031d099c1a70dd3f3d22b0e8a8", size = 3938244 }, + { url = "https://files.pythonhosted.org/packages/d6/93/e48962aca19af1f3d2cb0c2ff890ca305c51d1759a2e89c90a527228cf1d/psycopg_binary-3.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3434efe7c00f505f4c1e531519dac6c701df738ba7a1328eac81118d80019132", size = 4523096 }, + { url = "https://files.pythonhosted.org/packages/fe/52/21f4a9bb7123e07e06a712338eb6cc5794a23a56813deb4a8cd3de8ec91c/psycopg_binary-3.2.6-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bca8d9643191b13193940bbf84d51ac5a747e965c230177258fb02b8043fb7a", size = 4329659 }, + { url = "https://files.pythonhosted.org/packages/9e/72/8da1c98b4e0d4c3649f037101b70ae52e4f821597919dabc72c889e60ca9/psycopg_binary-3.2.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55fa40f11d37e6e5149a282a5fd7e0734ce55c623673bfba638480914fd1414c", size = 4575359 }, + { url = "https://files.pythonhosted.org/packages/83/5a/a85c98a5b2b3f771d7478ac0081b48749d4c07ce41d51f89f592f87cfbeb/psycopg_binary-3.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0690ac1061c655b1bcbe9284d07bf5276bc9c0d788a6c74aaf3b042e64984b83", size = 4287138 }, + { url = "https://files.pythonhosted.org/packages/b0/c3/0abafd3f300e5ff952dd9b3be95b4e2527ae1e2ea7fd7a7421e6bc1c0e37/psycopg_binary-3.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e9a4a9967ff650d2821d5fad6bec7b15f4c2072603e9fa3f89a39f351ade1fd3", size = 3872142 }, + { url = "https://files.pythonhosted.org/packages/0f/16/029aa400c4b7f4b7042307d8a854768463a65326d061ad2145f7b3989ca5/psycopg_binary-3.2.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d6f2894cc7aee8a15fe591e8536911d9c015cb404432cf7bdac2797e54cb2ba8", size = 3340033 }, + { url = "https://files.pythonhosted.org/packages/cd/a1/28e86b832d696ba5fd79c4d704b8ca46b827428f7ea063063ca280a678a4/psycopg_binary-3.2.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:05560c81312d7c2bee95a9860cd25198677f2320fb4a3527bc04e8cae7fcfb64", size = 3438823 }, + { url = "https://files.pythonhosted.org/packages/93/31/73546c999725b397bb7e7fd55f83a9c78787c6fe7fe457e4179d19a115dc/psycopg_binary-3.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4269cd23a485d6dd6eb6b10841c94551a53091cf0b1b6d5247a6a341f53f0d95", size = 3464031 }, + { url = "https://files.pythonhosted.org/packages/85/38/957bd4bdde976c9a38d61896bf9d2c8f5752b98d8f4d879a7902588a8583/psycopg_binary-3.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:7942f35a6f314608720116bcd9de240110ceadffd2ac5c34f68f74a31e52e46a", size = 2792159 }, + { url = "https://files.pythonhosted.org/packages/5a/71/5bfa1ffc4d59f0454b114ce0d017eca269b079ca2753a96302c2117067c7/psycopg_binary-3.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7afe181f6b3eb714362e9b6a2dc2a589bff60471a1d8639fd231a4e426e01523", size = 3876608 }, + { url = "https://files.pythonhosted.org/packages/7e/07/1724d842b876af7bef442f0853d6cbf951264229414e4d0a57b8e3787847/psycopg_binary-3.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34bb0fceba0773dc0bfb53224bb2c0b19dc97ea0a997a223615484cf02cae55c", size = 3942785 }, + { url = "https://files.pythonhosted.org/packages/09/51/a251a356f10c7947bcc2285ebf1541e1c2d851b8db20eb8f29ed3a5974bf/psycopg_binary-3.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54120122d2779dcd307f49e1f921d757fe5dacdced27deab37f277eef0c52a5b", size = 4519448 }, + { url = "https://files.pythonhosted.org/packages/6e/cf/0c92ab1270664a1341e52f5794ecc636b1f4ac67bf1743075091795151f8/psycopg_binary-3.2.6-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:816aa556f63b2303e66ba6c8888a8b3f3e6e4e47049ec7a4d62c84ac60b091ca", size = 4324382 }, + { url = "https://files.pythonhosted.org/packages/bf/2b/6921bd4a57fe19d4618798a8a8648e1a516c92563c37b2073639fffac5d5/psycopg_binary-3.2.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d19a0ba351eda9a59babf8c7c9d89c7bbc5b26bf096bc349b096bd0dd2482088", size = 4578720 }, + { url = "https://files.pythonhosted.org/packages/5c/30/1034d164e2be09f650a86eccc93625e51568e307c855bf6f94759c298303/psycopg_binary-3.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6e197e01290ef818a092c877025fc28096adbb6d0743e313491a21aab31bd96", size = 4281871 }, + { url = "https://files.pythonhosted.org/packages/c4/d0/67fdf0174c334a9a85a9672590d7da83e85d9cedfc35f473a557e310a1ca/psycopg_binary-3.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:274794b4b29ef426e09086404446b61a146f5e756da71366c5a6d57abec31f7d", size = 3870582 }, + { url = "https://files.pythonhosted.org/packages/9f/4e/3a4fd2d1fd715b11e7287023edde916e1174b58b37873c531f782a49803b/psycopg_binary-3.2.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:69845bdc0db519e1dfc27932cd3d5b1ecb3f72950af52a1987508ab0b52b3b55", size = 3334464 }, + { url = "https://files.pythonhosted.org/packages/4a/22/90a8032276fa5b215ce26cefb44abafa8fb09de396c6dc6f62a5e53fe2ad/psycopg_binary-3.2.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:66c3bed2caf0d1cabcb9365064de183b5209a7cbeaa131e79e68f350c9c963c2", size = 3431945 }, + { url = "https://files.pythonhosted.org/packages/e7/b0/e547e9a851ab19c79869c1d84a533e225d284e70c222720fed4529fcda60/psycopg_binary-3.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e3ae3201fe85c7f901349a2cf52f02ceca4cb97a5e2e2ac8b8a1c9a6eb747bed", size = 3463278 }, + { url = "https://files.pythonhosted.org/packages/e7/ce/e555bd8dd6fce8b34bbc3856125600f0842c85a8364702ebe0dc39372087/psycopg_binary-3.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:58f443b4df2adb59937c96775fadf4967f93d952fbcc82394446985faec11041", size = 2795094 }, + { url = "https://files.pythonhosted.org/packages/a3/c7/220b1273f0befb2cd9fe83d379b3484ae029a88798a90bc0d36f10bea5df/psycopg_binary-3.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f27a46ff0497e882e8c0286e8833c785b4d1a80f23e1bf606f4c90e5f9f3ce75", size = 3857986 }, + { url = "https://files.pythonhosted.org/packages/8a/d8/30176532826cf87c608a6f79dd668bf9aff0cdf8eb80209eddf4c5aa7229/psycopg_binary-3.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b30ee4821ded7de48b8048b14952512588e7c5477b0a5965221e1798afba61a1", size = 3940060 }, + { url = "https://files.pythonhosted.org/packages/54/7c/fa7cd1f057f33f7ae483d6bc5a03ec6eff111f8aa5c678d9aaef92705247/psycopg_binary-3.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e57edf3b1f5427f39660225b01f8e7b97f5cfab132092f014bf1638bc85d81d2", size = 4499082 }, + { url = "https://files.pythonhosted.org/packages/b8/81/1606966f6146187c273993ea6f88f2151b26741df8f4e01349a625983be9/psycopg_binary-3.2.6-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c5172ce3e4ae7a4fd450070210f801e2ce6bc0f11d1208d29268deb0cda34de", size = 4307509 }, + { url = "https://files.pythonhosted.org/packages/69/ad/01c87aab17a4b89128b8036800d11ab296c7c2c623940cc7e6f2668f375a/psycopg_binary-3.2.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcfab3804c43571a6615e559cdc4c4115785d258a4dd71a721be033f5f5f378d", size = 4547813 }, + { url = "https://files.pythonhosted.org/packages/65/30/f93a193846ee738ffe5d2a4837e7ddeb7279707af81d088cee96cae853a0/psycopg_binary-3.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fa1c920cce16f1205f37b20c685c58b9656b170b8b4c93629100d342d0d118e", size = 4259847 }, + { url = "https://files.pythonhosted.org/packages/8e/73/65c4ae71be86675a62154407c92af4b917146f9ff3baaf0e4166c0734aeb/psycopg_binary-3.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2e118d818101c1608c6b5ba52a6c977614d8f05aa89467501172ba4d10588e11", size = 3846550 }, + { url = "https://files.pythonhosted.org/packages/53/cc/a24626cac3f208c776bb22e15e9a5e483aa81145221e6427e50381f40811/psycopg_binary-3.2.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:763319a8bfeca77d31512da71f5a33459b9568a7621c481c3828c62f9c38f351", size = 3320269 }, + { url = "https://files.pythonhosted.org/packages/55/e6/68c76fb9d6c53d5e4170a0c9216c7aa6c2903808f626d84d002b47a16931/psycopg_binary-3.2.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:2fbc05819560389dbece046966bc88e0f2ea77673497e274c4293b8b4c1d0703", size = 3399365 }, + { url = "https://files.pythonhosted.org/packages/b4/2c/55b140f5a2c582dae42ef38502c45ef69c938274242a40bd04c143081029/psycopg_binary-3.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5a57f99bb953b4bd6f32d0a9844664e7f6ca5ead9ba40e96635be3cd30794813", size = 3438908 }, + { url = "https://files.pythonhosted.org/packages/ae/f6/589c95cceccee2ab408b6b2e16f1ed6db4536fb24f2f5c9ce568cf43270c/psycopg_binary-3.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:5de6809e19a465dcb9c269675bded46a135f2d600cd99f0735afbb21ddad2af4", size = 2782886 }, + { url = "https://files.pythonhosted.org/packages/bf/32/3d06c478fd3070ac25a49c2e8ca46b6d76b0048fa9fa255b99ee32f32312/psycopg_binary-3.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54af3fbf871baa2eb19df96fd7dc0cbd88e628a692063c3d1ab5cdd00aa04322", size = 3852672 }, + { url = "https://files.pythonhosted.org/packages/34/97/e581030e279500ede3096adb510f0e6071874b97cfc047a9a87b7d71fc77/psycopg_binary-3.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ad5da1e4636776c21eaeacdec42f25fa4612631a12f25cd9ab34ddf2c346ffb9", size = 3936562 }, + { url = "https://files.pythonhosted.org/packages/74/b6/6a8df4cb23c3d327403a83406c06c9140f311cb56c4e4d720ee7abf6fddc/psycopg_binary-3.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7956b9ea56f79cd86eddcfbfc65ae2af1e4fe7932fa400755005d903c709370", size = 4499167 }, + { url = "https://files.pythonhosted.org/packages/e4/5b/950eafef61e5e0b8ddb5afc5b6b279756411aa4bf70a346a6f091ad679bb/psycopg_binary-3.2.6-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e2efb763188008cf2914820dcb9fb23c10fe2be0d2c97ef0fac7cec28e281d8", size = 4311651 }, + { url = "https://files.pythonhosted.org/packages/72/b9/b366c49afc854c26b3053d4d35376046eea9aebdc48ded18ea249ea1f80c/psycopg_binary-3.2.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b3aab3451679f1e7932270e950259ed48c3b79390022d3f660491c0e65e4838", size = 4547852 }, + { url = "https://files.pythonhosted.org/packages/ab/d4/0e047360e2ea387dc7171ca017ffcee5214a0762f74b9dd982035f2e52fb/psycopg_binary-3.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:849a370ac4e125f55f2ad37f928e588291a67ccf91fa33d0b1e042bb3ee1f986", size = 4261725 }, + { url = "https://files.pythonhosted.org/packages/e3/ea/a1b969804250183900959ebe845d86be7fed2cbd9be58f64cd0fc24b2892/psycopg_binary-3.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:566d4ace928419d91f1eb3227fc9ef7b41cf0ad22e93dd2c3368d693cf144408", size = 3850073 }, + { url = "https://files.pythonhosted.org/packages/e5/71/ec2907342f0675092b76aea74365b56f38d960c4c635984dcfe25d8178c8/psycopg_binary-3.2.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f1981f13b10de2f11cfa2f99a8738b35b3f0a0f3075861446894a8d3042430c0", size = 3320323 }, + { url = "https://files.pythonhosted.org/packages/d7/d7/0d2cb4b42f231e2efe8ea1799ce917973d47486212a2c4d33cd331e7ac28/psycopg_binary-3.2.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:36f598300b55b3c983ae8df06473ad27333d2fd9f3e2cfdb913b3a5aaa3a8bcf", size = 3402335 }, + { url = "https://files.pythonhosted.org/packages/66/92/7050c372f78e53eba14695cec6c3a91b2d9ca56feaf0bfe95fe90facf730/psycopg_binary-3.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0f4699fa5fe1fffb0d6b2d14b31fd8c29b7ea7375f89d5989f002aaf21728b21", size = 3440442 }, + { url = "https://files.pythonhosted.org/packages/5f/4c/bebcaf754189283b2f3d457822a3d9b233d08ff50973d8f1e8d51f4d35ed/psycopg_binary-3.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:afe697b8b0071f497c5d4c0f41df9e038391534f5614f7fb3a8c1ca32d66e860", size = 2783465 }, + { url = "https://files.pythonhosted.org/packages/be/ac/5023320c46e0d233faff6cd2d902a60d6c09034f136e7cc0876463a97d50/psycopg_binary-3.2.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:260c43c329e668606388cee78ec0dab083a25c2c6e6f9cf74a130fd5a27b0f87", size = 3870121 }, + { url = "https://files.pythonhosted.org/packages/8d/2d/63cdd41112c954582d6bb8905970abe5e5a69b53d6dd2a38fc7ec624a6b4/psycopg_binary-3.2.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9870e51fad4684dbdec057fa757d65e61cb2acb16236836e9360044c2a1ec880", size = 3939150 }, + { url = "https://files.pythonhosted.org/packages/a8/c6/0a0fe3b5506d30d597c82b16b8c1e023393a10701109e917c4cb529ccc71/psycopg_binary-3.2.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:030e9c3082a931e972b029b3cef085784a3bf7f8e18367ae50d5b809aa6e1d87", size = 4525092 }, + { url = "https://files.pythonhosted.org/packages/0d/66/66c3c05a0535c022b1b8af501aec05161084e6a4ab44edebd99ff82de976/psycopg_binary-3.2.6-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b60c9ed291fbd5e777c2c630dcfd10b7a87d68512b0757d5e7406d9c4895a82a", size = 4330350 }, + { url = "https://files.pythonhosted.org/packages/90/f3/15ce9800b54c50ff01af1c39d0a727083bb3640067a54b58631ca6666b24/psycopg_binary-3.2.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e0f4a17a9c376c195e403b4826c18f325bd28f425231d36d1036258bf893e23", size = 4577917 }, + { url = "https://files.pythonhosted.org/packages/a0/aa/ffe622452a43376193d3d1e63b479e0f90ce9732c91366c05a16ce49e513/psycopg_binary-3.2.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac46da609624b16d961f604b3cbc3233ef43211ef1456a188f8c427109c9c3e1", size = 4286328 }, + { url = "https://files.pythonhosted.org/packages/bf/bf/53da912ba8ace3136318718112c5db85bf0ed4f079f92849536a78384875/psycopg_binary-3.2.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e77949b8e7014b85cee0bf6e9e041bcae7719b2693ebf59236368fb0b2a08814", size = 3874534 }, + { url = "https://files.pythonhosted.org/packages/1a/b5/e659e96ffc6846d3f90c4af785c84898e625f974c7f67255688addda6167/psycopg_binary-3.2.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:532322d9ef6e7d178a4f344970b017110633bcc3dc1c3403efcef55aad612517", size = 3339634 }, + { url = "https://files.pythonhosted.org/packages/9b/d6/92858290a8d710a27caf1b92183f4fa2ef3b99c9b6959fc73fd9639b8412/psycopg_binary-3.2.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:880c5fd76dcb50bdcc8f87359e5a6c7eb416697cc9aa02854c91223bd999c045", size = 3437930 }, + { url = "https://files.pythonhosted.org/packages/f7/a5/60b5a6519f58b2deca261d2e4566bce4beeeb30f6e7c3f72e4d8f204dcf6/psycopg_binary-3.2.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3c0cddc7458b8416d77cd8829d0192466502f31d1fb853d58613cf13ac64f41c", size = 3464810 }, + { url = "https://files.pythonhosted.org/packages/27/b6/e48cafe90e46b82b1393276ce6d92ddb7650c39aba8aa0256affbaa94c36/psycopg_binary-3.2.6-cp39-cp39-win_amd64.whl", hash = "sha256:ea158665676f42b19585dfe948071d3c5f28276f84a97522fb2e82c1d9194563", size = 2793902 }, ] [[package]] @@ -2345,15 +2350,15 @@ wheels = [ [[package]] name = "pydantic-extra-types" -version = "2.10.2" +version = "2.10.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/23/ed/69f3f3de12c02ebd58b2f66ffb73d0f5a1b10b322227897499753cebe818/pydantic_extra_types-2.10.2.tar.gz", hash = "sha256:934d59ab7a02ff788759c3a97bc896f5cfdc91e62e4f88ea4669067a73f14b98", size = 86893 } +sdist = { url = "https://files.pythonhosted.org/packages/53/fa/6b268a47839f8af46ffeb5bb6aee7bded44fbad54e6bf826c11f17aef91a/pydantic_extra_types-2.10.3.tar.gz", hash = "sha256:dcc0a7b90ac9ef1b58876c9b8fdede17fbdde15420de9d571a9fccde2ae175bb", size = 95128 } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/da/86bc9addde8a24348ac15f8f7dcb853f78e9573c7667800dd9bc60558678/pydantic_extra_types-2.10.2-py3-none-any.whl", hash = "sha256:9eccd55a2b7935cea25f0a67f6ff763d55d80c41d86b887d88915412ccf5b7fa", size = 35473 }, + { url = "https://files.pythonhosted.org/packages/38/0a/f6f8e5f79d188e2f3fa9ecfccfa72538b685985dd5c7c2886c67af70e685/pydantic_extra_types-2.10.3-py3-none-any.whl", hash = "sha256:e8b372752b49019cd8249cc192c62a820d8019f5382a8789d0f887338a59c0f3", size = 37175 }, ] [[package]] @@ -2836,27 +2841,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.9.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/8e/fafaa6f15c332e73425d9c44ada85360501045d5ab0b81400076aff27cf6/ruff-0.9.10.tar.gz", hash = "sha256:9bacb735d7bada9cfb0f2c227d3658fc443d90a727b47f206fb33f52f3c0eac7", size = 3759776 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/73/b2/af7c2cc9e438cbc19fafeec4f20bfcd72165460fe75b2b6e9a0958c8c62b/ruff-0.9.10-py3-none-linux_armv6l.whl", hash = "sha256:eb4d25532cfd9fe461acc83498361ec2e2252795b4f40b17e80692814329e42d", size = 10049494 }, - { url = "https://files.pythonhosted.org/packages/6d/12/03f6dfa1b95ddd47e6969f0225d60d9d7437c91938a310835feb27927ca0/ruff-0.9.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:188a6638dab1aa9bb6228a7302387b2c9954e455fb25d6b4470cb0641d16759d", size = 10853584 }, - { url = "https://files.pythonhosted.org/packages/02/49/1c79e0906b6ff551fb0894168763f705bf980864739572b2815ecd3c9df0/ruff-0.9.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5284dcac6b9dbc2fcb71fdfc26a217b2ca4ede6ccd57476f52a587451ebe450d", size = 10155692 }, - { url = "https://files.pythonhosted.org/packages/5b/01/85e8082e41585e0e1ceb11e41c054e9e36fed45f4b210991052d8a75089f/ruff-0.9.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47678f39fa2a3da62724851107f438c8229a3470f533894b5568a39b40029c0c", size = 10369760 }, - { url = "https://files.pythonhosted.org/packages/a1/90/0bc60bd4e5db051f12445046d0c85cc2c617095c0904f1aa81067dc64aea/ruff-0.9.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99713a6e2766b7a17147b309e8c915b32b07a25c9efd12ada79f217c9c778b3e", size = 9912196 }, - { url = "https://files.pythonhosted.org/packages/66/ea/0b7e8c42b1ec608033c4d5a02939c82097ddcb0b3e393e4238584b7054ab/ruff-0.9.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524ee184d92f7c7304aa568e2db20f50c32d1d0caa235d8ddf10497566ea1a12", size = 11434985 }, - { url = "https://files.pythonhosted.org/packages/d5/86/3171d1eff893db4f91755175a6e1163c5887be1f1e2f4f6c0c59527c2bfd/ruff-0.9.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:df92aeac30af821f9acf819fc01b4afc3dfb829d2782884f8739fb52a8119a16", size = 12155842 }, - { url = "https://files.pythonhosted.org/packages/89/9e/700ca289f172a38eb0bca752056d0a42637fa17b81649b9331786cb791d7/ruff-0.9.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de42e4edc296f520bb84954eb992a07a0ec5a02fecb834498415908469854a52", size = 11613804 }, - { url = "https://files.pythonhosted.org/packages/f2/92/648020b3b5db180f41a931a68b1c8575cca3e63cec86fd26807422a0dbad/ruff-0.9.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d257f95b65806104b6b1ffca0ea53f4ef98454036df65b1eda3693534813ecd1", size = 13823776 }, - { url = "https://files.pythonhosted.org/packages/5e/a6/cc472161cd04d30a09d5c90698696b70c169eeba2c41030344194242db45/ruff-0.9.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60dec7201c0b10d6d11be00e8f2dbb6f40ef1828ee75ed739923799513db24c", size = 11302673 }, - { url = "https://files.pythonhosted.org/packages/6c/db/d31c361c4025b1b9102b4d032c70a69adb9ee6fde093f6c3bf29f831c85c/ruff-0.9.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d838b60007da7a39c046fcdd317293d10b845001f38bcb55ba766c3875b01e43", size = 10235358 }, - { url = "https://files.pythonhosted.org/packages/d1/86/d6374e24a14d4d93ebe120f45edd82ad7dcf3ef999ffc92b197d81cdc2a5/ruff-0.9.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ccaf903108b899beb8e09a63ffae5869057ab649c1e9231c05ae354ebc62066c", size = 9886177 }, - { url = "https://files.pythonhosted.org/packages/00/62/a61691f6eaaac1e945a1f3f59f1eea9a218513139d5b6c2b8f88b43b5b8f/ruff-0.9.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f9567d135265d46e59d62dc60c0bfad10e9a6822e231f5b24032dba5a55be6b5", size = 10864747 }, - { url = "https://files.pythonhosted.org/packages/ee/94/2c7065e1d92a8a8a46d46d9c3cf07b0aa7e0a1e0153d74baa5e6620b4102/ruff-0.9.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5f202f0d93738c28a89f8ed9eaba01b7be339e5d8d642c994347eaa81c6d75b8", size = 11360441 }, - { url = "https://files.pythonhosted.org/packages/a7/8f/1f545ea6f9fcd7bf4368551fb91d2064d8f0577b3079bb3f0ae5779fb773/ruff-0.9.10-py3-none-win32.whl", hash = "sha256:bfb834e87c916521ce46b1788fbb8484966e5113c02df216680102e9eb960029", size = 10247401 }, - { url = "https://files.pythonhosted.org/packages/4f/18/fb703603ab108e5c165f52f5b86ee2aa9be43bb781703ec87c66a5f5d604/ruff-0.9.10-py3-none-win_amd64.whl", hash = "sha256:f2160eeef3031bf4b17df74e307d4c5fb689a6f3a26a2de3f7ef4044e3c484f1", size = 11366360 }, - { url = "https://files.pythonhosted.org/packages/35/85/338e603dc68e7d9994d5d84f24adbf69bae760ba5efd3e20f5ff2cec18da/ruff-0.9.10-py3-none-win_arm64.whl", hash = "sha256:5fd804c0327a5e5ea26615550e706942f348b197d5475ff34c19733aee4b2e69", size = 10436892 }, +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/ec/9c59d2956566517c98ac8267554f4eaceafb2a19710a429368518b7fab43/ruff-0.10.0.tar.gz", hash = "sha256:fa1554e18deaf8aa097dbcfeafaf38b17a2a1e98fdc18f50e62e8a836abee392", size = 3789921 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/3f/742afe91b43def2a75990b293c676355576c0ff9cdbcf4249f78fa592544/ruff-0.10.0-py3-none-linux_armv6l.whl", hash = "sha256:46a2aa0eaae5048e5f804f0be9489d8a661633e23277b7293089e70d5c1a35c4", size = 10078369 }, + { url = "https://files.pythonhosted.org/packages/8d/a0/8696fb4862e82f7b40bbbc2917137594b22826cc62d77278a91391507514/ruff-0.10.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:775a6bc61af9dd0a2e1763406522a137e62aabb743d8b43ed95f019cdd1526c7", size = 10876912 }, + { url = "https://files.pythonhosted.org/packages/40/aa/0d48b7b7d7a1f168bb8fd893ed559d633c7d68c4a8ef9b996f0c2bd07aca/ruff-0.10.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8b03e6fcd39d20f0004f9956f0ed5eadc404d3a299f9d9286323884e3b663730", size = 10229962 }, + { url = "https://files.pythonhosted.org/packages/21/de/861ced2f75b045d8cfc038d68961d8ac117344df1f43a11abdd05bf7991b/ruff-0.10.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:621101d1af80248827f2409a78c8177c8319986a57b4663613b9c72f8617bfcd", size = 10404627 }, + { url = "https://files.pythonhosted.org/packages/21/69/666e0b840191c3ce433962c0d05fc0f6800afe259ea5d230cc731655d8e2/ruff-0.10.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e2dfe85cb6bfbd4259801e7d4982f2a72bdbd5749dc73a09d68a6dbf77f2209a", size = 9939383 }, + { url = "https://files.pythonhosted.org/packages/76/bf/34a2adc58092c99cdfa9f1303acd82d840d56412022e477e2ab20c261d2d/ruff-0.10.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43ac3879a20c22fdc57e559f0bb27f0c71828656841d0b42d3505b1e5b3a83c8", size = 11492269 }, + { url = "https://files.pythonhosted.org/packages/31/3d/f7ccfcf69f15948623b190feea9d411d5029ae39725fcc078f8d43bd07a6/ruff-0.10.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ef5e3aac421bbc62f8a7aab21edd49a359ed42205f7a5091a74386bca1efa293", size = 12186939 }, + { url = "https://files.pythonhosted.org/packages/6e/3e/c557c0abfdea85c7d238a3cb238c73e7b6d17c30a584234c4fd8fe2cafb6/ruff-0.10.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9f4f62d7fac8b748fce67ad308116b4d4cc1a9f964b4804fc5408fbd06e13ba9", size = 11655896 }, + { url = "https://files.pythonhosted.org/packages/3b/8e/3bfa110f37e5192eb3943f14943d05fbb9a76fea380aa87655e6f6276a54/ruff-0.10.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:02f9f6205c5b0d626f98da01a0e75b724a64c21c554bba24b12522c9e9ba6a04", size = 13885502 }, + { url = "https://files.pythonhosted.org/packages/51/4a/22cdab59b5563dd7f4c504d0f1e6bb25fc800a5a057395bc24f8ff3a85b2/ruff-0.10.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46a97f3d55f68464c48d1e929a8582c7e5bb80ac73336bbc7b0da894d8e6cd9e", size = 11344767 }, + { url = "https://files.pythonhosted.org/packages/3d/0f/8f85de2ac565f82f47c6d8fb7ae04383e6300560f2d1b91c1268ff91e507/ruff-0.10.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a0b811197d0dc96c13d610f8cfdc56030b405bcff5c2f10eab187b329da0ca4a", size = 10300331 }, + { url = "https://files.pythonhosted.org/packages/90/4a/b337df327832cb30bd8607e8d1fdf1b6b5ca228307d5008dd49028fb66ae/ruff-0.10.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a13a3fda0870c1c964b47ff5d73805ae80d2a9de93ee2d185d453b8fddf85a84", size = 9926551 }, + { url = "https://files.pythonhosted.org/packages/d7/e9/141233730b85675ac806c4b62f70516bd9c0aae8a55823f3a6589ed411be/ruff-0.10.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6ceb8d9f062e90ddcbad929f6136edf764bbf6411420a07e8357602ea28cd99f", size = 10925061 }, + { url = "https://files.pythonhosted.org/packages/24/09/02987935b55c2d353a226ac1b4f9718830e2e195834929f46c07eeede746/ruff-0.10.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c41d07d573617ed2f287ea892af2446fd8a8d877481e8e1ba6928e020665d240", size = 11394949 }, + { url = "https://files.pythonhosted.org/packages/d6/ec/054f9879fb6f4122d43ffe5c9f88c8c323a9cd14220d5c813aea5805e02c/ruff-0.10.0-py3-none-win32.whl", hash = "sha256:76e2de0cbdd587e373cd3b4050d2c45babdd7014c1888a6f121c29525c748a15", size = 10272077 }, + { url = "https://files.pythonhosted.org/packages/6e/49/915d8682f24645b904fe6a1aac36101464fc814923fdf293c1388dc5533c/ruff-0.10.0-py3-none-win_amd64.whl", hash = "sha256:f943acdecdcc6786a8d1dad455dd9f94e6d57ccc115be4993f9b52ef8316027a", size = 11393300 }, + { url = "https://files.pythonhosted.org/packages/82/ed/5c59941634c9026ceeccc7c119f23f4356f09aafd28c15c1bc734ac66b01/ruff-0.10.0-py3-none-win_arm64.whl", hash = "sha256:935a943bdbd9ff0685acd80d484ea91088e27617537b5f7ef8907187d19d28d0", size = 10510133 }, ] [[package]] @@ -3338,11 +3343,11 @@ wheels = [ [[package]] name = "sqlglot" -version = "26.9.0" +version = "26.10.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bf/d8/b24299f64d98d8682a0e1a953412cce0d2e57795213eaf14f20945971dd2/sqlglot-26.9.0.tar.gz", hash = "sha256:7a2f5f83baca0ecfa0145f010e864e1b220be6a5b1e27e27a9c1777be428f557", size = 5327761 } +sdist = { url = "https://files.pythonhosted.org/packages/99/28/4644fe7c10d22a700f9f14b1ce8937c70016d7c6445d206b7ff4c717b468/sqlglot-26.10.1.tar.gz", hash = "sha256:ac6e4f7113f2b308acd904a9063a23bc1719a1cdc37279fc760eeb97d386985e", size = 5334132 } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/e5/8fe2d21319675362e93c2f5e6bb450b2e4e6eb56b216ede46952cd2c861d/sqlglot-26.9.0-py3-none-any.whl", hash = "sha256:d5c195d5f42ab8eca530ac4730369b4aa4b1b300277152700293bfb5615cd5c3", size = 451325 }, + { url = "https://files.pythonhosted.org/packages/b2/6e/6a93dd742907277f23a2a06299c4ab5ffd24ad05e36588fa9687a50cd8bf/sqlglot-26.10.1-py3-none-any.whl", hash = "sha256:b333f0a9192994ee01e0f760590877f446c95cd9a6740cf4e0fa76648f63ceac", size = 453141 }, ] [package.optional-dependencies]