# `disco` — Service discovery support (XEP-0030)

This module provides support for Service Discovery. For this, it provides a `Service` subclass which can be loaded into a client using `Client.summon()`.

## Services

The following services are provided by this subpackage and available directly from `aioxmpp`:

`DiscoServer` | Answer Service Discovery (XEP-0030) requests sent to this client.
`DiscoClient` | Provide cache-backed Service Discovery (XEP-0030) queries.

Changed in version 0.8: Prior to version 0.8, both services were provided by a single class (`aioxmpp.disco.Service`). This is not the case anymore, and there is no replacement.

If you need to write backwards compatible code, you could be doing something like this:

    try:
        aioxmpp.DiscoServer
    except AttributeError:
        aioxmpp.DiscoServer = aioxmpp.disco.Service
        aioxmpp.DiscoClient = aioxmpp.disco.Service

This should work, because the old `Service` class provided the features of both of the individual classes.

The detailed documentation of the classes follows:

class `aioxmpp.``DiscoServer`(client, **kwargs)

  Answer Service Discovery (XEP-0030) requests sent to this client.

  This service implements handlers for `…disco#info` and `…disco#items` IQ requests. It provides methods to configure the contents of these responses.

  See also

  `DiscoClient`
    for a service which provides methods to query Service Discovery information from other entities.

  The `DiscoServer` inherits from `Node` to manage the identities and features of the client. The identities and features declared in the service using the `Node` interface on the `DiscoServer` instance are returned when a query is received for the JID with an empty or unset `node` attribute. For completeness, the relevant methods are listed here. Refer to the `Node` documentation for details.

  Note

  Upon construction, the `DiscoServer` adds a default identity with category `"client"` and type `"bot"` to the root `Node`. This is to comply with XEP-0030, which specifies that at least one identity must always be returned. Otherwise, the service would be forced to send a malformed response or reply with `<feature-not-implemented/>`.

  After having added another identity, that default identity can be removed.

  Other `Node` instances can be registered with the service using the following methods:

  `mount_node`(mountpoint, node)

    Mount the `Node` node to be returned when a peer requests XEP-0030 information for the node mountpoint.

  `unmount_node`(mountpoint)

    Unmount the node mounted at mountpoint.

    See also

    `mount_node()`
      for a way for mounting `Node` instances.

