db-attribute 2.1.1.1__tar.gz → 2.1.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.
- {db_attribute-2.1.1.1 → db_attribute-2.1.2}/PKG-INFO +196 -42
- {db_attribute-2.1.1.1 → db_attribute-2.1.2}/db_attribute/__init__.py +68 -15
- db_attribute-2.1.2/db_attribute/connector.py +228 -0
- {db_attribute-2.1.1.1 → db_attribute-2.1.2}/db_attribute/db_class.py +22 -11
- {db_attribute-2.1.1.1 → db_attribute-2.1.2}/db_attribute/db_types.py +53 -25
- {db_attribute-2.1.1.1 → db_attribute-2.1.2}/db_attribute/db_work.py +220 -88
- {db_attribute-2.1.1.1 → db_attribute-2.1.2}/db_attribute/discriptor.py +45 -24
- {db_attribute-2.1.1.1 → db_attribute-2.1.2}/pyproject.toml +3 -4
- {db_attribute-2.1.1.1 → db_attribute-2.1.2}/readme.md +194 -39
- {db_attribute-2.1.1.1 → db_attribute-2.1.2}/setup.py +2 -9
- db_attribute-2.1.1.1/db_attribute/connector.py +0 -31
- {db_attribute-2.1.1.1 → db_attribute-2.1.2}/LICENSE +0 -0
- {db_attribute-2.1.1.1 → db_attribute-2.1.2}/MANIFEST.in +0 -0
- {db_attribute-2.1.1.1 → db_attribute-2.1.2}/db_attribute.egg-info/SOURCES.txt +0 -0
- {db_attribute-2.1.1.1 → db_attribute-2.1.2}/requirements.txt +0 -0
- {db_attribute-2.1.1.1 → db_attribute-2.1.2}/setup.cfg +0 -0
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: db_attribute
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.2
|
|
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
|
-
License: MIT
|
|
8
|
+
License-Expression: MIT
|
|
9
9
|
Project-URL: Homepage, https://github.com/shutkanos/Db-Attribute
|
|
10
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,orm,ORM,attribute orm,attribute ORM,attr,attr ORM,attr orm,db attr,DbAttr
|
|
13
13
|
Classifier: Development Status :: 5 - Production/Stable
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
15
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
16
15
|
Classifier: Programming Language :: Python :: 3
|
|
17
16
|
Requires-Python: >=3.9
|
|
18
17
|
Description-Content-Type: text/markdown
|
|
19
18
|
License-File: LICENSE
|
|
20
|
-
Requires-Dist: mysql-connector-python>=
|
|
19
|
+
Requires-Dist: mysql-connector-python>=8.4.0
|
|
21
20
|
Requires-Dist: orjson
|
|
22
21
|
Dynamic: author
|
|
23
22
|
Dynamic: home-page
|
|
@@ -57,8 +56,13 @@ The library provides tools for declarative model definition, relationship manage
|
|
|
57
56
|
* [Supported types](#supported-types)
|
|
58
57
|
* [Install](#install)
|
|
59
58
|
* [How to use it](#how-to-use-it)
|
|
59
|
+
* [Connect to DB](#connect-to-db)
|
|
60
|
+
* [MySQL](#mysql)
|
|
61
|
+
* [SQLite](#sqlite)
|
|
60
62
|
* [Create class](#create-class)
|
|
61
63
|
* [Options](#options)
|
|
64
|
+
* [Register db work object](#register-db-work-object)
|
|
65
|
+
* [Class inheritance](#class-inheritance)
|
|
62
66
|
* [Work with obj](#work-with-obj)
|
|
63
67
|
* [Create new object](#create-new-object)
|
|
64
68
|
* [Finding objects](#finding-objects)
|
|
@@ -66,6 +70,7 @@ The library provides tools for declarative model definition, relationship manage
|
|
|
66
70
|
* [Change attribute of obj](#change-attribute-of-obj)
|
|
67
71
|
* [Dump mode](#dump-mode)
|
|
68
72
|
* [Types](#types)
|
|
73
|
+
* [DbField](#dbfield)
|
|
69
74
|
* [Db attribute](#db-attribute)
|
|
70
75
|
* [Db classes](#db-classes)
|
|
71
76
|
* [Custom Db Classes](#custom-db-classes)
|
|
@@ -97,6 +102,37 @@ pip install git+https://github.com/shutkanos/Db-Attribute.git
|
|
|
97
102
|
|
|
98
103
|
# How to use it
|
|
99
104
|
|
|
105
|
+
## Connect to DB
|
|
106
|
+
|
|
107
|
+
### MySQL
|
|
108
|
+
Connect to MySQL database by providing host, credentials, and database name:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from db_attribute import db_work, connector
|
|
112
|
+
|
|
113
|
+
connect_obj = connector.MySQLConnection(host='localhost', user='root', password='password', database='mydb')
|
|
114
|
+
db_work_obj = db_work.Db_work(connect_obj)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### SQLite
|
|
118
|
+
Connect to SQLite database (local file or in-memory):
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from db_attribute import db_work, connector
|
|
122
|
+
|
|
123
|
+
# File-based database
|
|
124
|
+
connect_obj = connector.SQLiteConnection('/path/to/database.db')
|
|
125
|
+
db_work_obj = db_work.Db_work(connect_obj)
|
|
126
|
+
|
|
127
|
+
# In-memory database
|
|
128
|
+
connect_obj = connector.SQLiteConnection(':memory:')
|
|
129
|
+
db_work_obj = db_work.Db_work(connect_obj)
|
|
130
|
+
|
|
131
|
+
# With specific options
|
|
132
|
+
connect_obj = connector.SQLiteConnection('/path/to/database.db', timeout=10)
|
|
133
|
+
db_work_obj = db_work.Db_work(connect_obj)
|
|
134
|
+
```
|
|
135
|
+
|
|
100
136
|
## Create class
|
|
101
137
|
|
|
102
138
|
To create any class (Table):
|
|
@@ -110,16 +146,17 @@ To create any class (Table):
|
|
|
110
146
|
from db_attribute import DbAttribute, DbAttributeMetaclass, db_work, connector
|
|
111
147
|
from db_attribute.db_types import DbField
|
|
112
148
|
|
|
113
|
-
connect_obj = connector.
|
|
149
|
+
connect_obj = connector.MySQLConnection(host='localhost', user='root', password='password', database='mydb')
|
|
114
150
|
db_work_obj = db_work.Db_work(connect_obj)
|
|
115
151
|
|
|
152
|
+
|
|
116
153
|
class User(DbAttribute, metaclass=DbAttributeMetaclass, __dbworkobj__=db_work_obj):
|
|
117
|
-
name: str = DbField(default='NotSet')
|
|
118
|
-
age: int = -1
|
|
119
|
-
ban = DbField(default=False)
|
|
120
|
-
other_int_information = 100
|
|
121
|
-
list_of_books = DbField(default_factory=lambda: ['name of first book'])
|
|
122
|
-
settings: dict = DbField(default_factory=dict)
|
|
154
|
+
name: str = DbField(default='NotSet') # Ok
|
|
155
|
+
age: int = -1 # Ok
|
|
156
|
+
ban = DbField(default=False) # Ok
|
|
157
|
+
other_int_information = 100 # Need annotation or DbField - not error, but not saved
|
|
158
|
+
list_of_books = DbField(default_factory=lambda: ['name of first book']) # Ok
|
|
159
|
+
settings: dict = DbField(default_factory=dict) # Ok
|
|
123
160
|
```
|
|
124
161
|
|
|
125
162
|
Each instance has a unique `id` identifier. It is inherited from DbAttribute and stored in `__dict__`
|
|
@@ -155,6 +192,96 @@ All options:
|
|
|
155
192
|
* `__dbworkobj__` - database work object (required parameter),
|
|
156
193
|
* `__max_repr_recursion_limit__` - maximum recursion limit for `__repr__` of DbAttribute
|
|
157
194
|
* `__repr_class_name__` - sets the name of this class when using the method `__repr__` of DbAttribute
|
|
195
|
+
* `__table_name__` - sets custom table name instead of the class name. By default, it is inherited (like any other class attribute), so child classes automatically share the same tables as the parent.
|
|
196
|
+
|
|
197
|
+
### Register db work object
|
|
198
|
+
|
|
199
|
+
If `dbworkobj` is not available at the time of class definition, you can defer registration using one of two approaches.
|
|
200
|
+
|
|
201
|
+
#### Using `__skip_dbworkobj__ = True`
|
|
202
|
+
|
|
203
|
+
Set `__skip_dbworkobj__ = True` in `Meta`, then call `register_dbworkobj()` on each class individually:
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
class BaseMeta:
|
|
207
|
+
__skip_dbworkobj__ = True
|
|
208
|
+
|
|
209
|
+
class User(DbAttribute, metaclass=DbAttributeMetaclass):
|
|
210
|
+
Meta = BaseMeta
|
|
211
|
+
|
|
212
|
+
class Book(DbAttribute, metaclass=DbAttributeMetaclass):
|
|
213
|
+
Meta = BaseMeta
|
|
214
|
+
|
|
215
|
+
User.register_dbworkobj(dbworkobj)
|
|
216
|
+
Book.register_dbworkobj(dbworkobj)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
#### Using `DbWorkMarker` (recommended for multiple classes)
|
|
220
|
+
|
|
221
|
+
Define a marker with a group name, place it in `Meta.__dbworkobj__`, then connect all classes at once via `DbWorkManager`:
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
from db_attribute.db_types import DbWorkMarker, DbWorkManager
|
|
225
|
+
|
|
226
|
+
class MainMeta:
|
|
227
|
+
__dbworkobj__ = DbWorkMarker('main')
|
|
228
|
+
|
|
229
|
+
class User(DbAttribute, metaclass=DbAttributeMetaclass):
|
|
230
|
+
Meta = MainMeta
|
|
231
|
+
|
|
232
|
+
class Book(DbAttribute, metaclass=DbAttributeMetaclass):
|
|
233
|
+
Meta = MainMeta
|
|
234
|
+
|
|
235
|
+
DbWorkManager.connect('main', dbworkobj)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Class inheritance
|
|
239
|
+
|
|
240
|
+
A developer can create a child class from an existing DbAttribute class. The child class stores **all its fields** (including inherited ones) in its own separate database tables, fully isolated from the parent's tables.
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
from db_attribute.db_types import DbField
|
|
244
|
+
|
|
245
|
+
class UserBase(DbAttribute, metaclass=DbAttributeMetaclass):
|
|
246
|
+
Meta = BaseMeta
|
|
247
|
+
nameuser: str = DbField(default="")
|
|
248
|
+
rank: str = DbField(default="User")
|
|
249
|
+
|
|
250
|
+
def promote(self):
|
|
251
|
+
self.rank = 'Admin'
|
|
252
|
+
|
|
253
|
+
class UserChild(UserBase):
|
|
254
|
+
some_data: int = DbField(default=0)
|
|
255
|
+
|
|
256
|
+
user1 = UserChild(id=1, nameuser="Oleg", some_data=1)
|
|
257
|
+
user2 = UserChild(id=2, nameuser="Bob", some_data=2)
|
|
258
|
+
print(user1) # UserChild(id=1, nameuser='Oleg', rank='User', some_data=1)
|
|
259
|
+
print(user2) # UserChild(id=2, nameuser='Bob', rank='User', some_data=2)
|
|
260
|
+
user2.promote()
|
|
261
|
+
print(user2) # UserChild(id=2, nameuser='Bob', rank='Admin', some_data=2)
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Key points:
|
|
265
|
+
|
|
266
|
+
* The child class inherits all fields and methods from the parent
|
|
267
|
+
* No need to repeat `metaclass=DbAttributeMetaclass` — it is inherited automatically
|
|
268
|
+
* The child uses the same `dbworkobj` as the parent by default; a different one can be set via `Meta`
|
|
269
|
+
* `UserBase` and `UserChild` are completely independent in the database: `UserBase(id=1)` and `UserChild(id=1)` are different records stored in different tables
|
|
270
|
+
* Methods defined in the parent work correctly on child instances and write to the child's own tables:
|
|
271
|
+
|
|
272
|
+
```python
|
|
273
|
+
user1.promote()
|
|
274
|
+
print(user1.rank) # 'Admin' — written to table 'cls userchild atr rank', not 'cls userbase atr rank'
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
To use a different `dbworkobj` for the child class, set it via `Meta`:
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
class UserPremium(UserBase):
|
|
281
|
+
class Meta:
|
|
282
|
+
__dbworkobj__ = other_dbworkobj
|
|
283
|
+
premium_level: int = DbField(default=1)
|
|
284
|
+
```
|
|
158
285
|
|
|
159
286
|
## Work with obj
|
|
160
287
|
|
|
@@ -165,6 +292,9 @@ To create an object, use an id (optional) and other fields (optional),
|
|
|
165
292
|
```python
|
|
166
293
|
obj = User(id=3) # other field set to defaults value
|
|
167
294
|
print(obj) # User(id=3, name=*default value*)
|
|
295
|
+
# or:
|
|
296
|
+
obj = User.get(id=3)
|
|
297
|
+
print(obj) # User(id=3, name=*default value*)
|
|
168
298
|
```
|
|
169
299
|
```python
|
|
170
300
|
obj = User(name='Ben', id=3)
|
|
@@ -181,19 +311,19 @@ If a developer needs to recreate an object, he can call DbAttribute cls with id.
|
|
|
181
311
|
|
|
182
312
|
```python
|
|
183
313
|
obj = User(name='Ben', age=20, id=3) #insert obj to db
|
|
184
|
-
print(obj) #User(id=3, name='Ben', age=20)
|
|
314
|
+
print(obj) # User(id=3, name='Ben', age=20)
|
|
185
315
|
|
|
186
316
|
obj = User(id=3)
|
|
187
|
-
print(obj) #User(id=3, name='Ben', age=20)
|
|
317
|
+
print(obj) # User(id=3, name='Ben', age=20)
|
|
188
318
|
|
|
189
319
|
obj = User('Anna', id=3)
|
|
190
|
-
print(obj) #User(id=3, name='Anna', age=20)
|
|
320
|
+
print(obj) # User(id=3, name='Anna', age=20)
|
|
191
321
|
|
|
192
322
|
obj = User(age=25, id=3)
|
|
193
|
-
print(obj) #User(id=3, name='Anna', age=25)
|
|
323
|
+
print(obj) # User(id=3, name='Anna', age=25)
|
|
194
324
|
|
|
195
325
|
obj = User(id=3)
|
|
196
|
-
print(obj) #User(id=3, name='Anna', age=25)
|
|
326
|
+
print(obj) # User(id=3, name='Anna', age=25)
|
|
197
327
|
```
|
|
198
328
|
|
|
199
329
|
### Finding objects
|
|
@@ -211,18 +341,21 @@ obj = User(name='Bob', age=20, id=1)
|
|
|
211
341
|
obj = User(name='Bob', age=30, id=2)
|
|
212
342
|
obj = User(name='Anna', age=20, id=3)
|
|
213
343
|
#finds objs
|
|
214
|
-
print(User.get(id=2)) #User(id=2, name='Bob', age=30)
|
|
215
|
-
print(User.get((User.age == 30) & (User.name == 'Bob')))#User(id=2, name='Bob', age=30)
|
|
216
|
-
print(User.get(User.name == 'Anna')) #User(id=3, name='Anna', age=20)
|
|
217
|
-
print(User.get(User.name == 'Bob')) #User(id=1, name='Bob', age=20)
|
|
218
|
-
print(User.get(User.name == 'Other name')) #None
|
|
344
|
+
print(User.get(id=2)) # User(id=2, name='Bob', age=30)
|
|
345
|
+
print(User.get((User.age == 30) & (User.name == 'Bob')))# User(id=2, name='Bob', age=30)
|
|
346
|
+
print(User.get(User.name == 'Anna')) # User(id=3, name='Anna', age=20)
|
|
347
|
+
print(User.get(User.name == 'Bob')) # User(id=1, name='Bob', age=20)
|
|
348
|
+
print(User.get(User.name == 'Other name')) # None
|
|
349
|
+
#finds all objs
|
|
350
|
+
print(User.gets(User.name == 'Bob')) # [User(id=1, name='Bob', age=20), User(id=2, name='Bob', age=30)]
|
|
351
|
+
print(User.gets([2, 3])) # [User(id=2, name='Bob', age=30), User(id=3, name='Anna', age=20)]
|
|
219
352
|
```
|
|
220
353
|
|
|
221
354
|
To check the correctness of writing a logical expression, you can:
|
|
222
355
|
|
|
223
356
|
```python
|
|
224
|
-
print(User.name == 'Anna') #(User.name = 'Anna')
|
|
225
|
-
print((User.age == 30) & (User.name == 'Bob')) #((User.age = 30) and (User.name = 'Bob'))
|
|
357
|
+
print(User.name == 'Anna') # (User.name = 'Anna')
|
|
358
|
+
print((User.age == 30) & (User.name == 'Bob')) # ((User.age = 30) and (User.name = 'Bob'))
|
|
226
359
|
```
|
|
227
360
|
|
|
228
361
|
Use '&' and '|' instead of the 'and' and 'or' operators. The 'and' and 'or' operators are not supported
|
|
@@ -233,16 +366,16 @@ If a developer needs to iterate through all the elements of a class, they can us
|
|
|
233
366
|
|
|
234
367
|
```python
|
|
235
368
|
print(list(User))
|
|
236
|
-
#[User(id=1, name='Bob', age=30), User(id=2, name='Bob', age=20), User(id=3, name='Anna', age=20)]
|
|
369
|
+
# [User(id=1, name='Bob', age=30), User(id=2, name='Bob', age=20), User(id=3, name='Anna', age=20)]
|
|
237
370
|
|
|
238
371
|
print([i for i in User if i.age < 30])
|
|
239
|
-
#[User(id=2, name='Bob', age=20), User(id=3, name='Anna', age=20)]
|
|
372
|
+
# [User(id=2, name='Bob', age=20), User(id=3, name='Anna', age=20)]
|
|
240
373
|
|
|
241
374
|
for i in User:
|
|
242
375
|
print(i)
|
|
243
|
-
#User(id=1, name='Bob', age=30)
|
|
244
|
-
#User(id=2, name='Bob', age=20)
|
|
245
|
-
#User(id=3, name='Anna', age=20)
|
|
376
|
+
# User(id=1, name='Bob', age=30)
|
|
377
|
+
# User(id=2, name='Bob', age=20)
|
|
378
|
+
# User(id=3, name='Anna', age=20)
|
|
246
379
|
```
|
|
247
380
|
⚠️ Iterations loads all objects - not recommended for large tables
|
|
248
381
|
|
|
@@ -251,12 +384,12 @@ for i in User:
|
|
|
251
384
|
```python
|
|
252
385
|
obj = User(name='Bob', list_of_books=[], id=1)
|
|
253
386
|
|
|
254
|
-
print(obj) #User(id=1, name='Bob', list_of_books=[])
|
|
387
|
+
print(obj) # User(id=1, name='Bob', list_of_books=[])
|
|
255
388
|
|
|
256
389
|
obj.name = 'Anna'
|
|
257
390
|
obj.list_of_books.append('Any name of book')
|
|
258
391
|
|
|
259
|
-
print(obj) #User(id=1, name='Anna', list_of_books=['Any name of book'])
|
|
392
|
+
print(obj) # User(id=1, name='Anna', list_of_books=['Any name of book'])
|
|
260
393
|
```
|
|
261
394
|
|
|
262
395
|
### Dump mode
|
|
@@ -311,6 +444,27 @@ user.set_auto_dump_mode()
|
|
|
311
444
|
|
|
312
445
|
## Types
|
|
313
446
|
|
|
447
|
+
### DbField
|
|
448
|
+
|
|
449
|
+
Dbfield is used to configure fields, namely:
|
|
450
|
+
|
|
451
|
+
* `default` (Any): the default value of this Field (default takes precedence over the default_factory)
|
|
452
|
+
* `default_factory` (Any): the default factory of this Field
|
|
453
|
+
* `python_type` (Any): python type of data, example: str, int (python_type takes precedence over the data type specified in the annotation)
|
|
454
|
+
* `mysql_type` (str): mysql type of data, example: 'varchar(50)', 'bigint'
|
|
455
|
+
* `repr` (bool): Include field in `__repr__()` output
|
|
456
|
+
* `init` (bool): Include field in constructor (`__init__()`)
|
|
457
|
+
* `search_default` (bool): When True, applies default value during searches if record is missing in this field's table (Use this parameter if you understand what it is responsible for.)
|
|
458
|
+
|
|
459
|
+
For example:
|
|
460
|
+
|
|
461
|
+
```python
|
|
462
|
+
class User(DbAttribute, metaclass=DbAttributeMetaclass):
|
|
463
|
+
Meta = BaseMeta
|
|
464
|
+
name: str = DbField(default='NotSet', mysql_type='varchar(32)')
|
|
465
|
+
hash: str = DbField(default_factory=lambda: str(uuid.uuid4()), mysql_type='varchar(512)', repr=False, init=False, search_default=False)
|
|
466
|
+
```
|
|
467
|
+
|
|
314
468
|
### Db attribute
|
|
315
469
|
|
|
316
470
|
A developer can set the Db attribute class as data type for another Db attribute class
|
|
@@ -330,20 +484,20 @@ To create an object:
|
|
|
330
484
|
```python
|
|
331
485
|
obj_a = Class_A(id=15, name='Anna', obj_b=1)
|
|
332
486
|
obj_b = Class_B(id=1, name='Bob', obj_a=15)
|
|
333
|
-
print(obj_b) #Class_B(id=1, name='Bob', obj_a=Class_A(id=15, name='Anna', obj_b=Class_B(id=1, ...)))
|
|
487
|
+
print(obj_b) # Class_B(id=1, name='Bob', obj_a=Class_A(id=15, name='Anna', obj_b=Class_B(id=1, ...)))
|
|
334
488
|
#or
|
|
335
489
|
obj_a = Class_A(id=15, name='Anna', obj_b=obj_b)
|
|
336
|
-
print(obj_a) #Class_A(id=15, name='Anna', obj_b=Class_B(id=1, name='Bob', obj_a=Class_A(id=15, ...)))
|
|
490
|
+
print(obj_a) # Class_A(id=15, name='Anna', obj_b=Class_B(id=1, name='Bob', obj_a=Class_A(id=15, ...)))
|
|
337
491
|
```
|
|
338
492
|
For found obj:
|
|
339
493
|
```python
|
|
340
494
|
Class_A(id=15, name='Anna', obj_b=1)
|
|
341
495
|
obj = Class_B(id=1, name='Bob', obj_a=15)
|
|
342
496
|
obj = Class_A.get(Class_A.obj_b == obj)
|
|
343
|
-
print(obj) #Class_A(id=15, name='Anna', obj_b=Class_B(id=1, name='Bob', obj_a=Class_A(id=15, ...)))
|
|
344
|
-
#And Found with use id of obj:
|
|
497
|
+
print(obj) # Class_A(id=15, name='Anna', obj_b=Class_B(id=1, name='Bob', obj_a=Class_A(id=15, ...)))
|
|
498
|
+
# And Found with use id of obj:
|
|
345
499
|
obj = Class_A.get(Class_A.obj_b == 1)
|
|
346
|
-
print(obj) #Class_A(id=15, name='Anna', obj_b=Class_B(id=1, name='Bob', obj_a=Class_A(id=15, ...)))
|
|
500
|
+
print(obj) # Class_A(id=15, name='Anna', obj_b=Class_B(id=1, name='Bob', obj_a=Class_A(id=15, ...)))
|
|
347
501
|
```
|
|
348
502
|
One-to-Many relationship:
|
|
349
503
|
```python
|
|
@@ -363,29 +517,29 @@ author = Author(name="George Orwell")
|
|
|
363
517
|
book = Book(title="1984", author=author)
|
|
364
518
|
author.books.append(book)
|
|
365
519
|
|
|
366
|
-
print(author) #Author(id=1, name='George Orwell', books=[Book(id=1, title='1984', author=Author(id=1, ...))])
|
|
367
|
-
print(book)
|
|
520
|
+
print(author) # Author(id=1, name='George Orwell', books=[Book(id=1, title='1984', author=Author(id=1, ...))])
|
|
521
|
+
print(book) # Book(id=1, title='1984', author=Author(id=1, name='George Orwell', books=[Book(id=1, ...)]))
|
|
368
522
|
```
|
|
369
523
|
|
|
370
524
|
### Db classes
|
|
371
525
|
When collections are stored in memory, they converted to Db classes
|
|
372
526
|
```python
|
|
373
527
|
obj = User(1, list_of_books=[1, 2, 3])
|
|
374
|
-
print(type(obj.list_of_books)) #DbList
|
|
528
|
+
print(type(obj.list_of_books)) # DbList
|
|
375
529
|
```
|
|
376
530
|
```python
|
|
377
531
|
obj = User(1, times=[datetime(2024, 1, 1)])
|
|
378
|
-
print(type(obj.times[0])) #DbDatetime
|
|
532
|
+
print(type(obj.times[0])) # DbDatetime
|
|
379
533
|
```
|
|
380
534
|
And when collections dumped to db, they converted to json
|
|
381
535
|
```python
|
|
382
536
|
obj = User(1, list_of_books=[1, 2, 3])
|
|
383
|
-
print(obj.list_of_books.dumps()) #{"t": "DbList", "d": [1, 2, 3]}
|
|
537
|
+
print(obj.list_of_books.dumps()) # {"t": "DbList", "d": [1, 2, 3]}
|
|
384
538
|
```
|
|
385
539
|
```python
|
|
386
540
|
obj = User(1, times=[datetime(2024, 1, 1), datetime(2027, 7, 7)])
|
|
387
541
|
print(obj.list_of_books.dumps())
|
|
388
|
-
#{"t": "DbList", "d": [{"t": "DbDatetime", "d": "2024-01-01T00:00:00"}, {"t": "DbDatetime", "d": "2027-07-07T00:00:00"}]}
|
|
542
|
+
# {"t": "DbList", "d": [{"t": "DbDatetime", "d": "2024-01-01T00:00:00"}, {"t": "DbDatetime", "d": "2027-07-07T00:00:00"}]}
|
|
389
543
|
```
|
|
390
544
|
|
|
391
545
|
### Custom Db Classes
|
|
@@ -477,6 +631,7 @@ obj.settings = {1: 3} # changed
|
|
|
477
631
|
print(obj.settings) #{'1': 3}
|
|
478
632
|
```
|
|
479
633
|
|
|
634
|
+
|
|
480
635
|
# Speed Test
|
|
481
636
|
|
|
482
637
|
The execution speed may vary from computer to computer, so you need to focus on the specified number of operations per second of a regular mysql
|
|
@@ -513,4 +668,3 @@ JsonType | 7297 op/sec | -14%
|
|
|
513
668
|
# Data base
|
|
514
669
|
|
|
515
670
|
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>
|
|
516
|
-
|
|
@@ -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.2'
|
|
11
11
|
|
|
12
12
|
class DbAttributeMetaclass(type):
|
|
13
13
|
dict_classes = db_types.DictClasses()
|
|
@@ -28,7 +28,8 @@ class DbAttributeMetaclass(type):
|
|
|
28
28
|
'__dbworkobj__': db_types.NotSet,
|
|
29
29
|
'__max_repr_recursion_limit__': 10,
|
|
30
30
|
'__repr_class_name__': db_types.NotSet,
|
|
31
|
-
'__skip_dbworkobj__': False
|
|
31
|
+
'__skip_dbworkobj__': False,
|
|
32
|
+
'__table_name__': db_types.NotSet
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
__annotations__ = {}
|
|
@@ -66,12 +67,24 @@ class DbAttributeMetaclass(type):
|
|
|
66
67
|
if not params_for_metaclass['need_DbAttributeMetaclass']:
|
|
67
68
|
return new_cls
|
|
68
69
|
|
|
69
|
-
new_cls.__repr_class_name__ = name
|
|
70
|
-
|
|
71
70
|
for i in options:
|
|
72
71
|
if options[i] is not db_types.NotSet:
|
|
73
72
|
setattr(new_cls, i, options[i])
|
|
74
73
|
|
|
74
|
+
if getattr(new_cls, '__table_name__', db_types.NotSet) is db_types.NotSet:
|
|
75
|
+
new_cls.__table_name__ = name
|
|
76
|
+
|
|
77
|
+
dbworkobj_value = getattr(new_cls, '__dbworkobj__', None)
|
|
78
|
+
if isinstance(dbworkobj_value, db_types.DbWorkMarker):
|
|
79
|
+
db_types.DbWorkManager.register_class(dbworkobj_value.group_id, new_cls)
|
|
80
|
+
new_cls.__skip_dbworkobj__ = True
|
|
81
|
+
|
|
82
|
+
if '__repr_class_name__' not in kwargs:
|
|
83
|
+
own_meta = namespace.get('Meta', None)
|
|
84
|
+
own_meta_has_repr = own_meta is not None and '__repr_class_name__' in getattr(own_meta, '__dict__', {})
|
|
85
|
+
if not own_meta_has_repr:
|
|
86
|
+
new_cls.__repr_class_name__ = name
|
|
87
|
+
|
|
75
88
|
if (getattr(new_cls, '__dbworkobj__', None) is None) and (not getattr(new_cls, '__skip_dbworkobj__', False)):
|
|
76
89
|
raise Exception(f'The "{new_cls.__name__}" class dosn\'t have "__dbworkobj__" parameter: set "__dbworkobj__" or "Meta", see documentation')
|
|
77
90
|
|
|
@@ -90,6 +103,11 @@ class DbAttributeMetaclass(type):
|
|
|
90
103
|
if get_origin(attr_type) is ClassVar:
|
|
91
104
|
continue
|
|
92
105
|
|
|
106
|
+
if isinstance(attr_value, discriptor.DbAttributeDiscriptor):
|
|
107
|
+
if attr_name in __db_fields__:
|
|
108
|
+
continue
|
|
109
|
+
attr_value = db_types.MISSING
|
|
110
|
+
|
|
93
111
|
if isinstance(attr_value, db_types.DbField):
|
|
94
112
|
db_field = attr_value
|
|
95
113
|
elif isinstance(attr_value, db_types.Factory):
|
|
@@ -145,14 +163,14 @@ class DbAttributeMetaclass(type):
|
|
|
145
163
|
" used_keys = now_locals\n"
|
|
146
164
|
" for i in ['self', 'id', '_dont_add_id']:\n"
|
|
147
165
|
" used_keys.pop(i)\n"
|
|
148
|
-
" if not self.__dbworkobj__.cheak_exists_id_table(self.__class__.
|
|
149
|
-
" self.__dbworkobj__.create_id_table(self.__class__.
|
|
166
|
+
" if not self.__dbworkobj__.cheak_exists_id_table(self.__class__.__table_name__):\n"
|
|
167
|
+
" self.__dbworkobj__.create_id_table(self.__class__.__table_name__)\n"
|
|
150
168
|
" if isinstance(id, db_types.Id):\n"
|
|
151
169
|
" id = id.Id\n"
|
|
152
170
|
" if id is db_types.NotSet:\n"
|
|
153
|
-
" id = self.__dbworkobj__.get_new_id(self.__class__.
|
|
171
|
+
" id = self.__dbworkobj__.get_new_id(self.__class__.__table_name__)['data']\n"
|
|
154
172
|
" elif not (_dont_add_id and len([i for i in used_keys if used_keys[i] is not db_types.NotSet]) == 0):\n"
|
|
155
|
-
" self.__dbworkobj__.add_id(self.__class__.
|
|
173
|
+
" self.__dbworkobj__.add_id(self.__class__.__table_name__, id)\n"
|
|
156
174
|
" setattr(self, 'id', id)\n"
|
|
157
175
|
" for name in __db_fields__:\n"
|
|
158
176
|
" value = used_keys[name]\n"
|
|
@@ -190,6 +208,7 @@ class DbAttribute:
|
|
|
190
208
|
__max_repr_recursion_limit__: ClassVar[int] = 10
|
|
191
209
|
__repr_class_name__: ClassVar[str] = db_types.NotSet
|
|
192
210
|
__skip_dbworkobj__: ClassVar[bool] = False
|
|
211
|
+
__table_name__: ClassVar[str] = db_types.NotSet
|
|
193
212
|
|
|
194
213
|
def __init__(self, *args, ID=None, **kwargs):
|
|
195
214
|
raise 'Need set metaclass=DbAttributeMetaclass'
|
|
@@ -199,7 +218,7 @@ class DbAttribute:
|
|
|
199
218
|
if now > self.__max_repr_recursion_limit__ or (self.id, self.__repr_class_name__) in Objs:
|
|
200
219
|
return f'{self.__repr_class_name__}(id={self.id}, ...)'
|
|
201
220
|
Objs.add((self.id, self.__repr_class_name__))
|
|
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__])})'''
|
|
221
|
+
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__ if self.__db_fields__[i].repr])})'''
|
|
203
222
|
|
|
204
223
|
def _db_attribute_container_update(self, key, data=None):
|
|
205
224
|
"""
|
|
@@ -217,7 +236,7 @@ class DbAttribute:
|
|
|
217
236
|
@classmethod
|
|
218
237
|
def _db_attribute_found_ids_by_attribute(cls, attribute_name:str, attribute_value):
|
|
219
238
|
db_types.cheak_db_work_object(cls)
|
|
220
|
-
tempdata = cls.__dbworkobj__.found_ids_by_value(class_name=cls.
|
|
239
|
+
tempdata = cls.__dbworkobj__.found_ids_by_value(class_name=cls.__table_name__, attribute_name=attribute_name, data=attribute_value, _cls_dbattribute=cls)
|
|
221
240
|
if tempdata['status_code'] != 200:
|
|
222
241
|
return set()
|
|
223
242
|
return tempdata['data']
|
|
@@ -237,22 +256,56 @@ class DbAttribute:
|
|
|
237
256
|
if not Ids:
|
|
238
257
|
return None
|
|
239
258
|
id = Ids.list_ids[0]
|
|
259
|
+
if isinstance(id, db_types.Ids) and len(id) > 1:
|
|
260
|
+
id = id[0]
|
|
240
261
|
if isinstance(id, db_types.Id):
|
|
241
262
|
id = id.Id
|
|
242
|
-
if isinstance(id, int)
|
|
263
|
+
if isinstance(id, int):
|
|
243
264
|
obj = cls.__new__(cls)
|
|
244
265
|
obj.id = id
|
|
245
266
|
return obj
|
|
246
267
|
return None
|
|
247
268
|
|
|
269
|
+
@classmethod
|
|
270
|
+
def gets(cls, ids):
|
|
271
|
+
"""
|
|
272
|
+
return List of objects
|
|
273
|
+
:type ids: List[int | db_types.Id] | discriptor.Condition | db_types.Ids
|
|
274
|
+
:return: List[obj]
|
|
275
|
+
"""
|
|
276
|
+
if isinstance(ids, discriptor.Condition):
|
|
277
|
+
ids = ids.found()
|
|
278
|
+
if isinstance(ids, db_types.Ids):
|
|
279
|
+
ids = list(ids)
|
|
280
|
+
if not (isinstance(ids, list)) or isinstance(ids, db_types.Ids):
|
|
281
|
+
return []
|
|
282
|
+
res = []
|
|
283
|
+
for id in ids:
|
|
284
|
+
if isinstance(id, db_types.Id):
|
|
285
|
+
id = id.Id
|
|
286
|
+
if isinstance(id, int):
|
|
287
|
+
obj = cls.__new__(cls)
|
|
288
|
+
obj.id = id
|
|
289
|
+
res.append(obj)
|
|
290
|
+
return res
|
|
291
|
+
|
|
248
292
|
@classmethod
|
|
249
293
|
def get_all_ids(cls):
|
|
250
294
|
db_types.cheak_db_work_object(cls)
|
|
251
|
-
temp = cls.__dbworkobj__.get_all_ids(cls.
|
|
295
|
+
temp = cls.__dbworkobj__.get_all_ids(cls.__table_name__)
|
|
252
296
|
if temp['status_code'] != 200:
|
|
253
297
|
return db_types.Ids()
|
|
254
298
|
return db_types.Ids(temp['data'])
|
|
255
299
|
|
|
300
|
+
def get_attribute(self, attribute_name, attribute_type=None):
|
|
301
|
+
cls = object.__getattribute__(self, '__class__')
|
|
302
|
+
db_field = object.__getattribute__(self, '__db_fields__').get(attribute_name, None)
|
|
303
|
+
if db_field is None:
|
|
304
|
+
return
|
|
305
|
+
if attribute_type is None:
|
|
306
|
+
attribute_type = db_field.python_type
|
|
307
|
+
return cls.__dict__[attribute_name].get(self, attribute_type=attribute_type)
|
|
308
|
+
|
|
256
309
|
def dump(self, attributes:set[str]=None):
|
|
257
310
|
"""
|
|
258
311
|
Use it func, if you need dump the data to db, with manual_dump_mode
|
|
@@ -307,7 +360,7 @@ class DbAttribute:
|
|
|
307
360
|
attributes = all_attributes if attributes is None else attributes & all_attributes
|
|
308
361
|
IDs = {IDs} if isinstance(IDs, int) else IDs
|
|
309
362
|
dbworkobj = cls.__dbworkobj__
|
|
310
|
-
clsname = cls.
|
|
363
|
+
clsname = cls.__table_name__
|
|
311
364
|
for ID in IDs:
|
|
312
365
|
for db_attr in attributes:
|
|
313
366
|
dbworkobj.del_attribute_value(class_name=clsname, attribute_name=db_attr, ID=ID)
|
|
@@ -335,5 +388,5 @@ class DbAttribute:
|
|
|
335
388
|
def register_dbworkobj(cls, dbworkobj):
|
|
336
389
|
cls.__dbworkobj__ = dbworkobj
|
|
337
390
|
cls.__skip_dbworkobj__ = False
|
|
338
|
-
if not cls.__dbworkobj__.cheak_exists_id_table(cls.
|
|
339
|
-
cls.__dbworkobj__.create_id_table(cls.
|
|
391
|
+
if not cls.__dbworkobj__.cheak_exists_id_table(cls.__table_name__):
|
|
392
|
+
cls.__dbworkobj__.create_id_table(cls.__table_name__)
|