db-attribute 2.1.1.0__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.0 → db_attribute-2.1.2}/PKG-INFO +204 -49
- {db_attribute-2.1.1.0 → 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.0 → db_attribute-2.1.2}/db_attribute/db_class.py +22 -11
- {db_attribute-2.1.1.0 → db_attribute-2.1.2}/db_attribute/db_types.py +54 -26
- {db_attribute-2.1.1.0 → db_attribute-2.1.2}/db_attribute/db_work.py +220 -88
- {db_attribute-2.1.1.0 → db_attribute-2.1.2}/db_attribute/discriptor.py +45 -24
- {db_attribute-2.1.1.0 → db_attribute-2.1.2}/pyproject.toml +5 -7
- {db_attribute-2.1.1.0 → db_attribute-2.1.2}/readme.md +200 -44
- {db_attribute-2.1.1.0 → db_attribute-2.1.2}/setup.py +3 -10
- db_attribute-2.1.1.0/db_attribute/connector.py +0 -31
- {db_attribute-2.1.1.0 → db_attribute-2.1.2}/LICENSE +0 -0
- {db_attribute-2.1.1.0 → db_attribute-2.1.2}/MANIFEST.in +0 -0
- {db_attribute-2.1.1.0 → db_attribute-2.1.2}/db_attribute.egg-info/SOURCES.txt +0 -0
- {db_attribute-2.1.1.0 → db_attribute-2.1.2}/requirements.txt +0 -0
- {db_attribute-2.1.1.0 → 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
|
-
Keywords: db,database,attribute,db_attribute,db attribute,DbAttribute,database attribute
|
|
13
|
-
Classifier: Development Status ::
|
|
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
|
+
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)
|
|
@@ -180,20 +310,20 @@ print(obj) # User(id=5, name='Alica')
|
|
|
180
310
|
If a developer needs to recreate an object, he can call DbAttribute cls with id.
|
|
181
311
|
|
|
182
312
|
```python
|
|
183
|
-
obj = User(name='Ben', age=
|
|
184
|
-
print(obj) #User(id=3, name='Ben', age=
|
|
313
|
+
obj = User(name='Ben', age=20, id=3) #insert obj to db
|
|
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=
|
|
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=
|
|
320
|
+
print(obj) # User(id=3, name='Anna', age=20)
|
|
191
321
|
|
|
192
|
-
obj = User(age=
|
|
193
|
-
print(obj) #User(id=3, name='Anna', age=
|
|
322
|
+
obj = User(age=25, id=3)
|
|
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=
|
|
326
|
+
print(obj) # User(id=3, name='Anna', age=25)
|
|
197
327
|
```
|
|
198
328
|
|
|
199
329
|
### Finding objects
|
|
@@ -207,21 +337,25 @@ The `get()` method returns:
|
|
|
207
337
|
|
|
208
338
|
```python
|
|
209
339
|
#create objs
|
|
210
|
-
obj = User(name='Bob', age=
|
|
211
|
-
obj = User(name='Bob', age=
|
|
212
|
-
obj = User(name='Anna', age=
|
|
340
|
+
obj = User(name='Bob', age=20, id=1)
|
|
341
|
+
obj = User(name='Bob', age=30, id=2)
|
|
342
|
+
obj = User(name='Anna', age=20, id=3)
|
|
213
343
|
#finds objs
|
|
214
|
-
print(User.get(
|
|
215
|
-
print(User.get(User.name == '
|
|
216
|
-
print(User.get(User.name == '
|
|
217
|
-
print(User.get(User.name == '
|
|
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)]
|
|
218
352
|
```
|
|
219
353
|
|
|
220
354
|
To check the correctness of writing a logical expression, you can:
|
|
221
355
|
|
|
222
356
|
```python
|
|
223
|
-
print(User.name == 'Anna')
|
|
224
|
-
print((User.age ==
|
|
357
|
+
print(User.name == 'Anna') # (User.name = 'Anna')
|
|
358
|
+
print((User.age == 30) & (User.name == 'Bob')) # ((User.age = 30) and (User.name = 'Bob'))
|
|
225
359
|
```
|
|
226
360
|
|
|
227
361
|
Use '&' and '|' instead of the 'and' and 'or' operators. The 'and' and 'or' operators are not supported
|
|
@@ -232,16 +366,16 @@ If a developer needs to iterate through all the elements of a class, they can us
|
|
|
232
366
|
|
|
233
367
|
```python
|
|
234
368
|
print(list(User))
|
|
235
|
-
#[User(id=1, name='Bob', age=
|
|
369
|
+
# [User(id=1, name='Bob', age=30), User(id=2, name='Bob', age=20), User(id=3, name='Anna', age=20)]
|
|
236
370
|
|
|
237
|
-
print([i for i in User if i.age <
|
|
238
|
-
#[User(id=2, name='Bob', age=
|
|
371
|
+
print([i for i in User if i.age < 30])
|
|
372
|
+
# [User(id=2, name='Bob', age=20), User(id=3, name='Anna', age=20)]
|
|
239
373
|
|
|
240
374
|
for i in User:
|
|
241
375
|
print(i)
|
|
242
|
-
#User(id=1, name='Bob', age=
|
|
243
|
-
#User(id=2, name='Bob', age=
|
|
244
|
-
#User(id=3, name='Anna', age=
|
|
376
|
+
# User(id=1, name='Bob', age=30)
|
|
377
|
+
# User(id=2, name='Bob', age=20)
|
|
378
|
+
# User(id=3, name='Anna', age=20)
|
|
245
379
|
```
|
|
246
380
|
⚠️ Iterations loads all objects - not recommended for large tables
|
|
247
381
|
|
|
@@ -250,12 +384,12 @@ for i in User:
|
|
|
250
384
|
```python
|
|
251
385
|
obj = User(name='Bob', list_of_books=[], id=1)
|
|
252
386
|
|
|
253
|
-
print(obj) #User(id=1, name='Bob', list_of_books=[])
|
|
387
|
+
print(obj) # User(id=1, name='Bob', list_of_books=[])
|
|
254
388
|
|
|
255
389
|
obj.name = 'Anna'
|
|
256
390
|
obj.list_of_books.append('Any name of book')
|
|
257
391
|
|
|
258
|
-
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'])
|
|
259
393
|
```
|
|
260
394
|
|
|
261
395
|
### Dump mode
|
|
@@ -310,6 +444,27 @@ user.set_auto_dump_mode()
|
|
|
310
444
|
|
|
311
445
|
## Types
|
|
312
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
|
+
|
|
313
468
|
### Db attribute
|
|
314
469
|
|
|
315
470
|
A developer can set the Db attribute class as data type for another Db attribute class
|
|
@@ -329,20 +484,20 @@ To create an object:
|
|
|
329
484
|
```python
|
|
330
485
|
obj_a = Class_A(id=15, name='Anna', obj_b=1)
|
|
331
486
|
obj_b = Class_B(id=1, name='Bob', obj_a=15)
|
|
332
|
-
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, ...)))
|
|
333
488
|
#or
|
|
334
489
|
obj_a = Class_A(id=15, name='Anna', obj_b=obj_b)
|
|
335
|
-
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, ...)))
|
|
336
491
|
```
|
|
337
492
|
For found obj:
|
|
338
493
|
```python
|
|
339
494
|
Class_A(id=15, name='Anna', obj_b=1)
|
|
340
495
|
obj = Class_B(id=1, name='Bob', obj_a=15)
|
|
341
496
|
obj = Class_A.get(Class_A.obj_b == obj)
|
|
342
|
-
print(obj) #Class_A(id=15, name='Anna', obj_b=Class_B(id=1, name='Bob', obj_a=Class_A(id=15, ...)))
|
|
343
|
-
#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:
|
|
344
499
|
obj = Class_A.get(Class_A.obj_b == 1)
|
|
345
|
-
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, ...)))
|
|
346
501
|
```
|
|
347
502
|
One-to-Many relationship:
|
|
348
503
|
```python
|
|
@@ -362,29 +517,29 @@ author = Author(name="George Orwell")
|
|
|
362
517
|
book = Book(title="1984", author=author)
|
|
363
518
|
author.books.append(book)
|
|
364
519
|
|
|
365
|
-
print(author) #Author(id=1, name='George Orwell', books=[Book(id=1, title='1984', author=Author(id=1, ...))])
|
|
366
|
-
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, ...)]))
|
|
367
522
|
```
|
|
368
523
|
|
|
369
524
|
### Db classes
|
|
370
525
|
When collections are stored in memory, they converted to Db classes
|
|
371
526
|
```python
|
|
372
527
|
obj = User(1, list_of_books=[1, 2, 3])
|
|
373
|
-
print(type(obj.list_of_books)) #DbList
|
|
528
|
+
print(type(obj.list_of_books)) # DbList
|
|
374
529
|
```
|
|
375
530
|
```python
|
|
376
531
|
obj = User(1, times=[datetime(2024, 1, 1)])
|
|
377
|
-
print(type(obj.times[0])) #DbDatetime
|
|
532
|
+
print(type(obj.times[0])) # DbDatetime
|
|
378
533
|
```
|
|
379
534
|
And when collections dumped to db, they converted to json
|
|
380
535
|
```python
|
|
381
536
|
obj = User(1, list_of_books=[1, 2, 3])
|
|
382
|
-
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]}
|
|
383
538
|
```
|
|
384
539
|
```python
|
|
385
540
|
obj = User(1, times=[datetime(2024, 1, 1), datetime(2027, 7, 7)])
|
|
386
541
|
print(obj.list_of_books.dumps())
|
|
387
|
-
#{"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"}]}
|
|
388
543
|
```
|
|
389
544
|
|
|
390
545
|
### Custom Db Classes
|
|
@@ -476,6 +631,7 @@ obj.settings = {1: 3} # changed
|
|
|
476
631
|
print(obj.settings) #{'1': 3}
|
|
477
632
|
```
|
|
478
633
|
|
|
634
|
+
|
|
479
635
|
# Speed Test
|
|
480
636
|
|
|
481
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
|
|
@@ -512,4 +668,3 @@ JsonType | 7297 op/sec | -14%
|
|
|
512
668
|
# Data base
|
|
513
669
|
|
|
514
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>
|
|
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.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__)
|