ydb-dbapi 0.0.1b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ydb_dbapi/__init__.py ADDED
@@ -0,0 +1,16 @@
1
+ from .connections import AsyncConnection
2
+ from .connections import Connection
3
+ from .connections import IsolationLevel
4
+ from .connections import async_connect
5
+ from .connections import connect
6
+ from .constants import *
7
+ from .cursors import AsyncCursor
8
+ from .cursors import Cursor
9
+ from .errors import *
10
+ from .version import VERSION
11
+
12
+ __version__ = VERSION
13
+
14
+ apilevel = "2.0"
15
+ threadsafety = 0
16
+ paramstyle = "pyformat"
@@ -0,0 +1,392 @@
1
+ from __future__ import annotations
2
+
3
+ import posixpath
4
+ from enum import Enum
5
+ from typing import NamedTuple
6
+
7
+ import ydb
8
+ from ydb import QuerySessionPool as SessionPool
9
+ from ydb import QueryTxContext as TxContext
10
+ from ydb.aio import QuerySessionPool as AsyncSessionPool
11
+ from ydb.aio import QueryTxContext as AsyncTxContext
12
+ from ydb.retries import retry_operation_async
13
+ from ydb.retries import retry_operation_sync
14
+
15
+ from .cursors import AsyncCursor
16
+ from .cursors import Cursor
17
+ from .errors import InterfaceError
18
+ from .errors import InternalError
19
+ from .errors import NotSupportedError
20
+ from .utils import handle_ydb_errors
21
+
22
+
23
+ class IsolationLevel(str, Enum):
24
+ SERIALIZABLE = "SERIALIZABLE"
25
+ ONLINE_READONLY = "ONLINE READONLY"
26
+ ONLINE_READONLY_INCONSISTENT = "ONLINE READONLY INCONSISTENT"
27
+ STALE_READONLY = "STALE READONLY"
28
+ SNAPSHOT_READONLY = "SNAPSHOT READONLY"
29
+ AUTOCOMMIT = "AUTOCOMMIT"
30
+
31
+
32
+ class _IsolationSettings(NamedTuple):
33
+ ydb_mode: ydb.BaseQueryTxMode
34
+ interactive: bool
35
+
36
+
37
+ _ydb_isolation_settings_map = {
38
+ IsolationLevel.AUTOCOMMIT: _IsolationSettings(
39
+ ydb.QuerySerializableReadWrite(), interactive=False
40
+ ),
41
+ IsolationLevel.SERIALIZABLE: _IsolationSettings(
42
+ ydb.QuerySerializableReadWrite(), interactive=True
43
+ ),
44
+ IsolationLevel.ONLINE_READONLY: _IsolationSettings(
45
+ ydb.QueryOnlineReadOnly(), interactive=True
46
+ ),
47
+ IsolationLevel.ONLINE_READONLY_INCONSISTENT: _IsolationSettings(
48
+ ydb.QueryOnlineReadOnly().with_allow_inconsistent_reads(),
49
+ interactive=True,
50
+ ),
51
+ IsolationLevel.STALE_READONLY: _IsolationSettings(
52
+ ydb.QueryStaleReadOnly(), interactive=True
53
+ ),
54
+ IsolationLevel.SNAPSHOT_READONLY: _IsolationSettings(
55
+ ydb.QuerySnapshotReadOnly(), interactive=True
56
+ ),
57
+ }
58
+
59
+
60
+ class BaseConnection:
61
+ _driver_cls = ydb.Driver
62
+ _pool_cls = ydb.QuerySessionPool
63
+
64
+ def __init__(
65
+ self,
66
+ host: str = "",
67
+ port: str = "",
68
+ database: str = "",
69
+ ydb_table_path_prefix: str = "",
70
+ credentials: ydb.AbstractCredentials | None = None,
71
+ ydb_session_pool: SessionPool | AsyncSessionPool | None = None,
72
+ **kwargs: dict,
73
+ ) -> None:
74
+ self.endpoint = f"grpc://{host}:{port}"
75
+ self.database = database
76
+ self.credentials = credentials
77
+ self.table_path_prefix = ydb_table_path_prefix
78
+
79
+ self.connection_kwargs: dict = kwargs
80
+
81
+ self._tx_mode: ydb.BaseQueryTxMode = ydb.QuerySerializableReadWrite()
82
+ self._tx_context: TxContext | AsyncTxContext | None = None
83
+ self.interactive_transaction: bool = False
84
+ self._shared_session_pool: bool = False
85
+
86
+ if ydb_session_pool is not None:
87
+ self._shared_session_pool = True
88
+ self._session_pool = ydb_session_pool
89
+ self._driver = self._session_pool._driver
90
+ else:
91
+ driver_config = ydb.DriverConfig(
92
+ endpoint=self.endpoint,
93
+ database=self.database,
94
+ credentials=self.credentials,
95
+ )
96
+ self._driver = self._driver_cls(driver_config)
97
+ self._session_pool = self._pool_cls(self._driver, size=5)
98
+
99
+ self._session: ydb.QuerySession | ydb.aio.QuerySession | None = None
100
+
101
+ def set_isolation_level(self, isolation_level: IsolationLevel) -> None:
102
+ ydb_isolation_settings = _ydb_isolation_settings_map[isolation_level]
103
+ if self._tx_context and self._tx_context.tx_id:
104
+ raise InternalError(
105
+ "Failed to set transaction mode: transaction is already began"
106
+ )
107
+ self._tx_mode = ydb_isolation_settings.ydb_mode
108
+ self.interactive_transaction = ydb_isolation_settings.interactive
109
+
110
+ def get_isolation_level(self) -> str:
111
+ if self._tx_mode.name == ydb.QuerySerializableReadWrite().name:
112
+ if self.interactive_transaction:
113
+ return IsolationLevel.SERIALIZABLE
114
+ return IsolationLevel.AUTOCOMMIT
115
+ if self._tx_mode.name == ydb.QueryOnlineReadOnly().name:
116
+ if self._tx_mode.settings.allow_inconsistent_reads:
117
+ return IsolationLevel.ONLINE_READONLY_INCONSISTENT
118
+ return IsolationLevel.ONLINE_READONLY
119
+ if self._tx_mode.name == ydb.QueryStaleReadOnly().name:
120
+ return IsolationLevel.STALE_READONLY
121
+ if self._tx_mode.name == ydb.QuerySnapshotReadOnly().name:
122
+ return IsolationLevel.SNAPSHOT_READONLY
123
+ msg = f"{self._tx_mode.name} is not supported"
124
+ raise NotSupportedError(msg)
125
+
126
+
127
+ class Connection(BaseConnection):
128
+ _driver_cls = ydb.Driver
129
+ _pool_cls = ydb.QuerySessionPool
130
+ _cursor_cls = Cursor
131
+
132
+ def __init__(
133
+ self,
134
+ host: str = "",
135
+ port: str = "",
136
+ database: str = "",
137
+ ydb_table_path_prefix: str = "",
138
+ credentials: ydb.AbstractCredentials | None = None,
139
+ ydb_session_pool: SessionPool | AsyncSessionPool | None = None,
140
+ **kwargs: dict,
141
+ ) -> None:
142
+ super().__init__(
143
+ host=host,
144
+ port=port,
145
+ database=database,
146
+ ydb_table_path_prefix=ydb_table_path_prefix,
147
+ credentials=credentials,
148
+ ydb_session_pool=ydb_session_pool,
149
+ **kwargs,
150
+ )
151
+ self._current_cursor: Cursor | None = None
152
+
153
+ def cursor(self) -> Cursor:
154
+ if self._session is None:
155
+ raise RuntimeError("Connection is not ready, use wait_ready.")
156
+
157
+ if self.interactive_transaction:
158
+ self._tx_context = self._session.transaction(self._tx_mode)
159
+ else:
160
+ self._tx_context = None
161
+
162
+ self._current_cursor = self._cursor_cls(
163
+ session=self._session,
164
+ tx_context=self._tx_context,
165
+ autocommit=(not self.interactive_transaction),
166
+ )
167
+ return self._current_cursor
168
+
169
+ def wait_ready(self, timeout: int = 10) -> None:
170
+ try:
171
+ self._driver.wait(timeout, fail_fast=True)
172
+ except ydb.Error as e:
173
+ raise InterfaceError(e.message, original_error=e) from e
174
+ except Exception as e:
175
+ self._driver.stop()
176
+ msg = (
177
+ "Failed to connect to YDB, details "
178
+ f"{self._driver.discovery_debug_details()}"
179
+ )
180
+ raise InterfaceError(msg) from e
181
+
182
+ self._session = self._session_pool.acquire()
183
+
184
+ def commit(self) -> None:
185
+ if self._tx_context and self._tx_context.tx_id:
186
+ self._tx_context.commit()
187
+ self._tx_context = None
188
+
189
+ def rollback(self) -> None:
190
+ if self._tx_context and self._tx_context.tx_id:
191
+ self._tx_context.rollback()
192
+ self._tx_context = None
193
+
194
+ def close(self) -> None:
195
+ self.rollback()
196
+
197
+ if self._current_cursor:
198
+ self._current_cursor.close()
199
+
200
+ if self._session:
201
+ self._session_pool.release(self._session)
202
+
203
+ if not self._shared_session_pool:
204
+ self._session_pool.stop()
205
+ self._driver.stop()
206
+
207
+ @handle_ydb_errors
208
+ def describe(self, table_path: str) -> ydb.TableSchemeEntry:
209
+ abs_table_path = posixpath.join(
210
+ self.database, self.table_path_prefix, table_path
211
+ )
212
+ return self._driver.table_client.describe_table(abs_table_path)
213
+
214
+ @handle_ydb_errors
215
+ def check_exists(self, table_path: str) -> bool:
216
+ abs_table_path = posixpath.join(
217
+ self.database, self.table_path_prefix, table_path
218
+ )
219
+ return self._check_path_exists(abs_table_path)
220
+
221
+ @handle_ydb_errors
222
+ def get_table_names(self) -> list[str]:
223
+ abs_dir_path = posixpath.join(self.database, self.table_path_prefix)
224
+ names = self._get_table_names(abs_dir_path)
225
+ return [posixpath.relpath(path, abs_dir_path) for path in names]
226
+
227
+ def _check_path_exists(self, table_path: str) -> bool:
228
+ try:
229
+
230
+ def callee() -> None:
231
+ self._driver.scheme_client.describe_path(table_path)
232
+
233
+ retry_operation_sync(callee)
234
+ except ydb.SchemeError:
235
+ return False
236
+ else:
237
+ return True
238
+
239
+ def _get_table_names(self, abs_dir_path: str) -> list[str]:
240
+ def callee() -> ydb.Directory:
241
+ return self._driver.scheme_client.list_directory(abs_dir_path)
242
+
243
+ directory = retry_operation_sync(callee)
244
+ result = []
245
+ for child in directory.children:
246
+ child_abs_path = posixpath.join(abs_dir_path, child.name)
247
+ if child.is_table():
248
+ result.append(child_abs_path)
249
+ elif child.is_directory() and not child.name.startswith("."):
250
+ result.extend(self.get_table_names(child_abs_path))
251
+ return result
252
+
253
+
254
+ class AsyncConnection(BaseConnection):
255
+ _driver_cls = ydb.aio.Driver
256
+ _pool_cls = ydb.aio.QuerySessionPool
257
+ _cursor_cls = AsyncCursor
258
+
259
+ def __init__(
260
+ self,
261
+ host: str = "",
262
+ port: str = "",
263
+ database: str = "",
264
+ ydb_table_path_prefix: str = "",
265
+ credentials: ydb.AbstractCredentials | None = None,
266
+ ydb_session_pool: SessionPool | AsyncSessionPool | None = None,
267
+ **kwargs: dict,
268
+ ) -> None:
269
+ super().__init__(
270
+ host=host,
271
+ port=port,
272
+ database=database,
273
+ ydb_table_path_prefix=ydb_table_path_prefix,
274
+ credentials=credentials,
275
+ ydb_session_pool=ydb_session_pool,
276
+ **kwargs,
277
+ )
278
+ self._current_cursor: AsyncCursor | None = None
279
+
280
+ def cursor(self) -> AsyncCursor:
281
+ if self._session is None:
282
+ raise RuntimeError("Connection is not ready, use wait_ready.")
283
+
284
+ if self.interactive_transaction:
285
+ self._tx_context = self._session.transaction(self._tx_mode)
286
+ else:
287
+ self._tx_context = None
288
+
289
+ self._current_cursor = self._cursor_cls(
290
+ session=self._session,
291
+ tx_context=self._tx_context,
292
+ autocommit=(not self.interactive_transaction),
293
+ )
294
+ return self._current_cursor
295
+
296
+ async def wait_ready(self, timeout: int = 10) -> None:
297
+ try:
298
+ await self._driver.wait(timeout, fail_fast=True)
299
+ except ydb.Error as e:
300
+ raise InterfaceError(e.message, original_error=e) from e
301
+ except Exception as e:
302
+ await self._driver.stop()
303
+ msg = (
304
+ "Failed to connect to YDB, details "
305
+ f"{self._driver.discovery_debug_details()}"
306
+ )
307
+ raise InterfaceError(msg) from e
308
+
309
+ self._session = await self._session_pool.acquire()
310
+
311
+ async def commit(self) -> None:
312
+ if self._tx_context and self._tx_context.tx_id:
313
+ await self._tx_context.commit()
314
+ self._tx_context = None
315
+
316
+ async def rollback(self) -> None:
317
+ if self._tx_context and self._tx_context.tx_id:
318
+ await self._tx_context.rollback()
319
+ self._tx_context = None
320
+
321
+ async def close(self) -> None:
322
+ await self.rollback()
323
+
324
+ if self._current_cursor:
325
+ await self._current_cursor.close()
326
+
327
+ if self._session:
328
+ await self._session_pool.release(self._session)
329
+
330
+ if not self._shared_session_pool:
331
+ await self._session_pool.stop()
332
+ await self._driver.stop()
333
+
334
+ @handle_ydb_errors
335
+ async def describe(self, table_path: str) -> ydb.TableSchemeEntry:
336
+ abs_table_path = posixpath.join(
337
+ self.database, self.table_path_prefix, table_path
338
+ )
339
+ return await self._driver.table_client.describe_table(abs_table_path)
340
+
341
+ @handle_ydb_errors
342
+ async def check_exists(self, table_path: str) -> bool:
343
+ abs_table_path = posixpath.join(
344
+ self.database, self.table_path_prefix, table_path
345
+ )
346
+ return await self._check_path_exists(abs_table_path)
347
+
348
+ @handle_ydb_errors
349
+ async def get_table_names(self) -> list[str]:
350
+ abs_dir_path = posixpath.join(self.database, self.table_path_prefix)
351
+ names = await self._get_table_names(abs_dir_path)
352
+ return [posixpath.relpath(path, abs_dir_path) for path in names]
353
+
354
+ async def _check_path_exists(self, table_path: str) -> bool:
355
+ try:
356
+
357
+ async def callee() -> None:
358
+ await self._driver.scheme_client.describe_path(table_path)
359
+
360
+ await retry_operation_async(callee)
361
+ except ydb.SchemeError:
362
+ return False
363
+ else:
364
+ return True
365
+
366
+ async def _get_table_names(self, abs_dir_path: str) -> list[str]:
367
+ async def callee() -> ydb.Directory:
368
+ return await self._driver.scheme_client.list_directory(
369
+ abs_dir_path
370
+ )
371
+
372
+ directory = await retry_operation_async(callee)
373
+ result = []
374
+ for child in directory.children:
375
+ child_abs_path = posixpath.join(abs_dir_path, child.name)
376
+ if child.is_table():
377
+ result.append(child_abs_path)
378
+ elif child.is_directory() and not child.name.startswith("."):
379
+ result.extend(self.get_table_names(child_abs_path))
380
+ return result
381
+
382
+
383
+ def connect(*args: tuple, **kwargs: dict) -> Connection:
384
+ conn = Connection(*args, **kwargs) # type: ignore
385
+ conn.wait_ready()
386
+ return conn
387
+
388
+
389
+ async def async_connect(*args: tuple, **kwargs: dict) -> AsyncConnection:
390
+ conn = AsyncConnection(*args, **kwargs) # type: ignore
391
+ await conn.wait_ready()
392
+ return conn
ydb_dbapi/constants.py ADDED
@@ -0,0 +1,218 @@
1
+ YDB_KEYWORDS = {
2
+ "abort",
3
+ "action",
4
+ "add",
5
+ "after",
6
+ "all",
7
+ "alter",
8
+ "analyze",
9
+ "and",
10
+ "ansi",
11
+ "any",
12
+ "array",
13
+ "as",
14
+ "asc",
15
+ "assume",
16
+ "async",
17
+ "attach",
18
+ "autoincrement",
19
+ "before",
20
+ "begin",
21
+ "bernoulli",
22
+ "between",
23
+ "bitcast",
24
+ "by",
25
+ "cascade",
26
+ "case",
27
+ "cast",
28
+ "changefeed",
29
+ "check",
30
+ "collate",
31
+ "column",
32
+ "columns",
33
+ "commit",
34
+ "compact",
35
+ "conditional",
36
+ "conflict",
37
+ "constraint",
38
+ "consumer",
39
+ "cover",
40
+ "create",
41
+ "cross",
42
+ "cube",
43
+ "current",
44
+ "current_date",
45
+ "current_time",
46
+ "current_timestamp",
47
+ "data",
48
+ "database",
49
+ "decimal",
50
+ "declare",
51
+ "default",
52
+ "deferrable",
53
+ "deferred",
54
+ "define",
55
+ "delete",
56
+ "desc",
57
+ "detach",
58
+ "disable",
59
+ "discard",
60
+ "distinct",
61
+ "do",
62
+ "drop",
63
+ "each",
64
+ "else",
65
+ "empty",
66
+ "empty_action",
67
+ "encrypted",
68
+ "end",
69
+ "erase",
70
+ "error",
71
+ "escape",
72
+ "evaluate",
73
+ "except",
74
+ "exclude",
75
+ "exclusion",
76
+ "exclusive",
77
+ "exists",
78
+ "explain",
79
+ "export",
80
+ "external",
81
+ "fail",
82
+ "family",
83
+ "filter",
84
+ "flatten",
85
+ "following",
86
+ "for",
87
+ "foreign",
88
+ "from",
89
+ "full",
90
+ "function",
91
+ "glob",
92
+ "group",
93
+ "grouping",
94
+ "groups",
95
+ "hash",
96
+ "having",
97
+ "hop",
98
+ "if",
99
+ "ignore",
100
+ "ilike",
101
+ "immediate",
102
+ "import",
103
+ "in",
104
+ "index",
105
+ "indexed",
106
+ "inherits",
107
+ "initially",
108
+ "inner",
109
+ "insert",
110
+ "instead",
111
+ "intersect",
112
+ "into",
113
+ "is",
114
+ "isnull",
115
+ "join",
116
+ "json_exists",
117
+ "json_query",
118
+ "json_value",
119
+ "key",
120
+ "left",
121
+ "like",
122
+ "limit",
123
+ "local",
124
+ "match",
125
+ "natural",
126
+ "no",
127
+ "not",
128
+ "notnull",
129
+ "null",
130
+ "nulls",
131
+ "object",
132
+ "of",
133
+ "offset",
134
+ "on",
135
+ "only",
136
+ "or",
137
+ "order",
138
+ "others",
139
+ "outer",
140
+ "over",
141
+ "partition",
142
+ "passing",
143
+ "password",
144
+ "plan",
145
+ "pragma",
146
+ "preceding",
147
+ "presort",
148
+ "primary",
149
+ "process",
150
+ "raise",
151
+ "range",
152
+ "reduce",
153
+ "references",
154
+ "regexp",
155
+ "reindex",
156
+ "release",
157
+ "rename",
158
+ "replace",
159
+ "replication",
160
+ "reset",
161
+ "respect",
162
+ "restrict",
163
+ "result",
164
+ "return",
165
+ "returning",
166
+ "revert",
167
+ "right",
168
+ "rlike",
169
+ "rollback",
170
+ "rollup",
171
+ "row",
172
+ "rows",
173
+ "sample",
174
+ "savepoint",
175
+ "schema",
176
+ "select",
177
+ "semi",
178
+ "sets",
179
+ "source",
180
+ "stream",
181
+ "subquery",
182
+ "symbols",
183
+ "sync",
184
+ "system",
185
+ "table",
186
+ "tablesample",
187
+ "tablestore",
188
+ "temp",
189
+ "temporary",
190
+ "then",
191
+ "ties",
192
+ "to",
193
+ "topic",
194
+ "transaction",
195
+ "trigger",
196
+ "type",
197
+ "unbounded",
198
+ "unconditional",
199
+ "union",
200
+ "unique",
201
+ "unknown",
202
+ "update",
203
+ "upsert",
204
+ "use",
205
+ "user",
206
+ "using",
207
+ "vacuum",
208
+ "values",
209
+ "view",
210
+ "virtual",
211
+ "when",
212
+ "where",
213
+ "window",
214
+ "with",
215
+ "without",
216
+ "wrapper",
217
+ "xor",
218
+ }