Usable Changes

The first of the two Changes classes that can be encountered in a module’s execution is the UsableChanges class. The implementation of this class for the module being developed in this tutorial can be found here.

Purpose

The general purpose of all the Changes classes is to serve as a place for massaging data. The UsableChanges class is responsible for massaging data that is about to be sent to the API.

This class implements an Adapter pattern, similar to the Adapter patterns that were implemented by the ApiParameters and ModuleParameters classes. Because you have already worked with those classes, you should be more than familiar with what needs to happen in this class.

Additionally, because this class is nothing more than another Adapter, its implementation is completely optional. It will exist as a stub in your module by default. It is your responsibility to implement it as needed.

What sort of data do you need to adapt at this point in the module?

Consider the implementation that is found in the module that is being studied here.

Implementation

Let’s look at one of the adapted properties in this class. The other is largely similar in purpose and function, so we’ll skip it. You should, however, implement it for completeness in your copy of the module.

@property
def actions(self):
    if self._values['actions'] is None:
        return None
    result = []
    for action in self._values['actions']:
        if 'type' not in action:
            continue
        if action['type'] == 'forward':
            action['forward'] = True
            del action['type']
        elif action['type'] == 'enable':
            action['enable'] = True
            del action['type']
        elif action['type'] == 'ignore':
            result = []
            break
        result.append(action)
    return result

Remember that earlier it was mentioned that the purpose of these classes is to adapt data immediately before it hits the wire. This module made use of an actions property that was observed in the different Parameters classes, and even the Difference class.

For the module to have done its work, it needed to create an internal representation of the data to do things like comparison. It did this in the Parameters classes. Now that the comparison is done though, it needs to send those updates to the BIG-IP. The internal data format, though, is unlikely to be the same as the data format expected by BIG-IP.

Therefore, this adapter for the actions property is tasked with converting the internal representation of the data back into a format that is capable of being handled by the F5 device.

In the implementation here, you can see that key names are being changed to the ones that are known to the API. Additionally, data is being deleted from the existing dictionaries so that it is not accidentally sent to the API. Were it sent, the API would raise an exception and the module would fail.

Received values

The values that are received by the UsableChanges are those that were output by the Difference class. You can see this at work in the _update_changed_options method of the ModuleManager class.

For example:

if changed:
    self.changes = UsableChanges(params=changed)

Where changed is the dictionary produced by multiple calls to the Difference class’s compare method.

Conclusion

You’re now aware of this class’s place in the pipeline, and prior to that knowledge, you already had a firm understanding of the purpose of the various adapters in the module.

It’s not always necessary to implement this class. Indeed, in a good API, you will never need to be concerned with this class. Situations that warrant it usually involve complex data types that needed to be compared.

In the next section, we’ll look at the UsableChanges counter-class, the ReportableChanges.