This module, based on the SQLAlchemy Declarative extension, allows keywords to be configured as class attributes on custom classes.
These keywords can then be used as a class attribute which returns the keyword value, and when set, will trigger a keyword modify. Keyword
descriptors can also set event listeners for various Keyword methods, such as
An example declarative class using keywords is below:
>>> from Cauldron.ext.declarative import KeywordDescriptor, DescriptorBase >>> class Thing(DescriptorBase): ... ... enabled = KeywordDescriptor("THINGPOWER") ... ... @enabled.write ... def adjust_power(self, keyword, value): ... if bool(value): ... # TURN ON THE THING ... pass ... else: ... # TURN OFF THE THING ... pass ... ... @enabled.check ... def check_power(self, keyword, value): ... bool(value)
To use this declarative class, you must bind the class to a KTL Service object:
>>> from Cauldron import use >>> use("local") >>> from Cauldron import DFW >>> svc = DFW.Service("THINGYSERVICE", config=None) >>> Thing.bind(svc) >>> athingy = Thing() >>> athingy.enabled = True >>> athingy.enabled 'True' >>> anotherthingy = Thing() >>> anotherthingy.enabled 'True' >>> athingy.enabled = False >>> anotherthingy.enabled 'False'
The declarative interface provides events for several dispatcher keyword methods. These events recieve the same arguments as their API counterparts, with the keyword instance itself passed as the first argument.
|Event Name||Keyword Method|
Events are available by name on keyword descriptors. The events are callables which can be used as a decorator. They also have a
method which can be used to explicitly declare that a keyword listens for a function.
Implement the declarative descriptor.
||A descriptor which maintains a relationship with a keyword.|
||A keyword descriptor base class which assists in binding descriptors to keywords.|
||Error raised when a service is not bound to a descriptor.|
||Error raised when a service is already bound to a descriptor.|
||Raised to indicate an instance has a differing initial value from the one in the keyword store.|
Class Inheritance Diagram¶
The Declarative extension deserves some additional explanation, as some of the design decisions may be opaque to the user or casual developer.
First, some notes about descriptors. Descriptors are special python objects which implement a
__get__ method and optionally a
__set__ method. Importantly, instances of descriptors become class members of their parent class. This is required to ensure that attribute access is properly handled by the descriptor’s
__set__ methods. As such, each descriptor instance cannot be associated with an individual parent instance. Rather, it must dynamically alter attributes depending on the parent instance that it receives to
This is why descriptors know about their keyword name and value, but must be bound to a service and instance to connect events.
Binding is the process by which we associate descriptors with instances of their parent class, and with specific KTL services. Binding is necessary because the descriptor must know the service it should call, but it must also know how to call instance methods on the parent instance. The first half of this problem is solved by service binding, and can happen at the class level. The second part of this problem is solved by instance binding, and must happen at the instance level.
Service binding associates a KTL service with an instance of a descriptor. This can be done either using the instance attribute on the descriptor,
KeywordDescriptor.service, or the class method on the parent class,
DescriptorBase.bind(). When called on the parent class,
DescriptorBase.bind() simply sets
KeywordDescriptor.service for each
KeywordDescriptor instance associated with that base class.
Instance binding associates a particular instance of a parent class (an instance of
DescriptorBase) with the descriptors on that class. Each instance of the parent class can be bound to the descriptors, and the binding can happen multiple times with no side effects. Binding allows the descriptor instance, which was created as a class variable on the parent class, to call bound methods of each instance. In the example at the beginning of this document, the method
adjust_power is marked as listening for
enabled.write. Instance binding allows the keyword
enabled to call
adjust_power as a bound method, correctly filling in the
self argument (along with the
value arguments.) with an instance of the class
The concept of “binding” above explains how binding and descriptors work from the user’s point of view. Internally, there is a little more complexity. This complexity allows events to trigger on theoretically any
Keyword instance method, even if the Keyword class doesn’t know about this extension.
This process works through three layers of “Event” classes. The top layer,
_DescriptorEvent, is what implements the decorator interface (
@-syntax) in the example at the top of this document. The decorator interface registers each decorated function as a callback. Because most decorated functions are decorated at the time they are declared, they are decorated as regular functions (not even unbound methods). This means that the
_DescriptorEvent cannot track which methods should be called while bound to the instance, and which ones should be called as regular functions. The
_DescriptorEvent class is responsible for propagating event callbacks, but maintains no association with a specific parent instance.
The next layer is handled by the
_KeywordEvent class. This class is used to wrap methods on
Keyword instances. A single
_KeywordEvent instance is used to wrap each instance method on
Keyword. It intercepts method calls on
_KeywordEvent instances maintain a list of listeners. Listeners (
_KeywordListener) associate an instance of the parent class, an instance of the descriptor, and an instance of the keyword together. They are weakly referenced, so that if any one of the constituent components (descriptor, parent instance, or keyword) is garbage collected, the listener will be removed.
||Manage events attached to a keyword descriptor.|
||Instrumentation to apply an event to a keyword.|
||A listener to help fire events on a keyword object.|
Class Inheritance Diagram¶