api: add get_method API call.
- in addition use it's mechanics to propose users other methods if the calling method is not found.
This commit is contained in:
parent
a2de6540c6
commit
6568814f98
4 changed files with 171 additions and 5 deletions
|
|
@ -22,6 +22,7 @@ import inspect
|
|||
import itertools
|
||||
import logging
|
||||
import types
|
||||
import fnmatch
|
||||
|
||||
import decorator
|
||||
import venusian
|
||||
|
|
@ -47,6 +48,18 @@ DEFAULT_RENDERER = 'jsonrpc_renderer'
|
|||
DEFAULT_URL = '/_admin/apiv2'
|
||||
|
||||
|
||||
def find_methods(jsonrpc_methods, pattern):
|
||||
matches = OrderedDict()
|
||||
if not isinstance(pattern, (list, tuple)):
|
||||
pattern = [pattern]
|
||||
|
||||
for single_pattern in pattern:
|
||||
for method_name, method in jsonrpc_methods.items():
|
||||
if fnmatch.fnmatch(method_name, single_pattern):
|
||||
matches[method_name] = method
|
||||
return matches
|
||||
|
||||
|
||||
class ExtJsonRenderer(object):
|
||||
"""
|
||||
Custom renderer that mkaes use of our ext_json lib
|
||||
|
|
@ -143,7 +156,19 @@ def exception_view(exc, request):
|
|||
log.debug('json-rpc method `%s` not found in list of '
|
||||
'api calls: %s, rpc_id:%s',
|
||||
method, request.registry.jsonrpc_methods.keys(), rpc_id)
|
||||
fault_message = "No such method: {}".format(method)
|
||||
|
||||
similar = 'none'
|
||||
try:
|
||||
similar_paterns = ['*{}*'.format(x) for x in method.split('_')]
|
||||
similar_found = find_methods(
|
||||
request.registry.jsonrpc_methods, similar_paterns)
|
||||
similar = ', '.join(similar_found.keys()) or similar
|
||||
except Exception:
|
||||
# make the whole above block safe
|
||||
pass
|
||||
|
||||
fault_message = "No such method: {}. Similar methods: {}".format(
|
||||
method, similar)
|
||||
|
||||
return jsonrpc_error(request, fault_message, rpc_id)
|
||||
|
||||
|
|
@ -348,9 +373,10 @@ class RoutePredicate(object):
|
|||
class NotFoundPredicate(object):
|
||||
def __init__(self, val, config):
|
||||
self.val = val
|
||||
self.methods = config.registry.jsonrpc_methods
|
||||
|
||||
def text(self):
|
||||
return 'jsonrpc method not found = %s' % self.val
|
||||
return 'jsonrpc method not found = {}.'.format(self.val)
|
||||
|
||||
phash = text
|
||||
|
||||
|
|
|
|||
|
|
@ -80,7 +80,13 @@ class TestApi(object):
|
|||
def test_api_non_existing_method(self, request):
|
||||
id_, params = build_data(self.apikey, 'not_existing', args='xx')
|
||||
response = api_call(self.app, params)
|
||||
expected = 'No such method: not_existing'
|
||||
expected = 'No such method: not_existing. Similar methods: none'
|
||||
assert_error(id_, expected, given=response.body)
|
||||
|
||||
def test_api_non_existing_method_have_similar(self, request):
|
||||
id_, params = build_data(self.apikey, 'comment', args='xx')
|
||||
response = api_call(self.app, params)
|
||||
expected = 'No such method: comment. Similar methods: changeset_comment, comment_pull_request, comment_commit'
|
||||
assert_error(id_, expected, given=response.body)
|
||||
|
||||
def test_api_disabled_user(self, request):
|
||||
|
|
|
|||
58
rhodecode/api/tests/test_get_method.py
Normal file
58
rhodecode/api/tests/test_get_method.py
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2010-2017 RhodeCode GmbH
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License, version 3
|
||||
# (only), as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# This program is dual-licensed. If you wish to learn more about the
|
||||
# RhodeCode Enterprise Edition, including its added features, Support services,
|
||||
# and proprietary license terms, please see https://rhodecode.com/licenses/
|
||||
|
||||
|
||||
import pytest
|
||||
|
||||
from rhodecode.api.tests.utils import build_data, api_call, assert_ok
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("testuser_api", "app")
|
||||
class TestGetMethod(object):
|
||||
def test_get_methods_no_matches(self):
|
||||
id_, params = build_data(self.apikey, 'get_method', pattern='hello')
|
||||
response = api_call(self.app, params)
|
||||
|
||||
expected = []
|
||||
assert_ok(id_, expected, given=response.body)
|
||||
|
||||
def test_get_methods(self):
|
||||
id_, params = build_data(self.apikey, 'get_method', pattern='*comment*')
|
||||
response = api_call(self.app, params)
|
||||
|
||||
expected = ['changeset_comment', 'comment_pull_request',
|
||||
'comment_commit']
|
||||
assert_ok(id_, expected, given=response.body)
|
||||
|
||||
def test_get_methods_on_single_match(self):
|
||||
id_, params = build_data(self.apikey, 'get_method', pattern='*comment_commit*')
|
||||
response = api_call(self.app, params)
|
||||
|
||||
expected = ['comment_commit',
|
||||
{'apiuser': '<RequiredType>',
|
||||
'comment_type': "<Optional:u'note'>",
|
||||
'commit_id': '<RequiredType>',
|
||||
'message': '<RequiredType>',
|
||||
'repoid': '<RequiredType>',
|
||||
'request': '<RequiredType>',
|
||||
'resolves_comment_id': '<Optional:None>',
|
||||
'status': '<Optional:None>',
|
||||
'userid': '<Optional:<OptionalAttr:apiuser>>'}]
|
||||
assert_ok(id_, expected, given=response.body)
|
||||
|
|
@ -18,10 +18,12 @@
|
|||
# RhodeCode Enterprise Edition, including its added features, Support services,
|
||||
# and proprietary license terms, please see https://rhodecode.com/licenses/
|
||||
|
||||
|
||||
import inspect
|
||||
import logging
|
||||
import itertools
|
||||
|
||||
from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCForbidden
|
||||
from rhodecode.api import (
|
||||
jsonrpc_method, JSONRPCError, JSONRPCForbidden, find_methods)
|
||||
|
||||
from rhodecode.api.utils import (
|
||||
Optional, OAttr, has_superadmin_permission, get_user_or_error)
|
||||
|
|
@ -243,3 +245,77 @@ def cleanup_sessions(request, apiuser, older_then=Optional(60)):
|
|||
raise JSONRPCError(
|
||||
'Error occurred during session cleanup'
|
||||
)
|
||||
|
||||
|
||||
@jsonrpc_method()
|
||||
def get_method(request, apiuser, pattern=Optional('*')):
|
||||
"""
|
||||
Returns list of all available API methods. By default match pattern
|
||||
os "*" but any other pattern can be specified. eg *comment* will return
|
||||
all methods with comment inside them. If just single method is matched
|
||||
returned data will also include method specification
|
||||
|
||||
This command can only be run using an |authtoken| with admin rights to
|
||||
the specified repository.
|
||||
|
||||
This command takes the following options:
|
||||
|
||||
:param apiuser: This is filled automatically from the |authtoken|.
|
||||
:type apiuser: AuthUser
|
||||
:param pattern: pattern to match method names against
|
||||
:type older_then: Optional("*")
|
||||
|
||||
Example output:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
id : <id_given_in_input>
|
||||
"result": [
|
||||
"changeset_comment",
|
||||
"comment_pull_request",
|
||||
"comment_commit"
|
||||
]
|
||||
error : null
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
id : <id_given_in_input>
|
||||
"result": [
|
||||
"comment_commit",
|
||||
{
|
||||
"apiuser": "<RequiredType>",
|
||||
"comment_type": "<Optional:u'note'>",
|
||||
"commit_id": "<RequiredType>",
|
||||
"message": "<RequiredType>",
|
||||
"repoid": "<RequiredType>",
|
||||
"request": "<RequiredType>",
|
||||
"resolves_comment_id": "<Optional:None>",
|
||||
"status": "<Optional:None>",
|
||||
"userid": "<Optional:<OptionalAttr:apiuser>>"
|
||||
}
|
||||
]
|
||||
error : null
|
||||
"""
|
||||
if not has_superadmin_permission(apiuser):
|
||||
raise JSONRPCForbidden()
|
||||
|
||||
pattern = Optional.extract(pattern)
|
||||
|
||||
matches = find_methods(request.registry.jsonrpc_methods, pattern)
|
||||
|
||||
args_desc = []
|
||||
if len(matches) == 1:
|
||||
func = matches[matches.keys()[0]]
|
||||
|
||||
argspec = inspect.getargspec(func)
|
||||
arglist = argspec[0]
|
||||
defaults = map(repr, argspec[3] or [])
|
||||
|
||||
default_empty = '<RequiredType>'
|
||||
|
||||
# kw arguments required by this method
|
||||
func_kwargs = dict(itertools.izip_longest(
|
||||
reversed(arglist), reversed(defaults), fillvalue=default_empty))
|
||||
args_desc.append(func_kwargs)
|
||||
|
||||
return matches.keys() + args_desc
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue