Complex Resources

Sometimes we want our resources to contain sub-resources or sub-services (the difference is that sub-resources have ResourceData models in the DB and services does not). This can easily be achieved with Rotest.

Creating sub-resource model

In case we want to create a sub-resource, to Calcuator for example, we first need to point to it in the CalculatorData model.

(Skip this part if you want a sub-service, i.e. you don’t need to hold data on the sub-resource in the server’s DB, like when all its data is derived from the containing resource’s data)

from django.db import models
from import ResourceData

class SubCalculatorData(ResourceData):
    class Meta:
        app_label = "resources"

    process_id = models.IntegerField()

class CalculatorData(ResourceData):
    class Meta:
        app_label = "resources"

    ip_address = models.IPAddressField()
    sub_process = models.ForeignKey(SubCalculatorData)

In this example we created the ResourceData model for the sub-resource (like we’d do to any new resource), and pointed to it in the original CalculatorData model, declaring we intend to use a sub-resource here.

Don’t forget to add a reference to the model and the new field in

from import register_resource_to_admin

from . import models

register_resource_to_admin(models.SubCalculatorData, attr_list=['process_id'])
register_resource_to_admin(models.CalculatorData, attr_list=['ip_address'],

Note that we used the link_list to point to the sub-resource and not attr-list, since its a model and not a regular field.

Don’t forget to run makemigrations and migrate again after changing the models!

Declaring sub-resources

Let’s continue to modify the Calculator resource, where we want to add sub-resources.

For now, let’s assume we already wrote the sub-resource under resources/

Now, edit the file resources/

import rpyc
from import BaseResource

from .models import CalculatorData
from .sub_process import SubProcess

class Calculator(BaseResource):
    DATA_CLASS = CalculatorData

    PORT = 1357

    sub_process = SubProcess.request(data=CalculatorData.sub_process)

    def connect(self):
        super(Calculator, self).connect()
        self._rpyc = rpyc.classic.connect(, self.PORT)

    def finalize(self):
        super(Calculator, self).finalize()
        if self._rpyc is not None:
            self._rpyc = None

    def calculate(self, expression):
        return self._rpyc.eval(expression)

    def get_sub_process_id(self, expression):

Note the following:

  • Declaring the sub-resource:

    sub_process = SubProcess.request(data=CalculatorData.sub_process)

    The syntax is the same as requesting resources for a test.

    We assigned the SubCalculatorData model instance (pointed from the containing resource’s CalculatorData) as the data for out sub-resource.

    Alternatively, in case SubProcess was a service and not a full-fledged resource, we could have passed parameters to it in a similar way:

    sub_process = SubProcess.request(ip_address=CalculatorData.ip_address,
  • The usage of the sub-resource

    def get_sub_process_id(self, expression):
        return self.sub_process.process_id

    Once the sub-resource or service is declared, it can be accessed from any of the containing resource’s methods, using the assigned name (in this case, the declaration line name it sub_process).

Lastly, let’s show the sub-resource under resources/

from import BaseResource

from .models import SubCalculatorData

class SubProcess(BaseResource):
    DATA_CLASS = SubCalculatorData

    def container_calculate(self, expression):
        return self.parent.calculate(expression)

    def get_ip_address(self):

Note that we have access to the containing resource via parent.

This also applies when we write sub-services, which can use the parent’s methods, data, and even fields (e.g. self.parent._rpyc).

When writing sub-resources and services, remember two things:

  • Always call super when overriding BaseResource’s methods (connect, initialize, validate, finalize, store_state), since the basic method propagate the call to sub-resources.
  • It is ok to use self.parent and self.<sub-resource-name> , but mind the context. E.g. self.parent._rpyc in the above example is accessible from the sub-resource, but only after the connect() method (since firstly the sub-resource connects, and only afterwards the containing resource connects). The same applies for the other basic methods (first the sub-resources initialize, then the containing).