Skip to content

Tips and tricks

This part is dedicated to some code organisation within your application.

The examples are more focused on the Esmerald as the author is the same but again, you can do the same in your favourite framework.

Placing your connection in a centralised place

This is probably what you would like to do in your application since you don't want to declare over and over again the same variables.

The main reason for that is the fact that every time you declare a registry or a database, in fact you are generating a new object and this is not great if you need to access the models used with the main registry, right?

Place the connection details inside a global settings file

This is probably the easiest way to place the connection details and particulary for Esmerald since it comes with a simple and easy way of accesing the settings anywhere in the code.

Something simple like this:

"""
Generated by 'esmerald-admin createproject'
"""

from functools import cached_property
from typing import Optional, TYPE_CHECKING

from esmerald.conf.enums import EnvironmentType
from esmerald.conf.global_settings import EsmeraldAPISettings

if TYPE_CHECKING:
    from edgy import Registry, EdgySettings


class AppSettings(EsmeraldAPISettings):
    app_name: str = "My application in production mode."
    environment: Optional[str] = EnvironmentType.PRODUCTION
    secret_key: str = "esmerald-insecure-h35r*b9$+hw-x2hnt5c)vva=!zn$*a7#"  # auto generated

    @cached_property
    def db_connection(self) -> "Registry":
        """
        To make sure the registry and the database connection remains the same
        all the time, always use the cached_property.
        """
        from edgy import Registry

        return Registry(database="postgresql+asyncpg://user:pass@localhost:5432/my_database")

    # optional, in case we want a centralized place
    @cached_property
    def edgy_settings(self) -> "EdgySettings":
        from edgy import EdgySettings

        return EdgySettings(preloads=["myproject.models"])

As you can see, now you have the db_connection in one place and easy to access from anywhere in your code. In the case of Esmerald:

from esmerald.conf import settings

registry = settings.db_connection

But is this enough?. No.

As mentioned before, when assigning or creating a variable, python itself generates a new object with a different id which can differ from each time you need to import the settings into the needed places.

We won't talk about this pythonic tricks as there are plenty of documentation on the web and better suited for that same purpose.

How do we solve this issue? Enters lru_cache.

The LRU cache

LRU extends for least recently used.

A very common technique that aims to help caching certain pieces of functionality within your codebase and making sure you do not generate extra objects and this is exactly what we need.

Use the example above, let us now create a new file called utils.py where we will be applying the lru_cache technique for our db_connection.

utils.py
from functools import lru_cache


@lru_cache()
def get_db_connection():
    # Encapsulate to delay the import
    from esmerald.conf import settings

    return settings.db_connection

This will make sure that from now on you will always use the same connection and registry within your appliction by importing the get_db_connection() anywhere is needed.

Note, you cannot do that if get_db_connection() is in the same file like the application entrypoint. Here you can use a edgy.monkay.instance sandwich instead.

You can also read further the Practical Example.

Excurse: The edgy.monkay.instance sandwich

If you want to short down the code and concentrate in e.g. main.py you can also use manual post loads and do the initialization in get_application this way:

  1. Creating registry.
  2. Assigning the Instance to edgy.instance via set_instance() but without app and skip extensions.
  3. Post loading models.
  4. Creating the main app.
  5. Assigning the Instance to edgy.instance via set_instance() but with app.

this looks like:

main.py
from importlib import import_module

from esmerald import Esmerald


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():
    """
    Encapsulating in methods can be useful for controlling the import order but is optional.
    """
    build_path()
    # import now edgy when the path is set
    import edgy

    registry = edgy.Registry(url=...)
    # extensions shouldn't be applied yet
    edgy.monkay.set_instance(edgy.Instance(registry=registry), apply_extensions=False)
    # load extensions and preloads
    # not evaluate_settings_once because maybe some preloads weren't resolved
    monkay.evaluate_settings(on_conflict="keep")
    app = Esmerald()
    # now apply the extensions
    edgy.monkay.set_instance(edgy.Instance(registry=registry, app=app))
    return app


application = get_application()
myproject/models.py
from datetime import datetime
from enum import Enum

import edgy

registry = edgy.monkay.instance.registry


class ProfileChoice(Enum):
    ADMIN = "ADMIN"
    USER = "USER"


class BaseModel(edgy.Model):
    class Meta:
        abstract = True
        registry = registry


