class AUTO_TOPIC: pass
class ListenerMismatchError(ValueError):
class CallArgsInfo:
def getModule(obj: Any) -> ModuleType:
def getID(callable_obj: UserListener) -> Tuple[str, ModuleType]:
def getRawFunction(callable_obj: UserListener) -> Tuple[Callable]:
    def __init__(self, msg: str, listener: UserListener, *args):
    def __str__(self):
    def __init__(self, func: UserListener, ignoreArgs: Sequence[str] = ()):
    def getAllArgs(self) -> Tuple[str]:
    def getOptionalArgs(self) -> Tuple[str]:
    def getRequiredArgs(self) -> Tuple[str]:
def getArgs(callable_obj: UserListener, ignoreArgs: Sequence[str] = ()) -> CallArgsInfo: ALL_TOPICS = 'ALL_TOPICS' def tupleize(topicName: str) -> Tuple[str, ...]:
    """
    If topicName is a tuple of strings, just return it as is. Otherwise,
    convert it to tuple, assuming dotted notation used for topicName. I.e.
    'a.b.c' => ('a','b','c'). Empty topicName is not allowed (ValueError).
    The reverse operation is stringize(topicNameTuple).
    """
    # assume name is most often str; if more often tuple,
    # then better use isinstance(name, tuple)
    if hasattr(topicName, "msgDataSpec"):
        topicName = topicName._topicNameStr
    if isinstance(topicName, str):
        topicTuple = tuple(topicName.split('.'))
    else:
        topicTuple = tuple(topicName)  # assume already tuple of strings

    if not topicTuple:
        raise TopicNameError(topicTuple, "Topic name can't be empty!")

    return topicTuple def stringize(topicName: Sequence[str]) -> str:
    """
    If topicName is a string, just return it
    as is. If it is a topic definition object (ie an object that has
    'msgDataSpec' as data member), return the dotted name of corresponding
    topic. Otherwise, assume topicName is a tuple and convert it to to a
    dotted name i.e. ('a','b','c') => 'a.b.c'. Empty name is not allowed
    (ValueError). The reverse operation is tupleize(topicName).
    """
    if isinstance(topicName, str):
        return topicName

    if hasattr(topicName, "_topicNameStr"):
        return topicName._topicNameStr

    try:
        name = '.'.join(topicName)
    except Exception:
        exc = sys.exc_info()[1]
        raise TopicNameError(topicName, str(exc))

    return name class TopicNameError(ValueError):
    """
    Raised when the topic name is not properly formatted or
    no corresponding Topic object found.
    """

    def __init__(self, name: str, msg: str):
        ValueError.__init__(self, 'Topic name "%s": %s' % (name, msg)) class TopicDefnError(RuntimeError):
    """
    Raised when an operation requires a topic have an MDS, but it doesn't.
    See also pub.setTopicUnspecifiedFatal().
    """

    def __init__(self, topicNameTuple: Sequence[str]):
        msg = "No topic specification for topic '%s'." % '.'.join(topicNameTuple)
        RuntimeError.__init__(self, msg +
                              " See pub.addTopicDefnProvider() and/or pub.setTopicUnspecifiedFatal()") class ArgSpecGiven:
    """
    The message data specification (MDS) for a topic.

    This consists of each argument name that listener should have in its
    signature, plus which ones are required in any sendMessage(), and a
    documentation string for each argument. This instance will be transformed
    into an ArgsInfo object which is basically a superset of that information,
    needed to ensure that the arguments specifications satisfy
    pubsub policies for chosen API version.
    """

    SPEC_GIVEN_NONE = 1  # specification not given
    SPEC_GIVEN_ALL = 3  # all args specified

    def __init__(self, argsDocs: ArgsDocs = None, reqdArgs: Seq[str] = None):
        self.reqdArgs = tuple(reqdArgs or ())

        if argsDocs is None:
            self.argsSpecType = ArgSpecGiven.SPEC_GIVEN_NONE
            self.argsDocs = {}
        else:
            self.argsSpecType = ArgSpecGiven.SPEC_GIVEN_ALL
            self.argsDocs = argsDocs

            # check that all args marked as required are in argsDocs
            missingArgs = set(self.reqdArgs).difference(self.argsDocs.keys())  # py3: iter keys ok
            if missingArgs:
                msg = 'Params [%s] missing inherited required args [%%s]' % ','.join(argsDocs.keys())  # iter keys ok
                raise MessageDataSpecError(msg, missingArgs)

    def setAll(self, allArgsDocs: ArgsDocs, reqdArgs: Seq[str] = None):
        self.argsDocs = allArgsDocs
        self.reqdArgs = reqdArgs or ()
        self.argsSpecType = ArgSpecGiven.SPEC_GIVEN_ALL

    def isComplete(self) -> bool:
        """Returns True if the definition is usable, false otherwise."""
        return self.argsSpecType == ArgSpecGiven.SPEC_GIVEN_ALL

    def getOptional(self) -> List[str]:
        """Get the list of optional arguments"""
        return tuple(set(self.argsDocs.keys()).difference(self.reqdArgs))

    def __str__(self):
        return "%s, %s, %s" % (self.argsDocs, self.reqdArgs, self.argsSpecType) def topicArgsFromCallable(_callable: UserListener, ignoreArgs: Seq[str] = ()) -> Tuple[ArgsDocs, List[str]]:
    """
    Get the topic message data names and list of those that are required,
    by introspecting given callable. Returns a pair, (args, required)
    where args is a dictionary of allowed message data names vs docstring,
    and required states which ones are required rather than optional.
    """
    argsInfo = getListenerArgs(_callable, ignoreArgs=ignoreArgs)
    required = argsInfo.getRequiredArgs()
    defaultDoc = 'UNDOCUMENTED'
    args = dict.fromkeys(argsInfo.allParams, defaultDoc)
    return args, required class ArgsInfo:
    pass class Topic:
    pass class IListenerExcHandler:
    """
    Interface class base class for any handler given to pub.setListenerExcHandler()
    Such handler is called whenever a listener raises an exception during a
    pub.sendMessage(). Example::

        from pubsub import pub

        class MyHandler(pub.IListenerExcHandler):
            def __call__(self, listenerID, topicObj):
                ... do something with listenerID ...

        pub.setListenerExcHandler(MyHandler())

    Without an exception handler, the sendMessage() will fail.
    """

    def __call__(self, listenerID: str, topicObj: Topic):
        raise NotImplementedError('%s must override __call__()' % self.__class__) class ITopicDefnProvider:
    """
    All topic definition providers added via pub.addTopicDefnProvider()
    must have this interface. Derived classes must override the getDefn(),
    getTreeDoc() and topicNames() methods.
    """

    def getDefn(self, topicNameTuple: Sequence[str]) -> Tuple[str, ArgSpecGiven]:
        """
        Must return a pair (string, ArgSpecGiven) for given topic.
        The first item is a description for topic, the second item
        contains the message data specification (MDS). Note topic name
        is in tuple format ('a', 'b', 'c') rather than 'a.b.c'.
        """
        msg = 'Must return (string, ArgSpecGiven), or (None, None)'
        raise NotImplementedError(msg)

    def topicNames(self) -> List[str]:
        """
        Return an iterator over topic names available from this provider.
        Note that the topic names should be in tuple rather than dotted-string
        format so as to be compatible with getDefn().
        """
        msg = 'Must return a list of topic names available from this provider'
        raise NotImplementedError(msg)

    def getTreeDoc(self) -> str:
        """Get the docstring for the topic tree."""
        msg = 'Must return documentation string for root topic (tree)'
        raise NotImplementedError(msg)

    def __iter__(self):
        """Same as self.topicNames(), do NOT override."""
        return iter(self.topicNames()) class NotificationMgr:
    """
    Manages notifications for tracing pubsub activity. When pubsub takes a
    certain action such as sending a message or creating a topic, and
    the notification flag for that activity is True, all registered
    notification handlers get corresponding method called with information
    about the activity, such as which listener subscribed to which topic.
    See INotificationHandler for which method gets called for each activity.

    If more than one notification handler has been registered, the order in
    which they are notified is unspecified (do not rely on it).

    Note that this manager automatically unregisters all handlers when
    the Python interpreter exits, to help avoid NoneType exceptions during
    shutdown. This "shutdown" starts when the last line of app "main" has
    executed; the Python interpreter then starts cleaning up, garbage
    collecting everything, which could lead to various pubsub notifications
    -- by then they should be of no interest -- such as dead
    listeners, etc.
    """

    def __init__(self, notificationHandler: INotificationHandler = None):
        self.__notifyOnSend = False
        self.__notifyOnSubscribe = False
        self.__notifyOnUnsubscribe = False

        self.__notifyOnNewTopic = False
        self.__notifyOnDelTopic = False
        self.__notifyOnDeadListener = False

        self.__handlers = []
        if notificationHandler is not None:
            self.addHandler(notificationHandler)

        self.__atExitRegistered = False

    def addHandler(self, handler: INotificationHandler):
        if not self.__atExitRegistered:
            self.__registerForAppExit()
        self.__handlers.append(handler)

    def getHandlers(self) -> List[INotificationHandler]:
        return self.__handlers[:]

    def clearHandlers(self):
        self.__handlers = []

    def notifySubscribe(self, *args, **kwargs):
        if self.__notifyOnSubscribe and self.__handlers:
            for handler in self.__handlers:
                handler.notifySubscribe(*args, **kwargs)

    def notifyUnsubscribe(self, *args, **kwargs):
        if self.__notifyOnUnsubscribe and self.__handlers:
            for handler in self.__handlers:
                handler.notifyUnsubscribe(*args, **kwargs)

    def notifySend(self, *args, **kwargs):
        if self.__notifyOnSend and self.__handlers:
            for handler in self.__handlers:
                handler.notifySend(*args, **kwargs)

    def notifyNewTopic(self, *args, **kwargs):
        if self.__notifyOnNewTopic and self.__handlers:
            for handler in self.__handlers:
                handler.notifyNewTopic(*args, **kwargs)

    def notifyDelTopic(self, *args, **kwargs):
        if self.__notifyOnDelTopic and self.__handlers:
            for handler in self.__handlers:
                handler.notifyDelTopic(*args, **kwargs)

    def notifyDeadListener(self, *args, **kwargs):
        if self.__notifyOnDeadListener and self.__handlers:
            for handler in self.__handlers:
                handler.notifyDeadListener(*args, **kwargs)

    def getFlagStates(self) -> Mapping[str, bool]:
        """Return state of each notification flag, as a dict."""
        return dict(
            subscribe=self.__notifyOnSubscribe,
            unsubscribe=self.__notifyOnUnsubscribe,
            deadListener=self.__notifyOnDeadListener,
            sendMessage=self.__notifyOnSend,
            newTopic=self.__notifyOnNewTopic,
            delTopic=self.__notifyOnDelTopic,
        )

    def setFlagStates(self, subscribe: bool = None, unsubscribe: bool = None, deadListener: bool = None,
                      sendMessage: bool = None, newTopic: bool = None, delTopic: bool = None, all: bool = None):
        """
        Set the notification flag on/off for various aspects of pubsub.
        The kwargs that are None are left at their current value. The 'all',
        if not None, is set first. E.g.

            mgr.setFlagStates(all=True, delTopic=False)

        will toggle all notifications on, but will turn off the 'delTopic'
        notification.
        """
        if all is not None:
            # ignore all other arg settings, and set all of them to true:
            numArgs = 7  # how many args in this method
            self.setFlagStates(all=None, *((numArgs - 1) * [all]))

        if sendMessage is not None:
            self.__notifyOnSend = sendMessage
        if subscribe is not None:
            self.__notifyOnSubscribe = subscribe
        if unsubscribe is not None:
            self.__notifyOnUnsubscribe = unsubscribe

        if newTopic is not None:
            self.__notifyOnNewTopic = newTopic
        if delTopic is not None:
            self.__notifyOnDelTopic = delTopic
        if deadListener is not None:
            self.__notifyOnDeadListener = deadListener

    def __registerForAppExit(self):
        import atexit
        atexit.register(self.clearHandlers)
        self.__atExitRegistered = True class INotificationHandler:
    """
    Defines the interface expected by pubsub for pubsub activity
    notifications. Any instance that supports the same methods, or
    derives from this class, will work as a notification handler
    for pubsub events (see pub.addNotificationHandler).
    """

    def notifySubscribe(self, pubListener: Listener, topicObj: Topic, newSub: bool):
        """
        Called when a listener is subscribed to a topic.
        :param pubListener: the pubsub.core.Listener that wraps subscribed listener.
        :param topicObj: the pubsub.core.Topic object subscribed to.
        :param newSub: false if pubListener was already subscribed.
        """
        raise NotImplementedError

    def notifyUnsubscribe(self, pubListener: Listener, topicObj: Topic):
        """
        Called when a listener is unsubscribed from given topic.
        :param pubListener: the pubsub.core.Listener that wraps unsubscribed listener.
        :param topicObj: the pubsub.core.Topic object unsubscribed from.
        """
        raise NotImplementedError

    def notifyDeadListener(self, pubListener: Listener, topicObj: Topic):
        """
        Called when a listener has been garbage collected.
        :param pubListener: the pubsub.core.Listener that wraps GC'd listener.
        :param topicObj: the pubsub.core.Topic object it was subscribed to.
        """
        raise NotImplementedError

    def notifySend(self, stage: str, topicObj: Topic, pubListener: Listener = None):
        """
        Called multiple times during a sendMessage: once before message
        sending has started (pre), once for each listener about to be sent the
        message, and once after all listeners have received the message (post).

        :param stage: 'pre', 'post', or 'loop'.
        :param topicObj: the Topic object for the message.
        :param pubListener: None for pre and post stages; for loop, the listener
            that is about to be sent the message.
        """
        raise NotImplementedError

    def notifyNewTopic(self, topicObj: Topic, description: str, required: List[str], argsDocs: Mapping[str, str]):
        """
        Called whenever a new topic is added to the topic tree.

        :param topicObj: the Topic object for the message.
        :param description: docstring for the topic.
        :param required: list of message data names (keys in argsDocs) that are required.
        :param argsDocs: dictionary of all message data names, with the
            corresponding docstring.
        """
        raise NotImplementedError

    def notifyDelTopic(self, topicName: str):
        """
        Called whenever a topic is removed from topic tree.
        :param topicName: name of topic removed.
        """
        raise NotImplementedError
