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.
Release files for bunnyhopapi 2.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| bunnyhopapi-2.0.1.tar.gz | 33.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| bunnyhopapi-2.0.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 54.3 kB
Release files / bunnyhopapi-2.0.1.tar.gz
| Download URL | bunnyhopapi-2.0.1.tar.gz |
|---|---|
| Size | 33.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
64f3545f0d4f10bd67fd460bc63b752c8425282649b3c5f94b0ef5240aa5793d
|
|
BLAKE2b-256 checksum How to use checksums |
a114d3d5971948dec01551a09b5af4cf3e9d871c537aa5417e0c3bb12fc78c2f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.3
|
Release files / bunnyhopapi-2.0.1-py3-none-any.whl
| Download URL | bunnyhopapi-2.0.1-py3-none-any.whl |
|---|---|
| Size | 20.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cdd01d63dcf67d67dc3e67aa6165ff7672e3101aa3712d8ef226db54742157b1
|
|
BLAKE2b-256 checksum How to use checksums |
cdf940cc585e6d2c88dc104343a897671fa4ae5b1b095d23ba9163190cea833a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.3
|