class `aioxmpp.``DiscoClient`(client, **kwargs)

  Provide cache-backed Service Discovery (XEP-0030) queries.

  This service provides methods to query Service Discovery information from other entities in the XMPP network. The results are cached transparently.

  See also

  `DiscoServer`
    for a service which answers Service Discovery queries sent to the client by other entities.
  `EntityCapsService`
    for a service which uses XEP-0115 to fill the cache of the `DiscoClient` with offline information.

  Querying other entities’ service discovery information:

  coroutine `query_info`(jid, *, node=None, require_fresh=False, timeout=None, no_cache=False)

    Query the features and identities of the specified entity.

    Parameters:

    - jid (`aioxmpp.JID`) – The entity to query.
    - node (`str` or `None`) – The node to query.
    - require_fresh (`bool`) – Boolean flag to discard previous caches.
    - timeout (`float`) – Optional timeout for the response.
    - no_cache (`bool`) – Boolean flag to forbid caching of the request.

    Return type: | `xso.InfoQuery`
    Returns: | Service discovery information of the node at jid.

    The requests are cached. This means that only one request is ever fired for a given target (identified by the jid and the node). The request is re-used for all subsequent requests to that identity.

    If require_fresh is set to true, the above does not hold and a fresh request is always created. The new request is the request which will be used as alias for subsequent requests to the same identity.

    The visible effects of this are twofold:

    - Caching: Results of requests are implicitly cached
    - Aliasing: Two concurrent requests will be aliased to one request to save computing resources

    Both can be turned off by using require_fresh. In general, you should not need to use require_fresh, as all requests are implicitly cancelled whenever the underlying session gets destroyed.

    no_cache can be set to true to prevent future requests to be aliased to this request, i.e. the request is not stored in the internal request cache. This does not affect require_fresh, i.e. if a cached result is available, it is used.

    The timeout can be used to restrict the time to wait for a response. If the timeout triggers, `TimeoutError` is raised.

    If `send()` raises an exception, all queries which were running simultanously for the same target re-raise that exception. The result is not cached though. If a new query is sent at a later point for the same target, a new query is actually sent, independent of the value chosen for require_fresh.

    Changed in version 0.9: The no_cache argument was added.

  coroutine `query_items`(jid, *, node=None, require_fresh=False, timeout=None)

    Query the items of the specified entity.

    Parameters:

    - jid (`aioxmpp.JID`) – The entity to query.
    - node (`str` or `None`) – The node to query.
    - require_fresh (`bool`) – Boolean flag to discard previous caches.
    - timeout (`float`) – Optional timeout for the response.

    Return type: | `xso.ItemsQuery`
    Returns: | Service discovery items of the node at jid.

    The arguments have the same semantics as with `query_info()`, as does the caching and error handling.

  To prime the cache with information, the following methods can be used:

  `set_info_cache`(jid, node, info)

    This is a wrapper around `set_info_future()` which creates a future and immediately assigns info as its result.

    New in version 0.5.

  `set_info_future`(jid, node, fut)

    Override the cache entry (if one exists) for `query_info()` of the jid and node combination with the given `asyncio.Future` fut.

    The future must receive a `dict` compatible to the output of `xso.InfoQuery.to_dict()`.

    As usual, the cache can be bypassed and cleared by passing require_fresh to `query_info()`.

    See also

    Module `aioxmpp.entitycaps`
      XEP-0115 implementation which uses this method to prime the cache with information derived from Entity Capability announcements.

    Note

    If a future is set to exception state, it will still remain and make all queries for that target fail with that exception, until a query uses require_fresh.

    New in version 0.5.

  To control the size of caches, the following properties are available:

  `info_cache_size` = 10000

    Maximum number of cache entries in the cache for `query_info()`.

    This is mostly a measure to prevent malicious peers from exhausting memory by spamming `aioxmpp.entitycaps` capability hashes.

    New in version 0.9.

  `items_cache_size` = 100

    Maximum number of cache entries in the cache for `query_items()`.

    New in version 0.9.

  `flush_cache`()

    Clear the cache.

    This clears the internal cache in a way which lets existing queries continue, but the next query for each target will behave as if require_fresh had been set to true.

  Usage example, assuming that you have a `node.Client` client:

      import aioxmpp.disco as disco
      # load service into node
      sd = client.summon(aioxmpp.DiscoClient)

      # retrieve server information
      server_info = yield from sd.query_info(
          node.local_jid.replace(localpart=None, resource=None)
      )

      # retrieve resources
      resources = yield from sd.query_items(
          node.local_jid.bare()
      )

### Entity information

