Directives¶
What are these directives? In simple terms, those are special command-line
scripts that run special
pieces of code for Lilya.
Important¶
Before reading this section, you should get familiar with the ways Lilya handles the discovery of the applications.
The following examples and explanations will be using the --app and environment variables approach but the auto discovery is equally valid and works in the same way.
Built-in Lilya directives¶
Starting a project can be troublesome for some people mostly because there questions about the structure of the files and folders and how to maintain the consistency.
A lot of people cannot be bothered with running cookiecutters and go straight to their own design.
Check
Lilya is in no way, shape or form opinionated about the application structure of any application but it provides some suggested options but it does not mean it should always be in that way. It simply serves as an option.
Currently there are a few built-in directives.
- directives - Lists all the available directives.
- createproject - Used to generate a scaffold for a project.
- createapp - Used to generate a scaffold for an application.
- createdeployment - Used to generate files for a deployment with docker, nginx, supervisor and gunicorn.
- show_urls - Shows the information about the your lilya application.
- shell - Starts the python interactive shell for your Lilya application.
Help¶
To the help of any directive, run --help
in front of each one.
Example:
$ lilya runserver --help
Available Lilya Directives¶
List Available Directives¶
This is the most simple directive to run and lists all the available directives from Lilya
and with a flag --app
shows also the available directives in your project.
Only lilya directives
$ lilya directives
All the directives including your project
$ lilya --app myproject.main:app directives
Or
$ export LILYA_DEFAULT_APP=myproject.main:app
$ lilya directives
Create project¶
This is a simple directive that generates a folder structure with some files for your Lilya project.
Parameters¶
-
--with-deployment - Flag indicating if the project generation should include deployment files.
Default:
False
-
--deployment-folder-name - The custom name of the folder where the deployment files will be placed if
--with-deployment
isTrue
.Default:
deployment/
-
--with-structure - Creates a project with a given structure of folders and files.
-
-v/--verbosity -
1
for none and2
displays all generated files.Default:
1
$ lilya createproject <YOUR-PROJECT-NAME>
The directive will generate a tree of files and folders with some pre-populated files ready to be used.
Note
By default, Lilya will generate a simple project structure wth the bare minimum unless --with-structure
flag is specified
Example:
Starting a project with some default and some structure.
$ lilya createproject myproject --with-structure
You should have a folder called myproject
with a similar structure to this:
.
├── Taskfile.yaml
├── myproject
│ ├── apps
│ │ └── __init__.py
│ ├── configs
│ │ ├── development
│ │ │ ├── __init__.py
│ │ │ └── settings.py
│ │ ├── __init__.py
│ │ ├── settings.py
│ │ └── testing
│ │ ├── __init__.py
│ │ └── settings.py
│ ├── __init__.py
│ ├── main.py
│ ├── serve.py
│ ├── tests
│ │ ├── __init__.py
│ │ └── test_app.py
│ └── urls.py
└── requirements
├── base.txt
├── development.txt
└── testing.txt
A lot of files generated right? Yes but those are actually quite simple but let's talk about what is happening there.
-
Taskfile.yaml - This is a special file provided by the directive that contains some useful commands to run your peoject locally, for example:
task run
- Starts your project with the development settings.make test
- Runs your local tests with the testing settings.task clean
- Removes all the*.pyc
from your project.task requirements
- Installs the mininum requirements from therequirements
folder.
Info
The tests are using pytest but you can change to whatever you want.
-
serve.py - This file is simply a wrapper that is called by the
task run
and starts the local development. This should not be used in production. - main.py - The main file that builds the application path and adds it to the
$PYTHONPATH
. This file can also be used to add extra configurations as needed. - urls.py - Used as an entry-point for the application urls. This file is already being imported via
Include inside the
main.py
.
Apps¶
What is an app in the Lilya context?¶
An app is another way of saying that is a python module that contains your code and logic for the application.
As mentioned before, this is merely suggestive and in no way, shape or form consitutes as the unique way of building Lilya applications.
The apps
is a way that can be used to isolate your apis from the rest of the structure. This folder is already
added to the python path via main.py
.
You can simply ignore this folder or used it as intended, nothing is mandatory, we simply believe that besides a clean code, a clean structure makes everything more pleasant to work and maintain.
So, you are saying that we can use the apps to isolate the apis and we can ignore it or use it. Do you also provide any other directive that suggests how to design an app, just in case if we want?
Actually, we do! You can also use the createapp directive to also generate a scaffold for an app.
Create app¶
This is another directive that allows you to generate a scaffold for a possible app to be used within Lilya.
Parameters¶
-
-v/--verbosity -
1
for none and2
displays all generated files.Default:
1
$ lilya createapp <YOUR-APP-NAME>
Example:
Using our previous example of create project, let's use the already created myproject
.
$ cd myproject/apps/
$ lilya createapp accounts
You should have a folder called accounts
with a similar structure to this:
.
├── Makefile
├── myproject
│ ├── apps
│ │ ├── accounts
│ │ │ ├── directives
│ │ │ │ ├── __init__.py
│ │ │ │ └── operations
│ │ │ │ └── __init__.py
│ │ │ ├── __init__.py
│ │ │ ├── tests.py
│ │ │ └── v1
│ │ │ ├── __init__.py
│ │ │ ├── schemas.py
│ │ │ ├── urls.py
│ │ │ └── controllers.py
│ │ └── __init__.py
│ ├── configs
│ │ ├── development
│ │ │ ├── __init__.py
│ │ │ └── settings.py
│ │ ├── __init__.py
│ │ ├── settings.py
│ │ └── testing
│ │ ├── __init__.py
│ │ └── settings.py
│ ├── __init__.py
│ ├── main.py
│ ├── serve.py
│ ├── tests
│ │ ├── __init__.py
│ │ └── test_app.py
│ └── urls.py
└── requirements
├── base.txt
├── development.txt
└── testing.txt
As you can see, myproject/apps
contains an app called accounts
.
By default, the createapp
generates a python module with a v1
sub-module that contains:
- schemas.py - Empty file with a simple pydantic
BaseModel
import and where you can place any, as the import suggests, pydantic model to be used with theaccounts/v1
. - urls.py - You can place the urls of the views of your
accounts/v1
. - controllers.py - You can place all the handlers and views of your
accounts/v1
.
A tests file is also generated suggesting that you could also add some specific application tests there.
Check
Using a version like v1
will make it clear which version of APIs should be developed within that same
module and for that reason a default v1
is generated, but again, nothing is set in stone and you are free
to simply ignore this.
After generation¶
Once the project and apps are generated, executing task run
will throw a ImproperlyConfigured
exception. This
is because the urls.py
expects to be populated with application details.
Example¶
Let's do an example using exactly what we previously generated and put the application up and running.
The current structure:
.
├── Makefile
├── myproject
│ ├── apps
│ │ ├── accounts
│ │ │ ├── directives
│ │ │ │ ├── __init__.py
│ │ │ │ └── operations
│ │ │ │ └── __init__.py
│ │ │ ├── __init__.py
│ │ │ ├── tests.py
│ │ │ └── v1
│ │ │ ├── __init__.py
│ │ │ ├── schemas.py
│ │ │ ├── urls.py
│ │ │ └── controllers.py
│ │ └── __init__.py
│ ├── configs
│ │ ├── development
│ │ │ ├── __init__.py
│ │ │ └── settings.py
│ │ ├── __init__.py
│ │ ├── settings.py
│ │ └── testing
│ │ ├── __init__.py
│ │ └── settings.py
│ ├── __init__.py
│ ├── main.py
│ ├── serve.py
│ ├── tests
│ │ ├── __init__.py
│ │ └── test_app.py
│ └── urls.py
└── requirements
├── base.txt
├── development.txt
└── testing.txt
What are we going to do?
- Add a view to the accounts.
- Add the path to the
urls
of the accounts. - Add the
accounts
urls to the application urls. - Start the application.
Create the view¶
async def home():
return {"message": "Welcome home!"}
Create a view to return the message Welcome home!
.
Add the view to the urls¶
Now it is time to add the newly created view to the urls of the accounts.
from lilya.routing import Path
from .views import home
route_patterns = [Path("/home", home)]
Add the accounts urls to the application urls¶
Now that we have created the views and the urls for the accounts, it is time to add the accounts to the application urls.
Let's update the myproject/urls.py
.
from lilya.routing import Include
route_patterns = [Include("/api/v1", namespace="accounts.v1.urls")]
And that is it! The application is assembled and you can now start the application.
Start the application¶
Remember that a Taskfile.yaml
that was also generated? Let's use it to start the application.
task run
What this command is actually doing is:
LILYA_SETTINGS_MODULE=myproject.configs.development.settings.DevelopmentAppSettings python -m myproject.serve
If you want another settings you can simply update the command to run with your custom settings.
Once the application starts, you should have an output in the console similar to this:
INFO: Uvicorn running on http://localhost:8000 (Press CTRL+C to quit)
INFO: Started reloader process [4623] using WatchFiles
INFO: Started server process [4625]
INFO: Waiting for application startup.
INFO: Application startup complete.
Auto generated test files¶
The test files generated are using the TestClient, so make sure you run:
$ pip install lilya[full]
Or you can skip this step if you don't want to use the TestClient at all.
Create deployment¶
This is another directive that allows you to generate a scaffold for a deployment using nginx, supervisor, gunicorn and docker.
Note
This generates a ready based files containing the minimum information need to speedup the deployment process and can/should be adapted to your needs but at least 80% of the configurations are already prepared for you.
The Dockerfile
image comes with the minimum version of Python 3.12. It is recommended to update accordingly
if you have any restrictions.
There are two ways of generating the deployments. One with the createproject and providing the necessary flags and the other one in isolation.
This directive is considered in isolation.
Parameters¶
-
--deployment-folder-name - The custom name of the folder where the files will be placed.
Default:
deployment/
-
-v/--verbosity -
1
for none and2
displays all generated files.Default:
1
The default run and syntax is as follow:
$ lilya createdeployment <YOUR-PROJECT-NAME>
Example:
Using our previous example of create project, let's use the already created myproject
.
$ cd myproject/
$ lilya createdeployment myproject
You should have a folder called deployment
with a similar structure to this:
.
├── deployment
│ ├── docker
│ │ └── Dockerfile
│ ├── gunicorn
│ │ └── gunicorn_conf.py
│ ├── nginx
│ │ ├── nginx.conf
│ │ └── nginx.json-logging.conf
│ └── supervisor
│ └── supervisord.conf
├── Makefile
├── myproject
│ ├── apps
│ │ └── __init__.py
│ ├── configs
│ │ ├── development
│ │ │ ├── __init__.py
│ │ │ └── settings.py
│ │ ├── __init__.py
│ │ ├── settings.py
│ │ └── testing
│ │ ├── __init__.py
│ │ └── settings.py
│ ├── __init__.py
│ ├── main.py
│ ├── serve.py
│ ├── tests
│ │ ├── __init__.py
│ │ └── test_app.py
│ └── urls.py
└── requirements
├── base.txt
├── development.txt
└── testing.txt
As you can see, all of the minimum files for your project are generated inside a default deployment/
folder and ready to be used saving you a tremendous amount of time.
But, what if you want to provide a different name for the deployment folder instead of deployment/
?
Well, thanks to the parameter --deployment-folder-name
you can specify the name of the folder and
that will also reflect in the files.
Example:
Let us use myproject
as an example and call the folder deploy
instead of deployment
.
$ lilya createdeployment myproject --deployment-folder-name deploy
Once the directive runs, You should have a folder called deploy
with a similar structure to this:
.
├── deploy
│ ├── docker
│ │ └── Dockerfile
│ ├── gunicorn
│ │ └── gunicorn_conf.py
│ ├── nginx
│ │ ├── nginx.conf
│ │ └── nginx.json-logging.conf
│ └── supervisor
│ └── supervisord.conf
├── Makefile
├── myproject
│ ├── apps
│ │ └── __init__.py
│ ├── configs
│ │ ├── development
│ │ │ ├── __init__.py
│ │ │ └── settings.py
│ │ ├── __init__.py
│ │ ├── settings.py
│ │ └── testing
│ │ ├── __init__.py
│ │ └── settings.py
│ ├── __init__.py
│ ├── main.py
│ ├── serve.py
│ ├── tests
│ │ ├── __init__.py
│ │ └── test_app.py
│ └── urls.py
└── requirements
├── base.txt
├── development.txt
└── testing.txt
Run the Dockerfile¶
Since everything is already provided and your changes into the files are reflected, for example, making sure the requirements are installed inside the docker image, you can run the docker build for that same image directly from yhr project root.
Example
Using the myproject
example, it would be something like this:
$ docker build -t myorg/myproject:latest -f deployment/docker/Dockerfile .
Tip
If you are not familiar with Docker, it is highly recommended to read the official documentation and get yourself familiar with it.
This should trigger the whole process of your Dockerfile
and install everything accordingly.
Warning
If you don't want the same locations for the generated files, you can simply move them to any place at your discretion and update the files accordingly to reflect your custom settings.
Show URLs¶
This is another built-in Lilya application and it simply to show the information about the URLs of your application via command line.
This command can be run like this:
Tip
Lilya before trying anything, will try to go through some defaults and try to find a Lilya application automatically for you. If that is not found, you can then follow the next following instructions.
Using the --app parameter
$ lilya --app myproject.main:app show_urls
Using the LILYA_DEFAULT_APP environment variable already exported:
$ lilya myproject.main:app show_urls
Runserver¶
This is an extremly powerfull directive and it should only be used for development purposes.
This directive helps you starting your local development in a simple way, very similar to the
runserver
from Django, actually, since it was inspired by it, the same name was kept.
Danger
To use this directive, uvicorn
must be installed.
Parameters¶
-
-p/--port - The port where the server should start.
Default:
8000
-
-r/--reload - Reload server on file changes.
Default:
True
-
--host - Server host. Tipically
localhost
.Default:
localhost
-
--debug - Start the application in debug mode.
Default:
True
-
--log-level - What log level should uvicorn run.
Default:
debug
-
--lifespan - Enable lifespan events. Options:
on
,off
,auto
.Default:
on
-
--settings - Start the server with specific settings. This is an alternative to LILYA_SETTINGS_MODULE way of starting.
Default:
None
How to use it¶
Runserver has some defaults and those are the ones tipically used for development but let us run some of the options to see how it would look like.
Warning
The following examples and explanations will be using the auto discovery approach but the --app and environment variables is equally valid and works in the same way.
Run on a different port¶
$ lilya runserver -p 8001
Run on a different host¶
Although it will still be localhost, we just run againt the IP directly.
$ lilya runserver --host 127.0.0.1
Run with a different lifespan¶
$ lilya runserver --lifespan auto
Run with different settings¶
As mentioned before, this is an alternative to the LILYA_SETTINGS_MODULE approach and it should only be used for development purposes.
Use one or the other.
Let us assume the following structure of files and folders that will contain different settings.
.
├── Taskfile.yaml
└── src
├── __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
As you can see, we have three different types of settings:
- development
- testing
- production settings
Run with development settings
$ lilya runserver --settings src.configs.development.settings.DevelopmentAppSettings
Running with LILYA_SETTINGS_MODULE would be:
$ LILYA_SETTINGS_MODULE=src.configs.development.settings.DevelopmentAppSettings lilya runserver
Run with testing settings
$ lilya runserver --settings src.configs.testing.settings.TestingAppSettings
Running with LILYA_SETTINGS_MODULE would be:
$ LILYA_SETTINGS_MODULE=src.configs.testing.settings.TestingAppSettings lilya runserver
Run with production settings
$ lilya runserver --settings src.configs.settings.AppSettings
Running with LILYA_SETTINGS_MODULE would be:
$ LILYA_SETTINGS_MODULE=src.configs.settings.AppSettings lilya runserver