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 rotest.management.models.resource_data 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 rotest.management.admin 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'], link_list=['sub_process'])
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
migrate again after changing the models!
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
Now, edit the file
import rpyc from rotest.management.base_resource 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.data.ip_address, self.PORT) def finalize(self): super(Calculator, self).finalize() if self._rpyc is not None: self._rpyc.close() self._rpyc = None def calculate(self, expression): return self._rpyc.eval(expression) def get_sub_process_id(self, expression): return self.sub_process.data.process_id
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
datafor 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, process_id=5)
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
from rotest.management.base_resource 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): return self.parent.data.ip_address
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).