reydb 1.1.51__py3-none-any.whl → 1.1.53__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.
reydb/rbase.py CHANGED
@@ -9,19 +9,28 @@
9
9
  """
10
10
 
11
11
 
12
- from typing import Any, TypedDict, Literal, overload
12
+ from typing import Any, TypedDict, Literal
13
+ from enum import EnumType
14
+ from sqlalchemy import text as sqlalchemy_text
13
15
  from sqlalchemy.engine.base import Engine, Connection
14
16
  from sqlalchemy.engine.url import URL
15
17
  from sqlalchemy.sql.elements import TextClause
16
18
  from reykit.rbase import Base, throw
17
19
  from reykit.rre import search
18
20
 
21
+ from reykit.rdata import to_json
22
+ from reykit.rre import findall
23
+
19
24
 
20
25
  __all__ = (
21
26
  'DatabaseBase',
27
+ 'handle_sql',
28
+ 'handle_data',
22
29
  'extract_url',
23
30
  'extract_engine',
24
- 'extract_path'
31
+ 'extract_path',
32
+ 'get_syntax',
33
+ 'is_multi_sql'
25
34
  )
26
35
 
27
36
 
@@ -47,6 +56,85 @@ class DatabaseBase(Base):
47
56
  """
48
57
 
49
58
 
59
+ def handle_sql(sql: str | TextClause) -> TextClause:
60
+ """
61
+ Handle SQL.
62
+
63
+ Parameters
64
+ ----------
65
+ sql : SQL in method `sqlalchemy.text` format, or TextClause object.
66
+
67
+ Returns
68
+ -------
69
+ TextClause instance.
70
+ """
71
+
72
+ # Handle parameter.
73
+ if type(sql) == TextClause:
74
+ sql = sql.text
75
+
76
+ # Handle.
77
+ sql = sql.strip()
78
+ if sql[-1] != ';':
79
+ sql += ';'
80
+ sql = sqlalchemy_text(sql)
81
+
82
+ return sql
83
+
84
+
85
+ def handle_data(data: list[dict], sql: str | TextClause) -> list[dict]:
86
+ """
87
+ Handle data based on the content of SQL.
88
+
89
+ Parameters
90
+ ----------
91
+ data : Data set for filling.
92
+ sql : SQL in method `sqlalchemy.text` format, or TextClause object.
93
+
94
+ Returns
95
+ -------
96
+ Filled data.
97
+ """
98
+
99
+ # Handle parameter.
100
+ if type(sql) == TextClause:
101
+ sql = sql.text
102
+
103
+ # Extract keys.
104
+ pattern = '(?<!\\\\):(\\w+)'
105
+ sql_keys = findall(pattern, sql)
106
+
107
+ # Extract keys of syntax "in".
108
+ pattern = '[iI][nN]\\s+(?<!\\\\):(\\w+)'
109
+ sql_keys_in = findall(pattern, sql)
110
+
111
+ # Loop.
112
+ for row in data:
113
+ if row == {}:
114
+ continue
115
+ for key in sql_keys:
116
+ value = row.get(key)
117
+
118
+ # Empty string.
119
+ if value == '':
120
+ value = None
121
+
122
+ # Convert.
123
+ elif (
124
+ type(value) in (list, dict)
125
+ and key not in sql_keys_in
126
+ ):
127
+ value = to_json(value)
128
+
129
+ # Enum.
130
+ elif isinstance(type(value), EnumType):
131
+ value = value.value
132
+
133
+ row[key] = value
134
+
135
+ return data
136
+
137
+
50
138
  def extract_url(url: str | URL) -> URLParameters:
51
139
  """
52
140
  Extract parameters from URL of string.
reydb/rconfig.py CHANGED
@@ -17,7 +17,7 @@ from datetime import (
17
17
  time as Time,
18
18
  timedelta as Timedelta
19
19
  )
20
- from reykit.rbase import null, throw
20
+ from reykit.rbase import Null, throw
21
21
 
22
22
  from .rdb import Database
23
23
 
@@ -445,10 +445,10 @@ class DatabaseConfig(object):
445
445
  """
