db-attribute 2.1__tar.gz → 2.1.1__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.
- {db_attribute-2.1 → db_attribute-2.1.1.0}/PKG-INFO +179 -62
- {db_attribute-2.1 → db_attribute-2.1.1.0}/db_attribute/__init__.py +28 -9
- {db_attribute-2.1 → db_attribute-2.1.1.0}/db_attribute/db_class.py +242 -182
- {db_attribute-2.1 → db_attribute-2.1.1.0}/db_attribute/db_types.py +31 -9
- {db_attribute-2.1 → db_attribute-2.1.1.0}/db_attribute/db_work.py +21 -17
- {db_attribute-2.1 → db_attribute-2.1.1.0}/db_attribute/discriptor.py +18 -10
- {db_attribute-2.1 → db_attribute-2.1.1.0}/pyproject.toml +2 -2
- {db_attribute-2.1 → db_attribute-2.1.1.0}/readme.md +177 -60
- {db_attribute-2.1 → db_attribute-2.1.1.0}/setup.py +2 -4
- {db_attribute-2.1 → db_attribute-2.1.1.0}/LICENSE +0 -0
- {db_attribute-2.1 → db_attribute-2.1.1.0}/MANIFEST.in +0 -0
- {db_attribute-2.1 → db_attribute-2.1.1.0}/db_attribute/connector.py +0 -0
- {db_attribute-2.1 → db_attribute-2.1.1.0}/db_attribute.egg-info/SOURCES.txt +0 -0
- {db_attribute-2.1 → db_attribute-2.1.1.0}/requirements.txt +0 -0
- {db_attribute-2.1 → db_attribute-2.1.1.0}/setup.cfg +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: db_attribute
|
|
3
|
-
Version: 2.1
|
|
3
|
+
Version: 2.1.1.0
|
|
4
4
|
Summary: DataBase attribute package
|
|
5
5
|
Home-page: https://github.com/shutkanos/Db-Attribute
|
|
6
6
|
Author: Shutkanos
|
|
7
7
|
Author-email: Shutkanos <Shutkanos836926@mail.ru>
|
|
8
8
|
License: MIT
|
|
9
9
|
Project-URL: Homepage, https://github.com/shutkanos/Db-Attribute
|
|
10
|
-
Project-URL: Documentation, https://github.com/shutkanos/Db-Attribute
|
|
10
|
+
Project-URL: Documentation, https://github.com/shutkanos/Db-Attribute#readme.md
|
|
11
11
|
Project-URL: Repository, https://github.com/shutkanos/Db-Attribute
|
|
12
12
|
Keywords: db,database,attribute,db_attribute,db attribute,DbAttribute,database attribute
|
|
13
13
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -26,22 +26,49 @@ Dynamic: license-file
|
|
|
26
26
|
DbAttribute - Database Attribute
|
|
27
27
|
=========================
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
DbAttribute is an ORM library designed to simplify database interactions. Core capabilities:
|
|
30
30
|
|
|
31
|
+
* Automatic state synchronization
|
|
32
|
+
Object attribute changes are automatically tracked and persisted to the database without requiring explicit commit calls.
|
|
33
|
+
<br><br>
|
|
34
|
+
* Direct object manipulation
|
|
35
|
+
Supports both value assignment (obj.attr = value) and in-place modification of container types:
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
obj.books.append("New Book")
|
|
39
|
+
obj.settings["theme"] = "dark"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
* Expressive query syntax
|
|
43
|
+
Filtering uses Python operators with natural syntax:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
# Find users older than 18 named John
|
|
47
|
+
User.get((User.age > 18) & (User.name == "John"))
|
|
48
|
+
|
|
49
|
+
# Get all users named Bob
|
|
50
|
+
[user for user in User if user.name == "Bob"]
|
|
51
|
+
```
|
|
52
|
+
The library provides tools for declarative model definition, relationship management, and database operation optimization through configurable synchronization modes.
|
|
53
|
+
|
|
54
|
+
# Table of contents
|
|
55
|
+
|
|
56
|
+
* [Table of contents](#table-of-contents)
|
|
31
57
|
* [Supported types](#supported-types)
|
|
32
58
|
* [Install](#install)
|
|
33
|
-
* [How it
|
|
59
|
+
* [How to use it](#how-to-use-it)
|
|
34
60
|
* [Create class](#create-class)
|
|
35
61
|
* [Options](#options)
|
|
36
62
|
* [Work with obj](#work-with-obj)
|
|
37
|
-
* [Create new
|
|
38
|
-
* [
|
|
63
|
+
* [Create new object](#create-new-object)
|
|
64
|
+
* [Finding objects](#finding-objects)
|
|
39
65
|
* [Iterations](#iterations)
|
|
40
66
|
* [Change attribute of obj](#change-attribute-of-obj)
|
|
41
67
|
* [Dump mode](#dump-mode)
|
|
42
68
|
* [Types](#types)
|
|
43
69
|
* [Db attribute](#db-attribute)
|
|
44
70
|
* [Db classes](#db-classes)
|
|
71
|
+
* [Custom Db Classes](#custom-db-classes)
|
|
45
72
|
* [Json type](#json-type)
|
|
46
73
|
* [Speed Test](#speed-test)
|
|
47
74
|
* [Get attr](#get-attr)
|
|
@@ -50,28 +77,34 @@ This module allows you to save attributes of objects not in RAM, but in a databa
|
|
|
50
77
|
|
|
51
78
|
# Supported types
|
|
52
79
|
|
|
53
|
-
This module
|
|
80
|
+
This module supports standard types: `int`, `float`, `str`, `bool`, `None`, `tuple`, `list`, `set`, `dict`, `datetime`.
|
|
54
81
|
|
|
55
|
-
If developer needs other data types,
|
|
82
|
+
If a developer needs other data types, they will need to write an adapter class.
|
|
56
83
|
|
|
57
84
|
# Install
|
|
58
85
|
|
|
59
|
-
|
|
86
|
+
The package can be obtained from PyPI and installed in a single step:
|
|
60
87
|
|
|
61
88
|
```
|
|
62
|
-
|
|
89
|
+
pip install db_attribute
|
|
63
90
|
```
|
|
64
91
|
|
|
65
|
-
|
|
92
|
+
It can also be obtained from source (requires git):
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
pip install git+https://github.com/shutkanos/Db-Attribute.git
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
# How to use it
|
|
66
99
|
|
|
67
100
|
## Create class
|
|
68
101
|
|
|
69
|
-
|
|
102
|
+
To create any class (Table):
|
|
70
103
|
|
|
71
104
|
* Set metaclass `DbAttributeMetaclass`
|
|
72
105
|
* Inheritance the `DbAttribute` (optional, since it inherits automatically when using a metaclass)
|
|
73
106
|
* Set dbworkobj for connect to database
|
|
74
|
-
*
|
|
107
|
+
* Define fields using annotations or DbField for database columns
|
|
75
108
|
|
|
76
109
|
```python
|
|
77
110
|
from db_attribute import DbAttribute, DbAttributeMetaclass, db_work, connector
|
|
@@ -86,10 +119,10 @@ class User(DbAttribute, metaclass=DbAttributeMetaclass, __dbworkobj__=db_work_ob
|
|
|
86
119
|
ban = DbField(default=False) # Ok
|
|
87
120
|
other_int_information = 100 # Need annotation or DbField - not error, but not saved
|
|
88
121
|
list_of_books = DbField(default_factory=lambda: ['name of first book']) # Ok
|
|
89
|
-
|
|
122
|
+
settings: dict = DbField(default_factory=dict) # Ok
|
|
90
123
|
```
|
|
91
124
|
|
|
92
|
-
Each
|
|
125
|
+
Each instance has a unique `id` identifier. It is inherited from DbAttribute and stored in `__dict__`
|
|
93
126
|
|
|
94
127
|
### Options
|
|
95
128
|
|
|
@@ -111,7 +144,9 @@ class User(DbAttribute, metaclass=DbAttributeMetaclass):
|
|
|
111
144
|
```python
|
|
112
145
|
class BaseMeta:
|
|
113
146
|
__dbworkobj__ = dbworkobj
|
|
114
|
-
class
|
|
147
|
+
class Class_A(DbAttribute, metaclass=DbAttributeMetaclass):
|
|
148
|
+
Meta = BaseMeta
|
|
149
|
+
class Class_B(DbAttribute, metaclass=DbAttributeMetaclass):
|
|
115
150
|
Meta = BaseMeta
|
|
116
151
|
```
|
|
117
152
|
|
|
@@ -123,9 +158,9 @@ All options:
|
|
|
123
158
|
|
|
124
159
|
## Work with obj
|
|
125
160
|
|
|
126
|
-
### Create new
|
|
161
|
+
### Create new object
|
|
127
162
|
|
|
128
|
-
|
|
163
|
+
To create an object, use an id (optional) and other fields (optional),
|
|
129
164
|
|
|
130
165
|
```python
|
|
131
166
|
obj = User(id=3) # other field set to defaults value
|
|
@@ -142,7 +177,7 @@ obj = User(name='Alica')
|
|
|
142
177
|
print(obj) # User(id=5, name='Alica')
|
|
143
178
|
```
|
|
144
179
|
|
|
145
|
-
If
|
|
180
|
+
If a developer needs to recreate an object, he can call DbAttribute cls with id.
|
|
146
181
|
|
|
147
182
|
```python
|
|
148
183
|
obj = User(name='Ben', age=10, id=3) #insert obj to db
|
|
@@ -161,33 +196,35 @@ obj = User(id=3)
|
|
|
161
196
|
print(obj) #User(id=3, name='Anna', age=15)
|
|
162
197
|
```
|
|
163
198
|
|
|
164
|
-
###
|
|
199
|
+
### Finding objects
|
|
165
200
|
|
|
166
|
-
|
|
201
|
+
If a developer needs to find an object, they can use the 'get' method.
|
|
167
202
|
|
|
168
|
-
|
|
169
|
-
|
|
203
|
+
The `get()` method returns:
|
|
204
|
+
- Single object if found
|
|
205
|
+
- Object with smallest ID if multiple matches exist
|
|
206
|
+
- `None` if no matches found
|
|
170
207
|
|
|
171
208
|
```python
|
|
172
209
|
#create objs
|
|
173
|
-
obj = User(name='Bob', age=
|
|
174
|
-
obj = User(name='Bob', age=
|
|
210
|
+
obj = User(name='Bob', age=2, id=1)
|
|
211
|
+
obj = User(name='Bob', age=3, id=2)
|
|
175
212
|
obj = User(name='Anna', age=2, id=3)
|
|
176
213
|
#finds objs
|
|
177
|
-
print(User.get((User.age == 3) & (User.name == 'Bob'))) #User(id=
|
|
178
|
-
print(User.get(User.name == 'Anna')) #User(id=3, name=Anna, age=2)
|
|
179
|
-
print(User.get(User.name == 'Bob')) #User(id=1, name=Bob, age=
|
|
214
|
+
print(User.get((User.age == 3) & (User.name == 'Bob'))) #User(id=2, name='Bob', age=3)
|
|
215
|
+
print(User.get(User.name == 'Anna')) #User(id=3, name='Anna', age=2)
|
|
216
|
+
print(User.get(User.name == 'Bob')) #User(id=1, name='Bob', age=2)
|
|
180
217
|
print(User.get(User.name == 'Other name')) #None
|
|
181
218
|
```
|
|
182
219
|
|
|
183
220
|
To check the correctness of writing a logical expression, you can:
|
|
184
221
|
|
|
185
222
|
```python
|
|
186
|
-
print(User.name == 'Anna') #(User.name = Anna)
|
|
187
|
-
print((User.age == 3) & (User.name == 'Bob')) #((User.age = 3) and (User.name = Bob))
|
|
223
|
+
print(User.name == 'Anna') #(User.name = 'Anna')
|
|
224
|
+
print((User.age == 3) & (User.name == 'Bob')) #((User.age = 3) and (User.name = 'Bob'))
|
|
188
225
|
```
|
|
189
226
|
|
|
190
|
-
Use '&'
|
|
227
|
+
Use '&' and '|' instead of the 'and' and 'or' operators. The 'and' and 'or' operators are not supported
|
|
191
228
|
|
|
192
229
|
### Iterations
|
|
193
230
|
|
|
@@ -195,17 +232,18 @@ If a developer needs to iterate through all the elements of a class, they can us
|
|
|
195
232
|
|
|
196
233
|
```python
|
|
197
234
|
print(list(User))
|
|
198
|
-
#[User(id=1, name=Bob, age=3), User(id=2, name=Bob, age=2), User(id=3, name=Anna, age=2)]
|
|
235
|
+
#[User(id=1, name='Bob', age=3), User(id=2, name='Bob', age=2), User(id=3, name='Anna', age=2)]
|
|
199
236
|
|
|
200
|
-
print([i
|
|
201
|
-
#[
|
|
237
|
+
print([i for i in User if i.age < 3])
|
|
238
|
+
#[User(id=2, name='Bob', age=2), User(id=3, name='Anna', age=2)]
|
|
202
239
|
|
|
203
240
|
for i in User:
|
|
204
241
|
print(i)
|
|
205
|
-
#User(id=1, name=Bob, age=3)
|
|
206
|
-
#User(id=2, name=Bob, age=2)
|
|
207
|
-
#User(id=3, name=Anna, age=2)
|
|
242
|
+
#User(id=1, name='Bob', age=3)
|
|
243
|
+
#User(id=2, name='Bob', age=2)
|
|
244
|
+
#User(id=3, name='Anna', age=2)
|
|
208
245
|
```
|
|
246
|
+
⚠️ Iterations loads all objects - not recommended for large tables
|
|
209
247
|
|
|
210
248
|
### Change attribute of obj
|
|
211
249
|
|
|
@@ -222,7 +260,7 @@ print(obj) #User(id=1, name='Anna', list_of_books=['Any name of book'])
|
|
|
222
260
|
|
|
223
261
|
### Dump mode
|
|
224
262
|
|
|
225
|
-
If in any function you will work with obj, you can activate manual_dump_mode (auto_dump_mode is default),
|
|
263
|
+
If in any function you will work with obj, you can activate manual_dump_mode (auto_dump_mode is the default),
|
|
226
264
|
|
|
227
265
|
* `auto_dump_mode`: attributes don't save in self.__dict__, all changes automatic dump in db.
|
|
228
266
|
* `manual_dump_mode`: attributes save in self.__dict__, and won't dump in db until self.db_attribute_set_dump_mode is called. this helps to quickly perform operations on containers db attributes
|
|
@@ -239,7 +277,7 @@ user.set_manual_dump_mode()
|
|
|
239
277
|
print(user.__dict__)
|
|
240
278
|
# {'id': 1, '_any_db_data1': 531, '_any_db_data2': 'string'}
|
|
241
279
|
```
|
|
242
|
-
Or set dump
|
|
280
|
+
Or set dump mode for individual attributes
|
|
243
281
|
|
|
244
282
|
```python
|
|
245
283
|
user = User(id=1, any_db_data1=531, any_db_data2='string')
|
|
@@ -257,7 +295,7 @@ for i in range(10 ** 5):
|
|
|
257
295
|
user.list_of_books.append(i)
|
|
258
296
|
user.set_auto_dump_mode()
|
|
259
297
|
```
|
|
260
|
-
If
|
|
298
|
+
If a developer needs to dump attributes to db with manual_dump_mode, you can use DbAttribute.db_attribute_dump
|
|
261
299
|
|
|
262
300
|
```python
|
|
263
301
|
user = User(id=1, list_of_books=[])
|
|
@@ -274,7 +312,7 @@ user.set_auto_dump_mode()
|
|
|
274
312
|
|
|
275
313
|
### Db attribute
|
|
276
314
|
|
|
277
|
-
|
|
315
|
+
A developer can set the Db attribute class as data type for another Db attribute class
|
|
278
316
|
|
|
279
317
|
```python
|
|
280
318
|
from db_attribute.db_types import TableType
|
|
@@ -287,24 +325,45 @@ class Class_B(DbAttribute, metaclass=DbAttributeMetaclass):
|
|
|
287
325
|
Meta = BaseMeta
|
|
288
326
|
obj_a: Class_A
|
|
289
327
|
```
|
|
290
|
-
|
|
328
|
+
To create an object:
|
|
291
329
|
```python
|
|
292
330
|
obj_a = Class_A(id=15, name='Anna', obj_b=1)
|
|
293
331
|
obj_b = Class_B(id=1, name='Bob', obj_a=15)
|
|
294
|
-
print(obj_b) #Class_B(id=1, name=Bob, obj_a=Class_A(id=15, name=Anna, obj_b=Class_B(id=1, ...)))
|
|
332
|
+
print(obj_b) #Class_B(id=1, name='Bob', obj_a=Class_A(id=15, name='Anna', obj_b=Class_B(id=1, ...)))
|
|
295
333
|
#or
|
|
296
334
|
obj_a = Class_A(id=15, name='Anna', obj_b=obj_b)
|
|
297
|
-
print(obj_a) #Class_A(id=15, name=Anna, obj_b=Class_B(id=1, name=Bob, obj_a=Class_A(id=15, ...)))
|
|
335
|
+
print(obj_a) #Class_A(id=15, name='Anna', obj_b=Class_B(id=1, name='Bob', obj_a=Class_A(id=15, ...)))
|
|
298
336
|
```
|
|
299
337
|
For found obj:
|
|
300
338
|
```python
|
|
301
339
|
Class_A(id=15, name='Anna', obj_b=1)
|
|
302
340
|
obj = Class_B(id=1, name='Bob', obj_a=15)
|
|
303
341
|
obj = Class_A.get(Class_A.obj_b == obj)
|
|
304
|
-
print(obj) #Class_A(id=15, name=Anna, obj_b=Class_B(id=1, name=Bob, obj_a=Class_A(id=15, ...)))
|
|
342
|
+
print(obj) #Class_A(id=15, name='Anna', obj_b=Class_B(id=1, name='Bob', obj_a=Class_A(id=15, ...)))
|
|
305
343
|
#And Found with use id of obj:
|
|
306
344
|
obj = Class_A.get(Class_A.obj_b == 1)
|
|
307
|
-
print(obj) #Class_A(id=15, name=Anna, obj_b=Class_B(id=1, name=Bob, obj_a=Class_A(id=15, ...)))
|
|
345
|
+
print(obj) #Class_A(id=15, name='Anna', obj_b=Class_B(id=1, name='Bob', obj_a=Class_A(id=15, ...)))
|
|
346
|
+
```
|
|
347
|
+
One-to-Many relationship:
|
|
348
|
+
```python
|
|
349
|
+
from db_attribute.db_types import DbField
|
|
350
|
+
|
|
351
|
+
class Author(DbAttribute, metaclass=DbAttributeMetaclass):
|
|
352
|
+
Meta = BaseMeta
|
|
353
|
+
name: str = ""
|
|
354
|
+
books: list = DbField(default_factory=list)
|
|
355
|
+
|
|
356
|
+
class Book(DbAttribute, metaclass=DbAttributeMetaclass):
|
|
357
|
+
Meta = BaseMeta
|
|
358
|
+
title: str = ""
|
|
359
|
+
author: Author
|
|
360
|
+
|
|
361
|
+
author = Author(name="George Orwell")
|
|
362
|
+
book = Book(title="1984", author=author)
|
|
363
|
+
author.books.append(book)
|
|
364
|
+
|
|
365
|
+
print(author) #Author(id=1, name='George Orwell', books=[Book(id=1, title='1984', author=Author(id=1, ...))])
|
|
366
|
+
print(book) #Book(id=1, title='1984', author=Author(id=1, name='George Orwell', books=[Book(id=1, ...)]))
|
|
308
367
|
```
|
|
309
368
|
|
|
310
369
|
### Db classes
|
|
@@ -328,9 +387,67 @@ print(obj.list_of_books.dumps())
|
|
|
328
387
|
#{"t": "DbList", "d": [{"t": "DbDatetime", "d": "2024-01-01T00:00:00"}, {"t": "DbDatetime", "d": "2027-07-07T00:00:00"}]}
|
|
329
388
|
```
|
|
330
389
|
|
|
390
|
+
### Custom Db Classes
|
|
391
|
+
|
|
392
|
+
And to create a custom 'Db class', you need to
|
|
393
|
+
* Create regular class
|
|
394
|
+
* Inherit from DbClass (DbClass - first. It is important) and your regular class for custom Db class
|
|
395
|
+
* Set a Decorator with or without the necessary parameters
|
|
396
|
+
* Set at least the `__convert_to_db__` module, according to the documentation
|
|
397
|
+
* add additional modules.
|
|
398
|
+
|
|
399
|
+
```python
|
|
400
|
+
from db_attribute import db_class
|
|
401
|
+
|
|
402
|
+
# for exemple you have your class:
|
|
403
|
+
|
|
404
|
+
class UserDataClass:
|
|
405
|
+
def __init__(self, value = None):
|
|
406
|
+
self.value = value
|
|
407
|
+
def __repr__(self):
|
|
408
|
+
return f'UserDataClass(value={self.value})'
|
|
409
|
+
|
|
410
|
+
@db_class.DbClassDecorator
|
|
411
|
+
class DbUserDataClass(db_class.DbClass, UserDataClass):
|
|
412
|
+
def __init__(self, value=None, **kwargs):
|
|
413
|
+
# This is not a mandatory method
|
|
414
|
+
super().__init__(_call_init=False, **kwargs) # But this call is mandatory
|
|
415
|
+
self.__dict__['value'] = value
|
|
416
|
+
# Here we set the value of a variable using __dict__.
|
|
417
|
+
# This is not necessary, but it speeds up the work with the class.
|
|
418
|
+
|
|
419
|
+
@classmethod
|
|
420
|
+
def __convert_to_db__(cls, obj: UserDataClass, **kwargs):
|
|
421
|
+
"""Methode for convert obj to dbclass - need @classmethod and kwargs"""
|
|
422
|
+
# This is a mandatory method
|
|
423
|
+
# Call with _user_db=True
|
|
424
|
+
# Example:
|
|
425
|
+
# print(type(DbUserDataClass(value=10))) #UserDataClass
|
|
426
|
+
# print(type(DbUserDataClass(value=10, _use_db=True))) #DbUserDataClass
|
|
427
|
+
return cls(_use_db=True, value=obj.value, **kwargs)
|
|
428
|
+
|
|
429
|
+
def __convert_from_db__(self):
|
|
430
|
+
"""Reverse convert"""
|
|
431
|
+
# This is not a mandatory method.
|
|
432
|
+
return self._standart_class(value=self.value)
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
For example:
|
|
436
|
+
|
|
437
|
+
```python
|
|
438
|
+
class User(DbAttribute, metaclass=DbAttributeMetaclass):
|
|
439
|
+
Meta = BaseMeta
|
|
440
|
+
data: UserDataClass
|
|
441
|
+
|
|
442
|
+
user = User(id=1, data=UserDataClass(10))
|
|
443
|
+
print(user.data) # UserDataClass(value=10)
|
|
444
|
+
user.data.value = 5
|
|
445
|
+
print(user.data) # UserDataClass(value=5)
|
|
446
|
+
```
|
|
447
|
+
|
|
331
448
|
### Json type
|
|
332
449
|
|
|
333
|
-
|
|
450
|
+
DbAttribute supports `tuple`, `list`, `dict`, other collections, but these types are slow, because uses Db classes (see [speed test](#speed-test)).
|
|
334
451
|
|
|
335
452
|
To solve this problem, use a Json convertation
|
|
336
453
|
|
|
@@ -339,24 +456,24 @@ from db_attribute.db_types import JsonType, DbField
|
|
|
339
456
|
|
|
340
457
|
class User(DbAttribute, metaclass=DbAttributeMetaclass):
|
|
341
458
|
Meta = BaseMeta
|
|
342
|
-
|
|
459
|
+
settings: JsonType = DbField(default_factory=lambda: {})
|
|
343
460
|
|
|
344
|
-
obj = User(1,
|
|
345
|
-
print(obj.
|
|
346
|
-
print(type(obj.
|
|
461
|
+
obj = User(1, settings={1: 2, 3: [4, 5]})
|
|
462
|
+
print(obj.settings) # {'1': 2, '3': [4, 5]}
|
|
463
|
+
print(type(obj.settings)) # dict
|
|
347
464
|
```
|
|
348
465
|
|
|
349
466
|
* If Developer change obj with JsonType, this obj don't dump to db, you need set the new obj
|
|
350
|
-
*
|
|
467
|
+
* JsonType only supports: `dict`, `list`, `str`, `int`, `float`, `bool`, `None`
|
|
351
468
|
|
|
352
469
|
```python
|
|
353
|
-
obj = User(1,
|
|
354
|
-
del obj.
|
|
355
|
-
obj.
|
|
356
|
-
obj.
|
|
357
|
-
print(obj.
|
|
358
|
-
obj.
|
|
359
|
-
print(obj.
|
|
470
|
+
obj = User(1, settings={1: 2, 3: [4, 5]})
|
|
471
|
+
del obj.settings['3'] # not changed
|
|
472
|
+
obj.settings['1'] = 3 # not changed
|
|
473
|
+
obj.settings |= {4: 5} # not changed
|
|
474
|
+
print(obj.settings) #{'1': 2, '3': [4, 5]}
|
|
475
|
+
obj.settings = {1: 3} # changed
|
|
476
|
+
print(obj.settings) #{'1': 3}
|
|
360
477
|
```
|
|
361
478
|
|
|
362
479
|
# Speed Test
|
|
@@ -370,7 +487,7 @@ The execution speed may vary from computer to computer, so you need to focus on
|
|
|
370
487
|
|
|
371
488
|
Mysql `select` - 12500 op/sec
|
|
372
489
|
|
|
373
|
-
Type | Operation/seconds |
|
|
490
|
+
Type | Operation/seconds | Performance impact
|
|
374
491
|
----------|-------------------|---------------------------
|
|
375
492
|
int | 11658 op/sec | -6%
|
|
376
493
|
str | 11971 op/sec | -4%
|
|
@@ -383,7 +500,7 @@ JsonType | 11937 op/sec | -4%
|
|
|
383
500
|
|
|
384
501
|
Mysql `insert` - 8500 op/sec<br>
|
|
385
502
|
|
|
386
|
-
Type | Operation/seconds |
|
|
503
|
+
Type | Operation/seconds | Performance impact
|
|
387
504
|
----------|-------------------|---------------------------
|
|
388
505
|
int | 8056 op/sec | -5%
|
|
389
506
|
str | 8173 op/sec | -3%
|
|
@@ -394,5 +511,5 @@ JsonType | 7297 op/sec | -14%
|
|
|
394
511
|
|
|
395
512
|
# Data base
|
|
396
513
|
|
|
397
|
-
|
|
514
|
+
This module uses MySQL db (<a href="https://github.com/mysql/mysql-connector-python/blob/trunk/LICENSE.txt">License</a>), and for use it, you need install <a href='https://www.mysql.com'>mysql</a>
|
|
398
515
|
|
|
@@ -7,7 +7,7 @@ import db_attribute.connector as connector
|
|
|
7
7
|
import db_attribute.discriptor as discriptor
|
|
8
8
|
|
|
9
9
|
__all__ = ['DbAttribute', 'DbAttributeMetaclass', 'db_work', 'db_class', 'discriptor', 'connector', 'db_types']
|
|
10
|
-
__version__ = '2.1'
|
|
10
|
+
__version__ = '2.1.1.0'
|
|
11
11
|
|
|
12
12
|
class DbAttributeMetaclass(type):
|
|
13
13
|
dict_classes = db_types.DictClasses()
|
|
@@ -21,10 +21,15 @@ class DbAttributeMetaclass(type):
|
|
|
21
21
|
if not cheak_class_in_bases(bases, DbAttribute):
|
|
22
22
|
bases = (DbAttribute,) + bases
|
|
23
23
|
|
|
24
|
-
new_cls = super().__new__(cls, name, bases, namespace)
|
|
24
|
+
new_cls: DbAttribute = super().__new__(cls, name, bases, namespace)
|
|
25
25
|
|
|
26
26
|
params_for_metaclass = {'need_add_this_class_to_dict_classes': True, 'need_DbAttributeMetaclass': True}
|
|
27
|
-
options = {
|
|
27
|
+
options = {
|
|
28
|
+
'__dbworkobj__': db_types.NotSet,
|
|
29
|
+
'__max_repr_recursion_limit__': 10,
|
|
30
|
+
'__repr_class_name__': db_types.NotSet,
|
|
31
|
+
'__skip_dbworkobj__': False
|
|
32
|
+
}
|
|
28
33
|
|
|
29
34
|
__annotations__ = {}
|
|
30
35
|
__dict__ = {}
|
|
@@ -67,7 +72,7 @@ class DbAttributeMetaclass(type):
|
|
|
67
72
|
if options[i] is not db_types.NotSet:
|
|
68
73
|
setattr(new_cls, i, options[i])
|
|
69
74
|
|
|
70
|
-
if getattr(new_cls, '__dbworkobj__', None) is None:
|
|
75
|
+
if (getattr(new_cls, '__dbworkobj__', None) is None) and (not getattr(new_cls, '__skip_dbworkobj__', False)):
|
|
71
76
|
raise Exception(f'The "{new_cls.__name__}" class dosn\'t have "__dbworkobj__" parameter: set "__dbworkobj__" or "Meta", see documentation')
|
|
72
77
|
|
|
73
78
|
attr_names = list(__annotations__.keys())
|
|
@@ -87,6 +92,8 @@ class DbAttributeMetaclass(type):
|
|
|
87
92
|
|
|
88
93
|
if isinstance(attr_value, db_types.DbField):
|
|
89
94
|
db_field = attr_value
|
|
95
|
+
elif isinstance(attr_value, db_types.Factory):
|
|
96
|
+
db_field = db_types.DbField(default_factory=attr_value)
|
|
90
97
|
else:
|
|
91
98
|
db_field = db_types.DbField(default=attr_value)
|
|
92
99
|
|
|
@@ -95,7 +102,7 @@ class DbAttributeMetaclass(type):
|
|
|
95
102
|
if db_field.python_type is db_types.MISSING:
|
|
96
103
|
if db_field.default is not db_types.MISSING:
|
|
97
104
|
db_field.python_type = type(db_field.default)
|
|
98
|
-
elif db_field.default_factory is not db_types.MISSING:
|
|
105
|
+
elif db_field.default_factory is not db_types.MISSING: #the idea: is to add a parameter for metaclass so as not to call default_factory to determine the type of the variable.
|
|
99
106
|
db_field.python_type = type(db_field.default_factory.get_value())
|
|
100
107
|
if db_field.python_type is db_types.MISSING:
|
|
101
108
|
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)'
|
|
@@ -132,7 +139,8 @@ class DbAttributeMetaclass(type):
|
|
|
132
139
|
params_str = ', '.join(params)
|
|
133
140
|
|
|
134
141
|
init_code = (
|
|
135
|
-
f"def __init__(self, {params_str
|
|
142
|
+
f"def __init__(self, {params_str + ', ' if params_str else ''}id:int=db_types.NotSet, _dont_add_id:bool = False):\n"
|
|
143
|
+
" db_types.cheak_db_work_object(self.__class__)\n"
|
|
136
144
|
" now_locals = locals()\n"
|
|
137
145
|
" used_keys = now_locals\n"
|
|
138
146
|
" for i in ['self', 'id', '_dont_add_id']:\n"
|
|
@@ -167,8 +175,8 @@ class DbAttributeMetaclass(type):
|
|
|
167
175
|
if params_for_metaclass['need_add_this_class_to_dict_classes']:
|
|
168
176
|
cls.dict_classes.add(new_cls)
|
|
169
177
|
|
|
170
|
-
if not new_cls.
|
|
171
|
-
new_cls.
|
|
178
|
+
if not new_cls.__skip_dbworkobj__:
|
|
179
|
+
new_cls.register_dbworkobj(new_cls.__dbworkobj__)
|
|
172
180
|
|
|
173
181
|
return new_cls
|
|
174
182
|
|
|
@@ -181,6 +189,7 @@ class DbAttribute:
|
|
|
181
189
|
__dbworkobj__: ClassVar[db_work.Db_work] = None
|
|
182
190
|
__max_repr_recursion_limit__: ClassVar[int] = 10
|
|
183
191
|
__repr_class_name__: ClassVar[str] = db_types.NotSet
|
|
192
|
+
__skip_dbworkobj__: ClassVar[bool] = False
|
|
184
193
|
|
|
185
194
|
def __init__(self, *args, ID=None, **kwargs):
|
|
186
195
|
raise 'Need set metaclass=DbAttributeMetaclass'
|
|
@@ -190,7 +199,7 @@ class DbAttribute:
|
|
|
190
199
|
if now > self.__max_repr_recursion_limit__ or (self.id, self.__repr_class_name__) in Objs:
|
|
191
200
|
return f'{self.__repr_class_name__}(id={self.id}, ...)'
|
|
192
201
|
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__])})'
|
|
202
|
+
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'{repr(getattr(self, i))}'}" for i in self.__db_fields__])})'''
|
|
194
203
|
|
|
195
204
|
def _db_attribute_container_update(self, key, data=None):
|
|
196
205
|
"""
|
|
@@ -207,6 +216,7 @@ class DbAttribute:
|
|
|
207
216
|
|
|
208
217
|
@classmethod
|
|
209
218
|
def _db_attribute_found_ids_by_attribute(cls, attribute_name:str, attribute_value):
|
|
219
|
+
db_types.cheak_db_work_object(cls)
|
|
210
220
|
tempdata = cls.__dbworkobj__.found_ids_by_value(class_name=cls.__name__, attribute_name=attribute_name, data=attribute_value, _cls_dbattribute=cls)
|
|
211
221
|
if tempdata['status_code'] != 200:
|
|
212
222
|
return set()
|
|
@@ -237,6 +247,7 @@ class DbAttribute:
|
|
|
237
247
|
|
|
238
248
|
@classmethod
|
|
239
249
|
def get_all_ids(cls):
|
|
250
|
+
db_types.cheak_db_work_object(cls)
|
|
240
251
|
temp = cls.__dbworkobj__.get_all_ids(cls.__name__)
|
|
241
252
|
if temp['status_code'] != 200:
|
|
242
253
|
return db_types.Ids()
|
|
@@ -291,6 +302,7 @@ class DbAttribute:
|
|
|
291
302
|
|
|
292
303
|
@classmethod
|
|
293
304
|
def delete_objs(cls, IDs: set[int] | int, attributes:set[str]=None):
|
|
305
|
+
db_types.cheak_db_work_object(cls)
|
|
294
306
|
all_attributes = object.__getattribute__(cls, '__db_fields__')
|
|
295
307
|
attributes = all_attributes if attributes is None else attributes & all_attributes
|
|
296
308
|
IDs = {IDs} if isinstance(IDs, int) else IDs
|
|
@@ -318,3 +330,10 @@ class DbAttribute:
|
|
|
318
330
|
for key in kwargs:
|
|
319
331
|
res &= cls._db_attribute_found_ids_by_attribute(attribute_name=key, attribute_value=kwargs[key])
|
|
320
332
|
return res
|
|
333
|
+
|
|
334
|
+
@classmethod
|
|
335
|
+
def register_dbworkobj(cls, dbworkobj):
|
|
336
|
+
cls.__dbworkobj__ = dbworkobj
|
|
337
|
+
cls.__skip_dbworkobj__ = False
|
|
338
|
+
if not cls.__dbworkobj__.cheak_exists_id_table(cls.__name__):
|
|
339
|
+
cls.__dbworkobj__.create_id_table(cls.__name__)
|