Python PubSub Website

Site Contents

About Topics

Message Topics

The topic of a pubsub message can be understood in several ways:

  • in OOP parlance:
    • as its “type”, or “class”
    • the message data correspond to attributes of a type or class instance (but are not represented as such)
  • in radio broadcasting parlance:
    • as a “channel”, or “frequency”
    • all “receivers” “tuned” to the right channel or frequency will receive the message

Topic Names

A topic is uniquely identified by its full name, represented as a character string. Example of some topic names are

  • “sports”,
  • “sports.hockey”,
  • “app.options.editor”, etc.

As explained in another section, topics form a hierarchy. Every topic therefore has a “node name”. Some of the node names from the previous examples are

  • “sports”
  • “hockey”
  • “options” and “editor”

The “full name” consists of the node-names from the root of the hierarchy, separated by dots (as the first examples showed).

For this reason, topic names can also be given to pubsub functions as tuples of strings rather than a dotted string. The tuple equivalent of the topic names from the previous examples are (“sports”,), (“sports”, “hockey”), (“app”,”options”,”editor”).

Most pub functions and methods that require a topic name accept either forms of topic name.

Topic MDS: Basics

A topic specifies what data can be transported as part of messages of that topic. This specification is called Message Data Specification, or MDS for short. The specification covers the data name and whether the data is required or optional.

Assume the following MDS for “topic_A”:

messages carry the data:
arg1: required
arg2: optional

Then the following messages could be sent:

pub.sendMessage("topic_A", arg1=123)
pub.sendMessage("topic_A", arg1=123, arg2=456)

but the following would raise an exception:

pub.sendMessage("topic_A", arg2=456)

By default, the MDS is created for you at runtime by introspection of the first listener subscribed to a topic. This makes it simple to use pubsub “out of the box” (since you don’t have to write the MDS yourself), but has the disadvantage of making your MDS specification dependant on the order of subscription of listeners. If this is problematic, the MDS can be imported, as explained next.

Pubsub allows the MDS to be imported from a Python file and referenced at runtime. This will make the MDS independent of the order of subscription of listeners and even allow you to document your topics and MDS.

You can output the MDS of each pubsub topic defined in an application via the pub.exportTopicTree() function:

# init:
from pubsub import pb

# main:
... subscribe listeners ...
... send messages ...

# upon exit:
exportModuleDoc = '''...'''
pub.exportTopicTree(
    'filename.py', moduleDoc=exportModuleDoc)

Gradual Lockdown of MDS

A technique that you may find useful when developing a new application is to start by using the runtime introspection-based MDS of pubsub; when your application core is sufficiently developed, use pub.exportTopicTree() to export it, clean it up, save it, and add a pub.importTopicTree() early in the startup of you application. Also call pub.setTopicUnspecifiedFatal(True)() so that new topics can’t be created inadvertently.

Topic Hierarchy

Topics form a hierarchy:

  • Root of topic tree:

    • The root of the topic hierarchy is the pub.ALL_TOPICS topic.
    • This topic is automatically created by pubsub upon first import. It cannot be created or deleted.
    • This topic’s node name is not included in the full name of topics since it is implicit. Hence topic “topic_A” is in fact a child of the ALL_TOPICS topic.
    • The MDS of this topic is empty, ie no data should be sent directly. However, listeners can get any data sent by having **kwargs in their signature, ie such listeners receive all data sent by all topics, regardless of actual message topic.
    • The root topic is mostly useful for logging pubsub messages.
  • Subtopics: a subtopic is to its parent topic as a derived class is to its parent class in Python, ie something more specific, involving more data. A subtopic “inherits” its parent’s MDS and may “extend” it by specifying additional message data permitted for the subtopic.

  • Messages are delivered to the listeners of the specified topic, as well as listeners of topics up the topic branch:

    pub.sendMessage('a.b.c', argA=1, argB=2, argC=3)
    

    The message, of topic “a.b.c”, will be received by listeners of “a.b.c”. Parts of the message will be received by listeners of “a.b”, by listeners of “a”, and by listeners of ALL_TOPICS.

    The part of the message received by a parent topic is determined by the parent topic’s MDS. IE only the message data that pertains to a topic’s parent is sent to the listeners of the parent topic. In the above example, if topic “a.b” specifies argA and argB only, then only those two items will be sent to listeners of “a.b”. Similarly, if topic “a” specifies argA only as MDS, then only argA will be sent to listeners of “a”.

  • The order of delivery of a message to listeners is unspecified. Your application must not depend on it.

