db-attribute 2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- db_attribute/__init__.py +320 -0
- db_attribute/connector.py +31 -0
- db_attribute/db_class.py +942 -0
- db_attribute/db_types.py +178 -0
- db_attribute/db_work.py +365 -0
- db_attribute/discriptor.py +176 -0
- db_attribute-2.1.dist-info/METADATA +398 -0
- db_attribute-2.1.dist-info/RECORD +11 -0
- db_attribute-2.1.dist-info/WHEEL +5 -0
- db_attribute-2.1.dist-info/licenses/LICENSE +21 -0
- db_attribute-2.1.dist-info/top_level.txt +1 -0
db_attribute/__init__.py
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
from typing import ClassVar, get_origin
|
|
2
|
+
|
|
3
|
+
import db_attribute.db_class as db_class
|
|
4
|
+
import db_attribute.db_work as db_work
|
|
5
|
+
import db_attribute.db_types as db_types
|
|
6
|
+
import db_attribute.connector as connector
|
|
7
|
+
import db_attribute.discriptor as discriptor
|
|
8
|
+
|
|
9
|
+
__all__ = ['DbAttribute', 'DbAttributeMetaclass', 'db_work', 'db_class', 'discriptor', 'connector', 'db_types']
|
|
10
|
+
__version__ = '2.1'
|
|
11
|
+
|
|
12
|
+
class DbAttributeMetaclass(type):
|
|
13
|
+
dict_classes = db_types.DictClasses()
|
|
14
|
+
def __new__(cls, name, bases, namespace, **kwargs):
|
|
15
|
+
def cheak_class_in_bases(bases, Class):
|
|
16
|
+
for i in bases:
|
|
17
|
+
if Class in i.__mro__:
|
|
18
|
+
return True
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
if not cheak_class_in_bases(bases, DbAttribute):
|
|
22
|
+
bases = (DbAttribute,) + bases
|
|
23
|
+
|
|
24
|
+
new_cls = super().__new__(cls, name, bases, namespace)
|
|
25
|
+
|
|
26
|
+
params_for_metaclass = {'need_add_this_class_to_dict_classes': True, 'need_DbAttributeMetaclass': True}
|
|
27
|
+
options = {'__dbworkobj__': db_types.NotSet, '__max_repr_recursion_limit__': 10, '__repr_class_name__': db_types.NotSet}
|
|
28
|
+
|
|
29
|
+
__annotations__ = {}
|
|
30
|
+
__dict__ = {}
|
|
31
|
+
__db_fields__ = {}
|
|
32
|
+
__meta_options__ = {}
|
|
33
|
+
|
|
34
|
+
for i in new_cls.__mro__[::-1]:
|
|
35
|
+
__db_fields__ |= getattr(i, '__db_fields__', {})
|
|
36
|
+
__dict__ |= getattr(i, '__dict__', {})
|
|
37
|
+
__annotations__ |= getattr(i, '__annotations__', {})
|
|
38
|
+
Meta = getattr(i, 'Meta', None)
|
|
39
|
+
if Meta is not None:
|
|
40
|
+
for i in Meta.__mro__[::-1]:
|
|
41
|
+
__meta_options__ |= getattr(i, '__dict__', {})
|
|
42
|
+
|
|
43
|
+
for i in __dict__:
|
|
44
|
+
if i in options:
|
|
45
|
+
options[i] = __dict__[i]
|
|
46
|
+
if i in params_for_metaclass:
|
|
47
|
+
params_for_metaclass[i] = kwargs[i]
|
|
48
|
+
|
|
49
|
+
for i in __meta_options__:
|
|
50
|
+
if i in options:
|
|
51
|
+
options[i] = __meta_options__[i]
|
|
52
|
+
if i in params_for_metaclass:
|
|
53
|
+
params_for_metaclass[i] = kwargs[i]
|
|
54
|
+
|
|
55
|
+
for i in kwargs:
|
|
56
|
+
if i in options:
|
|
57
|
+
options[i] = kwargs[i]
|
|
58
|
+
if i in params_for_metaclass:
|
|
59
|
+
params_for_metaclass[i] = kwargs[i]
|
|
60
|
+
|
|
61
|
+
if not params_for_metaclass['need_DbAttributeMetaclass']:
|
|
62
|
+
return new_cls
|
|
63
|
+
|
|
64
|
+
new_cls.__repr_class_name__ = name
|
|
65
|
+
|
|
66
|
+
for i in options:
|
|
67
|
+
if options[i] is not db_types.NotSet:
|
|
68
|
+
setattr(new_cls, i, options[i])
|
|
69
|
+
|
|
70
|
+
if getattr(new_cls, '__dbworkobj__', None) is None:
|
|
71
|
+
raise Exception(f'The "{new_cls.__name__}" class dosn\'t have "__dbworkobj__" parameter: set "__dbworkobj__" or "Meta", see documentation')
|
|
72
|
+
|
|
73
|
+
attr_names = list(__annotations__.keys())
|
|
74
|
+
set_attr_names = set(attr_names)
|
|
75
|
+
for i in __dict__:
|
|
76
|
+
if isinstance(__dict__[i], db_types.DbField) and i not in set_attr_names:
|
|
77
|
+
attr_names.append(i)
|
|
78
|
+
set_attr_names.add(i)
|
|
79
|
+
|
|
80
|
+
for attr_name in attr_names:
|
|
81
|
+
if attr_name == 'id': continue
|
|
82
|
+
attr_value = __dict__.get(attr_name, db_types.MISSING)
|
|
83
|
+
attr_type = __annotations__.get(attr_name, db_types.MISSING)
|
|
84
|
+
|
|
85
|
+
if get_origin(attr_type) is ClassVar:
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
if isinstance(attr_value, db_types.DbField):
|
|
89
|
+
db_field = attr_value
|
|
90
|
+
else:
|
|
91
|
+
db_field = db_types.DbField(default=attr_value)
|
|
92
|
+
|
|
93
|
+
if attr_type is not db_types.MISSING and db_field.python_type is db_types.MISSING:
|
|
94
|
+
db_field.python_type = attr_type
|
|
95
|
+
if db_field.python_type is db_types.MISSING:
|
|
96
|
+
if db_field.default is not db_types.MISSING:
|
|
97
|
+
db_field.python_type = type(db_field.default)
|
|
98
|
+
elif db_field.default_factory is not db_types.MISSING:
|
|
99
|
+
db_field.python_type = type(db_field.default_factory.get_value())
|
|
100
|
+
if db_field.python_type is db_types.MISSING:
|
|
101
|
+
raise f'the type for {attr_name} of {name} is not set (add python_type for DbField or set type in annotations or set default for DbField or set default_factory for DbField)'
|
|
102
|
+
|
|
103
|
+
__db_fields__[attr_name] = db_field
|
|
104
|
+
|
|
105
|
+
__cls_dict__ = getattr(new_cls, '__dict__')
|
|
106
|
+
setattr(new_cls, '__db_fields__', __db_fields__)
|
|
107
|
+
|
|
108
|
+
for attr_name in __cls_dict__['__db_fields__']:
|
|
109
|
+
setattr(new_cls, attr_name, discriptor.DbAttributeDiscriptor(new_cls, attr_name))
|
|
110
|
+
|
|
111
|
+
cls.dict_classes.replace(new_cls)
|
|
112
|
+
|
|
113
|
+
required_params = []
|
|
114
|
+
optional_params = []
|
|
115
|
+
|
|
116
|
+
for field_name, field_value in __db_fields__.items():
|
|
117
|
+
is_required = False
|
|
118
|
+
if isinstance(field_value, db_types.DbField):
|
|
119
|
+
default = field_value.get_default()
|
|
120
|
+
if default is db_types.MISSING:
|
|
121
|
+
is_required = True
|
|
122
|
+
else:
|
|
123
|
+
if field_value is db_types.MISSING:
|
|
124
|
+
is_required = True
|
|
125
|
+
|
|
126
|
+
if is_required:
|
|
127
|
+
required_params.append(field_name)
|
|
128
|
+
else:
|
|
129
|
+
optional_params.append(f"{field_name}=db_types.NotSet")
|
|
130
|
+
|
|
131
|
+
params = required_params + optional_params
|
|
132
|
+
params_str = ', '.join(params)
|
|
133
|
+
|
|
134
|
+
init_code = (
|
|
135
|
+
f"def __init__(self, {params_str}, id:int=db_types.NotSet, _dont_add_id:bool = False):\n"
|
|
136
|
+
" now_locals = locals()\n"
|
|
137
|
+
" used_keys = now_locals\n"
|
|
138
|
+
" for i in ['self', 'id', '_dont_add_id']:\n"
|
|
139
|
+
" used_keys.pop(i)\n"
|
|
140
|
+
" if not self.__dbworkobj__.cheak_exists_id_table(self.__class__.__name__):\n"
|
|
141
|
+
" self.__dbworkobj__.create_id_table(self.__class__.__name__)\n"
|
|
142
|
+
" if isinstance(id, db_types.Id):\n"
|
|
143
|
+
" id = id.Id\n"
|
|
144
|
+
" if id is db_types.NotSet:\n"
|
|
145
|
+
" id = self.__dbworkobj__.get_new_id(self.__class__.__name__)['data']\n"
|
|
146
|
+
" elif not (_dont_add_id and len([i for i in used_keys if used_keys[i] is not db_types.NotSet]) == 0):\n"
|
|
147
|
+
" self.__dbworkobj__.add_id(self.__class__.__name__, id)\n"
|
|
148
|
+
" setattr(self, 'id', id)\n"
|
|
149
|
+
" for name in __db_fields__:\n"
|
|
150
|
+
" value = used_keys[name]\n"
|
|
151
|
+
" if value is db_types.NotSet:\n"
|
|
152
|
+
" continue\n"
|
|
153
|
+
" if isinstance(value, db_types.Factory):\n"
|
|
154
|
+
" setattr(self, name, value.get_value())\n"
|
|
155
|
+
" else:\n"
|
|
156
|
+
" setattr(self, name, value)\n"
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
exec_globals = {
|
|
160
|
+
'db_types': db_types,
|
|
161
|
+
'__db_fields__': __db_fields__
|
|
162
|
+
}
|
|
163
|
+
exec(init_code, exec_globals)
|
|
164
|
+
init_method = exec_globals['__init__']
|
|
165
|
+
|
|
166
|
+
new_cls.__init__ = init_method
|
|
167
|
+
if params_for_metaclass['need_add_this_class_to_dict_classes']:
|
|
168
|
+
cls.dict_classes.add(new_cls)
|
|
169
|
+
|
|
170
|
+
if not new_cls.__dbworkobj__.cheak_exists_id_table(new_cls.__name__):
|
|
171
|
+
new_cls.__dbworkobj__.create_id_table(new_cls.__name__)
|
|
172
|
+
|
|
173
|
+
return new_cls
|
|
174
|
+
|
|
175
|
+
def __iter__(self):
|
|
176
|
+
return (self.get(id=i) for i in self.get_all_ids())
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class DbAttribute:
|
|
180
|
+
id: int
|
|
181
|
+
__dbworkobj__: ClassVar[db_work.Db_work] = None
|
|
182
|
+
__max_repr_recursion_limit__: ClassVar[int] = 10
|
|
183
|
+
__repr_class_name__: ClassVar[str] = db_types.NotSet
|
|
184
|
+
|
|
185
|
+
def __init__(self, *args, ID=None, **kwargs):
|
|
186
|
+
raise 'Need set metaclass=DbAttributeMetaclass'
|
|
187
|
+
def __repr__(self):
|
|
188
|
+
return self.__get_repr__(set())
|
|
189
|
+
def __get_repr__(self, Objs: set, now: int=0):
|
|
190
|
+
if now > self.__max_repr_recursion_limit__ or (self.id, self.__repr_class_name__) in Objs:
|
|
191
|
+
return f'{self.__repr_class_name__}(id={self.id}, ...)'
|
|
192
|
+
Objs.add((self.id, self.__repr_class_name__))
|
|
193
|
+
return f'{self.__repr_class_name__}(id={self.id}, {", ".join([f"{i}={obj.__get_repr__(Objs, now+1) if hasattr(obj:=getattr(self, i), '__get_repr__') else f'{getattr(self, i)}'}" for i in self.__db_fields__])})'
|
|
194
|
+
|
|
195
|
+
def _db_attribute_container_update(self, key, data=None):
|
|
196
|
+
"""
|
|
197
|
+
call this functions, when any container attribute is updated, to update this attribute in db
|
|
198
|
+
:param key: name attribute container which update
|
|
199
|
+
:type key: str
|
|
200
|
+
:param data: the attribute (DbDict, DbSet and others containers)
|
|
201
|
+
"""
|
|
202
|
+
self_dict = object.__getattribute__(self, '__dict__')
|
|
203
|
+
cls = object.__getattribute__(self, '__class__')
|
|
204
|
+
if ('_'+key in self_dict) or (key not in cls.__dict__['__db_fields__']):
|
|
205
|
+
return
|
|
206
|
+
cls.__dict__[key].container_update(self, data)
|
|
207
|
+
|
|
208
|
+
@classmethod
|
|
209
|
+
def _db_attribute_found_ids_by_attribute(cls, attribute_name:str, attribute_value):
|
|
210
|
+
tempdata = cls.__dbworkobj__.found_ids_by_value(class_name=cls.__name__, attribute_name=attribute_name, data=attribute_value, _cls_dbattribute=cls)
|
|
211
|
+
if tempdata['status_code'] != 200:
|
|
212
|
+
return set()
|
|
213
|
+
return tempdata['data']
|
|
214
|
+
|
|
215
|
+
@classmethod
|
|
216
|
+
def get(cls, id):
|
|
217
|
+
"""
|
|
218
|
+
return the one object with this id or None, if it's obj is not found
|
|
219
|
+
if the 'get' method finds multiple search results, it selects the smallest id.
|
|
220
|
+
if the 'get' method does not find any search results, it returns None.
|
|
221
|
+
:param id:
|
|
222
|
+
:type id: int | db_types.Id | discriptor.Condition
|
|
223
|
+
:return: obj | None
|
|
224
|
+
"""
|
|
225
|
+
if isinstance(id, discriptor.Condition):
|
|
226
|
+
Ids = id.found()
|
|
227
|
+
if not Ids:
|
|
228
|
+
return None
|
|
229
|
+
id = Ids.list_ids[0]
|
|
230
|
+
if isinstance(id, db_types.Id):
|
|
231
|
+
id = id.Id
|
|
232
|
+
if isinstance(id, int) or isinstance(id, db_types.Id):
|
|
233
|
+
obj = cls.__new__(cls)
|
|
234
|
+
obj.id = id
|
|
235
|
+
return obj
|
|
236
|
+
return None
|
|
237
|
+
|
|
238
|
+
@classmethod
|
|
239
|
+
def get_all_ids(cls):
|
|
240
|
+
temp = cls.__dbworkobj__.get_all_ids(cls.__name__)
|
|
241
|
+
if temp['status_code'] != 200:
|
|
242
|
+
return db_types.Ids()
|
|
243
|
+
return db_types.Ids(temp['data'])
|
|
244
|
+
|
|
245
|
+
def dump(self, attributes:set[str]=None):
|
|
246
|
+
"""
|
|
247
|
+
Use it func, if you need dump the data to db, with manual_dump_mode
|
|
248
|
+
"""
|
|
249
|
+
self_dict = object.__getattribute__(self, '__dict__')
|
|
250
|
+
cls = object.__getattribute__(self, '__class__')
|
|
251
|
+
all_attributes = object.__getattribute__(self, '__db_fields__')
|
|
252
|
+
for db_attr in all_attributes if attributes is None else all_attributes & attributes:
|
|
253
|
+
if '_'+db_attr in self_dict:
|
|
254
|
+
cls.__dict__[db_attr].dump_attr(self)
|
|
255
|
+
|
|
256
|
+
def set_manual_dump_mode(self, attributes:set[str]=None):
|
|
257
|
+
"""
|
|
258
|
+
auto_dump_mode: attributes don't save in self.__dict__, all changes automatic dump in db.
|
|
259
|
+
manual_dump_mode: all attributes save in self.__dict__, and won't dump in db until self.db_attribute_set_auto_dump_mode is called.
|
|
260
|
+
function set undump_mode (dump_mode = False)
|
|
261
|
+
:param attributes: for which attributes will set the mode, ex: atributes={'name', 'age'}
|
|
262
|
+
"""
|
|
263
|
+
all_attributes = object.__getattribute__(self, '__db_fields__')
|
|
264
|
+
self_dict = object.__getattribute__(self, '__dict__')
|
|
265
|
+
for db_attr in (all_attributes if attributes is None else all_attributes.keys() & attributes):
|
|
266
|
+
self_dict['_'+db_attr] = getattr(self, db_attr)
|
|
267
|
+
|
|
268
|
+
def set_auto_dump_mode(self, attributes:set[str]=None):
|
|
269
|
+
"""
|
|
270
|
+
auto_dump_mode: attributes don't save in self.__dict__, all changes automatic dump in db.
|
|
271
|
+
manual_dump_mode: all attributes save in self.__dict__, and won't dump in db until self.db_attribute_set_auto_dump_mode is called.
|
|
272
|
+
function set auto_dump_mode and call self.db_attribute_dump() for dump attributes
|
|
273
|
+
:param attributes: for which attributes will set the mode, ex: atributes={'name', 'age'}
|
|
274
|
+
"""
|
|
275
|
+
self.dump(attributes=attributes)
|
|
276
|
+
self_dict = object.__getattribute__(self, '__dict__')
|
|
277
|
+
all_attributes = object.__getattribute__(self, '__db_fields__')
|
|
278
|
+
for db_attr in all_attributes if attributes is None else all_attributes & attributes:
|
|
279
|
+
if '_'+db_attr in self_dict:
|
|
280
|
+
del self_dict['_'+db_attr]
|
|
281
|
+
|
|
282
|
+
def delete(self, attributes:set[str]=None):
|
|
283
|
+
"""
|
|
284
|
+
Delete this id from db
|
|
285
|
+
:param attributes: attributes to be deleted, ex: obj.delete({'name', 'age'})
|
|
286
|
+
:return:
|
|
287
|
+
"""
|
|
288
|
+
all_attributes = object.__getattribute__(self, '__db_fields__')
|
|
289
|
+
for db_attr in all_attributes if attributes is None else all_attributes & attributes:
|
|
290
|
+
delattr(self, db_attr)
|
|
291
|
+
|
|
292
|
+
@classmethod
|
|
293
|
+
def delete_objs(cls, IDs: set[int] | int, attributes:set[str]=None):
|
|
294
|
+
all_attributes = object.__getattribute__(cls, '__db_fields__')
|
|
295
|
+
attributes = all_attributes if attributes is None else attributes & all_attributes
|
|
296
|
+
IDs = {IDs} if isinstance(IDs, int) else IDs
|
|
297
|
+
dbworkobj = cls.__dbworkobj__
|
|
298
|
+
clsname = cls.__name__
|
|
299
|
+
for ID in IDs:
|
|
300
|
+
for db_attr in attributes:
|
|
301
|
+
dbworkobj.del_attribute_value(class_name=clsname, attribute_name=db_attr, ID=ID)
|
|
302
|
+
|
|
303
|
+
@classmethod
|
|
304
|
+
def found(cls, **kwargs):
|
|
305
|
+
"""
|
|
306
|
+
WARNING: This function has lost its relevance and is not supported. Use '(cls.attr == value).found()' or 'cls.get(cls.attr == value)'
|
|
307
|
+
|
|
308
|
+
found ids objs with this values of attributes, ex:
|
|
309
|
+
objs: User(id=1, name='Bob', age=3), User(id=2, name='Bob', age=2), User(id=3, name='Anna', age=2)
|
|
310
|
+
User.found(name='Bob') -> {1, 2}
|
|
311
|
+
User.found(age=2) -> {2, 3}
|
|
312
|
+
User.found(name='Bob', age=2) -> {2}
|
|
313
|
+
:param kwargs: names and values of attributes (see doc.)
|
|
314
|
+
:return: set of ids
|
|
315
|
+
"""
|
|
316
|
+
if not kwargs: return set()
|
|
317
|
+
res = cls._db_attribute_found_ids_by_attribute(attribute_name=(temp:=next(iter(kwargs))), attribute_value=kwargs[temp])
|
|
318
|
+
for key in kwargs:
|
|
319
|
+
res &= cls._db_attribute_found_ids_by_attribute(attribute_name=key, attribute_value=kwargs[key])
|
|
320
|
+
return res
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from mysql.connector import connect
|
|
2
|
+
|
|
3
|
+
"""Used mysql"""
|
|
4
|
+
|
|
5
|
+
"""1** - connect errors, 2** - ok work functions, 3** and 4** - errors in project"""
|
|
6
|
+
|
|
7
|
+
status_cod = {100: {"eng": "There is no connection to the database", "ru": "Нет соединения с базой данных"},
|
|
8
|
+
200: {"eng": "it's ok, no error", "ru": "Функция работает корректно, нет ошибок"},
|
|
9
|
+
300: {"eng": "This attribute / object type is not supported", "ru": "Данный тип атрибута/объекта не поддерживается"},
|
|
10
|
+
301: {"eng": "The table already exists", "ru": "Таблица уже существует"},
|
|
11
|
+
302: {"eng": "The table doesn't exist", "ru": "Таблицы не существует"},
|
|
12
|
+
303: {"eng": "The object is already in the table", "ru": "Объект уже находится в таблице"},
|
|
13
|
+
304: {"eng": "Object not found", "ru": "Объект не найден"},
|
|
14
|
+
305: {"eng": "The object argument has neither a default value nor a value in the database.", "ru": "Аргумент объекта не имеет ни значения по умолчанию, ни значения в database."},
|
|
15
|
+
400: {"eng": "Erroneous use of the function", "ru": "Ошибка использования функции"},
|
|
16
|
+
402: {"eng": "Unexpected function error", "ru": "Непредвиденная ошибка функции"},
|
|
17
|
+
403: {"eng": "Not supported by this version of the program", "ru": "Не поддерживается данной версией программы"}}
|
|
18
|
+
|
|
19
|
+
class Connection:
|
|
20
|
+
"""Used mysql db"""
|
|
21
|
+
def __init__(self, /, *, host='127.0.0.1', port=3306, user, password, database, **kwargs):
|
|
22
|
+
"""*for params see 'https://dev.mysql.com/doc/connector-python/en/connector-python-connectargs.html'*"""
|
|
23
|
+
self.sql_name = 'MySQL'
|
|
24
|
+
try:
|
|
25
|
+
self.conn = connect(host=host, port=port, user=user, password=password, database=database, **kwargs)
|
|
26
|
+
self.cur = self.conn.cursor()
|
|
27
|
+
self.notconn = False
|
|
28
|
+
except:
|
|
29
|
+
self.conn = None
|
|
30
|
+
self.cur = None
|
|
31
|
+
self.notconn = True
|