Skip to main content

FrappeAPI, FastAPI for Frappe Framework

Project description

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

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_model parameter
  • Return type annotations
  • Output filtering
  • response_model takes 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

Download files

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

Source Distribution

frappeapi-0.3.2.tar.gz (886.6 kB view details)

Uploaded Source

Built Distribution

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

frappeapi-0.3.2-py3-none-any.whl (1.2 MB view details)

Uploaded Python 3

File details

Details for the file frappeapi-0.3.2.tar.gz.

File metadata

  • Download URL: frappeapi-0.3.2.tar.gz
  • Upload date:
  • Size: 886.6 kB
  • Tags: Source
  • Uploaded using 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}

File hashes

Hashes for frappeapi-0.3.2.tar.gz
Algorithm Hash digest
SHA256 4c2b25e4ed8f7b3626e4592e541bfb8c8e12802a8fbb959f2834a8028af1e70b
MD5 a5186be3657e39727701b3d1983758a6
BLAKE2b-256 7cdbce0d0d4c9fa9c14eabdaf487aa29c67d9d4f7b09297f1a8f7469050be7c8

See more details on using hashes here.

File details

Details for the file frappeapi-0.3.2-py3-none-any.whl.

File metadata

  • Download URL: frappeapi-0.3.2-py3-none-any.whl
  • Upload date:
  • Size: 1.2 MB
  • Tags: Python 3
  • Uploaded using 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}

File hashes

Hashes for frappeapi-0.3.2-py3-none-any.whl
Algorithm Hash digest
SHA256 f5564c4258f4887d233a0f07df5f29b9a288164caaed2b4a980627763953f87f
MD5 a7aa36ac4c811d49a7ae90e559975f9a
BLAKE2b-256 d5a101df1163fdfd530f164f695198d16b307dc7789c033cade48b6e472a31b8

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