Simplify, document, and fix the API code.

We used to create endpoints with Client._register.

Now we now have explicit methods for the endpoints.

This allows us to add docstrings and stricter mypy annotations.

This fix also introduces a call_endpoint() method that avoids
the need for manually building urls with API_VERSTRING when you
know the URL pattern of the endpoint you want to hit (and when
the API doesn't have a convenient wrapper).

I fixed a bug with create_users where it now uses PUT instead
of POST.

I also removed client.export(), which was just broken.

I had to change recent-messages and zulip-export, which were
using client.do_api_query and Client._register.

Now it's easier to just call client.call_endpoint() for
situations where our API doesn't have convenient wrappers,
so that's what I did with those scripts.
This commit is contained in:
Steve Howell 2016-12-17 12:19:15 -08:00 committed by Tim Abbott
parent 1b58c13d91
commit 14ee40bf52
2 changed files with 198 additions and 64 deletions

View file

@ -35,7 +35,7 @@ Example: recent-messages --count=101 --user=username@example.com --api-key=a0b1c
You can omit --user and --api-key arguments if you have a properly set up ~/.zuliprc You can omit --user and --api-key arguments if you have a properly set up ~/.zuliprc
""" """
sys.path.append(os.path.join(os.path.dirname(__file__), '..')) sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
import zulip import zulip
parser = optparse.OptionParser(usage=usage) parser = optparse.OptionParser(usage=usage)
@ -45,7 +45,7 @@ parser.add_option_group(zulip.generate_option_group(parser))
client = zulip.init_from_options(options) client = zulip.init_from_options(options)
req = { request = {
'narrow': [["stream", "Denmark"]], 'narrow': [["stream", "Denmark"]],
'num_before': options.count, 'num_before': options.count,
'num_after': 0, 'num_after': 0,
@ -53,7 +53,12 @@ req = {
'apply_markdown': False 'apply_markdown': False
} }
old_messages = client.do_api_query(req, zulip.API_VERSTRING + 'messages', method='GET') old_messages = client.call_endpoint(
url='messages',
method='GET',
request=request,
)
if 'messages' in old_messages: if 'messages' in old_messages:
for message in old_messages['messages']: for message in old_messages['messages']:
print(json.dumps(message, indent=4)) print(json.dumps(message, indent=4))

View file

@ -38,7 +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, Callable, Dict, Mapping, Optional, Tuple, Union from typing import Any, Callable, Dict, Mapping, Optional, Tuple, Union, Iterable
__version__ = "0.2.5" __version__ = "0.2.5"
@ -402,26 +402,11 @@ class Client(object):
return {'msg': "Unexpected error from the server", "result": "http-error", return {'msg': "Unexpected error from the server", "result": "http-error",
"status_code": res.status_code} "status_code": res.status_code}
@classmethod def call_endpoint(self, url=None, method="POST", request=None, longpolling=False):
def _register(cls, name, url=None, make_request=None, # type: (str, str, Dict[str, Any], bool) -> Dict[str, Any]
method="POST", computed_url=None, **query_kwargs): if request is None:
# type: (Any, str, Optional[Callable], str, Optional[Callable], **Any) -> Any request = dict()
if url is None: return self.do_api_query(request, API_VERSTRING + url, method=method)
url = name
if make_request is None:
def make_request(**kwargs):
# type: (**Any) -> Any
return kwargs.get("request", {})
def call(self, *args, **kwargs):
# type: (*Any, **Any) -> str
request = make_request(*args, **kwargs)
if computed_url is not None:
req_url = computed_url(request)
else:
req_url = url
return self.do_api_query(request, API_VERSTRING + req_url, method=method, **query_kwargs)
call.__name__ = name
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 # type: (Callable, Optional[List[str]], Any) -> None
@ -485,31 +470,193 @@ class Client(object):
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 send_message(self, message_data):
# type: (str, **Any ) -> Dict[str, str] # type: (Dict[str, Any]) -> Dict[str, Any]
result = kwargs '''
result['subscriptions'] = streams See api/examples/send-message for example usage.
return result '''
return self.call_endpoint(
url='messages',
request=message_data,
)
def _mk_rm_subs(streams): def update_message(self, message_data):
# type: (str) -> Dict[str, str] # type: (Dict[str, Any]) -> Dict[str, Any]
return {'delete': streams} '''
See api/examples/edit-message for example usage.
'''
return self.call_endpoint(
url='messages',
method='PATCH',
request=message_data,
)
def _mk_deregister(queue_id): def get_events(self, **request):
# type: (str) -> Dict[str, str] # type: (**Any) -> Dict[str, Any]
return {'queue_id': queue_id} '''
See the register() method for example usage.
'''
return self.call_endpoint(
url='events',
method='GET',
longpolling=True,
request=request,
)
def register(self, event_types=None, narrow=None, **kwargs):
# type: (Iterable[str], Any, **Any) -> Dict[str, Any]
'''
Example usage:
>>> client.register(['message'])
{u'msg': u'', u'max_message_id': 112, u'last_event_id': -1, u'result': u'success', u'queue_id': u'1482093786:2'}
>>> client.get_events(queue_id='1482093786:2', last_event_id=0)
{...}
'''
def _mk_events(event_types=None, narrow=None):
# type: (Any, List[None]) -> Dict[Any, List[None]]
if event_types is None:
return dict()
if narrow is None: if narrow is None:
narrow = [] narrow = []
return dict(event_types=event_types, narrow=narrow)
def _kwargs_to_dict(**kwargs): request = dict(
# type: (**Any) -> Any event_types=event_types,
return kwargs narrow=narrow,
**kwargs
)
return self.call_endpoint(
url='register',
request=request,
)
def deregister(self, queue_id):
# type: (str) -> Dict[str, Any]
'''
Example usage:
>>> client.register(['message'])
{u'msg': u'', u'max_message_id': 113, u'last_event_id': -1, u'result': u'success', u'queue_id': u'1482093786:3'}
>>> client.deregister('1482093786:3')
{u'msg': u'', u'result': u'success'}
'''
request = dict(queue_id=queue_id)
return self.call_endpoint(
url="events",
method="DELETE",
request=request,
)
def get_profile(self, request=None):
# type: (Dict[str, Any]) -> Dict[str, Any]
'''
Example usage:
>>> client.get_profile()
{u'user_id': 5, u'full_name': u'Iago', u'short_name': u'iago', ...}
'''
return self.call_endpoint(
url='users/me',
method='GET',
request=request,
)
def get_streams(self, **request):
# type: (**Any) -> Dict[str, Any]
'''
See api/examples/get-public-streams for example usage.
'''
return self.call_endpoint(
url='streams',
method='GET',
request=request,
)
def get_members(self, request=None):
# type: (Dict[str, Any]) -> Dict[str, Any]
'''
See api/examples/list-members for example usage.
'''
return self.call_endpoint(
url='users',
method='GET',
request=request,
)
def list_subscriptions(self, request=None):
# type: (Dict[str, Any]) -> Dict[str, Any]
'''
See api/examples/list-subscriptions for example usage.
'''
return self.call_endpoint(
url='users/me/subscriptions',
method='GET',
request=request,
)
def add_subscriptions(self, streams, **kwargs):
# type: (Iterable[Dict[str, Any]], **Any) -> Dict[str, Any]
'''
See api/examples/subscribe for example usage.
'''
request = dict(
subscriptions=streams,
**kwargs
)
return self.call_endpoint(
url='users/me/subscriptions',
request=request,
)
def remove_subscriptions(self, streams):
# type: (Iterable[str]) -> Dict[str, Any]
'''
See api/examples/unsubscribe for example usage.
'''
request = dict(delete=streams)
return self.call_endpoint(
url='users/me/subscriptions',
method='PATCH',
request=request,
)
def get_subscribers(self, **request):
# type: (**Any) -> Dict[str, Any]
'''
Example usage: client.get_subscribers(stream='devel')
'''
stream = urllib.parse.quote(request['stream'], safe='')
url = 'streams/%s/members' % (stream,)
return self.call_endpoint(
url=url,
method='GET',
request=request,
)
def render_message(self, request=None):
# type: (Dict[str, Any]) -> Dict[str, Any]
'''
Example usage:
>>> client.render_message(request=dict(content='foo **bar**'))
{u'msg': u'', u'rendered': u'<p>foo <strong>bar</strong></p>', u'result': u'success'}
'''
return self.call_endpoint(
url='messages/render',
method='GET',
request=request,
)
def create_user(self, request=None):
# type: (Dict[str, Any]) -> Dict[str, Any]
'''
See api/examples/create-user for example usage.
'''
return self.call_endpoint(
method='PUT',
url='users',
request=request,
)
class ZulipStream(object): class ZulipStream(object):
""" """
@ -534,21 +681,3 @@ class ZulipStream(object):
def flush(self): def flush(self):
# type: () -> None # type: () -> None
pass pass
Client._register('send_message', url='messages', make_request=(lambda request: request))
Client._register('update_message', method='PATCH', url='messages', make_request=(lambda request: request))
Client._register('get_events', url='events', method='GET', longpolling=True, make_request=(lambda **kwargs: kwargs))
Client._register('register', make_request=_mk_events)
Client._register('export', method='GET', url='export')
Client._register('deregister', url="events", method="DELETE", make_request=_mk_deregister)
Client._register('get_profile', method='GET', url='users/me')
Client._register('get_streams', method='GET', url='streams', make_request=_kwargs_to_dict)
Client._register('get_members', method='GET', url='users')
Client._register('list_subscriptions', method='GET', url='users/me/subscriptions')
Client._register('add_subscriptions', url='users/me/subscriptions', make_request=_mk_subs)
Client._register('remove_subscriptions', method='PATCH', url='users/me/subscriptions', make_request=_mk_rm_subs)
Client._register('get_subscribers', method='GET',
computed_url=lambda request: 'streams/%s/members' % (urllib.parse.quote(request['stream'], safe=''),),
make_request=_kwargs_to_dict)
Client._register('render_message', method='GET', url='messages/render')
Client._register('create_user', method='POST', url='users')