Skip to content
Snippets Groups Projects
schema_injection.rst 5.01 KiB
Newer Older
Schema Injection
================
A schema injection is a modification of a device schema, to bring further
properties visible outside or to update attributes of existing properties
(such as the maximum size of a vector). Any number and types of properties can
be injected, whether parameters or slots.

In this example, we will define a slot that adds a boolean `injectedProperty`.

Begin by defining a slot that will call the `extend` function:

.. code-block:: Python

   from karabo.bound import BOOL_ELEMENT, PythonDevice, SLOT_ELEMENT

   class MyDevice(PythonDevice):

       @staticmethod
       def expectedParameters(expected):
           (
               SLOT_ELEMENT(expected).key('extend')
                   .displayedName('Extend')
                   .description('Extend the schema and introduce a new boolean property')
                   .commit(),
            )

       # Register the slot in the device schema
       def onInitialization(self):
           self.KARABO_SLOT(self.extend)


There are two methods to injecting properties: `appendSchema` and `updateSchema`,
both inherited from :class:`karabo.bound.PythonDevice`.

:func:`appendSchema` will add properties to the device, and can be called any
number of time.
:func:`updateSchema` will take the the original schema of the device, and add
the new one to the device. However, it will discard what has been previously
appended or updated!

Nonetheless, if :func:`appendSchema` is called after :func:`updateSchema`, then
the parameters from both injections are kept.

If :func:`updateSchema` is called with an empty schema, then the device will be
reset to its original schema, as defined in :func:`MyDevice.expectedParameters`.

This works, as internally, a device keeps three schemas: its `static` schema,
as defined in `expectedParameters`, the `injected` schema, which is modified on
injections, and the `full` schema, the combination of both which is exposed to
the rest of the ecosystem.

.. code-block:: Python

    from karabo.bound import BOOL_ELEMENT, DAQPolicy, Schema

    def extend(self):
        # Define a new Schema object
        schema = Schema()

        # Then populate that new Schema
        (
            BOOL_ELEMENT(schema).key('injectedProperty')
                .displayedName('Hello')
                .description('A fresh property')
                .daqPolicy(DAQPolicy.OMIT)
                .readOnly().initialValue(True)
                .commit()

        # Finally, append the schema to our existing device
        self.appendSchema(schema)  # Or self.updateSchema(schema)
        

Once a device has been modified, either of these log messages will be given::

    INFO  MyDevice  : Schema updated
    
    INFO  MyDevice  : Schema appended


Re-injecting a property, such as `injectedProperty`, will keep its current value
if possible. However, with different types, an error will be raised if the
value cannot be cast e.g. going from `UINT32_ELEMENT` to `INT16_ELEMENT` with a
value out of bound.

Check Whether A Property Exists
-------------------------------
If your device has an update loop, you can either use flags to check
whether schema injection has already been done, or use :func:`getFullSchema`:

.. code-block:: Python

   def update(self):
       if self.getFullSchema().has("injectedProperty"):
           self.set("injectedProperty", not self.get("injectedProperty"))


Injected Properties and DAQ
---------------------------
Injected Properties and the DAQ need some ground rules in order to record these
properties correctly.

In order for the DAQ to record injected properties, the DAQ needs to request the
updated schema again, using the Run Controller's :func:`applyConfiguration` slot.

This can be prone to operator errors, and therefore it is recommended that only
properties injected at instantiation to be recorded.

However, a common need for Schema updates is to specify the `maxSize` attribute
fo vector or table elements, as the DAQ only supports fixed length arrays, of
which the size has to be predefined.

For that, there is the special function :func:`karabo.bound.PythonDevice.appendSchemaMaxSize`
which, given a property path and the new length, will update the schema
accordingly.

Such a vector:

.. code-block:: Python

   from karabo.bound import NODE_ELEMENT, VECTOR_INT32_ELEMENT

   @staticmethod
   def expectedParameters(expected):
       (
           NODE_ELEMENT(expected).key('node').commit(),

           VECTOR_INT32_ELEMENT(expected').key('node.vector')
               .displayedName('Vector')
               .readOnly()
               .maxSize(5)
               .commit(),
       )


Can have its `maxSize` be redefined as follows:

.. code-block:: Python

   self.appendSchemaMaxSize('node.vector', 50)

If several updates are made, then it is recommended to set `emitFlag` to false for
all vectors apart of the last one. Only a single update will be then sent:

.. code-block:: Python

   self.appendSchemaMaxSize('node.vector0', 50, emitFlag=False)
   self.appendSchemaMaxSize('node.vector1', 50, emitFlag=False)
   self.appendSchemaMaxSize('node.vector2', 50)