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.
Files changed (30) hide show
  1. {ab_engine-0.2.0 → ab_engine-0.2.2}/PKG-INFO +56 -21
  2. {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/db/driver.py +2 -2
  3. {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/db/driver_mysql.py +9 -1
  4. {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/db/driver_postgresql.py +3 -1
  5. {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/db/driver_sqlite.py +6 -2
  6. {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/db/option.py +6 -2
  7. {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/db/processor.py +2 -1
  8. {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/db/table.py +2 -2
  9. {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/env/config.py +46 -37
  10. {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/env/db_context.py +5 -5
  11. {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/error.yaml +6 -0
  12. {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/rpc/json_rpc.py +10 -10
  13. {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/rpc/rpc.py +1 -1
  14. {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine.egg-info/PKG-INFO +56 -21
  15. {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine.egg-info/requires.txt +1 -1
  16. {ab_engine-0.2.0 → ab_engine-0.2.2}/pyproject.toml +2 -2
  17. {ab_engine-0.2.0 → ab_engine-0.2.2}/readme.md +54 -19
  18. {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/__init__.py +0 -0
  19. {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/class_tools.py +0 -0
  20. {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/db/__init__.py +0 -0
  21. {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/db/driver_valkey.py +0 -0
  22. {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/env/__init__.py +0 -0
  23. {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/env/timer.py +0 -0
  24. {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/error.py +0 -0
  25. {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/rpc/__init__.py +0 -0
  26. {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine/rpc/fnc.py +0 -0
  27. {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine.egg-info/SOURCES.txt +0 -0
  28. {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine.egg-info/dependency_links.txt +0 -0
  29. {ab_engine-0.2.0 → ab_engine-0.2.2}/ab_engine.egg-info/top_level.txt +0 -0
  30. {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.0
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: pyaml>=25.7.0
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** передается в имя таймера, в **prev_time** предыдущее время запуска, первый раз **prev_time = None**
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 # задает класс исключения. По умолчанию это Error из библиотеки (т.е. 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
- Также можно задать несколько соединений, если использовать database как секцию:
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?user=postgres&password=postgres"
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
- Для некоторых драйверо, например, для встроенного драйвера posstgresql строка сооединения с БД
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, которая принимает на вход парамектры defs и self.
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"SET {x} = '{params[x]}'")
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)[1][:-1].strip().split("\n")
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[row["name"]]
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].split("&")
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 raise_error, error_msg
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(t), key
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
- raise_error("BAD_FORMAT", format_name=fn)
124
+ raise error("BAD_FORMAT", format_name=fn)
118
125
 
119
126
  self._log_level = LogLevel.INFO
120
127
  if path is None:
121
- raise_error("BAD_CONFIG_PATH")
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
- data[x] = item
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
- raise_error("ATTR_NOT_FOUND", name=item)
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__ == self.logging["handler"]:
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
- raise_error("BAD_LOG_HANDLER", handler=self.logging['handler'])
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
- raise_error("NA_FILE_NAME")
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
- if 'stacklevel' not in kwargs:
480
- lvl = 2
481
- info = log_to.findCaller(stacklevel=lvl)
482
- p = str(Path(__file__).parent.parent)
483
- while info[0].startswith(p):
484
- lvl += 1
485
- x = log_to.findCaller(stacklevel=lvl)
486
- if '/venv/' in x[0]:
487
- break
488
- elif not x[0].startswith(p):
489
- info = x
490
- break
491
- else:
492
- info = x
493
- else:
494
- info = log_to.findCaller(stacklevel=kwargs.get('stacklevel', 2))
495
-
496
- if isinstance(msg, Exception):
497
- params["fn"] = info[0]
498
- else:
499
- x = info[0].rsplit("/", 2)
500
- del x[0]
501
- params["fn"] = '/'.join(x)
502
- params["func"] = info[2]
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
- raise_error("NA_DB_IN_CONFIG")
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
- raise_error("NA_DB", connection=connection)
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 + f"{{{dumps({x:self[x] for x in self._db_params if self.has_item(x)})}}}"
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
- else:
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, header, params):
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, header=header)
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, message, params)
63
- if isinstance(res, tuple):
64
- res = list(res)
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
- elif not (isinstance(res, dict) and len(res) == 1 and "error" in res):
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.0
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: pyaml>=25.7.0
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** передается в имя таймера, в **prev_time** предыдущее время запуска, первый раз **prev_time = None**
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 # задает класс исключения. По умолчанию это Error из библиотеки (т.е. 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
- Также можно задать несколько соединений, если использовать database как секцию:
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?user=postgres&password=postgres"
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
- Для некоторых драйверо, например, для встроенного драйвера posstgresql строка сооединения с БД
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, которая принимает на вход парамектры defs и self.
1258
+ Регистрация списка функций осуществляется с помощью register_rpc_list, которая принимает на вход параметры defs и self.
1224
1259
  * **defs** - содержит список или словарь с описаниями
1225
1260
  * **self** - необязательный параметр, в котором передается модуль или объект из которого будут регистрироваться функции. если в self ничего не передано, то будут регистрироваться функции из модуля, в котором вызвана register_rpc_list.
1226
1261
 
@@ -1,4 +1,4 @@
1
1
  json5>=0.12.0
2
2
  psycopg>=3.2.9
3
- pyaml>=25.7.0
3
+ pyyaml>=6.0.3
4
4
  toml>=0.10.2
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ab_engine"
3
- version = "0.2.0"
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
- "pyaml>=25.7.0",
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** передается в имя таймера, в **prev_time** предыдущее время запуска, первый раз **prev_time = None**
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 # задает класс исключения. По умолчанию это Error из библиотеки (т.е. 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
- Также можно задать несколько соединений, если использовать database как секцию:
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?user=postgres&password=postgres"
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
- Для некоторых драйверо, например, для встроенного драйвера posstgresql строка сооединения с БД
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, которая принимает на вход парамектры defs и self.
1244
+ Регистрация списка функций осуществляется с помощью register_rpc_list, которая принимает на вход параметры defs и self.
1210
1245
  * **defs** - содержит список или словарь с описаниями
1211
1246
  * **self** - необязательный параметр, в котором передается модуль или объект из которого будут регистрироваться функции. если в self ничего не передано, то будут регистрироваться функции из модуля, в котором вызвана register_rpc_list.
1212
1247
 
File without changes
File without changes