ab_engine 0.2.0__tar.gz → 0.2.2__tar.gz
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.
- {ab_engine-0.2.0 → ab_engine-0.2.2}/PKG-INFO +56 -21
- {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/db/driver.py +2 -2
- {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/db/driver_mysql.py +9 -1
- {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/db/driver_postgresql.py +3 -1
- {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/db/driver_sqlite.py +6 -2
- {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/db/option.py +6 -2
- {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/db/processor.py +2 -1
- {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/db/table.py +2 -2
- {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/env/config.py +46 -37
- {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/env/db_context.py +5 -5
- {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/error.yaml +6 -0
- {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/rpc/json_rpc.py +10 -10
- {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/rpc/rpc.py +1 -1
- {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine.egg-info/PKG-INFO +56 -21
- {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine.egg-info/requires.txt +1 -1
- {ab_engine-0.2.0 → ab_engine-0.2.2}/pyproject.toml +2 -2
- {ab_engine-0.2.0 → ab_engine-0.2.2}/readme.md +54 -19
- {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/__init__.py +0 -0
- {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/class_tools.py +0 -0
- {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/db/__init__.py +0 -0
- {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/db/driver_valkey.py +0 -0
- {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/env/__init__.py +0 -0
- {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/env/timer.py +0 -0
- {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/error.py +0 -0
- {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/rpc/__init__.py +0 -0
- {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/rpc/fnc.py +0 -0
- {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine.egg-info/SOURCES.txt +0 -0
- {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine.egg-info/dependency_links.txt +0 -0
- {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine.egg-info/top_level.txt +0 -0
- {ab_engine-0.2.0 → ab_engine-0.2.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ab_engine
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Library of standard modules
|
|
5
5
|
Author-email: Andrew Bulatov <BulatovAndrew@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -9,7 +9,7 @@ Requires-Python: >=3.10
|
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
Requires-Dist: json5>=0.12.0
|
|
11
11
|
Requires-Dist: psycopg>=3.2.9
|
|
12
|
-
Requires-Dist:
|
|
12
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
13
13
|
Requires-Dist: toml>=0.10.2
|
|
14
14
|
|
|
15
15
|
Библиотека стандартных модулей
|
|
@@ -167,17 +167,17 @@ create table config(
|
|
|
167
167
|
|
|
168
168
|
нресколько имен ключей файла конфигурации зарезервированы и являются специальными:
|
|
169
169
|
|
|
170
|
-
**timers** - определяет интервалы для таймеров системы
|
|
171
|
-
**logging** - позволяет задать настройки логирования
|
|
172
|
-
**database**- содержит строки соединения с одной или несколькими БД
|
|
173
|
-
**defaults**- содержит значения по умолчанию
|
|
170
|
+
* **timers** - определяет интервалы для таймеров системы
|
|
171
|
+
* **logging** - позволяет задать настройки логирования
|
|
172
|
+
* **database**- содержит строки соединения с одной или несколькими БД
|
|
173
|
+
* **defaults**- содержит значения по умолчанию
|
|
174
174
|
|
|
175
175
|
#### Значения по умолчанию
|
|
176
176
|
|
|
177
|
-
**defaults.name** - имя, подставляемое в лог как имя сервиса
|
|
178
|
-
**defaults.database** - имя соединения БД по умолчанию
|
|
179
|
-
**defaults.plugin_path** - путь к каталогу плагинов
|
|
180
|
-
**defaults.db_driver_path** - путь к драйверам БД (по умолчанию ab_engine/db)
|
|
177
|
+
* **defaults.name** - имя, подставляемое в лог как имя сервиса
|
|
178
|
+
* **defaults.database** - имя соединения БД по умолчанию
|
|
179
|
+
* **defaults.plugin_path** - путь к каталогу плагинов
|
|
180
|
+
* **defaults.db_driver_path** - путь к драйверам БД (по умолчанию ab_engine/db)
|
|
181
181
|
|
|
182
182
|
## Управление таймерами
|
|
183
183
|
|
|
@@ -209,7 +209,7 @@ callbac должен быть функцией вида:
|
|
|
209
209
|
>
|
|
210
210
|
> ...
|
|
211
211
|
|
|
212
|
-
в параметр **name** передается
|
|
212
|
+
в параметр **name** передается имя таймера, в **prev_time** предыдущее время запуска, первый раз **prev_time = None**
|
|
213
213
|
|
|
214
214
|
Предусмотрены следующие типы интервалов работы таймера:
|
|
215
215
|
|
|
@@ -219,7 +219,7 @@ callbac должен быть функцией вида:
|
|
|
219
219
|
* TimerInterval.HOUR
|
|
220
220
|
* TimerInterval.DAY
|
|
221
221
|
|
|
222
|
-
Для управления таймерами, также,
|
|
222
|
+
Для управления таймерами, также, используются методы:
|
|
223
223
|
|
|
224
224
|
* **start**(immediately_start=True) - запускает таймер, если он остановлен. Если immediately_start=False, то таймер ждет свой
|
|
225
225
|
интервал и только потом запускает callback, иначе callback будет запущен сразу после вызова start()
|
|
@@ -277,7 +277,7 @@ if __name__ == '__main__':
|
|
|
277
277
|
- code: ERROR # код для вызова из функции raise_error
|
|
278
278
|
http: 444 # код для возврата для HTTP. можно не указывать
|
|
279
279
|
msg: В {place} у объекта {name} случилось {what} # Сообщение в которое будут подставлены kwargs из raise_error
|
|
280
|
-
class: Error #
|
|
280
|
+
class: Error # задаёт класс исключения. По умолчанию это Error из библиотеки (т.е. Error можно было не указывать)
|
|
281
281
|
- code: OTHER_ERROR
|
|
282
282
|
class: Exception
|
|
283
283
|
msg: Еще ошибка
|
|
@@ -373,16 +373,44 @@ logging:
|
|
|
373
373
|
## Доступ к БД
|
|
374
374
|
|
|
375
375
|
Библиотека поддерживает хранение настроек соединений и выполнение команд SQL на этих соединениях с БД.
|
|
376
|
+
|
|
376
377
|
Из коробки реализованы драйвер Postgresql, Sqlite и Valkey (Redis). Также реализован драйвер Mysql но для его работы дополнительно надо установить зависимость mysql-connector-python.
|
|
377
378
|
|
|
378
379
|
Можно описывать драйвера к другим БД. Для того чтобы использовать собственный драйвер, можно добавить класс драйвера
|
|
379
380
|
в словарь DRIVER_CLASSES, который находится в модуле ab_engine/db/option, либо положить драйвер в каталог драйверов.
|
|
381
|
+
|
|
380
382
|
Пример драйвера в DRIVER_CLASSES:
|
|
383
|
+
```python
|
|
384
|
+
from ab_engine.db import sql, ONE, DB
|
|
385
|
+
from ab_engine.db.option import DRIVER_CLASSES
|
|
386
|
+
from ab_engine.db.driver_postgresql import Driver
|
|
387
|
+
import asyncio
|
|
388
|
+
|
|
389
|
+
class MyPgDriver(Driver):
|
|
390
|
+
|
|
391
|
+
async def begin(self):
|
|
392
|
+
await super().begin()
|
|
393
|
+
print("BEGIN TRANSACTION", flush=True)
|
|
394
|
+
|
|
395
|
+
async def rollback(self):
|
|
396
|
+
await super().rollback()
|
|
397
|
+
print("END TRANSACTION", flush=True)
|
|
398
|
+
|
|
399
|
+
DRIVER_CLASSES["my_pg"] = MyPgDriver
|
|
400
|
+
|
|
401
|
+
async def main():
|
|
402
|
+
x = await sql("select version();", ONE, DB("my_pg://postgres:postgres@localhost:5432/postgres"))
|
|
403
|
+
print(x)
|
|
404
|
+
await DB.garbage_collect()
|
|
405
|
+
|
|
406
|
+
if __name__ == '__main__':
|
|
407
|
+
asyncio.run(main())
|
|
408
|
+
```
|
|
381
409
|
|
|
382
|
-
необходимости
|
|
410
|
+
При необходимости задать путь поиска с собственным драйвером, можно в строке соединения, в
|
|
383
411
|
фигурных скобках задать параметр **driver_path**, определяющий путь для поиска драйвера.
|
|
384
412
|
|
|
385
|
-
Если нужно задать свой путь поиска для всех драйверов БД, можно задать параметр db_driver_path в defaults
|
|
413
|
+
Если нужно задать свой путь поиска для всех драйверов БД, можно задать параметр **db_driver_path** в секции конфигурации **defaults**.
|
|
386
414
|
|
|
387
415
|
Если приложению требуется только одна строка соединения, то ее можно задать в переменной database, например:
|
|
388
416
|
|
|
@@ -390,18 +418,18 @@ logging:
|
|
|
390
418
|
database: postgresql://localhost:5432/postgres?user=postgres&password=postgres
|
|
391
419
|
```
|
|
392
420
|
|
|
393
|
-
|
|
421
|
+
Чтобы задать несколько соединений, нужно использовать database как секцию:
|
|
394
422
|
|
|
395
423
|
```toml
|
|
396
424
|
[database]
|
|
397
425
|
test = "postgresql://localhost:5432/test?user=postgres&password=postgres"
|
|
398
|
-
mydb = "postgresql://localhost:5432/mydb
|
|
426
|
+
mydb = "postgresql://postgres:postgres@localhost:5432/mydb"
|
|
399
427
|
|
|
400
428
|
[defaults]
|
|
401
429
|
database = "test"
|
|
402
430
|
```
|
|
403
431
|
|
|
404
|
-
При этом, в секции defaults можно задать имя соединения БД, которое будет использоваться по
|
|
432
|
+
При этом, в секции **defaults** можно задать имя соединения БД, которое будет использоваться по умолчанию. Для этого следует задать значение **database** в секции **defaults**.
|
|
405
433
|
|
|
406
434
|
Строка соединения с БД имеет следующую структуру:
|
|
407
435
|
<имя_драйвера>**://**<адрес сервера>**:**<номер порта>**/**<имя БД>**?**<параметры соединения>**{**дополнительные параметры**}**
|
|
@@ -418,10 +446,15 @@ database = "test"
|
|
|
418
446
|
который ограничит максимальное количество таких соединений. Т.е. по достижении данного числа соединений система
|
|
419
447
|
не станет выполнять очередную команду до тех пор, пока не освободится одно из текущих соединений с БД.
|
|
420
448
|
|
|
421
|
-
Для некоторых
|
|
449
|
+
Для некоторых драйверов, например, для встроенного драйвера postgresql строка сооединения с БД
|
|
422
450
|
может иметь альтернативную структуру:
|
|
423
451
|
|
|
424
|
-
<имя_драйвера>**://**<имя
|
|
452
|
+
<имя_драйвера>**://**<имя пользователя>**:**<пароль>**@**<адрес сервера>**:**<номер порта>**/**<имя БД>**?**<параметры соединения>**{**дополнительные параметры**}**
|
|
453
|
+
|
|
454
|
+
Если нужно установить **peer** соединение c БД postgresql, то в строке соединения достаточно передать имя БД и доп. параметры (если нужно). Например:
|
|
455
|
+
```python
|
|
456
|
+
x = await sql("select version()", ONE, DB("postgresql://postgres?application_name=test"))
|
|
457
|
+
```
|
|
425
458
|
|
|
426
459
|
Драйвер БД должен иметь имя driver_<имя в строке соединения>.py и предоставлять следующий контракт:
|
|
427
460
|
|
|
@@ -584,6 +617,7 @@ inner join t2 on t2.t_id=t.id and t2.name=$name
|
|
|
584
617
|
where t.date < now()::date;
|
|
585
618
|
```
|
|
586
619
|
система автоматически создает подстроку со значением параметра и, при необходимости, заключает это значение в кавычки.
|
|
620
|
+
|
|
587
621
|
|**Тип python**|**Тип БД**|**В кавычках**|
|
|
588
622
|
|--------|---------|-----|
|
|
589
623
|
| None | NULL | [ ] |
|
|
@@ -653,6 +687,7 @@ async def sql(query:str, *args, **kwargs):
|
|
|
653
687
|
|
|
654
688
|
Опции, это специальные параметры, которые влияют на выполнение запроса в целом.
|
|
655
689
|
Передавать опции можно только в конце списка args, после позиционных параметров, либо вместо них.
|
|
690
|
+
|
|
656
691
|
В библиотеке реализованы следующие опции:
|
|
657
692
|
* DB - служит для передачи соединения в функцию sql. Передается как DB("строка соединения").
|
|
658
693
|
* NOTICE - то же, что DB, но запросы с такой опцией возвращают как результат список сообщений нотификации
|
|
@@ -1220,7 +1255,7 @@ if __name__ == '__main__':
|
|
|
1220
1255
|
|
|
1221
1256
|
Методы RPC регистрируются функцией register_rpc_list либо c помощью декоратора register_rpc, который также можно использовать как функцию.
|
|
1222
1257
|
|
|
1223
|
-
Регистрация списка функций осуществляется с помощью register_rpc_list, которая принимает на вход
|
|
1258
|
+
Регистрация списка функций осуществляется с помощью register_rpc_list, которая принимает на вход параметры defs и self.
|
|
1224
1259
|
* **defs** - содержит список или словарь с описаниями
|
|
1225
1260
|
* **self** - необязательный параметр, в котором передается модуль или объект из которого будут регистрироваться функции. если в self ничего не передано, то будут регистрироваться функции из модуля, в котором вызвана register_rpc_list.
|
|
1226
1261
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
2
|
from json import dumps
|
|
3
|
-
from datetime import datetime
|
|
3
|
+
from datetime import datetime, time
|
|
4
4
|
from abc import ABC, abstractmethod
|
|
5
5
|
from collections import namedtuple
|
|
6
6
|
from typing import Optional
|
|
@@ -158,7 +158,7 @@ class Driver(ABC):
|
|
|
158
158
|
elif isinstance(var, dict):
|
|
159
159
|
var = dumps(var, ensure_ascii=False).replace("'", "''")
|
|
160
160
|
return reencode(var)
|
|
161
|
-
elif isinstance(var, datetime):
|
|
161
|
+
elif isinstance(var, (datetime, time)):
|
|
162
162
|
return f" '{var}'"
|
|
163
163
|
else:
|
|
164
164
|
return f" {var}"
|
|
@@ -69,8 +69,16 @@ class Driver(BaseDriver):
|
|
|
69
69
|
self._conn_params[k] = v
|
|
70
70
|
|
|
71
71
|
async def begin(self):
|
|
72
|
-
await self._before_open()
|
|
72
|
+
params = await self._before_open()
|
|
73
73
|
self._conn = await connect(**self._conn_params)
|
|
74
|
+
if params:
|
|
75
|
+
cur = await self._conn.cursor()
|
|
76
|
+
try:
|
|
77
|
+
for x in params:
|
|
78
|
+
p = x if x.startswith("@") else f"@{x.strip()}"
|
|
79
|
+
await cur.execute(f"SET {p} = '{params[x]}'")
|
|
80
|
+
finally:
|
|
81
|
+
await cur.close()
|
|
74
82
|
|
|
75
83
|
async def sql(self, query, one_row=False, row_factory=RowFactory.DICT):
|
|
76
84
|
if self._conn is None:
|
|
@@ -56,8 +56,10 @@ class Driver(BaseDriver):
|
|
|
56
56
|
for x in params:
|
|
57
57
|
if x == "TIMEZONE":
|
|
58
58
|
await self._conn.execute(f"set session timezone '{params[x]}'")
|
|
59
|
+
elif x.startswith("pg_"):
|
|
60
|
+
await self._conn.execute(f"SET {x[3:]} = '{params[x]}'")
|
|
59
61
|
else:
|
|
60
|
-
await self._conn.execute(f"
|
|
62
|
+
await self._conn.execute(f"select set_config('{x if '.' in x else f'my_app.{x}'}','{params[x]}', true)")
|
|
61
63
|
if self._notify:
|
|
62
64
|
self._conn.add_notify_handler(self._notify_callback)
|
|
63
65
|
|
|
@@ -19,7 +19,11 @@ _Info = namedtuple("Info", ["column_name", "data_type", "is_nullable", "characte
|
|
|
19
19
|
"numeric_precision", "numeric_scale", "column_default", "autoincrement", "pk"])
|
|
20
20
|
|
|
21
21
|
def _field_ifo_tuples(fields, str_fields):
|
|
22
|
-
str_fields = str_fields["sql"].split("\n", 1)
|
|
22
|
+
str_fields = str_fields["sql"].split("\n", 1)
|
|
23
|
+
if len(str_fields) > 1:
|
|
24
|
+
str_fields = str_fields[1][:-1].strip().split("\n")
|
|
25
|
+
else:
|
|
26
|
+
str_fields = []
|
|
23
27
|
str_def = {}
|
|
24
28
|
for x in str_fields:
|
|
25
29
|
n, x = x.strip().split(" ", 1)
|
|
@@ -30,7 +34,7 @@ def _field_ifo_tuples(fields, str_fields):
|
|
|
30
34
|
sz = int(sz)
|
|
31
35
|
else:
|
|
32
36
|
sz = None
|
|
33
|
-
ai = str_def
|
|
37
|
+
ai = str_def.get(row["name"],[])
|
|
34
38
|
fields[n] = _Info(column_name=row["name"], data_type=row["type"].lower(), character_maximum_length=sz,
|
|
35
39
|
column_default=row["dflt_value"], is_nullable=row["notnull"] == 0 and row["pk"] == 0,
|
|
36
40
|
autoincrement=" autoincrement" in ai, pk=row["pk"],
|
|
@@ -126,11 +126,15 @@ class DB(Option):
|
|
|
126
126
|
driver = DRIVER_CLASSES.get(driver_name)
|
|
127
127
|
driver_path = None
|
|
128
128
|
if conn_params:
|
|
129
|
-
conn_params = conn_params[:-1]
|
|
129
|
+
conn_params = conn_params[:-1]
|
|
130
|
+
mono_params = [x for x in conn_params.split("&") if "=" not in x ]
|
|
131
|
+
conn_params = {x[0].strip():x[1].strip() for x in [x.split("=", 1) for x in conn_params.split("&") if "=" in x]}
|
|
130
132
|
if "driver_path" in conn_params:
|
|
131
133
|
driver_path = conn_params["driver_path"]
|
|
132
134
|
del conn_params["driver_path"]
|
|
133
|
-
conn_params = "?" + "&".join(conn_params)
|
|
135
|
+
conn_params = "?" + "&".join([f"{x}={conn_params[x]}" for x in conn_params])
|
|
136
|
+
if mono_params:
|
|
137
|
+
conn_params +="&" + "&".join(mono_params)
|
|
134
138
|
connection_string += conn_params
|
|
135
139
|
if driver is None:
|
|
136
140
|
if driver_path is None and _CFG_ and _CFG_.initialized:
|
|
@@ -61,12 +61,13 @@ async def sql(query:str, *args, **kwargs):
|
|
|
61
61
|
await db.garbage_collect(False)
|
|
62
62
|
one_row = one_row.one_row if one_row else False
|
|
63
63
|
row_factory = row_factory.row_factory if row_factory and row_factory.row_factory != RowFactory.ANY else RowFactory.DICT
|
|
64
|
+
if callback is not None:
|
|
65
|
+
kwargs["__PARAM_CALLBACK_GETTER"] = callback
|
|
64
66
|
if parse:
|
|
65
67
|
query = await db.connection.parse_query(query, *args, **kwargs)
|
|
66
68
|
if page:
|
|
67
69
|
query = await page(db.connection, query)
|
|
68
70
|
if callback is not None:
|
|
69
|
-
kwargs["__PARAM_CALLBACK_GETTER"] = callback
|
|
70
71
|
query = await callback(query)
|
|
71
72
|
if itr:
|
|
72
73
|
if page:
|
|
@@ -193,7 +193,7 @@ class Field:
|
|
|
193
193
|
q = f"{q} where {self.table._filter}"
|
|
194
194
|
ret = await sql(q, self.table._db, ONE)
|
|
195
195
|
if self.table._auto_close_conn:
|
|
196
|
-
self.table._db.rollback()
|
|
196
|
+
await self.table._db.connection.rollback()
|
|
197
197
|
return ret
|
|
198
198
|
|
|
199
199
|
async def min(self):
|
|
@@ -648,7 +648,7 @@ class EnvTable(Table):
|
|
|
648
648
|
|
|
649
649
|
@classmethod
|
|
650
650
|
async def create(cls, table_name, env, page_size=100, async_delay=0.0001):
|
|
651
|
-
table_struct = await env.sql("\d "+table_name)
|
|
651
|
+
table_struct = await env.sql(r"\d "+table_name)
|
|
652
652
|
if not table_struct:
|
|
653
653
|
raise_error("NA_TABLE", name=table_name)
|
|
654
654
|
t = cls(table_struct, env, page_size, async_delay)
|
|
@@ -10,10 +10,13 @@ import logging as LOG
|
|
|
10
10
|
from enum import Enum
|
|
11
11
|
from .timer import TimerList
|
|
12
12
|
from ..class_tools import classproperty
|
|
13
|
-
from ..error import
|
|
13
|
+
from ..error import error, error_msg
|
|
14
14
|
import asyncio
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
_LIB_PATH = str(Path(__file__).parent.parent)
|
|
18
|
+
|
|
19
|
+
|
|
17
20
|
class LogLevel(Enum):
|
|
18
21
|
CRITICAL = LOG.CRITICAL
|
|
19
22
|
FATAL = LOG.FATAL
|
|
@@ -60,7 +63,7 @@ def typed_val(key, values: dict):
|
|
|
60
63
|
case 'STR)':
|
|
61
64
|
return str(v), key
|
|
62
65
|
case 'FLOAT)':
|
|
63
|
-
return float(
|
|
66
|
+
return float(v), key
|
|
64
67
|
case _:
|
|
65
68
|
return v
|
|
66
69
|
return v, key
|
|
@@ -74,6 +77,10 @@ def from_env(key:str, key_map:dict):
|
|
|
74
77
|
eval_key = "x" + "".join(eval_key)
|
|
75
78
|
else:
|
|
76
79
|
eval_key = f"x['{key}']"
|
|
80
|
+
|
|
81
|
+
if any(char in eval_key for char in ("+", "-", "*", "/", "(", ")", "%", " ")):
|
|
82
|
+
raise error("BAD_KEY", key=eval_key)
|
|
83
|
+
|
|
77
84
|
node = eval(eval_key, {"x": Config._settings}) if key else Config._settings[key]
|
|
78
85
|
node.update(ret)
|
|
79
86
|
|
|
@@ -114,11 +121,11 @@ class Config:
|
|
|
114
121
|
case ".toml":
|
|
115
122
|
return toml_load(data)
|
|
116
123
|
case _:
|
|
117
|
-
|
|
124
|
+
raise error("BAD_FORMAT", format_name=fn)
|
|
118
125
|
|
|
119
126
|
self._log_level = LogLevel.INFO
|
|
120
127
|
if path is None:
|
|
121
|
-
|
|
128
|
+
raise error("BAD_CONFIG_PATH")
|
|
122
129
|
elif isinstance(path, dict):
|
|
123
130
|
Config._settings = path
|
|
124
131
|
elif isinstance(path, str):
|
|
@@ -133,7 +140,9 @@ class Config:
|
|
|
133
140
|
if isinstance(dest, dict):
|
|
134
141
|
dest.update(item)
|
|
135
142
|
else:
|
|
136
|
-
|
|
143
|
+
Config._settings[x] = item
|
|
144
|
+
else:
|
|
145
|
+
raise error("BAD_CONFIG_TYPE")
|
|
137
146
|
|
|
138
147
|
if not Config._settings:
|
|
139
148
|
Config._settings = {}
|
|
@@ -145,6 +154,7 @@ class Config:
|
|
|
145
154
|
f = Config._settings.get(x)
|
|
146
155
|
if isinstance(f, str):
|
|
147
156
|
Config._settings[x] = load(f)
|
|
157
|
+
self.__log_timers = {}
|
|
148
158
|
if "logging" in Config._settings:
|
|
149
159
|
self._logger = ...
|
|
150
160
|
if self.logger:
|
|
@@ -262,7 +272,7 @@ class Config:
|
|
|
262
272
|
|
|
263
273
|
def __getattr__(self, item):
|
|
264
274
|
if not(item in Config._settings):
|
|
265
|
-
|
|
275
|
+
raise error("ATTR_NOT_FOUND", name=item)
|
|
266
276
|
return Config._settings[item]
|
|
267
277
|
|
|
268
278
|
@property
|
|
@@ -359,7 +369,7 @@ class Config:
|
|
|
359
369
|
|
|
360
370
|
hndl = self.logging["handler"]["class"]
|
|
361
371
|
for x in LOG.Handler.__subclasses__():
|
|
362
|
-
if x.__name__ ==
|
|
372
|
+
if x.__name__ == hndl:
|
|
363
373
|
hndl = x
|
|
364
374
|
break
|
|
365
375
|
|
|
@@ -373,7 +383,7 @@ class Config:
|
|
|
373
383
|
hndl = x
|
|
374
384
|
break
|
|
375
385
|
if isinstance(hndl, str):
|
|
376
|
-
|
|
386
|
+
raise error("BAD_LOG_HANDLER", handler=self.logging['handler'])
|
|
377
387
|
|
|
378
388
|
self._logger = logging.getLogger(self._name)
|
|
379
389
|
|
|
@@ -407,7 +417,7 @@ class Config:
|
|
|
407
417
|
if self.logging["handler"]["class"] == "TimedRotatingFileHandler":
|
|
408
418
|
x = params.get('filename')
|
|
409
419
|
if x is None:
|
|
410
|
-
|
|
420
|
+
raise error("NA_FILE_NAME")
|
|
411
421
|
Path(x).parents[0].mkdir(parents=True, exist_ok=True)
|
|
412
422
|
with open(x, 'a'):
|
|
413
423
|
...
|
|
@@ -475,32 +485,31 @@ class Config:
|
|
|
475
485
|
"extra": None,
|
|
476
486
|
"sinfo": None
|
|
477
487
|
}
|
|
478
|
-
if self._log_level.value <= LogLevel.DEBUG.value or isinstance(msg, Exception):
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
params["lno"] = info[1]
|
|
488
|
+
#if self._log_level.value <= LogLevel.DEBUG.value or isinstance(msg, Exception):
|
|
489
|
+
if level.value > LogLevel.DEBUG.value:
|
|
490
|
+
info = ("", "", "")
|
|
491
|
+
elif 'stacklevel' not in kwargs:
|
|
492
|
+
lvl = 2
|
|
493
|
+
info = log_to.findCaller(stacklevel=lvl)
|
|
494
|
+
while info[0].startswith(_LIB_PATH):
|
|
495
|
+
lvl += 1
|
|
496
|
+
x = log_to.findCaller(stacklevel=lvl)
|
|
497
|
+
if not x[0].startswith(_LIB_PATH):
|
|
498
|
+
info = x
|
|
499
|
+
break
|
|
500
|
+
else:
|
|
501
|
+
info = x
|
|
502
|
+
else:
|
|
503
|
+
info = log_to.findCaller(stacklevel=kwargs.get('stacklevel', 2))
|
|
504
|
+
|
|
505
|
+
if isinstance(msg, Exception):
|
|
506
|
+
params["fn"] = info[0]
|
|
507
|
+
else:
|
|
508
|
+
x = info[0].rsplit("/", 2)
|
|
509
|
+
del x[0]
|
|
510
|
+
params["fn"] = '/'.join(x)
|
|
511
|
+
params["func"] = info[2]
|
|
512
|
+
params["lno"] = info[1]
|
|
504
513
|
|
|
505
514
|
if "timer" in kwargs:
|
|
506
515
|
n = kwargs["timer"]
|
|
@@ -522,7 +531,7 @@ class Config:
|
|
|
522
531
|
|
|
523
532
|
def db_connection(self, connection:str="")->str:
|
|
524
533
|
if not Config.hasattr("database"):
|
|
525
|
-
|
|
534
|
+
raise error("NA_DB_IN_CONFIG")
|
|
526
535
|
db = self.database
|
|
527
536
|
if isinstance(db, str) and connection=="":
|
|
528
537
|
return db
|
|
@@ -530,5 +539,5 @@ class Config:
|
|
|
530
539
|
dflt = self.defaults if self.hasattr("defaults") else {}
|
|
531
540
|
db = db.get(connection, db.get(dflt.get("database", "main")))
|
|
532
541
|
if not db:
|
|
533
|
-
|
|
542
|
+
raise error("NA_DB", connection=connection)
|
|
534
543
|
return db
|
|
@@ -29,7 +29,7 @@ class DB_ENV(UserDict):
|
|
|
29
29
|
"""
|
|
30
30
|
Окружение для работы с БД
|
|
31
31
|
:param connection: строка соеддинения или экземпляр DB_ENV, на основе которого нужно создать данный
|
|
32
|
-
:param db_params: список переменных, которые должны передаваться в
|
|
32
|
+
:param db_params: список имен переменных, которые должны передаваться в соединение с БД
|
|
33
33
|
:param notify: список или функция, в которую будут переданы извещения о событиях
|
|
34
34
|
:param kwargs: значения переменных
|
|
35
35
|
"""
|
|
@@ -88,11 +88,11 @@ class DB_ENV(UserDict):
|
|
|
88
88
|
return ret.value if isinstance(ret, Property) else ret
|
|
89
89
|
|
|
90
90
|
def __setitem__(self, key, value):
|
|
91
|
-
x = self.get(key)
|
|
91
|
+
x = self.data.get(key)
|
|
92
92
|
if isinstance(x, Property):
|
|
93
93
|
x.value = value
|
|
94
94
|
else:
|
|
95
|
-
super.__setitem__(key, value)
|
|
95
|
+
super().__setitem__(key, value)
|
|
96
96
|
|
|
97
97
|
def has_item(self, key):
|
|
98
98
|
# возвращает True если в данном DB_ENV хранится переменная с именем key
|
|
@@ -102,7 +102,7 @@ class DB_ENV(UserDict):
|
|
|
102
102
|
if self._context is not None:
|
|
103
103
|
raise_error("ENV_CONTEXT_EXISTS")
|
|
104
104
|
if self._db_params:
|
|
105
|
-
s = self._connection_str +
|
|
105
|
+
s = self._connection_str + dumps({x:self[x] for x in self._db_params if self.has_item(x)})
|
|
106
106
|
else:
|
|
107
107
|
s = self._connection_str
|
|
108
108
|
self._context = DB(s, notify=self._notify)
|
|
@@ -140,7 +140,7 @@ class DB_ENV(UserDict):
|
|
|
140
140
|
query = await self._context.connection.parse_func(query, **kwargs)
|
|
141
141
|
elif cmd==r"\d":
|
|
142
142
|
return await self._context.connection.table_struct(query.strip())
|
|
143
|
-
elif cmd=="\json":
|
|
143
|
+
elif cmd==r"\json":
|
|
144
144
|
query, params = query.split("(", 1)
|
|
145
145
|
if params.lower() in ("json)", "jsonb)"):
|
|
146
146
|
args = (kwargs,) + args
|
|
@@ -35,6 +35,9 @@
|
|
|
35
35
|
- code: BAD_FN_PARAMS
|
|
36
36
|
class: RuntimeError
|
|
37
37
|
msg: Ошибка параметров регистрации функции {name} {defs}
|
|
38
|
+
- code: BAD_KEY
|
|
39
|
+
class: RuntimeError
|
|
40
|
+
msg: ключ не может содержать знаков операций, открывающих круглых скобок и пробелов {key}
|
|
38
41
|
### Ошибки JsonRPC
|
|
39
42
|
- code: -32700
|
|
40
43
|
msg: Ошибка разбора сообщения # "Parse error"
|
|
@@ -56,6 +59,9 @@
|
|
|
56
59
|
- code: BAD_CONFIG_PATH
|
|
57
60
|
class: AttributeError
|
|
58
61
|
msg: Не задан путь к файлу конфигурации
|
|
62
|
+
- code: BAD_CONFIG_TYPE
|
|
63
|
+
class: AttributeError
|
|
64
|
+
msg: Неизвестный формат данных конфигурации
|
|
59
65
|
- code: INFO_LOG_START
|
|
60
66
|
msg: Настройки логирования инициализированы
|
|
61
67
|
- code: ATTR_NOT_FOUND
|
|
@@ -29,19 +29,17 @@ class JSON_RPC:
|
|
|
29
29
|
if header:
|
|
30
30
|
if "id" in header:
|
|
31
31
|
code["id"] = header["id"]
|
|
32
|
-
|
|
33
|
-
header = {}
|
|
34
|
-
code["jsonrpc"] = header.get("jsonrpc", "2.0")
|
|
32
|
+
code["jsonrpc"] = header.get("jsonrpc", "2.0")
|
|
35
33
|
return code
|
|
36
34
|
|
|
37
35
|
def __init__(self, connection = None):
|
|
38
36
|
self._con = connection
|
|
39
37
|
|
|
40
|
-
async def _rpc(self, env, method,
|
|
38
|
+
async def _rpc(self, env, method, params):
|
|
41
39
|
# можно перезагрузить в наследниках, чтобы например, вызывать команды мз другого списка
|
|
42
40
|
f = Fnc.search(method)
|
|
43
41
|
if f is None:
|
|
44
|
-
return self.rpc_error(-32601
|
|
42
|
+
return self.rpc_error(-32601)
|
|
45
43
|
if isinstance(params, list):
|
|
46
44
|
params = {"LIST_OF_PARAMS": params}
|
|
47
45
|
elif not isinstance(params, dict):
|
|
@@ -59,12 +57,14 @@ class JSON_RPC:
|
|
|
59
57
|
f = message['method']
|
|
60
58
|
self.log(f"CALL:{f}")
|
|
61
59
|
try:
|
|
62
|
-
res = await self._rpc(env, f,
|
|
63
|
-
if isinstance(res,
|
|
64
|
-
|
|
60
|
+
res = await self._rpc(env, f, params)
|
|
61
|
+
if isinstance(res, dict) and len(res) == 1 and "error" in res:
|
|
62
|
+
...
|
|
63
|
+
elif isinstance(res, tuple):
|
|
64
|
+
res = {"result": list(res)}
|
|
65
65
|
elif not isinstance(res, (dict, list, str)):
|
|
66
|
-
res = str(res)
|
|
67
|
-
|
|
66
|
+
res = {"result": str(res)}
|
|
67
|
+
else:
|
|
68
68
|
res = {"result": res}
|
|
69
69
|
res["jsonrpc"]=message.get("jsonrpc", "2.0")
|
|
70
70
|
if res["jsonrpc"]=="1.0":
|
|
@@ -250,7 +250,7 @@ def register_list(defs:list | dict, self=None)->None:
|
|
|
250
250
|
else:
|
|
251
251
|
match fnc.get("type", "db"):
|
|
252
252
|
case "db":
|
|
253
|
-
SqlFnc(x, f"\JSON {fnc['function']}(JSONB)", help=fnc.get('help'))
|
|
253
|
+
SqlFnc(x, f"{r'\JSON'} {fnc['function']}(JSONB)", help=fnc.get('help'))
|
|
254
254
|
case "self":
|
|
255
255
|
fn = getattr(self, fnc['function'])
|
|
256
256
|
PythonFnc(x, fn, help = fnc.get('help') )
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ab_engine
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Library of standard modules
|
|
5
5
|
Author-email: Andrew Bulatov <BulatovAndrew@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -9,7 +9,7 @@ Requires-Python: >=3.10
|
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
Requires-Dist: json5>=0.12.0
|
|
11
11
|
Requires-Dist: psycopg>=3.2.9
|
|
12
|
-
Requires-Dist:
|
|
12
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
13
13
|
Requires-Dist: toml>=0.10.2
|
|
14
14
|
|
|
15
15
|
Библиотека стандартных модулей
|
|
@@ -167,17 +167,17 @@ create table config(
|
|
|
167
167
|
|
|
168
168
|
нресколько имен ключей файла конфигурации зарезервированы и являются специальными:
|
|
169
169
|
|
|
170
|
-
**timers** - определяет интервалы для таймеров системы
|
|
171
|
-
**logging** - позволяет задать настройки логирования
|
|
172
|
-
**database**- содержит строки соединения с одной или несколькими БД
|
|
173
|
-
**defaults**- содержит значения по умолчанию
|
|
170
|
+
* **timers** - определяет интервалы для таймеров системы
|
|
171
|
+
* **logging** - позволяет задать настройки логирования
|
|
172
|
+
* **database**- содержит строки соединения с одной или несколькими БД
|
|
173
|
+
* **defaults**- содержит значения по умолчанию
|
|
174
174
|
|
|
175
175
|
#### Значения по умолчанию
|
|
176
176
|
|
|
177
|
-
**defaults.name** - имя, подставляемое в лог как имя сервиса
|
|
178
|
-
**defaults.database** - имя соединения БД по умолчанию
|
|
179
|
-
**defaults.plugin_path** - путь к каталогу плагинов
|
|
180
|
-
**defaults.db_driver_path** - путь к драйверам БД (по умолчанию ab_engine/db)
|
|
177
|
+
* **defaults.name** - имя, подставляемое в лог как имя сервиса
|
|
178
|
+
* **defaults.database** - имя соединения БД по умолчанию
|
|
179
|
+
* **defaults.plugin_path** - путь к каталогу плагинов
|
|
180
|
+
* **defaults.db_driver_path** - путь к драйверам БД (по умолчанию ab_engine/db)
|
|
181
181
|
|
|
182
182
|
## Управление таймерами
|
|
183
183
|
|
|
@@ -209,7 +209,7 @@ callbac должен быть функцией вида:
|
|
|
209
209
|
>
|
|
210
210
|
> ...
|
|
211
211
|
|
|
212
|
-
в параметр **name** передается
|
|
212
|
+
в параметр **name** передается имя таймера, в **prev_time** предыдущее время запуска, первый раз **prev_time = None**
|
|
213
213
|
|
|
214
214
|
Предусмотрены следующие типы интервалов работы таймера:
|
|
215
215
|
|
|
@@ -219,7 +219,7 @@ callbac должен быть функцией вида:
|
|
|
219
219
|
* TimerInterval.HOUR
|
|
220
220
|
* TimerInterval.DAY
|
|
221
221
|
|
|
222
|
-
Для управления таймерами, также,
|
|
222
|
+
Для управления таймерами, также, используются методы:
|
|
223
223
|
|
|
224
224
|
* **start**(immediately_start=True) - запускает таймер, если он остановлен. Если immediately_start=False, то таймер ждет свой
|
|
225
225
|
интервал и только потом запускает callback, иначе callback будет запущен сразу после вызова start()
|
|
@@ -277,7 +277,7 @@ if __name__ == '__main__':
|
|
|
277
277
|
- code: ERROR # код для вызова из функции raise_error
|
|
278
278
|
http: 444 # код для возврата для HTTP. можно не указывать
|
|
279
279
|
msg: В {place} у объекта {name} случилось {what} # Сообщение в которое будут подставлены kwargs из raise_error
|
|
280
|
-
class: Error #
|
|
280
|
+
class: Error # задаёт класс исключения. По умолчанию это Error из библиотеки (т.е. Error можно было не указывать)
|
|
281
281
|
- code: OTHER_ERROR
|
|
282
282
|
class: Exception
|
|
283
283
|
msg: Еще ошибка
|
|
@@ -373,16 +373,44 @@ logging:
|
|
|
373
373
|
## Доступ к БД
|
|
374
374
|
|
|
375
375
|
Библиотека поддерживает хранение настроек соединений и выполнение команд SQL на этих соединениях с БД.
|
|
376
|
+
|
|
376
377
|
Из коробки реализованы драйвер Postgresql, Sqlite и Valkey (Redis). Также реализован драйвер Mysql но для его работы дополнительно надо установить зависимость mysql-connector-python.
|
|
377
378
|
|
|
378
379
|
Можно описывать драйвера к другим БД. Для того чтобы использовать собственный драйвер, можно добавить класс драйвера
|
|
379
380
|
в словарь DRIVER_CLASSES, который находится в модуле ab_engine/db/option, либо положить драйвер в каталог драйверов.
|
|
381
|
+
|
|
380
382
|
Пример драйвера в DRIVER_CLASSES:
|
|
383
|
+
```python
|
|
384
|
+
from ab_engine.db import sql, ONE, DB
|
|
385
|
+
from ab_engine.db.option import DRIVER_CLASSES
|
|
386
|
+
from ab_engine.db.driver_postgresql import Driver
|
|
387
|
+
import asyncio
|
|
388
|
+
|
|
389
|
+
class MyPgDriver(Driver):
|
|
390
|
+
|
|
391
|
+
async def begin(self):
|
|
392
|
+
await super().begin()
|
|
393
|
+
print("BEGIN TRANSACTION", flush=True)
|
|
394
|
+
|
|
395
|
+
async def rollback(self):
|
|
396
|
+
await super().rollback()
|
|
397
|
+
print("END TRANSACTION", flush=True)
|
|
398
|
+
|
|
399
|
+
DRIVER_CLASSES["my_pg"] = MyPgDriver
|
|
400
|
+
|
|
401
|
+
async def main():
|
|
402
|
+
x = await sql("select version();", ONE, DB("my_pg://postgres:postgres@localhost:5432/postgres"))
|
|
403
|
+
print(x)
|
|
404
|
+
await DB.garbage_collect()
|
|
405
|
+
|
|
406
|
+
if __name__ == '__main__':
|
|
407
|
+
asyncio.run(main())
|
|
408
|
+
```
|
|
381
409
|
|
|
382
|
-
необходимости
|
|
410
|
+
При необходимости задать путь поиска с собственным драйвером, можно в строке соединения, в
|
|
383
411
|
фигурных скобках задать параметр **driver_path**, определяющий путь для поиска драйвера.
|
|
384
412
|
|
|
385
|
-
Если нужно задать свой путь поиска для всех драйверов БД, можно задать параметр db_driver_path в defaults
|
|
413
|
+
Если нужно задать свой путь поиска для всех драйверов БД, можно задать параметр **db_driver_path** в секции конфигурации **defaults**.
|
|
386
414
|
|
|
387
415
|
Если приложению требуется только одна строка соединения, то ее можно задать в переменной database, например:
|
|
388
416
|
|
|
@@ -390,18 +418,18 @@ logging:
|
|
|
390
418
|
database: postgresql://localhost:5432/postgres?user=postgres&password=postgres
|
|
391
419
|
```
|
|
392
420
|
|
|
393
|
-
|
|
421
|
+
Чтобы задать несколько соединений, нужно использовать database как секцию:
|
|
394
422
|
|
|
395
423
|
```toml
|
|
396
424
|
[database]
|
|
397
425
|
test = "postgresql://localhost:5432/test?user=postgres&password=postgres"
|
|
398
|
-
mydb = "postgresql://localhost:5432/mydb
|
|
426
|
+
mydb = "postgresql://postgres:postgres@localhost:5432/mydb"
|
|
399
427
|
|
|
400
428
|
[defaults]
|
|
401
429
|
database = "test"
|
|
402
430
|
```
|
|
403
431
|
|
|
404
|
-
При этом, в секции defaults можно задать имя соединения БД, которое будет использоваться по
|
|
432
|
+
При этом, в секции **defaults** можно задать имя соединения БД, которое будет использоваться по умолчанию. Для этого следует задать значение **database** в секции **defaults**.
|
|
405
433
|
|
|
406
434
|
Строка соединения с БД имеет следующую структуру:
|
|
407
435
|
<имя_драйвера>**://**<адрес сервера>**:**<номер порта>**/**<имя БД>**?**<параметры соединения>**{**дополнительные параметры**}**
|
|
@@ -418,10 +446,15 @@ database = "test"
|
|
|
418
446
|
который ограничит максимальное количество таких соединений. Т.е. по достижении данного числа соединений система
|
|
419
447
|
не станет выполнять очередную команду до тех пор, пока не освободится одно из текущих соединений с БД.
|
|
420
448
|
|
|
421
|
-
Для некоторых
|
|
449
|
+
Для некоторых драйверов, например, для встроенного драйвера postgresql строка сооединения с БД
|
|
422
450
|
может иметь альтернативную структуру:
|
|
423
451
|
|
|
424
|
-
<имя_драйвера>**://**<имя
|
|
452
|
+
<имя_драйвера>**://**<имя пользователя>**:**<пароль>**@**<адрес сервера>**:**<номер порта>**/**<имя БД>**?**<параметры соединения>**{**дополнительные параметры**}**
|
|
453
|
+
|
|
454
|
+
Если нужно установить **peer** соединение c БД postgresql, то в строке соединения достаточно передать имя БД и доп. параметры (если нужно). Например:
|
|
455
|
+
```python
|
|
456
|
+
x = await sql("select version()", ONE, DB("postgresql://postgres?application_name=test"))
|
|
457
|
+
```
|
|
425
458
|
|
|
426
459
|
Драйвер БД должен иметь имя driver_<имя в строке соединения>.py и предоставлять следующий контракт:
|
|
427
460
|
|
|
@@ -584,6 +617,7 @@ inner join t2 on t2.t_id=t.id and t2.name=$name
|
|
|
584
617
|
where t.date < now()::date;
|
|
585
618
|
```
|
|
586
619
|
система автоматически создает подстроку со значением параметра и, при необходимости, заключает это значение в кавычки.
|
|
620
|
+
|
|
587
621
|
|**Тип python**|**Тип БД**|**В кавычках**|
|
|
588
622
|
|--------|---------|-----|
|
|
589
623
|
| None | NULL | [ ] |
|
|
@@ -653,6 +687,7 @@ async def sql(query:str, *args, **kwargs):
|
|
|
653
687
|
|
|
654
688
|
Опции, это специальные параметры, которые влияют на выполнение запроса в целом.
|
|
655
689
|
Передавать опции можно только в конце списка args, после позиционных параметров, либо вместо них.
|
|
690
|
+
|
|
656
691
|
В библиотеке реализованы следующие опции:
|
|
657
692
|
* DB - служит для передачи соединения в функцию sql. Передается как DB("строка соединения").
|
|
658
693
|
* NOTICE - то же, что DB, но запросы с такой опцией возвращают как результат список сообщений нотификации
|
|
@@ -1220,7 +1255,7 @@ if __name__ == '__main__':
|
|
|
1220
1255
|
|
|
1221
1256
|
Методы RPC регистрируются функцией register_rpc_list либо c помощью декоратора register_rpc, который также можно использовать как функцию.
|
|
1222
1257
|
|
|
1223
|
-
Регистрация списка функций осуществляется с помощью register_rpc_list, которая принимает на вход
|
|
1258
|
+
Регистрация списка функций осуществляется с помощью register_rpc_list, которая принимает на вход параметры defs и self.
|
|
1224
1259
|
* **defs** - содержит список или словарь с описаниями
|
|
1225
1260
|
* **self** - необязательный параметр, в котором передается модуль или объект из которого будут регистрироваться функции. если в self ничего не передано, то будут регистрироваться функции из модуля, в котором вызвана register_rpc_list.
|
|
1226
1261
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ab_engine"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.2"
|
|
4
4
|
description = "Library of standard modules"
|
|
5
5
|
authors = [{name = "Andrew Bulatov", email = "BulatovAndrew@gmail.com"}]
|
|
6
6
|
license = "MIT"
|
|
@@ -9,7 +9,7 @@ requires-python = ">=3.10"
|
|
|
9
9
|
dependencies = [
|
|
10
10
|
"json5>=0.12.0",
|
|
11
11
|
"psycopg>=3.2.9",
|
|
12
|
-
"
|
|
12
|
+
"pyyaml>=6.0.3",
|
|
13
13
|
"toml>=0.10.2",
|
|
14
14
|
]
|
|
15
15
|
[project.urls]
|
|
@@ -153,17 +153,17 @@ create table config(
|
|
|
153
153
|
|
|
154
154
|
нресколько имен ключей файла конфигурации зарезервированы и являются специальными:
|
|
155
155
|
|
|
156
|
-
**timers** - определяет интервалы для таймеров системы
|
|
157
|
-
**logging** - позволяет задать настройки логирования
|
|
158
|
-
**database**- содержит строки соединения с одной или несколькими БД
|
|
159
|
-
**defaults**- содержит значения по умолчанию
|
|
156
|
+
* **timers** - определяет интервалы для таймеров системы
|
|
157
|
+
* **logging** - позволяет задать настройки логирования
|
|
158
|
+
* **database**- содержит строки соединения с одной или несколькими БД
|
|
159
|
+
* **defaults**- содержит значения по умолчанию
|
|
160
160
|
|
|
161
161
|
#### Значения по умолчанию
|
|
162
162
|
|
|
163
|
-
**defaults.name** - имя, подставляемое в лог как имя сервиса
|
|
164
|
-
**defaults.database** - имя соединения БД по умолчанию
|
|
165
|
-
**defaults.plugin_path** - путь к каталогу плагинов
|
|
166
|
-
**defaults.db_driver_path** - путь к драйверам БД (по умолчанию ab_engine/db)
|
|
163
|
+
* **defaults.name** - имя, подставляемое в лог как имя сервиса
|
|
164
|
+
* **defaults.database** - имя соединения БД по умолчанию
|
|
165
|
+
* **defaults.plugin_path** - путь к каталогу плагинов
|
|
166
|
+
* **defaults.db_driver_path** - путь к драйверам БД (по умолчанию ab_engine/db)
|
|
167
167
|
|
|
168
168
|
## Управление таймерами
|
|
169
169
|
|
|
@@ -195,7 +195,7 @@ callbac должен быть функцией вида:
|
|
|
195
195
|
>
|
|
196
196
|
> ...
|
|
197
197
|
|
|
198
|
-
в параметр **name** передается
|
|
198
|
+
в параметр **name** передается имя таймера, в **prev_time** предыдущее время запуска, первый раз **prev_time = None**
|
|
199
199
|
|
|
200
200
|
Предусмотрены следующие типы интервалов работы таймера:
|
|
201
201
|
|
|
@@ -205,7 +205,7 @@ callbac должен быть функцией вида:
|
|
|
205
205
|
* TimerInterval.HOUR
|
|
206
206
|
* TimerInterval.DAY
|
|
207
207
|
|
|
208
|
-
Для управления таймерами, также,
|
|
208
|
+
Для управления таймерами, также, используются методы:
|
|
209
209
|
|
|
210
210
|
* **start**(immediately_start=True) - запускает таймер, если он остановлен. Если immediately_start=False, то таймер ждет свой
|
|
211
211
|
интервал и только потом запускает callback, иначе callback будет запущен сразу после вызова start()
|
|
@@ -263,7 +263,7 @@ if __name__ == '__main__':
|
|
|
263
263
|
- code: ERROR # код для вызова из функции raise_error
|
|
264
264
|
http: 444 # код для возврата для HTTP. можно не указывать
|
|
265
265
|
msg: В {place} у объекта {name} случилось {what} # Сообщение в которое будут подставлены kwargs из raise_error
|
|
266
|
-
class: Error #
|
|
266
|
+
class: Error # задаёт класс исключения. По умолчанию это Error из библиотеки (т.е. Error можно было не указывать)
|
|
267
267
|
- code: OTHER_ERROR
|
|
268
268
|
class: Exception
|
|
269
269
|
msg: Еще ошибка
|
|
@@ -359,16 +359,44 @@ logging:
|
|
|
359
359
|
## Доступ к БД
|
|
360
360
|
|
|
361
361
|
Библиотека поддерживает хранение настроек соединений и выполнение команд SQL на этих соединениях с БД.
|
|
362
|
+
|
|
362
363
|
Из коробки реализованы драйвер Postgresql, Sqlite и Valkey (Redis). Также реализован драйвер Mysql но для его работы дополнительно надо установить зависимость mysql-connector-python.
|
|
363
364
|
|
|
364
365
|
Можно описывать драйвера к другим БД. Для того чтобы использовать собственный драйвер, можно добавить класс драйвера
|
|
365
366
|
в словарь DRIVER_CLASSES, который находится в модуле ab_engine/db/option, либо положить драйвер в каталог драйверов.
|
|
367
|
+
|
|
366
368
|
Пример драйвера в DRIVER_CLASSES:
|
|
369
|
+
```python
|
|
370
|
+
from ab_engine.db import sql, ONE, DB
|
|
371
|
+
from ab_engine.db.option import DRIVER_CLASSES
|
|
372
|
+
from ab_engine.db.driver_postgresql import Driver
|
|
373
|
+
import asyncio
|
|
374
|
+
|
|
375
|
+
class MyPgDriver(Driver):
|
|
376
|
+
|
|
377
|
+
async def begin(self):
|
|
378
|
+
await super().begin()
|
|
379
|
+
print("BEGIN TRANSACTION", flush=True)
|
|
380
|
+
|
|
381
|
+
async def rollback(self):
|
|
382
|
+
await super().rollback()
|
|
383
|
+
print("END TRANSACTION", flush=True)
|
|
384
|
+
|
|
385
|
+
DRIVER_CLASSES["my_pg"] = MyPgDriver
|
|
386
|
+
|
|
387
|
+
async def main():
|
|
388
|
+
x = await sql("select version();", ONE, DB("my_pg://postgres:postgres@localhost:5432/postgres"))
|
|
389
|
+
print(x)
|
|
390
|
+
await DB.garbage_collect()
|
|
391
|
+
|
|
392
|
+
if __name__ == '__main__':
|
|
393
|
+
asyncio.run(main())
|
|
394
|
+
```
|
|
367
395
|
|
|
368
|
-
необходимости
|
|
396
|
+
При необходимости задать путь поиска с собственным драйвером, можно в строке соединения, в
|
|
369
397
|
фигурных скобках задать параметр **driver_path**, определяющий путь для поиска драйвера.
|
|
370
398
|
|
|
371
|
-
Если нужно задать свой путь поиска для всех драйверов БД, можно задать параметр db_driver_path в defaults
|
|
399
|
+
Если нужно задать свой путь поиска для всех драйверов БД, можно задать параметр **db_driver_path** в секции конфигурации **defaults**.
|
|
372
400
|
|
|
373
401
|
Если приложению требуется только одна строка соединения, то ее можно задать в переменной database, например:
|
|
374
402
|
|
|
@@ -376,18 +404,18 @@ logging:
|
|
|
376
404
|
database: postgresql://localhost:5432/postgres?user=postgres&password=postgres
|
|
377
405
|
```
|
|
378
406
|
|
|
379
|
-
|
|
407
|
+
Чтобы задать несколько соединений, нужно использовать database как секцию:
|
|
380
408
|
|
|
381
409
|
```toml
|
|
382
410
|
[database]
|
|
383
411
|
test = "postgresql://localhost:5432/test?user=postgres&password=postgres"
|
|
384
|
-
mydb = "postgresql://localhost:5432/mydb
|
|
412
|
+
mydb = "postgresql://postgres:postgres@localhost:5432/mydb"
|
|
385
413
|
|
|
386
414
|
[defaults]
|
|
387
415
|
database = "test"
|
|
388
416
|
```
|
|
389
417
|
|
|
390
|
-
При этом, в секции defaults можно задать имя соединения БД, которое будет использоваться по
|
|
418
|
+
При этом, в секции **defaults** можно задать имя соединения БД, которое будет использоваться по умолчанию. Для этого следует задать значение **database** в секции **defaults**.
|
|
391
419
|
|
|
392
420
|
Строка соединения с БД имеет следующую структуру:
|
|
393
421
|
<имя_драйвера>**://**<адрес сервера>**:**<номер порта>**/**<имя БД>**?**<параметры соединения>**{**дополнительные параметры**}**
|
|
@@ -404,10 +432,15 @@ database = "test"
|
|
|
404
432
|
который ограничит максимальное количество таких соединений. Т.е. по достижении данного числа соединений система
|
|
405
433
|
не станет выполнять очередную команду до тех пор, пока не освободится одно из текущих соединений с БД.
|
|
406
434
|
|
|
407
|
-
Для некоторых
|
|
435
|
+
Для некоторых драйверов, например, для встроенного драйвера postgresql строка сооединения с БД
|
|
408
436
|
может иметь альтернативную структуру:
|
|
409
437
|
|
|
410
|
-
<имя_драйвера>**://**<имя
|
|
438
|
+
<имя_драйвера>**://**<имя пользователя>**:**<пароль>**@**<адрес сервера>**:**<номер порта>**/**<имя БД>**?**<параметры соединения>**{**дополнительные параметры**}**
|
|
439
|
+
|
|
440
|
+
Если нужно установить **peer** соединение c БД postgresql, то в строке соединения достаточно передать имя БД и доп. параметры (если нужно). Например:
|
|
441
|
+
```python
|
|
442
|
+
x = await sql("select version()", ONE, DB("postgresql://postgres?application_name=test"))
|
|
443
|
+
```
|
|
411
444
|
|
|
412
445
|
Драйвер БД должен иметь имя driver_<имя в строке соединения>.py и предоставлять следующий контракт:
|
|
413
446
|
|
|
@@ -570,6 +603,7 @@ inner join t2 on t2.t_id=t.id and t2.name=$name
|
|
|
570
603
|
where t.date < now()::date;
|
|
571
604
|
```
|
|
572
605
|
система автоматически создает подстроку со значением параметра и, при необходимости, заключает это значение в кавычки.
|
|
606
|
+
|
|
573
607
|
|**Тип python**|**Тип БД**|**В кавычках**|
|
|
574
608
|
|--------|---------|-----|
|
|
575
609
|
| None | NULL | [ ] |
|
|
@@ -639,6 +673,7 @@ async def sql(query:str, *args, **kwargs):
|
|
|
639
673
|
|
|
640
674
|
Опции, это специальные параметры, которые влияют на выполнение запроса в целом.
|
|
641
675
|
Передавать опции можно только в конце списка args, после позиционных параметров, либо вместо них.
|
|
676
|
+
|
|
642
677
|
В библиотеке реализованы следующие опции:
|
|
643
678
|
* DB - служит для передачи соединения в функцию sql. Передается как DB("строка соединения").
|
|
644
679
|
* NOTICE - то же, что DB, но запросы с такой опцией возвращают как результат список сообщений нотификации
|
|
@@ -1206,7 +1241,7 @@ if __name__ == '__main__':
|
|
|
1206
1241
|
|
|
1207
1242
|
Методы RPC регистрируются функцией register_rpc_list либо c помощью декоратора register_rpc, который также можно использовать как функцию.
|
|
1208
1243
|
|
|
1209
|
-
Регистрация списка функций осуществляется с помощью register_rpc_list, которая принимает на вход
|
|
1244
|
+
Регистрация списка функций осуществляется с помощью register_rpc_list, которая принимает на вход параметры defs и self.
|
|
1210
1245
|
* **defs** - содержит список или словарь с описаниями
|
|
1211
1246
|
* **self** - необязательный параметр, в котором передается модуль или объект из которого будут регистрироваться функции. если в self ничего не передано, то будут регистрироваться функции из модуля, в котором вызвана register_rpc_list.
|
|
1212
1247
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|