Skip to content

Documents

In simple terms, documents are a representation of a database table in the format of an object declared by the language implementing.

User documents

Integrating with Mongoz, Esmerald already provides some of the documents that helps you with the initial configuration.

  1. AbstractUser - The base user class containing all the fields required a user.
  2. User - A subsclass of AbstractUser

User

Extenting the existing User document is as simple as this:

from datetime import datetime
from enum import Enum

import mongoz
from mongoz import Registry

from esmerald.contrib.auth.mongoz.base_user import User as BaseUser

database = "mongodb://localhost:27017"
registry = Registry(database)


class UserType(str, Enum):
    ADMIN = "admin"
    USER = "user"
    OTHER = "other"


class Role(mongoz.EmbeddedDocument):
    name: str = mongoz.String(max_length=255, default=UserType.USER)


class User(BaseUser):
    """
    Inherits from the BaseUser all the fields and adds extra unique ones.
    """

    date_of_birth: datetime = mongoz.Date()
    is_verified: bool = mongoz.Boolean(default=False)
    role: Role = mongoz.Embed(Role)

    class Meta:
        registry = registry
        database = "my_db"

    def __str__(self):
        return f"{self.email} - {self.role.name}"


# Using the manager
user = await User.objects.create(is_active=False)

user = await User.objects.get(id=user.id)
print(user)
# User(id=ObjectId(...))

This is a clean way of declaring the documents and using the Mongoz docs, you can easily understand why this is like the way it is.

Meta class

There are way of making the documents and the registry cleaner, after all, you might want to use the same registry in different documents across multiple applications in your codebase.

One way and a way Esmerald always recommend, is by leveraging the settings.

Leveraging the settings for your documents

Let us use the same example but this time, we will be using the settings. Since you can access the settings anywhere in the codebase.

Check it out the example below and how by using the settings, you can literally leverage Esmerald with Mongoz.

from mongoz import Registry

from esmerald.conf.global_settings import EsmeraldAPISettings


class AppSettings(EsmeraldAPISettings):
    @property
    def registry(self) -> Registry:
        database = "<YOUR-SQL-QUERY-STRING"
        return Registry(database)
from datetime import datetime
from enum import Enum

import mongoz

from esmerald.conf import settings
from esmerald.contrib.auth.mongoz.base_user import User as BaseUser

registry = settings.registry


class UserType(str, Enum):
    ADMIN = "admin"
    USER = "user"
    OTHER = "other"


class Role(mongoz.EmbeddedDocument):
    name: str = mongoz.String(max_length=255, default=UserType.USER)


class User(BaseUser):
    """
    Inherits from the BaseUser all the fields and adds extra unique ones.
    """

    date_of_birth: datetime = mongoz.Date()
    is_verified: bool = mongoz.Boolean(default=False)
    role: Role = mongoz.Embed(Role)

    class Meta:
        registry = registry
        database = "my_db"

    def __str__(self):
        return f"{self.email} - {self.role.name}"

You simply isolated your common database connection and registry inside the globally accessible settings and with that you can import in any Esmerald application, ChildEsmerald or whatever you prefer without the need of repeating yourself.

User document fields

If you are familiar with Django then you are also aware of the way they have their users table and the way they have the fields declared. Esmerald has a similar approach and provides the following.

  • first_name
  • last_name
  • username
  • email
  • password
  • last_login
  • is_active
  • is_staff
  • is_superuser

The functions available

Using simply this document it does not bring too much benefits as it is something you can do easily and fast but the functionality applied to it is already something that would require some extra time to assemble.

Warning

The following examples assume that you are taking advantage of the settings as decribed before.

create_user

from pydantic import EmailStr

from esmerald.conf import settings
from esmerald.contrib.auth.mongoz.base_user import User as BaseUser

registry = settings.registry


class User(BaseUser):
    """
    Inherits from the BaseUser all the fields and adds extra unique ones.
    """

    class Meta:
        registry = registry
        database = "my_db"

    def __str__(self):
        return f"{self.email} - {self.last_login}"


