fsrest
fsrest provides reusable single-resource REST CRUD orchestration with action
names familiar to Django REST Framework users. It is framework-independent:
request and response objects are Pydantic models, while persistence is supplied
through a small repository protocol.
Designed and developed by Codex.
Install
pip install fsrest
Python 3.9+ and Pydantic 1.10/2.x are supported.
DRF-style actions
CrudViewSet follows DRF's standard action vocabulary:
listretrievecreateupdatepartial_updatedestroy
It is deliberately not an HTTP view and does not depend on Django. A FastAPI, Flask, Django, or other framework adapter can call these actions after request validation.
Bind a repository to CrudViewSet. Request schemas provide the conversion
methods needed to turn HTTP-facing data into repository fields.
from typing import Optional
from pydantic import BaseModel
from fsrest import CrudViewSet, PageRequest, PageResponse
class Item(BaseModel):
id: str
name: str
class Filters(BaseModel):
name: Optional[str] = None
class Ordering(BaseModel):
field: str = "id"
class CreateFields(BaseModel):
name: str
class UpdateFields(BaseModel):
name: Optional[str] = None
class PageData(BaseModel):
items: list[Item]
total: int
class ListQuery(PageRequest):
name: Optional[str] = None
def build_filters(self) -> Filters:
return Filters(name=self.name)
def build_ordering(self) -> Ordering:
return Ordering()
def build_response(self, *, page_data: PageData) -> PageResponse[Item]:
return PageResponse[Item](
items=page_data.items,
total=page_data.total,
page=self.page,
page_size=self.page_size,
)
class ItemRepository:
@classmethod
def list_schema_page(
cls,
*,
filters: Filters,
ordering: Ordering,
page: int,
page_size: int,
) -> PageData:
...
@classmethod
def get_schema_by_id(cls, *, item_id: str) -> Optional[Item]:
...
@classmethod
def create_schema(cls, *, fields: CreateFields) -> Item:
...
@classmethod
def update_schema_by_id(
cls,
*,
item_id: str,
fields: UpdateFields,
) -> Optional[Item]:
...
@classmethod
def delete_by_id(cls, *, item_id: str) -> bool:
...
class ItemViewSet(CrudViewSet):
repository = ItemRepository
Framework code can now use familiar action names:
page = ItemViewSet.list(query=query)
item = ItemViewSet.retrieve(query=lookup)
created = ItemViewSet.create(payload=create_payload)
updated = ItemViewSet.update(payload=update_payload)
patched = ItemViewSet.partial_update(payload=patch_payload)
result = ItemViewSet.destroy(payload=delete_payload)
Customizing behavior
Subclass a viewset and override only the smallest relevant hook. The public actions stay unchanged, so framework adapters do not need special cases.
class TenantItemViewSet(ItemViewSet):
not_found_message = "Item {lookup_value} does not exist"
@classmethod
def get_repository(cls) -> type[ItemRepository]:
# Select a repository at runtime, for example by tenant context.
return repository_for_current_tenant()
@classmethod
def get_filters(cls, *, query: ListQuery) -> Filters:
filters = super().get_filters(query=query)
return filters.model_copy(update={"tenant_id": current_tenant_id()})
@classmethod
def perform_create(cls, *, fields: CreateFields) -> Item:
item = super().perform_create(fields=fields)
publish_item_created(item)
return item
Available customization layers:
| Concern | Hook |
|---|---|
| Runtime persistence selection | get_repository |
| URL/request lookup extraction | get_lookup_value |
| Object loading | get_object |
| Filters and ordering | get_filters, get_ordering |
| Pagination execution | paginate |
| List response construction | build_list_response |
| Create/update field conversion | get_create_fields, get_update_fields |
| Persistence side effects | perform_create, perform_update, perform_destroy |
| Error construction and messages | get_exception, handle_not_found, handle_destroy_failure |
get_update_fields(payload, partial=...) receives whether the caller used
update or partial_update, so applications can implement PUT/PATCH semantics
without replacing either action.
Composing capabilities
Like DRF, fsrest exposes action mixins for building smaller viewsets:
from fsrest import (
CreateModelMixin,
GenericViewSet,
ListModelMixin,
)
class CreateListItemViewSet(
CreateModelMixin,
ListModelMixin,
GenericViewSet,
):
repository = ItemRepository
Available mixins are ListModelMixin, RetrieveModelMixin,
CreateModelMixin, UpdateModelMixin, and DestroyModelMixin.
For the common read-only case, use the precomposed viewset:
from fsrest import ReadOnlyViewSet
class PublicItemViewSet(ReadOnlyViewSet):
repository = ItemRepository
ReadOnlyViewSet exposes only list and retrieve; write actions are not
present. CrudViewSet remains the precomposed full CRUD option.
The library raises RestApiError for missing records and failed deletes. To integrate with an application's existing exception middleware, subclass it and bind error_class:
class ApplicationApiError(RestApiError):
error_code = 400455
class ItemViewSet(CrudViewSet):
repository = ItemRepository
error_class = ApplicationApiError
Migrating from 0.1
The 0.1 API remains available for compatibility. New code should prefer these names:
| 0.1 API | 0.2 API |
|---|---|
RestCrudLogicBase |
CrudViewSet |
dao_rest_crud |
repository |
list_items |
list |
get_item |
retrieve |
create_item |
create |
update_item |
update |
delete_item |
destroy |
RestPageReqSchema |
PageRequest |
RestPageRespSchema |
PageResponse |
RestDeleteRespSchema |
DestroyResponse |
Development and publishing
From the pytools repository root, use the unified release script:
python make.py fsrest test
python make.py fsrest build
python make.py fsrest publish
publish uploads the artifacts under fsrest/dist/ using the PyPI credentials
configured in ~/.pypirc. Before publishing a new release, update the version
in pyproject.toml, run tests, and build fresh artifacts.
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 fsrest-0.4.0.tar.gz.
File metadata
- Download URL: fsrest-0.4.0.tar.gz
- Upload date:
- Size: 33.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bee966a5aad309d88f90431ef1eee8657f9b3f5248986408b443b1d8ecfa8441
|
|
| MD5 |
7a74b313180718b6aea97030a7f5b619
|
|
| BLAKE2b-256 |
0f4c77c5e40db1aa5c9909ea871ec3f1c9f08511952af01ff4979c89f4dab352
|
File details
Details for the file fsrest-0.4.0-py3-none-any.whl.
File metadata
- Download URL: fsrest-0.4.0-py3-none-any.whl
- Upload date:
- Size: 9.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ad15f8ce6fb87297cde0699bdb18d93971064e1212d9937c48770fa86a6c6bdb
|
|
| MD5 |
31e371bb86df56911feb4f3f3d8969e4
|
|
| BLAKE2b-256 |
1bc995100d84fd3c00927aed8939de1e9d4bc5b8ea0dcef2de7672148db1e5f5
|