Annotate api/zulip/__init__.py.
Note that we still can't run mypy against this file and other files, because of how the interface is dynamically created via _register. We will need to change that or use a stub file to make it possible to annotate this. This was tweaked by tabbott to fix some bugs.
This commit is contained in:
parent
2e2b8af9fd
commit
25a8315f71
|
@ -38,8 +38,7 @@ from six.moves.configparser import SafeConfigParser
|
||||||
from six.moves import urllib
|
from six.moves import urllib
|
||||||
import logging
|
import logging
|
||||||
import six
|
import six
|
||||||
from typing import Any, Dict
|
from typing import Any, Callable, Dict, Mapping, Optional, Tuple, Union
|
||||||
|
|
||||||
|
|
||||||
__version__ = "0.2.5"
|
__version__ = "0.2.5"
|
||||||
|
|
||||||
|
@ -47,7 +46,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Check that we have a recent enough version
|
# Check that we have a recent enough version
|
||||||
# Older versions don't provide the 'json' attribute on responses.
|
# Older versions don't provide the 'json' attribute on responses.
|
||||||
assert(LooseVersion(requests.__version__) >= LooseVersion('0.12.1')) # type: ignore # https://github.com/python/mypy/issues/1165 and https://github.com/python/typeshed/pull/206
|
assert(LooseVersion(requests.__version__) >= LooseVersion('0.12.1'))
|
||||||
# In newer versions, the 'json' attribute is a function, not a property
|
# In newer versions, the 'json' attribute is a function, not a property
|
||||||
requests_json_is_function = callable(requests.Response.json)
|
requests_json_is_function = callable(requests.Response.json)
|
||||||
|
|
||||||
|
@ -55,26 +54,31 @@ API_VERSTRING = "v1/"
|
||||||
|
|
||||||
class CountingBackoff(object):
|
class CountingBackoff(object):
|
||||||
def __init__(self, maximum_retries=10, timeout_success_equivalent=None):
|
def __init__(self, maximum_retries=10, timeout_success_equivalent=None):
|
||||||
|
# type: (int, Optional[float]) -> None
|
||||||
self.number_of_retries = 0
|
self.number_of_retries = 0
|
||||||
self.maximum_retries = maximum_retries
|
self.maximum_retries = maximum_retries
|
||||||
self.timeout_success_equivalent = timeout_success_equivalent
|
self.timeout_success_equivalent = timeout_success_equivalent
|
||||||
self.last_attempt_time = 0
|
self.last_attempt_time = 0.0
|
||||||
|
|
||||||
def keep_going(self):
|
def keep_going(self):
|
||||||
|
# type: () -> bool
|
||||||
self._check_success_timeout()
|
self._check_success_timeout()
|
||||||
return self.number_of_retries < self.maximum_retries
|
return self.number_of_retries < self.maximum_retries
|
||||||
|
|
||||||
def succeed(self):
|
def succeed(self):
|
||||||
|
# type: () -> None
|
||||||
self.number_of_retries = 0
|
self.number_of_retries = 0
|
||||||
self.last_attempt_time = time.time()
|
self.last_attempt_time = time.time()
|
||||||
|
|
||||||
def fail(self):
|
def fail(self):
|
||||||
|
# type: () -> None
|
||||||
self._check_success_timeout()
|
self._check_success_timeout()
|
||||||
self.number_of_retries = min(self.number_of_retries + 1,
|
self.number_of_retries = min(self.number_of_retries + 1,
|
||||||
self.maximum_retries)
|
self.maximum_retries)
|
||||||
self.last_attempt_time = time.time()
|
self.last_attempt_time = time.time()
|
||||||
|
|
||||||
def _check_success_timeout(self):
|
def _check_success_timeout(self):
|
||||||
|
# type: () -> None
|
||||||
if (self.timeout_success_equivalent is not None
|
if (self.timeout_success_equivalent is not None
|
||||||
and self.last_attempt_time != 0
|
and self.last_attempt_time != 0
|
||||||
and time.time() - self.last_attempt_time > self.timeout_success_equivalent):
|
and time.time() - self.last_attempt_time > self.timeout_success_equivalent):
|
||||||
|
@ -82,6 +86,7 @@ class CountingBackoff(object):
|
||||||
|
|
||||||
class RandomExponentialBackoff(CountingBackoff):
|
class RandomExponentialBackoff(CountingBackoff):
|
||||||
def fail(self):
|
def fail(self):
|
||||||
|
# type: () -> None
|
||||||
super(RandomExponentialBackoff, self).fail()
|
super(RandomExponentialBackoff, self).fail()
|
||||||
# Exponential growth with ratio sqrt(2); compute random delay
|
# Exponential growth with ratio sqrt(2); compute random delay
|
||||||
# between x and 2x where x is growing exponentially
|
# between x and 2x where x is growing exponentially
|
||||||
|
@ -95,9 +100,11 @@ class RandomExponentialBackoff(CountingBackoff):
|
||||||
time.sleep(delay)
|
time.sleep(delay)
|
||||||
|
|
||||||
def _default_client():
|
def _default_client():
|
||||||
|
# type: () -> str
|
||||||
return "ZulipPython/" + __version__
|
return "ZulipPython/" + __version__
|
||||||
|
|
||||||
def generate_option_group(parser, prefix=''):
|
def generate_option_group(parser, prefix=''):
|
||||||
|
# type: (optparse.OptionParser, str) -> optparse.OptionGroup
|
||||||
group = optparse.OptionGroup(parser, 'Zulip API configuration')
|
group = optparse.OptionGroup(parser, 'Zulip API configuration')
|
||||||
group.add_option('--%ssite' % (prefix,),
|
group.add_option('--%ssite' % (prefix,),
|
||||||
dest="zulip_site",
|
dest="zulip_site",
|
||||||
|
@ -148,6 +155,7 @@ def generate_option_group(parser, prefix=''):
|
||||||
return group
|
return group
|
||||||
|
|
||||||
def init_from_options(options, client=None):
|
def init_from_options(options, client=None):
|
||||||
|
# type: (Any, Optional[str]) -> Client
|
||||||
if options.zulip_client is not None:
|
if options.zulip_client is not None:
|
||||||
client = options.zulip_client
|
client = options.zulip_client
|
||||||
elif client is None:
|
elif client is None:
|
||||||
|
@ -160,6 +168,7 @@ def init_from_options(options, client=None):
|
||||||
client_cert_key=options.client_cert_key)
|
client_cert_key=options.client_cert_key)
|
||||||
|
|
||||||
def get_default_config_filename():
|
def get_default_config_filename():
|
||||||
|
# type: () -> str
|
||||||
config_file = os.path.join(os.environ["HOME"], ".zuliprc")
|
config_file = os.path.join(os.environ["HOME"], ".zuliprc")
|
||||||
if (not os.path.exists(config_file) and
|
if (not os.path.exists(config_file) and
|
||||||
os.path.exists(os.path.join(os.environ["HOME"], ".humbugrc"))):
|
os.path.exists(os.path.join(os.environ["HOME"], ".humbugrc"))):
|
||||||
|
@ -173,6 +182,7 @@ class Client(object):
|
||||||
site=None, client=None,
|
site=None, client=None,
|
||||||
cert_bundle=None, insecure=None,
|
cert_bundle=None, insecure=None,
|
||||||
client_cert=None, client_cert_key=None):
|
client_cert=None, client_cert_key=None):
|
||||||
|
# type: (Optional[str], Optional[str], Optional[str], bool, bool, Optional[str], Optional[str], Optional[str], bool, Optional[str], Optional[str]) -> None
|
||||||
if client is None:
|
if client is None:
|
||||||
client = _default_client()
|
client = _default_client()
|
||||||
|
|
||||||
|
@ -230,7 +240,7 @@ class Client(object):
|
||||||
self.client_name = client
|
self.client_name = client
|
||||||
|
|
||||||
if insecure:
|
if insecure:
|
||||||
self.tls_verification = False
|
self.tls_verification = False # type: Union[bool, str]
|
||||||
elif cert_bundle is not None:
|
elif cert_bundle is not None:
|
||||||
if not os.path.isfile(cert_bundle):
|
if not os.path.isfile(cert_bundle):
|
||||||
raise RuntimeError("tls bundle '%s' does not exist"
|
raise RuntimeError("tls bundle '%s' does not exist"
|
||||||
|
@ -256,6 +266,7 @@ class Client(object):
|
||||||
self.client_cert_key = client_cert_key
|
self.client_cert_key = client_cert_key
|
||||||
|
|
||||||
def get_user_agent(self):
|
def get_user_agent(self):
|
||||||
|
# type: () -> str
|
||||||
vendor = ''
|
vendor = ''
|
||||||
vendor_version = ''
|
vendor_version = ''
|
||||||
try:
|
try:
|
||||||
|
@ -280,6 +291,7 @@ class Client(object):
|
||||||
)
|
)
|
||||||
|
|
||||||
def do_api_query(self, orig_request, url, method="POST", longpolling = False):
|
def do_api_query(self, orig_request, url, method="POST", longpolling = False):
|
||||||
|
# type: (Mapping[str, Any], str, str, bool) -> Dict[str, Any]
|
||||||
request = {}
|
request = {}
|
||||||
|
|
||||||
for (key, val) in six.iteritems(orig_request):
|
for (key, val) in six.iteritems(orig_request):
|
||||||
|
@ -295,6 +307,7 @@ class Client(object):
|
||||||
} # type: Dict[str, Any]
|
} # type: Dict[str, Any]
|
||||||
|
|
||||||
def error_retry(error_string):
|
def error_retry(error_string):
|
||||||
|
# type: (str) -> bool
|
||||||
if not self.retry_on_errors or query_state["failures"] >= 10:
|
if not self.retry_on_errors or query_state["failures"] >= 10:
|
||||||
return False
|
return False
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
|
@ -311,6 +324,7 @@ class Client(object):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def end_error_retry(succeeded):
|
def end_error_retry(succeeded):
|
||||||
|
# type: (bool) -> None
|
||||||
if query_state["had_error_retry"] and self.verbose:
|
if query_state["had_error_retry"] and self.verbose:
|
||||||
if succeeded:
|
if succeeded:
|
||||||
print("Success!")
|
print("Success!")
|
||||||
|
@ -327,7 +341,7 @@ class Client(object):
|
||||||
|
|
||||||
# Build a client cert object for requests
|
# Build a client cert object for requests
|
||||||
if self.client_cert_key is not None:
|
if self.client_cert_key is not None:
|
||||||
client_cert = (self.client_cert, self.client_cert_key)
|
client_cert = (self.client_cert, self.client_cert_key) # type: Union[str, Tuple[str, str]]
|
||||||
else:
|
else:
|
||||||
client_cert = self.client_cert
|
client_cert = self.client_cert
|
||||||
|
|
||||||
|
@ -391,15 +405,15 @@ class Client(object):
|
||||||
@classmethod
|
@classmethod
|
||||||
def _register(cls, name, url=None, make_request=None,
|
def _register(cls, name, url=None, make_request=None,
|
||||||
method="POST", computed_url=None, **query_kwargs):
|
method="POST", computed_url=None, **query_kwargs):
|
||||||
|
# type: (Any, str, Optional[Callable], str, Optional[Callable], **Any) -> Any
|
||||||
if url is None:
|
if url is None:
|
||||||
url = name
|
url = name
|
||||||
if make_request is None:
|
if make_request is None:
|
||||||
def make_request(request=None):
|
def make_request(**kwargs):
|
||||||
if request is None:
|
# type: (**Any) -> Any
|
||||||
request = {}
|
return kwargs.get("request", {})
|
||||||
return request
|
|
||||||
|
|
||||||
def call(self, *args, **kwargs):
|
def call(self, *args, **kwargs):
|
||||||
|
# type: (*Any, **Any) -> str
|
||||||
request = make_request(*args, **kwargs)
|
request = make_request(*args, **kwargs)
|
||||||
if computed_url is not None:
|
if computed_url is not None:
|
||||||
req_url = computed_url(request)
|
req_url = computed_url(request)
|
||||||
|
@ -410,15 +424,16 @@ class Client(object):
|
||||||
setattr(cls, name, call)
|
setattr(cls, name, call)
|
||||||
|
|
||||||
def call_on_each_event(self, callback, event_types=None, narrow=None):
|
def call_on_each_event(self, callback, event_types=None, narrow=None):
|
||||||
|
# type: (Callable, Optional[List[str]], Any) -> None
|
||||||
if narrow is None:
|
if narrow is None:
|
||||||
narrow = []
|
narrow = []
|
||||||
|
|
||||||
def do_register():
|
def do_register():
|
||||||
|
# type: () -> Tuple[str, int]
|
||||||
while True:
|
while True:
|
||||||
if event_types is None:
|
if event_types is None:
|
||||||
res = self.register()
|
res = self.register() # type: ignore
|
||||||
else:
|
else:
|
||||||
res = self.register(event_types=event_types, narrow=narrow)
|
res = self.register(event_types=event_types, narrow=narrow) # type: ignore
|
||||||
|
|
||||||
if 'error' in res.get('result'):
|
if 'error' in res.get('result'):
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
|
@ -432,7 +447,7 @@ class Client(object):
|
||||||
if queue_id is None:
|
if queue_id is None:
|
||||||
(queue_id, last_event_id) = do_register()
|
(queue_id, last_event_id) = do_register()
|
||||||
|
|
||||||
res = self.get_events(queue_id=queue_id, last_event_id=last_event_id)
|
res = self.get_events(queue_id=queue_id, last_event_id=last_event_id) # type: ignore
|
||||||
if 'error' in res.get('result'):
|
if 'error' in res.get('result'):
|
||||||
if res["result"] == "http-error":
|
if res["result"] == "http-error":
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
|
@ -463,24 +478,29 @@ class Client(object):
|
||||||
callback(event)
|
callback(event)
|
||||||
|
|
||||||
def call_on_each_message(self, callback):
|
def call_on_each_message(self, callback):
|
||||||
|
# type: (Callable) -> None
|
||||||
def event_callback(event):
|
def event_callback(event):
|
||||||
|
# type: (Dict[str, str]) -> None
|
||||||
if event['type'] == 'message':
|
if event['type'] == 'message':
|
||||||
callback(event['message'])
|
callback(event['message'])
|
||||||
|
|
||||||
self.call_on_each_event(event_callback, ['message'])
|
self.call_on_each_event(event_callback, ['message'])
|
||||||
|
|
||||||
def _mk_subs(streams, **kwargs):
|
def _mk_subs(streams, **kwargs):
|
||||||
|
# type: (str, **Any ) -> Dict[str, str]
|
||||||
result = kwargs
|
result = kwargs
|
||||||
result['subscriptions'] = streams
|
result['subscriptions'] = streams
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _mk_rm_subs(streams):
|
def _mk_rm_subs(streams):
|
||||||
|
# type: (str) -> Dict[str, str]
|
||||||
return {'delete': streams}
|
return {'delete': streams}
|
||||||
|
|
||||||
def _mk_deregister(queue_id):
|
def _mk_deregister(queue_id):
|
||||||
|
# type: (str) -> Dict[str, str]
|
||||||
return {'queue_id': queue_id}
|
return {'queue_id': queue_id}
|
||||||
|
|
||||||
def _mk_events(event_types=None, narrow=None):
|
def _mk_events(event_types=None, narrow=None):
|
||||||
|
# type: (Any, List[None]) -> Dict[Any, List[None]]
|
||||||
if event_types is None:
|
if event_types is None:
|
||||||
return dict()
|
return dict()
|
||||||
if narrow is None:
|
if narrow is None:
|
||||||
|
@ -488,6 +508,7 @@ def _mk_events(event_types=None, narrow=None):
|
||||||
return dict(event_types=event_types, narrow=narrow)
|
return dict(event_types=event_types, narrow=narrow)
|
||||||
|
|
||||||
def _kwargs_to_dict(**kwargs):
|
def _kwargs_to_dict(**kwargs):
|
||||||
|
# type: (**Any) -> Any
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
class ZulipStream(object):
|
class ZulipStream(object):
|
||||||
|
@ -496,19 +517,22 @@ class ZulipStream(object):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, type, to, subject, **kwargs):
|
def __init__(self, type, to, subject, **kwargs):
|
||||||
|
# type: (str, str, str, **Any) -> None
|
||||||
self.client = Client(**kwargs)
|
self.client = Client(**kwargs)
|
||||||
self.type = type
|
self.type = type
|
||||||
self.to = to
|
self.to = to
|
||||||
self.subject = subject
|
self.subject = subject
|
||||||
|
|
||||||
def write(self, content):
|
def write(self, content):
|
||||||
|
# type: (str) -> None
|
||||||
message = {"type": self.type,
|
message = {"type": self.type,
|
||||||
"to": self.to,
|
"to": self.to,
|
||||||
"subject": self.subject,
|
"subject": self.subject,
|
||||||
"content": content}
|
"content": content}
|
||||||
self.client.send_message(message)
|
self.client.send_message(message) # type: ignore
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
|
# type: () -> None
|
||||||
pass
|
pass
|
||||||
|
|
||||||
Client._register('send_message', url='messages', make_request=(lambda request: request))
|
Client._register('send_message', url='messages', make_request=(lambda request: request))
|
||||||
|
|
Loading…
Reference in a new issue