class `aioxmpp.disco.``Node`

  A `Node` holds the information related to a specific node within the entity referred to by a JID, with respect to XEP-0030 semantics.

  A `Node` always has at least one identity (or it will return as `<item-not-found/>`). It may have zero or more features beyond the XEP-0030 features which are statically included.

  To manage the identities and the features of a node, use the following methods:

  `register_feature`(var)

    Register a feature with the namespace variable var.

    If the feature is already registered or part of the default XEP-0030 features, a `ValueError` is raised.

  `unregister_feature`(var)

    Unregister a feature which has previously been registered using `register_feature()`.

    If the feature has not been registered previously, `KeyError` is raised.

    Note

    The features which are mandatory per XEP-0030 are always registered and cannot be unregistered. For the purpose of unregistration, they behave as if they had never been registered; for the purpose of registration, they behave as if they had been registered before.

  `register_identity`(category, type_, *, names={})

    Register an identity with the given category and type_.

    If there is already a registered identity with the same category and type_, `ValueError` is raised.

    names may be a mapping which maps `structs.LanguageTag` instances to strings. This mapping will be used to produce `<identity/>` declarations with the respective `xml:lang` and `name` attributes.

  `set_identity_names`(category, type_, names={})

    Update the names of an identity.

    Parameters:

    - category (`str`) – The category of the identity to update.
    - type (`str`) – The type of the identity to update.
    - names (`Mapping` from `structs.LanguageTag` to `str`) – The new internationalised names to set for the identity.

    Raises: | ValueError – if no identity with the given category and type is currently registered.

  `unregister_identity`(category, type_)

    Unregister an identity previously registered using `register_identity()`.

    If no identity with the given category and type_ has been registered before, `KeyError` is raised.

    If the identity to remove is the last identity of the `Node`, `ValueError` is raised; a node must always have at least one identity.

  To access the declared features and identities, use:

  `iter_features`(stanza=None)

    Return an iterator which yields the features of the node.

    Parameters: | stanza (`IQ`) – The IQ request stanza
    Return type: | iterable of `str`
    Returns: | XEP-0030 features of this node

    stanza is the `aioxmpp.IQ` stanza of the request. This can be used to filter the list according to who is asking (not recommended).

    stanza may be `None` if the features are queried without a specific request context. In that case, implementors should assume that the result is visible to everybody.

    Note

    Subclasses must allow `None` for stanza and default it to `None`.

    The features are returned as strings. The features demanded by XEP-0030 are always returned.

  `iter_identities`(stanza=None)

    Return an iterator of tuples describing the identities of the node.

    Parameters: | stanza (`IQ` or `None`) – The IQ request stanza
    Return type: | iterable of (`str`, `str`, `str` or `None`, `str` or `None`) tuples
    Returns: | XEP-0030 identities of this node

    stanza can be the `aioxmpp.IQ` stanza of the request. This can be used to hide a node depending on who is asking. If the returned iterable is empty, the `DiscoServer` returns an `<item-not-found/>` error.

    stanza may be `None` if the identities are queried without a specific request context. In that case, implementors should assume that the result is visible to everybody.

    Note

    Subclasses must allow `None` for stanza and default it to `None`.

    Return an iterator which yields tuples consisting of the category, the type, the language code and the name of each identity declared in this `Node`.

    Both the language code and the name may be `None`, if no names or a name without language code have been declared.

  `as_info_xso`(stanza=None)

    Construct a `InfoQuery` response object for this node.

    Parameters: | stanza (`IQ`) – The IQ request stanza
    Return type: | iterable of `InfoQuery`
    Returns: | The disco#info response for this node.

    The resulting `InfoQuery` carries the features and identities as returned by `iter_features()` and `iter_identities()`. The `node` attribute is at its default value and may need to be set by the caller accordingly.

    stanza is passed to `iter_features()` and `iter_identities()`. See those methods for information on the effects.

    New in version 0.9.

  To access items, use:

  `iter_items`(stanza=None)

    Return an iterator which yields the items of the node.

    Parameters: | stanza (`IQ`) – The IQ request stanza
    Return type: | iterable of `Item`
    Returns: | Items of the node

    stanza is the `aioxmpp.IQ` stanza of the request. This can be used to localize the list to the language of the stanza or filter it according to who is asking.

    stanza may be `None` if the items are queried without a specific request context. In that case, implementors should assume that the result is visible to everybody.

    Note

    Subclasses must allow `None` for stanza and default it to `None`.

    A bare `Node` cannot hold any items and will thus return an iterator which does not yield any element.

  Signals provide information about changes:

  signal `on_info_changed`()

    This signal emits when a feature or identity is registered or unregistered.

  As mentioned, bare `Node` objects have no items; there are subclasses of `Node` which support items:

  `StaticNode` | Support for a list of `xso.Item` instances
  `DiscoServer` | Support for “mountpoints” for node subtrees

class `aioxmpp.disco.``StaticNode`

  A `StaticNode` is a `Node` with a non-dynamic set of items.

  `items`

    A list of `xso.Item` instances. These items will be returned when the node is queried for it’s XEP-0030 items.

    It is the responsibility of the user to ensure that the set of items is valid. This includes avoiding duplicate items.

  classmethod `clone`(other_node)

    Clone another `Node` and return as `StaticNode`.

    Parameters: | other_node (`Node`) – The node which shall be cloned
    Return type: | `StaticNode`
    Returns: | A static node which has the exact same features, identities and items as other_node.

    The features and identities are copied over into the resulting `StaticNode`. The items of other_node are not copied but merely referenced, so changes to the item objects of other_node will be reflected in the result.

    New in version 0.9.

class `aioxmpp.disco.``mount_as_node`(mountpoint)

  Service descriptor which mounts the `Service` as `DiscoServer` node.

  Parameters: | mountpoint (`str`) – The mountpoint at which to mount the node.

  New in version 0.8.

  When the service is instaniated, it is mounted as `Node` at the given mountpoint; it must thus also inherit from `Node` or implement a compatible interface.

  `mountpoint`

    The mountpoint at which the node is mounted.

class `aioxmpp.disco.``register_feature`(feature)

  Service descriptor which registers a service discovery feature.

  Parameters: | feature (`str`) – The feature to register.

  New in version 0.8.

  When the service is instaniated, the feature is registered at the `DiscoServer`.

  On instances, the attribute which is described with this is a `RegisteredFeature` instance.

  Changed in version 0.9: `RegisteredFeature` was added; before, the attribute reads as `None`.

