Lihil /ˈliːhaɪl/ — a performant, productive, and professional web framework with a vision:
Making Python the mainstream programming language for web development.
lihil is 100% test covered and strictly typed.
📚 Docs: https://lihil.cc/lihil
Lihil is
- Productive: ergonomic API with strong typing support and built-in solutions for common problems — along with beloved features like openapi docs generation — empowers users to build their apps swiftly without sacrificing extensibility.
- Professional: Lihil comes with middlewares that are essential for enterprise development—such as authentication, authorization, event publishing, etc. Ensure productivity from day zero. Catered to modern development styles and architectures, including TDD and DDD.
- Performant: Blazing fast across tasks and conditions—Lihil ranks among the fastest Python web frameworks, outperforming comparable ASGI frameworks by 50%–100%, see lihil benchmarks, independent benchmarks
- Param Parsing & Validation
Lihil provides a sophisticated parameter parsing system that automatically extracts and converts parameters from different request locations:
- Multiple Parameter Sources: Automatically parse parameters from query strings, path parameters, headers, and request bodies
- Type-Based Parsing: Parameters are automatically converted to their annotated types
- Alias Support: Define custom parameter names that differ from function argument names
- Custom Decoders: Apply custom decoders to transform raw input into complex types
class UserPayload(Payload): # memory optimized data structure that does not involve in gc.
user_name: Annotated[str, Meta(min_length=1)] # non-empty string with length >= 1
all_users = Route("users")
# All parameters are automatically parsed and validated
@all_users.sub("{user_id}").post # POST /users/{user_id}
async def create_user(
user_id: str, # from URL path
auth_token: Header[str, Literal["x-auth-token"]], # from request headers
user_data: UserPayload # from request body
) -> Resp[Empty, 201]: ...
- Dependency injection: inject factories, functions, sync/async, scoped/singletons based on type hints, blazingly fast.
from lihil import Route, Ignore
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncConnection
async def get_conn(engine: AsyncEngine) -> AsyncConnection:
async with engine.connect() as conn:
yield conn
async def get_users(conn: AsyncConnection, nums: Annotated[int, Meta(lt=100)]) -> Ignore[list[User]]:
return await conn.execute(text(f"SELECT * FROM users limit {nums}"))
all_users = Route("users")
@all_users.get
async def list_users(users: Annotated[list[User], use(get_users)], is_active: bool=True) -> list[[User]]:
return [u for u in users if u.is_active == is_active]
- OpenAPI docs & Error Response Generator
lihil creates smart & accurate openapi schemas based on your routes/endpoints, union types, oneOf
responses, all supported.
your exception classes are also automatically transformed to a Problem
and genrate detailed response accordingly.
class OutOfStockError(HTTPException[str]):
"The order can't be placed because items are out of stock"
__status__ = 422
def __init__(self, order: Order):
detail: str = f"{order} can't be placed, because {order.items} is short in quantity"
super().__init__(detail)
when such exception is raised from endpoint, client would receive a response like this
- Problems Page: declare exceptions using route decorator and they will be displayed as route response at openapi schemas & problem page
- Data validation & Param Parsing: Powerful and extendable data validation using
msgspec
, 12x faster and 25x more memory efficient than pydantic v2, see benchmarks, Deep memory optimizations significantly reduce GC overhead, making your services more robust and resilient under load.
- Auth Builtin: lihil comes with authentification & authorization plugins out of the box.
from lihil import Payload, Route
from lihil.auth.jwt import JWTAuth, JWTPayload
from lihil.auth.oauth import OAuth2PasswordFlow, OAuthLoginForm
class UserProfile(JWTPayload):
# get support from typehints about what are the available claims
__jwt_claims__ = {"expires_in": 300} # jwt expires in 300 seconds.
user_id: str = field(name="sub")
role: Literal["admin", "user"] = "user"
@me.get(auth_scheme=OAuth2PasswordFlow(token_url="token"))
async def get_user(profile: JWTAuth[UserProfile]) -> User:
assert profile.role == "user"
return User(name="user", email="user@email.com")
@token.post
async def create_token(credentials: OAuthLoginForm) -> JWTAuth[UserProfile]:
return UserProfile(user_id="user123")
When you return UserProfile
from create_token
endpoint, it would automatically be serialized as a json web token.
- Message System Bulitin: publish command/event anywhere in your app with both in-process and out-of-process event handlers. Optimized data structure for maximum efficiency, de/serialize millions events from external service within seconds.
from lihil import Route, EventBus, Empty, Resp, status
@Route("users").post
async def create_user(data: UserCreate, service: UserService, bus: EventBus) -> Resp[Empty, status.Created]:
user_id = await service.create_user(data)
await bus.publish(UserCreated(**data.asdict(), user_id=user_id))
-
Great Testability: bulit-in
LocalClient
to easily and independently test your endpoints, routes, middlewares, app. -
Strong support for AI featuers: lihil takes AI as a main usecase, AI related features such as SSE, remote handler will be well supported, there will also be tutorials on how to develop your own AI agent/chatbot using lihil.
Lihil is ASGI compatible and absorbs the Request
, Response
interfaces from Starlette.
You can declare Request
in your endpoint and return an instance of Response
(or its subclass).
from lihil import Request, Response
@users.post
async def create_user(req: Request):
return Response(content=b"hello, world")
- ASGI middlewares that works for any ASGIApp should also work with lihil, including those from Starlette.
from lihil import Lihil
lhl = Lihil()
@lhl.get
async def hello():
return {"hello": "world!"}
from lihil import Lihil, Route, use, EventBus
from msgspec import Meta
chat_route = Route("/chats/{chat_id}")
message_route = chat_route / "messages"
UserToken = NewType("UserToken", str)
chat_route.factory(UserService)
message_route.factory(get_chat_service)
@chat_route.factory
def parse_access_token(
service: UserService, token: UserToken
) -> ParsedToken:
return service.decrypt_access_token(token)
@message_route.post
async def stream(
service: ChatService, # get_chat_service gets called and inject instance of ChatService here.
profile: JWTAuth[UserProfile], # Auth Bearer: {jwt_token}` Header gets validated into UserProfile.
bus: EventBus,
chat_id: Annotated[str, Meta[min_length=1]], # validates the path param `chat_id` has to be a string with length > 1.
data: CreateMessage # request body
) -> Annotated[Stream[GPTMessage], CustomEncoder(gpt_encoder)]: # returns server side events
chat = service.get_user_chat(profile.user_id)
chat.add_message(data)
answer = service.ask(chat, model=data.model)
buffer = []
async for word in answer:
buffer.append(word)
yield word
await bus.publish(NewMessageCreated(chat, buffer))
lihil(currently) requires python>=3.12
pip install lihil
if you want to install lihil using uv
- init your web project with
project_name
uv init project_name
- install lihil via uv, this will solve all dependencies for your in a dedicated virtual environment.
uv add lihil
from lihil import Lihil
# your application code
lhl = Lihil()
if __name__ == "__main__":
lhl.run(__file__)
then in command line
uv run python -m myproject.app --server.port=8080
This allows you to override configurations using command-line arguments.
If your app is deployed in a containerized environment, such as Kubernetes, providing secrets this way is usually safer than storing them in files.
use --help
to see available configs.
lihil is ASGI compatible, you can run it with an ASGI server, such as uvicorn
start a server with app.py
, default to port 8000
- create
__main__.py
under your project root. - use uvicorn to run you app in your
__main__.py
import uvicorn
uvicorn.run(app)
lihil follows semantic versioning, where a version in x.y.z represents:
- x: major, breaking change
- y: minor, feature updates
- z: patch, bug fixes, typing updates
v1.0.0 will be the first stable major version.
check detailed tutorials at https://lihil.cc/lihil/tutorials/, covering
- Core concepts, create endpoint, route, middlewares, etc.
- Configuring your app via
pyproject.toml
, or via command line arguments. - Dependency Injection & Plugins
- Testing
- Type-Based Message System, Event listeners, atomic event handling, etc.
- Error Handling
- ...and much more
No contribution is trivial, and every contribution is appreciated. However, our focus and goals vary at different stages of this project.
-
Feature Parity: we should offer core functionalities of a web framework ASAP, similar to what fastapi is offering right now. Given both fastapi and lihil uses starlette, this should not take too much effort.
-
Correctness: We should have a preliminary understanding of lihil's capabilities—knowing what should be supported and what shouldn't. This allows us to distinguish between correct and incorrect usage by users.
-
Test Coverage: There's no such thing as too many tests. For every patch, we should maintain at least 99% test coverage, and 100% for the last patch of 0.1.x. For core code, 100% coverage is just the baseline—we should continuously add test cases to ensure reliability.
Based on the above points, in version v0.1.x, we welcome contributions in the following areas:
-
Documentation: Fix and expand the documentation. Since lihil is actively evolving, features may change or extend, and we need to keep the documentation up to date.
-
Testing: Contribute both successful and failing test cases to improve coverage and reliability.
-
Feature Requests: We are open to discussions on what features lihil should have or how existing features can be improved. However, at this stage, we take a conservative approach to adding new features unless there is a significant advantage.
- Out-of-process event system (RabbitMQ, Kafka, etc.).
- A highly performant schema-based query builder based on asyncpg
- Local command handler(http rpc) and remote command handler (gRPC)
- More middleware and official plugins (e.g., throttling, caching, auth).
Premature optimization is the root of all eveil, we will not do heavy optimization unless lihil has almost every feature we want.
We might deliver our own server in c, also rewrite some hot-spot classes such as Request
and Response
.
Experiments show that this would make lihil as fast as some rust webframworks like actix and axum, but the price for that is the speed of iteration gets much slower.