Settings for your FastAPI application + tests

Gustavo Caetano
4 min readMay 20, 2022

--

In this article I will present how to create settings for your application using pydantic models and how to load then during the application’s startup and the bonus of this article is the tests for the whole system.

Why should I use pydantic ? Pydantic provides a professional setup for your application AND it validates the variables types automatically, at the following link you can find field types than the presented ones in this article https://pydantic-docs.helpmanual.io/usage/types/.

1. Create the settings setup

Since you have your FastAPI application already done (if you do not have it, please check the following link), the settings folder should be created under your project folder and under this settings folder a .py file called <project_name>_settings.py should be created.

project_folder
- settings
- project_name_settings.py

2. Create the settings models and the .env file

In this example we will have 3 models in our settings, they are:

  • Database
  • Authentication settings
  • Domains of our application

All these variables will be loaded from a .env file and it will be possible with a custom config class that pydantic provides for us, the example below shows a possible way to create this models in a nested way:

#<project_name>_settings.pyclass ProjectDatabase(BaseSettings):
pg_dsn: PostgresDsn
class ProjectAuthentication(BaseSettings):
secret: SecretStr
algorithm: str
sub: str
class ProjectDomains(BaseSettings):
base_url: AnyHttpUrl
class ProjectSettings(BaseSettings):
db: ProjectDatabase
jwt: ProjectAuthentication
domains: ProjectDomains

class Config:
env_file: str = '.env'
env_prefix: 'project_'
env_nested_delimiter = '--'
case_sensitive: False

The config class allows us to use the .env file, however inside the file the variables should be configured using the provided layout in our config class, for example:

#.envPROJECT_DB__PG_DSN=<VALID_PG_URL>
PROJECT_JWT__SECRET=<VALID_PG_URL>
PROJECT_JWT__ALGORITHM=<VALID_PG_URL>
PROJECT_JWT__SUB=<VALID_STR>
PROJECT_DOMAINS__PG_DSN=<VALID_URL>

3. Create the loading settings and the get settings function

Since the models are done, functions to load them and retrieve this settings are needed, the example below shows how it is possible:

#<project_name>_settings.py__settings: Optional[ProjectSettings] = Noneasync def load_settings() -> Optional[ProjectSettings]:
global __settings
__settings = ProjectSettings()
async def get_settings() -> Optional[ProjectSettings]:
global __settings
if not __settings:
raise Exception('Project settings are not loaded')
return __settings

4. Load the settings during the FastAPI startup

Now all the needed stuffs are done, so we can go to the file where the FastAPI instance is created and create the startup event handler (for more information, please access the following link).

# main.py...@app.on_event('startup')
async def startup():
await load_settings()
...

Now, after the application’s start, it is possible to retrieve all the settings using the get_settings().

5. Advantages of this setup

This layout allow you run the tests independently of your env variables. For example: You can run your tests on Jenkins now without inserting the environment variables, then your application is running decoupled and in the right way :)

6. Create the test setup

For our tests, we will use the pytest, and we need to mock our FastAPI instance, because it will simulate an application that started. This fixture can be created in a conftest.py file where all the tests can access it.

# conftest.pyfrom project.app import app, startup
from project.settings.project_settings import get_settings
@pytest.fixture
async def app_lifespan():
await startup()
yield app

Now the last thing that it is needed for our test environment is the settings that we will load during the startup event handler, and this is easy peasy to provide for our tests since we have already built the function to retrieve them:

# conftest.pyfrom project.app import app, startup
from project.settings.project_settings import get_settings
...@pytest.fixture
async def default_settings():
return await get_settings()

7. Create tests

These examples will show how you can test the built setup with some insights that you can use to build better tests that make sense for your application. These tests will verify if the settings were loaded correctly and if everything that we defined in the models is right.

# test_settings.py@pytest.mark.asyncio
async def test_database_settings(app_lifespan, default_settings):
assert type(default_settings.db.pg_dsn) == PostgresDsn
assert default_settings.db.pg_dsn

The last example, we will test if our function can get the settings after the application started is working, and this task is simple, since we only need one fixture.

# test_settings.py@pytest.mark.asyncio
async def test_get_settings(app_lifespan):
settings = await get_settings()
assert settings
for attr in settings:
assert attr[1]

In the end we tested if the settings were loaded and if they are not None :)

8. Conclusion

In this article we learned how to create a professional settings for our FastAPI application where we can run our tests independently, it means that if we are using Jenkins, it does not need to know our env variables since it will be inside our docker container and pydantic can load them in our models with the created config class.

To validate our setup, we used pytest to create our tests, where we checked the loaded settings and if they are not null.

Note: If the .env is not correct configured, pydantic will raise validation exception.

--

--