iker-python-common 1.0.60__py3-none-any.whl → 1.0.62__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.
@@ -1,11 +1,13 @@
1
1
  import contextlib
2
2
  import dataclasses
3
- from typing import Any, Sequence
3
+ from typing import Any, Self, Sequence
4
4
 
5
+ import asyncpg
5
6
  import packaging.version
6
7
  import psycopg
7
8
  import pymysql
8
9
  import sqlalchemy
10
+ import sqlalchemy.ext.asyncio
9
11
  import sqlalchemy.ext.compiler
10
12
  import sqlalchemy.orm
11
13
 
@@ -16,6 +18,7 @@ __all__ = [
16
18
  "Drivers",
17
19
  "make_scheme",
18
20
  "ConnectionMaker",
21
+ "AsyncConnectionMaker",
19
22
  "orm_to_dict",
20
23
  "orm_clone",
21
24
  "mysql_insert_ignore",
@@ -31,6 +34,7 @@ class Dialects:
31
34
  class Drivers:
32
35
  pymysql = pymysql.__name__
33
36
  psycopg = psycopg.__name__
37
+ asyncpg = asyncpg.__name__
34
38
 
35
39
 
36
40
  def make_scheme(dialect: str, driver: str | None = None) -> str:
@@ -65,9 +69,10 @@ class ConnectionMaker(object):
65
69
  self.engine_opts = engine_opts or {}
66
70
  self.session_opts = session_opts or {}
67
71
 
68
- @staticmethod
72
+ @classmethod
69
73
  def create(
70
- driver: str | None = None,
74
+ cls,
75
+ scheme: str | None = None,
71
76
  host: str | None = None,
72
77
  port: int | None = None,
73
78
  username: str | None = None,
@@ -76,11 +81,11 @@ class ConnectionMaker(object):
76
81
  *,
77
82
  engine_opts: dict[str, JsonType] | None = None,
78
83
  session_opts: dict[str, JsonType] | None = None,
79
- ):
84
+ ) -> Self:
80
85
  """
81
86
  Creates a new instance of ``ConnectionMaker`` using the provided parameters to construct a SQLAlchemy URL.
82
87
 
83
- :param driver: Optional database driver.
88
+ :param scheme: The database scheme (e.g., 'mysql+pymysql', 'postgresql+psycopg').
84
89
  :param host: The database host (e.g., 'localhost').
85
90
  :param port: The database port.
86
91
  :param username: The database username.
@@ -89,22 +94,23 @@ class ConnectionMaker(object):
89
94
  :param engine_opts: Optional dictionary of SQLAlchemy engine options.
90
95
  :param session_opts: Optional dictionary of SQLAlchemy session options.
91
96
  """
92
- return ConnectionMaker(sqlalchemy.URL.create(drivername=driver,
93
- host=host,
94
- port=port,
95
- username=username,
96
- password=password,
97
- database=database),
98
- engine_opts=engine_opts,
99
- session_opts=session_opts)
100
-
101
- @staticmethod
97
+ return cls(sqlalchemy.URL.create(drivername=scheme,
98
+ host=host,
99
+ port=port,
100
+ username=username,
101
+ password=password,
102
+ database=database),
103
+ engine_opts=engine_opts,
104
+ session_opts=session_opts)
105
+
106
+ @classmethod
102
107
  def from_url(
108
+ cls,
103
109
  url: str | sqlalchemy.URL,
104
110
  *,
105
111
  engine_opts: dict[str, JsonType] | None = None,
106
112
  session_opts: dict[str, JsonType] | None = None,
107
- ) -> "ConnectionMaker":
113
+ ) -> Self:
108
114
  """
109
115
  Creates a new instance of ``ConnectionMaker`` from a SQLAlchemy URL string or object.
110
116
 
@@ -113,7 +119,7 @@ class ConnectionMaker(object):
113
119
  :param session_opts: Optional dictionary of SQLAlchemy session options.
114
120
  :return: A new instance of ``ConnectionMaker`` configured with the provided URL and options.
115
121
  """
116
- return ConnectionMaker(sqlalchemy.make_url(url), engine_opts=engine_opts, session_opts=session_opts)
122
+ return cls(sqlalchemy.make_url(url), engine_opts=engine_opts, session_opts=session_opts)
117
123
 
118
124
  @property
119
125
  def connection_string(self) -> str:
@@ -155,7 +161,6 @@ class ConnectionMaker(object):
155
161
  Creates all tables defined in the given ORM base using the current engine.
156
162
 
157
163
  :param orm_base: The SQLAlchemy ORM base class.
158
- :return: None.
159
164
  """
160
165
  if packaging.version.parse(sqlalchemy.__version__) >= packaging.version.parse("2"):
161
166
  if not isinstance(orm_base, type) or not issubclass(orm_base, sqlalchemy.orm.DeclarativeBase):
@@ -168,7 +173,6 @@ class ConnectionMaker(object):
168
173
  Drops all tables defined in the given ORM base using the current engine.
169
174
 
170
175
  :param orm_base: The SQLAlchemy ORM base class.
171
- :return: None.
172
176
  """
