diff --git a/.gitignore b/.gitignore index 7b06064..a4b0998 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ MANIFEST dist .cache .eggs/* +build/* restea.egg-info/* diff --git a/restea/resource.py b/restea/resource.py index 75d5272..625632c 100644 --- a/restea/resource.py +++ b/restea/resource.py @@ -2,6 +2,8 @@ import collections +import six + import restea.errors as errors import restea.formats as formats import restea.fields as fields @@ -19,6 +21,7 @@ class Resource(object): 'post': 'create', 'put': 'edit', 'delete': 'delete', + 'options': 'describe', } def __init__(self, request, formatter): @@ -45,7 +48,7 @@ def _iden_required(self, method_name): :returns: boolean value of whatever iden is needed or not :rtype: bool ''' - return method_name not in ('list', 'create') + return method_name not in ('list', 'create', 'describe') def _match_response_to_fields(self, dct): ''' @@ -104,9 +107,10 @@ def _get_method_name(self, has_iden): 'HTTP_X_HTTP_METHOD_OVERRIDE', method ) - method_name = self.method_map.get(method.lower()) - if not method_name: + try: + method_name = self.method_map[method.lower()] + except KeyError: raise errors.MethodNotAllowedError( 'Method "{}" is not supported'.format(self.request.method) ) @@ -231,6 +235,8 @@ def process(self, *args, **kwargs): method = self._get_method(method_name) method = self._apply_decorators(method) + if method_name == 'describe': + self._add_available_methods_to_response_headers() self.prepare() response = method(self, *args, **kwargs) response = self.finish(response) @@ -242,11 +248,10 @@ def process(self, *args, **kwargs): def dispatch(self, *args, **kwargs): ''' - Dispatches the request and handles exception to return data, status - and content type - - :returns: 4-element tuple: result, HTTP status code, content type, and + Dispatches the request and handles exception to return data, status, content type, and headers + + :returns: 4-element tuple: result, HTTP status code, content type, and headers :rtype: tuple ''' try: @@ -267,6 +272,22 @@ def dispatch(self, *args, **kwargs): self._response_headers ) + def _add_available_methods_to_response_headers(self): + methods_available = [] + for http_method, method_name in self._stream_http_method_and_restea_method(): + if hasattr(self, method_name): + methods_available.append(http_method.upper()) + self.set_header('Allow', ','.join(set(methods_available))) + + @classmethod + def _stream_http_method_and_restea_method(cls): + for http_method, method_names in six.iteritems(cls.method_map): + if isinstance(method_names, tuple): + for method_name in method_names: + yield http_method, method_name + else: + yield http_method, method_names + def set_header(self, name, value): ''' Sets the given response header name and value. diff --git a/setup.py b/setup.py index 4a5f3d1..08eab50 100644 --- a/setup.py +++ b/setup.py @@ -1,13 +1,12 @@ from setuptools import setup -readme_content = '' with open("README.rst") as f: readme_content = f.read() setup( name='restea', packages=['restea', 'restea.adapters'], - version='0.3.12', + version='0.3.13', description='Simple RESTful server toolkit', long_description=readme_content, author='Walery Jadlowski', @@ -33,6 +32,7 @@ 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Internet :: WWW/HTTP', diff --git a/tests/test_resource.py b/tests/test_resource.py index eb67e1e..1134b34 100644 --- a/tests/test_resource.py +++ b/tests/test_resource.py @@ -73,6 +73,11 @@ def test_get_method_name_delete(): assert 'delete' == resource._get_method_name(has_iden=True) +def test_get_method_name_options(): + resource, _, _ = create_resource_helper(method='OPTIONS') + assert 'describe' == resource._get_method_name(has_iden=True) + + def test_get_method_name_unpecefied_method(): resource, _, _ = create_resource_helper(method='HEAD') @@ -98,6 +103,7 @@ def test_iden_required_negative(): resource, _, _ = create_resource_helper() assert resource._iden_required('create') is False assert resource._iden_required('list') is False + assert resource._iden_required('describe') is False def test_match_response_to_fields():