lihil


Namelihil JSON
Version 0.2.26 PyPI version JSON
download
home_pageNone
Summaryasync python framework offering high level development, low level performance.
upload_time2025-09-16 14:39:15
maintainerNone
docs_urlNone
authorNone
requires_python>=3.10
licenseNone
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            ![Lihil](assets/lhl_logo_ts.png)

# Lihil

**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.**

[![codecov](https://codecov.io/gh/raceychan/lihil/graph/badge.svg?token=KOK5S1IGVX)](https://codecov.io/gh/raceychan/lihil)
[![PyPI version](https://badge.fury.io/py/lihil.svg)](https://badge.fury.io/py/lihil)
[![License](https://img.shields.io/github/license/raceychan/lihil)](https://github.com/raceychan/lihil/blob/master/LICENSE)
[![Python Version](https://img.shields.io/pypi/pyversions/lihil.svg)](https://pypi.org/project/lihil/)

# Lihil

## πŸ“š Docs: https://lihil.cc

## Lihil is

- **Performant**: Blazing fast across tasks and conditionsβ€”Lihil ranks among the fastest Python web frameworks, outperforming other webframeworks by 50%–100%, see reproducible, automated tests [lihil benchmarks](https://github.com/raceychan/lhl_bench), [independent benchmarks](https://web-frameworks-benchmark.netlify.app/result?l=python)

![bench](/assets/bench_ping.png)

- **Designed to be tested**: Built with testability in mind, making it easy for users to write unit, integration, and e2e tests. Lihil supports Starlette's TestClient and provides LocalClient that allows testing at different levels: endpoint, route, middleware, and application.
- **Built for large scale applications**: Architected to handle enterprise-level applications with robust dependency injection and modular design
- **AI-centric**: While usable as a generic web framework, Lihil is optimized for AI applications with specialized features for AI/ML workloads
- **AI Agent Friendly**: Designed to work seamlessly with AI coding assistants - see [LIHIL_COPILOT.md](LIHIL_COPILOT.md) for comprehensive guidance on using Lihil with AI agents
- **Productive**: Provides extensive typing information for superior developer experience, complemented by detailed error messages and docstrings for effortless debugging

## Lihil is not

- **Not a microframework**: Lihil has an ever-growing and prosperous ecosystem that provides industrial, enterprise-ready features such as throttler, timeout, auth, and more
- **Not a one-man project**: Lihil is open-minded and contributions are always welcome.you can safely assume that your PR will be carefully reviewed
- **Not experimental**: Lihil optimizes based on real-world use cases rather than benchmarks

## Install

lihil requires python>=3.10

### pip

```bash
pip install "lihil[standard]"
```

The standard version comes with uvicorn

## Qucik Start

```python
from lihil import Lihil, Route, Stream
from openai import OpenAI
from openai.types.chat import ChatCompletionChunk as Chunk
from openai.types.chat import ChatCompletionUserMessageParam as MessageIn

gpt = Route("/gpt", deps=[OpenAI])

def message_encoder(chunk: Chunk) -> bytes:
    if not chunk.choices:
        return b""
    return chunk.choices[0].delta.content.encode() or b""

@gpt.sub("/messages").post(encoder=message_encoder)
async def add_new_message(
    client: OpenAPI, question: MessageIn, model: str
) -> Stream[Chunk]:
    chat_iter = client.responses.create(messages=[question], model=model, stream=True)
    async for chunk in chat_iter:
        yield chunk
```

## Features

- **Param Parsing & Validation**

  Lihil provides a high level abstraction for parsing request, validating rquest data against endpoint type hints. various model is supported including
  	- `msgspec.Struct`,
	- `pydantic.BaseModel`,
	- `dataclasses.dataclass`,
	- `typing.TypedDict`

  By default, lihil uses `msgspec` to serialize/deserialize json data, which is extremly fast, we maintain first-class support for `pydantic.BaseModel` as well, no plugin required.
  see [benchmarks](https://jcristharif.com/msgspec/benchmarks.html),

  - Param Parsing: Automatically parse parameters from query strings, path parameters, headers, cookies, and request bodies
  - Validation: Parameters are automatically converted to & validated against their annotated types and constraints.
  - Custom Decoders: Apply custom decoders to have the maximum control of how your param should be parsed & validated.

- **Dependency injection**:
  **Inject factories, functions, sync/async, scoped/singletons based on type hints, blazingly fast.**

- **WebSocket**
  lihil supports the usage of websocket, you might use `WebSocketRoute.ws_handler` to register a function that handles websockets.

- **OpenAPI docs & Error Response Generator**
  Lihil creates smart & accurate openapi schemas based on your routes/endpoints, union types, `oneOf` responses, all supported.

- **Powerful Plugin System**:
  Lihil features a sophisticated plugin architecture that allows seamless integration of external libraries as if they were built-in components. Create custom plugins to extend functionality or integrate third-party services effortlessly.

- **Strong support for AI featuers**:
  lihil takes AI as a main usecase, AI related features such as SSE, MCP, remote handler will be implemented in the next few patches

There will also be tutorials on how to develop your own AI agent/chatbot using lihil.

- ASGI-compatibility & Vendor types from starlette
  - Lihil is ASGI copatible and works well with uvicorn and other ASGI servers.
  - ASGI middlewares that works for any ASGIApp should also work with lihil, including those from Starlette.


## Plugin System

Lihil's plugin system enables you to integrate external libraries seamlessly into your application as if they were built-in features. Any plugin that implements the `IPlugin` protocol can access endpoint information and wrap functionality around your endpoints.

### Plugin Execution Flow

When you apply multiple plugins like `@app.sub("/api/data").get(plugins=[plugin1.dec, plugin2.dec])`, here's how they execute:

```

Plugin Application (Setup Time - Left to Right)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  original_func β†’ plugin1(ep_info) β†’ plugin2(ep_info)        β”‚
β”‚                                                             β”‚
β”‚  Result: plugin2(plugin1(original_func))                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Request Execution (Runtime - Nested/Onion Pattern)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                                                            β”‚
β”‚   Request                                                  β”‚
β”‚       β”‚                                                    β”‚
β”‚       β–Ό                                                    β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚ Plugin2 (Outermost)                                 β”‚   β”‚
β”‚  β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚   β”‚
β”‚  β”‚ β”‚ Plugin1 (Middle)                                β”‚ β”‚   β”‚
β”‚  β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚   β”‚
β”‚  β”‚ β”‚ β”‚ Original Function (Core)                    β”‚ β”‚ β”‚   β”‚
β”‚  β”‚ β”‚ β”‚                                             β”‚ β”‚ β”‚   β”‚
β”‚  β”‚ β”‚ β”‚ async def get_data():                       β”‚ β”‚ β”‚   β”‚
β”‚  β”‚ β”‚ β”‚     return {"data": "value"}                β”‚ β”‚ β”‚   β”‚
β”‚  β”‚ β”‚ β”‚                                             β”‚ β”‚ β”‚   β”‚
β”‚  β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚   β”‚
β”‚  β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚       β”‚                                                    β”‚
β”‚       β–Ό                                                    β”‚
β”‚   Response                                                 β”‚
β”‚                                                            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

#### Execution Order:
   Request β†’ Plugin2 β†’ Plugin1 β†’ get_data() β†’ Plugin1 β†’ Plugin2 β†’ Response

#### Real Example with Premier Plugins:
```python
   @app.sub("/api").get(plugins=[
       plugin.timeout(5),           # Applied 1st β†’ Executes Outermost
       plugin.retry(max_attempts=3), # Applied 2nd β†’ Executes Middle
       plugin.cache(expire_s=60),   # Applied 3rd β†’ Executes Innermost
   ])
```

Flow: Request β†’ timeout β†’ retry β†’ cache β†’ endpoint β†’ cache β†’ retry β†’ timeout β†’ Response


### Creating a Custom Plugin

A plugin is anything that implements the `IPlugin` protocol - either a callable or a class with a `decorate` method:

```python
from lihil.plugins.interface import IPlugin, IEndpointInfo
from lihil.interface import IAsyncFunc, P, R
from typing import Callable, Awaitable

class MyCustomPlugin:
    """Plugin that integrates external libraries with lihil endpoints"""

    def __init__(self, external_service):
        self.service = external_service

    def decorate(self, ep_info: IEndpointInfo[P, R]) -> Callable[P, Awaitable[R]]:
        """
        Access endpoint info and wrap functionality around it.
        ep_info contains:
        - ep_info.func: The original endpoint function
        - ep_info.sig: Parsed signature with type information
        - ep_info.graph: Dependency injection graph
        """
        original_func = ep_info.func

        async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
            # Pre-processing with external library
            await self.service.before_request(ep_info.sig)

            try:
                result = await original_func(*args, **kwargs)
                # Post-processing with external library
                return await self.service.process_result(result)
            except Exception as e:
                # Error handling with external library
                await self.service.handle_error(e)
                raise

        return wrapper

# Usage - integrate any external library
from some_external_lib import ExternalService

plugin = MyCustomPlugin(ExternalService())

@app.sub("/api/data").get(plugins=[plugin.decorate])
async def get_data() -> dict:
    return {"data": "value"}
```


This architecture allows you to:
- **Integrate any external library** as if it were built-in to lihil
- **Access full endpoint context** - signatures, types, dependency graphs
- **Wrap functionality** around endpoints with full control
- **Compose multiple plugins** for complex integrations
- **Zero configuration** - plugins work automatically based on decorators

## Error Handling with HTTPException

Lihil provides a powerful and flexible error handling system based on RFC 9457 Problem Details specification. The `HTTPException` class extends `DetailBase` and allows you to create structured, consistent error responses with rich metadata.

### Basic Usage

By default, Lihil automatically generates problem details from your exception class:

```python
from lihil import HTTPException

class UserNotFound(HTTPException[str]):
    """The user you are looking for does not exist"""
    __status__ = 404

# Usage in endpoint
@app.sub("/users/{user_id}").get
async def get_user(user_id: str):
    if not user_exists(user_id):
        raise UserNotFound(f"User with ID {user_id} not found")
    return get_user_data(user_id)
```

This will produce a JSON response like:
```json
{
  "type": "user-not-found",
  "title": "The user you are looking for does not exist",
  "status": 404,
  "detail": "User with ID 123 not found",
  "instance": "/users/123"
}
```

### Customizing Problem Details

#### 1. Default Behavior
- **Problem Type**: Automatically generated from class name in kebab-case (`UserNotFound` β†’ `user-not-found`)
- **Problem Title**: Taken from the class docstring
- **Status Code**: Set via `__status__` class attribute (defaults to 422)

#### 2. Custom Problem Type and Title

You can customize the problem type and title using class attributes:

```python
class UserNotFound(HTTPException[str]):
    """The user you are looking for does not exist"""
    __status__ = 404
    __problem_type__ = "user-lookup-failed"
    __problem_title__ = "User Lookup Failed"
```

#### 3. Runtime Customization

You can also override problem details at runtime:

```python
@app.sub("/users/{user_id}").get
async def get_user(user_id: str):
    if not user_exists(user_id):
        raise UserNotFound(
            detail=f"User with ID {user_id} not found",
            problem_type="custom-user-error",
            problem_title="Custom User Error",
            status=404
        )
    return get_user_data(user_id)
```

### Advanced Customization

#### 1. Override `__problem_detail__` Method

For fine-grained control over how your exception transforms into a `ProblemDetail` object:

```python
from lihil.interface.problem import ProblemDetail

class ValidationError(HTTPException[dict]):
    """Request validation failed"""
    __status__ = 400

    def __problem_detail__(self, instance: str) -> ProblemDetail[dict]:
        return ProblemDetail(
            type_="validation-error",
            title="Request Validation Failed",
            status=400,
            detail=self.detail,
            instance=f"users/{instance}",
        )

# Usage
@app.sub("/users/{user_id}").post
async def update_user(user_data: UserUpdate):
    validation_errors = validate_user_data(user_data)
    if validation_errors:
        raise ValidationError(title="Updating user failed")
    return create_user_in_db(user_data)
```

#### 2. Override `__json_example__` Method

Customize how your exceptions appear in OpenAPI documentation:

```python
class UserNotFound(HTTPException[str]):
    """The user you are looking for does not exist"""
    __status__ = 404

    @classmethod
    def __json_example__(cls) -> ProblemDetail[str]:
        return ProblemDetail(
            type_="user-not-found",
            title="User Not Found",
            status=404,
            detail="User with ID 'user123' was not found in the system",
            instance="/api/v1/users/user123"
        )
```

This is especially useful for providing realistic examples in your API documentation, including specific `detail` and `instance` values that Lihil cannot automatically resolve from class attributes.

### Complex Error Scenarios

#### Generic Error with Type Information

```python
from typing import Generic, TypeVar

T = TypeVar('T')

class ResourceNotFound(HTTPException[T], Generic[T]):
    """The requested resource was not found"""
    __status__ = 404

    def __init__(self, detail: T, resource_type: str):
        super().__init__(detail)
        self.resource_type = resource_type

    def __problem_detail__(self, instance: str) -> ProblemDetail[T]:
        return ProblemDetail(
            type_=f"{self.resource_type}-not-found",
            title=f"{self.resource_type.title()} Not Found",
            status=404,
            detail=self.detail,
            instance=instance
        )

# Usage
@app.sub("/posts/{post_id}").get
async def get_post(post_id: str):
    if not post_exists(post_id):
        raise ResourceNotFound(
            detail=f"Post {post_id} does not exist",
            resource_type="post"
        )
    return get_post_data(post_id)
```

### Benefits

- **Consistency**: All error responses follow RFC 9457 Problem Details specification
- **Developer Experience**: Rich type information and clear error messages
- **Documentation**: Automatic OpenAPI schema generation with examples
- **Flexibility**: Multiple levels of customization from simple to advanced
- **Traceability**: Built-in problem page links in OpenAPI docs for debugging

The error handling system integrates seamlessly with Lihil's OpenAPI documentation generation, providing developers with comprehensive error schemas and examples in the generated API docs.

## AI Agent Support

**Using AI coding assistants with Lihil?** Check out [LIHIL_COPILOT.md](LIHIL_COPILOT.md) for:

- **AI Agent Best Practices** - Comprehensive guide for AI assistants working with Lihil
- **Common Mistakes & Solutions** - Learn from real AI agent errors and how to avoid them
- **Complete Templates** - Ready-to-use patterns that AI agents can follow
- **Lihil vs FastAPI Differences** - Critical syntax differences AI agents must know
- **How to Use as Prompt** - Instructions for Claude Code, Cursor, ChatGPT, and GitHub Copilot

**Quick Setup:** Copy the entire LIHIL_COPILOT.md content and paste it as system context in your AI tool. This ensures your AI assistant understands Lihil's unique syntax and avoids FastAPI assumptions.

## Tutorials

Check our detailed tutorials at https://lihil.cc, 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

## Lihil Admin & Full stack template

See how lihil works here, a production-ready full stack template that uses react and lihil,

[lihil-fullstack-solopreneur-template](https://github.com/raceychan/fullstack-solopreneur-template)

covering real world usage & best practices of lihil.
A fullstack template for my fellow solopreneur, uses shadcn+tailwindcss+react+lihil+sqlalchemy+supabase+vercel+cloudlfare to end modern slavery

## Versioning

lihil follows semantic versioning after v1.0.0, where a version in x.y.z represents:

- x: major, breaking change
- y: minor, feature updates
- z: patch, bug fixes, typing updates

## Contributing

We welcome all contributions! Whether you're fixing bugs, adding features, improving documentation, or enhancing tests - every contribution matters.

### Quick Start for Contributors

1. **Fork & Clone**: Fork the repository and clone your fork
2. **Find Latest Branch**: Use `git branch -r | grep "version/"` to find the latest development branch (e.g., `version/0.2.23`)
3. **Create Feature Branch**: Branch from the latest version branch
4. **Make Changes**: Follow existing code conventions and add tests
5. **Submit PR**: Target your PR to the latest development branch

For detailed contributing guidelines, workflow, and project conventions, see our [Contributing Guide](.github/CONTRIBUTING.md).

## Roadmap

### Road Map before v1.0.0

- [x] **v0.1.x: Feature parity** (alpha stage)

Implementing core functionalities of lihil, feature parity with fastapi

- [x] **v0.2.x: Official Plugins** (current stage)

We would keep adding new features & plugins to lihil without making breaking changes.
This might be the last minor versions before v1.0.0.

- [ ] **v0.3.x: Performance boost**

The plan is to rewrite some components in c, roll out a server in c, or other performance optimizations in 0.3.x.

If we can do this without affect current implementations in 0.2.0 at all, 0.3.x may never occur and we would go straight to v1.0.0 from v0.2.x

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "lihil",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.10",
    "maintainer_email": null,
    "keywords": null,
    "author": null,
    "author_email": "raceychan <raceychan@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/b1/4e/b5be5b2643f8c1fa71aa20f898050ea10faa7203755c0454d80532fb0624/lihil-0.2.26.tar.gz",
    "platform": null,
    "description": "![Lihil](assets/lhl_logo_ts.png)\n\n# Lihil\n\n**Lihil** &nbsp;_/\u02c8li\u02d0ha\u026al/_ \u2014 a **performant**, **productive**, and **professional** web framework with a vision:\n\n> **Making Python the mainstream programming language for web development.**\n\n**lihil is _100%_ test covered and _strictly_ typed.**\n\n[![codecov](https://codecov.io/gh/raceychan/lihil/graph/badge.svg?token=KOK5S1IGVX)](https://codecov.io/gh/raceychan/lihil)\n[![PyPI version](https://badge.fury.io/py/lihil.svg)](https://badge.fury.io/py/lihil)\n[![License](https://img.shields.io/github/license/raceychan/lihil)](https://github.com/raceychan/lihil/blob/master/LICENSE)\n[![Python Version](https://img.shields.io/pypi/pyversions/lihil.svg)](https://pypi.org/project/lihil/)\n\n# Lihil\n\n## \ud83d\udcda Docs: https://lihil.cc\n\n## Lihil is\n\n- **Performant**: Blazing fast across tasks and conditions\u2014Lihil ranks among the fastest Python web frameworks, outperforming other webframeworks by 50%\u2013100%, see reproducible, automated tests [lihil benchmarks](https://github.com/raceychan/lhl_bench), [independent benchmarks](https://web-frameworks-benchmark.netlify.app/result?l=python)\n\n![bench](/assets/bench_ping.png)\n\n- **Designed to be tested**: Built with testability in mind, making it easy for users to write unit, integration, and e2e tests. Lihil supports Starlette's TestClient and provides LocalClient that allows testing at different levels: endpoint, route, middleware, and application.\n- **Built for large scale applications**: Architected to handle enterprise-level applications with robust dependency injection and modular design\n- **AI-centric**: While usable as a generic web framework, Lihil is optimized for AI applications with specialized features for AI/ML workloads\n- **AI Agent Friendly**: Designed to work seamlessly with AI coding assistants - see [LIHIL_COPILOT.md](LIHIL_COPILOT.md) for comprehensive guidance on using Lihil with AI agents\n- **Productive**: Provides extensive typing information for superior developer experience, complemented by detailed error messages and docstrings for effortless debugging\n\n## Lihil is not\n\n- **Not a microframework**: Lihil has an ever-growing and prosperous ecosystem that provides industrial, enterprise-ready features such as throttler, timeout, auth, and more\n- **Not a one-man project**: Lihil is open-minded and contributions are always welcome.you can safely assume that your PR will be carefully reviewed\n- **Not experimental**: Lihil optimizes based on real-world use cases rather than benchmarks\n\n## Install\n\nlihil requires python>=3.10\n\n### pip\n\n```bash\npip install \"lihil[standard]\"\n```\n\nThe standard version comes with uvicorn\n\n## Qucik Start\n\n```python\nfrom lihil import Lihil, Route, Stream\nfrom openai import OpenAI\nfrom openai.types.chat import ChatCompletionChunk as Chunk\nfrom openai.types.chat import ChatCompletionUserMessageParam as MessageIn\n\ngpt = Route(\"/gpt\", deps=[OpenAI])\n\ndef message_encoder(chunk: Chunk) -> bytes:\n    if not chunk.choices:\n        return b\"\"\n    return chunk.choices[0].delta.content.encode() or b\"\"\n\n@gpt.sub(\"/messages\").post(encoder=message_encoder)\nasync def add_new_message(\n    client: OpenAPI, question: MessageIn, model: str\n) -> Stream[Chunk]:\n    chat_iter = client.responses.create(messages=[question], model=model, stream=True)\n    async for chunk in chat_iter:\n        yield chunk\n```\n\n## Features\n\n- **Param Parsing & Validation**\n\n  Lihil provides a high level abstraction for parsing request, validating rquest data against endpoint type hints. various model is supported including\n  \t- `msgspec.Struct`,\n\t- `pydantic.BaseModel`,\n\t- `dataclasses.dataclass`,\n\t- `typing.TypedDict`\n\n  By default, lihil uses `msgspec` to serialize/deserialize json data, which is extremly fast, we maintain first-class support for `pydantic.BaseModel` as well, no plugin required.\n  see [benchmarks](https://jcristharif.com/msgspec/benchmarks.html),\n\n  - Param Parsing: Automatically parse parameters from query strings, path parameters, headers, cookies, and request bodies\n  - Validation: Parameters are automatically converted to & validated against their annotated types and constraints.\n  - Custom Decoders: Apply custom decoders to have the maximum control of how your param should be parsed & validated.\n\n- **Dependency injection**:\n  **Inject factories, functions, sync/async, scoped/singletons based on type hints, blazingly fast.**\n\n- **WebSocket**\n  lihil supports the usage of websocket, you might use `WebSocketRoute.ws_handler` to register a function that handles websockets.\n\n- **OpenAPI docs & Error Response Generator**\n  Lihil creates smart & accurate openapi schemas based on your routes/endpoints, union types, `oneOf` responses, all supported.\n\n- **Powerful Plugin System**:\n  Lihil features a sophisticated plugin architecture that allows seamless integration of external libraries as if they were built-in components. Create custom plugins to extend functionality or integrate third-party services effortlessly.\n\n- **Strong support for AI featuers**:\n  lihil takes AI as a main usecase, AI related features such as SSE, MCP, remote handler will be implemented in the next few patches\n\nThere will also be tutorials on how to develop your own AI agent/chatbot using lihil.\n\n- ASGI-compatibility & Vendor types from starlette\n  - Lihil is ASGI copatible and works well with uvicorn and other ASGI servers.\n  - ASGI middlewares that works for any ASGIApp should also work with lihil, including those from Starlette.\n\n\n## Plugin System\n\nLihil's plugin system enables you to integrate external libraries seamlessly into your application as if they were built-in features. Any plugin that implements the `IPlugin` protocol can access endpoint information and wrap functionality around your endpoints.\n\n### Plugin Execution Flow\n\nWhen you apply multiple plugins like `@app.sub(\"/api/data\").get(plugins=[plugin1.dec, plugin2.dec])`, here's how they execute:\n\n```\n\nPlugin Application (Setup Time - Left to Right)\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502  original_func \u2192 plugin1(ep_info) \u2192 plugin2(ep_info)        \u2502\n\u2502                                                             \u2502\n\u2502  Result: plugin2(plugin1(original_func))                    \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\nRequest Execution (Runtime - Nested/Onion Pattern)\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502                                                            \u2502\n\u2502   Request                                                  \u2502\n\u2502       \u2502                                                    \u2502\n\u2502       \u25bc                                                    \u2502\n\u2502  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510   \u2502\n\u2502  \u2502 Plugin2 (Outermost)                                 \u2502   \u2502\n\u2502  \u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502   \u2502\n\u2502  \u2502 \u2502 Plugin1 (Middle)                                \u2502 \u2502   \u2502\n\u2502  \u2502 \u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502 \u2502   \u2502\n\u2502  \u2502 \u2502 \u2502 Original Function (Core)                    \u2502 \u2502 \u2502   \u2502\n\u2502  \u2502 \u2502 \u2502                                             \u2502 \u2502 \u2502   \u2502\n\u2502  \u2502 \u2502 \u2502 async def get_data():                       \u2502 \u2502 \u2502   \u2502\n\u2502  \u2502 \u2502 \u2502     return {\"data\": \"value\"}                \u2502 \u2502 \u2502   \u2502\n\u2502  \u2502 \u2502 \u2502                                             \u2502 \u2502 \u2502   \u2502\n\u2502  \u2502 \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502 \u2502   \u2502\n\u2502  \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502   \u2502\n\u2502  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518   \u2502\n\u2502       \u2502                                                    \u2502\n\u2502       \u25bc                                                    \u2502\n\u2502   Response                                                 \u2502\n\u2502                                                            \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n```\n\n#### Execution Order:\n   Request \u2192 Plugin2 \u2192 Plugin1 \u2192 get_data() \u2192 Plugin1 \u2192 Plugin2 \u2192 Response\n\n#### Real Example with Premier Plugins:\n```python\n   @app.sub(\"/api\").get(plugins=[\n       plugin.timeout(5),           # Applied 1st \u2192 Executes Outermost\n       plugin.retry(max_attempts=3), # Applied 2nd \u2192 Executes Middle\n       plugin.cache(expire_s=60),   # Applied 3rd \u2192 Executes Innermost\n   ])\n```\n\nFlow: Request \u2192 timeout \u2192 retry \u2192 cache \u2192 endpoint \u2192 cache \u2192 retry \u2192 timeout \u2192 Response\n\n\n### Creating a Custom Plugin\n\nA plugin is anything that implements the `IPlugin` protocol - either a callable or a class with a `decorate` method:\n\n```python\nfrom lihil.plugins.interface import IPlugin, IEndpointInfo\nfrom lihil.interface import IAsyncFunc, P, R\nfrom typing import Callable, Awaitable\n\nclass MyCustomPlugin:\n    \"\"\"Plugin that integrates external libraries with lihil endpoints\"\"\"\n\n    def __init__(self, external_service):\n        self.service = external_service\n\n    def decorate(self, ep_info: IEndpointInfo[P, R]) -> Callable[P, Awaitable[R]]:\n        \"\"\"\n        Access endpoint info and wrap functionality around it.\n        ep_info contains:\n        - ep_info.func: The original endpoint function\n        - ep_info.sig: Parsed signature with type information\n        - ep_info.graph: Dependency injection graph\n        \"\"\"\n        original_func = ep_info.func\n\n        async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:\n            # Pre-processing with external library\n            await self.service.before_request(ep_info.sig)\n\n            try:\n                result = await original_func(*args, **kwargs)\n                # Post-processing with external library\n                return await self.service.process_result(result)\n            except Exception as e:\n                # Error handling with external library\n                await self.service.handle_error(e)\n                raise\n\n        return wrapper\n\n# Usage - integrate any external library\nfrom some_external_lib import ExternalService\n\nplugin = MyCustomPlugin(ExternalService())\n\n@app.sub(\"/api/data\").get(plugins=[plugin.decorate])\nasync def get_data() -> dict:\n    return {\"data\": \"value\"}\n```\n\n\nThis architecture allows you to:\n- **Integrate any external library** as if it were built-in to lihil\n- **Access full endpoint context** - signatures, types, dependency graphs\n- **Wrap functionality** around endpoints with full control\n- **Compose multiple plugins** for complex integrations\n- **Zero configuration** - plugins work automatically based on decorators\n\n## Error Handling with HTTPException\n\nLihil provides a powerful and flexible error handling system based on RFC 9457 Problem Details specification. The `HTTPException` class extends `DetailBase` and allows you to create structured, consistent error responses with rich metadata.\n\n### Basic Usage\n\nBy default, Lihil automatically generates problem details from your exception class:\n\n```python\nfrom lihil import HTTPException\n\nclass UserNotFound(HTTPException[str]):\n    \"\"\"The user you are looking for does not exist\"\"\"\n    __status__ = 404\n\n# Usage in endpoint\n@app.sub(\"/users/{user_id}\").get\nasync def get_user(user_id: str):\n    if not user_exists(user_id):\n        raise UserNotFound(f\"User with ID {user_id} not found\")\n    return get_user_data(user_id)\n```\n\nThis will produce a JSON response like:\n```json\n{\n  \"type\": \"user-not-found\",\n  \"title\": \"The user you are looking for does not exist\",\n  \"status\": 404,\n  \"detail\": \"User with ID 123 not found\",\n  \"instance\": \"/users/123\"\n}\n```\n\n### Customizing Problem Details\n\n#### 1. Default Behavior\n- **Problem Type**: Automatically generated from class name in kebab-case (`UserNotFound` \u2192 `user-not-found`)\n- **Problem Title**: Taken from the class docstring\n- **Status Code**: Set via `__status__` class attribute (defaults to 422)\n\n#### 2. Custom Problem Type and Title\n\nYou can customize the problem type and title using class attributes:\n\n```python\nclass UserNotFound(HTTPException[str]):\n    \"\"\"The user you are looking for does not exist\"\"\"\n    __status__ = 404\n    __problem_type__ = \"user-lookup-failed\"\n    __problem_title__ = \"User Lookup Failed\"\n```\n\n#### 3. Runtime Customization\n\nYou can also override problem details at runtime:\n\n```python\n@app.sub(\"/users/{user_id}\").get\nasync def get_user(user_id: str):\n    if not user_exists(user_id):\n        raise UserNotFound(\n            detail=f\"User with ID {user_id} not found\",\n            problem_type=\"custom-user-error\",\n            problem_title=\"Custom User Error\",\n            status=404\n        )\n    return get_user_data(user_id)\n```\n\n### Advanced Customization\n\n#### 1. Override `__problem_detail__` Method\n\nFor fine-grained control over how your exception transforms into a `ProblemDetail` object:\n\n```python\nfrom lihil.interface.problem import ProblemDetail\n\nclass ValidationError(HTTPException[dict]):\n    \"\"\"Request validation failed\"\"\"\n    __status__ = 400\n\n    def __problem_detail__(self, instance: str) -> ProblemDetail[dict]:\n        return ProblemDetail(\n            type_=\"validation-error\",\n            title=\"Request Validation Failed\",\n            status=400,\n            detail=self.detail,\n            instance=f\"users/{instance}\",\n        )\n\n# Usage\n@app.sub(\"/users/{user_id}\").post\nasync def update_user(user_data: UserUpdate):\n    validation_errors = validate_user_data(user_data)\n    if validation_errors:\n        raise ValidationError(title=\"Updating user failed\")\n    return create_user_in_db(user_data)\n```\n\n#### 2. Override `__json_example__` Method\n\nCustomize how your exceptions appear in OpenAPI documentation:\n\n```python\nclass UserNotFound(HTTPException[str]):\n    \"\"\"The user you are looking for does not exist\"\"\"\n    __status__ = 404\n\n    @classmethod\n    def __json_example__(cls) -> ProblemDetail[str]:\n        return ProblemDetail(\n            type_=\"user-not-found\",\n            title=\"User Not Found\",\n            status=404,\n            detail=\"User with ID 'user123' was not found in the system\",\n            instance=\"/api/v1/users/user123\"\n        )\n```\n\nThis is especially useful for providing realistic examples in your API documentation, including specific `detail` and `instance` values that Lihil cannot automatically resolve from class attributes.\n\n### Complex Error Scenarios\n\n#### Generic Error with Type Information\n\n```python\nfrom typing import Generic, TypeVar\n\nT = TypeVar('T')\n\nclass ResourceNotFound(HTTPException[T], Generic[T]):\n    \"\"\"The requested resource was not found\"\"\"\n    __status__ = 404\n\n    def __init__(self, detail: T, resource_type: str):\n        super().__init__(detail)\n        self.resource_type = resource_type\n\n    def __problem_detail__(self, instance: str) -> ProblemDetail[T]:\n        return ProblemDetail(\n            type_=f\"{self.resource_type}-not-found\",\n            title=f\"{self.resource_type.title()} Not Found\",\n            status=404,\n            detail=self.detail,\n            instance=instance\n        )\n\n# Usage\n@app.sub(\"/posts/{post_id}\").get\nasync def get_post(post_id: str):\n    if not post_exists(post_id):\n        raise ResourceNotFound(\n            detail=f\"Post {post_id} does not exist\",\n            resource_type=\"post\"\n        )\n    return get_post_data(post_id)\n```\n\n### Benefits\n\n- **Consistency**: All error responses follow RFC 9457 Problem Details specification\n- **Developer Experience**: Rich type information and clear error messages\n- **Documentation**: Automatic OpenAPI schema generation with examples\n- **Flexibility**: Multiple levels of customization from simple to advanced\n- **Traceability**: Built-in problem page links in OpenAPI docs for debugging\n\nThe error handling system integrates seamlessly with Lihil's OpenAPI documentation generation, providing developers with comprehensive error schemas and examples in the generated API docs.\n\n## AI Agent Support\n\n**Using AI coding assistants with Lihil?** Check out [LIHIL_COPILOT.md](LIHIL_COPILOT.md) for:\n\n- **AI Agent Best Practices** - Comprehensive guide for AI assistants working with Lihil\n- **Common Mistakes & Solutions** - Learn from real AI agent errors and how to avoid them\n- **Complete Templates** - Ready-to-use patterns that AI agents can follow\n- **Lihil vs FastAPI Differences** - Critical syntax differences AI agents must know\n- **How to Use as Prompt** - Instructions for Claude Code, Cursor, ChatGPT, and GitHub Copilot\n\n**Quick Setup:** Copy the entire LIHIL_COPILOT.md content and paste it as system context in your AI tool. This ensures your AI assistant understands Lihil's unique syntax and avoids FastAPI assumptions.\n\n## Tutorials\n\nCheck our detailed tutorials at https://lihil.cc, covering\n\n- Core concepts, create endpoint, route, middlewares, etc.\n- Configuring your app via `pyproject.toml`, or via command line arguments.\n- Dependency Injection & Plugins\n- Testing\n- Type-Based Message System, Event listeners, atomic event handling, etc.\n- Error Handling\n- ...and much more\n\n## Lihil Admin & Full stack template\n\nSee how lihil works here, a production-ready full stack template that uses react and lihil,\n\n[lihil-fullstack-solopreneur-template](https://github.com/raceychan/fullstack-solopreneur-template)\n\ncovering real world usage & best practices of lihil.\nA fullstack template for my fellow solopreneur, uses shadcn+tailwindcss+react+lihil+sqlalchemy+supabase+vercel+cloudlfare to end modern slavery\n\n## Versioning\n\nlihil follows semantic versioning after v1.0.0, where a version in x.y.z represents:\n\n- x: major, breaking change\n- y: minor, feature updates\n- z: patch, bug fixes, typing updates\n\n## Contributing\n\nWe welcome all contributions! Whether you're fixing bugs, adding features, improving documentation, or enhancing tests - every contribution matters.\n\n### Quick Start for Contributors\n\n1. **Fork & Clone**: Fork the repository and clone your fork\n2. **Find Latest Branch**: Use `git branch -r | grep \"version/\"` to find the latest development branch (e.g., `version/0.2.23`)\n3. **Create Feature Branch**: Branch from the latest version branch\n4. **Make Changes**: Follow existing code conventions and add tests\n5. **Submit PR**: Target your PR to the latest development branch\n\nFor detailed contributing guidelines, workflow, and project conventions, see our [Contributing Guide](.github/CONTRIBUTING.md).\n\n## Roadmap\n\n### Road Map before v1.0.0\n\n- [x] **v0.1.x: Feature parity** (alpha stage)\n\nImplementing core functionalities of lihil, feature parity with fastapi\n\n- [x] **v0.2.x: Official Plugins** (current stage)\n\nWe would keep adding new features & plugins to lihil without making breaking changes.\nThis might be the last minor versions before v1.0.0.\n\n- [ ] **v0.3.x: Performance boost**\n\nThe plan is to rewrite some components in c, roll out a server in c, or other performance optimizations in 0.3.x.\n\nIf we can do this without affect current implementations in 0.2.0 at all, 0.3.x may never occur and we would go straight to v1.0.0 from v0.2.x\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "async python framework offering high level development, low level performance.",
    "version": "0.2.26",
    "project_urls": null,
    "split_keywords": [],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "2d968366abaacd5e4a93ef66e5bdb5661c7fcce5a65811058cb815fefdf7c4fe",
                "md5": "09179f1f8d8765ac02b6d4d60784341d",
                "sha256": "1e96490eb5bef1a719ecbb97ba53a31b4d6305bbba38eb562a0f479635e27e64"
            },
            "downloads": -1,
            "filename": "lihil-0.2.26-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "09179f1f8d8765ac02b6d4d60784341d",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10",
            "size": 92734,
            "upload_time": "2025-09-16T14:39:14",
            "upload_time_iso_8601": "2025-09-16T14:39:14.196005Z",
            "url": "https://files.pythonhosted.org/packages/2d/96/8366abaacd5e4a93ef66e5bdb5661c7fcce5a65811058cb815fefdf7c4fe/lihil-0.2.26-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "b14eb5be5b2643f8c1fa71aa20f898050ea10faa7203755c0454d80532fb0624",
                "md5": "c6a99cdd38274f17e23cfce9bf59719c",
                "sha256": "9909dc57a354b53a2d7f8bf19520cfd6fd11fcd283c27568ba47339faa205944"
            },
            "downloads": -1,
            "filename": "lihil-0.2.26.tar.gz",
            "has_sig": false,
            "md5_digest": "c6a99cdd38274f17e23cfce9bf59719c",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10",
            "size": 343597,
            "upload_time": "2025-09-16T14:39:15",
            "upload_time_iso_8601": "2025-09-16T14:39:15.904685Z",
            "url": "https://files.pythonhosted.org/packages/b1/4e/b5be5b2643f8c1fa71aa20f898050ea10faa7203755c0454d80532fb0624/lihil-0.2.26.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-09-16 14:39:15",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "lihil"
}
        
Elapsed time: 2.04464s