173
177
  if packaging.version.parse(sqlalchemy.__version__) >= packaging.version.parse("2"):
174
178
  if not isinstance(orm_base, type) or not issubclass(orm_base, sqlalchemy.orm.DeclarativeBase):
@@ -182,7 +186,6 @@ class ConnectionMaker(object):
182
186
 
183
187
  :param sql: The SQL statement to execute.
184
188
  :param params: The parameters dictionary for the SQL statement.
185
- :return: None.
186
189
  """
187
190
  with self.make_session() as session:
188
191
  session.execute(sqlalchemy.text(sql), params)
@@ -214,6 +217,104 @@ class ConnectionMaker(object):
214
217
  return result.first()
215
218
 
216
219
 
220
+ class AsyncConnectionMaker(ConnectionMaker):
221
+ """
222
+ Provides utilities to simplify establishing asynchronous database connections and sessions, including connection
223
+ string construction, async engine and session creation, and model management.
224
+ """
225
+
226
+ @property
227
+ def engine(self) -> sqlalchemy.ext.asyncio.AsyncEngine:
228
+ """
229
+ Returns a SQLAlchemy ``AsyncEngine`` instance for the configured connection string and engine options.
230
+
231
+ :return: The SQLAlchemy ``AsyncEngine``.
232
+ """
233
+ return sqlalchemy.ext.asyncio.create_async_engine(self.connection_string, **self.engine_opts)
234
+
235
+ async def make_connection(self) -> sqlalchemy.ext.asyncio.AsyncConnection:
236
+ """
237
+ Asynchronously establishes and returns a new database connection using the SQLAlchemy async engine.
238
+
239
+ :return: A database connection object.
240
+ """
241
+ return await self.engine.connect()
242
+
243
+ def make_session(self, **kwargs) -> contextlib.AbstractAsyncContextManager[
244
+ sqlalchemy.ext.asyncio.AsyncSession
245
+ ]:
246
+ """
247
+ Creates a context-managed asynchronous SQLAlchemy session with the configured async engine and session options.
248
+
249
+ :param kwargs: Additional keyword arguments for session creation.
250
+ :return: A context manager yielding a SQLAlchemy ``AsyncSession``.
251
+ """
252
+ return contextlib.aclosing(
253
+ sqlalchemy.ext.asyncio.async_sessionmaker(self.engine, **{**self.session_opts, **kwargs})())
254
+
255
+ async def create_model(self, orm_base):
256
+ """
257
+ Asynchronously creates all tables defined in the given ORM base using the current async engine.
258
+
259
+ :param orm_base: The SQLAlchemy ORM base class.
260
+ """
261
+ if packaging.version.parse(sqlalchemy.__version__) >= packaging.version.parse("2"):
262
+ if not isinstance(orm_base, type) or not issubclass(orm_base, sqlalchemy.orm.DeclarativeBase):
263
+ raise TypeError("not a subclass of 'sqlalchemy.orm.DeclarativeBase'")
264
+
265
+ async with self.engine.begin() as conn:
266
+ await conn.run_sync(orm_base.metadata.create_all)
267
+
268
+ async def drop_model(self, orm_base):
269
+ """
270
+ Asynchronously drops all tables defined in the given ORM base using the current async engine.
271
+
272
+ :param orm_base: The SQLAlchemy ORM base class.
273
+ """
274
+ if packaging.version.parse(sqlalchemy.__version__) >= packaging.version.parse("2"):
275
+ if not isinstance(orm_base, type) or not issubclass(orm_base, sqlalchemy.orm.DeclarativeBase):
276
+ raise TypeError("not a subclass of 'sqlalchemy.orm.DeclarativeBase'")
277
+
278
+ async with self.engine.begin() as conn:
279
+ await conn.run_sync(orm_base.metadata.drop_all)
280
+
281
+ async def execute(self, sql: str, **params):
282
+ """
283
+ Executes the given SQL statement with the specified parameters.
284
+
285
+ :param sql: The SQL statement to execute.
286
+ :param params: The parameters dictionary for the SQL statement.
287
+ """
288
+ async with self.make_session() as session:
289
+ await session.execute(sqlalchemy.text(sql), params)
290
+ await session.commit()
291
+
292
+ async def query_all(self, sql: str, **params) -> Sequence[sqlalchemy.Row[tuple[Any, ...]]]:
293
+ """
294
+ Executes the given SQL query with the specified parameters and returns all result tuples.
295
+
296
+ :param sql: The SQL query to execute.
297
+ :param params: The parameters dictionary for the SQL query.
298
+ :return: A list of result tuples.
299
+ """
300
+ async with self.make_session() as session:
301
+ query = await session.execute(sqlalchemy.text(sql), params)
302
+ return query.all()
303
+
304
+ async def query_first(self, sql: str, **params) -> sqlalchemy.Row[tuple[Any, ...]] | None:
305
+ """
306
+ Executes the given SQL query with the specified parameters and returns the first result tuple, or ``None``
307
+ if no results are found.
308
+
309
+ :param sql: The SQL query to execute.
310
+ :param params: The parameters dictionary for the SQL query.
311
+ :return: The first result tuple, or ``None`` if no results are found.
312
+ """
313
+ async with self.make_session() as session:
314
+ query = await session.execute(sqlalchemy.text(sql), params)
315
+ return query.first()
316
+
317
+
217
318
  def orm_to_dict(orm, exclude: set[str] = None) -> dict[str, Any]:
218
319
  """
