Lessweb is a ready-to-use, production-grade Python web framework with the following goals:
- Ready-to-use: Easily parse configuration files, set up logging with ease, and dynamically override configuration items using environ variables.
- Production-grade: Built on the aiohttp ecosystem and boasting powerful IOC capabilities.
- Pythonic: Supports the latest Python versions and syntax.
To install the latest version of lessweb for Python β₯ 3.10, run:
pip install lesswebSave the following to main.py:
from typing import Annotated
from lessweb import Bridge
from lessweb.annotation import Get
async def hello(*, who: str = 'world') -> Annotated[dict, Get('/')]:
return {'message': f'Hello, {who}!'}
def main():
bridge = Bridge()
bridge.scan(hello)
bridge.run_app()
if __name__ == '__main__':
main()Run your app:
python main.pyNow open your browser at http://localhost:8080
Output:
{"message": "Hello, world!"}Or try http://127.0.0.1:8080?who=John:
{"message": "Hello, John!"}- Quick Start: https://lessweb.castdict.com/quickstart/
- IOC / Dependency Injection: https://lessweb.castdict.com/ioc/
- Reference Manual: https://lessweb.castdict.com/reference/
Below is a summarized version of the Quick Start and IOC (Dependency Injection) documentation.
An endpoint in lessweb is an async function bound to a specific HTTP route (URL). Endpoints can automatically receive parameters from query strings, path variables, or the request body.
Example:
from typing import Annotated
from aiohttp.web import Request
from lessweb import Bridge
from lessweb.annotation import Get, Post
async def get_pet_detail(request: Request) -> Annotated[dict, Get('/pet/{pet_id}')]:
return {'pet_id': request.match_info['pet_id']}
async def create_pet(request: Request) -> Annotated[dict, Post('/pet')]:
pet = await request.json()
return pet
if __name__ == '__main__':
bridge = Bridge()
bridge.scan(get_pet_detail, create_pet)
bridge.run_app()You can also scan an entire package:
bridge.scan('myapp.endpoint')Supports regex-based path matching:
async def get_pet_detail(request: Request) -> Annotated[dict, Get('/pet/{pet_id:[0-9]+}')]:
...Lessweb automatically injects request data into endpoint parameters:
- keyword-only parameters (
*, name: str) β path/query parameters - positional-only parameters (
data: Model, /) β JSON body - normal parameters (
request: Request) β context objects or services
Example:
from pydantic import BaseModel
from lessweb.annotation import Get, Post
from typing import Annotated
class Pet(BaseModel):
pet_id: int
name: str
async def get_pet_detail(*, pet_id: int) -> Annotated[dict, Get('/pet/{pet_id}')]:
return {'pet_id': pet_id}
async def create_pet(pet: Pet, /) -> Annotated[dict, Post('/pet')]:
return petSupported types include:
str,int,float,bool,listdatetime,date,timeenum,Literal,Union,NewTypepydantic.BaseModel
Endpoints can directly return JSON-compatible data:
async def get_pet_list() -> Annotated[dict, Get('/pet')]:
return [{'pet_id': 1, 'name': 'Kitty'}]Or use helper functions for advanced responses:
from lessweb import rest_response, rest_error
from aiohttp.web import HTTPBadRequest
async def get_pet_list() -> Annotated[dict, Get('/pet')]:
return rest_response([{'name': 'Kitty'}], headers={'X-TOKEN': 'abc123'})
async def bad_request() -> Annotated[dict, Get('/error')]:
raise rest_error(HTTPBadRequest, {'code': -1, 'message': 'Invalid request'})Based on orjson with support for:
- dataclasses
- datetime / date / time (RFC 3339)
- enum
- numpy types
- uuid
- pydantic models
Configurable in config.toml:
[lessweb]
orjson_option = 'APPEND_NEWLINE,INDENT_2,UTC_Z'Lesswebβs IOC system auto-injects dependencies based on parameter types, similar to Spring Boot.
| Type | Scope | Description |
|---|---|---|
| Module | Process-level singleton | e.g. DB connection pool |
| Middleware | Request-level wrapper | Pre/post processing |
| Service | Request-level singleton | Business logic |
| Bean | Request-level factory result | Complex object creation |
class Mysql(Module):
async def on_startup(self, app):
self.pool = await aiomysql.create_pool(...)
class RedisModule(Module):
async def on_startup(self, app):
self.redis_client = redis.Redis(...)class MysqlConn(Middleware):
def __init__(self, mysql: Mysql):
self.mysql = mysql
async def on_request(self, request, handler):
async with self.mysql.pool.acquire() as conn:
self.conn = conn
return await handler(request)class TaskService(Service):
def __init__(self, dao: Commondao, redis: redis.Redis):
self.dao = dao
self.redis = redis
def commondao_bean(mysqlConn: MysqlConn) -> Commondao:
return Commondao(mysqlConn.conn, mysqlConn.cur)
def redis_bean(redis_module: RedisModule) -> redis.Redis:
return redis_module.redis_clientRegister all in main.py:
def main():
bridge = Bridge()
bridge.beans(commondao_bean, redis_bean)
bridge.middlewares(MysqlConn)
bridge.scan('src')
bridge.run_app()- Use Middleware for request pre/post hooks (e.g. auth, logging)
- Use Service for business logic
- Keep Module dependencies only between other Modules
- Let Beans create reusable request-scoped objects
Lessweb is offered under the Apache 2.0 License.
GitHub Repository: π https://github.com/lessweb/lessweb