ab_engine 0.2.3__tar.gz → 0.2.5__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.3 → ab_engine-0.2.5}/PKG-INFO +1 -2
  2. {ab_engine-0.2.3 → ab_engine-0.2.5}/ab_engine/class_tools.py +7 -9
  3. {ab_engine-0.2.3 → ab_engine-0.2.5}/ab_engine/db/driver.py +19 -5
  4. {ab_engine-0.2.3 → ab_engine-0.2.5}/ab_engine/db/option.py +8 -14
  5. {ab_engine-0.2.3 → ab_engine-0.2.5}/ab_engine/db/table.py +25 -20
  6. {ab_engine-0.2.3 → ab_engine-0.2.5}/ab_engine/env/config.py +12 -3
  7. {ab_engine-0.2.3 → ab_engine-0.2.5}/ab_engine/env/db_context.py +1 -1
  8. {ab_engine-0.2.3 → ab_engine-0.2.5}/ab_engine/rpc/json_rpc.py +1 -0
  9. {ab_engine-0.2.3 → ab_engine-0.2.5}/ab_engine.egg-info/PKG-INFO +1 -2
  10. {ab_engine-0.2.3 → ab_engine-0.2.5}/ab_engine.egg-info/requires.txt +0 -1
  11. {ab_engine-0.2.3 → ab_engine-0.2.5}/pyproject.toml +2 -3
  12. {ab_engine-0.2.3 → ab_engine-0.2.5}/ab_engine/__init__.py +0 -0
  13. {ab_engine-0.2.3 → ab_engine-0.2.5}/ab_engine/db/__init__.py +0 -0
  14. {ab_engine-0.2.3 → ab_engine-0.2.5}/ab_engine/db/driver_mysql.py +0 -0
  15. {ab_engine-0.2.3 → ab_engine-0.2.5}/ab_engine/db/driver_postgresql.py +0 -0
  16. {ab_engine-0.2.3 → ab_engine-0.2.5}/ab_engine/db/driver_sqlite.py +0 -0
  17. {ab_engine-0.2.3 → ab_engine-0.2.5}/ab_engine/db/driver_valkey.py +0 -0
  18. {ab_engine-0.2.3 → ab_engine-0.2.5}/ab_engine/db/processor.py +0 -0
  19. {ab_engine-0.2.3 → ab_engine-0.2.5}/ab_engine/env/__init__.py +0 -0
  20. {ab_engine-0.2.3 → ab_engine-0.2.5}/ab_engine/env/timer.py +0 -0
  21. {ab_engine-0.2.3 → ab_engine-0.2.5}/ab_engine/error.py +0 -0
  22. {ab_engine-0.2.3 → ab_engine-0.2.5}/ab_engine/error.yaml +0 -0
  23. {ab_engine-0.2.3 → ab_engine-0.2.5}/ab_engine/rpc/__init__.py +0 -0
  24. {ab_engine-0.2.3 → ab_engine-0.2.5}/ab_engine/rpc/fnc.py +0 -0
  25. {ab_engine-0.2.3 → ab_engine-0.2.5}/ab_engine/rpc/rpc.py +0 -0
  26. {ab_engine-0.2.3 → ab_engine-0.2.5}/ab_engine.egg-info/SOURCES.txt +0 -0
  27. {ab_engine-0.2.3 → ab_engine-0.2.5}/ab_engine.egg-info/dependency_links.txt +0 -0
  28. {ab_engine-0.2.3 → ab_engine-0.2.5}/ab_engine.egg-info/top_level.txt +0 -0
  29. {ab_engine-0.2.3 → ab_engine-0.2.5}/readme.md +0 -0
  30. {ab_engine-0.2.3 → ab_engine-0.2.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ab_engine
3
- Version: 0.2.3
3
+ Version: 0.2.5
4
4
  Summary: Library of standard modules
5
5
  Author-email: Andrew Bulatov <BulatovAndrew@gmail.com>
6
6
  License-Expression: MIT
@@ -10,7 +10,6 @@ Description-Content-Type: text/markdown
10
10
  Requires-Dist: json5>=0.12.0
11
11
  Requires-Dist: psycopg>=3.2.9
12
12
  Requires-Dist: pyyaml>=6.0.3
13
- Requires-Dist: toml>=0.10.2
14
13
 
15
14
  Библиотека стандартных модулей
16
15
  ==========================================================
@@ -41,19 +41,17 @@ class ReadOnlyPropDict:
41
41
 
42
42
  def __init__(self, **kwargs):
43
43
  self._data = kwargs
44
- for x in kwargs:
45
- self._set_f(x)
46
-
47
- def _set_f(self, x):
48
- setattr(self.__class__, x, property(lambda p: self._data[x]))
49
44
 
50
45
  def __getitem__(self, item):
51
46
  return self._data[item]
52
47
 
48
+ def __getattr__(self, item):
49
+ return self._data[item]
50
+
53
51
 
54
52
  class PropDict(ReadOnlyPropDict):
55
53
 
56
- def _set_f(self, x):
57
- def set_f(p, v):
58
- self._data[x] = v
59
- setattr(self.__class__, x, property(lambda p: self._data[x], set_f))
54
+ def __setattr__(self, name, value):
55
+ if name not in self._data:
56
+ raise KeyError(name)
57
+ self._data[name] = value
@@ -1,11 +1,12 @@
1
1
  from enum import Enum
2
2
  from json import dumps
3
- from datetime import datetime, time
4
3
  from abc import ABC, abstractmethod
5
4
  from collections import namedtuple
6
5
  from typing import Optional
7
6
  from urllib.parse import unquote
8
7
  from inspect import iscoroutinefunction
8
+ from uuid import UUID
9
+ import datetime, uuid
9
10
 
10
11
  _is_option = None
11
12
 
@@ -32,6 +33,17 @@ def reencode(s):
32
33
  return s
33
34
 
34
35
 
36
+ def in_arr_str(s):
37
+ if not isinstance(s, str):
38
+ return str(s)
39
+ s = s.replace('"','\\"')
40
+ as_is = True
41
+ for x in s:
42
+ if x in '{} ,"\\;':
43
+ as_is = False
44
+ break
45
+ return s if as_is else f'"{s}"'
46
+
35
47
  class Driver(ABC):
36
48
 
37
49
  LIKE = "LIKE" # что использовать как like, например для postgresql это ILIKE
@@ -142,6 +154,8 @@ class Driver(ABC):
142
154
  def var_to_sql(var, str_after=" "):
143
155
  if var is None:
144
156
  return " NULL"
157
+ elif isinstance(var, (int, float, bool)):
158
+ return f" {var}"
145
159
  elif isinstance(var, str):
146
160
  var = var.replace("'", "''")
147
161
  return reencode(var)
@@ -150,15 +164,15 @@ class Driver(ABC):
150
164
  var = dumps(var, ensure_ascii=False).replace("'", "''")
151
165
  return reencode(var)
152
166
  else:
153
- var = ','.join(['NULL' if x is None else str(x) for x in var])
154
- return reencode(f"{{{var}}}")
167
+ var = ','.join(['NULL' if x is None else in_arr_str(x) for x in var])
168
+ return reencode(f"{{{var.replace("'", "''")}}}")
155
169
  elif isinstance(var, set):
156
- var = ','.join('NULL' if x is None else str(x) for x in var)
170
+ var = ','.join('NULL' if x is None else in_arr_str(x) for x in var)
157
171
  return reencode(f"{{{var}}}")
158
172
  elif isinstance(var, dict):
159
173
  var = dumps(var, ensure_ascii=False).replace("'", "''")
160
174
  return reencode(var)
161
- elif isinstance(var, (datetime, time)):
175
+ elif hasattr(datetime, var.__class__.__name__) or isinstance(var, UUID):
162
176
  return f" '{var}'"
163
177
  else:
164
178
  return f" {var}"
@@ -113,6 +113,7 @@ class DB(Option):
113
113
  self._params[x.replace("$",".")] = v
114
114
  else:
115
115
  self._params = {}
116
+ self._hash = hash(connection_string)
116
117
  if "://" not in connection_string:
117
118
  try:
118
119
  _check_cfg()
@@ -155,14 +156,10 @@ class DB(Option):
155
156
  else:
156
157
  driver_path = ""
157
158
 
158
- self._hash = hash(driver_path + connection_string)
159
159
  if "LIMIT" in self._params:
160
160
  m = self._params["LIMIT"]
161
161
  del self._params["LIMIT"]
162
- else:
163
- m = 0
164
- self._conn_limit = None
165
- self.connection_limit = m
162
+ self.connection_limit = m
166
163
  self._connection = driver(connection_string, self._on_open_close, notify)
167
164
 
168
165
  @property
@@ -192,21 +189,18 @@ class DB(Option):
192
189
  else:
193
190
  lmt = BoundedSemaphore(value)
194
191
  _LIMITS_[self._hash] = lmt
195
- self._conn_limit = lmt
196
192
 
197
193
 
198
194
  async def _on_open_close(self, close=False)->dict:
199
- if self._conn_limit:
195
+ if lmt:=_LIMITS_.get(self._hash):
200
196
  if close:
201
- await self._conn_limit.release()
202
- if self._hash not in _LIMITS_:
203
- self._conn_limit = None
204
- elif self._hash not in _LIMITS_:
205
- self._conn_limit = None
197
+ lmt.release()
206
198
  else:
207
- if self._conn_limit.locked():
199
+ if lmt.locked():
208
200
  await self.garbage_collect()
209
- await self._conn_limit.acquire()
201
+ while lmt.locked():
202
+ await sleep(0.0001)
203
+ await lmt.acquire()
210
204
  return self._params
211
205
 
212
206
  @property
@@ -4,22 +4,6 @@ from ..error import raise_error
4
4
  from .processor import sql
5
5
  import asyncio
6
6
 
7
-
8
- class ROW(ReadOnlyPropDict):
9
-
10
- def __str__(self):
11
- s = ""
12
- for x in self._data:
13
- v = str(self._data[x].value).replace("\t"," ")
14
- s += f" {x}: {v}" + "\t"
15
- return s[:-1]
16
-
17
- def __getitem__(self, item):
18
- if item not in self._data:
19
- raise_error("FIELD_NOT_FOUND", name=item)
20
- return self._data[item]
21
-
22
-
23
7
  class Field:
24
8
 
25
9
  class _Condition:
@@ -42,7 +26,8 @@ class Field:
42
26
  if isinstance(right, Field):
43
27
  if right._table == left._table:
44
28
  right = Field._Condition.Name(right.name)
45
- right = right.value
29
+ else:
30
+ right = right.value
46
31
  if not isinstance(left, Field):
47
32
  raise_error("NOT_LFIELD")
48
33
  elif op in ("%", "//"):
@@ -222,6 +207,20 @@ class Table:
222
207
  class _FILTER(ALL):
223
208
  ...
224
209
 
210
+ class ROW(ReadOnlyPropDict):
211
+
212
+ def __str__(self):
213
+ s = ""
214
+ for x in self._data:
215
+ v = str(self._data[x].value).replace("\t"," ")
216
+ s += f" {x}: {v}" + "\t"
217
+ return s[:-1]
218
+
219
+ def __getitem__(self, item):
220
+ if item not in self._data:
221
+ raise_error("FIELD_NOT_FOUND", name=item)
222
+ return self._data[item]
223
+
225
224
  def __init__(self, table_struct:dict, db:DB, page_size=100, auto_close_conn=True, async_delay=0.001):
226
225
  self._table = table_struct["table"]
227
226
  self._db = db
@@ -233,7 +232,7 @@ class Table:
233
232
  self._offset = 0
234
233
  self._page = None
235
234
  self._filter = ""
236
- self._row = ROW(**{x["name"]:Field(x, self) for x in table_struct.get("fields", [])})
235
+ self._row = Table.ROW(**{x["name"]:Field(x, self) for x in table_struct.get("fields", [])})
237
236
  self._key = 1
238
237
  constraint = table_struct.get("constraints")
239
238
  for x in constraint:
@@ -329,7 +328,6 @@ class Table:
329
328
  """строка, на которой стоит курсор (счет с 0)"""
330
329
  return self._offset + (self._ptr or 0)
331
330
 
332
-
333
331
  async def seek(self, *args, **kwargs):
334
332
  if kwargs or len(args) != 1 or (len(args) > 1 and not isinstance(args[0], int)):
335
333
  if self._filter:
@@ -371,6 +369,10 @@ class Table:
371
369
  self._offset = need_ofs
372
370
  return True
373
371
 
372
+ async def refresh(self):
373
+ pos = self.position
374
+ await self.seek(pos)
375
+
374
376
  async def next(self):
375
377
  """
376
378
  переход на следующую запись
@@ -482,7 +484,7 @@ class Table:
482
484
  if x not in nms:
483
485
  raise_error("NA_FIELD", field=x, table=self._table)
484
486
  if qry:
485
- qry += "and"
487
+ qry += " and "
486
488
  qry += f"{x} = {Field._Condition._lquot(self.row._data[x]._p_type, kwargs[x])}"
487
489
  return qry
488
490
 
@@ -573,6 +575,8 @@ class Table:
573
575
  """
574
576
  if args or kwargs:
575
577
  qry = self._gen_filter(*args, **kwargs)
578
+ elif self.key and isinstance(self.key, str):
579
+ qry = {self.key:self.row[self.key].value}
576
580
  elif self.key:
577
581
  qry = {x:self.row[x].value for x in self.key}
578
582
  else:
@@ -598,6 +602,7 @@ class Table:
598
602
  while n < len(args):
599
603
  if args[n]==ALL:
600
604
  fltr = None
605
+ args = list(args)
601
606
  del args[n]
602
607
  else:
603
608
  n += 1
@@ -3,7 +3,10 @@ from datetime import datetime
3
3
  from json import dumps, loads
4
4
  from json5 import loads as json5_load
5
5
  from yaml import safe_load as yaml_load
6
- from toml import loads as toml_load
6
+ try:
7
+ from tomllib import loads as toml_load
8
+ except:
9
+ toml_load = None
7
10
  from pathlib import Path
8
11
  from os import environ as ENV
9
12
  import logging as LOG
@@ -69,7 +72,11 @@ def typed_val(key, values: dict):
69
72
  return v, key
70
73
 
71
74
 
72
- def from_env(key:str, key_map:dict):
75
+ def from_env(key:str, key_map:dict | str):
76
+ if isinstance(key_map, str):
77
+ if key in ENV:
78
+ Config._settings[key_map] = ENV[key]
79
+ return
73
80
  ret = {key_map[x]: ENV[x] for x in key_map if x in ENV}
74
81
  if key and "." in key:
75
82
  eval_key = key.split(".")
@@ -100,7 +107,7 @@ class Config:
100
107
  def initialized(cls):
101
108
  return cls._settings is not None
102
109
 
103
- def __init__(self, path: Optional[str | list[str] | dict] = None, env_map: Optional[dict] = None, can_include: Optional[list]=None) -> None:
110
+ def __init__(self, path: Optional[str | list[str] | dict] = None, env_map: Optional[dict] = None, can_include: Optional[set]=None) -> None:
104
111
  if Config.initialized:
105
112
  return
106
113
 
@@ -119,6 +126,8 @@ class Config:
119
126
  case ".yaml" | ".yml":
120
127
  return yaml_load(data)
121
128
  case ".toml":
129
+ if toml_load is None:
130
+ raise error("BAD_FORMAT", format_name=fn)
122
131
  return toml_load(data)
123
132
  case _:
124
133
  raise error("BAD_FORMAT", format_name=fn)
@@ -39,7 +39,7 @@ class DB_ENV(UserDict):
39
39
  if isinstance(connection, DB_ENV):
40
40
  env = connection
41
41
  self._db_params = env._db_params.copy()
42
- params = env._params.copy()
42
+ params = env.data.copy()
43
43
  connection = env.connection_string
44
44
  elif isinstance(connection, DB):
45
45
  connection = DB.connection.connection_string
@@ -74,6 +74,7 @@ class JSON_RPC:
74
74
  if "id" in message:
75
75
  res["id"] = message["id"]
76
76
  except Exception as e:
77
+ Config().log_level
77
78
  res = self.rpc_error(-32000, data=e, header=message)
78
79
  if res.get("error") is not None:
79
80
  self.log(f"{f}: ERROR:\n{res['error']}!!")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ab_engine
3
- Version: 0.2.3
3
+ Version: 0.2.5
4
4
  Summary: Library of standard modules
5
5
  Author-email: Andrew Bulatov <BulatovAndrew@gmail.com>
6
6
  License-Expression: MIT
@@ -10,7 +10,6 @@ Description-Content-Type: text/markdown
10
10
  Requires-Dist: json5>=0.12.0
11
11
  Requires-Dist: psycopg>=3.2.9
12
12
  Requires-Dist: pyyaml>=6.0.3
13
- Requires-Dist: toml>=0.10.2
14
13
 
15
14
  Библиотека стандартных модулей
16
15
  ==========================================================
@@ -1,4 +1,3 @@
1
1
  json5>=0.12.0
2
2
  psycopg>=3.2.9
3
3
  pyyaml>=6.0.3
4
- toml>=0.10.2
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ab_engine"
3
- version = "0.2.3"
3
+ version = "0.2.5"
4
4
  description = "Library of standard modules"
5
5
  authors = [{name = "Andrew Bulatov", email = "BulatovAndrew@gmail.com"}]
6
6
  license = "MIT"
@@ -9,8 +9,7 @@ requires-python = ">=3.10"
9
9
  dependencies = [
10
10
  "json5>=0.12.0",
11
11
  "psycopg>=3.2.9",
12
- "pyyaml>=6.0.3",
13
- "toml>=0.10.2",
12
+ "pyyaml>=6.0.3"
14
13
  ]
15
14
  [project.urls]
16
15
  Repository = "https://github.com/a-bulatov/ab_engine"
File without changes
File without changes
File without changes