446
446
 
447
447
  # Get.
448
- value = self.get(key, null)
448
+ value = self.get(key, Null)
449
449
 
450
450
  # Check.
451
- if value == null:
451
+ if value == Null:
452
452
  throw(KeyError, key)
453
453
 
454
454
  return value
reydb/rconn.py CHANGED
@@ -10,7 +10,8 @@
10
10
 
11
11
 
12
12
  from typing import Self
13
- from sqlalchemy import Transaction
13
+ from sqlalchemy import Connection, Transaction
14
+ from sqlalchemy.ext.asyncio import AsyncConnection, AsyncTransaction
14
15
 
15
16
  from .rbase import DatabaseBase
16
17
  from .rdb import Database
@@ -18,6 +19,7 @@ from .rdb import Database
18
19
 
19
20
  __all__ = (
20
21
  'DatabaseConnection',
22
+ 'DatabaseConnectionAsync'
21
23
  )
22
24
 
23
25
 
@@ -38,7 +40,7 @@ class DatabaseConnection(DatabaseBase):
38
40
  Parameters
39
41
  ----------
40
42
  db : `Database` instance.
41
- autocommit: Whether automatic commit connection.
43
+ autocommit: Whether automatic commit execute.
42
44
  """
43
45
 
44
46
  # Import.
@@ -47,11 +49,44 @@ class DatabaseConnection(DatabaseBase):
47
49
  # Build.
48
50
  self.db = db
49
51
  self.autocommit = autocommit
50
- self.conn = db.engine.connect()
51
- self.exec = DatabaseExecute(self)
52
+ self.execute = DatabaseExecute(self)
53
+ self.conn: Connection | None = None
52
54
  self.begin: Transaction | None = None
53
55
 
54
56
 
57
+ def get_conn(self) -> Connection:
58
+ """
59
+ Get `Connection` instance.
60
+
61
+ Returns
62
+ -------
63
+ Instance.
64
+ """
65
+
66
+ # Create.
67
+ if self.conn is None:
68
+ self.conn = self.db.engine.connect()
69
+
70
+ return self.conn
71
+
72
+
73
+ def get_begin(self) -> Transaction:
74
+ """
75
+ Get `Transaction` instance.
76
+
77
+ Returns
78
+ -------
79
+ Instance.
80
+ """
81
+
82
+ # Create.
83
+ if self.begin is None:
84
+ conn = self.get_conn()
85
+ self.begin = conn.begin()
86
+
87
+ return self.begin
88
+
89
+
55
90
  def commit(self) -> None:
56
91
  """
57
92
  Commit cumulative executions.
@@ -80,7 +115,12 @@ class DatabaseConnection(DatabaseBase):
80
115
  """
81
116
 
82
117
  # Close.
83
- self.conn.close()
118
+ if self.begin is not None:
119
+ self.begin.close()
120
+ self.begin = None
121
+ if self.conn is not None:
122
+ self.conn.close()
123
+ self.conn = None
84
124
 
85
125
 
86
126
  def __enter__(self) -> Self:
@@ -117,30 +157,160 @@ class DatabaseConnection(DatabaseBase):
117
157
  self.close()
118
158
 
119
159
 
120
- __del__ = close
160
+ def insert_id(self) -> int:
161
+ """
162
+ Return last self increasing ID.
163
+
164
+ Returns
165
+ -------
166
+ ID.
167
+ """
168
+
169
+ # Get.
170
+ sql = 'SELECT LAST_INSERT_ID()'
171
+ result = self.execute(sql)
172
+ id_ = result.scalar()
173
+
174
+ return id_
175
+
176
+
177
+ class DatabaseConnectionAsync(DatabaseBase):
178
+ """
179
+ Asynchronous database connection type.
180
+ """
181
+
182
+
183
+ def __init__(
184
+ self,
185
+ db: Database,
186
+ autocommit: bool
187
+ ) -> None:
188
+ """
189
+ Build instance attributes.
190
+
191
+ Parameters
192
+ ----------
193
+ db : `DatabaseAsync` instance.
194
+ autocommit: Whether automatic commit execute.
195
+ """
196
+
197
+ # Import.
198
+ from .rexec import DatabaseExecuteAsync
199
+
200
+ # Build.
201
+ self.db = db
202
+ self.autocommit = autocommit
203
+ self.execute = DatabaseExecuteAsync(self)
204
+ self.conn: AsyncConnection | None = None
205
+ self.begin: AsyncTransaction | None = None
206
+
207
+
208
+ async def get_conn(self) -> AsyncConnection:
209
+ """
210
+ Asynchronous get `Connection` instance.
211
+
212
+ Returns
213
+ -------
214
+ Instance.
215
+ """
216
+
217
+ # Create.
218
+ if self.conn is None:
219
+ self.conn = await self.db.aengine.connect()
220
+
221
+ return self.conn
121
222
 
122
223
 
123
- @property
124
- def execute(self):
224
+ async def get_begin(self) -> AsyncTransaction:
125
225
  """
