Skip to content

model_apps and the importance for Edgy and Saffier

Today marks a great milestone achieved for Edgy and Saffier.

Edgy and Saffier, both ORMs with different purposes and use cases were known for for being framework agnostic but there was a known problem that was causing some headaches when migrations where the subject.

What was the headache?

When using Edgy/Saffier, declaring models is very simple and very straightforward. With their ease of use and friendly interface, it looks like assembling legos but with database tables.

Example with Edgy

import edgy

database = edgy.Database("sqlite:///db.sqlite")
registry = edgy.Registry(database=database)

class User(edgy.Model):
    id: int = edgy.IntegerField(primary_key=True)
    name: str = edgy.CharField(max_length=255)
    email: str = edgy.CharField(max_length=100)
    username: str = edgy.CharField(max_length=50)

    class Meta:
        registry = registry

Example with Saffier

import saffier

database = saffier.Database("sqlite:///db.sqlite")
registry = saffier.Registry(database=database)

class User(saffier.Model):
    id = saffier.IntegerField(primary_key=True)
    name = saffier.CharField(max_length=255)
    email = saffier.CharField(max_length=100)
    username = saffier.CharField(max_length=50)

    class Meta:
        registry = registry

Both extremly similar with the key difference that Edgy is 100% built on top of Pydantic and Saffier is not, making it more of generic ORM.

The headaches were when generating the migrations for both of them, both offer a Migrate object that facilitates the discovery of the models and automate the migrations but this would also happen if the models were being imported somehow in the top level of a package of if explicitly declared.

Anyway, this was not a blocker as you could also change the migrations generated by both of them and import your models for generation but again, whatif we could make it simpler? Just in one place?

Enters model_apps

From today, the release 0.10.0 of Edgy and 1.4.0 of Saffier offer the model_apps parameter that aims to solve this issue.

No more workarounds and no more hacks for migrations anymore.

Example

Let us assume we have an application with the following structure using Edgy (the same is for Saffier).

.
└── README.md
└── .gitignore
└── myproject
    ├── __init__.py
    ├── apps
       ├── __init__.py
       └── accounts
           ├── __init__.py
           ├── tests.py
           ├── models.py
           └── v1
               ├── __init__.py
               ├── schemas.py
               ├── urls.py
               └── views.py
    ├── configs
       ├── __init__.py
       ├── development
          ├── __init__.py
          └── settings.py
       ├── settings.py
       └── testing
           ├── __init__.py
           └── settings.py
    ├── main.py
    ├── serve.py
    ├── utils.py
    ├── tests
       ├── __init__.py
       └── test_app.py
    └── urls.py

As you can see, it is quite structured but let us focus specifically on accounts/models.py.

There is where your models for the accounts application will be placed. Something like this:

from datetime import datetime

from my_project.utils import get_db_connection

import edgy

_, registry = get_db_connection()


class User(edgy.Model):
    """
    Base model for a user
    """

    first_name: str = edgy.CharField(max_length=150)
    last_name: str = edgy.CharField(max_length=150)
    username: str = edgy.CharField(max_length=150, unique=True)
    email: str = edgy.EmailField(max_length=120, unique=True)
    password: str = edgy.CharField(max_length=128)
    last_login: datetime = edgy.DateTimeField(null=True)
    is_active: bool = edgy.BooleanField(default=True)
    is_staff: bool = edgy.BooleanField(default=False)
    is_superuser: bool = edgy.BooleanField(default=False)

    class Meta:
        registry = registry

Now we want to tell the Migrate object to make sure it knows about this.

#!/usr/bin/env python
import os
import sys
from pathlib import Path

from my_project.utils import get_db_connection

from edgy import Migrate
from esmerald import Esmerald, Include


def build_path():
    """
    Builds the path of the project and project root.
    """
    Path(__file__).resolve().parent.parent
    SITE_ROOT = os.path.dirname(os.path.realpath(__file__))

    if SITE_ROOT not in sys.path:
        sys.path.append(SITE_ROOT)
        sys.path.append(os.path.join(SITE_ROOT, "apps"))


def get_application():
    """
    This is optional. The function is only used for organisation purposes.
    """
    build_path()
    database, registry = get_db_connection()

    app = Esmerald(
        routes=[Include(namespace="my_project.urls")],
    )

    Migrate(
        app=app,
        registry=registry,
        model_apps={"accounts": "accounts.models"},
    )
    return app


app = get_application()

As you can see the model_apps = {"accounts": "accounts.models"} was added in a simple fashion. Every time you add new model or any changes, it should behave as normal as before with the key difference that now Edgy has a way to know exactly where your models are specifically.

This solves the headaches of discovering the models within any application no matter what location you place them as long as you specify in the model_apps where to look at.

Comments