class User(BaseModel):
    """
    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 Profile(BaseModel):
    """
    A profile for a given user.
    """

    user: User = edgy.OneToOneField(User, on_delete=edgy.CASCADE)
    profile_type: ProfileChoice = edgy.ChoiceField(ProfileChoice, default=ProfileChoice.USER)

The sandwich way has the disadvantage of having just one registry, while with the lru_cache way you can have many registries in parallel and mix them.

Practical example

Let us now assemble everything and generate an application that will have:

  • User model
  • Ready to generate migrations
  • Starts the database connections

For this example we will have the following structure (we won't be use using all of the files). We won't be creating views as this is not the purpose of the example.

.
└── myproject
    ├── __init__.py
    ├── apps
       ├── __init__.py
       └── accounts
           ├── __init__.py
           ├── tests.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

This structure is generated by using the Esmerald directives

The settings

As mentioned before we will have a settings file with database connection properties assembled. We have also edgy_settings defined (any name is possible). It will be used for the central configuration management

my_project/configs/settings.py
"""
Generated by 'esmerald-admin createproject'
"""

from functools import cached_property
from typing import Optional, TYPE_CHECKING

from esmerald.conf.enums import EnvironmentType
from esmerald.conf.global_settings import EsmeraldAPISettings

if TYPE_CHECKING:
    from edgy import Registry, EdgySettings


class AppSettings(EsmeraldAPISettings):
    app_name: str = "My application in production mode."
    environment: Optional[str] = EnvironmentType.PRODUCTION
    secret_key: str = "esmerald-insecure-h35r*b9$+hw-x2hnt5c)vva=!zn$*a7#"  # auto generated

    @cached_property
    def db_connection(self) -> "Registry":
        """
        To make sure the registry and the database connection remains the same
        all the time, always use the cached_property.
        """
        from edgy import Registry

        return Registry(database="postgresql+asyncpg://user:pass@localhost:5432/my_database")

    # optional, in case we want a centralized place
    @cached_property
    def edgy_settings(self) -> "EdgySettings":
        from edgy import EdgySettings

        return EdgySettings(preloads=["myproject.models"])

The utils

Now we create the utils.py where we appy the LRU technique.

myproject/utils.py
from functools import lru_cache


@lru_cache()
def get_db_connection():
    # Encapsulate to delay the import
    from esmerald.conf import settings

    return settings.db_connection

Note: here we cannot just import settings. We should wait until build_path is called.

The models

We can now start creating our models and making sure we keep them always in the same registry

myproject/apps/accounts/models.py
from datetime import datetime
from enum import Enum

from my_project.utils import get_db_connection

import edgy

registry = get_db_connection()


class ProfileChoice(Enum):
    ADMIN = "ADMIN"
    USER = "USER"


class BaseModel(edgy.Model):
    class Meta:
        abstract = True
        registry = registry


class User(BaseModel):
    """
    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 Profile(BaseModel):
    """
    A profile for a given user.
    """

    user: User = edgy.OneToOneField(User, on_delete=edgy.CASCADE)
    profile_type: ProfileChoice = edgy.ChoiceField(ProfileChoice, default=ProfileChoice.USER)

Here applied the inheritance to make it clean and more readable in case we want even more models.

As you could also notice, we are importing the get_db_connection() previously created. This is now what we will be using everywhere.

Prepare the application to allow migrations

Now it is time to tell the application that your models and migrations are managed by Edgy. More information on migrations where explains how to use it.

myproject/main.py
#!/usr/bin/env python
"""
Generated by 'esmerald-admin createproject'
"""

import os
import sys

from esmerald import Esmerald, Include
from my_project.utils import get_db_connection


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

    if SITE_ROOT not in sys.path:
        sys.path.append(SITE_ROOT)
        # in case of an application model with apps
        sys.path.append(os.path.join(SITE_ROOT, "apps"))


def get_application():
    """
    Encapsulating in methods can be useful for controlling the import order but is optional.
    """
    # first call build_path
    build_path()
    # because edgy tries to load settings eagerly
    from edgy import monkay, Instance

    registry = get_db_connection()
    # ensure the settings are loaded
    monkay.evaluate_settings_once(ignore_import_errors=False)

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

    monkay.set_instance(Instance(app=app, registry=registry))
    return app


app = get_application()

This will make sure that your application migrations are now managed by Edgy.

Hook the connection

As a final step we now need to make sure we hook the connection in our application. We use an approach for the central management of configuration via esmerald. For this we provide a settings forwarder. You can also remove the settings forward and manage edgy settings via environment variable too.

myproject/main.py
#!/usr/bin/env python
"""
Generated by 'esmerald-admin createproject'
"""

import os
import sys

from esmerald import Esmerald, Include


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

    if SITE_ROOT not in sys.path:
        sys.path.append(SITE_ROOT)
        # in case of an application model with apps
        sys.path.append(os.path.join(SITE_ROOT, "apps"))


def disable_edgy_settings_load():
    os.environ["EDGY_SETTINGS_MODULE"] = ""


def get_application():
    """
    Encapsulating in methods can be useful for controlling the import order but is optional.
    """
    # first call build_path
    build_path()
    # this is optional, for rewiring edgy settings to esmerald settings
    disable_edgy_settings_load()  # disable any settings load
    # import edgy now
    from edgy import Instance, monkay
    from esmerald.conf import settings

    monkay.settings = lambda: settings.edgy_settings  # rewire
    monkay.evaluate_settings_once(ignore_import_errors=False)  # import manually

    # now the project is in the search path and we can import
    from my_project.utils import get_db_connection

    registry = get_db_connection()

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

    monkay.set_instance(Instance(registry=registry, app=app))
    return app


app = get_application()

And this is it.

Notes

The above example shows how you could take leverage of a centralised place to manage your connections and then use it across your application keeping your code always clean not redundant and beautiful.

This example is applied to any of your favourite frameworks and you can use as many and different techniques as the ones you see fit for your own purposes.

Edgy is framework agnostic.