Quickstart
A Minimal Application
A minimal Air application:
main.py
import air
app = air.Air()
@app.get('/')
async def index():
return air.H1('Hello, world')
So what does this code do?
- First we import the air project
- Next we instantiate the Air app.
air.Air
is just a convenience wrapper aroundfastapi.FastAPI
that sets thedefault_response_class
to beair.AirResponse
- We define a GET route using
@app.get
, with comes with a response class ofAirResponse
. Now, when we return Air Tags, they are automatically rendered as HTML - We return
air.H1
, which renders as an<h1></h1>
tag. The response type istext/html
, so browsers display web pages
main.py
import air
from fastapi import Request
app = Air()
jinja = air.Jinja2Renderer(directory="templates")
@app.get('/')
async def index(request: Request):
return jinja(
request,
name="home.html"
)
templates/home.html
<h1>Hello, world</h1>
So what does this code do?
- First we import the air project and a few select things from FastAPI.
- Next we instantiate the Air app.
air.Air
is just a convenience wrapper aroundfastapi.FastAPI
that sets thedefault_response_class
to beair.AirResponse
- We use
Jinja2Renderer
factory to configure arender()
shortcut. This is easier to remember and faster to type thantemplate.TemplateResponse
- We define a GET route using
@app.get
. Unlike normal FastAPI projects using Jinja we don't need to set theresponse_class
to HtmlResponse. That's because theair.Air
wrapper handles that for us - Our return calls
render()
, which reads the specified Jinja2 template and then produces the result as an<h1></h1>
tag. The response type istext/html
, so browsers display web pages
Running Applications
To run your FastAPI application with uvicorn:
uvicorn main:app --reload
Where:
main
is the name of your Python file (main.py)app
is the name of your FastAPI instance--reload
enables auto-reloading when you make changes to your code (useful for development)
Once the server is running, open your browser and navigate to:
- http://localhost:8000 - Your application
The app.page
decorator
For simple HTTP GET requests, Air provides the handy @app.page
shortcut.
main.py
import air
app = air.Air()
@app.page
def dashboard():
return H1('Dashboard')
main.py
import air
from fastapi import Request
app = air.Air()
jinja = air.Jinja2Renderer(directory="templates")
@app.page
async def dashboard(request: Request):
return jinja(
request,
name="dashboard.html"
)
templates/dashboard.html
<h1>Dashboard</h1>
Form Validation with Air Forms
Built on pydantic's BaseModel
, the air.AirForm
class is used to validate data coming from HTML forms.
Form handling in views
main.py
from typing import Annotated
from fastapi import Depends, Request
from pydantic import BaseModel
import air
app = air.Air()
class CheeseModel(BaseModel):
name: str
age: int
class CheeseForm(air.AirForm):
model = CheeseModel
@app.page
async def cheese():
return air.Html(
air.H1("Cheese Form"),
air.Form(
air.Input(name="name"),
air.Input(name="age", type="number"),
air.Button("Submit", type="submit"),
method="post",
action="/cheese-info",
),
)
@app.post("/cheese-info")
async def cheese_info(request: Request):
cheese = await CheeseForm.validate(request)
if cheese.is_valid:
return air.Html(air.H1(cheese.data.name))
return air.Html(air.H1(f"Errors {len(cheese.errors)}"))
main.py
import air
from fastapi import Request, Depends
from pydantic import BaseModel
from typing import Annotated
app = air.Air()
jinja = air.Jinja2Renderer(directory="templates")
class CheeseModel(BaseModel):
name: str
age: int
class CheeseForm(air.AirForm):
model = CheeseModel
@app.page
async def cheese(request: Request):
return jinja(request, name="cheese_form.html")
@app.post("/cheese-info")
async def cheese_info(request: Request):
cheese = await CheeseForm.validate(request)
return jinja(request, name="cheese_info.html", cheese=cheese)
templates/cheese_form.html
<h1>Cheese Form</h1>
<form method="post" action="/cheese-info">
<input name="name">
<input name="age" type="number">
<button type="submit">Submit</button>
</form>
templates/cheese_info.html
{% if cheese.is_valid %}
<h1>{{cheese.data.name}}</h1>
<p>Age: {{cheese.data.age}}</p>
{% else %}
<h1>Errors {{len(cheese.errors)}}</h1>
{% endif %}
Form handling using dependency injection
It is possible to use AirForms through FastAPI's dependency injection mechanism.
main.py
from typing import Annotated
from fastapi import Depends
from pydantic import BaseModel
import air
app = air.Air()
class CheeseModel(BaseModel):
name: str
age: int
class CheeseForm(air.AirForm):
model = CheeseModel
@app.page
async def cheese():
return air.Html(
air.H1("Cheese Form"),
air.Form(
air.Input(name="name"),
air.Input(name="age", type="number"),
air.Button("Submit", type="submit"),
method="post",
action="/cheese-info",
),
)
@app.post("/cheese-info")
async def cheese_info(cheese: Annotated[CheeseForm, Depends(CheeseForm.validate)]):
if cheese.is_valid:
return air.Html(air.H1(cheese.data.name))
return air.Html(air.H1(f"Errors {len(cheese.errors)}"))
main.py
import air
from fastapi import Request, Depends
from pydantic import BaseModel
from typing import Annotated
app = air.Air()
jinja = air.Jinja2Renderer(directory="templates")
class CheeseModel(BaseModel):
name: str
age: int
class CheeseForm(air.AirForm):
model = CheeseModel
@app.page
async def cheese(request: Request):
return jinja(request, name="cheese_form.html")
@app.post("/cheese-info")
async def cheese_info(
request: Request, cheese: Annotated[CheeseForm, Depends(CheeseForm.validate)]
):
return jinja(request, name="cheese_info.html", cheese=cheese)
templates/cheese_form.html
<h1>Cheese Form</h1>
<form method="post" action="/cheese-info">
<input name="name">
<input name="age" type="number">
<button type="submit">Submit</button>
</form>
templates/cheese_info.html
{% if cheese.is_valid %}
<h1>{{cheese.data.name}}</h1>
<p>Age: {{cheese.data.age}}</p>
{% else %}
<h1>Errors {{len(cheese.errors)}}</h1>
{% endif %}