bedrock.endpoints

This module contains the Endpoint class, which is the base class for all endpoints and any decorators that are applied to endpoints or their methods.

Example:

A simple endpoint at the root path

Let's make an endpoint for CRUDing a Country entity.

We'll call it Countries and it will be accessible at /countries/.

from bedrock.endpoints.endpoint import Endpoint
from bedrock.endpoints.decorators import protected, with_query_param, filter_on_columns
from model.country import Country

class Countries(Endpoint):
    def __init__(self):
        super().__init__("/countries/", related_model=Country)

    @with_query_param("sort_order", str)
    @with_query_param("sort_column", str)
    @with_query_param("offset", int)
    @with_query_param("limit", int)
    @filter_on_columns()
    def get_global(self, event, sort_order=None, sort_column=None, offset=None, limit=None, filters={}):
        return self.get_global_generic(event, Country, filters, order_by=sort_column, order=sort_order, offset=offset, limit=limit)

    def get_single(self, event):
        return self.get_single_generic(event, Country)

    @protected(scopes=["global:admin", "country:admin", "country:write"])
    def post_global(self, event):
        return self.post_global_generic(event, Country)

    @protected(scopes=["global:admin", "country:admin", "country:write"])
    def put_single(self, event):
        # Let's make sure the user doesn't try to change the uuid
        body = json.loads(event['body'])
        body["uuid"] = event["pathParameters"][self.param_key]
        return self.put_single_generic(event, Country, altered_body=body)

    @protected(scopes=["global:admin", "country:admin", "country:delete"])
    def delete_single(self, event):
        return self.delete_single_generic(event, Country, event["pathParameters"][self.param_key])

Now let's break down what's happening above:

  • Countries is a subclass of Endpoint, which is the base class for all endpoints.
  • Countries constructor defines how the endpoint is accessed. In this case, it's at /countries/ and it's related to the Country model.
    • The related_model parameter is used by the generic helpers but also to help generate the OpenAPI spec.
  • get_global is a method that will be called when a GET request is made to /countries/.
    • It's decorated with a few @with_query_param and @filter_on_columns to allow for sorting, pagination and filtering.
    • It calls the generic get_global_generic method, which will fetch all Country entities and apply the filters, sorting and pagination.
  • get_single is a method that will be called when a GET request is made to /countries/{uuid}.
    • It calls the generic get_single_generic method, which will fetch a single Country entity.
  • post_global is a method that will be called when a POST request is made to /countries/.
    • It's decorated by the @protected decorator, which will ensure that the user has sufficient access.
    • It calls the generic post_global_generic method, which will create a new Country entity.
  • put_single is a method that will be called when a PUT request is made to /countries/{uuid}.
    • It's decorated by the @protected decorator, which will ensure that the user has sufficient access.
    • It calls the generic put_single_generic method, which will update a single Country entity.
    • It also has some logic to ensure that the uuid in the body matches the one in the URL.
  • delete_single is a method that will be called when a DELETE request is made to /countries/{uuid}.
    • It's decorated by the @protected decorator, which will ensure that the user has sufficient access.
    • It calls the generic delete_single_generic method, which will delete a single Country entity.

A simple endpoint at a nested path

Let's make an endpoint for CRUDing a Region entity.

We'll call it Regions and it will be accessible at /countries/{countryUuid}/regions/.

from bedrock.endpoints.endpoint import Endpoint
from bedrock.endpoints.decorators import protected, with_query_param, filter_on_columns
from model.region import Region

class Regions(Endpoint):
    def __init__(self):
        super().__init__("/regions/", "/countries/{countryUuid}", related_model=Region)

    @with_query_param("sort_order", str)
    @with_query_param("sort_column", str)
    @with_query_param("offset", int)
    @with_query_param("limit", int)
    @filter_on_columns()
    def get_global(self, event, countryUuid, sort_order=None, sort_column=None, offset=None, limit=None, filters={}):
        # Make sure we have a countryUuid, this should be handled by API Gateway but just in case
        try:
            country_uuid = event["pathParameters"]["countryUuid"]
        except Exception as e:
            raise NotFoundException("country uuid not provided in url") from e

        # Then fetch all Regions and automatically add a filter on country_uuid
        return self.get_global_generic(event, Region, {
            **filters,
            add_default_equality_operator("country_uuid"): countryUuid
        }, order_by=sort_column, order=sort_order, offset=offset, limit=limit)

    def get_single(self, event, countryUuid):
        # Make sure we have a countryUuid, this should be handled by API Gateway but just in case
        try:
            country_uuid = event["pathParameters"]["countryUuid"]
        except Exception as e:
            raise NotFoundException("country uuid not provided in url") from e
        # Then fetch the single Region and automatically add a filter on country_uuid (so we get nothing if the countryUuid is wrong)
        return self.get_single_generic(event, Region, {
            add_default_equality_operator("country_uuid"): countryUuid
        })

    @protected(scopes=["global:admin", "region:admin", "region:write"])
    def post_global(self, event):
        # Lets make it easier for the user and automatically add the countryUuid to the body (while also replacing any value it could have)
        body = json.loads(event['body'])
        body["countryUuid"] = event["pathParameters"]["countryUuid"]
        # Then we can just call the generic post_global with the altered body
        return self.post_global_generic(event, Region, altered_body=body)

    @protected(scopes=["global:admin", "region:admin", "region:write"])
    def put_single(self, event):
        # Lets make sure the user doesn't try to change the countryUuid or this region's uuid
        body = json.loads(event['body'])
        body["countryUuid"] = event["pathParameters"]["countryUuid"]
        body["uuid"] = event["pathParameters"][self.param_key]
        # Then we can just call the generic put_single with the altered body (and the countryUuid filter)
        return self.put_single_generic(event, Region, altered_body=body, belongs_to={
            add_default_equality_operator("country_uuid"): countryUuid
        })

    @protected(scopes=["global:admin", "region:admin", "region:delete"])
    def delete_single(self, event):
        return self.delete_single_generic(event, Region, event["pathParameters"][self.param_key], belongs_to={
            add_default_equality_operator("country_uuid"): countryUuid
        })

Again, let's break down what's happening above:

  • Regions is a subclass of Endpoint, which is the base class for all endpoints.
  • Regions constructor defines how the endpoint is accessed. In this case, it's at /countries/{countryUuid}/regions/ and it's related to the Region model.
  • get_global is a method that will be called when a GET request is made to /countries/{countryUuid}/regions/.
    • It's decorated with a few @with_query_param and @filter_on_columns to allow for sorting, pagination and filtering.
    • It calls the generic get_global_generic method, which will fetch all Region entities and apply the filters, sorting and pagination.
    • It also adds a filter on country_uuid to ensure that we only get Region entities that belong to the Country entity with the provided countryUuid.
  • get_single is a method that will be called when a GET request is made to /countries/{countryUuid}/regions/{uuid}.
    • It calls the generic get_single_generic method, which will fetch a single Region entity.
    • It also adds a filter on country_uuid to ensure that we only get a Region entity that belongs to the Country entity with the provided countryUuid.
  • post_global is a method that will be called when a POST request is made to /countries/{countryUuid}/regions/.
    • It's decorated by the @protected decorator, which will ensure that the user has sufficient access.
    • It calls the generic post_global_generic method, which will create a new Region entity.
    • It also adds the countryUuid to the body to ensure that the new Region entity belongs to the Country entity with the provided countryUuid.
  • put_single is a method that will be called when a PUT request is made to /countries/{countryUuid}/regions/{uuid}.
    • It's decorated by the @protected decorator, which will ensure that the user has sufficient access.
    • It calls the generic put_single_generic method, which will update a single Region entity.
    • It also adds the countryUuid to the body to ensure that the updated Region entity belongs to the Country entity with the provided countryUuid.
    • It also has some logic to ensure that the uuid in the body matches the one in the URL.
  • delete_single is a method that will be called when a DELETE request is made to /countries/{countryUuid}/regions/{uuid}.
    • It's decorated by the @protected decorator, which will ensure that the user has sufficient access.
    • It calls the generic delete_single_generic method, which will delete a single Region entity.
    • It also adds a filter on country_uuid to ensure that we only delete a Region entity that belongs to the Country entity with the provided countryUuid.
  1"""
  2This module contains the [`Endpoint`](endpoints/endpoint.html#Endpoint) class, which is the base class for all endpoints and any [decorators](endpoints/decorators.html) that are applied to endpoints or their methods.
  3
  4# Example:
  5
  6## A simple endpoint at the root path
  7Let's make an endpoint for CRUDing a `Country` entity.
  8
  9We'll call it `Countries` and it will be accessible at `/countries/`.
 10
 11```python
 12from bedrock.endpoints.endpoint import Endpoint
 13from bedrock.endpoints.decorators import protected, with_query_param, filter_on_columns
 14from model.country import Country
 15
 16class Countries(Endpoint):
 17    def __init__(self):
 18        super().__init__("/countries/", related_model=Country)
 19
 20    @with_query_param("sort_order", str)
 21    @with_query_param("sort_column", str)
 22    @with_query_param("offset", int)
 23    @with_query_param("limit", int)
 24    @filter_on_columns()
 25    def get_global(self, event, sort_order=None, sort_column=None, offset=None, limit=None, filters={}):
 26        return self.get_global_generic(event, Country, filters, order_by=sort_column, order=sort_order, offset=offset, limit=limit)
 27
 28    def get_single(self, event):
 29        return self.get_single_generic(event, Country)
 30
 31    @protected(scopes=["global:admin", "country:admin", "country:write"])
 32    def post_global(self, event):
 33        return self.post_global_generic(event, Country)
 34
 35    @protected(scopes=["global:admin", "country:admin", "country:write"])
 36    def put_single(self, event):
 37        # Let's make sure the user doesn't try to change the uuid
 38        body = json.loads(event['body'])
 39        body["uuid"] = event["pathParameters"][self.param_key]
 40        return self.put_single_generic(event, Country, altered_body=body)
 41
 42    @protected(scopes=["global:admin", "country:admin", "country:delete"])
 43    def delete_single(self, event):
 44        return self.delete_single_generic(event, Country, event["pathParameters"][self.param_key])
 45```
 46
 47Now let's break down what's happening above:
 48* `Countries` is a subclass of `Endpoint`, which is the base class for all endpoints.
 49* `Countries` constructor defines how the endpoint is accessed. In this case, it's at `/countries/` and it's related to the `Country` model.
 50    * The `related_model` parameter is used by the generic helpers but also to help generate the OpenAPI spec.
 51* `get_global` is a method that will be called when a `GET` request is made to `/countries/`.
 52    * It's decorated with a few [`@with_query_param`](endpoints/decorators/with_query_param.html) and [`@filter_on_columns`](endpoints/decorators/filter_on_columns.html) to allow for sorting, pagination and filtering.
 53    * It calls the generic `get_global_generic` method, which will fetch all `Country` entities and apply the filters, sorting and pagination.
 54* `get_single` is a method that will be called when a `GET` request is made to `/countries/{uuid}`.
 55    * It calls the generic `get_single_generic` method, which will fetch a single `Country` entity.
 56* `post_global` is a method that will be called when a `POST` request is made to `/countries/`.
 57    * It's decorated by the [`@protected`](endpoints/decorators/protected.html) decorator, which will ensure that the user has sufficient access.
 58    * It calls the generic `post_global_generic` method, which will create a new `Country` entity.
 59* `put_single` is a method that will be called when a `PUT` request is made to `/countries/{uuid}`.
 60    * It's decorated by the [`@protected`](endpoints/decorators/protected.html) decorator, which will ensure that the user has sufficient access.
 61    * It calls the generic `put_single_generic` method, which will update a single `Country` entity.
 62    * It also has some logic to ensure that the `uuid` in the body matches the one in the URL.
 63* `delete_single` is a method that will be called when a `DELETE` request is made to `/countries/{uuid}`.
 64    * It's decorated by the [`@protected`](endpoints/decorators/protected.html) decorator, which will ensure that the user has sufficient access.
 65    * It calls the generic `delete_single_generic` method, which will delete a single `Country` entity.
 66
 67## A simple endpoint at a nested path
 68
 69Let's make an endpoint for CRUDing a `Region` entity.
 70
 71We'll call it `Regions` and it will be accessible at `/countries/{countryUuid}/regions/`.
 72
 73```python
 74from bedrock.endpoints.endpoint import Endpoint
 75from bedrock.endpoints.decorators import protected, with_query_param, filter_on_columns
 76from model.region import Region
 77
 78class Regions(Endpoint):
 79    def __init__(self):
 80        super().__init__("/regions/", "/countries/{countryUuid}", related_model=Region)
 81
 82    @with_query_param("sort_order", str)
 83    @with_query_param("sort_column", str)
 84    @with_query_param("offset", int)
 85    @with_query_param("limit", int)
 86    @filter_on_columns()
 87    def get_global(self, event, countryUuid, sort_order=None, sort_column=None, offset=None, limit=None, filters={}):
 88        # Make sure we have a countryUuid, this should be handled by API Gateway but just in case
 89        try:
 90            country_uuid = event["pathParameters"]["countryUuid"]
 91        except Exception as e:
 92            raise NotFoundException("country uuid not provided in url") from e
 93
 94        # Then fetch all Regions and automatically add a filter on country_uuid
 95        return self.get_global_generic(event, Region, {
 96            **filters,
 97            add_default_equality_operator("country_uuid"): countryUuid
 98        }, order_by=sort_column, order=sort_order, offset=offset, limit=limit)
 99
100    def get_single(self, event, countryUuid):
101        # Make sure we have a countryUuid, this should be handled by API Gateway but just in case
102        try:
103            country_uuid = event["pathParameters"]["countryUuid"]
104        except Exception as e:
105            raise NotFoundException("country uuid not provided in url") from e
106        # Then fetch the single Region and automatically add a filter on country_uuid (so we get nothing if the countryUuid is wrong)
107        return self.get_single_generic(event, Region, {
108            add_default_equality_operator("country_uuid"): countryUuid
109        })
110
111    @protected(scopes=["global:admin", "region:admin", "region:write"])
112    def post_global(self, event):
113        # Lets make it easier for the user and automatically add the countryUuid to the body (while also replacing any value it could have)
114        body = json.loads(event['body'])
115        body["countryUuid"] = event["pathParameters"]["countryUuid"]
116        # Then we can just call the generic post_global with the altered body
117        return self.post_global_generic(event, Region, altered_body=body)
118
119    @protected(scopes=["global:admin", "region:admin", "region:write"])
120    def put_single(self, event):
121        # Lets make sure the user doesn't try to change the countryUuid or this region's uuid
122        body = json.loads(event['body'])
123        body["countryUuid"] = event["pathParameters"]["countryUuid"]
124        body["uuid"] = event["pathParameters"][self.param_key]
125        # Then we can just call the generic put_single with the altered body (and the countryUuid filter)
126        return self.put_single_generic(event, Region, altered_body=body, belongs_to={
127            add_default_equality_operator("country_uuid"): countryUuid
128        })
129
130    @protected(scopes=["global:admin", "region:admin", "region:delete"])
131    def delete_single(self, event):
132        return self.delete_single_generic(event, Region, event["pathParameters"][self.param_key], belongs_to={
133            add_default_equality_operator("country_uuid"): countryUuid
134        })
135```
136
137Again, let's break down what's happening above:
138* `Regions` is a subclass of `Endpoint`, which is the base class for all endpoints.
139* `Regions` constructor defines how the endpoint is accessed. In this case, it's at `/countries/{countryUuid}/regions/` and it's related to the `Region` model.
140* `get_global` is a method that will be called when a `GET` request is made to `/countries/{countryUuid}/regions/`.
141    * It's decorated with a few [`@with_query_param`](endpoints/decorators/with_query_param.html) and [`@filter_on_columns`](endpoints/decorators/filter_on_columns.html) to allow for sorting, pagination and filtering.
142    * It calls the generic `get_global_generic` method, which will fetch all `Region` entities and apply the filters, sorting and pagination.
143    * It also adds a filter on `country_uuid` to ensure that we only get `Region` entities that belong to the `Country` entity with the provided `countryUuid`.
144* `get_single` is a method that will be called when a `GET` request is made to `/countries/{countryUuid}/regions/{uuid}`.
145    * It calls the generic `get_single_generic` method, which will fetch a single `Region` entity.
146    * It also adds a filter on `country_uuid` to ensure that we only get a `Region` entity that belongs to the `Country` entity with the provided `countryUuid`.
147* `post_global` is a method that will be called when a `POST` request is made to `/countries/{countryUuid}/regions/`.
148    * It's decorated by the [`@protected`](endpoints/decorators/protected.html) decorator, which will ensure that the user has sufficient access.
149    * It calls the generic `post_global_generic` method, which will create a new `Region` entity.
150    * It also adds the `countryUuid` to the body to ensure that the new `Region` entity belongs to the `Country` entity with the provided `countryUuid`.
151* `put_single` is a method that will be called when a `PUT` request is made to `/countries/{countryUuid}/regions/{uuid}`.
152    * It's decorated by the [`@protected`](endpoints/decorators/protected.html) decorator, which will ensure that the user has sufficient access.
153    * It calls the generic `put_single_generic` method, which will update a single `Region` entity.
154    * It also adds the `countryUuid` to the body to ensure that the updated `Region` entity belongs to the `Country` entity with the provided `countryUuid`.
155    * It also has some logic to ensure that the `uuid` in the body matches the one in the URL.
156* `delete_single` is a method that will be called when a `DELETE` request is made to `/countries/{countryUuid}/regions/{uuid}`.
157    * It's decorated by the [`@protected`](endpoints/decorators/protected.html) decorator, which will ensure that the user has sufficient access.
158    * It calls the generic `delete_single_generic` method, which will delete a single `Region` entity.
159    * It also adds a filter on `country_uuid` to ensure that we only delete a `Region` entity that belongs to the `Country` entity with the provided `countryUuid`.
160
161"""