Commit 22fa8bc4 authored by mathieui's avatar mathieui

Merge branch 'more-typing' into 'master'

Add more typing

See merge request !166
parents b1411d8e 5c548065
Pipeline #4028 passed with stages
in 7 minutes and 56 seconds
stages:
- lint
- test
- trigger
mypy:
stage: lint
tags:
- docker
image: python:3
script:
- pip3 install mypy
- mypy slixmpp
test:
stage: test
tags:
......
[mypy]
check_untyped_defs = False
ignore_missing_imports = True
[mypy-slixmpp.types]
ignore_errors = True
[mypy-slixmpp.thirdparty.*]
ignore_errors = True
[mypy-slixmpp.plugins.*]
ignore_errors = True
[mypy-slixmpp.plugins.base]
ignore_errors = False
......@@ -83,6 +83,7 @@ setup(
url='https://lab.louiz.org/poezio/slixmpp',
license='MIT',
platforms=['any'],
package_data={'slixmpp': ['py.typed']},
packages=packages,
ext_modules=ext_modules,
install_requires=['aiodns>=1.0', 'pyasn1', 'pyasn1_modules', 'typing_extensions; python_version < "3.8.0"'],
......
......@@ -19,7 +19,6 @@ from slixmpp.xmlstream.stanzabase import ET, ElementBase, register_stanza_plugin
from slixmpp.xmlstream.handler import *
from slixmpp.xmlstream import XMLStream
from slixmpp.xmlstream.matcher import *
from slixmpp.xmlstream.asyncio import asyncio, future_wrapper
from slixmpp.basexmpp import BaseXMPP
from slixmpp.clientxmpp import ClientXMPP
from slixmpp.componentxmpp import ComponentXMPP
......
......@@ -21,7 +21,7 @@ class APIWrapper(object):
if name not in self.api.settings:
self.api.settings[name] = {}
def __getattr__(self, attr):
def __getattr__(self, attr: str):
"""Curry API management commands with the API name."""
if attr == 'name':
return self.name
......@@ -33,13 +33,13 @@ class APIWrapper(object):
return register(handler, self.name, op, jid, node, default)
return partial
elif attr == 'register_default':
def partial(handler, op, jid=None, node=None):
def partial1(handler, op, jid=None, node=None):
return getattr(self.api, attr)(handler, self.name, op)
return partial
return partial1
elif attr in ('run', 'restore_default', 'unregister'):
def partial(*args, **kwargs):
def partial2(*args, **kwargs):
return getattr(self.api, attr)(self.name, *args, **kwargs)
return partial
return partial2
return None
def __getitem__(self, attr):
......@@ -82,7 +82,7 @@ class APIRegistry(object):
"""Return a wrapper object that targets a specific API."""
return APIWrapper(self, ctype)
def purge(self, ctype: str):
def purge(self, ctype: str) -> None:
"""Remove all information for a given API."""
del self.settings[ctype]
del self._handler_defaults[ctype]
......@@ -131,22 +131,23 @@ class APIRegistry(object):
jid = JID(jid)
elif jid == JID(''):
jid = self.xmpp.boundjid
assert jid is not None
if node is None:
node = ''
if self.xmpp.is_component:
if self.settings[ctype].get('component_bare', False):
jid = jid.bare
jid_str = jid.bare
else:
jid = jid.full
jid_str = jid.full
else:
if self.settings[ctype].get('client_bare', False):
jid = jid.bare
jid_str = jid.bare
else:
jid = jid.full
jid_str = jid.full
jid = JID(jid)
jid = JID(jid_str)
handler = self._handlers[ctype][op]['node'].get((jid, node), None)
if handler is None:
......@@ -167,8 +168,11 @@ class APIRegistry(object):
# To preserve backward compatibility, drop the ifrom
# parameter for existing handlers that don't understand it.
return handler(jid, node, args)
future = Future()
future.set_result(None)
return future
def register(self, handler: APIHandler, ctype: str, op: str,
def register(self, handler: Optional[APIHandler], ctype: str, op: str,
jid: Optional[JID] = None, node: Optional[str] = None,
default: bool = False):
"""Register an API callback, with JID+node specificity.
......
......@@ -45,10 +45,11 @@ log = logging.getLogger(__name__)
from slixmpp.types import (
PresenceShows,
PresenceTypes,
MessageTypes,
IqTypes,
JidStr,
OptJidStr,
)
if TYPE_CHECKING:
......@@ -263,9 +264,9 @@ class BaseXMPP(XMLStream):
if not pconfig:
pconfig = self.plugin_config.get(plugin, {})
if not self.plugin.registered(plugin):
if not self.plugin.registered(plugin): # type: ignore
load_plugin(plugin, module)
self.plugin.enable(plugin, pconfig)
self.plugin.enable(plugin, pconfig) # type: ignore
def register_plugins(self):
"""Register and initialize all built-in plugins.
......@@ -298,25 +299,25 @@ 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) -> Message:
def Message(self, *args, **kwargs) -> stanza.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) -> Iq:
def Iq(self, *args, **kwargs) -> stanza.Iq:
"""Create an Iq stanza associated with this stream."""
return Iq(self, *args, **kwargs)
def Presence(self, *args, **kwargs) -> Presence:
def Presence(self, *args, **kwargs) -> stanza.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: str = "0", ifrom: Optional[JID] = None,
ito: Optional[JID] = None, itype: Optional[IqTypes] = None,
iquery: Optional[str] = None) -> Iq:
def make_iq(self, id: str = "0", ifrom: OptJidStr = None,
ito: OptJidStr = None, itype: Optional[IqTypes] = None,
iquery: Optional[str] = None) -> stanza.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.
......@@ -339,8 +340,8 @@ class BaseXMPP(XMLStream):
return iq
def make_iq_get(self, queryxmlns: Optional[str] =None,
ito: Optional[JID] = None, ifrom: Optional[JID] = None,
iq: Optional[Iq] = None) -> Iq:
ito: OptJidStr = None, ifrom: OptJidStr = None,
iq: Optional[stanza.Iq] = None) -> stanza.Iq:
"""Create an :class:`~.Iq` stanza of type ``'get'``.
Optionally, a query element may be added.
......@@ -364,8 +365,8 @@ class BaseXMPP(XMLStream):
return iq
def make_iq_result(self, id: Optional[str] = None,
ito: Optional[JID] = None, ifrom: Optional[JID] = None,
iq: Optional[Iq] = None) -> Iq:
ito: OptJidStr = None, ifrom: OptJidStr = None,
iq: Optional[stanza.Iq] = None) -> stanza.Iq:
"""
Create an :class:`~.Iq` stanza of type
``'result'`` with the given ID value.
......@@ -391,8 +392,8 @@ class BaseXMPP(XMLStream):
return iq
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:
ito: OptJidStr = None, ifrom: OptJidStr = None,
iq: Optional[stanza.Iq] = None) -> stanza.Iq:
"""
Create an :class:`~.Iq` stanza of type ``'set'``.
......@@ -414,7 +415,7 @@ class BaseXMPP(XMLStream):
if not iq:
iq = self.Iq()
iq['type'] = 'set'
if sub != None:
if sub is not None:
iq.append(sub)
if ito:
iq['to'] = ito
......@@ -453,9 +454,9 @@ class BaseXMPP(XMLStream):
iq['from'] = ifrom
return iq
def make_iq_query(self, iq: Optional[Iq] = None, xmlns: str = '',
ito: Optional[JID] = None,
ifrom: Optional[JID] = None) -> Iq:
def make_iq_query(self, iq: Optional[stanza.Iq] = None, xmlns: str = '',
ito: OptJidStr = None,
ifrom: OptJidStr = None) -> stanza.Iq:
"""
Create or modify an :class:`~.Iq` stanza
to use the given query namespace.
......@@ -477,7 +478,7 @@ class BaseXMPP(XMLStream):
iq['from'] = ifrom
return iq
def make_query_roster(self, iq: Optional[Iq] = None) -> ET.Element:
def make_query_roster(self, iq: Optional[stanza.Iq] = None) -> ET.Element:
"""Create a roster query element.
:param iq: Optionally use an existing stanza instead
......@@ -487,11 +488,11 @@ class BaseXMPP(XMLStream):
iq['query'] = 'jabber:iq:roster'
return ET.Element("{jabber:iq:roster}query")
def make_message(self, mto: JID, mbody: Optional[str] = None,
def make_message(self, mto: JidStr, 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:
mhtml: Optional[str] = None, mfrom: OptJidStr = None,
mnick: Optional[str] = None) -> stanza.Message:
"""
Create and initialize a new
:class:`~.Message` stanza.
......@@ -516,13 +517,13 @@ class BaseXMPP(XMLStream):
message['html']['body'] = mhtml
return message
def make_presence(self, pshow: Optional[PresenceShows] = None,
def make_presence(self, pshow: Optional[str] = None,
pstatus: Optional[str] = None,
ppriority: Optional[int] = None,
pto: Optional[JID] = None,
pto: OptJidStr = None,
ptype: Optional[PresenceTypes] = None,
pfrom: Optional[JID] = None,
pnick: Optional[str] = None) -> Presence:
pfrom: OptJidStr = None,
pnick: Optional[str] = None) -> stanza.Presence:
"""
Create and initialize a new
:class:`~.Presence` stanza.
......@@ -548,7 +549,7 @@ class BaseXMPP(XMLStream):
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,
mhtml: Optional[str] = None, mfrom: OptJidStr = None,
mnick: Optional[str] = None):
"""
Create, initialize, and send a new
......@@ -568,12 +569,12 @@ class BaseXMPP(XMLStream):
self.make_message(mto, mbody, msubject, mtype,
mhtml, mfrom, mnick).send()
def send_presence(self, pshow: Optional[PresenceShows] = None,
def send_presence(self, pshow: Optional[str] = None,
pstatus: Optional[str] = None,
ppriority: Optional[int] = None,
pto: Optional[JID] = None,
pto: OptJidStr = None,
ptype: Optional[PresenceTypes] = None,
pfrom: Optional[JID] = None,
pfrom: OptJidStr = None,
pnick: Optional[str] = None):
"""
Create, initialize, and send a new
......@@ -590,8 +591,9 @@ class BaseXMPP(XMLStream):
self.make_presence(pshow, pstatus, ppriority, pto,
ptype, pfrom, pnick).send()
def send_presence_subscription(self, pto, pfrom=None,
ptype='subscribe', pnick=None):
def send_presence_subscription(self, pto: JidStr, pfrom: OptJidStr = None,
ptype: PresenceTypes='subscribe', pnick:
Optional[str] = None):
"""
Create, initialize, and send a new
:class:`~.Presence` stanza of
......@@ -608,62 +610,62 @@ class BaseXMPP(XMLStream):
pnick=pnick).send()
@property
def jid(self):
def jid(self) -> str:
"""Attribute accessor for bare jid"""
log.warning("jid property deprecated. Use boundjid.bare")
return self.boundjid.bare
@jid.setter
def jid(self, value):
def jid(self, value: str):
log.warning("jid property deprecated. Use boundjid.bare")
self.boundjid.bare = value
@property
def fulljid(self):
def fulljid(self) -> str:
"""Attribute accessor for full jid"""
log.warning("fulljid property deprecated. Use boundjid.full")
return self.boundjid.full
@fulljid.setter
def fulljid(self, value):
def fulljid(self, value: str):
log.warning("fulljid property deprecated. Use boundjid.full")
self.boundjid.full = value
@property
def resource(self):
def resource(self) -> str:
"""Attribute accessor for jid resource"""
log.warning("resource property deprecated. Use boundjid.resource")
return self.boundjid.resource
@resource.setter
def resource(self, value):
def resource(self, value: str):
log.warning("fulljid property deprecated. Use boundjid.resource")
self.boundjid.resource = value
@property
def username(self):
def username(self) -> str:
"""Attribute accessor for jid usernode"""
log.warning("username property deprecated. Use boundjid.user")
return self.boundjid.user
@username.setter
def username(self, value):
def username(self, value: str):
log.warning("username property deprecated. Use boundjid.user")
self.boundjid.user = value
@property
def server(self):
def server(self) -> str:
"""Attribute accessor for jid host"""
log.warning("server property deprecated. Use boundjid.host")
return self.boundjid.server
@server.setter
def server(self, value):
def server(self, value: str):
log.warning("server property deprecated. Use boundjid.host")
self.boundjid.server = value
@property
def auto_authorize(self):
def auto_authorize(self) -> Optional[bool]:
"""Auto accept or deny subscription requests.
If ``True``, auto accept subscription requests.
......@@ -673,11 +675,11 @@ class BaseXMPP(XMLStream):
return self.roster.auto_authorize
@auto_authorize.setter
def auto_authorize(self, value):
def auto_authorize(self, value: Optional[bool]):
self.roster.auto_authorize = value
@property
def auto_subscribe(self):
def auto_subscribe(self) -> bool:
"""Auto send requests for mutual subscriptions.
If ``True``, auto send mutual subscription requests.
......@@ -685,21 +687,21 @@ class BaseXMPP(XMLStream):
return self.roster.auto_subscribe
@auto_subscribe.setter
def auto_subscribe(self, value):
def auto_subscribe(self, value: bool):
self.roster.auto_subscribe = value
def set_jid(self, jid):
def set_jid(self, jid: JidStr):
"""Rip a JID apart and claim it as our own."""
log.debug("setting jid to %s", jid)
self.boundjid = JID(jid)
def getjidresource(self, fulljid):
def getjidresource(self, fulljid: str):
if '/' in fulljid:
return fulljid.split('/', 1)[-1]
else:
return ''
def getjidbare(self, fulljid):
def getjidbare(self, fulljid: str):
return fulljid.split('/', 1)[0]
def _handle_session_start(self, event):
......
......@@ -8,23 +8,18 @@
# :license: MIT, see LICENSE for more details
import asyncio
import logging
from typing import Optional, Any, Callable, Tuple, Dict, Set, List
from slixmpp.jid import JID
from slixmpp.stanza import StreamFeatures
from slixmpp.stanza import StreamFeatures, Iq
from slixmpp.basexmpp import BaseXMPP
from slixmpp.exceptions import XMPPError
from slixmpp.types import JidStr
from slixmpp.xmlstream import XMLStream
from slixmpp.xmlstream.stanzabase import StanzaBase
from slixmpp.xmlstream.matcher import StanzaPath, MatchXPath
from slixmpp.xmlstream.handler import Callback, CoroutineCallback
# Flag indicating if DNS SRV records are available for use.
try:
import dns.resolver
except ImportError:
DNSPYTHON = False
else:
DNSPYTHON = True
log = logging.getLogger(__name__)
......@@ -53,7 +48,7 @@ class ClientXMPP(BaseXMPP):
:param escape_quotes: **Deprecated.**
"""
def __init__(self, jid, password, plugin_config=None,
def __init__(self, jid: JidStr, password: str, plugin_config=None,
plugin_whitelist=None, escape_quotes=True, sasl_mech=None,
lang='en', **kwargs):
if not plugin_whitelist:
......@@ -69,7 +64,7 @@ class ClientXMPP(BaseXMPP):
self.default_port = 5222
self.default_lang = lang
self.credentials = {}
self.credentials: Dict[str, str] = {}
self.password = password
......@@ -81,9 +76,9 @@ class ClientXMPP(BaseXMPP):
"version='1.0'")
self.stream_footer = "</stream:stream>"
self.features = set()
self._stream_feature_handlers = {}
self._stream_feature_order = []
self.features: Set[str] = set()
self._stream_feature_handlers: Dict[str, Tuple[Callable, bool]] = {}
self._stream_feature_order: List[Tuple[int, str]] = []
self.dns_service = 'xmpp-client'
......@@ -100,10 +95,14 @@ class ClientXMPP(BaseXMPP):
self.register_stanza(StreamFeatures)
self.register_handler(
CoroutineCallback('Stream Features',
MatchXPath('{%s}features' % self.stream_ns),
self._handle_stream_features))
def roster_push_filter(iq):
CoroutineCallback(
'Stream Features',
MatchXPath('{%s}features' % self.stream_ns),
self._handle_stream_features, # type: ignore
)
)
def roster_push_filter(iq: StanzaBase) -> None:
from_ = iq['from']
if from_ and from_ != JID('') and from_ != self.boundjid.bare:
reply = iq.reply()
......@@ -131,15 +130,16 @@ class ClientXMPP(BaseXMPP):
self['feature_mechanisms'].use_mech = sasl_mech
@property
def password(self):
def password(self) -> str:
return self.credentials.get('password', '')
@password.setter
def password(self, value):
def password(self, value: str) -> None:
self.credentials['password'] = value
def connect(self, address=tuple(), use_ssl=False,
force_starttls=True, disable_starttls=False):
def connect(self, address: Optional[Tuple[str, int]] = None, # type: ignore
use_ssl: bool = False, force_starttls: bool = True,
disable_starttls: bool = False) -> None:
"""Connect to the XMPP server.
When no address is given, a SRV lookup for the server will
......@@ -161,14 +161,15 @@ class ClientXMPP(BaseXMPP):
# XMPP client port and allow SRV lookup.
if address:
self.dns_service = None
host, port = address
else:
address = (self.boundjid.host, 5222)
host, port = (self.boundjid.host, 5222)
self.dns_service = 'xmpp-client'
return XMLStream.connect(self, address[0], address[1], use_ssl=use_ssl,
return XMLStream.connect(self, host, port, use_ssl=use_ssl,
force_starttls=force_starttls, disable_starttls=disable_starttls)
def register_feature(self, name, handler, restart=False, order=5000):
def register_feature(self, name: str, handler: Callable, restart: bool = False, order: int = 5000) -> None:
"""Register a stream feature handler.
:param name: The name of the stream feature.
......@@ -183,13 +184,13 @@ class ClientXMPP(BaseXMPP):
self._stream_feature_order.append((order, name))
self._stream_feature_order.sort()
def unregister_feature(self, name, order):
def unregister_feature(self, name: str, order: int) -> None:
if name in self._stream_feature_handlers:
del self._stream_feature_handlers[name]
self._stream_feature_order.remove((order, name))
self._stream_feature_order.sort()
def update_roster(self, jid, **kwargs):
def update_roster(self, jid: JID, **kwargs) -> None:
"""Add or change a roster item.
:param jid: The JID of the entry to modify.
......@@ -251,7 +252,7 @@ class ClientXMPP(BaseXMPP):
return iq.send(callback, timeout, timeout_callback)
def _reset_connection_state(self, event=None):
def _reset_connection_state(self, event: Optional[Any] = None) -> None:
#TODO: Use stream state here
self.authenticated = False
self.sessionstarted = False
......@@ -259,7 +260,7 @@ class ClientXMPP(BaseXMPP):
self.bindfail = False
self.features = set()
async def _handle_stream_features(self, features):
async def _handle_stream_features(self, features: StreamFeatures) -> Optional[bool]:
"""Process the received stream features.
:param features: The features stanza.
......@@ -277,8 +278,9 @@ class ClientXMPP(BaseXMPP):
return True
log.debug('Finished processing stream features.')
self.event('stream_negotiated')
return None
def _handle_roster(self, iq):
def _handle_roster(self, iq: Iq) -> None:
"""Update the roster after receiving a roster stanza.
:param iq: The roster stanza.
......@@ -310,7 +312,7 @@ class ClientXMPP(BaseXMPP):
resp.enable('roster')
resp.send()
def _handle_session_bind(self, jid):
def _handle_session_bind(self, jid: JID) -> None:
"""Set the client roster to the JID set by the server.
:param :class:`slixmpp.xmlstream.jid.JID` jid: The bound JID as
......
# Slixmpp: The Slick XMPP Library
# Copyright (C) 2011 Nathanael C. Fritz
# This file is part of Slixmpp.
......@@ -11,6 +10,7 @@ from slixmpp.stanza import Iq, StreamFeatures
from slixmpp.features.feature_bind import stanza
from slixmpp.xmlstream import register_stanza_plugin
from slixmpp.plugins import BasePlugin
from typing import ClassVar, Set
log = logging.getLogger(__name__)
......@@ -20,7 +20,7 @@ class FeatureBind(BasePlugin):