219
320
  Converts an ORM object to a dictionary, optionally excluding specified fields.
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iker-python-common
3
- Version: 1.0.60
3
+ Version: 1.0.62
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.12
6
6
  Classifier: Programming Language :: Python :: 3.13
7
7
  Classifier: Programming Language :: Python :: 3.14
8
8
  Requires-Python: <3.15,>=3.12
9
+ Requires-Dist: asyncpg>=0.30
9
10
  Requires-Dist: docker>=7.1
10
11
  Requires-Dist: numpy>=2.3
11
12
  Requires-Dist: psycopg>=3.2
@@ -15,6 +16,7 @@ Provides-Extra: all
15
16
  Requires-Dist: iker-python-common; extra == "all"
16
17
  Provides-Extra: test
17
18
  Requires-Dist: ddt>=1.7; extra == "test"
19
+ Requires-Dist: pytest-asyncio>=1.2; extra == "test"
18
20
  Requires-Dist: pytest-cov>=5.0; extra == "test"
19
21
  Requires-Dist: pytest-mysql>=3.0; extra == "test"
20
22
  Requires-Dist: pytest-order>=1.3; extra == "test"
@@ -3,7 +3,7 @@ iker/common/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
3
3
  iker/common/utils/argutils.py,sha256=hMLNqdZs_Kjc2hw4Npm6N47RivP6JRNzCKIbJr1jYy8,9274
4
4
  iker/common/utils/config.py,sha256=z8rLqli961A-qAV9EaELp-pKuhNUNaq1Btdv-uwG7_I,4690
5
5
  iker/common/utils/csv.py,sha256=_V9OUrKcojec2L-hWagEIVnL2uvGjyJAFTrD7tHNr48,7573
6
- iker/common/utils/dbutils.py,sha256=zXZVJCz7HZPityFRF7sHRRMpMraegV_hyYnzApUUPhY,11852
6
+ iker/common/utils/dbutils.py,sha256=09DgvfPVDCPXwOAO_FTynLXhSq--ZzRz2fCQ6vJ5qqk,16151
7
7
  iker/common/utils/dockerutils.py,sha256=n2WuzXaZB6_WocSljvPOnfExSIjIHRUbuWp2oBbaPKQ,8004
8
8
  iker/common/utils/dtutils.py,sha256=86vbaa4pgcBWERZvTfJ92PKB3IimxP6tf0O11ho2Ffk,12554
9
9
  iker/common/utils/funcutils.py,sha256=4AkkvK9_Z2tgk1-Sp6-vLLVhI15cIgN9xW58QqL5QL4,7780
@@ -18,7 +18,7 @@ iker/common/utils/span.py,sha256=u_KuWi2U7QDMUotl4AeW2_57ItL3YhVDSeCwaOiFDvs,596
18
18
  iker/common/utils/strutils.py,sha256=Tu_qFeH3K-SfwvMxdrZAc9iLPV8ZmtX4ntyyFGNslf8,5094
19
19
  iker/common/utils/testutils.py,sha256=2VieV5yeCDntSKQSpIeyqRT8BZmZYE_ArMeQz3g7fXY,5568
20
20
  iker/common/utils/typeutils.py,sha256=RVkYkFRgDrx77OHFH7PavMV0AIB0S8ly40rs4g7JWE4,8220
21
- iker_python_common-1.0.60.dist-info/METADATA,sha256=2B3f-_-H83ceea3JraxM602M47-BcdClWBtZIZLF_I0,813
22
- iker_python_common-1.0.60.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
- iker_python_common-1.0.60.dist-info/top_level.txt,sha256=4_B8Prfc_lxFafFYTQThIU1ZqOYQ4pHHHnJ_fQ_oHs8,5
24
- iker_python_common-1.0.60.dist-info/RECORD,,
21
+ iker_python_common-1.0.62.dist-info/METADATA,sha256=0xFB0M0zbyGoFcUy9Y-lfZGzNDbAhtW0574bl8XS7XU,894
22
+ iker_python_common-1.0.62.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
+ iker_python_common-1.0.62.dist-info/top_level.txt,sha256=4_B8Prfc_lxFafFYTQThIU1ZqOYQ4pHHHnJ_fQ_oHs8,5
24
+ iker_python_common-1.0.62.dist-info/RECORD,,