Metadata-Version: 2.5
Name: dynamicforms-fastapi-viewsets
Version: 0.9.1
Summary: Django REST Framework-style viewsets for FastAPI, with optional Celery-backed async execution and a Vue/TypeScript client counterpart.
Project-URL: Homepage, https://github.com/dynamicforms/fastapi-viewsets
Project-URL: Repository, https://github.com/dynamicforms/fastapi-viewsets
Project-URL: Issues, https://github.com/dynamicforms/fastapi-viewsets/issues
Project-URL: Documentation, https://docs.velis.si/dynamicforms/fastapi-viewsets/
Author-email: Jure Erznožnik <jure.erznoznik@gmail.com>
License: MIT
License-File: LICENSE
Keywords: celery,django-rest-framework,dynamicforms,fastapi,rest,viewsets
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: fastapi>=0.100
Requires-Dist: pydantic>=2.11
Requires-Dist: typing-extensions>=4.10
Provides-Extra: celery
Requires-Dist: celery>=5.3; extra == 'celery'
Requires-Dist: redis>=5.0; extra == 'celery'
Provides-Extra: dev
Requires-Dist: asgiref; extra == 'dev'
Requires-Dist: celery; extra == 'dev'
Requires-Dist: coverage; extra == 'dev'
Requires-Dist: django; extra == 'dev'
Requires-Dist: httpx2; extra == 'dev'
Requires-Dist: muxws[starlette,websockets]>=0.4.1; extra == 'dev'
Requires-Dist: pyjwt; extra == 'dev'
Requires-Dist: pytest; extra == 'dev'
Requires-Dist: pytest-asyncio; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: redis; extra == 'dev'
Provides-Extra: django
Requires-Dist: asgiref>=3.6; extra == 'django'
Requires-Dist: django>=4.2; extra == 'django'
Provides-Extra: jwt
Requires-Dist: pyjwt>=2.8; extra == 'jwt'
Provides-Extra: muxws
Requires-Dist: muxws[starlette]>=0.4.1; extra == 'muxws'
Description-Content-Type: text/markdown

# DynamicForms FastAPI Viewsets

Django REST Framework-style viewsets for [FastAPI](https://fastapi.tiangolo.com/), with optional
Celery-backed async execution and a matching Vue/TypeScript client counterpart.

- **Python mixins for FastAPI** — compose CRUD and bulk endpoints from small, focused mixin classes.
- **`route_viewset` decorator** — register a viewset on a FastAPI router with a single decorator call.
  Handles type resolution, lifecycle management and OpenAPI schema automatically.
- **`CollectionViewSet`** — zero-boilerplate in-memory viewset backed by any Python list, set or dict.
  Great for prototyping and testing.
- **`DjangoORMViewSet`** — back a viewset with a Django QuerySet. The filters it can compile, the
  sort order with its NULL placement, the page as LIMIT/OFFSET and the total as `COUNT(*)` all
  become SQL; a stage it declines falls back to the in-memory pass instead of failing, and
  `pk_field_name` comes from the model's primary key (requires the `django` extra).
- **`celery_viewset` decorator** — move a viewset's execution to a Celery worker with no code changes
  to the viewset itself, for long-running or background processing scenarios (requires the `celery`
  extra).
- **Bulk operations** — first-class support for bulk create, update, partial update and destroy.
- **muxws transport** — reach the same viewsets over a single WebSocket instead of one HTTP request
  per call. A command goes through your application as an HTTP request does - its middleware,
  exception handlers, router dependencies and state - so both transports behave identically. In the
  demo, a burst of 100 requests takes 156 ms over REST and 37 ms over muxws.
- **Events** — publish an event to a topic and every frontend following it learns of it, as a muxws
  push or on its next poll. `ChangeEvents` publishes every create, update and destroy of a viewset to
  its routed base path; custom events are declared with `source()`. Every event is recorded before it
  is pushed, so a dropped push costs latency, not an event. On the frontend, `watch()` on a ViewSet
  follows its topic and `applyChanges()` applies each change to a list of rows.
- **Three list shapes** — a bare array, offset paging, or cursor paging. A viewset declares which,
  and may let a client pick per request with an `X-List-Shape` header.
- **Declarative filters** — declare which fields accept which operators and get query parameters,
  an OpenAPI schema and filtering for free; backends translate what they can into their own query.
- **Vue / TypeScript counterpart** — mirror mixin classes and the `restViewSet` / `muxwsViewSet` class
  factory give you a fully typed client that matches your backend viewset exactly: the mixins a ViewSet
  declares are its public surface, so calling an action it did not declare is a compile error rather
  than a runtime 404 (published separately as
  [`@dynamicforms/fastapi-viewsets`](https://www.npmjs.com/package/@dynamicforms/fastapi-viewsets) on npm).

## Installation

```bash
pip install dynamicforms-fastapi-viewsets

# with Celery-backed viewset support
pip install "dynamicforms-fastapi-viewsets[celery]"

# with the muxws WebSocket transport
pip install "dynamicforms-fastapi-viewsets[muxws]"

# with the Django ORM backend (Django 4.2+ and asgiref)
pip install "dynamicforms-fastapi-viewsets[django]"
```

Requires Python 3.10+, FastAPI and Pydantic 2.11+.

## Quick example

```python
from fastapi import APIRouter, FastAPI
from pydantic import BaseModel

from fastapi_viewsets.collection_viewset import CollectionViewSet
from fastapi_viewsets.decorators.route_viewset import route_viewset
from fastapi_viewsets.mixins import BulkViewSetMixin


class Item(BaseModel):
    id: int
    name: str


database: dict[int, Item] = {1: Item(id=1, name="First element")}

app = FastAPI()
router = APIRouter()


@route_viewset(router, base_path="/items", pk_field_name="id")
class ItemViewSet(CollectionViewSet[int, Item], BulkViewSetMixin[int, Item]):
    def __init__(self):
        super().__init__(container=database, pk_field="id")


app.include_router(router)
```

See the [full documentation](https://docs.velis.si/dynamicforms/fastapi-viewsets/) for guides on
the mixin system, `route_viewset`, `CollectionViewSet`, `DjangoORMViewSet`, `celery_viewset`, the
list pipeline and pagination, the muxws transport, and the Vue client.

## Demo

```bash
python demo.py                       # backend on :8000, frontend on :5173
python demo.py --celery              # ... with every viewset call routed through a Celery worker
npm run test:e2e                     # drives the demo in a browser
```

An infinite-scrolling grid over a 5000-track library, cursor-paged. Switch the whole grid between
the REST and muxws transports and between the in-memory and SQLite backends, and compare their
latency side by side. Sorting and filtering are server-side.

The grid follows its viewset's change events (`ChangeEvents` on the backend, `watch()` in the
grid). Click a Favorite cell to toggle it: every tab showing the library applies the change, over
muxws as a push, over REST on its next poll. A sorted or filtered grid reloads its list on the
change: the row may have moved or left it.

`--celery` needs Redis on localhost:6379. The worker runs the viewset calls of both backends and
publishes their events through Redis. It runs as one process, because the in-memory library is a
dict held by each process; the SQLite library is one file shared by every process. The end-to-end
suite starts its own backend and dev server on their own ports.

## License

MIT — see [LICENSE](LICENSE).
