pubsub

the pubsub module contains a number of base classes for the publish/subscribe pattern as used in pydsol. The package is based on the Java implementation of DSOL (Distributed Simulation Object Library), first documented in 2002. Information about the Java and Python DSOL libraries can be found at https://simulation.tudelft.nl. Specific information about the publish/subscribe implementation in Java can be found in the djutils project (Delft Java Utilities) at https://djutils.org

Typically, implemented listener classes inherit from the abstract base class EventListener (possibly through mixin), and classes that produce events inherit from the abstract base class EventProducer (possibly using multiple inheritance).

The whole idea of publish/subscribe is that you decouple the producer of information and the possible receiver(s), which otherwise has to be hardcoded, making information exchange between objects very inflexible. With pub/sub, the producer will notify only subscribed objects of the content in the event. The receivers can change dynamically over time, by subscribing or unsubscribing for the receipt of certain EventTypes. We call the process of producing an event by the publisher “firing” of an event with the fire() method; receipt by the receiver is done in the notify() method.

Note that Events are completely different from SimEvents. Events are used in publish/subscribe; SimEvents contain information about code to be executed at a later point in time.

exception pydsol.core.pubsub.EventError[source]

Bases: Exception

General Exception for working with Events and publish/subscribe

with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

class pydsol.core.pubsub.EventType(name: str, metadata: Dict[str, Type] | None = None)[source]

Bases: object

EventType is a strongly typed identifier for an event, which can contain additional information about the event itself such as metadata. The EventType is typically instantiated as a static variable in a class.

The implementation of the EventType takes care that there will not be a problem with duplicate names for EventTypes. As part of the EventType, the defining class is coded as well to make the EventType unique. So, in the above example, the name “Producer.PRODUCTION” is stored internally as the unique name for the EventType.

Example

class Producer
    PRODUCTION_EVENT = EventType("PRODUCTION")

The event type can then be used anywhere in the code as Producer.PRODUCTION_EVENT. Note that an Event and a SimEvent are two completely different concepts: Events are for the publish/subscribe pattern, SimEvents are to store deferred method information.

__init__(name: str, metadata: Dict[str, Type] | None = None)[source]

Instantiate a new EventType, usually in a static manner.

Example

class Producer
    PRODUCTION_EVENT = EventType("PRODUCTION")

The event type can then be used anywhere in the code as Producer.PRODUCTION_EVENT.

Parameters:
  • name (str) – the human readable name of the event

  • metadata (dict[str, Type], optional) – a dict with the metadata, defining the structure of the payload in the event, as pairs of the name to be used in the dict and the expected type of the data field. When metadata is None or undefined, the payload of the event can be anything. When metadata IS defined, the payload has to be a dict.

Raises:
  • EventError – when name is not a str, or defining_class is not a type

  • EventError – when there was already an event defined with this name in the defining_class

  • EventError – when the metadata does not consist of [str, Type] pairs

property name

Return the human readable name of the event type.

property defining_class

Return the name of the defining class.

The defining class is the class in which the event type has been defined

property metadata: Dict[str, Type] | None

Return the metadata dict or None.

The metadata dict contains the expected structure of the payload dict of the event in case it is defined. metadata can be None, in which case the payload does not have to be a dict, and can have any structure.

class pydsol.core.pubsub.Event(event_type: EventType, content, check: bool = True)[source]

Bases: object

An event is an object with a payload that is to be sent between a producer and zero or more listeners. In a sense, the Event is the “envelope” of the content.

__init__(event_type: EventType, content, check: bool = True)[source]

Instantiate an event with content. Events are strongly typed using a (usually static) instance of the EventType class, to distinguish different types of events from each other.

Parameters:
  • event_type (EventType) – a reference to a (usually static) event type for identification

  • content – the payload of the event, which can be of any type; common types are list, dict, or simple types

  • check (bool, optional) – whether to check the fields in the content in case the event_type has metadata; the check whether the content is a dict is always checked when there is metadata

Raises:
  • EventError – if event_type is not an EventType

  • EventError – if the specified metadata and the content is not a dict

  • EventError – if the dict content is not consistent with the EventType metadata

property event_type: EventType

Returns the (usually static) event type for identificatio.n

property content: Any

Returns the payload of the event; can be of any type.

class pydsol.core.pubsub.TimedEvent(timestamp: float | int, event_type: EventType, content, check: bool = True)[source]

Bases: Event

A TimedEvent is an event with a time identifier (int, float).

The time identifier is not specified as an extra element in the payload, but rather as an extra attribute of the Event. The constructor explicitly demands for the specification of a timestamp, typically the simulator time. Like an event, it is an object with a payload that is to be sent between a producer and zero or more listeners. In a sense, the TimedEvent is the timestamped “envelope” of the content.

__init__(timestamp: float | int, event_type: EventType, content, check: bool = True)[source]

Instantiate a timed event with content.

TimedEvents are strongly typed using a (usually static) instance of the EventType class, to distinguish different types of events from each other. Furthermore, the timestamp indicates when the event was fired. Typically the value of the timestamp is the simulator time.

Parameters:
  • timestamp (int or float) – the timestamp of the event, typically the simulator time

  • event_type (EventType) – a reference to a (usually static) event type for identification

  • content – the payload of the event, which can be of any type; common types are list, dict, or simple types

  • check (bool, optional) – whether to check the fields in the content in case the event_type has metadata; the check whether the content is a dict is always checked when there is metadata