126
- Build `database execute` instance.
226
+ Asynchronous get `Transaction` instance.
127
227
 
128
228
  Returns
129
229
  -------
130
230
  Instance.
131
231
  """
132
232
 
133
- # Create transaction.
233
+ # Create.
134
234
  if self.begin is None:
135
- self.begin = self.conn.begin()
235
+ conn = await self.get_conn()
236
+ self.begin = await conn.begin()
136
237
 
137
- return self.exec
238
+ return self.begin
138
239
 
139
240
 
140
- @property
141
- def insert_id(self) -> int:
241
+ async def commit(self) -> None:
142
242
  """
143
- Return last self increasing ID.
243
+ Asynchronous commit cumulative executions.
244
+ """
245
+
246
+ # Commit.
247
+ if self.begin is not None:
248
+ await self.begin.commit()
249
+ self.begin = None
250
+
251
+
252
+ async def rollback(self) -> None:
253
+ """
254
+ Asynchronous rollback cumulative executions.
255
+ """
256
+
257
+ # Rollback.
258
+ if self.begin is not None:
259
+ await self.begin.rollback()
260
+ self.begin = None
261
+
262
+
263
+ async def close(self) -> None:
264
+ """
265
+ Asynchronous close database connection.
266
+ """
267
+
268
+ # Close.
269
+ if self.begin is not None:
270
+ await self.begin.close()
271
+ self.begin = None
272
+ if self.conn is not None:
273
+ await self.conn.close()
274
+ self.conn = None
275
+
276
+
277
+ async def __aenter__(self) -> Self:
278
+ """
279
+ Asynchronous enter syntax `async with`.
280
+
281
+ Returns
282
+ -------
283
+ Self.
284
+ """
285
+
286
+ return self
287
+
288
+
289
+ async def __aexit__(
290
+ self,
291
+ exc_type: type[BaseException] | None,
292
+ *_
293
+ ) -> None:
294
+ """
295
+ Asynchronous exit syntax `async with`.
296
+
297
+ Parameters
298
+ ----------
299
+ exc_type : Exception type.
300
+ """
301
+
302
+ # Commit.
303
+ if exc_type is None:
304
+ await self.commit()
305
+
306
+ # Close.
307
+ else:
308
+ await self.close()
309
+
310
+
311
+ async def insert_id(self) -> int:
312
+ """
313
+ Asynchronous return last self increasing ID.
144
314
 
145
315
  Returns
146
316
  -------
@@ -149,7 +319,7 @@ class DatabaseConnection(DatabaseBase):
149
319
 
150
320
  # Get.
151
321
  sql = 'SELECT LAST_INSERT_ID()'
152
- result = self.execute(sql)
322
+ result = await self.execute(sql)
153
323
  id_ = result.scalar()
154
324
 
155
325
  return id_
