Skip to main content

BunnyHopApi is a lightweight and fast web framework designed to handle modern web development needs.

Project description

BunnyHopApi

Check out the full documentation

BunnyHopApi is a lightweight and fast web framework designed to handle modern web development needs. It provides full support for:

  • HTTP Requests: Easily handle all HTTP methods.
  • SSE (Server-Sent Events): Support for server-sent events.
  • WebSockets: Real-time bidirectional communication.
  • Middlewares:
    • At the global level.
    • At the route level.
    • At the endpoint level.
  • CORS: Simple configuration to enable CORS.
  • Cookies: Read request cookies and set response cookies with full attribute support.
  • Web Page Rendering:
    • Static pages.
    • Dynamic pages with Jinja2.
  • Type Validation: Automatic validation for query parameters, path parameters, and request bodies.
  • Swagger Documentation: Automatically generated Swagger documentation for all endpoints.
  • Exceptional Performance: Designed to be fast and efficient.

Key Features

1. HTTP, SSE, and WebSocket Support

BunnyHopApi allows handling standard HTTP requests, SSE for real-time updates, and WebSockets for bidirectional communication.

Example: HTTP Endpoint

@server.get("/health")
def health(headers):
    return 200, {"message": "GET /health"}

Example: SSE Endpoint

class SseEndpoint(Endpoint):
    path = "/sse/events"

    @Endpoint.GET(content_type=Router.CONTENT_TYPE_SSE)
    async def get(self, headers) -> {200: str}:
        events = ["start", "progress", "complete"]

        for event in events:
            yield f"event: {event}\ndata: Processing {event}\n\n"
            await asyncio.sleep(1.5)

        yield "event: end\ndata: Processing complete\n\n"

Example: WebSocket Endpoint

class WSEndpoint(Endpoint):
    path = "/ws/chat"

    async def connection(self, headers):
        logger.info("Client connected")
        logger.info(f"Headers: {headers}")

        return True

    async def disconnect(self, connection_id, headers):
        logger.info(f"Client {connection_id} disconnected")

    async def ws(self, connection_id, message, headers):
        logger.info(f"Received message from {connection_id}: {message}")
        for i in range(10):
            yield f"event: message\ndata: {i}\n\n"
            await asyncio.sleep(0.2)

2. Flexible Middlewares

Define middlewares at different levels:

  • Global: Applied to all routes and endpoints.
  • Route-specific: Applied to a specific set of endpoints.
  • Endpoint-specific: Applied to an individual endpoint.

Example: Global Middleware

async def global_middleware(endpoint, headers, **kwargs):
    logger.info("global_middleware: Before calling the endpoint")
    result = endpoint(headers=headers, **kwargs)
    response = await result if asyncio.iscoroutine(result) else result
    logger.info("global_middleware: After calling the endpoint")
    return response

Example: Database-Specific Middleware

class UserEndpoint(Endpoint):
    path: str = "/users"

    @Endpoint.MIDDLEWARE()
    def db_middleware(self, endpoint, headers, *args, **kwargs):
        logger.info("db_middleware: Before calling the endpoint")
        db = Database()
        return endpoint(headers=headers, db=db, *args, **kwargs)

3. CRUD with SQLite

BunnyHopApi makes it easy to implement CRUD operations with support for databases like SQLite.

Example: CRUD Operations

class UserEndpoint(Endpoint):
    path: str = "/users"

    @Endpoint.MIDDLEWARE()
    def db_middleware(self, endpoint, headers, *args, **kwargs):
        logger.info("db_middleware: Before calling the endpoint")
        db = Database()
        return endpoint(headers=headers, db=db, *args, **kwargs)

    @Endpoint.GET()
    def get(self, headers, db: Database, *args, **kwargs) -> {200: UserList}:
        users = db.get_users()
        return 200, {"users": users}

    @Endpoint.POST()
    def post(self, user: UserInput, headers, db, *args, **kwargs) -> {201: UserOutput}:
        new_user = db.add_user(user)
        return 201, new_user

    @Endpoint.PUT()
    def put(
        self, db, user_id: PathParam[str], user: UserInput, headers, *args, **kwargs
    ) -> {200: UserOutput, 404: Message}:
        updated_user = db.update_user(user_id, user)

        if updated_user is None:
            return 404, {"message": "User not found"}

        return 200, updated_user

    @Endpoint.DELETE()
    def delete(
        self, db, user_id: PathParam[str], headers, *args, **kwargs
    ) -> {200: Message, 404: Message}:
        if db.delete_user(user_id):
            return 200, {"message": "User deleted"}
        else:
            return 404, {"message": "User not found"}

4. Cookies

BunnyHopApi provides built-in support for reading cookies from incoming requests and setting cookies on responses.

Reading cookies

Declare cookies: dict in your handler signature and the framework will automatically inject the cookies parsed from the Cookie request header.

@server.get("/profile")
def profile(headers, cookies: dict):
    user_id = cookies.get("user_id")
    if not user_id:
        return 401, {"error": "Not authenticated"}
    return 200, {"user_id": user_id}

Setting cookies

Return a third element in the response tuple: a dict mapping cookie names to values. Use a plain string for simple cookies, or CookieOptions when you need to control attributes like Max-Age, Path, HttpOnly, Secure, or SameSite.

