Getting Started¶
Installation¶
Today, there is a myriad of tools to start a Python project.
The fastlifeweb
package can be installed from PyPI with your preferred virtualenv
manager.
First Steps¶
We can start with a simple hello world app, the FastAPI first step, revisited.
hello world app¶
# file hello_world.py
from fastlife import Configurator, Response, Settings
async def hello_world() -> Response:
return Response("Hello World")
def build_app():
config = Configurator(Settings())
config.add_route("hello", "/", hello_world, methods=["GET"])
return config.build_asgi_app()
app = build_app()
In the example above, we can see that Fastlife build a FastAPI application.
In a classical FastAPI application, the application is created an route, routers,
are added to it.
Using fastlife, the process has been reversed in a dependency injection process,
a Configurator
object collect
all the routes, or any configuration like locale manager for i18n, and create
an app after everything has been collected.
The app build is a FastAPI instance.
Note
If you have used the Pyramid framework, you are already familiar with its Configurator. The Fastlife Configurator is a more naive implementation and less feature-rich.
The app can be started with the fastapi dev
command or run with uvicorn asgi server.
fastapi dev hello_world.py
hello world app with a template¶
# file hello_world_with_template.py
from pathlib import Path
from fastlife import Configurator, JinjaXTemplate, Settings
templates_dir = Path(__file__).parent / "templates"
class HelloWorld(JinjaXTemplate):
template = """
<!DOCTYPE html>
<html>
<body>
Hello World
</body>
</html>
"""
async def hello_world() -> HelloWorld:
return HelloWorld()
def build_app():
config = Configurator(Settings())
config.add_route("hello", "/", hello_world, methods=["GET"])
return config.build_asgi_app()
app = build_app()
The template is included inline, for many reasons.
The first one is to encourage the principle of locality of behavior, instead of the “separation of concern”.
The second reason is that it encore typing. While inlining template in the code,
we can add parameters to the returned object too, an instance of a
fastlife.domain.model.templates.JinjaXTemplate
is a self complete object
ready to be rendered.
Don’t be afraid, we have written html node here, for the introduction, but,
it is also encouraged to build a set of library component with pure template,
such as a Layout component to set the template to somethinf like
<Layout>Hello World</Layout>
.
Note
The fastlife.config.configurator.GenericConfigurator.add_template_search_path()
method is here to register your components library.
The most concerning can develop and register their own template engine using the
fastlife.config.configurator.GenericConfigurator.add_renderer()
.
modular approach¶
A maintainable application over time is an application that encourage modularity. In general, any application is divided into layers that have their own responsability.
The http views is one of them and now we are going split those views in a submodule.
Let’s write a simple views
module, with our previous view, and nothing more.
# views.py
from pathlib import Path
from fastlife import Configurator, JinjaXTemplate, configure, view_config
templates_dir = Path(__file__).parent
class HelloWorld(JinjaXTemplate):
template = "<Layout>Hello World</Layout>"
@view_config("hello_world", "/")
async def hello_world() -> HelloWorld:
return HelloWorld()
@configure
def includeme(config: Configurator):
config.add_template_search_path(templates_dir)
We have a library of component, created directly in the same directory for simplicity.
{# Layout.jinja #}
<!DOCTYPE html>
<html>
<body>
{{ content }}
</body>
<html>
And now, we can use the
config.include()
method to inject routes in the final application.
# entrypoint.py
from fastlife import Configurator, Settings
def build_app():
config = Configurator(Settings())
config.include("views")
return config.build_asgi_app()
app = build_app()
The config.include()
call will
import the module and all its submodules and grab all decorated method with a
@configure
afterwhat, the decorated method
will be called with the configurator as first argument in order to configure the module.
Note
If you have used the Pyramid framework, the config.include does not works exactly the same.
In Pyramid, there is no decorator on includeme
function, and the include does not
include submodule.
In fastlife, config.include()
works like if config.scan()
will also call the includeme
scanned.
In Pyramid, the view and the route are not registered together, views are registered by
a scan to set a view_name the the include attach the view name to routes in order to
ensure that the routes are always registered in the same order.
fastlife use a more traditional approach, and does not respect this strict approach,
this is why the @view_config also register the path of the view.
Writing tests¶
The fastlife.testing.testclient
module define a set of class to help
writing test for web pages.
Note
This module required the extra testing
installable via the command
pip install fastlifeweb[testing]
import pytest
from entrypoint import app
from fastlife.testing import WebTestClient
@pytest.fixture
def client():
return WebTestClient(app)
def test_views(client: WebTestClient):
page = client.get("/")
assert page.html.h1.text == "Hello world!"