from typing import Tuple, List, Sequence, Mapping, Dict, Callable, Any, Optional, Union, TextIO
from .callables import getID, UserListener
from .topicutils import (
    ALL_TOPICS,
    tupleize,
    stringize,
)
from .topicexc import (
    TopicNameError,
    TopicDefnError,
)
from .topicargspec import (
    ArgSpecGiven,
    topicArgsFromCallable,
    ArgsInfo,
)
from .topicobj import Topic
from .listener import IListenerExcHandler
from .topicdefnprovider import ITopicDefnProvider
from .notificationmgr import NotificationMgr, INotificationHandler
            from .topicdefnprovider import TopicDefnProvider, TOPIC_TREE_FROM_MODULE
# ---------------------------------------------------------

class TreeConfig:
    """
    Each topic tree has its own topic manager and configuration,
    such as notification and exception handling.
    """

    def __init__(self, notificationHandler: INotificationHandler = None,
                 listenerExcHandler: IListenerExcHandler = None):
        self.notificationMgr = NotificationMgr(notificationHandler)
        self.listenerExcHandler = listenerExcHandler
        self.raiseOnTopicUnspecified = False


class TopicManager:
    """
    Manages the registry of all topics and creation/deletion
    of topics.

    Note that any method that accepts a topic name can accept it in the
    'dotted' format such as ``'a.b.c.'`` or in tuple format such as
    ``('a', 'b', 'c')``. Any such method will raise a ValueError
    if name not valid (empty, invalid characters, etc).
    """

    # Allowed return values for isTopicSpecified()
    TOPIC_SPEC_NOT_SPECIFIED = 0  # false
    TOPIC_SPEC_ALREADY_CREATED = 1  # all other values equate to "true" but different reason
    TOPIC_SPEC_ALREADY_DEFINED = 2

    def __init__(self, treeConfig: TreeConfig = None):
        """
        The optional treeConfig is an instance of TreeConfig, used to
        configure the topic tree such as notification settings, etc. A
        default config is created if not given. This method should only be
        called by an instance of Publisher (see Publisher.getTopicManager()).
        """
        self.__allTopics = None  # root of topic tree
        self._topicsMap = {}  # registry of all topics
        self.__treeConfig = treeConfig or TreeConfig()
        self.__defnProvider = _MasterTopicDefnProvider(self.__treeConfig)

        # define root of all topics
        assert self.__allTopics is None
        argsDocs, reqdArgs = {}, ()
        desc = 'Root of all topics'
        specGiven = ArgSpecGiven(argsDocs, reqdArgs)
        self.__allTopics = self.__createTopic((ALL_TOPICS,), desc, specGiven=specGiven)

    def getRootAllTopics(self) -> Topic:
        """
        Get the topic that is parent of all root (ie top-level) topics,
        for default TopicManager instance created when this module is imported.
        Some notes:

        - "root of all topics" topic satisfies isAll()==True, isRoot()==False,
          getParent() is None;
        - all root-level topics satisfy isAll()==False, isRoot()==True, and
          getParent() is getDefaultTopicTreeRoot();
        - all other topics satisfy neither.
        """
        return self.__allTopics

    def addDefnProvider(self, providerOrSource: Any, format=None) -> ITopicDefnProvider:
        """
        Register a topic definition provider. After this method is called, whenever a topic must
        be created, the first definition provider that has a definition for the required topic
        is used to instantiate the topic.

        If providerOrSource is an instance of ITopicDefnProvider, register
        it as a provider of topic definitions. Otherwise, register a new
        instance of TopicDefnProvider(providerOrSource, format). In that case,
        if format is not given, it defaults to TOPIC_TREE_FROM_MODULE. Either
        way, returns the instance of ITopicDefnProvider registered.
        """
        if isinstance(providerOrSource, ITopicDefnProvider):
            provider = providerOrSource
        else:
            source = providerOrSource
            provider = TopicDefnProvider(source, format or TOPIC_TREE_FROM_MODULE)
        self.__defnProvider.addProvider(provider)
        return provider

    def clearDefnProviders(self):
        """Remove all registered topic definition providers"""
        self.__defnProvider.clear()

    def getNumDefnProviders(self) -> int:
        """Get how many topic definitions providers are registered."""
        return self.__defnProvider.getNumProviders()

    def getTopic(self, name: str, okIfNone: bool = False) -> Topic:
        """
        Get the Topic instance for the given topic name. By default, raises
        an TopicNameError exception if a topic with given name doesn't exist. If
        okIfNone=True, returns None instead of raising an exception.
        """
        topicNameDotted = stringize(name)
        # if not name:
        #    raise TopicNameError(name, 'Empty topic name not allowed')
        obj = self._topicsMap.get(topicNameDotted, None)
        if obj is not None:
            return obj

        if okIfNone:
            return None

        # NOT FOUND! Determine what problem is and raise accordingly:
        # find the closest parent up chain that does exists:
        parentObj, subtopicNames = self.__getClosestParent(topicNameDotted)
        assert subtopicNames

        subtopicName = subtopicNames[0]
        if parentObj is self.__allTopics:
            raise TopicNameError(name, 'Root topic "%s" doesn\'t exist' % subtopicName)

        msg = 'Topic "%s" doesn\'t have "%s" as subtopic' % (parentObj.getName(), subtopicName)
        raise TopicNameError(name, msg)


    def getOrCreateTopic(self, name: str, protoListener: UserListener = None) -> Topic: