diff --git a/swh/web/admin/adminurls.py b/swh/web/admin/adminurls.py --- a/swh/web/admin/adminurls.py +++ b/swh/web/admin/adminurls.py @@ -14,25 +14,22 @@ scope = 'admin' -class admin_route(object): # noqa: N801 +def admin_route(*url_patterns, view_name=None): """ Decorator to ease the registration of a swh-web admin endpoint Args: - url_patterns: list of url patterns used by Django to identify the admin routes - view_name: the name of the Django view associated to the routes used to - reverse the url - """ # noqa - - def __init__(self, *url_patterns, view_name=None): - super().__init__() - self.url_patterns = [] - for url_pattern in url_patterns: - self.url_patterns.append('^' + url_pattern + '$') - self.view_name = view_name + url_patterns: list of url patterns used by Django to identify the + admin routes + view_name: the name of the Django view associated to the routes used + to reverse the url + """ + url_patterns = ['^' + url_pattern + '$' for url_pattern in url_patterns] - def __call__(self, f): + def decorator(f): # register the route and its view in the browse endpoints index - for url_pattern in self.url_patterns: - AdminUrls.add_url_pattern(url_pattern, f, self.view_name) + for url_pattern in url_patterns: + AdminUrls.add_url_pattern(url_pattern, f, view_name) return f + + return decorator diff --git a/swh/web/api/apidoc.py b/swh/web/api/apidoc.py --- a/swh/web/api/apidoc.py +++ b/swh/web/api/apidoc.py @@ -245,7 +245,8 @@ """ -class api_doc(object): # noqa: N801 +def api_doc(route, noargs=False, need_params=False, tags=[], + handle_response=False, api_version='1'): """ Decorate an API function to register it in the API doc route index and create the corresponding DRF route. @@ -270,99 +271,95 @@ api_version (str): api version string """ - def __init__(self, route, noargs=False, need_params=False, tags=[], - handle_response=False, api_version='1'): - super().__init__() - self.route = route - self.urlpattern = '^' + api_version + route + '$' - self.noargs = noargs - self.need_params = need_params - self.tags = set(tags) - self.handle_response = handle_response + urlpattern = '^' + api_version + route + '$' + tags = set(tags) # @api_doc() Decorator call - def __call__(self, f): + def decorator(f): # If the route is not hidden, add it to the index - if 'hidden' not in self.tags: - doc_data = self.get_doc_data(f) + if 'hidden' not in tags: + doc_data = get_doc_data(f, route, noargs) doc_desc = doc_data['description'] first_dot_pos = doc_desc.find('.') - APIUrls.add_route(self.route, doc_desc[:first_dot_pos+1], - tags=self.tags) + APIUrls.add_route(route, doc_desc[:first_dot_pos+1], + tags=tags) # If the decorated route has arguments, we create a specific # documentation view - if not self.noargs: + if not noargs: @api_view(['GET', 'HEAD']) def doc_view(request): - doc_data = self.get_doc_data(f) + doc_data = get_doc_data(f, route, noargs) return make_api_response(request, None, doc_data) - view_name = 'api-%s' % self.route[1:-1].replace('/', '-') - APIUrls.add_url_pattern(self.urlpattern, doc_view, view_name) + view_name = 'api-%s' % route[1:-1].replace('/', '-') + APIUrls.add_url_pattern(urlpattern, doc_view, view_name) @wraps(f) def documented_view(request, **kwargs): - doc_data = self.get_doc_data(f) + doc_data = get_doc_data(f, route, noargs) try: response = f(request, **kwargs) except Exception as exc: if request.accepted_media_type == 'text/html' and \ - self.need_params and not request.query_params: + need_params and not request.query_params: response = None else: return error_response(request, exc, doc_data) - if self.handle_response: + if handle_response: return response else: return make_api_response(request, response, doc_data) return documented_view - @functools.lru_cache(maxsize=32) - def get_doc_data(self, f): - """ - Build documentation data for the decorated api endpoint function - """ - data = { - 'description': '', - 'response_data': None, - 'urls': [], - 'args': [], - 'params': [], - 'resheaders': [], - 'reqheaders': [], - 'return_type': '', - 'returns': [], - 'status_codes': [], - 'examples': [], - 'route': self.route, - 'noargs': self.noargs - } - - if not f.__doc__: - raise APIDocException('apidoc %s: expected a docstring' - ' for function %s' - % (self.__class__.__name__, f.__name__)) - - # use raw docstring as endpoint documentation if sphinx - # httpdomain is not used - if '.. http' not in f.__doc__: - data['description'] = f.__doc__ - # else parse the sphinx httpdomain docstring with docutils - # (except when building the swh-web documentation through autodoc - # sphinx extension, not needed and raise errors with sphinx >= 1.7) - elif 'SWH_WEB_DOC_BUILD' not in os.environ: - _parse_httpdomain_doc(f.__doc__, data) - # process returned object info for nicer html display - returns_list = '' - for ret in data['returns']: - returns_list += '\t* **%s (%s)**: %s\n' %\ - (ret['name'], ret['type'], ret['doc']) - data['returns_list'] = returns_list - - return data + return decorator + + +@functools.lru_cache(maxsize=32) +def get_doc_data(f, route, noargs): + """ + Build documentation data for the decorated api endpoint function + """ + data = { + 'description': '', + 'response_data': None, + 'urls': [], + 'args': [], + 'params': [], + 'resheaders': [], + 'reqheaders': [], + 'return_type': '', + 'returns': [], + 'status_codes': [], + 'examples': [], + 'route': route, + 'noargs': noargs + } + + if not f.__doc__: + raise APIDocException('apidoc: expected a docstring' + ' for function %s' + % (f.__name__,)) + + # use raw docstring as endpoint documentation if sphinx + # httpdomain is not used + if '.. http' not in f.__doc__: + data['description'] = f.__doc__ + # else parse the sphinx httpdomain docstring with docutils + # (except when building the swh-web documentation through autodoc + # sphinx extension, not needed and raise errors with sphinx >= 1.7) + elif 'SWH_WEB_DOC_BUILD' not in os.environ: + _parse_httpdomain_doc(f.__doc__, data) + # process returned object info for nicer html display + returns_list = '' + for ret in data['returns']: + returns_list += '\t* **%s (%s)**: %s\n' %\ + (ret['name'], ret['type'], ret['doc']) + data['returns_list'] = returns_list + + return data diff --git a/swh/web/api/apiurls.py b/swh/web/api/apiurls.py --- a/swh/web/api/apiurls.py +++ b/swh/web/api/apiurls.py @@ -6,7 +6,7 @@ from rest_framework.decorators import api_view from swh.web.common.urlsindex import UrlsIndex -from swh.web.common.throttling import throttle_scope +from swh.web.common import throttling class APIUrls(UrlsIndex): @@ -40,7 +40,11 @@ cls._apidoc_routes[route] = d -class api_route(object): # noqa: N801 +def api_route(url_pattern=None, view_name=None, + methods=['GET', 'HEAD', 'OPTIONS'], + throttle_scope='swh_api', + api_version='1', + checksum_args=None): """ Decorator to ease the registration of an API endpoint using the Django REST Framework. @@ -53,34 +57,26 @@ """ - def __init__(self, url_pattern=None, view_name=None, - methods=['GET', 'HEAD', 'OPTIONS'], - throttle_scope='swh_api', - api_version='1', - checksum_args=None): - super().__init__() - self.url_pattern = '^' + api_version + url_pattern + '$' - self.view_name = view_name - self.methods = methods - self.throttle_scope = throttle_scope - self.checksum_args = checksum_args - - def __call__(self, f): + url_pattern = '^' + api_version + url_pattern + '$' + + def decorator(f): # create a DRF view from the wrapped function - @api_view(self.methods) - @throttle_scope(self.throttle_scope) + @api_view(methods) + @throttling.throttle_scope(throttle_scope) def api_view_f(*args, **kwargs): return f(*args, **kwargs) # small hacks for correctly generating API endpoints index doc api_view_f.__name__ = f.__name__ - api_view_f.http_method_names = self.methods + api_view_f.http_method_names = methods # register the route and its view in the endpoints index - APIUrls.add_url_pattern(self.url_pattern, api_view_f, - self.view_name) + APIUrls.add_url_pattern(url_pattern, api_view_f, + view_name) - if self.checksum_args: - APIUrls.add_redirect_for_checksum_args(self.view_name, - [self.url_pattern], - self.checksum_args) + if checksum_args: + APIUrls.add_redirect_for_checksum_args(view_name, + [url_pattern], + checksum_args) return f + + return decorator diff --git a/swh/web/browse/browseurls.py b/swh/web/browse/browseurls.py --- a/swh/web/browse/browseurls.py +++ b/swh/web/browse/browseurls.py @@ -14,32 +14,29 @@ scope = 'browse' -class browse_route(object): # noqa: N801 +def browse_route(*url_patterns, view_name=None, checksum_args=None): """ Decorator to ease the registration of a swh-web browse endpoint Args: - url_patterns: list of url patterns used by Django to identify the browse routes - view_name: the name of the Django view associated to the routes used to - reverse the url - """ # noqa - - def __init__(self, *url_patterns, view_name=None, checksum_args=None): - super().__init__() - self.url_patterns = [] - self.checksum_args = checksum_args - for url_pattern in url_patterns: - self.url_patterns.append('^' + url_pattern + '$') - self.view_name = view_name + url_patterns: list of url patterns used by Django to identify the + browse routes + view_name: the name of the Django view associated to the routes used + to reverse the url + """ + url_patterns = ['^' + url_pattern + '$' for url_pattern in url_patterns] + view_name = view_name - def __call__(self, f): + def decorator(f): # register the route and its view in the browse endpoints index - for url_pattern in self.url_patterns: - BrowseUrls.add_url_pattern(url_pattern, f, self.view_name) + for url_pattern in url_patterns: + BrowseUrls.add_url_pattern(url_pattern, f, view_name) - if self.checksum_args: - BrowseUrls.add_redirect_for_checksum_args(self.view_name, - self.url_patterns, - self.checksum_args) + if checksum_args: + BrowseUrls.add_redirect_for_checksum_args(view_name, + url_patterns, + checksum_args) return f + + return decorator