Models
Models you create using DbmModel look similar to your Pydantic or Django models.
The purpose of your models is to validate your data, if you want to normalize it and save it using dbm. If you wish, you can use your model to perform simple operations on your related data, for example; get, filter, all, update and save.
Pydbm can perform type validation, custom validation and custom normalization.
Basic model usage¶
from pydbm import DbmModel
__all__ = (
"UserModel",
)
class UserModel(DbmModel):
username: str
User here is a model with one field; username. Field of username is str type, and it is required field. Also, It includes a one more field named id. This field is automatically generated by Pydbm and these fields are equal.
Note: The
idfield is read-only. It cannot be overwritten, redefined, or have its type changed. You can only change the id creation behavior by usingunique_togetherin theConfigclass. Attempting to defineidin your model annotations, pass it to the constructor, or modify it after creation will raise aReadOnlyFieldError.
Now, let’s create a user.
user = UserModel(username="hakancelik")
ValidationError raises, it means that the data is valid. Let’s get attributes of user.
assert user.username == "hakancelik"
assert user.id == "20087fb9cf70fb074239826d21c18ea3"
assert user.fields == {'username': 'hakancelik'}
Default values¶
If you want to set a default value for a field, you can assign a default value to the field directly.
from pydbm import DbmModel
__all__ = (
"UserModel",
)
class UserModel(DbmModel):
salt: str = "my-unpredictable-salt"
username: str
While id generates in my new user model, I set a salt value to generate different in my model, now my id will be different from the one above.
user = UserModel(username="hakancelik")
assert user.username == "hakancelik"
assert user.id == "908cbfedd18749b69a39d3771f6762a2"
assert user.fields == {'username': 'hakancelik'}
Unique Together¶
from pydbm import DbmModel
__all__ = (
"UserModel",
)
class UserModel(DbmModel):
salt: str = "my-unpredictable-salt"
username: str
class Config:
unique_together = ("username",)
Now, We create a new user same as above, user’s id will be same.
user = UserModel(username="hakancelik")
assert user.username == "hakancelik"
assert user.id == "20087fb9cf70fb074239826d21c18ea3"
assert user.fields == {'username': 'hakancelik'}
Table Name¶
In Pydbm, default table name is model name. You can change or control it by using table_name attribute in Config class.
from pydbm import DbmModel
__all__ = (
"UserModel",
)
class UserModel(DbmModel):
salt: str = "my-unpredictable-salt"
username: str
class Config:
unique_together = ("username",)
table_name = "users"
Now even if the class name changes, you will not lose your data.
Operations Supported by the Model¶
So far, we have written a simple model example and got its values, but we have not performed any data operations yet, let’s examine what’s available.
Save¶
Save method is used to save the valid data to the database.
user = UserModel(username="hakancelik")
user.save()
user = UserModel(username="hakan")
user.save() # save user to database for the first time
user.username="hakancelik"
user.save() # update username field
UserModel.objects.get(id=user.id) == UserModel(username=”hakancelik”)
Get¶
Get method is used to get the data from the database and return it as a model instance. To fetch single data from the database and get it as a model instance, you can pass the unique together fields of the model.
try:
user = UserModel.objects.get(id=user.id)
except UserModel.DoesNotExist:
print("User does not exist")
If the user exists in the database, it will return the user, otherwise it raises UserModel.DoesNotExists exception.
try:
user = UserModel.objects.get(name="hakan", surname="celik")
except UserModel.RiskofReturningMultipleObjects:
print("Risk of Returning Multiple Users")
If name and surname fields are unique together, it will return the user, otherwise it raises UserModel.RiskofReturningMultipleObjects exception.
Update¶
Update method is used to update the data in the database, when you use this method id is not changed.
user = UserModel.objects.get(user.id)
user.update(username="hakan")
assert user.username == "hakan"
assert user.id == "908cbfedd18749b69a39d3771f6762a2" # id is not changed
Delete¶
Delete user from database.
user = UserModel.objects.get(user.id)
user.delete()
Create¶
Create method is used to save the data to the database and return the data as a model instance.
user = UserModel.objects.create(username="hakancelik")
It is the same as save, but it returns the user.
Get or Create¶
Get or create method tries to get an existing record from the database. If the record does not exist, it creates a new one. It returns a tuple of (instance, created) where created is a boolean indicating whether the record was created.
user, created = UserModel.objects.get_or_create(username="hakancelik")
if created:
print("New user created")
else:
print("Existing user found")
All¶
All method is used to get all data from the database and iterate model instances.
users = list(UserModel.objects.all())
Filter¶
Filter method is used to filter data from the database and iterate model instances. You can filter data by using the fields of the model.
users = list(UserModel.objects.filter(username="hakancelik"))
First¶
First method returns the first record from the database as a model instance. If no records exist, it returns None.
user = UserModel.objects.first()
if user is not None:
print(user.username)
Last¶
Last method returns the last record from the database as a model instance. If no records exist, it returns None.
user = UserModel.objects.last()
if user is not None:
print(user.username)
Exists¶
Exists method is used to check if the data exists in the database, returns True if the data exists, otherwise returns False. If you pass unique together fields of the model, it will be faster.
is_exists: bool = UserModel.objects.exists(username="hakancelik")
Model inheritance¶
You can reuse fields and behavior by subclassing models. Each concrete model has its own table; subclasses inherit all fields from their parent(s).
Concrete inheritance¶
Define a base model and extend it with extra fields. The child model gets its own table (named from the child class, e.g. Dog → "dogs") and stores both inherited and its own fields.
from pydbm import DbmModel
class Animal(DbmModel):
name: str
class Dog(Animal):
breed: str
# Dog has fields: id, name, breed
dog = Dog(name="Max", breed="Labrador")
dog.save()
loaded = Dog.objects.get(id=dog.id)
assert loaded.name == "Max"
assert loaded.breed == "Labrador"
- Fields from all base classes are merged; the child must define at least one field or inherit at least one (so the model is not empty).
- Each concrete model uses its own table.
Animal.objectsandDog.objectsrefer to different tables (animalsanddogs).
Abstract base models¶
To share fields and config without creating a table, set abstract = True in the model’s Config. Abstract models do not have a database table or objects manager; they cannot be instantiated. Only concrete subclasses (non-abstract) get a table and can be saved.
from pydbm import DbmModel
class AbstractAnimal(DbmModel):
name: str
class Config:
abstract = True
class Dog(AbstractAnimal):
breed: str
# AbstractAnimal() # TypeError: Cannot instantiate abstract model AbstractAnimal
dog = Dog(name="Max", breed="Labrador")
dog.save() # uses table "dogs"
- Use abstract bases for common fields and optional shared
Config(e.g.unique_together). Subclasses can overrideConfig(e.g. their ownunique_togetherortable_name). - Table name for a subclass is always derived from the subclass name;
table_namefrom the base is not reused for the child.
Config inheritance¶
If a subclass does not define a Config class, it inherits unique_together from the first base that has a Config. Table name is always generated from the subclass name.
Model properties¶
as_dict()
returns a dictionary of the model’s fields and values.
fields
returns a dictionary of the model’s fields and values.
Field¶
By using the Field, it is possible to define the default value, default factory, validators and normalizations of the field. If type of field is int, then it can be validated min_value and max_value.
Type validation is performed automatically, you do not need to define it. Just define the type annotation of the field.
Default value¶
When it is used, then the field is a not required field.
from pydbm import DbmModel, Field
__all__ = (
"UserModel",
)
class UserModel(DbmModel):
salt: str = Field(default="my-unpredictable-salt")
username: str
Default factory¶
When it is used, then the field is a not required field.
from pydbm import DbmModel, Field
__all__ = (
"UserModel",
)
class UserModel(DbmModel):
salt: str = Field(default_factory=lambda: "my-unpredictable-salt")
username: str
Custom Normalization¶
It can be defined list of normalization functions, to normalize the field value. For more information, see Normalizers.
from pydbm import DbmModel, Field
__all__ = (
"UserModel",
)
class UserModel(DbmModel):
username: str = Field(normalizers=[lambda x: x.lower()])
In here, username field will be lowered case.
Custom Validation¶
We have some built-in validators, but you can also create your own validator. For more information, see Validators.
from pydbm import DbmModel, Field
__all__ = (
"UserModel",
)
class UserModel(DbmModel):
username: str = Field(
validators=[lambda value: value.startswith("@")]
)