Declarative Keywords¶
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 check()
and preread()
.
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'
Events¶
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 |
---|---|
callback | _propagate() (as if callback() was used.) |
preread | preread() |
read | read() |
postread | postread() |
prewrite | prewrite() |
write | write() |
postwrite | postwrite() |
check | check() |
Events are available by name on keyword descriptors. The events are callables which can be used as a decorator. They also have a listen()
method which can be used to explicitly declare that a keyword listens for a function.
API/Reference¶
Cauldron.ext.declarative.descriptor Module¶
Implement the declarative descriptor.
Classes¶
KeywordDescriptor (name[, initial, type, ...]) |
A descriptor which maintains a relationship with a keyword. |
DescriptorBase (*args, **kwargs) |
A keyword descriptor base class which assists in binding descriptors to keywords. |
ServiceNotBound |
Error raised when a service is not bound to a descriptor. |
ServiceAlreadyBound |
Error raised when a service is already bound to a descriptor. |
IntegrityError |
Raised to indicate an instance has a differing initial value from the one in the keyword store. |
Class Inheritance Diagram¶
Internals¶
The Declarative extension deserves some additional explanation, as some of the design decisions may be opaque to the user or casual developer.
Descriptors¶
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 __get__
and __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 __get__
or __set__
.
This is why descriptors know about their keyword name and value, but must be bound to a service and instance to connect events.
Binding¶
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¶
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¶
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 keyword
and value
arguments.) with an instance of the class Thing
.
Events¶
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 Keyword
. _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.
Cauldron.ext.declarative.events Module¶
Classes¶
_DescriptorEvent (name[, replace_method]) |
Manage events attached to a keyword descriptor. |
_KeywordEvent (keyword, instance, event) |
Instrumentation to apply an event to a keyword. |
_KeywordListener (keyword, instance, event) |
A listener to help fire events on a keyword object. |
Class Inheritance Diagram¶