Skip to main content

FastAPI modules loading for better structure in your projects (like Django AppConfig)

Project description

fastapi-module-loader

This library allows you to load modules in a structured and well defined way. The overall idea is explained pretty quick, we have a list of modules and then ensure all those are basically imported. Also we allow the module classes to execute additional code on loading. This is useful to for example resolve forward references with pydantic, to register some hooks or just load all SQLAlchemy ORM models so relationships work.

The module loader idea is based on what Django does using AppConfig's - so if you know those you might find this pretty straight forward.

Example FastAPI setup

Lets say we are using the following folder structure in your project, modules may contain a list of modules used by your application:

yourapp
|-- modules
|   `-- something
|       `-- __init__.py     <- SomethingModule is defined here, see technical details below
|-- config.py               <- Your config file
|-- loader.py               <- This is where you put the global loader instance
`-- main.py                 <- Your normal FastAPI application lives here

Now lets put the MODULES list into config.py:

MODULES = [
    "yourapp.modules.something.SomethingModules",
]

Then add the loader to your loader.py:

from fastapi_module_loader import ModuleLoader

from yourapp.config import MODULES


loader = ModuleLoader(MODULES)

Note: We are NOT calling loader.load() or loader.setup() here. This is because we want to do this in our main.py file so we have control when this actually happens.

Then change your main.py to look like this:

from fastapi import FastAPI

from yourapp.loader import loader


@asynccontextmanager
async def lifespan(app: FastAPI):
    loader.setup()  # Setup everything on FastAPI startup
    yield


loader.load()  # Ensure everything is loaded
app = FastAPI(lifespan=lifespan)

# ...put the actual FastAPI code here

Use cases

Those are some use cases we encountered while working on projects. Those use cases led us to create this library:

  • main.py: Load the modules to ensure everything is setup for the FastAPI app.
  • In alembic migrations env.py: Ensure all orm.py are loaded and the alembic migrations can "see" all the ORM models. Otherwise, detecting any changes would not be possible at all.
    (see https://alembic.sqlalchemy.org/en/latest/tutorial.html#the-migration-environment)
  • To register any hooks bound to async-signals Signal instances.
    (see https://github.com/team23/async-signals)
  • There will be many additional use cases for using the module loader. If you have any interesting use case please open an issue and tell us about it. 😉

Technical details

Basic structure

The class ModuleLoader is the main entry point for the module loader system. It needs to be instantiated with a list of modules as strings. You have to initialize the module loader in some global place for your application. This can be done directly in main.py, although it is recommended to use a separate file for this like loader.py.

Getting you own loader instance can look like this:

from fastapi_module_loader import ModuleLoader


# We recommend putting the module list into your normal config file - if you have one
MODULES = [
    "yourapp.modules.something.SomethingModules",
    "yourapp.modules.something_else.SomethingElseModules",
    "yourapp.modules.etc.EtcModules",
]


loader = ModuleLoader(MODULES)

The actual loading process then works like this:

  1. Using loader.load() will import all the modules passed to the constructor. It will use importlib to load the module and then instantiate the module class. After this is done it will call the load() method on the modules - you can use this hook to load additional modules or similar.
  2. The loader.setup() will execute the setup() method of all the modules. This is where the modules should do additional setup work like resolving ForwardRef's or registering hooks.
    Note that loader.setup() will actually call pre_setup(), then setup() and finally post_setup() on the modules. This allows you to have different stages of your setup process.

Module structure

A module is a class that inherits from BaseModule. It may implement the setup() and other methods as hooks. All methods will be called on module loading/setup.

Example for a module:

from fastapi_module_loader import BaseModule


class SomethingModule(BaseModule):
    def setup(self):  # providing a setup method is optional
        print("Hello world!")

Loading process

loader.load() will import all the modules and then call the load() method on them. This is the place where you should load further modules if you need to.

from yourapp.loader import loader  # Import has to match your own setup


loader.load()  # will import all modules and call load() on each of them

When all modules are loaded you may use loader.setup() to execute the setup() method on all modules. This is the place where you should do your actual setup work.

from yourapp.loader import loader  # Import has to match your own setup


loader.setup()  # will call pre_setup(), setup() and then post_setup() on all modules

Loading further modules in load()

It is a very common use case to load further modules in the setup() method. To support this use case the BaseModule class provides a method load_in_module() then will load given module names inside the current module scope.

Example:

from fastapi_module_loader import BaseModule


class SomethingModule(BaseModule):
    def setup(self):
        self.load_in_module("orm")

This will load yourapp.modules.something.orm if the module file itself is placed in yourapp/modules/something/__init__.py.

See this example structure:

yourapp
`-- modules
    `-- something
        |-- __init__.py     <- SomethingModule is defined here
        |-- etc...py
        |-- orm.py          <- This is loaded
        `-- etc...py

Note: For load_in_module to work you are required to put the modules class (SomethingModule in this example) into the __init__.py file of the module. Otherwise the module loader will not be able to find the module you want it to load.

Integrating the module loader

To ensure everything is setup properly you should just call the methods mentioned above. This may for example look like this:

from fastapi_module_loader import loader


loader.load()
loader.setup()


# Put the actual code here

Exceptions

The module loader will raise ImproperlyConfiguredModules if anything went wrong during handling the modules and loading those, this includes:

  • A module could not be imported
  • A module does not inherit from BaseModule
  • You are calling loader.setup() when loader.load() has not been called before

Note however that all exceptions raised by the setup() method of the modules will just be passed through. The module loader will not catch now handle them. This is also the case if a modules to be loaded by Self.load_in_module() does not exist. Generally you have to handle those errors in setup() yourself.

Contributing

If you want to contribute to this project, feel free to just fork the project, create a dev branch in your fork and then create a pull request (PR). If you are unsure about whether your changes really suit the project please create an issue first, to talk about this.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fastapi_module_loader-0.4.1.tar.gz (5.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fastapi_module_loader-0.4.1-py3-none-any.whl (7.5 kB view details)

Uploaded Python 3

File details

Details for the file fastapi_module_loader-0.4.1.tar.gz.

File metadata

File hashes

Hashes for fastapi_module_loader-0.4.1.tar.gz
Algorithm Hash digest
SHA256 9c535ebf05fb0e924fe72451bdafa1fe3c72f9ad79b064e3dbe58e4bc5cf606e
MD5 b31ed9e9ed2aa9cff89a81769d54168f
BLAKE2b-256 3fe314d9f7de57b0cacd869c3bca7eb307a61d82dd38425c36c05cbc09f8e0b4

See more details on using hashes here.

File details

Details for the file fastapi_module_loader-0.4.1-py3-none-any.whl.

File metadata

File hashes

Hashes for fastapi_module_loader-0.4.1-py3-none-any.whl
Algorithm Hash digest
SHA256 fcf13978bef3557f4fab15afc477790a721e3380edf827daa5c7a8788f70833d
MD5 a4a3b5e0fa9440b40d0a887a87dfe65e
BLAKE2b-256 8037fc15ddeef910b74ffe2c665478c9422ae34d7edcb3496980090ead74e8d9

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page