From 8ef5640b2f2cf0fa31f249dbeb4cf9f0f262f34f Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:10:11 +0300 Subject: [PATCH 01/33] connection and cursor interface --- ydb_dbapi/connection.py | 19 +++++++++++++++++++ ydb_dbapi/cursors.py | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 ydb_dbapi/connection.py create mode 100644 ydb_dbapi/cursors.py diff --git a/ydb_dbapi/connection.py b/ydb_dbapi/connection.py new file mode 100644 index 0000000..59469d8 --- /dev/null +++ b/ydb_dbapi/connection.py @@ -0,0 +1,19 @@ +class Connection: + def __init__(self): + pass + + def cursor(self): + pass + + async def commit(self): + pass + + async def rollback(self): + pass + + async def close(self): + pass + + +async def connect() -> Connection: + return Connection() diff --git a/ydb_dbapi/cursors.py b/ydb_dbapi/cursors.py new file mode 100644 index 0000000..738d3ea --- /dev/null +++ b/ydb_dbapi/cursors.py @@ -0,0 +1,38 @@ +class Cursor: + def __init__(self): + self.arraysize = 1 + + async def execute(self): + pass + + async def executemany(self): + pass + + async def fetchone(self): + pass + + async def fetchmany(self): + pass + + async def fetchall(self): + pass + + async def nextset(self): + pass + + def setinputsizes(self): + pass + + def setoutputsize(self): + pass + + async def close(self): + pass + + @property + def description(self): + pass + + @property + def rowcount(self): + pass From b8f0f72acf7334700f3c7cc9a379dafbb8c2928a Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:10:11 +0300 Subject: [PATCH 02/33] basic connection methods --- ydb_dbapi/connection.py | 47 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/ydb_dbapi/connection.py b/ydb_dbapi/connection.py index 59469d8..1ea75e9 100644 --- a/ydb_dbapi/connection.py +++ b/ydb_dbapi/connection.py @@ -1,15 +1,54 @@ +from typing import ( + Any, + Optional, +) + +import ydb + + class Connection: - def __init__(self): - pass + def __init__( + self, + host: str = "", + port: str = "", + database: str = "", + **conn_kwargs: Any, + ): + self.endpoint = f"grpc://{host}:{port}" + self.database = database + self.conn_kwargs = conn_kwargs + self.credentials = self.conn_kwargs.pop("credentials", None) + self.table_path_prefix = self.conn_kwargs.pop( + "ydb_table_path_prefix", "" + ) + + self.session_pool: ydb.aio.QuerySessionPool = None + self.session: ydb.aio.QuerySession = None + self.tx_context: Optional[ydb.QueryTxContext] = None + self.tx_mode: ydb.BaseQueryTxMode = ydb.QuerySerializableReadWrite() def cursor(self): pass + async def begin(self): + self.tx_context = None + self.session = await self.session_pool.acquire() + self.tx_context = self.session.transaction(self.tx_mode) + await self.tx_context.begin() + async def commit(self): - pass + if self.tx_context and self.tx_context.tx_id: + await self.tx_context.commit() + await self.session_pool.release(self.session) + self.session = None + self.tx_context = None async def rollback(self): - pass + if self.tx_context and self.tx_context.tx_id: + await self.tx_context.rollback() + await self.session_pool.release(self.session) + self.session = None + self.tx_context = None async def close(self): pass From 251bfe6b69bf2ab0c4800b77fe48f8e7eb0990cf Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:10:11 +0300 Subject: [PATCH 03/33] Add isolation level settings --- ydb_dbapi/connection.py | 76 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/ydb_dbapi/connection.py b/ydb_dbapi/connection.py index 1ea75e9..055b3ad 100644 --- a/ydb_dbapi/connection.py +++ b/ydb_dbapi/connection.py @@ -1,10 +1,26 @@ from typing import ( Any, + NamedTuple, Optional, ) import ydb +from .errors import ( + # InterfaceError, + InternalError, + NotSupportedError, +) + + +class IsolationLevel: + SERIALIZABLE = "SERIALIZABLE" + ONLINE_READONLY = "ONLINE READONLY" + ONLINE_READONLY_INCONSISTENT = "ONLINE READONLY INCONSISTENT" + STALE_READONLY = "STALE READONLY" + SNAPSHOT_READONLY = "SNAPSHOT READONLY" + AUTOCOMMIT = "AUTOCOMMIT" + class Connection: def __init__( @@ -22,11 +38,15 @@ def __init__( "ydb_table_path_prefix", "" ) - self.session_pool: ydb.aio.QuerySessionPool = None + self.session_pool: ydb.aio.QuerySessionPool = self.conn_kwargs.pop( + "ydb_session_pool", None + ) self.session: ydb.aio.QuerySession = None self.tx_context: Optional[ydb.QueryTxContext] = None self.tx_mode: ydb.BaseQueryTxMode = ydb.QuerySerializableReadWrite() + self.interactive_transaction: bool = False # AUTOCOMMIT + def cursor(self): pass @@ -51,7 +71,59 @@ async def rollback(self): self.tx_context = None async def close(self): - pass + await self.rollback() + + def set_isolation_level(self, isolation_level: str): + class IsolationSettings(NamedTuple): + ydb_mode: ydb.BaseQueryTxMode + interactive: bool + + ydb_isolation_settings_map = { + IsolationLevel.AUTOCOMMIT: IsolationSettings( + ydb.QuerySerializableReadWrite(), interactive=False + ), + IsolationLevel.SERIALIZABLE: IsolationSettings( + ydb.QuerySerializableReadWrite(), interactive=True + ), + IsolationLevel.ONLINE_READONLY: IsolationSettings( + ydb.QueryOnlineReadOnly(), interactive=False + ), + IsolationLevel.ONLINE_READONLY_INCONSISTENT: IsolationSettings( + ydb.QueryOnlineReadOnly().with_allow_inconsistent_reads(), + interactive=False, + ), + IsolationLevel.STALE_READONLY: IsolationSettings( + ydb.QueryStaleReadOnly(), interactive=False + ), + IsolationLevel.SNAPSHOT_READONLY: IsolationSettings( + ydb.QuerySnapshotReadOnly(), interactive=True + ), + } + ydb_isolation_settings = ydb_isolation_settings_map[isolation_level] + if self.tx_context and self.tx_context.tx_id: + raise InternalError( + "Failed to set transaction mode: transaction is already began" + ) + self.tx_mode = ydb_isolation_settings.ydb_mode + self.interactive_transaction = ydb_isolation_settings.interactive + + def get_isolation_level(self) -> str: + if self.tx_mode.name == ydb.SerializableReadWrite().name: + if self.interactive_transaction: + return IsolationLevel.SERIALIZABLE + else: + return IsolationLevel.AUTOCOMMIT + elif self.tx_mode.name == ydb.OnlineReadOnly().name: + if self.tx_mode.settings.allow_inconsistent_reads: + return IsolationLevel.ONLINE_READONLY_INCONSISTENT + else: + return IsolationLevel.ONLINE_READONLY + elif self.tx_mode.name == ydb.StaleReadOnly().name: + return IsolationLevel.STALE_READONLY + elif self.tx_mode.name == ydb.SnapshotReadOnly().name: + return IsolationLevel.SNAPSHOT_READONLY + else: + raise NotSupportedError(f"{self.tx_mode.name} is not supported") async def connect() -> Connection: From 179124ee20af968387aa6d0262f0fca15cbffa57 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:10:11 +0300 Subject: [PATCH 04/33] scheme&table queries --- ydb_dbapi/connection.py | 51 +++++++++++++++++++++++++++++++++++++++++ ydb_dbapi/utils.py | 49 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 ydb_dbapi/utils.py diff --git a/ydb_dbapi/connection.py b/ydb_dbapi/connection.py index 055b3ad..a1ab509 100644 --- a/ydb_dbapi/connection.py +++ b/ydb_dbapi/connection.py @@ -1,10 +1,17 @@ from typing import ( Any, + List, NamedTuple, Optional, ) +import posixpath import ydb +from ydb.retries import retry_operation_async + +from .utils import ( + handle_ydb_errors, +) from .errors import ( # InterfaceError, @@ -38,6 +45,8 @@ def __init__( "ydb_table_path_prefix", "" ) + self.driver: ydb.aio.Driver = self.conn_kwargs.pop("ydb_driver", None) + self.session_pool: ydb.aio.QuerySessionPool = self.conn_kwargs.pop( "ydb_session_pool", None ) @@ -125,6 +134,48 @@ def get_isolation_level(self) -> str: else: raise NotSupportedError(f"{self.tx_mode.name} is not supported") + @handle_ydb_errors + async def describe(self, table_path: str) -> ydb.TableSchemeEntry: + abs_table_path = posixpath.join( + self.database, self.table_path_prefix, table_path + ) + return self.driver.table_client.describe_table(abs_table_path) + + @handle_ydb_errors + async def check_exists(self, table_path: str) -> ydb.SchemeEntry: + abs_table_path = posixpath.join( + self.database, self.table_path_prefix, table_path + ) + return await self._check_path_exists(abs_table_path) + + @handle_ydb_errors + async def get_table_names(self) -> List[str]: + abs_dir_path = posixpath.join(self.database, self.table_path_prefix) + names = await self._get_table_names(abs_dir_path) + return [posixpath.relpath(path, abs_dir_path) for path in names] + + async def _check_path_exists(self, table_path: str) -> ydb.SchemeEntry: + try: + await retry_operation_async( + self.driver.scheme_client.describe_path, table_path + ) + return True + except ydb.SchemeError: + return False + + async def _get_table_names(self, abs_dir_path: str) -> List[str]: + directory = await retry_operation_async( + self.driver.scheme_client.list_directory, abs_dir_path + ) + result = [] + for child in directory.children: + child_abs_path = posixpath.join(abs_dir_path, child.name) + if child.is_table(): + result.append(child_abs_path) + elif child.is_directory() and not child.name.startswith("."): + result.extend(self.get_table_names(child_abs_path)) + return result + async def connect() -> Connection: return Connection() diff --git a/ydb_dbapi/utils.py b/ydb_dbapi/utils.py new file mode 100644 index 0000000..380a3ff --- /dev/null +++ b/ydb_dbapi/utils.py @@ -0,0 +1,49 @@ +import functools +import ydb + +from .errors import ( + DatabaseError, + DataError, + IntegrityError, + InternalError, + NotSupportedError, + OperationalError, + ProgrammingError, +) + + +def handle_ydb_errors(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except (ydb.issues.AlreadyExists, ydb.issues.PreconditionFailed) as e: + raise IntegrityError(e.message, original_error=e) from e + except (ydb.issues.Unsupported, ydb.issues.Unimplemented) as e: + raise NotSupportedError(e.message, original_error=e) from e + except (ydb.issues.BadRequest, ydb.issues.SchemeError) as e: + raise ProgrammingError(e.message, original_error=e) from e + except ( + ydb.issues.TruncatedResponseError, + ydb.issues.ConnectionError, + ydb.issues.Aborted, + ydb.issues.Unavailable, + ydb.issues.Overloaded, + ydb.issues.Undetermined, + ydb.issues.Timeout, + ydb.issues.Cancelled, + ydb.issues.SessionBusy, + ydb.issues.SessionExpired, + ydb.issues.SessionPoolEmpty, + ) as e: + raise OperationalError(e.message, original_error=e) from e + except ydb.issues.GenericError as e: + raise DataError(e.message, original_error=e) from e + except ydb.issues.InternalError as e: + raise InternalError(e.message, original_error=e) from e + except ydb.Error as e: + raise DatabaseError(e.message, original_error=e) from e + except Exception as e: + raise DatabaseError("Failed to execute query") from e + + return wrapper From 37600faba9d4b113d22562e80eed38e09eabb01f Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:10:11 +0300 Subject: [PATCH 05/33] remove parameters from YdbQuery --- ydb_dbapi/cursors.py | 56 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/ydb_dbapi/cursors.py b/ydb_dbapi/cursors.py index 738d3ea..a7155c2 100644 --- a/ydb_dbapi/cursors.py +++ b/ydb_dbapi/cursors.py @@ -1,8 +1,60 @@ +import dataclasses +from typing import ( + Any, + Dict, + List, + Optional, + Tuple, + Union, +) + +import ydb + + +ParametersType = Dict[ + str, + Union[ + Any, + Tuple[Any, Union[ydb.PrimitiveType, ydb.AbstractTypeBuilder]], + ydb.TypedValue, + ], +] + + +@dataclasses.dataclass +class YdbQuery: + yql_text: str + is_ddl: bool = False + + class Cursor: - def __init__(self): + def __init__( + self, + session_pool: ydb.aio.QuerySessionPool, + session: ydb.aio.QuerySession, + tx_mode: ydb.BaseQueryTxMode, + tx_context: Optional[ydb.QueryTxContext] = None, + table_path_prefix: str = "", + ): self.arraysize = 1 + self._description = None + + self._pool = session_pool + self._session = session + self._tx_mode = tx_mode + self._tx_context = tx_context + self._table_path_prefix = table_path_prefix + + async def _execute_ddl_query( + self, query: YdbQuery, parameters: Optional[ParametersType] = None + ) -> List[ydb.convert.ResultSet]: + return await self._pool.execute_with_retries( + query=query, parameters=parameters + ) - async def execute(self): + async def execute( + self, operation: YdbQuery, parameters: Optional[ParametersType] = None + ): pass async def executemany(self): From 2d0c38a9ea5190bfea98367bf6a1dde48adfc30d Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:10:11 +0300 Subject: [PATCH 06/33] ddl and dml queries --- ydb_dbapi/connection.py | 4 ++-- ydb_dbapi/cursors.py | 32 ++++++++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/ydb_dbapi/connection.py b/ydb_dbapi/connection.py index a1ab509..d723ef2 100644 --- a/ydb_dbapi/connection.py +++ b/ydb_dbapi/connection.py @@ -142,7 +142,7 @@ async def describe(self, table_path: str) -> ydb.TableSchemeEntry: return self.driver.table_client.describe_table(abs_table_path) @handle_ydb_errors - async def check_exists(self, table_path: str) -> ydb.SchemeEntry: + async def check_exists(self, table_path: str) -> bool: abs_table_path = posixpath.join( self.database, self.table_path_prefix, table_path ) @@ -154,7 +154,7 @@ async def get_table_names(self) -> List[str]: names = await self._get_table_names(abs_dir_path) return [posixpath.relpath(path, abs_dir_path) for path in names] - async def _check_path_exists(self, table_path: str) -> ydb.SchemeEntry: + async def _check_path_exists(self, table_path: str) -> bool: try: await retry_operation_async( self.driver.scheme_client.describe_path, table_path diff --git a/ydb_dbapi/cursors.py b/ydb_dbapi/cursors.py index a7155c2..678d85d 100644 --- a/ydb_dbapi/cursors.py +++ b/ydb_dbapi/cursors.py @@ -1,4 +1,5 @@ import dataclasses +import typing from typing import ( Any, Dict, @@ -10,6 +11,9 @@ import ydb +if typing.TYPE_CHECKING: + from ydb.aio.query.base import AsyncResponseContextIterator + ParametersType = Dict[ str, @@ -32,9 +36,10 @@ def __init__( self, session_pool: ydb.aio.QuerySessionPool, session: ydb.aio.QuerySession, - tx_mode: ydb.BaseQueryTxMode, - tx_context: Optional[ydb.QueryTxContext] = None, + tx_mode: ydb.aio.QueryTxContext, + tx_context: ydb.aio.QueryTxContext, table_path_prefix: str = "", + autocommit: bool = True, ): self.arraysize = 1 self._description = None @@ -44,18 +49,37 @@ def __init__( self._tx_mode = tx_mode self._tx_context = tx_context self._table_path_prefix = table_path_prefix + self._autocommit = autocommit async def _execute_ddl_query( - self, query: YdbQuery, parameters: Optional[ParametersType] = None + self, query: str, parameters: Optional[ParametersType] = None ) -> List[ydb.convert.ResultSet]: return await self._pool.execute_with_retries( query=query, parameters=parameters ) + async def _execute_dml_query( + self, query: str, parameters: Optional[ParametersType] = None + ) -> AsyncResponseContextIterator: + return await self._tx_context.execute( + query=query, + parameters=parameters, + commit_tx=self._autocommit, + ) + async def execute( self, operation: YdbQuery, parameters: Optional[ParametersType] = None ): - pass + if operation.is_ddl: + result_sets = await self._execute_ddl_query( + query=operation.yql_text, parameters=parameters + ) + else: + result_sets_stream = await self._execute_dml_query( + query=operation.yql_text, parameters=parameters + ) + + return result_sets or result_sets_stream async def executemany(self): pass From d63f863e41fbaa0f03d1702a7dc125a793d0526d Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:10:11 +0300 Subject: [PATCH 07/33] update min version of ydb --- poetry.lock | 551 ++++++++++++++++++++++++------------------------- pyproject.toml | 2 +- 2 files changed, 274 insertions(+), 279 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0404574..273ef5a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,102 +13,102 @@ files = [ [[package]] name = "aiohttp" -version = "3.10.9" +version = "3.10.10" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.10.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8b3fb28a9ac8f2558760d8e637dbf27aef1e8b7f1d221e8669a1074d1a266bb2"}, - {file = "aiohttp-3.10.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:91aa966858593f64c8a65cdefa3d6dc8fe3c2768b159da84c1ddbbb2c01ab4ef"}, - {file = "aiohttp-3.10.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:63649309da83277f06a15bbdc2a54fbe75efb92caa2c25bb57ca37762789c746"}, - {file = "aiohttp-3.10.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3e7fabedb3fe06933f47f1538df7b3a8d78e13d7167195f51ca47ee12690373"}, - {file = "aiohttp-3.10.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c070430fda1a550a1c3a4c2d7281d3b8cfc0c6715f616e40e3332201a253067"}, - {file = "aiohttp-3.10.9-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:51d0a4901b27272ae54e42067bc4b9a90e619a690b4dc43ea5950eb3070afc32"}, - {file = "aiohttp-3.10.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fec5fac7aea6c060f317f07494961236434928e6f4374e170ef50b3001e14581"}, - {file = "aiohttp-3.10.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:172ad884bb61ad31ed7beed8be776eb17e7fb423f1c1be836d5cb357a096bf12"}, - {file = "aiohttp-3.10.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d646fdd74c25bbdd4a055414f0fe32896c400f38ffbdfc78c68e62812a9e0257"}, - {file = "aiohttp-3.10.9-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e86260b76786c28acf0b5fe31c8dca4c2add95098c709b11e8c35b424ebd4f5b"}, - {file = "aiohttp-3.10.9-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d7cafc11d70fdd8801abfc2ff276744ae4cb39d8060b6b542c7e44e5f2cfc2"}, - {file = "aiohttp-3.10.9-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fc262c3df78c8ff6020c782d9ce02e4bcffe4900ad71c0ecdad59943cba54442"}, - {file = "aiohttp-3.10.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:482c85cf3d429844396d939b22bc2a03849cb9ad33344689ad1c85697bcba33a"}, - {file = "aiohttp-3.10.9-cp310-cp310-win32.whl", hash = "sha256:aeebd3061f6f1747c011e1d0b0b5f04f9f54ad1a2ca183e687e7277bef2e0da2"}, - {file = "aiohttp-3.10.9-cp310-cp310-win_amd64.whl", hash = "sha256:fa430b871220dc62572cef9c69b41e0d70fcb9d486a4a207a5de4c1f25d82593"}, - {file = "aiohttp-3.10.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:16e6a51d8bc96b77f04a6764b4ad03eeef43baa32014fce71e882bd71302c7e4"}, - {file = "aiohttp-3.10.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8bd9125dd0cc8ebd84bff2be64b10fdba7dc6fd7be431b5eaf67723557de3a31"}, - {file = "aiohttp-3.10.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dcf354661f54e6a49193d0b5653a1b011ba856e0b7a76bda2c33e4c6892f34ea"}, - {file = "aiohttp-3.10.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42775de0ca04f90c10c5c46291535ec08e9bcc4756f1b48f02a0657febe89b10"}, - {file = "aiohttp-3.10.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87d1e4185c5d7187684d41ebb50c9aeaaaa06ca1875f4c57593071b0409d2444"}, - {file = "aiohttp-3.10.9-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2695c61cf53a5d4345a43d689f37fc0f6d3a2dc520660aec27ec0f06288d1f9"}, - {file = "aiohttp-3.10.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a3f063b41cc06e8d0b3fcbbfc9c05b7420f41287e0cd4f75ce0a1f3d80729e6"}, - {file = "aiohttp-3.10.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d37f4718002863b82c6f391c8efd4d3a817da37030a29e2682a94d2716209de"}, - {file = "aiohttp-3.10.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2746d8994ebca1bdc55a1e998feff4e94222da709623bb18f6e5cfec8ec01baf"}, - {file = "aiohttp-3.10.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6f3c6648aa123bcd73d6f26607d59967b607b0da8ffcc27d418a4b59f4c98c7c"}, - {file = "aiohttp-3.10.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:558b3d223fd631ad134d89adea876e7fdb4c93c849ef195049c063ada82b7d08"}, - {file = "aiohttp-3.10.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4e6cb75f8ddd9c2132d00bc03c9716add57f4beff1263463724f6398b813e7eb"}, - {file = "aiohttp-3.10.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:608cecd8d58d285bfd52dbca5b6251ca8d6ea567022c8a0eaae03c2589cd9af9"}, - {file = "aiohttp-3.10.9-cp311-cp311-win32.whl", hash = "sha256:36d4fba838be5f083f5490ddd281813b44d69685db910907636bc5dca6322316"}, - {file = "aiohttp-3.10.9-cp311-cp311-win_amd64.whl", hash = "sha256:8be1a65487bdfc285bd5e9baf3208c2132ca92a9b4020e9f27df1b16fab998a9"}, - {file = "aiohttp-3.10.9-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4fd16b30567c5b8e167923be6e027eeae0f20cf2b8a26b98a25115f28ad48ee0"}, - {file = "aiohttp-3.10.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:40ff5b7660f903dc587ed36ef08a88d46840182d9d4b5694e7607877ced698a1"}, - {file = "aiohttp-3.10.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4edc3fd701e2b9a0d605a7b23d3de4ad23137d23fc0dbab726aa71d92f11aaaf"}, - {file = "aiohttp-3.10.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e525b69ee8a92c146ae5b4da9ecd15e518df4d40003b01b454ad694a27f498b5"}, - {file = "aiohttp-3.10.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5002a02c17fcfd796d20bac719981d2fca9c006aac0797eb8f430a58e9d12431"}, - {file = "aiohttp-3.10.9-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd4ceeae2fb8cabdd1b71c82bfdd39662473d3433ec95b962200e9e752fb70d0"}, - {file = "aiohttp-3.10.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6e395c3d1f773cf0651cd3559e25182eb0c03a2777b53b4575d8adc1149c6e9"}, - {file = "aiohttp-3.10.9-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbdb8def5268f3f9cd753a265756f49228a20ed14a480d151df727808b4531dd"}, - {file = "aiohttp-3.10.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f82ace0ec57c94aaf5b0e118d4366cff5889097412c75aa14b4fd5fc0c44ee3e"}, - {file = "aiohttp-3.10.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6ebdc3b3714afe1b134b3bbeb5f745eed3ecbcff92ab25d80e4ef299e83a5465"}, - {file = "aiohttp-3.10.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f9ca09414003c0e96a735daa1f071f7d7ed06962ef4fa29ceb6c80d06696d900"}, - {file = "aiohttp-3.10.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1298b854fd31d0567cbb916091be9d3278168064fca88e70b8468875ef9ff7e7"}, - {file = "aiohttp-3.10.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:60ad5b8a7452c0f5645c73d4dad7490afd6119d453d302cd5b72b678a85d6044"}, - {file = "aiohttp-3.10.9-cp312-cp312-win32.whl", hash = "sha256:1a0ee6c0d590c917f1b9629371fce5f3d3f22c317aa96fbdcce3260754d7ea21"}, - {file = "aiohttp-3.10.9-cp312-cp312-win_amd64.whl", hash = "sha256:c46131c6112b534b178d4e002abe450a0a29840b61413ac25243f1291613806a"}, - {file = "aiohttp-3.10.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2bd9f3eac515c16c4360a6a00c38119333901b8590fe93c3257a9b536026594d"}, - {file = "aiohttp-3.10.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8cc0d13b4e3b1362d424ce3f4e8c79e1f7247a00d792823ffd640878abf28e56"}, - {file = "aiohttp-3.10.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ba1a599255ad6a41022e261e31bc2f6f9355a419575b391f9655c4d9e5df5ff5"}, - {file = "aiohttp-3.10.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:776e9f3c9b377fcf097c4a04b241b15691e6662d850168642ff976780609303c"}, - {file = "aiohttp-3.10.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8debb45545ad95b58cc16c3c1cc19ad82cffcb106db12b437885dbee265f0ab5"}, - {file = "aiohttp-3.10.9-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2555e4949c8d8782f18ef20e9d39730d2656e218a6f1a21a4c4c0b56546a02e"}, - {file = "aiohttp-3.10.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c54dc329cd44f7f7883a9f4baaefe686e8b9662e2c6c184ea15cceee587d8d69"}, - {file = "aiohttp-3.10.9-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e709d6ac598c5416f879bb1bae3fd751366120ac3fa235a01de763537385d036"}, - {file = "aiohttp-3.10.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:17c272cfe7b07a5bb0c6ad3f234e0c336fb53f3bf17840f66bd77b5815ab3d16"}, - {file = "aiohttp-3.10.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0c21c82df33b264216abffff9f8370f303dab65d8eee3767efbbd2734363f677"}, - {file = "aiohttp-3.10.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:9331dd34145ff105177855017920dde140b447049cd62bb589de320fd6ddd582"}, - {file = "aiohttp-3.10.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ac3196952c673822ebed8871cf8802e17254fff2a2ed4835d9c045d9b88c5ec7"}, - {file = "aiohttp-3.10.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2c33fa6e10bb7ed262e3ff03cc69d52869514f16558db0626a7c5c61dde3c29f"}, - {file = "aiohttp-3.10.9-cp313-cp313-win32.whl", hash = "sha256:a14e4b672c257a6b94fe934ee62666bacbc8e45b7876f9dd9502d0f0fe69db16"}, - {file = "aiohttp-3.10.9-cp313-cp313-win_amd64.whl", hash = "sha256:a35ed3d03910785f7d9d6f5381f0c24002b2b888b298e6f941b2fc94c5055fcd"}, - {file = "aiohttp-3.10.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5f392ef50e22c31fa49b5a46af7f983fa3f118f3eccb8522063bee8bfa6755f8"}, - {file = "aiohttp-3.10.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d1f5c9169e26db6a61276008582d945405b8316aae2bb198220466e68114a0f5"}, - {file = "aiohttp-3.10.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8d9d10d10ec27c0d46ddaecc3c5598c4db9ce4e6398ca872cdde0525765caa2f"}, - {file = "aiohttp-3.10.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d97273a52d7f89a75b11ec386f786d3da7723d7efae3034b4dda79f6f093edc1"}, - {file = "aiohttp-3.10.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d271f770b52e32236d945911b2082f9318e90ff835d45224fa9e28374303f729"}, - {file = "aiohttp-3.10.9-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7003f33f5f7da1eb02f0446b0f8d2ccf57d253ca6c2e7a5732d25889da82b517"}, - {file = "aiohttp-3.10.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6e00c8a92e7663ed2be6fcc08a2997ff06ce73c8080cd0df10cc0321a3168d7"}, - {file = "aiohttp-3.10.9-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a61df62966ce6507aafab24e124e0c3a1cfbe23c59732987fc0fd0d71daa0b88"}, - {file = "aiohttp-3.10.9-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:60555211a006d26e1a389222e3fab8cd379f28e0fbf7472ee55b16c6c529e3a6"}, - {file = "aiohttp-3.10.9-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:d15a29424e96fad56dc2f3abed10a89c50c099f97d2416520c7a543e8fddf066"}, - {file = "aiohttp-3.10.9-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:a19caae0d670771ea7854ca30df76f676eb47e0fd9b2ee4392d44708f272122d"}, - {file = "aiohttp-3.10.9-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:99f9678bf0e2b1b695e8028fedac24ab6770937932eda695815d5a6618c37e04"}, - {file = "aiohttp-3.10.9-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2914caa46054f3b5ff910468d686742ff8cff54b8a67319d75f5d5945fd0a13d"}, - {file = "aiohttp-3.10.9-cp38-cp38-win32.whl", hash = "sha256:0bc059ecbce835630e635879f5f480a742e130d9821fbe3d2f76610a6698ee25"}, - {file = "aiohttp-3.10.9-cp38-cp38-win_amd64.whl", hash = "sha256:e883b61b75ca6efc2541fcd52a5c8ccfe288b24d97e20ac08fdf343b8ac672ea"}, - {file = "aiohttp-3.10.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fcd546782d03181b0b1d20b43d612429a90a68779659ba8045114b867971ab71"}, - {file = "aiohttp-3.10.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:85711eec2d875cd88c7eb40e734c4ca6d9ae477d6f26bd2b5bb4f7f60e41b156"}, - {file = "aiohttp-3.10.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:02d1d6610588bcd743fae827bd6f2e47e0d09b346f230824b4c6fb85c6065f9c"}, - {file = "aiohttp-3.10.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3668d0c2a4d23fb136a753eba42caa2c0abbd3d9c5c87ee150a716a16c6deec1"}, - {file = "aiohttp-3.10.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d7c071235a47d407b0e93aa6262b49422dbe48d7d8566e1158fecc91043dd948"}, - {file = "aiohttp-3.10.9-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ac74e794e3aee92ae8f571bfeaa103a141e409863a100ab63a253b1c53b707eb"}, - {file = "aiohttp-3.10.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bbf94d4a0447705b7775417ca8bb8086cc5482023a6e17cdc8f96d0b1b5aba6"}, - {file = "aiohttp-3.10.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb0b2d5d51f96b6cc19e6ab46a7b684be23240426ae951dcdac9639ab111b45e"}, - {file = "aiohttp-3.10.9-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e83dfefb4f7d285c2d6a07a22268344a97d61579b3e0dce482a5be0251d672ab"}, - {file = "aiohttp-3.10.9-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f0a44bb40b6aaa4fb9a5c1ee07880570ecda2065433a96ccff409c9c20c1624a"}, - {file = "aiohttp-3.10.9-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c2b627d3c8982691b06d89d31093cee158c30629fdfebe705a91814d49b554f8"}, - {file = "aiohttp-3.10.9-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:03690541e4cc866eef79626cfa1ef4dd729c5c1408600c8cb9e12e1137eed6ab"}, - {file = "aiohttp-3.10.9-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ad3675c126f2a95bde637d162f8231cff6bc0bc9fbe31bd78075f9ff7921e322"}, - {file = "aiohttp-3.10.9-cp39-cp39-win32.whl", hash = "sha256:1321658f12b6caffafdc35cfba6c882cb014af86bef4e78c125e7e794dfb927b"}, - {file = "aiohttp-3.10.9-cp39-cp39-win_amd64.whl", hash = "sha256:9fdf5c839bf95fc67be5794c780419edb0dbef776edcfc6c2e5e2ffd5ee755fa"}, - {file = "aiohttp-3.10.9.tar.gz", hash = "sha256:143b0026a9dab07a05ad2dd9e46aa859bffdd6348ddc5967b42161168c24f857"}, + {file = "aiohttp-3.10.10-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:be7443669ae9c016b71f402e43208e13ddf00912f47f623ee5994e12fc7d4b3f"}, + {file = "aiohttp-3.10.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7b06b7843929e41a94ea09eb1ce3927865387e3e23ebe108e0d0d09b08d25be9"}, + {file = "aiohttp-3.10.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:333cf6cf8e65f6a1e06e9eb3e643a0c515bb850d470902274239fea02033e9a8"}, + {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:274cfa632350225ce3fdeb318c23b4a10ec25c0e2c880eff951a3842cf358ac1"}, + {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9e5e4a85bdb56d224f412d9c98ae4cbd032cc4f3161818f692cd81766eee65a"}, + {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b606353da03edcc71130b52388d25f9a30a126e04caef1fd637e31683033abd"}, + {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab5a5a0c7a7991d90446a198689c0535be89bbd6b410a1f9a66688f0880ec026"}, + {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:578a4b875af3e0daaf1ac6fa983d93e0bbfec3ead753b6d6f33d467100cdc67b"}, + {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8105fd8a890df77b76dd3054cddf01a879fc13e8af576805d667e0fa0224c35d"}, + {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3bcd391d083f636c06a68715e69467963d1f9600f85ef556ea82e9ef25f043f7"}, + {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fbc6264158392bad9df19537e872d476f7c57adf718944cc1e4495cbabf38e2a"}, + {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e48d5021a84d341bcaf95c8460b152cfbad770d28e5fe14a768988c461b821bc"}, + {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2609e9ab08474702cc67b7702dbb8a80e392c54613ebe80db7e8dbdb79837c68"}, + {file = "aiohttp-3.10.10-cp310-cp310-win32.whl", hash = "sha256:84afcdea18eda514c25bc68b9af2a2b1adea7c08899175a51fe7c4fb6d551257"}, + {file = "aiohttp-3.10.10-cp310-cp310-win_amd64.whl", hash = "sha256:9c72109213eb9d3874f7ac8c0c5fa90e072d678e117d9061c06e30c85b4cf0e6"}, + {file = "aiohttp-3.10.10-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c30a0eafc89d28e7f959281b58198a9fa5e99405f716c0289b7892ca345fe45f"}, + {file = "aiohttp-3.10.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:258c5dd01afc10015866114e210fb7365f0d02d9d059c3c3415382ab633fcbcb"}, + {file = "aiohttp-3.10.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:15ecd889a709b0080f02721255b3f80bb261c2293d3c748151274dfea93ac871"}, + {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3935f82f6f4a3820270842e90456ebad3af15810cf65932bd24da4463bc0a4c"}, + {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:413251f6fcf552a33c981c4709a6bba37b12710982fec8e558ae944bfb2abd38"}, + {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1720b4f14c78a3089562b8875b53e36b51c97c51adc53325a69b79b4b48ebcb"}, + {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:679abe5d3858b33c2cf74faec299fda60ea9de62916e8b67e625d65bf069a3b7"}, + {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79019094f87c9fb44f8d769e41dbb664d6e8fcfd62f665ccce36762deaa0e911"}, + {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe2fb38c2ed905a2582948e2de560675e9dfbee94c6d5ccdb1301c6d0a5bf092"}, + {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a3f00003de6eba42d6e94fabb4125600d6e484846dbf90ea8e48a800430cc142"}, + {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1bbb122c557a16fafc10354b9d99ebf2f2808a660d78202f10ba9d50786384b9"}, + {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:30ca7c3b94708a9d7ae76ff281b2f47d8eaf2579cd05971b5dc681db8caac6e1"}, + {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:df9270660711670e68803107d55c2b5949c2e0f2e4896da176e1ecfc068b974a"}, + {file = "aiohttp-3.10.10-cp311-cp311-win32.whl", hash = "sha256:aafc8ee9b742ce75044ae9a4d3e60e3d918d15a4c2e08a6c3c3e38fa59b92d94"}, + {file = "aiohttp-3.10.10-cp311-cp311-win_amd64.whl", hash = "sha256:362f641f9071e5f3ee6f8e7d37d5ed0d95aae656adf4ef578313ee585b585959"}, + {file = "aiohttp-3.10.10-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9294bbb581f92770e6ed5c19559e1e99255e4ca604a22c5c6397b2f9dd3ee42c"}, + {file = "aiohttp-3.10.10-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a8fa23fe62c436ccf23ff930149c047f060c7126eae3ccea005f0483f27b2e28"}, + {file = "aiohttp-3.10.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c6a5b8c7926ba5d8545c7dd22961a107526562da31a7a32fa2456baf040939f"}, + {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:007ec22fbc573e5eb2fb7dec4198ef8f6bf2fe4ce20020798b2eb5d0abda6138"}, + {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9627cc1a10c8c409b5822a92d57a77f383b554463d1884008e051c32ab1b3742"}, + {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:50edbcad60d8f0e3eccc68da67f37268b5144ecc34d59f27a02f9611c1d4eec7"}, + {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a45d85cf20b5e0d0aa5a8dca27cce8eddef3292bc29d72dcad1641f4ed50aa16"}, + {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b00807e2605f16e1e198f33a53ce3c4523114059b0c09c337209ae55e3823a8"}, + {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f2d4324a98062be0525d16f768a03e0bbb3b9fe301ceee99611dc9a7953124e6"}, + {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:438cd072f75bb6612f2aca29f8bd7cdf6e35e8f160bc312e49fbecab77c99e3a"}, + {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:baa42524a82f75303f714108fea528ccacf0386af429b69fff141ffef1c534f9"}, + {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a7d8d14fe962153fc681f6366bdec33d4356f98a3e3567782aac1b6e0e40109a"}, + {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c1277cd707c465cd09572a774559a3cc7c7a28802eb3a2a9472588f062097205"}, + {file = "aiohttp-3.10.10-cp312-cp312-win32.whl", hash = "sha256:59bb3c54aa420521dc4ce3cc2c3fe2ad82adf7b09403fa1f48ae45c0cbde6628"}, + {file = "aiohttp-3.10.10-cp312-cp312-win_amd64.whl", hash = "sha256:0e1b370d8007c4ae31ee6db7f9a2fe801a42b146cec80a86766e7ad5c4a259cf"}, + {file = "aiohttp-3.10.10-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ad7593bb24b2ab09e65e8a1d385606f0f47c65b5a2ae6c551db67d6653e78c28"}, + {file = "aiohttp-3.10.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1eb89d3d29adaf533588f209768a9c02e44e4baf832b08118749c5fad191781d"}, + {file = "aiohttp-3.10.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3fe407bf93533a6fa82dece0e74dbcaaf5d684e5a51862887f9eaebe6372cd79"}, + {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50aed5155f819873d23520919e16703fc8925e509abbb1a1491b0087d1cd969e"}, + {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f05e9727ce409358baa615dbeb9b969db94324a79b5a5cea45d39bdb01d82e6"}, + {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dffb610a30d643983aeb185ce134f97f290f8935f0abccdd32c77bed9388b42"}, + {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa6658732517ddabe22c9036479eabce6036655ba87a0224c612e1ae6af2087e"}, + {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:741a46d58677d8c733175d7e5aa618d277cd9d880301a380fd296975a9cdd7bc"}, + {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e00e3505cd80440f6c98c6d69269dcc2a119f86ad0a9fd70bccc59504bebd68a"}, + {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ffe595f10566f8276b76dc3a11ae4bb7eba1aac8ddd75811736a15b0d5311414"}, + {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdfcf6443637c148c4e1a20c48c566aa694fa5e288d34b20fcdc58507882fed3"}, + {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d183cf9c797a5291e8301790ed6d053480ed94070637bfaad914dd38b0981f67"}, + {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:77abf6665ae54000b98b3c742bc6ea1d1fb31c394bcabf8b5d2c1ac3ebfe7f3b"}, + {file = "aiohttp-3.10.10-cp313-cp313-win32.whl", hash = "sha256:4470c73c12cd9109db8277287d11f9dd98f77fc54155fc71a7738a83ffcc8ea8"}, + {file = "aiohttp-3.10.10-cp313-cp313-win_amd64.whl", hash = "sha256:486f7aabfa292719a2753c016cc3a8f8172965cabb3ea2e7f7436c7f5a22a151"}, + {file = "aiohttp-3.10.10-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:1b66ccafef7336a1e1f0e389901f60c1d920102315a56df85e49552308fc0486"}, + {file = "aiohttp-3.10.10-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:acd48d5b80ee80f9432a165c0ac8cbf9253eaddb6113269a5e18699b33958dbb"}, + {file = "aiohttp-3.10.10-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3455522392fb15ff549d92fbf4b73b559d5e43dc522588f7eb3e54c3f38beee7"}, + {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45c3b868724137f713a38376fef8120c166d1eadd50da1855c112fe97954aed8"}, + {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:da1dee8948d2137bb51fbb8a53cce6b1bcc86003c6b42565f008438b806cccd8"}, + {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c5ce2ce7c997e1971b7184ee37deb6ea9922ef5163c6ee5aa3c274b05f9e12fa"}, + {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28529e08fde6f12eba8677f5a8608500ed33c086f974de68cc65ab218713a59d"}, + {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f7db54c7914cc99d901d93a34704833568d86c20925b2762f9fa779f9cd2e70f"}, + {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:03a42ac7895406220124c88911ebee31ba8b2d24c98507f4a8bf826b2937c7f2"}, + {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:7e338c0523d024fad378b376a79faff37fafb3c001872a618cde1d322400a572"}, + {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:038f514fe39e235e9fef6717fbf944057bfa24f9b3db9ee551a7ecf584b5b480"}, + {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:64f6c17757251e2b8d885d728b6433d9d970573586a78b78ba8929b0f41d045a"}, + {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:93429602396f3383a797a2a70e5f1de5df8e35535d7806c9f91df06f297e109b"}, + {file = "aiohttp-3.10.10-cp38-cp38-win32.whl", hash = "sha256:c823bc3971c44ab93e611ab1a46b1eafeae474c0c844aff4b7474287b75fe49c"}, + {file = "aiohttp-3.10.10-cp38-cp38-win_amd64.whl", hash = "sha256:54ca74df1be3c7ca1cf7f4c971c79c2daf48d9aa65dea1a662ae18926f5bc8ce"}, + {file = "aiohttp-3.10.10-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:01948b1d570f83ee7bbf5a60ea2375a89dfb09fd419170e7f5af029510033d24"}, + {file = "aiohttp-3.10.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9fc1500fd2a952c5c8e3b29aaf7e3cc6e27e9cfc0a8819b3bce48cc1b849e4cc"}, + {file = "aiohttp-3.10.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f614ab0c76397661b90b6851a030004dac502e48260ea10f2441abd2207fbcc7"}, + {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00819de9e45d42584bed046314c40ea7e9aea95411b38971082cad449392b08c"}, + {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05646ebe6b94cc93407b3bf34b9eb26c20722384d068eb7339de802154d61bc5"}, + {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:998f3bd3cfc95e9424a6acd7840cbdd39e45bc09ef87533c006f94ac47296090"}, + {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9010c31cd6fa59438da4e58a7f19e4753f7f264300cd152e7f90d4602449762"}, + {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ea7ffc6d6d6f8a11e6f40091a1040995cdff02cfc9ba4c2f30a516cb2633554"}, + {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ef9c33cc5cbca35808f6c74be11eb7f5f6b14d2311be84a15b594bd3e58b5527"}, + {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ce0cdc074d540265bfeb31336e678b4e37316849d13b308607efa527e981f5c2"}, + {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:597a079284b7ee65ee102bc3a6ea226a37d2b96d0418cc9047490f231dc09fe8"}, + {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:7789050d9e5d0c309c706953e5e8876e38662d57d45f936902e176d19f1c58ab"}, + {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e7f8b04d83483577fd9200461b057c9f14ced334dcb053090cea1da9c8321a91"}, + {file = "aiohttp-3.10.10-cp39-cp39-win32.whl", hash = "sha256:c02a30b904282777d872266b87b20ed8cc0d1501855e27f831320f471d54d983"}, + {file = "aiohttp-3.10.10-cp39-cp39-win_amd64.whl", hash = "sha256:edfe3341033a6b53a5c522c802deb2079eee5cbfbb0af032a55064bd65c73a23"}, + {file = "aiohttp-3.10.10.tar.gz", hash = "sha256:0631dd7c9f0822cc61c88586ca76d5b5ada26538097d0f1df510b082bad3411a"}, ] [package.dependencies] @@ -180,13 +180,13 @@ files = [ [[package]] name = "distlib" -version = "0.3.8" +version = "0.3.9" description = "Distribution utilities" optional = false python-versions = "*" files = [ - {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, - {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, + {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, + {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, ] [[package]] @@ -293,70 +293,70 @@ files = [ [[package]] name = "grpcio" -version = "1.66.2" +version = "1.67.0" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.8" files = [ - {file = "grpcio-1.66.2-cp310-cp310-linux_armv7l.whl", hash = "sha256:fe96281713168a3270878255983d2cb1a97e034325c8c2c25169a69289d3ecfa"}, - {file = "grpcio-1.66.2-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:73fc8f8b9b5c4a03e802b3cd0c18b2b06b410d3c1dcbef989fdeb943bd44aff7"}, - {file = "grpcio-1.66.2-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:03b0b307ba26fae695e067b94cbb014e27390f8bc5ac7a3a39b7723fed085604"}, - {file = "grpcio-1.66.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d69ce1f324dc2d71e40c9261d3fdbe7d4c9d60f332069ff9b2a4d8a257c7b2b"}, - {file = "grpcio-1.66.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05bc2ceadc2529ab0b227b1310d249d95d9001cd106aa4d31e8871ad3c428d73"}, - {file = "grpcio-1.66.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ac475e8da31484efa25abb774674d837b343afb78bb3bcdef10f81a93e3d6bf"}, - {file = "grpcio-1.66.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0be4e0490c28da5377283861bed2941d1d20ec017ca397a5df4394d1c31a9b50"}, - {file = "grpcio-1.66.2-cp310-cp310-win32.whl", hash = "sha256:4e504572433f4e72b12394977679161d495c4c9581ba34a88d843eaf0f2fbd39"}, - {file = "grpcio-1.66.2-cp310-cp310-win_amd64.whl", hash = "sha256:2018b053aa15782db2541ca01a7edb56a0bf18c77efed975392583725974b249"}, - {file = "grpcio-1.66.2-cp311-cp311-linux_armv7l.whl", hash = "sha256:2335c58560a9e92ac58ff2bc5649952f9b37d0735608242973c7a8b94a6437d8"}, - {file = "grpcio-1.66.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:45a3d462826f4868b442a6b8fdbe8b87b45eb4f5b5308168c156b21eca43f61c"}, - {file = "grpcio-1.66.2-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:a9539f01cb04950fd4b5ab458e64a15f84c2acc273670072abe49a3f29bbad54"}, - {file = "grpcio-1.66.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce89f5876662f146d4c1f695dda29d4433a5d01c8681fbd2539afff535da14d4"}, - {file = "grpcio-1.66.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25a14af966438cddf498b2e338f88d1c9706f3493b1d73b93f695c99c5f0e2a"}, - {file = "grpcio-1.66.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6001e575b8bbd89eee11960bb640b6da6ae110cf08113a075f1e2051cc596cae"}, - {file = "grpcio-1.66.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4ea1d062c9230278793820146c95d038dc0f468cbdd172eec3363e42ff1c7d01"}, - {file = "grpcio-1.66.2-cp311-cp311-win32.whl", hash = "sha256:38b68498ff579a3b1ee8f93a05eb48dc2595795f2f62716e797dc24774c1aaa8"}, - {file = "grpcio-1.66.2-cp311-cp311-win_amd64.whl", hash = "sha256:6851de821249340bdb100df5eacfecfc4e6075fa85c6df7ee0eb213170ec8e5d"}, - {file = "grpcio-1.66.2-cp312-cp312-linux_armv7l.whl", hash = "sha256:802d84fd3d50614170649853d121baaaa305de7b65b3e01759247e768d691ddf"}, - {file = "grpcio-1.66.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:80fd702ba7e432994df208f27514280b4b5c6843e12a48759c9255679ad38db8"}, - {file = "grpcio-1.66.2-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:12fda97ffae55e6526825daf25ad0fa37483685952b5d0f910d6405c87e3adb6"}, - {file = "grpcio-1.66.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:950da58d7d80abd0ea68757769c9db0a95b31163e53e5bb60438d263f4bed7b7"}, - {file = "grpcio-1.66.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e636ce23273683b00410f1971d209bf3689238cf5538d960adc3cdfe80dd0dbd"}, - {file = "grpcio-1.66.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a917d26e0fe980b0ac7bfcc1a3c4ad6a9a4612c911d33efb55ed7833c749b0ee"}, - {file = "grpcio-1.66.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49f0ca7ae850f59f828a723a9064cadbed90f1ece179d375966546499b8a2c9c"}, - {file = "grpcio-1.66.2-cp312-cp312-win32.whl", hash = "sha256:31fd163105464797a72d901a06472860845ac157389e10f12631025b3e4d0453"}, - {file = "grpcio-1.66.2-cp312-cp312-win_amd64.whl", hash = "sha256:ff1f7882e56c40b0d33c4922c15dfa30612f05fb785074a012f7cda74d1c3679"}, - {file = "grpcio-1.66.2-cp313-cp313-linux_armv7l.whl", hash = "sha256:3b00efc473b20d8bf83e0e1ae661b98951ca56111feb9b9611df8efc4fe5d55d"}, - {file = "grpcio-1.66.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1caa38fb22a8578ab8393da99d4b8641e3a80abc8fd52646f1ecc92bcb8dee34"}, - {file = "grpcio-1.66.2-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:c408f5ef75cfffa113cacd8b0c0e3611cbfd47701ca3cdc090594109b9fcbaed"}, - {file = "grpcio-1.66.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c806852deaedee9ce8280fe98955c9103f62912a5b2d5ee7e3eaa284a6d8d8e7"}, - {file = "grpcio-1.66.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f145cc21836c332c67baa6fc81099d1d27e266401565bf481948010d6ea32d46"}, - {file = "grpcio-1.66.2-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:73e3b425c1e155730273f73e419de3074aa5c5e936771ee0e4af0814631fb30a"}, - {file = "grpcio-1.66.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9c509a4f78114cbc5f0740eb3d7a74985fd2eff022971bc9bc31f8bc93e66a3b"}, - {file = "grpcio-1.66.2-cp313-cp313-win32.whl", hash = "sha256:20657d6b8cfed7db5e11b62ff7dfe2e12064ea78e93f1434d61888834bc86d75"}, - {file = "grpcio-1.66.2-cp313-cp313-win_amd64.whl", hash = "sha256:fb70487c95786e345af5e854ffec8cb8cc781bcc5df7930c4fbb7feaa72e1cdf"}, - {file = "grpcio-1.66.2-cp38-cp38-linux_armv7l.whl", hash = "sha256:a18e20d8321c6400185b4263e27982488cb5cdd62da69147087a76a24ef4e7e3"}, - {file = "grpcio-1.66.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:02697eb4a5cbe5a9639f57323b4c37bcb3ab2d48cec5da3dc2f13334d72790dd"}, - {file = "grpcio-1.66.2-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:99a641995a6bc4287a6315989ee591ff58507aa1cbe4c2e70d88411c4dcc0839"}, - {file = "grpcio-1.66.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ed71e81782966ffead60268bbda31ea3f725ebf8aa73634d5dda44f2cf3fb9c"}, - {file = "grpcio-1.66.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbd27c24a4cc5e195a7f56cfd9312e366d5d61b86e36d46bbe538457ea6eb8dd"}, - {file = "grpcio-1.66.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d9a9724a156c8ec6a379869b23ba3323b7ea3600851c91489b871e375f710bc8"}, - {file = "grpcio-1.66.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d8d4732cc5052e92cea2f78b233c2e2a52998ac40cd651f40e398893ad0d06ec"}, - {file = "grpcio-1.66.2-cp38-cp38-win32.whl", hash = "sha256:7b2c86457145ce14c38e5bf6bdc19ef88e66c5fee2c3d83285c5aef026ba93b3"}, - {file = "grpcio-1.66.2-cp38-cp38-win_amd64.whl", hash = "sha256:e88264caad6d8d00e7913996030bac8ad5f26b7411495848cc218bd3a9040b6c"}, - {file = "grpcio-1.66.2-cp39-cp39-linux_armv7l.whl", hash = "sha256:c400ba5675b67025c8a9f48aa846f12a39cf0c44df5cd060e23fda5b30e9359d"}, - {file = "grpcio-1.66.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:66a0cd8ba6512b401d7ed46bb03f4ee455839957f28b8d61e7708056a806ba6a"}, - {file = "grpcio-1.66.2-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:06de8ec0bd71be123eec15b0e0d457474931c2c407869b6c349bd9bed4adbac3"}, - {file = "grpcio-1.66.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb57870449dfcfac428afbb5a877829fcb0d6db9d9baa1148705739e9083880e"}, - {file = "grpcio-1.66.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b672abf90a964bfde2d0ecbce30f2329a47498ba75ce6f4da35a2f4532b7acbc"}, - {file = "grpcio-1.66.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ad2efdbe90c73b0434cbe64ed372e12414ad03c06262279b104a029d1889d13e"}, - {file = "grpcio-1.66.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9c3a99c519f4638e700e9e3f83952e27e2ea10873eecd7935823dab0c1c9250e"}, - {file = "grpcio-1.66.2-cp39-cp39-win32.whl", hash = "sha256:78fa51ebc2d9242c0fc5db0feecc57a9943303b46664ad89921f5079e2e4ada7"}, - {file = "grpcio-1.66.2-cp39-cp39-win_amd64.whl", hash = "sha256:728bdf36a186e7f51da73be7f8d09457a03061be848718d0edf000e709418987"}, - {file = "grpcio-1.66.2.tar.gz", hash = "sha256:563588c587b75c34b928bc428548e5b00ea38c46972181a4d8b75ba7e3f24231"}, + {file = "grpcio-1.67.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:bd79929b3bb96b54df1296cd3bf4d2b770bd1df6c2bdf549b49bab286b925cdc"}, + {file = "grpcio-1.67.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:16724ffc956ea42967f5758c2f043faef43cb7e48a51948ab593570570d1e68b"}, + {file = "grpcio-1.67.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:2b7183c80b602b0ad816315d66f2fb7887614ead950416d60913a9a71c12560d"}, + {file = "grpcio-1.67.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:efe32b45dd6d118f5ea2e5deaed417d8a14976325c93812dd831908522b402c9"}, + {file = "grpcio-1.67.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe89295219b9c9e47780a0f1c75ca44211e706d1c598242249fe717af3385ec8"}, + {file = "grpcio-1.67.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa8d025fae1595a207b4e47c2e087cb88d47008494db258ac561c00877d4c8f8"}, + {file = "grpcio-1.67.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f95e15db43e75a534420e04822df91f645664bf4ad21dfaad7d51773c80e6bb4"}, + {file = "grpcio-1.67.0-cp310-cp310-win32.whl", hash = "sha256:a6b9a5c18863fd4b6624a42e2712103fb0f57799a3b29651c0e5b8119a519d65"}, + {file = "grpcio-1.67.0-cp310-cp310-win_amd64.whl", hash = "sha256:b6eb68493a05d38b426604e1dc93bfc0137c4157f7ab4fac5771fd9a104bbaa6"}, + {file = "grpcio-1.67.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:e91d154689639932305b6ea6f45c6e46bb51ecc8ea77c10ef25aa77f75443ad4"}, + {file = "grpcio-1.67.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cb204a742997277da678611a809a8409657b1398aaeebf73b3d9563b7d154c13"}, + {file = "grpcio-1.67.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:ae6de510f670137e755eb2a74b04d1041e7210af2444103c8c95f193340d17ee"}, + {file = "grpcio-1.67.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74b900566bdf68241118f2918d312d3bf554b2ce0b12b90178091ea7d0a17b3d"}, + {file = "grpcio-1.67.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4e95e43447a02aa603abcc6b5e727d093d161a869c83b073f50b9390ecf0fa8"}, + {file = "grpcio-1.67.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0bb94e66cd8f0baf29bd3184b6aa09aeb1a660f9ec3d85da615c5003154bc2bf"}, + {file = "grpcio-1.67.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:82e5bd4b67b17c8c597273663794a6a46a45e44165b960517fe6d8a2f7f16d23"}, + {file = "grpcio-1.67.0-cp311-cp311-win32.whl", hash = "sha256:7fc1d2b9fd549264ae585026b266ac2db53735510a207381be509c315b4af4e8"}, + {file = "grpcio-1.67.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac11ecb34a86b831239cc38245403a8de25037b448464f95c3315819e7519772"}, + {file = "grpcio-1.67.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:227316b5631260e0bef8a3ce04fa7db4cc81756fea1258b007950b6efc90c05d"}, + {file = "grpcio-1.67.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d90cfdafcf4b45a7a076e3e2a58e7bc3d59c698c4f6470b0bb13a4d869cf2273"}, + {file = "grpcio-1.67.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:77196216d5dd6f99af1c51e235af2dd339159f657280e65ce7e12c1a8feffd1d"}, + {file = "grpcio-1.67.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15c05a26a0f7047f720da41dc49406b395c1470eef44ff7e2c506a47ac2c0591"}, + {file = "grpcio-1.67.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3840994689cc8cbb73d60485c594424ad8adb56c71a30d8948d6453083624b52"}, + {file = "grpcio-1.67.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:5a1e03c3102b6451028d5dc9f8591131d6ab3c8a0e023d94c28cb930ed4b5f81"}, + {file = "grpcio-1.67.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:682968427a63d898759474e3b3178d42546e878fdce034fd7474ef75143b64e3"}, + {file = "grpcio-1.67.0-cp312-cp312-win32.whl", hash = "sha256:d01793653248f49cf47e5695e0a79805b1d9d4eacef85b310118ba1dfcd1b955"}, + {file = "grpcio-1.67.0-cp312-cp312-win_amd64.whl", hash = "sha256:985b2686f786f3e20326c4367eebdaed3e7aa65848260ff0c6644f817042cb15"}, + {file = "grpcio-1.67.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:8c9a35b8bc50db35ab8e3e02a4f2a35cfba46c8705c3911c34ce343bd777813a"}, + {file = "grpcio-1.67.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:42199e704095b62688998c2d84c89e59a26a7d5d32eed86d43dc90e7a3bd04aa"}, + {file = "grpcio-1.67.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:c4c425f440fb81f8d0237c07b9322fc0fb6ee2b29fbef5f62a322ff8fcce240d"}, + {file = "grpcio-1.67.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:323741b6699cd2b04a71cb38f502db98f90532e8a40cb675393d248126a268af"}, + {file = "grpcio-1.67.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:662c8e105c5e5cee0317d500eb186ed7a93229586e431c1bf0c9236c2407352c"}, + {file = "grpcio-1.67.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:f6bd2ab135c64a4d1e9e44679a616c9bc944547357c830fafea5c3caa3de5153"}, + {file = "grpcio-1.67.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:2f55c1e0e2ae9bdd23b3c63459ee4c06d223b68aeb1961d83c48fb63dc29bc03"}, + {file = "grpcio-1.67.0-cp313-cp313-win32.whl", hash = "sha256:fd6bc27861e460fe28e94226e3673d46e294ca4673d46b224428d197c5935e69"}, + {file = "grpcio-1.67.0-cp313-cp313-win_amd64.whl", hash = "sha256:cf51d28063338608cd8d3cd64677e922134837902b70ce00dad7f116e3998210"}, + {file = "grpcio-1.67.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:7f200aca719c1c5dc72ab68be3479b9dafccdf03df530d137632c534bb6f1ee3"}, + {file = "grpcio-1.67.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0892dd200ece4822d72dd0952f7112c542a487fc48fe77568deaaa399c1e717d"}, + {file = "grpcio-1.67.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:f4d613fbf868b2e2444f490d18af472ccb47660ea3df52f068c9c8801e1f3e85"}, + {file = "grpcio-1.67.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c69bf11894cad9da00047f46584d5758d6ebc9b5950c0dc96fec7e0bce5cde9"}, + {file = "grpcio-1.67.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9bca3ca0c5e74dea44bf57d27e15a3a3996ce7e5780d61b7c72386356d231db"}, + {file = "grpcio-1.67.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:014dfc020e28a0d9be7e93a91f85ff9f4a87158b7df9952fe23cc42d29d31e1e"}, + {file = "grpcio-1.67.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d4ea4509d42c6797539e9ec7496c15473177ce9abc89bc5c71e7abe50fc25737"}, + {file = "grpcio-1.67.0-cp38-cp38-win32.whl", hash = "sha256:9d75641a2fca9ae1ae86454fd25d4c298ea8cc195dbc962852234d54a07060ad"}, + {file = "grpcio-1.67.0-cp38-cp38-win_amd64.whl", hash = "sha256:cff8e54d6a463883cda2fab94d2062aad2f5edd7f06ae3ed030f2a74756db365"}, + {file = "grpcio-1.67.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:62492bd534979e6d7127b8a6b29093161a742dee3875873e01964049d5250a74"}, + {file = "grpcio-1.67.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eef1dce9d1a46119fd09f9a992cf6ab9d9178b696382439446ca5f399d7b96fe"}, + {file = "grpcio-1.67.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:f623c57a5321461c84498a99dddf9d13dac0e40ee056d884d6ec4ebcab647a78"}, + {file = "grpcio-1.67.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54d16383044e681f8beb50f905249e4e7261dd169d4aaf6e52eab67b01cbbbe2"}, + {file = "grpcio-1.67.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2a44e572fb762c668e4812156b81835f7aba8a721b027e2d4bb29fb50ff4d33"}, + {file = "grpcio-1.67.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:391df8b0faac84d42f5b8dfc65f5152c48ed914e13c522fd05f2aca211f8bfad"}, + {file = "grpcio-1.67.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cfd9306511fdfc623a1ba1dc3bc07fbd24e6cfbe3c28b4d1e05177baa2f99617"}, + {file = "grpcio-1.67.0-cp39-cp39-win32.whl", hash = "sha256:30d47dbacfd20cbd0c8be9bfa52fdb833b395d4ec32fe5cff7220afc05d08571"}, + {file = "grpcio-1.67.0-cp39-cp39-win_amd64.whl", hash = "sha256:f55f077685f61f0fbd06ea355142b71e47e4a26d2d678b3ba27248abfe67163a"}, + {file = "grpcio-1.67.0.tar.gz", hash = "sha256:e090b2553e0da1c875449c8e75073dd4415dd71c9bde6a406240fdf4c0ee467c"}, ] [package.extras] -protobuf = ["grpcio-tools (>=1.66.2)"] +protobuf = ["grpcio-tools (>=1.67.0)"] [[package]] name = "identify" @@ -492,38 +492,43 @@ typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} [[package]] name = "mypy" -version = "1.11.2" +version = "1.12.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"}, - {file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"}, - {file = "mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383"}, - {file = "mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8"}, - {file = "mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7"}, - {file = "mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385"}, - {file = "mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca"}, - {file = "mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104"}, - {file = "mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4"}, - {file = "mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6"}, - {file = "mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318"}, - {file = "mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36"}, - {file = "mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987"}, - {file = "mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca"}, - {file = "mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70"}, - {file = "mypy-1.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b"}, - {file = "mypy-1.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86"}, - {file = "mypy-1.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce"}, - {file = "mypy-1.11.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1"}, - {file = "mypy-1.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b"}, - {file = "mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6"}, - {file = "mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70"}, - {file = "mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d"}, - {file = "mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d"}, - {file = "mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24"}, - {file = "mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12"}, - {file = "mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79"}, + {file = "mypy-1.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4397081e620dc4dc18e2f124d5e1d2c288194c2c08df6bdb1db31c38cd1fe1ed"}, + {file = "mypy-1.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:684a9c508a283f324804fea3f0effeb7858eb03f85c4402a967d187f64562469"}, + {file = "mypy-1.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cabe4cda2fa5eca7ac94854c6c37039324baaa428ecbf4de4567279e9810f9e"}, + {file = "mypy-1.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:060a07b10e999ac9e7fa249ce2bdcfa9183ca2b70756f3bce9df7a92f78a3c0a"}, + {file = "mypy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:0eff042d7257f39ba4ca06641d110ca7d2ad98c9c1fb52200fe6b1c865d360ff"}, + {file = "mypy-1.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b86de37a0da945f6d48cf110d5206c5ed514b1ca2614d7ad652d4bf099c7de7"}, + {file = "mypy-1.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:20c7c5ce0c1be0b0aea628374e6cf68b420bcc772d85c3c974f675b88e3e6e57"}, + {file = "mypy-1.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a64ee25f05fc2d3d8474985c58042b6759100a475f8237da1f4faf7fcd7e6309"}, + {file = "mypy-1.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:faca7ab947c9f457a08dcb8d9a8664fd438080e002b0fa3e41b0535335edcf7f"}, + {file = "mypy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:5bc81701d52cc8767005fdd2a08c19980de9ec61a25dbd2a937dfb1338a826f9"}, + {file = "mypy-1.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8462655b6694feb1c99e433ea905d46c478041a8b8f0c33f1dab00ae881b2164"}, + {file = "mypy-1.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:923ea66d282d8af9e0f9c21ffc6653643abb95b658c3a8a32dca1eff09c06475"}, + {file = "mypy-1.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ebf9e796521f99d61864ed89d1fb2926d9ab6a5fab421e457cd9c7e4dd65aa9"}, + {file = "mypy-1.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e478601cc3e3fa9d6734d255a59c7a2e5c2934da4378f3dd1e3411ea8a248642"}, + {file = "mypy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:c72861b7139a4f738344faa0e150834467521a3fba42dc98264e5aa9507dd601"}, + {file = "mypy-1.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52b9e1492e47e1790360a43755fa04101a7ac72287b1a53ce817f35899ba0521"}, + {file = "mypy-1.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:48d3e37dd7d9403e38fa86c46191de72705166d40b8c9f91a3de77350daa0893"}, + {file = "mypy-1.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f106db5ccb60681b622ac768455743ee0e6a857724d648c9629a9bd2ac3f721"}, + {file = "mypy-1.12.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:233e11b3f73ee1f10efada2e6da0f555b2f3a5316e9d8a4a1224acc10e7181d3"}, + {file = "mypy-1.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:4ae8959c21abcf9d73aa6c74a313c45c0b5a188752bf37dace564e29f06e9c1b"}, + {file = "mypy-1.12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eafc1b7319b40ddabdc3db8d7d48e76cfc65bbeeafaa525a4e0fa6b76175467f"}, + {file = "mypy-1.12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9b9ce1ad8daeb049c0b55fdb753d7414260bad8952645367e70ac91aec90e07e"}, + {file = "mypy-1.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfe012b50e1491d439172c43ccb50db66d23fab714d500b57ed52526a1020bb7"}, + {file = "mypy-1.12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2c40658d4fa1ab27cb53d9e2f1066345596af2f8fe4827defc398a09c7c9519b"}, + {file = "mypy-1.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:dee78a8b9746c30c1e617ccb1307b351ded57f0de0d287ca6276378d770006c0"}, + {file = "mypy-1.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b5df6c8a8224f6b86746bda716bbe4dbe0ce89fd67b1fa4661e11bfe38e8ec8"}, + {file = "mypy-1.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5feee5c74eb9749e91b77f60b30771563327329e29218d95bedbe1257e2fe4b0"}, + {file = "mypy-1.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:77278e8c6ffe2abfba6db4125de55f1024de9a323be13d20e4f73b8ed3402bd1"}, + {file = "mypy-1.12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:dcfb754dea911039ac12434d1950d69a2f05acd4d56f7935ed402be09fad145e"}, + {file = "mypy-1.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:06de0498798527451ffb60f68db0d368bd2bae2bbfb5237eae616d4330cc87aa"}, + {file = "mypy-1.12.0-py3-none-any.whl", hash = "sha256:fd313226af375d52e1e36c383f39bf3836e1f192801116b31b090dfcd3ec5266"}, + {file = "mypy-1.12.0.tar.gz", hash = "sha256:65a22d87e757ccd95cbbf6f7e181e6caa87128255eb2b6be901bb71b26d8a99d"}, ] [package.dependencies] @@ -905,103 +910,93 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [[package]] name = "yarl" -version = "1.14.0" +version = "1.15.3" description = "Yet another URL library" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "yarl-1.14.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1bfc25aa6a7c99cf86564210f79a0b7d4484159c67e01232b116e445b3036547"}, - {file = "yarl-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0cf21f46a15d445417de8fc89f2568852cf57fe8ca1ab3d19ddb24d45c0383ae"}, - {file = "yarl-1.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1dda53508df0de87b6e6b0a52d6718ff6c62a5aca8f5552748404963df639269"}, - {file = "yarl-1.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:587c3cc59bc148a9b1c07a019346eda2549bc9f468acd2f9824d185749acf0a6"}, - {file = "yarl-1.14.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3007a5b75cb50140708420fe688c393e71139324df599434633019314ceb8b59"}, - {file = "yarl-1.14.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:06ff23462398333c78b6f4f8d3d70410d657a471c2c5bbe6086133be43fc8f1a"}, - {file = "yarl-1.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:689a99a42ee4583fcb0d3a67a0204664aa1539684aed72bdafcbd505197a91c4"}, - {file = "yarl-1.14.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0547ab1e9345dc468cac8368d88ea4c5bd473ebc1d8d755347d7401982b5dd8"}, - {file = "yarl-1.14.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:742aef0a99844faaac200564ea6f5e08facb285d37ea18bd1a5acf2771f3255a"}, - {file = "yarl-1.14.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:176110bff341b6730f64a1eb3a7070e12b373cf1c910a9337e7c3240497db76f"}, - {file = "yarl-1.14.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46a9772a1efa93f9cd170ad33101c1817c77e0e9914d4fe33e2da299d7cf0f9b"}, - {file = "yarl-1.14.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ee2c68e4f2dd1b1c15b849ba1c96fac105fca6ffdb7c1e8be51da6fabbdeafb9"}, - {file = "yarl-1.14.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:047b258e00b99091b6f90355521f026238c63bd76dcf996d93527bb13320eefd"}, - {file = "yarl-1.14.0-cp310-cp310-win32.whl", hash = "sha256:0aa92e3e30a04f9462a25077db689c4ac5ea9ab6cc68a2e563881b987d42f16d"}, - {file = "yarl-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:d9baec588f015d0ee564057aa7574313c53a530662ffad930b7886becc85abdf"}, - {file = "yarl-1.14.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:07f9eaf57719d6721ab15805d85f4b01a5b509a0868d7320134371bcb652152d"}, - {file = "yarl-1.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c14b504a74e58e2deb0378b3eca10f3d076635c100f45b113c18c770b4a47a50"}, - {file = "yarl-1.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:16a682a127930f3fc4e42583becca6049e1d7214bcad23520c590edd741d2114"}, - {file = "yarl-1.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73bedd2be05f48af19f0f2e9e1353921ce0c83f4a1c9e8556ecdcf1f1eae4892"}, - {file = "yarl-1.14.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3ab950f8814f3b7b5e3eebc117986f817ec933676f68f0a6c5b2137dd7c9c69"}, - {file = "yarl-1.14.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b693c63e7e64b524f54aa4888403c680342d1ad0d97be1707c531584d6aeeb4f"}, - {file = "yarl-1.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85cb3e40eaa98489f1e2e8b29f5ad02ee1ee40d6ce6b88d50cf0f205de1d9d2c"}, - {file = "yarl-1.14.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f24f08b6c9b9818fd80612c97857d28f9779f0d1211653ece9844fc7b414df2"}, - {file = "yarl-1.14.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:29a84a46ec3ebae7a1c024c055612b11e9363a8a23238b3e905552d77a2bc51b"}, - {file = "yarl-1.14.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5cd5dad8366e0168e0fd23d10705a603790484a6dbb9eb272b33673b8f2cce72"}, - {file = "yarl-1.14.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a152751af7ef7b5d5fa6d215756e508dd05eb07d0cf2ba51f3e740076aa74373"}, - {file = "yarl-1.14.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:3d569f877ed9a708e4c71a2d13d2940cb0791da309f70bd970ac1a5c088a0a92"}, - {file = "yarl-1.14.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6a615cad11ec3428020fb3c5a88d85ce1b5c69fd66e9fcb91a7daa5e855325dd"}, - {file = "yarl-1.14.0-cp311-cp311-win32.whl", hash = "sha256:bab03192091681d54e8225c53f270b0517637915d9297028409a2a5114ff4634"}, - {file = "yarl-1.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:985623575e5c4ea763056ffe0e2d63836f771a8c294b3de06d09480538316b13"}, - {file = "yarl-1.14.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fc2c80bc87fba076e6cbb926216c27fba274dae7100a7b9a0983b53132dd99f2"}, - {file = "yarl-1.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:55c144d363ad4626ca744556c049c94e2b95096041ac87098bb363dcc8635e8d"}, - {file = "yarl-1.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b03384eed107dbeb5f625a99dc3a7de8be04fc8480c9ad42fccbc73434170b20"}, - {file = "yarl-1.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f72a0d746d38cb299b79ce3d4d60ba0892c84bbc905d0d49c13df5bace1b65f8"}, - {file = "yarl-1.14.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8648180b34faaea4aa5b5ca7e871d9eb1277033fa439693855cf0ea9195f85f1"}, - {file = "yarl-1.14.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9557c9322aaa33174d285b0c1961fb32499d65ad1866155b7845edc876c3c835"}, - {file = "yarl-1.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f50eb3837012a937a2b649ec872b66ba9541ad9d6f103ddcafb8231cfcafd22"}, - {file = "yarl-1.14.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8892fa575ac9b1b25fae7b221bc4792a273877b9b56a99ee2d8d03eeb3dbb1d2"}, - {file = "yarl-1.14.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6a2c5c5bb2556dfbfffffc2bcfb9c235fd2b566d5006dfb2a37afc7e3278a07"}, - {file = "yarl-1.14.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ab3abc0b78a5dfaa4795a6afbe7b282b6aa88d81cf8c1bb5e394993d7cae3457"}, - {file = "yarl-1.14.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:47eede5d11d669ab3759b63afb70d28d5328c14744b8edba3323e27dc52d298d"}, - {file = "yarl-1.14.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fe4d2536c827f508348d7b40c08767e8c7071614250927233bf0c92170451c0a"}, - {file = "yarl-1.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0fd7b941dd1b00b5f0acb97455fea2c4b7aac2dd31ea43fb9d155e9bc7b78664"}, - {file = "yarl-1.14.0-cp312-cp312-win32.whl", hash = "sha256:99ff3744f5fe48288be6bc402533b38e89749623a43208e1d57091fc96b783b9"}, - {file = "yarl-1.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:1ca3894e9e9f72da93544f64988d9c052254a338a9f855165f37f51edb6591de"}, - {file = "yarl-1.14.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5d02d700705d67e09e1f57681f758f0b9d4412eeb70b2eb8d96ca6200b486db3"}, - {file = "yarl-1.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:30600ba5db60f7c0820ef38a2568bb7379e1418ecc947a0f76fd8b2ff4257a97"}, - {file = "yarl-1.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e85d86527baebb41a214cc3b45c17177177d900a2ad5783dbe6f291642d4906f"}, - {file = "yarl-1.14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37001e5d4621cef710c8dc1429ca04e189e572f128ab12312eab4e04cf007132"}, - {file = "yarl-1.14.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f4f4547944d4f5cfcdc03f3f097d6f05bbbc915eaaf80a2ee120d0e756de377d"}, - {file = "yarl-1.14.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75ff4c819757f9bdb35de049a509814d6ce851fe26f06eb95a392a5640052482"}, - {file = "yarl-1.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68ac1a09392ed6e3fd14be880d39b951d7b981fd135416db7d18a6208c536561"}, - {file = "yarl-1.14.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96952f642ac69075e44c7d0284528938fdff39422a1d90d3e45ce40b72e5e2d9"}, - {file = "yarl-1.14.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a56fbe3d7f3bce1d060ea18d2413a2ca9ca814eea7cedc4d247b5f338d54844e"}, - {file = "yarl-1.14.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7e2637d75e92763d1322cb5041573279ec43a80c0f7fbbd2d64f5aee98447b17"}, - {file = "yarl-1.14.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:9abe80ae2c9d37c17599557b712e6515f4100a80efb2cda15f5f070306477cd2"}, - {file = "yarl-1.14.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:217a782020b875538eebf3948fac3a7f9bbbd0fd9bf8538f7c2ad7489e80f4e8"}, - {file = "yarl-1.14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9cfef3f14f75bf6aba73a76caf61f9d00865912a04a4393c468a7ce0981b519"}, - {file = "yarl-1.14.0-cp313-cp313-win32.whl", hash = "sha256:d8361c7d04e6a264481f0b802e395f647cd3f8bbe27acfa7c12049efea675bd1"}, - {file = "yarl-1.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:bc24f968b82455f336b79bf37dbb243b7d76cd40897489888d663d4e028f5069"}, - {file = "yarl-1.14.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:91d875f75fabf76b3018c5f196bf3d308ed2b49ddcb46c1576d6b075754a1393"}, - {file = "yarl-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4009def9be3a7e5175db20aa2d7307ecd00bbf50f7f0f989300710eee1d0b0b9"}, - {file = "yarl-1.14.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:582cedde49603f139be572252a318b30dc41039bc0b8165f070f279e5d12187f"}, - {file = "yarl-1.14.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbd9ff43a04f8ffe8a959a944c2dca10d22f5f99fc6a459f49c3ebfb409309d9"}, - {file = "yarl-1.14.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9f805e37ed16cc212fdc538a608422d7517e7faf539bedea4fe69425bc55d76"}, - {file = "yarl-1.14.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:95e16e9eaa2d7f5d87421b8fe694dd71606aa61d74b824c8d17fc85cc51983d1"}, - {file = "yarl-1.14.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:816d24f584edefcc5ca63428f0b38fee00b39fe64e3c5e558f895a18983efe96"}, - {file = "yarl-1.14.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd2660c01367eb3ef081b8fa0a5da7fe767f9427aa82023a961a5f28f0d4af6c"}, - {file = "yarl-1.14.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:94b2bb9bcfd5be9d27004ea4398fb640373dd0c1a9e219084f42c08f77a720ab"}, - {file = "yarl-1.14.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c2089a9afef887664115f7fa6d3c0edd6454adaca5488dba836ca91f60401075"}, - {file = "yarl-1.14.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:2192f718db4a8509f63dd6d950f143279211fa7e6a2c612edc17d85bf043d36e"}, - {file = "yarl-1.14.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:8385ab36bf812e9d37cf7613999a87715f27ef67a53f0687d28c44b819df7cb0"}, - {file = "yarl-1.14.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b4c1ecba93e7826dc71ddba75fb7740cdb52e7bd0be9f03136b83f54e6a1f511"}, - {file = "yarl-1.14.0-cp38-cp38-win32.whl", hash = "sha256:e749af6c912a7bb441d105c50c1a3da720474e8acb91c89350080dd600228f0e"}, - {file = "yarl-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:147e36331f6f63e08a14640acf12369e041e0751bb70d9362df68c2d9dcf0c87"}, - {file = "yarl-1.14.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a9f917966d27f7ce30039fe8d900f913c5304134096554fd9bea0774bcda6d1"}, - {file = "yarl-1.14.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a2f8fb7f944bcdfecd4e8d855f84c703804a594da5123dd206f75036e536d4d"}, - {file = "yarl-1.14.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f4e475f29a9122f908d0f1f706e1f2fc3656536ffd21014ff8a6f2e1b14d1d8"}, - {file = "yarl-1.14.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8089d4634d8fa2b1806ce44fefa4979b1ab2c12c0bc7ef3dfa45c8a374811348"}, - {file = "yarl-1.14.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b16f6c75cffc2dc0616ea295abb0e1967601bd1fb1e0af6a1de1c6c887f3439"}, - {file = "yarl-1.14.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498b3c55087b9d762636bca9b45f60d37e51d24341786dc01b81253f9552a607"}, - {file = "yarl-1.14.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3f8bfc1db82589ef965ed234b87de30d140db8b6dc50ada9e33951ccd8ec07a"}, - {file = "yarl-1.14.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:625f207b1799e95e7c823f42f473c1e9dbfb6192bd56bba8695656d92be4535f"}, - {file = "yarl-1.14.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:781e2495e408a81e4eaeedeb41ba32b63b1980dddf8b60dbbeff6036bcd35049"}, - {file = "yarl-1.14.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:659603d26d40dd4463200df9bfbc339fbfaed3fe32e5c432fe1dc2b5d4aa94b4"}, - {file = "yarl-1.14.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4e0d45ebf975634468682c8bec021618b3ad52c37619e5c938f8f831fa1ac5c0"}, - {file = "yarl-1.14.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:a2e4725a08cb2b4794db09e350c86dee18202bb8286527210e13a1514dc9a59a"}, - {file = "yarl-1.14.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:19268b4fec1d7760134f2de46ef2608c2920134fb1fa61e451f679e41356dc55"}, - {file = "yarl-1.14.0-cp39-cp39-win32.whl", hash = "sha256:337912bcdcf193ade64b9aae5a4017a0a1950caf8ca140362e361543c6773f21"}, - {file = "yarl-1.14.0-cp39-cp39-win_amd64.whl", hash = "sha256:b6d0147574ce2e7b812c989e50fa72bbc5338045411a836bd066ce5fc8ac0bce"}, - {file = "yarl-1.14.0-py3-none-any.whl", hash = "sha256:c8ed4034f0765f8861620c1f2f2364d2e58520ea288497084dae880424fc0d9f"}, - {file = "yarl-1.14.0.tar.gz", hash = "sha256:88c7d9d58aab0724b979ab5617330acb1c7030b79379c8138c1c8c94e121d1b3"}, + {file = "yarl-1.15.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:14d6f07b7b4b3b8fba521904db58442281730b44318d6abb9908de79e2a4e4f4"}, + {file = "yarl-1.15.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eacd9de9b5b8262818a2e1f88efbd8d523abc8453de238c5d2f6a91fa85032dd"}, + {file = "yarl-1.15.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5a63ed17af784da3de39b82adfd4f8404ad5ee2ec8f616b063f37da3e64e0521"}, + {file = "yarl-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b55cc82ba92c07af6ba619dcf70cc89f7b9626adefb87d251f80f2e77419f1da"}, + {file = "yarl-1.15.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63ba82841ce315e4b5dc8b9345062638c74b1864d38172d0a0403e5a083b0950"}, + {file = "yarl-1.15.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59dce412b2515de05ab2eb6aef19ad7f70857ad436cd65fc4276df007106fb42"}, + {file = "yarl-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e337737b8c9d837e5b4d9e906cc57ed7a639e16e515c8094509b17f556fdb642"}, + {file = "yarl-1.15.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2128315cdc517a45ceb72ec17b256a7940eeb4843c66834c203e7d6580c83405"}, + {file = "yarl-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:69c2d111e67a818e702ba957da8c8e62de916f5c1b3da043f744084c63f12d46"}, + {file = "yarl-1.15.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:d2a70e8bec768be7423d8d465858a3646b34257a20cc02fd92612f1b14931f50"}, + {file = "yarl-1.15.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:efe758958a7bffce68d91ade238df72667e1f18966ed7b1d3d390eead51a8903"}, + {file = "yarl-1.15.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:b765f19e23c29b68e4f8bbadd36f1da2333ba983d8da2d6518e5f0a7eb2579c2"}, + {file = "yarl-1.15.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df494e5a79f2ef8f81f966f787e515760e639c6319a321c16198b379c256a157"}, + {file = "yarl-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:68b27a7d9fb0f145de608da2e45e37fd2397b00266f10487e557f769afa2842d"}, + {file = "yarl-1.15.3-cp310-cp310-win32.whl", hash = "sha256:6d1aba1f644d6e5e16edada31938c11b6c9c97e3bf065742a2c7740d38af0c19"}, + {file = "yarl-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:925e72fc7a4222a5bf6d288876d5afacc8f833b49c4cca85f65089131ba25afa"}, + {file = "yarl-1.15.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dbd4808a209b175b5ebbac24c4798dd7511c5ee522a16f2f0eac78c717dfcdfc"}, + {file = "yarl-1.15.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:20f8bdaf667386cea1a8f49cb69a85f90346656d750d3c1278be1dbc76601065"}, + {file = "yarl-1.15.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:adeac55335669a189189373c93d131ebfc2de3ec04f0d3aa7dff6661f83b89b6"}, + {file = "yarl-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:690d8f702945506b58c9c5834d586e8fd819b845fe6239ab16ebc64a92a6fd3d"}, + {file = "yarl-1.15.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:df7784a29b9689341c17d06d826e3b52ee59d6b6916177e4db0477be7aad5f72"}, + {file = "yarl-1.15.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12c80ec2af97ff3e433699bcabc787ef34e7c08ec038a6e6a25fb81d7bb83607"}, + {file = "yarl-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39533b927c665bcff7da80bf299218e4af12f3e2be27e9c456e29547bcefd631"}, + {file = "yarl-1.15.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db32a5c2912db45e73f80107d178e30f5c48cf596762b3c60ddfebdd655385f0"}, + {file = "yarl-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bde319602111e9acca3c4f87f4205b38ba6166004bf108de47553633f9a580fc"}, + {file = "yarl-1.15.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:493760c4ced954582db83c4760166992c016e1777ebc0f3ef1bb5eb60b2b5924"}, + {file = "yarl-1.15.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d9cd73f7bff5079d87c2622aa418a75d5d3cdc944d3edb905c5dfc3235466eb0"}, + {file = "yarl-1.15.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e924040582499f7514ec64691031504e6224b5ae7224216208fc2c94f8b13c89"}, + {file = "yarl-1.15.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1c3e9ae98719fe180751b093d02dbcc33b78a37e861d0f2c9571720bd31555db"}, + {file = "yarl-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f2911cae6dd012adaaf51494dad4cafb4284ad1f3b588df6ea3e3017e053750"}, + {file = "yarl-1.15.3-cp311-cp311-win32.whl", hash = "sha256:acdfe626607a245aedca35b211f9305a9e7a33349da525bf4ef3caaec8ef51cd"}, + {file = "yarl-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:0ace3927502a9f90a868d62c66623703cf5096dcb586187266e9b964d8dd6c81"}, + {file = "yarl-1.15.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:decf9d76191bfe34835f1abd3fa8ebe8a9cd7e16300a5c7e82b18c0812bb22a2"}, + {file = "yarl-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ce65ed7ad7b6cbca06b0c011b170bd2b0bc56b0a740540e2713e5ac12d7b9b2e"}, + {file = "yarl-1.15.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3cf2b50352df8775591869aaa22c52b64d60376ba99c0802b42778fedc90b775"}, + {file = "yarl-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32e8ebf0080ddd38ec05f8be940a3719e5fe1ab8bb6d2b3f6f8b89c9e34149aa"}, + {file = "yarl-1.15.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05183fd49244517cb11c208d0ae128f2e8a85ddb7caf22ad8b0ffcdf5481fcb6"}, + {file = "yarl-1.15.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:46653b5fd29e63ffe63335da343829a2b00bb43b0bd9bb21240d3b42629629e2"}, + {file = "yarl-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6316af233610b9868eda92cf68c016750cbf50085ac6c51faa17905ddd25605"}, + {file = "yarl-1.15.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5685ebc333c95b75be3a0a83a81b82b6411beee9585eaeb9e2e588ae8df23848"}, + {file = "yarl-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6da6f6c6ee5595658f21bb9d1ecd702f7a7f22f224ac063dfb595624aec4a2e0"}, + {file = "yarl-1.15.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:45c05b87a8494d9820ea1ac82118fd2f1d795d868e94766fe8ff670377bf6280"}, + {file = "yarl-1.15.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:04f930fcc940f96b8b29110c56882bcff8703f87a7b9354d3acf60ffded5a23d"}, + {file = "yarl-1.15.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8df77742b403e71c5d62d22d150e6e35efd6096a15f2c7419815911c62225100"}, + {file = "yarl-1.15.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f785d83ece0998e4ce4fadda22fa6c1ecc40e10f41617013a8726d2e9af0d98f"}, + {file = "yarl-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7794aade99be0d48b69bd5942acddfeff0de3d09c724d9abe4f19736708ef18f"}, + {file = "yarl-1.15.3-cp312-cp312-win32.whl", hash = "sha256:a3a98d70c667c957c7cd0b153d4cb5e45d43f5e2e23de73be6f7b5c883c01f72"}, + {file = "yarl-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:90257bc627897a2c1d562efcd6a6b18887e9dacae795cad2367e8e16df47d966"}, + {file = "yarl-1.15.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f94d8adfdec402ff97cecc243b310c01d571362ca87bcf8def8e15cb3aaac3ee"}, + {file = "yarl-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0328f798052a33803a77d0868c7f802e952127092c1738fc9e7bfcaac7207c5"}, + {file = "yarl-1.15.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f5f0a0691e39c2e7b5c0f23e6765fa6cb162dce99d9ab1897fdd0f7a4a38b6fb"}, + {file = "yarl-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370f646d3654e196ddbf772a2d737fe4e1dd738267015b73ff6267ca592fd9d6"}, + {file = "yarl-1.15.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3487c57bc8f17f2586ae7fd0e77f65cd298d45b64d15f604bbb29f4cce0e7961"}, + {file = "yarl-1.15.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef67989d480358482830dc3bc232709804f46a61e7e9841d3f0b1c13a4735b3b"}, + {file = "yarl-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5ab6c64921802176f56c36aa67c5e6a8baf9557ec1662cb41ecdb5580b67eb9"}, + {file = "yarl-1.15.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb474a06023d01ead9c072b2580c22b2691aa1cabdcc19c3171ab1fa6d8496e3"}, + {file = "yarl-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92f9a45230d3aa8568c1d692ab27bf505a32dfe3b404721458fc374f411e8bd2"}, + {file = "yarl-1.15.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:24cad94cf2f46cc8e4b9cd44e4e8a84483536a6c54554960b02b10b5724ab122"}, + {file = "yarl-1.15.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:380f30073cbd9b740891bb56f44ee31f870e8721269b618ccc9913400936d9f6"}, + {file = "yarl-1.15.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:353306ba6f0218af1aefe4b9c8b3a0b81b209bc75d79357dac6aca70a7b09d6a"}, + {file = "yarl-1.15.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fe03cea925d884b8f1157a7037df2f5b6a6478a64b78ee600832d8a9f044c83e"}, + {file = "yarl-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5c4cc1a438ac52562427330e33891f50a78ffd38d335abc64f93f201c83bdc82"}, + {file = "yarl-1.15.3-cp313-cp313-win32.whl", hash = "sha256:956975a3a1ce1f4537be22278d6a283b8bc74d77671f7f6469ab1e800f4e9b02"}, + {file = "yarl-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:2e61b72cf15922a7a665299a6b6825bd9901d67ec3b9d3cf9b256dc1667c9bb1"}, + {file = "yarl-1.15.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:270fef2b335e60c91ee835c524445e2248af841c8b72f48769ed6c02fbff5873"}, + {file = "yarl-1.15.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:59b77f0682e1917be197fc8229530f0c6fb3ef8e242d8256ba091a3a1c0ef7e6"}, + {file = "yarl-1.15.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc4b999718287073dccd3acb0ef1593961bd7923af08991cb3c94080db503935"}, + {file = "yarl-1.15.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9b251d3f90e125ff0d1f76257329a9190fa1bfd2157344c875580bff6dedc62"}, + {file = "yarl-1.15.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ccb4667e0c0a25815efbfe251d24b56624449a319d4bb497074dd49444fb306"}, + {file = "yarl-1.15.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ac26e43b56dbafb30256906bc763cc1f22e05825ae1ced4c6afbd0e6584f18de"}, + {file = "yarl-1.15.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2207491555af5dbbee4c3179a76766f7bc1ecff858f420ea96f2e105ca42c4dd"}, + {file = "yarl-1.15.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14effa29db6113be065a594e13a0f45afb9c1e374fd22b4bc3a4eff0725184b2"}, + {file = "yarl-1.15.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:19077525cd36c797cae19262e15f2881da33c602fb35d075ff0e4263b51b8b88"}, + {file = "yarl-1.15.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:d80c019083506886df098b7bb0d844e19db7e226736829ef49f892ed0a070fa5"}, + {file = "yarl-1.15.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c24debeec87908a864a2b4cb700f863db9441cabacdb22dc448c5d38b55c6f62"}, + {file = "yarl-1.15.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:1c49fe426c45520b4b8a48544d3a9a58194f39c1b57d92451883f847c299a137"}, + {file = "yarl-1.15.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:66ddcd7ee3264bc937860f4780290d60f6472ca0484c214fe805116a831121e8"}, + {file = "yarl-1.15.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2a5cbbb06559757f091f9e71d3f76c27d4dfe0652cc3f17ccce398b8377bfda4"}, + {file = "yarl-1.15.3-cp39-cp39-win32.whl", hash = "sha256:d798de0b50efb66583fc096bcdaa852ed6ea3485a4eb610d6a634f8010d932f4"}, + {file = "yarl-1.15.3-cp39-cp39-win_amd64.whl", hash = "sha256:8f0b33fd088e93ba5f7f6dd55226630e7b78212752479c8fcc6abbd143b9c1ce"}, + {file = "yarl-1.15.3-py3-none-any.whl", hash = "sha256:a1d49ed6f4b812dde88e937d4c2bd3f13d72c23ef7de1e17a63b7cacef4b5691"}, + {file = "yarl-1.15.3.tar.gz", hash = "sha256:fbcff47f8ba82467f203037f7a30decf5c724211b224682f7236edb0dcbb5b95"}, ] [package.dependencies] @@ -1011,13 +1006,13 @@ propcache = ">=0.2.0" [[package]] name = "ydb" -version = "3.18.1" +version = "3.18.3" description = "YDB Python SDK" optional = false python-versions = "*" files = [ - {file = "ydb-3.18.1-py2.py3-none-any.whl", hash = "sha256:0e0475e07abe36103256cb13244f7e41fe8b85d6bda9b4da8a2c9e7b3204ef68"}, - {file = "ydb-3.18.1.tar.gz", hash = "sha256:e47607a5ce1a2d0023ada29836e27b24b5d002ba0f85e5115524a014f04e75d7"}, + {file = "ydb-3.18.3-py2.py3-none-any.whl", hash = "sha256:90ec7431edf8ce518da57d9b636d4f9f6f2c449190259f129b40caac441bec19"}, + {file = "ydb-3.18.3.tar.gz", hash = "sha256:5c9f849d110324f9cfd4e7a84eb79fe95a9554c949ad936bb7285c126fffd4fa"}, ] [package.dependencies] @@ -1032,4 +1027,4 @@ yc = ["yandexcloud"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "1c41be9ee1fb0b2463c877d84f63930f2351701b2f8f032cbd5983383d3e12f6" +content-hash = "e2324b1357052e3478cc0e0ace0d65dfad79d7b166b7ff268fac03d6e78c7530" diff --git a/pyproject.toml b/pyproject.toml index 185198c..386a571 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.9" -ydb = "^3.18.1" +ydb = "^3.18.3" [tool.poetry.group.dev.dependencies] From 39cfaad2b60fe029959b54aa9fbc0ea9a9b9dd66 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:10:11 +0300 Subject: [PATCH 08/33] basic tests --- .github/workflows/tests.yml | 47 ++ .gitignore | 135 +++++ docker-compose.yml | 11 + poetry.lock | 985 ++++++++++++++++++++++++++++++------ pyproject.toml | 13 +- tests/conftest.py | 57 +++ tests/test_cursor.py | 33 ++ ydb_dbapi/cursors.py | 94 +++- ydb_dbapi/utils.py | 12 + 9 files changed, 1219 insertions(+), 168 deletions(-) create mode 100644 .github/workflows/tests.yml create mode 100644 .gitignore create mode 100644 docker-compose.yml create mode 100644 tests/conftest.py create mode 100644 tests/test_cursor.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..f062155 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,47 @@ +name: Tests + +on: + push: + branches: + - master + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + + concurrency: + group: unit-${{ github.ref }}-${{ matrix.python-version }}-${{ matrix.poetry-version }} + cancel-in-progress: true + + strategy: + fail-fast: false + matrix: + python-version: [3.9] + poetry-version: [1.8] + + steps: + - uses: actions/checkout@v3 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install Poetry ${{ matrix.poetry-version }} + run: | + pip install "poetry~=${{ matrix.poetry-version }}.0" + + # Ensure that Poetry is not upgraded past the version we are testing + poetry add "poetry@~${{ matrix.poetry-version }}" --lock + + - name: Install packages + run: | + poetry install + + - name: Run tests + run: | + poetry run -- pytest tests diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..adcbed1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,135 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# PyCharm +.idea/ + +# VSCode +.vscode diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1a466fa --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ +version: "3.3" +services: + ydb: + image: ydbplatform/local-ydb:trunk + restart: always + ports: + - 2136:2136 + hostname: localhost + environment: + - YDB_USE_IN_MEMORY_PDISKS=true + - YDB_ENABLE_COLUMN_TABLES=true diff --git a/poetry.lock b/poetry.lock index 273ef5a..819439e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -167,6 +167,136 @@ docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphi tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] +[[package]] +name = "bcrypt" +version = "4.2.0" +description = "Modern password hashing for your software and your servers" +optional = false +python-versions = ">=3.7" +files = [ + {file = "bcrypt-4.2.0-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:096a15d26ed6ce37a14c1ac1e48119660f21b24cba457f160a4b830f3fe6b5cb"}, + {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c02d944ca89d9b1922ceb8a46460dd17df1ba37ab66feac4870f6862a1533c00"}, + {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d84cf6d877918620b687b8fd1bf7781d11e8a0998f576c7aa939776b512b98d"}, + {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1bb429fedbe0249465cdd85a58e8376f31bb315e484f16e68ca4c786dcc04291"}, + {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:655ea221910bcac76ea08aaa76df427ef8625f92e55a8ee44fbf7753dbabb328"}, + {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:1ee38e858bf5d0287c39b7a1fc59eec64bbf880c7d504d3a06a96c16e14058e7"}, + {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:0da52759f7f30e83f1e30a888d9163a81353ef224d82dc58eb5bb52efcabc399"}, + {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3698393a1b1f1fd5714524193849d0c6d524d33523acca37cd28f02899285060"}, + {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:762a2c5fb35f89606a9fde5e51392dad0cd1ab7ae64149a8b935fe8d79dd5ed7"}, + {file = "bcrypt-4.2.0-cp37-abi3-win32.whl", hash = "sha256:5a1e8aa9b28ae28020a3ac4b053117fb51c57a010b9f969603ed885f23841458"}, + {file = "bcrypt-4.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:8f6ede91359e5df88d1f5c1ef47428a4420136f3ce97763e31b86dd8280fbdf5"}, + {file = "bcrypt-4.2.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:c52aac18ea1f4a4f65963ea4f9530c306b56ccd0c6f8c8da0c06976e34a6e841"}, + {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3bbbfb2734f0e4f37c5136130405332640a1e46e6b23e000eeff2ba8d005da68"}, + {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3413bd60460f76097ee2e0a493ccebe4a7601918219c02f503984f0a7ee0aebe"}, + {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8d7bb9c42801035e61c109c345a28ed7e84426ae4865511eb82e913df18f58c2"}, + {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3d3a6d28cb2305b43feac298774b997e372e56c7c7afd90a12b3dc49b189151c"}, + {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:9c1c4ad86351339c5f320ca372dfba6cb6beb25e8efc659bedd918d921956bae"}, + {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:27fe0f57bb5573104b5a6de5e4153c60814c711b29364c10a75a54bb6d7ff48d"}, + {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8ac68872c82f1add6a20bd489870c71b00ebacd2e9134a8aa3f98a0052ab4b0e"}, + {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:cb2a8ec2bc07d3553ccebf0746bbf3d19426d1c6d1adbd4fa48925f66af7b9e8"}, + {file = "bcrypt-4.2.0-cp39-abi3-win32.whl", hash = "sha256:77800b7147c9dc905db1cba26abe31e504d8247ac73580b4aa179f98e6608f34"}, + {file = "bcrypt-4.2.0-cp39-abi3-win_amd64.whl", hash = "sha256:61ed14326ee023917ecd093ee6ef422a72f3aec6f07e21ea5f10622b735538a9"}, + {file = "bcrypt-4.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:39e1d30c7233cfc54f5c3f2c825156fe044efdd3e0b9d309512cc514a263ec2a"}, + {file = "bcrypt-4.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f4f4acf526fcd1c34e7ce851147deedd4e26e6402369304220250598b26448db"}, + {file = "bcrypt-4.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:1ff39b78a52cf03fdf902635e4c81e544714861ba3f0efc56558979dd4f09170"}, + {file = "bcrypt-4.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:373db9abe198e8e2c70d12b479464e0d5092cc122b20ec504097b5f2297ed184"}, + {file = "bcrypt-4.2.0.tar.gz", hash = "sha256:cf69eaf5185fd58f268f805b505ce31f9b9fc2d64b376642164e9244540c1221"}, +] + +[package.extras] +tests = ["pytest (>=3.2.1,!=3.3.0)"] +typecheck = ["mypy"] + +[[package]] +name = "certifi" +version = "2024.8.30" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, +] + +[[package]] +name = "cffi" +version = "1.17.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, +] + +[package.dependencies] +pycparser = "*" + [[package]] name = "cfgv" version = "3.4.0" @@ -178,6 +308,180 @@ files = [ {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, ] +[[package]] +name = "charset-normalizer" +version = "3.4.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "cryptography" +version = "43.0.1" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a"}, + {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042"}, + {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494"}, + {file = "cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2"}, + {file = "cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d"}, + {file = "cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1"}, + {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa"}, + {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4"}, + {file = "cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47"}, + {file = "cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2"}, + {file = "cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d"}, +] + +[package.dependencies] +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] +nox = ["nox"] +pep8test = ["check-sdist", "click", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["certifi", "cryptography-vectors (==43.0.1)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + [[package]] name = "distlib" version = "0.3.9" @@ -189,6 +493,103 @@ files = [ {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, ] +[[package]] +name = "distro" +version = "1.9.0" +description = "Distro - an OS platform information API" +optional = false +python-versions = ">=3.6" +files = [ + {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, + {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, +] + +[[package]] +name = "docker" +version = "5.0.0" +description = "A Python library for the Docker Engine API." +optional = false +python-versions = ">=3.6" +files = [ + {file = "docker-5.0.0-py2.py3-none-any.whl", hash = "sha256:fc961d622160e8021c10d1bcabc388c57d55fb1f917175afbe24af442e6879bd"}, + {file = "docker-5.0.0.tar.gz", hash = "sha256:3e8bc47534e0ca9331d72c32f2881bb13b93ded0bcdeab3c833fb7cf61c0a9a5"}, +] + +[package.dependencies] +paramiko = {version = ">=2.4.2", optional = true, markers = "extra == \"ssh\""} +pywin32 = {version = "227", markers = "sys_platform == \"win32\""} +requests = ">=2.14.2,<2.18.0 || >2.18.0" +websocket-client = ">=0.32.0" + +[package.extras] +ssh = ["paramiko (>=2.4.2)"] +tls = ["cryptography (>=3.4.7)", "idna (>=2.0.0)", "pyOpenSSL (>=17.5.0)"] + +[[package]] +name = "docker-compose" +version = "1.29.2" +description = "Multi-container orchestration for Docker" +optional = false +python-versions = ">=3.4" +files = [ + {file = "docker-compose-1.29.2.tar.gz", hash = "sha256:4c8cd9d21d237412793d18bd33110049ee9af8dab3fe2c213bbd0733959b09b7"}, + {file = "docker_compose-1.29.2-py2.py3-none-any.whl", hash = "sha256:8d5589373b35c8d3b1c8c1182c6e4a4ff14bffa3dd0b605fcd08f73c94cef809"}, +] + +[package.dependencies] +colorama = {version = ">=0.4,<1", markers = "sys_platform == \"win32\""} +distro = ">=1.5.0,<2" +docker = {version = ">=5", extras = ["ssh"]} +dockerpty = ">=0.4.1,<1" +docopt = ">=0.6.1,<1" +jsonschema = ">=2.5.1,<4" +python-dotenv = ">=0.13.0,<1" +PyYAML = ">=3.10,<6" +requests = ">=2.20.0,<3" +texttable = ">=0.9.0,<2" +websocket-client = ">=0.32.0,<1" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2)"] +tests = ["ddt (>=1.2.2,<2)", "pytest (<6)"] + +[[package]] +name = "dockerpty" +version = "0.4.1" +description = "Python library to use the pseudo-tty of a docker container" +optional = false +python-versions = "*" +files = [ + {file = "dockerpty-0.4.1.tar.gz", hash = "sha256:69a9d69d573a0daa31bcd1c0774eeed5c15c295fe719c61aca550ed1393156ce"}, +] + +[package.dependencies] +six = ">=1.3.0" + +[[package]] +name = "docopt" +version = "0.6.2" +description = "Pythonic argument parser, that will make you smile" +optional = false +python-versions = "*" +files = [ + {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + [[package]] name = "filelock" version = "3.16.1" @@ -386,6 +787,38 @@ files = [ [package.extras] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "jsonschema" +version = "3.2.0" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = "*" +files = [ + {file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"}, + {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"}, +] + +[package.dependencies] +attrs = ">=17.4.0" +pyrsistent = ">=0.14.0" +setuptools = "*" +six = ">=1.11.0" + +[package.extras] +format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"] +format-nongpl = ["idna", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "webcolors"] + [[package]] name = "multidict" version = "6.1.0" @@ -575,6 +1008,27 @@ files = [ {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] +[[package]] +name = "paramiko" +version = "3.5.0" +description = "SSH2 protocol library" +optional = false +python-versions = ">=3.6" +files = [ + {file = "paramiko-3.5.0-py3-none-any.whl", hash = "sha256:1fedf06b085359051cd7d0d270cebe19e755a8a921cc2ddbfa647fb0cd7d68f9"}, + {file = "paramiko-3.5.0.tar.gz", hash = "sha256:ad11e540da4f55cedda52931f1a3f812a8238a7af7f62a60de538cd80bb28124"}, +] + +[package.dependencies] +bcrypt = ">=3.2" +cryptography = ">=3.3" +pynacl = ">=1.5" + +[package.extras] +all = ["gssapi (>=1.4.1)", "invoke (>=2.0)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] +gssapi = ["gssapi (>=1.4.1)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] +invoke = ["invoke (>=2.0)"] + [[package]] name = "pastel" version = "0.2.1" @@ -602,21 +1056,35 @@ docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-a test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] type = ["mypy (>=1.11.2)"] +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + [[package]] name = "poethepoet" -version = "0.29.0" +version = "0.28.0" description = "A task runner that works well with poetry." optional = false python-versions = ">=3.8" files = [ - {file = "poethepoet-0.29.0-py3-none-any.whl", hash = "sha256:f8dfe55006dcfb5cf31bcb1904e1262e1c642a4502fee3688cbf1bddfe5c7601"}, - {file = "poethepoet-0.29.0.tar.gz", hash = "sha256:676842302f2304a86b31ac56398dd672fae8471128d2086896393384dbafc095"}, + {file = "poethepoet-0.28.0-py3-none-any.whl", hash = "sha256:db6946ff39a1244235950cd720ee7182107f64126d3dcc64c9a996cc4d755404"}, + {file = "poethepoet-0.28.0.tar.gz", hash = "sha256:5dc3ee036ab0c93e918b5caed628274618b07d788e5cff6c4ae480913cbe009c"}, ] [package.dependencies] pastel = ">=0.2.1,<0.3.0" -pyyaml = ">=6.0.2,<7.0.0" -tomli = {version = ">=1.2.2", markers = "python_version < \"3.11\""} +tomli = ">=1.2.2" [package.extras] poetry-plugin = ["poetry (>=1.0,<2.0)"] @@ -766,68 +1234,217 @@ files = [ {file = "protobuf-4.25.5.tar.gz", hash = "sha256:7f8249476b4a9473645db7f8ab42b02fe1488cbe5fb72fddd445e0665afd8584"}, ] +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + +[[package]] +name = "pynacl" +version = "1.5.0" +description = "Python binding to the Networking and Cryptography (NaCl) library" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, + {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, +] + +[package.dependencies] +cffi = ">=1.4.1" + +[package.extras] +docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] +tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] + +[[package]] +name = "pyrsistent" +version = "0.20.0" +description = "Persistent/Functional/Immutable data structures" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyrsistent-0.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c3aba3e01235221e5b229a6c05f585f344734bd1ad42a8ac51493d74722bbce"}, + {file = "pyrsistent-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1beb78af5423b879edaf23c5591ff292cf7c33979734c99aa66d5914ead880f"}, + {file = "pyrsistent-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21cc459636983764e692b9eba7144cdd54fdec23ccdb1e8ba392a63666c60c34"}, + {file = "pyrsistent-0.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5ac696f02b3fc01a710427585c855f65cd9c640e14f52abe52020722bb4906b"}, + {file = "pyrsistent-0.20.0-cp310-cp310-win32.whl", hash = "sha256:0724c506cd8b63c69c7f883cc233aac948c1ea946ea95996ad8b1380c25e1d3f"}, + {file = "pyrsistent-0.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:8441cf9616d642c475684d6cf2520dd24812e996ba9af15e606df5f6fd9d04a7"}, + {file = "pyrsistent-0.20.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0f3b1bcaa1f0629c978b355a7c37acd58907390149b7311b5db1b37648eb6958"}, + {file = "pyrsistent-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cdd7ef1ea7a491ae70d826b6cc64868de09a1d5ff9ef8d574250d0940e275b8"}, + {file = "pyrsistent-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cae40a9e3ce178415040a0383f00e8d68b569e97f31928a3a8ad37e3fde6df6a"}, + {file = "pyrsistent-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6288b3fa6622ad8a91e6eb759cfc48ff3089e7c17fb1d4c59a919769314af224"}, + {file = "pyrsistent-0.20.0-cp311-cp311-win32.whl", hash = "sha256:7d29c23bdf6e5438c755b941cef867ec2a4a172ceb9f50553b6ed70d50dfd656"}, + {file = "pyrsistent-0.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:59a89bccd615551391f3237e00006a26bcf98a4d18623a19909a2c48b8e986ee"}, + {file = "pyrsistent-0.20.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:09848306523a3aba463c4b49493a760e7a6ca52e4826aa100ee99d8d39b7ad1e"}, + {file = "pyrsistent-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a14798c3005ec892bbada26485c2eea3b54109cb2533713e355c806891f63c5e"}, + {file = "pyrsistent-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b14decb628fac50db5e02ee5a35a9c0772d20277824cfe845c8a8b717c15daa3"}, + {file = "pyrsistent-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e2c116cc804d9b09ce9814d17df5edf1df0c624aba3b43bc1ad90411487036d"}, + {file = "pyrsistent-0.20.0-cp312-cp312-win32.whl", hash = "sha256:e78d0c7c1e99a4a45c99143900ea0546025e41bb59ebc10182e947cf1ece9174"}, + {file = "pyrsistent-0.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:4021a7f963d88ccd15b523787d18ed5e5269ce57aa4037146a2377ff607ae87d"}, + {file = "pyrsistent-0.20.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:79ed12ba79935adaac1664fd7e0e585a22caa539dfc9b7c7c6d5ebf91fb89054"}, + {file = "pyrsistent-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f920385a11207dc372a028b3f1e1038bb244b3ec38d448e6d8e43c6b3ba20e98"}, + {file = "pyrsistent-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f5c2d012671b7391803263419e31b5c7c21e7c95c8760d7fc35602353dee714"}, + {file = "pyrsistent-0.20.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef3992833fbd686ee783590639f4b8343a57f1f75de8633749d984dc0eb16c86"}, + {file = "pyrsistent-0.20.0-cp38-cp38-win32.whl", hash = "sha256:881bbea27bbd32d37eb24dd320a5e745a2a5b092a17f6debc1349252fac85423"}, + {file = "pyrsistent-0.20.0-cp38-cp38-win_amd64.whl", hash = "sha256:6d270ec9dd33cdb13f4d62c95c1a5a50e6b7cdd86302b494217137f760495b9d"}, + {file = "pyrsistent-0.20.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ca52d1ceae015859d16aded12584c59eb3825f7b50c6cfd621d4231a6cc624ce"}, + {file = "pyrsistent-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b318ca24db0f0518630e8b6f3831e9cba78f099ed5c1d65ffe3e023003043ba0"}, + {file = "pyrsistent-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fed2c3216a605dc9a6ea50c7e84c82906e3684c4e80d2908208f662a6cbf9022"}, + {file = "pyrsistent-0.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e14c95c16211d166f59c6611533d0dacce2e25de0f76e4c140fde250997b3ca"}, + {file = "pyrsistent-0.20.0-cp39-cp39-win32.whl", hash = "sha256:f058a615031eea4ef94ead6456f5ec2026c19fb5bd6bfe86e9665c4158cf802f"}, + {file = "pyrsistent-0.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:58b8f6366e152092194ae68fefe18b9f0b4f89227dfd86a07770c3d86097aebf"}, + {file = "pyrsistent-0.20.0-py3-none-any.whl", hash = "sha256:c55acc4733aad6560a7f5f818466631f07efc001fd023f34a6c203f8b6df0f0b"}, + {file = "pyrsistent-0.20.0.tar.gz", hash = "sha256:4c48f78f62ab596c679086084d0dd13254ae4f3d6c72a83ffdf5ebdef8f265a4"}, +] + +[[package]] +name = "pytest" +version = "7.4.4" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.21.0" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-asyncio-0.21.0.tar.gz", hash = "sha256:2b38a496aef56f56b0e87557ec313e11e1ab9276fc3863f6a7be0f1d0e415e1b"}, + {file = "pytest_asyncio-0.21.0-py3-none-any.whl", hash = "sha256:f2b3366b7cd501a4056858bd39349d5af19742aed2d81660b7998b6341c7eb9c"}, +] + +[package.dependencies] +pytest = ">=7.0.0" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] + +[[package]] +name = "pytest-docker-compose" +version = "3.2.1" +description = "Manages Docker containers during your integration tests" +optional = false +python-versions = "*" +files = [ + {file = "pytest-docker-compose-3.2.1.tar.gz", hash = "sha256:bb58f1915688e71232ae86f2c3c8348b10afb8af0f9800f7c68a75cda77c9b48"}, + {file = "pytest_docker_compose-3.2.1-py3-none-any.whl", hash = "sha256:5d9dd78138fb03fa4e8c742cd4104f72c5aa9579e0d31cb6f7a0751db4a6f698"}, +] + +[package.dependencies] +docker-compose = "*" +pytest = ">=3.3" + +[[package]] +name = "python-dotenv" +version = "0.21.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.7" +files = [ + {file = "python-dotenv-0.21.1.tar.gz", hash = "sha256:1c93de8f636cde3ce377292818d0e440b6e45a82f215c3744979151fa8151c49"}, + {file = "python_dotenv-0.21.1-py3-none-any.whl", hash = "sha256:41e12e0318bebc859fcc4d97d4db8d20ad21721a6aa5047dd59f090391cb549a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "pywin32" +version = "227" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-227-cp27-cp27m-win32.whl", hash = "sha256:371fcc39416d736401f0274dd64c2302728c9e034808e37381b5e1b22be4a6b0"}, + {file = "pywin32-227-cp27-cp27m-win_amd64.whl", hash = "sha256:4cdad3e84191194ea6d0dd1b1b9bdda574ff563177d2adf2b4efec2a244fa116"}, + {file = "pywin32-227-cp35-cp35m-win32.whl", hash = "sha256:f4c5be1a293bae0076d93c88f37ee8da68136744588bc5e2be2f299a34ceb7aa"}, + {file = "pywin32-227-cp35-cp35m-win_amd64.whl", hash = "sha256:a929a4af626e530383a579431b70e512e736e9588106715215bf685a3ea508d4"}, + {file = "pywin32-227-cp36-cp36m-win32.whl", hash = "sha256:300a2db938e98c3e7e2093e4491439e62287d0d493fe07cce110db070b54c0be"}, + {file = "pywin32-227-cp36-cp36m-win_amd64.whl", hash = "sha256:9b31e009564fb95db160f154e2aa195ed66bcc4c058ed72850d047141b36f3a2"}, + {file = "pywin32-227-cp37-cp37m-win32.whl", hash = "sha256:47a3c7551376a865dd8d095a98deba954a98f326c6fe3c72d8726ca6e6b15507"}, + {file = "pywin32-227-cp37-cp37m-win_amd64.whl", hash = "sha256:31f88a89139cb2adc40f8f0e65ee56a8c585f629974f9e07622ba80199057511"}, + {file = "pywin32-227-cp38-cp38-win32.whl", hash = "sha256:7f18199fbf29ca99dff10e1f09451582ae9e372a892ff03a28528a24d55875bc"}, + {file = "pywin32-227-cp38-cp38-win_amd64.whl", hash = "sha256:7c1ae32c489dc012930787f06244426f8356e129184a02c25aef163917ce158e"}, + {file = "pywin32-227-cp39-cp39-win32.whl", hash = "sha256:c054c52ba46e7eb6b7d7dfae4dbd987a1bb48ee86debe3f245a2884ece46e295"}, + {file = "pywin32-227-cp39-cp39-win_amd64.whl", hash = "sha256:f27cec5e7f588c3d1051651830ecc00294f90728d19c3bf6916e6dba93ea357c"}, +] + [[package]] name = "pyyaml" -version = "6.0.2" +version = "5.3.1" description = "YAML parser and emitter for Python" optional = false -python-versions = ">=3.8" +python-versions = "*" +files = [ + {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, + {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"}, + {file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"}, + {file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"}, + {file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"}, + {file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"}, + {file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"}, + {file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"}, + {file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"}, + {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"}, + {file = "PyYAML-5.3.1-cp39-cp39-win32.whl", hash = "sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a"}, + {file = "PyYAML-5.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e"}, + {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, +] + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, ] +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + [[package]] name = "ruff" version = "0.6.9" @@ -855,6 +1472,48 @@ files = [ {file = "ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2"}, ] +[[package]] +name = "setuptools" +version = "75.2.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-75.2.0-py3-none-any.whl", hash = "sha256:a7fcb66f68b4d9e8e66b42f9876150a3371558f98fa32222ffaa5bced76406f8"}, + {file = "setuptools-75.2.0.tar.gz", hash = "sha256:753bb6ebf1f465a1912e19ed1d41f403a79173a9acf66a42e7e6aec45c3c16ec"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.11.*)", "pytest-mypy"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "texttable" +version = "1.7.0" +description = "module to create simple ASCII tables" +optional = false +python-versions = "*" +files = [ + {file = "texttable-1.7.0-py2.py3-none-any.whl", hash = "sha256:72227d592c82b3d7f672731ae73e4d1f88cd8e2ef5b075a7a7f01a23a3743917"}, + {file = "texttable-1.7.0.tar.gz", hash = "sha256:2d2068fb55115807d3ac77a4ca68fa48803e84ebb0ee2340f858107a36522638"}, +] + [[package]] name = "tomli" version = "2.0.2" @@ -888,15 +1547,31 @@ files = [ {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] +[[package]] +name = "urllib3" +version = "1.26.6" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +files = [ + {file = "urllib3-1.26.6-py2.py3-none-any.whl", hash = "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4"}, + {file = "urllib3-1.26.6.tar.gz", hash = "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"}, +] + +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + [[package]] name = "virtualenv" -version = "20.26.6" +version = "20.27.0" description = "Virtual Python Environment builder" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2"}, - {file = "virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48"}, + {file = "virtualenv-20.27.0-py3-none-any.whl", hash = "sha256:44a72c29cceb0ee08f300b314848c86e57bf8d1f13107a5e671fb9274138d655"}, + {file = "virtualenv-20.27.0.tar.gz", hash = "sha256:2ca56a68ed615b8fe4326d11a0dca5dfbe8fd68510fb6c6349163bed3c15f2b2"}, ] [package.dependencies] @@ -908,95 +1583,109 @@ platformdirs = ">=3.9.1,<5" docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +[[package]] +name = "websocket-client" +version = "0.59.0" +description = "WebSocket client for Python with low level API options" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "websocket-client-0.59.0.tar.gz", hash = "sha256:d376bd60eace9d437ab6d7ee16f4ab4e821c9dae591e1b783c58ebd8aaf80c5c"}, + {file = "websocket_client-0.59.0-py2.py3-none-any.whl", hash = "sha256:2e50d26ca593f70aba7b13a489435ef88b8fc3b5c5643c1ce8808ff9b40f0b32"}, +] + +[package.dependencies] +six = "*" + [[package]] name = "yarl" -version = "1.15.3" +version = "1.15.4" description = "Yet another URL library" optional = false python-versions = ">=3.9" files = [ - {file = "yarl-1.15.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:14d6f07b7b4b3b8fba521904db58442281730b44318d6abb9908de79e2a4e4f4"}, - {file = "yarl-1.15.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eacd9de9b5b8262818a2e1f88efbd8d523abc8453de238c5d2f6a91fa85032dd"}, - {file = "yarl-1.15.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5a63ed17af784da3de39b82adfd4f8404ad5ee2ec8f616b063f37da3e64e0521"}, - {file = "yarl-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b55cc82ba92c07af6ba619dcf70cc89f7b9626adefb87d251f80f2e77419f1da"}, - {file = "yarl-1.15.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63ba82841ce315e4b5dc8b9345062638c74b1864d38172d0a0403e5a083b0950"}, - {file = "yarl-1.15.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59dce412b2515de05ab2eb6aef19ad7f70857ad436cd65fc4276df007106fb42"}, - {file = "yarl-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e337737b8c9d837e5b4d9e906cc57ed7a639e16e515c8094509b17f556fdb642"}, - {file = "yarl-1.15.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2128315cdc517a45ceb72ec17b256a7940eeb4843c66834c203e7d6580c83405"}, - {file = "yarl-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:69c2d111e67a818e702ba957da8c8e62de916f5c1b3da043f744084c63f12d46"}, - {file = "yarl-1.15.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:d2a70e8bec768be7423d8d465858a3646b34257a20cc02fd92612f1b14931f50"}, - {file = "yarl-1.15.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:efe758958a7bffce68d91ade238df72667e1f18966ed7b1d3d390eead51a8903"}, - {file = "yarl-1.15.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:b765f19e23c29b68e4f8bbadd36f1da2333ba983d8da2d6518e5f0a7eb2579c2"}, - {file = "yarl-1.15.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df494e5a79f2ef8f81f966f787e515760e639c6319a321c16198b379c256a157"}, - {file = "yarl-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:68b27a7d9fb0f145de608da2e45e37fd2397b00266f10487e557f769afa2842d"}, - {file = "yarl-1.15.3-cp310-cp310-win32.whl", hash = "sha256:6d1aba1f644d6e5e16edada31938c11b6c9c97e3bf065742a2c7740d38af0c19"}, - {file = "yarl-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:925e72fc7a4222a5bf6d288876d5afacc8f833b49c4cca85f65089131ba25afa"}, - {file = "yarl-1.15.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dbd4808a209b175b5ebbac24c4798dd7511c5ee522a16f2f0eac78c717dfcdfc"}, - {file = "yarl-1.15.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:20f8bdaf667386cea1a8f49cb69a85f90346656d750d3c1278be1dbc76601065"}, - {file = "yarl-1.15.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:adeac55335669a189189373c93d131ebfc2de3ec04f0d3aa7dff6661f83b89b6"}, - {file = "yarl-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:690d8f702945506b58c9c5834d586e8fd819b845fe6239ab16ebc64a92a6fd3d"}, - {file = "yarl-1.15.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:df7784a29b9689341c17d06d826e3b52ee59d6b6916177e4db0477be7aad5f72"}, - {file = "yarl-1.15.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12c80ec2af97ff3e433699bcabc787ef34e7c08ec038a6e6a25fb81d7bb83607"}, - {file = "yarl-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39533b927c665bcff7da80bf299218e4af12f3e2be27e9c456e29547bcefd631"}, - {file = "yarl-1.15.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db32a5c2912db45e73f80107d178e30f5c48cf596762b3c60ddfebdd655385f0"}, - {file = "yarl-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bde319602111e9acca3c4f87f4205b38ba6166004bf108de47553633f9a580fc"}, - {file = "yarl-1.15.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:493760c4ced954582db83c4760166992c016e1777ebc0f3ef1bb5eb60b2b5924"}, - {file = "yarl-1.15.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d9cd73f7bff5079d87c2622aa418a75d5d3cdc944d3edb905c5dfc3235466eb0"}, - {file = "yarl-1.15.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e924040582499f7514ec64691031504e6224b5ae7224216208fc2c94f8b13c89"}, - {file = "yarl-1.15.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1c3e9ae98719fe180751b093d02dbcc33b78a37e861d0f2c9571720bd31555db"}, - {file = "yarl-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f2911cae6dd012adaaf51494dad4cafb4284ad1f3b588df6ea3e3017e053750"}, - {file = "yarl-1.15.3-cp311-cp311-win32.whl", hash = "sha256:acdfe626607a245aedca35b211f9305a9e7a33349da525bf4ef3caaec8ef51cd"}, - {file = "yarl-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:0ace3927502a9f90a868d62c66623703cf5096dcb586187266e9b964d8dd6c81"}, - {file = "yarl-1.15.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:decf9d76191bfe34835f1abd3fa8ebe8a9cd7e16300a5c7e82b18c0812bb22a2"}, - {file = "yarl-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ce65ed7ad7b6cbca06b0c011b170bd2b0bc56b0a740540e2713e5ac12d7b9b2e"}, - {file = "yarl-1.15.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3cf2b50352df8775591869aaa22c52b64d60376ba99c0802b42778fedc90b775"}, - {file = "yarl-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32e8ebf0080ddd38ec05f8be940a3719e5fe1ab8bb6d2b3f6f8b89c9e34149aa"}, - {file = "yarl-1.15.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05183fd49244517cb11c208d0ae128f2e8a85ddb7caf22ad8b0ffcdf5481fcb6"}, - {file = "yarl-1.15.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:46653b5fd29e63ffe63335da343829a2b00bb43b0bd9bb21240d3b42629629e2"}, - {file = "yarl-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6316af233610b9868eda92cf68c016750cbf50085ac6c51faa17905ddd25605"}, - {file = "yarl-1.15.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5685ebc333c95b75be3a0a83a81b82b6411beee9585eaeb9e2e588ae8df23848"}, - {file = "yarl-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6da6f6c6ee5595658f21bb9d1ecd702f7a7f22f224ac063dfb595624aec4a2e0"}, - {file = "yarl-1.15.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:45c05b87a8494d9820ea1ac82118fd2f1d795d868e94766fe8ff670377bf6280"}, - {file = "yarl-1.15.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:04f930fcc940f96b8b29110c56882bcff8703f87a7b9354d3acf60ffded5a23d"}, - {file = "yarl-1.15.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8df77742b403e71c5d62d22d150e6e35efd6096a15f2c7419815911c62225100"}, - {file = "yarl-1.15.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f785d83ece0998e4ce4fadda22fa6c1ecc40e10f41617013a8726d2e9af0d98f"}, - {file = "yarl-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7794aade99be0d48b69bd5942acddfeff0de3d09c724d9abe4f19736708ef18f"}, - {file = "yarl-1.15.3-cp312-cp312-win32.whl", hash = "sha256:a3a98d70c667c957c7cd0b153d4cb5e45d43f5e2e23de73be6f7b5c883c01f72"}, - {file = "yarl-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:90257bc627897a2c1d562efcd6a6b18887e9dacae795cad2367e8e16df47d966"}, - {file = "yarl-1.15.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f94d8adfdec402ff97cecc243b310c01d571362ca87bcf8def8e15cb3aaac3ee"}, - {file = "yarl-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0328f798052a33803a77d0868c7f802e952127092c1738fc9e7bfcaac7207c5"}, - {file = "yarl-1.15.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f5f0a0691e39c2e7b5c0f23e6765fa6cb162dce99d9ab1897fdd0f7a4a38b6fb"}, - {file = "yarl-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370f646d3654e196ddbf772a2d737fe4e1dd738267015b73ff6267ca592fd9d6"}, - {file = "yarl-1.15.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3487c57bc8f17f2586ae7fd0e77f65cd298d45b64d15f604bbb29f4cce0e7961"}, - {file = "yarl-1.15.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef67989d480358482830dc3bc232709804f46a61e7e9841d3f0b1c13a4735b3b"}, - {file = "yarl-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5ab6c64921802176f56c36aa67c5e6a8baf9557ec1662cb41ecdb5580b67eb9"}, - {file = "yarl-1.15.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb474a06023d01ead9c072b2580c22b2691aa1cabdcc19c3171ab1fa6d8496e3"}, - {file = "yarl-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92f9a45230d3aa8568c1d692ab27bf505a32dfe3b404721458fc374f411e8bd2"}, - {file = "yarl-1.15.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:24cad94cf2f46cc8e4b9cd44e4e8a84483536a6c54554960b02b10b5724ab122"}, - {file = "yarl-1.15.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:380f30073cbd9b740891bb56f44ee31f870e8721269b618ccc9913400936d9f6"}, - {file = "yarl-1.15.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:353306ba6f0218af1aefe4b9c8b3a0b81b209bc75d79357dac6aca70a7b09d6a"}, - {file = "yarl-1.15.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fe03cea925d884b8f1157a7037df2f5b6a6478a64b78ee600832d8a9f044c83e"}, - {file = "yarl-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5c4cc1a438ac52562427330e33891f50a78ffd38d335abc64f93f201c83bdc82"}, - {file = "yarl-1.15.3-cp313-cp313-win32.whl", hash = "sha256:956975a3a1ce1f4537be22278d6a283b8bc74d77671f7f6469ab1e800f4e9b02"}, - {file = "yarl-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:2e61b72cf15922a7a665299a6b6825bd9901d67ec3b9d3cf9b256dc1667c9bb1"}, - {file = "yarl-1.15.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:270fef2b335e60c91ee835c524445e2248af841c8b72f48769ed6c02fbff5873"}, - {file = "yarl-1.15.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:59b77f0682e1917be197fc8229530f0c6fb3ef8e242d8256ba091a3a1c0ef7e6"}, - {file = "yarl-1.15.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc4b999718287073dccd3acb0ef1593961bd7923af08991cb3c94080db503935"}, - {file = "yarl-1.15.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9b251d3f90e125ff0d1f76257329a9190fa1bfd2157344c875580bff6dedc62"}, - {file = "yarl-1.15.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ccb4667e0c0a25815efbfe251d24b56624449a319d4bb497074dd49444fb306"}, - {file = "yarl-1.15.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ac26e43b56dbafb30256906bc763cc1f22e05825ae1ced4c6afbd0e6584f18de"}, - {file = "yarl-1.15.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2207491555af5dbbee4c3179a76766f7bc1ecff858f420ea96f2e105ca42c4dd"}, - {file = "yarl-1.15.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14effa29db6113be065a594e13a0f45afb9c1e374fd22b4bc3a4eff0725184b2"}, - {file = "yarl-1.15.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:19077525cd36c797cae19262e15f2881da33c602fb35d075ff0e4263b51b8b88"}, - {file = "yarl-1.15.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:d80c019083506886df098b7bb0d844e19db7e226736829ef49f892ed0a070fa5"}, - {file = "yarl-1.15.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c24debeec87908a864a2b4cb700f863db9441cabacdb22dc448c5d38b55c6f62"}, - {file = "yarl-1.15.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:1c49fe426c45520b4b8a48544d3a9a58194f39c1b57d92451883f847c299a137"}, - {file = "yarl-1.15.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:66ddcd7ee3264bc937860f4780290d60f6472ca0484c214fe805116a831121e8"}, - {file = "yarl-1.15.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2a5cbbb06559757f091f9e71d3f76c27d4dfe0652cc3f17ccce398b8377bfda4"}, - {file = "yarl-1.15.3-cp39-cp39-win32.whl", hash = "sha256:d798de0b50efb66583fc096bcdaa852ed6ea3485a4eb610d6a634f8010d932f4"}, - {file = "yarl-1.15.3-cp39-cp39-win_amd64.whl", hash = "sha256:8f0b33fd088e93ba5f7f6dd55226630e7b78212752479c8fcc6abbd143b9c1ce"}, - {file = "yarl-1.15.3-py3-none-any.whl", hash = "sha256:a1d49ed6f4b812dde88e937d4c2bd3f13d72c23ef7de1e17a63b7cacef4b5691"}, - {file = "yarl-1.15.3.tar.gz", hash = "sha256:fbcff47f8ba82467f203037f7a30decf5c724211b224682f7236edb0dcbb5b95"}, + {file = "yarl-1.15.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:551205388d1da18a9975302c9a274ba24788f53bb9bb86187496ebf9e938916e"}, + {file = "yarl-1.15.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eee724176b5bc50ee64905f559345448119b860a30b9489bd7a073f61baf925f"}, + {file = "yarl-1.15.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db818e33599f7b2e4c6507f2b2c24f45ff539a1b6e4e09163bb6f3cfb4616ca7"}, + {file = "yarl-1.15.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07019a9de859c5a29916defd1e8c7557de6491a10bf50c49ff5284e6aedf5313"}, + {file = "yarl-1.15.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db64a20e78969fc66665d2e5fc96cb4f4dc80f2137d8fed4b5a650ad569bb60f"}, + {file = "yarl-1.15.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4076bfd8f1621449b19b9826848ed51bf0f2d1d38e82647c312c0730d8778903"}, + {file = "yarl-1.15.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c23a442973dba3646811c284fce3dddd7fe5c2bd674ac73a122198e8218d6115"}, + {file = "yarl-1.15.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2bdb038b3f5c284e3919218c580dedc95f592c417a358361450b9519b22f7a8"}, + {file = "yarl-1.15.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:59db8e6888d5302b8dbca0c1026ddabe99d81d67cdc101941519e13ffc9050fe"}, + {file = "yarl-1.15.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f3294ce265011547630a59c20085fcb6af8cc5fa1fa44a203251f7d86cd5d913"}, + {file = "yarl-1.15.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4851618679ca70b863ba2e7109be5f09f8fd7715ec505bd42e5a947dcfde3a45"}, + {file = "yarl-1.15.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:dce1c56beef74d9c799a6ed94001693232a1402138292353a8ce302b64f457d9"}, + {file = "yarl-1.15.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1e7468f31de61a82817f918743e5229fce774f73fad58487cdf88eef4f06d864"}, + {file = "yarl-1.15.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:527c68f48a91d953691291d3bce0209293aa5ad13ff05286ddb506791c331818"}, + {file = "yarl-1.15.4-cp310-cp310-win32.whl", hash = "sha256:c30115cecaf25fdcb67cc71c669d08425207f62d7a2f6d5416057c1460529216"}, + {file = "yarl-1.15.4-cp310-cp310-win_amd64.whl", hash = "sha256:df09c80f4bc2bc2efde309af383c3fe8fd8c51fe0519edb350b9c9e0af43ffa4"}, + {file = "yarl-1.15.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:76259901cf1ac3db65e7e6dff04775b626d0715f9b51d92b447351144c756a82"}, + {file = "yarl-1.15.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:98d8dc1e8133f86d916125deca9780d791b22645f0d62bafe1452d1cd5eac631"}, + {file = "yarl-1.15.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d0f16c87c62b7a94b389ddf6a8c9d081265d788875c39f3a80108c4856eea7b"}, + {file = "yarl-1.15.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8de5328d91859b461899497980d4cc8269e84e2d18640f6ac643886fda9000bf"}, + {file = "yarl-1.15.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84937d00e2ea03616c40977de20189fa13a9213e5744a3c6afa0e7dd9141d69c"}, + {file = "yarl-1.15.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:691a3b498fdebef63308e8967bb598cfd326c56d628da82b799dd181bace4503"}, + {file = "yarl-1.15.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a706db0c3b7e4578ff34ed2b1d2507b08fd491346ffc64468786fdf1151d938"}, + {file = "yarl-1.15.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:adb6b5d07d17c32f9d34c9dd4a693637a72323cfcb1f8a52d57033ab2dd21e99"}, + {file = "yarl-1.15.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6e100c6c7d9e9d469009fd55cc4d7ad168d67d40758865c50da713f7ada491e5"}, + {file = "yarl-1.15.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:df6b254e55c8ac2362afaa651e3e53453aa19a095570792346245773b434176e"}, + {file = "yarl-1.15.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8721f8bedaa722c3c483cc06a1399cbfdb280eadf443aa5d324b0203cef2a75f"}, + {file = "yarl-1.15.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1005921b30f4f39bf893946df6173567ff650307babb5ec04bbf64342a1f62c1"}, + {file = "yarl-1.15.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ab79cc13307065a0b3ef087f09f0509996fc605d35d6642bb28e5d85b2648e1e"}, + {file = "yarl-1.15.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f337486742c700b102d640830aab3faf2848bed966b479a39e6783edd4ab1c6c"}, + {file = "yarl-1.15.4-cp311-cp311-win32.whl", hash = "sha256:20acf84bd1ce530065f8e957e4a5878fda4bc5f18cb02659828210e1519de54e"}, + {file = "yarl-1.15.4-cp311-cp311-win_amd64.whl", hash = "sha256:ab9ccf26cb3fa32747ba2a637a189d2d42386a2fc4afc10dbc7f85922dd23b0f"}, + {file = "yarl-1.15.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f923e94e93a37fd990e8336e0b9bedea533e7cbed14e0c572bf9357ef2a70681"}, + {file = "yarl-1.15.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3198da7d7c34e29fc8c823e0c3ce6c7274aac35760de557c2017489c7d98fc5a"}, + {file = "yarl-1.15.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d886de2ea81f513ba2d6820451d33b767a97c37867ba688d42e164b2dbca1362"}, + {file = "yarl-1.15.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ac85e760543129a1912a82438fc8075223e35eaa2d457d61cd83c27d00d17be"}, + {file = "yarl-1.15.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e58c5d07b1f78dd4cb180c5b3b82465cd281aaeee8aafea0e5d72a4b97922cb1"}, + {file = "yarl-1.15.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9060589d0acad1fca048861fa9ee3e8ed060f67894fa885969648ab6e9e99a54"}, + {file = "yarl-1.15.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccd6774aa7bebdf9ca608bb0839318757a71b8e0d2cf7b10c002bc8790bd343e"}, + {file = "yarl-1.15.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7694f109867ee428c21b85ae19fd31d164c691eb45cc95c561cfdeba237a12e3"}, + {file = "yarl-1.15.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:83e7154aa0d17f5c93d27ac01088fd9ab6673e7bab1acbd07cd7a865b980c045"}, + {file = "yarl-1.15.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:f16d1940c0cbc342f1d29d6212a006d172be616d2942c5c41966e8a3ce4c3be1"}, + {file = "yarl-1.15.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7d5226c70af3ad9569ccc4ccc04ab65be79eeb22c87d7ae789c89e62ef76bbd6"}, + {file = "yarl-1.15.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f25906e4a72d9833e81717c39a39dee7297ff5cb44957d06d177a2ab8ef2ef7f"}, + {file = "yarl-1.15.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e07e4b17b648c880e8e42bf1ac0a730bde114961646ae1c2ec4433f0c11ca94"}, + {file = "yarl-1.15.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f8136bde8dfa4477c6a85c79a366581b4a505b51a52b669318fb631d3f4f638"}, + {file = "yarl-1.15.4-cp312-cp312-win32.whl", hash = "sha256:ccbeaf5b18b173b9d78e332e017b30ba8bedcf03cdce1d13490b82a3f421bc98"}, + {file = "yarl-1.15.4-cp312-cp312-win_amd64.whl", hash = "sha256:f74f6ffdc633aefecbc80282242a5395058db9d1247fa7dd2f070ef84dc82583"}, + {file = "yarl-1.15.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4f66a0eda48844508736e47ed476d8fdd7cdbf16a4053b5d439509a25f708504"}, + {file = "yarl-1.15.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fd2bb86f40962d53a91def15a2f7684c62e081a7b96ec74ed0259c34b15973b9"}, + {file = "yarl-1.15.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f864b412557e69a6b953d62c01a0ed0ee342666298aa7f2a29af526bfa80f6e9"}, + {file = "yarl-1.15.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a79c0a8bbb046add85663af85e9993b691bf20c2a109518bd35e0ce77edfe42"}, + {file = "yarl-1.15.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de479e30abd2dfd49fdad3bd6953f2d930a45380be5143c0c9f7a1215cffc8cc"}, + {file = "yarl-1.15.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:21fabe58042f3e567b4edc75b2cf44cea02f228e41ac09d73de126bf685fe883"}, + {file = "yarl-1.15.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77390496f2f32437a721c854897f889abefae0f3009daf90a2f703508d96c920"}, + {file = "yarl-1.15.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3896bf15284dd23acab1f2e7fceb350d8da6f6f2436b922f7ec6b3de685d34ca"}, + {file = "yarl-1.15.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:590e2d733a82ecf004c5c531cbef0d6be328e93adec960024eb213f10cb9503e"}, + {file = "yarl-1.15.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:1ceb677fb583971351627eac70eec6763fbc889761828da7a276681b5e39742d"}, + {file = "yarl-1.15.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:69f628d2da1489b27959f4d63fdb326781fe484944dce94abbf919e416c54abe"}, + {file = "yarl-1.15.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:35a6b69cc44bda002705d6138346bf0a0234cbb7c26c3bf192513eb946aee6f9"}, + {file = "yarl-1.15.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:49f886e8dcf591275c6e20915b516fd81647857566b0c0158c52df1e468849c9"}, + {file = "yarl-1.15.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:49190eb2ece70313742b0ea51520340288a059674da1f39eefb589d598d9453e"}, + {file = "yarl-1.15.4-cp313-cp313-win32.whl", hash = "sha256:48334a6c8afee93097eb17c0a094234dac2d88da076c8cf372e09e2a5dcc4b66"}, + {file = "yarl-1.15.4-cp313-cp313-win_amd64.whl", hash = "sha256:f68025d6ba1816428b7de615c80f61cb03d5b7061158d4ced7696657a64aa59c"}, + {file = "yarl-1.15.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8b569f4f511b59518ba6719feb5b8bf0a5d4115e6ac903c89e10a8a9ac656017"}, + {file = "yarl-1.15.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9fe17744d60fc404ac61f824118e1e15ce3c2e92eced9b8e22f3c7847acafbf2"}, + {file = "yarl-1.15.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:115346433fad2084ee3a1a925ccc0659990aa42e208ca54c278830a150a3caf3"}, + {file = "yarl-1.15.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60165b8bc260f453321004b193770a66cc1b1a5c57c07d4b8dcc96839e7ad578"}, + {file = "yarl-1.15.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65a0168691373e08d869d48b62c8bed0af0cdaef19c76e11ad73b43901bbdb5a"}, + {file = "yarl-1.15.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:787532f00543a21b8f4ec3050b4e01b8fe437797903c0156a0b03dfca5e1ba6c"}, + {file = "yarl-1.15.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f51c9d173e5fa4b12d06ddca09a41cabbdeb660471dbe55432423eec095709ab"}, + {file = "yarl-1.15.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c96eaa30030e1cfafe533f3da8983812281235b7c50ef2a6c78ceca7aea1a0b"}, + {file = "yarl-1.15.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4feab2dcb725eb5b4835207ecf3d370ff7ce930b253cba5e681646cb80d64c2c"}, + {file = "yarl-1.15.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:de38b0b5b86e57efb129d179854e78b65cb8e294a8c75560877869c43aa2415a"}, + {file = "yarl-1.15.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:65e0467f90f2acf3bc83bbfeedece8f1fd84df8add1a54e9600ed7b7b5debdb0"}, + {file = "yarl-1.15.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:40c18f96696549e73b92dc12619f07019cbf5faefc1612608f967c144816e493"}, + {file = "yarl-1.15.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:46491b3e058de7b484e1c9fb20aa8441f06d6c9a18395d711c1c2a9ad6707d6a"}, + {file = "yarl-1.15.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:faa3dd7f4620ab5e5da7a0789d0aac78a9ad0376f102409d442ec5a4179e200a"}, + {file = "yarl-1.15.4-cp39-cp39-win32.whl", hash = "sha256:c33ea7c55a73be343f02361795caf52a187357ea07708fb1cae6661ee1d689c8"}, + {file = "yarl-1.15.4-cp39-cp39-win_amd64.whl", hash = "sha256:11b207061f28b4b6d980239b22ab0ecfadc47846b5a3b8e79f27fcc019d02cf9"}, + {file = "yarl-1.15.4-py3-none-any.whl", hash = "sha256:e5cc288111c450c0a54a74475591b206d3b1cb47dc71bb6200f6be8b1337184c"}, + {file = "yarl-1.15.4.tar.gz", hash = "sha256:a0c5e271058d148d730219ca4f33c5d841c6bd46e05b0da60fea7b516906ccd3"}, ] [package.dependencies] @@ -1027,4 +1716,4 @@ yc = ["yandexcloud"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "e2324b1357052e3478cc0e0ace0d65dfad79d7b166b7ff268fac03d6e78c7530" +content-hash = "35fc7529574aa99a2c5fb27165f5ad937bb7b2978b834a761068d0a4a6fb39f4" diff --git a/pyproject.toml b/pyproject.toml index 386a571..afea8aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,8 +14,16 @@ ydb = "^3.18.3" pre-commit = "^4.0.1" ruff = "^0.6.9" mypy = "^1.11.2" -poethepoet = "^0.29.0" +poethepoet = "0.28.0" types-protobuf = "^5.28.0.20240924" +PyYAML = "~5.3.0" +pytest = "^7.0.0" +pytest-asyncio = "0.21.0" +pytest-docker-compose = "3.2.1" +docker-compose = "1.29.2" +docker = "5.0.0" +requests = "2.31.0" +urllib3 = "1.26.6" [build-system] requires = ["poetry-core"] @@ -38,6 +46,9 @@ src = ["ydb_dbapi", "tests"] [tool.ruff.lint] select = ["F", "E"] +[tool.pytest.ini_options] +asyncio_mode = "auto" + [[tool.mypy.overrides]] module = "ydb.*" ignore_missing_imports = true diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..4784876 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,57 @@ +import os +import pytest +import time +import ydb + + +@pytest.fixture(scope="module") +def docker_compose_file(pytestconfig): + return os.path.join(str(pytestconfig.rootdir), "docker-compose.yml") + + +def wait_container_ready(driver): + driver.wait(timeout=30) + + with ydb.SessionPool(driver) as pool: + started_at = time.time() + while time.time() - started_at < 30: + try: + with pool.checkout() as session: + session.execute_scheme( + "create table `.sys_health/test_table` " + "(A int32, primary key(A));" + ) + + return True + + except ydb.Error: + time.sleep(1) + + raise RuntimeError("Container is not ready after timeout.") + + +@pytest.fixture(scope="module") +def endpoint(pytestconfig, module_scoped_container_getter): + with ydb.Driver(endpoint="localhost:2136", database="/local") as driver: + wait_container_ready(driver) + yield "localhost:2136" + + +@pytest.fixture(scope="module") +def database(): + return "/local" + + +@pytest.fixture() +async def driver(endpoint, database, event_loop): + driver_config = ydb.DriverConfig( + endpoint, + database, + ) + + driver = ydb.aio.Driver(driver_config=driver_config) + await driver.wait(timeout=15) + + yield driver + + await driver.stop(timeout=10) diff --git a/tests/test_cursor.py b/tests/test_cursor.py new file mode 100644 index 0000000..28fc551 --- /dev/null +++ b/tests/test_cursor.py @@ -0,0 +1,33 @@ +import pytest + +import ydb + +import ydb_dbapi +import ydb_dbapi.cursors + + +@pytest.fixture +async def cursor(driver: ydb.aio.Driver): + session_pool = ydb.aio.QuerySessionPool(driver) + session = await session_pool.acquire() + cursor = ydb_dbapi.cursors.Cursor(session_pool, session) + + yield cursor + + await session_pool.release(session) + + +@pytest.mark.asyncio +async def test_cursor_ddl(cursor): + op = ydb_dbapi.cursors.YdbQuery( + yql_text=""" + CREATE TABLE table ( + id Int64 NOT NULL, + i64Val Int64, + PRIMARY KEY(id) + ) + """, + is_ddl=True, + ) + + await cursor.execute(op) diff --git a/ydb_dbapi/cursors.py b/ydb_dbapi/cursors.py index 678d85d..9a4da5e 100644 --- a/ydb_dbapi/cursors.py +++ b/ydb_dbapi/cursors.py @@ -1,8 +1,10 @@ import dataclasses -import typing +import itertools from typing import ( Any, + AsyncIterator, Dict, + Iterator, List, Optional, Tuple, @@ -10,9 +12,8 @@ ) import ydb - -if typing.TYPE_CHECKING: - from ydb.aio.query.base import AsyncResponseContextIterator +from .errors import DatabaseError +from .utils import handle_ydb_errors, AsyncFromSyncIterator ParametersType = Dict[ @@ -31,26 +32,34 @@ class YdbQuery: is_ddl: bool = False +def _get_column_type(type_obj: Any) -> str: + return str(ydb.convert.type_to_native(type_obj)) + + class Cursor: def __init__( self, session_pool: ydb.aio.QuerySessionPool, session: ydb.aio.QuerySession, - tx_mode: ydb.aio.QueryTxContext, - tx_context: ydb.aio.QueryTxContext, + tx_mode: Optional[ydb.aio.QueryTxContext] = None, + tx_context: Optional[ydb.aio.QueryTxContext] = None, table_path_prefix: str = "", autocommit: bool = True, ): - self.arraysize = 1 - self._description = None + self.arraysize: int = 1 + self._description: Optional[List[Tuple]] = None self._pool = session_pool self._session = session self._tx_mode = tx_mode - self._tx_context = tx_context + self._tx_context: ydb.aio.QueryTxContext = tx_context self._table_path_prefix = table_path_prefix self._autocommit = autocommit + self._stream: Optional[AsyncIterator] = None + self._rows: Optional[Iterator[Dict]] = None + + @handle_ydb_errors async def _execute_ddl_query( self, query: str, parameters: Optional[ParametersType] = None ) -> List[ydb.convert.ResultSet]: @@ -58,15 +67,17 @@ async def _execute_ddl_query( query=query, parameters=parameters ) + @handle_ydb_errors async def _execute_dml_query( self, query: str, parameters: Optional[ParametersType] = None - ) -> AsyncResponseContextIterator: + ) -> AsyncIterator: return await self._tx_context.execute( query=query, parameters=parameters, commit_tx=self._autocommit, ) + @handle_ydb_errors async def execute( self, operation: YdbQuery, parameters: Optional[ParametersType] = None ): @@ -74,27 +85,70 @@ async def execute( result_sets = await self._execute_ddl_query( query=operation.yql_text, parameters=parameters ) + self._stream = AsyncFromSyncIterator(iter(result_sets)) else: - result_sets_stream = await self._execute_dml_query( + self._stream = await self._execute_dml_query( query=operation.yql_text, parameters=parameters ) - return result_sets or result_sets_stream + if self._stream is None: + return + + result_set = await self._stream.__anext__() + self._update_result_set(result_set) + + def _update_result_set(self, result_set: ydb.convert.ResultSet): + # self._result_set = result_set + self._update_description(result_set) + self._rows = self._rows_iterable(result_set) + + def _update_description(self, result_set: ydb.convert.ResultSet): + self._description = [ + ( + col.name, + _get_column_type(col.type), + None, + None, + None, + None, + None, + ) + for col in result_set.columns + ] + + def _rows_iterable(self, result_set): + try: + for row in result_set.rows: + # returns tuple to be compatible with SqlAlchemy and because + # of this PEP to return a sequence: + # https://www.python.org/dev/peps/pep-0249/#fetchmany + yield row[::] + except ydb.Error as e: + raise DatabaseError(e.message, original_error=e) from e async def executemany(self): pass async def fetchone(self): - pass + return next(self._rows or iter([]), None) - async def fetchmany(self): - pass + async def fetchmany(self, size: Optional[int] = None): + return list( + itertools.islice(self._rows or iter([]), size or self.arraysize) + ) async def fetchall(self): - pass + return list(self._rows or iter([])) async def nextset(self): - pass + if self._stream is None: + return False + try: + result_set = await self._stream.__anext__() + self._update_result_set(result_set) + except StopIteration: + return False + return True def setinputsizes(self): pass @@ -103,11 +157,13 @@ def setoutputsize(self): pass async def close(self): - pass + next_set_available = True + while next_set_available: + next_set_available = await self.nextset() @property def description(self): - pass + return self._description @property def rowcount(self): diff --git a/ydb_dbapi/utils.py b/ydb_dbapi/utils.py index 380a3ff..859a077 100644 --- a/ydb_dbapi/utils.py +++ b/ydb_dbapi/utils.py @@ -1,4 +1,5 @@ import functools +from typing import Iterator import ydb from .errors import ( @@ -47,3 +48,14 @@ def wrapper(*args, **kwargs): raise DatabaseError("Failed to execute query") from e return wrapper + + +class AsyncFromSyncIterator: + def __init__(self, sync_iter: Iterator): + self._sync_iter = sync_iter + + def __aiter__(self): + return self + + async def __anext__(self): + return next(self._sync_iter) From 8b839d2639684160be3da7a8e78b5856edbbba7e Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:10:11 +0300 Subject: [PATCH 09/33] cursor methods --- .github/workflows/tests.yml | 2 +- poetry.lock | 296 ++++++++++++++++++------------------ pyproject.toml | 2 + tests/conftest.py | 20 +++ tests/test_cursor.py | 136 ++++++++++++++--- ydb_dbapi/__init__.py | 1 + ydb_dbapi/cursors.py | 44 +++--- ydb_dbapi/utils.py | 4 +- 8 files changed, 312 insertions(+), 193 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f062155..40b2b31 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -44,4 +44,4 @@ jobs: - name: Run tests run: | - poetry run -- pytest tests + poetry run pytest tests diff --git a/poetry.lock b/poetry.lock index 819439e..25962f4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -435,38 +435,38 @@ files = [ [[package]] name = "cryptography" -version = "43.0.1" +version = "43.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a"}, - {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042"}, - {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494"}, - {file = "cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2"}, - {file = "cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d"}, - {file = "cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1"}, - {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa"}, - {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4"}, - {file = "cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47"}, - {file = "cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2"}, - {file = "cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d"}, + {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18"}, + {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd"}, + {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73"}, + {file = "cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2"}, + {file = "cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd"}, + {file = "cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405"}, + {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16"}, + {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73"}, + {file = "cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995"}, + {file = "cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"}, + {file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"}, ] [package.dependencies] @@ -479,7 +479,7 @@ nox = ["nox"] pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "cryptography-vectors (==43.0.1)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] @@ -925,43 +925,43 @@ typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} [[package]] name = "mypy" -version = "1.12.0" +version = "1.12.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4397081e620dc4dc18e2f124d5e1d2c288194c2c08df6bdb1db31c38cd1fe1ed"}, - {file = "mypy-1.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:684a9c508a283f324804fea3f0effeb7858eb03f85c4402a967d187f64562469"}, - {file = "mypy-1.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cabe4cda2fa5eca7ac94854c6c37039324baaa428ecbf4de4567279e9810f9e"}, - {file = "mypy-1.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:060a07b10e999ac9e7fa249ce2bdcfa9183ca2b70756f3bce9df7a92f78a3c0a"}, - {file = "mypy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:0eff042d7257f39ba4ca06641d110ca7d2ad98c9c1fb52200fe6b1c865d360ff"}, - {file = "mypy-1.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b86de37a0da945f6d48cf110d5206c5ed514b1ca2614d7ad652d4bf099c7de7"}, - {file = "mypy-1.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:20c7c5ce0c1be0b0aea628374e6cf68b420bcc772d85c3c974f675b88e3e6e57"}, - {file = "mypy-1.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a64ee25f05fc2d3d8474985c58042b6759100a475f8237da1f4faf7fcd7e6309"}, - {file = "mypy-1.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:faca7ab947c9f457a08dcb8d9a8664fd438080e002b0fa3e41b0535335edcf7f"}, - {file = "mypy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:5bc81701d52cc8767005fdd2a08c19980de9ec61a25dbd2a937dfb1338a826f9"}, - {file = "mypy-1.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8462655b6694feb1c99e433ea905d46c478041a8b8f0c33f1dab00ae881b2164"}, - {file = "mypy-1.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:923ea66d282d8af9e0f9c21ffc6653643abb95b658c3a8a32dca1eff09c06475"}, - {file = "mypy-1.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ebf9e796521f99d61864ed89d1fb2926d9ab6a5fab421e457cd9c7e4dd65aa9"}, - {file = "mypy-1.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e478601cc3e3fa9d6734d255a59c7a2e5c2934da4378f3dd1e3411ea8a248642"}, - {file = "mypy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:c72861b7139a4f738344faa0e150834467521a3fba42dc98264e5aa9507dd601"}, - {file = "mypy-1.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52b9e1492e47e1790360a43755fa04101a7ac72287b1a53ce817f35899ba0521"}, - {file = "mypy-1.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:48d3e37dd7d9403e38fa86c46191de72705166d40b8c9f91a3de77350daa0893"}, - {file = "mypy-1.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f106db5ccb60681b622ac768455743ee0e6a857724d648c9629a9bd2ac3f721"}, - {file = "mypy-1.12.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:233e11b3f73ee1f10efada2e6da0f555b2f3a5316e9d8a4a1224acc10e7181d3"}, - {file = "mypy-1.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:4ae8959c21abcf9d73aa6c74a313c45c0b5a188752bf37dace564e29f06e9c1b"}, - {file = "mypy-1.12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eafc1b7319b40ddabdc3db8d7d48e76cfc65bbeeafaa525a4e0fa6b76175467f"}, - {file = "mypy-1.12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9b9ce1ad8daeb049c0b55fdb753d7414260bad8952645367e70ac91aec90e07e"}, - {file = "mypy-1.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfe012b50e1491d439172c43ccb50db66d23fab714d500b57ed52526a1020bb7"}, - {file = "mypy-1.12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2c40658d4fa1ab27cb53d9e2f1066345596af2f8fe4827defc398a09c7c9519b"}, - {file = "mypy-1.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:dee78a8b9746c30c1e617ccb1307b351ded57f0de0d287ca6276378d770006c0"}, - {file = "mypy-1.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b5df6c8a8224f6b86746bda716bbe4dbe0ce89fd67b1fa4661e11bfe38e8ec8"}, - {file = "mypy-1.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5feee5c74eb9749e91b77f60b30771563327329e29218d95bedbe1257e2fe4b0"}, - {file = "mypy-1.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:77278e8c6ffe2abfba6db4125de55f1024de9a323be13d20e4f73b8ed3402bd1"}, - {file = "mypy-1.12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:dcfb754dea911039ac12434d1950d69a2f05acd4d56f7935ed402be09fad145e"}, - {file = "mypy-1.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:06de0498798527451ffb60f68db0d368bd2bae2bbfb5237eae616d4330cc87aa"}, - {file = "mypy-1.12.0-py3-none-any.whl", hash = "sha256:fd313226af375d52e1e36c383f39bf3836e1f192801116b31b090dfcd3ec5266"}, - {file = "mypy-1.12.0.tar.gz", hash = "sha256:65a22d87e757ccd95cbbf6f7e181e6caa87128255eb2b6be901bb71b26d8a99d"}, + {file = "mypy-1.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3d7d4371829184e22fda4015278fbfdef0327a4b955a483012bd2d423a788801"}, + {file = "mypy-1.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f59f1dfbf497d473201356966e353ef09d4daec48caeacc0254db8ef633a28a5"}, + {file = "mypy-1.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b947097fae68004b8328c55161ac9db7d3566abfef72d9d41b47a021c2fba6b1"}, + {file = "mypy-1.12.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:96af62050971c5241afb4701c15189ea9507db89ad07794a4ee7b4e092dc0627"}, + {file = "mypy-1.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:d90da248f4c2dba6c44ddcfea94bb361e491962f05f41990ff24dbd09969ce20"}, + {file = "mypy-1.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1230048fec1380faf240be6385e709c8570604d2d27ec6ca7e573e3bc09c3735"}, + {file = "mypy-1.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:02dcfe270c6ea13338210908f8cadc8d31af0f04cee8ca996438fe6a97b4ec66"}, + {file = "mypy-1.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5a437c9102a6a252d9e3a63edc191a3aed5f2fcb786d614722ee3f4472e33f6"}, + {file = "mypy-1.12.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:186e0c8346efc027ee1f9acf5ca734425fc4f7dc2b60144f0fbe27cc19dc7931"}, + {file = "mypy-1.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:673ba1140a478b50e6d265c03391702fa11a5c5aff3f54d69a62a48da32cb811"}, + {file = "mypy-1.12.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9fb83a7be97c498176fb7486cafbb81decccaef1ac339d837c377b0ce3743a7f"}, + {file = "mypy-1.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:389e307e333879c571029d5b93932cf838b811d3f5395ed1ad05086b52148fb0"}, + {file = "mypy-1.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:94b2048a95a21f7a9ebc9fbd075a4fcd310410d078aa0228dbbad7f71335e042"}, + {file = "mypy-1.12.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ee5932370ccf7ebf83f79d1c157a5929d7ea36313027b0d70a488493dc1b179"}, + {file = "mypy-1.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:19bf51f87a295e7ab2894f1d8167622b063492d754e69c3c2fed6563268cb42a"}, + {file = "mypy-1.12.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d34167d43613ffb1d6c6cdc0cc043bb106cac0aa5d6a4171f77ab92a3c758bcc"}, + {file = "mypy-1.12.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:427878aa54f2e2c5d8db31fa9010c599ed9f994b3b49e64ae9cd9990c40bd635"}, + {file = "mypy-1.12.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5fcde63ea2c9f69d6be859a1e6dd35955e87fa81de95bc240143cf00de1f7f81"}, + {file = "mypy-1.12.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d54d840f6c052929f4a3d2aab2066af0f45a020b085fe0e40d4583db52aab4e4"}, + {file = "mypy-1.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:20db6eb1ca3d1de8ece00033b12f793f1ea9da767334b7e8c626a4872090cf02"}, + {file = "mypy-1.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b16fe09f9c741d85a2e3b14a5257a27a4f4886c171d562bc5a5e90d8591906b8"}, + {file = "mypy-1.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0dcc1e843d58f444fce19da4cce5bd35c282d4bde232acdeca8279523087088a"}, + {file = "mypy-1.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e10ba7de5c616e44ad21005fa13450cd0de7caaa303a626147d45307492e4f2d"}, + {file = "mypy-1.12.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0e6fe449223fa59fbee351db32283838a8fee8059e0028e9e6494a03802b4004"}, + {file = "mypy-1.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:dc6e2a2195a290a7fd5bac3e60b586d77fc88e986eba7feced8b778c373f9afe"}, + {file = "mypy-1.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:de5b2a8988b4e1269a98beaf0e7cc71b510d050dce80c343b53b4955fff45f19"}, + {file = "mypy-1.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:843826966f1d65925e8b50d2b483065c51fc16dc5d72647e0236aae51dc8d77e"}, + {file = "mypy-1.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9fe20f89da41a95e14c34b1ddb09c80262edcc295ad891f22cc4b60013e8f78d"}, + {file = "mypy-1.12.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8135ffec02121a75f75dc97c81af7c14aa4ae0dda277132cfcd6abcd21551bfd"}, + {file = "mypy-1.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:a7b76fa83260824300cc4834a3ab93180db19876bce59af921467fd03e692810"}, + {file = "mypy-1.12.1-py3-none-any.whl", hash = "sha256:ce561a09e3bb9863ab77edf29ae3a50e65685ad74bba1431278185b7e5d5486e"}, + {file = "mypy-1.12.1.tar.gz", hash = "sha256:f5b3936f7a6d0e8280c9bdef94c7ce4847f5cdfc258fbb2c29a8c1711e8bb96d"}, ] [package.dependencies] @@ -1599,93 +1599,93 @@ six = "*" [[package]] name = "yarl" -version = "1.15.4" +version = "1.15.5" description = "Yet another URL library" optional = false python-versions = ">=3.9" files = [ - {file = "yarl-1.15.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:551205388d1da18a9975302c9a274ba24788f53bb9bb86187496ebf9e938916e"}, - {file = "yarl-1.15.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eee724176b5bc50ee64905f559345448119b860a30b9489bd7a073f61baf925f"}, - {file = "yarl-1.15.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db818e33599f7b2e4c6507f2b2c24f45ff539a1b6e4e09163bb6f3cfb4616ca7"}, - {file = "yarl-1.15.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07019a9de859c5a29916defd1e8c7557de6491a10bf50c49ff5284e6aedf5313"}, - {file = "yarl-1.15.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db64a20e78969fc66665d2e5fc96cb4f4dc80f2137d8fed4b5a650ad569bb60f"}, - {file = "yarl-1.15.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4076bfd8f1621449b19b9826848ed51bf0f2d1d38e82647c312c0730d8778903"}, - {file = "yarl-1.15.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c23a442973dba3646811c284fce3dddd7fe5c2bd674ac73a122198e8218d6115"}, - {file = "yarl-1.15.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2bdb038b3f5c284e3919218c580dedc95f592c417a358361450b9519b22f7a8"}, - {file = "yarl-1.15.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:59db8e6888d5302b8dbca0c1026ddabe99d81d67cdc101941519e13ffc9050fe"}, - {file = "yarl-1.15.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f3294ce265011547630a59c20085fcb6af8cc5fa1fa44a203251f7d86cd5d913"}, - {file = "yarl-1.15.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4851618679ca70b863ba2e7109be5f09f8fd7715ec505bd42e5a947dcfde3a45"}, - {file = "yarl-1.15.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:dce1c56beef74d9c799a6ed94001693232a1402138292353a8ce302b64f457d9"}, - {file = "yarl-1.15.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1e7468f31de61a82817f918743e5229fce774f73fad58487cdf88eef4f06d864"}, - {file = "yarl-1.15.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:527c68f48a91d953691291d3bce0209293aa5ad13ff05286ddb506791c331818"}, - {file = "yarl-1.15.4-cp310-cp310-win32.whl", hash = "sha256:c30115cecaf25fdcb67cc71c669d08425207f62d7a2f6d5416057c1460529216"}, - {file = "yarl-1.15.4-cp310-cp310-win_amd64.whl", hash = "sha256:df09c80f4bc2bc2efde309af383c3fe8fd8c51fe0519edb350b9c9e0af43ffa4"}, - {file = "yarl-1.15.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:76259901cf1ac3db65e7e6dff04775b626d0715f9b51d92b447351144c756a82"}, - {file = "yarl-1.15.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:98d8dc1e8133f86d916125deca9780d791b22645f0d62bafe1452d1cd5eac631"}, - {file = "yarl-1.15.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d0f16c87c62b7a94b389ddf6a8c9d081265d788875c39f3a80108c4856eea7b"}, - {file = "yarl-1.15.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8de5328d91859b461899497980d4cc8269e84e2d18640f6ac643886fda9000bf"}, - {file = "yarl-1.15.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84937d00e2ea03616c40977de20189fa13a9213e5744a3c6afa0e7dd9141d69c"}, - {file = "yarl-1.15.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:691a3b498fdebef63308e8967bb598cfd326c56d628da82b799dd181bace4503"}, - {file = "yarl-1.15.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a706db0c3b7e4578ff34ed2b1d2507b08fd491346ffc64468786fdf1151d938"}, - {file = "yarl-1.15.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:adb6b5d07d17c32f9d34c9dd4a693637a72323cfcb1f8a52d57033ab2dd21e99"}, - {file = "yarl-1.15.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6e100c6c7d9e9d469009fd55cc4d7ad168d67d40758865c50da713f7ada491e5"}, - {file = "yarl-1.15.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:df6b254e55c8ac2362afaa651e3e53453aa19a095570792346245773b434176e"}, - {file = "yarl-1.15.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8721f8bedaa722c3c483cc06a1399cbfdb280eadf443aa5d324b0203cef2a75f"}, - {file = "yarl-1.15.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1005921b30f4f39bf893946df6173567ff650307babb5ec04bbf64342a1f62c1"}, - {file = "yarl-1.15.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ab79cc13307065a0b3ef087f09f0509996fc605d35d6642bb28e5d85b2648e1e"}, - {file = "yarl-1.15.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f337486742c700b102d640830aab3faf2848bed966b479a39e6783edd4ab1c6c"}, - {file = "yarl-1.15.4-cp311-cp311-win32.whl", hash = "sha256:20acf84bd1ce530065f8e957e4a5878fda4bc5f18cb02659828210e1519de54e"}, - {file = "yarl-1.15.4-cp311-cp311-win_amd64.whl", hash = "sha256:ab9ccf26cb3fa32747ba2a637a189d2d42386a2fc4afc10dbc7f85922dd23b0f"}, - {file = "yarl-1.15.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f923e94e93a37fd990e8336e0b9bedea533e7cbed14e0c572bf9357ef2a70681"}, - {file = "yarl-1.15.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3198da7d7c34e29fc8c823e0c3ce6c7274aac35760de557c2017489c7d98fc5a"}, - {file = "yarl-1.15.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d886de2ea81f513ba2d6820451d33b767a97c37867ba688d42e164b2dbca1362"}, - {file = "yarl-1.15.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ac85e760543129a1912a82438fc8075223e35eaa2d457d61cd83c27d00d17be"}, - {file = "yarl-1.15.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e58c5d07b1f78dd4cb180c5b3b82465cd281aaeee8aafea0e5d72a4b97922cb1"}, - {file = "yarl-1.15.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9060589d0acad1fca048861fa9ee3e8ed060f67894fa885969648ab6e9e99a54"}, - {file = "yarl-1.15.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccd6774aa7bebdf9ca608bb0839318757a71b8e0d2cf7b10c002bc8790bd343e"}, - {file = "yarl-1.15.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7694f109867ee428c21b85ae19fd31d164c691eb45cc95c561cfdeba237a12e3"}, - {file = "yarl-1.15.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:83e7154aa0d17f5c93d27ac01088fd9ab6673e7bab1acbd07cd7a865b980c045"}, - {file = "yarl-1.15.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:f16d1940c0cbc342f1d29d6212a006d172be616d2942c5c41966e8a3ce4c3be1"}, - {file = "yarl-1.15.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7d5226c70af3ad9569ccc4ccc04ab65be79eeb22c87d7ae789c89e62ef76bbd6"}, - {file = "yarl-1.15.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f25906e4a72d9833e81717c39a39dee7297ff5cb44957d06d177a2ab8ef2ef7f"}, - {file = "yarl-1.15.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e07e4b17b648c880e8e42bf1ac0a730bde114961646ae1c2ec4433f0c11ca94"}, - {file = "yarl-1.15.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f8136bde8dfa4477c6a85c79a366581b4a505b51a52b669318fb631d3f4f638"}, - {file = "yarl-1.15.4-cp312-cp312-win32.whl", hash = "sha256:ccbeaf5b18b173b9d78e332e017b30ba8bedcf03cdce1d13490b82a3f421bc98"}, - {file = "yarl-1.15.4-cp312-cp312-win_amd64.whl", hash = "sha256:f74f6ffdc633aefecbc80282242a5395058db9d1247fa7dd2f070ef84dc82583"}, - {file = "yarl-1.15.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4f66a0eda48844508736e47ed476d8fdd7cdbf16a4053b5d439509a25f708504"}, - {file = "yarl-1.15.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fd2bb86f40962d53a91def15a2f7684c62e081a7b96ec74ed0259c34b15973b9"}, - {file = "yarl-1.15.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f864b412557e69a6b953d62c01a0ed0ee342666298aa7f2a29af526bfa80f6e9"}, - {file = "yarl-1.15.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a79c0a8bbb046add85663af85e9993b691bf20c2a109518bd35e0ce77edfe42"}, - {file = "yarl-1.15.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de479e30abd2dfd49fdad3bd6953f2d930a45380be5143c0c9f7a1215cffc8cc"}, - {file = "yarl-1.15.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:21fabe58042f3e567b4edc75b2cf44cea02f228e41ac09d73de126bf685fe883"}, - {file = "yarl-1.15.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77390496f2f32437a721c854897f889abefae0f3009daf90a2f703508d96c920"}, - {file = "yarl-1.15.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3896bf15284dd23acab1f2e7fceb350d8da6f6f2436b922f7ec6b3de685d34ca"}, - {file = "yarl-1.15.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:590e2d733a82ecf004c5c531cbef0d6be328e93adec960024eb213f10cb9503e"}, - {file = "yarl-1.15.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:1ceb677fb583971351627eac70eec6763fbc889761828da7a276681b5e39742d"}, - {file = "yarl-1.15.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:69f628d2da1489b27959f4d63fdb326781fe484944dce94abbf919e416c54abe"}, - {file = "yarl-1.15.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:35a6b69cc44bda002705d6138346bf0a0234cbb7c26c3bf192513eb946aee6f9"}, - {file = "yarl-1.15.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:49f886e8dcf591275c6e20915b516fd81647857566b0c0158c52df1e468849c9"}, - {file = "yarl-1.15.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:49190eb2ece70313742b0ea51520340288a059674da1f39eefb589d598d9453e"}, - {file = "yarl-1.15.4-cp313-cp313-win32.whl", hash = "sha256:48334a6c8afee93097eb17c0a094234dac2d88da076c8cf372e09e2a5dcc4b66"}, - {file = "yarl-1.15.4-cp313-cp313-win_amd64.whl", hash = "sha256:f68025d6ba1816428b7de615c80f61cb03d5b7061158d4ced7696657a64aa59c"}, - {file = "yarl-1.15.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8b569f4f511b59518ba6719feb5b8bf0a5d4115e6ac903c89e10a8a9ac656017"}, - {file = "yarl-1.15.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9fe17744d60fc404ac61f824118e1e15ce3c2e92eced9b8e22f3c7847acafbf2"}, - {file = "yarl-1.15.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:115346433fad2084ee3a1a925ccc0659990aa42e208ca54c278830a150a3caf3"}, - {file = "yarl-1.15.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60165b8bc260f453321004b193770a66cc1b1a5c57c07d4b8dcc96839e7ad578"}, - {file = "yarl-1.15.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65a0168691373e08d869d48b62c8bed0af0cdaef19c76e11ad73b43901bbdb5a"}, - {file = "yarl-1.15.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:787532f00543a21b8f4ec3050b4e01b8fe437797903c0156a0b03dfca5e1ba6c"}, - {file = "yarl-1.15.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f51c9d173e5fa4b12d06ddca09a41cabbdeb660471dbe55432423eec095709ab"}, - {file = "yarl-1.15.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c96eaa30030e1cfafe533f3da8983812281235b7c50ef2a6c78ceca7aea1a0b"}, - {file = "yarl-1.15.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4feab2dcb725eb5b4835207ecf3d370ff7ce930b253cba5e681646cb80d64c2c"}, - {file = "yarl-1.15.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:de38b0b5b86e57efb129d179854e78b65cb8e294a8c75560877869c43aa2415a"}, - {file = "yarl-1.15.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:65e0467f90f2acf3bc83bbfeedece8f1fd84df8add1a54e9600ed7b7b5debdb0"}, - {file = "yarl-1.15.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:40c18f96696549e73b92dc12619f07019cbf5faefc1612608f967c144816e493"}, - {file = "yarl-1.15.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:46491b3e058de7b484e1c9fb20aa8441f06d6c9a18395d711c1c2a9ad6707d6a"}, - {file = "yarl-1.15.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:faa3dd7f4620ab5e5da7a0789d0aac78a9ad0376f102409d442ec5a4179e200a"}, - {file = "yarl-1.15.4-cp39-cp39-win32.whl", hash = "sha256:c33ea7c55a73be343f02361795caf52a187357ea07708fb1cae6661ee1d689c8"}, - {file = "yarl-1.15.4-cp39-cp39-win_amd64.whl", hash = "sha256:11b207061f28b4b6d980239b22ab0ecfadc47846b5a3b8e79f27fcc019d02cf9"}, - {file = "yarl-1.15.4-py3-none-any.whl", hash = "sha256:e5cc288111c450c0a54a74475591b206d3b1cb47dc71bb6200f6be8b1337184c"}, - {file = "yarl-1.15.4.tar.gz", hash = "sha256:a0c5e271058d148d730219ca4f33c5d841c6bd46e05b0da60fea7b516906ccd3"}, + {file = "yarl-1.15.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b6c57972a406ea0f61e3f28f2b3a780fb71fbe1d82d267afe5a2f889a83ee7e7"}, + {file = "yarl-1.15.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c3ac5bdcc1375c8ee52784adf94edbce37c471dd2100a117cfef56fe8dbc2b4"}, + {file = "yarl-1.15.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:68d21d0563d82aaf46163eac529adac301b20be3181b8a2811f7bd5615466055"}, + {file = "yarl-1.15.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7d317fb80bc17ed4b34a9aad8b80cef34bea0993654f3e8566daf323def7ef9"}, + {file = "yarl-1.15.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed9c72d5361cfd5af5ccadffa8f8077f4929640e1f938aa0f4b92c5a24996ac5"}, + {file = "yarl-1.15.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bb707859218e8335447b210f41a755e7b1367c33e87add884128bba144694a7f"}, + {file = "yarl-1.15.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6563394492c96cb57f4dff0c69c63d2b28b5469c59c66f35a1e6451583cd0ab4"}, + {file = "yarl-1.15.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c2d1109c8d92059314cc34dd8f0a31f74b720dc140744923ed7ca228bf9b491"}, + {file = "yarl-1.15.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8fc727f0fb388debc771eaa7091c092bd2e8b6b4741b73354b8efadcf96d6031"}, + {file = "yarl-1.15.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:94189746c5ad62e1014a16298130e696fe593d031d442ef135fb7787b7a1f820"}, + {file = "yarl-1.15.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b06d8b05d0fafef204d635a4711283ddbf19c7c0facdc61b4b775f6e47e2d4be"}, + {file = "yarl-1.15.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:de6917946dc6bc237d4b354e38aa13a232e0c7948fdbdb160edee3862e9d735f"}, + {file = "yarl-1.15.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:34816f1d833433a16c4832562a050b0a60eac53dcb71b2032e6ebff82d74b6a7"}, + {file = "yarl-1.15.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:19e2a4b2935f95fad0949f420514c5d862f5f18058fbbfd8854f496a97d9fd87"}, + {file = "yarl-1.15.5-cp310-cp310-win32.whl", hash = "sha256:30ca64521f1a96b72886dd9e8652f16eab11891b4572dcfcfc1ad6d6ccb27abd"}, + {file = "yarl-1.15.5-cp310-cp310-win_amd64.whl", hash = "sha256:86648c53b10c53db8b967a75fb41e0c89dbec7398f6525e34af2b6c456bb0ac0"}, + {file = "yarl-1.15.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e652aa9f8dfa808bc5b2da4d1f4e286cf1d640570fdfa72ffc0c1d16ba114651"}, + {file = "yarl-1.15.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:21050b6cd569980fe20ceeab4baeb900d3f7247270475e42bafe117416a5496c"}, + {file = "yarl-1.15.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:18940191ec9a83bbfe63eea61c3e9d12474bb910d5613bce8fa46e84a80b75b2"}, + {file = "yarl-1.15.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a082dc948045606f62dca0228ab24f13737180b253378d6443f5b2b9ef8beefe"}, + {file = "yarl-1.15.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a843e692f9d5402b3455653f4607dc521de2385f01c5cad7ba4a87c46e2ea8d"}, + {file = "yarl-1.15.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5093a453176a4fad4f9c3006f507cf300546190bb3e27944275a37cfd6323a65"}, + {file = "yarl-1.15.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2597a589859b94d0a5e2f5d30fee95081867926e57cb751f8b44a7dd92da4e79"}, + {file = "yarl-1.15.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f5a1ca6eaabfe62718b87eac06d9a47b30cf92ffa065fee9196d3ecd24a3cf1"}, + {file = "yarl-1.15.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4ac83b307cc4b8907345b52994055c6c3c2601ceb6fcb94c5ed6a93c6b4e8257"}, + {file = "yarl-1.15.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:325e2beb2cd8654b276e7686a3cd203628dd3fe32d5c616e632bc35a2901fb16"}, + {file = "yarl-1.15.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:75d04ba8ed335042328086e643e01165e0c24598216f72da709b375930ae3bdb"}, + {file = "yarl-1.15.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7abd7d15aedb3961a967cc65f8144dbbca42e3626a21c5f4f29919cf43eeafb9"}, + {file = "yarl-1.15.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:294c742a273f44511f14b03a9e06b66094dcdf4bbb75a5e23fead548fd5310ae"}, + {file = "yarl-1.15.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:63d46606b20f80a6476f1044bab78e1a69c2e0747f174583e2f12fc70bad2170"}, + {file = "yarl-1.15.5-cp311-cp311-win32.whl", hash = "sha256:b1217102a455e3ac9ac293081093f21f0183e978c7692171ff669fee5296fa28"}, + {file = "yarl-1.15.5-cp311-cp311-win_amd64.whl", hash = "sha256:5848500b6a01497560969e8c3a7eb1b2570853c74a0ca6f67ebaf6064106c49b"}, + {file = "yarl-1.15.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d3309ee667f2d9c7ac9ecf44620d6b274bfdd8065b8c5019ff6795dd887b8fed"}, + {file = "yarl-1.15.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:96ce879799fee124d241ea3b84448378f638e290c49493d00b706f3fd57ec22b"}, + {file = "yarl-1.15.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c884dfa56b050f718ea3cbbfd972e29a6f07f63a7449b10d9a20d64f7eec92e2"}, + {file = "yarl-1.15.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0327081978fe186c3390dd4f73f95f825d0bb9c74967e22c2a1a87735974d8f5"}, + {file = "yarl-1.15.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:524b3bb7dff320e305bc979c65eddc0342548c56ea9241502f907853fe53c408"}, + {file = "yarl-1.15.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd56de8b645421ff09c993fdb0ee9c5a3b50d290a8f55793b500d99b34d0c1ce"}, + {file = "yarl-1.15.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c166ad987265bb343be58cdf4fbc4478cc1d81f2246d2be9a15f94393b269faa"}, + {file = "yarl-1.15.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d56980374a10c74255fcea6ebcfb0aeca7166d212ee9fd7e823ddef35fb62ad0"}, + {file = "yarl-1.15.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cbf36099a9b407e1456dbf55844743a98603fcba32d2a46fb3a698d926facf1b"}, + {file = "yarl-1.15.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d7fa4b033e2f267e37aabcc36949fa89f9f1716a723395912147f9cf3fb437c7"}, + {file = "yarl-1.15.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bb129f77ddaea2d8e6e00417b8d907448de3407af4eddacca0a515574ad71493"}, + {file = "yarl-1.15.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:68e837b3edfcd037f9706157e7cb8efda832de6248c7d9e893e2638356dfae5d"}, + {file = "yarl-1.15.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5b8af4165e097ff84d9bbb97bb4f4d7f71b9c1c9565a2d0e27d93e5f92dae220"}, + {file = "yarl-1.15.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:70d074d5a96e0954fe6db81ff356f4361397da1cda3f7c127fc0902f671a087e"}, + {file = "yarl-1.15.5-cp312-cp312-win32.whl", hash = "sha256:362da97ad4360e4ef1dd24ccdd3bceb18332da7f40026a42f49b7edd686e31c3"}, + {file = "yarl-1.15.5-cp312-cp312-win_amd64.whl", hash = "sha256:9aa054d97033beac9cb9b19b7c0b8784b85b12cd17879087ca6bffba57884e02"}, + {file = "yarl-1.15.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5fadcf532fd9f6cbad71485ef8c2462dd9a91d3efc72ca01eb0970792c92552a"}, + {file = "yarl-1.15.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8b7dd6983c81523f9de0ae6334c3b7a3cb33283936e0525f80c4f713f54a9bb6"}, + {file = "yarl-1.15.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fcfd663dc88465ebe41c7c938bdc91c4b01cda96a0d64bf38fd66c1877323771"}, + {file = "yarl-1.15.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd529e637cd23204bd82072f6637cff7af2516ad2c132e8f3342cbc84871f7d1"}, + {file = "yarl-1.15.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b30f13fac56598474071a4f1ecd66c78fdaf2f8619042d7ca135f72dbb348cf"}, + {file = "yarl-1.15.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:44088ec0be82fba118ed29b6b429f80bf295297727adae4c257ac297e01e8bcd"}, + {file = "yarl-1.15.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:607683991bab8607e5158cd290dd8fdaa613442aeab802fe1c237d3a3eee7358"}, + {file = "yarl-1.15.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da48cdff56b01ea4282a6d04b83b07a2088351a4a3ff7aacc1e7e9b6b04b90b9"}, + {file = "yarl-1.15.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9162ea117ce8bad8ebc95b7376b4135988acd888d2cf4702f8281e3c11f8b81f"}, + {file = "yarl-1.15.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:e8aa19c39cb20bfb16f0266df175a6004943122cf20707fbf0cacc21f6468a25"}, + {file = "yarl-1.15.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5d6be369488d503c8edc14e2f63d71ab2a607041ad216a8ad444fa18e8dea792"}, + {file = "yarl-1.15.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6e2c674cfe4c03ad7a4d536b1f808221f0d11a360486b4b032d2557c0bd633ad"}, + {file = "yarl-1.15.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:041bafaa82b77fd4ec2826d42a55461ec86d999adf7ed9644eef7e8a9febb366"}, + {file = "yarl-1.15.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2eeb9ba53c055740cd282ae9d34eb7970d65e73a46f15adec4b0c1b0f2e55cc2"}, + {file = "yarl-1.15.5-cp313-cp313-win32.whl", hash = "sha256:73143dd279e641543da52c55652ad7b4c7c5f79e797f124f58f04cc060f14271"}, + {file = "yarl-1.15.5-cp313-cp313-win_amd64.whl", hash = "sha256:94ab1185900f43760d5487c8e49f5f1a66f864e36092f282f1813597479b9dfa"}, + {file = "yarl-1.15.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6b3d2767bd64c62909ea33525b954ba05c8f9726bfdf2141d175da4e344f19ae"}, + {file = "yarl-1.15.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:44359c52af9c383e5107f3b6301446fc8269599721fa42fafb2afb5f31a42dcb"}, + {file = "yarl-1.15.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6493da9ba5c551978c679ab04856c2cf8f79c316e8ec8c503460a135705edc3b"}, + {file = "yarl-1.15.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a6b6e95bc621c11cf9ff21012173337e789f2461ebc3b4e5bf65c74ef69adb8"}, + {file = "yarl-1.15.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7983290ede3aaa2c9620879530849532529b4dcbf5b12a0b6a91163a773eadb9"}, + {file = "yarl-1.15.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07a4b53abe85813c538b9cdbb02909ebe3734e3af466a587df516e960d500cc8"}, + {file = "yarl-1.15.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5882faa2a6e684f65ee44f18c701768749a950cbd5e72db452fc07805f6bdec0"}, + {file = "yarl-1.15.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e27861251d9c094f641d39a8a78dd2371fb9a252ea2f689d1ad353a31d46a0bc"}, + {file = "yarl-1.15.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8669a110f655c9eb22f16fb68a7d4942020aeaa09f1def584a80183e3e89953c"}, + {file = "yarl-1.15.5-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:10bfe0bef4cf5ea0383886beda004071faadedf2647048b9f876664284c5b60d"}, + {file = "yarl-1.15.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f7de0d4b6b4d8a77e422eb54d765255c0ec6883ee03b8fd537101633948619d7"}, + {file = "yarl-1.15.5-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:00bb3a559d7bd006a5302ecd7e409916939106a8cdbe31f4eb5e5b9ffcca57ea"}, + {file = "yarl-1.15.5-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:06ec070a2d71415f90dbe9d70af3158e7da97a128519dba2d1581156ee27fb92"}, + {file = "yarl-1.15.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b997a806846c00d1f41d6a251803732837771b2091bead7566f68820e317bfe7"}, + {file = "yarl-1.15.5-cp39-cp39-win32.whl", hash = "sha256:7825506fbee4055265528ec3532a8197ff26fc53d4978917a4c8ddbb4c1667d7"}, + {file = "yarl-1.15.5-cp39-cp39-win_amd64.whl", hash = "sha256:71730658be0b5de7c570a9795d7404c577b2313c1db370407092c66f70e04ccb"}, + {file = "yarl-1.15.5-py3-none-any.whl", hash = "sha256:625f31d6650829fba4030b4e7bdb2d69e41510dddfa29a1da27076c199521757"}, + {file = "yarl-1.15.5.tar.gz", hash = "sha256:8249147ee81c1cf4d1dc6f26ba28a1b9d92751529f83c308ad02164bb93abd0d"}, ] [package.dependencies] @@ -1695,13 +1695,13 @@ propcache = ">=0.2.0" [[package]] name = "ydb" -version = "3.18.3" +version = "3.18.4" description = "YDB Python SDK" optional = false python-versions = "*" files = [ - {file = "ydb-3.18.3-py2.py3-none-any.whl", hash = "sha256:90ec7431edf8ce518da57d9b636d4f9f6f2c449190259f129b40caac441bec19"}, - {file = "ydb-3.18.3.tar.gz", hash = "sha256:5c9f849d110324f9cfd4e7a84eb79fe95a9554c949ad936bb7285c126fffd4fa"}, + {file = "ydb-3.18.4-py2.py3-none-any.whl", hash = "sha256:6db894b2b186bea1c779acb6bf6dfdc4c98eca7d0a9c8bab51140cafa5f27e15"}, + {file = "ydb-3.18.4.tar.gz", hash = "sha256:ad481bc761ef3977364e9b358e7f7525b28e971dcadf3e2f83918f91ed35025f"}, ] [package.dependencies] diff --git a/pyproject.toml b/pyproject.toml index afea8aa..5dd365e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ ruff = "ruff check" lint = ["mypy", "ruff"] format-check = "ruff format --check" format = "ruff format" +tests = "pytest -v --docker-compose-remove-volumes --docker-compose=docker-compose.yml" [tool.ruff] exclude = [".venv", ".git", "__pycache__", "build", "dist", "venv"] @@ -48,6 +49,7 @@ select = ["F", "E"] [tool.pytest.ini_options] asyncio_mode = "auto" +addopts = "-p no:warnings" [[tool.mypy.overrides]] module = "ydb.*" diff --git a/tests/conftest.py b/tests/conftest.py index 4784876..5217f11 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -55,3 +55,23 @@ async def driver(endpoint, database, event_loop): yield driver await driver.stop(timeout=10) + + +@pytest.fixture +async def session_pool(driver: ydb.aio.Driver, event_loop): + session_pool = ydb.aio.QuerySessionPool(driver) + async with session_pool: + await session_pool.execute_with_retries( + """DROP TABLE IF EXISTS table""" + ) + await session_pool.execute_with_retries( + """ + CREATE TABLE table ( + id Int64 NOT NULL, + val Int64, + PRIMARY KEY(id) + ) + """ + ) + + yield session_pool diff --git a/tests/test_cursor.py b/tests/test_cursor.py index 28fc551..1539ea1 100644 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -1,33 +1,125 @@ import pytest - -import ydb - import ydb_dbapi import ydb_dbapi.cursors -@pytest.fixture -async def cursor(driver: ydb.aio.Driver): - session_pool = ydb.aio.QuerySessionPool(driver) - session = await session_pool.acquire() - cursor = ydb_dbapi.cursors.Cursor(session_pool, session) - - yield cursor - - await session_pool.release(session) - - @pytest.mark.asyncio -async def test_cursor_ddl(cursor): - op = ydb_dbapi.cursors.YdbQuery( - yql_text=""" +async def test_cursor_ddl(session_pool): + cursor = ydb_dbapi.cursors.Cursor(session_pool=session_pool) + + yql = """ CREATE TABLE table ( id Int64 NOT NULL, - i64Val Int64, + val Int64, PRIMARY KEY(id) ) - """, - is_ddl=True, - ) + """ + + with pytest.raises(ydb_dbapi.Error): + await cursor.execute(query=yql) + + yql = """ + DROP TABLE table + """ + + await cursor.execute(query=yql) + + assert await cursor.fetchone() is None + + +@pytest.mark.asyncio +async def test_cursor_dml(session_pool): + cursor = ydb_dbapi.cursors.Cursor(session_pool=session_pool) + yql_text = """ + INSERT INTO table (id, val) VALUES + (1, 1), + (2, 2), + (3, 3) + """ + + await cursor.execute(query=yql_text) + assert await cursor.fetchone() is None + + cursor = ydb_dbapi.cursors.Cursor(session_pool=session_pool) + + yql_text = """ + SELECT COUNT(*) FROM table as sum + """ + + await cursor.execute(query=yql_text) + + res = await cursor.fetchone() + assert len(res) == 1 + assert res[0] == 3 + + +@pytest.mark.asyncio +async def test_cursor_fetch_one(session_pool): + cursor = ydb_dbapi.cursors.Cursor(session_pool=session_pool) + yql_text = """ + INSERT INTO table (id, val) VALUES + (1, 1), + (2, 2) + """ + + await cursor.execute(query=yql_text) + assert await cursor.fetchone() is None + + cursor = ydb_dbapi.cursors.Cursor(session_pool=session_pool) + + yql_text = """ + SELECT id, val FROM table + """ + + await cursor.execute(query=yql_text) + + res = await cursor.fetchone() + assert res[0] == 1 + + res = await cursor.fetchone() + assert res[0] == 2 + + assert await cursor.fetchone() is None + + +@pytest.mark.asyncio +async def test_cursor_fetch_many(session_pool): + cursor = ydb_dbapi.cursors.Cursor(session_pool=session_pool) + yql_text = """ + INSERT INTO table (id, val) VALUES + (1, 1), + (2, 2), + (3, 3), + (4, 4) + """ - await cursor.execute(op) + await cursor.execute(query=yql_text) + assert await cursor.fetchone() is None + + cursor = ydb_dbapi.cursors.Cursor(session_pool=session_pool) + + yql_text = """ + SELECT id, val FROM table + """ + + await cursor.execute(query=yql_text) + + res = await cursor.fetchmany() + assert len(res) == 1 + assert res[0][0] == 1 + + res = await cursor.fetchmany(size=2) + assert len(res) == 2 + assert res[0][0] == 2 + assert res[1][0] == 3 + + res = await cursor.fetchmany(size=2) + assert len(res) == 1 + assert res[0][0] == 4 + + assert await cursor.fetchmany(size=2) is None + + +@pytest.mark.asyncio +async def test_cursor_fetch_all(session_pool): + pass diff --git a/ydb_dbapi/__init__.py b/ydb_dbapi/__init__.py index e69de29..e25fc93 100644 --- a/ydb_dbapi/__init__.py +++ b/ydb_dbapi/__init__.py @@ -0,0 +1 @@ +from .errors import * # noqa diff --git a/ydb_dbapi/cursors.py b/ydb_dbapi/cursors.py index 9a4da5e..6973055 100644 --- a/ydb_dbapi/cursors.py +++ b/ydb_dbapi/cursors.py @@ -12,7 +12,7 @@ ) import ydb -from .errors import DatabaseError +from .errors import Error, DatabaseError from .utils import handle_ydb_errors, AsyncFromSyncIterator @@ -40,8 +40,6 @@ class Cursor: def __init__( self, session_pool: ydb.aio.QuerySessionPool, - session: ydb.aio.QuerySession, - tx_mode: Optional[ydb.aio.QueryTxContext] = None, tx_context: Optional[ydb.aio.QueryTxContext] = None, table_path_prefix: str = "", autocommit: bool = True, @@ -49,10 +47,8 @@ def __init__( self.arraysize: int = 1 self._description: Optional[List[Tuple]] = None - self._pool = session_pool - self._session = session - self._tx_mode = tx_mode - self._tx_context: ydb.aio.QueryTxContext = tx_context + self._session_pool = session_pool + self._tx_context = tx_context self._table_path_prefix = table_path_prefix self._autocommit = autocommit @@ -60,17 +56,21 @@ def __init__( self._rows: Optional[Iterator[Dict]] = None @handle_ydb_errors - async def _execute_ddl_query( + async def _execute_generic_query( self, query: str, parameters: Optional[ParametersType] = None ) -> List[ydb.convert.ResultSet]: - return await self._pool.execute_with_retries( + return await self._session_pool.execute_with_retries( query=query, parameters=parameters ) @handle_ydb_errors - async def _execute_dml_query( + async def _execute_transactional_query( self, query: str, parameters: Optional[ParametersType] = None ) -> AsyncIterator: + if self._tx_context is None: + raise Error( + "Unable to execute tx based queries without transaction." + ) return await self._tx_context.execute( query=query, parameters=parameters, @@ -79,17 +79,17 @@ async def _execute_dml_query( @handle_ydb_errors async def execute( - self, operation: YdbQuery, parameters: Optional[ParametersType] = None + self, query: str, parameters: Optional[ParametersType] = None ): - if operation.is_ddl: - result_sets = await self._execute_ddl_query( - query=operation.yql_text, parameters=parameters + if self._tx_context is not None: + self._stream = await self._execute_transactional_query( + query=query, parameters=parameters ) - self._stream = AsyncFromSyncIterator(iter(result_sets)) else: - self._stream = await self._execute_dml_query( - query=operation.yql_text, parameters=parameters + result_sets = await self._execute_generic_query( + query=query, parameters=parameters ) + self._stream = AsyncFromSyncIterator(iter(result_sets)) if self._stream is None: return @@ -98,7 +98,6 @@ async def execute( self._update_result_set(result_set) def _update_result_set(self, result_set: ydb.convert.ResultSet): - # self._result_set = result_set self._update_description(result_set) self._rows = self._rows_iterable(result_set) @@ -133,8 +132,13 @@ async def fetchone(self): return next(self._rows or iter([]), None) async def fetchmany(self, size: Optional[int] = None): - return list( - itertools.islice(self._rows or iter([]), size or self.arraysize) + return ( + list( + itertools.islice( + self._rows or iter([]), size or self.arraysize + ) + ) + or None ) async def fetchall(self): diff --git a/ydb_dbapi/utils.py b/ydb_dbapi/utils.py index 859a077..a11b1cd 100644 --- a/ydb_dbapi/utils.py +++ b/ydb_dbapi/utils.py @@ -15,9 +15,9 @@ def handle_ydb_errors(func): @functools.wraps(func) - def wrapper(*args, **kwargs): + async def wrapper(*args, **kwargs): try: - return func(*args, **kwargs) + return await func(*args, **kwargs) except (ydb.issues.AlreadyExists, ydb.issues.PreconditionFailed) as e: raise IntegrityError(e.message, original_error=e) from e except (ydb.issues.Unsupported, ydb.issues.Unimplemented) as e: From 068707a0b56befa9e1f75848969493a018e289ad Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:10:11 +0300 Subject: [PATCH 10/33] new tests and connection fixes --- .github/workflows/lint.yml | 43 ++++++++++ tests/test_connection.py | 172 +++++++++++++++++++++++++++++++++++++ tests/test_cursor.py | 71 +++++++++++++-- ydb_dbapi/__init__.py | 2 + ydb_dbapi/connection.py | 118 ++++++++++++++++--------- ydb_dbapi/cursors.py | 10 +-- 6 files changed, 361 insertions(+), 55 deletions(-) create mode 100644 .github/workflows/lint.yml create mode 100644 tests/test_connection.py diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..c5c401d --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,43 @@ +name: Lint + +on: + push: + branches: + - master + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + python-version: [3.9] + poetry-version: [1.8] + + steps: + - uses: actions/checkout@v3 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install Poetry ${{ matrix.poetry-version }} + run: | + pip install "poetry~=${{ matrix.poetry-version }}.0" + + # Ensure that Poetry is not upgraded past the version we are testing + poetry add "poetry@~${{ matrix.poetry-version }}" --lock + + - name: Install packages + run: | + poetry install + + - name: Run tests + run: | + poetry run poe lint diff --git a/tests/test_connection.py b/tests/test_connection.py new file mode 100644 index 0000000..4081be8 --- /dev/null +++ b/tests/test_connection.py @@ -0,0 +1,172 @@ +from contextlib import suppress + +import pytest +import pytest_asyncio +import ydb + +import ydb_dbapi as dbapi + + +class BaseDBApiTestSuit: + async def _test_isolation_level_read_only( + self, + connection: dbapi.Connection, + isolation_level: str, + read_only: bool, + ): + await connection.cursor().execute( + "CREATE TABLE foo(id Int64 NOT NULL, PRIMARY KEY (id))" + ) + connection.set_isolation_level(isolation_level) + + cursor = connection.cursor() + + await connection.begin() + + query = "UPSERT INTO foo(id) VALUES (1)" + if read_only: + with pytest.raises(dbapi.DatabaseError): + await cursor.execute(query) + else: + await cursor.execute(query) + + await connection.rollback() + + await connection.cursor().execute("DROP TABLE foo") + await connection.cursor().close() + + async def _test_connection(self, connection: dbapi.Connection): + await connection.commit() + await connection.rollback() + + cur = connection.cursor() + with suppress(dbapi.DatabaseError): + await cur.execute("DROP TABLE foo") + + assert not await connection.check_exists("/local/foo") + with pytest.raises(dbapi.ProgrammingError): + await connection.describe("/local/foo") + + await cur.execute( + "CREATE TABLE foo(id Int64 NOT NULL, PRIMARY KEY (id))" + ) + + assert await connection.check_exists("/local/foo") + + col = (await connection.describe("/local/foo")).columns[0] + assert col.name == "id" + assert col.type == ydb.PrimitiveType.Int64 + + await cur.execute("DROP TABLE foo") + await cur.close() + + async def _test_cursor_raw_query(self, connection: dbapi.Connection): + cur = connection.cursor() + assert cur + + with suppress(dbapi.DatabaseError): + await cur.execute("DROP TABLE test") + + await cur.execute( + "CREATE TABLE test(id Int64 NOT NULL, text Utf8, PRIMARY KEY (id))" + ) + + await cur.execute( + """ + DECLARE $data AS List>; + + INSERT INTO test SELECT id, text FROM AS_TABLE($data); + """, + { + "$data": ydb.TypedValue( + [ + {"id": 17, "text": "seventeen"}, + {"id": 21, "text": "twenty one"}, + ], + ydb.ListType( + ydb.StructType() + .add_member("id", ydb.PrimitiveType.Int64) + .add_member("text", ydb.PrimitiveType.Utf8) + ), + ) + }, + ) + + await cur.execute("DROP TABLE test") + + await cur.close() + + async def _test_errors(self, connection: dbapi.Connection): + with pytest.raises(dbapi.InterfaceError): + await dbapi.connect("localhost:2136", database="/local666") + + cur = connection.cursor() + + with suppress(dbapi.DatabaseError): + await cur.execute("DROP TABLE test") + + with pytest.raises(dbapi.DataError): + await cur.execute("SELECT 18446744073709551616") + + with pytest.raises(dbapi.DataError): + await cur.execute("SELECT * FROM 拉屎") + + with pytest.raises(dbapi.DataError): + await cur.execute("SELECT floor(5 / 2)") + + with pytest.raises(dbapi.ProgrammingError): + await cur.execute("SELECT * FROM test") + + await cur.execute("CREATE TABLE test(id Int64, PRIMARY KEY (id))") + + await cur.execute("INSERT INTO test(id) VALUES(1)") + with pytest.raises(dbapi.IntegrityError): + await cur.execute("INSERT INTO test(id) VALUES(1)") + + await cur.execute("DROP TABLE test") + await cur.close() + + +class TestAsyncConnection(BaseDBApiTestSuit): + @pytest_asyncio.fixture + async def connection(self, endpoint, database): + host, port = endpoint.split(":") + conn = await dbapi.connect(host=host, port=port, database=database) + try: + yield conn + finally: + await conn.close() + + @pytest.mark.asyncio + @pytest.mark.parametrize( + "isolation_level, read_only", + [ + (dbapi.IsolationLevel.SERIALIZABLE, False), + (dbapi.IsolationLevel.AUTOCOMMIT, False), + # (dbapi.IsolationLevel.ONLINE_READONLY, True), + # (dbapi.IsolationLevel.ONLINE_READONLY_INCONSISTENT, True), + # (dbapi.IsolationLevel.STALE_READONLY, True), + # (dbapi.IsolationLevel.SNAPSHOT_READONLY, True), + ], + ) + async def test_isolation_level_read_only( + self, + isolation_level: str, + read_only: bool, + connection: dbapi.Connection, + ): + await self._test_isolation_level_read_only( + connection, isolation_level, read_only + ) + + @pytest.mark.asyncio + async def test_connection(self, connection: dbapi.Connection): + await self._test_connection(connection) + + @pytest.mark.asyncio + async def test_cursor_raw_query(self, connection: dbapi.Connection): + await self._test_cursor_raw_query(connection) + + @pytest.mark.asyncio + async def test_errors(self, connection: dbapi.Connection): + await self._test_errors(connection) diff --git a/tests/test_cursor.py b/tests/test_cursor.py index 1539ea1..3ea2004 100644 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -1,11 +1,10 @@ import pytest import ydb_dbapi -import ydb_dbapi.cursors @pytest.mark.asyncio async def test_cursor_ddl(session_pool): - cursor = ydb_dbapi.cursors.Cursor(session_pool=session_pool) + cursor = ydb_dbapi.Cursor(session_pool=session_pool) yql = """ CREATE TABLE table ( @@ -29,7 +28,7 @@ async def test_cursor_ddl(session_pool): @pytest.mark.asyncio async def test_cursor_dml(session_pool): - cursor = ydb_dbapi.cursors.Cursor(session_pool=session_pool) + cursor = ydb_dbapi.Cursor(session_pool=session_pool) yql_text = """ INSERT INTO table (id, val) VALUES (1, 1), @@ -40,7 +39,7 @@ async def test_cursor_dml(session_pool): await cursor.execute(query=yql_text) assert await cursor.fetchone() is None - cursor = ydb_dbapi.cursors.Cursor(session_pool=session_pool) + cursor = ydb_dbapi.Cursor(session_pool=session_pool) yql_text = """ SELECT COUNT(*) FROM table as sum @@ -55,7 +54,7 @@ async def test_cursor_dml(session_pool): @pytest.mark.asyncio async def test_cursor_fetch_one(session_pool): - cursor = ydb_dbapi.cursors.Cursor(session_pool=session_pool) + cursor = ydb_dbapi.Cursor(session_pool=session_pool) yql_text = """ INSERT INTO table (id, val) VALUES (1, 1), @@ -65,7 +64,7 @@ async def test_cursor_fetch_one(session_pool): await cursor.execute(query=yql_text) assert await cursor.fetchone() is None - cursor = ydb_dbapi.cursors.Cursor(session_pool=session_pool) + cursor = ydb_dbapi.Cursor(session_pool=session_pool) yql_text = """ SELECT id, val FROM table @@ -84,7 +83,7 @@ async def test_cursor_fetch_one(session_pool): @pytest.mark.asyncio async def test_cursor_fetch_many(session_pool): - cursor = ydb_dbapi.cursors.Cursor(session_pool=session_pool) + cursor = ydb_dbapi.Cursor(session_pool=session_pool) yql_text = """ INSERT INTO table (id, val) VALUES (1, 1), @@ -96,7 +95,7 @@ async def test_cursor_fetch_many(session_pool): await cursor.execute(query=yql_text) assert await cursor.fetchone() is None - cursor = ydb_dbapi.cursors.Cursor(session_pool=session_pool) + cursor = ydb_dbapi.Cursor(session_pool=session_pool) yql_text = """ SELECT id, val FROM table @@ -122,4 +121,58 @@ async def test_cursor_fetch_many(session_pool): @pytest.mark.asyncio async def test_cursor_fetch_all(session_pool): - pass + cursor = ydb_dbapi.Cursor(session_pool=session_pool) + yql_text = """ + INSERT INTO table (id, val) VALUES + (1, 1), + (2, 2), + (3, 3) + """ + + await cursor.execute(query=yql_text) + assert await cursor.fetchone() is None + + cursor = ydb_dbapi.Cursor(session_pool=session_pool) + + yql_text = """ + SELECT id, val FROM table + """ + + await cursor.execute(query=yql_text) + + assert cursor.rowcount == 3 + + res = await cursor.fetchall() + assert len(res) == 3 + assert res[0][0] == 1 + assert res[1][0] == 2 + assert res[2][0] == 3 + + assert await cursor.fetchall() is None + + +@pytest.mark.asyncio +async def test_cursor_next_set(session_pool): + cursor = ydb_dbapi.Cursor(session_pool=session_pool) + yql_text = """SELECT 1 as val; SELECT 2 as val;""" + + await cursor.execute(query=yql_text) + + res = await cursor.fetchall() + assert len(res) == 1 + assert res[0][0] == 1 + + nextset = await cursor.nextset() + assert nextset + + res = await cursor.fetchall() + assert len(res) == 1 + assert res[0][0] == 2 + + nextset = await cursor.nextset() + assert nextset + + assert await cursor.fetchall() is None + + nextset = await cursor.nextset() + assert not nextset diff --git a/ydb_dbapi/__init__.py b/ydb_dbapi/__init__.py index e25fc93..f85f00b 100644 --- a/ydb_dbapi/__init__.py +++ b/ydb_dbapi/__init__.py @@ -1 +1,3 @@ from .errors import * # noqa +from .connection import Connection, IsolationLevel, connect # noqa +from .cursors import Cursor # noqa diff --git a/ydb_dbapi/connection.py b/ydb_dbapi/connection.py index d723ef2..d4c9c89 100644 --- a/ydb_dbapi/connection.py +++ b/ydb_dbapi/connection.py @@ -9,12 +9,14 @@ import ydb from ydb.retries import retry_operation_async +from .cursors import Cursor + from .utils import ( handle_ydb_errors, ) from .errors import ( - # InterfaceError, + InterfaceError, InternalError, NotSupportedError, ) @@ -45,42 +47,69 @@ def __init__( "ydb_table_path_prefix", "" ) - self.driver: ydb.aio.Driver = self.conn_kwargs.pop("ydb_driver", None) + if ( + "ydb_session_pool" in self.conn_kwargs + ): # Use session pool managed manually + self._shared_session_pool = True + self._session_pool: ydb.aio.SessionPool = self.conn_kwargs.pop( + "ydb_session_pool" + ) + self._driver = self._session_pool._driver + else: + self._shared_session_pool = False + driver_config = ydb.DriverConfig( + endpoint=self.endpoint, + database=self.database, + credentials=self.credentials, + ) + self._driver = ydb.aio.Driver(driver_config) + self._session_pool = ydb.aio.QuerySessionPool(self._driver, size=5) - self.session_pool: ydb.aio.QuerySessionPool = self.conn_kwargs.pop( - "ydb_session_pool", None - ) - self.session: ydb.aio.QuerySession = None - self.tx_context: Optional[ydb.QueryTxContext] = None - self.tx_mode: ydb.BaseQueryTxMode = ydb.QuerySerializableReadWrite() + self._tx_context: Optional[ydb.QueryTxContext] = None + self._tx_mode: ydb.BaseQueryTxMode = ydb.QuerySerializableReadWrite() - self.interactive_transaction: bool = False # AUTOCOMMIT + async def _wait(self, timeout: int = 5): + try: + await self._driver.wait(timeout, fail_fast=True) + except ydb.Error as e: + raise InterfaceError(e.message, original_error=e) from e + except Exception as e: + await self._driver.stop() + raise InterfaceError( + "Failed to connect to YDB, details " + f"{self._driver.discovery_debug_details()}" + ) from e def cursor(self): - pass + return Cursor( + session_pool=self._session_pool, tx_context=self._tx_context + ) async def begin(self): - self.tx_context = None - self.session = await self.session_pool.acquire() - self.tx_context = self.session.transaction(self.tx_mode) - await self.tx_context.begin() + self._tx_context = None + self._session = await self._session_pool.acquire() + self._tx_context = self._session.transaction(self._tx_mode) + await self._tx_context.begin() async def commit(self): - if self.tx_context and self.tx_context.tx_id: - await self.tx_context.commit() - await self.session_pool.release(self.session) - self.session = None - self.tx_context = None + if self._tx_context and self._tx_context.tx_id: + await self._tx_context.commit() + await self._session_pool.release(self._session) + self._session = None + self._tx_context = None async def rollback(self): - if self.tx_context and self.tx_context.tx_id: - await self.tx_context.rollback() - await self.session_pool.release(self.session) - self.session = None - self.tx_context = None + if self._tx_context and self._tx_context.tx_id: + await self._tx_context.rollback() + await self._session_pool.release(self._session) + self._session = None + self._tx_context = None async def close(self): await self.rollback() + if not self._shared_session_pool: + await self._session_pool.stop() + await self._driver.stop() def set_isolation_level(self, isolation_level: str): class IsolationSettings(NamedTuple): @@ -109,37 +138,37 @@ class IsolationSettings(NamedTuple): ), } ydb_isolation_settings = ydb_isolation_settings_map[isolation_level] - if self.tx_context and self.tx_context.tx_id: + if self._tx_context and self._tx_context.tx_id: raise InternalError( "Failed to set transaction mode: transaction is already began" ) - self.tx_mode = ydb_isolation_settings.ydb_mode + self._tx_mode = ydb_isolation_settings.ydb_mode self.interactive_transaction = ydb_isolation_settings.interactive def get_isolation_level(self) -> str: - if self.tx_mode.name == ydb.SerializableReadWrite().name: + if self._tx_mode.name == ydb.SerializableReadWrite().name: if self.interactive_transaction: return IsolationLevel.SERIALIZABLE else: return IsolationLevel.AUTOCOMMIT - elif self.tx_mode.name == ydb.OnlineReadOnly().name: - if self.tx_mode.settings.allow_inconsistent_reads: + elif self._tx_mode.name == ydb.OnlineReadOnly().name: + if self._tx_mode.settings.allow_inconsistent_reads: return IsolationLevel.ONLINE_READONLY_INCONSISTENT else: return IsolationLevel.ONLINE_READONLY - elif self.tx_mode.name == ydb.StaleReadOnly().name: + elif self._tx_mode.name == ydb.StaleReadOnly().name: return IsolationLevel.STALE_READONLY - elif self.tx_mode.name == ydb.SnapshotReadOnly().name: + elif self._tx_mode.name == ydb.SnapshotReadOnly().name: return IsolationLevel.SNAPSHOT_READONLY else: - raise NotSupportedError(f"{self.tx_mode.name} is not supported") + raise NotSupportedError(f"{self._tx_mode.name} is not supported") @handle_ydb_errors async def describe(self, table_path: str) -> ydb.TableSchemeEntry: abs_table_path = posixpath.join( self.database, self.table_path_prefix, table_path ) - return self.driver.table_client.describe_table(abs_table_path) + return await self._driver.table_client.describe_table(abs_table_path) @handle_ydb_errors async def check_exists(self, table_path: str) -> bool: @@ -156,17 +185,22 @@ async def get_table_names(self) -> List[str]: async def _check_path_exists(self, table_path: str) -> bool: try: - await retry_operation_async( - self.driver.scheme_client.describe_path, table_path - ) + + async def callee(): + await self._driver.scheme_client.describe_path(table_path) + + await retry_operation_async(callee) return True except ydb.SchemeError: return False async def _get_table_names(self, abs_dir_path: str) -> List[str]: - directory = await retry_operation_async( - self.driver.scheme_client.list_directory, abs_dir_path - ) + async def callee(): + return await self._driver.scheme_client.list_directory( + abs_dir_path + ) + + directory = await retry_operation_async(callee) result = [] for child in directory.children: child_abs_path = posixpath.join(abs_dir_path, child.name) @@ -177,5 +211,7 @@ async def _get_table_names(self, abs_dir_path: str) -> List[str]: return result -async def connect() -> Connection: - return Connection() +async def connect(*args, **kwargs) -> Connection: + conn = Connection(*args, **kwargs) + await conn._wait() + return conn diff --git a/ydb_dbapi/cursors.py b/ydb_dbapi/cursors.py index 6973055..779bf0e 100644 --- a/ydb_dbapi/cursors.py +++ b/ydb_dbapi/cursors.py @@ -54,8 +54,8 @@ def __init__( self._stream: Optional[AsyncIterator] = None self._rows: Optional[Iterator[Dict]] = None + self._rows_count: int = -1 - @handle_ydb_errors async def _execute_generic_query( self, query: str, parameters: Optional[ParametersType] = None ) -> List[ydb.convert.ResultSet]: @@ -63,7 +63,6 @@ async def _execute_generic_query( query=query, parameters=parameters ) - @handle_ydb_errors async def _execute_transactional_query( self, query: str, parameters: Optional[ParametersType] = None ) -> AsyncIterator: @@ -100,6 +99,7 @@ async def execute( def _update_result_set(self, result_set: ydb.convert.ResultSet): self._update_description(result_set) self._rows = self._rows_iterable(result_set) + self._rows_count = len(result_set.rows) or -1 def _update_description(self, result_set: ydb.convert.ResultSet): self._description = [ @@ -142,7 +142,7 @@ async def fetchmany(self, size: Optional[int] = None): ) async def fetchall(self): - return list(self._rows or iter([])) + return list(self._rows or iter([])) or None async def nextset(self): if self._stream is None: @@ -150,7 +150,7 @@ async def nextset(self): try: result_set = await self._stream.__anext__() self._update_result_set(result_set) - except StopIteration: + except (StopIteration, RuntimeError): return False return True @@ -171,4 +171,4 @@ def description(self): @property def rowcount(self): - pass + return self._rows_count From 3e351d5ee4459e80d54ae9a16c8fa955a6fb9377 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:10:11 +0300 Subject: [PATCH 11/33] fix all tests --- poetry.lock | 172 +++++++++++++++++++-------------------- pyproject.toml | 2 +- tests/conftest.py | 3 +- tests/test_connection.py | 47 +++++++---- ydb_dbapi/connection.py | 20 ++++- ydb_dbapi/cursors.py | 92 +++++++++++++++------ ydb_dbapi/utils.py | 8 ++ 7 files changed, 214 insertions(+), 130 deletions(-) diff --git a/poetry.lock b/poetry.lock index 25962f4..049e777 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1599,93 +1599,93 @@ six = "*" [[package]] name = "yarl" -version = "1.15.5" +version = "1.16.0" description = "Yet another URL library" optional = false python-versions = ">=3.9" files = [ - {file = "yarl-1.15.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b6c57972a406ea0f61e3f28f2b3a780fb71fbe1d82d267afe5a2f889a83ee7e7"}, - {file = "yarl-1.15.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c3ac5bdcc1375c8ee52784adf94edbce37c471dd2100a117cfef56fe8dbc2b4"}, - {file = "yarl-1.15.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:68d21d0563d82aaf46163eac529adac301b20be3181b8a2811f7bd5615466055"}, - {file = "yarl-1.15.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7d317fb80bc17ed4b34a9aad8b80cef34bea0993654f3e8566daf323def7ef9"}, - {file = "yarl-1.15.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed9c72d5361cfd5af5ccadffa8f8077f4929640e1f938aa0f4b92c5a24996ac5"}, - {file = "yarl-1.15.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bb707859218e8335447b210f41a755e7b1367c33e87add884128bba144694a7f"}, - {file = "yarl-1.15.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6563394492c96cb57f4dff0c69c63d2b28b5469c59c66f35a1e6451583cd0ab4"}, - {file = "yarl-1.15.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c2d1109c8d92059314cc34dd8f0a31f74b720dc140744923ed7ca228bf9b491"}, - {file = "yarl-1.15.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8fc727f0fb388debc771eaa7091c092bd2e8b6b4741b73354b8efadcf96d6031"}, - {file = "yarl-1.15.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:94189746c5ad62e1014a16298130e696fe593d031d442ef135fb7787b7a1f820"}, - {file = "yarl-1.15.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b06d8b05d0fafef204d635a4711283ddbf19c7c0facdc61b4b775f6e47e2d4be"}, - {file = "yarl-1.15.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:de6917946dc6bc237d4b354e38aa13a232e0c7948fdbdb160edee3862e9d735f"}, - {file = "yarl-1.15.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:34816f1d833433a16c4832562a050b0a60eac53dcb71b2032e6ebff82d74b6a7"}, - {file = "yarl-1.15.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:19e2a4b2935f95fad0949f420514c5d862f5f18058fbbfd8854f496a97d9fd87"}, - {file = "yarl-1.15.5-cp310-cp310-win32.whl", hash = "sha256:30ca64521f1a96b72886dd9e8652f16eab11891b4572dcfcfc1ad6d6ccb27abd"}, - {file = "yarl-1.15.5-cp310-cp310-win_amd64.whl", hash = "sha256:86648c53b10c53db8b967a75fb41e0c89dbec7398f6525e34af2b6c456bb0ac0"}, - {file = "yarl-1.15.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e652aa9f8dfa808bc5b2da4d1f4e286cf1d640570fdfa72ffc0c1d16ba114651"}, - {file = "yarl-1.15.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:21050b6cd569980fe20ceeab4baeb900d3f7247270475e42bafe117416a5496c"}, - {file = "yarl-1.15.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:18940191ec9a83bbfe63eea61c3e9d12474bb910d5613bce8fa46e84a80b75b2"}, - {file = "yarl-1.15.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a082dc948045606f62dca0228ab24f13737180b253378d6443f5b2b9ef8beefe"}, - {file = "yarl-1.15.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a843e692f9d5402b3455653f4607dc521de2385f01c5cad7ba4a87c46e2ea8d"}, - {file = "yarl-1.15.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5093a453176a4fad4f9c3006f507cf300546190bb3e27944275a37cfd6323a65"}, - {file = "yarl-1.15.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2597a589859b94d0a5e2f5d30fee95081867926e57cb751f8b44a7dd92da4e79"}, - {file = "yarl-1.15.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f5a1ca6eaabfe62718b87eac06d9a47b30cf92ffa065fee9196d3ecd24a3cf1"}, - {file = "yarl-1.15.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4ac83b307cc4b8907345b52994055c6c3c2601ceb6fcb94c5ed6a93c6b4e8257"}, - {file = "yarl-1.15.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:325e2beb2cd8654b276e7686a3cd203628dd3fe32d5c616e632bc35a2901fb16"}, - {file = "yarl-1.15.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:75d04ba8ed335042328086e643e01165e0c24598216f72da709b375930ae3bdb"}, - {file = "yarl-1.15.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7abd7d15aedb3961a967cc65f8144dbbca42e3626a21c5f4f29919cf43eeafb9"}, - {file = "yarl-1.15.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:294c742a273f44511f14b03a9e06b66094dcdf4bbb75a5e23fead548fd5310ae"}, - {file = "yarl-1.15.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:63d46606b20f80a6476f1044bab78e1a69c2e0747f174583e2f12fc70bad2170"}, - {file = "yarl-1.15.5-cp311-cp311-win32.whl", hash = "sha256:b1217102a455e3ac9ac293081093f21f0183e978c7692171ff669fee5296fa28"}, - {file = "yarl-1.15.5-cp311-cp311-win_amd64.whl", hash = "sha256:5848500b6a01497560969e8c3a7eb1b2570853c74a0ca6f67ebaf6064106c49b"}, - {file = "yarl-1.15.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d3309ee667f2d9c7ac9ecf44620d6b274bfdd8065b8c5019ff6795dd887b8fed"}, - {file = "yarl-1.15.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:96ce879799fee124d241ea3b84448378f638e290c49493d00b706f3fd57ec22b"}, - {file = "yarl-1.15.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c884dfa56b050f718ea3cbbfd972e29a6f07f63a7449b10d9a20d64f7eec92e2"}, - {file = "yarl-1.15.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0327081978fe186c3390dd4f73f95f825d0bb9c74967e22c2a1a87735974d8f5"}, - {file = "yarl-1.15.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:524b3bb7dff320e305bc979c65eddc0342548c56ea9241502f907853fe53c408"}, - {file = "yarl-1.15.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd56de8b645421ff09c993fdb0ee9c5a3b50d290a8f55793b500d99b34d0c1ce"}, - {file = "yarl-1.15.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c166ad987265bb343be58cdf4fbc4478cc1d81f2246d2be9a15f94393b269faa"}, - {file = "yarl-1.15.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d56980374a10c74255fcea6ebcfb0aeca7166d212ee9fd7e823ddef35fb62ad0"}, - {file = "yarl-1.15.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cbf36099a9b407e1456dbf55844743a98603fcba32d2a46fb3a698d926facf1b"}, - {file = "yarl-1.15.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d7fa4b033e2f267e37aabcc36949fa89f9f1716a723395912147f9cf3fb437c7"}, - {file = "yarl-1.15.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bb129f77ddaea2d8e6e00417b8d907448de3407af4eddacca0a515574ad71493"}, - {file = "yarl-1.15.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:68e837b3edfcd037f9706157e7cb8efda832de6248c7d9e893e2638356dfae5d"}, - {file = "yarl-1.15.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5b8af4165e097ff84d9bbb97bb4f4d7f71b9c1c9565a2d0e27d93e5f92dae220"}, - {file = "yarl-1.15.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:70d074d5a96e0954fe6db81ff356f4361397da1cda3f7c127fc0902f671a087e"}, - {file = "yarl-1.15.5-cp312-cp312-win32.whl", hash = "sha256:362da97ad4360e4ef1dd24ccdd3bceb18332da7f40026a42f49b7edd686e31c3"}, - {file = "yarl-1.15.5-cp312-cp312-win_amd64.whl", hash = "sha256:9aa054d97033beac9cb9b19b7c0b8784b85b12cd17879087ca6bffba57884e02"}, - {file = "yarl-1.15.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5fadcf532fd9f6cbad71485ef8c2462dd9a91d3efc72ca01eb0970792c92552a"}, - {file = "yarl-1.15.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8b7dd6983c81523f9de0ae6334c3b7a3cb33283936e0525f80c4f713f54a9bb6"}, - {file = "yarl-1.15.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fcfd663dc88465ebe41c7c938bdc91c4b01cda96a0d64bf38fd66c1877323771"}, - {file = "yarl-1.15.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd529e637cd23204bd82072f6637cff7af2516ad2c132e8f3342cbc84871f7d1"}, - {file = "yarl-1.15.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b30f13fac56598474071a4f1ecd66c78fdaf2f8619042d7ca135f72dbb348cf"}, - {file = "yarl-1.15.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:44088ec0be82fba118ed29b6b429f80bf295297727adae4c257ac297e01e8bcd"}, - {file = "yarl-1.15.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:607683991bab8607e5158cd290dd8fdaa613442aeab802fe1c237d3a3eee7358"}, - {file = "yarl-1.15.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da48cdff56b01ea4282a6d04b83b07a2088351a4a3ff7aacc1e7e9b6b04b90b9"}, - {file = "yarl-1.15.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9162ea117ce8bad8ebc95b7376b4135988acd888d2cf4702f8281e3c11f8b81f"}, - {file = "yarl-1.15.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:e8aa19c39cb20bfb16f0266df175a6004943122cf20707fbf0cacc21f6468a25"}, - {file = "yarl-1.15.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5d6be369488d503c8edc14e2f63d71ab2a607041ad216a8ad444fa18e8dea792"}, - {file = "yarl-1.15.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6e2c674cfe4c03ad7a4d536b1f808221f0d11a360486b4b032d2557c0bd633ad"}, - {file = "yarl-1.15.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:041bafaa82b77fd4ec2826d42a55461ec86d999adf7ed9644eef7e8a9febb366"}, - {file = "yarl-1.15.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2eeb9ba53c055740cd282ae9d34eb7970d65e73a46f15adec4b0c1b0f2e55cc2"}, - {file = "yarl-1.15.5-cp313-cp313-win32.whl", hash = "sha256:73143dd279e641543da52c55652ad7b4c7c5f79e797f124f58f04cc060f14271"}, - {file = "yarl-1.15.5-cp313-cp313-win_amd64.whl", hash = "sha256:94ab1185900f43760d5487c8e49f5f1a66f864e36092f282f1813597479b9dfa"}, - {file = "yarl-1.15.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6b3d2767bd64c62909ea33525b954ba05c8f9726bfdf2141d175da4e344f19ae"}, - {file = "yarl-1.15.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:44359c52af9c383e5107f3b6301446fc8269599721fa42fafb2afb5f31a42dcb"}, - {file = "yarl-1.15.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6493da9ba5c551978c679ab04856c2cf8f79c316e8ec8c503460a135705edc3b"}, - {file = "yarl-1.15.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a6b6e95bc621c11cf9ff21012173337e789f2461ebc3b4e5bf65c74ef69adb8"}, - {file = "yarl-1.15.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7983290ede3aaa2c9620879530849532529b4dcbf5b12a0b6a91163a773eadb9"}, - {file = "yarl-1.15.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07a4b53abe85813c538b9cdbb02909ebe3734e3af466a587df516e960d500cc8"}, - {file = "yarl-1.15.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5882faa2a6e684f65ee44f18c701768749a950cbd5e72db452fc07805f6bdec0"}, - {file = "yarl-1.15.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e27861251d9c094f641d39a8a78dd2371fb9a252ea2f689d1ad353a31d46a0bc"}, - {file = "yarl-1.15.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8669a110f655c9eb22f16fb68a7d4942020aeaa09f1def584a80183e3e89953c"}, - {file = "yarl-1.15.5-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:10bfe0bef4cf5ea0383886beda004071faadedf2647048b9f876664284c5b60d"}, - {file = "yarl-1.15.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f7de0d4b6b4d8a77e422eb54d765255c0ec6883ee03b8fd537101633948619d7"}, - {file = "yarl-1.15.5-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:00bb3a559d7bd006a5302ecd7e409916939106a8cdbe31f4eb5e5b9ffcca57ea"}, - {file = "yarl-1.15.5-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:06ec070a2d71415f90dbe9d70af3158e7da97a128519dba2d1581156ee27fb92"}, - {file = "yarl-1.15.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b997a806846c00d1f41d6a251803732837771b2091bead7566f68820e317bfe7"}, - {file = "yarl-1.15.5-cp39-cp39-win32.whl", hash = "sha256:7825506fbee4055265528ec3532a8197ff26fc53d4978917a4c8ddbb4c1667d7"}, - {file = "yarl-1.15.5-cp39-cp39-win_amd64.whl", hash = "sha256:71730658be0b5de7c570a9795d7404c577b2313c1db370407092c66f70e04ccb"}, - {file = "yarl-1.15.5-py3-none-any.whl", hash = "sha256:625f31d6650829fba4030b4e7bdb2d69e41510dddfa29a1da27076c199521757"}, - {file = "yarl-1.15.5.tar.gz", hash = "sha256:8249147ee81c1cf4d1dc6f26ba28a1b9d92751529f83c308ad02164bb93abd0d"}, + {file = "yarl-1.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32468f41242d72b87ab793a86d92f885355bcf35b3355aa650bfa846a5c60058"}, + {file = "yarl-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:234f3a3032b505b90e65b5bc6652c2329ea7ea8855d8de61e1642b74b4ee65d2"}, + {file = "yarl-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a0296040e5cddf074c7f5af4a60f3fc42c0237440df7bcf5183be5f6c802ed5"}, + {file = "yarl-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de6c14dd7c7c0badba48157474ea1f03ebee991530ba742d381b28d4f314d6f3"}, + {file = "yarl-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b140e532fe0266003c936d017c1ac301e72ee4a3fd51784574c05f53718a55d8"}, + {file = "yarl-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:019f5d58093402aa8f6661e60fd82a28746ad6d156f6c5336a70a39bd7b162b9"}, + {file = "yarl-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c42998fd1cbeb53cd985bff0e4bc25fbe55fd6eb3a545a724c1012d69d5ec84"}, + {file = "yarl-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c7c30fb38c300fe8140df30a046a01769105e4cf4282567a29b5cdb635b66c4"}, + {file = "yarl-1.16.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e49e0fd86c295e743fd5be69b8b0712f70a686bc79a16e5268386c2defacaade"}, + {file = "yarl-1.16.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:b9ca7b9147eb1365c8bab03c003baa1300599575effad765e0b07dd3501ea9af"}, + {file = "yarl-1.16.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:27e11db3f1e6a51081a981509f75617b09810529de508a181319193d320bc5c7"}, + {file = "yarl-1.16.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8994c42f4ca25df5380ddf59f315c518c81df6a68fed5bb0c159c6cb6b92f120"}, + {file = "yarl-1.16.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:542fa8e09a581bcdcbb30607c7224beff3fdfb598c798ccd28a8184ffc18b7eb"}, + {file = "yarl-1.16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2bd6a51010c7284d191b79d3b56e51a87d8e1c03b0902362945f15c3d50ed46b"}, + {file = "yarl-1.16.0-cp310-cp310-win32.whl", hash = "sha256:178ccb856e265174a79f59721031060f885aca428983e75c06f78aa24b91d929"}, + {file = "yarl-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe8bba2545427418efc1929c5c42852bdb4143eb8d0a46b09de88d1fe99258e7"}, + {file = "yarl-1.16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d8643975a0080f361639787415a038bfc32d29208a4bf6b783ab3075a20b1ef3"}, + {file = "yarl-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:676d96bafc8c2d0039cea0cd3fd44cee7aa88b8185551a2bb93354668e8315c2"}, + {file = "yarl-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d9525f03269e64310416dbe6c68d3b23e5d34aaa8f47193a1c45ac568cecbc49"}, + {file = "yarl-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b37d5ec034e668b22cf0ce1074d6c21fd2a08b90d11b1b73139b750a8b0dd97"}, + {file = "yarl-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f32c4cb7386b41936894685f6e093c8dfaf0960124d91fe0ec29fe439e201d0"}, + {file = "yarl-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b8e265a0545637492a7e12fd7038370d66c9375a61d88c5567d0e044ded9202"}, + {file = "yarl-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:789a3423f28a5fff46fbd04e339863c169ece97c827b44de16e1a7a42bc915d2"}, + {file = "yarl-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1d1f45e3e8d37c804dca99ab3cf4ab3ed2e7a62cd82542924b14c0a4f46d243"}, + {file = "yarl-1.16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:621280719c4c5dad4c1391160a9b88925bb8b0ff6a7d5af3224643024871675f"}, + {file = "yarl-1.16.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ed097b26f18a1f5ff05f661dc36528c5f6735ba4ce8c9645e83b064665131349"}, + {file = "yarl-1.16.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2f1fe2b2e3ee418862f5ebc0c0083c97f6f6625781382f828f6d4e9b614eba9b"}, + {file = "yarl-1.16.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:87dd10bc0618991c66cee0cc65fa74a45f4ecb13bceec3c62d78ad2e42b27a16"}, + {file = "yarl-1.16.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4199db024b58a8abb2cfcedac7b1292c3ad421684571aeb622a02f242280e8d6"}, + {file = "yarl-1.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:99a9dcd4b71dd5f5f949737ab3f356cfc058c709b4f49833aeffedc2652dac56"}, + {file = "yarl-1.16.0-cp311-cp311-win32.whl", hash = "sha256:a9394c65ae0ed95679717d391c862dece9afacd8fa311683fc8b4362ce8a410c"}, + {file = "yarl-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:5b9101f528ae0f8f65ac9d64dda2bb0627de8a50344b2f582779f32fda747c1d"}, + {file = "yarl-1.16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4ffb7c129707dd76ced0a4a4128ff452cecf0b0e929f2668ea05a371d9e5c104"}, + {file = "yarl-1.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1a5e9d8ce1185723419c487758d81ac2bde693711947032cce600ca7c9cda7d6"}, + {file = "yarl-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d743e3118b2640cef7768ea955378c3536482d95550222f908f392167fe62059"}, + {file = "yarl-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26768342f256e6e3c37533bf9433f5f15f3e59e3c14b2409098291b3efaceacb"}, + {file = "yarl-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1b0796168b953bca6600c5f97f5ed407479889a36ad7d17183366260f29a6b9"}, + {file = "yarl-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:858728086914f3a407aa7979cab743bbda1fe2bdf39ffcd991469a370dd7414d"}, + {file = "yarl-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5570e6d47bcb03215baf4c9ad7bf7c013e56285d9d35013541f9ac2b372593e7"}, + {file = "yarl-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66ea8311422a7ba1fc79b4c42c2baa10566469fe5a78500d4e7754d6e6db8724"}, + {file = "yarl-1.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:649bddcedee692ee8a9b7b6e38582cb4062dc4253de9711568e5620d8707c2a3"}, + {file = "yarl-1.16.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3a91654adb7643cb21b46f04244c5a315a440dcad63213033826549fa2435f71"}, + {file = "yarl-1.16.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b439cae82034ade094526a8f692b9a2b5ee936452de5e4c5f0f6c48df23f8604"}, + {file = "yarl-1.16.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:571f781ae8ac463ce30bacebfaef2c6581543776d5970b2372fbe31d7bf31a07"}, + {file = "yarl-1.16.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:aa7943f04f36d6cafc0cf53ea89824ac2c37acbdb4b316a654176ab8ffd0f968"}, + {file = "yarl-1.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1a5cf32539373ff39d97723e39a9283a7277cbf1224f7aef0c56c9598b6486c3"}, + {file = "yarl-1.16.0-cp312-cp312-win32.whl", hash = "sha256:a5b6c09b9b4253d6a208b0f4a2f9206e511ec68dce9198e0fbec4f160137aa67"}, + {file = "yarl-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:1208ca14eed2fda324042adf8d6c0adf4a31522fa95e0929027cd487875f0240"}, + {file = "yarl-1.16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5ace0177520bd4caa99295a9b6fb831d0e9a57d8e0501a22ffaa61b4c024283"}, + {file = "yarl-1.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7118bdb5e3ed81acaa2095cba7ec02a0fe74b52a16ab9f9ac8e28e53ee299732"}, + {file = "yarl-1.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38fec8a2a94c58bd47c9a50a45d321ab2285ad133adefbbadf3012c054b7e656"}, + {file = "yarl-1.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8791d66d81ee45866a7bb15a517b01a2bcf583a18ebf5d72a84e6064c417e64b"}, + {file = "yarl-1.16.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cf936ba67bc6c734f3aa1c01391da74ab7fc046a9f8bbfa230b8393b90cf472"}, + {file = "yarl-1.16.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1aab176dd55b59f77a63b27cffaca67d29987d91a5b615cbead41331e6b7428"}, + {file = "yarl-1.16.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:995d0759004c08abd5d1b81300a91d18c8577c6389300bed1c7c11675105a44d"}, + {file = "yarl-1.16.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1bc22e00edeb068f71967ab99081e9406cd56dbed864fc3a8259442999d71552"}, + {file = "yarl-1.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:35b4f7842154176523e0a63c9b871168c69b98065d05a4f637fce342a6a2693a"}, + {file = "yarl-1.16.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:7ace71c4b7a0c41f317ae24be62bb61e9d80838d38acb20e70697c625e71f120"}, + {file = "yarl-1.16.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8f639e3f5795a6568aa4f7d2ac6057c757dcd187593679f035adbf12b892bb00"}, + {file = "yarl-1.16.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e8be3aff14f0120ad049121322b107f8a759be76a6a62138322d4c8a337a9e2c"}, + {file = "yarl-1.16.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:122d8e7986043d0549e9eb23c7fd23be078be4b70c9eb42a20052b3d3149c6f2"}, + {file = "yarl-1.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0fd9c227990f609c165f56b46107d0bc34553fe0387818c42c02f77974402c36"}, + {file = "yarl-1.16.0-cp313-cp313-win32.whl", hash = "sha256:595ca5e943baed31d56b33b34736461a371c6ea0038d3baec399949dd628560b"}, + {file = "yarl-1.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:921b81b8d78f0e60242fb3db615ea3f368827a76af095d5a69f1c3366db3f596"}, + {file = "yarl-1.16.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab2b2ac232110a1fdb0d3ffcd087783edd3d4a6ced432a1bf75caf7b7be70916"}, + {file = "yarl-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f8713717a09acbfee7c47bfc5777e685539fefdd34fa72faf504c8be2f3df4e"}, + {file = "yarl-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cdcffe1dbcb4477d2b4202f63cd972d5baa155ff5a3d9e35801c46a415b7f71a"}, + {file = "yarl-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a91217208306d82357c67daeef5162a41a28c8352dab7e16daa82e3718852a7"}, + {file = "yarl-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ab3ed42c78275477ea8e917491365e9a9b69bb615cb46169020bd0aa5e2d6d3"}, + {file = "yarl-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:707ae579ccb3262dfaef093e202b4c3fb23c3810e8df544b1111bd2401fd7b09"}, + {file = "yarl-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad7a852d1cd0b8d8b37fc9d7f8581152add917a98cfe2ea6e241878795f917ae"}, + {file = "yarl-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3f1cc3d3d4dc574bebc9b387f6875e228ace5748a7c24f49d8f01ac1bc6c31b"}, + {file = "yarl-1.16.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5ff96da263740779b0893d02b718293cc03400c3a208fc8d8cd79d9b0993e532"}, + {file = "yarl-1.16.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:3d375a19ba2bfe320b6d873f3fb165313b002cef8b7cc0a368ad8b8a57453837"}, + {file = "yarl-1.16.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:62c7da0ad93a07da048b500514ca47b759459ec41924143e2ddb5d7e20fd3db5"}, + {file = "yarl-1.16.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:147b0fcd0ee33b4b5f6edfea80452d80e419e51b9a3f7a96ce98eaee145c1581"}, + {file = "yarl-1.16.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:504e1fe1cc4f170195320eb033d2b0ccf5c6114ce5bf2f617535c01699479bca"}, + {file = "yarl-1.16.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:bdcf667a5dec12a48f669e485d70c54189f0639c2157b538a4cffd24a853624f"}, + {file = "yarl-1.16.0-cp39-cp39-win32.whl", hash = "sha256:e9951afe6557c75a71045148890052cb942689ee4c9ec29f5436240e1fcc73b7"}, + {file = "yarl-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:7d7aaa8ff95d0840e289423e7dc35696c2b058d635f945bf05b5cd633146b027"}, + {file = "yarl-1.16.0-py3-none-any.whl", hash = "sha256:e6980a558d8461230c457218bd6c92dfc1d10205548215c2c21d79dc8d0a96f3"}, + {file = "yarl-1.16.0.tar.gz", hash = "sha256:b6f687ced5510a9a2474bbae96a4352e5ace5fa34dc44a217b0537fec1db00b4"}, ] [package.dependencies] @@ -1695,13 +1695,13 @@ propcache = ">=0.2.0" [[package]] name = "ydb" -version = "3.18.4" +version = "3.18.5" description = "YDB Python SDK" optional = false python-versions = "*" files = [ - {file = "ydb-3.18.4-py2.py3-none-any.whl", hash = "sha256:6db894b2b186bea1c779acb6bf6dfdc4c98eca7d0a9c8bab51140cafa5f27e15"}, - {file = "ydb-3.18.4.tar.gz", hash = "sha256:ad481bc761ef3977364e9b358e7f7525b28e971dcadf3e2f83918f91ed35025f"}, + {file = "ydb-3.18.5-py2.py3-none-any.whl", hash = "sha256:96aa7ccc73ba5f5e1db62fb19f5499c2204de1bc20a1f5e7898c32d09a85fd6b"}, + {file = "ydb-3.18.5.tar.gz", hash = "sha256:c8d066fac49a25697ec8ea7a0e925d26e8c1969a5cfa38f3ddd49a4fa3ddb64f"}, ] [package.dependencies] diff --git a/pyproject.toml b/pyproject.toml index 5dd365e..438015f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ select = ["F", "E"] [tool.pytest.ini_options] asyncio_mode = "auto" -addopts = "-p no:warnings" +addopts = "--tb native -v -r fxX -p no:warnings" [[tool.mypy.overrides]] module = "ydb.*" diff --git a/tests/conftest.py b/tests/conftest.py index 5217f11..4bba994 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -55,10 +55,11 @@ async def driver(endpoint, database, event_loop): yield driver await driver.stop(timeout=10) + del driver @pytest.fixture -async def session_pool(driver: ydb.aio.Driver, event_loop): +async def session_pool(driver: ydb.aio.Driver): session_pool = ydb.aio.QuerySessionPool(driver) async with session_pool: await session_pool.execute_with_retries( diff --git a/tests/test_connection.py b/tests/test_connection.py index 4081be8..fe98fe6 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -14,26 +14,32 @@ async def _test_isolation_level_read_only( isolation_level: str, read_only: bool, ): - await connection.cursor().execute( - "CREATE TABLE foo(id Int64 NOT NULL, PRIMARY KEY (id))" - ) - connection.set_isolation_level(isolation_level) + async with connection.cursor() as cursor: + with suppress(dbapi.DatabaseError): + await cursor.execute("DROP TABLE foo") - cursor = connection.cursor() + async with connection.cursor() as cursor: + await cursor.execute( + "CREATE TABLE foo(id Int64 NOT NULL, PRIMARY KEY (id))" + ) + + connection.set_isolation_level(isolation_level) await connection.begin() - query = "UPSERT INTO foo(id) VALUES (1)" - if read_only: - with pytest.raises(dbapi.DatabaseError): + async with connection.cursor() as cursor: + query = "UPSERT INTO foo(id) VALUES (1)" + if read_only: + with pytest.raises(dbapi.DatabaseError): + await cursor.execute(query) + await cursor.finish_query() + else: await cursor.execute(query) - else: - await cursor.execute(query) await connection.rollback() - await connection.cursor().execute("DROP TABLE foo") - await connection.cursor().close() + async with connection.cursor() as cursor: + cursor.execute("DROP TABLE foo") async def _test_connection(self, connection: dbapi.Connection): await connection.commit() @@ -42,6 +48,7 @@ async def _test_connection(self, connection: dbapi.Connection): cur = connection.cursor() with suppress(dbapi.DatabaseError): await cur.execute("DROP TABLE foo") + await cur.finish_query() assert not await connection.check_exists("/local/foo") with pytest.raises(dbapi.ProgrammingError): @@ -50,6 +57,7 @@ async def _test_connection(self, connection: dbapi.Connection): await cur.execute( "CREATE TABLE foo(id Int64 NOT NULL, PRIMARY KEY (id))" ) + await cur.finish_query() assert await connection.check_exists("/local/foo") @@ -66,10 +74,12 @@ async def _test_cursor_raw_query(self, connection: dbapi.Connection): with suppress(dbapi.DatabaseError): await cur.execute("DROP TABLE test") + await cur.finish_query() await cur.execute( "CREATE TABLE test(id Int64 NOT NULL, text Utf8, PRIMARY KEY (id))" ) + await cur.finish_query() await cur.execute( """ @@ -91,6 +101,7 @@ async def _test_cursor_raw_query(self, connection: dbapi.Connection): ) }, ) + await cur.finish_query() await cur.execute("DROP TABLE test") @@ -104,6 +115,7 @@ async def _test_errors(self, connection: dbapi.Connection): with suppress(dbapi.DatabaseError): await cur.execute("DROP TABLE test") + await cur.finish_query() with pytest.raises(dbapi.DataError): await cur.execute("SELECT 18446744073709551616") @@ -118,8 +130,11 @@ async def _test_errors(self, connection: dbapi.Connection): await cur.execute("SELECT * FROM test") await cur.execute("CREATE TABLE test(id Int64, PRIMARY KEY (id))") + await cur.finish_query() await cur.execute("INSERT INTO test(id) VALUES(1)") + await cur.finish_query() + with pytest.raises(dbapi.IntegrityError): await cur.execute("INSERT INTO test(id) VALUES(1)") @@ -143,10 +158,10 @@ async def connection(self, endpoint, database): [ (dbapi.IsolationLevel.SERIALIZABLE, False), (dbapi.IsolationLevel.AUTOCOMMIT, False), - # (dbapi.IsolationLevel.ONLINE_READONLY, True), - # (dbapi.IsolationLevel.ONLINE_READONLY_INCONSISTENT, True), - # (dbapi.IsolationLevel.STALE_READONLY, True), - # (dbapi.IsolationLevel.SNAPSHOT_READONLY, True), + (dbapi.IsolationLevel.ONLINE_READONLY, True), + (dbapi.IsolationLevel.ONLINE_READONLY_INCONSISTENT, True), + (dbapi.IsolationLevel.STALE_READONLY, True), + (dbapi.IsolationLevel.SNAPSHOT_READONLY, True), ], ) async def test_isolation_level_read_only( diff --git a/ydb_dbapi/connection.py b/ydb_dbapi/connection.py index d4c9c89..395898d 100644 --- a/ydb_dbapi/connection.py +++ b/ydb_dbapi/connection.py @@ -68,6 +68,9 @@ def __init__( self._tx_context: Optional[ydb.QueryTxContext] = None self._tx_mode: ydb.BaseQueryTxMode = ydb.QuerySerializableReadWrite() + self._current_cursor: Optional[Cursor] = None + self.interactive_transaction: bool = False + async def _wait(self, timeout: int = 5): try: await self._driver.wait(timeout, fail_fast=True) @@ -81,15 +84,22 @@ async def _wait(self, timeout: int = 5): ) from e def cursor(self): - return Cursor( - session_pool=self._session_pool, tx_context=self._tx_context + if self._current_cursor and not self._current_cursor._closed: + raise RuntimeError( + "Unable to create new Cursor before closing existing one." + ) + self._current_cursor = Cursor( + session_pool=self._session_pool, + tx_context=self._tx_context, + autocommit=(not self.interactive_transaction), ) + return self._current_cursor async def begin(self): self._tx_context = None self._session = await self._session_pool.acquire() self._tx_context = self._session.transaction(self._tx_mode) - await self._tx_context.begin() + # await self._tx_context.begin() async def commit(self): if self._tx_context and self._tx_context.tx_id: @@ -107,6 +117,10 @@ async def rollback(self): async def close(self): await self.rollback() + + if self._current_cursor: + await self._current_cursor.close() + if not self._shared_session_pool: await self._session_pool.stop() await self._driver.stop() diff --git a/ydb_dbapi/cursors.py b/ydb_dbapi/cursors.py index 779bf0e..9fdf774 100644 --- a/ydb_dbapi/cursors.py +++ b/ydb_dbapi/cursors.py @@ -1,4 +1,3 @@ -import dataclasses import itertools from typing import ( Any, @@ -12,8 +11,8 @@ ) import ydb -from .errors import Error, DatabaseError -from .utils import handle_ydb_errors, AsyncFromSyncIterator +from .errors import Error, DatabaseError, InterfaceError, ProgrammingError +from .utils import handle_ydb_errors, AsyncFromSyncIterator, CursorStatus ParametersType = Dict[ @@ -26,12 +25,6 @@ ] -@dataclasses.dataclass -class YdbQuery: - yql_text: str - is_ddl: bool = False - - def _get_column_type(type_obj: Any) -> str: return str(ydb.convert.type_to_native(type_obj)) @@ -56,6 +49,18 @@ def __init__( self._rows: Optional[Iterator[Dict]] = None self._rows_count: int = -1 + self._closed: bool = False + self._state = CursorStatus.ready + + @property + def description(self): + return self._description + + @property + def rowcount(self): + return self._rows_count + + @handle_ydb_errors async def _execute_generic_query( self, query: str, parameters: Optional[ParametersType] = None ) -> List[ydb.convert.ResultSet]: @@ -63,6 +68,7 @@ async def _execute_generic_query( query=query, parameters=parameters ) + @handle_ydb_errors async def _execute_transactional_query( self, query: str, parameters: Optional[ParametersType] = None ) -> AsyncIterator: @@ -76,10 +82,14 @@ async def _execute_transactional_query( commit_tx=self._autocommit, ) - @handle_ydb_errors async def execute( - self, query: str, parameters: Optional[ParametersType] = None + self, + query: str, + parameters: Optional[ParametersType] = None, + prefetch_first_set: bool = True, ): + self._check_cursor_closed() + self._check_pending_query() if self._tx_context is not None: self._stream = await self._execute_transactional_query( query=query, parameters=parameters @@ -93,8 +103,10 @@ async def execute( if self._stream is None: return - result_set = await self._stream.__anext__() - self._update_result_set(result_set) + self._begin_query() + + if prefetch_first_set: + await self.nextset() def _update_result_set(self, result_set: ydb.convert.ResultSet): self._update_description(result_set) @@ -144,16 +156,33 @@ async def fetchmany(self, size: Optional[int] = None): async def fetchall(self): return list(self._rows or iter([])) or None + @handle_ydb_errors async def nextset(self): if self._stream is None: return False try: result_set = await self._stream.__anext__() self._update_result_set(result_set) - except (StopIteration, RuntimeError): + except (StopIteration, StopAsyncIteration, RuntimeError): + self._state = CursorStatus.finished return False + except ydb.Error as e: + self._state = CursorStatus.finished + raise e return True + async def finish_query(self): + self._check_cursor_closed() + + if not self._state == CursorStatus.running: + return + + next_set_available = True + while next_set_available: + next_set_available = await self.nextset() + + self._state = CursorStatus.finished + def setinputsizes(self): pass @@ -161,14 +190,31 @@ def setoutputsize(self): pass async def close(self): - next_set_available = True - while next_set_available: - next_set_available = await self.nextset() + if self._closed: + return - @property - def description(self): - return self._description + await self.finish_query() + self._state = CursorStatus.closed + self._closed = True - @property - def rowcount(self): - return self._rows_count + def _begin_query(self): + self._state = CursorStatus.running + + def _check_pending_query(self): + if self._state == CursorStatus.running: + raise ProgrammingError( + "Some records have not been fetched. " + "Fetch the remaining records before executing the next query." + ) + + def _check_cursor_closed(self): + if self._state == CursorStatus.closed: + raise InterfaceError( + "Could not perform operation: Cursor is closed." + ) + + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc, tb): + await self.close() diff --git a/ydb_dbapi/utils.py b/ydb_dbapi/utils.py index a11b1cd..7f5ec09 100644 --- a/ydb_dbapi/utils.py +++ b/ydb_dbapi/utils.py @@ -1,3 +1,4 @@ +from enum import Enum import functools from typing import Iterator import ydb @@ -50,6 +51,13 @@ async def wrapper(*args, **kwargs): return wrapper +class CursorStatus(str, Enum): + ready = "ready" + running = "running" + finished = "finished" + closed = "closed" + + class AsyncFromSyncIterator: def __init__(self, sync_iter: Iterator): self._sync_iter = sync_iter From b9cb4ec9b3ddce265eb557c3a89da45db647647e Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:10:11 +0300 Subject: [PATCH 12/33] move docker to testcontainers --- docker-compose.yml | 11 - poetry.lock | 716 +++++++++++++-------------------------- pyproject.toml | 13 +- tests/conftest.py | 149 +++++--- tests/test_connection.py | 5 +- 5 files changed, 346 insertions(+), 548 deletions(-) delete mode 100644 docker-compose.yml diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 1a466fa..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,11 +0,0 @@ -version: "3.3" -services: - ydb: - image: ydbplatform/local-ydb:trunk - restart: always - ports: - - 2136:2136 - hostname: localhost - environment: - - YDB_USE_IN_MEMORY_PDISKS=true - - YDB_ENABLE_COLUMN_TABLES=true diff --git a/poetry.lock b/poetry.lock index 049e777..e54f407 100644 --- a/poetry.lock +++ b/poetry.lock @@ -167,46 +167,6 @@ docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphi tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] -[[package]] -name = "bcrypt" -version = "4.2.0" -description = "Modern password hashing for your software and your servers" -optional = false -python-versions = ">=3.7" -files = [ - {file = "bcrypt-4.2.0-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:096a15d26ed6ce37a14c1ac1e48119660f21b24cba457f160a4b830f3fe6b5cb"}, - {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c02d944ca89d9b1922ceb8a46460dd17df1ba37ab66feac4870f6862a1533c00"}, - {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d84cf6d877918620b687b8fd1bf7781d11e8a0998f576c7aa939776b512b98d"}, - {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1bb429fedbe0249465cdd85a58e8376f31bb315e484f16e68ca4c786dcc04291"}, - {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:655ea221910bcac76ea08aaa76df427ef8625f92e55a8ee44fbf7753dbabb328"}, - {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:1ee38e858bf5d0287c39b7a1fc59eec64bbf880c7d504d3a06a96c16e14058e7"}, - {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:0da52759f7f30e83f1e30a888d9163a81353ef224d82dc58eb5bb52efcabc399"}, - {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3698393a1b1f1fd5714524193849d0c6d524d33523acca37cd28f02899285060"}, - {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:762a2c5fb35f89606a9fde5e51392dad0cd1ab7ae64149a8b935fe8d79dd5ed7"}, - {file = "bcrypt-4.2.0-cp37-abi3-win32.whl", hash = "sha256:5a1e8aa9b28ae28020a3ac4b053117fb51c57a010b9f969603ed885f23841458"}, - {file = "bcrypt-4.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:8f6ede91359e5df88d1f5c1ef47428a4420136f3ce97763e31b86dd8280fbdf5"}, - {file = "bcrypt-4.2.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:c52aac18ea1f4a4f65963ea4f9530c306b56ccd0c6f8c8da0c06976e34a6e841"}, - {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3bbbfb2734f0e4f37c5136130405332640a1e46e6b23e000eeff2ba8d005da68"}, - {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3413bd60460f76097ee2e0a493ccebe4a7601918219c02f503984f0a7ee0aebe"}, - {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8d7bb9c42801035e61c109c345a28ed7e84426ae4865511eb82e913df18f58c2"}, - {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3d3a6d28cb2305b43feac298774b997e372e56c7c7afd90a12b3dc49b189151c"}, - {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:9c1c4ad86351339c5f320ca372dfba6cb6beb25e8efc659bedd918d921956bae"}, - {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:27fe0f57bb5573104b5a6de5e4153c60814c711b29364c10a75a54bb6d7ff48d"}, - {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8ac68872c82f1add6a20bd489870c71b00ebacd2e9134a8aa3f98a0052ab4b0e"}, - {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:cb2a8ec2bc07d3553ccebf0746bbf3d19426d1c6d1adbd4fa48925f66af7b9e8"}, - {file = "bcrypt-4.2.0-cp39-abi3-win32.whl", hash = "sha256:77800b7147c9dc905db1cba26abe31e504d8247ac73580b4aa179f98e6608f34"}, - {file = "bcrypt-4.2.0-cp39-abi3-win_amd64.whl", hash = "sha256:61ed14326ee023917ecd093ee6ef422a72f3aec6f07e21ea5f10622b735538a9"}, - {file = "bcrypt-4.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:39e1d30c7233cfc54f5c3f2c825156fe044efdd3e0b9d309512cc514a263ec2a"}, - {file = "bcrypt-4.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f4f4acf526fcd1c34e7ce851147deedd4e26e6402369304220250598b26448db"}, - {file = "bcrypt-4.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:1ff39b78a52cf03fdf902635e4c81e544714861ba3f0efc56558979dd4f09170"}, - {file = "bcrypt-4.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:373db9abe198e8e2c70d12b479464e0d5092cc122b20ec504097b5f2297ed184"}, - {file = "bcrypt-4.2.0.tar.gz", hash = "sha256:cf69eaf5185fd58f268f805b505ce31f9b9fc2d64b376642164e9244540c1221"}, -] - -[package.extras] -tests = ["pytest (>=3.2.1,!=3.3.0)"] -typecheck = ["mypy"] - [[package]] name = "certifi" version = "2024.8.30" @@ -218,85 +178,6 @@ files = [ {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] -[[package]] -name = "cffi" -version = "1.17.1" -description = "Foreign Function Interface for Python calling C code." -optional = false -python-versions = ">=3.8" -files = [ - {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, - {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, - {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, - {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, - {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, - {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, - {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, - {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, - {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, - {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, - {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, - {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, - {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, - {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, - {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, - {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, -] - -[package.dependencies] -pycparser = "*" - [[package]] name = "cfgv" version = "3.4.0" @@ -433,55 +314,6 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -[[package]] -name = "cryptography" -version = "43.0.3" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -optional = false -python-versions = ">=3.7" -files = [ - {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, - {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, - {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f"}, - {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6"}, - {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18"}, - {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd"}, - {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73"}, - {file = "cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2"}, - {file = "cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd"}, - {file = "cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984"}, - {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5"}, - {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4"}, - {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"}, - {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405"}, - {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16"}, - {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73"}, - {file = "cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995"}, - {file = "cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362"}, - {file = "cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c"}, - {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3"}, - {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83"}, - {file = "cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7"}, - {file = "cryptography-43.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664"}, - {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08"}, - {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa"}, - {file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"}, - {file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"}, -] - -[package.dependencies] -cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} - -[package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] -nox = ["nox"] -pep8test = ["check-sdist", "click", "mypy", "ruff"] -sdist = ["build"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] -test-randomorder = ["pytest-randomly"] - [[package]] name = "distlib" version = "0.3.9" @@ -493,88 +325,27 @@ files = [ {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, ] -[[package]] -name = "distro" -version = "1.9.0" -description = "Distro - an OS platform information API" -optional = false -python-versions = ">=3.6" -files = [ - {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, - {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, -] - [[package]] name = "docker" -version = "5.0.0" +version = "7.1.0" description = "A Python library for the Docker Engine API." optional = false -python-versions = ">=3.6" -files = [ - {file = "docker-5.0.0-py2.py3-none-any.whl", hash = "sha256:fc961d622160e8021c10d1bcabc388c57d55fb1f917175afbe24af442e6879bd"}, - {file = "docker-5.0.0.tar.gz", hash = "sha256:3e8bc47534e0ca9331d72c32f2881bb13b93ded0bcdeab3c833fb7cf61c0a9a5"}, -] - -[package.dependencies] -paramiko = {version = ">=2.4.2", optional = true, markers = "extra == \"ssh\""} -pywin32 = {version = "227", markers = "sys_platform == \"win32\""} -requests = ">=2.14.2,<2.18.0 || >2.18.0" -websocket-client = ">=0.32.0" - -[package.extras] -ssh = ["paramiko (>=2.4.2)"] -tls = ["cryptography (>=3.4.7)", "idna (>=2.0.0)", "pyOpenSSL (>=17.5.0)"] - -[[package]] -name = "docker-compose" -version = "1.29.2" -description = "Multi-container orchestration for Docker" -optional = false -python-versions = ">=3.4" +python-versions = ">=3.8" files = [ - {file = "docker-compose-1.29.2.tar.gz", hash = "sha256:4c8cd9d21d237412793d18bd33110049ee9af8dab3fe2c213bbd0733959b09b7"}, - {file = "docker_compose-1.29.2-py2.py3-none-any.whl", hash = "sha256:8d5589373b35c8d3b1c8c1182c6e4a4ff14bffa3dd0b605fcd08f73c94cef809"}, + {file = "docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0"}, + {file = "docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c"}, ] [package.dependencies] -colorama = {version = ">=0.4,<1", markers = "sys_platform == \"win32\""} -distro = ">=1.5.0,<2" -docker = {version = ">=5", extras = ["ssh"]} -dockerpty = ">=0.4.1,<1" -docopt = ">=0.6.1,<1" -jsonschema = ">=2.5.1,<4" -python-dotenv = ">=0.13.0,<1" -PyYAML = ">=3.10,<6" -requests = ">=2.20.0,<3" -texttable = ">=0.9.0,<2" -websocket-client = ">=0.32.0,<1" +pywin32 = {version = ">=304", markers = "sys_platform == \"win32\""} +requests = ">=2.26.0" +urllib3 = ">=1.26.0" [package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2)"] -tests = ["ddt (>=1.2.2,<2)", "pytest (<6)"] - -[[package]] -name = "dockerpty" -version = "0.4.1" -description = "Python library to use the pseudo-tty of a docker container" -optional = false -python-versions = "*" -files = [ - {file = "dockerpty-0.4.1.tar.gz", hash = "sha256:69a9d69d573a0daa31bcd1c0774eeed5c15c295fe719c61aca550ed1393156ce"}, -] - -[package.dependencies] -six = ">=1.3.0" - -[[package]] -name = "docopt" -version = "0.6.2" -description = "Pythonic argument parser, that will make you smile" -optional = false -python-versions = "*" -files = [ - {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, -] +dev = ["coverage (==7.2.7)", "pytest (==7.4.2)", "pytest-cov (==4.1.0)", "pytest-timeout (==2.1.0)", "ruff (==0.1.8)"] +docs = ["myst-parser (==0.18.0)", "sphinx (==5.1.1)"] +ssh = ["paramiko (>=2.4.3)"] +websockets = ["websocket-client (>=1.3.0)"] [[package]] name = "exceptiongroup" @@ -798,27 +569,6 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] -[[package]] -name = "jsonschema" -version = "3.2.0" -description = "An implementation of JSON Schema validation for Python" -optional = false -python-versions = "*" -files = [ - {file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"}, - {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"}, -] - -[package.dependencies] -attrs = ">=17.4.0" -pyrsistent = ">=0.14.0" -setuptools = "*" -six = ">=1.11.0" - -[package.extras] -format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"] -format-nongpl = ["idna", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "webcolors"] - [[package]] name = "multidict" version = "6.1.0" @@ -1008,27 +758,6 @@ files = [ {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] -[[package]] -name = "paramiko" -version = "3.5.0" -description = "SSH2 protocol library" -optional = false -python-versions = ">=3.6" -files = [ - {file = "paramiko-3.5.0-py3-none-any.whl", hash = "sha256:1fedf06b085359051cd7d0d270cebe19e755a8a921cc2ddbfa647fb0cd7d68f9"}, - {file = "paramiko-3.5.0.tar.gz", hash = "sha256:ad11e540da4f55cedda52931f1a3f812a8238a7af7f62a60de538cd80bb28124"}, -] - -[package.dependencies] -bcrypt = ">=3.2" -cryptography = ">=3.3" -pynacl = ">=1.5" - -[package.extras] -all = ["gssapi (>=1.4.1)", "invoke (>=2.0)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] -gssapi = ["gssapi (>=1.4.1)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] -invoke = ["invoke (>=2.0)"] - [[package]] name = "pastel" version = "0.2.1" @@ -1234,93 +963,15 @@ files = [ {file = "protobuf-4.25.5.tar.gz", hash = "sha256:7f8249476b4a9473645db7f8ab42b02fe1488cbe5fb72fddd445e0665afd8584"}, ] -[[package]] -name = "pycparser" -version = "2.22" -description = "C parser in Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, - {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, -] - -[[package]] -name = "pynacl" -version = "1.5.0" -description = "Python binding to the Networking and Cryptography (NaCl) library" -optional = false -python-versions = ">=3.6" -files = [ - {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, - {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, - {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, - {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, - {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, - {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, -] - -[package.dependencies] -cffi = ">=1.4.1" - -[package.extras] -docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] -tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] - -[[package]] -name = "pyrsistent" -version = "0.20.0" -description = "Persistent/Functional/Immutable data structures" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyrsistent-0.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c3aba3e01235221e5b229a6c05f585f344734bd1ad42a8ac51493d74722bbce"}, - {file = "pyrsistent-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1beb78af5423b879edaf23c5591ff292cf7c33979734c99aa66d5914ead880f"}, - {file = "pyrsistent-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21cc459636983764e692b9eba7144cdd54fdec23ccdb1e8ba392a63666c60c34"}, - {file = "pyrsistent-0.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5ac696f02b3fc01a710427585c855f65cd9c640e14f52abe52020722bb4906b"}, - {file = "pyrsistent-0.20.0-cp310-cp310-win32.whl", hash = "sha256:0724c506cd8b63c69c7f883cc233aac948c1ea946ea95996ad8b1380c25e1d3f"}, - {file = "pyrsistent-0.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:8441cf9616d642c475684d6cf2520dd24812e996ba9af15e606df5f6fd9d04a7"}, - {file = "pyrsistent-0.20.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0f3b1bcaa1f0629c978b355a7c37acd58907390149b7311b5db1b37648eb6958"}, - {file = "pyrsistent-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cdd7ef1ea7a491ae70d826b6cc64868de09a1d5ff9ef8d574250d0940e275b8"}, - {file = "pyrsistent-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cae40a9e3ce178415040a0383f00e8d68b569e97f31928a3a8ad37e3fde6df6a"}, - {file = "pyrsistent-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6288b3fa6622ad8a91e6eb759cfc48ff3089e7c17fb1d4c59a919769314af224"}, - {file = "pyrsistent-0.20.0-cp311-cp311-win32.whl", hash = "sha256:7d29c23bdf6e5438c755b941cef867ec2a4a172ceb9f50553b6ed70d50dfd656"}, - {file = "pyrsistent-0.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:59a89bccd615551391f3237e00006a26bcf98a4d18623a19909a2c48b8e986ee"}, - {file = "pyrsistent-0.20.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:09848306523a3aba463c4b49493a760e7a6ca52e4826aa100ee99d8d39b7ad1e"}, - {file = "pyrsistent-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a14798c3005ec892bbada26485c2eea3b54109cb2533713e355c806891f63c5e"}, - {file = "pyrsistent-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b14decb628fac50db5e02ee5a35a9c0772d20277824cfe845c8a8b717c15daa3"}, - {file = "pyrsistent-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e2c116cc804d9b09ce9814d17df5edf1df0c624aba3b43bc1ad90411487036d"}, - {file = "pyrsistent-0.20.0-cp312-cp312-win32.whl", hash = "sha256:e78d0c7c1e99a4a45c99143900ea0546025e41bb59ebc10182e947cf1ece9174"}, - {file = "pyrsistent-0.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:4021a7f963d88ccd15b523787d18ed5e5269ce57aa4037146a2377ff607ae87d"}, - {file = "pyrsistent-0.20.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:79ed12ba79935adaac1664fd7e0e585a22caa539dfc9b7c7c6d5ebf91fb89054"}, - {file = "pyrsistent-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f920385a11207dc372a028b3f1e1038bb244b3ec38d448e6d8e43c6b3ba20e98"}, - {file = "pyrsistent-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f5c2d012671b7391803263419e31b5c7c21e7c95c8760d7fc35602353dee714"}, - {file = "pyrsistent-0.20.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef3992833fbd686ee783590639f4b8343a57f1f75de8633749d984dc0eb16c86"}, - {file = "pyrsistent-0.20.0-cp38-cp38-win32.whl", hash = "sha256:881bbea27bbd32d37eb24dd320a5e745a2a5b092a17f6debc1349252fac85423"}, - {file = "pyrsistent-0.20.0-cp38-cp38-win_amd64.whl", hash = "sha256:6d270ec9dd33cdb13f4d62c95c1a5a50e6b7cdd86302b494217137f760495b9d"}, - {file = "pyrsistent-0.20.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ca52d1ceae015859d16aded12584c59eb3825f7b50c6cfd621d4231a6cc624ce"}, - {file = "pyrsistent-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b318ca24db0f0518630e8b6f3831e9cba78f099ed5c1d65ffe3e023003043ba0"}, - {file = "pyrsistent-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fed2c3216a605dc9a6ea50c7e84c82906e3684c4e80d2908208f662a6cbf9022"}, - {file = "pyrsistent-0.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e14c95c16211d166f59c6611533d0dacce2e25de0f76e4c140fde250997b3ca"}, - {file = "pyrsistent-0.20.0-cp39-cp39-win32.whl", hash = "sha256:f058a615031eea4ef94ead6456f5ec2026c19fb5bd6bfe86e9665c4158cf802f"}, - {file = "pyrsistent-0.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:58b8f6366e152092194ae68fefe18b9f0b4f89227dfd86a07770c3d86097aebf"}, - {file = "pyrsistent-0.20.0-py3-none-any.whl", hash = "sha256:c55acc4733aad6560a7f5f818466631f07efc001fd023f34a6c203f8b6df0f0b"}, - {file = "pyrsistent-0.20.0.tar.gz", hash = "sha256:4c48f78f62ab596c679086084d0dd13254ae4f3d6c72a83ffdf5ebdef8f265a4"}, -] - [[package]] name = "pytest" -version = "7.4.4" +version = "8.3.3" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, ] [package.dependencies] @@ -1328,111 +979,128 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-asyncio" -version = "0.21.0" +version = "0.24.0" description = "Pytest support for asyncio" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pytest-asyncio-0.21.0.tar.gz", hash = "sha256:2b38a496aef56f56b0e87557ec313e11e1ab9276fc3863f6a7be0f1d0e415e1b"}, - {file = "pytest_asyncio-0.21.0-py3-none-any.whl", hash = "sha256:f2b3366b7cd501a4056858bd39349d5af19742aed2d81660b7998b6341c7eb9c"}, + {file = "pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b"}, + {file = "pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276"}, ] [package.dependencies] -pytest = ">=7.0.0" +pytest = ">=8.2,<9" [package.extras] docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] -testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] - -[[package]] -name = "pytest-docker-compose" -version = "3.2.1" -description = "Manages Docker containers during your integration tests" -optional = false -python-versions = "*" -files = [ - {file = "pytest-docker-compose-3.2.1.tar.gz", hash = "sha256:bb58f1915688e71232ae86f2c3c8348b10afb8af0f9800f7c68a75cda77c9b48"}, - {file = "pytest_docker_compose-3.2.1-py3-none-any.whl", hash = "sha256:5d9dd78138fb03fa4e8c742cd4104f72c5aa9579e0d31cb6f7a0751db4a6f698"}, -] - -[package.dependencies] -docker-compose = "*" -pytest = ">=3.3" - -[[package]] -name = "python-dotenv" -version = "0.21.1" -description = "Read key-value pairs from a .env file and set them as environment variables" -optional = false -python-versions = ">=3.7" -files = [ - {file = "python-dotenv-0.21.1.tar.gz", hash = "sha256:1c93de8f636cde3ce377292818d0e440b6e45a82f215c3744979151fa8151c49"}, - {file = "python_dotenv-0.21.1-py3-none-any.whl", hash = "sha256:41e12e0318bebc859fcc4d97d4db8d20ad21721a6aa5047dd59f090391cb549a"}, -] - -[package.extras] -cli = ["click (>=5.0)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] [[package]] name = "pywin32" -version = "227" +version = "308" description = "Python for Window Extensions" optional = false python-versions = "*" files = [ - {file = "pywin32-227-cp27-cp27m-win32.whl", hash = "sha256:371fcc39416d736401f0274dd64c2302728c9e034808e37381b5e1b22be4a6b0"}, - {file = "pywin32-227-cp27-cp27m-win_amd64.whl", hash = "sha256:4cdad3e84191194ea6d0dd1b1b9bdda574ff563177d2adf2b4efec2a244fa116"}, - {file = "pywin32-227-cp35-cp35m-win32.whl", hash = "sha256:f4c5be1a293bae0076d93c88f37ee8da68136744588bc5e2be2f299a34ceb7aa"}, - {file = "pywin32-227-cp35-cp35m-win_amd64.whl", hash = "sha256:a929a4af626e530383a579431b70e512e736e9588106715215bf685a3ea508d4"}, - {file = "pywin32-227-cp36-cp36m-win32.whl", hash = "sha256:300a2db938e98c3e7e2093e4491439e62287d0d493fe07cce110db070b54c0be"}, - {file = "pywin32-227-cp36-cp36m-win_amd64.whl", hash = "sha256:9b31e009564fb95db160f154e2aa195ed66bcc4c058ed72850d047141b36f3a2"}, - {file = "pywin32-227-cp37-cp37m-win32.whl", hash = "sha256:47a3c7551376a865dd8d095a98deba954a98f326c6fe3c72d8726ca6e6b15507"}, - {file = "pywin32-227-cp37-cp37m-win_amd64.whl", hash = "sha256:31f88a89139cb2adc40f8f0e65ee56a8c585f629974f9e07622ba80199057511"}, - {file = "pywin32-227-cp38-cp38-win32.whl", hash = "sha256:7f18199fbf29ca99dff10e1f09451582ae9e372a892ff03a28528a24d55875bc"}, - {file = "pywin32-227-cp38-cp38-win_amd64.whl", hash = "sha256:7c1ae32c489dc012930787f06244426f8356e129184a02c25aef163917ce158e"}, - {file = "pywin32-227-cp39-cp39-win32.whl", hash = "sha256:c054c52ba46e7eb6b7d7dfae4dbd987a1bb48ee86debe3f245a2884ece46e295"}, - {file = "pywin32-227-cp39-cp39-win_amd64.whl", hash = "sha256:f27cec5e7f588c3d1051651830ecc00294f90728d19c3bf6916e6dba93ea357c"}, + {file = "pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e"}, + {file = "pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e"}, + {file = "pywin32-308-cp310-cp310-win_arm64.whl", hash = "sha256:a5ab5381813b40f264fa3495b98af850098f814a25a63589a8e9eb12560f450c"}, + {file = "pywin32-308-cp311-cp311-win32.whl", hash = "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a"}, + {file = "pywin32-308-cp311-cp311-win_amd64.whl", hash = "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b"}, + {file = "pywin32-308-cp311-cp311-win_arm64.whl", hash = "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6"}, + {file = "pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897"}, + {file = "pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47"}, + {file = "pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091"}, + {file = "pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed"}, + {file = "pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4"}, + {file = "pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd"}, + {file = "pywin32-308-cp37-cp37m-win32.whl", hash = "sha256:1f696ab352a2ddd63bd07430080dd598e6369152ea13a25ebcdd2f503a38f1ff"}, + {file = "pywin32-308-cp37-cp37m-win_amd64.whl", hash = "sha256:13dcb914ed4347019fbec6697a01a0aec61019c1046c2b905410d197856326a6"}, + {file = "pywin32-308-cp38-cp38-win32.whl", hash = "sha256:5794e764ebcabf4ff08c555b31bd348c9025929371763b2183172ff4708152f0"}, + {file = "pywin32-308-cp38-cp38-win_amd64.whl", hash = "sha256:3b92622e29d651c6b783e368ba7d6722b1634b8e70bd376fd7610fe1992e19de"}, + {file = "pywin32-308-cp39-cp39-win32.whl", hash = "sha256:7873ca4dc60ab3287919881a7d4f88baee4a6e639aa6962de25a98ba6b193341"}, + {file = "pywin32-308-cp39-cp39-win_amd64.whl", hash = "sha256:71b3322d949b4cc20776436a9c9ba0eeedcbc9c650daa536df63f0ff111bb920"}, ] [[package]] name = "pyyaml" -version = "5.3.1" +version = "6.0.2" description = "YAML parser and emitter for Python" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, - {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"}, - {file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"}, - {file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"}, - {file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"}, - {file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"}, - {file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"}, - {file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"}, - {file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"}, - {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"}, - {file = "PyYAML-5.3.1-cp39-cp39-win32.whl", hash = "sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a"}, - {file = "PyYAML-5.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e"}, - {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] [[package]] name = "requests" -version = "2.31.0" +version = "2.32.3" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] @@ -1473,46 +1141,56 @@ files = [ ] [[package]] -name = "setuptools" -version = "75.2.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" +name = "testcontainers" +version = "4.8.2" +description = "Python library for throwaway instances of anything that can run in a Docker container" optional = false -python-versions = ">=3.8" +python-versions = "<4.0,>=3.9" files = [ - {file = "setuptools-75.2.0-py3-none-any.whl", hash = "sha256:a7fcb66f68b4d9e8e66b42f9876150a3371558f98fa32222ffaa5bced76406f8"}, - {file = "setuptools-75.2.0.tar.gz", hash = "sha256:753bb6ebf1f465a1912e19ed1d41f403a79173a9acf66a42e7e6aec45c3c16ec"}, + {file = "testcontainers-4.8.2-py3-none-any.whl", hash = "sha256:9e19af077cd96e1957c13ee466f1f32905bc6c5bc1bc98643eb18be1a989bfb0"}, + {file = "testcontainers-4.8.2.tar.gz", hash = "sha256:dd4a6a2ea09e3c3ecd39e180b6548105929d0bb78d665ce9919cb3f8c98f9853"}, ] -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] -core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.11.*)", "pytest-mypy"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] +[package.dependencies] +docker = "*" +typing-extensions = "*" +urllib3 = "*" +wrapt = "*" -[[package]] -name = "texttable" -version = "1.7.0" -description = "module to create simple ASCII tables" -optional = false -python-versions = "*" -files = [ - {file = "texttable-1.7.0-py2.py3-none-any.whl", hash = "sha256:72227d592c82b3d7f672731ae73e4d1f88cd8e2ef5b075a7a7f01a23a3743917"}, - {file = "texttable-1.7.0.tar.gz", hash = "sha256:2d2068fb55115807d3ac77a4ca68fa48803e84ebb0ee2340f858107a36522638"}, -] +[package.extras] +arangodb = ["python-arango (>=7.8,<8.0)"] +aws = ["boto3", "httpx"] +azurite = ["azure-storage-blob (>=12.19,<13.0)"] +chroma = ["chromadb-client"] +clickhouse = ["clickhouse-driver"] +cosmosdb = ["azure-cosmos"] +db2 = ["ibm_db_sa", "sqlalchemy"] +generic = ["httpx", "redis"] +google = ["google-cloud-datastore (>=2)", "google-cloud-pubsub (>=2)"] +influxdb = ["influxdb", "influxdb-client"] +k3s = ["kubernetes", "pyyaml"] +keycloak = ["python-keycloak"] +localstack = ["boto3"] +mailpit = ["cryptography"] +minio = ["minio"] +mongodb = ["pymongo"] +mssql = ["pymssql", "sqlalchemy"] +mysql = ["pymysql[rsa]", "sqlalchemy"] +nats = ["nats-py"] +neo4j = ["neo4j"] +opensearch = ["opensearch-py"] +oracle = ["oracledb", "sqlalchemy"] +oracle-free = ["oracledb", "sqlalchemy"] +qdrant = ["qdrant-client"] +rabbitmq = ["pika"] +redis = ["redis"] +registry = ["bcrypt"] +scylla = ["cassandra-driver (==3.29.1)"] +selenium = ["selenium"] +sftp = ["cryptography"] +test-module-import = ["httpx"] +trino = ["trino"] +weaviate = ["weaviate-client (>=4.5.4,<5.0.0)"] [[package]] name = "tomli" @@ -1549,19 +1227,20 @@ files = [ [[package]] name = "urllib3" -version = "1.26.6" +version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +python-versions = ">=3.8" files = [ - {file = "urllib3-1.26.6-py2.py3-none-any.whl", hash = "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4"}, - {file = "urllib3-1.26.6.tar.gz", hash = "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"}, + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, ] [package.extras] -brotli = ["brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" @@ -1584,19 +1263,84 @@ docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "s test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] -name = "websocket-client" -version = "0.59.0" -description = "WebSocket client for Python with low level API options" +name = "wrapt" +version = "1.16.0" +description = "Module for decorators, wrappers and monkey patching." optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" files = [ - {file = "websocket-client-0.59.0.tar.gz", hash = "sha256:d376bd60eace9d437ab6d7ee16f4ab4e821c9dae591e1b783c58ebd8aaf80c5c"}, - {file = "websocket_client-0.59.0-py2.py3-none-any.whl", hash = "sha256:2e50d26ca593f70aba7b13a489435ef88b8fc3b5c5643c1ce8808ff9b40f0b32"}, + {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, + {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, + {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, + {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, + {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, + {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, + {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, + {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, + {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, + {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, + {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, + {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, + {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, + {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, + {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, + {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, + {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, + {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, ] -[package.dependencies] -six = "*" - [[package]] name = "yarl" version = "1.16.0" @@ -1716,4 +1460,4 @@ yc = ["yandexcloud"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "35fc7529574aa99a2c5fb27165f5ad937bb7b2978b834a761068d0a4a6fb39f4" +content-hash = "c5df94291a23b3f5f18bf6524e801d9c933c2125bfc3dc1eac40f0fac062101a" diff --git a/pyproject.toml b/pyproject.toml index 438015f..be544fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,14 +16,9 @@ ruff = "^0.6.9" mypy = "^1.11.2" poethepoet = "0.28.0" types-protobuf = "^5.28.0.20240924" -PyYAML = "~5.3.0" -pytest = "^7.0.0" -pytest-asyncio = "0.21.0" -pytest-docker-compose = "3.2.1" -docker-compose = "1.29.2" -docker = "5.0.0" -requests = "2.31.0" -urllib3 = "1.26.6" +testcontainers = "^4.8.2" +pytest = "^8.3.3" +pytest-asyncio = "^0.24.0" [build-system] requires = ["poetry-core"] @@ -36,7 +31,7 @@ ruff = "ruff check" lint = ["mypy", "ruff"] format-check = "ruff format --check" format = "ruff format" -tests = "pytest -v --docker-compose-remove-volumes --docker-compose=docker-compose.yml" +tests = "pytest" [tool.ruff] exclude = [".venv", ".git", "__pycache__", "build", "dist", "venv"] diff --git a/tests/conftest.py b/tests/conftest.py index 4bba994..9963a02 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,56 +1,127 @@ -import os import pytest -import time import ydb - -@pytest.fixture(scope="module") -def docker_compose_file(pytestconfig): - return os.path.join(str(pytestconfig.rootdir), "docker-compose.yml") - - -def wait_container_ready(driver): - driver.wait(timeout=30) - - with ydb.SessionPool(driver) as pool: - started_at = time.time() - while time.time() - started_at < 30: +from typing import ( + Any, + Callable, + Generator, + Optional, +) + +from testcontainers.core.generic import DbContainer +from testcontainers.core.generic import wait_container_is_ready +from testcontainers.core.utils import setup_logger + +logger = setup_logger(__name__) + + +class YDBContainer(DbContainer): + def __init__( + self, + name: Optional[str] = None, + port: str = "2135", + image: str = "ydbplatform/local-ydb:trunk", + **kwargs: Any, + ) -> None: + docker_client_kw: dict[str, Any] = kwargs.pop("docker_client_kw", {}) + docker_client_kw["timeout"] = docker_client_kw.get("timeout") or 300 + super().__init__( + image=image, + hostname="localhost", + docker_client_kw=docker_client_kw, + **kwargs, + ) + self.port_to_expose = port + self._name = name + self._database_name = "local" + + def start(self): + self._maybe_stop_old_container() + super().start() + return self + + def get_connection_url(self, driver: str = "ydb") -> str: + host = self.get_container_host_ip() + port = self.get_exposed_port(self.port_to_expose) + return f"yql+{driver}://{host}:{port}/local" + + def get_connection_string(self) -> str: + host = self.get_container_host_ip() + port = self.get_exposed_port(self.port_to_expose) + return f"grpc://{host}:{port}/?database=/local" + + def get_ydb_database_name(self) -> str: + return self._database_name + + def get_ydb_host(self) -> str: + return self.get_container_host_ip() + + def get_ydb_port(self) -> str: + return self.get_exposed_port(self.port_to_expose) + + @wait_container_is_ready(ydb.ConnectionError) + def _connect(self): + with ydb.Driver( + connection_string=self.get_connection_string() + ) as driver: + driver.wait(fail_fast=True) try: - with pool.checkout() as session: - session.execute_scheme( - "create table `.sys_health/test_table` " - "(A int32, primary key(A));" - ) - - return True + driver.scheme_client.describe_path("/local/.sys_health/test") + except ydb.SchemeError as e: + msg = "Database is not ready" + raise ydb.ConnectionError(msg) from e + + def _configure(self): + self.with_bind_ports(self.port_to_expose, self.port_to_expose) + if self._name: + self.with_name(self._name) + self.with_env("YDB_USE_IN_MEMORY_PDISKS", "true") + self.with_env("YDB_DEFAULT_LOG_LEVEL", "DEBUG") + self.with_env("GRPC_PORT", self.port_to_expose) + self.with_env("GRPC_TLS_PORT", self.port_to_expose) + + def _maybe_stop_old_container(self): + if not self._name: + return + docker_client = self.get_docker_client() + running_container = docker_client.client.api.containers( + filters={"name": self._name} + ) + if running_container: + logger.info("Stop existing container") + docker_client.client.api.remove_container( + running_container[0], force=True, v=True + ) - except ydb.Error: - time.sleep(1) - raise RuntimeError("Container is not ready after timeout.") +@pytest.fixture(scope="session") +def ydb_container( + unused_tcp_port_factory: Callable[[], int], +) -> Generator[YDBContainer, None, None]: + with YDBContainer(port=str(unused_tcp_port_factory())) as ydb_container: + yield ydb_container -@pytest.fixture(scope="module") -def endpoint(pytestconfig, module_scoped_container_getter): - with ydb.Driver(endpoint="localhost:2136", database="/local") as driver: - wait_container_ready(driver) - yield "localhost:2136" +@pytest.fixture +def connection_string(ydb_container: YDBContainer) -> str: + return ydb_container.get_connection_string() -@pytest.fixture(scope="module") -def database(): - return "/local" +@pytest.fixture +def connection_kwargs(ydb_container: YDBContainer) -> dict: + return { + "host": ydb_container.get_ydb_host(), + "port": ydb_container.get_ydb_port(), + "database": ydb_container.get_ydb_database_name(), + } @pytest.fixture() -async def driver(endpoint, database, event_loop): - driver_config = ydb.DriverConfig( - endpoint, - database, +async def driver(ydb_container, event_loop): + driver = ydb.aio.Driver( + connection_string=ydb_container.get_connection_string() ) - - driver = ydb.aio.Driver(driver_config=driver_config) - await driver.wait(timeout=15) + await driver.wait(timeout=15, fail_fast=True) yield driver diff --git a/tests/test_connection.py b/tests/test_connection.py index fe98fe6..59a2cc5 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -144,9 +144,8 @@ async def _test_errors(self, connection: dbapi.Connection): class TestAsyncConnection(BaseDBApiTestSuit): @pytest_asyncio.fixture - async def connection(self, endpoint, database): - host, port = endpoint.split(":") - conn = await dbapi.connect(host=host, port=port, database=database) + async def connection(self, connection_kwargs): + conn = await dbapi.connect(**connection_kwargs) try: yield conn finally: From b82c3442c08b37ffe72eb50599e1e308835b02ba Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:10:11 +0300 Subject: [PATCH 13/33] refactor connection --- pyproject.toml | 35 ++++++- tests/conftest.py | 32 ++++--- tests/test_connection.py | 14 ++- tests/test_cursor.py | 32 +++---- ydb_dbapi/__init__.py | 8 +- ydb_dbapi/connection.py | 200 +++++++++++++++++++++------------------ ydb_dbapi/cursors.py | 97 ++++++++++--------- ydb_dbapi/errors.py | 12 +-- ydb_dbapi/utils.py | 31 ++---- 9 files changed, 250 insertions(+), 211 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index be544fe..81d0db9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,40 @@ target-version = "py39" src = ["ydb_dbapi", "tests"] [tool.ruff.lint] -select = ["F", "E"] +ignore = [ + "D", # Allow not to have docstring for each method + "ANN101", # Allow not to specify `Self` type for `self` param + "ANN102", # Allow not to specify `Class` type for `cls` param + "ANN401", # Allow to use Any type + "FIX002", # Allow to use TODO in code + "TD001", # Allow to specify FIXME (still trigger FIX001) + "TD002", # Allow not to specify author of todo + "TD003", # Allow not to specify ticket of todo (it doesn't work) + "PERF401", # Allow not to use list comprehension each time + "FBT", # Allow boolean positional arguments + "TCH002", # Allow not to use `if TYPE_CHECKING` for all imports, which don't used explicitly + "TCH003", # Allow not to use `if TYPE_CHECKING` for std lib imports + "ISC001", # Conflicts with formatter + "COM812", # Conflicts with formatter, + # Ignores below could be deleted + "EM101", # Allow to use string literals in exceptions + "TRY003", # Allow specifying long messages outside the exception class +] +select = ["ALL"] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = ["B"] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[tool.ruff.lint.isort] +force-single-line = true + +[tool.ruff.lint.per-file-ignores] +"**/test_*.py" = ["S", "SLF", "ANN201", "ARG", "PLR2004"] +"__init__.py" = ["F401", "F403"] [tool.pytest.ini_options] asyncio_mode = "auto" diff --git a/tests/conftest.py b/tests/conftest.py index 9963a02..07e8dab 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,13 +1,11 @@ -import pytest -import ydb +from __future__ import annotations -from typing import ( - Any, - Callable, - Generator, - Optional, -) +from collections.abc import Generator +from typing import Any +from typing import Callable +import pytest +import ydb from testcontainers.core.generic import DbContainer from testcontainers.core.generic import wait_container_is_ready from testcontainers.core.utils import setup_logger @@ -18,7 +16,7 @@ class YDBContainer(DbContainer): def __init__( self, - name: Optional[str] = None, + name: str | None = None, port: str = "2135", image: str = "ydbplatform/local-ydb:trunk", **kwargs: Any, @@ -60,7 +58,7 @@ def get_ydb_port(self) -> str: return self.get_exposed_port(self.port_to_expose) @wait_container_is_ready(ydb.ConnectionError) - def _connect(self): + def _connect(self) -> None: with ydb.Driver( connection_string=self.get_connection_string() ) as driver: @@ -71,7 +69,7 @@ def _connect(self): msg = "Database is not ready" raise ydb.ConnectionError(msg) from e - def _configure(self): + def _configure(self) -> None: self.with_bind_ports(self.port_to_expose, self.port_to_expose) if self._name: self.with_name(self._name) @@ -80,7 +78,7 @@ def _configure(self): self.with_env("GRPC_PORT", self.port_to_expose) self.with_env("GRPC_TLS_PORT", self.port_to_expose) - def _maybe_stop_old_container(self): + def _maybe_stop_old_container(self) -> None: if not self._name: return docker_client = self.get_docker_client() @@ -116,7 +114,7 @@ def connection_kwargs(ydb_container: YDBContainer) -> dict: } -@pytest.fixture() +@pytest.fixture async def driver(ydb_container, event_loop): driver = ydb.aio.Driver( connection_string=ydb_container.get_connection_string() @@ -147,3 +145,11 @@ async def session_pool(driver: ydb.aio.Driver): ) yield session_pool + +@pytest.fixture +async def session(session_pool: ydb.aio.QuerySessionPool): + session = await session_pool.acquire() + + yield session + + await session_pool.release(session) diff --git a/tests/test_connection.py b/tests/test_connection.py index 59a2cc5..4a683d6 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -3,7 +3,6 @@ import pytest import pytest_asyncio import ydb - import ydb_dbapi as dbapi @@ -13,7 +12,7 @@ async def _test_isolation_level_read_only( connection: dbapi.Connection, isolation_level: str, read_only: bool, - ): + ) -> None: async with connection.cursor() as cursor: with suppress(dbapi.DatabaseError): await cursor.execute("DROP TABLE foo") @@ -25,14 +24,13 @@ async def _test_isolation_level_read_only( connection.set_isolation_level(isolation_level) - await connection.begin() - async with connection.cursor() as cursor: query = "UPSERT INTO foo(id) VALUES (1)" if read_only: with pytest.raises(dbapi.DatabaseError): await cursor.execute(query) await cursor.finish_query() + else: await cursor.execute(query) @@ -41,7 +39,7 @@ async def _test_isolation_level_read_only( async with connection.cursor() as cursor: cursor.execute("DROP TABLE foo") - async def _test_connection(self, connection: dbapi.Connection): + async def _test_connection(self, connection: dbapi.Connection) -> None: await connection.commit() await connection.rollback() @@ -68,7 +66,7 @@ async def _test_connection(self, connection: dbapi.Connection): await cur.execute("DROP TABLE foo") await cur.close() - async def _test_cursor_raw_query(self, connection: dbapi.Connection): + async def _test_cursor_raw_query(self, connection: dbapi.Connection) -> None: cur = connection.cursor() assert cur @@ -107,7 +105,7 @@ async def _test_cursor_raw_query(self, connection: dbapi.Connection): await cur.close() - async def _test_errors(self, connection: dbapi.Connection): + async def _test_errors(self, connection: dbapi.Connection) -> None: with pytest.raises(dbapi.InterfaceError): await dbapi.connect("localhost:2136", database="/local666") @@ -153,7 +151,7 @@ async def connection(self, connection_kwargs): @pytest.mark.asyncio @pytest.mark.parametrize( - "isolation_level, read_only", + ("isolation_level", "read_only"), [ (dbapi.IsolationLevel.SERIALIZABLE, False), (dbapi.IsolationLevel.AUTOCOMMIT, False), diff --git a/tests/test_cursor.py b/tests/test_cursor.py index 3ea2004..2ddd8b2 100644 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -3,8 +3,8 @@ @pytest.mark.asyncio -async def test_cursor_ddl(session_pool): - cursor = ydb_dbapi.Cursor(session_pool=session_pool) +async def test_cursor_ddl(session): + cursor = ydb_dbapi.Cursor(session=session) yql = """ CREATE TABLE table ( @@ -27,8 +27,8 @@ async def test_cursor_ddl(session_pool): @pytest.mark.asyncio -async def test_cursor_dml(session_pool): - cursor = ydb_dbapi.Cursor(session_pool=session_pool) +async def test_cursor_dml(session): + cursor = ydb_dbapi.Cursor(session=session) yql_text = """ INSERT INTO table (id, val) VALUES (1, 1), @@ -39,7 +39,7 @@ async def test_cursor_dml(session_pool): await cursor.execute(query=yql_text) assert await cursor.fetchone() is None - cursor = ydb_dbapi.Cursor(session_pool=session_pool) + cursor = ydb_dbapi.Cursor(session=session) yql_text = """ SELECT COUNT(*) FROM table as sum @@ -53,8 +53,8 @@ async def test_cursor_dml(session_pool): @pytest.mark.asyncio -async def test_cursor_fetch_one(session_pool): - cursor = ydb_dbapi.Cursor(session_pool=session_pool) +async def test_cursor_fetch_one(session): + cursor = ydb_dbapi.Cursor(session=session) yql_text = """ INSERT INTO table (id, val) VALUES (1, 1), @@ -64,7 +64,7 @@ async def test_cursor_fetch_one(session_pool): await cursor.execute(query=yql_text) assert await cursor.fetchone() is None - cursor = ydb_dbapi.Cursor(session_pool=session_pool) + cursor = ydb_dbapi.Cursor(session=session) yql_text = """ SELECT id, val FROM table @@ -82,8 +82,8 @@ async def test_cursor_fetch_one(session_pool): @pytest.mark.asyncio -async def test_cursor_fetch_many(session_pool): - cursor = ydb_dbapi.Cursor(session_pool=session_pool) +async def test_cursor_fetch_many(session): + cursor = ydb_dbapi.Cursor(session=session) yql_text = """ INSERT INTO table (id, val) VALUES (1, 1), @@ -95,7 +95,7 @@ async def test_cursor_fetch_many(session_pool): await cursor.execute(query=yql_text) assert await cursor.fetchone() is None - cursor = ydb_dbapi.Cursor(session_pool=session_pool) + cursor = ydb_dbapi.Cursor(session=session) yql_text = """ SELECT id, val FROM table @@ -120,8 +120,8 @@ async def test_cursor_fetch_many(session_pool): @pytest.mark.asyncio -async def test_cursor_fetch_all(session_pool): - cursor = ydb_dbapi.Cursor(session_pool=session_pool) +async def test_cursor_fetch_all(session): + cursor = ydb_dbapi.Cursor(session=session) yql_text = """ INSERT INTO table (id, val) VALUES (1, 1), @@ -132,7 +132,7 @@ async def test_cursor_fetch_all(session_pool): await cursor.execute(query=yql_text) assert await cursor.fetchone() is None - cursor = ydb_dbapi.Cursor(session_pool=session_pool) + cursor = ydb_dbapi.Cursor(session=session) yql_text = """ SELECT id, val FROM table @@ -152,8 +152,8 @@ async def test_cursor_fetch_all(session_pool): @pytest.mark.asyncio -async def test_cursor_next_set(session_pool): - cursor = ydb_dbapi.Cursor(session_pool=session_pool) +async def test_cursor_next_set(session): + cursor = ydb_dbapi.Cursor(session=session) yql_text = """SELECT 1 as val; SELECT 2 as val;""" await cursor.execute(query=yql_text) diff --git a/ydb_dbapi/__init__.py b/ydb_dbapi/__init__.py index f85f00b..93d9e23 100644 --- a/ydb_dbapi/__init__.py +++ b/ydb_dbapi/__init__.py @@ -1,3 +1,5 @@ -from .errors import * # noqa -from .connection import Connection, IsolationLevel, connect # noqa -from .cursors import Cursor # noqa +from .connection import Connection +from .connection import IsolationLevel +from .connection import connect +from .cursors import Cursor +from .errors import * diff --git a/ydb_dbapi/connection.py b/ydb_dbapi/connection.py index 395898d..bce7e03 100644 --- a/ydb_dbapi/connection.py +++ b/ydb_dbapi/connection.py @@ -1,25 +1,17 @@ -from typing import ( - Any, - List, - NamedTuple, - Optional, -) +from __future__ import annotations + import posixpath +from typing import Any +from typing import NamedTuple import ydb from ydb.retries import retry_operation_async from .cursors import Cursor - -from .utils import ( - handle_ydb_errors, -) - -from .errors import ( - InterfaceError, - InternalError, - NotSupportedError, -) +from .errors import InterfaceError +from .errors import InternalError +from .errors import NotSupportedError +from .utils import handle_ydb_errors class IsolationLevel: @@ -31,14 +23,21 @@ class IsolationLevel: AUTOCOMMIT = "AUTOCOMMIT" -class Connection: +_DRIVER_TYPE: ydb.Driver | ydb.aio.Driver +_SESSION_POOL_TYPE: ydb.QuerySessionPool | ydb.aio.QuerySessionPool + + +class BaseYDBConnection: + _ydb_driver_class = ydb.Driver + _ydb_session_pool_class = ydb.QuerySessionPool + def __init__( self, host: str = "", port: str = "", database: str = "", **conn_kwargs: Any, - ): + ) -> None: self.endpoint = f"grpc://{host}:{port}" self.database = database self.conn_kwargs = conn_kwargs @@ -51,7 +50,7 @@ def __init__( "ydb_session_pool" in self.conn_kwargs ): # Use session pool managed manually self._shared_session_pool = True - self._session_pool: ydb.aio.SessionPool = self.conn_kwargs.pop( + self._session_pool = self.conn_kwargs.pop( "ydb_session_pool" ) self._driver = self._session_pool._driver @@ -62,70 +61,18 @@ def __init__( database=self.database, credentials=self.credentials, ) - self._driver = ydb.aio.Driver(driver_config) - self._session_pool = ydb.aio.QuerySessionPool(self._driver, size=5) + self._driver = self._ydb_driver_class(driver_config) + self._session_pool = self._ydb_session_pool_class( + self._driver, size=5 + ) - self._tx_context: Optional[ydb.QueryTxContext] = None + self._tx_context: ydb.QueryTxContext | None = None self._tx_mode: ydb.BaseQueryTxMode = ydb.QuerySerializableReadWrite() - self._current_cursor: Optional[Cursor] = None + self._current_cursor: Cursor | None = None self.interactive_transaction: bool = False - async def _wait(self, timeout: int = 5): - try: - await self._driver.wait(timeout, fail_fast=True) - except ydb.Error as e: - raise InterfaceError(e.message, original_error=e) from e - except Exception as e: - await self._driver.stop() - raise InterfaceError( - "Failed to connect to YDB, details " - f"{self._driver.discovery_debug_details()}" - ) from e - - def cursor(self): - if self._current_cursor and not self._current_cursor._closed: - raise RuntimeError( - "Unable to create new Cursor before closing existing one." - ) - self._current_cursor = Cursor( - session_pool=self._session_pool, - tx_context=self._tx_context, - autocommit=(not self.interactive_transaction), - ) - return self._current_cursor - - async def begin(self): - self._tx_context = None - self._session = await self._session_pool.acquire() - self._tx_context = self._session.transaction(self._tx_mode) - # await self._tx_context.begin() - - async def commit(self): - if self._tx_context and self._tx_context.tx_id: - await self._tx_context.commit() - await self._session_pool.release(self._session) - self._session = None - self._tx_context = None - - async def rollback(self): - if self._tx_context and self._tx_context.tx_id: - await self._tx_context.rollback() - await self._session_pool.release(self._session) - self._session = None - self._tx_context = None - - async def close(self): - await self.rollback() - - if self._current_cursor: - await self._current_cursor.close() - - if not self._shared_session_pool: - await self._session_pool.stop() - await self._driver.stop() - - def set_isolation_level(self, isolation_level: str): + def set_isolation_level(self, isolation_level: str) -> None: class IsolationSettings(NamedTuple): ydb_mode: ydb.BaseQueryTxMode interactive: bool @@ -138,14 +85,14 @@ class IsolationSettings(NamedTuple): ydb.QuerySerializableReadWrite(), interactive=True ), IsolationLevel.ONLINE_READONLY: IsolationSettings( - ydb.QueryOnlineReadOnly(), interactive=False + ydb.QueryOnlineReadOnly(), interactive=True ), IsolationLevel.ONLINE_READONLY_INCONSISTENT: IsolationSettings( ydb.QueryOnlineReadOnly().with_allow_inconsistent_reads(), - interactive=False, + interactive=True, ), IsolationLevel.STALE_READONLY: IsolationSettings( - ydb.QueryStaleReadOnly(), interactive=False + ydb.QueryStaleReadOnly(), interactive=True ), IsolationLevel.SNAPSHOT_READONLY: IsolationSettings( ydb.QuerySnapshotReadOnly(), interactive=True @@ -160,22 +107,89 @@ class IsolationSettings(NamedTuple): self.interactive_transaction = ydb_isolation_settings.interactive def get_isolation_level(self) -> str: - if self._tx_mode.name == ydb.SerializableReadWrite().name: + if self._tx_mode.name == ydb.QuerySerializableReadWrite().name: if self.interactive_transaction: return IsolationLevel.SERIALIZABLE - else: - return IsolationLevel.AUTOCOMMIT - elif self._tx_mode.name == ydb.OnlineReadOnly().name: + return IsolationLevel.AUTOCOMMIT + if self._tx_mode.name == ydb.QueryOnlineReadOnly().name: if self._tx_mode.settings.allow_inconsistent_reads: return IsolationLevel.ONLINE_READONLY_INCONSISTENT - else: - return IsolationLevel.ONLINE_READONLY - elif self._tx_mode.name == ydb.StaleReadOnly().name: + return IsolationLevel.ONLINE_READONLY + if self._tx_mode.name == ydb.QueryStaleReadOnly().name: return IsolationLevel.STALE_READONLY - elif self._tx_mode.name == ydb.SnapshotReadOnly().name: + if self._tx_mode.name == ydb.QuerySnapshotReadOnly().name: return IsolationLevel.SNAPSHOT_READONLY + msg = f"{self._tx_mode.name} is not supported" + raise NotSupportedError(msg) + + +class Connection(BaseYDBConnection): + _ydb_driver_class = ydb.aio.Driver + _ydb_session_pool_class = ydb.aio.QuerySessionPool + + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + + self._session: ydb.aio.QuerySession | None = None + self._tx_context: ydb.QueryTxContext | None = None + + async def _wait(self, timeout: int = 5) -> None: + try: + await self._driver.wait(timeout, fail_fast=True) + except ydb.Error as e: + raise InterfaceError(e.message, original_error=e) from e + except Exception as e: + await self._driver.stop() + msg = ( + "Failed to connect to YDB, details " + f"{self._driver.discovery_debug_details()}" + ) + raise InterfaceError( + msg + ) from e + + self._session = await self._session_pool.acquire() + + def cursor(self): + if self._current_cursor and not self._current_cursor._closed: + raise RuntimeError( + "Unable to create new Cursor before closing existing one." + ) + + if self.interactive_transaction: + self._tx_context = self._session.transaction(self._tx_mode) else: - raise NotSupportedError(f"{self._tx_mode.name} is not supported") + self._tx_context = None + + self._current_cursor = Cursor( + session=self._session, + tx_context=self._tx_context, + autocommit=(not self.interactive_transaction), + ) + return self._current_cursor + + async def commit(self) -> None: + if self._tx_context and self._tx_context.tx_id: + await self._tx_context.commit() + self._tx_context = None + + async def rollback(self) -> None: + if self._tx_context and self._tx_context.tx_id: + await self._tx_context.rollback() + self._tx_context = None + + async def close(self) -> None: + await self.rollback() + + if self._current_cursor: + await self._current_cursor.close() + + if self._session: + await self._session_pool.release(self._session) + + if not self._shared_session_pool: + await self._session_pool.stop() + await self._driver.stop() @handle_ydb_errors async def describe(self, table_path: str) -> ydb.TableSchemeEntry: @@ -192,7 +206,7 @@ async def check_exists(self, table_path: str) -> bool: return await self._check_path_exists(abs_table_path) @handle_ydb_errors - async def get_table_names(self) -> List[str]: + async def get_table_names(self) -> list[str]: abs_dir_path = posixpath.join(self.database, self.table_path_prefix) names = await self._get_table_names(abs_dir_path) return [posixpath.relpath(path, abs_dir_path) for path in names] @@ -200,7 +214,7 @@ async def get_table_names(self) -> List[str]: async def _check_path_exists(self, table_path: str) -> bool: try: - async def callee(): + async def callee() -> None: await self._driver.scheme_client.describe_path(table_path) await retry_operation_async(callee) @@ -208,7 +222,7 @@ async def callee(): except ydb.SchemeError: return False - async def _get_table_names(self, abs_dir_path: str) -> List[str]: + async def _get_table_names(self, abs_dir_path: str) -> list[str]: async def callee(): return await self._driver.scheme_client.list_directory( abs_dir_path diff --git a/ydb_dbapi/cursors.py b/ydb_dbapi/cursors.py index 9fdf774..51ff139 100644 --- a/ydb_dbapi/cursors.py +++ b/ydb_dbapi/cursors.py @@ -1,25 +1,25 @@ +from __future__ import annotations + import itertools -from typing import ( - Any, - AsyncIterator, - Dict, - Iterator, - List, - Optional, - Tuple, - Union, -) +from collections.abc import AsyncIterator +from collections.abc import Iterator +from typing import Any +from typing import Union import ydb -from .errors import Error, DatabaseError, InterfaceError, ProgrammingError -from .utils import handle_ydb_errors, AsyncFromSyncIterator, CursorStatus +from .errors import DatabaseError +from .errors import Error +from .errors import InterfaceError +from .errors import ProgrammingError +from .utils import CursorStatus +from .utils import handle_ydb_errors -ParametersType = Dict[ +ParametersType = dict[ str, Union[ Any, - Tuple[Any, Union[ydb.PrimitiveType, ydb.AbstractTypeBuilder]], + tuple[Any, Union[ydb.PrimitiveType, ydb.AbstractTypeBuilder]], ydb.TypedValue, ], ] @@ -32,21 +32,21 @@ def _get_column_type(type_obj: Any) -> str: class Cursor: def __init__( self, - session_pool: ydb.aio.QuerySessionPool, - tx_context: Optional[ydb.aio.QueryTxContext] = None, + session: ydb.aio.QuerySession, + tx_context: ydb.aio.QueryTxContext | None = None, table_path_prefix: str = "", autocommit: bool = True, - ): + ) -> None: self.arraysize: int = 1 - self._description: Optional[List[Tuple]] = None + self._description: list[tuple] | None = None - self._session_pool = session_pool + self._session = session self._tx_context = tx_context self._table_path_prefix = table_path_prefix self._autocommit = autocommit - self._stream: Optional[AsyncIterator] = None - self._rows: Optional[Iterator[Dict]] = None + self._stream: AsyncIterator | None = None + self._rows: Iterator[tuple] | None = None self._rows_count: int = -1 self._closed: bool = False @@ -62,16 +62,16 @@ def rowcount(self): @handle_ydb_errors async def _execute_generic_query( - self, query: str, parameters: Optional[ParametersType] = None - ) -> List[ydb.convert.ResultSet]: - return await self._session_pool.execute_with_retries( + self, query: str, parameters: ParametersType | None = None + ) -> AsyncIterator[ydb.convert.ResultSet]: + return await self._session.execute( query=query, parameters=parameters ) @handle_ydb_errors async def _execute_transactional_query( - self, query: str, parameters: Optional[ParametersType] = None - ) -> AsyncIterator: + self, query: str, parameters: ParametersType | None = None + ) -> AsyncIterator[ydb.convert.ResultSet]: if self._tx_context is None: raise Error( "Unable to execute tx based queries without transaction." @@ -85,9 +85,9 @@ async def _execute_transactional_query( async def execute( self, query: str, - parameters: Optional[ParametersType] = None, + parameters: ParametersType | None = None, prefetch_first_set: bool = True, - ): + ) -> None: self._check_cursor_closed() self._check_pending_query() if self._tx_context is not None: @@ -95,10 +95,9 @@ async def execute( query=query, parameters=parameters ) else: - result_sets = await self._execute_generic_query( + self._stream = await self._execute_generic_query( query=query, parameters=parameters ) - self._stream = AsyncFromSyncIterator(iter(result_sets)) if self._stream is None: return @@ -108,12 +107,12 @@ async def execute( if prefetch_first_set: await self.nextset() - def _update_result_set(self, result_set: ydb.convert.ResultSet): + def _update_result_set(self, result_set: ydb.convert.ResultSet) -> None: self._update_description(result_set) self._rows = self._rows_iterable(result_set) self._rows_count = len(result_set.rows) or -1 - def _update_description(self, result_set: ydb.convert.ResultSet): + def _update_description(self, result_set: ydb.convert.ResultSet) -> None: self._description = [ ( col.name, @@ -137,13 +136,13 @@ def _rows_iterable(self, result_set): except ydb.Error as e: raise DatabaseError(e.message, original_error=e) from e - async def executemany(self): + async def executemany(self) -> None: pass - async def fetchone(self): + async def fetchone(self) -> tuple | None: return next(self._rows or iter([]), None) - async def fetchmany(self, size: Optional[int] = None): + async def fetchmany(self, size: int | None = None) -> list | None: return ( list( itertools.islice( @@ -153,11 +152,11 @@ async def fetchmany(self, size: Optional[int] = None): or None ) - async def fetchall(self): + async def fetchall(self) -> list | None: return list(self._rows or iter([])) or None @handle_ydb_errors - async def nextset(self): + async def nextset(self) -> bool: if self._stream is None: return False try: @@ -166,15 +165,15 @@ async def nextset(self): except (StopIteration, StopAsyncIteration, RuntimeError): self._state = CursorStatus.finished return False - except ydb.Error as e: + except ydb.Error: self._state = CursorStatus.finished - raise e + raise return True - async def finish_query(self): + async def finish_query(self) -> None: self._check_cursor_closed() - if not self._state == CursorStatus.running: + if self._state != CursorStatus.running: return next_set_available = True @@ -183,13 +182,13 @@ async def finish_query(self): self._state = CursorStatus.finished - def setinputsizes(self): + def setinputsizes(self) -> None: pass - def setoutputsize(self): + def setoutputsize(self) -> None: pass - async def close(self): + async def close(self) -> None: if self._closed: return @@ -197,24 +196,24 @@ async def close(self): self._state = CursorStatus.closed self._closed = True - def _begin_query(self): + def _begin_query(self) -> None: self._state = CursorStatus.running - def _check_pending_query(self): + def _check_pending_query(self) -> None: if self._state == CursorStatus.running: raise ProgrammingError( "Some records have not been fetched. " "Fetch the remaining records before executing the next query." ) - def _check_cursor_closed(self): + def _check_cursor_closed(self) -> None: if self._state == CursorStatus.closed: raise InterfaceError( "Could not perform operation: Cursor is closed." ) - async def __aenter__(self): + async def __aenter__(self) -> Cursor: return self - async def __aexit__(self, exc_type, exc, tb): + async def __aexit__(self, exc_type, exc, tb) -> None: await self.close() diff --git a/ydb_dbapi/errors.py b/ydb_dbapi/errors.py index 3cd1842..e5463b6 100644 --- a/ydb_dbapi/errors.py +++ b/ydb_dbapi/errors.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from __future__ import annotations import ydb from google.protobuf.message import Message @@ -12,9 +12,9 @@ class Error(Exception): def __init__( self, message: str, - original_error: Optional[ydb.Error] = None, - ): - super(Error, self).__init__(message) + original_error: ydb.Error | None = None, + ) -> None: + super().__init__(message) self.original_error = original_error if original_error: @@ -56,7 +56,7 @@ class NotSupportedError(DatabaseError): pass -def _pretty_issues(issues: List[Message]) -> Optional[str]: +def _pretty_issues(issues: list[Message]) -> str | None: if issues is None: return None @@ -74,7 +74,7 @@ def _get_messages( indent: int = 2, depth: int = 0, root: bool = False, -) -> Optional[str]: +) -> str | None: if depth >= max_depth: return None diff --git a/ydb_dbapi/utils.py b/ydb_dbapi/utils.py index 7f5ec09..55ea9cb 100644 --- a/ydb_dbapi/utils.py +++ b/ydb_dbapi/utils.py @@ -1,17 +1,15 @@ -from enum import Enum import functools -from typing import Iterator +from enum import Enum + import ydb -from .errors import ( - DatabaseError, - DataError, - IntegrityError, - InternalError, - NotSupportedError, - OperationalError, - ProgrammingError, -) +from .errors import DatabaseError +from .errors import DataError +from .errors import IntegrityError +from .errors import InternalError +from .errors import NotSupportedError +from .errors import OperationalError +from .errors import ProgrammingError def handle_ydb_errors(func): @@ -56,14 +54,3 @@ class CursorStatus(str, Enum): running = "running" finished = "finished" closed = "closed" - - -class AsyncFromSyncIterator: - def __init__(self, sync_iter: Iterator): - self._sync_iter = sync_iter - - def __aiter__(self): - return self - - async def __anext__(self): - return next(self._sync_iter) From 55c2a2f6f0853aaf1afe17abd4c9236f3b8294c8 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:10:32 +0300 Subject: [PATCH 14/33] style fixes --- pyproject.toml | 5 ++- tests/conftest.py | 18 +++++++-- tests/test_connection.py | 30 ++++++++++----- tests/test_cursor.py | 22 ++++++++--- ydb_dbapi/__init__.py | 6 +-- ydb_dbapi/{connection.py => connections.py} | 42 +++++++++++++-------- ydb_dbapi/cursors.py | 23 +++++++---- ydb_dbapi/errors.py | 4 -- ydb_dbapi/utils.py | 6 ++- 9 files changed, 103 insertions(+), 53 deletions(-) rename ydb_dbapi/{connection.py => connections.py} (90%) diff --git a/pyproject.toml b/pyproject.toml index 81d0db9..1b3cd76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,6 +58,8 @@ ignore = [ # Ignores below could be deleted "EM101", # Allow to use string literals in exceptions "TRY003", # Allow specifying long messages outside the exception class + "SLF001", # Allow access private member, + "PGH003", # Allow not to specify rule codes ] select = ["ALL"] @@ -72,7 +74,8 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" force-single-line = true [tool.ruff.lint.per-file-ignores] -"**/test_*.py" = ["S", "SLF", "ANN201", "ARG", "PLR2004"] +"**/test_*.py" = ["S", "SLF", "ANN201", "ARG", "PLR2004", "PT012"] +"conftest.py" = ["ARG001"] "__init__.py" = ["F401", "F403"] [tool.pytest.ini_options] diff --git a/tests/conftest.py b/tests/conftest.py index 07e8dab..a863061 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,7 @@ from __future__ import annotations +from asyncio import AbstractEventLoop +from collections.abc import AsyncGenerator from collections.abc import Generator from typing import Any from typing import Callable @@ -9,6 +11,7 @@ from testcontainers.core.generic import DbContainer from testcontainers.core.generic import wait_container_is_ready from testcontainers.core.utils import setup_logger +from typing_extensions import Self logger = setup_logger(__name__) @@ -33,7 +36,7 @@ def __init__( self._name = name self._database_name = "local" - def start(self): + def start(self) -> Self: self._maybe_stop_old_container() super().start() return self @@ -115,7 +118,9 @@ def connection_kwargs(ydb_container: YDBContainer) -> dict: @pytest.fixture -async def driver(ydb_container, event_loop): +async def driver( + ydb_container: YDBContainer, event_loop: AbstractEventLoop +) -> AsyncGenerator[ydb.aio.Driver]: driver = ydb.aio.Driver( connection_string=ydb_container.get_connection_string() ) @@ -128,7 +133,9 @@ async def driver(ydb_container, event_loop): @pytest.fixture -async def session_pool(driver: ydb.aio.Driver): +async def session_pool( + driver: ydb.aio.Driver, +) -> AsyncGenerator[ydb.aio.QuerySessionPool]: session_pool = ydb.aio.QuerySessionPool(driver) async with session_pool: await session_pool.execute_with_retries( @@ -146,8 +153,11 @@ async def session_pool(driver: ydb.aio.Driver): yield session_pool + @pytest.fixture -async def session(session_pool: ydb.aio.QuerySessionPool): +async def session( + session_pool: ydb.aio.QuerySessionPool, +) -> AsyncGenerator[ydb.aio.QuerySession]: session = await session_pool.acquire() yield session diff --git a/tests/test_connection.py b/tests/test_connection.py index 4a683d6..2300efd 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -1,3 +1,6 @@ +from __future__ import annotations + +from collections.abc import AsyncGenerator from contextlib import suppress import pytest @@ -37,7 +40,7 @@ async def _test_isolation_level_read_only( await connection.rollback() async with connection.cursor() as cursor: - cursor.execute("DROP TABLE foo") + await cursor.execute("DROP TABLE foo") async def _test_connection(self, connection: dbapi.Connection) -> None: await connection.commit() @@ -66,7 +69,9 @@ async def _test_connection(self, connection: dbapi.Connection) -> None: await cur.execute("DROP TABLE foo") await cur.close() - async def _test_cursor_raw_query(self, connection: dbapi.Connection) -> None: + async def _test_cursor_raw_query( + self, connection: dbapi.Connection + ) -> None: cur = connection.cursor() assert cur @@ -107,7 +112,10 @@ async def _test_cursor_raw_query(self, connection: dbapi.Connection) -> None: async def _test_errors(self, connection: dbapi.Connection) -> None: with pytest.raises(dbapi.InterfaceError): - await dbapi.connect("localhost:2136", database="/local666") + await dbapi.connect( + "localhost:2136", # type: ignore + database="/local666", # type: ignore + ) cur = connection.cursor() @@ -142,8 +150,10 @@ async def _test_errors(self, connection: dbapi.Connection) -> None: class TestAsyncConnection(BaseDBApiTestSuit): @pytest_asyncio.fixture - async def connection(self, connection_kwargs): - conn = await dbapi.connect(**connection_kwargs) + async def connection( + self, connection_kwargs: dict + ) -> AsyncGenerator[dbapi.Connection]: + conn = await dbapi.connect(**connection_kwargs) # ignore: typing try: yield conn finally: @@ -166,19 +176,21 @@ async def test_isolation_level_read_only( isolation_level: str, read_only: bool, connection: dbapi.Connection, - ): + ) -> None: await self._test_isolation_level_read_only( connection, isolation_level, read_only ) @pytest.mark.asyncio - async def test_connection(self, connection: dbapi.Connection): + async def test_connection(self, connection: dbapi.Connection) -> None: await self._test_connection(connection) @pytest.mark.asyncio - async def test_cursor_raw_query(self, connection: dbapi.Connection): + async def test_cursor_raw_query( + self, connection: dbapi.Connection + ) -> None: await self._test_cursor_raw_query(connection) @pytest.mark.asyncio - async def test_errors(self, connection: dbapi.Connection): + async def test_errors(self, connection: dbapi.Connection) -> None: await self._test_errors(connection) diff --git a/tests/test_cursor.py b/tests/test_cursor.py index 2ddd8b2..481d782 100644 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -1,9 +1,10 @@ import pytest import ydb_dbapi +from ydb.aio import QuerySession @pytest.mark.asyncio -async def test_cursor_ddl(session): +async def test_cursor_ddl(session: QuerySession) -> None: cursor = ydb_dbapi.Cursor(session=session) yql = """ @@ -27,7 +28,7 @@ async def test_cursor_ddl(session): @pytest.mark.asyncio -async def test_cursor_dml(session): +async def test_cursor_dml(session: QuerySession) -> None: cursor = ydb_dbapi.Cursor(session=session) yql_text = """ INSERT INTO table (id, val) VALUES @@ -48,12 +49,13 @@ async def test_cursor_dml(session): await cursor.execute(query=yql_text) res = await cursor.fetchone() + assert res is not None assert len(res) == 1 assert res[0] == 3 @pytest.mark.asyncio -async def test_cursor_fetch_one(session): +async def test_cursor_fetch_one(session: QuerySession) -> None: cursor = ydb_dbapi.Cursor(session=session) yql_text = """ INSERT INTO table (id, val) VALUES @@ -73,16 +75,18 @@ async def test_cursor_fetch_one(session): await cursor.execute(query=yql_text) res = await cursor.fetchone() + assert res is not None assert res[0] == 1 res = await cursor.fetchone() + assert res is not None assert res[0] == 2 assert await cursor.fetchone() is None @pytest.mark.asyncio -async def test_cursor_fetch_many(session): +async def test_cursor_fetch_many(session: QuerySession) -> None: cursor = ydb_dbapi.Cursor(session=session) yql_text = """ INSERT INTO table (id, val) VALUES @@ -104,15 +108,18 @@ async def test_cursor_fetch_many(session): await cursor.execute(query=yql_text) res = await cursor.fetchmany() + assert res is not None assert len(res) == 1 assert res[0][0] == 1 res = await cursor.fetchmany(size=2) + assert res is not None assert len(res) == 2 assert res[0][0] == 2 assert res[1][0] == 3 res = await cursor.fetchmany(size=2) + assert res is not None assert len(res) == 1 assert res[0][0] == 4 @@ -120,7 +127,7 @@ async def test_cursor_fetch_many(session): @pytest.mark.asyncio -async def test_cursor_fetch_all(session): +async def test_cursor_fetch_all(session: QuerySession) -> None: cursor = ydb_dbapi.Cursor(session=session) yql_text = """ INSERT INTO table (id, val) VALUES @@ -143,6 +150,7 @@ async def test_cursor_fetch_all(session): assert cursor.rowcount == 3 res = await cursor.fetchall() + assert res is not None assert len(res) == 3 assert res[0][0] == 1 assert res[1][0] == 2 @@ -152,13 +160,14 @@ async def test_cursor_fetch_all(session): @pytest.mark.asyncio -async def test_cursor_next_set(session): +async def test_cursor_next_set(session: QuerySession) -> None: cursor = ydb_dbapi.Cursor(session=session) yql_text = """SELECT 1 as val; SELECT 2 as val;""" await cursor.execute(query=yql_text) res = await cursor.fetchall() + assert res is not None assert len(res) == 1 assert res[0][0] == 1 @@ -166,6 +175,7 @@ async def test_cursor_next_set(session): assert nextset res = await cursor.fetchall() + assert res is not None assert len(res) == 1 assert res[0][0] == 2 diff --git a/ydb_dbapi/__init__.py b/ydb_dbapi/__init__.py index 93d9e23..475d377 100644 --- a/ydb_dbapi/__init__.py +++ b/ydb_dbapi/__init__.py @@ -1,5 +1,5 @@ -from .connection import Connection -from .connection import IsolationLevel -from .connection import connect +from .connections import Connection +from .connections import IsolationLevel +from .connections import connect from .cursors import Cursor from .errors import * diff --git a/ydb_dbapi/connection.py b/ydb_dbapi/connections.py similarity index 90% rename from ydb_dbapi/connection.py rename to ydb_dbapi/connections.py index bce7e03..4a6c084 100644 --- a/ydb_dbapi/connection.py +++ b/ydb_dbapi/connections.py @@ -50,9 +50,7 @@ def __init__( "ydb_session_pool" in self.conn_kwargs ): # Use session pool managed manually self._shared_session_pool = True - self._session_pool = self.conn_kwargs.pop( - "ydb_session_pool" - ) + self._session_pool = self.conn_kwargs.pop("ydb_session_pool") self._driver = self._session_pool._driver else: self._shared_session_pool = False @@ -127,13 +125,24 @@ class Connection(BaseYDBConnection): _ydb_driver_class = ydb.aio.Driver _ydb_session_pool_class = ydb.aio.QuerySessionPool - def __init__(self, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) + def __init__( + self, + host: str = "", + port: str = "", + database: str = "", + **conn_kwargs: Any, + ) -> None: + super().__init__( + host, + port, + database, + **conn_kwargs, + ) self._session: ydb.aio.QuerySession | None = None - self._tx_context: ydb.QueryTxContext | None = None + self._tx_context: ydb.aio.QueryTxContext | None = None - async def _wait(self, timeout: int = 5) -> None: + async def wait_ready(self, timeout: int = 5) -> None: try: await self._driver.wait(timeout, fail_fast=True) except ydb.Error as e: @@ -144,13 +153,13 @@ async def _wait(self, timeout: int = 5) -> None: "Failed to connect to YDB, details " f"{self._driver.discovery_debug_details()}" ) - raise InterfaceError( - msg - ) from e + raise InterfaceError(msg) from e self._session = await self._session_pool.acquire() - def cursor(self): + def cursor(self) -> Cursor: + if self._session is None: + raise RuntimeError("Connection is not ready, use wait_ready.") if self._current_cursor and not self._current_cursor._closed: raise RuntimeError( "Unable to create new Cursor before closing existing one." @@ -218,12 +227,13 @@ async def callee() -> None: await self._driver.scheme_client.describe_path(table_path) await retry_operation_async(callee) - return True except ydb.SchemeError: return False + else: + return True async def _get_table_names(self, abs_dir_path: str) -> list[str]: - async def callee(): + async def callee() -> ydb.Directory: return await self._driver.scheme_client.list_directory( abs_dir_path ) @@ -239,7 +249,7 @@ async def callee(): return result -async def connect(*args, **kwargs) -> Connection: - conn = Connection(*args, **kwargs) - await conn._wait() +async def connect(*args: tuple, **kwargs: dict) -> Connection: + conn = Connection(*args, **kwargs) # type: ignore + await conn.wait_ready() return conn diff --git a/ydb_dbapi/cursors.py b/ydb_dbapi/cursors.py index 51ff139..7d87c03 100644 --- a/ydb_dbapi/cursors.py +++ b/ydb_dbapi/cursors.py @@ -2,11 +2,13 @@ import itertools from collections.abc import AsyncIterator +from collections.abc import Generator from collections.abc import Iterator from typing import Any from typing import Union import ydb +from typing_extensions import Self from .errors import DatabaseError from .errors import Error @@ -53,20 +55,18 @@ def __init__( self._state = CursorStatus.ready @property - def description(self): + def description(self) -> list[tuple] | None: return self._description @property - def rowcount(self): + def rowcount(self) -> int: return self._rows_count @handle_ydb_errors async def _execute_generic_query( self, query: str, parameters: ParametersType | None = None ) -> AsyncIterator[ydb.convert.ResultSet]: - return await self._session.execute( - query=query, parameters=parameters - ) + return await self._session.execute(query=query, parameters=parameters) @handle_ydb_errors async def _execute_transactional_query( @@ -126,7 +126,9 @@ def _update_description(self, result_set: ydb.convert.ResultSet) -> None: for col in result_set.columns ] - def _rows_iterable(self, result_set): + def _rows_iterable( + self, result_set: ydb.convert.ResultSet + ) -> Generator[tuple]: try: for row in result_set.rows: # returns tuple to be compatible with SqlAlchemy and because @@ -212,8 +214,13 @@ def _check_cursor_closed(self) -> None: "Could not perform operation: Cursor is closed." ) - async def __aenter__(self) -> Cursor: + async def __aenter__(self) -> Self: return self - async def __aexit__(self, exc_type, exc, tb) -> None: + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + tb: object, + ) -> None: await self.close() diff --git a/ydb_dbapi/errors.py b/ydb_dbapi/errors.py index e5463b6..011e16c 100644 --- a/ydb_dbapi/errors.py +++ b/ydb_dbapi/errors.py @@ -4,10 +4,6 @@ from google.protobuf.message import Message -class Warning(Exception): - pass - - class Error(Exception): def __init__( self, diff --git a/ydb_dbapi/utils.py b/ydb_dbapi/utils.py index 55ea9cb..d11f418 100644 --- a/ydb_dbapi/utils.py +++ b/ydb_dbapi/utils.py @@ -1,5 +1,7 @@ import functools from enum import Enum +from typing import Any +from typing import Callable import ydb @@ -12,9 +14,9 @@ from .errors import ProgrammingError -def handle_ydb_errors(func): +def handle_ydb_errors(func: Callable) -> Callable: @functools.wraps(func) - async def wrapper(*args, **kwargs): + async def wrapper(*args: tuple, **kwargs: dict) -> Any: try: return await func(*args, **kwargs) except (ydb.issues.AlreadyExists, ydb.issues.PreconditionFailed) as e: From 234c07c852705b1cae14682eeabc58d2819e8582 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:10:32 +0300 Subject: [PATCH 15/33] fix isolation level test --- tests/test_connection.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_connection.py b/tests/test_connection.py index 2300efd..c14ef93 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -16,6 +16,7 @@ async def _test_isolation_level_read_only( isolation_level: str, read_only: bool, ) -> None: + connection.set_isolation_level("AUTOCOMMIT") async with connection.cursor() as cursor: with suppress(dbapi.DatabaseError): await cursor.execute("DROP TABLE foo") @@ -39,6 +40,8 @@ async def _test_isolation_level_read_only( await connection.rollback() + connection.set_isolation_level("AUTOCOMMIT") + async with connection.cursor() as cursor: await cursor.execute("DROP TABLE foo") From 7aab016f2874522aebca0944741e99e6df0bfb5e Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:10:32 +0300 Subject: [PATCH 16/33] sync interface --- tests/conftest.py | 45 ++++ tests/test_connection.py | 209 +++++++++++++++++-- tests/test_cursor.py | 441 +++++++++++++++++++++++++++------------ ydb_dbapi/__init__.py | 3 + ydb_dbapi/connections.py | 233 ++++++++++++++++++--- ydb_dbapi/cursors.py | 278 ++++++++++++++++++++---- ydb_dbapi/utils.py | 39 +++- 7 files changed, 1031 insertions(+), 217 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index a863061..2c4eb4b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -132,6 +132,21 @@ async def driver( del driver +@pytest.fixture +def driver_sync( + ydb_container: YDBContainer, +) -> Generator[ydb.Driver]: + driver = ydb.Driver( + connection_string=ydb_container.get_connection_string() + ) + driver.wait(timeout=15, fail_fast=True) + + yield driver + + driver.stop(timeout=10) + del driver + + @pytest.fixture async def session_pool( driver: ydb.aio.Driver, @@ -154,6 +169,25 @@ async def session_pool( yield session_pool +@pytest.fixture +def session_pool_sync( + driver_sync: ydb.Driver, +) -> Generator[ydb.QuerySessionPool]: + session_pool = ydb.QuerySessionPool(driver_sync) + with session_pool: + session_pool.execute_with_retries("""DROP TABLE IF EXISTS table""") + session_pool.execute_with_retries( + """ + CREATE TABLE table ( + id Int64 NOT NULL, + val Int64, + PRIMARY KEY(id) + ) + """ + ) + yield session_pool + + @pytest.fixture async def session( session_pool: ydb.aio.QuerySessionPool, @@ -163,3 +197,14 @@ async def session( yield session await session_pool.release(session) + + +@pytest.fixture +def session_sync( + session_pool_sync: ydb.QuerySessionPool, +) -> Generator[ydb.QuerySession]: + session = session_pool_sync.acquire() + + yield session + + session_pool_sync.release(session) diff --git a/tests/test_connection.py b/tests/test_connection.py index c14ef93..073f8f2 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -1,6 +1,7 @@ from __future__ import annotations from collections.abc import AsyncGenerator +from collections.abc import Generator from contextlib import suppress import pytest @@ -9,12 +10,192 @@ import ydb_dbapi as dbapi -class BaseDBApiTestSuit: - async def _test_isolation_level_read_only( +class BaseSyncDBApiTestSuit: + def _test_isolation_level_read_only( self, connection: dbapi.Connection, isolation_level: str, read_only: bool, + ) -> None: + connection.set_isolation_level("AUTOCOMMIT") + with connection.cursor() as cursor: # noqa: SIM117 + with suppress(dbapi.DatabaseError): + cursor.execute("DROP TABLE foo") + + with connection.cursor() as cursor: + cursor.execute( + "CREATE TABLE foo(id Int64 NOT NULL, PRIMARY KEY (id))" + ) + + connection.set_isolation_level(isolation_level) + + with connection.cursor() as cursor: + query = "UPSERT INTO foo(id) VALUES (1)" + if read_only: + with pytest.raises(dbapi.DatabaseError): + cursor.execute(query) + cursor.finish_query() + + else: + cursor.execute(query) + + connection.rollback() + + connection.set_isolation_level("AUTOCOMMIT") + + with connection.cursor() as cursor: + cursor.execute("DROP TABLE foo") + + def _test_connection(self, connection: dbapi.Connection) -> None: + connection.commit() + connection.rollback() + + cur = connection.cursor() + with suppress(dbapi.DatabaseError): + cur.execute("DROP TABLE foo") + cur.finish_query() + + assert not connection.check_exists("/local/foo") + with pytest.raises(dbapi.ProgrammingError): + connection.describe("/local/foo") + + cur.execute("CREATE TABLE foo(id Int64 NOT NULL, PRIMARY KEY (id))") + cur.finish_query() + + assert connection.check_exists("/local/foo") + + col = (connection.describe("/local/foo")).columns[0] + assert col.name == "id" + assert col.type == ydb.PrimitiveType.Int64 + + cur.execute("DROP TABLE foo") + cur.close() + + def _test_cursor_raw_query(self, connection: dbapi.Connection) -> None: + cur = connection.cursor() + assert cur + + with suppress(dbapi.DatabaseError): + cur.execute("DROP TABLE test") + cur.finish_query() + + cur.execute( + "CREATE TABLE test(id Int64 NOT NULL, text Utf8, PRIMARY KEY (id))" + ) + cur.finish_query() + + cur.execute( + """ + DECLARE $data AS List>; + + INSERT INTO test SELECT id, text FROM AS_TABLE($data); + """, + { + "$data": ydb.TypedValue( + [ + {"id": 17, "text": "seventeen"}, + {"id": 21, "text": "twenty one"}, + ], + ydb.ListType( + ydb.StructType() + .add_member("id", ydb.PrimitiveType.Int64) + .add_member("text", ydb.PrimitiveType.Utf8) + ), + ) + }, + ) + cur.finish_query() + + cur.execute("DROP TABLE test") + + cur.close() + + def _test_errors(self, connection: dbapi.Connection) -> None: + with pytest.raises(dbapi.InterfaceError): + dbapi.connect( + "localhost:2136", # type: ignore + database="/local666", # type: ignore + ) + + cur = connection.cursor() + + with suppress(dbapi.DatabaseError): + cur.execute("DROP TABLE test") + cur.finish_query() + + with pytest.raises(dbapi.DataError): + cur.execute("SELECT 18446744073709551616") + + with pytest.raises(dbapi.DataError): + cur.execute("SELECT * FROM 拉屎") + + with pytest.raises(dbapi.DataError): + cur.execute("SELECT floor(5 / 2)") + + with pytest.raises(dbapi.ProgrammingError): + cur.execute("SELECT * FROM test") + + cur.execute("CREATE TABLE test(id Int64, PRIMARY KEY (id))") + cur.finish_query() + + cur.execute("INSERT INTO test(id) VALUES(1)") + cur.finish_query() + + with pytest.raises(dbapi.IntegrityError): + cur.execute("INSERT INTO test(id) VALUES(1)") + + cur.execute("DROP TABLE test") + cur.close() + + +class TestConnection(BaseSyncDBApiTestSuit): + @pytest.fixture + def connection( + self, connection_kwargs: dict + ) -> Generator[dbapi.Connection]: + conn = dbapi.connect(**connection_kwargs) # ignore: typing + try: + yield conn + finally: + conn.close() + + @pytest.mark.parametrize( + ("isolation_level", "read_only"), + [ + (dbapi.IsolationLevel.SERIALIZABLE, False), + (dbapi.IsolationLevel.AUTOCOMMIT, False), + (dbapi.IsolationLevel.ONLINE_READONLY, True), + (dbapi.IsolationLevel.ONLINE_READONLY_INCONSISTENT, True), + (dbapi.IsolationLevel.STALE_READONLY, True), + (dbapi.IsolationLevel.SNAPSHOT_READONLY, True), + ], + ) + def test_isolation_level_read_only( + self, + isolation_level: str, + read_only: bool, + connection: dbapi.Connection, + ) -> None: + self._test_isolation_level_read_only( + connection, isolation_level, read_only + ) + + def test_connection(self, connection: dbapi.Connection) -> None: + self._test_connection(connection) + + def test_cursor_raw_query(self, connection: dbapi.Connection) -> None: + self._test_cursor_raw_query(connection) + + def test_errors(self, connection: dbapi.Connection) -> None: + self._test_errors(connection) + + +class BaseAsyncDBApiTestSuit: + async def _test_isolation_level_read_only( + self, + connection: dbapi.AsyncConnection, + isolation_level: str, + read_only: bool, ) -> None: connection.set_isolation_level("AUTOCOMMIT") async with connection.cursor() as cursor: @@ -45,7 +226,9 @@ async def _test_isolation_level_read_only( async with connection.cursor() as cursor: await cursor.execute("DROP TABLE foo") - async def _test_connection(self, connection: dbapi.Connection) -> None: + async def _test_connection( + self, connection: dbapi.AsyncConnection + ) -> None: await connection.commit() await connection.rollback() @@ -73,7 +256,7 @@ async def _test_connection(self, connection: dbapi.Connection) -> None: await cur.close() async def _test_cursor_raw_query( - self, connection: dbapi.Connection + self, connection: dbapi.AsyncConnection ) -> None: cur = connection.cursor() assert cur @@ -113,9 +296,9 @@ async def _test_cursor_raw_query( await cur.close() - async def _test_errors(self, connection: dbapi.Connection) -> None: + async def _test_errors(self, connection: dbapi.AsyncConnection) -> None: with pytest.raises(dbapi.InterfaceError): - await dbapi.connect( + await dbapi.async_connect( "localhost:2136", # type: ignore database="/local666", # type: ignore ) @@ -151,12 +334,12 @@ async def _test_errors(self, connection: dbapi.Connection) -> None: await cur.close() -class TestAsyncConnection(BaseDBApiTestSuit): +class TestAsyncConnection(BaseAsyncDBApiTestSuit): @pytest_asyncio.fixture async def connection( self, connection_kwargs: dict - ) -> AsyncGenerator[dbapi.Connection]: - conn = await dbapi.connect(**connection_kwargs) # ignore: typing + ) -> AsyncGenerator[dbapi.AsyncConnection]: + conn = await dbapi.async_connect(**connection_kwargs) # ignore: typing try: yield conn finally: @@ -178,22 +361,22 @@ async def test_isolation_level_read_only( self, isolation_level: str, read_only: bool, - connection: dbapi.Connection, + connection: dbapi.AsyncConnection, ) -> None: await self._test_isolation_level_read_only( connection, isolation_level, read_only ) @pytest.mark.asyncio - async def test_connection(self, connection: dbapi.Connection) -> None: + async def test_connection(self, connection: dbapi.AsyncConnection) -> None: await self._test_connection(connection) @pytest.mark.asyncio async def test_cursor_raw_query( - self, connection: dbapi.Connection + self, connection: dbapi.AsyncConnection ) -> None: await self._test_cursor_raw_query(connection) @pytest.mark.asyncio - async def test_errors(self, connection: dbapi.Connection) -> None: + async def test_errors(self, connection: dbapi.AsyncConnection) -> None: await self._test_errors(connection) diff --git a/tests/test_cursor.py b/tests/test_cursor.py index 481d782..7b31ca1 100644 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -1,188 +1,363 @@ import pytest import ydb_dbapi +from ydb import QuerySession as QuerySessionSync from ydb.aio import QuerySession -@pytest.mark.asyncio -async def test_cursor_ddl(session: QuerySession) -> None: - cursor = ydb_dbapi.Cursor(session=session) +class TestAsyncCursor: + @pytest.mark.asyncio + async def test_cursor_ddl(self, session: QuerySession) -> None: + cursor = ydb_dbapi.AsyncCursor(session=session) - yql = """ - CREATE TABLE table ( - id Int64 NOT NULL, - val Int64, - PRIMARY KEY(id) - ) - """ + yql = """ + CREATE TABLE table ( + id Int64 NOT NULL, + val Int64, + PRIMARY KEY(id) + ) + """ + + with pytest.raises(ydb_dbapi.Error): + await cursor.execute(query=yql) + + yql = """ + DROP TABLE table + """ - with pytest.raises(ydb_dbapi.Error): await cursor.execute(query=yql) - yql = """ - DROP TABLE table - """ + assert cursor.fetchone() is None + + @pytest.mark.asyncio + async def test_cursor_dml(self, session: QuerySession) -> None: + cursor = ydb_dbapi.AsyncCursor(session=session) + yql_text = """ + INSERT INTO table (id, val) VALUES + (1, 1), + (2, 2), + (3, 3) + """ + + await cursor.execute(query=yql_text) + assert cursor.fetchone() is None + + cursor = ydb_dbapi.AsyncCursor(session=session) + + yql_text = """ + SELECT COUNT(*) FROM table as sum + """ + + await cursor.execute(query=yql_text) + + res = cursor.fetchone() + assert res is not None + assert len(res) == 1 + assert res[0] == 3 + + @pytest.mark.asyncio + async def test_cursor_fetch_one(self, session: QuerySession) -> None: + cursor = ydb_dbapi.AsyncCursor(session=session) + yql_text = """ + INSERT INTO table (id, val) VALUES + (1, 1), + (2, 2) + """ + + await cursor.execute(query=yql_text) + assert cursor.fetchone() is None + + cursor = ydb_dbapi.AsyncCursor(session=session) + + yql_text = """ + SELECT id, val FROM table + """ + + await cursor.execute(query=yql_text) + + res = cursor.fetchone() + assert res is not None + assert res[0] == 1 + + res = cursor.fetchone() + assert res is not None + assert res[0] == 2 + + assert cursor.fetchone() is None + + @pytest.mark.asyncio + async def test_cursor_fetch_many(self, session: QuerySession) -> None: + cursor = ydb_dbapi.AsyncCursor(session=session) + yql_text = """ + INSERT INTO table (id, val) VALUES + (1, 1), + (2, 2), + (3, 3), + (4, 4) + """ + + await cursor.execute(query=yql_text) + assert cursor.fetchone() is None + + cursor = ydb_dbapi.AsyncCursor(session=session) + + yql_text = """ + SELECT id, val FROM table + """ + + await cursor.execute(query=yql_text) + + res = cursor.fetchmany() + assert res is not None + assert len(res) == 1 + assert res[0][0] == 1 + + res = cursor.fetchmany(size=2) + assert res is not None + assert len(res) == 2 + assert res[0][0] == 2 + assert res[1][0] == 3 + + res = cursor.fetchmany(size=2) + assert res is not None + assert len(res) == 1 + assert res[0][0] == 4 + + assert cursor.fetchmany(size=2) is None + + @pytest.mark.asyncio + async def test_cursor_fetch_all(self, session: QuerySession) -> None: + cursor = ydb_dbapi.AsyncCursor(session=session) + yql_text = """ + INSERT INTO table (id, val) VALUES + (1, 1), + (2, 2), + (3, 3) + """ + + await cursor.execute(query=yql_text) + assert cursor.fetchone() is None + + cursor = ydb_dbapi.AsyncCursor(session=session) + + yql_text = """ + SELECT id, val FROM table + """ + + await cursor.execute(query=yql_text) + + assert cursor.rowcount == 3 + + res = cursor.fetchall() + assert res is not None + assert len(res) == 3 + assert res[0][0] == 1 + assert res[1][0] == 2 + assert res[2][0] == 3 + + assert cursor.fetchall() is None + + @pytest.mark.asyncio + async def test_cursor_next_set(self, session: QuerySession) -> None: + cursor = ydb_dbapi.AsyncCursor(session=session) + yql_text = """SELECT 1 as val; SELECT 2 as val;""" + + await cursor.execute(query=yql_text) + + res = cursor.fetchall() + assert res is not None + assert len(res) == 1 + assert res[0][0] == 1 + + nextset = await cursor.nextset() + assert nextset + + res = cursor.fetchall() + assert res is not None + assert len(res) == 1 + assert res[0][0] == 2 + + nextset = await cursor.nextset() + assert nextset + + assert cursor.fetchall() is None + + nextset = await cursor.nextset() + assert not nextset + + +# The same test class as above but for Cursor - await cursor.execute(query=yql) - assert await cursor.fetchone() is None +class TestCursor: + def test_cursor_ddl(self, session_sync: QuerySessionSync) -> None: + cursor = ydb_dbapi.Cursor(session=session_sync) + yql = """ + CREATE TABLE table ( + id Int64 NOT NULL, + val Int64, + PRIMARY KEY(id) + ) + """ -@pytest.mark.asyncio -async def test_cursor_dml(session: QuerySession) -> None: - cursor = ydb_dbapi.Cursor(session=session) - yql_text = """ - INSERT INTO table (id, val) VALUES - (1, 1), - (2, 2), - (3, 3) - """ + with pytest.raises(ydb_dbapi.Error): + cursor.execute(query=yql) - await cursor.execute(query=yql_text) - assert await cursor.fetchone() is None + yql = """ + DROP TABLE table + """ - cursor = ydb_dbapi.Cursor(session=session) + cursor.execute(query=yql) - yql_text = """ - SELECT COUNT(*) FROM table as sum - """ + assert cursor.fetchone() is None - await cursor.execute(query=yql_text) + def test_cursor_dml(self, session_sync: QuerySessionSync) -> None: + cursor = ydb_dbapi.Cursor(session=session_sync) + yql_text = """ + INSERT INTO table (id, val) VALUES + (1, 1), + (2, 2), + (3, 3) + """ - res = await cursor.fetchone() - assert res is not None - assert len(res) == 1 - assert res[0] == 3 + cursor.execute(query=yql_text) + assert cursor.fetchone() is None + cursor = ydb_dbapi.Cursor(session=session_sync) -@pytest.mark.asyncio -async def test_cursor_fetch_one(session: QuerySession) -> None: - cursor = ydb_dbapi.Cursor(session=session) - yql_text = """ - INSERT INTO table (id, val) VALUES - (1, 1), - (2, 2) - """ + yql_text = """ + SELECT COUNT(*) FROM table as sum + """ - await cursor.execute(query=yql_text) - assert await cursor.fetchone() is None + cursor.execute(query=yql_text) - cursor = ydb_dbapi.Cursor(session=session) + res = cursor.fetchone() + assert res is not None + assert len(res) == 1 + assert res[0] == 3 - yql_text = """ - SELECT id, val FROM table - """ + def test_cursor_fetch_one(self, session_sync: QuerySessionSync) -> None: + cursor = ydb_dbapi.Cursor(session=session_sync) + yql_text = """ + INSERT INTO table (id, val) VALUES + (1, 1), + (2, 2) + """ - await cursor.execute(query=yql_text) + cursor.execute(query=yql_text) + assert cursor.fetchone() is None - res = await cursor.fetchone() - assert res is not None - assert res[0] == 1 + cursor = ydb_dbapi.Cursor(session=session_sync) - res = await cursor.fetchone() - assert res is not None - assert res[0] == 2 + yql_text = """ + SELECT id, val FROM table + """ - assert await cursor.fetchone() is None + cursor.execute(query=yql_text) + res = cursor.fetchone() + assert res is not None + assert res[0] == 1 -@pytest.mark.asyncio -async def test_cursor_fetch_many(session: QuerySession) -> None: - cursor = ydb_dbapi.Cursor(session=session) - yql_text = """ - INSERT INTO table (id, val) VALUES - (1, 1), - (2, 2), - (3, 3), - (4, 4) - """ + res = cursor.fetchone() + assert res is not None + assert res[0] == 2 - await cursor.execute(query=yql_text) - assert await cursor.fetchone() is None + assert cursor.fetchone() is None - cursor = ydb_dbapi.Cursor(session=session) + def test_cursor_fetch_many(self, session_sync: QuerySessionSync) -> None: + cursor = ydb_dbapi.Cursor(session=session_sync) + yql_text = """ + INSERT INTO table (id, val) VALUES + (1, 1), + (2, 2), + (3, 3), + (4, 4) + """ - yql_text = """ - SELECT id, val FROM table - """ + cursor.execute(query=yql_text) + assert cursor.fetchone() is None - await cursor.execute(query=yql_text) + cursor = ydb_dbapi.Cursor(session=session_sync) - res = await cursor.fetchmany() - assert res is not None - assert len(res) == 1 - assert res[0][0] == 1 + yql_text = """ + SELECT id, val FROM table + """ - res = await cursor.fetchmany(size=2) - assert res is not None - assert len(res) == 2 - assert res[0][0] == 2 - assert res[1][0] == 3 + cursor.execute(query=yql_text) - res = await cursor.fetchmany(size=2) - assert res is not None - assert len(res) == 1 - assert res[0][0] == 4 + res = cursor.fetchmany() + assert res is not None + assert len(res) == 1 + assert res[0][0] == 1 - assert await cursor.fetchmany(size=2) is None + res = cursor.fetchmany(size=2) + assert res is not None + assert len(res) == 2 + assert res[0][0] == 2 + assert res[1][0] == 3 + res = cursor.fetchmany(size=2) + assert res is not None + assert len(res) == 1 + assert res[0][0] == 4 -@pytest.mark.asyncio -async def test_cursor_fetch_all(session: QuerySession) -> None: - cursor = ydb_dbapi.Cursor(session=session) - yql_text = """ - INSERT INTO table (id, val) VALUES - (1, 1), - (2, 2), - (3, 3) - """ + assert cursor.fetchmany(size=2) is None - await cursor.execute(query=yql_text) - assert await cursor.fetchone() is None + def test_cursor_fetch_all(self, session_sync: QuerySessionSync) -> None: + cursor = ydb_dbapi.Cursor(session=session_sync) + yql_text = """ + INSERT INTO table (id, val) VALUES + (1, 1), + (2, 2), + (3, 3) + """ - cursor = ydb_dbapi.Cursor(session=session) + cursor.execute(query=yql_text) + assert cursor.fetchone() is None - yql_text = """ - SELECT id, val FROM table - """ + cursor = ydb_dbapi.Cursor(session=session_sync) - await cursor.execute(query=yql_text) + yql_text = """ + SELECT id, val FROM table + """ - assert cursor.rowcount == 3 + cursor.execute(query=yql_text) - res = await cursor.fetchall() - assert res is not None - assert len(res) == 3 - assert res[0][0] == 1 - assert res[1][0] == 2 - assert res[2][0] == 3 + assert cursor.rowcount == 3 - assert await cursor.fetchall() is None + res = cursor.fetchall() + assert res is not None + assert len(res) == 3 + assert res[0][0] == 1 + assert res[1][0] == 2 + assert res[2][0] == 3 + assert cursor.fetchall() is None -@pytest.mark.asyncio -async def test_cursor_next_set(session: QuerySession) -> None: - cursor = ydb_dbapi.Cursor(session=session) - yql_text = """SELECT 1 as val; SELECT 2 as val;""" + def test_cursor_next_set(self, session_sync: QuerySessionSync) -> None: + cursor = ydb_dbapi.Cursor(session=session_sync) + yql_text = """SELECT 1 as val; SELECT 2 as val;""" - await cursor.execute(query=yql_text) + cursor.execute(query=yql_text) - res = await cursor.fetchall() - assert res is not None - assert len(res) == 1 - assert res[0][0] == 1 + res = cursor.fetchall() + assert res is not None + assert len(res) == 1 + assert res[0][0] == 1 - nextset = await cursor.nextset() - assert nextset + nextset = cursor.nextset() + assert nextset - res = await cursor.fetchall() - assert res is not None - assert len(res) == 1 - assert res[0][0] == 2 + res = cursor.fetchall() + assert res is not None + assert len(res) == 1 + assert res[0][0] == 2 - nextset = await cursor.nextset() - assert nextset + nextset = cursor.nextset() + assert nextset - assert await cursor.fetchall() is None + assert cursor.fetchall() is None - nextset = await cursor.nextset() - assert not nextset + nextset = cursor.nextset() + assert not nextset diff --git a/ydb_dbapi/__init__.py b/ydb_dbapi/__init__.py index 475d377..c93f593 100644 --- a/ydb_dbapi/__init__.py +++ b/ydb_dbapi/__init__.py @@ -1,5 +1,8 @@ +from .connections import AsyncConnection from .connections import Connection from .connections import IsolationLevel +from .connections import async_connect from .connections import connect +from .cursors import AsyncCursor from .cursors import Cursor from .errors import * diff --git a/ydb_dbapi/connections.py b/ydb_dbapi/connections.py index 4a6c084..2ab94a8 100644 --- a/ydb_dbapi/connections.py +++ b/ydb_dbapi/connections.py @@ -6,12 +6,15 @@ import ydb from ydb.retries import retry_operation_async +from ydb.retries import retry_operation_sync +from .cursors import AsyncCursor from .cursors import Cursor from .errors import InterfaceError from .errors import InternalError from .errors import NotSupportedError from .utils import handle_ydb_errors +from .utils import handle_ydb_errors_async class IsolationLevel: @@ -23,14 +26,7 @@ class IsolationLevel: AUTOCOMMIT = "AUTOCOMMIT" -_DRIVER_TYPE: ydb.Driver | ydb.aio.Driver -_SESSION_POOL_TYPE: ydb.QuerySessionPool | ydb.aio.QuerySessionPool - - -class BaseYDBConnection: - _ydb_driver_class = ydb.Driver - _ydb_session_pool_class = ydb.QuerySessionPool - +class Connection: def __init__( self, host: str = "", @@ -51,7 +47,7 @@ def __init__( ): # Use session pool managed manually self._shared_session_pool = True self._session_pool = self.conn_kwargs.pop("ydb_session_pool") - self._driver = self._session_pool._driver + self._driver: ydb.Driver = self._session_pool._driver else: self._shared_session_pool = False driver_config = ydb.DriverConfig( @@ -59,17 +55,32 @@ def __init__( database=self.database, credentials=self.credentials, ) - self._driver = self._ydb_driver_class(driver_config) - self._session_pool = self._ydb_session_pool_class( - self._driver, size=5 - ) + self._driver = ydb.Driver(driver_config) + self._session_pool = ydb.QuerySessionPool(self._driver, size=5) - self._tx_context: ydb.QueryTxContext | None = None self._tx_mode: ydb.BaseQueryTxMode = ydb.QuerySerializableReadWrite() self._current_cursor: Cursor | None = None self.interactive_transaction: bool = False + self._session: ydb.QuerySession | None = None + self._tx_context: ydb.QueryTxContext | None = None + + def wait_ready(self, timeout: int = 10) -> None: + try: + self._driver.wait(timeout, fail_fast=True) + except ydb.Error as e: + raise InterfaceError(e.message, original_error=e) from e + except Exception as e: + self._driver.stop() + msg = ( + "Failed to connect to YDB, details " + f"{self._driver.discovery_debug_details()}" + ) + raise InterfaceError(msg) from e + + self._session = self._session_pool.acquire() + def set_isolation_level(self, isolation_level: str) -> None: class IsolationSettings(NamedTuple): ydb_mode: ydb.BaseQueryTxMode @@ -120,11 +131,97 @@ def get_isolation_level(self) -> str: msg = f"{self._tx_mode.name} is not supported" raise NotSupportedError(msg) + def cursor(self) -> Cursor: + if self._session is None: + raise RuntimeError("Connection is not ready, use wait_ready.") + if self._current_cursor and not self._current_cursor._closed: + raise RuntimeError( + "Unable to create new Cursor before closing existing one." + ) + + if self.interactive_transaction: + self._tx_context = self._session.transaction(self._tx_mode) + else: + self._tx_context = None + + self._current_cursor = Cursor( + session=self._session, + tx_context=self._tx_context, + autocommit=(not self.interactive_transaction), + ) + return self._current_cursor + + def commit(self) -> None: + if self._tx_context and self._tx_context.tx_id: + self._tx_context.commit() + self._tx_context = None + + def rollback(self) -> None: + if self._tx_context and self._tx_context.tx_id: + self._tx_context.rollback() + self._tx_context = None + + def close(self) -> None: + self.rollback() + + if self._current_cursor: + self._current_cursor.close() + + if self._session: + self._session_pool.release(self._session) + + if not self._shared_session_pool: + self._session_pool.stop() + self._driver.stop() + + @handle_ydb_errors + def describe(self, table_path: str) -> ydb.TableSchemeEntry: + abs_table_path = posixpath.join( + self.database, self.table_path_prefix, table_path + ) + return self._driver.table_client.describe_table(abs_table_path) + + @handle_ydb_errors + def check_exists(self, table_path: str) -> bool: + abs_table_path = posixpath.join( + self.database, self.table_path_prefix, table_path + ) + return self._check_path_exists(abs_table_path) + + @handle_ydb_errors + def get_table_names(self) -> list[str]: + abs_dir_path = posixpath.join(self.database, self.table_path_prefix) + names = self._get_table_names(abs_dir_path) + return [posixpath.relpath(path, abs_dir_path) for path in names] + + def _check_path_exists(self, table_path: str) -> bool: + try: + + def callee() -> None: + self._driver.scheme_client.describe_path(table_path) + + retry_operation_sync(callee) + except ydb.SchemeError: + return False + else: + return True + + def _get_table_names(self, abs_dir_path: str) -> list[str]: + def callee() -> ydb.Directory: + return self._driver.scheme_client.list_directory(abs_dir_path) + + directory = retry_operation_sync(callee) + result = [] + for child in directory.children: + child_abs_path = posixpath.join(abs_dir_path, child.name) + if child.is_table(): + result.append(child_abs_path) + elif child.is_directory() and not child.name.startswith("."): + result.extend(self.get_table_names(child_abs_path)) + return result -class Connection(BaseYDBConnection): - _ydb_driver_class = ydb.aio.Driver - _ydb_session_pool_class = ydb.aio.QuerySessionPool +class AsyncConnection: def __init__( self, host: str = "", @@ -132,17 +229,39 @@ def __init__( database: str = "", **conn_kwargs: Any, ) -> None: - super().__init__( - host, - port, - database, - **conn_kwargs, + self.endpoint = f"grpc://{host}:{port}" + self.database = database + self.conn_kwargs = conn_kwargs + self.credentials = self.conn_kwargs.pop("credentials", None) + self.table_path_prefix = self.conn_kwargs.pop( + "ydb_table_path_prefix", "" ) + if ( + "ydb_session_pool" in self.conn_kwargs + ): # Use session pool managed manually + self._shared_session_pool = True + self._session_pool = self.conn_kwargs.pop("ydb_session_pool") + self._driver: ydb.aio.Driver = self._session_pool._driver + else: + self._shared_session_pool = False + driver_config = ydb.DriverConfig( + endpoint=self.endpoint, + database=self.database, + credentials=self.credentials, + ) + self._driver = ydb.aio.Driver(driver_config) + self._session_pool = ydb.aio.QuerySessionPool(self._driver, size=5) + + self._tx_mode: ydb.BaseQueryTxMode = ydb.QuerySerializableReadWrite() + + self._current_cursor: AsyncCursor | None = None + self.interactive_transaction: bool = False + self._session: ydb.aio.QuerySession | None = None self._tx_context: ydb.aio.QueryTxContext | None = None - async def wait_ready(self, timeout: int = 5) -> None: + async def wait_ready(self, timeout: int = 10) -> None: try: await self._driver.wait(timeout, fail_fast=True) except ydb.Error as e: @@ -157,7 +276,57 @@ async def wait_ready(self, timeout: int = 5) -> None: self._session = await self._session_pool.acquire() - def cursor(self) -> Cursor: + def set_isolation_level(self, isolation_level: str) -> None: + class IsolationSettings(NamedTuple): + ydb_mode: ydb.BaseQueryTxMode + interactive: bool + + ydb_isolation_settings_map = { + IsolationLevel.AUTOCOMMIT: IsolationSettings( + ydb.QuerySerializableReadWrite(), interactive=False + ), + IsolationLevel.SERIALIZABLE: IsolationSettings( + ydb.QuerySerializableReadWrite(), interactive=True + ), + IsolationLevel.ONLINE_READONLY: IsolationSettings( + ydb.QueryOnlineReadOnly(), interactive=True + ), + IsolationLevel.ONLINE_READONLY_INCONSISTENT: IsolationSettings( + ydb.QueryOnlineReadOnly().with_allow_inconsistent_reads(), + interactive=True, + ), + IsolationLevel.STALE_READONLY: IsolationSettings( + ydb.QueryStaleReadOnly(), interactive=True + ), + IsolationLevel.SNAPSHOT_READONLY: IsolationSettings( + ydb.QuerySnapshotReadOnly(), interactive=True + ), + } + ydb_isolation_settings = ydb_isolation_settings_map[isolation_level] + if self._tx_context and self._tx_context.tx_id: + raise InternalError( + "Failed to set transaction mode: transaction is already began" + ) + self._tx_mode = ydb_isolation_settings.ydb_mode + self.interactive_transaction = ydb_isolation_settings.interactive + + def get_isolation_level(self) -> str: + if self._tx_mode.name == ydb.QuerySerializableReadWrite().name: + if self.interactive_transaction: + return IsolationLevel.SERIALIZABLE + return IsolationLevel.AUTOCOMMIT + if self._tx_mode.name == ydb.QueryOnlineReadOnly().name: + if self._tx_mode.settings.allow_inconsistent_reads: + return IsolationLevel.ONLINE_READONLY_INCONSISTENT + return IsolationLevel.ONLINE_READONLY + if self._tx_mode.name == ydb.QueryStaleReadOnly().name: + return IsolationLevel.STALE_READONLY + if self._tx_mode.name == ydb.QuerySnapshotReadOnly().name: + return IsolationLevel.SNAPSHOT_READONLY + msg = f"{self._tx_mode.name} is not supported" + raise NotSupportedError(msg) + + def cursor(self) -> AsyncCursor: if self._session is None: raise RuntimeError("Connection is not ready, use wait_ready.") if self._current_cursor and not self._current_cursor._closed: @@ -170,7 +339,7 @@ def cursor(self) -> Cursor: else: self._tx_context = None - self._current_cursor = Cursor( + self._current_cursor = AsyncCursor( session=self._session, tx_context=self._tx_context, autocommit=(not self.interactive_transaction), @@ -200,21 +369,21 @@ async def close(self) -> None: await self._session_pool.stop() await self._driver.stop() - @handle_ydb_errors + @handle_ydb_errors_async async def describe(self, table_path: str) -> ydb.TableSchemeEntry: abs_table_path = posixpath.join( self.database, self.table_path_prefix, table_path ) return await self._driver.table_client.describe_table(abs_table_path) - @handle_ydb_errors + @handle_ydb_errors_async async def check_exists(self, table_path: str) -> bool: abs_table_path = posixpath.join( self.database, self.table_path_prefix, table_path ) return await self._check_path_exists(abs_table_path) - @handle_ydb_errors + @handle_ydb_errors_async async def get_table_names(self) -> list[str]: abs_dir_path = posixpath.join(self.database, self.table_path_prefix) names = await self._get_table_names(abs_dir_path) @@ -249,7 +418,13 @@ async def callee() -> ydb.Directory: return result -async def connect(*args: tuple, **kwargs: dict) -> Connection: +def connect(*args: tuple, **kwargs: dict) -> Connection: conn = Connection(*args, **kwargs) # type: ignore + conn.wait_ready() + return conn + + +async def async_connect(*args: tuple, **kwargs: dict) -> AsyncConnection: + conn = AsyncConnection(*args, **kwargs) # type: ignore await conn.wait_ready() return conn diff --git a/ydb_dbapi/cursors.py b/ydb_dbapi/cursors.py index 7d87c03..a0bed28 100644 --- a/ydb_dbapi/cursors.py +++ b/ydb_dbapi/cursors.py @@ -16,6 +16,7 @@ from .errors import ProgrammingError from .utils import CursorStatus from .utils import handle_ydb_errors +from .utils import handle_ydb_errors_async ParametersType = dict[ str, @@ -34,8 +35,8 @@ def _get_column_type(type_obj: Any) -> str: class Cursor: def __init__( self, - session: ydb.aio.QuerySession, - tx_context: ydb.aio.QueryTxContext | None = None, + session: ydb.QuerySession, + tx_context: ydb.QueryTxContext | None = None, table_path_prefix: str = "", autocommit: bool = True, ) -> None: @@ -47,7 +48,7 @@ def __init__( self._table_path_prefix = table_path_prefix self._autocommit = autocommit - self._stream: AsyncIterator | None = None + self._stream: Iterator | None = None self._rows: Iterator[tuple] | None = None self._rows_count: int = -1 @@ -62,27 +63,96 @@ def description(self) -> list[tuple] | None: def rowcount(self) -> int: return self._rows_count + def setinputsizes(self) -> None: + pass + + def setoutputsize(self) -> None: + pass + + def _update_result_set(self, result_set: ydb.convert.ResultSet) -> None: + self._update_description(result_set) + self._rows = self._rows_iterable(result_set) + self._rows_count = len(result_set.rows) or -1 + + def _update_description(self, result_set: ydb.convert.ResultSet) -> None: + self._description = [ + ( + col.name, + _get_column_type(col.type), + None, + None, + None, + None, + None, + ) + for col in result_set.columns + ] + + def _rows_iterable( + self, result_set: ydb.convert.ResultSet + ) -> Generator[tuple]: + try: + for row in result_set.rows: + # returns tuple to be compatible with SqlAlchemy and because + # of this PEP to return a sequence: + # https://www.python.org/dev/peps/pep-0249/#fetchmany + yield row[::] + except ydb.Error as e: + raise DatabaseError(e.message, original_error=e) from e + + def _check_pending_query(self) -> None: + if self._state == CursorStatus.running: + raise ProgrammingError( + "Some records have not been fetched. " + "Fetch the remaining records before executing the next query." + ) + + def _check_cursor_closed(self) -> None: + if self._state == CursorStatus.closed: + raise InterfaceError( + "Could not perform operation: Cursor is closed." + ) + + def _begin_query(self) -> None: + self._state = CursorStatus.running + + def fetchone(self) -> tuple | None: + return next(self._rows or iter([]), None) + + def fetchmany(self, size: int | None = None) -> list | None: + return ( + list( + itertools.islice( + self._rows or iter([]), size or self.arraysize + ) + ) + or None + ) + + def fetchall(self) -> list | None: + return list(self._rows or iter([])) or None + @handle_ydb_errors - async def _execute_generic_query( + def _execute_generic_query( self, query: str, parameters: ParametersType | None = None - ) -> AsyncIterator[ydb.convert.ResultSet]: - return await self._session.execute(query=query, parameters=parameters) + ) -> Iterator[ydb.convert.ResultSet]: + return self._session.execute(query=query, parameters=parameters) @handle_ydb_errors - async def _execute_transactional_query( + def _execute_transactional_query( self, query: str, parameters: ParametersType | None = None - ) -> AsyncIterator[ydb.convert.ResultSet]: + ) -> Iterator[ydb.convert.ResultSet]: if self._tx_context is None: raise Error( "Unable to execute tx based queries without transaction." ) - return await self._tx_context.execute( + return self._tx_context.execute( query=query, parameters=parameters, commit_tx=self._autocommit, ) - async def execute( + def execute( self, query: str, parameters: ParametersType | None = None, @@ -91,11 +161,11 @@ async def execute( self._check_cursor_closed() self._check_pending_query() if self._tx_context is not None: - self._stream = await self._execute_transactional_query( + self._stream = self._execute_transactional_query( query=query, parameters=parameters ) else: - self._stream = await self._execute_generic_query( + self._stream = self._execute_generic_query( query=query, parameters=parameters ) @@ -105,7 +175,94 @@ async def execute( self._begin_query() if prefetch_first_set: - await self.nextset() + self.nextset() + + async def executemany(self) -> None: + pass + + @handle_ydb_errors + def nextset(self) -> bool: + if self._stream is None: + return False + try: + result_set = self._stream.__next__() + self._update_result_set(result_set) + except (StopIteration, StopAsyncIteration, RuntimeError): + self._state = CursorStatus.finished + return False + except ydb.Error: + self._state = CursorStatus.finished + raise + return True + + def finish_query(self) -> None: + self._check_cursor_closed() + + if self._state != CursorStatus.running: + return + + next_set_available = True + while next_set_available: + next_set_available = self.nextset() + + self._state = CursorStatus.finished + + def close(self) -> None: + if self._closed: + return + + self.finish_query() + self._state = CursorStatus.closed + self._closed = True + + def __enter__(self) -> Self: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + tb: object, + ) -> None: + self.close() + + +class AsyncCursor: + def __init__( + self, + session: ydb.aio.QuerySession, + tx_context: ydb.aio.QueryTxContext | None = None, + table_path_prefix: str = "", + autocommit: bool = True, + ) -> None: + self.arraysize: int = 1 + self._description: list[tuple] | None = None + + self._session = session + self._tx_context = tx_context + self._table_path_prefix = table_path_prefix + self._autocommit = autocommit + + self._stream: AsyncIterator | None = None + self._rows: Iterator[tuple] | None = None + self._rows_count: int = -1 + + self._closed: bool = False + self._state = CursorStatus.ready + + @property + def description(self) -> list[tuple] | None: + return self._description + + @property + def rowcount(self) -> int: + return self._rows_count + + def setinputsizes(self) -> None: + pass + + def setoutputsize(self) -> None: + pass def _update_result_set(self, result_set: ydb.convert.ResultSet) -> None: self._update_description(result_set) @@ -138,13 +295,26 @@ def _rows_iterable( except ydb.Error as e: raise DatabaseError(e.message, original_error=e) from e - async def executemany(self) -> None: - pass + def _check_pending_query(self) -> None: + if self._state == CursorStatus.running: + raise ProgrammingError( + "Some records have not been fetched. " + "Fetch the remaining records before executing the next query." + ) + + def _check_cursor_closed(self) -> None: + if self._state == CursorStatus.closed: + raise InterfaceError( + "Could not perform operation: Cursor is closed." + ) + + def _begin_query(self) -> None: + self._state = CursorStatus.running - async def fetchone(self) -> tuple | None: + def fetchone(self) -> tuple | None: return next(self._rows or iter([]), None) - async def fetchmany(self, size: int | None = None) -> list | None: + def fetchmany(self, size: int | None = None) -> list | None: return ( list( itertools.islice( @@ -154,10 +324,58 @@ async def fetchmany(self, size: int | None = None) -> list | None: or None ) - async def fetchall(self) -> list | None: + def fetchall(self) -> list | None: return list(self._rows or iter([])) or None - @handle_ydb_errors + @handle_ydb_errors_async + async def _execute_generic_query( + self, query: str, parameters: ParametersType | None = None + ) -> AsyncIterator[ydb.convert.ResultSet]: + return await self._session.execute(query=query, parameters=parameters) + + @handle_ydb_errors_async + async def _execute_transactional_query( + self, query: str, parameters: ParametersType | None = None + ) -> AsyncIterator[ydb.convert.ResultSet]: + if self._tx_context is None: + raise Error( + "Unable to execute tx based queries without transaction." + ) + return await self._tx_context.execute( + query=query, + parameters=parameters, + commit_tx=self._autocommit, + ) + + async def execute( + self, + query: str, + parameters: ParametersType | None = None, + prefetch_first_set: bool = True, + ) -> None: + self._check_cursor_closed() + self._check_pending_query() + if self._tx_context is not None: + self._stream = await self._execute_transactional_query( + query=query, parameters=parameters + ) + else: + self._stream = await self._execute_generic_query( + query=query, parameters=parameters + ) + + if self._stream is None: + return + + self._begin_query() + + if prefetch_first_set: + await self.nextset() + + async def executemany(self) -> None: + pass + + @handle_ydb_errors_async async def nextset(self) -> bool: if self._stream is None: return False @@ -184,12 +402,6 @@ async def finish_query(self) -> None: self._state = CursorStatus.finished - def setinputsizes(self) -> None: - pass - - def setoutputsize(self) -> None: - pass - async def close(self) -> None: if self._closed: return @@ -198,22 +410,6 @@ async def close(self) -> None: self._state = CursorStatus.closed self._closed = True - def _begin_query(self) -> None: - self._state = CursorStatus.running - - def _check_pending_query(self) -> None: - if self._state == CursorStatus.running: - raise ProgrammingError( - "Some records have not been fetched. " - "Fetch the remaining records before executing the next query." - ) - - def _check_cursor_closed(self) -> None: - if self._state == CursorStatus.closed: - raise InterfaceError( - "Could not perform operation: Cursor is closed." - ) - async def __aenter__(self) -> Self: return self diff --git a/ydb_dbapi/utils.py b/ydb_dbapi/utils.py index d11f418..6d7ae35 100644 --- a/ydb_dbapi/utils.py +++ b/ydb_dbapi/utils.py @@ -14,7 +14,7 @@ from .errors import ProgrammingError -def handle_ydb_errors(func: Callable) -> Callable: +def handle_ydb_errors_async(func: Callable) -> Callable: @functools.wraps(func) async def wrapper(*args: tuple, **kwargs: dict) -> Any: try: @@ -51,6 +51,43 @@ async def wrapper(*args: tuple, **kwargs: dict) -> Any: return wrapper +def handle_ydb_errors(func: Callable) -> Callable: + @functools.wraps(func) + def wrapper(*args: tuple, **kwargs: dict) -> Any: + try: + return func(*args, **kwargs) + except (ydb.issues.AlreadyExists, ydb.issues.PreconditionFailed) as e: + raise IntegrityError(e.message, original_error=e) from e + except (ydb.issues.Unsupported, ydb.issues.Unimplemented) as e: + raise NotSupportedError(e.message, original_error=e) from e + except (ydb.issues.BadRequest, ydb.issues.SchemeError) as e: + raise ProgrammingError(e.message, original_error=e) from e + except ( + ydb.issues.TruncatedResponseError, + ydb.issues.ConnectionError, + ydb.issues.Aborted, + ydb.issues.Unavailable, + ydb.issues.Overloaded, + ydb.issues.Undetermined, + ydb.issues.Timeout, + ydb.issues.Cancelled, + ydb.issues.SessionBusy, + ydb.issues.SessionExpired, + ydb.issues.SessionPoolEmpty, + ) as e: + raise OperationalError(e.message, original_error=e) from e + except ydb.issues.GenericError as e: + raise DataError(e.message, original_error=e) from e + except ydb.issues.InternalError as e: + raise InternalError(e.message, original_error=e) from e + except ydb.Error as e: + raise DatabaseError(e.message, original_error=e) from e + except Exception as e: + raise DatabaseError("Failed to execute query") from e + + return wrapper + + class CursorStatus(str, Enum): ready = "ready" running = "running" From b2f0c05003575b5078159c5d2b523aa41ce59377 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:10:32 +0300 Subject: [PATCH 17/33] add concurrent.futures.TimeoutError to connect --- tests/conftest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 2c4eb4b..9528326 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,7 @@ from asyncio import AbstractEventLoop from collections.abc import AsyncGenerator from collections.abc import Generator +from concurrent.futures import TimeoutError from typing import Any from typing import Callable @@ -60,7 +61,7 @@ def get_ydb_host(self) -> str: def get_ydb_port(self) -> str: return self.get_exposed_port(self.port_to_expose) - @wait_container_is_ready(ydb.ConnectionError) + @wait_container_is_ready(ydb.ConnectionError, TimeoutError) def _connect(self) -> None: with ydb.Driver( connection_string=self.get_connection_string() From 5e51764718a72ae070cea9e1ee75f26e23f4843e Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:10:32 +0300 Subject: [PATCH 18/33] refactor cursors --- tests/conftest.py | 45 +--- tests/test_cursor.py | 517 ++++++++++++++------------------------- ydb_dbapi/connections.py | 11 +- ydb_dbapi/cursors.py | 215 ++++++---------- ydb_dbapi/utils.py | 79 +++--- 5 files changed, 317 insertions(+), 550 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 9528326..a908e5c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -21,7 +21,7 @@ class YDBContainer(DbContainer): def __init__( self, name: str | None = None, - port: str = "2135", + port: str = "2136", image: str = "ydbplatform/local-ydb:trunk", **kwargs: Any, ) -> None: @@ -104,12 +104,12 @@ def ydb_container( yield ydb_container -@pytest.fixture +@pytest.fixture(scope="session") def connection_string(ydb_container: YDBContainer) -> str: return ydb_container.get_connection_string() -@pytest.fixture +@pytest.fixture(scope="session") def connection_kwargs(ydb_container: YDBContainer) -> dict: return { "host": ydb_container.get_ydb_host(), @@ -120,12 +120,11 @@ def connection_kwargs(ydb_container: YDBContainer) -> dict: @pytest.fixture async def driver( - ydb_container: YDBContainer, event_loop: AbstractEventLoop + connection_string: str, + event_loop: AbstractEventLoop, ) -> AsyncGenerator[ydb.aio.Driver]: - driver = ydb.aio.Driver( - connection_string=ydb_container.get_connection_string() - ) - await driver.wait(timeout=15, fail_fast=True) + driver = ydb.aio.Driver(connection_string=connection_string) + await driver.wait(timeout=10) yield driver @@ -135,12 +134,10 @@ async def driver( @pytest.fixture def driver_sync( - ydb_container: YDBContainer, + connection_string: str, ) -> Generator[ydb.Driver]: - driver = ydb.Driver( - connection_string=ydb_container.get_connection_string() - ) - driver.wait(timeout=15, fail_fast=True) + driver = ydb.Driver(connection_string=connection_string) + driver.wait(timeout=10) yield driver @@ -187,25 +184,3 @@ def session_pool_sync( """ ) yield session_pool - - -@pytest.fixture -async def session( - session_pool: ydb.aio.QuerySessionPool, -) -> AsyncGenerator[ydb.aio.QuerySession]: - session = await session_pool.acquire() - - yield session - - await session_pool.release(session) - - -@pytest.fixture -def session_sync( - session_pool_sync: ydb.QuerySessionPool, -) -> Generator[ydb.QuerySession]: - session = session_pool_sync.acquire() - - yield session - - session_pool_sync.release(session) diff --git a/tests/test_cursor.py b/tests/test_cursor.py index 7b31ca1..01b5111 100644 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -1,363 +1,220 @@ +from collections.abc import AsyncGenerator +from collections.abc import Generator + import pytest +import ydb import ydb_dbapi -from ydb import QuerySession as QuerySessionSync -from ydb.aio import QuerySession - - -class TestAsyncCursor: - @pytest.mark.asyncio - async def test_cursor_ddl(self, session: QuerySession) -> None: - cursor = ydb_dbapi.AsyncCursor(session=session) - yql = """ - CREATE TABLE table ( - id Int64 NOT NULL, - val Int64, - PRIMARY KEY(id) - ) - """ +INSERT_YQL = """ +DELETE FROM table; +INSERT INTO table (id, val) VALUES +(1, 1), +(2, 2), +(3, 3), +(4, 4) +""" - with pytest.raises(ydb_dbapi.Error): - await cursor.execute(query=yql) - yql = """ - DROP TABLE table - """ - - await cursor.execute(query=yql) - - assert cursor.fetchone() is None - - @pytest.mark.asyncio - async def test_cursor_dml(self, session: QuerySession) -> None: - cursor = ydb_dbapi.AsyncCursor(session=session) - yql_text = """ - INSERT INTO table (id, val) VALUES - (1, 1), - (2, 2), - (3, 3) - """ +@pytest.fixture +async def session( + session_pool: ydb.aio.QuerySessionPool, +) -> AsyncGenerator[ydb.aio.QuerySession]: + await session_pool.execute_with_retries(INSERT_YQL) - await cursor.execute(query=yql_text) - assert cursor.fetchone() is None + session = await session_pool.acquire() + yield session + await session_pool.release(session) - cursor = ydb_dbapi.AsyncCursor(session=session) - yql_text = """ - SELECT COUNT(*) FROM table as sum - """ +@pytest.fixture +def session_sync( + session_pool_sync: ydb.QuerySessionPool, +) -> Generator[ydb.QuerySession]: + session_pool_sync.execute_with_retries(INSERT_YQL) - await cursor.execute(query=yql_text) + session = session_pool_sync.acquire() + yield session + session_pool_sync.release(session) - res = cursor.fetchone() - assert res is not None - assert len(res) == 1 - assert res[0] == 3 +class TestAsyncCursor: @pytest.mark.asyncio - async def test_cursor_fetch_one(self, session: QuerySession) -> None: - cursor = ydb_dbapi.AsyncCursor(session=session) - yql_text = """ - INSERT INTO table (id, val) VALUES - (1, 1), - (2, 2) - """ - - await cursor.execute(query=yql_text) - assert cursor.fetchone() is None - - cursor = ydb_dbapi.AsyncCursor(session=session) - - yql_text = """ - SELECT id, val FROM table - """ - - await cursor.execute(query=yql_text) - - res = cursor.fetchone() - assert res is not None - assert res[0] == 1 - - res = cursor.fetchone() - assert res is not None - assert res[0] == 2 - - assert cursor.fetchone() is None + async def test_cursor_fetch_one( + self, session: ydb.aio.QuerySession + ) -> None: + async with ydb_dbapi.AsyncCursor(session=session) as cursor: + yql_text = """ + SELECT id, val FROM table + """ + await cursor.execute(query=yql_text) + + for i in range(4): + res = await cursor.fetchone() + assert res is not None + assert res[0] == i + 1 + + assert await cursor.fetchone() is None @pytest.mark.asyncio - async def test_cursor_fetch_many(self, session: QuerySession) -> None: - cursor = ydb_dbapi.AsyncCursor(session=session) - yql_text = """ - INSERT INTO table (id, val) VALUES - (1, 1), - (2, 2), - (3, 3), - (4, 4) - """ - - await cursor.execute(query=yql_text) - assert cursor.fetchone() is None - - cursor = ydb_dbapi.AsyncCursor(session=session) - - yql_text = """ - SELECT id, val FROM table - """ - - await cursor.execute(query=yql_text) - - res = cursor.fetchmany() - assert res is not None - assert len(res) == 1 - assert res[0][0] == 1 - - res = cursor.fetchmany(size=2) - assert res is not None - assert len(res) == 2 - assert res[0][0] == 2 - assert res[1][0] == 3 - - res = cursor.fetchmany(size=2) - assert res is not None - assert len(res) == 1 - assert res[0][0] == 4 - - assert cursor.fetchmany(size=2) is None + async def test_cursor_fetch_many( + self, session: ydb.aio.QuerySession + ) -> None: + async with ydb_dbapi.AsyncCursor(session=session) as cursor: + yql_text = """ + SELECT id, val FROM table + """ + await cursor.execute(query=yql_text) + + res = await cursor.fetchmany() + assert res is not None + assert len(res) == 1 + assert res[0][0] == 1 + + res = await cursor.fetchmany(size=2) + assert res is not None + assert len(res) == 2 + assert res[0][0] == 2 + assert res[1][0] == 3 + + res = await cursor.fetchmany(size=2) + assert res is not None + assert len(res) == 1 + assert res[0][0] == 4 + + assert await cursor.fetchmany(size=2) is None @pytest.mark.asyncio - async def test_cursor_fetch_all(self, session: QuerySession) -> None: - cursor = ydb_dbapi.AsyncCursor(session=session) - yql_text = """ - INSERT INTO table (id, val) VALUES - (1, 1), - (2, 2), - (3, 3) - """ - - await cursor.execute(query=yql_text) - assert cursor.fetchone() is None - - cursor = ydb_dbapi.AsyncCursor(session=session) + async def test_cursor_fetch_all( + self, session: ydb.aio.QuerySession + ) -> None: + async with ydb_dbapi.AsyncCursor(session=session) as cursor: + yql_text = """ + SELECT id, val FROM table + """ + await cursor.execute(query=yql_text) - yql_text = """ - SELECT id, val FROM table - """ + assert cursor.rowcount == 4 - await cursor.execute(query=yql_text) + res = await cursor.fetchall() + assert res is not None + assert len(res) == 4 + for i in range(4): + assert res[i][0] == i + 1 - assert cursor.rowcount == 3 - - res = cursor.fetchall() - assert res is not None - assert len(res) == 3 - assert res[0][0] == 1 - assert res[1][0] == 2 - assert res[2][0] == 3 - - assert cursor.fetchall() is None + assert await cursor.fetchall() is None @pytest.mark.asyncio - async def test_cursor_next_set(self, session: QuerySession) -> None: - cursor = ydb_dbapi.AsyncCursor(session=session) - yql_text = """SELECT 1 as val; SELECT 2 as val;""" - - await cursor.execute(query=yql_text) + async def test_cursor_next_set( + self, session: ydb.aio.QuerySession + ) -> None: + async with ydb_dbapi.AsyncCursor(session=session) as cursor: + yql_text = """SELECT 1 as val; SELECT 2 as val;""" + await cursor.execute(query=yql_text) - res = cursor.fetchall() - assert res is not None - assert len(res) == 1 - assert res[0][0] == 1 + res = await cursor.fetchall() + assert res is not None + assert len(res) == 1 + assert res[0][0] == 1 - nextset = await cursor.nextset() - assert nextset + nextset = await cursor.nextset() + assert nextset - res = cursor.fetchall() - assert res is not None - assert len(res) == 1 - assert res[0][0] == 2 + res = await cursor.fetchall() + assert res is not None + assert len(res) == 1 + assert res[0][0] == 2 - nextset = await cursor.nextset() - assert nextset + nextset = await cursor.nextset() + assert nextset - assert cursor.fetchall() is None + assert await cursor.fetchall() is None - nextset = await cursor.nextset() - assert not nextset + nextset = await cursor.nextset() + assert not nextset # The same test class as above but for Cursor class TestCursor: - def test_cursor_ddl(self, session_sync: QuerySessionSync) -> None: - cursor = ydb_dbapi.Cursor(session=session_sync) - - yql = """ - CREATE TABLE table ( - id Int64 NOT NULL, - val Int64, - PRIMARY KEY(id) - ) - """ - - with pytest.raises(ydb_dbapi.Error): - cursor.execute(query=yql) - - yql = """ - DROP TABLE table - """ - - cursor.execute(query=yql) - - assert cursor.fetchone() is None - - def test_cursor_dml(self, session_sync: QuerySessionSync) -> None: - cursor = ydb_dbapi.Cursor(session=session_sync) - yql_text = """ - INSERT INTO table (id, val) VALUES - (1, 1), - (2, 2), - (3, 3) - """ - - cursor.execute(query=yql_text) - assert cursor.fetchone() is None - - cursor = ydb_dbapi.Cursor(session=session_sync) - - yql_text = """ - SELECT COUNT(*) FROM table as sum - """ - - cursor.execute(query=yql_text) - - res = cursor.fetchone() - assert res is not None - assert len(res) == 1 - assert res[0] == 3 - - def test_cursor_fetch_one(self, session_sync: QuerySessionSync) -> None: - cursor = ydb_dbapi.Cursor(session=session_sync) - yql_text = """ - INSERT INTO table (id, val) VALUES - (1, 1), - (2, 2) - """ - - cursor.execute(query=yql_text) - assert cursor.fetchone() is None - - cursor = ydb_dbapi.Cursor(session=session_sync) - - yql_text = """ - SELECT id, val FROM table - """ - - cursor.execute(query=yql_text) - - res = cursor.fetchone() - assert res is not None - assert res[0] == 1 - - res = cursor.fetchone() - assert res is not None - assert res[0] == 2 - - assert cursor.fetchone() is None - - def test_cursor_fetch_many(self, session_sync: QuerySessionSync) -> None: - cursor = ydb_dbapi.Cursor(session=session_sync) - yql_text = """ - INSERT INTO table (id, val) VALUES - (1, 1), - (2, 2), - (3, 3), - (4, 4) - """ - - cursor.execute(query=yql_text) - assert cursor.fetchone() is None - - cursor = ydb_dbapi.Cursor(session=session_sync) - - yql_text = """ - SELECT id, val FROM table - """ - - cursor.execute(query=yql_text) - - res = cursor.fetchmany() - assert res is not None - assert len(res) == 1 - assert res[0][0] == 1 - - res = cursor.fetchmany(size=2) - assert res is not None - assert len(res) == 2 - assert res[0][0] == 2 - assert res[1][0] == 3 - - res = cursor.fetchmany(size=2) - assert res is not None - assert len(res) == 1 - assert res[0][0] == 4 - - assert cursor.fetchmany(size=2) is None - - def test_cursor_fetch_all(self, session_sync: QuerySessionSync) -> None: - cursor = ydb_dbapi.Cursor(session=session_sync) - yql_text = """ - INSERT INTO table (id, val) VALUES - (1, 1), - (2, 2), - (3, 3) - """ - - cursor.execute(query=yql_text) - assert cursor.fetchone() is None - - cursor = ydb_dbapi.Cursor(session=session_sync) - - yql_text = """ - SELECT id, val FROM table - """ - - cursor.execute(query=yql_text) - - assert cursor.rowcount == 3 - - res = cursor.fetchall() - assert res is not None - assert len(res) == 3 - assert res[0][0] == 1 - assert res[1][0] == 2 - assert res[2][0] == 3 - - assert cursor.fetchall() is None - - def test_cursor_next_set(self, session_sync: QuerySessionSync) -> None: - cursor = ydb_dbapi.Cursor(session=session_sync) - yql_text = """SELECT 1 as val; SELECT 2 as val;""" - - cursor.execute(query=yql_text) - - res = cursor.fetchall() - assert res is not None - assert len(res) == 1 - assert res[0][0] == 1 - - nextset = cursor.nextset() - assert nextset - - res = cursor.fetchall() - assert res is not None - assert len(res) == 1 - assert res[0][0] == 2 - - nextset = cursor.nextset() - assert nextset - - assert cursor.fetchall() is None - - nextset = cursor.nextset() - assert not nextset + def test_cursor_fetch_one(self, session_sync: ydb.QuerySession) -> None: + with ydb_dbapi.Cursor(session=session_sync) as cursor: + yql_text = """ + SELECT id, val FROM table + """ + cursor.execute(query=yql_text) + + for i in range(4): + res = cursor.fetchone() + assert res is not None + assert res[0] == i + 1 + + assert cursor.fetchone() is None + + def test_cursor_fetch_many(self, session_sync: ydb.QuerySession) -> None: + with ydb_dbapi.Cursor(session=session_sync) as cursor: + yql_text = """ + SELECT id, val FROM table + """ + cursor.execute(query=yql_text) + + res = cursor.fetchmany() + assert res is not None + assert len(res) == 1 + assert res[0][0] == 1 + + res = cursor.fetchmany(size=2) + assert res is not None + assert len(res) == 2 + assert res[0][0] == 2 + assert res[1][0] == 3 + + res = cursor.fetchmany(size=2) + assert res is not None + assert len(res) == 1 + assert res[0][0] == 4 + + assert cursor.fetchmany(size=2) is None + + def test_cursor_fetch_all(self, session_sync: ydb.QuerySession) -> None: + with ydb_dbapi.Cursor(session=session_sync) as cursor: + yql_text = """ + SELECT id, val FROM table + """ + cursor.execute(query=yql_text) + + assert cursor.rowcount == 4 + + res = cursor.fetchall() + assert res is not None + assert len(res) == 4 + for i in range(4): + assert res[i][0] == i + 1 + + assert cursor.fetchall() is None + + def test_cursor_next_set(self, session_sync: ydb.QuerySession) -> None: + with ydb_dbapi.Cursor(session=session_sync) as cursor: + yql_text = """SELECT 1 as val; SELECT 2 as val;""" + cursor.execute(query=yql_text) + + res = cursor.fetchall() + assert res is not None + assert len(res) == 1 + assert res[0][0] == 1 + + nextset = cursor.nextset() + assert nextset + + res = cursor.fetchall() + assert res is not None + assert len(res) == 1 + assert res[0][0] == 2 + + nextset = cursor.nextset() + assert nextset + + assert cursor.fetchall() is None + + nextset = cursor.nextset() + assert not nextset diff --git a/ydb_dbapi/connections.py b/ydb_dbapi/connections.py index 2ab94a8..1f71e54 100644 --- a/ydb_dbapi/connections.py +++ b/ydb_dbapi/connections.py @@ -14,7 +14,6 @@ from .errors import InternalError from .errors import NotSupportedError from .utils import handle_ydb_errors -from .utils import handle_ydb_errors_async class IsolationLevel: @@ -134,7 +133,7 @@ def get_isolation_level(self) -> str: def cursor(self) -> Cursor: if self._session is None: raise RuntimeError("Connection is not ready, use wait_ready.") - if self._current_cursor and not self._current_cursor._closed: + if self._current_cursor and not self._current_cursor.is_closed: raise RuntimeError( "Unable to create new Cursor before closing existing one." ) @@ -329,7 +328,7 @@ def get_isolation_level(self) -> str: def cursor(self) -> AsyncCursor: if self._session is None: raise RuntimeError("Connection is not ready, use wait_ready.") - if self._current_cursor and not self._current_cursor._closed: + if self._current_cursor and not self._current_cursor.is_closed: raise RuntimeError( "Unable to create new Cursor before closing existing one." ) @@ -369,21 +368,21 @@ async def close(self) -> None: await self._session_pool.stop() await self._driver.stop() - @handle_ydb_errors_async + @handle_ydb_errors async def describe(self, table_path: str) -> ydb.TableSchemeEntry: abs_table_path = posixpath.join( self.database, self.table_path_prefix, table_path ) return await self._driver.table_client.describe_table(abs_table_path) - @handle_ydb_errors_async + @handle_ydb_errors async def check_exists(self, table_path: str) -> bool: abs_table_path = posixpath.join( self.database, self.table_path_prefix, table_path ) return await self._check_path_exists(abs_table_path) - @handle_ydb_errors_async + @handle_ydb_errors async def get_table_names(self) -> list[str]: abs_dir_path = posixpath.join(self.database, self.table_path_prefix) names = await self._get_table_names(abs_dir_path) diff --git a/ydb_dbapi/cursors.py b/ydb_dbapi/cursors.py index a0bed28..d4a6143 100644 --- a/ydb_dbapi/cursors.py +++ b/ydb_dbapi/cursors.py @@ -16,7 +16,6 @@ from .errors import ProgrammingError from .utils import CursorStatus from .utils import handle_ydb_errors -from .utils import handle_ydb_errors_async ParametersType = dict[ str, @@ -32,28 +31,12 @@ def _get_column_type(type_obj: Any) -> str: return str(ydb.convert.type_to_native(type_obj)) -class Cursor: - def __init__( - self, - session: ydb.QuerySession, - tx_context: ydb.QueryTxContext | None = None, - table_path_prefix: str = "", - autocommit: bool = True, - ) -> None: - self.arraysize: int = 1 - self._description: list[tuple] | None = None - - self._session = session - self._tx_context = tx_context - self._table_path_prefix = table_path_prefix - self._autocommit = autocommit - - self._stream: Iterator | None = None - self._rows: Iterator[tuple] | None = None - self._rows_count: int = -1 - - self._closed: bool = False - self._state = CursorStatus.ready +class BaseCursor: + arraysize: int = 1 + _rows: Iterator | None = None + _rows_count: int = -1 + _description: list[tuple] | None = None + _state: CursorStatus = CursorStatus.ready @property def description(self) -> list[tuple] | None: @@ -69,6 +52,18 @@ def setinputsizes(self) -> None: def setoutputsize(self) -> None: pass + def _rows_iterable( + self, result_set: ydb.convert.ResultSet + ) -> Generator[tuple]: + try: + for row in result_set.rows: + # returns tuple to be compatible with SqlAlchemy and because + # of this PEP to return a sequence: + # https://www.python.org/dev/peps/pep-0249/#fetchmany + yield row[::] + except ydb.Error as e: + raise DatabaseError(e.message, original_error=e) from e + def _update_result_set(self, result_set: ydb.convert.ResultSet) -> None: self._update_description(result_set) self._rows = self._rows_iterable(result_set) @@ -88,38 +83,30 @@ def _update_description(self, result_set: ydb.convert.ResultSet) -> None: for col in result_set.columns ] - def _rows_iterable( - self, result_set: ydb.convert.ResultSet - ) -> Generator[tuple]: - try: - for row in result_set.rows: - # returns tuple to be compatible with SqlAlchemy and because - # of this PEP to return a sequence: - # https://www.python.org/dev/peps/pep-0249/#fetchmany - yield row[::] - except ydb.Error as e: - raise DatabaseError(e.message, original_error=e) from e - - def _check_pending_query(self) -> None: + def _raise_if_running(self) -> None: if self._state == CursorStatus.running: raise ProgrammingError( "Some records have not been fetched. " "Fetch the remaining records before executing the next query." ) - def _check_cursor_closed(self) -> None: - if self._state == CursorStatus.closed: + def _raise_if_closed(self) -> None: + if self.is_closed: raise InterfaceError( "Could not perform operation: Cursor is closed." ) + @property + def is_closed(self) -> bool: + return self._state == CursorStatus.closed + def _begin_query(self) -> None: self._state = CursorStatus.running - def fetchone(self) -> tuple | None: + def _fetchone_from_buffer(self) -> tuple | None: return next(self._rows or iter([]), None) - def fetchmany(self, size: int | None = None) -> list | None: + def _fetchmany_from_buffer(self, size: int | None = None) -> list | None: return ( list( itertools.islice( @@ -129,9 +116,34 @@ def fetchmany(self, size: int | None = None) -> list | None: or None ) - def fetchall(self) -> list | None: + def _fetchall_from_buffer(self) -> list | None: return list(self._rows or iter([])) or None + +class Cursor(BaseCursor): + def __init__( + self, + session: ydb.QuerySession, + tx_context: ydb.QueryTxContext | None = None, + table_path_prefix: str = "", + autocommit: bool = True, + ) -> None: + self._session = session + self._tx_context = tx_context + self._table_path_prefix = table_path_prefix + self._autocommit = autocommit + + self._stream: Iterator | None = None + + def fetchone(self) -> tuple | None: + return self._fetchone_from_buffer() + + def fetchmany(self, size: int | None = None) -> list | None: + return self._fetchmany_from_buffer(size) + + def fetchall(self) -> list | None: + return self._fetchall_from_buffer() + @handle_ydb_errors def _execute_generic_query( self, query: str, parameters: ParametersType | None = None @@ -158,8 +170,8 @@ def execute( parameters: ParametersType | None = None, prefetch_first_set: bool = True, ) -> None: - self._check_cursor_closed() - self._check_pending_query() + self._raise_if_closed() + self._raise_if_running() if self._tx_context is not None: self._stream = self._execute_transactional_query( query=query, parameters=parameters @@ -196,10 +208,7 @@ def nextset(self) -> bool: return True def finish_query(self) -> None: - self._check_cursor_closed() - - if self._state != CursorStatus.running: - return + self._raise_if_closed() next_set_available = True while next_set_available: @@ -208,12 +217,11 @@ def finish_query(self) -> None: self._state = CursorStatus.finished def close(self) -> None: - if self._closed: + if self._state == CursorStatus.closed: return self.finish_query() self._state = CursorStatus.closed - self._closed = True def __enter__(self) -> Self: return self @@ -227,7 +235,7 @@ def __exit__( self.close() -class AsyncCursor: +class AsyncCursor(BaseCursor): def __init__( self, session: ydb.aio.QuerySession, @@ -235,105 +243,29 @@ def __init__( table_path_prefix: str = "", autocommit: bool = True, ) -> None: - self.arraysize: int = 1 - self._description: list[tuple] | None = None - self._session = session self._tx_context = tx_context self._table_path_prefix = table_path_prefix self._autocommit = autocommit self._stream: AsyncIterator | None = None - self._rows: Iterator[tuple] | None = None - self._rows_count: int = -1 - self._closed: bool = False - self._state = CursorStatus.ready + async def fetchone(self) -> tuple | None: + return self._fetchone_from_buffer() - @property - def description(self) -> list[tuple] | None: - return self._description + async def fetchmany(self, size: int | None = None) -> list | None: + return self._fetchmany_from_buffer(size) - @property - def rowcount(self) -> int: - return self._rows_count - - def setinputsizes(self) -> None: - pass - - def setoutputsize(self) -> None: - pass - - def _update_result_set(self, result_set: ydb.convert.ResultSet) -> None: - self._update_description(result_set) - self._rows = self._rows_iterable(result_set) - self._rows_count = len(result_set.rows) or -1 - - def _update_description(self, result_set: ydb.convert.ResultSet) -> None: - self._description = [ - ( - col.name, - _get_column_type(col.type), - None, - None, - None, - None, - None, - ) - for col in result_set.columns - ] - - def _rows_iterable( - self, result_set: ydb.convert.ResultSet - ) -> Generator[tuple]: - try: - for row in result_set.rows: - # returns tuple to be compatible with SqlAlchemy and because - # of this PEP to return a sequence: - # https://www.python.org/dev/peps/pep-0249/#fetchmany - yield row[::] - except ydb.Error as e: - raise DatabaseError(e.message, original_error=e) from e - - def _check_pending_query(self) -> None: - if self._state == CursorStatus.running: - raise ProgrammingError( - "Some records have not been fetched. " - "Fetch the remaining records before executing the next query." - ) + async def fetchall(self) -> list | None: + return self._fetchall_from_buffer() - def _check_cursor_closed(self) -> None: - if self._state == CursorStatus.closed: - raise InterfaceError( - "Could not perform operation: Cursor is closed." - ) - - def _begin_query(self) -> None: - self._state = CursorStatus.running - - def fetchone(self) -> tuple | None: - return next(self._rows or iter([]), None) - - def fetchmany(self, size: int | None = None) -> list | None: - return ( - list( - itertools.islice( - self._rows or iter([]), size or self.arraysize - ) - ) - or None - ) - - def fetchall(self) -> list | None: - return list(self._rows or iter([])) or None - - @handle_ydb_errors_async + @handle_ydb_errors async def _execute_generic_query( self, query: str, parameters: ParametersType | None = None ) -> AsyncIterator[ydb.convert.ResultSet]: return await self._session.execute(query=query, parameters=parameters) - @handle_ydb_errors_async + @handle_ydb_errors async def _execute_transactional_query( self, query: str, parameters: ParametersType | None = None ) -> AsyncIterator[ydb.convert.ResultSet]: @@ -353,8 +285,8 @@ async def execute( parameters: ParametersType | None = None, prefetch_first_set: bool = True, ) -> None: - self._check_cursor_closed() - self._check_pending_query() + self._raise_if_closed() + self._raise_if_running() if self._tx_context is not None: self._stream = await self._execute_transactional_query( query=query, parameters=parameters @@ -375,7 +307,7 @@ async def execute( async def executemany(self) -> None: pass - @handle_ydb_errors_async + @handle_ydb_errors async def nextset(self) -> bool: if self._stream is None: return False @@ -383,6 +315,7 @@ async def nextset(self) -> bool: result_set = await self._stream.__anext__() self._update_result_set(result_set) except (StopIteration, StopAsyncIteration, RuntimeError): + self._stream = None self._state = CursorStatus.finished return False except ydb.Error: @@ -391,10 +324,7 @@ async def nextset(self) -> bool: return True async def finish_query(self) -> None: - self._check_cursor_closed() - - if self._state != CursorStatus.running: - return + self._raise_if_closed() next_set_available = True while next_set_available: @@ -403,12 +333,11 @@ async def finish_query(self) -> None: self._state = CursorStatus.finished async def close(self) -> None: - if self._closed: + if self._state == CursorStatus.closed: return await self.finish_query() self._state = CursorStatus.closed - self._closed = True async def __aenter__(self) -> Self: return self diff --git a/ydb_dbapi/utils.py b/ydb_dbapi/utils.py index 6d7ae35..b69b58a 100644 --- a/ydb_dbapi/utils.py +++ b/ydb_dbapi/utils.py @@ -1,5 +1,6 @@ import functools from enum import Enum +from inspect import iscoroutinefunction from typing import Any from typing import Callable @@ -14,49 +15,55 @@ from .errors import ProgrammingError -def handle_ydb_errors_async(func: Callable) -> Callable: - @functools.wraps(func) - async def wrapper(*args: tuple, **kwargs: dict) -> Any: - try: - return await func(*args, **kwargs) - except (ydb.issues.AlreadyExists, ydb.issues.PreconditionFailed) as e: - raise IntegrityError(e.message, original_error=e) from e - except (ydb.issues.Unsupported, ydb.issues.Unimplemented) as e: - raise NotSupportedError(e.message, original_error=e) from e - except (ydb.issues.BadRequest, ydb.issues.SchemeError) as e: - raise ProgrammingError(e.message, original_error=e) from e - except ( - ydb.issues.TruncatedResponseError, - ydb.issues.ConnectionError, - ydb.issues.Aborted, - ydb.issues.Unavailable, - ydb.issues.Overloaded, - ydb.issues.Undetermined, - ydb.issues.Timeout, - ydb.issues.Cancelled, - ydb.issues.SessionBusy, - ydb.issues.SessionExpired, - ydb.issues.SessionPoolEmpty, - ) as e: - raise OperationalError(e.message, original_error=e) from e - except ydb.issues.GenericError as e: - raise DataError(e.message, original_error=e) from e - except ydb.issues.InternalError as e: - raise InternalError(e.message, original_error=e) from e - except ydb.Error as e: - raise DatabaseError(e.message, original_error=e) from e - except Exception as e: - raise DatabaseError("Failed to execute query") from e +def handle_ydb_errors(func: Callable) -> Callable: # noqa: C901 + if iscoroutinefunction(func): - return wrapper + @functools.wraps(func) + async def awrapper(*args: tuple, **kwargs: dict) -> Any: + try: + return await func(*args, **kwargs) + except ( + ydb.issues.AlreadyExists, + ydb.issues.PreconditionFailed, + ) as e: + raise IntegrityError(e.message, original_error=e) from e + except (ydb.issues.Unsupported, ydb.issues.Unimplemented) as e: + raise NotSupportedError(e.message, original_error=e) from e + except (ydb.issues.BadRequest, ydb.issues.SchemeError) as e: + raise ProgrammingError(e.message, original_error=e) from e + except ( + ydb.issues.TruncatedResponseError, + ydb.issues.ConnectionError, + ydb.issues.Aborted, + ydb.issues.Unavailable, + ydb.issues.Overloaded, + ydb.issues.Undetermined, + ydb.issues.Timeout, + ydb.issues.Cancelled, + ydb.issues.SessionBusy, + ydb.issues.SessionExpired, + ydb.issues.SessionPoolEmpty, + ) as e: + raise OperationalError(e.message, original_error=e) from e + except ydb.issues.GenericError as e: + raise DataError(e.message, original_error=e) from e + except ydb.issues.InternalError as e: + raise InternalError(e.message, original_error=e) from e + except ydb.Error as e: + raise DatabaseError(e.message, original_error=e) from e + except Exception as e: + raise DatabaseError("Failed to execute query") from e + return awrapper -def handle_ydb_errors(func: Callable) -> Callable: @functools.wraps(func) def wrapper(*args: tuple, **kwargs: dict) -> Any: try: return func(*args, **kwargs) - except (ydb.issues.AlreadyExists, ydb.issues.PreconditionFailed) as e: + except ( + ydb.issues.AlreadyExists, + ydb.issues.PreconditionFailed, + ) as e: raise IntegrityError(e.message, original_error=e) from e except (ydb.issues.Unsupported, ydb.issues.Unimplemented) as e: raise NotSupportedError(e.message, original_error=e) from e From aa9601468e92949ef0c37c7838cfad7152bd3e53 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:10:32 +0300 Subject: [PATCH 19/33] small refactor connections --- ydb_dbapi/connections.py | 160 ++++++++++++++------------------------- 1 file changed, 58 insertions(+), 102 deletions(-) diff --git a/ydb_dbapi/connections.py b/ydb_dbapi/connections.py index 1f71e54..a172bb5 100644 --- a/ydb_dbapi/connections.py +++ b/ydb_dbapi/connections.py @@ -25,7 +25,63 @@ class IsolationLevel: AUTOCOMMIT = "AUTOCOMMIT" -class Connection: +class BaseConnection: + _tx_mode: ydb.BaseQueryTxMode = ydb.QuerySerializableReadWrite() + _tx_context: ydb.QueryTxContext | ydb.aio.QueryTxContext | None = None + interactive_transaction: bool = False + + def set_isolation_level(self, isolation_level: str) -> None: + class IsolationSettings(NamedTuple): + ydb_mode: ydb.BaseQueryTxMode + interactive: bool + + ydb_isolation_settings_map = { + IsolationLevel.AUTOCOMMIT: IsolationSettings( + ydb.QuerySerializableReadWrite(), interactive=False + ), + IsolationLevel.SERIALIZABLE: IsolationSettings( + ydb.QuerySerializableReadWrite(), interactive=True + ), + IsolationLevel.ONLINE_READONLY: IsolationSettings( + ydb.QueryOnlineReadOnly(), interactive=True + ), + IsolationLevel.ONLINE_READONLY_INCONSISTENT: IsolationSettings( + ydb.QueryOnlineReadOnly().with_allow_inconsistent_reads(), + interactive=True, + ), + IsolationLevel.STALE_READONLY: IsolationSettings( + ydb.QueryStaleReadOnly(), interactive=True + ), + IsolationLevel.SNAPSHOT_READONLY: IsolationSettings( + ydb.QuerySnapshotReadOnly(), interactive=True + ), + } + ydb_isolation_settings = ydb_isolation_settings_map[isolation_level] + if self._tx_context and self._tx_context.tx_id: + raise InternalError( + "Failed to set transaction mode: transaction is already began" + ) + self._tx_mode = ydb_isolation_settings.ydb_mode + self.interactive_transaction = ydb_isolation_settings.interactive + + def get_isolation_level(self) -> str: + if self._tx_mode.name == ydb.QuerySerializableReadWrite().name: + if self.interactive_transaction: + return IsolationLevel.SERIALIZABLE + return IsolationLevel.AUTOCOMMIT + if self._tx_mode.name == ydb.QueryOnlineReadOnly().name: + if self._tx_mode.settings.allow_inconsistent_reads: + return IsolationLevel.ONLINE_READONLY_INCONSISTENT + return IsolationLevel.ONLINE_READONLY + if self._tx_mode.name == ydb.QueryStaleReadOnly().name: + return IsolationLevel.STALE_READONLY + if self._tx_mode.name == ydb.QuerySnapshotReadOnly().name: + return IsolationLevel.SNAPSHOT_READONLY + msg = f"{self._tx_mode.name} is not supported" + raise NotSupportedError(msg) + + +class Connection(BaseConnection): def __init__( self, host: str = "", @@ -80,56 +136,6 @@ def wait_ready(self, timeout: int = 10) -> None: self._session = self._session_pool.acquire() - def set_isolation_level(self, isolation_level: str) -> None: - class IsolationSettings(NamedTuple): - ydb_mode: ydb.BaseQueryTxMode - interactive: bool - - ydb_isolation_settings_map = { - IsolationLevel.AUTOCOMMIT: IsolationSettings( - ydb.QuerySerializableReadWrite(), interactive=False - ), - IsolationLevel.SERIALIZABLE: IsolationSettings( - ydb.QuerySerializableReadWrite(), interactive=True - ), - IsolationLevel.ONLINE_READONLY: IsolationSettings( - ydb.QueryOnlineReadOnly(), interactive=True - ), - IsolationLevel.ONLINE_READONLY_INCONSISTENT: IsolationSettings( - ydb.QueryOnlineReadOnly().with_allow_inconsistent_reads(), - interactive=True, - ), - IsolationLevel.STALE_READONLY: IsolationSettings( - ydb.QueryStaleReadOnly(), interactive=True - ), - IsolationLevel.SNAPSHOT_READONLY: IsolationSettings( - ydb.QuerySnapshotReadOnly(), interactive=True - ), - } - ydb_isolation_settings = ydb_isolation_settings_map[isolation_level] - if self._tx_context and self._tx_context.tx_id: - raise InternalError( - "Failed to set transaction mode: transaction is already began" - ) - self._tx_mode = ydb_isolation_settings.ydb_mode - self.interactive_transaction = ydb_isolation_settings.interactive - - def get_isolation_level(self) -> str: - if self._tx_mode.name == ydb.QuerySerializableReadWrite().name: - if self.interactive_transaction: - return IsolationLevel.SERIALIZABLE - return IsolationLevel.AUTOCOMMIT - if self._tx_mode.name == ydb.QueryOnlineReadOnly().name: - if self._tx_mode.settings.allow_inconsistent_reads: - return IsolationLevel.ONLINE_READONLY_INCONSISTENT - return IsolationLevel.ONLINE_READONLY - if self._tx_mode.name == ydb.QueryStaleReadOnly().name: - return IsolationLevel.STALE_READONLY - if self._tx_mode.name == ydb.QuerySnapshotReadOnly().name: - return IsolationLevel.SNAPSHOT_READONLY - msg = f"{self._tx_mode.name} is not supported" - raise NotSupportedError(msg) - def cursor(self) -> Cursor: if self._session is None: raise RuntimeError("Connection is not ready, use wait_ready.") @@ -220,7 +226,7 @@ def callee() -> ydb.Directory: return result -class AsyncConnection: +class AsyncConnection(BaseConnection): def __init__( self, host: str = "", @@ -275,56 +281,6 @@ async def wait_ready(self, timeout: int = 10) -> None: self._session = await self._session_pool.acquire() - def set_isolation_level(self, isolation_level: str) -> None: - class IsolationSettings(NamedTuple): - ydb_mode: ydb.BaseQueryTxMode - interactive: bool - - ydb_isolation_settings_map = { - IsolationLevel.AUTOCOMMIT: IsolationSettings( - ydb.QuerySerializableReadWrite(), interactive=False - ), - IsolationLevel.SERIALIZABLE: IsolationSettings( - ydb.QuerySerializableReadWrite(), interactive=True - ), - IsolationLevel.ONLINE_READONLY: IsolationSettings( - ydb.QueryOnlineReadOnly(), interactive=True - ), - IsolationLevel.ONLINE_READONLY_INCONSISTENT: IsolationSettings( - ydb.QueryOnlineReadOnly().with_allow_inconsistent_reads(), - interactive=True, - ), - IsolationLevel.STALE_READONLY: IsolationSettings( - ydb.QueryStaleReadOnly(), interactive=True - ), - IsolationLevel.SNAPSHOT_READONLY: IsolationSettings( - ydb.QuerySnapshotReadOnly(), interactive=True - ), - } - ydb_isolation_settings = ydb_isolation_settings_map[isolation_level] - if self._tx_context and self._tx_context.tx_id: - raise InternalError( - "Failed to set transaction mode: transaction is already began" - ) - self._tx_mode = ydb_isolation_settings.ydb_mode - self.interactive_transaction = ydb_isolation_settings.interactive - - def get_isolation_level(self) -> str: - if self._tx_mode.name == ydb.QuerySerializableReadWrite().name: - if self.interactive_transaction: - return IsolationLevel.SERIALIZABLE - return IsolationLevel.AUTOCOMMIT - if self._tx_mode.name == ydb.QueryOnlineReadOnly().name: - if self._tx_mode.settings.allow_inconsistent_reads: - return IsolationLevel.ONLINE_READONLY_INCONSISTENT - return IsolationLevel.ONLINE_READONLY - if self._tx_mode.name == ydb.QueryStaleReadOnly().name: - return IsolationLevel.STALE_READONLY - if self._tx_mode.name == ydb.QuerySnapshotReadOnly().name: - return IsolationLevel.SNAPSHOT_READONLY - msg = f"{self._tx_mode.name} is not supported" - raise NotSupportedError(msg) - def cursor(self) -> AsyncCursor: if self._session is None: raise RuntimeError("Connection is not ready, use wait_ready.") From e194e4baa39920fa639862ec4e8b6ecb0f29aaef Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:10:32 +0300 Subject: [PATCH 20/33] continue to move common parts from connections --- ydb_dbapi/connections.py | 136 ++++++++++++++++++--------------------- 1 file changed, 61 insertions(+), 75 deletions(-) diff --git a/ydb_dbapi/connections.py b/ydb_dbapi/connections.py index a172bb5..499cd45 100644 --- a/ydb_dbapi/connections.py +++ b/ydb_dbapi/connections.py @@ -1,10 +1,12 @@ from __future__ import annotations import posixpath -from typing import Any from typing import NamedTuple +from typing import TypedDict import ydb +from typing_extensions import NotRequired +from typing_extensions import Unpack from ydb.retries import retry_operation_async from ydb.retries import retry_operation_sync @@ -25,11 +27,59 @@ class IsolationLevel: AUTOCOMMIT = "AUTOCOMMIT" +class ConnectionKwargs(TypedDict): + credentials: NotRequired[ydb.AbstractCredentials] + ydb_table_path_prefix: NotRequired[str] + ydb_session_pool: NotRequired[ + ydb.QuerySessionPool | ydb.aio.QuerySessionPool + ] + + class BaseConnection: _tx_mode: ydb.BaseQueryTxMode = ydb.QuerySerializableReadWrite() _tx_context: ydb.QueryTxContext | ydb.aio.QueryTxContext | None = None interactive_transaction: bool = False + _shared_session_pool: bool = False + _driver_cls = ydb.Driver + _driver: ydb.Driver | ydb.aio.Driver + _pool_cls = ydb.QuerySessionPool + _pool: ydb.QuerySessionPool | ydb.aio.QuerySessionPool + + _current_cursor: AsyncCursor | Cursor | None = None + + def __init__( + self, + host: str = "", + port: str = "", + database: str = "", + **conn_kwargs: Unpack[ConnectionKwargs], + ) -> None: + self.endpoint = f"grpc://{host}:{port}" + self.database = database + self.conn_kwargs = conn_kwargs + self.credentials = self.conn_kwargs.pop("credentials", None) + self.table_path_prefix = self.conn_kwargs.pop( + "ydb_table_path_prefix", "" + ) + + if ( + "ydb_session_pool" in self.conn_kwargs + ): # Use session pool managed manually + self._shared_session_pool = True + self._session_pool = self.conn_kwargs.pop("ydb_session_pool") + self._driver = self._session_pool._driver + else: + driver_config = ydb.DriverConfig( + endpoint=self.endpoint, + database=self.database, + credentials=self.credentials, + ) + self._driver = self._driver_cls(driver_config) + self._session_pool = self._pool_cls(self._driver, size=5) + + self._session: ydb.QuerySession | ydb.aio.QuerySession | None = None + def set_isolation_level(self, isolation_level: str) -> None: class IsolationSettings(NamedTuple): ydb_mode: ydb.BaseQueryTxMode @@ -82,44 +132,12 @@ def get_isolation_level(self) -> str: class Connection(BaseConnection): - def __init__( - self, - host: str = "", - port: str = "", - database: str = "", - **conn_kwargs: Any, - ) -> None: - self.endpoint = f"grpc://{host}:{port}" - self.database = database - self.conn_kwargs = conn_kwargs - self.credentials = self.conn_kwargs.pop("credentials", None) - self.table_path_prefix = self.conn_kwargs.pop( - "ydb_table_path_prefix", "" - ) + _driver_cls = ydb.Driver + _pool_cls = ydb.QuerySessionPool - if ( - "ydb_session_pool" in self.conn_kwargs - ): # Use session pool managed manually - self._shared_session_pool = True - self._session_pool = self.conn_kwargs.pop("ydb_session_pool") - self._driver: ydb.Driver = self._session_pool._driver - else: - self._shared_session_pool = False - driver_config = ydb.DriverConfig( - endpoint=self.endpoint, - database=self.database, - credentials=self.credentials, - ) - self._driver = ydb.Driver(driver_config) - self._session_pool = ydb.QuerySessionPool(self._driver, size=5) - - self._tx_mode: ydb.BaseQueryTxMode = ydb.QuerySerializableReadWrite() - - self._current_cursor: Cursor | None = None - self.interactive_transaction: bool = False - - self._session: ydb.QuerySession | None = None - self._tx_context: ydb.QueryTxContext | None = None + _driver: ydb.Driver + _pool: ydb.QuerySessionPool + _current_cursor: Cursor | None = None def wait_ready(self, timeout: int = 10) -> None: try: @@ -227,44 +245,12 @@ def callee() -> ydb.Directory: class AsyncConnection(BaseConnection): - def __init__( - self, - host: str = "", - port: str = "", - database: str = "", - **conn_kwargs: Any, - ) -> None: - self.endpoint = f"grpc://{host}:{port}" - self.database = database - self.conn_kwargs = conn_kwargs - self.credentials = self.conn_kwargs.pop("credentials", None) - self.table_path_prefix = self.conn_kwargs.pop( - "ydb_table_path_prefix", "" - ) - - if ( - "ydb_session_pool" in self.conn_kwargs - ): # Use session pool managed manually - self._shared_session_pool = True - self._session_pool = self.conn_kwargs.pop("ydb_session_pool") - self._driver: ydb.aio.Driver = self._session_pool._driver - else: - self._shared_session_pool = False - driver_config = ydb.DriverConfig( - endpoint=self.endpoint, - database=self.database, - credentials=self.credentials, - ) - self._driver = ydb.aio.Driver(driver_config) - self._session_pool = ydb.aio.QuerySessionPool(self._driver, size=5) - - self._tx_mode: ydb.BaseQueryTxMode = ydb.QuerySerializableReadWrite() - - self._current_cursor: AsyncCursor | None = None - self.interactive_transaction: bool = False + _driver_cls = ydb.aio.Driver + _pool_cls = ydb.aio.QuerySessionPool - self._session: ydb.aio.QuerySession | None = None - self._tx_context: ydb.aio.QueryTxContext | None = None + _driver: ydb.aio.Driver + _pool: ydb.aio.QuerySessionPool + _current_cursor: AsyncCursor | None = None async def wait_ready(self, timeout: int = 10) -> None: try: From cf536610af294178a713e7e29d7b05a0c385900c Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:10:32 +0300 Subject: [PATCH 21/33] one more method to common interface --- ydb_dbapi/connections.py | 68 +++++++++++++++------------------------- 1 file changed, 26 insertions(+), 42 deletions(-) diff --git a/ydb_dbapi/connections.py b/ydb_dbapi/connections.py index 499cd45..10312da 100644 --- a/ydb_dbapi/connections.py +++ b/ydb_dbapi/connections.py @@ -39,11 +39,13 @@ class BaseConnection: _tx_mode: ydb.BaseQueryTxMode = ydb.QuerySerializableReadWrite() _tx_context: ydb.QueryTxContext | ydb.aio.QueryTxContext | None = None interactive_transaction: bool = False - _shared_session_pool: bool = False + _driver_cls = ydb.Driver - _driver: ydb.Driver | ydb.aio.Driver _pool_cls = ydb.QuerySessionPool + _cursor_cls: type[Cursor | AsyncCursor] = Cursor + + _driver: ydb.Driver | ydb.aio.Driver _pool: ydb.QuerySessionPool | ydb.aio.QuerySessionPool _current_cursor: AsyncCursor | Cursor | None = None @@ -130,10 +132,31 @@ def get_isolation_level(self) -> str: msg = f"{self._tx_mode.name} is not supported" raise NotSupportedError(msg) + def cursor(self) -> Cursor | AsyncCursor: + if self._session is None: + raise RuntimeError("Connection is not ready, use wait_ready.") + if self._current_cursor and not self._current_cursor.is_closed: + raise RuntimeError( + "Unable to create new Cursor before closing existing one." + ) + + if self.interactive_transaction: + self._tx_context = self._session.transaction(self._tx_mode) + else: + self._tx_context = None + + self._current_cursor = self._cursor_cls( + session=self._session, + tx_context=self._tx_context, + autocommit=(not self.interactive_transaction), + ) + return self._current_cursor + class Connection(BaseConnection): _driver_cls = ydb.Driver _pool_cls = ydb.QuerySessionPool + _cursor_cls = Cursor _driver: ydb.Driver _pool: ydb.QuerySessionPool @@ -154,26 +177,6 @@ def wait_ready(self, timeout: int = 10) -> None: self._session = self._session_pool.acquire() - def cursor(self) -> Cursor: - if self._session is None: - raise RuntimeError("Connection is not ready, use wait_ready.") - if self._current_cursor and not self._current_cursor.is_closed: - raise RuntimeError( - "Unable to create new Cursor before closing existing one." - ) - - if self.interactive_transaction: - self._tx_context = self._session.transaction(self._tx_mode) - else: - self._tx_context = None - - self._current_cursor = Cursor( - session=self._session, - tx_context=self._tx_context, - autocommit=(not self.interactive_transaction), - ) - return self._current_cursor - def commit(self) -> None: if self._tx_context and self._tx_context.tx_id: self._tx_context.commit() @@ -247,6 +250,7 @@ def callee() -> ydb.Directory: class AsyncConnection(BaseConnection): _driver_cls = ydb.aio.Driver _pool_cls = ydb.aio.QuerySessionPool + _cursor_cls = AsyncCursor _driver: ydb.aio.Driver _pool: ydb.aio.QuerySessionPool @@ -267,26 +271,6 @@ async def wait_ready(self, timeout: int = 10) -> None: self._session = await self._session_pool.acquire() - def cursor(self) -> AsyncCursor: - if self._session is None: - raise RuntimeError("Connection is not ready, use wait_ready.") - if self._current_cursor and not self._current_cursor.is_closed: - raise RuntimeError( - "Unable to create new Cursor before closing existing one." - ) - - if self.interactive_transaction: - self._tx_context = self._session.transaction(self._tx_mode) - else: - self._tx_context = None - - self._current_cursor = AsyncCursor( - session=self._session, - tx_context=self._tx_context, - autocommit=(not self.interactive_transaction), - ) - return self._current_cursor - async def commit(self) -> None: if self._tx_context and self._tx_context.tx_id: await self._tx_context.commit() From d36e60d90be9e776a243f0eabe6c9cbd068635b3 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:10:32 +0300 Subject: [PATCH 22/33] Enable autoscroll for fetchone --- tests/test_cursor.py | 15 +++++++++++++++ ydb_dbapi/cursors.py | 15 ++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/tests/test_cursor.py b/tests/test_cursor.py index 01b5111..6db6f3e 100644 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -218,3 +218,18 @@ def test_cursor_next_set(self, session_sync: ydb.QuerySession) -> None: nextset = cursor.nextset() assert not nextset + + def test_cursor_autoscroll(self, session_sync: ydb.QuerySession) -> None: + with ydb_dbapi.Cursor( + session=session_sync, auto_scroll_result_sets=True + ) as cursor: + yql_text = "SELECT 1 as val; SELECT 2 as val; SELECT 3 as val;" + cursor.execute(query=yql_text) + + for i in range(3): + res = cursor.fetchone() + assert res is not None + assert res[0] == i + 1 + + assert cursor.fetchone() is None + assert not cursor.nextset() diff --git a/ydb_dbapi/cursors.py b/ydb_dbapi/cursors.py index d4a6143..1839cc8 100644 --- a/ydb_dbapi/cursors.py +++ b/ydb_dbapi/cursors.py @@ -127,16 +127,29 @@ def __init__( tx_context: ydb.QueryTxContext | None = None, table_path_prefix: str = "", autocommit: bool = True, + auto_scroll_result_sets: bool = False, ) -> None: self._session = session self._tx_context = tx_context self._table_path_prefix = table_path_prefix self._autocommit = autocommit + self._auto_scroll = auto_scroll_result_sets self._stream: Iterator | None = None def fetchone(self) -> tuple | None: - return self._fetchone_from_buffer() + row = self._fetchone_from_buffer() + if not self._auto_scroll: + return row + + if row is None: + while self.nextset(): + # We should skip empty result sets + row = self._fetchone_from_buffer() + if row is not None: + return row + + return row def fetchmany(self, size: int | None = None) -> list | None: return self._fetchmany_from_buffer(size) From ad31eb577c91aa99fe542ea0d4a60e2173067190 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:10:32 +0300 Subject: [PATCH 23/33] implement autoscroll for all fetches --- tests/conftest.py | 41 ++++----- tests/test_cursor.py | 203 +++++++++++++++++++++++++++++++++++-------- ydb_dbapi/cursors.py | 79 +++++++++++++---- 3 files changed, 247 insertions(+), 76 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index a908e5c..d3eb051 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -151,18 +151,17 @@ async def session_pool( ) -> AsyncGenerator[ydb.aio.QuerySessionPool]: session_pool = ydb.aio.QuerySessionPool(driver) async with session_pool: - await session_pool.execute_with_retries( - """DROP TABLE IF EXISTS table""" - ) - await session_pool.execute_with_retries( - """ - CREATE TABLE table ( - id Int64 NOT NULL, - val Int64, - PRIMARY KEY(id) + for name in ["table", "table1", "table2"]: + await session_pool.execute_with_retries( + f""" + DROP TABLE IF EXISTS {name}; + CREATE TABLE {name} ( + id Int64 NOT NULL, + val Int64, + PRIMARY KEY(id) + ) + """ ) - """ - ) yield session_pool @@ -173,14 +172,16 @@ def session_pool_sync( ) -> Generator[ydb.QuerySessionPool]: session_pool = ydb.QuerySessionPool(driver_sync) with session_pool: - session_pool.execute_with_retries("""DROP TABLE IF EXISTS table""") - session_pool.execute_with_retries( - """ - CREATE TABLE table ( - id Int64 NOT NULL, - val Int64, - PRIMARY KEY(id) + for name in ["table", "table1", "table2"]: + session_pool.execute_with_retries( + f""" + DROP TABLE IF EXISTS {name}; + CREATE TABLE {name} ( + id Int64 NOT NULL, + val Int64, + PRIMARY KEY(id) + ) + """ ) - """ - ) + yield session_pool diff --git a/tests/test_cursor.py b/tests/test_cursor.py index 6db6f3e..0a96e08 100644 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -5,21 +5,22 @@ import ydb import ydb_dbapi -INSERT_YQL = """ -DELETE FROM table; -INSERT INTO table (id, val) VALUES -(1, 1), -(2, 2), -(3, 3), -(4, 4) -""" - @pytest.fixture async def session( session_pool: ydb.aio.QuerySessionPool, ) -> AsyncGenerator[ydb.aio.QuerySession]: - await session_pool.execute_with_retries(INSERT_YQL) + for name in ["table", "table1", "table2"]: + await session_pool.execute_with_retries( + f""" + DELETE FROM {name}; + INSERT INTO {name} (id, val) VALUES + (0, 0), + (1, 1), + (2, 2), + (3, 3) + """ + ) session = await session_pool.acquire() yield session @@ -30,13 +31,27 @@ async def session( def session_sync( session_pool_sync: ydb.QuerySessionPool, ) -> Generator[ydb.QuerySession]: - session_pool_sync.execute_with_retries(INSERT_YQL) + for name in ["table", "table1", "table2"]: + session_pool_sync.execute_with_retries( + f""" + DELETE FROM {name}; + INSERT INTO {name} (id, val) VALUES + (0, 0), + (1, 1), + (2, 2), + (3, 3) + """ + ) session = session_pool_sync.acquire() yield session session_pool_sync.release(session) +RESULT_SET_LENGTH = 4 +RESULT_SET_COUNT = 3 + + class TestAsyncCursor: @pytest.mark.asyncio async def test_cursor_fetch_one( @@ -48,10 +63,10 @@ async def test_cursor_fetch_one( """ await cursor.execute(query=yql_text) - for i in range(4): + for i in range(RESULT_SET_LENGTH): res = await cursor.fetchone() assert res is not None - assert res[0] == i + 1 + assert res[0] == i assert await cursor.fetchone() is None @@ -68,20 +83,20 @@ async def test_cursor_fetch_many( res = await cursor.fetchmany() assert res is not None assert len(res) == 1 - assert res[0][0] == 1 + assert res[0][0] == 0 res = await cursor.fetchmany(size=2) assert res is not None assert len(res) == 2 - assert res[0][0] == 2 - assert res[1][0] == 3 + assert res[0][0] == 1 + assert res[1][0] == 2 res = await cursor.fetchmany(size=2) assert res is not None assert len(res) == 1 - assert res[0][0] == 4 + assert res[0][0] == 3 - assert await cursor.fetchmany(size=2) is None + assert await cursor.fetchmany(size=2) == [] @pytest.mark.asyncio async def test_cursor_fetch_all( @@ -93,15 +108,15 @@ async def test_cursor_fetch_all( """ await cursor.execute(query=yql_text) - assert cursor.rowcount == 4 + assert cursor.rowcount == RESULT_SET_LENGTH res = await cursor.fetchall() assert res is not None - assert len(res) == 4 - for i in range(4): - assert res[i][0] == i + 1 + assert len(res) == RESULT_SET_LENGTH + for i in range(RESULT_SET_LENGTH): + assert res[i][0] == i - assert await cursor.fetchall() is None + assert await cursor.fetchall() == [] @pytest.mark.asyncio async def test_cursor_next_set( @@ -127,11 +142,77 @@ async def test_cursor_next_set( nextset = await cursor.nextset() assert nextset - assert await cursor.fetchall() is None + assert await cursor.fetchall() == [] nextset = await cursor.nextset() assert not nextset + @pytest.mark.asyncio + async def test_cursor_fetch_one_autoscroll( + self, session: ydb.aio.QuerySession + ) -> None: + async with ydb_dbapi.AsyncCursor( + session=session, auto_scroll_result_sets=True + ) as cursor: + yql_text = """ + SELECT id, val FROM table; + SELECT id, val FROM table1; + SELECT id, val FROM table2; + """ + await cursor.execute(query=yql_text) + + for i in range(RESULT_SET_LENGTH * RESULT_SET_COUNT): + res = await cursor.fetchone() + assert res is not None + assert res[0] == i % RESULT_SET_LENGTH + + assert await cursor.fetchone() is None + assert not await cursor.nextset() + + @pytest.mark.asyncio + async def test_cursor_fetch_many_autoscroll( + self, session: ydb.aio.QuerySession + ) -> None: + async with ydb_dbapi.AsyncCursor( + session=session, auto_scroll_result_sets=True + ) as cursor: + yql_text = """ + SELECT id, val FROM table; + SELECT id, val FROM table1; + SELECT id, val FROM table2; + """ + await cursor.execute(query=yql_text) + + halfsize = (RESULT_SET_LENGTH * RESULT_SET_COUNT) // 2 + for _ in range(2): + res = await cursor.fetchmany(size=halfsize) + assert res is not None + assert len(res) == halfsize + + assert await cursor.fetchmany(2) == [] + assert not await cursor.nextset() + + @pytest.mark.asyncio + async def test_cursor_fetch_all_autoscroll( + self, session: ydb.aio.QuerySession + ) -> None: + async with ydb_dbapi.AsyncCursor( + session=session, auto_scroll_result_sets=True + ) as cursor: + yql_text = """ + SELECT id, val FROM table; + SELECT id, val FROM table1; + SELECT id, val FROM table2; + """ + await cursor.execute(query=yql_text) + + res = await cursor.fetchall() + + assert len(res) == RESULT_SET_COUNT * RESULT_SET_LENGTH + + assert await cursor.fetchall() == [] + assert not await cursor.nextset() + # The same test class as above but for Cursor @@ -147,7 +228,7 @@ def test_cursor_fetch_one(self, session_sync: ydb.QuerySession) -> None: for i in range(4): res = cursor.fetchone() assert res is not None - assert res[0] == i + 1 + assert res[0] == i assert cursor.fetchone() is None @@ -161,20 +242,20 @@ def test_cursor_fetch_many(self, session_sync: ydb.QuerySession) -> None: res = cursor.fetchmany() assert res is not None assert len(res) == 1 - assert res[0][0] == 1 + assert res[0][0] == 0 res = cursor.fetchmany(size=2) assert res is not None assert len(res) == 2 - assert res[0][0] == 2 - assert res[1][0] == 3 + assert res[0][0] == 1 + assert res[1][0] == 2 res = cursor.fetchmany(size=2) assert res is not None assert len(res) == 1 - assert res[0][0] == 4 + assert res[0][0] == 3 - assert cursor.fetchmany(size=2) is None + assert cursor.fetchmany(size=2) == [] def test_cursor_fetch_all(self, session_sync: ydb.QuerySession) -> None: with ydb_dbapi.Cursor(session=session_sync) as cursor: @@ -189,9 +270,9 @@ def test_cursor_fetch_all(self, session_sync: ydb.QuerySession) -> None: assert res is not None assert len(res) == 4 for i in range(4): - assert res[i][0] == i + 1 + assert res[i][0] == i - assert cursor.fetchall() is None + assert cursor.fetchall() == [] def test_cursor_next_set(self, session_sync: ydb.QuerySession) -> None: with ydb_dbapi.Cursor(session=session_sync) as cursor: @@ -214,22 +295,70 @@ def test_cursor_next_set(self, session_sync: ydb.QuerySession) -> None: nextset = cursor.nextset() assert nextset - assert cursor.fetchall() is None + assert cursor.fetchall() == [] nextset = cursor.nextset() assert not nextset - def test_cursor_autoscroll(self, session_sync: ydb.QuerySession) -> None: + def test_cursor_fetch_one_autoscroll( + self, session_sync: ydb.QuerySession + ) -> None: with ydb_dbapi.Cursor( session=session_sync, auto_scroll_result_sets=True ) as cursor: - yql_text = "SELECT 1 as val; SELECT 2 as val; SELECT 3 as val;" + yql_text = """ + SELECT id, val FROM table; + SELECT id, val FROM table1; + SELECT id, val FROM table2; + """ cursor.execute(query=yql_text) - for i in range(3): + for i in range(RESULT_SET_LENGTH * RESULT_SET_COUNT): res = cursor.fetchone() assert res is not None - assert res[0] == i + 1 + assert res[0] == i % RESULT_SET_LENGTH assert cursor.fetchone() is None assert not cursor.nextset() + + def test_cursor_fetch_many_autoscroll( + self, session_sync: ydb.QuerySession + ) -> None: + with ydb_dbapi.Cursor( + session=session_sync, auto_scroll_result_sets=True + ) as cursor: + yql_text = """ + SELECT id, val FROM table; + SELECT id, val FROM table1; + SELECT id, val FROM table2; + """ + cursor.execute(query=yql_text) + + halfsize = (RESULT_SET_LENGTH * RESULT_SET_COUNT) // 2 + for _ in range(2): + res = cursor.fetchmany(size=halfsize) + assert res is not None + assert len(res) == halfsize + + assert cursor.fetchmany(2) == [] + assert not cursor.nextset() + + def test_cursor_fetch_all_autoscroll( + self, session_sync: ydb.QuerySession + ) -> None: + with ydb_dbapi.Cursor( + session=session_sync, auto_scroll_result_sets=True + ) as cursor: + yql_text = """ + SELECT id, val FROM table; + SELECT id, val FROM table1; + SELECT id, val FROM table2; + """ + cursor.execute(query=yql_text) + + res = cursor.fetchall() + + assert len(res) == RESULT_SET_COUNT * RESULT_SET_LENGTH + + assert cursor.fetchall() == [] + assert not cursor.nextset() diff --git a/ydb_dbapi/cursors.py b/ydb_dbapi/cursors.py index 1839cc8..8213503 100644 --- a/ydb_dbapi/cursors.py +++ b/ydb_dbapi/cursors.py @@ -106,18 +106,13 @@ def _begin_query(self) -> None: def _fetchone_from_buffer(self) -> tuple | None: return next(self._rows or iter([]), None) - def _fetchmany_from_buffer(self, size: int | None = None) -> list | None: - return ( - list( - itertools.islice( - self._rows or iter([]), size or self.arraysize - ) - ) - or None + def _fetchmany_from_buffer(self, size: int | None = None) -> list: + return list( + itertools.islice(self._rows or iter([]), size or self.arraysize) ) - def _fetchall_from_buffer(self) -> list | None: - return list(self._rows or iter([])) or None + def _fetchall_from_buffer(self) -> list: + return list(self._rows or iter([])) class Cursor(BaseCursor): @@ -151,11 +146,28 @@ def fetchone(self) -> tuple | None: return row - def fetchmany(self, size: int | None = None) -> list | None: - return self._fetchmany_from_buffer(size) + def fetchmany(self, size: int | None = None) -> list: + size = size or self.arraysize + rows = self._fetchmany_from_buffer(size) + if not self._auto_scroll: + return rows + + while len(rows) < size and self.nextset(): + new_rows = self._fetchmany_from_buffer(size - len(rows)) + rows.extend(new_rows) + + return rows + + def fetchall(self) -> list: + rows = self._fetchall_from_buffer() + if not self._auto_scroll: + return rows + + while self.nextset(): + new_rows = self._fetchall_from_buffer() + rows.extend(new_rows) - def fetchall(self) -> list | None: - return self._fetchall_from_buffer() + return rows @handle_ydb_errors def _execute_generic_query( @@ -255,22 +267,51 @@ def __init__( tx_context: ydb.aio.QueryTxContext | None = None, table_path_prefix: str = "", autocommit: bool = True, + auto_scroll_result_sets: bool = False, ) -> None: self._session = session self._tx_context = tx_context self._table_path_prefix = table_path_prefix self._autocommit = autocommit + self._auto_scroll = auto_scroll_result_sets self._stream: AsyncIterator | None = None async def fetchone(self) -> tuple | None: - return self._fetchone_from_buffer() + row = self._fetchone_from_buffer() + if not self._auto_scroll: + return row + + if row is None: + while await self.nextset(): + row = self._fetchone_from_buffer() + if row is not None: + return row + + return row + + async def fetchmany(self, size: int | None = None) -> list: + size = size or self.arraysize + rows = self._fetchmany_from_buffer(size) + if not self._auto_scroll: + return rows + + while len(rows) < size and await self.nextset(): + new_rows = self._fetchmany_from_buffer(size - len(rows)) + rows.extend(new_rows) + + return rows + + async def fetchall(self) -> list: + rows = self._fetchall_from_buffer() + if not self._auto_scroll: + return rows - async def fetchmany(self, size: int | None = None) -> list | None: - return self._fetchmany_from_buffer(size) + while await self.nextset(): + new_rows = self._fetchall_from_buffer() + rows.extend(new_rows) - async def fetchall(self) -> list | None: - return self._fetchall_from_buffer() + return rows @handle_ydb_errors async def _execute_generic_query( From 7532331501242974a366980c5e22c7cb0b30a40d Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:10:32 +0300 Subject: [PATCH 24/33] dbapi attributes --- ydb_dbapi/__init__.py | 7 +++++++ ydb_dbapi/errors.py | 4 ++++ ydb_dbapi/version.py | 1 + 3 files changed, 12 insertions(+) create mode 100644 ydb_dbapi/version.py diff --git a/ydb_dbapi/__init__.py b/ydb_dbapi/__init__.py index c93f593..fe39f81 100644 --- a/ydb_dbapi/__init__.py +++ b/ydb_dbapi/__init__.py @@ -6,3 +6,10 @@ from .cursors import AsyncCursor from .cursors import Cursor from .errors import * +from .version import version + +__version__ = version + +apilevel = "2.0" +threadsafety = 0 +paramstyle = "pyformat" diff --git a/ydb_dbapi/errors.py b/ydb_dbapi/errors.py index 011e16c..44e9dd2 100644 --- a/ydb_dbapi/errors.py +++ b/ydb_dbapi/errors.py @@ -4,6 +4,10 @@ from google.protobuf.message import Message +class Warning(Exception): # noqa: N818,A001 + pass + + class Error(Exception): def __init__( self, diff --git a/ydb_dbapi/version.py b/ydb_dbapi/version.py new file mode 100644 index 0000000..5820a72 --- /dev/null +++ b/ydb_dbapi/version.py @@ -0,0 +1 @@ +version = "0.0.1" From b397b243206ea28e69213922ac048bc43c032760 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:10:32 +0300 Subject: [PATCH 25/33] Simple README added --- README.md | 47 +++++++++++ poetry.lock | 238 ++++++++++++++++++++++++++++------------------------ 2 files changed, 174 insertions(+), 111 deletions(-) diff --git a/README.md b/README.md index e69de29..80bdf27 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,47 @@ +# YDB Python DBAPI + +## Introduction + +Python DBAPI to `YDB`, which provides both sync and async drivers and complies with [PEP249](https://www.python.org/dev/peps/pep-0249/). + +## Installation + +**TBD after first release** + +## Usage + +To establish a new DBAPI connection you should provide `host`, `port` and `database`: + + ```python + import ydb_dbapi + + connection = ydb_dbapi.connect( + host="localhost", port="2136", database="/local" + ) # sync connection + + async_connection = await ydb_dbapi.async_connect( + host="localhost", port="2136", database="/local" + ) # async connection + ``` + +Usage of connection: + + ```python + with connection.cursor() as cursor: + cursor.execute("SELECT id, val FROM table") + + row = cursor.fetchone() + rows = cursor.fetchmany(size=5) + rows = cursor.fetchall() + ``` + +Usage of async connection: + + ```python + async with async_connection.cursor() as cursor: + await cursor.execute("SELECT id, val FROM table") + + row = await cursor.fetchone() + rows = await cursor.fetchmany(size=5) + rows = await cursor.fetchall() + ``` diff --git a/poetry.lock b/poetry.lock index e54f407..36b446e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -379,88 +379,103 @@ typing = ["typing-extensions (>=4.12.2)"] [[package]] name = "frozenlist" -version = "1.4.1" +version = "1.5.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.8" files = [ - {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, - {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, - {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, - {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, - {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, - {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, - {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, - {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, - {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, - {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, - {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, - {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, - {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, - {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, - {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, + {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, + {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, + {file = "frozenlist-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5"}, + {file = "frozenlist-1.5.0-cp310-cp310-win32.whl", hash = "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb"}, + {file = "frozenlist-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4"}, + {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30"}, + {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5"}, + {file = "frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf"}, + {file = "frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942"}, + {file = "frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d"}, + {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21"}, + {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d"}, + {file = "frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f"}, + {file = "frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8"}, + {file = "frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f"}, + {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953"}, + {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0"}, + {file = "frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03"}, + {file = "frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c"}, + {file = "frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28"}, + {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dd94994fc91a6177bfaafd7d9fd951bc8689b0a98168aa26b5f543868548d3ca"}, + {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0da8bbec082bf6bf18345b180958775363588678f64998c2b7609e34719b10"}, + {file = "frozenlist-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73f2e31ea8dd7df61a359b731716018c2be196e5bb3b74ddba107f694fbd7604"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828afae9f17e6de596825cf4228ff28fbdf6065974e5ac1410cecc22f699d2b3"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1577515d35ed5649d52ab4319db757bb881ce3b2b796d7283e6634d99ace307"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2150cc6305a2c2ab33299453e2968611dacb970d2283a14955923062c8d00b10"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a72b7a6e3cd2725eff67cd64c8f13335ee18fc3c7befc05aed043d24c7b9ccb9"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c16d2fa63e0800723139137d667e1056bee1a1cf7965153d2d104b62855e9b99"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:17dcc32fc7bda7ce5875435003220a457bcfa34ab7924a49a1c19f55b6ee185c"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:97160e245ea33d8609cd2b8fd997c850b56db147a304a262abc2b3be021a9171"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f1e6540b7fa044eee0bb5111ada694cf3dc15f2b0347ca125ee9ca984d5e9e6e"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:91d6c171862df0a6c61479d9724f22efb6109111017c87567cfeb7b5d1449fdf"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c1fac3e2ace2eb1052e9f7c7db480818371134410e1f5c55d65e8f3ac6d1407e"}, + {file = "frozenlist-1.5.0-cp38-cp38-win32.whl", hash = "sha256:b97f7b575ab4a8af9b7bc1d2ef7f29d3afee2226bd03ca3875c16451ad5a7723"}, + {file = "frozenlist-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:374ca2dabdccad8e2a76d40b1d037f5bd16824933bf7bcea3e59c891fd4a0923"}, + {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9bbcdfaf4af7ce002694a4e10a0159d5a8d20056a12b05b45cea944a4953f972"}, + {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1893f948bf6681733aaccf36c5232c231e3b5166d607c5fa77773611df6dc336"}, + {file = "frozenlist-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2b5e23253bb709ef57a8e95e6ae48daa9ac5f265637529e4ce6b003a37b2621f"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f253985bb515ecd89629db13cb58d702035ecd8cfbca7d7a7e29a0e6d39af5f"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04a5c6babd5e8fb7d3c871dc8b321166b80e41b637c31a995ed844a6139942b6"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fe0f1c29ba24ba6ff6abf688cb0b7cf1efab6b6aa6adc55441773c252f7411"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226d72559fa19babe2ccd920273e767c96a49b9d3d38badd7c91a0fdeda8ea08"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b731db116ab3aedec558573c1a5eec78822b32292fe4f2f0345b7f697745c2"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:366d8f93e3edfe5a918c874702f78faac300209a4d5bf38352b2c1bdc07a766d"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1b96af8c582b94d381a1c1f51ffaedeb77c821c690ea5f01da3d70a487dd0a9b"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c03eff4a41bd4e38415cbed054bbaff4a075b093e2394b6915dca34a40d1e38b"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:50cf5e7ee9b98f22bdecbabf3800ae78ddcc26e4a435515fc72d97903e8488e0"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1e76bfbc72353269c44e0bc2cfe171900fbf7f722ad74c9a7b638052afe6a00c"}, + {file = "frozenlist-1.5.0-cp39-cp39-win32.whl", hash = "sha256:666534d15ba8f0fda3f53969117383d5dc021266b3c1a42c9ec4855e4b58b9d3"}, + {file = "frozenlist-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:5c28f4b5dbef8a0d8aad0d4de24d1e9e981728628afaf4ea0792f5d0939372f0"}, + {file = "frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3"}, + {file = "frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817"}, ] [[package]] @@ -675,43 +690,43 @@ typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} [[package]] name = "mypy" -version = "1.12.1" +version = "1.13.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3d7d4371829184e22fda4015278fbfdef0327a4b955a483012bd2d423a788801"}, - {file = "mypy-1.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f59f1dfbf497d473201356966e353ef09d4daec48caeacc0254db8ef633a28a5"}, - {file = "mypy-1.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b947097fae68004b8328c55161ac9db7d3566abfef72d9d41b47a021c2fba6b1"}, - {file = "mypy-1.12.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:96af62050971c5241afb4701c15189ea9507db89ad07794a4ee7b4e092dc0627"}, - {file = "mypy-1.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:d90da248f4c2dba6c44ddcfea94bb361e491962f05f41990ff24dbd09969ce20"}, - {file = "mypy-1.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1230048fec1380faf240be6385e709c8570604d2d27ec6ca7e573e3bc09c3735"}, - {file = "mypy-1.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:02dcfe270c6ea13338210908f8cadc8d31af0f04cee8ca996438fe6a97b4ec66"}, - {file = "mypy-1.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5a437c9102a6a252d9e3a63edc191a3aed5f2fcb786d614722ee3f4472e33f6"}, - {file = "mypy-1.12.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:186e0c8346efc027ee1f9acf5ca734425fc4f7dc2b60144f0fbe27cc19dc7931"}, - {file = "mypy-1.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:673ba1140a478b50e6d265c03391702fa11a5c5aff3f54d69a62a48da32cb811"}, - {file = "mypy-1.12.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9fb83a7be97c498176fb7486cafbb81decccaef1ac339d837c377b0ce3743a7f"}, - {file = "mypy-1.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:389e307e333879c571029d5b93932cf838b811d3f5395ed1ad05086b52148fb0"}, - {file = "mypy-1.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:94b2048a95a21f7a9ebc9fbd075a4fcd310410d078aa0228dbbad7f71335e042"}, - {file = "mypy-1.12.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ee5932370ccf7ebf83f79d1c157a5929d7ea36313027b0d70a488493dc1b179"}, - {file = "mypy-1.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:19bf51f87a295e7ab2894f1d8167622b063492d754e69c3c2fed6563268cb42a"}, - {file = "mypy-1.12.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d34167d43613ffb1d6c6cdc0cc043bb106cac0aa5d6a4171f77ab92a3c758bcc"}, - {file = "mypy-1.12.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:427878aa54f2e2c5d8db31fa9010c599ed9f994b3b49e64ae9cd9990c40bd635"}, - {file = "mypy-1.12.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5fcde63ea2c9f69d6be859a1e6dd35955e87fa81de95bc240143cf00de1f7f81"}, - {file = "mypy-1.12.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d54d840f6c052929f4a3d2aab2066af0f45a020b085fe0e40d4583db52aab4e4"}, - {file = "mypy-1.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:20db6eb1ca3d1de8ece00033b12f793f1ea9da767334b7e8c626a4872090cf02"}, - {file = "mypy-1.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b16fe09f9c741d85a2e3b14a5257a27a4f4886c171d562bc5a5e90d8591906b8"}, - {file = "mypy-1.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0dcc1e843d58f444fce19da4cce5bd35c282d4bde232acdeca8279523087088a"}, - {file = "mypy-1.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e10ba7de5c616e44ad21005fa13450cd0de7caaa303a626147d45307492e4f2d"}, - {file = "mypy-1.12.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0e6fe449223fa59fbee351db32283838a8fee8059e0028e9e6494a03802b4004"}, - {file = "mypy-1.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:dc6e2a2195a290a7fd5bac3e60b586d77fc88e986eba7feced8b778c373f9afe"}, - {file = "mypy-1.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:de5b2a8988b4e1269a98beaf0e7cc71b510d050dce80c343b53b4955fff45f19"}, - {file = "mypy-1.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:843826966f1d65925e8b50d2b483065c51fc16dc5d72647e0236aae51dc8d77e"}, - {file = "mypy-1.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9fe20f89da41a95e14c34b1ddb09c80262edcc295ad891f22cc4b60013e8f78d"}, - {file = "mypy-1.12.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8135ffec02121a75f75dc97c81af7c14aa4ae0dda277132cfcd6abcd21551bfd"}, - {file = "mypy-1.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:a7b76fa83260824300cc4834a3ab93180db19876bce59af921467fd03e692810"}, - {file = "mypy-1.12.1-py3-none-any.whl", hash = "sha256:ce561a09e3bb9863ab77edf29ae3a50e65685ad74bba1431278185b7e5d5486e"}, - {file = "mypy-1.12.1.tar.gz", hash = "sha256:f5b3936f7a6d0e8280c9bdef94c7ce4847f5cdfc258fbb2c29a8c1711e8bb96d"}, + {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, + {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, + {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, + {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, + {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, + {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, + {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, + {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, + {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, + {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, + {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, + {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, + {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, + {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, + {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, + {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, + {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, + {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, + {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, + {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, + {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, + {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, ] [package.dependencies] @@ -721,6 +736,7 @@ typing-extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] install-types = ["pip"] mypyc = ["setuptools (>=50)"] reports = ["lxml"] From 3dd9e63d860e86a58d2cc0203271b8b1c2c1835e Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:10:52 +0300 Subject: [PATCH 26/33] update pyproject.toml --- pyproject.toml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1b3cd76..d952b59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,15 +1,14 @@ [tool.poetry] name = "ydb-dbapi" version = "0.0.0" -description = "" -authors = ["Oleg Ovcharuk "] +description = "YDB Python DBAPI which complies with PEP 249" +authors = ["Yandex LLC "] readme = "README.md" [tool.poetry.dependencies] python = "^3.9" ydb = "^3.18.3" - [tool.poetry.group.dev.dependencies] pre-commit = "^4.0.1" ruff = "^0.6.9" From a8e28b53145a7b8206983dc1707f348805ecd648 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:11:04 +0300 Subject: [PATCH 27/33] autoincrement version --- .github/scripts/increment_version.py | 170 ++++++++++++++++++++++ .github/scripts/increment_version_test.py | 28 ++++ .pre-commit-config.yaml | 2 + CHANGELOG.md | 1 + poetry.lock | 6 +- pyproject.toml | 4 +- ydb_dbapi/__init__.py | 4 +- ydb_dbapi/version.py | 2 +- 8 files changed, 209 insertions(+), 8 deletions(-) create mode 100644 .github/scripts/increment_version.py create mode 100644 .github/scripts/increment_version_test.py create mode 100644 CHANGELOG.md diff --git a/.github/scripts/increment_version.py b/.github/scripts/increment_version.py new file mode 100644 index 0000000..08ca12e --- /dev/null +++ b/.github/scripts/increment_version.py @@ -0,0 +1,170 @@ +#!/bin/env python +import argparse +from dataclasses import dataclass + +from packaging.version import Version + +PYPROJECT_PATH = "pyproject.toml" +DEFAULT_CHANGELOG_PATH = "CHANGELOG.md" +DEFAULT_YDB_VERSION_FILE = "ydb_dbapi/version.py" +MARKER = "# AUTOVERSION" + + +@dataclass(init=False) +class VersionLine: + old_line: str + major: int + minor: int + patch: int + pre: int + + def __init__(self, old_line: str, version_str: str): + self.old_line = old_line + + version = Version(version_str) + self.major = version.major + self.minor = version.minor + self.micro = version.micro + + if version.pre is None: + self.pre = 0 + else: + self.pre = version.pre[1] + + def increment(self, part_name: str, with_beta: bool): + if part_name == "minor": + self.increment_minor(with_beta) + elif part_name == "patch" or part_name == "micro": + self.increment_micro(with_beta) + else: + raise Exception("unexpected increment type: '%s'" % part_name) + + def increment_minor(self, with_beta: bool): + if with_beta: + if self.pre == 0 or self.micro != 0: + self.increment_minor(False) + self.pre += 1 + return + + if self.micro == 0 and self.pre > 0: + self.pre = 0 + return + + self.minor += 1 + self.micro = 0 + self.pre = 0 + + def increment_micro(self, with_beta: bool): + if with_beta: + if self.pre == 0: + self.increment_micro(False) + self.pre += 1 + return + + if self.pre > 0: + self.pre = 0 + return + + self.micro += 1 + + def __str__(self): + if self.pre > 0: + pre = "b%s" % self.pre + else: + pre = "" + + return "%s.%s.%s%s" % (self.major, self.minor, self.micro, pre) + + def version_line_with_mark(self): + return 'version = "%s" %s' % (str(self), MARKER) + + +def extract_version(pyproject_content: str): + version_line = "" + for line in pyproject_content.splitlines(): + if MARKER in line: + version_line = line + break + + if version_line == "": + raise Exception("Not found version line") + + version_line = version_line.strip() + + parts = version_line.split('"') + version_part = parts[1] + + return VersionLine(old_line=version_line, version_str=version_part) + + +def increment_version_at_pyproject( + pyproject_path: str, inc_type: str, with_beta: bool +) -> str: + with open(pyproject_path, "rt") as f: + setup_content = f.read() + + version = extract_version(setup_content) + version.increment(inc_type, with_beta) + setup_content = setup_content.replace( + version.old_line, version.version_line_with_mark() + ) + + with open(pyproject_path, "w") as f: + f.write(setup_content) + + return str(version) + + +def add_changelog_version(changelog_path, version: str): + with open(changelog_path, "rt") as f: + content = f.read() + content = content.strip() + + if content.startswith("##"): + return + + content = """## %s ## +%s +""" % (version, content) + with open(changelog_path, "w") as f: + f.write(content) + + +def set_version_in_version_file(file_path: str, version: str): + with open(file_path, "w") as f: + f.write('VERSION = "%s"\n' % version) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "--inc-type", + default="minor", + help="increment version type: patch or minor", + choices=["minor", "patch"], + ) + parser.add_argument( + "--beta", choices=["true", "false"], help="is beta version" + ) + parser.add_argument( + "--changelog-path", + default=DEFAULT_CHANGELOG_PATH, + help="path to changelog", + type=str, + ) + parser.add_argument("--pyproject-path", default=PYPROJECT_PATH) + + args = parser.parse_args() + + is_beta = args.beta == "true" + + new_version = increment_version_at_pyproject( + args.pyproject_path, args.inc_type, is_beta + ) + add_changelog_version(args.changelog_path, new_version) + set_version_in_version_file(DEFAULT_YDB_VERSION_FILE, new_version) + print(new_version) + + +if __name__ == "__main__": + main() diff --git a/.github/scripts/increment_version_test.py b/.github/scripts/increment_version_test.py new file mode 100644 index 0000000..b8e08e1 --- /dev/null +++ b/.github/scripts/increment_version_test.py @@ -0,0 +1,28 @@ +import pytest + +from .increment_version import VersionLine + + +@pytest.mark.parametrize( + "source,inc_type,with_beta,result", + [ + ("0.0.0", "patch", False, "0.0.1"), + ("0.0.1", "patch", False, "0.0.2"), + ("0.0.1b1", "patch", False, "0.0.1"), + ("0.0.0", "patch", True, "0.0.1b1"), + ("0.0.1", "patch", True, "0.0.2b1"), + ("0.0.2b1", "patch", True, "0.0.2b2"), + ("0.0.1", "minor", False, "0.1.0"), + ("0.0.1b1", "minor", False, "0.1.0"), + ("0.1.0b1", "minor", False, "0.1.0"), + ("0.1.0", "minor", True, "0.2.0b1"), + ("0.1.0b1", "minor", True, "0.1.0b2"), + ("0.1.1b1", "minor", True, "0.2.0b1"), + ("3.0.0b1", "patch", True, "3.0.0b2"), + ], +) +def test_increment_version(source, inc_type, with_beta, result): + version = VersionLine("", source) + version.increment(inc_type, with_beta) + incremented = str(version) + assert incremented == result diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 458935b..c3eabe6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,3 +23,5 @@ repos: hooks: - id: mypy name: mypy + +exclude: '.github/' diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..21537f1 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1 @@ +* YDB DBAPI based on QueryService diff --git a/poetry.lock b/poetry.lock index 36b446e..464d8b1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1455,13 +1455,13 @@ propcache = ">=0.2.0" [[package]] name = "ydb" -version = "3.18.5" +version = "3.18.6" description = "YDB Python SDK" optional = false python-versions = "*" files = [ - {file = "ydb-3.18.5-py2.py3-none-any.whl", hash = "sha256:96aa7ccc73ba5f5e1db62fb19f5499c2204de1bc20a1f5e7898c32d09a85fd6b"}, - {file = "ydb-3.18.5.tar.gz", hash = "sha256:c8d066fac49a25697ec8ea7a0e925d26e8c1969a5cfa38f3ddd49a4fa3ddb64f"}, + {file = "ydb-3.18.6-py2.py3-none-any.whl", hash = "sha256:3d67551aa1dd177ef10d94d0e79a955eacde055321b24c83fa4e89d9efa47281"}, + {file = "ydb-3.18.6.tar.gz", hash = "sha256:0c05c355bb41a90334be17ca12439f72465a03950c82f736ad2871d2a6bcac8b"}, ] [package.dependencies] diff --git a/pyproject.toml b/pyproject.toml index d952b59..2bd3837 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ydb-dbapi" -version = "0.0.0" +version = "0.0.0" # AUTOVERSION description = "YDB Python DBAPI which complies with PEP 249" authors = ["Yandex LLC "] readme = "README.md" @@ -33,7 +33,7 @@ format = "ruff format" tests = "pytest" [tool.ruff] -exclude = [".venv", ".git", "__pycache__", "build", "dist", "venv"] +exclude = [".venv", ".git", ".github/scripts", "__pycache__", "build", "dist", "venv"] line-length = 79 target-version = "py39" src = ["ydb_dbapi", "tests"] diff --git a/ydb_dbapi/__init__.py b/ydb_dbapi/__init__.py index fe39f81..c442905 100644 --- a/ydb_dbapi/__init__.py +++ b/ydb_dbapi/__init__.py @@ -6,9 +6,9 @@ from .cursors import AsyncCursor from .cursors import Cursor from .errors import * -from .version import version +from .version import VERSION -__version__ = version +__version__ = VERSION apilevel = "2.0" threadsafety = 0 diff --git a/ydb_dbapi/version.py b/ydb_dbapi/version.py index 5820a72..901e511 100644 --- a/ydb_dbapi/version.py +++ b/ydb_dbapi/version.py @@ -1 +1 @@ -version = "0.0.1" +VERSION = "0.0.1" From 7bc3bb20e7a8d80bb9095fe5431316786e1f4830 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:11:04 +0300 Subject: [PATCH 28/33] publish action --- .github/workflows/python-publish.yml | 111 +++++++++++++++++++++++++++ ydb_dbapi/__init__.py | 1 + 2 files changed, 112 insertions(+) create mode 100644 .github/workflows/python-publish.yml diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..bb6dd29 --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,111 @@ +# This workflow will upload a Python Package using Twine when a release is created +# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Publish package release + +on: + workflow_dispatch: + inputs: + version-change: + description: Version part + required: true + type: choice + default: minor + options: + - minor + - patch + beta: + description: Is beta version + required: true + type: boolean + default: True +jobs: + publish: + env: + VERSION_CHANGE: ${{ github.event.inputs.version-change }} + WITH_BETA: ${{ github.event.inputs.beta }} + GH_TOKEN: ${{ secrets.YDB_PLATFORM_BOT_TOKEN_REPO }} + CHANGELOG_FILE: CHANGELOG.md + PYPROJECT_PATH: pyproject.toml + + permissions: + contents: write + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + with: + token: ${{ secrets.YDB_PLATFORM_BOT_TOKEN_REPO }} + + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.9' + + - name: read changelog + id: read-changelog + run: | + CHANGELOG=$(cat $CHANGELOG_FILE | sed -e '/^## .*$/,$d') + echo "CHANGELOG<> $GITHUB_ENV + echo "$CHANGELOG" >> $GITHUB_ENV + echo "CHANGELOGEOF_MARKER" >> $GITHUB_ENV + echo "# Changelog" >> $GITHUB_STEP_SUMMARY + echo "$CHANGELOG" >> $GITHUB_STEP_SUMMARY + + + - name: Increment version + id: increment-version + run: | + NEW_VERSION=$(python3 ./.github/scripts/increment_version.py --inc-type=$VERSION_CHANGE --beta=$WITH_BETA) + echo new version: $NEW_VERSION + echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_OUTPUT + echo "New version: $NEW_VERSION" >> $GITHUB_STEP_SUMMARY + + - name: Install Poetry + run: pip install poetry + + - name: Build package + run: poetry build + + - name: Publish release on github + run: | + if [[ -z "$CHANGELOG" ]] + then + echo "CHANGELOG empty" + exit 1; + fi; + + TAG="${{ steps.increment-version.outputs.NEW_VERSION }}" + + # Get previous version from changelog + # pre-incremented version not used for consistent changelog with release notes + # for example changelog may be rewrited when switch from beta to release + # and remove internal beta changes + LAST_TAG=$(cat $CHANGELOG_FILE | grep '^## .* ##$' | head -n 2 | tail -n 1 | cut -d ' ' -f 2) + + git config --global user.email "robot@umbrella"; + git config --global user.name "robot"; + git commit -am "Release: $TAG"; + + git tag "$TAG" + git push && git push --tags + + CHANGELOG="$CHANGELOG + + Full Changelog: [$LAST_TAG...$TAG](https://github.com/ydb-platform/ydb-sqlalchemy/compare/$LAST_TAG...$TAG)" + if [ "$WITH_BETA" = true ] + then + gh release create --prerelease $TAG --title "$TAG" --notes "$CHANGELOG" + else + gh release create $TAG --title "$TAG" --notes "$CHANGELOG" + fi; + + - name: Publish package + uses: pypa/gh-action-pypi-publish@release/v1.8 diff --git a/ydb_dbapi/__init__.py b/ydb_dbapi/__init__.py index c442905..1d2f2dd 100644 --- a/ydb_dbapi/__init__.py +++ b/ydb_dbapi/__init__.py @@ -3,6 +3,7 @@ from .connections import IsolationLevel from .connections import async_connect from .connections import connect +from .constants import * from .cursors import AsyncCursor from .cursors import Cursor from .errors import * From 86a282d07d40232aa005b16d8361a99d258a0a20 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 15:11:04 +0300 Subject: [PATCH 29/33] make cursor client-side --- .pre-commit-config.yaml | 3 +- poetry.lock | 183 ++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + tests/test_connection.py | 36 ++++---- tests/test_cursor.py | 101 +++++---------------- ydb_dbapi/cursors.py | 128 +++++++++------------------ 6 files changed, 268 insertions(+), 184 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c3eabe6..21ecaed 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,4 @@ +files: ydb_dbapi repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 @@ -23,5 +24,3 @@ repos: hooks: - id: mypy name: mypy - -exclude: '.github/' diff --git a/poetry.lock b/poetry.lock index 464d8b1..4acaeff 100644 --- a/poetry.lock +++ b/poetry.lock @@ -478,6 +478,92 @@ files = [ {file = "frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817"}, ] +[[package]] +name = "greenlet" +version = "3.1.1" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.7" +files = [ + {file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617"}, + {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7"}, + {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6"}, + {file = "greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80"}, + {file = "greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a"}, + {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511"}, + {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395"}, + {file = "greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39"}, + {file = "greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9"}, + {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0"}, + {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942"}, + {file = "greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01"}, + {file = "greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e"}, + {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1"}, + {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c"}, + {file = "greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822"}, + {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01"}, + {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47da355d8687fd65240c364c90a31569a133b7b60de111c255ef5b606f2ae291"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98884ecf2ffb7d7fe6bd517e8eb99d31ff7855a840fa6d0d63cd07c037f6a981"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1d4aeb8891338e60d1ab6127af1fe45def5259def8094b9c7e34690c8858803"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db32b5348615a04b82240cc67983cb315309e88d444a288934ee6ceaebcad6cc"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dcc62f31eae24de7f8dce72134c8651c58000d3b1868e01392baea7c32c247de"}, + {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1d3755bcb2e02de341c55b4fca7a745a24a9e7212ac953f6b3a48d117d7257aa"}, + {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b8da394b34370874b4572676f36acabac172602abf054cbc4ac910219f3340af"}, + {file = "greenlet-3.1.1-cp37-cp37m-win32.whl", hash = "sha256:a0dfc6c143b519113354e780a50381508139b07d2177cb6ad6a08278ec655798"}, + {file = "greenlet-3.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:54558ea205654b50c438029505def3834e80f0869a70fb15b871c29b4575ddef"}, + {file = "greenlet-3.1.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37b9de5a96111fc15418819ab4c4432e4f3c2ede61e660b1e33971eba26ef9ba"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1"}, + {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd"}, + {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7"}, + {file = "greenlet-3.1.1-cp38-cp38-win32.whl", hash = "sha256:8b8b36671f10ba80e159378df9c4f15c14098c4fd73a36b9ad715f057272fbef"}, + {file = "greenlet-3.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:7017b2be767b9d43cc31416aba48aab0d2309ee31b4dbf10a1d38fb7972bdf9d"}, + {file = "greenlet-3.1.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c"}, + {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e"}, + {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e"}, + {file = "greenlet-3.1.1-cp39-cp39-win32.whl", hash = "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c"}, + {file = "greenlet-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22"}, + {file = "greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil"] + [[package]] name = "grpcio" version = "1.67.0" @@ -1156,6 +1242,101 @@ files = [ {file = "ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2"}, ] +[[package]] +name = "sqlalchemy" +version = "2.0.36" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:59b8f3adb3971929a3e660337f5dacc5942c2cdb760afcabb2614ffbda9f9f72"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37350015056a553e442ff672c2d20e6f4b6d0b2495691fa239d8aa18bb3bc908"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8318f4776c85abc3f40ab185e388bee7a6ea99e7fa3a30686580b209eaa35c08"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c245b1fbade9c35e5bd3b64270ab49ce990369018289ecfde3f9c318411aaa07"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:69f93723edbca7342624d09f6704e7126b152eaed3cdbb634cb657a54332a3c5"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f9511d8dd4a6e9271d07d150fb2f81874a3c8c95e11ff9af3a2dfc35fe42ee44"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-win32.whl", hash = "sha256:c3f3631693003d8e585d4200730616b78fafd5a01ef8b698f6967da5c605b3fa"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-win_amd64.whl", hash = "sha256:a86bfab2ef46d63300c0f06936bd6e6c0105faa11d509083ba8f2f9d237fb5b5"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fd3a55deef00f689ce931d4d1b23fa9f04c880a48ee97af488fd215cf24e2a6c"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f5e9cd989b45b73bd359f693b935364f7e1f79486e29015813c338450aa5a71"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ddd9db6e59c44875211bc4c7953a9f6638b937b0a88ae6d09eb46cced54eff"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2519f3a5d0517fc159afab1015e54bb81b4406c278749779be57a569d8d1bb0d"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59b1ee96617135f6e1d6f275bbe988f419c5178016f3d41d3c0abb0c819f75bb"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:39769a115f730d683b0eb7b694db9789267bcd027326cccc3125e862eb03bfd8"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-win32.whl", hash = "sha256:66bffbad8d6271bb1cc2f9a4ea4f86f80fe5e2e3e501a5ae2a3dc6a76e604e6f"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-win_amd64.whl", hash = "sha256:23623166bfefe1487d81b698c423f8678e80df8b54614c2bf4b4cfcd7c711959"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7b64e6ec3f02c35647be6b4851008b26cff592a95ecb13b6788a54ef80bbdd4"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46331b00096a6db1fdc052d55b101dbbfc99155a548e20a0e4a8e5e4d1362855"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdf3386a801ea5aba17c6410dd1dc8d39cf454ca2565541b5ac42a84e1e28f53"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9dfa18ff2a67b09b372d5db8743c27966abf0e5344c555d86cc7199f7ad83a"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:90812a8933df713fdf748b355527e3af257a11e415b613dd794512461eb8a686"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1bc330d9d29c7f06f003ab10e1eaced295e87940405afe1b110f2eb93a233588"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-win32.whl", hash = "sha256:79d2e78abc26d871875b419e1fd3c0bca31a1cb0043277d0d850014599626c2e"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-win_amd64.whl", hash = "sha256:b544ad1935a8541d177cb402948b94e871067656b3a0b9e91dbec136b06a2ff5"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5cc79df7f4bc3d11e4b542596c03826063092611e481fcf1c9dfee3c94355ef"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3c01117dd36800f2ecaa238c65365b7b16497adc1522bf84906e5710ee9ba0e8"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bc633f4ee4b4c46e7adcb3a9b5ec083bf1d9a97c1d3854b92749d935de40b9b"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e46ed38affdfc95d2c958de328d037d87801cfcbea6d421000859e9789e61c2"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b2985c0b06e989c043f1dc09d4fe89e1616aadd35392aea2844f0458a989eacf"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a121d62ebe7d26fec9155f83f8be5189ef1405f5973ea4874a26fab9f1e262c"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-win32.whl", hash = "sha256:0572f4bd6f94752167adfd7c1bed84f4b240ee6203a95e05d1e208d488d0d436"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-win_amd64.whl", hash = "sha256:8c78ac40bde930c60e0f78b3cd184c580f89456dd87fc08f9e3ee3ce8765ce88"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:be9812b766cad94a25bc63bec11f88c4ad3629a0cec1cd5d4ba48dc23860486b"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50aae840ebbd6cdd41af1c14590e5741665e5272d2fee999306673a1bb1fdb4d"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4557e1f11c5f653ebfdd924f3f9d5ebfc718283b0b9beebaa5dd6b77ec290971"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:07b441f7d03b9a66299ce7ccf3ef2900abc81c0db434f42a5694a37bd73870f2"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:28120ef39c92c2dd60f2721af9328479516844c6b550b077ca450c7d7dc68575"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-win32.whl", hash = "sha256:b81ee3d84803fd42d0b154cb6892ae57ea6b7c55d8359a02379965706c7efe6c"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-win_amd64.whl", hash = "sha256:f942a799516184c855e1a32fbc7b29d7e571b52612647866d4ec1c3242578fcb"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3d6718667da04294d7df1670d70eeddd414f313738d20a6f1d1f379e3139a545"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:72c28b84b174ce8af8504ca28ae9347d317f9dba3999e5981a3cd441f3712e24"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b11d0cfdd2b095e7b0686cf5fabeb9c67fae5b06d265d8180715b8cfa86522e3"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e32092c47011d113dc01ab3e1d3ce9f006a47223b18422c5c0d150af13a00687"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:6a440293d802d3011028e14e4226da1434b373cbaf4a4bbb63f845761a708346"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c54a1e53a0c308a8e8a7dffb59097bff7facda27c70c286f005327f21b2bd6b1"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-win32.whl", hash = "sha256:1e0d612a17581b6616ff03c8e3d5eff7452f34655c901f75d62bd86449d9750e"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-win_amd64.whl", hash = "sha256:8958b10490125124463095bbdadda5aa22ec799f91958e410438ad6c97a7b793"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc022184d3e5cacc9579e41805a681187650e170eb2fd70e28b86192a479dcaa"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b817d41d692bf286abc181f8af476c4fbef3fd05e798777492618378448ee689"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4e46a888b54be23d03a89be510f24a7652fe6ff660787b96cd0e57a4ebcb46d"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4ae3005ed83f5967f961fd091f2f8c5329161f69ce8480aa8168b2d7fe37f06"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:03e08af7a5f9386a43919eda9de33ffda16b44eb11f3b313e6822243770e9763"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3dbb986bad3ed5ceaf090200eba750b5245150bd97d3e67343a3cfed06feecf7"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-win32.whl", hash = "sha256:9fe53b404f24789b5ea9003fc25b9a3988feddebd7e7b369c8fac27ad6f52f28"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-win_amd64.whl", hash = "sha256:af148a33ff0349f53512a049c6406923e4e02bf2f26c5fb285f143faf4f0e46a"}, + {file = "SQLAlchemy-2.0.36-py3-none-any.whl", hash = "sha256:fddbe92b4760c6f5d48162aef14824add991aeda8ddadb3c31d56eb15ca69f8e"}, + {file = "sqlalchemy-2.0.36.tar.gz", hash = "sha256:7f2767680b6d2398aea7082e45a774b2b0767b5c8d8ffb9c8b683088ea9b29c5"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} +typing-extensions = ">=4.6.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=8)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3_binary"] + [[package]] name = "testcontainers" version = "4.8.2" @@ -1476,4 +1657,4 @@ yc = ["yandexcloud"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "c5df94291a23b3f5f18bf6524e801d9c933c2125bfc3dc1eac40f0fac062101a" +content-hash = "8e53fd5648bef83b6ff7b15bcaa03b0368a05b9491c51ee36c64de055b491081" diff --git a/pyproject.toml b/pyproject.toml index 2bd3837..f4f4d68 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ types-protobuf = "^5.28.0.20240924" testcontainers = "^4.8.2" pytest = "^8.3.3" pytest-asyncio = "^0.24.0" +sqlalchemy = "^2.0.36" [build-system] requires = ["poetry-core"] diff --git a/tests/test_connection.py b/tests/test_connection.py index 073f8f2..c81e304 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -34,7 +34,7 @@ def _test_isolation_level_read_only( if read_only: with pytest.raises(dbapi.DatabaseError): cursor.execute(query) - cursor.finish_query() + cursor._scroll_stream() else: cursor.execute(query) @@ -53,14 +53,14 @@ def _test_connection(self, connection: dbapi.Connection) -> None: cur = connection.cursor() with suppress(dbapi.DatabaseError): cur.execute("DROP TABLE foo") - cur.finish_query() + cur._scroll_stream() assert not connection.check_exists("/local/foo") with pytest.raises(dbapi.ProgrammingError): connection.describe("/local/foo") cur.execute("CREATE TABLE foo(id Int64 NOT NULL, PRIMARY KEY (id))") - cur.finish_query() + cur._scroll_stream() assert connection.check_exists("/local/foo") @@ -77,12 +77,12 @@ def _test_cursor_raw_query(self, connection: dbapi.Connection) -> None: with suppress(dbapi.DatabaseError): cur.execute("DROP TABLE test") - cur.finish_query() + cur._scroll_stream() cur.execute( "CREATE TABLE test(id Int64 NOT NULL, text Utf8, PRIMARY KEY (id))" ) - cur.finish_query() + cur._scroll_stream() cur.execute( """ @@ -104,7 +104,7 @@ def _test_cursor_raw_query(self, connection: dbapi.Connection) -> None: ) }, ) - cur.finish_query() + cur._scroll_stream() cur.execute("DROP TABLE test") @@ -121,7 +121,7 @@ def _test_errors(self, connection: dbapi.Connection) -> None: with suppress(dbapi.DatabaseError): cur.execute("DROP TABLE test") - cur.finish_query() + cur._scroll_stream() with pytest.raises(dbapi.DataError): cur.execute("SELECT 18446744073709551616") @@ -136,10 +136,10 @@ def _test_errors(self, connection: dbapi.Connection) -> None: cur.execute("SELECT * FROM test") cur.execute("CREATE TABLE test(id Int64, PRIMARY KEY (id))") - cur.finish_query() + cur._scroll_stream() cur.execute("INSERT INTO test(id) VALUES(1)") - cur.finish_query() + cur._scroll_stream() with pytest.raises(dbapi.IntegrityError): cur.execute("INSERT INTO test(id) VALUES(1)") @@ -214,7 +214,7 @@ async def _test_isolation_level_read_only( if read_only: with pytest.raises(dbapi.DatabaseError): await cursor.execute(query) - await cursor.finish_query() + await cursor._scroll_stream() else: await cursor.execute(query) @@ -235,7 +235,7 @@ async def _test_connection( cur = connection.cursor() with suppress(dbapi.DatabaseError): await cur.execute("DROP TABLE foo") - await cur.finish_query() + await cur._scroll_stream() assert not await connection.check_exists("/local/foo") with pytest.raises(dbapi.ProgrammingError): @@ -244,7 +244,7 @@ async def _test_connection( await cur.execute( "CREATE TABLE foo(id Int64 NOT NULL, PRIMARY KEY (id))" ) - await cur.finish_query() + await cur._scroll_stream() assert await connection.check_exists("/local/foo") @@ -263,12 +263,12 @@ async def _test_cursor_raw_query( with suppress(dbapi.DatabaseError): await cur.execute("DROP TABLE test") - await cur.finish_query() + await cur._scroll_stream() await cur.execute( "CREATE TABLE test(id Int64 NOT NULL, text Utf8, PRIMARY KEY (id))" ) - await cur.finish_query() + await cur._scroll_stream() await cur.execute( """ @@ -290,7 +290,7 @@ async def _test_cursor_raw_query( ) }, ) - await cur.finish_query() + await cur._scroll_stream() await cur.execute("DROP TABLE test") @@ -307,7 +307,7 @@ async def _test_errors(self, connection: dbapi.AsyncConnection) -> None: with suppress(dbapi.DatabaseError): await cur.execute("DROP TABLE test") - await cur.finish_query() + await cur._scroll_stream() with pytest.raises(dbapi.DataError): await cur.execute("SELECT 18446744073709551616") @@ -322,10 +322,10 @@ async def _test_errors(self, connection: dbapi.AsyncConnection) -> None: await cur.execute("SELECT * FROM test") await cur.execute("CREATE TABLE test(id Int64, PRIMARY KEY (id))") - await cur.finish_query() + await cur._scroll_stream() await cur.execute("INSERT INTO test(id) VALUES(1)") - await cur.finish_query() + await cur._scroll_stream() with pytest.raises(dbapi.IntegrityError): await cur.execute("INSERT INTO test(id) VALUES(1)") diff --git a/tests/test_cursor.py b/tests/test_cursor.py index 0a96e08..193a662 100644 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -119,41 +119,10 @@ async def test_cursor_fetch_all( assert await cursor.fetchall() == [] @pytest.mark.asyncio - async def test_cursor_next_set( + async def test_cursor_fetch_one_multiple_result_sets( self, session: ydb.aio.QuerySession ) -> None: async with ydb_dbapi.AsyncCursor(session=session) as cursor: - yql_text = """SELECT 1 as val; SELECT 2 as val;""" - await cursor.execute(query=yql_text) - - res = await cursor.fetchall() - assert res is not None - assert len(res) == 1 - assert res[0][0] == 1 - - nextset = await cursor.nextset() - assert nextset - - res = await cursor.fetchall() - assert res is not None - assert len(res) == 1 - assert res[0][0] == 2 - - nextset = await cursor.nextset() - assert nextset - - assert await cursor.fetchall() == [] - - nextset = await cursor.nextset() - assert not nextset - - @pytest.mark.asyncio - async def test_cursor_fetch_one_autoscroll( - self, session: ydb.aio.QuerySession - ) -> None: - async with ydb_dbapi.AsyncCursor( - session=session, auto_scroll_result_sets=True - ) as cursor: yql_text = """ SELECT id, val FROM table; SELECT id, val FROM table1; @@ -161,6 +130,8 @@ async def test_cursor_fetch_one_autoscroll( """ await cursor.execute(query=yql_text) + assert cursor.rowcount == 12 + for i in range(RESULT_SET_LENGTH * RESULT_SET_COUNT): res = await cursor.fetchone() assert res is not None @@ -170,12 +141,10 @@ async def test_cursor_fetch_one_autoscroll( assert not await cursor.nextset() @pytest.mark.asyncio - async def test_cursor_fetch_many_autoscroll( + async def test_cursor_fetch_many_multiple_result_sets( self, session: ydb.aio.QuerySession ) -> None: - async with ydb_dbapi.AsyncCursor( - session=session, auto_scroll_result_sets=True - ) as cursor: + async with ydb_dbapi.AsyncCursor(session=session) as cursor: yql_text = """ SELECT id, val FROM table; SELECT id, val FROM table1; @@ -183,6 +152,8 @@ async def test_cursor_fetch_many_autoscroll( """ await cursor.execute(query=yql_text) + assert cursor.rowcount == 12 + halfsize = (RESULT_SET_LENGTH * RESULT_SET_COUNT) // 2 for _ in range(2): res = await cursor.fetchmany(size=halfsize) @@ -193,12 +164,10 @@ async def test_cursor_fetch_many_autoscroll( assert not await cursor.nextset() @pytest.mark.asyncio - async def test_cursor_fetch_all_autoscroll( + async def test_cursor_fetch_all_multiple_result_sets( self, session: ydb.aio.QuerySession ) -> None: - async with ydb_dbapi.AsyncCursor( - session=session, auto_scroll_result_sets=True - ) as cursor: + async with ydb_dbapi.AsyncCursor(session=session) as cursor: yql_text = """ SELECT id, val FROM table; SELECT id, val FROM table1; @@ -206,6 +175,8 @@ async def test_cursor_fetch_all_autoscroll( """ await cursor.execute(query=yql_text) + assert cursor.rowcount == 12 + res = await cursor.fetchall() assert len(res) == RESULT_SET_COUNT * RESULT_SET_LENGTH @@ -274,38 +245,10 @@ def test_cursor_fetch_all(self, session_sync: ydb.QuerySession) -> None: assert cursor.fetchall() == [] - def test_cursor_next_set(self, session_sync: ydb.QuerySession) -> None: - with ydb_dbapi.Cursor(session=session_sync) as cursor: - yql_text = """SELECT 1 as val; SELECT 2 as val;""" - cursor.execute(query=yql_text) - - res = cursor.fetchall() - assert res is not None - assert len(res) == 1 - assert res[0][0] == 1 - - nextset = cursor.nextset() - assert nextset - - res = cursor.fetchall() - assert res is not None - assert len(res) == 1 - assert res[0][0] == 2 - - nextset = cursor.nextset() - assert nextset - - assert cursor.fetchall() == [] - - nextset = cursor.nextset() - assert not nextset - - def test_cursor_fetch_one_autoscroll( + def test_cursor_fetch_one_multiple_result_sets( self, session_sync: ydb.QuerySession ) -> None: - with ydb_dbapi.Cursor( - session=session_sync, auto_scroll_result_sets=True - ) as cursor: + with ydb_dbapi.Cursor(session=session_sync) as cursor: yql_text = """ SELECT id, val FROM table; SELECT id, val FROM table1; @@ -313,6 +256,8 @@ def test_cursor_fetch_one_autoscroll( """ cursor.execute(query=yql_text) + assert cursor.rowcount == 12 + for i in range(RESULT_SET_LENGTH * RESULT_SET_COUNT): res = cursor.fetchone() assert res is not None @@ -321,12 +266,10 @@ def test_cursor_fetch_one_autoscroll( assert cursor.fetchone() is None assert not cursor.nextset() - def test_cursor_fetch_many_autoscroll( + def test_cursor_fetch_many_multiple_result_sets( self, session_sync: ydb.QuerySession ) -> None: - with ydb_dbapi.Cursor( - session=session_sync, auto_scroll_result_sets=True - ) as cursor: + with ydb_dbapi.Cursor(session=session_sync) as cursor: yql_text = """ SELECT id, val FROM table; SELECT id, val FROM table1; @@ -334,6 +277,8 @@ def test_cursor_fetch_many_autoscroll( """ cursor.execute(query=yql_text) + assert cursor.rowcount == 12 + halfsize = (RESULT_SET_LENGTH * RESULT_SET_COUNT) // 2 for _ in range(2): res = cursor.fetchmany(size=halfsize) @@ -343,12 +288,10 @@ def test_cursor_fetch_many_autoscroll( assert cursor.fetchmany(2) == [] assert not cursor.nextset() - def test_cursor_fetch_all_autoscroll( + def test_cursor_fetch_all_multiple_result_sets( self, session_sync: ydb.QuerySession ) -> None: - with ydb_dbapi.Cursor( - session=session_sync, auto_scroll_result_sets=True - ) as cursor: + with ydb_dbapi.Cursor(session=session_sync) as cursor: yql_text = """ SELECT id, val FROM table; SELECT id, val FROM table1; @@ -356,6 +299,8 @@ def test_cursor_fetch_all_autoscroll( """ cursor.execute(query=yql_text) + assert cursor.rowcount == 12 + res = cursor.fetchall() assert len(res) == RESULT_SET_COUNT * RESULT_SET_LENGTH diff --git a/ydb_dbapi/cursors.py b/ydb_dbapi/cursors.py index 8213503..07a6727 100644 --- a/ydb_dbapi/cursors.py +++ b/ydb_dbapi/cursors.py @@ -31,7 +31,7 @@ def _get_column_type(type_obj: Any) -> str: return str(ydb.convert.type_to_native(type_obj)) -class BaseCursor: +class BufferedCursor: arraysize: int = 1 _rows: Iterator | None = None _rows_count: int = -1 @@ -64,10 +64,26 @@ def _rows_iterable( except ydb.Error as e: raise DatabaseError(e.message, original_error=e) from e - def _update_result_set(self, result_set: ydb.convert.ResultSet) -> None: + def _update_result_set( + self, + result_set: ydb.convert.ResultSet, + replace_current: bool = True, + ) -> None: self._update_description(result_set) - self._rows = self._rows_iterable(result_set) - self._rows_count = len(result_set.rows) or -1 + + new_rows_iter = self._rows_iterable(result_set) + new_rows_count = len(result_set.rows) or -1 + + if self._rows is None or replace_current: + self._rows = new_rows_iter + self._rows_count = new_rows_count + else: + self._rows = itertools.chain(self._rows, new_rows_iter) + if new_rows_count != -1: + if self._rows_count != -1: + self._rows_count += new_rows_count + else: + self._rows_count = new_rows_count def _update_description(self, result_set: ydb.convert.ResultSet) -> None: self._description = [ @@ -104,70 +120,44 @@ def _begin_query(self) -> None: self._state = CursorStatus.running def _fetchone_from_buffer(self) -> tuple | None: + self._raise_if_closed() return next(self._rows or iter([]), None) def _fetchmany_from_buffer(self, size: int | None = None) -> list: + self._raise_if_closed() return list( itertools.islice(self._rows or iter([]), size or self.arraysize) ) def _fetchall_from_buffer(self) -> list: + self._raise_if_closed() return list(self._rows or iter([])) -class Cursor(BaseCursor): +class Cursor(BufferedCursor): def __init__( self, session: ydb.QuerySession, tx_context: ydb.QueryTxContext | None = None, table_path_prefix: str = "", autocommit: bool = True, - auto_scroll_result_sets: bool = False, ) -> None: self._session = session self._tx_context = tx_context self._table_path_prefix = table_path_prefix self._autocommit = autocommit - self._auto_scroll = auto_scroll_result_sets self._stream: Iterator | None = None def fetchone(self) -> tuple | None: - row = self._fetchone_from_buffer() - if not self._auto_scroll: - return row - - if row is None: - while self.nextset(): - # We should skip empty result sets - row = self._fetchone_from_buffer() - if row is not None: - return row - - return row + return self._fetchone_from_buffer() def fetchmany(self, size: int | None = None) -> list: size = size or self.arraysize - rows = self._fetchmany_from_buffer(size) - if not self._auto_scroll: - return rows - - while len(rows) < size and self.nextset(): - new_rows = self._fetchmany_from_buffer(size - len(rows)) - rows.extend(new_rows) - - return rows + return self._fetchmany_from_buffer(size) def fetchall(self) -> list: - rows = self._fetchall_from_buffer() - if not self._auto_scroll: - return rows - - while self.nextset(): - new_rows = self._fetchall_from_buffer() - rows.extend(new_rows) - - return rows + return self._fetchall_from_buffer() @handle_ydb_errors def _execute_generic_query( @@ -193,7 +183,6 @@ def execute( self, query: str, parameters: ParametersType | None = None, - prefetch_first_set: bool = True, ) -> None: self._raise_if_closed() self._raise_if_running() @@ -211,19 +200,18 @@ def execute( self._begin_query() - if prefetch_first_set: - self.nextset() + self._scroll_stream(replace_current=False) async def executemany(self) -> None: pass @handle_ydb_errors - def nextset(self) -> bool: + def nextset(self, replace_current: bool = True) -> bool: if self._stream is None: return False try: result_set = self._stream.__next__() - self._update_result_set(result_set) + self._update_result_set(result_set, replace_current) except (StopIteration, StopAsyncIteration, RuntimeError): self._state = CursorStatus.finished return False @@ -232,12 +220,12 @@ def nextset(self) -> bool: raise return True - def finish_query(self) -> None: + def _scroll_stream(self, replace_current: bool = True) -> None: self._raise_if_closed() next_set_available = True while next_set_available: - next_set_available = self.nextset() + next_set_available = self.nextset(replace_current) self._state = CursorStatus.finished @@ -245,7 +233,7 @@ def close(self) -> None: if self._state == CursorStatus.closed: return - self.finish_query() + self._scroll_stream() self._state = CursorStatus.closed def __enter__(self) -> Self: @@ -260,58 +248,30 @@ def __exit__( self.close() -class AsyncCursor(BaseCursor): +class AsyncCursor(BufferedCursor): def __init__( self, session: ydb.aio.QuerySession, tx_context: ydb.aio.QueryTxContext | None = None, table_path_prefix: str = "", autocommit: bool = True, - auto_scroll_result_sets: bool = False, ) -> None: self._session = session self._tx_context = tx_context self._table_path_prefix = table_path_prefix self._autocommit = autocommit - self._auto_scroll = auto_scroll_result_sets self._stream: AsyncIterator | None = None async def fetchone(self) -> tuple | None: - row = self._fetchone_from_buffer() - if not self._auto_scroll: - return row - - if row is None: - while await self.nextset(): - row = self._fetchone_from_buffer() - if row is not None: - return row - - return row + return self._fetchone_from_buffer() async def fetchmany(self, size: int | None = None) -> list: size = size or self.arraysize - rows = self._fetchmany_from_buffer(size) - if not self._auto_scroll: - return rows - - while len(rows) < size and await self.nextset(): - new_rows = self._fetchmany_from_buffer(size - len(rows)) - rows.extend(new_rows) - - return rows + return self._fetchmany_from_buffer(size) async def fetchall(self) -> list: - rows = self._fetchall_from_buffer() - if not self._auto_scroll: - return rows - - while await self.nextset(): - new_rows = self._fetchall_from_buffer() - rows.extend(new_rows) - - return rows + return self._fetchall_from_buffer() @handle_ydb_errors async def _execute_generic_query( @@ -337,7 +297,6 @@ async def execute( self, query: str, parameters: ParametersType | None = None, - prefetch_first_set: bool = True, ) -> None: self._raise_if_closed() self._raise_if_running() @@ -355,19 +314,18 @@ async def execute( self._begin_query() - if prefetch_first_set: - await self.nextset() + await self._scroll_stream(replace_current=False) async def executemany(self) -> None: pass @handle_ydb_errors - async def nextset(self) -> bool: + async def nextset(self, replace_current: bool = True) -> bool: if self._stream is None: return False try: result_set = await self._stream.__anext__() - self._update_result_set(result_set) + self._update_result_set(result_set, replace_current) except (StopIteration, StopAsyncIteration, RuntimeError): self._stream = None self._state = CursorStatus.finished @@ -377,12 +335,12 @@ async def nextset(self) -> bool: raise return True - async def finish_query(self) -> None: + async def _scroll_stream(self, replace_current: bool = True) -> None: self._raise_if_closed() next_set_available = True while next_set_available: - next_set_available = await self.nextset() + next_set_available = await self.nextset(replace_current) self._state = CursorStatus.finished @@ -390,7 +348,7 @@ async def close(self) -> None: if self._state == CursorStatus.closed: return - await self.finish_query() + await self._scroll_stream() self._state = CursorStatus.closed async def __aenter__(self) -> Self: From d33aa109128cd4c6c28808bc19d1358c7b77b0bb Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 17:52:46 +0300 Subject: [PATCH 30/33] refactor test --- poetry.lock | 2 +- pyproject.toml | 1 + tests/test_connection.py | 278 ++++++++++----------------------------- ydb_dbapi/connections.py | 4 - 4 files changed, 71 insertions(+), 214 deletions(-) diff --git a/poetry.lock b/poetry.lock index 4acaeff..9a28c30 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1657,4 +1657,4 @@ yc = ["yandexcloud"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "8e53fd5648bef83b6ff7b15bcaa03b0368a05b9491c51ee36c64de055b491081" +content-hash = "7d4d6451a59b96db5c55cfc98f1659e08223fcf702bbeca16d1c9da5a10b19bc" diff --git a/pyproject.toml b/pyproject.toml index f4f4d68..f904b59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ testcontainers = "^4.8.2" pytest = "^8.3.3" pytest-asyncio = "^0.24.0" sqlalchemy = "^2.0.36" +greenlet = "^3.1.1" [build-system] requires = ["poetry-core"] diff --git a/tests/test_connection.py b/tests/test_connection.py index c81e304..fff8b06 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -8,9 +8,17 @@ import pytest_asyncio import ydb import ydb_dbapi as dbapi +from sqlalchemy.util import await_only, greenlet_spawn +from inspect import iscoroutine -class BaseSyncDBApiTestSuit: +def maybe_await(obj): + if not iscoroutine(obj): + return obj + return await_only(obj) + + +class BaseDBApiTestSuit: def _test_isolation_level_read_only( self, connection: dbapi.Connection, @@ -18,73 +26,69 @@ def _test_isolation_level_read_only( read_only: bool, ) -> None: connection.set_isolation_level("AUTOCOMMIT") - with connection.cursor() as cursor: # noqa: SIM117 - with suppress(dbapi.DatabaseError): - cursor.execute("DROP TABLE foo") + cursor = connection.cursor() + with suppress(dbapi.DatabaseError): + maybe_await(cursor.execute("DROP TABLE foo")) - with connection.cursor() as cursor: - cursor.execute( - "CREATE TABLE foo(id Int64 NOT NULL, PRIMARY KEY (id))" - ) + cursor = connection.cursor() + maybe_await(cursor.execute( + "CREATE TABLE foo(id Int64 NOT NULL, PRIMARY KEY (id))" + )) connection.set_isolation_level(isolation_level) + cursor = connection.cursor() - with connection.cursor() as cursor: - query = "UPSERT INTO foo(id) VALUES (1)" - if read_only: - with pytest.raises(dbapi.DatabaseError): - cursor.execute(query) - cursor._scroll_stream() + query = "UPSERT INTO foo(id) VALUES (1)" + if read_only: + with pytest.raises(dbapi.DatabaseError): + maybe_await(cursor.execute(query)) - else: - cursor.execute(query) + else: + maybe_await(cursor.execute(query)) - connection.rollback() + maybe_await(connection.rollback()) connection.set_isolation_level("AUTOCOMMIT") - with connection.cursor() as cursor: - cursor.execute("DROP TABLE foo") + cursor = connection.cursor() + + maybe_await(cursor.execute("DROP TABLE foo")) def _test_connection(self, connection: dbapi.Connection) -> None: - connection.commit() - connection.rollback() + maybe_await(connection.commit()) + maybe_await(connection.rollback()) cur = connection.cursor() with suppress(dbapi.DatabaseError): - cur.execute("DROP TABLE foo") - cur._scroll_stream() + maybe_await(cur.execute("DROP TABLE foo")) - assert not connection.check_exists("/local/foo") + assert not maybe_await(connection.check_exists("/local/foo")) with pytest.raises(dbapi.ProgrammingError): - connection.describe("/local/foo") + maybe_await(connection.describe("/local/foo")) - cur.execute("CREATE TABLE foo(id Int64 NOT NULL, PRIMARY KEY (id))") - cur._scroll_stream() + maybe_await(cur.execute("CREATE TABLE foo(id Int64 NOT NULL, PRIMARY KEY (id))")) - assert connection.check_exists("/local/foo") + assert maybe_await(connection.check_exists("/local/foo")) - col = (connection.describe("/local/foo")).columns[0] + col = (maybe_await(connection.describe("/local/foo"))).columns[0] assert col.name == "id" assert col.type == ydb.PrimitiveType.Int64 - cur.execute("DROP TABLE foo") - cur.close() + maybe_await(cur.execute("DROP TABLE foo")) + maybe_await(cur.close()) def _test_cursor_raw_query(self, connection: dbapi.Connection) -> None: cur = connection.cursor() assert cur with suppress(dbapi.DatabaseError): - cur.execute("DROP TABLE test") - cur._scroll_stream() + maybe_await(cur.execute("DROP TABLE test")) - cur.execute( + maybe_await(cur.execute( "CREATE TABLE test(id Int64 NOT NULL, text Utf8, PRIMARY KEY (id))" - ) - cur._scroll_stream() + )) - cur.execute( + maybe_await(cur.execute( """ DECLARE $data AS List>; @@ -103,52 +107,48 @@ def _test_cursor_raw_query(self, connection: dbapi.Connection) -> None: ), ) }, - ) - cur._scroll_stream() + )) - cur.execute("DROP TABLE test") + maybe_await(cur.execute("DROP TABLE test")) - cur.close() + maybe_await(cur.close()) - def _test_errors(self, connection: dbapi.Connection) -> None: + def _test_errors(self, connection: dbapi.Connection, connect_method=dbapi.connect) -> None: with pytest.raises(dbapi.InterfaceError): - dbapi.connect( + maybe_await(connect_method( "localhost:2136", # type: ignore database="/local666", # type: ignore - ) + )) cur = connection.cursor() with suppress(dbapi.DatabaseError): - cur.execute("DROP TABLE test") - cur._scroll_stream() + maybe_await(cur.execute("DROP TABLE test")) with pytest.raises(dbapi.DataError): - cur.execute("SELECT 18446744073709551616") + maybe_await(cur.execute("SELECT 18446744073709551616")) with pytest.raises(dbapi.DataError): - cur.execute("SELECT * FROM 拉屎") + maybe_await(cur.execute("SELECT * FROM 拉屎")) with pytest.raises(dbapi.DataError): - cur.execute("SELECT floor(5 / 2)") + maybe_await(cur.execute("SELECT floor(5 / 2)")) with pytest.raises(dbapi.ProgrammingError): - cur.execute("SELECT * FROM test") + maybe_await(cur.execute("SELECT * FROM test")) - cur.execute("CREATE TABLE test(id Int64, PRIMARY KEY (id))") - cur._scroll_stream() + maybe_await(cur.execute("CREATE TABLE test(id Int64, PRIMARY KEY (id))")) - cur.execute("INSERT INTO test(id) VALUES(1)") - cur._scroll_stream() + maybe_await(cur.execute("INSERT INTO test(id) VALUES(1)")) with pytest.raises(dbapi.IntegrityError): - cur.execute("INSERT INTO test(id) VALUES(1)") + maybe_await(cur.execute("INSERT INTO test(id) VALUES(1)")) - cur.execute("DROP TABLE test") - cur.close() + maybe_await(cur.execute("DROP TABLE test")) + maybe_await(cur.close()) -class TestConnection(BaseSyncDBApiTestSuit): +class TestConnection(BaseDBApiTestSuit): @pytest.fixture def connection( self, connection_kwargs: dict @@ -190,160 +190,19 @@ def test_errors(self, connection: dbapi.Connection) -> None: self._test_errors(connection) -class BaseAsyncDBApiTestSuit: - async def _test_isolation_level_read_only( - self, - connection: dbapi.AsyncConnection, - isolation_level: str, - read_only: bool, - ) -> None: - connection.set_isolation_level("AUTOCOMMIT") - async with connection.cursor() as cursor: - with suppress(dbapi.DatabaseError): - await cursor.execute("DROP TABLE foo") - - async with connection.cursor() as cursor: - await cursor.execute( - "CREATE TABLE foo(id Int64 NOT NULL, PRIMARY KEY (id))" - ) - - connection.set_isolation_level(isolation_level) - - async with connection.cursor() as cursor: - query = "UPSERT INTO foo(id) VALUES (1)" - if read_only: - with pytest.raises(dbapi.DatabaseError): - await cursor.execute(query) - await cursor._scroll_stream() - - else: - await cursor.execute(query) - - await connection.rollback() - - connection.set_isolation_level("AUTOCOMMIT") - - async with connection.cursor() as cursor: - await cursor.execute("DROP TABLE foo") - - async def _test_connection( - self, connection: dbapi.AsyncConnection - ) -> None: - await connection.commit() - await connection.rollback() - - cur = connection.cursor() - with suppress(dbapi.DatabaseError): - await cur.execute("DROP TABLE foo") - await cur._scroll_stream() - - assert not await connection.check_exists("/local/foo") - with pytest.raises(dbapi.ProgrammingError): - await connection.describe("/local/foo") - - await cur.execute( - "CREATE TABLE foo(id Int64 NOT NULL, PRIMARY KEY (id))" - ) - await cur._scroll_stream() - - assert await connection.check_exists("/local/foo") - - col = (await connection.describe("/local/foo")).columns[0] - assert col.name == "id" - assert col.type == ydb.PrimitiveType.Int64 - - await cur.execute("DROP TABLE foo") - await cur.close() - - async def _test_cursor_raw_query( - self, connection: dbapi.AsyncConnection - ) -> None: - cur = connection.cursor() - assert cur - - with suppress(dbapi.DatabaseError): - await cur.execute("DROP TABLE test") - await cur._scroll_stream() - - await cur.execute( - "CREATE TABLE test(id Int64 NOT NULL, text Utf8, PRIMARY KEY (id))" - ) - await cur._scroll_stream() - - await cur.execute( - """ - DECLARE $data AS List>; - - INSERT INTO test SELECT id, text FROM AS_TABLE($data); - """, - { - "$data": ydb.TypedValue( - [ - {"id": 17, "text": "seventeen"}, - {"id": 21, "text": "twenty one"}, - ], - ydb.ListType( - ydb.StructType() - .add_member("id", ydb.PrimitiveType.Int64) - .add_member("text", ydb.PrimitiveType.Utf8) - ), - ) - }, - ) - await cur._scroll_stream() - - await cur.execute("DROP TABLE test") - - await cur.close() - - async def _test_errors(self, connection: dbapi.AsyncConnection) -> None: - with pytest.raises(dbapi.InterfaceError): - await dbapi.async_connect( - "localhost:2136", # type: ignore - database="/local666", # type: ignore - ) - - cur = connection.cursor() - - with suppress(dbapi.DatabaseError): - await cur.execute("DROP TABLE test") - await cur._scroll_stream() - - with pytest.raises(dbapi.DataError): - await cur.execute("SELECT 18446744073709551616") - - with pytest.raises(dbapi.DataError): - await cur.execute("SELECT * FROM 拉屎") - - with pytest.raises(dbapi.DataError): - await cur.execute("SELECT floor(5 / 2)") - - with pytest.raises(dbapi.ProgrammingError): - await cur.execute("SELECT * FROM test") - - await cur.execute("CREATE TABLE test(id Int64, PRIMARY KEY (id))") - await cur._scroll_stream() - - await cur.execute("INSERT INTO test(id) VALUES(1)") - await cur._scroll_stream() - - with pytest.raises(dbapi.IntegrityError): - await cur.execute("INSERT INTO test(id) VALUES(1)") - - await cur.execute("DROP TABLE test") - await cur.close() - - -class TestAsyncConnection(BaseAsyncDBApiTestSuit): +class TestAsyncConnection(BaseDBApiTestSuit): @pytest_asyncio.fixture async def connection( self, connection_kwargs: dict ) -> AsyncGenerator[dbapi.AsyncConnection]: - conn = await dbapi.async_connect(**connection_kwargs) # ignore: typing + def connect(): + return maybe_await(dbapi.async_connect(**connection_kwargs)) + + conn = await greenlet_spawn(connect) try: yield conn finally: - await conn.close() + await greenlet_spawn(conn.close) @pytest.mark.asyncio @pytest.mark.parametrize( @@ -363,20 +222,21 @@ async def test_isolation_level_read_only( read_only: bool, connection: dbapi.AsyncConnection, ) -> None: - await self._test_isolation_level_read_only( + await greenlet_spawn( + self._test_isolation_level_read_only, connection, isolation_level, read_only ) @pytest.mark.asyncio async def test_connection(self, connection: dbapi.AsyncConnection) -> None: - await self._test_connection(connection) + await greenlet_spawn(self._test_connection, connection) @pytest.mark.asyncio async def test_cursor_raw_query( self, connection: dbapi.AsyncConnection ) -> None: - await self._test_cursor_raw_query(connection) + await greenlet_spawn(self._test_cursor_raw_query, connection) @pytest.mark.asyncio async def test_errors(self, connection: dbapi.AsyncConnection) -> None: - await self._test_errors(connection) + await greenlet_spawn(self._test_errors, connection) diff --git a/ydb_dbapi/connections.py b/ydb_dbapi/connections.py index 10312da..98f2af2 100644 --- a/ydb_dbapi/connections.py +++ b/ydb_dbapi/connections.py @@ -135,10 +135,6 @@ def get_isolation_level(self) -> str: def cursor(self) -> Cursor | AsyncCursor: if self._session is None: raise RuntimeError("Connection is not ready, use wait_ready.") - if self._current_cursor and not self._current_cursor.is_closed: - raise RuntimeError( - "Unable to create new Cursor before closing existing one." - ) if self.interactive_transaction: self._tx_context = self._session.transaction(self._tx_mode) From c98d9583fe85ed3a6187081f59d25539cc0050cf Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Tue, 29 Oct 2024 18:47:41 +0300 Subject: [PATCH 31/33] refactor cursor test --- pyproject.toml | 2 +- tests/conftest.py | 42 ++++ tests/test_connection.py | 23 ++- tests/test_cursor.py | 409 +++++++++++++++------------------------ 4 files changed, 217 insertions(+), 259 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f904b59..9be0694 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,7 @@ force-single-line = true [tool.ruff.lint.per-file-ignores] "**/test_*.py" = ["S", "SLF", "ANN201", "ARG", "PLR2004", "PT012"] -"conftest.py" = ["ARG001"] +"conftest.py" = ["S", "ARG001"] "__init__.py" = ["F401", "F403"] [tool.pytest.ini_options] diff --git a/tests/conftest.py b/tests/conftest.py index d3eb051..169e463 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -185,3 +185,45 @@ def session_pool_sync( ) yield session_pool + + +@pytest.fixture +async def session( + session_pool: ydb.aio.QuerySessionPool, +) -> AsyncGenerator[ydb.aio.QuerySession]: + for name in ["table", "table1", "table2"]: + await session_pool.execute_with_retries( + f""" + DELETE FROM {name}; + INSERT INTO {name} (id, val) VALUES + (0, 0), + (1, 1), + (2, 2), + (3, 3) + """ + ) + + session = await session_pool.acquire() + yield session + await session_pool.release(session) + + +@pytest.fixture +def session_sync( + session_pool_sync: ydb.QuerySessionPool, +) -> Generator[ydb.QuerySession]: + for name in ["table", "table1", "table2"]: + session_pool_sync.execute_with_retries( + f""" + DELETE FROM {name}; + INSERT INTO {name} (id, val) VALUES + (0, 0), + (1, 1), + (2, 2), + (3, 3) + """ + ) + + session = session_pool_sync.acquire() + yield session + session_pool_sync.release(session) diff --git a/tests/test_connection.py b/tests/test_connection.py index fff8b06..0488f0f 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -3,16 +3,17 @@ from collections.abc import AsyncGenerator from collections.abc import Generator from contextlib import suppress +from inspect import iscoroutine import pytest import pytest_asyncio import ydb import ydb_dbapi as dbapi -from sqlalchemy.util import await_only, greenlet_spawn -from inspect import iscoroutine +from sqlalchemy.util import await_only +from sqlalchemy.util import greenlet_spawn -def maybe_await(obj): +def maybe_await(obj: callable) -> any: if not iscoroutine(obj): return obj return await_only(obj) @@ -66,7 +67,9 @@ def _test_connection(self, connection: dbapi.Connection) -> None: with pytest.raises(dbapi.ProgrammingError): maybe_await(connection.describe("/local/foo")) - maybe_await(cur.execute("CREATE TABLE foo(id Int64 NOT NULL, PRIMARY KEY (id))")) + maybe_await(cur.execute( + "CREATE TABLE foo(id Int64 NOT NULL, PRIMARY KEY (id))" + )) assert maybe_await(connection.check_exists("/local/foo")) @@ -113,7 +116,11 @@ def _test_cursor_raw_query(self, connection: dbapi.Connection) -> None: maybe_await(cur.close()) - def _test_errors(self, connection: dbapi.Connection, connect_method=dbapi.connect) -> None: + def _test_errors( + self, + connection: dbapi.Connection, + connect_method: callable = dbapi.connect + ) -> None: with pytest.raises(dbapi.InterfaceError): maybe_await(connect_method( "localhost:2136", # type: ignore @@ -137,7 +144,9 @@ def _test_errors(self, connection: dbapi.Connection, connect_method=dbapi.connec with pytest.raises(dbapi.ProgrammingError): maybe_await(cur.execute("SELECT * FROM test")) - maybe_await(cur.execute("CREATE TABLE test(id Int64, PRIMARY KEY (id))")) + maybe_await(cur.execute( + "CREATE TABLE test(id Int64, PRIMARY KEY (id))" + )) maybe_await(cur.execute("INSERT INTO test(id) VALUES(1)")) @@ -195,7 +204,7 @@ class TestAsyncConnection(BaseDBApiTestSuit): async def connection( self, connection_kwargs: dict ) -> AsyncGenerator[dbapi.AsyncConnection]: - def connect(): + def connect() -> dbapi.AsyncConnection: return maybe_await(dbapi.async_connect(**connection_kwargs)) conn = await greenlet_spawn(connect) diff --git a/tests/test_cursor.py b/tests/test_cursor.py index 193a662..7e5c433 100644 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -1,309 +1,216 @@ +from __future__ import annotations + from collections.abc import AsyncGenerator from collections.abc import Generator +from inspect import iscoroutine import pytest import ydb -import ydb_dbapi - - -@pytest.fixture -async def session( - session_pool: ydb.aio.QuerySessionPool, -) -> AsyncGenerator[ydb.aio.QuerySession]: - for name in ["table", "table1", "table2"]: - await session_pool.execute_with_retries( - f""" - DELETE FROM {name}; - INSERT INTO {name} (id, val) VALUES - (0, 0), - (1, 1), - (2, 2), - (3, 3) - """ - ) +from sqlalchemy.util import await_only +from sqlalchemy.util import greenlet_spawn +from ydb_dbapi import AsyncCursor +from ydb_dbapi import Cursor - session = await session_pool.acquire() - yield session - await session_pool.release(session) - - -@pytest.fixture -def session_sync( - session_pool_sync: ydb.QuerySessionPool, -) -> Generator[ydb.QuerySession]: - for name in ["table", "table1", "table2"]: - session_pool_sync.execute_with_retries( - f""" - DELETE FROM {name}; - INSERT INTO {name} (id, val) VALUES - (0, 0), - (1, 1), - (2, 2), - (3, 3) - """ - ) - session = session_pool_sync.acquire() - yield session - session_pool_sync.release(session) +def maybe_await(obj: callable) -> any: + if not iscoroutine(obj): + return obj + return await_only(obj) RESULT_SET_LENGTH = 4 RESULT_SET_COUNT = 3 -class TestAsyncCursor: - @pytest.mark.asyncio - async def test_cursor_fetch_one( - self, session: ydb.aio.QuerySession - ) -> None: - async with ydb_dbapi.AsyncCursor(session=session) as cursor: - yql_text = """ - SELECT id, val FROM table - """ - await cursor.execute(query=yql_text) +class BaseCursorTestSuit: + def _test_cursor_fetch_one(self, cursor: Cursor | AsyncCursor) -> None: + yql_text = """ + SELECT id, val FROM table + """ + maybe_await(cursor.execute(query=yql_text)) - for i in range(RESULT_SET_LENGTH): - res = await cursor.fetchone() - assert res is not None - assert res[0] == i + for i in range(4): + res = maybe_await(cursor.fetchone()) + assert res is not None + assert res[0] == i - assert await cursor.fetchone() is None + assert maybe_await(cursor.fetchone()) is None - @pytest.mark.asyncio - async def test_cursor_fetch_many( - self, session: ydb.aio.QuerySession - ) -> None: - async with ydb_dbapi.AsyncCursor(session=session) as cursor: - yql_text = """ - SELECT id, val FROM table - """ - await cursor.execute(query=yql_text) + def _test_cursor_fetch_many(self, cursor: Cursor | AsyncCursor) -> None: + yql_text = """ + SELECT id, val FROM table + """ + maybe_await(cursor.execute(query=yql_text)) - res = await cursor.fetchmany() - assert res is not None - assert len(res) == 1 - assert res[0][0] == 0 + res = maybe_await(cursor.fetchmany()) + assert res is not None + assert len(res) == 1 + assert res[0][0] == 0 - res = await cursor.fetchmany(size=2) - assert res is not None - assert len(res) == 2 - assert res[0][0] == 1 - assert res[1][0] == 2 + res = maybe_await(cursor.fetchmany(size=2)) + assert res is not None + assert len(res) == 2 + assert res[0][0] == 1 + assert res[1][0] == 2 - res = await cursor.fetchmany(size=2) - assert res is not None - assert len(res) == 1 - assert res[0][0] == 3 + res = maybe_await(cursor.fetchmany(size=2)) + assert res is not None + assert len(res) == 1 + assert res[0][0] == 3 - assert await cursor.fetchmany(size=2) == [] + assert maybe_await(cursor.fetchmany(size=2)) == [] - @pytest.mark.asyncio - async def test_cursor_fetch_all( - self, session: ydb.aio.QuerySession - ) -> None: - async with ydb_dbapi.AsyncCursor(session=session) as cursor: - yql_text = """ - SELECT id, val FROM table - """ - await cursor.execute(query=yql_text) + def _test_cursor_fetch_all(self, cursor: Cursor | AsyncCursor) -> None: + yql_text = """ + SELECT id, val FROM table + """ + maybe_await(cursor.execute(query=yql_text)) - assert cursor.rowcount == RESULT_SET_LENGTH + assert cursor.rowcount == 4 - res = await cursor.fetchall() - assert res is not None - assert len(res) == RESULT_SET_LENGTH - for i in range(RESULT_SET_LENGTH): - assert res[i][0] == i + res = maybe_await(cursor.fetchall()) + assert res is not None + assert len(res) == 4 + for i in range(4): + assert res[i][0] == i - assert await cursor.fetchall() == [] + assert maybe_await(cursor.fetchall()) == [] - @pytest.mark.asyncio - async def test_cursor_fetch_one_multiple_result_sets( - self, session: ydb.aio.QuerySession + def _test_cursor_fetch_one_multiple_result_sets( + self, cursor: Cursor | AsyncCursor ) -> None: - async with ydb_dbapi.AsyncCursor(session=session) as cursor: - yql_text = """ - SELECT id, val FROM table; - SELECT id, val FROM table1; - SELECT id, val FROM table2; - """ - await cursor.execute(query=yql_text) + yql_text = """ + SELECT id, val FROM table; + SELECT id, val FROM table1; + SELECT id, val FROM table2; + """ + maybe_await(cursor.execute(query=yql_text)) - assert cursor.rowcount == 12 + assert cursor.rowcount == 12 - for i in range(RESULT_SET_LENGTH * RESULT_SET_COUNT): - res = await cursor.fetchone() - assert res is not None - assert res[0] == i % RESULT_SET_LENGTH + for i in range(RESULT_SET_LENGTH * RESULT_SET_COUNT): + res = maybe_await(cursor.fetchone()) + assert res is not None + assert res[0] == i % RESULT_SET_LENGTH - assert await cursor.fetchone() is None - assert not await cursor.nextset() + assert maybe_await(cursor.fetchone()) is None + assert not maybe_await(cursor.nextset()) - @pytest.mark.asyncio - async def test_cursor_fetch_many_multiple_result_sets( - self, session: ydb.aio.QuerySession + def _test_cursor_fetch_many_multiple_result_sets( + self, cursor: Cursor | AsyncCursor ) -> None: - async with ydb_dbapi.AsyncCursor(session=session) as cursor: - yql_text = """ - SELECT id, val FROM table; - SELECT id, val FROM table1; - SELECT id, val FROM table2; - """ - await cursor.execute(query=yql_text) - - assert cursor.rowcount == 12 - - halfsize = (RESULT_SET_LENGTH * RESULT_SET_COUNT) // 2 - for _ in range(2): - res = await cursor.fetchmany(size=halfsize) - assert res is not None - assert len(res) == halfsize + yql_text = """ + SELECT id, val FROM table; + SELECT id, val FROM table1; + SELECT id, val FROM table2; + """ + maybe_await(cursor.execute(query=yql_text)) + + assert cursor.rowcount == 12 + + halfsize = (RESULT_SET_LENGTH * RESULT_SET_COUNT) // 2 + for _ in range(2): + res = maybe_await(cursor.fetchmany(size=halfsize)) + assert res is not None + assert len(res) == halfsize - assert await cursor.fetchmany(2) == [] - assert not await cursor.nextset() + assert maybe_await(cursor.fetchmany(2)) == [] + assert not maybe_await(cursor.nextset()) - @pytest.mark.asyncio - async def test_cursor_fetch_all_multiple_result_sets( - self, session: ydb.aio.QuerySession + def _test_cursor_fetch_all_multiple_result_sets( + self, cursor: Cursor | AsyncCursor ) -> None: - async with ydb_dbapi.AsyncCursor(session=session) as cursor: - yql_text = """ - SELECT id, val FROM table; - SELECT id, val FROM table1; - SELECT id, val FROM table2; - """ - await cursor.execute(query=yql_text) + yql_text = """ + SELECT id, val FROM table; + SELECT id, val FROM table1; + SELECT id, val FROM table2; + """ + maybe_await(cursor.execute(query=yql_text)) - assert cursor.rowcount == 12 + assert cursor.rowcount == 12 - res = await cursor.fetchall() + res = maybe_await(cursor.fetchall()) - assert len(res) == RESULT_SET_COUNT * RESULT_SET_LENGTH + assert len(res) == RESULT_SET_COUNT * RESULT_SET_LENGTH - assert await cursor.fetchall() == [] - assert not await cursor.nextset() + assert maybe_await(cursor.fetchall()) == [] + assert not maybe_await(cursor.nextset()) -# The same test class as above but for Cursor +class TestCursor(BaseCursorTestSuit): + @pytest.fixture + def sync_cursor(self, session_sync: ydb.QuerySession) -> Generator[Cursor]: + cursor = Cursor(session_sync) + yield cursor + cursor.close() + def test_cursor_fetch_one(self, sync_cursor: Cursor) -> None: + self._test_cursor_fetch_one(sync_cursor) -class TestCursor: - def test_cursor_fetch_one(self, session_sync: ydb.QuerySession) -> None: - with ydb_dbapi.Cursor(session=session_sync) as cursor: - yql_text = """ - SELECT id, val FROM table - """ - cursor.execute(query=yql_text) + def test_cursor_fetch_many(self, sync_cursor: Cursor) -> None: + self._test_cursor_fetch_many(sync_cursor) - for i in range(4): - res = cursor.fetchone() - assert res is not None - assert res[0] == i + def test_cursor_fetch_all(self, sync_cursor: Cursor) -> None: + self._test_cursor_fetch_all(sync_cursor) - assert cursor.fetchone() is None - - def test_cursor_fetch_many(self, session_sync: ydb.QuerySession) -> None: - with ydb_dbapi.Cursor(session=session_sync) as cursor: - yql_text = """ - SELECT id, val FROM table - """ - cursor.execute(query=yql_text) + def test_cursor_fetch_one_multiple_result_sets( + self, sync_cursor: Cursor + ) -> None: + self._test_cursor_fetch_one_multiple_result_sets(sync_cursor) - res = cursor.fetchmany() - assert res is not None - assert len(res) == 1 - assert res[0][0] == 0 + def test_cursor_fetch_many_multiple_result_sets( + self, sync_cursor: Cursor + ) -> None: + self._test_cursor_fetch_many_multiple_result_sets(sync_cursor) - res = cursor.fetchmany(size=2) - assert res is not None - assert len(res) == 2 - assert res[0][0] == 1 - assert res[1][0] == 2 + def test_cursor_fetch_all_multiple_result_sets( + self, sync_cursor: Cursor + ) -> None: + self._test_cursor_fetch_all_multiple_result_sets(sync_cursor) - res = cursor.fetchmany(size=2) - assert res is not None - assert len(res) == 1 - assert res[0][0] == 3 - assert cursor.fetchmany(size=2) == [] - def test_cursor_fetch_all(self, session_sync: ydb.QuerySession) -> None: - with ydb_dbapi.Cursor(session=session_sync) as cursor: - yql_text = """ - SELECT id, val FROM table - """ - cursor.execute(query=yql_text) +class TestAsyncCursor(BaseCursorTestSuit): + @pytest.fixture + async def async_cursor( + self, session: ydb.aio.QuerySession + ) -> AsyncGenerator[Cursor]: + cursor = AsyncCursor(session) + yield cursor + await greenlet_spawn(cursor.close) - assert cursor.rowcount == 4 + @pytest.mark.asyncio + async def test_cursor_fetch_one(self, async_cursor: AsyncCursor) -> None: + await greenlet_spawn(self._test_cursor_fetch_one, async_cursor) - res = cursor.fetchall() - assert res is not None - assert len(res) == 4 - for i in range(4): - assert res[i][0] == i + @pytest.mark.asyncio + async def test_cursor_fetch_many(self, async_cursor: AsyncCursor) -> None: + await greenlet_spawn(self._test_cursor_fetch_many, async_cursor) - assert cursor.fetchall() == [] + @pytest.mark.asyncio + async def test_cursor_fetch_all(self, async_cursor: AsyncCursor) -> None: + await greenlet_spawn(self._test_cursor_fetch_all, async_cursor) - def test_cursor_fetch_one_multiple_result_sets( - self, session_sync: ydb.QuerySession + @pytest.mark.asyncio + async def test_cursor_fetch_one_multiple_result_sets( + self, async_cursor: AsyncCursor ) -> None: - with ydb_dbapi.Cursor(session=session_sync) as cursor: - yql_text = """ - SELECT id, val FROM table; - SELECT id, val FROM table1; - SELECT id, val FROM table2; - """ - cursor.execute(query=yql_text) - - assert cursor.rowcount == 12 - - for i in range(RESULT_SET_LENGTH * RESULT_SET_COUNT): - res = cursor.fetchone() - assert res is not None - assert res[0] == i % RESULT_SET_LENGTH - - assert cursor.fetchone() is None - assert not cursor.nextset() + await greenlet_spawn( + self._test_cursor_fetch_one_multiple_result_sets, async_cursor + ) - def test_cursor_fetch_many_multiple_result_sets( - self, session_sync: ydb.QuerySession + @pytest.mark.asyncio + async def test_cursor_fetch_many_multiple_result_sets( + self, async_cursor: AsyncCursor ) -> None: - with ydb_dbapi.Cursor(session=session_sync) as cursor: - yql_text = """ - SELECT id, val FROM table; - SELECT id, val FROM table1; - SELECT id, val FROM table2; - """ - cursor.execute(query=yql_text) - - assert cursor.rowcount == 12 - - halfsize = (RESULT_SET_LENGTH * RESULT_SET_COUNT) // 2 - for _ in range(2): - res = cursor.fetchmany(size=halfsize) - assert res is not None - assert len(res) == halfsize - - assert cursor.fetchmany(2) == [] - assert not cursor.nextset() + await greenlet_spawn( + self._test_cursor_fetch_many_multiple_result_sets, async_cursor + ) - def test_cursor_fetch_all_multiple_result_sets( - self, session_sync: ydb.QuerySession + @pytest.mark.asyncio + async def test_cursor_fetch_all_multiple_result_sets( + self, async_cursor: AsyncCursor ) -> None: - with ydb_dbapi.Cursor(session=session_sync) as cursor: - yql_text = """ - SELECT id, val FROM table; - SELECT id, val FROM table1; - SELECT id, val FROM table2; - """ - cursor.execute(query=yql_text) - - assert cursor.rowcount == 12 - - res = cursor.fetchall() - - assert len(res) == RESULT_SET_COUNT * RESULT_SET_LENGTH - - assert cursor.fetchall() == [] - assert not cursor.nextset() + await greenlet_spawn( + self._test_cursor_fetch_all_multiple_result_sets, async_cursor + ) From af51b1fe1b99972616586153e7e7d8dd74856461 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Wed, 30 Oct 2024 12:25:08 +0300 Subject: [PATCH 32/33] review fixes --- pyproject.toml | 1 + ydb_dbapi/connections.py | 185 ++++++++++++++++++++++++--------------- ydb_dbapi/cursors.py | 46 +++++----- 3 files changed, 134 insertions(+), 98 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9be0694..743077c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,6 +61,7 @@ ignore = [ "TRY003", # Allow specifying long messages outside the exception class "SLF001", # Allow access private member, "PGH003", # Allow not to specify rule codes + "PLR0913", # Allow to have many arguments in function definition ] select = ["ALL"] diff --git a/ydb_dbapi/connections.py b/ydb_dbapi/connections.py index 98f2af2..b7c353e 100644 --- a/ydb_dbapi/connections.py +++ b/ydb_dbapi/connections.py @@ -1,12 +1,14 @@ from __future__ import annotations import posixpath +from enum import Enum from typing import NamedTuple -from typing import TypedDict import ydb -from typing_extensions import NotRequired -from typing_extensions import Unpack +from ydb import QuerySessionPool as SessionPool +from ydb import QueryTxContext as TxContext +from ydb.aio import QuerySessionPool as AsyncSessionPool +from ydb.aio import QueryTxContext as AsyncTxContext from ydb.retries import retry_operation_async from ydb.retries import retry_operation_sync @@ -18,7 +20,7 @@ from .utils import handle_ydb_errors -class IsolationLevel: +class IsolationLevel(str, Enum): SERIALIZABLE = "SERIALIZABLE" ONLINE_READONLY = "ONLINE READONLY" ONLINE_READONLY_INCONSISTENT = "ONLINE READONLY INCONSISTENT" @@ -27,49 +29,63 @@ class IsolationLevel: AUTOCOMMIT = "AUTOCOMMIT" -class ConnectionKwargs(TypedDict): - credentials: NotRequired[ydb.AbstractCredentials] - ydb_table_path_prefix: NotRequired[str] - ydb_session_pool: NotRequired[ - ydb.QuerySessionPool | ydb.aio.QuerySessionPool - ] +class _IsolationSettings(NamedTuple): + ydb_mode: ydb.BaseQueryTxMode + interactive: bool + + +_ydb_isolation_settings_map = { + IsolationLevel.AUTOCOMMIT: _IsolationSettings( + ydb.QuerySerializableReadWrite(), interactive=False + ), + IsolationLevel.SERIALIZABLE: _IsolationSettings( + ydb.QuerySerializableReadWrite(), interactive=True + ), + IsolationLevel.ONLINE_READONLY: _IsolationSettings( + ydb.QueryOnlineReadOnly(), interactive=True + ), + IsolationLevel.ONLINE_READONLY_INCONSISTENT: _IsolationSettings( + ydb.QueryOnlineReadOnly().with_allow_inconsistent_reads(), + interactive=True, + ), + IsolationLevel.STALE_READONLY: _IsolationSettings( + ydb.QueryStaleReadOnly(), interactive=True + ), + IsolationLevel.SNAPSHOT_READONLY: _IsolationSettings( + ydb.QuerySnapshotReadOnly(), interactive=True + ), +} class BaseConnection: - _tx_mode: ydb.BaseQueryTxMode = ydb.QuerySerializableReadWrite() - _tx_context: ydb.QueryTxContext | ydb.aio.QueryTxContext | None = None - interactive_transaction: bool = False - _shared_session_pool: bool = False - _driver_cls = ydb.Driver _pool_cls = ydb.QuerySessionPool - _cursor_cls: type[Cursor | AsyncCursor] = Cursor - - _driver: ydb.Driver | ydb.aio.Driver - _pool: ydb.QuerySessionPool | ydb.aio.QuerySessionPool - - _current_cursor: AsyncCursor | Cursor | None = None def __init__( self, host: str = "", port: str = "", database: str = "", - **conn_kwargs: Unpack[ConnectionKwargs], + ydb_table_path_prefix: str = "", + credentials: ydb.AbstractCredentials | None = None, + ydb_session_pool: SessionPool | AsyncSessionPool | None = None, + **kwargs: dict, ) -> None: self.endpoint = f"grpc://{host}:{port}" self.database = database - self.conn_kwargs = conn_kwargs - self.credentials = self.conn_kwargs.pop("credentials", None) - self.table_path_prefix = self.conn_kwargs.pop( - "ydb_table_path_prefix", "" - ) + self.credentials = credentials + self.table_path_prefix = ydb_table_path_prefix - if ( - "ydb_session_pool" in self.conn_kwargs - ): # Use session pool managed manually + self.connection_kwargs: dict = kwargs + + self._tx_mode: ydb.BaseQueryTxMode = ydb.QuerySerializableReadWrite() + self._tx_context: TxContext | AsyncTxContext | None = None + self.interactive_transaction: bool = False + self._shared_session_pool: bool = False + + if ydb_session_pool is not None: self._shared_session_pool = True - self._session_pool = self.conn_kwargs.pop("ydb_session_pool") + self._session_pool = ydb_session_pool self._driver = self._session_pool._driver else: driver_config = ydb.DriverConfig( @@ -82,33 +98,8 @@ def __init__( self._session: ydb.QuerySession | ydb.aio.QuerySession | None = None - def set_isolation_level(self, isolation_level: str) -> None: - class IsolationSettings(NamedTuple): - ydb_mode: ydb.BaseQueryTxMode - interactive: bool - - ydb_isolation_settings_map = { - IsolationLevel.AUTOCOMMIT: IsolationSettings( - ydb.QuerySerializableReadWrite(), interactive=False - ), - IsolationLevel.SERIALIZABLE: IsolationSettings( - ydb.QuerySerializableReadWrite(), interactive=True - ), - IsolationLevel.ONLINE_READONLY: IsolationSettings( - ydb.QueryOnlineReadOnly(), interactive=True - ), - IsolationLevel.ONLINE_READONLY_INCONSISTENT: IsolationSettings( - ydb.QueryOnlineReadOnly().with_allow_inconsistent_reads(), - interactive=True, - ), - IsolationLevel.STALE_READONLY: IsolationSettings( - ydb.QueryStaleReadOnly(), interactive=True - ), - IsolationLevel.SNAPSHOT_READONLY: IsolationSettings( - ydb.QuerySnapshotReadOnly(), interactive=True - ), - } - ydb_isolation_settings = ydb_isolation_settings_map[isolation_level] + def set_isolation_level(self, isolation_level: IsolationLevel) -> None: + ydb_isolation_settings = _ydb_isolation_settings_map[isolation_level] if self._tx_context and self._tx_context.tx_id: raise InternalError( "Failed to set transaction mode: transaction is already began" @@ -132,7 +123,34 @@ def get_isolation_level(self) -> str: msg = f"{self._tx_mode.name} is not supported" raise NotSupportedError(msg) - def cursor(self) -> Cursor | AsyncCursor: + +class Connection(BaseConnection): + _driver_cls = ydb.Driver + _pool_cls = ydb.QuerySessionPool + _cursor_cls = Cursor + + def __init__( + self, + host: str = "", + port: str = "", + database: str = "", + ydb_table_path_prefix: str = "", + credentials: ydb.AbstractCredentials | None = None, + ydb_session_pool: SessionPool | AsyncSessionPool | None = None, + **kwargs: dict, + ) -> None: + super().__init__( + host=host, + port=port, + database=database, + ydb_table_path_prefix=ydb_table_path_prefix, + credentials=credentials, + ydb_session_pool=ydb_session_pool, + **kwargs, + ) + self._current_cursor: Cursor | None = None + + def cursor(self) -> Cursor: if self._session is None: raise RuntimeError("Connection is not ready, use wait_ready.") @@ -148,16 +166,6 @@ def cursor(self) -> Cursor | AsyncCursor: ) return self._current_cursor - -class Connection(BaseConnection): - _driver_cls = ydb.Driver - _pool_cls = ydb.QuerySessionPool - _cursor_cls = Cursor - - _driver: ydb.Driver - _pool: ydb.QuerySessionPool - _current_cursor: Cursor | None = None - def wait_ready(self, timeout: int = 10) -> None: try: self._driver.wait(timeout, fail_fast=True) @@ -248,9 +256,42 @@ class AsyncConnection(BaseConnection): _pool_cls = ydb.aio.QuerySessionPool _cursor_cls = AsyncCursor - _driver: ydb.aio.Driver - _pool: ydb.aio.QuerySessionPool - _current_cursor: AsyncCursor | None = None + def __init__( + self, + host: str = "", + port: str = "", + database: str = "", + ydb_table_path_prefix: str = "", + credentials: ydb.AbstractCredentials | None = None, + ydb_session_pool: SessionPool | AsyncSessionPool | None = None, + **kwargs: dict, + ) -> None: + super().__init__( + host=host, + port=port, + database=database, + ydb_table_path_prefix=ydb_table_path_prefix, + credentials=credentials, + ydb_session_pool=ydb_session_pool, + **kwargs, + ) + self._current_cursor: AsyncCursor | None = None + + def cursor(self) -> AsyncCursor: + if self._session is None: + raise RuntimeError("Connection is not ready, use wait_ready.") + + if self.interactive_transaction: + self._tx_context = self._session.transaction(self._tx_mode) + else: + self._tx_context = None + + self._current_cursor = self._cursor_cls( + session=self._session, + tx_context=self._tx_context, + autocommit=(not self.interactive_transaction), + ) + return self._current_cursor async def wait_ready(self, timeout: int = 10) -> None: try: diff --git a/ydb_dbapi/cursors.py b/ydb_dbapi/cursors.py index 07a6727..8f0259e 100644 --- a/ydb_dbapi/cursors.py +++ b/ydb_dbapi/cursors.py @@ -11,7 +11,6 @@ from typing_extensions import Self from .errors import DatabaseError -from .errors import Error from .errors import InterfaceError from .errors import ProgrammingError from .utils import CursorStatus @@ -32,11 +31,12 @@ def _get_column_type(type_obj: Any) -> str: class BufferedCursor: - arraysize: int = 1 - _rows: Iterator | None = None - _rows_count: int = -1 - _description: list[tuple] | None = None - _state: CursorStatus = CursorStatus.ready + def __init__(self) -> None: + self.arraysize: int = 1 + self._rows: Iterator | None = None + self._rows_count: int = -1 + self._description: list[tuple] | None = None + self._state: CursorStatus = CursorStatus.ready @property def description(self) -> list[tuple] | None: @@ -142,6 +142,7 @@ def __init__( table_path_prefix: str = "", autocommit: bool = True, ) -> None: + super().__init__() self._session = session self._tx_context = tx_context self._table_path_prefix = table_path_prefix @@ -167,13 +168,12 @@ def _execute_generic_query( @handle_ydb_errors def _execute_transactional_query( - self, query: str, parameters: ParametersType | None = None + self, + tx_context: ydb.QueryTxContext, + query: str, + parameters: ParametersType | None = None, ) -> Iterator[ydb.convert.ResultSet]: - if self._tx_context is None: - raise Error( - "Unable to execute tx based queries without transaction." - ) - return self._tx_context.execute( + return tx_context.execute( query=query, parameters=parameters, commit_tx=self._autocommit, @@ -188,16 +188,13 @@ def execute( self._raise_if_running() if self._tx_context is not None: self._stream = self._execute_transactional_query( - query=query, parameters=parameters + tx_context=self._tx_context, query=query, parameters=parameters ) else: self._stream = self._execute_generic_query( query=query, parameters=parameters ) - if self._stream is None: - return - self._begin_query() self._scroll_stream(replace_current=False) @@ -256,6 +253,7 @@ def __init__( table_path_prefix: str = "", autocommit: bool = True, ) -> None: + super().__init__() self._session = session self._tx_context = tx_context self._table_path_prefix = table_path_prefix @@ -281,13 +279,12 @@ async def _execute_generic_query( @handle_ydb_errors async def _execute_transactional_query( - self, query: str, parameters: ParametersType | None = None + self, + tx_context: ydb.aio.QueryTxContext, + query: str, + parameters: ParametersType | None = None, ) -> AsyncIterator[ydb.convert.ResultSet]: - if self._tx_context is None: - raise Error( - "Unable to execute tx based queries without transaction." - ) - return await self._tx_context.execute( + return await tx_context.execute( query=query, parameters=parameters, commit_tx=self._autocommit, @@ -302,16 +299,13 @@ async def execute( self._raise_if_running() if self._tx_context is not None: self._stream = await self._execute_transactional_query( - query=query, parameters=parameters + tx_context=self._tx_context, query=query, parameters=parameters ) else: self._stream = await self._execute_generic_query( query=query, parameters=parameters ) - if self._stream is None: - return - self._begin_query() await self._scroll_stream(replace_current=False) From 9346679bf15d8f98f5789cecabb7f2189b9509d4 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Wed, 30 Oct 2024 15:16:22 +0300 Subject: [PATCH 33/33] rename test files --- tests/{test_connection.py => test_connections.py} | 0 tests/{test_cursor.py => test_cursors.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/{test_connection.py => test_connections.py} (100%) rename tests/{test_cursor.py => test_cursors.py} (100%) diff --git a/tests/test_connection.py b/tests/test_connections.py similarity index 100% rename from tests/test_connection.py rename to tests/test_connections.py diff --git a/tests/test_cursor.py b/tests/test_cursors.py similarity index 100% rename from tests/test_cursor.py rename to tests/test_cursors.py