Skip to content

lessweb/lessweb

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

69 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

lessweb

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.

πŸ“¦ Installation

To install the latest version of lessweb for Python β‰₯ 3.10, run:

pip install lessweb

Dependencies


⚑ Quick Start Example

Save 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.py

Now open your browser at http://localhost:8080

Output:

{"message": "Hello, world!"}

Or try http://127.0.0.1:8080?who=John:

{"message": "Hello, John!"}

πŸ“š Official Documentation


πŸ“˜ Lessweb Framework Guide

Below is a summarized version of the Quick Start and IOC (Dependency Injection) documentation.


🧭 Endpoint Basics

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')

Dynamic Path Parameters

Supports regex-based path matching:

async def get_pet_detail(request: Request) -> Annotated[dict, Get('/pet/{pet_id:[0-9]+}')]:
    ...

🧩 Request Parameter Injection

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 pet

Supported types include:

  • str, int, float, bool, list
  • datetime, date, time
  • enum, Literal, Union, NewType
  • pydantic.BaseModel

πŸ’‘ JSON Responses

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'})

βš™οΈ JSON Serialization

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'

🧠 Dependency Injection (IOC)

Lessweb’s IOC system auto-injects dependencies based on parameter types, similar to Spring Boot.

Lifecycle Levels

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

Example: Modules

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(...)

Example: Middleware

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)

Example: Services and Beans

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_client

Register all in main.py:

def main():
    bridge = Bridge()
    bridge.beans(commondao_bean, redis_bean)
    bridge.middlewares(MysqlConn)
    bridge.scan('src')
    bridge.run_app()

βœ… Best Practices

  • 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

πŸ“„ License

Lessweb is offered under the Apache 2.0 License.


🧭 Source Code

GitHub Repository: πŸ‘‰ https://github.com/lessweb/lessweb

Releases

No releases published

Packages

No packages published