DbAttribute - Database Attribute
=========================
DbAttribute is an ORM library designed to simplify database interactions. Core capabilities:
* Automatic state synchronization
Object attribute changes are automatically tracked and persisted to the database without requiring explicit commit calls.
<br><br>
* Direct object manipulation
Supports both value assignment (obj.attr = value) and in-place modification of container types:
```python
obj.books.append("New Book")
obj.settings["theme"] = "dark"
```
* Expressive query syntax
Filtering uses Python operators with natural syntax:
```python
# Find users older than 18 named John
User.get((User.age > 18) & (User.name == "John"))
# Get all users named Bob
[user for user in User if user.name == "Bob"]
```
The library provides tools for declarative model definition, relationship management, and database operation optimization through configurable synchronization modes.
# Table of contents
* [Table of contents](#table-of-contents)
* [Supported types](#supported-types)
* [Install](#install)
* [How to use it](#how-to-use-it)
* [Create class](#create-class)
* [Options](#options)
* [Work with obj](#work-with-obj)
* [Create new object](#create-new-object)
* [Finding objects](#finding-objects)
* [Iterations](#iterations)
* [Change attribute of obj](#change-attribute-of-obj)
* [Dump mode](#dump-mode)
* [Types](#types)
* [Db attribute](#db-attribute)
* [Db classes](#db-classes)
* [Custom Db Classes](#custom-db-classes)
* [Json type](#json-type)
* [Speed Test](#speed-test)
* [Get attr](#get-attr)
* [Set attr](#set-attr)
* [Data base](#data-base)
# Supported types
This module supports standard types: `int`, `float`, `str`, `bool`, `None`, `tuple`, `list`, `set`, `dict`, `datetime`.
If a developer needs other data types, they will need to write an adapter class.
# Install
The package can be obtained from PyPI and installed in a single step:
```
pip install db_attribute
```
It can also be obtained from source (requires git):
```
pip install git+https://github.com/shutkanos/Db-Attribute.git
```
# How to use it
## Create class
To create any class (Table):
* Set metaclass `DbAttributeMetaclass`
* Inheritance the `DbAttribute` (optional, since it inherits automatically when using a metaclass)
* Set dbworkobj for connect to database
* Define fields using annotations or DbField for database columns
```python
from db_attribute import DbAttribute, DbAttributeMetaclass, db_work, connector
from db_attribute.db_types import DbField
connect_obj = connector.Connection(host=*mysqlhost*, user=*user*, password=*password*, database=*databasename*)
db_work_obj = db_work.Db_work(connect_obj)
class User(DbAttribute, metaclass=DbAttributeMetaclass, __dbworkobj__=db_work_obj):
name: str = DbField(default='NotSet') # Ok
age: int = -1 # Ok
ban = DbField(default=False) # Ok
other_int_information = 100 # Need annotation or DbField - not error, but not saved
list_of_books = DbField(default_factory=lambda: ['name of first book']) # Ok
settings: dict = DbField(default_factory=dict) # Ok
```
Each instance has a unique `id` identifier. It is inherited from DbAttribute and stored in `__dict__`
### Options
Options can be set in different ways:
```python
class User(DbAttribute, metaclass=DbAttributeMetaclass, __dbworkobj__ = db_work_obj):
pass
```
```python
class User(DbAttribute, metaclass=DbAttributeMetaclass):
__dbworkobj__ = db_work_obj
```
```python
class User(DbAttribute, metaclass=DbAttributeMetaclass):
class Meta:
__dbworkobj__ = db_work_obj
```
```python
class BaseMeta:
__dbworkobj__ = dbworkobj
class Class_A(DbAttribute, metaclass=DbAttributeMetaclass):
Meta = BaseMeta
class Class_B(DbAttribute, metaclass=DbAttributeMetaclass):
Meta = BaseMeta
```
All options:
* `__dbworkobj__` - database work object (required parameter),
* `__max_repr_recursion_limit__` - maximum recursion limit for `__repr__` of DbAttribute
* `__repr_class_name__` - sets the name of this class when using the method `__repr__` of DbAttribute
## Work with obj
### Create new object
To create an object, use an id (optional) and other fields (optional),
```python
obj = User(id=3) # other field set to defaults value
print(obj) # User(id=3, name=*default value*)
```
```python
obj = User(name='Ben', id=3)
print(obj) # User(id=3, name='Ben')
```
```python
obj = User(name='Alica')
print(obj) # User(id=4, name='Alica')
obj = User(name='Alica')
print(obj) # User(id=5, name='Alica')
```
If a developer needs to recreate an object, he can call DbAttribute cls with id.
```python
obj = User(name='Ben', age=20, id=3) #insert obj to db
print(obj) #User(id=3, name='Ben', age=20)
obj = User(id=3)
print(obj) #User(id=3, name='Ben', age=20)
obj = User('Anna', id=3)
print(obj) #User(id=3, name='Anna', age=20)
obj = User(age=25, id=3)
print(obj) #User(id=3, name='Anna', age=25)
obj = User(id=3)
print(obj) #User(id=3, name='Anna', age=25)
```
### Finding objects
If a developer needs to find an object, they can use the 'get' method.
The `get()` method returns:
- Single object if found
- Object with smallest ID if multiple matches exist
- `None` if no matches found
```python
#create objs
obj = User(name='Bob', age=20, id=1)
obj = User(name='Bob', age=30, id=2)
obj = User(name='Anna', age=20, id=3)
#finds objs
print(User.get(id=2)) #User(id=2, name='Bob', age=30)
print(User.get((User.age == 30) & (User.name == 'Bob')))#User(id=2, name='Bob', age=30)
print(User.get(User.name == 'Anna')) #User(id=3, name='Anna', age=20)
print(User.get(User.name == 'Bob')) #User(id=1, name='Bob', age=20)
print(User.get(User.name == 'Other name')) #None
```
To check the correctness of writing a logical expression, you can:
```python
print(User.name == 'Anna') #(User.name = 'Anna')
print((User.age == 30) & (User.name == 'Bob')) #((User.age = 30) and (User.name = 'Bob'))
```
Use '&' and '|' instead of the 'and' and 'or' operators. The 'and' and 'or' operators are not supported
### Iterations
If a developer needs to iterate through all the elements of a class, they can use standard Python tools.
```python
print(list(User))
#[User(id=1, name='Bob', age=30), User(id=2, name='Bob', age=20), User(id=3, name='Anna', age=20)]
print([i for i in User if i.age < 30])
#[User(id=2, name='Bob', age=20), User(id=3, name='Anna', age=20)]
for i in User:
print(i)
#User(id=1, name='Bob', age=30)
#User(id=2, name='Bob', age=20)
#User(id=3, name='Anna', age=20)
```
⚠️ Iterations loads all objects - not recommended for large tables
### Change attribute of obj
```python
obj = User(name='Bob', list_of_books=[], id=1)
print(obj) #User(id=1, name='Bob', list_of_books=[])
obj.name = 'Anna'
obj.list_of_books.append('Any name of book')
print(obj) #User(id=1, name='Anna', list_of_books=['Any name of book'])
```
### Dump mode
If in any function you will work with obj, you can activate manual_dump_mode (auto_dump_mode is the default),
* `auto_dump_mode`: attributes don't save in self.__dict__, all changes automatic dump in db.
* `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
DbAttribute.set_auto_dump_mode set auto_dump_mode and call dump
DbAttribute.set_manual_dump_mode set manual_dump_mode
```python
user = User(id=1, any_db_data1=531, any_db_data2='string')
print(user.__dict__)
# {'id': 1}
user.set_manual_dump_mode()
print(user.__dict__)
# {'id': 1, '_any_db_data1': 531, '_any_db_data2': 'string'}
```
Or set dump mode for individual attributes
```python
user = User(id=1, any_db_data1=531, any_db_data2='string')
print(user.__dict__)
# {'id': 1}
user.set_manual_dump_mode({'any_db_data1'})
print(user.__dict__)
# {'id': 1, '_any_db_data1': 531}
```
```python
user = User(id=1, list_of_books=[])
user.set_manual_dump_mode()
for i in range(10 ** 5):
user.list_of_books.append(i)
user.set_auto_dump_mode()
```
If a developer needs to dump attributes to db with manual_dump_mode, you can use DbAttribute.db_attribute_dump
```python
user = User(id=1, list_of_books=[])
user.set_manual_dump_mode()
for i in range(10 ** 4):
user.list_of_books.append(i)
user.dump() # dump the list_of_books to db
for i in range(10 ** 4):
user.list_of_books.append(i)
user.set_auto_dump_mode()
```
## Types
### Db attribute
A developer can set the Db attribute class as data type for another Db attribute class
```python
from db_attribute.db_types import TableType
class Class_A(DbAttribute, metaclass=DbAttributeMetaclass):
Meta = BaseMeta
obj_b: TableType('Class_B')
class Class_B(DbAttribute, metaclass=DbAttributeMetaclass):
Meta = BaseMeta
obj_a: Class_A
```
To create an object:
```python
obj_a = Class_A(id=15, name='Anna', obj_b=1)
obj_b = Class_B(id=1, name='Bob', obj_a=15)
print(obj_b) #Class_B(id=1, name='Bob', obj_a=Class_A(id=15, name='Anna', obj_b=Class_B(id=1, ...)))
#or
obj_a = Class_A(id=15, name='Anna', obj_b=obj_b)
print(obj_a) #Class_A(id=15, name='Anna', obj_b=Class_B(id=1, name='Bob', obj_a=Class_A(id=15, ...)))
```
For found obj:
```python
Class_A(id=15, name='Anna', obj_b=1)
obj = Class_B(id=1, name='Bob', obj_a=15)
obj = Class_A.get(Class_A.obj_b == obj)
print(obj) #Class_A(id=15, name='Anna', obj_b=Class_B(id=1, name='Bob', obj_a=Class_A(id=15, ...)))
#And Found with use id of obj:
obj = Class_A.get(Class_A.obj_b == 1)
print(obj) #Class_A(id=15, name='Anna', obj_b=Class_B(id=1, name='Bob', obj_a=Class_A(id=15, ...)))
```
One-to-Many relationship:
```python
from db_attribute.db_types import DbField
class Author(DbAttribute, metaclass=DbAttributeMetaclass):
Meta = BaseMeta
name: str = ""
books: list = DbField(default_factory=list)
class Book(DbAttribute, metaclass=DbAttributeMetaclass):
Meta = BaseMeta
title: str = ""
author: Author
author = Author(name="George Orwell")
book = Book(title="1984", author=author)
author.books.append(book)
print(author) #Author(id=1, name='George Orwell', books=[Book(id=1, title='1984', author=Author(id=1, ...))])
print(book) #Book(id=1, title='1984', author=Author(id=1, name='George Orwell', books=[Book(id=1, ...)]))
```
### Db classes
When collections are stored in memory, they converted to Db classes
```python
obj = User(1, list_of_books=[1, 2, 3])
print(type(obj.list_of_books)) #DbList
```
```python
obj = User(1, times=[datetime(2024, 1, 1)])
print(type(obj.times[0])) #DbDatetime
```
And when collections dumped to db, they converted to json
```python
obj = User(1, list_of_books=[1, 2, 3])
print(obj.list_of_books.dumps()) #{"t": "DbList", "d": [1, 2, 3]}
```
```python
obj = User(1, times=[datetime(2024, 1, 1), datetime(2027, 7, 7)])
print(obj.list_of_books.dumps())
#{"t": "DbList", "d": [{"t": "DbDatetime", "d": "2024-01-01T00:00:00"}, {"t": "DbDatetime", "d": "2027-07-07T00:00:00"}]}
```
### Custom Db Classes
And to create a custom 'Db class', you need to
* Create regular class
* Inherit from DbClass (DbClass - first. It is important) and your regular class for custom Db class
* Set a Decorator with or without the necessary parameters
* Set at least the `__convert_to_db__` module, according to the documentation
* add additional modules.
```python
from db_attribute import db_class
# for exemple you have your class:
class UserDataClass:
def __init__(self, value = None):
self.value = value
def __repr__(self):
return f'UserDataClass(value={self.value})'
@db_class.DbClassDecorator
class DbUserDataClass(db_class.DbClass, UserDataClass):
def __init__(self, value=None, **kwargs):
# This is not a mandatory method
super().__init__(_call_init=False, **kwargs) # But this call is mandatory
self.__dict__['value'] = value
# Here we set the value of a variable using __dict__.
# This is not necessary, but it speeds up the work with the class.
@classmethod
def __convert_to_db__(cls, obj: UserDataClass, **kwargs):
"""Methode for convert obj to dbclass - need @classmethod and kwargs"""
# This is a mandatory method
# Call with _user_db=True
# Example:
# print(type(DbUserDataClass(value=10))) #UserDataClass
# print(type(DbUserDataClass(value=10, _use_db=True))) #DbUserDataClass
return cls(_use_db=True, value=obj.value, **kwargs)
def __convert_from_db__(self):
"""Reverse convert"""
# This is not a mandatory method.
return self._standart_class(value=self.value)
```
For example:
```python
class User(DbAttribute, metaclass=DbAttributeMetaclass):
Meta = BaseMeta
data: UserDataClass
user = User(id=1, data=UserDataClass(10))
print(user.data) # UserDataClass(value=10)
user.data.value = 5
print(user.data) # UserDataClass(value=5)
```
### Json type
DbAttribute supports `tuple`, `list`, `dict`, other collections, but these types are slow, because uses Db classes (see [speed test](#speed-test)).
To solve this problem, use a Json convertation
```python
from db_attribute.db_types import JsonType, DbField
class User(DbAttribute, metaclass=DbAttributeMetaclass):
Meta = BaseMeta
settings: JsonType = DbField(default_factory=lambda: {})
obj = User(1, settings={1: 2, 3: [4, 5]})
print(obj.settings) # {'1': 2, '3': [4, 5]}
print(type(obj.settings)) # dict
```
* If Developer change obj with JsonType, this obj don't dump to db, you need set the new obj
* JsonType only supports: `dict`, `list`, `str`, `int`, `float`, `bool`, `None`
```python
obj = User(1, settings={1: 2, 3: [4, 5]})
del obj.settings['3'] # not changed
obj.settings['1'] = 3 # not changed
obj.settings |= {4: 5} # not changed
print(obj.settings) #{'1': 2, '3': [4, 5]}
obj.settings = {1: 3} # changed
print(obj.settings) #{'1': 3}
```
# Speed Test
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
* mysql `select` - 12500 op/sec
* mysql `insert` - 8500 op/sec<br>
## Get attr
Mysql `select` - 12500 op/sec
Type | Operation/seconds | Performance impact
----------|-------------------|---------------------------
int | 11658 op/sec | -6%
str | 11971 op/sec | -4%
tuple | 9685 op/sec | -22%
list | 9630 op/sec | -23%
dict | 9545 op/sec | -23%
JsonType | 11937 op/sec | -4%
## Set attr
Mysql `insert` - 8500 op/sec<br>
Type | Operation/seconds | Performance impact
----------|-------------------|---------------------------
int | 8056 op/sec | -5%
str | 8173 op/sec | -3%
tuple | 6284 op/sec | -26%
list | 6043 op/sec | -28%
dict | 6354 op/sec | -25%
JsonType | 7297 op/sec | -14%
# Data base
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>
Raw data
{
"_id": null,
"home_page": "https://github.com/shutkanos/Db-Attribute",
"name": "db-attribute",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.9",
"maintainer_email": null,
"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",
"author": "Shutkanos",
"author_email": "Shutkanos <Shutkanos836926@mail.ru>",
"download_url": "https://files.pythonhosted.org/packages/b3/b1/d4e328c65cd509bb82770188ac7ce43a9143e3a2f687fab60ed707174083/db_attribute-2.1.1.1.tar.gz",
"platform": null,
"description": "DbAttribute - Database Attribute\r\n=========================\r\n\r\nDbAttribute is an ORM library designed to simplify database interactions. Core capabilities:\r\n\r\n* Automatic state synchronization\r\nObject attribute changes are automatically tracked and persisted to the database without requiring explicit commit calls.\r\n<br><br>\r\n* Direct object manipulation\r\nSupports both value assignment (obj.attr = value) and in-place modification of container types:\r\n\r\n```python\r\nobj.books.append(\"New Book\")\r\nobj.settings[\"theme\"] = \"dark\"\r\n```\r\n\r\n* Expressive query syntax\r\nFiltering uses Python operators with natural syntax:\r\n\r\n```python\r\n# Find users older than 18 named John\r\nUser.get((User.age > 18) & (User.name == \"John\"))\r\n\r\n# Get all users named Bob\r\n[user for user in User if user.name == \"Bob\"]\r\n```\r\nThe library provides tools for declarative model definition, relationship management, and database operation optimization through configurable synchronization modes.\r\n\r\n# Table of contents\r\n\r\n* [Table of contents](#table-of-contents)\r\n* [Supported types](#supported-types)\r\n* [Install](#install)\r\n* [How to use it](#how-to-use-it)\r\n * [Create class](#create-class)\r\n * [Options](#options)\r\n * [Work with obj](#work-with-obj)\r\n * [Create new object](#create-new-object)\r\n * [Finding objects](#finding-objects)\r\n * [Iterations](#iterations)\r\n * [Change attribute of obj](#change-attribute-of-obj)\r\n * [Dump mode](#dump-mode)\r\n * [Types](#types)\r\n * [Db attribute](#db-attribute)\r\n * [Db classes](#db-classes)\r\n * [Custom Db Classes](#custom-db-classes) \r\n * [Json type](#json-type)\r\n* [Speed Test](#speed-test)\r\n * [Get attr](#get-attr)\r\n * [Set attr](#set-attr)\r\n* [Data base](#data-base)\r\n\r\n# Supported types\r\n\r\nThis module supports standard types: `int`, `float`, `str`, `bool`, `None`, `tuple`, `list`, `set`, `dict`, `datetime`.\r\n\r\nIf a developer needs other data types, they will need to write an adapter class.\r\n\r\n# Install\r\n\r\nThe package can be obtained from PyPI and installed in a single step:\r\n\r\n```\r\npip install db_attribute\r\n```\r\n\r\nIt can also be obtained from source (requires git):\r\n\r\n```\r\npip install git+https://github.com/shutkanos/Db-Attribute.git\r\n```\r\n\r\n# How to use it\r\n\r\n## Create class\r\n\r\nTo create any class (Table):\r\n\r\n* Set metaclass `DbAttributeMetaclass`\r\n* Inheritance the `DbAttribute` (optional, since it inherits automatically when using a metaclass)\r\n* Set dbworkobj for connect to database\r\n* Define fields using annotations or DbField for database columns\r\n\r\n```python\r\nfrom db_attribute import DbAttribute, DbAttributeMetaclass, db_work, connector\r\nfrom db_attribute.db_types import DbField\r\n\r\nconnect_obj = connector.Connection(host=*mysqlhost*, user=*user*, password=*password*, database=*databasename*)\r\ndb_work_obj = db_work.Db_work(connect_obj)\r\n\r\nclass User(DbAttribute, metaclass=DbAttributeMetaclass, __dbworkobj__=db_work_obj):\r\n name: str = DbField(default='NotSet') # Ok\r\n age: int = -1 # Ok\r\n ban = DbField(default=False) # Ok\r\n other_int_information = 100 # Need annotation or DbField - not error, but not saved\r\n list_of_books = DbField(default_factory=lambda: ['name of first book']) # Ok\r\n settings: dict = DbField(default_factory=dict) # Ok\r\n```\r\n\r\nEach instance has a unique `id` identifier. It is inherited from DbAttribute and stored in `__dict__`\r\n\r\n### Options\r\n\r\nOptions can be set in different ways:\r\n\r\n```python\r\nclass User(DbAttribute, metaclass=DbAttributeMetaclass, __dbworkobj__ = db_work_obj):\r\n pass\r\n```\r\n```python\r\nclass User(DbAttribute, metaclass=DbAttributeMetaclass):\r\n __dbworkobj__ = db_work_obj\r\n```\r\n```python\r\nclass User(DbAttribute, metaclass=DbAttributeMetaclass):\r\n class Meta:\r\n __dbworkobj__ = db_work_obj\r\n```\r\n```python\r\nclass BaseMeta:\r\n __dbworkobj__ = dbworkobj\r\nclass Class_A(DbAttribute, metaclass=DbAttributeMetaclass):\r\n Meta = BaseMeta\r\nclass Class_B(DbAttribute, metaclass=DbAttributeMetaclass):\r\n Meta = BaseMeta\r\n```\r\n\r\nAll options:\r\n\r\n* `__dbworkobj__` - database work object (required parameter),\r\n* `__max_repr_recursion_limit__` - maximum recursion limit for `__repr__` of DbAttribute\r\n* `__repr_class_name__` - sets the name of this class when using the method `__repr__` of DbAttribute\r\n\r\n## Work with obj\r\n\r\n### Create new object\r\n\r\nTo create an object, use an id (optional) and other fields (optional),\r\n\r\n```python\r\nobj = User(id=3) # other field set to defaults value\r\nprint(obj) # User(id=3, name=*default value*)\r\n```\r\n```python\r\nobj = User(name='Ben', id=3)\r\nprint(obj) # User(id=3, name='Ben')\r\n```\r\n```python\r\nobj = User(name='Alica')\r\nprint(obj) # User(id=4, name='Alica')\r\nobj = User(name='Alica')\r\nprint(obj) # User(id=5, name='Alica')\r\n```\r\n\r\nIf a developer needs to recreate an object, he can call DbAttribute cls with id.\r\n\r\n```python\r\nobj = User(name='Ben', age=20, id=3) #insert obj to db\r\nprint(obj) #User(id=3, name='Ben', age=20)\r\n\r\nobj = User(id=3)\r\nprint(obj) #User(id=3, name='Ben', age=20)\r\n\r\nobj = User('Anna', id=3)\r\nprint(obj) #User(id=3, name='Anna', age=20)\r\n\r\nobj = User(age=25, id=3)\r\nprint(obj) #User(id=3, name='Anna', age=25)\r\n\r\nobj = User(id=3)\r\nprint(obj) #User(id=3, name='Anna', age=25)\r\n```\r\n\r\n### Finding objects\r\n\r\nIf a developer needs to find an object, they can use the 'get' method.\r\n\r\nThe `get()` method returns:\r\n- Single object if found\r\n- Object with smallest ID if multiple matches exist\r\n- `None` if no matches found\r\n\r\n```python\r\n#create objs\r\nobj = User(name='Bob', age=20, id=1)\r\nobj = User(name='Bob', age=30, id=2)\r\nobj = User(name='Anna', age=20, id=3)\r\n#finds objs\r\nprint(User.get(id=2)) #User(id=2, name='Bob', age=30)\r\nprint(User.get((User.age == 30) & (User.name == 'Bob')))#User(id=2, name='Bob', age=30)\r\nprint(User.get(User.name == 'Anna')) #User(id=3, name='Anna', age=20)\r\nprint(User.get(User.name == 'Bob')) #User(id=1, name='Bob', age=20)\r\nprint(User.get(User.name == 'Other name')) #None\r\n```\r\n\r\nTo check the correctness of writing a logical expression, you can:\r\n\r\n```python\r\nprint(User.name == 'Anna') #(User.name = 'Anna')\r\nprint((User.age == 30) & (User.name == 'Bob')) #((User.age = 30) and (User.name = 'Bob'))\r\n```\r\n\r\nUse '&' and '|' instead of the 'and' and 'or' operators. The 'and' and 'or' operators are not supported\r\n\r\n### Iterations\r\n\r\nIf a developer needs to iterate through all the elements of a class, they can use standard Python tools.\r\n\r\n```python\r\nprint(list(User))\r\n#[User(id=1, name='Bob', age=30), User(id=2, name='Bob', age=20), User(id=3, name='Anna', age=20)]\r\n\r\nprint([i for i in User if i.age < 30])\r\n#[User(id=2, name='Bob', age=20), User(id=3, name='Anna', age=20)]\r\n\r\nfor i in User:\r\n print(i)\r\n#User(id=1, name='Bob', age=30)\r\n#User(id=2, name='Bob', age=20)\r\n#User(id=3, name='Anna', age=20)\r\n```\r\n\u26a0\ufe0f Iterations loads all objects - not recommended for large tables\r\n\r\n### Change attribute of obj\r\n\r\n```python\r\nobj = User(name='Bob', list_of_books=[], id=1)\r\n\r\nprint(obj) #User(id=1, name='Bob', list_of_books=[])\r\n\r\nobj.name = 'Anna'\r\nobj.list_of_books.append('Any name of book')\r\n\r\nprint(obj) #User(id=1, name='Anna', list_of_books=['Any name of book'])\r\n```\r\n\r\n### Dump mode\r\n\r\nIf in any function you will work with obj, you can activate manual_dump_mode (auto_dump_mode is the default),\r\n\r\n* `auto_dump_mode`: attributes don't save in self.__dict__, all changes automatic dump in db.\r\n* `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\r\n\r\nDbAttribute.set_auto_dump_mode set auto_dump_mode and call dump\r\n\r\nDbAttribute.set_manual_dump_mode set manual_dump_mode\r\n\r\n```python\r\nuser = User(id=1, any_db_data1=531, any_db_data2='string')\r\nprint(user.__dict__)\r\n# {'id': 1}\r\nuser.set_manual_dump_mode()\r\nprint(user.__dict__)\r\n# {'id': 1, '_any_db_data1': 531, '_any_db_data2': 'string'}\r\n```\r\nOr set dump mode for individual attributes\r\n\r\n```python\r\nuser = User(id=1, any_db_data1=531, any_db_data2='string')\r\nprint(user.__dict__)\r\n# {'id': 1}\r\nuser.set_manual_dump_mode({'any_db_data1'})\r\nprint(user.__dict__)\r\n# {'id': 1, '_any_db_data1': 531}\r\n```\r\n\r\n```python\r\nuser = User(id=1, list_of_books=[])\r\nuser.set_manual_dump_mode()\r\nfor i in range(10 ** 5):\r\n user.list_of_books.append(i)\r\nuser.set_auto_dump_mode()\r\n```\r\nIf a developer needs to dump attributes to db with manual_dump_mode, you can use DbAttribute.db_attribute_dump\r\n\r\n```python\r\nuser = User(id=1, list_of_books=[])\r\nuser.set_manual_dump_mode()\r\nfor i in range(10 ** 4):\r\n user.list_of_books.append(i)\r\nuser.dump() # dump the list_of_books to db\r\nfor i in range(10 ** 4):\r\n user.list_of_books.append(i)\r\nuser.set_auto_dump_mode()\r\n```\r\n\r\n## Types\r\n\r\n### Db attribute\r\n\r\nA developer can set the Db attribute class as data type for another Db attribute class\r\n\r\n```python\r\nfrom db_attribute.db_types import TableType\r\n\r\nclass Class_A(DbAttribute, metaclass=DbAttributeMetaclass):\r\n Meta = BaseMeta\r\n obj_b: TableType('Class_B')\r\n\r\nclass Class_B(DbAttribute, metaclass=DbAttributeMetaclass):\r\n Meta = BaseMeta\r\n obj_a: Class_A\r\n```\r\nTo create an object:\r\n```python\r\nobj_a = Class_A(id=15, name='Anna', obj_b=1)\r\nobj_b = Class_B(id=1, name='Bob', obj_a=15)\r\nprint(obj_b) #Class_B(id=1, name='Bob', obj_a=Class_A(id=15, name='Anna', obj_b=Class_B(id=1, ...)))\r\n#or\r\nobj_a = Class_A(id=15, name='Anna', obj_b=obj_b)\r\nprint(obj_a) #Class_A(id=15, name='Anna', obj_b=Class_B(id=1, name='Bob', obj_a=Class_A(id=15, ...)))\r\n```\r\nFor found obj:\r\n```python\r\nClass_A(id=15, name='Anna', obj_b=1)\r\nobj = Class_B(id=1, name='Bob', obj_a=15)\r\nobj = Class_A.get(Class_A.obj_b == obj)\r\nprint(obj) #Class_A(id=15, name='Anna', obj_b=Class_B(id=1, name='Bob', obj_a=Class_A(id=15, ...)))\r\n#And Found with use id of obj:\r\nobj = Class_A.get(Class_A.obj_b == 1)\r\nprint(obj) #Class_A(id=15, name='Anna', obj_b=Class_B(id=1, name='Bob', obj_a=Class_A(id=15, ...)))\r\n```\r\nOne-to-Many relationship:\r\n```python\r\nfrom db_attribute.db_types import DbField\r\n\r\nclass Author(DbAttribute, metaclass=DbAttributeMetaclass):\r\n Meta = BaseMeta\r\n name: str = \"\"\r\n books: list = DbField(default_factory=list)\r\n\r\nclass Book(DbAttribute, metaclass=DbAttributeMetaclass):\r\n Meta = BaseMeta\r\n title: str = \"\"\r\n author: Author\r\n\r\nauthor = Author(name=\"George Orwell\")\r\nbook = Book(title=\"1984\", author=author)\r\nauthor.books.append(book)\r\n\r\nprint(author) #Author(id=1, name='George Orwell', books=[Book(id=1, title='1984', author=Author(id=1, ...))])\r\nprint(book) #Book(id=1, title='1984', author=Author(id=1, name='George Orwell', books=[Book(id=1, ...)]))\r\n```\r\n\r\n### Db classes\r\nWhen collections are stored in memory, they converted to Db classes\r\n```python\r\nobj = User(1, list_of_books=[1, 2, 3])\r\nprint(type(obj.list_of_books)) #DbList\r\n```\r\n```python\r\nobj = User(1, times=[datetime(2024, 1, 1)])\r\nprint(type(obj.times[0])) #DbDatetime\r\n```\r\nAnd when collections dumped to db, they converted to json\r\n```python\r\nobj = User(1, list_of_books=[1, 2, 3])\r\nprint(obj.list_of_books.dumps()) #{\"t\": \"DbList\", \"d\": [1, 2, 3]}\r\n```\r\n```python\r\nobj = User(1, times=[datetime(2024, 1, 1), datetime(2027, 7, 7)])\r\nprint(obj.list_of_books.dumps())\r\n#{\"t\": \"DbList\", \"d\": [{\"t\": \"DbDatetime\", \"d\": \"2024-01-01T00:00:00\"}, {\"t\": \"DbDatetime\", \"d\": \"2027-07-07T00:00:00\"}]}\r\n```\r\n\r\n### Custom Db Classes\r\n\r\nAnd to create a custom 'Db class', you need to\r\n* Create regular class\r\n* Inherit from DbClass (DbClass - first. It is important) and your regular class for custom Db class\r\n* Set a Decorator with or without the necessary parameters\r\n* Set at least the `__convert_to_db__` module, according to the documentation\r\n* add additional modules.\r\n\r\n```python\r\nfrom db_attribute import db_class\r\n\r\n# for exemple you have your class:\r\n\r\nclass UserDataClass:\r\n def __init__(self, value = None):\r\n self.value = value\r\n def __repr__(self):\r\n return f'UserDataClass(value={self.value})'\r\n\r\n@db_class.DbClassDecorator\r\nclass DbUserDataClass(db_class.DbClass, UserDataClass):\r\n def __init__(self, value=None, **kwargs):\r\n # This is not a mandatory method\r\n super().__init__(_call_init=False, **kwargs) # But this call is mandatory\r\n self.__dict__['value'] = value\r\n # Here we set the value of a variable using __dict__.\r\n # This is not necessary, but it speeds up the work with the class.\r\n\r\n @classmethod\r\n def __convert_to_db__(cls, obj: UserDataClass, **kwargs):\r\n \"\"\"Methode for convert obj to dbclass - need @classmethod and kwargs\"\"\"\r\n # This is a mandatory method\r\n # Call with _user_db=True\r\n # Example:\r\n # print(type(DbUserDataClass(value=10))) #UserDataClass\r\n # print(type(DbUserDataClass(value=10, _use_db=True))) #DbUserDataClass\r\n return cls(_use_db=True, value=obj.value, **kwargs)\r\n\r\n def __convert_from_db__(self):\r\n \"\"\"Reverse convert\"\"\"\r\n # This is not a mandatory method.\r\n return self._standart_class(value=self.value)\r\n```\r\n\r\nFor example:\r\n\r\n```python\r\nclass User(DbAttribute, metaclass=DbAttributeMetaclass):\r\n Meta = BaseMeta\r\n data: UserDataClass\r\n\r\nuser = User(id=1, data=UserDataClass(10))\r\nprint(user.data) # UserDataClass(value=10)\r\nuser.data.value = 5\r\nprint(user.data) # UserDataClass(value=5)\r\n```\r\n\r\n### Json type\r\n\r\nDbAttribute supports `tuple`, `list`, `dict`, other collections, but these types are slow, because uses Db classes (see [speed test](#speed-test)).\r\n\r\nTo solve this problem, use a Json convertation\r\n\r\n```python\r\nfrom db_attribute.db_types import JsonType, DbField\r\n\r\nclass User(DbAttribute, metaclass=DbAttributeMetaclass):\r\n Meta = BaseMeta\r\n settings: JsonType = DbField(default_factory=lambda: {})\r\n\r\nobj = User(1, settings={1: 2, 3: [4, 5]})\r\nprint(obj.settings) # {'1': 2, '3': [4, 5]}\r\nprint(type(obj.settings)) # dict\r\n```\r\n\r\n* If Developer change obj with JsonType, this obj don't dump to db, you need set the new obj \r\n* JsonType only supports: `dict`, `list`, `str`, `int`, `float`, `bool`, `None`\r\n\r\n```python\r\nobj = User(1, settings={1: 2, 3: [4, 5]})\r\ndel obj.settings['3'] # not changed\r\nobj.settings['1'] = 3 # not changed\r\nobj.settings |= {4: 5} # not changed\r\nprint(obj.settings) #{'1': 2, '3': [4, 5]}\r\nobj.settings = {1: 3} # changed\r\nprint(obj.settings) #{'1': 3}\r\n```\r\n\r\n# Speed Test\r\n\r\nThe 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\r\n\r\n* mysql `select` - 12500 op/sec\r\n* mysql `insert` - 8500 op/sec<br>\r\n\r\n## Get attr\r\n\r\nMysql `select` - 12500 op/sec\r\n\r\nType | Operation/seconds | Performance impact\r\n----------|-------------------|---------------------------\r\nint | 11658 op/sec | -6%\r\nstr | 11971 op/sec | -4%\r\ntuple | 9685 op/sec | -22%\r\nlist | 9630 op/sec | -23%\r\ndict | 9545 op/sec | -23%\r\nJsonType | 11937 op/sec | -4%\r\n\r\n## Set attr\r\n\r\nMysql `insert` - 8500 op/sec<br>\r\n\r\nType | Operation/seconds | Performance impact\r\n----------|-------------------|---------------------------\r\nint | 8056 op/sec | -5%\r\nstr | 8173 op/sec | -3%\r\ntuple | 6284 op/sec | -26%\r\nlist | 6043 op/sec | -28%\r\ndict | 6354 op/sec | -25%\r\nJsonType | 7297 op/sec | -14%\r\n\r\n# Data base\r\n\r\nThis 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>\r\n\r\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "DataBase attribute package",
"version": "2.1.1.1",
"project_urls": {
"Documentation": "https://github.com/shutkanos/Db-Attribute#readme.md",
"Homepage": "https://github.com/shutkanos/Db-Attribute",
"Repository": "https://github.com/shutkanos/Db-Attribute"
},
"split_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"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "df05324a2c069c30676b145eff596ce03c5a7a9a05ef1cd3f4cc4e82b94abe48",
"md5": "f22ba5378eb7d8897e426d997862d6cf",
"sha256": "0a7ebe7245c02d8f6dd8007268389366e8c36c104dab44337e4746b4d213918e"
},
"downloads": -1,
"filename": "db_attribute-2.1.1.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "f22ba5378eb7d8897e426d997862d6cf",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.9",
"size": 28297,
"upload_time": "2025-08-28T07:45:16",
"upload_time_iso_8601": "2025-08-28T07:45:16.814410Z",
"url": "https://files.pythonhosted.org/packages/df/05/324a2c069c30676b145eff596ce03c5a7a9a05ef1cd3f4cc4e82b94abe48/db_attribute-2.1.1.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "b3b1d4e328c65cd509bb82770188ac7ce43a9143e3a2f687fab60ed707174083",
"md5": "0f212e82e07228b5eb229b80edca2981",
"sha256": "c8084e5e44883ca2084039400033f45ead00e16547f675e036a49becf4b39c13"
},
"downloads": -1,
"filename": "db_attribute-2.1.1.1.tar.gz",
"has_sig": false,
"md5_digest": "0f212e82e07228b5eb229b80edca2981",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 31321,
"upload_time": "2025-08-28T07:45:18",
"upload_time_iso_8601": "2025-08-28T07:45:18.132499Z",
"url": "https://files.pythonhosted.org/packages/b3/b1/d4e328c65cd509bb82770188ac7ce43a9143e3a2f687fab60ed707174083/db_attribute-2.1.1.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-28 07:45:18",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "shutkanos",
"github_project": "Db-Attribute",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"requirements": [
{
"name": "mysql-connector-python",
"specs": [
[
">=",
"8.4.0"
]
]
},
{
"name": "orjson",
"specs": []
}
],
"lcname": "db-attribute"
}