From d850b9a9f7e18d72c84ccce22b97726a561e866a Mon Sep 17 00:00:00 2001 From: mathieui Date: Sat, 6 Feb 2021 12:28:19 +0100 Subject: types: move pluginsdict to a dedicated file And add some Literal types. --- slixmpp/pluginsdict.py | 193 +++++++++++++++++++++++++++++++++++++++++++++ slixmpp/types.py | 207 ++++++------------------------------------------- 2 files changed, 217 insertions(+), 183 deletions(-) create mode 100644 slixmpp/pluginsdict.py diff --git a/slixmpp/pluginsdict.py b/slixmpp/pluginsdict.py new file mode 100644 index 00000000..d9954f51 --- /dev/null +++ b/slixmpp/pluginsdict.py @@ -0,0 +1,193 @@ +# Slixmpp: The Slick XMPP Library +# Copyright © 2021 Mathieu Pasquet +# This file is part of Slixmpp. +# See the file LICENSE for copying permission. + +try: + from typing import TypedDict +except ImportError: + from typing_extensions import TypedDict + +# Plugins mega-dict + +from slixmpp.plugins.xep_0004 import XEP_0004 +from slixmpp.plugins.xep_0009 import XEP_0009 +from slixmpp.plugins.xep_0012 import XEP_0012 +from slixmpp.plugins.xep_0013 import XEP_0013 +from slixmpp.plugins.xep_0020 import XEP_0020 +from slixmpp.plugins.xep_0027 import XEP_0027 +from slixmpp.plugins.xep_0030 import XEP_0030 +from slixmpp.plugins.xep_0033 import XEP_0033 +from slixmpp.plugins.xep_0045 import XEP_0045 +from slixmpp.plugins.xep_0047 import XEP_0047 +from slixmpp.plugins.xep_0049 import XEP_0049 +from slixmpp.plugins.xep_0050 import XEP_0050 +from slixmpp.plugins.xep_0054 import XEP_0054 +from slixmpp.plugins.xep_0059 import XEP_0059 +from slixmpp.plugins.xep_0060 import XEP_0060 +from slixmpp.plugins.xep_0065 import XEP_0065 +from slixmpp.plugins.xep_0066 import XEP_0066 +from slixmpp.plugins.xep_0070 import XEP_0070 +from slixmpp.plugins.xep_0071 import XEP_0071 +from slixmpp.plugins.xep_0077 import XEP_0077 +from slixmpp.plugins.xep_0079 import XEP_0079 +from slixmpp.plugins.xep_0080 import XEP_0080 +from slixmpp.plugins.xep_0082 import XEP_0082 +from slixmpp.plugins.xep_0084 import XEP_0084 +from slixmpp.plugins.xep_0085 import XEP_0085 +from slixmpp.plugins.xep_0086 import XEP_0086 +from slixmpp.plugins.xep_0092 import XEP_0092 +from slixmpp.plugins.xep_0106 import XEP_0106 +from slixmpp.plugins.xep_0107 import XEP_0107 +from slixmpp.plugins.xep_0108 import XEP_0108 +from slixmpp.plugins.xep_0115 import XEP_0115 +from slixmpp.plugins.xep_0118 import XEP_0118 +from slixmpp.plugins.xep_0122 import XEP_0122 +from slixmpp.plugins.xep_0128 import XEP_0128 +from slixmpp.plugins.xep_0131 import XEP_0131 +from slixmpp.plugins.xep_0133 import XEP_0133 +from slixmpp.plugins.xep_0152 import XEP_0152 +from slixmpp.plugins.xep_0153 import XEP_0153 +from slixmpp.plugins.xep_0163 import XEP_0163 +from slixmpp.plugins.xep_0172 import XEP_0172 +from slixmpp.plugins.xep_0184 import XEP_0184 +from slixmpp.plugins.xep_0186 import XEP_0186 +from slixmpp.plugins.xep_0191 import XEP_0191 +from slixmpp.plugins.xep_0196 import XEP_0196 +from slixmpp.plugins.xep_0198 import XEP_0198 +from slixmpp.plugins.xep_0199 import XEP_0199 +from slixmpp.plugins.xep_0202 import XEP_0202 +from slixmpp.plugins.xep_0203 import XEP_0203 +from slixmpp.plugins.xep_0221 import XEP_0221 +from slixmpp.plugins.xep_0222 import XEP_0222 +from slixmpp.plugins.xep_0223 import XEP_0223 +from slixmpp.plugins.xep_0224 import XEP_0224 +from slixmpp.plugins.xep_0231 import XEP_0231 +from slixmpp.plugins.xep_0235 import XEP_0235 +from slixmpp.plugins.xep_0249 import XEP_0249 +from slixmpp.plugins.xep_0256 import XEP_0256 +from slixmpp.plugins.xep_0257 import XEP_0257 +from slixmpp.plugins.xep_0258 import XEP_0258 +from slixmpp.plugins.xep_0279 import XEP_0279 +from slixmpp.plugins.xep_0280 import XEP_0280 +from slixmpp.plugins.xep_0297 import XEP_0297 +from slixmpp.plugins.xep_0300 import XEP_0300 +from slixmpp.plugins.xep_0308 import XEP_0308 +from slixmpp.plugins.xep_0313 import XEP_0313 +from slixmpp.plugins.xep_0319 import XEP_0319 +from slixmpp.plugins.xep_0332 import XEP_0332 +from slixmpp.plugins.xep_0333 import XEP_0333 +from slixmpp.plugins.xep_0334 import XEP_0334 +from slixmpp.plugins.xep_0335 import XEP_0335 +from slixmpp.plugins.xep_0352 import XEP_0352 +from slixmpp.plugins.xep_0353 import XEP_0353 +from slixmpp.plugins.xep_0359 import XEP_0359 +from slixmpp.plugins.xep_0363 import XEP_0363 +from slixmpp.plugins.xep_0369 import XEP_0369 +from slixmpp.plugins.xep_0377 import XEP_0377 +from slixmpp.plugins.xep_0380 import XEP_0380 +from slixmpp.plugins.xep_0382 import XEP_0382 +from slixmpp.plugins.xep_0394 import XEP_0394 +from slixmpp.plugins.xep_0403 import XEP_0403 +from slixmpp.plugins.xep_0404 import XEP_0404 +from slixmpp.plugins.xep_0405 import XEP_0405 +from slixmpp.plugins.xep_0421 import XEP_0421 +from slixmpp.plugins.xep_0422 import XEP_0422 +from slixmpp.plugins.xep_0424 import XEP_0424 +from slixmpp.plugins.xep_0425 import XEP_0425 +from slixmpp.plugins.xep_0428 import XEP_0428 +from slixmpp.plugins.xep_0437 import XEP_0437 +from slixmpp.plugins.xep_0439 import XEP_0439 +from slixmpp.plugins.xep_0444 import XEP_0444 + + +class PluginsDict(TypedDict): + xep_0004: XEP_0004 + xep_0009: XEP_0009 + xep_0012: XEP_0012 + xep_0013: XEP_0013 + xep_0020: XEP_0020 + xep_0027: XEP_0027 + xep_0030: XEP_0030 + xep_0033: XEP_0033 + xep_0045: XEP_0045 + xep_0047: XEP_0047 + xep_0049: XEP_0049 + xep_0050: XEP_0050 + xep_0054: XEP_0054 + xep_0059: XEP_0059 + xep_0060: XEP_0060 + xep_0065: XEP_0065 + xep_0066: XEP_0066 + xep_0070: XEP_0070 + xep_0071: XEP_0071 + xep_0077: XEP_0077 + xep_0079: XEP_0079 + xep_0080: XEP_0080 + xep_0082: XEP_0082 + xep_0084: XEP_0084 + xep_0085: XEP_0085 + xep_0086: XEP_0086 + xep_0092: XEP_0092 + xep_0106: XEP_0106 + xep_0107: XEP_0107 + xep_0108: XEP_0108 + xep_0115: XEP_0115 + xep_0118: XEP_0118 + xep_0122: XEP_0122 + xep_0128: XEP_0128 + xep_0131: XEP_0131 + xep_0133: XEP_0133 + xep_0152: XEP_0152 + xep_0153: XEP_0153 + xep_0163: XEP_0163 + xep_0172: XEP_0172 + xep_0184: XEP_0184 + xep_0186: XEP_0186 + xep_0191: XEP_0191 + xep_0196: XEP_0196 + xep_0198: XEP_0198 + xep_0199: XEP_0199 + xep_0202: XEP_0202 + xep_0203: XEP_0203 + xep_0221: XEP_0221 + xep_0222: XEP_0222 + xep_0223: XEP_0223 + xep_0224: XEP_0224 + xep_0231: XEP_0231 + xep_0235: XEP_0235 + xep_0249: XEP_0249 + xep_0256: XEP_0256 + xep_0257: XEP_0257 + xep_0258: XEP_0258 + xep_0279: XEP_0279 + xep_0280: XEP_0280 + xep_0297: XEP_0297 + xep_0300: XEP_0300 + xep_0308: XEP_0308 + xep_0313: XEP_0313 + xep_0319: XEP_0319 + xep_0332: XEP_0332 + xep_0333: XEP_0333 + xep_0334: XEP_0334 + xep_0335: XEP_0335 + xep_0352: XEP_0352 + xep_0353: XEP_0353 + xep_0359: XEP_0359 + xep_0363: XEP_0363 + xep_0369: XEP_0369 + xep_0377: XEP_0377 + xep_0380: XEP_0380 + xep_0382: XEP_0382 + xep_0394: XEP_0394 + xep_0403: XEP_0403 + xep_0404: XEP_0404 + xep_0405: XEP_0405 + xep_0421: XEP_0421 + xep_0422: XEP_0422 + xep_0424: XEP_0424 + xep_0425: XEP_0425 + xep_0428: XEP_0428 + xep_0437: XEP_0437 + xep_0439: XEP_0439 + xep_0444: XEP_0444 diff --git a/slixmpp/types.py b/slixmpp/types.py index c48ed97b..44e24a1e 100644 --- a/slixmpp/types.py +++ b/slixmpp/types.py @@ -4,193 +4,34 @@ # See the file LICENSE for copying permission. """ -This file contains boilerplate to define types relevant to slixmpp, -such as the plugins dict. +This file contains boilerplate to define types relevant to slixmpp. """ try: - from typing import TypedDict + from typing import ( + Literal, + ) except ImportError: - from typing_extensions import TypedDict + from typing_extensions import ( + Literal, + ) -from slixmpp.plugins.xep_0004 import XEP_0004 -from slixmpp.plugins.xep_0009 import XEP_0009 -from slixmpp.plugins.xep_0012 import XEP_0012 -from slixmpp.plugins.xep_0013 import XEP_0013 -from slixmpp.plugins.xep_0020 import XEP_0020 -from slixmpp.plugins.xep_0027 import XEP_0027 -from slixmpp.plugins.xep_0030 import XEP_0030 -from slixmpp.plugins.xep_0033 import XEP_0033 -from slixmpp.plugins.xep_0045 import XEP_0045 -from slixmpp.plugins.xep_0047 import XEP_0047 -from slixmpp.plugins.xep_0049 import XEP_0049 -from slixmpp.plugins.xep_0050 import XEP_0050 -from slixmpp.plugins.xep_0054 import XEP_0054 -from slixmpp.plugins.xep_0059 import XEP_0059 -from slixmpp.plugins.xep_0060 import XEP_0060 -from slixmpp.plugins.xep_0065 import XEP_0065 -from slixmpp.plugins.xep_0066 import XEP_0066 -from slixmpp.plugins.xep_0070 import XEP_0070 -from slixmpp.plugins.xep_0071 import XEP_0071 -from slixmpp.plugins.xep_0077 import XEP_0077 -from slixmpp.plugins.xep_0079 import XEP_0079 -from slixmpp.plugins.xep_0080 import XEP_0080 -from slixmpp.plugins.xep_0082 import XEP_0082 -from slixmpp.plugins.xep_0084 import XEP_0084 -from slixmpp.plugins.xep_0085 import XEP_0085 -from slixmpp.plugins.xep_0086 import XEP_0086 -from slixmpp.plugins.xep_0092 import XEP_0092 -from slixmpp.plugins.xep_0106 import XEP_0106 -from slixmpp.plugins.xep_0107 import XEP_0107 -from slixmpp.plugins.xep_0108 import XEP_0108 -from slixmpp.plugins.xep_0115 import XEP_0115 -from slixmpp.plugins.xep_0118 import XEP_0118 -from slixmpp.plugins.xep_0122 import XEP_0122 -from slixmpp.plugins.xep_0128 import XEP_0128 -from slixmpp.plugins.xep_0131 import XEP_0131 -from slixmpp.plugins.xep_0133 import XEP_0133 -from slixmpp.plugins.xep_0152 import XEP_0152 -from slixmpp.plugins.xep_0153 import XEP_0153 -from slixmpp.plugins.xep_0163 import XEP_0163 -from slixmpp.plugins.xep_0172 import XEP_0172 -from slixmpp.plugins.xep_0184 import XEP_0184 -from slixmpp.plugins.xep_0186 import XEP_0186 -from slixmpp.plugins.xep_0191 import XEP_0191 -from slixmpp.plugins.xep_0196 import XEP_0196 -from slixmpp.plugins.xep_0198 import XEP_0198 -from slixmpp.plugins.xep_0199 import XEP_0199 -from slixmpp.plugins.xep_0202 import XEP_0202 -from slixmpp.plugins.xep_0203 import XEP_0203 -from slixmpp.plugins.xep_0221 import XEP_0221 -from slixmpp.plugins.xep_0222 import XEP_0222 -from slixmpp.plugins.xep_0223 import XEP_0223 -from slixmpp.plugins.xep_0224 import XEP_0224 -from slixmpp.plugins.xep_0231 import XEP_0231 -from slixmpp.plugins.xep_0235 import XEP_0235 -from slixmpp.plugins.xep_0249 import XEP_0249 -from slixmpp.plugins.xep_0256 import XEP_0256 -from slixmpp.plugins.xep_0257 import XEP_0257 -from slixmpp.plugins.xep_0258 import XEP_0258 -from slixmpp.plugins.xep_0279 import XEP_0279 -from slixmpp.plugins.xep_0280 import XEP_0280 -from slixmpp.plugins.xep_0297 import XEP_0297 -from slixmpp.plugins.xep_0300 import XEP_0300 -from slixmpp.plugins.xep_0308 import XEP_0308 -from slixmpp.plugins.xep_0313 import XEP_0313 -from slixmpp.plugins.xep_0319 import XEP_0319 -from slixmpp.plugins.xep_0332 import XEP_0332 -from slixmpp.plugins.xep_0333 import XEP_0333 -from slixmpp.plugins.xep_0334 import XEP_0334 -from slixmpp.plugins.xep_0335 import XEP_0335 -from slixmpp.plugins.xep_0352 import XEP_0352 -from slixmpp.plugins.xep_0353 import XEP_0353 -from slixmpp.plugins.xep_0359 import XEP_0359 -from slixmpp.plugins.xep_0363 import XEP_0363 -from slixmpp.plugins.xep_0369 import XEP_0369 -from slixmpp.plugins.xep_0377 import XEP_0377 -from slixmpp.plugins.xep_0380 import XEP_0380 -from slixmpp.plugins.xep_0382 import XEP_0382 -from slixmpp.plugins.xep_0394 import XEP_0394 -from slixmpp.plugins.xep_0403 import XEP_0403 -from slixmpp.plugins.xep_0404 import XEP_0404 -from slixmpp.plugins.xep_0405 import XEP_0405 -from slixmpp.plugins.xep_0421 import XEP_0421 -from slixmpp.plugins.xep_0422 import XEP_0422 -from slixmpp.plugins.xep_0424 import XEP_0424 -from slixmpp.plugins.xep_0425 import XEP_0425 -from slixmpp.plugins.xep_0428 import XEP_0428 -from slixmpp.plugins.xep_0437 import XEP_0437 -from slixmpp.plugins.xep_0439 import XEP_0439 -from slixmpp.plugins.xep_0444 import XEP_0444 +PresenceTypes = Literal[ + 'error', 'probe', 'subscribe', 'subscribed', + 'unavailable', 'unsubscribe', 'unsubscribed', +] +PresenceShows = Literal[ + 'away', 'chat', 'dnd', 'xa', +] + + +MessageTypes = Literal[ + 'chat', 'error', 'groupchat', + 'headline', 'normal', +] + +IqTypes = Literal[ + "error", "get", "set", "result", +] -class PluginsDict(TypedDict): - xep_0004: XEP_0004 - xep_0009: XEP_0009 - xep_0012: XEP_0012 - xep_0013: XEP_0013 - xep_0020: XEP_0020 - xep_0027: XEP_0027 - xep_0030: XEP_0030 - xep_0033: XEP_0033 - xep_0045: XEP_0045 - xep_0047: XEP_0047 - xep_0049: XEP_0049 - xep_0050: XEP_0050 - xep_0054: XEP_0054 - xep_0059: XEP_0059 - xep_0060: XEP_0060 - xep_0065: XEP_0065 - xep_0066: XEP_0066 - xep_0070: XEP_0070 - xep_0071: XEP_0071 - xep_0077: XEP_0077 - xep_0079: XEP_0079 - xep_0080: XEP_0080 - xep_0082: XEP_0082 - xep_0084: XEP_0084 - xep_0085: XEP_0085 - xep_0086: XEP_0086 - xep_0092: XEP_0092 - xep_0106: XEP_0106 - xep_0107: XEP_0107 - xep_0108: XEP_0108 - xep_0115: XEP_0115 - xep_0118: XEP_0118 - xep_0122: XEP_0122 - xep_0128: XEP_0128 - xep_0131: XEP_0131 - xep_0133: XEP_0133 - xep_0152: XEP_0152 - xep_0153: XEP_0153 - xep_0163: XEP_0163 - xep_0172: XEP_0172 - xep_0184: XEP_0184 - xep_0186: XEP_0186 - xep_0191: XEP_0191 - xep_0196: XEP_0196 - xep_0198: XEP_0198 - xep_0199: XEP_0199 - xep_0202: XEP_0202 - xep_0203: XEP_0203 - xep_0221: XEP_0221 - xep_0222: XEP_0222 - xep_0223: XEP_0223 - xep_0224: XEP_0224 - xep_0231: XEP_0231 - xep_0235: XEP_0235 - xep_0249: XEP_0249 - xep_0256: XEP_0256 - xep_0257: XEP_0257 - xep_0258: XEP_0258 - xep_0279: XEP_0279 - xep_0280: XEP_0280 - xep_0297: XEP_0297 - xep_0300: XEP_0300 - xep_0308: XEP_0308 - xep_0313: XEP_0313 - xep_0319: XEP_0319 - xep_0332: XEP_0332 - xep_0333: XEP_0333 - xep_0334: XEP_0334 - xep_0335: XEP_0335 - xep_0352: XEP_0352 - xep_0353: XEP_0353 - xep_0359: XEP_0359 - xep_0363: XEP_0363 - xep_0369: XEP_0369 - xep_0377: XEP_0377 - xep_0380: XEP_0380 - xep_0382: XEP_0382 - xep_0394: XEP_0394 - xep_0403: XEP_0403 - xep_0404: XEP_0404 - xep_0405: XEP_0405 - xep_0421: XEP_0421 - xep_0422: XEP_0422 - xep_0424: XEP_0424 - xep_0425: XEP_0425 - xep_0428: XEP_0428 - xep_0437: XEP_0437 - xep_0439: XEP_0439 - xep_0444: XEP_0444 -- cgit v1.2.3 From 622cfd4ed73b6fada9b445772ab0805d893f7678 Mon Sep 17 00:00:00 2001 From: mathieui Date: Sat, 6 Feb 2021 12:29:14 +0100 Subject: basexmpp: add more typing, fix some docs --- slixmpp/basexmpp.py | 139 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 92 insertions(+), 47 deletions(-) diff --git a/slixmpp/basexmpp.py b/slixmpp/basexmpp.py index e94883b1..25aa0d75 100644 --- a/slixmpp/basexmpp.py +++ b/slixmpp/basexmpp.py @@ -11,6 +11,9 @@ import asyncio import logging from typing import ( + Dict, + Optional, + Union, TYPE_CHECKING, ) @@ -18,14 +21,22 @@ from slixmpp import plugins, roster, stanza from slixmpp.api import APIRegistry from slixmpp.exceptions import IqError, IqTimeout -from slixmpp.stanza import Message, Presence, Iq, StreamError +from slixmpp.stanza import ( + Message, + Presence, + Iq, + StreamError, +) from slixmpp.stanza.roster import Roster from slixmpp.xmlstream import XMLStream, JID from slixmpp.xmlstream import ET, register_stanza_plugin from slixmpp.xmlstream.matcher import MatchXPath from slixmpp.xmlstream.handler import Callback -from slixmpp.xmlstream.stanzabase import XML_NS +from slixmpp.xmlstream.stanzabase import ( + ElementBase, + XML_NS, +) from slixmpp.plugins import PluginManager, load_plugin @@ -33,8 +44,16 @@ from slixmpp.plugins import PluginManager, load_plugin log = logging.getLogger(__name__) +from slixmpp.types import ( + PresenceShows, + PresenceTypes, + MessageTypes, + IqTypes, +) + if TYPE_CHECKING: - from slixmpp.types import PluginsDict + # Circular imports + from slixmpp.pluginsdict import PluginsDict class BaseXMPP(XMLStream): @@ -229,7 +248,7 @@ class BaseXMPP(XMLStream): self.plugin[name].post_init() self.plugin[name].post_inited = True - def register_plugin(self, plugin, pconfig=None, module=None): + def register_plugin(self, plugin: str, pconfig: Optional[Dict] = None, module=None): """Register and configure a plugin for use in this stream. :param plugin: The name of the plugin class. Plugin names must @@ -279,32 +298,34 @@ class BaseXMPP(XMLStream): """Return a plugin given its name, if it has been registered.""" return self.plugin.get(key, default) - def Message(self, *args, **kwargs): + def Message(self, *args, **kwargs) -> Message: """Create a Message stanza associated with this stream.""" msg = Message(self, *args, **kwargs) msg['lang'] = self.default_lang return msg - def Iq(self, *args, **kwargs): + def Iq(self, *args, **kwargs) -> Iq: """Create an Iq stanza associated with this stream.""" return Iq(self, *args, **kwargs) - def Presence(self, *args, **kwargs): + def Presence(self, *args, **kwargs) -> Presence: """Create a Presence stanza associated with this stream.""" pres = Presence(self, *args, **kwargs) pres['lang'] = self.default_lang return pres - def make_iq(self, id=0, ifrom=None, ito=None, itype=None, iquery=None): - """Create a new Iq stanza with a given Id and from JID. + def make_iq(self, id: str = "0", ifrom: Optional[JID] = None, + ito: Optional[JID] = None, itype: Optional[IqTypes] = None, + iquery: Optional[str] = None) -> Iq: + """Create a new :class:`~.Iq` stanza with a given Id and from JID. :param id: An ideally unique ID value for this stanza thread. Defaults to 0. - :param ifrom: The from :class:`~slixmpp.xmlstream.jid.JID` + :param ifrom: The from :class:`~.JID` to use for this stanza. - :param ito: The destination :class:`~slixmpp.xmlstream.jid.JID` + :param ito: The destination :class:`~.JID` for this stanza. - :param itype: The :class:`~slixmpp.stanza.iq.Iq`'s type, + :param itype: The :class:`~.Iq`'s type, one of: ``'get'``, ``'set'``, ``'result'``, or ``'error'``. :param iquery: Optional namespace for adding a query element. @@ -317,15 +338,17 @@ class BaseXMPP(XMLStream): iq['query'] = iquery return iq - def make_iq_get(self, queryxmlns=None, ito=None, ifrom=None, iq=None): - """Create an :class:`~slixmpp.stanza.iq.Iq` stanza of type ``'get'``. + def make_iq_get(self, queryxmlns: Optional[str] =None, + ito: Optional[JID] = None, ifrom: Optional[JID] = None, + iq: Optional[Iq] = None) -> Iq: + """Create an :class:`~.Iq` stanza of type ``'get'``. Optionally, a query element may be added. :param queryxmlns: The namespace of the query to use. - :param ito: The destination :class:`~slixmpp.xmlstream.jid.JID` + :param ito: The destination :class:`~.JID` for this stanza. - :param ifrom: The ``'from'`` :class:`~slixmpp.xmlstream.jid.JID` + :param ifrom: The ``'from'`` :class:`~.JID` to use for this stanza. :param iq: Optionally use an existing stanza instead of generating a new one. @@ -340,15 +363,17 @@ class BaseXMPP(XMLStream): iq['from'] = ifrom return iq - def make_iq_result(self, id=None, ito=None, ifrom=None, iq=None): + def make_iq_result(self, id: Optional[str] = None, + ito: Optional[JID] = None, ifrom: Optional[JID] = None, + iq: Optional[Iq] = None) -> Iq: """ - Create an :class:`~slixmpp.stanza.iq.Iq` stanza of type + Create an :class:`~.Iq` stanza of type ``'result'`` with the given ID value. :param id: An ideally unique ID value. May use :meth:`new_id()`. - :param ito: The destination :class:`~slixmpp.xmlstream.jid.JID` + :param ito: The destination :class:`~.JID` for this stanza. - :param ifrom: The ``'from'`` :class:`~slixmpp.xmlstream.jid.JID` + :param ifrom: The ``'from'`` :class:`~.JID` to use for this stanza. :param iq: Optionally use an existing stanza instead of generating a new one. @@ -365,21 +390,23 @@ class BaseXMPP(XMLStream): iq['from'] = ifrom return iq - def make_iq_set(self, sub=None, ito=None, ifrom=None, iq=None): + def make_iq_set(self, sub: Optional[Union[ElementBase, ET.Element]] = None, + ito: Optional[JID] = None, ifrom: Optional[JID] = None, + iq: Optional[Iq] = None) -> Iq: """ - Create an :class:`~slixmpp.stanza.iq.Iq` stanza of type ``'set'``. + Create an :class:`~.Iq` stanza of type ``'set'``. Optionally, a substanza may be given to use as the stanza's payload. :param sub: Either an - :class:`~slixmpp.xmlstream.stanzabase.ElementBase` + :class:`~.ElementBase` stanza object or an :class:`~xml.etree.ElementTree.Element` XML object - to use as the :class:`~slixmpp.stanza.iq.Iq`'s payload. - :param ito: The destination :class:`~slixmpp.xmlstream.jid.JID` + to use as the :class:`~.Iq`'s payload. + :param ito: The destination :class:`~.JID` for this stanza. - :param ifrom: The ``'from'`` :class:`~slixmpp.xmlstream.jid.JID` + :param ifrom: The ``'from'`` :class:`~.JID` to use for this stanza. :param iq: Optionally use an existing stanza instead of generating a new one. @@ -399,7 +426,7 @@ class BaseXMPP(XMLStream): condition='feature-not-implemented', text=None, ito=None, ifrom=None, iq=None): """ - Create an :class:`~slixmpp.stanza.iq.Iq` stanza of type ``'error'``. + Create an :class:`~.Iq` stanza of type ``'error'``. :param id: An ideally unique ID value. May use :meth:`new_id()`. :param type: The type of the error, such as ``'cancel'`` or @@ -407,9 +434,9 @@ class BaseXMPP(XMLStream): :param condition: The error condition. Defaults to ``'feature-not-implemented'``. :param text: A message describing the cause of the error. - :param ito: The destination :class:`~slixmpp.xmlstream.jid.JID` + :param ito: The destination :class:`~.JID` for this stanza. - :param ifrom: The ``'from'`` :class:`~slixmpp.xmlstream.jid.JID` + :param ifrom: The ``'from'`` :class:`~jid.JID` to use for this stanza. :param iq: Optionally use an existing stanza instead of generating a new one. @@ -426,17 +453,19 @@ class BaseXMPP(XMLStream): iq['from'] = ifrom return iq - def make_iq_query(self, iq=None, xmlns='', ito=None, ifrom=None): + def make_iq_query(self, iq: Optional[Iq] = None, xmlns: str = '', + ito: Optional[JID] = None, + ifrom: Optional[JID] = None) -> Iq: """ - Create or modify an :class:`~slixmpp.stanza.iq.Iq` stanza + Create or modify an :class:`~.Iq` stanza to use the given query namespace. :param iq: Optionally use an existing stanza instead of generating a new one. :param xmlns: The query's namespace. - :param ito: The destination :class:`~slixmpp.xmlstream.jid.JID` + :param ito: The destination :class:`~.JID` for this stanza. - :param ifrom: The ``'from'`` :class:`~slixmpp.xmlstream.jid.JID` + :param ifrom: The ``'from'`` :class:`~.JID` to use for this stanza. """ if not iq: @@ -448,7 +477,7 @@ class BaseXMPP(XMLStream): iq['from'] = ifrom return iq - def make_query_roster(self, iq=None): + def make_query_roster(self, iq: Optional[Iq] = None) -> ET.Element: """Create a roster query element. :param iq: Optionally use an existing stanza instead @@ -458,11 +487,14 @@ class BaseXMPP(XMLStream): iq['query'] = 'jabber:iq:roster' return ET.Element("{jabber:iq:roster}query") - def make_message(self, mto, mbody=None, msubject=None, mtype=None, - mhtml=None, mfrom=None, mnick=None): + def make_message(self, mto: JID, mbody: Optional[str] = None, + msubject: Optional[str] = None, + mtype: Optional[MessageTypes] = None, + mhtml: Optional[str] = None, mfrom: Optional[JID] = None, + mnick: Optional[str] = None) -> Message: """ Create and initialize a new - :class:`~slixmpp.stanza.message.Message` stanza. + :class:`~.Message` stanza. :param mto: The recipient of the message. :param mbody: The main contents of the message. @@ -484,11 +516,16 @@ class BaseXMPP(XMLStream): message['html']['body'] = mhtml return message - def make_presence(self, pshow=None, pstatus=None, ppriority=None, - pto=None, ptype=None, pfrom=None, pnick=None): + def make_presence(self, pshow: Optional[PresenceShows] = None, + pstatus: Optional[str] = None, + ppriority: Optional[int] = None, + pto: Optional[JID] = None, + ptype: Optional[PresenceTypes] = None, + pfrom: Optional[JID] = None, + pnick: Optional[str] = None) -> Presence: """ Create and initialize a new - :class:`~slixmpp.stanza.presence.Presence` stanza. + :class:`~.Presence` stanza. :param pshow: The presence's show value. :param pstatus: The presence's status message. @@ -508,11 +545,14 @@ class BaseXMPP(XMLStream): presence['nick'] = pnick return presence - def send_message(self, mto, mbody, msubject=None, mtype=None, - mhtml=None, mfrom=None, mnick=None): + def send_message(self, mto: JID, mbody: Optional[str] = None, + msubject: Optional[str] = None, + mtype: Optional[MessageTypes] = None, + mhtml: Optional[str] = None, mfrom: Optional[JID] = None, + mnick: Optional[str] = None): """ Create, initialize, and send a new - :class:`~slixmpp.stanza.message.Message` stanza. + :class:`~.Message` stanza. :param mto: The recipient of the message. :param mbody: The main contents of the message. @@ -528,11 +568,16 @@ class BaseXMPP(XMLStream): self.make_message(mto, mbody, msubject, mtype, mhtml, mfrom, mnick).send() - def send_presence(self, pshow=None, pstatus=None, ppriority=None, - pto=None, pfrom=None, ptype=None, pnick=None): + def send_presence(self, pshow: Optional[PresenceShows] = None, + pstatus: Optional[str] = None, + ppriority: Optional[int] = None, + pto: Optional[JID] = None, + ptype: Optional[PresenceTypes] = None, + pfrom: Optional[JID] = None, + pnick: Optional[str] = None): """ Create, initialize, and send a new - :class:`~slixmpp.stanza.presence.Presence` stanza. + :class:`~.Presence` stanza. :param pshow: The presence's show value. :param pstatus: The presence's status message. @@ -549,7 +594,7 @@ class BaseXMPP(XMLStream): ptype='subscribe', pnick=None): """ Create, initialize, and send a new - :class:`~slixmpp.stanza.presence.Presence` stanza of + :class:`~.Presence` stanza of type ``'subscribe'``. :param pto: The recipient of a directed presence. -- cgit v1.2.3 From 648ca16b4ceb935e837619ecbf34385a1044f7c6 Mon Sep 17 00:00:00 2001 From: mathieui Date: Sat, 6 Feb 2021 12:29:31 +0100 Subject: docs: fill the stanza howto --- docs/howto/stanzas.rst | 401 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 392 insertions(+), 9 deletions(-) diff --git a/docs/howto/stanzas.rst b/docs/howto/stanzas.rst index d52a90d4..56dfce79 100644 --- a/docs/howto/stanzas.rst +++ b/docs/howto/stanzas.rst @@ -3,28 +3,411 @@ How to Work with Stanza Objects =============================== +Slixmpp provides a large variety of facilities for abstracting the underlying +XML payloads of XMPP. Most of the visible user interface comes in a +dict-like interface provided in a specific ``__getitem__`` implementation +for :class:`~slixmpp.xmlstream.ElementBase` objects. + + +As a very high-level example, here is how to create a stanza with +an XEP-0191 payload, assuming the :class:`xep_0191 ` +plugin is loaded: + +.. code-block:: python + + from slixmpp.stanza import Iq + iq = Iq() + iq['to'] = 'toto@example.com' + iq['type'] = 'set' + iq['block']['items'] = {'a@example.com', 'b@example.com'} + +Printing the resulting :class:`~slixmpp.stanaz.Iq` object gives us the +following XML (reformatted for readability): + +.. code-block:: xml + + + + + + + + + +Realistically, users of the Slixmpp library should make use of the shorthand +functions available in their :class:`~.ClientXMPP` or +:class:`~.ComponentXMPP` objects to create :class:`~.Iq`, :class:`~.Message` +or :class:`~.Presence` objects that are bound to a stream, and which have +a generated unique identifier. + +The most relevant functions are: + +.. autofunction:: slixmpp.BaseXMPP.make_iq_get + +.. autofunction:: slixmpp.BaseXMPP.make_iq_set + +.. autofunction:: slixmpp.BaseXMPP.make_message + +.. autofunction:: slixmpp.BaseXMPP.make_presence + +The previous example then becomes: + +.. code-block:: python + + iq = xmpp.make_iq_get(ito='toto@example.com') + iq['block']['items'] = {'a@example.com', 'b@example.com'} + + +.. note:: + + xml:lang is handled by piping the lang name after the attribute. For + example ``message['body|fr']`` will return the ```` attribute + with ``xml:lang="fr``. + +The next sections will try to explain as clearly as possible +how the magic operates. .. _create-stanza-interfaces: Defining Stanza Interfaces -------------------------- +The stanza interface is very rich and let developers have full control +over the API they want to have to manipulate stanzas. -.. _create-stanza-plugins: +The entire interface is defined as class attributes that are redefined +when subclassing :class:`~.ElementBase` when `creating a stanza plugin `_. -Creating Stanza Plugins ------------------------ +The main attributes defining a stanza interface: +- plugin_attrib_: ``str``, the name of this element on the parent +- plugin_multi_attrib_: ``str``, the name of the iterable for this element on the parent +- interfaces_: ``set``, all known interfaces for this element +- sub_interfaces_: ``set`` (subset of ``interfaces``), for sub-elements with only text nodes +- bool_interfaces_: ``set`` (subset of ``interfaces``), for empty-sub-elements +- overrides_: ``list`` (subset of ``interfaces``), for ``interfaces`` to ovverride on the parent +- is_extension_: ``bool``, if the element is only an extension of the parent stanza -.. _create-extension-plugins: +.. _plugin_attrib: -Creating a Stanza Extension ---------------------------- +plugin_attrib +~~~~~~~~~~~~~ +The ``plugin_attrib`` string is the defining element of any stanza plugin, +as it the name through which the element is accessed (except for ``overrides`` +and ``is_extension``). +The extension is then registered through the help of :func:`~.register_stanza_plugin` +which will attach the plugin to its parent. -.. _override-parent-interfaces: +.. code-block:: python -Overriding a Parent Stanza --------------------------- + from slixmpp import ElementBase, Iq + + class Payload(ElementBase): + name = 'apayload' + plugin_attrib = 'mypayload' + namespace = 'x-toto' + + register_stanza_plugin(Iq, Payload) + + iq = Iq() + iq.enable('mypayload') # Similar to iq['mypayload'] + +The :class:`~.Iq` element created now contains our custom ```` element. + +.. code-block:: xml + + + + + + +.. _plugin_multi_attrib: + +plugin_multi_attrib +~~~~~~~~~~~~~~~~~~~ + +The :func:`~.register_stanza_plugin` function has an ``iterable`` parameter, which +defaults to ``False``. When set to ``True``, it means that iterating over the element +is possible. + + +.. code-block:: python + + class Parent(ElementBase): + pass # does not matter + + class Sub(ElementBase): + name = 'sub' + plugin_attrib = 'sub' + + class Sub2(ElementBase): + name = 'sub2' + plugin_attrib = 'sub2' + + register_stanza_plugin(Parent, Sub, iterable=True) + register_stanza_plugin(Parent, Sub2, iterable=True) + + parent = Parent() + parent.append(Sub()) + parent.append(Sub2()) + parent.append(Sub2()) + parent.append(Sub()) + + for element in parent: + do_something # A mix of Sub and Sub2 elements + +In this situation, iterating over ``parent`` will yield each of the appended elements, +one after the other. + +Sometimes you only want one specific type of sub-element, which is the use of +the ``plugin_multi_attrib`` string interface. This name will be mapped on the +parent, just like ``plugin_attrib``, but will return a list of all elements +of the same type only. + +Re-using our previous example: + +.. code-block:: python + + class Parent(ElementBase): + pass # does not matter + + class Sub(ElementBase): + name = 'sub' + plugin_attrib = 'sub' + plugin_multi_attrib = 'subs' + + class Sub2(ElementBase): + name = 'sub2' + plugin_attrib = 'sub2' + plugin_multi_attrib = 'subs2' + + register_stanza_plugin(Parent, Sub, iterable=True) + register_stanza_plugin(Parent, Sub2, iterable=True) + + parent = Parent() + parent.append(Sub()) + parent.append(Sub2()) + parent.append(Sub2()) + parent.append(Sub()) + + for sub in parent['subs']: + do_something # ony Sub objects here + + for sub2 in parent['subs2']: + do_something # ony Sub2 objects here + + +.. _interfaces: + +interfaces +~~~~~~~~~~ + +The ``interfaces`` set **must** contain all the known ways to interact with +this element. It does not include plugins (registered to the element through +:func:`~.register_stanza_plugin`), which are dynamic. + +By default, a name present in ``interfaces`` will be mapped to an attribute +of the element with the same name. + +.. code-block:: python + + class Example(Element): + name = 'example' + interfaces = {'toto'} + + example = Example() + example['toto'] = 'titi' + +In this case, ``example`` contains ````. + +For empty and text_only sub-elements, there are sub_interfaces_ and +bool_interfaces_ (the keys **must** still be in ``interfaces``. + +You can however define any getter, setter, and delete custom method for any of +those interfaces. Keep in mind that if one of the three is not custom, +Slixmpp will use the default one, so you have to make sure that either you +redefine all get/set/del custom methods, or that your custom methods are +compatible with the default ones. + +In the following example, we want the ``toto`` attribute to be an integer. + +.. code-block:: python + + class Example(Element): + interfaces = {'toto', 'titi', 'tata'} + + def get_toto(self) -> Optional[int]: + try: + return int(self.xml.attrib.get('toto', '')) + except ValueError: + return None + + def set_toto(self, value: int): + int(value) # make sure the value is an int + self.xml.attrib['toto'] = str(value) + + example = Example() + example['tata'] = "Test" # works + example['toto'] = 1 # works + print(type(example['toto'])) # the value is an int + example['toto'] = "Test 2" # ValueError + + +One important thing to keep in mind is that the ``get_`` methods must be resilient +(when having a default value makes sense) because they are called on objects +received from the network. + +.. _sub_interfaces: + +sub_interfaces +~~~~~~~~~~~~~~ + +The ``bool_interfaces`` set allows mapping an interface to the text node of +sub-element of the current payload, with the same namespace + +Here is a simple example: + +.. code-block:: python + + class FirstLevel(ElementBase): + name = 'first' + namespace = 'ns' + interfaces = {'second'} + sub_interfaces = {'second'} + + parent = FirstLevel() + parent['second'] = 'Content of second node' + + +Which will produces the following: + +.. code-block:: xml + + + Content of second node + + +We can see that ``sub_interfaces`` allows to quickly create a sub-element and +manipulate its text node without requiring a custom element, getter or setter. + +.. _bool_interfaces: + +bool_interfaces +~~~~~~~~~~~~~~~ + +The ``bool_interfaces`` set allows mapping an interface to a direct sub-element of the +current payload, with the same namespace. + + +Here is a simple example: + +.. code-block:: python + + class FirstLevel(ElementBase): + name = 'first' + namespace = 'ns' + interfaces = {'second'} + bool_interfaces = {'second'} + + parent = FirstLevel() + parent['second'] = True + + +Which will produces the following: + +.. code-block:: xml + + + + + +We can see that ``bool_interfaces`` allows to quickly create sub-elements with no +content, without the need to create a custom class or getter/setter. + +overrides +~~~~~~~~~ + +List of ``interfaces`` on the present element that should override the +parent ``interfaces`` with the same name. + +.. code-block:: python + + class Parent(ElementBase): + name = 'parent' + interfaces = {'toto', 'titi'} + + class Sub(ElementBase): + name = 'sub' + plugin_attrib = name + interfaces = {'toto', 'titi'} + overrides = ['toto'] + + register_stanza_plugin(Parent, Sub) + + parent = Parent() + parent['toto'] = 'test' # equivalent to parent['sub']['toto'] = "test" + +is_extension +~~~~~~~~~~~~ + +Stanza extensions are a specific kind of stanza plugin which have +the ``is_extension`` class attribute set to ``True``. + +The following code will directly plug the extension into the +:class:`~.Message` element, allowing direct access +to the interface: + +.. code-block:: python + + class MyCustomExtension(ElementBase): + is_extension = True + name = 'mycustom' + namespace = 'custom-ns' + plugin_attrib = 'mycustom' + interfaces = {'mycustom'} + + register_stanza_plugin(Message, MyCustomExtension) + +With this extension, we can do the folliowing: + +.. code-block:: python + + message = Message() + message['mycustom'] = 'toto' + +Without the extension, obtaining the same results would be: + +.. code-block:: python + + message = Message() + message['mycustom']['mycustom'] = 'toto' + + +The extension is therefore named extension because it extends the +parent element transparently. + + +.. _create-stanza-plugins: + +Creating Stanza Plugins +----------------------- + +A stanza plugin is a class that inherits from :class:`~.ElementBase`, and +**must** contain at least the following attributes: + +- name: XML element name (e.g. ``toto`` if the element is ```` +- namespace: The XML namespace of the element. +- plugin_attrib_: ``str``, the name of this element on the parent +- interfaces_: ``set``, all known interfaces for this element + +It is then registered through :func:`~.register_stanza_plugin` on the parent +element. + +.. note:: + + :func:`~.register_stanza_plugin` should NOT be called at the module level, + because it executes code, and executing code at the module level can slow + down import significantly! -- cgit v1.2.3