from bunnyhopapi.models import CookieOptions

@server.post("/login")
def login(headers):
    response_cookies = {
        # Simple cookie (no attributes)
        "theme": "dark",

        # Cookie with full attribute control
        "session": CookieOptions(
            value="abc123",
            path="/",
            max_age=60 * 60 * 8,  # 8 hours
            httponly=True,
            secure=True,
            samesite="Lax",
        ),
    }
    return 200, {"message": "Logged in"}, response_cookies

Modifying an existing cookie

Read the current value from cookies, compute the new value, and return it in the response cookies dict under the same name.

@server.get("/visit")
def visit(headers, cookies: dict):
    count = int(cookies.get("visits", 0)) + 1
    return 200, {"visits": count}, {
        "visits": CookieOptions(value=str(count), path="/", max_age=60 * 60 * 24 * 7),
    }

Deleting a cookie

Set max_age=0 to instruct the browser to expire the cookie immediately.

@server.get("/logout")
def logout(headers):
    return 200, {"message": "Logged out"}, {
        "session": CookieOptions(value="", path="/", max_age=0),
    }

CookieOptions reference

Attribute Type Default Description
value str Cookie value (required)
path str "/" URL path scope
max_age int None Lifetime in seconds. 0 deletes the cookie
expires str None Expiry date string (RFC 7231 format)
domain str None Domain scope
httponly bool False Blocks JavaScript access via document.cookie
secure bool False Only sent over HTTPS
samesite str None "Strict", "Lax", or "None"

5. Swagger Documentation

BunnyHopApi automatically generates Swagger documentation for all endpoints, making it easy to explore and test your API.

Example: Access Swagger

Once the server is running, visit /docs in your browser to view the Swagger UI.

6. Installation

You can install BunnyHopApi directly from PyPI:

pip install bunnyhopapi

7. Example Project

Check the example/crud.py file for an example of how to generate a CRUD using BunnyHopApi. or Check the example/main.py file for a complete example of how to use BunnyHopApi.

8. Benchmark

With Bunnyhopapi python example/health.py

root@4b6138c8bf6c:/# wrk -t12 -c400 -d30s http://127.0.0.1:8000/health    
Running 30s test @ http://127.0.0.1:8000/health
  12 threads and 400 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    17.83ms   91.31ms   1.79s    98.66%
    Req/Sec     0.96k   587.57     2.56k    64.54%
  341722 requests in 30.08s, 37.80MB read
Requests/sec:  11358.95
Transfer/sec:      1.26MB

With Fastapi https://fastapi.tiangolo.com/#example

root@4b6138c8bf6c:/# wrk -t12 -c400 -d30s http://127.0.0.1:8000/health
Running 30s test @ http://127.0.0.1:8000/health
  12 threads and 400 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   198.46ms  114.91ms   2.00s    95.19%
    Req/Sec   175.68     47.44     0.88k    88.62%
  61903 requests in 30.05s, 8.38MB read
Requests/sec:   2059.83
Transfer/sec:    285.64KB

With Django

root@4b6138c8bf6c:/# wrk -t12 -c400 -d30s --timeout=1m http://127.0.0.1:8000/health
Running 30s test @ http://127.0.0.1:8000/health
  12 threads and 400 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   116.12ms    5.01ms 132.72ms   97.85%
    Req/Sec   284.80     49.70   560.00     80.69%
  102274 requests in 30.09s, 28.38MB read
Requests/sec:   3398.65
Transfer/sec:      0.94MB

8. Summary

Framework Requests/sec Latencia Promedio
Bunnyhopapi 11358.95 17.83ms
FastAPI 2059.83 198.46ms
Django 3398.65 116.12ms

9. License

This project is licensed under the MIT License. See the LICENSE file for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bunnyhopapi-2.0.1.tar.gz (33.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bunnyhopapi-2.0.1-py3-none-any.whl (20.9 kB view details)

Uploaded Python 3

File details

Details for the file bunnyhopapi-2.0.1.tar.gz.

File metadata

  • Download URL: bunnyhopapi-2.0.1.tar.gz
  • Upload date:
  • Size: 33.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for bunnyhopapi-2.0.1.tar.gz
Algorithm Hash digest
SHA256 64f3545f0d4f10bd67fd460bc63b752c8425282649b3c5f94b0ef5240aa5793d
MD5 ea7854092229240333e3accfb4bbf88e
BLAKE2b-256 a114d3d5971948dec01551a09b5af4cf3e9d871c537aa5417e0c3bb12fc78c2f

See more details on using hashes here.

File details

Details for the file bunnyhopapi-2.0.1-py3-none-any.whl.

File metadata

  • Download URL: bunnyhopapi-2.0.1-py3-none-any.whl
  • Upload date:
  • Size: 20.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for bunnyhopapi-2.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 cdd01d63dcf67d67dc3e67aa6165ff7672e3101aa3712d8ef226db54742157b1
MD5 bd0c43c22e8430b8200be679d70f611e
BLAKE2b-256 cdf940cc585e6d2c88dc104343a897671fa4ae5b1b095d23ba9163190cea833a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page