Skip to content

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 id field is read-only. It cannot be overwritten, redefined, or have its type changed. You can only change the id creation behavior by using unique_together in the Config class. Attempting to define id in your model annotations, pass it to the constructor, or modify it after creation will raise a ReadOnlyFieldError.

Now, let’s create a user.

user = UserModel(username="hakancelik")
user is an instance of UserModel. It has a username field and id field. If no 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'}
As you can see with this example, the values of all fields are taken by default during id generation. But If you want you can change of the fields while generating id.

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()
That’s it, now we have saved our user to the database. In addition If you wish, you can update any field belonging to the instance and then have it updated in your database.

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.objects and Dog.objects refer to different tables (animals and dogs).
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 override Config (e.g. their own unique_together or table_name).
  • Table name for a subclass is always derived from the subclass name; table_name from 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("@")]
    )