FrappeAPI
Better APIs for Frappe.
FrappeAPI brings FastAPI-style routing and validation to the Frappe Framework. Define endpoints with type hints, get automatic validation and documentation.
Documentation
Installation
pip install frappeapi
Quick Start
from frappeapi import FrappeAPI
app = FrappeAPI()
@app.get()
def hello(name: str = "World"):
return {"message": f"Hello, {name}!"}
Examples
Path Parameters
Enable FastAPI-style paths for cleaner URLs:
from frappeapi import FrappeAPI
app = FrappeAPI(fastapi_path_format=True)
@app.get("/items/{item_id}")
def get_item(item_id: str):
return {"id": item_id}
# GET /api/items/abc123
# Response: {"id": "abc123"}
Multiple path parameters:
@app.get("/users/{user_id}/orders/{order_id}")
def get_user_order(user_id: str, order_id: int):
return {"user_id": user_id, "order_id": order_id}
# GET /api/users/john/orders/42
# Response: {"user_id": "john", "order_id": 42}
Combine path and query parameters:
@app.get("/products/{category}")
def list_products(
category: str, # Path parameter
sort_by: str = "name", # Query parameter
limit: int = 10 # Query parameter
):
return {"category": category, "sort_by": sort_by, "limit": limit}
# GET /api/products/electronics?sort_by=price&limit=20
Query Parameters
Automatic type parsing:
@app.get()
def get_product_details(
product_id: int,
unit_price: float,
in_stock: bool
):
return {
"product_id": product_id, # "123" -> 123
"unit_price": unit_price, # "9.99" -> 9.99
"in_stock": in_stock # "true" -> True
}
Optional parameters with defaults:
@app.get()
def list_products(
category: str = "all",
page: int = 1,
search: str | None = None
):
return {"category": category, "page": page, "search": search}
Enum parameters:
from enum import Enum
class OrderStatus(str, Enum):
pending = "pending"
processing = "processing"
completed = "completed"
@app.get()
def list_orders(status: OrderStatus = OrderStatus.pending):
return {"status": status}
List parameters:
from frappeapi import Query
@app.get()
def search_products(
tags: List[str] = Query(default=[]),
categories: List[int] = Query(default=[])
):
return {"tags": tags, "categories": categories}
# GET ?tags=electronics&tags=sale&categories=1&categories=2
# Response: {"tags": ["electronics", "sale"], "categories": [1, 2]}
Aliased parameters:
from typing import Annotated
from frappeapi import Query
@app.get()
def search_items(
search_text: Annotated[str, Query(alias="q")] = "",
page_number: Annotated[int, Query(alias="p")] = 1
):
return {"search": search_text, "page": page_number}
# GET ?q=laptop&p=2
Query parameter models:
from pydantic import BaseModel, Field
class ProductFilter(BaseModel):
search: str | None = None
category: str = "all"
min_price: float = Field(0, ge=0)
in_stock: bool = True
@app.get()
def filter_products(filters: Annotated[ProductFilter, Query()]):
return filters
Request Body
Single model:
from pydantic import BaseModel, Field
class Item(BaseModel):
name: str = Field(..., min_length=1, max_length=50)
description: str | None = None
price: float = Field(..., gt=0)
@app.post()
def create_item(item: Item):
return item
Multiple body parameters:
class User(BaseModel):
username: str
email: str
class Item(BaseModel):
name: str
price: float
@app.post()
def create_user_item(user: User, item: Item):
return {"user": user, "item": item}
# Request body:
# {
# "user": {"username": "john", "email": "john@example.com"},
# "item": {"name": "Laptop", "price": 999.99}
# }
Nested models:
from pydantic import HttpUrl
class Image(BaseModel):
url: HttpUrl
name: str
class Product(BaseModel):
name: str
price: float
images: List[Image]
@app.post()
def create_product(product: Product):
return product
Form Data
from typing import Annotated
from frappeapi import Form
@app.post()
def create_user_profile(
username: Annotated[str, Form()],
email: Annotated[str, Form()],
bio: Annotated[str | None, Form()] = None
):
return {"username": username, "email": email, "bio": bio}
File Uploads
Small files (in-memory):
from typing import Annotated
from frappeapi import File, Form
@app.post()
def upload_document(
file: Annotated[bytes, File()],
description: Annotated[str | None, Form()] = None
):
return {"file_size": len(file), "description": description}
Large files (streamed):
from frappeapi import UploadFile
@app.post()
def upload_large_file(file: UploadFile):
return {
"filename": file.filename,
"content_type": file.content_type
}
Response Models
Filter response data:
class UserResponse(BaseModel):
id: int
username: str
email: str
@app.get(response_model=UserResponse)
def get_user(user_id: int):
return {
"id": user_id,
"username": "john_doe",
"email": "john@example.com",
"password": "secret" # Filtered out
}
List responses:
class Product(BaseModel):
id: int
name: str
price: float
@app.get(response_model=List[Product])
def list_products():
return [
{"id": 1, "name": "Laptop", "price": 999.99},
{"id": 2, "name": "Mouse", "price": 24.99}
]
Error Handling
Raise HTTP exceptions:
from frappeapi.exceptions import HTTPException
@app.get()
def get_item(item_id: int):
if item_id < 0:
raise HTTPException(status_code=400, detail="Item ID must be positive")
return {"id": item_id}
Custom exception handlers:
from frappeapi import JSONResponse, Request
class ItemNotFound(Exception):
def __init__(self, item_id: int):
self.item_id = item_id
@app.exception_handler(ItemNotFound)
def item_not_found_handler(request: Request, exc: ItemNotFound):
return JSONResponse(
status_code=404,
content={"error": "ITEM_NOT_FOUND", "detail": f"Item {exc.item_id} not found"}
)
Override validation error handler:
from frappeapi.exceptions import RequestValidationError
@app.exception_handler(RequestValidationError)
def validation_error_handler(request: Request, exc: RequestValidationError):
return JSONResponse(
status_code=422,
content={
"error": "VALIDATION_ERROR",
"details": [{"field": e["loc"][-1], "message": e["msg"]} for e in exc.errors()]
}
)
Header Parameters
from typing import Annotated
from frappeapi import Header
@app.get()
def get_user_info(
user_agent: Annotated[str, Header()],
x_custom_header: Annotated[str, Header()]
):
return {"user_agent": user_agent, "custom_header": x_custom_header}
# Headers: User-Agent, X-Custom-Header (hyphen converted to underscore)
Field Validation
String validation:
class Product(BaseModel):
name: str = Field(min_length=3, max_length=50)
sku: str = Field(pattern="^[A-Z]{2}-[0-9]{4}$") # Format: XX-0000
Numeric validation:
class Order(BaseModel):
quantity: int = Field(gt=0, le=100)
unit_price: float = Field(gt=0)
discount_percent: float = Field(ge=0, le=100)
Version Compatibility
FrappeAPI automatically detects your Frappe version:
| Frappe Version | Support |
|---|---|
| v14.x | Stable |
| v15.x | Stable |
| v16.x | Beta |
Check detected version:
import frappeapi
print(frappeapi.get_detected_frappe_version()) # Returns: 14, 15, or 16
Documentation
FrappeAPI follows FastAPI's interface. For detailed information, see FastAPI's documentation.
Roadmap
Frappe Versions
- Frappe V14 support
- Frappe V15 support
- Frappe V16 support (develop branch)
Methods
-
app.get(...) -
app.post(...) -
app.put(...) -
app.patch(...) -
app.delete(...)
Query Parameters
- Automatic type parsing based on type hints
- Required parameters (
needy: str,needy: str = ...) - Optional parameters with defaults (
skip: int = 0) - Optional parameters without defaults (
limit: int | None = None) - Enum support
- Boolean parameters
- List parameters (
?q=foo&q=bar) - Aliased parameters (
Query(alias="query")) - Query parameter models
- Automatic documentation generation
Body Parameters
- Pydantic model body (
item: Item) - Multiple body parameters
- Singular values with
Body() - Embed body parameter
- Nested models
- Automatic type parsing
Header Parameters
- Basic header support
- Header parameter models
- Duplicate headers
- Forbid extra headers
Cookie Parameters
- Cookie parameter support
Form Data
- Form fields with
Form() - Multiple form fields
- Form data as Pydantic model
- Forbid extra form fields
File Uploads
- Small files with
File() - Large files with
UploadFile - Optional file uploads
- Multiple file uploads
Error Handling
- HTTPException
- RequestValidationError
- ResponseValidationError
- Custom exception handlers
- Override default handlers
- Frappe transaction management
Response Models
-
response_modelparameter - Return type annotations
- Output filtering
-
response_modeltakes precedence over return type
Validation
- String validations (
min_length,max_length,pattern) - Numeric validations (
gt,ge,lt,le) - Metadata (
title,description,deprecated) -
include_in_schema
Planned
- Rate limiting
- Dependencies
- Middleware
- Debugging capabilities
- Dotted path parameters
Related
- PR #23135: Type hints for API functions
- PR #22300: Enhanced
frappe.whitelist() - PR #19029: Type safety improvements
- Issue #14905: API documentation discussion
Metadata
Release files for frappeapi 0.3.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| frappeapi-0.3.2.tar.gz | 886.6 kB | Without provenance | Details for frappeapi-0.3.2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| frappeapi-0.3.2-py3-none-any.whl | Python 3 | none | any | Details for frappeapi-0.3.2-py3-none-any.whl |
Total release size: 2.1 MB
Release files / frappeapi-0.3.2.tar.gz
| Download URL | frappeapi-0.3.2.tar.gz |
|---|---|
| Size | 886.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4c2b25e4ed8f7b3626e4592e541bfb8c8e12802a8fbb959f2834a8028af1e70b
|
|
BLAKE2b-256 checksum How to use checksums |
7cdbce0d0d4c9fa9c14eabdaf487aa29c67d9d4f7b09297f1a8f7469050be7c8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / frappeapi-0.3.2-py3-none-any.whl
| Download URL | frappeapi-0.3.2-py3-none-any.whl |
|---|---|
| Size | 1.2 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f5564c4258f4887d233a0f07df5f29b9a288164caaed2b4a980627763953f87f
|
|
BLAKE2b-256 checksum How to use checksums |
d5a101df1163fdfd530f164f695198d16b307dc7789c033cade48b6e472a31b8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for frappeapi 0.3.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.