Nested resources und Actions#
Dieses How-to erklärt, wie man:
- verschachtelte Ressourcen implementiert (z. B.
/foos/<uuid>/bars/), - Actions auf Ressourcen implementiert (z. B.
/v1/files/<id>/actions/download).
Es baut auf dem Basic-CRUD-Beispiel auf und nutzt Ideen aus examples/restapi_foo_bar_service.py und examples/openapi_app.py.
1. Verschachtelte Ressourcen#
Verschachtelte Ressourcen bilden hierarchische Beziehungen ab, z. B.:
- Elternelement:
FooModelunter/v1/foos/<uuid> - Verschachtelte Collection:
BarModelunter/v1/foos/<uuid>/bars/
1.1 DM-Modelle#
Wie im Basic-CRUD-Beispiel:
class FooModel(models.ModelWithUUID):
foo_field1 = properties.property(types.Integer(), required=True)
foo_field2 = properties.property(types.String(), default="foo_str")
class BarModel(models.ModelWithUUID):
bar_field1 = properties.property(types.String(min_length=1, max_length=10))
foo = relationships.relationship(FooModel)
1.2 Verschachtelter Controller#
Sie können BaseNestedResourceController verwenden oder die Logik explizit in einem eigenen Controller implementieren.
class BarController1(controllers.Controller):
"""Handler für /v1/foos/<uuid>/bars/."""
__resource__ = resources.ResourceByRAModel(BarModel, process_filters=True)
def create(self, bar_field1, parent_resource):
bar = BarModel(bar_field1=bar_field1, foo=parent_resource)
bar_storage[str(bar.get_id())] = bar
return bar
def filter(self, filters, parent_resource, order_by=None):
# Einfache Filterung: nur Bars für das gegebene Foo
return [
bar for bar in bar_storage.values() if bar.foo == parent_resource
]
Stattdessen kann auch BaseNestedResourceController verwendet werden, um die Behandlung von parent_resource zu kapseln.
1.3 Verschachtelte Route#
class BarRoute1(routes.Route):
__controller__ = BarController1
__allow_methods__ = [routes.CREATE, routes.FILTER]
class FooRoute(routes.Route):
__controller__ = FooController
__allow_methods__ = [routes.FILTER, routes.CREATE, routes.GET]
bars = routes.route(BarRoute1, resource_route=True)
resource_route=Truesignalisiert, dassBarRoute1unterhalb einer konkreten Elternressource (FooModelInstanz) arbeitet.- Für
/v1/foos/<foo_uuid>/bars/löst RA das übergeordneteFooModelaus der URL auf und übergibt es alsparent_resource.
2. Actions auf Ressourcen#
Actions repräsentieren Operationen, die nicht reine CRUD-Operationen auf der Hauptressource sind, z. B.:
/v1/files/<id>/actions/download— Datei-Download./v1/objects/<id>/actions/some_business_operation.
Siehe auch Besonderheiten der Serialisierung von Action-Ergebnissen:
2.1 Definition einer Action mit ActionHandler#
Modul: restalchemy.api.actions
from restalchemy.api import actions, constants
from restalchemy.api import controllers, resources
class FileModel(models.ModelWithUUID):
name = properties.property(types.String(), required=True)
class FilesController(controllers.Controller):
__resource__ = resources.ResourceByRAModel(
model_class=FileModel,
process_filters=True,
convert_underscore=False,
)
@actions.get
def download(self, resource, **kwargs):
# resource repräsentiert hier z. B. eine Datei-Beschreibung
data = resource.download_file()
headers = {
"Content-Type": constants.CONTENT_TYPE_OCTET_STREAM,
"Content-Disposition": f'attachment; filename="{resource.name}"',
}
return data, 200, headers
@actions.getwrappt die Methode in einenActionHandler.- Der Controller erhält
selfalscontrollerundresourceals aktuelle DM-Instanz. - Über
controller.process_result()(indirekt viaActionHandler.do_*) wird die Response erzeugt.
2.2 Action-Route definieren#
from restalchemy.api import routes
class FileDownloadAction(routes.Action):
"""Handler für /v1/files/<id>/actions/download."""
__controller__ = FilesController
Anschließend wird die Action an die Ressource gehängt:
class FilesRoute(routes.Route):
"""Handler für /v1/files/<id> Endpunkt."""
__controller__ = FilesController
__allow_methods__ = [
routes.CREATE,
routes.DELETE,
routes.FILTER,
routes.GET,
routes.UPDATE,
]
download = routes.action(FileDownloadAction, invoke=True)
routes.action(FileDownloadAction, invoke=True)bedeutet:- Die Action ist unter
/v1/files/<id>/actions/download/invokeerreichbar. - HTTP-Methoden
GET/POST/PUTwerden aufdo_get/do_post/do_putimActionHandlergemappt.
2.3 Request-/Response-Flow für Actions#
Für eine URL wie /v1/files/<id>/actions/download/invoke:
Route.do()erkennt das Segmentactionsim Pfad.- Die Ressource wird über
<id>mit Hilfe des Controllers geladen. - Die Action (
download) wird ermittelt. FileDownloadActionwird mit dem Request instanziiert.Action.do(resource=resource, **kwargs)wird aufgerufen.ActionnutztActionHandler, um die passendedo_*-Methode auszuführen.
3. Kombination von verschachtelten Ressourcen und Actions#
Actions lassen sich auch auf verschachtelten Ressourcen verwenden, z. B.:
/v1/foos/<foo_id>/bars/<bar_id>/actions/archive.
In diesem Fall:
- beschreibt der Routenbaum verschachtelte Routen bis hin zur
BarRoute; - eine
Actionunterhalb vonBarRoutearbeitet auf der verschachteltenBarModel-Instanz.
Das Muster ist identisch zu Top-Level-Ressourcen; lediglich der Pfad wird länger.
Zusammenfassung#
- Verschachtelte Ressourcen werden über verschachtelte
Route-Klassen undresource_route=Trueausgedrückt. - Controller können
parent_resource(oderBaseNestedResourceController) nutzen, um Logik relativ zu einer Elterninstanz zu implementieren. - Actions werden über
ActionHandler-Dekoratoren undroutes.Action/routes.actionimplementiert und erlauben eine saubere Modellierung von Nicht-CRUD-Operationen auf Ressourcen.