reydb/rdb.py CHANGED
@@ -9,10 +9,12 @@
9
9
  """
10
10
 
11
11
 
12
+ from typing import Literal, Final, overload
12
13
  from urllib.parse import quote as urllib_quote
13
14
  from pymysql.constants.CLIENT import MULTI_STATEMENTS
14
15
  from sqlalchemy import create_engine as sqlalchemy_create_engine
15
16
  from sqlalchemy.engine.base import Engine
17
+ from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine as sqlalchemy_create_async_engine
16
18
  from reykit.rtext import join_data_text
17
19
 
18
20
  from .rbase import DatabaseBase, extract_url
@@ -26,10 +28,6 @@ __all__ = (
26
28
  class Database(DatabaseBase):
27
29
  """
28
30
  Database type, based `MySQL`.
29
-
30
- Attributes
31
- ----------
32
- default_report : Whether default to report execution.
33
31
  """
34
32
 
35
33
  default_report: bool = False
@@ -45,7 +43,7 @@ class Database(DatabaseBase):
45
43
  pool_size: int = 5,
46
44
  max_overflow: int = 10,
47
45
  pool_timeout: float = 30.0,
48
- pool_recycle: int | None = None,
46
+ pool_recycle: int | None = 3600,
49
47
  **query: str
50
48
  ) -> None:
51
49
  """
@@ -62,10 +60,7 @@ class Database(DatabaseBase):
62
60
  max_overflow : Number of connections `allowed overflow`.
63
61
  pool_timeout : Number of seconds `wait create` connection.
64
62
  pool_recycle : Number of seconds `recycle` connection.
65
- - `None`: Automatic select.
66
- When is remote server database, then is database variable `wait_timeout` value.
67
- When is local database file, then is `-1`.
68
- - `Literal[-1]`: No recycle.
63
+ - `None | Literal[-1]`: No recycle.
69
64
  - `int`: Use this value.
70
65
  query : Remote server database parameters.
71
66
  """
@@ -90,14 +85,8 @@ class Database(DatabaseBase):
90
85
  self.query = query
91
86
 
92
87
  # Create engine.
93
- self.engine = self.__create_engine()
94
-
95
- # Server recycle time.
96
- if pool_recycle is None:
97
- wait_timeout = self.variables['wait_timeout']
98
- if wait_timeout is not None:
99
- self.pool_recycle = int(wait_timeout)
100
- self.engine.pool._recycle = self.pool_recycle
88
+ self.engine = self.__create_engine(False)
89
+ self.aengine = self.__create_engine(True)
101
90
 
102
91
 
103
92
  @property
@@ -146,7 +135,10 @@ class Database(DatabaseBase):
146
135
 
147
136
  # Generate URL.
148
137
  password = urllib_quote(self.password)
149
- url_ = f'mysql+pymysql://{self.username}:{password}@{self.host}:{self.port}/{self.database}'
138
+ if self.is_async:
139
+ url_ = f'mysql+aiomysql://{self.username}:{password}@{self.host}:{self.port}/{self.database}'
140
+ else:
141
+ url_ = f'mysql+pymysql://{self.username}:{password}@{self.host}:{self.port}/{self.database}'
150
142
 
151
143
  # Add Server parameter.
152
144
  if self.query != {}:
@@ -161,10 +153,20 @@ class Database(DatabaseBase):
161
153
  return url_
162
154
 
163
155
 
164
- def __create_engine(self) -> Engine:
156
+ @overload
157
+ def __create_engine(self, is_async: Literal[False]) -> Engine: ...
158
+
159
+ @overload
160
+ def __create_engine(self, is_async: Literal[True]) -> AsyncEngine: ...
161
+
162
+ def __create_engine(self, is_async: bool) -> Engine | AsyncEngine:
165
163
  """
166
164
  Create database `Engine` object.
167
165
 
166
+ Parameters
167
+ ----------
168
+ is_async : Whether to use asynchronous engine.
169
+
168
170
  Returns
169
171
  -------
170
172
  Engine object.
@@ -181,23 +183,35 @@ class Database(DatabaseBase):
181
183
  }
182
184
 
183
185
  # Create Engine.
184
- engine = sqlalchemy_create_engine(**engine_params)
186
+ if is_async:
187
+ engine = sqlalchemy_create_async_engine(**engine_params)
188
+ else:
189
+ engine = sqlalchemy_create_engine(**engine_params)
185
190
 
