Project structure
Apps spanning multiple files
When your project spans multiple files, more care is required to package the full structure for running or deploying on Modal.
There are two main considerations: (1) ensuring that all of your Functions get registered to the App, and (2) ensuring that any local dependencies get included in the Modal container.
Say that you have a simple project that’s distributed across three files:
src/
├── app.py # Defines the `modal.App` as a variable named `app`
├── llm.py # Imports `app` and decorates some functions
└── web.py # Imports `app` and decorates other functions
With this structure, if you deploy using modal deploy src/app.py
, Modal won’t
discover the Functions defined in the other two modules, because they never get
imported.
If you instead run modal deploy src/llm.py
, Modal will deploy the App with
just the Functions defined in that module.
One option would be to ensure that one module in the project transitively
imports all of the other modules and to point the modal deploy
CLI at it, but
this approach can lead to an awkard project structure.
Defining your project as a Python package
A better approach would be to define your project as a Python package and to use the Modal CLI’s “module mode” invocation pattern.
In Python, a package is a directory containing an __init__.py
file (and
usually some other Python modules). If you have a src/__init__.py
that
imports all of the member modules, it will ensure that any decorated Functions
contained within them get registered to the App:
# Contents of __init__.py
import .app
import .llm
import .web
Important: use relative imports (import .app
) between member modules.
Unfortunately, it’s not enough just to set this up and make your deploy command modal deploy src/app.py
. Instead, you need to invoke Modal in module mode: modal deploy -m src.app
. Note the use of the -m
flag and the module path
(src.app
instead of src/app.py
). Akin to python -m ...
, this incantation
treats the target as a package rather than just a single script.
App composition
As your project grows in scope, it may become helpful to organize it into multiple component Apps, rather than having the project defined as one large monolith. That way, as you iterate during development, you can target a specific component, which will build faster and avoid any conflicts with concurrent work on other parts of the project.
Projects set up this way can still be deployed as one unit by using App.include
.
Say our project from above defines separate Apps in llm.py
and web.py
and then
adds a new deploy.py
file:
# Contents of deploy.py
import modal
from .llm import llm_app
from .web import web_app
app = modal.App("full-app").include(llm_app).include(web_app)
This lets you run modal deploy -m src.deploy
to package everything in one
step.
Note: Since the multi-file app still has a single namespace for all functions, it’s important to name your Modal functions uniquely across the project even when splitting it up across files: otherwise you risk some functions “shadowing” others with the same name.
Including local dependencies
Another factor to consider is whether Modal will package all of the local dependencies that your App requires.
Even if your Modal App itself can be contained to a single file, any local
modules that file imports (like, say, a helpers.py
) also need to be available
in the Modal container.
By default, Modal will automatically include the module or package where a
Function is defined in all containers that run that Function. So if the project
is set up as a package and the helper modules are part of that package, you
should be all set. If you’re not using a package setup, or if the local
dependencies are external to your project’s package, you’ll need to explicitly
include them in the Image, i.e. with modal.Image.add_local_python_source
.
Note: This behavior changed in Modal 1.0. Previously, Modal would “automount” any local dependencies that were imported by your App source into a container. This was changed to be more selective to avoid unnecessary inclusion of large local packages.