Raises:
  • EventError – if timestamp is not of type int or float

  • EventError – if event_type is not an EventType

  • EventError – if the EventType specified metadata and the content is not a dict

  • EventError – if the dict content is not consistent with the EventType metadata

property timestamp: int | float

Returns the timestamp of the event; typically the simulator time.

property content: Any

Returns the payload of the event; can be of any type.

property event_type: EventType

Returns the (usually static) event type for identificatio.n

class pydsol.core.pubsub.EventListener[source]

Bases: ABC

The EventListener abstract class defines the behavior of an event subscriber.

The EventListener is an interface for a class that needs to be able to receive events from one or more EventProducers. In order to receive events, a listener has to implement the notify() method. In the notify() method, events can be tested for their EventType with if-elif statements, and then the corresponding content can be acted upon.

Its most important method is notify(event) that is called from the EventProducer (using the fier(event) method) to handle the Event.

abstract notify(event: Event)[source]

Handle an event received from an EventProducer

class pydsol.core.pubsub.EventProducer[source]

Bases: object

EventProducer is an abstract class defining the producer behavior.

The EventProducer class acts as a superclass for classes that need to fire events to an unknown and possibly varying number of subscribers (also called listeners). The main methods that can be called on the EventProducer are: add_listener and remove_listener. In addition, the logic of the class that extends the base EventProducer class calls the fire(event_type, content) method to notify the listener(s) (if any).

The most important private attribute of the EventProducer is _listeners: dict[EventType, list[EventListener]]. This structure Maps the EventType to a list of listeners for that EventType. Note that this is a list to make behavior reproducible: we want events to subscribers to be fired in the same order when replicating the model run. The dictionary is ordered (unsorted) in Python 3.7+, and the list is reproducible. A dict[EventType, set[EventListener]] would not be reproducible, since the set is unordered.

__init__()[source]

Instantiate the EventProducer, and initialize the empty listener data structure

add_listener(event_type: EventType, listener: EventListener)[source]

Add an EventListener to this EventProducer for a given EventType. If the listener already is registered for this EventType, this will be ignored.

Parameters:
  • event_type (EventType) – the EventType for which this listener subscribes

  • listener (EventListener) – the subscriber to register for the provided Eventtype

Raises:

EventError – if any of the arguments is of the wrong type

remove_listener(event_type: EventType, listener: EventListener)[source]

Remove an EventListener of this EventProducer for a given EventType. If the listener is not registered for this EventType, this will be ignored.

Parameters:
  • event_type (EventType) – the EventType for which this listener unsubscribes

  • listener (EventListener) – the subscriber to remove for the provided Eventtype

Raises:

EventError – if any of the arguments is of the wrong type

remove_all_listeners(event_type: EventType | None = None, listener: EventListener | None = None)[source]

Remove an EventListener (if given) for a provided EventType (if given) for this EventProducer. It is no problem if there are no matches. There are four situations:

event_type == None and listener == None

all listeners for all event types are removed

event_type == None and listener is specified

the listener is removed for any event for which it was registered

event_type is specified and listener == None

all listeners are removed for the given event_type

event_type and listener are both specified

the listener for the given event type is removed, if it was registered; in essence this is the same as remove_listener

Parameters:
  • event_type (EventType, optional) – the EventType for which this listener unsubscribes

  • listener (EventListener, optional) – the subscriber to remove for the provided EventType

Raises:

EventError – if any of the arguments is of the wrong type

has_listeners() bool[source]

indicate whether this producer has any listeners or not

fire_event(event: Event)[source]

fire this event to the subscribed listeners for the EventType of the event.

Parameters:

event (Event) – the event to fire to the subscribed listeners

Raises:

EventError – if the event is not of the right type

fire(event_type: EventType, content, check: bool = True)[source]

construct an event based on the arguments and fire this event to the subscribed listeners for the event_type

Parameters:
  • event_type (EventType) – a reference to a (usually static) event type for identification

  • content – the payload of the event, which can be of any type; common types are list, dict, or simple types

  • check (bool, optional) – whether to check the fields in the content in case the event_type has metadata; the check whether the content is a dict is always checked when there is metadata

Raises:
  • EventError – if event_type is not an EventType

  • EventError – if the EventType specified metadata and the content is not a dict

  • EventError – if the dict content is not consistent with the EventType metadata

fire_timed_event(timed_event: TimedEvent)[source]

fire this timed_event to the subscribed listeners for the EventType of the event.

Parameters:

event (TimedEvent) – the timed_event to fire to the subscribed listeners

Raises:

EventError – if the timed_event is not of the right type

fire_timed(time: int | float, event_type: EventType, content, check: bool = True)[source]

construct a timed event based on the arguments and fire this event to the subscribed listeners for the event_type

Parameters:
  • timestamp (int or float) – the timestamp of the event, typically the simulator time

  • event_type (EventType) – a reference to a (usually static) event type for identification

  • content – the payload of the event, which can be of any type; common types are list, dict, or simple types

  • check (bool, optional) – whether to check the fields in the content in case the event_type has metadata; the check whether the content is a dict is always checked when there is metadata

Raises:
  • EventError – if timestamp is not of type int or float

  • EventError – if event_type is not an EventType

  • EventError – if the EventType specified metadata and the content is not a dict

  • EventError – if the dict content is not consistent with the EventType metadata