class `aioxmpp.disco.``RegisteredFeature`(service, feature)

  Manage registration of a feature with a `DiscoServer`.

  Parameters:

  - service (`DiscoServer`) – The service implementing the service discovery server.
  - feature (`str`) – The feature to register.

  Note

  Normally, you would not create an instance of this object manually. Use the `register_feature` descriptor on your `aioxmpp.Service` which will provide a `RegisteredFeature` object:

      class Foo(aioxmpp.Service):
          _some_feature = aioxmpp.disco.register_feature(
              "urn:of:the:feature"
          )

          # after __init__, self._some_feature is a RegisteredFeature
          # instance.

          @property
          def some_feature_enabled(self):
              # better do not expose the enabled boolean directly; this
              # gives you the opportunity to do additional things when it
              # is changed, such as disabling multiple features at once.
              return self._some_feature.enabled

          @some_feature_enabled.setter
          def some_feature_enabled(self, value):
              self._some_feature.enabled = value

  New in version 0.9.

  This object can be used as a context manager. Upon entering the context, the feature is registered. When the context is left, the feature is unregistered.

  Note

  The context-manager use does not nest sensibly. Thus, do not use th context-manager feature on `RegisteredFeature` instances which are created by `register_feature`, as `register_feature` uses the context manager to register/unregister the feature on initialisation/shutdown.

  Independently, it is possible to control the registration status of the feature using `enabled`.

  `enabled`

    Boolean indicating whether the feature is registered by this object or not.

    When this attribute is changed to `True`, the feature is registered. When the attribute is changed to `False`, the feature is unregisterd.

  `feature`

    The feature this object is controlling (read-only).

## `disco.xso` — IQ payloads

The submodule `aioxmpp.disco.xso` contains the `XSO` classes which describe the IQ payloads used by this subpackage.

You will encounter some of these in return values, but there should never be a need to construct them by yourself; the `Service` handles it all.

### Information queries

class `aioxmpp.disco.xso.``InfoQuery`(*[, identities][, features][, node])

  A query for features and identities of an entity. The keyword arguments to the constructor can be used to initialize the attributes. Note that identities and features must be iterables of `Identity` and `Feature`, respectively; these iterables are evaluated and the items are stored in the respective attributes.

  `node`

    The node at which the query is directed.

  `identities`

    The identities of the entity, as `Identity` instances. Each entity has at least one identity.

  `features`

    The features of the entity, as a set of strings. Each string represents a `Feature` instance with the corresponding `var` attribute.

  `captured_events`

    If the object was created by parsing an XML stream, this attribute holds a list of events which were used when parsing it.

    Otherwise, this is `None`.

    New in version 0.5.

  `to_dict`()

    Convert the query result to a normalized JSON-like representation.

    The format is a subset of the format used by the capsdb. Obviously, the node name and hash type are not included; otherwise, the format is identical.

class `aioxmpp.disco.xso.``Feature`(*[, var])

  A feature declaration. The keyword argument to the constructor can be used to initialize the attribute of the `Feature` instance.

  `var`

    The namespace which identifies the feature.

class `aioxmpp.disco.xso.``Identity`(*[, category][, type_][, name][, lang])

  An identity declaration. The keyword arguments to the constructor can be used to initialize attributes of the `Identity` instance.

  `category`

    The category of the identity. The value is not validated against the values in the registry.

  `type_`

    The type of the identity. The value is not validated against the values in the registry.

  `name`

    The optional human-readable name of the identity. See also the `lang` attribute.

  `lang`

    The language of the `name`. This may be not `None` even if `name` is not set due to `xml:lang` propagation.

### Item queries

class `aioxmpp.disco.xso.``ItemsQuery`(*[, node][, items])

  A query for items at a specific entity. The keyword arguments to the constructor can be used to initialize the attributes of the `ItemsQuery`. Note that items must be an iterable of `Item` instances. The iterable will be evaluated and the items will be stored in the `items` attribute.

  `node`

    Node at which the query is directed

  `items`

    The items at the addressed entity.

class `aioxmpp.disco.xso.``Item`(*[, jid][, name][, node])

  An item declaration. The keyword arguments to the constructor can be used to initialize the attributes of the `Item` instance.

  `jid`

    `JID` of the entity represented by the item.

  `node`

    Node of the item

  `name`

    Name of the item