Skip to content

distantmagic/aloni

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

32 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Aloni

Aloni is a Python framework designed to increase productivity when developing applications. Simplicity, productivity, and great developer experience are our primary goals.

It is based on an innovative Role-Based Services approach. (more info below).

It is async and thus performs well with IO-bound services (for example, anything that makes a lot of long-running HTTP calls to large language models or uses a lot of microservices and third-party services)—but not just those.

Try it out

Aloni will take only a few minutes to set up, and it might amaze you and change how you develop apps. :)

Key Features

  • Effortlessly split your project into multiple files: Aloni automatically combines your project files based on the roles you assign them, making it easy to manage large projects.
  • Dependency Injection: Manage your application's dependencies effortlessly with a container that holds one instance of each service, ensuring efficient resource management.
  • Async-First: Aloni is optimized for asynchronous programming, making it perfect for IO-bound tasks.
  • Minial boilerplate: Aloni adds no redundant boilerplate code. Keep your project as simple as possible.

Getting Started

Installation

Requirements

Linux or MacOS (should work on all Unix systems). It does not work on Windows because Aloni requires the fork multiprocessing method (which Windows does not have).

That might change in the future (see also: emmett-framework/granian#330).

Steps

Aloni works best with Poetry. Install Poetry first and follow the steps:

  1. Create a new Poetry project, then install Aloni:
    poetry add aloni
  2. Create your application's module:
    mkdir my_app
    touch my_app/__init__.py
  3. Create the app.py (primary application file). That is the entire boilerplate code that Aloni needs to work:
    import my_app
    import aloni
    
    aloni.start(my_app).exit_after_finishing()

Invoking poetry run python ./app.py should display something like:

usage: app.py [-h] {hello,serve} ...

Aloni CLI

positional arguments:
  {serve}
    serve        Start the app in HTTP server mode

options:
  -h, --help     show this help message and exit

Congratulations! You have installed the Aloni project. You can continue with the next steps.

Usage

Check the demo project for basic usage.

Aloni will scan your module (in this case, my_app) for services with a role decorator and start your CLI application. That's it!

Add a new CLI command if you want to start developing something new. Use responds_to_cli role. Add a new file in my_app/hello_command.py (file name can be anything; it's just an example - file names and directory structure do not matter for Aloni):

from aloni.cli_foundation import Command
from aloni.role import responds_to_cli


@responds_to_cli(
    name="hello",
    description="Say hello!",
)
class Hello(Command):
    async def respond(self) -> int:
        print("Hello, World!")

        return 0

You can then run it with:

python ./app.py hello

You should see:

Hello, World!

Quick Tutorials

Responding to HTTP Requests

Create a responder in your application's module. Filename and location do not matter:

from aloni.http import Responder, TextResponse
from aloni.role import responds_to_http


@responds_to_http(pattern='/ping')
class Ping(Responder):
    async def respond(self) -> TextResponse:
        return TextResponse("pong")

Responding with Jinja2 Templates

Place a template inside your application's templates directory. Name it hello.j2:

<p>Hello, world!</p>
from aloni.http import Responder, JinjaResponse
from aloni.role import responds_to_http


@responds_to_http(pattern='/hello')
class Hello(Responder):
    async def respond(self) -> JinjaResponse:
        return JinjaResponse('hello.j2')

Injecting Services

Create a service in your application's module. Filename and location do not matter:

from aloni.role import service


@service
class MyService:
    pass

Use it in your other services:

from aloni.role import service

from .my_service import MyService


@service
class OtherService:
    def __init__(self, my_service: MyService) -> None:
        self.my_service = my_service

Creating Service Providers

Create a base service class in your application's module. Do not add @service role to that class:

class MyService:
    def __init__(self, foo: str) -> None:
        self.foo = foo

Create service provider (again, location and filename do not matter as long as it's in your application's module):

from aloni.application_state import ApplicationState
from aloni.role import service_provider
from aloni.service_provider import ServiceProvider

from .my_service import MyService


@service_provider(provides=MyService)
class MyServiceProvider(ServiceProvider[MyService]):
    def provide(self) -> MyService:
        return MyService(foo="bar")

Use it in your other services:

from aloni.role import service

from .my_service import MyService


@service
class OtherService:
    def __init__(self, my_service: MyService) -> None:
        self.my_service = my_service

Registering Jinja Functions

Custom Jinja functions have their constructor (__init__) arguments injected by the dependency injection container.

Arguments passed to the __call__ method are passed from a template.

from aloni.jinja_function import JinjaFunction
from aloni.role.jinja_function import jinja_function


@jinja_function(name="say_hello")
class UrlFor(JinjaFunction):
    def __call__(self) -> str:
        return "Hello, world!"

Then use it in a template:

{{ say_hello() }}

API Reference

HTTP Responses

All the available roles are accessible from aloni.http module.

For example:

from aloni.http import AssetResponse
Response Description
AssetResponse Returns an asset file if it is present inside your application's assets directory
JinjaResponse Returns a parsed Jinja2 template if it is present inside your application's templates directory
TextResponse Returns a plain text response

Available Roles

All the available roles are accessible from aloni.role module.

For example:

from aloni.role import responds_to_http
Role Description
intercepts_http_response Allows to intercept any response returned by your http responder and convert it into a renderable response. It acts kind of like inversed middleware - instead of intercepting a request, it intercepts and modifies a response.
responds_to_cli Responds to CLI command
responds_to_http Responds to HTTP request
service Marks the current class as a service. Its constructor arguments will be injected from the dependency injection container
service_provider Registers a service provider for dependency injection. Use it to create a class that provides an instance of a different class to the dependency injection container.

Special Thanks

  • Granian for creating an awesome HTTP Python runner with excellent performance

License

This project is licensed under the MIT License - see the LICENSE file for details.

About

Async Python framework optimized for IO-heavy applications.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published