Skip to content

Application Discovery

Saffier has many different ways of understanding the commands, one is via environment variables and another is via auto discovery.

Auto Discovery

If you are familiar with other frameworks like Django, you are surely familiar with the way the use the manage.py to basically run every command internally.

Although not having that same level, Saffier does a similar job by having "a guess" of what it should be and throws an error if not found or if no environment variables or --app are provided.

The application discovery works as an alternative to providing the --app or a SAFFIER_DEFAULT_APP environment variable.

So, what does this mean?

This means if you do not provide an --app or a SAFFIER_DEFAULT_APP, Saffier will try to find the application for you automatically.

Let us see a practical example of what does this mean.

Imagine the following folder and file structure:

myproject
.
├── Makefile
└── myproject
    ├── __init__.py
    ├── apps
       ├── __init__.py
    ├── configs
       ├── __init__.py
       ├── development
          ├── __init__.py
          └── settings.py
       ├── settings.py
       └── testing
           ├── __init__.py
           └── settings.py
    ├── main.py
    ├── tests
       ├── __init__.py
       └── test_app.py
    └── urls.py

Tip

The application can be anything from Esmerald, Starlette, Sanic and even FastAPI.

The structure above of myproject has a lot of files and the one higlighted is the one that contains the application object with the Migration from Saffier.

How does it work?

When no --app or no SAFFIER_DEFAULT_APP environment variable is provided, Saffier will automatically look for:

  • The current directory where saffier is being called contains a file called:

    • main.py
    • app.py
    • application.py

    Warning

    If none of these files are found, Saffier will look at the first children nodes, only, and repeats the same process. If no files are found then throws an CommandEnvironmentError exception.

  • Once one of those files is found, Saffier will analised the type of objects contained in the module and will check if any of them contains the Migration object attached and return it.

  • If Saffier understand that none of those objects contain the Migration, it will do one last attempt and try to find specific function declarations:

    • get_application()
    • get_app()

This is the way that Saffier can auto discover your application.

Note

Flask has a similar pattern for the functions called create_app. Saffier doesn't use the create_app, instead uses the get_application or get_app as a pattern as it seems cleaner.

Environment variables

When generating migrations, Saffier expects at least one environment variable to be present.

  • SAFFIER_DATABASE_URL - The database url for your database.

The reason for this is because Saffier is agnostic to any framework and this way it makes it easier to work with the migrations.

Also, gives a clean design for the time where it is needed to go to production as the procedure is very likely to be done using environment variables.

This variable must be present. So to save time you can simply do:

$ export SAFFIER_DATABASE_URL=postgresql+asyncpg://user:pass@localhost:5432/my_database

Or whatever connection string you are using.

Alternatively, you can simply pass --app as a parameter with the location of your application instead.

Example:

$ saffier --app myproject.main:app init

How to use and when to use it

Previously it was used a folder structure as example and then an explanation of how Saffier would understand the auto discovery but in practice, how would that work?

This is applied to any command within Saffier.

Let us see again the structure, in case you have forgotten already.

myproject
.
├── Makefile
└── src
    ├── __init__.py
    ├── apps
       ├── accounts
          ├── directives
             ├── __init__.py
             └── operations
                 └── __init__.py
    ├── configs
       ├── __init__.py
       ├── development
          ├── __init__.py
          └── settings.py
       ├── settings.py
       └── testing
           ├── __init__.py
           └── settings.py
    ├── main.py
    ├── tests
       ├── __init__.py
       └── test_app.py
    └── urls.py

The main.py is the file that contains the saffier migration. A file that could look like this:

myproject/src/main.py
#!/usr/bin/env python
import os
import sys
from pathlib import Path

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

from saffier.cli import Migrate


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)
    return app


app = get_application()

This is a simple example with two endpoints, you can do as you desire with the patterns you wish to add and with any desired structure.

What will be doing now is run the following commands using the auto discovery and the --app or SAFFIER_DEFAULT_APP:

  • init - Starts the migrations and creates the migrations folder.
  • makemigrations - Generates the migrations for the application.

We will be also executing the commands inside myproject.

You can see more information about these commands, including parameters, in the next section.

Using the auto discover

init

Using the auto discover
$ saffier init

Yes! Simply this and because the --app or a SAFFIER_DEFAULT_APP was provided, it triggered the auto discovery of the application that contains the saffier information.

Because the application is inside src/main.py it will be automatically discovered by Saffier as it followed the discovery pattern.

Using the --app or SAFFIER_DISCOVERY_APP

This is the other way to tell Saffier where to find your application. Since the application is inside the src/main.py we need to provide the proper location is a <module>:<app> format.

--app

With the --app flag.

$ saffier --app src.main:app init
SAFFIER_DEFAULT_APP

With the SAFFIER_DEFAULT_APP.

Export the env var first:

$ export SAFFIER_DEFAULT_APP=src.main:app

And then run:

$ saffier init

makemigrations

You can see more details how to use it.

It is time to run this command.

Using the auto discover
$ saffier makemigrations

Again, same principle as before because the --app or a SAFFIER_DEFAULT_APP was provided, it triggered the auto discovery of the application.

Using the --app or SAFFIER_DISCOVERY_APP
--app

With the --app flag.

$ saffier --app src.main:app makemigrations
SAFFIER_DEFAULT_APP

With the SAFFIER_DEFAULT_APP.

Export the env var first:

$ export SAFFIER_DEFAULT_APP=src.main:app

And then run:

$ saffier makemigrations