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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
64f3545f0d4f10bd67fd460bc63b752c8425282649b3c5f94b0ef5240aa5793d
|
|
| MD5 |
ea7854092229240333e3accfb4bbf88e
|
|
| BLAKE2b-256 |
a114d3d5971948dec01551a09b5af4cf3e9d871c537aa5417e0c3bb12fc78c2f
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cdd01d63dcf67d67dc3e67aa6165ff7672e3101aa3712d8ef226db54742157b1
|
|
| MD5 |
bd0c43c22e8430b8200be679d70f611e
|
|
| BLAKE2b-256 |
cdf940cc585e6d2c88dc104343a897671fa4ae5b1b095d23ba9163190cea833a
|