This guide explains how to structure your Stelvio project and how Stelvio finds
and loads your infrastructure code.
Installation
Since Stelvio is used for infrastructure deployment rather than application
runtime, you might want to install it as a development or CI dependency:
# As regular dependency
uvaddstelvio
# As dev dependency
uvadd--devstelvio
# As regular dependency
poetryaddstelvio
# As dev dependency
poetryadd--groupdevstelvio
# As regular dependency
pipinstallstelvio
# In requirements-dev.txtecho"stelvio">>requirements-dev.txt
pipinstall-rrequirements-dev.txt
Critical: Component Creation Order
The Rule: Stelvio components can only be created after the @app.config
function runs. This happens automatically when the CLI loads your project.
You can import Stelvio classes anywhere:
# Always fine to import Stelvio classesfromstelvio.aws.functionimportFunctionfromstelvio.aws.dynamo_dbimportDynamoTable
Don't Import Files with Top-Level Components
Say you define Dynamo table in infra/tables.py:
infra/tables.py
# - This will cause an error if imported at the top level stlv_app.pyfromstelvio.aws.dynamo_dbimportDynamoTable# This creates a component at import time, before config is loadedusers_table=DynamoTable(name="users",...)# Error: "Stelvio context not initialized"
Then if you do this in stlv_app.py it will fail:
stlv_app.py
# stlv_app.py - This will failfrominfra.tablesimportusers_table# Imports file that creates components
The problem is that python has eager imports - file is executed upon import. So
when stlv_app.py file is loaded Python will import infra/tables.py
and it will be also execute it, trying to create users_table = DynamoTable(...
before Stelvio had a chance to call configuration function.
You have two good solutions:
Solution 1: Import Functions and Call from @app.run
infra/tables.py
fromstelvio.aws.dynamo_dbimportDynamoTabledefcreate_tables():users_table=DynamoTable(name="users",...)# Works inside functionreturnusers_table
stlv_app.py
frominfra.tablesimportcreate_tables# Fine to import function@app.rundefrun()->None:users_table=create_tables()# Works when called in run
Solution 2: Use Module Auto-Discovery
stlv_app.py
# Using glob patternsapp=StelvioApp("my-project",modules=["infra/**/*.py"])# Or explicit module namesapp=StelvioApp("my-project",modules=["infra.tables","infra.api","infra.functions"])
infra/tables.py
fromstelvio.aws.dynamo_dbimportDynamoTableusers_table=DynamoTable(name="users",...)# Works at module level with auto-discovery
Third Solution: import inside run function
You can also import your modules with top level definitions inside function
marked with @app.run like this:
And while it is technically correct and it will work it's discouraged in
Python (See PEP8).
Also IDEs, linters or other tools might flag or remove such imports as they're
unused.
Auto-Discovery Requirements
With auto-discovery, components must be created at module level (top of
file) because Stelvio only imports the modules - it doesn't call any functions
inside them. The timing works because Stelvio imports these files after the
config is loaded.
Importing Between Infrastructure Files
Of course, you can and import between your infrastructure files:
# infra/functions.pyfromstelvio.aws.functionimportFunctionfrominfra.storage.usersimportusers_table# Importing from other infrastructure filesusers_func=Function(name="process-users",handler='functions/users.process',links=[users_table])
This allows you to organize your infrastructure in different files.
Project Organization
Stelvio is flexible about how you organize your code. Here are some common
patterns:
my-project/
├── __main__.py
├── stlv_app.py
└── services/
├── users/
│ ├── stlv.py # Any file names works as far as it's defined in modules
│ └── handler.py
└── orders/
└── stlv.py
└── handler.py
Project Organization Tips
To avoid conflicts with your application code and frameworks:
Keep infrastructure code separate from application code
Be mindful of framework auto-loaders that might scan all .py files
Consider adding infrastructure paths to framework exclude lists
Next Steps
Now that you understand project structure in Stelvio, you might want to explore: