Relationships and Links¶

Any good api is going to need references to on resources to other references. For example, a parent and child resource would likely need hypermedia references to the other. The _relationships and _links class attributes on the ResourceBase subclass. These attributes inform the resource instances how to construct related and linked resources.

Example¶

from ripozo import apimethod, Relationship, ResourceBase, RequestContainer


class MyResource(ResourceBase):
    _relationships = (
        Relationship('related', relation='RelatedResource'),
    )

    @apimethod(methods=['GET'])
    def retrieve(cls, request, *args, **kwargs):
        properties = request.body_args
        return cls(properties=properties)


class RelatedResource(ResourceBase):
    pks = ['id']

If we were to call the retrieve method now and inspect the properties on the returned instance we would see that it no longer contained the ‘related’ key or it’s corresponding value.

>>> request = RequestContainer(body_args={'name': 'Yay!', 'related': {'id': 1}})
>>> res = MyResource.retrieve(request)
>>> res.properties
{'name': 'Yay!'}
>>> resource_tuple = res.related_resources[0]
>>> print(resource_tuple.name)
related
>>> print(resource_tuple.resource.url)
/related_resource/1
>>> print(resource_tuple.resource.properties)
{'id': 1}

Instead the relationship ‘related_resource’ popped the value with the key ‘related’ from the properties dict and passed the properties to the instantiatior for the RelatedResource. The RelatedResource now exists in the res.relationships list. Adapters can properly retrieve urls using the resource object and format them appropriately.

Links vs Relationships¶

The links and the relationships attributes both use the Relationship and ListRelationship. In fact in many aspects they are extraordinarily similiar. They both construct resources. The main difference is in how they are constructed (links use the resource.meta['links'] dictionary and relationships directly access the properties) and their fundamental meaning. A relationship is effectively a part of the resource itself. For example, a child and parent relationship. The child is directly part of the parent. A link would be closer to a sibling. They may be a ‘next’ link which points to the next sibling from a child resource. However, there is no next attribute directly on the child. A common use case for links is describing next and previous links on paginated lists. The resource is the list and the next and previous is not actually an attribute of the resource. Instead it is meta information about the resource.

Cookbook¶

A link with query args¶

Sometimes, you want a link to have query args in the url. A prime example of this is a next link when paginating. You don’t want to change the path but the query args should be adjusted. In this case, we can simply pass the a list of arguments that should be query args.

class MyResource(ResourceBase):
    _links = (
        Relationship('next', relation='MyResource', query_args=['page', 'count']),
    )

    @apimethod()
    def my_endpoint(cls, request):
        page = request.get('page', 1)
        count = request.get('count', 10)
        # get your resource list
        next = dict(page=page+1, count=count)
        meta=dict(links=dict(next=next))
        return cls(meta=meta)

Linked resources are created from the meta[‘links’] attribute on a resource. This means that we’ve given the next link the properties from the meta[‘links’][‘next’] dictionary. Additionally, we’ve passed in ['page', 'count'] into the created linked resource as query_args parameter in the constructor. In other words, the next linked resource would be constructed as follows: MyResource(properties=dict(page=page+1, count=count), query_args=['page', 'count'])

>>> from ripozo import RequestContainer
>>> req = RequestContainer(query_args=dict(page=1, count=20))
>>> resource = MyResource.my_endpoint(req)
>>> print(resource.url)
/my_resource
>>> next_link = resource.linked_resources[0].resource
>>> print(next_link.url)
/my_resource?count=20&page=2

Emedding resources¶

In this scenario, you want related resources to be fully embedded in the requested resource. Although what the representation depends on the adapter used, it would look something like this:

{
    "parent": {
        "href": "http://myapi.io/parent_resource/1",
        "value": 1,
        "children": [
            {
                "href": "http://myapi.io/child_resource/5",
                "parent": "http://myapi.io/parent_resource/1",
                "val": 2
            },
            {
                "href": "http://myapi.io/child_resource/6",
                "parent": "http://myapi.io/parent_resource/1",
                "val": 2
            },
            {
                "href": "http://myapi.io/child_resource/7",
                "parent": "http://myapi.io/parent_resource/1",
                "val": 2
            }
        ]
    }
}

Assuming that we somehow constructed a dictionary representing the parent dictionary above (for example from your manager) creating an embedded resource(s) is very easy.

class ParentResource(ResourceBase):
    pks = ('id',)
    _relationships = (
        ListRelationship('children', relation='ChildResource', embedded=True),
    )

    @apimethod()
    def retrieve(cls, request):
        children = [dict(id=5, parent_id=1, val=2), dict(id=6, parent_id=1, val=2), dict(id=7, parent_id=1, val=2)]
        parent_props = dict(id=1, value=1, children=children)
        return cls(properties=parent_props)

class ChildResource(ResourceBase):
    pks = ('id',)
    _relationships = (
        Relationship('parent', property_map=dict(parent_id='id'), relation='ParentResource'),
    )

In this case, we have embedded children relationship each of which has a relationship pointing back to the parent. The key is the embedded=True which tells the adapter to include all of the properties. If embedded was not equal to true we would only get the links to the resources in the response.