186
191
  return engine
187
192
 
188
193
 
189
- @property
190
- def count_conn(self) -> tuple[int, int]:
194
+ def __conn_count(self, is_async: bool) -> tuple[int, int]:
191
195
  """
192
196
  Count number of keep open and allowed overflow connection.
193
197
 
198
+ Parameters
199
+ ----------
200
+ is_async : Whether to use asynchronous engine.
201
+
194
202
  Returns
195
203
  -------
196
204
  Number of keep open and allowed overflow connection.
197
205
  """
198
206
 
207
+ # Handle parameter.
208
+ if is_async:
209
+ engine = self.aengine
210
+ else:
211
+ engine = self.engine
212
+
199
213
  # Count.
200
- _overflow = self.engine.pool._overflow
214
+ _overflow: int = engine.pool._overflow
201
215
  if _overflow < 0:
202
216
  keep_n = self.pool_size + _overflow
203
217
  overflow_n = 0
@@ -208,6 +222,38 @@ class Database(DatabaseBase):
208
222
  return keep_n, overflow_n
209
223
 
210
224
 
225
+ @property
226
+ def conn_count(self) -> tuple[int, int]:
227
+ """
228
+ Count number of keep open and allowed overflow connection.
229
+
230
+ Returns
231
+ -------
232
+ Number of keep open and allowed overflow connection.
233
+ """
234
+
235
+ # Count.
236
+ keep_n, overflow_n = self.__conn_count(False)
237
+
238
+ return keep_n, overflow_n
239
+
240
+
241
+ @property
242
+ def aconn_count(self) -> tuple[int, int]:
243
+ """
244
+ Count number of keep open and allowed overflow asynchronous connection.
245
+
246
+ Returns
247
+ -------
248
+ Number of keep open and allowed overflow asynchronous connection.
249
+ """
250
+
251
+ # Count.
252
+ keep_n, overflow_n = self.__conn_count(True)
253
+
254
+ return keep_n, overflow_n
255
+
256
+
211
257
  def schema(self, filter_default: bool = True) -> dict[str, dict[str, list[str]]]:
212
258
  """
213
259
  Get schemata of databases and tables and columns.
@@ -284,7 +330,7 @@ class Database(DatabaseBase):
284
330
 
285
331
  Parameters
286
332
  ----------
287
- autocommit: Whether automatic commit connection.
333
+ autocommit: Whether automatic commit execute.
288
334
 
289
335
  Returns
290
336
  -------
@@ -317,6 +363,45 @@ class Database(DatabaseBase):
317
363
  return exec
318
364
 
319
365
 
366
+ def aconnect(self, autocommit: bool = False):
367
+ """
368
+ Build `DatabaseConnectionAsync` instance.
369
+
370
+ Parameters
371
+ ----------
372
+ autocommit: Whether automatic commit execute.
373
+
374
+ Returns
375
+ -------
376
+ Database connection instance.
377
+ """
378
+
379
+ # Import.
380
+ from .rconn import DatabaseConnectionAsync
381
+
382
+ # Build.
383
+ conn = DatabaseConnectionAsync(self, autocommit)
384
+
385
+ return conn
386
+
387
+
388
+ @property
389
+ def aexecute(self):
390
+ """
391
+ Build `DatabaseConnectionAsync` instance.
392
+
393
+ Returns
394
+ -------
395
+ Instance.
396
+ """
397
+
398
+ # Build.
399
+ dbconn = self.aconnect(True)
400
+ exec = dbconn.execute
401
+
402
+ return exec
403
+
404
+
320
405
  @property
321
406
  def orm(self):
322
407
  """
@@ -546,7 +631,8 @@ class Database(DatabaseBase):
546
631
  for key, value in self.__dict__.items()
547
632
  if key not in filter_key
548
633
  }
549
- info['count'] = self.count_conn
634
+ info['conn_count'] = self.conn_count
635
+ info['aconn_count'] = self.aconn_count
550
636
  text = join_data_text(info)
551
637
 
552
638
  return text