| DELETE | Yes | delete | 204 |
+--------------+------------+----------------+-------------+
+To use a custom endpoint for the above listed methods, you can
+use ``@RESTController.MethodMap``
+
+.. code-block:: python
+
+ import cherrypy
+ from ..tools import ApiController, RESTController
+
+ @RESTController.MethodMap(version='0.1')
+ def create(self):
+ return {"msg": "Hello"}
+
+This decorator supports three parameters to customize the
+endpoint:
+
+* ``resource"``: resource id.
+* ``status=200``: set the HTTP status response code
+* ``version``: version
+
How to use a custom API endpoint in a RESTController?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def some_post_endpoint(self, **data):
return {"msg": data}
-Both decorators also support four parameters to customize the
+Both decorators also support five parameters to customize the
endpoint:
* ``method="GET"``: the HTTP method allowed to access this endpoint.
* ``status=200``: set the HTTP status response code
* ``query_params=[]``: list of method parameter names that correspond to URL
query parameters.
+* ``version``: version
How to restrict access to a controller?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
_method_mapping = collections.OrderedDict([
- ('list', {'method': 'GET', 'resource': False, 'status': 200}),
- ('create', {'method': 'POST', 'resource': False, 'status': 201}),
- ('bulk_set', {'method': 'PUT', 'resource': False, 'status': 200}),
- ('bulk_delete', {'method': 'DELETE', 'resource': False, 'status': 204}),
- ('get', {'method': 'GET', 'resource': True, 'status': 200}),
- ('delete', {'method': 'DELETE', 'resource': True, 'status': 204}),
- ('set', {'method': 'PUT', 'resource': True, 'status': 200}),
- ('singleton_set', {'method': 'PUT', 'resource': False, 'status': 200})
+ ('list', {'method': 'GET', 'resource': False, 'status': 200, 'version': DEFAULT_VERSION}),
+ ('create', {'method': 'POST', 'resource': False, 'status': 201, 'version': DEFAULT_VERSION}), # noqa E501 #pylint: disable=line-too-long
+ ('bulk_set', {'method': 'PUT', 'resource': False, 'status': 200, 'version': DEFAULT_VERSION}), # noqa E501 #pylint: disable=line-too-long
+ ('bulk_delete', {'method': 'DELETE', 'resource': False, 'status': 204, 'version': DEFAULT_VERSION}), # noqa E501 #pylint: disable=line-too-long
+ ('get', {'method': 'GET', 'resource': True, 'status': 200, 'version': DEFAULT_VERSION}),
+ ('delete', {'method': 'DELETE', 'resource': True, 'status': 204, 'version': DEFAULT_VERSION}), # noqa E501 #pylint: disable=line-too-long
+ ('set', {'method': 'PUT', 'resource': True, 'status': 200, 'version': DEFAULT_VERSION}),
+ ('singleton_set', {'method': 'PUT', 'resource': False, 'status': 200, 'version': DEFAULT_VERSION}) # noqa E501 #pylint: disable=line-too-long
])
@classmethod
status = meth['status']
method = meth['method']
+ if hasattr(func, "__method_map_method__"):
+ version = func.__method_map_method__['version']
if not sec_permissions:
permission = cls._permission_map[method]
- elif hasattr(func, "_collection_method_"):
- if func._collection_method_['path']:
- path = func._collection_method_['path']
+ elif hasattr(func, "__collection_method__"):
+ if func.__collection_method__['path']:
+ path = func.__collection_method__['path']
else:
path = "/{}".format(func.__name__)
- status = func._collection_method_['status']
- method = func._collection_method_['method']
- query_params = func._collection_method_['query_params']
- version = func._collection_method_['version']
+ status = func.__collection_method__['status']
+ method = func.__collection_method__['method']
+ query_params = func.__collection_method__['query_params']
+ version = func.__collection_method__['version']
if not sec_permissions:
permission = cls._permission_map[method]
- elif hasattr(func, "_resource_method_"):
+ elif hasattr(func, "__resource_method__"):
if not res_id_params:
no_resource_id_params = True
else:
path_params = ["{{{}}}".format(p) for p in res_id_params]
path += "/{}".format("/".join(path_params))
- if func._resource_method_['path']:
- path += func._resource_method_['path']
+ if func.__resource_method__['path']:
+ path += func.__resource_method__['path']
else:
path += "/{}".format(func.__name__)
- status = func._resource_method_['status']
- method = func._resource_method_['method']
- version = func._resource_method_['version']
- query_params = func._resource_method_['query_params']
+ status = func.__resource_method__['status']
+ method = func.__resource_method__['method']
+ version = func.__resource_method__['version']
+ query_params = func.__resource_method__['query_params']
if not sec_permissions:
permission = cls._permission_map[method]
status = 200
def _wrapper(func):
- func._resource_method_ = {
+ func.__resource_method__ = {
'method': method,
'path': path,
'status': status,
return func
return _wrapper
+ @staticmethod
+ def MethodMap(resource=False, status=None, version=DEFAULT_VERSION): # noqa: N802
+
+ if status is None:
+ status = 200
+
+ def _wrapper(func):
+ func.__method_map_method__ = {
+ 'resource': resource,
+ 'status': status,
+ 'version': version
+ }
+ return func
+ return _wrapper
+
@staticmethod
def Collection(method=None, path=None, status=None, query_params=None, # noqa: N802
version=DEFAULT_VERSION):
status = 200
def _wrapper(func):
- func._collection_method_ = {
+ func.__collection_method__ = {
'method': method,
'path': path,
'status': status,
return schema.as_dict()
@classmethod
- def _gen_responses(cls, method, resp_object=None):
+ def _gen_responses(cls, method, resp_object=None, version=None):
resp: Dict[str, Dict[str, Union[str, Any]]] = {
'400': {
"description": "Operation exception. Please check the "
"response body for the stack trace."
}
}
+
+ if not version:
+ version = DEFAULT_VERSION
+
if method.lower() == 'get':
resp['200'] = {'description': "OK",
- 'content': {'application/vnd.ceph.api.v{}+json'.format(DEFAULT_VERSION):
+ 'content': {'application/vnd.ceph.api.v{}+json'.format(version):
{'type': 'object'}}}
if method.lower() == 'post':
resp['201'] = {'description': "Resource created.",
- 'content': {'application/vnd.ceph.api.v{}+json'.format(DEFAULT_VERSION):
+ 'content': {'application/vnd.ceph.api.v{}+json'.format(version):
{'type': 'object'}}}
if method.lower() == 'put':
resp['200'] = {'description': "Resource updated.",
- 'content': {'application/vnd.ceph.api.v{}+json'.format(DEFAULT_VERSION):
+ 'content': {'application/vnd.ceph.api.v{}+json'.format(version):
{'type': 'object'}}}
if method.lower() == 'delete':
resp['204'] = {'description': "Resource deleted.",
- 'content': {'application/vnd.ceph.api.v{}+json'.format(DEFAULT_VERSION):
+ 'content': {'application/vnd.ceph.api.v{}+json'.format(version):
{'type': 'object'}}}
if method.lower() in ['post', 'put', 'delete']:
resp['202'] = {'description': "Operation is still executing."
" Please check the task queue.",
- 'content': {'application/vnd.ceph.api.v{}+json'.format(DEFAULT_VERSION):
+ 'content': {'application/vnd.ceph.api.v{}+json'.format(version):
{'type': 'object'}}}
if resp_object:
if status_code in resp:
resp[status_code].update({
'content': {
- 'application/vnd.ceph.api.v{}+json'.format(DEFAULT_VERSION): {
+ 'application/vnd.ceph.api.v{}+json'.format(version): {
'schema': cls._gen_schema_for_content(response_body)}}})
return resp
return parameters
@classmethod
- def _gen_paths(cls, all_endpoints):
+ def gen_paths(cls, all_endpoints):
# pylint: disable=R0912
method_order = ['get', 'post', 'put', 'delete']
paths = {}
func = endpoint.func
summary = ''
+ version = ''
resp = {}
p_info = []
+
+ if hasattr(func, 'method_map_method'):
+ version = func.method_map_method['version']
+
+ elif hasattr(func, 'resource_method'):
+ version = func.resource_method['version']
+
+ elif hasattr(func, 'collection_method'):
+ version = func.collection_method['version']
+
if hasattr(func, 'doc_info'):
if func.doc_info['summary']:
summary = func.doc_info['summary']
'tags': [cls._get_tag(endpoint)],
'description': func.__doc__,
'parameters': params,
- 'responses': cls._gen_responses(method, resp)
+ 'responses': cls._gen_responses(method, resp, version)
}
if summary:
methods[method.lower()]['summary'] = summary
host = cherrypy.request.base.split('://', 1)[1] if not offline else 'example.com'
logger.debug("Host: %s", host)
- paths = cls._gen_paths(all_endpoints)
+ paths = cls.gen_paths(all_endpoints)
if not base_url:
base_url = "/"
},
)
@Endpoint(json_response=False)
- @RESTController.Resource('PUT')
+ @RESTController.Resource('PUT', version='0.1')
def decorated_func(self, parameter):
pass
+ @RESTController.MethodMap(version='0.1')
+ def list(self):
+ pass
+
# To assure functionality of @EndpointDoc, @GroupDoc
class DocDecoratorsTest(ControllerTestCase):
self.assertEqual(Docs()._type_to_str(None), str(SchemaType.OBJECT))
def test_gen_paths(self):
- outcome = Docs()._gen_paths(False)['/api/doctest//{doctest}/decorated_func']['put']
+ outcome = Docs().gen_paths(False)['/api/doctest//{doctest}/decorated_func']['put']
self.assertIn('tags', outcome)
self.assertIn('summary', outcome)
self.assertIn('parameters', outcome)
expected_response_content = {
'200': {
- 'application/vnd.ceph.api.v{}+json'.format(DEFAULT_VERSION): {
+ 'application/vnd.ceph.api.v0.1+json': {
'schema': {'type': 'array',
'items': {'type': 'object', 'properties': {
'my_prop': {
'description': '200 property desc.'}}},
'required': ['my_prop']}}},
'202': {
- 'application/vnd.ceph.api.v{}+json'.format(DEFAULT_VERSION): {
+ 'application/vnd.ceph.api.v0.1+json': {
'schema': {'type': 'object',
'properties': {'my_prop': {
'type': 'string',
# Check that a schema of type 'object' is received in the response.
self.assertEqual(expected_response_content['202'], outcome['responses']['202']['content'])
+ def test_gen_method_paths(self):
+ outcome = Docs().gen_paths(False)['/api/doctest/']['get']
+
+ self.assertEqual({'application/vnd.ceph.api.v0.1+json': {'type': 'object'}},
+ outcome['responses']['200']['content'])
+
def test_gen_paths_all(self):
- paths = Docs()._gen_paths(False)
+ paths = Docs().gen_paths(False)
for key in paths:
self.assertTrue(any(base in key.split('/')[1] for base in ['api', 'ui-api']))
class VTest(RESTController):
RESOURCE_ID = "vid"
+ @RESTController.MethodMap(version="0.1")
def list(self):
return {'version': ""}
def setup_server(cls):
cls.setup_controllers([VTest], "/test")
+ def test_list(self):
+ for (version, expected_status) in [
+ ("0.1", 200),
+ ("2.0", 415)
+ ]:
+ with self.subTest(version=version):
+ self._get('/test/api/vtest', version=version)
+ self.assertStatus(expected_status)
+
def test_v1(self):
for (version, expected_status) in [
("1.0", 200),