Topic MDS: Hierarchy

A subtopic “inherits” its parent’s MDS and may “extend” it by specifying additional message data permitted for the subtopic. For instance if topic “a” has message data arg1 only, then its subtopics (such as “a.b”) must support at least arg1:

def listen(    arg1): pass
def listenSubB(arg1): pass
def listenSubC(arg1, arg2): pass

pub.subscribe(listen,     "a")
pub.subscribe(listenSubB, "a.b")
pub.subscribe(listenSubC, "a.c")

Assuming the above, the following would cause pubsub to raise an exception:

def listenSubD(arg2): pass
pub.subscribe(listenSubD, "a.d") // raises: arg1 missing

Note

Because the root topic has an empty MDS, no data is transmitted to listeners of the root topic, except if a listener uses the catch-all **kwargs in its signature.

Note

A subtopic can make one or more of its parent data items required for the subtopic if it was optional for the topic, but cannot do the reverse. In

def listen(   arg1=1): pass
def listenSub(arg1): pass

pub.subscribe(listen,    "a")
pub.subscribe(listenSub, "a.b")

the subtopic “a.b” is valid: arg1 is required, so a sender will always provide it, so eventhough it is optional for topic 1 listeners, those listeners will always receive it too.

Topic Definition Tree and Providers

topic definition tree and its syntax, topic tree printing

Topics (OLD)

The topic hierarchy can be created on the fly by the application (default), or can be specified and documented via one or more Topic Definition Providers (TDP). TDP’s are objects that state

  • what are valid topic names in the application
  • what is the topic hierarchy
  • what data is allowed or required when sending messages for each topic
  • describe what is the purpose of each topic and message data

Pubsub can be setup to deny (via an exception) any attempt to use a topic that is not specified by a TDP. This is very useful in larger applications.

Every topic has a message arguments specification, i.e. a prescribed set of argument names that are allowed when sending a message of that topic. This is the TMAS, for topic message arguments specification. For instance, topic “sports.baseball” might have following TMAS:

  • playersA: list of players in team A
  • playersB: list of players in team B
  • location: where the game is taking place
  • dateTime: when is the game

but only location and playersA are required: playersB and dateTime are only used during a match (vs a practice). Then every message of topic “sports.baseball” must be given at least a location and list for playersA, and every recipient of the message must accept all four parameters (since some messages will have all four parameters). I.e.:

pub.sendMessage("sports.baseball", playersA=[], location="Montreal")
pub.sendMessage("sports.baseball", playersA=[], playersB=[], location="Montreal")
pub.sendMessage("sports.baseball", playersA=[], location="Montreal", dateTime=today() )
pub.sendMessage("sports.baseball", playersA=[], playersB=[], location="Montreal", dateTime=today() )

pub.sendMessage("sports.baseball", playersA=[]) # ERROR: required location missing

The TMAS of each topic:

  • Is obtained from the first TDP that provides it. If the application does not use TDP’s, the TMAS is inferred from the first listener subscribed to the topic.
  • Extends the TMAS of the parent topic. This is almost identical to inheritence of attributes in a class hierarchy. For instance if topic “a.b” has TMAS (arg1 (required), arg2 (optional)), then topic “a.b.c” has at least that TMAS, and topic “a” has at most that TMAS.