From 21dadaef0e9752dfc00277379cb3da8fd75147c5 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 1 May 2019 10:32:38 -0600 Subject: [PATCH] Remove cornice integration As with turbogears, no one was using this in OpenStack and therefore we can and should remove this adaptor. Change-Id: I0d3942680c1156e57d70f334caea6b89590b46c7 Signed-off-by: Stephen Finucane --- README.rst | 3 +- doc/changes.rst | 1 + doc/functions.rst | 12 +-- doc/integrate.rst | 106 +----------------------- tests/test_cornice.py | 183 ------------------------------------------ tox.ini | 25 +----- wsmeext/cornice.py | 168 -------------------------------------- 7 files changed, 11 insertions(+), 487 deletions(-) delete mode 100644 tests/test_cornice.py delete mode 100644 wsmeext/cornice.py diff --git a/README.rst b/README.rst index 4cc7fea..abd630b 100644 --- a/README.rst +++ b/README.rst @@ -62,7 +62,7 @@ Main features - Extensible : easy to add more protocols or more base types. - Framework independence : adapters are provided to easily integrate your API in any web framework, for example a wsgi container, - Pecan_, Flask_, cornice_... + Pecan_, Flask_, ... - Very few runtime dependencies: webob, simplegeneric. Optionnaly lxml and simplejson if you need better performances. - Integration in `Sphinx`_ for making clean documentation with @@ -70,7 +70,6 @@ Main features .. _Pecan: http://pecanpy.org/ .. _Flask: http://flask.pocoo.org/ -.. _cornice: http://pypi.python.org/pypi/cornice Install ~~~~~~~ diff --git a/doc/changes.rst b/doc/changes.rst index bccc5dd..fd8cb90 100644 --- a/doc/changes.rst +++ b/doc/changes.rst @@ -5,6 +5,7 @@ Changes -------------- * Remove support for turbogears +* Remove support for cornice * Remove SQLAlchemy support. It has never actually worked to begin with. 0.9.2 (2017-02-14) diff --git a/doc/functions.rst b/doc/functions.rst index da56d25..5e23970 100644 --- a/doc/functions.rst +++ b/doc/functions.rst @@ -14,22 +14,22 @@ The decorators Depending on the framework you are using, you will have to use either a @\ :class:`wsme.signature` decorator or a @\ :class:`wsme.wsexpose` decorator. -@signature +@signature ~~~~~~~~~~ The base @\ :class:`wsme.signature` decorator defines the return and argument types of the function, and if needed a few more options. -The Flask and Cornice adapters both propose a specific version of it, which -also wrap the function so that it becomes suitable for the host framework. +The Flask and adapter proposes a specific version of it, which also wrap the +function so that it becomes suitable for the host framework. -In any case, the use of @\ :class:`wsme.signature` has the same meaning: tell WSME what is the -signature of the function. +In any case, the use of @\ :class:`wsme.signature` has the same meaning: tell +WSME what is the signature of the function. @wsexpose ~~~~~~~~~ -The native Rest implementation, and the TG and Pecan adapters add a @\ :class:`wsme.wsexpose` +The native Rest implementation, and the Pecan adapter add a @\ :class:`wsme.wsexpose` decorator. It does what @\ :class:`wsme.signature` does, *and* exposes the function in the routing system diff --git a/doc/integrate.rst b/doc/integrate.rst index 3857f05..843a8a2 100644 --- a/doc/integrate.rst +++ b/doc/integrate.rst @@ -24,8 +24,7 @@ This decorator can have two different names depending on the adapter. Generally this decorator is provided for frameworks that expect functions taking a request object as a single parameter and returning a response - object. This is the case for :ref:`adapter-cornice` and - :ref:`adapter-flask`. + object. This is the case for :ref:`adapter-flask`. If you want to enable additional protocols, you will need to mount a :class:`WSRoot` instance somewhere in the application, generally @@ -62,73 +61,6 @@ WSME, which is the case if you write a WSME standalone application. application = root.wsgiapp() -.. _adapter-cornice: - -Cornice -------- - -.. _cornice: http://cornice.readthedocs.org/en/latest/ - - *"* Cornice_ *provides helpers to build & document REST-ish Web Services with - Pyramid, with decent default behaviors. It takes care of following the HTTP - specification in an automated way where possible."* - - -:mod:`wsmeext.cornice` -- Cornice adapter -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. module:: wsmeext.cornice - -.. function:: signature - - Declare the parameters of a function and returns a function suitable for - cornice (ie that takes a request and returns a response). - -Configuration -~~~~~~~~~~~~~ - -To use WSME with Cornice you have to add a configuration option to your Pyramid application. - -.. code-block:: python - - from pyramid.config import Configurator - - - def make_app(): - config = Configurator() - config.include("cornice") - config.include("wsmeext.cornice") # This includes WSME cornice support - # ... - return config.make_wsgi_app() - -Example -~~~~~~~ - -.. code-block:: python - - from cornice import Service - from wsmeext.cornice import signature - import wsme.types - - hello = Service(name='hello', path='/', description="Simplest app") - - class Info(wsme.types.Base): - message = wsme.types.text - - - @hello.get() - @signature(Info) - def get_info(): - """Returns Hello in JSON or XML.""" - return Info(message='Hello World') - - - @hello.post() - @signature(None, Info) - def set_info(info): - print("Got a message: %s" % info.message) - - .. _adapter-flask: Flask @@ -224,7 +156,7 @@ The `example 1first' - ) - - def test_post_json_data(self): - data = json.dumps({"name": "new"}) - resp = self.app.post( - '/users', data, - headers={"Content-Type": "application/json"} - ) - self.assertEqual( - resp.body, - b'{"id": 2, "name": "new"}' - ) - - def test_post_xml_data(self): - data = 'new' - resp = self.app.post( - '/users', data, - headers={"Content-Type": "text/xml"} - ) - self.assertEqual( - resp.body, - b'2new' - ) - - def test_pass_request(self): - resp = self.app.get('/needrequest') - assert resp.json is True - - def test_resource_collection_get(self): - resp = self.app.get('/author') - assert len(resp.json) == 3 - assert resp.json[0]['name'] == 'Author 1' - assert resp.json[1]['name'] == 'Author 2' - assert resp.json[2]['name'] == 'Author 3' - - def test_resource_get(self): - resp = self.app.get('/author/5') - assert resp.json['name'] == 'Author 5' - - def test_resource_post(self): - resp = self.app.post( - '/author/5', - json.dumps({"name": "Author 5"}), - headers={"Content-Type": "application/json"} - ) - assert resp.json['authorId'] == 5 - assert resp.json['name'] == 'Author 5' - - def test_server_error(self): - resp = self.app.get('/divide?a=1&b=0', expect_errors=True) - self.assertEqual(resp.json['faultcode'], 'Server') - self.assertEqual(resp.status_code, 500) - - def test_client_error(self): - resp = self.app.get( - '/divide?a=1&c=0', - headers={'Accept': 'application/json'}, - expect_errors=True - ) - self.assertEqual(resp.json['faultcode'], 'Client') - self.assertEqual(resp.status_code, 400) - - def test_runtime_error(self): - resp = self.app.get( - '/secret', - headers={'Accept': 'application/json'}, - expect_errors=True - ) - self.assertEqual(resp.json['faultcode'], 'Client') - self.assertEqual(resp.status_code, 401) diff --git a/tox.ini b/tox.ini index 278bc1b..d10e6ed 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27,py27-nolxml,pypy,cornice,cornice-py3,coverage,py36,py35,py36-nolxml,py35-nolxml,pecan-dev27,pecan-dev35,pecan-dev36,pep8 +envlist = py27,py27-nolxml,pypy,coverage,py36,py35,py36-nolxml,py35-nolxml,pecan-dev27,pecan-dev35,pecan-dev36,pep8 [common] testtools = @@ -20,29 +20,6 @@ basedeps = setenv = COVERAGE_FILE=.coverage.{envname} -[testenv:cornice] -basepython = python2.7 -usedevelop = True -deps = - pbr - nose - webtest - coverage < 3.99 - cornice -commands = - {envbindir}/nosetests tests/test_cornice.py --with-xunit --xunit-file nosetests-{envname}.xml --verbose --with-coverage --cover-package wsmeext {posargs} - {envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsmeext/cornice.py - -[testenv:cornice-py3] -basepython = python3.6 -usedevelop = {[testenv:cornice]usedevelop} -deps = {[testenv:cornice]deps} -setenv = - PYTHONHASHSEED=0 -commands = - {envbindir}/nosetests tests/test_cornice.py --with-xunit --xunit-file nosetests-{envname}.xml --verbose --with-coverage --cover-package wsmeext {posargs} - {envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsmeext/cornice.py - [testenv:pecan-dev-base] deps = {[common]testtools} diff --git a/wsmeext/cornice.py b/wsmeext/cornice.py deleted file mode 100644 index 6c81386..0000000 --- a/wsmeext/cornice.py +++ /dev/null @@ -1,168 +0,0 @@ -""" -WSME for cornice - - -Activate it:: - - config.include('wsme.cornice') - - -And use it:: - - @hello.get() - @wsexpose(Message, wsme.types.text) - def get_hello(who=u'World'): - return Message(text='Hello %s' % who) -""" -from __future__ import absolute_import - -import inspect -import sys - -import wsme -from wsme.rest import json as restjson -from wsme.rest import xml as restxml -import wsme.runtime -import wsme.api -import functools - -from wsme.rest.args import ( - args_from_args, args_from_params, args_from_body, combine_args -) - - -class WSMEJsonRenderer(object): - def __init__(self, info): - pass - - def __call__(self, data, context): - response = context['request'].response - response.content_type = 'application/json' - if 'faultcode' in data: - if 'orig_code' in data: - response.status_code = data['orig_code'] - elif data['faultcode'] == 'Client': - response.status_code = 400 - else: - response.status_code = 500 - return restjson.encode_error(None, data) - obj = data['result'] - if isinstance(obj, wsme.api.Response): - response.status_code = obj.status_code - if obj.error: - return restjson.encode_error(None, obj.error) - obj = obj.obj - return restjson.encode_result(obj, data['datatype']) - - -class WSMEXmlRenderer(object): - def __init__(self, info): - pass - - def __call__(self, data, context): - response = context['request'].response - if 'faultcode' in data: - if data['faultcode'] == 'Client': - response.status_code = 400 - else: - response.status_code = 500 - return restxml.encode_error(None, data) - response.content_type = 'text/xml' - return restxml.encode_result(data['result'], data['datatype']) - - -def get_outputformat(request): - df = None - if 'Accept' in request.headers: - if 'application/json' in request.headers['Accept']: - df = 'json' - elif 'text/xml' in request.headers['Accept']: - df = 'xml' - if df is None and 'Content-Type' in request.headers: - if 'application/json' in request.headers['Content-Type']: - df = 'json' - elif 'text/xml' in request.headers['Content-Type']: - df = 'xml' - return df if df else 'json' - - -def signature(*args, **kwargs): - sig = wsme.signature(*args, **kwargs) - - def decorate(f): - args = inspect.getargspec(f)[0] - with_self = args[0] == 'self' if args else False - f = sig(f) - funcdef = wsme.api.FunctionDefinition.get(f) - funcdef.resolve_types(wsme.types.registry) - - @functools.wraps(f) - def callfunction(*args): - if with_self: - if len(args) == 1: - self = args[0] - request = self.request - elif len(args) == 2: - self, request = args - else: - raise ValueError("Cannot do anything with these arguments") - else: - request = args[0] - request.override_renderer = 'wsme' + get_outputformat(request) - try: - args, kwargs = combine_args(funcdef, ( - args_from_args(funcdef, (), request.matchdict), - args_from_params(funcdef, request.params), - args_from_body(funcdef, request.body, request.content_type) - )) - wsme.runtime.check_arguments(funcdef, args, kwargs) - if funcdef.pass_request: - kwargs[funcdef.pass_request] = request - if with_self: - args.insert(0, self) - - result = f(*args, **kwargs) - return { - 'datatype': funcdef.return_type, - 'result': result - } - except Exception: - try: - exception_info = sys.exc_info() - orig_exception = exception_info[1] - orig_code = getattr(orig_exception, 'code', None) - data = wsme.api.format_exception(exception_info) - if orig_code is not None: - data['orig_code'] = orig_code - return data - finally: - del exception_info - - callfunction.wsme_func = f - return callfunction - return decorate - - -def scan_api(root=None): - from cornice.service import get_services - for service in get_services(): - for method, func, options in service.definitions: - wsme_func = getattr(func, 'wsme_func') - basepath = service.path.split('/') - if basepath and not basepath[0]: - del basepath[0] - if wsme_func: - yield ( - basepath + [method.lower()], - wsme_func._wsme_definition - ) - - -def includeme(config): - import pyramid.wsgi - wsroot = wsme.WSRoot(scan_api=scan_api, webpath='/ws') - wsroot.addprotocol('extdirect') - config.add_renderer('wsmejson', WSMEJsonRenderer) - config.add_renderer('wsmexml', WSMEXmlRenderer) - config.add_route('wsme', '/ws/*path') - config.add_view(pyramid.wsgi.wsgiapp(wsroot.wsgiapp()), route_name='wsme')