def test_get_with_capabilities_bad_params(self): test_address_book = AddressBook() with self.assertRaises(ValueError): test_address_book.get_with_capabilities(required_capabilities=[ SimpleCapability("NotImportantForThisTest") ], match_threshold=-0.1) with self.assertRaises(ValueError): test_address_book.get_with_capabilities(required_capabilities=[ SimpleCapability("NotImportantForThisTest") ], match_threshold=1.1) with self.assertRaises(ValueError): test_address_book.get_with_capabilities(required_capabilities=[ SimpleCapability("NotImportantForThisTest") ], match_threshold=.5, n=0) with self.assertRaises(ValueError): test_address_book.get_with_capabilities(required_capabilities=[ SimpleCapability("NotImportantForThisTest") ], match_threshold=.5, n=-1) return
def __init__(self, pool_name: str): """ Check this or child class has correctly implemented callable __call__ as needed to handle both the PubSub listener events and the work timer events. """ self._address_book = AddressBook() super().__init__() self._call_lock = threading.Lock() self._handler = NotificationHandler(object_to_be_handler_for=self, throw_unhandled=False) self._handler.register_handler(self._get_task, WorkRequest) self._handler.register_handler(self._put_task, WorkNotificationDo) self._handler.register_handler(self._do_srcsink_ping, SrcSinkPing) self._handler.register_handler(self._do_srcsink_ping_notification, SrcSinkPingNotification) self._handler.register_handler(self._do_work_finalise, WorkNotificationFinalise) self._handler.register_activity( handler_for_activity=self._do_pub, activity_interval=TaskPool.PUB_TIMER, activity_name="{}-do_pub_activity".format(pool_name)) self._handler.register_activity( handler_for_activity=self._do_manage_presence, activity_interval=TaskPool.PRS_TIMER, activity_name="{}-do_manage_presence".format(pool_name)) self._capabilities = self._get_capabilities() self._unique_topic = self._create_topic_and_subscription() return
def __init__(self, ether_name: str): """ Register the notification handlers and install publication activity. Note: Make sure properties name & topic are defined in the sub class before super().__inti__ is called otherwise the activity timer will reference non existent properties in the sub class as the timer will fire (here) before they have been defined in the sub-class init() """ self._stopped = False self._address_book = AddressBook() super().__init__() self._call_lock = threading.Lock() self._handler = NotificationHandler(object_to_be_handler_for=self, throw_unhandled=False) self._handler.register_handler(self._do_srcsink_ping, SrcSinkPing) self._handler.register_handler(self._do_srcsink_ping_notification, SrcSinkPingNotification) return
def test_basics(self): test_srcsink = DummySrcSink("DummySrcSink-1") test_address_book = AddressBook() self.assertEqual(0, len(test_address_book.get())) test_address_book.update(test_srcsink) self.assertEqual(1, len(test_address_book.get())) self.assertTrue(test_srcsink in test_address_book.get()) return
def __init__(self, agent_name: str): """ Register all notification handlers & activities. """ self._address_book = AddressBook() self._lock = threading.RLock() self._subscribed_topics = list() super().__init__() self._work_timer = Agent.WORK_TIMER self._timer = None self._task_consumption_policy = None self._handler = NotificationHandler(object_to_be_handler_for=self, throw_unhandled=False) self._handler.register_handler(self._do_notification, TaskNotification) self._handler.register_handler(self._do_work, WorkNotificationDo) self._handler.register_handler(self._do_work_finalise, WorkNotificationFinalise) self._handler.register_handler(self._do_work_initiate, WorkNotificationInitiate) self._handler.register_handler(self._do_srcsink_ping, SrcSinkPing) self._handler.register_handler(self._do_srcsink_ping_notification, SrcSinkPingNotification) self._handler.register_activity( handler_for_activity=self._activity_check_work_to_do, activity_interval=self._work_timer, activity_name="{}-activity-work-to-do".format(agent_name)) self._handler.register_activity( handler_for_activity=self._activity_manage_presence, activity_interval=Agent.PRS_TIMER, activity_name="{}-activity-manage-presence".format(agent_name)) self._handler.register_activity( handler_for_activity=self._activity_initiate_work, activity_interval=Agent.WORK_INIT_TIMER, activity_name="{}-activity-work-init".format(agent_name)) self._unique_topic = self._create_topic_and_subscriptions() self._capabilities = self._get_capabilities() return
def __init__(self, name: str, capability: SimpleCapability = SimpleCapability( capability_name='DummySrcSink'), ping_topic: str = Ether.ETHER_BACK_PLANE_TOPIC): # SrcSink - Standard boot-strap & protected members # self._name = name self._topic = UniqueTopic().topic() self._address_book = AddressBook() super().__init__() self._capabilities = [capability] self._lock = threading.Lock() self._handler = NotificationHandler(object_to_be_handler_for=self, throw_unhandled=True) self._handler.register_handler(self._do_srcsink_ping, SrcSinkPing) self._handler.register_handler(self._do_srcsink_ping_notification, SrcSinkPingNotification) self._handler.register_handler(self._do_notification, TaskNotification) self._handler.register_handler(self._do_work, WorkNotificationDo) self._handler.register_handler(self._do_work_finalise, WorkNotificationFinalise) # Public Members just for Unit Test Asserts # self.pings = list() self.ping_notifications = list() self.task_notification = list() self.work_notification = list() self.work_finalise = list() self._ping_topic = ping_topic # The topic to issue ping's on # Get connected ! self.setup_subscriptions() return
def test_get_with_capabilities_single_ss(self): test_srcsink = DummySrcSink("DummySrcSink-1") test_address_book = AddressBook() self.assertEqual( None, test_address_book.get_with_capabilities(test_srcsink.capabilities)) test_address_book.update(test_srcsink) self.assertEqual( test_srcsink, test_address_book.get_with_capabilities( test_srcsink.capabilities)[0]) return
class Agent(SrcSink): WORK_TIMER = float(.25) WORK_TIMER_MAX = float(30) PRS_TIMER = float(.25) PRD_TIMER_MAX = float(60) WORK_INIT_TIMER = float(.25) WORK_INIT_TIMER_MAX = float(180) AGENT_TOPIC_PREFIX = "agent" def __init__(self, agent_name: str): """ Register all notification handlers & activities. """ self._address_book = AddressBook() self._lock = threading.RLock() self._subscribed_topics = list() super().__init__() self._work_timer = Agent.WORK_TIMER self._timer = None self._task_consumption_policy = None self._handler = NotificationHandler(object_to_be_handler_for=self, throw_unhandled=False) self._handler.register_handler(self._do_notification, TaskNotification) self._handler.register_handler(self._do_work, WorkNotificationDo) self._handler.register_handler(self._do_work_finalise, WorkNotificationFinalise) self._handler.register_handler(self._do_work_initiate, WorkNotificationInitiate) self._handler.register_handler(self._do_srcsink_ping, SrcSinkPing) self._handler.register_handler(self._do_srcsink_ping_notification, SrcSinkPingNotification) self._handler.register_activity( handler_for_activity=self._activity_check_work_to_do, activity_interval=self._work_timer, activity_name="{}-activity-work-to-do".format(agent_name)) self._handler.register_activity( handler_for_activity=self._activity_manage_presence, activity_interval=Agent.PRS_TIMER, activity_name="{}-activity-manage-presence".format(agent_name)) self._handler.register_activity( handler_for_activity=self._activity_initiate_work, activity_interval=Agent.WORK_INIT_TIMER, activity_name="{}-activity-work-init".format(agent_name)) self._unique_topic = self._create_topic_and_subscriptions() self._capabilities = self._get_capabilities() return def __del__(self): """ Shut down """ self._handler.activity_state(paused=True) with self._lock: sub_list = deepcopy(self._subscribed_topics) for topic in sub_list: pub.unsubscribe(self, topic) return def __call__(self, notification: Notification): """ Handle notification requests :param notification: The notification to be passed to the handler """ if isinstance(notification, Notification): self._handler.call_handler(notification) else: raise ValueError( "{} is an un supported notification type for Agent}".format( type(notification).__name__)) return def _create_topic_and_subscriptions(self) -> str: """ Create the unique topic for the agent that it will listen on for work (task) deliveries that it has requested from the task-pool """ topics = list() unique_topic = UniqueTopic().topic(Agent.AGENT_TOPIC_PREFIX) topics.append(unique_topic) for topic in self._work_topics(): topics.append(topic) self.add_subscription_topics(topics=topics) for topic in topics: # do potentially high latency subscriptions outside of the lock. pub.subscribe(self, topic) return unique_topic def add_subscription_topics(self, topics: List[str]) -> None: """ Add the given list of topics to the list of topics agent is subscribed to """ with self._lock: for topic in topics: if topic not in self._subscribed_topics: self._subscribed_topics.append(topic) return @staticmethod def _get_capabilities() -> List[Capability]: """ The capabilities of this Agent :return: List of Capabilities """ return [ SimpleCapability(capability_name=str(CapabilityRegister.AGENT)) ] @abstractmethod @purevirtual def _do_notification(self, task_notification: TaskNotification): """ callback to Notify agent of a task that needs attention. The agent can optionally grab the task from the task pool and work on it or ignore it. :param task_notification: The notification event for task requiring attention """ pass @abstractmethod @purevirtual def _do_work(self, work_notification: WorkNotificationDo) -> None: """ Process any out standing tasks associated with the agent. """ pass @abstractmethod @purevirtual def _do_work_initiate(self, work_notification: WorkNotificationInitiate) -> None: """ Handle the initiation the given work item from this agent """ pass @abstractmethod @purevirtual def _do_work_finalise( self, work_notification_finalise: WorkNotificationFinalise) -> None: """ Take receipt of the given completed work item that was initiated from this agent and do any final processing. """ pass @abstractmethod @purevirtual def work_initiate(self, work_notification: WorkNotificationDo) -> None: """ Initiate the given work item with the agent as the owner of the work. """ pass @abstractmethod @purevirtual def _activity_check_work_to_do(self, current_activity_interval: float) -> float: """ Are there any tasks associated with the Agent that need working on ? of so schedule them by calling work execute handler. :param current_activity_interval: The current delay in seconds before activity is re-triggered. :return: The new delay in seconds before the activity is re-triggered. """ pass @purevirtual @abstractmethod def _activity_manage_presence(self, current_activity_interval: float) -> float: """ Ensure that we are known on the ether & our address book has the name of at least one local pool in it. :param current_activity_interval: The current delay in seconds before activity is re-triggered. :return: The new delay in seconds before the activity is re-triggered. """ pass @purevirtual @abstractmethod def _activity_initiate_work(self, current_activity_interval: float) -> float: """ If the agent is a source (origin) of work then this activity will create and inject the new tasks. Zero or more tasks may be created depending on the specific task creation policy. :param current_activity_interval: The current delay in seconds before activity is re-triggered. :return: The new delay in seconds before the activity is re-triggered. """ pass @purevirtual @abstractmethod def _work_topics(self) -> List[str]: """ The list of topics to subscribe to based on the Work Topics (status transitions) supported by the agent. """ pass @property def task_consumption_policy(self) -> TaskConsumptionPolicy: """ Get the policy that the agent uses to decide to process (or not) the task based on tasks meta data :return: The consumption policy """ return self._task_consumption_policy @task_consumption_policy.setter def task_consumption_policy(self, p: TaskConsumptionPolicy) -> None: """ Set the policy that the agent uses to decide to process (or not) the task based on tasks meta data """ self._task_consumption_policy = p @abstractmethod @purevirtual def reset(self) -> None: """ Return the Actor to the same state at which it was constructed """ pass # ----- P R O P E R T I E S ----- @property @abstractmethod @purevirtual def name(self) -> str: """ The unique name of the Agent :return: The Agent name """ pass @property @abstractmethod @purevirtual def capacity(self) -> int: """ The current work capacity of the actor. :return: Current work capacity as int """ pass @property @abstractmethod @purevirtual def from_state(self) -> State: """ The state the actor expects to receive tasks in :return: from state """ pass @abstractmethod @purevirtual @property def to_state(self) -> State: """ The state the actor will process tasks into :return: to state """ pass @abstractmethod @purevirtual @property def failure_rate(self) -> float: """ The rate at which completed tasks fail. :return: Failure state of the actor """ pass @property def work_interval(self) -> float: """ The wait period between doing work. :return: Wait interval between work timer events in seconds """ return self._work_timer @property def capabilities(self) -> List[Capability]: """ The collection of capabilities of the SrcSink :return: The collection of capabilities """ return self._capabilities def get_addressbook(self) -> List[SrcSink]: """ The list of srcsinks known to the Ether :return: srcsinks """ return self._address_book.get() def _update_addressbook(self, srcsink: SrcSink) -> None: """ Update the given src_sink in the collection of registered srcsinks. If src_sink is not in the collection add it with a current time stamp. :param srcsink: The src_sink to update / add. """ self._address_book.update(srcsink) return
def test_get_with_capabilities_partial_matches(self): test_address_book = AddressBook() cap1 = SimpleCapability("Cap1") cap2 = SimpleCapability("Cap2") cap3 = SimpleCapability("Cap3") cap4 = SimpleCapability("Cap4") capx = SimpleCapability("Cap?") ss1 = DummySrcSink("DummySrcSink-1") ss1.capabilities = [cap1] ss2 = DummySrcSink("DummySrcSink-2") ss2.capabilities = [cap1, cap2] ss3 = DummySrcSink("DummySrcSink-3") ss3.capabilities = [cap1, cap2, cap3] ss4 = DummySrcSink("DummySrcSink-4") ss4.capabilities = [cap2, cap4] test_address_book.update(ss1) time.sleep(.01) test_address_book.update(ss2) time.sleep(.01) test_address_book.update(ss3) time.sleep(.01) test_address_book.update(ss4) scenarios = [ [1, [cap1, capx], 1.0, [None], 1], [2, [cap1, cap2, cap3], 1.0, [ss3], 1], [3, [cap1, cap2], 1.0, [ss3], 1], # As ss-3 also has Cap1, Cap2 and was created last [4, [capx], 0.0, [ss4], 1], # Zero => match any so will return last one created [5, [capx], 0.3142, [None], 1], [6, [capx], 1.0, [None], 1], [7, [cap1], 1.0, [ss3], 1], [8, [cap1, cap2], 1.0, [ss3], 1], [9, [cap1, cap2], 0.5, [ss4], 1], [10, [cap1], 1.0, [ss3, ss2], 2], [11, [cap1], 1.0, [ss3, ss2, ss1], 3], [12, [cap1], 1.0, [ss3, ss2, ss1], 4], [13, [cap1], 0.0, [ss4, ss3, ss2, ss1], 4], [14, [cap1], 1.0, [ss3, ss2, ss1], 5], [15, [cap1], 1.0, [ss3, ss2, ss1], 500], [16, [cap4, capx], 1.0, [None], 10], [17, [cap4, capx], 0.5, [ss4], 10], [18, [cap3, cap4, capx], 1.0, [None], 10], [19, [cap3, cap4, capx], 0.33, [ss4, ss3], 10] ] for scenario in scenarios: case_num, caps, threshold, expected, n = scenario logging.info( "test_get_with_capabilities_partial_matches case {}".format( case_num)) res = test_address_book.get_with_capabilities( required_capabilities=caps, match_threshold=threshold, n=n) if res is None: self.assertEqual(expected[0], res) self.assertEqual(len(expected), 1) else: self.assertEqual(len(expected), len(res)) # Should have same num results. for i in range(len(expected)): # Order dependent equality. self.assertEqual(expected[i], res[i]) logging.info("case {} passed OK".format(case_num)) return
def test_get_with_capabilities_multi_ss(self): ss_list = list() for i in range(10): ss_list.append(DummySrcSink("DummySrcSink-{}".format(i))) # Confirm empty at create time. test_address_book = AddressBook() self.assertEqual(0, len(test_address_book.get())) # Confirm nothing matches empty address book in terms of capabilities for ss in ss_list: self.assertEqual( None, test_address_book.get_with_capabilities(ss.capabilities)) # Add to the AddressBook with a time delay so the address timestamps are clearly different. # The last one added should always bethe one returned as it the last in time. for ss in ss_list: time.sleep(0.1) test_address_book.update(ss) self.assertEqual( ss, test_address_book.get_with_capabilities(ss.capabilities)[0]) # Check everything is in the addressBook addr_bk = test_address_book.get() for ss in ss_list: self.assertTrue(ss in addr_bk) # Wait 5 seconds and then ask for items newer than 10 seconds - should be none delay = 2 time.sleep(delay) ss_last = ss_list[-1] self.assertEqual( None, test_address_book.get_with_capabilities(ss_last.capabilities, max_age_in_seconds=delay - 0.1)) self.assertEqual( ss_last, test_address_book.get_with_capabilities(ss_last.capabilities, max_age_in_seconds=100)[0]) # Add one more and make sure it comes back ss_new = DummySrcSink("DummySrcSink-new") test_address_book.update(ss_new) self.assertEqual( ss_new, test_address_book.get_with_capabilities( ss_new.capabilities, max_age_in_seconds=None)[0]) self.assertEqual( ss_new, test_address_book.get_with_capabilities(ss_new.capabilities, max_age_in_seconds=0.5)[0]) return
class DummySrcSink(SrcSink): def __init__(self, name: str, capability: SimpleCapability = SimpleCapability( capability_name='DummySrcSink'), ping_topic: str = Ether.ETHER_BACK_PLANE_TOPIC): # SrcSink - Standard boot-strap & protected members # self._name = name self._topic = UniqueTopic().topic() self._address_book = AddressBook() super().__init__() self._capabilities = [capability] self._lock = threading.Lock() self._handler = NotificationHandler(object_to_be_handler_for=self, throw_unhandled=True) self._handler.register_handler(self._do_srcsink_ping, SrcSinkPing) self._handler.register_handler(self._do_srcsink_ping_notification, SrcSinkPingNotification) self._handler.register_handler(self._do_notification, TaskNotification) self._handler.register_handler(self._do_work, WorkNotificationDo) self._handler.register_handler(self._do_work_finalise, WorkNotificationFinalise) # Public Members just for Unit Test Asserts # self.pings = list() self.ping_notifications = list() self.task_notification = list() self.work_notification = list() self.work_finalise = list() self._ping_topic = ping_topic # The topic to issue ping's on # Get connected ! self.setup_subscriptions() return def __call__(self, notification: Notification): """ Handle notification requests :param notification: The notification to be passed to the handler """ if isinstance(notification, Notification): self._handler.call_handler(notification) else: raise ValueError( "{} un supported notification type {} RX'ed".format( self.name, type(notification).__name__)) return def setup_subscriptions(self) -> None: pub.subscribe(self, self._topic) return def __del__(self): pub.unsubscribe(self, self._topic) return @property def capabilities(self) -> List[Capability]: return self._capabilities @capabilities.setter def capabilities(self, capabilities: List[Capability]): """ Override capabilities, only really meaningful in a test setting. :param capabilities: :return: """ self._capabilities = capabilities return def _do_srcsink_ping_notification(self, ping_notification: Notification) -> None: logging.info("{} :: {} RX handled by".format( self.__class__.__name__, self.name, "_srcsink_ping_notification")) with self._lock: self.ping_notifications.append(ping_notification) return def _do_srcsink_ping(self, ping_request: Notification) -> None: logging.info("{} :: {} RX handled by".format(self.__class__.__name__, self.name, "_srcsink_ping")) with self._lock: self.pings.append(ping_request) return def _do_notification(self, task_notification: Notification): logging.info("{} :: {} RX handled by".format(self.__class__.__name__, self.name, "_do_notification")) with self._lock: self.task_notification.append(task_notification) return def _do_work(self, work_notification: Notification): logging.info("{} :: {} RX handled by".format(self.__class__.__name__, self.name, "_do_work")) with self._lock: self.work_notification.append(work_notification) return def _do_work_finalise(self, do_work_finalise: WorkNotificationFinalise): logging.info("{} :: {} RX handled by".format(self.__class__.__name__, self.name, "_do_work_finalise")) with self._lock: self.work_finalise.append(do_work_finalise) if self.topic != do_work_finalise.originator.topic: pub.sendMessage(topicName=do_work_finalise.originator.topic, notification=do_work_finalise) return def send_ping(self, required_capabilities: List[Capability]) -> UniqueWorkRef: logging.info("{} :: {} Sent Ping".format(self.__class__.__name__, self.name, "send_ping")) ping = SimpleSrcSinkPing(sender_srcsink=self, required_capabilities=required_capabilities) pub.sendMessage(self._ping_topic, notification=ping) return ping.work_ref @property def name(self) -> str: return self._name @property def topic(self) -> str: return self._topic @property def get_addressbook(self) -> List['SrcSink']: logging.info("{} :: {} get_addressbook".format(self.__class__.__name__, self.name, "get_addressbook")) return self._address_book.get() def _update_addressbook(self, srcsink: 'SrcSink') -> None: logging.info("{} :: {} update_addressbook".format( self.__class__.__name__, self.name, "update_addressbook")) self._address_book.update(srcsink) return def __str__(self): return "DummySrcSink name := {}".format(self.name) def __repr__(self): return self.__str__()
class TaskPool(SrcSink): PUB_TIMER = float(.25) PUB_TIMER_MAX = float(30) PRS_TIMER = float(.25) PRS_TIMER_MAX = float(30) POOL_TOPIC_PREFIX = "TaskPool" def __init__(self, pool_name: str): """ Check this or child class has correctly implemented callable __call__ as needed to handle both the PubSub listener events and the work timer events. """ self._address_book = AddressBook() super().__init__() self._call_lock = threading.Lock() self._handler = NotificationHandler(object_to_be_handler_for=self, throw_unhandled=False) self._handler.register_handler(self._get_task, WorkRequest) self._handler.register_handler(self._put_task, WorkNotificationDo) self._handler.register_handler(self._do_srcsink_ping, SrcSinkPing) self._handler.register_handler(self._do_srcsink_ping_notification, SrcSinkPingNotification) self._handler.register_handler(self._do_work_finalise, WorkNotificationFinalise) self._handler.register_activity( handler_for_activity=self._do_pub, activity_interval=TaskPool.PUB_TIMER, activity_name="{}-do_pub_activity".format(pool_name)) self._handler.register_activity( handler_for_activity=self._do_manage_presence, activity_interval=TaskPool.PRS_TIMER, activity_name="{}-do_manage_presence".format(pool_name)) self._capabilities = self._get_capabilities() self._unique_topic = self._create_topic_and_subscription() return def __del__(self): """ Clean up """ self._handler.activity_state(paused=True) pub.unsubscribe(self, self._unique_topic) return def __call__(self, notification: Notification): """ Handle notification requests :param notification: The notification to be passed to the handler """ if isinstance(notification, Notification): self._handler.call_handler(notification) else: raise ValueError( "{} un supported notification type for Task Pool".format( type(notification).__name__)) return def _create_topic_and_subscription(self) -> str: """ Create the unique topic for the agent that it will listen on for work (task) deliveries that it has requested from the task-pool """ unique_topic = UniqueTopic().topic(TaskPool.POOL_TOPIC_PREFIX) pub.subscribe(self, unique_topic) return unique_topic @staticmethod def _get_capabilities() -> List[Capability]: """ The capabilities of this Agent :return: List of Capabilities """ return [SimpleCapability(capability_name=str(CapabilityRegister.POOL))] @purevirtual @abstractmethod def _put_task(self, work_initiate: WorkNotificationDo) -> None: """ Add a task to the task pool which will cause it to be advertised via the relevant topic unless the task is in it's terminal state. :param work_initiate: Details of the task being injected. """ pass @purevirtual @abstractmethod def _get_task(self, work_request: WorkRequest) -> None: """ Send the requested task to the consumer if the task has not already been sent to a consumer :param work_request: The details of the task and the consumer """ pass @purevirtual @abstractmethod def _do_pub(self, current_interval: float) -> float: """ Check for any pending tasks and advertise or re-advertise them on the relevant topic :param current_interval: The current activity interval :return: The optionally revised interval before the action is invoked again """ pass @purevirtual @abstractmethod def _do_manage_presence(self, current_interval: float) -> float: """ Ensure that we are known on the ether. :param current_interval: The current activity interval :return: The optionally revised interval before the action is invoked again """ pass @purevirtual @abstractmethod def _do_work_finalise( self, work_notification_final: WorkNotificationFinalise) -> None: """ Handle the event where a task is in terminal state with no work to do. Default is to notify the originator of the task that their work is done by forwarding the finalise notification. """ pass @classmethod def topic_for_capability(cls, state: State) -> str: """ The topic string on which tasks needing work in that state are published on :param state: The state for which the topic is required :return: The topic string for the given state """ # TODO https://github.com/parrisma/AI-Intuition/issues/1 return "topic-{}".format(str(state.id())) @property @abstractmethod @purevirtual def name(self) -> str: """ The name of the task pool :return: The name of the task pool as string """ pass @property def capabilities(self) -> List[Capability]: """ The collection of capabilities of the SrcSink :return: The collection of capabilities """ return self._capabilities def get_addressbook(self) -> List[SrcSink]: """ The list of srcsinks known to the Ether :return: srcsinks """ return self._address_book.get() def _update_addressbook(self, srcsink: SrcSink) -> None: """ Update the given src_sink in the collection of registered srcsinks. If src_sink is not in the collection add it with a current time stamp. :param srcsink: The src_sink to update / add. """ self._address_book.update(srcsink) return def _get_recent_ether_address(self) -> SrcSink: """ Get a recent Ether address from the AddressBook. If there is no recent Ether then return None :return: Ether SrcSink or None """ ss = self._address_book.get_with_capabilities(required_capabilities=[ SimpleCapability(str(CapabilityRegister.ETHER)) ], max_age_in_seconds=60, n=1) if ss is not None: ss = ss[0] return ss
class Ether(SrcSink): ETHER_BACK_PLANE_TOPIC = "ether-back-plane" PUB_TIMER = float(.25) def __init__(self, ether_name: str): """ Register the notification handlers and install publication activity. Note: Make sure properties name & topic are defined in the sub class before super().__inti__ is called otherwise the activity timer will reference non existent properties in the sub class as the timer will fire (here) before they have been defined in the sub-class init() """ self._stopped = False self._address_book = AddressBook() super().__init__() self._call_lock = threading.Lock() self._handler = NotificationHandler(object_to_be_handler_for=self, throw_unhandled=False) self._handler.register_handler(self._do_srcsink_ping, SrcSinkPing) self._handler.register_handler(self._do_srcsink_ping_notification, SrcSinkPingNotification) return def __del__(self): self._handler.activity_state(paused=True) return def __call__(self, notification: Notification): """ Handle notification requests :param notification: The notification to be passed to the handler """ if isinstance(notification, Notification): self._handler.call_handler(notification) else: raise ValueError( "{} un supported notification type for Task Pool".format( type(notification).__name__)) return def stop(self) -> None: self._handler.activity_state(paused=True) return def start(self) -> None: self._handler.activity_state(paused=False) return @classmethod def back_plane_topic(cls) -> str: """ The back-plane topic to which all Ether objects subscribe :return: """ return cls.ETHER_BACK_PLANE_TOPIC @purevirtual @abstractmethod def _do_pub(self) -> None: """ Publish any changes to known src sinks in the ether """ pass @property @purevirtual @abstractmethod def name(self) -> str: """ The unique name of the SrcSink :return: The SrcSink name """ pass @property @purevirtual @abstractmethod def topic(self) -> str: """ The unique topic name that SrcSink listens on for activity specific to it. :return: The unique SrcSink listen topic name """ pass def get_addressbook(self) -> List[SrcSink]: """ The list of srcsinks known to the Ether :return: srcsinks """ return self._address_book.get() def _update_addressbook(self, srcsink: SrcSink) -> None: """ Update the given src_sink in the collection of registered srcsinks. If src_sink is not in the collection add it with a current time stamp. :param srcsink: The src_sink to update / add. """ self._address_book.update(srcsink) return def _get_addresses_with_capabilities( self, required_capabilities: List[Capability]) -> List[SrcSink]: """ Get the top five addresses in the address book that match the required capabilities. Always include this Ether (self) in the list. :param required_capabilities: The capabilities required :return: The list of matching addresses """ addr = self._address_book.get_with_capabilities( required_capabilities=required_capabilities, match_threshold=float(1), max_age_in_seconds=600, n=5) if addr is None: addr = list() addr.append(self) return addr