>>> from ripozo import RequestContainer
>>> req = RequestContainer()
>>> parent = ParentResource.retrieve(req)
>>> children = parent.related_resources[0].resource
>>> print(parent.related_resources[0].embedded)
True
>>> print(parent.url)
/parent_resource/1
>>> for child in children:
...     print(child.url)
/child_resource/5
/child_resource/6
/child_resource/7
>>> child = children[0]
>>> childs_parent = child.related_resources[0].resource
>>> print(childs_parent.url)
/parent_resource/1
>>> assert childs_parent.url == parent.url

Relationships API¶

class ripozo.resources.relationships.relationship.Relationship(name, property_map=None, relation=None, embedded=False, required=False, no_pks=False, query_args=None, templated=False, remove_properties=True)[source]¶

Defines a relationship on a resource. This allows you to create related resources and construct them appropriately. As usual, the actual response is created by an adapter which must determine whether to return the whole resource, a link, etc…

__init__(name, property_map=None, relation=None, embedded=False, required=False, no_pks=False, query_args=None, templated=False, remove_properties=True)[source]¶
Parameters:
  • name (unicode) –
  • property_map (dict) – A map of the parent’s property name to the corresponding related fields properties. For example, it may be called “child” on the parent but it corresponds to the id field on the related field.
  • relation (unicode) – The name of the resource class that this relation is a type of. It uses a string so that you do not have to worry about the order of how relations were defined. It looks up the actual type from the ResourceMetaClass.
  • embedded (bool) – Indicates whether the related resource should be embedded in the parent resource when returned. Otherwise, a more basic representation will be used (e.g. a link or id)
  • required (bool) – An indicator for whether the relation must be constructed.
  • no_pks (bool) – A flag that indicates that the resources created do not need pks (for example a next link in RetrieveList mixin)
  • query_args (list[unicode]|tuple[unicode]) – A list of strings that should be passed to the query_args parameter for resource construction.
  • templated (bool) – If templated is True, then the resource does not need to have all pks. However, embedded is negated if templated = True to prevent infinite loops.
  • remove_properties (bool) – If True, then the properties in the child relationship will be removed. Otherwise, the properties will simply be copied to the relationship
__weakref__¶

list of weak references to the object (if defined)

construct_resource(properties)[source]¶

Takes the properties from the parent and and maps them to the named properties for the parent resource to its relationships

Parameters:properties (dict) –
Returns:An instance of a self.relation class that corresponds to this related resource
Return type:rest.viewsets.resource_base.ResourceBase
relation¶

The ResourceBase subclass that describes the related object If no _relation property is available on the instance it returns None. Raises a key error when the relation keyword argument passed on construction is not available in the self._resource_meta_class.registered_names_map dictionary (By default the self._resource_meta_class is the ResourceMetaClass).

Returns:The ResourceBase subclass that describes the related resource
Return type:type
Raises:KeyError
remove_child_resource_properties(properties)[source]¶

Removes the properties that are supposed to be on the child resource and not on the parent resource. It copies the properties argument before it removes the copied values. It does not have side effects in other words.

Parameters:properties (dict) – The properties that are in the related resource map that should not be in the parent resource.
Returns:a dictionary of the updated properties
Return type:dict
class ripozo.resources.relationships.list_relationship.ListRelationship(name, property_map=None, relation=None, embedded=False, required=False, no_pks=False, query_args=None, templated=False, remove_properties=True)[source]¶

Special case for a list of relationships.

construct_resource(properties)[source]¶

Takes a list of properties and returns a generator that yields Resource instances. These related ResourceBase subclass will be asked to construct an instance with the keyword argument properties equal to each item in the list of properties provided to this function.

Parameters:properties (dict) – A dictionary of the properties on the parent model. The list_name provided in the construction of an instance of this class is used to find the list that will be iterated over to generate the resources.
Returns:A generator that yields the relationships.
Return type:types.GeneratorType
class ripozo.resources.relationships.relationship.FilteredRelationship(*args, **kwargs)[source]¶

A relationship class that helps to easily create relationships that point to a filtered relationship.

For example, suppose we had the following resources

from ripozo import restmixins

class Parent(ResourceBase):
    resource_name = 'parent'
    pks = 'id',

class Child(restmixins.RetrieveList):
    resource_name = 'child'
    pks = 'id',

Assuming that a parent can have many children and that a child has a property called parent_id, we want a link to get all of the children, but we don’t want to embed the links to all of the individual children. We simply want a link with ‘/child?parent_id=<id>’. This can be done by doing:

class Parent(ResourceBase):
    _relationships = Relationship('children', relation='Child',
                                  property_map=dict(id='parent_id'),
                                  query_args=['parent_id'], no_pks = True,
                                  remove_properties=False)

However, that is a lot of set up. With this class you would simply do:

class Parent(ResourceBase):
    _relationships = FilteredRelationship('children', relation='Child',
                                          property_map=dict(id='parent_id'))
__init__(*args, **kwargs)[source]¶

Sets the query_args to the values of the property_map remove_properties=False, and no_pks=True then calls super

ripozo

Navigation

  • Topics
    • Adapters
    • Dispatchers
    • Fields, Translation, and Validation
    • API Documentation
    • Managers
    • Preprocessors and Postprocessors
    • Relationships and Links
    • Resources
    • Rest Mixins
    • Utitilities API
  • Examples
  • Philosophy and Architecture
  • Changelog

Related Topics

  • Documentation overview
    • Topics
      • Previous: Preprocessors and Postprocessors
      • Next: Resources

Quick search

©2019, Tim Martin. | Powered by Sphinx 1.7.6 & Alabaster 0.7.12 | Page source