Package: xnodes
XNodes (Exchanging nodes)WORK IN PROGRESS, CURRENTLY NOT FUNCTIONALAboutXNodes is an application framework which provides an event mechanism for unrelated nodes by utilizing a global event bus. The framework's initial purpose was to establish a lightweigth mechanism for exchanging information between UI widgets and a data model, all without necessitating their mutual awareness. Upon receiving an event which alters the state of a node, the receiving node can provide an event in return which undos the made changes. This way an easy to use but powerful undo/redo mechanism is realized.How to useApplication startBefore any node is constructed, all events with their parameters which are used within the system have to be registers in the event bus calledXNodeBus.XNodeBusconsists of staticmethods only, so instantiating it is not necessary. When adding an event to the bus, one can provide an optional log level for that event which will be used during logging when that event is published, default log level isINFO.import logging

from xnodes import XNodeBus

EVENT_A = "EVENT_A"
EVENT_B = "EVENT_B"

XNodeBus.add_event(EVENT_A, (int, str))
XNodeBus.add_event(EVENT_B, (bool, list), logging.DEBUG)If a node wants to publish an event, the types of the parameters have to match the types provided during registration. If the parameter types do not match, aValueErroris raised during publication.Node initializationTo create a node, a class has to inherit from theXNodeclass. Every node has a type and when a class inherits fromXNode, the type has to be provided toXNode. Purpose of this type is easier identification of nodes and the possibility of other nodes to publish events to all nodes of a certain type.from xnodes import XNode

MY_NODE_TYPE = "MY_NODE"

class MyNode(XNode):
    
    def __init__():
        XNode.__init__(self, MY_NODE_TYPE)

instance1 = MyNode()  # ID: MY_NODE
instance2 = MyNode()  # ID: MY_NODE_1
instance3 = MyNode()  # ID: MY_NODE_2The created instances of MyNode both have unique IDs within the node type MY_NODE. Note that the first instance will receive an ID equal to the node type, but the second ID has a suffix. Background for this is that if only one node instance of a type exists in the application, it can be directly referred to by its type. This makes it easier to address services in the application.Receiving eventsIn order to receive an event, a node has to implement an event handler function and decorate it with thexeventdecorator function.from xnodes import XNode, xevent

MY_NODE_TYPE = "MY_NODE"
EVENT_A = "EVENT_A"
EVENT_B = "EVENT_B"

class MyNode(XNode):
    
    def __init__():
        XNode.__init__(self, MY_NODE_TYPE)
    
    @xevent(EVENT_A)
    def handle_event(self, parameter_1: int, parameter_2: str):
        pass  # Do something with the event.
    
    @xevent(EVENT_B)
    def handle_event(self, parameter_1: bool, parameter_2: list):
        pass  # Do something with the event.The decoratorxeventtakes as argument the identifier of the event which the decorated function is supposed to handle. Note that the names of both of the event handler functions above are the same, which is not possible in standard Python. This is achieved by a custom metaclass ofXNode, which stores all functions which are decorated withxeventin a separate dictionary and identifies the function by the event identifier and not by its name. In fact, the name of an event handler function is completely irrelevant and one is free to name them all the same or give them different names.Publishing eventsXNode provides two functions to publish events. The first is thepublishfunction, which delivers one single event to one single node which is specified by its ID. The second is thebroadcastfunction which delivers an event to every node which subscribed to the published event.from xnodes import XNode

MY_NODE_TYPE = "MY_NODE"
EVENT_A = "EVENT_A"
EVENT_B = "EVENT_B"

class MyNode(XNode):
    
    def __init__():
        XNode.__init__(self, MY_NODE_TYPE)
    
    def publish_event_a(self, target_node_id: str, parameter_1: int, parameter_2: str):
        self.publish(EVENT_A, target_node_id, parameter_1, parameter_2)
    
    def broadcast_event_b(self, parameter_1: bool, parameter_2: list):
        self.broadcast(EVENT_B, parameter_1, parameter_2)Conflicting metaclassesIf you want to convert a class to an XNode, but also need the class to derive from another class which also has a custom metaclass, one has to combine the metaclasses of both into a common metaclass. In order to do this, xnodes provdides a function for this calledcreate_xnode:import ConflictingMetaclassType

from xnodes import create_xnode

MY_NODE_TYPE = "MY_NODE"

class MyNode(create_xnode(ConflictingMetaclassType, MY_NODE_TYPE)):
    
    def __init__():
        super().__init__(parameters_of_conflicting_metaclass_type)Instead of deriving from ConflictingMetaclassType and XNode in the super class list, one can callcreate_xnodeinside the super class list and pass the conflicting class together with the xnode type to combine the metaclasses. In the init of the new class, one now only has to provide the super class init arguments of the conflicting metaclass type.
One example of a conflicting metaclass type are any widgets of the Python Qt bindings. If the goal is to implement a custom QLineEdit and send an XEvent whenever the text changes for example, one has to use the create_xnode function to combine QLineEdit with XNode to resolve this issue.