https://github.com/distantmagic/aloni
Async Python framework optimized for IO-heavy applications.
https://github.com/distantmagic/aloni
Last synced: 17 days ago
JSON representation
Async Python framework optimized for IO-heavy applications.
- Host: GitHub
- URL: https://github.com/distantmagic/aloni
- Owner: distantmagic
- License: mit
- Created: 2024-06-05T16:24:06.000Z (almost 2 years ago)
- Default Branch: main
- Last Pushed: 2024-06-29T15:12:05.000Z (over 1 year ago)
- Last Synced: 2025-12-09T23:35:56.392Z (3 months ago)
- Language: Python
- Homepage:
- Size: 215 KB
- Stars: 4
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE.md
Awesome Lists containing this project
README
# 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: https://github.com/emmett-framework/granian/issues/330).
#### Steps
Aloni works best with [Poetry](https://python-poetry.org/). Install [Poetry](https://python-poetry.org/) first and follow the steps:
1. Create a new Poetry project, then install Aloni:
```shell
poetry add aloni
```
2. Create your application's module:
```shell
mkdir my_app
```
```shell
touch my_app/__init__.py
```
3. Create the `app.py` (primary application file). That is the entire boilerplate code that Aloni needs to work:
```py
import my_app
import aloni
aloni.start(my_app).exit_after_finishing()
```
Invoking `poetry run python ./app.py` should display something like:
```shell
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](/examples/demo-app) 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):
```py
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:
```shell
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:
```py
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`:
```html
Hello, world!
```
```py
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:
```py
from aloni.role import service
@service
class MyService:
pass
```
Use it in your other services:
```py
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:
```py
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):
```py
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:
```py
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.
```py
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:
```j2
{{ say_hello() }}
```
## API Reference
### HTTP Responses
All the available roles are accessible from `aloni.http` module.
For example:
```py
from aloni.http import AssetResponse
```
| Response | Description |
| ------------- | ------------- |
| [AssetResponse](aloni/http/asset_response.py) | Returns an asset file if it is present inside your application's `assets` directory |
| [JinjaResponse](aloni/http/jinja_response.py) | Returns a parsed Jinja2 template if it is present inside your application's `templates` directory |
| [TextResponse](aloni/http/text_response.py) | Returns a plain text response |
### Available Roles
All the available roles are accessible from `aloni.role` module.
For example:
```py
from aloni.role import responds_to_http
```
| Role | Description |
| ------------- | ------------- |
| [intercepts_http_response](aloni/role/intercepts_http_response.py) | 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](aloni/role/responds_to_cli.py) | Responds to CLI command |
| [responds_to_http](aloni/role/responds_to_http.py) | Responds to HTTP request |
| [service](aloni/role/service.py) | Marks the current class as a service. Its constructor arguments will be injected from the dependency injection container |
| [service_provider](aloni/role/service_provider.py) | 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](https://github.com/emmett-framework/granian) for creating an awesome HTTP Python runner with excellent performance
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.