Quickstart

A minimal Jivago application is shown below :

from jivago.jivago_application import JivagoApplication
from jivago.wsgi.annotations import Resource
from jivago.wsgi.methods import GET


@Resource("/")
class HelloResource(object):

    @GET
    def get_hello(self) -> str:
        return "Hello World!"


app = JivagoApplication()

if __name__ == '__main__':
    app.run_dev()

Notice that the example is made up of three separate parts:

  • A Resource class, which defines a route for our application;

  • The JivagoApplication object, which contains the application itself;

  • A __main__ function which runs our application in a debug environment, listening on port 4000.

Now, pointing a web browser to http://localhost:4000 should print our Hello World! message.

Component Auto-discovery

While defining our resource classes in our main file is definitely possible, it can become quite unwieldy. In fact, one of the key goals of the Jivago framework is to maintain loose-coupling of our components.

We will therefore move our resource classes into their own files, and use Jivago’s built-in package discovery mechanism to automatically register our routes.

hello_resource.py

from jivago.wsgi.annotations import Resource
from jivago.wsgi.methods import GET


@Resource("/")
class HelloResource(object):

    @GET
    def get_hello(self) -> str:
        return "Hello World!"

main.py

import my_hello_world_application
from jivago.jivago_application import JivagoApplication

app = JivagoApplication(my_hello_world_application)

if __name__ == '__main__':
    app.run_dev()
my_hello_world_application
 ├── __init__.py
 └── resources
     ├── __init__.py
     └── hello_resource.py
main.py

Note that, when creating the JivagoApplication object, a reference to the application’s root package is passed as the first argument. The root package should contain all Jivago-annotated classes. (i.e. @Resource, @Component, etc.)

The app object (main.py) should be outside of the explored package.

Warning : Since all python files are imported at run-time, any lines of code outside a class or a function will be executed before the application is started. It is therefore highly advised to avoid having any line of code outside a declarative block.

The Resource Class

The resource class is the fundamental way of declaring API routes. To define a route, simply declare the path inside the @Resource decorator on the class. Sub-paths can be defined on any of the class’ methods using the @Path decorator. Allowed HTTP methods have to be explicitly defined for each routing function. Use @GET, @POST, @PUT, @DELETE, etc.

Unlike other Python web frameworks, method invocation relies heavily on type annotations, which resemble the static typing present in other languages like C++ and Java. Given missing parameters, a method will not be invoked and simply be rejected at the framework level. For instance, declaring a route receiving a dict as a parameter matches a JSON-encoded request body. Request and Response objects can be requested/returned, when having direct control over low-level HTTP elements is required.

To use query or path parameters, parameters should be declared using the QueryParam[T], OptionalQueryParam[T] or PathParam[T] typing generics. In this case, T should either be str, int or float.

A complex resource example

from jivago.wsgi.annotations import Resource, Path
from jivago.wsgi.invocation.parameters import PathParam, QueryParam
from jivago.wsgi.methods import GET, POST, PUT
from jivago.wsgi.request.request import Request
from jivago.wsgi.request.response import Response


@Resource("/hello")
class HelloWorldResource(object):

    @GET
    def get_hello(self) -> str:
        return "Hello"

    @POST
    @Path("/{name}")
    def post_hello(self, name: PathParam[str]) -> str:
        return "name: {}".format(name)

    @Path("/request/json")
    @POST
    def read_request_body_from_dict(self, body: dict) -> dict:
        return {"the body": body}

    @GET
    @Path("/query")
    def with_query(self, name: QueryParam[str]) -> str:
        return "Hello {}!".format(name)

    @GET
    @Path("/request/raw")
    def read_raw_request(self, request: Request) -> Response:
        return Response(200, {}, "body")

While return type annotations are not strictly required, they are nonetheless recommended to increase readability and enforce stylistic consistency.

For manual route registration, see Manual Route Registration.

Serialization

Jivago supports the definition of DTO classes, which can be directly serialized/deserialized. These classes explicitly define a JSON schema and attribute typing, negating the need to use an external schema validator. To define a DTO, use the @Serializable decorator :

from jivago.lang.annotations import Serializable


@Serializable
class MyDto(object):
    name: str
    age: int

If a constructor is declared, it is used when deserializing. Otherwise, each attribute is set using __setattr__.

See Serialization for more details.

Dependency Injection

To allow for modularity and loose-coupling, dependency injection is built into the framework. Resource classes can therefore request dependencies from their constructor.

from jivago.inject.annotation import Component
from jivago.lang.annotations import Inject
from jivago.wsgi.annotations import Resource


@Component
class CalculatorClass(object):

    def do_calculation(self) -> int:
        return 4


@Resource("/calculation")
class CalculatedResource(object):

    @Inject
    def __init__(self, calculator: CalculatorClass):
        self.calculator = calculator

@Component is a general-purpose annotation which registers a class to the internal service locator. Whenever a class requires dependencies from their constructor, those get recursively instantiated and injected. Note that the @Inject annotation is required.

See Dependency Injection for advanced configurations.

View Rendering

Jivago also supports rendered HTML views, using the Jinja2 templating engine. Optionally, the content type can be overridden using the optional content_type keyword argument in the RenderedView controller.

templated_resource.py

from jivago.templating.rendered_view import RenderedView
from jivago.wsgi.annotations import Resource
from jivago.wsgi.methods import GET


@Resource("/template")
class TemplatedResource(object):

    @GET
    def get(self) -> RenderedView:
        return RenderedView("my-template.html", {"name": "john"})

my-template.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>Hello {{ name }}</h1>
<form method="post">
    <input name="name" />
    <input type="submit">
</form>
</body>
</html>

By default, the framework looks for a views package directly underneath the root package.

my_hello_world_application
 ├── __init__.py
 ├── application.py
 └── views
     ├── __init__.py
     └── my-template.html