Concepts
=========




Topics
-------

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.


Listeners
----------

A listener is a Python callable object (i.e. a function, method or 
instance with a __call__ method). A listener subscribes to topics that
it wants to listen to. The only restrictions on listeners are: 

- A listener must satisfy the TMAS of a topic 
  otherwise it will be rejected when requesting to subscribe, leading 
  to a ListenerInadequate exception.
 
- A listener **must not** raise any exceptions since neither pubsub 
  nor the sender of a message know anything about the listener, let 
  alone what exceptions it can raise and what to do with them. See
  :ref:`exception-handling`.
  
A listener should not make any assumptions about: 

- The order of calls of listeners subscribed to same or other topics
- Where the message originates 

Listeners that are subscribed are held by pubsub in a registry. 
However, Listeners are stored in this registry in such a way that when the 
application no longer uses the listener (reference count goes to 
zero), it is removed from the registry. In other words, the 
listener is stored in the registry by weak reference only. This 
prevents pubsub from artificially keeping listeners alive when 
the application no longer needs them. 

A listener can be given the Topic object when receiving a message. 


Kwargs Messaging Protocol
--------------------------

The *kwargs* protocol is defined as follows:

- the transport of all topic message data occurs via keyword arguments
  in sendMessage(); the argument names must be the same for all
  sendMessage() for a given topic.

- the listener's parameters must be named the same as the keyword
  arguments used in sendMessage()

- subtopics can only extend the list of argument names accepted

Example: assume a topic 'someTopic' and subtopic 'someTopic.subTopic'::

    from pubsub import pub

    def listener(hi): assert msg == 123
    pub.subscribe(listener, 'someTopic')

    def listener2(hi, foo): assert hi == 123; assert foo = 'bar'
    pub.subscribe(listener2, 'someTopic.subTopic')

    pub.sendMessage('someTopic.subTopic', hi=123, foo='bar')


Arg1 Messaging Protocol
------------------------

This protocol
is defined as follows:

- the transport of all topic message data occurs as one user-defined
  Python object, stored in the .data attribute of the object received
  by listeners

- the listener must accept being called as f(message)

Example::

    from pubsub import setuparg1
    from pubsub import pub
    def listener(msg): assert msg.data['hi'] == 123
    data = dict('hi':123, ...)
    pub.sendMessage('someTopic', data)

