diff --git a/cornice-0.2-py2.7.egg-info/PKG-INFO b/cornice-0.2-py2.7.egg-info/PKG-INFO deleted file mode 100644 index 9bf8ccb..0000000 --- a/cornice-0.2-py2.7.egg-info/PKG-INFO +++ /dev/null @@ -1,102 +0,0 @@ -Metadata-Version: 1.0 -Name: cornice -Version: 0.2 -Summary: Define Web Services in Pyramid. -Home-page: https://github.com/mozilla-services/cornice -Author: Mozilla Services -Author-email: services-dev@mozilla.org -License: UNKNOWN -Description: ======= - Cornice - ======= - - Overview - ======== - - **Cornice** provides a *Service* class you can use to define web services in - Pyramid. - - Each instance of a Service class corresponds to a server path and you may - implement various methods HTTP on the path with simple decorators. - - Cornice will automatically return a 405 error with the right Allow header - if a method that was not implemented is requested. - - Cornice also provides a Sphinx directive that can be used to document your - web services. The extension iterates over defined services and will - automatically generate your web service documentation. - - - QuickStart - ========== - - To use Cornice, start by including it in your project with the **include** - method in Pyramid:: - - def main(global_config, **settings): - ... - config.include("cornice") - ... - return config.make_wsgi_app() - - - Learn more about include at http://docs.pylonsproject.org/projects/pyramid/1.0/narr/advconfig.html#including-configuration-from-external-sources - - Then you can start to define web services in your views. - - For example, let's - define a service where you can **GET** and **POST** a value at - **/values/{value}**, where *value* is an ascii value representing the - name of the value:: - - - import json - from cornice import Service - - values = Service(name='foo', path='/values/{value}', - description="Cornice Demo") - - _VALUES = {} - - - @values.get() - def get_value(request): - """Returns the value. - """ - key = request.matchdict['value'] - return _VALUES.get(key) - - - @values.post() - def set_value(request): - """Set the value. - - Returns *True* or *False*. - """ - key = request.matchdict['value'] - try: - _VALUES.set(key, json.loads(request.body)) - except ValueError: - return False - return True - - - By default, Cornice uses a Json rendered. - - - 0.2 - 2011-11-05 - ================ - - - Fixed the MANIFEST - - 0.1 - 2011-11-03 - ================ - - - Initial release - -Keywords: web pyramid pylons -Platform: UNKNOWN -Classifier: Programming Language :: Python -Classifier: Framework :: Pylons -Classifier: Topic :: Internet :: WWW/HTTP -Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application diff --git a/cornice-0.4-py2.7.egg-info/PKG-INFO b/cornice-0.4-py2.7.egg-info/PKG-INFO new file mode 100644 index 0000000..22d97ce --- /dev/null +++ b/cornice-0.4-py2.7.egg-info/PKG-INFO @@ -0,0 +1,253 @@ +Metadata-Version: 1.0 +Name: cornice +Version: 0.4 +Summary: Define Web Services in Pyramid. +Home-page: https://github.com/mozilla-services/cornice +Author: Mozilla Services +Author-email: services-dev@mozilla.org +License: UNKNOWN +Description: ======= + Cornice + ======= + + Overview + ======== + + **Cornice** provides a *Service* class you can use to define web services in + Pyramid. + + Each instance of a Service class corresponds to a server path and you may + implement various methods HTTP on the path with simple decorators. + + Cornice will automatically return a 405 error with the right Allow header + if a method that was not implemented is requested. + + Cornice also provides: + + - a Sphinx directive that can be used to document your web services. + The extension iterates over defined services and will automatically + generate your web service documentation. + + - a validation hook you can use to control the request + + + QuickStart + ========== + + + To use Cornice, start by including it in your project with the **include** + method in Pyramid:: + + def main(global_config, **settings): + ... + config.include("cornice") + ... + return config.make_wsgi_app() + + + Learn more about include at http://docs.pylonsproject.org/projects/pyramid/1.0/narr/advconfig.html#including-configuration-from-external-sources + + Then you can start to define web services in your views. + + For example, let's + define a service where you can **GET** and **POST** a value at + **/values/{value}**, where *value* is an ascii value representing the + name of the value:: + + + import json + from cornice import Service + + values = Service(name='foo', path='/values/{value}', + description="Cornice Demo") + + _VALUES = {} + + + @values.get() + def get_value(request): + """Returns the value. + """ + key = request.matchdict['value'] + return _VALUES.get(key) + + + @values.post() + def set_value(request): + """Set the value. + + Returns *True* or *False*. + """ + key = request.matchdict['value'] + try: + _VALUES.set(key, json.loads(request.body)) + except ValueError: + return False + return True + + + By default, Cornice uses a Json rendered. + + + Validation + ========== + + Cornice provides a *validator* option that you can use to control the request + before it's passed to the code. A validator is a simple callable that gets + the request object and returns an HTTP Error code followed by an explanation + in case the request does not comply. + + When it returns nothing, the request is considered valid. + + Validators can also convert values and saves them so they can be reused + by the code. This is done with the **save_converted**, **get_converted** + functions from the **cornice.schemas** module. + + Let's take an example: we want to make sure the incoming request has an + **X-Paid** header. If not, we want the server to return a 402 (payment + required) :: + + + from cornice import Service + + foo = Service(name='foo', path='/foo') + + def has_paid(request): + if not 'X-Paid' in request.headers: + return 402, 'You need to pay' + + + @foo.get(validator='has_paid') + def get_value(request): + """Returns the value. + """ + return 'Hello' + + + Cornice comes with built-in validators: + + - **JsonBody**: makes sure a POST body is a Json object + - **GetChecker**: checks the params for a GET + - **PostChecker**: checks a POST form + + Notice that you can chain the validators by passing a sequence + to the **validator** option. + + **GetChecker** and **PostChecker** are classes you can use to control + a request params. The classes have a **fields** attribute you + can fill with fields you expect in the request to have. + + Each field is defined by a Field object. Cornice defines one built-in + field object: **Integer**. This field makes sure the value is an + integer then saves it with **save_converted**. + + + In the example below, we create a Checker that controls that the param + **foo** in a GET request is an integer:: + + + from cornice.schemas import Checker, get_converted + + + class Checker(GetChecker): + """When provided, the **foo** param must be an integer""" + fields = [Integer('foo')] + + + service = Service(name="service", path="/service") + + + def has_payed(request): + if not 'paid' in request.GET: + return 402, 'You must pay!' + + + @service.get(validator=(Checker(), has_payed)) + def get1(request): + res = {"test": "succeeded"} + try: + res['foo'] = get_converted(request, 'foo') + except KeyError: + pass + + return res + + + The **get1** function uses two validators here, and grabs back the **foo** + value that was converted by the **Checker** validator. Notice that the + **foo** option is optional here. + + + Colander integration + -------------------- + + Colander (http://docs.pylonsproject.org/projects/colander/en/latest/) is a + validation framework from the Pylons project that can be used with Cornice's + validation hook to control a request and deserialize its content into + objects. + + Let's say, you have a **Person** schema in Colander, that defines + a person's attributes -- See http://docs.pylonsproject.org/projects/colander/en/latest/basics.html#defining-a-schema-imperatively + + And you want to provide a POST Web Service to create a person, where + the request body is the person data serialized in JSON. + + Here's how you can do:: + + + def check_person(request): + """Unserialize the data from the request.""" + try: + person = json.loads(request) + except ValueError: + return 400, 'Bad Json data!' + + schema = Person() + try: + deserialized = schema.deserialized(person) + except InvalidError, e: + # the struct is invalid + return 400, e.message + + save_converted(request, 'person', deserialized) + + + @service.post(validator=check_person) + def data_posted(request): + person = get_converted(request, 'person') + ... do the work on person ... + + + Here, Colander takes care of validating the data against its + schema then converting it into an object you can work with + in your code. + + + + 0.4 - ? + ======= + + - ? + + 0.3 - 2011-11-23 + ================ + + - remove singleton "_defined" state from Service class; this allows service + definitions to be loaded into more than one Configurator. + + 0.2 - 2011-11-05 + ================ + + - Fixed the MANIFEST + + 0.1 - 2011-11-03 + ================ + + - Initial release + +Keywords: web pyramid pylons +Platform: UNKNOWN +Classifier: Programming Language :: Python +Classifier: Framework :: Pylons +Classifier: Topic :: Internet :: WWW/HTTP +Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application diff --git a/cornice-0.2-py2.7.egg-info/SOURCES.txt b/cornice-0.4-py2.7.egg-info/SOURCES.txt similarity index 63% rename from cornice-0.2-py2.7.egg-info/SOURCES.txt rename to cornice-0.4-py2.7.egg-info/SOURCES.txt index c489922..445df5f 100644 --- a/cornice-0.2-py2.7.egg-info/SOURCES.txt +++ b/cornice-0.4-py2.7.egg-info/SOURCES.txt @@ -1,9 +1,8 @@ CHANGES.txt MANIFEST.in README.txt -setup.cfg -setup.py cornice/__init__.py +cornice/schemas.py cornice/service.py cornice/sphinxext.py cornice/util.py @@ -11,7 +10,11 @@ cornice.egg-info/PKG-INFO cornice.egg-info/SOURCES.txt cornice.egg-info/dependency_links.txt cornice.egg-info/not-zip-safe +cornice.egg-info/paster_plugins.txt cornice.egg-info/requires.txt cornice.egg-info/top_level.txt cornice/tests/__init__.py -cornice/tests/test_util.py \ No newline at end of file +cornice/tests/test_service_definition.py +cornice/tests/test_util.py +cornice/tests/test_validation.py +cornice/tests/validationapp.py \ No newline at end of file diff --git a/cornice-0.2-py2.7.egg-info/dependency_links.txt b/cornice-0.4-py2.7.egg-info/dependency_links.txt similarity index 100% rename from cornice-0.2-py2.7.egg-info/dependency_links.txt rename to cornice-0.4-py2.7.egg-info/dependency_links.txt diff --git a/cornice-0.2-py2.7.egg-info/installed-files.txt b/cornice-0.4-py2.7.egg-info/installed-files.txt similarity index 56% rename from cornice-0.2-py2.7.egg-info/installed-files.txt rename to cornice-0.4-py2.7.egg-info/installed-files.txt index 9e4c27c..b0f602f 100644 --- a/cornice-0.2-py2.7.egg-info/installed-files.txt +++ b/cornice-0.4-py2.7.egg-info/installed-files.txt @@ -1,19 +1,28 @@ ../cornice/util.py +../cornice/schemas.py ../cornice/sphinxext.py ../cornice/__init__.py ../cornice/service.py +../cornice/tests/validationapp.py +../cornice/tests/test_service_definition.py ../cornice/tests/test_util.py +../cornice/tests/test_validation.py ../cornice/tests/__init__.py ../cornice/util.pyc +../cornice/schemas.pyc ../cornice/sphinxext.pyc ../cornice/__init__.pyc ../cornice/service.pyc +../cornice/tests/validationapp.pyc +../cornice/tests/test_service_definition.pyc ../cornice/tests/test_util.pyc +../cornice/tests/test_validation.pyc ../cornice/tests/__init__.pyc ./ requires.txt not-zip-safe top_level.txt +paster_plugins.txt PKG-INFO dependency_links.txt SOURCES.txt diff --git a/cornice-0.2-py2.7.egg-info/not-zip-safe b/cornice-0.4-py2.7.egg-info/not-zip-safe similarity index 100% rename from cornice-0.2-py2.7.egg-info/not-zip-safe rename to cornice-0.4-py2.7.egg-info/not-zip-safe diff --git a/cornice-0.4-py2.7.egg-info/paster_plugins.txt b/cornice-0.4-py2.7.egg-info/paster_plugins.txt new file mode 100644 index 0000000..d94f7c9 --- /dev/null +++ b/cornice-0.4-py2.7.egg-info/paster_plugins.txt @@ -0,0 +1 @@ +pyramid diff --git a/cornice-0.2-py2.7.egg-info/requires.txt b/cornice-0.4-py2.7.egg-info/requires.txt similarity index 77% rename from cornice-0.2-py2.7.egg-info/requires.txt rename to cornice-0.4-py2.7.egg-info/requires.txt index 6a32d38..460a051 100644 --- a/cornice-0.2-py2.7.egg-info/requires.txt +++ b/cornice-0.4-py2.7.egg-info/requires.txt @@ -3,4 +3,5 @@ coverage simplejson docutils unittest2 -Sphinx \ No newline at end of file +Sphinx +webtest \ No newline at end of file diff --git a/cornice-0.2-py2.7.egg-info/top_level.txt b/cornice-0.4-py2.7.egg-info/top_level.txt similarity index 100% rename from cornice-0.2-py2.7.egg-info/top_level.txt rename to cornice-0.4-py2.7.egg-info/top_level.txt diff --git a/cornice/schemas.py b/cornice/schemas.py new file mode 100644 index 0000000..360bb75 --- /dev/null +++ b/cornice/schemas.py @@ -0,0 +1,164 @@ +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is Sync Server +# +# The Initial Developer of the Original Code is the Mozilla Foundation. +# Portions created by the Initial Developer are Copyright (C) 2011 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Tarek Ziade (tarek@mozilla.com) +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** +""" Validators. +""" +import json + + +_CONVKEY = '_cornice_converted' + + +def save_converted(request, name, value, forced=False): + """Saves the converted value in the request. + + Converted values are kept in request.environ['converted'] + + If the value is already set a ValueError is raised, + unless forced is set to True : the value is updated + """ + converted = request.environ.setdefault(_CONVKEY, {}) + + if name in converted and not forced: + raise ValueError('%r was already set' % name) + + converted[name] = value + + +def get_converted(request, name): + """Returns a converted value. + + If the value was not set, returns a KeyError + """ + if _CONVKEY not in request.environ: + raise KeyError(name) + return request.environ[_CONVKEY][name] + + +class JsonBody(object): + """The request body should be a JSON object. + """ + def __call__(self, request): + try: + body = json.loads(request.body) + save_converted(request, 'body', body) + except ValueError: + return 400, 'Not a json body' + + +class Field(object): + def __init__(self, name, required=False): + self.name = name + self.required = required + + def convert(self, value): + return value + + def get_description(self): + return self.name + + +class Integer(Field): + def __init__(self, name, min=None, max=None, required=False): + super(Integer, self).__init__(name, required) + self.min = min + self.max = max + + def convert(self, value): + value = int(value) + + if self.min and value < self.min: + raise ValueError('%r is too small' % self.name) + + if self.max and value > self.max: + raise ValueError('%r is too big' % self.name) + + return value + + def get_description(self): + desc = '%r must be an Integer.' + if self.min: + desc += ', min value: %d' % self.min + if self.max: + desc += ', max value: %d' % self.max + if self.required: + desc += ' (required)' + + return desc + + +class FormChecker(object): + fields = [] + + def __init__(self, description=None): + if description is not None: + self.__doc__ = description + else: + self.__doc__ = self._set_description() + + def _get_form(self, request): + raise NotImplementedError() + + def _set_description(self): + desc = [] + for field in self.fields: + desc.append(field.get_description()) + self.__doc__ = '\n'.join(desc).strip() + + def __call__(self, request): + form = self._get_form(request) + + for field in self.fields: + if field.name not in form: + if field.required: + return 400, '%r missing' % field.name + else: + continue + try: + value = field.convert(form[field.name]) + except ValueError, e: + return 400, e.message + + save_converted(request, field.name, value) + + +class GetChecker(FormChecker): + def _get_form(self, request): + return request.GET + + +class PostChecker(FormChecker): + def _get_form(self, request): + return request.POST diff --git a/cornice/service.py b/cornice/service.py index 0439b68..6426bef 100644 --- a/cornice/service.py +++ b/cornice/service.py @@ -33,7 +33,20 @@ # the terms of any one of the MPL, the GPL or the LGPL. # # ***** END LICENSE BLOCK ***** +import functools import venusian +from cornice.util import code2exception + + +def _apply_validator(func, validator): + @functools.wraps(func) + def __apply(request): + res = validator(request) + if res is not None: + code, detail = res + raise code2exception(code, res) + return func(request) + return __apply class Service(object): @@ -49,7 +62,6 @@ def __init__(self, **kw): self.description = None self.acl_factory = kw.pop('acl', None) self.kw = kw - self._defined = False def __repr__(self): return "<%s Service at %s>" % (self.renderer.capitalize(), @@ -58,20 +70,18 @@ def __repr__(self): def _define(self, config, method): # setup the services hash if it isn't already services = config.registry.setdefault('cornice_services', {}) + + # define the route if it isn't already if self.route_pattern not in services: services[self.route_pattern] = self - - # registring the method - if method not in self.defined_methods: - self.defined_methods.append(method) - - if not self._defined: - # defining the route route_kw = {} if self.acl_factory is not None: route_kw["factory"] = self._make_route_factory() config.add_route(self.route_name, self.route_pattern, **route_kw) - self._defined = True + + # registering the method + if method not in self.defined_methods: + self.defined_methods.append(method) def _make_route_factory(self): acl_factory = self.acl_factory @@ -107,10 +117,23 @@ def api(self, **kw): if 'renderer' not in api_kw: api_kw['renderer'] = self.renderer + validators = api_kw.pop('validator', []) + if not isinstance(validators, (list, tuple)): + validators = [validators] + def _api(func): _api_kw = api_kw.copy() docstring = func.__doc__ + for validator in validators: + func = _apply_validator(func, validator) + + if validator.__doc__ is not None: + if docstring is not None: + docstring += validator.__doc__.strip() + else: + docstring = validator.__doc__.strip() + def callback(context, name, ob): config = context.config.with_package(info.module) self._define(config, method) @@ -129,5 +152,6 @@ def callback(context, name, ob): kw['attr'] = func.__name__ kw['_info'] = info.codeinfo # fbo "action_method" + return func return _api diff --git a/cornice/tests/test_service_definition.py b/cornice/tests/test_service_definition.py new file mode 100644 index 0000000..60624fd --- /dev/null +++ b/cornice/tests/test_service_definition.py @@ -0,0 +1,84 @@ + +import unittest +import json +from StringIO import StringIO + +from pyramid import testing +from pyramid.exceptions import HTTPNotFound + +from cornice import Service + + +service1 = Service(name="service1", path="/service1") + + +@service1.get() +def get1(request): + return {"test": "succeeded"} + + +@service1.post() +def post1(request): + return {"body": request.body} + + +def make_request(**kwds): + environ = {} + environ["wsgi.version"] = (1, 0) + environ["wsgi.url_scheme"] = "http" + environ["SERVER_NAME"] = "localhost" + environ["SERVER_PORT"] = "80" + environ["REQUEST_METHOD"] = "GET" + environ["SCRIPT_NAME"] = "" + environ["PATH_INFO"] = "/" + environ.update(kwds) + return testing.DummyRequest(environ=environ) + + +class TestServiceDefinition(unittest.TestCase): + + def setUp(self): + self.config = testing.setUp() + self.config.include("cornice") + self.config.scan("cornice.tests.test_service_definition") + + def tearDown(self): + testing.tearDown() + + def test_basic_service_operation(self): + app = self.config.make_wsgi_app() + + # An unknown URL raises HTTPNotFound + def start_response(status, headers, exc_info=None): + pass + req = make_request(PATH_INFO="/unknown") + self.assertRaises(HTTPNotFound, app, req.environ, start_response) + + # A request to the service calls the apppriate view function. + req = make_request(PATH_INFO="/service1") + result = json.loads("".join(app(req.environ, start_response))) + self.assertEquals(result["test"], "succeeded") + + req = make_request(PATH_INFO="/service1", REQUEST_METHOD="POST") + req.environ["wsgi.input"] = StringIO("BODY") + result = json.loads("".join(app(req.environ, start_response))) + self.assertEquals(result["body"], "BODY") + + def test_loading_into_multiple_configurators(self): + config2 = testing.setUp() + config2.include("cornice") + config2.scan("cornice.tests.test_service_definition") + + # Calling the new configurator works as expected. + def start_response(status, headers, exc_info=None): + pass + app = config2.make_wsgi_app() + req = make_request(PATH_INFO="/service1") + result = json.loads("".join(app(req.environ, start_response))) + self.assertEquals(result["test"], "succeeded") + + # Calling the old configurator works as expected. + app = self.config.make_wsgi_app() + req = make_request(PATH_INFO="/service1") + result = json.loads("".join(app(req.environ, start_response))) + self.assertEquals(result["test"], "succeeded") diff --git a/cornice/tests/test_validation.py b/cornice/tests/test_validation.py new file mode 100644 index 0000000..afc3328 --- /dev/null +++ b/cornice/tests/test_validation.py @@ -0,0 +1,34 @@ +import unittest +import json + +from webtest import TestApp +from cornice.tests.validationapp import main +from webtest.app import AppError + + +class TestServiceDefinition(unittest.TestCase): + + def test_validation(self): + app = TestApp(main({})) + + # 413 + self.assertRaises(AppError, app.get, '/service') + + self.assertRaises(AppError, app.post, '/service', params='buh') + res = app.post('/service', params=json.dumps('buh')) + + self.assertEqual(res.body, json.dumps({'body': '"buh"'})) + + app.get('/service?paid=yup') + + # valid = foo is one + res = app.get('/service?foo=1&paid=yup') + self.assertEqual(res.json['foo'], 1) + + # invalid value for foo + self.assertRaises(AppError, app.get, '/service?foo=buh&paid=yup') + + # let's see the docstring ! + apidocs = app.app.registry.settings['apidocs'] + post_doc = apidocs[('/service', 'POST')]['docstring'] + self.assertEqual(post_doc, 'The request body should be a JSON object.') diff --git a/cornice/tests/validationapp.py b/cornice/tests/validationapp.py new file mode 100644 index 0000000..2853ae2 --- /dev/null +++ b/cornice/tests/validationapp.py @@ -0,0 +1,43 @@ +from pyramid.config import Configurator + +from cornice import Service +from cornice.schemas import * + + +class Checker(GetChecker): + fields = [Integer('foo')] + + +service = Service(name="service", path="/service") + + +def has_payed(request): + if not 'paid' in request.GET: + return 402, 'You must pay!' + + +@service.get(validator=(Checker(), has_payed)) +def get1(request): + res = {"test": "succeeded"} + try: + res['foo'] = get_converted(request, 'foo') + except KeyError: + pass + + return res + + +@service.post(validator=JsonBody()) +def post1(request): + return {"body": request.body} + + +def includeme(config): + config.include("cornice") + config.scan("cornice.tests.validationapp") + + +def main(global_config, **settings): + config = Configurator(settings={}) + config.include(includeme) + return config.make_wsgi_app() diff --git a/cornice/util.py b/cornice/util.py index 27edbb1..a1dd2c0 100644 --- a/cornice/util.py +++ b/cornice/util.py @@ -33,6 +33,7 @@ # the terms of any one of the MPL, the GPL or the LGPL. # # ***** END LICENSE BLOCK ***** +from webob import exc import simplejson as json from docutils import core from docutils.writers.html4css1 import Writer, HTMLTranslator @@ -100,3 +101,57 @@ def __call__(self, data, context): response = context['request'].response response.content_type = 'application/json' return json.dumps(data, use_decimal=True) + + +def code2exception(code, detail): + """Transforms a code + detail into a WebOb exception""" + if code == 400: + return exc.HTTPBadRequest(detail) + if code == 401: + return exc.HTTPUnauthorized(detail) + if code == 402: + return exc.HTTPPaymentRequired(detail) + if code == 403: + return exc.HTTPForbidden(detail) + if code == 404: + return exc.HTTPNotFound(detail) + if code == 405: + return exc.HTTPMethodNotAllowed(detail) + if code == 406: + return exc.HTTPNotAcceptable(detail) + if code == 407: + return exc.HTTPProxyAuthenticationRequired(detail) + if code == 408: + return exc.HTTPRequestTimeout(detail) + if code == 409: + return exc.HTTPConflict(detail) + if code == 410: + return exc.HTTPGone(detail) + if code == 411: + return exc.HTTPLengthRequired(detail) + if code == 412: + return exc.HTTPPreconditionFailed(detail) + if code == 413: + return exc.HTTPRequestEntityTooLarge(detail) + if code == 414: + return exc.HTTPRequestURITooLong(detail) + if code == 415: + return exc.HTTPUnsupportedMediaType(detail) + if code == 416: + return exc.HTTPRequestRangeNotSatisfiable(detail) + if code == 417: + return exc.HTTPExpectationFailed(detail) + if code == 500: + return exc.HTTPInternalServerError(detail) + if code == 501: + return exc.HTTPNotImplemented(detail) + if code == 502: + return exc.HTTPBadGateway(detail) + if code == 503: + return exc.HTTPServiceUnavailable(detail) + if code == 504: + return exc.HTTPGatewayTimeout(detail) + if code == 505: + return exc.HTTPVersionNotSupported(detail) + + raise NotImplementedError(code)