Nested resources and actions#
This guide explains how to:
- Implement nested resources (e.g.
/foos/<uuid>/bars/). - Implement actions on resources (e.g.
/v1/files/<id>/actions/download).
It builds on the basic CRUD example and uses ideas from examples/restapi_foo_bar_service.py and examples/openapi_app.py.
1. Nested resources#
Nested resources represent hierarchical relationships, such as:
- A parent resource:
FooModelat/v1/foos/<uuid>. - A nested collection:
BarModelat/v1/foos/<uuid>/bars/.
1.1. DM models#
Same as in the basic CRUD example:
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. Nested controller#
You can use BaseNestedResourceController to work with nested resources, or implement a simple controller manually.
Example with explicit parent handling (simplified):
class BarController1(controllers.Controller):
"""Handle /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):
# Add parent filter to restrict bars to this foo
return [
bar for bar in bar_storage.values() if bar.foo == parent_resource
]
Alternatively, BaseNestedResourceController provides built-in patterns for parent_resource handling.
1.3. Nested 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=Truetells RA thatBarRoute1works with nested resources under a specific parent instance.- For
/v1/foos/<foo_uuid>/bars/, RA resolves the parentFooModeland passes it asparent_resource.
2. Actions on resources#
Actions represent operations that are not pure CRUD on the primary resource. Typical examples:
/v1/files/<id>/actions/download— download a file./v1/objects/<id>/actions/some_business_operation.
See also action result serialization nuances:
2.1. Defining an action using ActionHandler#
Module: 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):
# Suppose resource represents a file description and you
# want to return binary content.
data = resource.download_file() # user-implemented method
headers = {
"Content-Type": constants.CONTENT_TYPE_OCTET_STREAM,
"Content-Disposition": f'attachment; filename="{resource.name}"',
}
return data, 200, headers
@actions.getwrapsdownloadinto anActionHandler.- The controller method receives
selfascontrollerandresourceas the current DM instance. - Use
controller.process_result()(viaActionHandler.do_*) to build a response.
2.2. Defining an Action route#
from restalchemy.api import routes
class FileDownloadAction(routes.Action):
"""Handler for /v1/files/<id>/actions/download endpoint."""
__controller__ = FilesController
You then attach this action to the resource route:
class FilesRoute(routes.Route):
"""Handler for /v1/files/<id> endpoint."""
__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)declares that:- The action is reachable via
/v1/files/<id>/actions/download/invoke. - HTTP methods
GET/POST/PUTmap todo_get/do_post/do_putinActionHandler.
2.3. Request/response flow for actions#
For a URL like /v1/files/<id>/actions/download/invoke:
Route.do()recognizes anactionspath segment.- It fetches the resource by
<id>using the controller. - It resolves the action (
download). - It instantiates
FileDownloadActionwith the request. - It calls
Action.do(resource=resource, **kwargs). ActionusesActionHandlerto call the rightdo_*method on the wrapped function.
3. Combining nested resources and actions#
Actions can be used on nested resources as well:
- For example,
/v1/foos/<foo_id>/bars/<bar_id>/actions/archive.
In that case:
- The route tree describes nested routes down to
BarRoute. - An
ActionunderBarRoutecan operate on the nestedBarModelinstance.
The pattern is the same as for top-level resources; only the routing path changes.
Summary#
- Nested resources are expressed via nested
Routeclasses andresource_route=True. - Controllers can use
parent_resource(orBaseNestedResourceController) to implement logic tied to a parent DM instance. - Actions are implemented via
ActionHandlerdecorators androutes.Action/routes.action, providing a clean way to express non-CRUD operations on resources.