async def create_user(
    first_name: str, last_name: str, username: str, email: EmailStr, password: str
) -> User:
    """
    Creates a user in the database.
    """
    user = await User.create_user(
        username=username,
        password=password,
        email=email,
        first_name=first_name,
        last_name=last_name,
    )
    return user

create_superuser

from pydantic import EmailStr

from esmerald.conf import settings
from esmerald.contrib.auth.mongoz.base_user import User as BaseUser

registry = settings.registry


class User(BaseUser):
    """
    Inherits from the BaseUser all the fields and adds extra unique ones.
    """

    class Meta:
        registry = registry
        database = "my_db"

    def __str__(self):
        return f"{self.email} - {self.last_login}"


async def create_superuser(
    first_name: str, last_name: str, username: str, email: EmailStr, password: str
) -> User:
    """
    Creates a superuser in the database.
    """
    user = await User.create_user(
        username=username,
        password=password,
        email=email,
        first_name=first_name,
        last_name=last_name,
    )
    return user

check_password

from pydantic import EmailStr

from esmerald.conf import settings
from esmerald.contrib.auth.mongoz.base_user import User as BaseUser

registry = settings.registry


class User(BaseUser):
    """
    Inherits from the BaseUser all the fields and adds extra unique ones.
    """

    class Meta:
        registry = registry
        database = "my_db"

    def __str__(self):
        return f"{self.email} - {self.last_login}"


# Check if password is valid or correct
async def check_password(email: EmailStr, password: str) -> bool:
    """
    Check if the password of a user is correct.
    """
    user: User = await User.objects.get(email=email)

    is_valid_password = await user.check_password(password)
    return is_valid_password

Because you are using the User provided by Esmerald, the same object is also prepared to validate the password against the system. If you are familiar with Django, this was based on it and has the same principle.

set_password

from pydantic import EmailStr

from esmerald.conf import settings
from esmerald.contrib.auth.mongoz.base_user import User as BaseUser

registry = settings.registry


class User(BaseUser):
    """
    Inherits from the BaseUser all the fields and adds extra unique ones.
    """

    class Meta:
        registry = registry
        database = "my_db"

    def __str__(self):
        return f"{self.email} - {self.last_login}"


# Update password
async def set_password(email: EmailStr, password: str) -> None:
    """
    Set the password of a user is correct.
    """
    user: User = await User.query.get(email=email)

    await user.set_password(password)

The same for setting passwords. The User already contains the functionality to set a password of a given User instance.

What happened

Although the way of using the User table was intentionally designed to be simple there is in fact a lot going on behind the scenes.

When using the create_user and create_superuser behind the scenes it is not only creating that same record and storing in the database but is also hashing the password for you, using the built-in Esmerald password hashers and this is a life saving time and implementation.

Esmerald also provides the set_password and check_password functions to make it easier to validate and change a user's password using the User instance.

Password Hashers

Esmerald already brings some pre-defined password hashers that are available in the Esmerald settings and ready to be used.

@property
def password_hashers(self) -> List[str]:
    return [
        "esmerald.contrib.auth.hashers.PBKDF2PasswordHasher",
        "esmerald.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
    ]

Esmerald uses passlib under the hood in order to facilitate the process of hashing passwords.

You can always override the property password_hashers in your custom settings and use your own.

from typing import List

from esmerald import EsmeraldAPISettings
from esmerald.contrib.auth.hashers import PBKDF2PasswordHasher


class CustomHasher(PBKDF2PasswordHasher):
    """
    All the hashers inherit from BasePasswordHasher
    """

    salt_entropy = 3000


class MySettings(EsmeraldAPISettings):
    @property
    def password_hashers(self) -> List[str]:
        return ["myapp.hashers.CustomHasher"]

General example

More examples and more thorough explanations how to use Mongoz can be consulted in its own documentation.