{ "info": { "author": "Joel Perras", "author_email": "joel@nerderati.com", "bugtrack_url": null, "classifiers": [ "Environment :: Web Environment", "Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6" ], "description": "Flask-ApiExceptions\n===================\n\nFlask-ApiExceptions is a Flask extension that provides the basic\nfunctionality for serializing uncaught exceptions as HTTP responses for\na JSON-based REST API.\n\nInstallation\n------------\n\nYou can install this extension with ``pip``:\n\n.. code:: bash\n\n $ pip install flask_apiexceptions\n\nOr, you can clone the repository:\n\n.. code:: bash\n\n $ git clone https://github.com/jperras/flask_apiexceptions.git\n\nRunning the Tests\n-----------------\n\n`Tox `__ is used to run the tests,\nwhich are written using `PyTest `__.\nTo run them, clone the repository (indicated above), ensure ``tox`` is\ninstalled and available, and run:\n\n.. code:: bash\n\n $ cd path/to/flask_apiexceptions\n $ tox\n\nUsage\n-----\n\nThis package includes an extension named ``JSONExceptionHandler``, which\ncan be added to your application in the usual way:\n\n::\n\n from flask import Flask\n from flask_apiexceptions import JSONExceptionHandler\n\n app = Flask(__name__)\n exception_handler = JSONExceptionHandler(app)\n\nThe extension can also be initialized via deferred application init if\nyou\u2019re using an application factory:\n\n::\n\n exception_handler = JSONExceptionHandler()\n exception_hander.init_app(app)\n\nOnce initialized, the extension doesn\u2019t actually do anything by default.\nYou\u2019ll have to configure it to handle Werkzeug HTTP error codes or\ncustom ``Exception`` classes.\n\nCustom Exception Class Handling\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nAn example showing how we can raise a custom exception within a view\nmethod, and have that exception be transformed into a JSON response:\n\n.. code:: python\n\n class MissingUserError(Exception):\n status_code = 404\n message = 'No such user exists.'\n\n @app.route('/not-found')\n def testing():\n raise MissingUserError()\n\n ext = JSONExceptionHandler(app)\n ext.register(code_or_exception=MissingUserError)\n\n with app.app_context():\n with app.test_client() as c:\n rv = c.get('/not-found')\n\n assert rv.status_code == 404\n assert rv.headers['content-type'] == 'application/json'\n assert json.loads(rv.data)['message'] == 'No such user exists.'\n\nThis uses the ``JSONExceptionHandler.default_handler()`` to transform\nthe ``CustomError`` exception class into a suitable response. It\nattempts to introspect the exception instance returned for a ``message``\nor ``description`` attribute, and also checks to see if there exists a\n``status_code`` attribute.\n\nIf any of those fields are found, the default handler will populate the\nresponse data with the given message, and set the response status code.\nIf no message or status code is present, a default response of\n``{\"message\": \"An error occurred!\"}`` with an\n``HTTP/1.1 500 Internal Server Error`` status code is set.\n\nIf you\u2019d like to handle custom exception classes in a different manner,\nsay because you have more complex data captured within an exception\ninstance, or the attributes are not conveniently named ``message`` or\n``description``, then you can specify a custom handler for the exception\ntype:\n\n.. code:: python\n\n from flask_apiexceptions import JSONExceptionHandler\n\n app = Flask(__name__)\n ext = JSONExceptionHandler(app)\n\n class CaffeineError(Exception):\n teapot_code = 418\n special = {'foo': 'bar'}\n\n def caffeine_handler(error):\n response = jsonify(data=error.special)\n response.status_code = error.teapot_code\n return response\n\n @app.route('/testing')\n def testing():\n raise CaffeineError()\n\n ext.register(code_or_exception=CaffeineError, handler=caffeine_handler)\n\n with app.app_context():\n with app.test_client() as c:\n rv = c.get('/testing')\n\n assert rv.status_code == 418\n assert rv.headers['content-type'] == 'application/json'\n assert json.loads(rv.data)['data'] == CaffeineError.special\n\nThis is also how, incidentally, you could use a response content type\nother than ``application/json``. Simply construct your own response\nobject isntead of using ``jsonify()`` within your handler, as long as it\nproduces a valid response as a return value.\n\nUsing ``ApiException`` and ``ApiError`` objects\n-----------------------------------------------\n\n``Flask-ApiExceptions`` includes a few convenience classes and a handler\nmethod for setting up structured API error responses. They are entirely\noptional, but provide some sane defaults that should cover most\nsituatiosn.\n\nAn ``ApiException`` instance wraps one or more ``ApiError`` instances.\nIn this sense the ``ApiException`` is simply the container for the\nactual error message. The ``ApiError`` instance accepts optional\n``code``, ``message``, and ``info`` attributes.\n\nThe idea is that the ``code`` should be an identifier for the type of\nerror, for example ``invalid-data`` or ``does-not-exist``. The\n``message`` field should provide a more detailed and precise description\nof the error. The ``info`` field can be used for any additional metadata\nor unstructured information that may be required.\n\nThe ``info`` field, if utilized, should contain data that is JSON\nserializable.\n\nTo use these constructs, you need to register the appropriate exception\nclass as well as an ``api_exception_handler`` that is provided for just\nthis purpose:\n\n.. code:: python\n\n from flask_apiexceptions import (\n JSONExceptionHandler, ApiException, ApiError, api_exception_handler)\n\n app = Flask(__name__)\n ext = JSONExceptionHandler(app)\n ext.register(code_or_exception=ApiException, handler=api_exception_handler)\n\n @app.route('/custom')\n def testing():\n error = ApiError(code='teapot', message='I am a little teapot.')\n raise ApiException(status_code=418, error=error)\n\n\n with app.app_context():\n with app.test_client() as c:\n rv = c.get('/custom')\n\n # JSON response looks like...\n # {\"errors\": [{\"code\": \"teapot\", \"message\": \"I am a little teapot.\"}]}\n\n assert rv.status_code == 418\n assert rv.headers['content-type'] == 'application/json'\n\n json_data = json.loads(rv.data)\n assert json_data['errors'][0]['message'] == 'I am a little teapot.'\n assert json_data['errors'][0]['code'] == 'teapot'\n assert json_data['errors'][0]['info'] is None\n\nNote that, when using the ``ApiException`` and ``ApiError`` classes, the\nstatus code is set on the ``ApiException`` instance. This makes more\nsense when you can set multiple ``ApiError`` objects to the same\n``ApiException``:\n\n.. code:: python\n\n from flask_apiexceptions import ApiException, ApiError\n\n # ...\n\n @app.route('/testing')\n def testing():\n exc = ApiException(status_code=400)\n invalid_address_error = ApiError(code='invalid-data',\n message='The address provided is invalid.')\n invalid_phone_error = ApiError(code='invalid-data',\n message='The phone number does not exist.',\n info={'area_code': '555'})\n exc.add_error(invalid_address_error)\n exc.add_error(invalid_phone_error)\n\n raise exc\n\n # JSON response format:\n # {\"errors\": [\n # {\"code\": \"invalid-data\", \"message\": \"The address provided is invalid.\"},\n # {\"code\": \"invalid-data\", \"message\": \"The phone number does not exist.\", \"info\": {\"area_code\": \"444\"}}\n # ]}\n\nIf you only want a single ``error`` to be instantiated within the\n``ApiException``, this can be done via the constructor of the latter as a\nshorthand:\n\n.. code:: python\n\n exc = ApiException(\n status_code=400,\n code='invalid-data',\n message='The address provided is invalid',\n info={'zip_code': '90210'})\n\nwhich is the equivalent of:\n\n.. code:: python\n\n exc = ApiException(status_code=400)\n error=ApiError(\n code='invalid-data',\n message='The address provided is invalid',\n info={'zip_code': '90210'}))\n\n exc.add_error(error)\n\nA useful pattern is to subclass ``ApiException`` into distinctly useful\nexception types, on which you can define default class-level attributes that\nwill be used to populate the correct ``error`` object on instantiation. For\nexample:\n\n.. code:: python\n\n class MissingResourceError(ApiException):\n status_code = 404\n message = \"No such resource exists.\"\n code = 'not-found'\n\n # ...\n\n @app.route('/posts/')\n def post_by_id(post_id):\n \"\"\"Fetch a single post by ID from the database.\"\"\"\n\n post = Post.query.filter(Post.id == post_id).one_or_none()\n if post is None:\n raise MissingResourceError()\n\n # 404 response, wiht JSON body:\n # {\"errors\": [\n # {\"code\": \"not-found\", \"message\": \"No such resource exists.\"}\n # ]}\n\nThe nice thing about this particular pattern is that you can raise\n*semantically correct* exceptions within your codebase, and can choose to\nhandle them in the call stack. If you don't handle them, they simply bubble up\nto the exception handler (if you've configured the\n``flask_apiexceptions.api_exception_handler`` or similar) registered with\nFlask, and are then transformed into a useful response for the requesting client.\n\n.. code:: python\n\n class MissingResourceError(ApiException):\n status_code = 404\n message = \"No such resource exists.\"\n code = 'not-found'\n\n class Post(db.Model):\n # ...\n @classmethod\n def query_by_id(cls, post_id):\n \"\"\"Query Post by ID, raise exception if not found.\"\"\"\n result = cls.query.filter(cls.id == post_id).one_or_none()\n if result is None:\n raise MissingResourceError()\n\n return result\n\n @app.route('/posts/')\n def post_by_id(post_id):\n \"\"\"Fetch a single post by ID from the database.\"\"\"\n\n try:\n post = Post.query_by_id(post_id)\n except MissingResourceError as e:\n # We can do whatever we want now that we've caught the exception.\n # For the sake of illustration, we're just going to log it.\n app.logger.exception(\"Could not locate post!\")\n\n # Will bubble up the exception until it is rendered to JSON\n # for the client.\n raise e\n\n\n", "description_content_type": "", "docs_url": null, "download_url": "https://github.com/jperras/Flask-ApiExceptions/archive/1.1.2.tar.gz", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/jperras/Flask-ApiExceptions", "keywords": "flask,json,exceptions,api", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "Flask-ApiExceptions", "package_url": "https://pypi.org/project/Flask-ApiExceptions/", "platform": "any", "project_url": "https://pypi.org/project/Flask-ApiExceptions/", "project_urls": { "Download": "https://github.com/jperras/Flask-ApiExceptions/archive/1.1.2.tar.gz", "Homepage": "https://github.com/jperras/Flask-ApiExceptions" }, "release_url": "https://pypi.org/project/Flask-ApiExceptions/1.1.2/", "requires_dist": [ "Flask (>=0.10)" ], "requires_python": "", "summary": "Python exceptions serializable to Flask HTTP responses.", "version": "1.1.2" }, "last_serial": 4082774, "releases": { "1.0.5": [ { "comment_text": "", "digests": { "md5": "2d912baf4e27d19a58728c73e0fa7b21", "sha256": "681b64805bd4049708fbd32f8d7864b98e5babb82e07ab155fe6e843059bc882" }, "downloads": -1, "filename": "Flask-ApiExceptions-1.0.5.tar.gz", "has_sig": true, "md5_digest": "2d912baf4e27d19a58728c73e0fa7b21", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 5943, "upload_time": "2018-02-21T15:16:55", "url": "https://files.pythonhosted.org/packages/54/3c/7e814b937d516144889191320d0d9e4c67286523191b2fd8647682965d6f/Flask-ApiExceptions-1.0.5.tar.gz" } ], "1.0.6": [ { "comment_text": "", "digests": { "md5": "1369b86ea1582a8f8af25151785314a9", "sha256": "fd7f69e10766c41fed98136d51a239a75f69b4dd820863dbab6fa0e159703050" }, "downloads": -1, "filename": "Flask-ApiExceptions-1.0.6.tar.gz", "has_sig": true, "md5_digest": "1369b86ea1582a8f8af25151785314a9", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 6979, "upload_time": "2018-02-21T20:22:12", "url": "https://files.pythonhosted.org/packages/f0/de/72c98c5033406a98b4348c076bd1bb8394e0286da5157af9db87031cb4de/Flask-ApiExceptions-1.0.6.tar.gz" } ], "1.1.0": [ { "comment_text": "", "digests": { "md5": "b83dda418760a564c920a0d29175e1aa", "sha256": "f1072a39fb5ade2d110252dd99791e8540a200cafc1c6b88c6d800997a46680f" }, "downloads": -1, "filename": "Flask-ApiExceptions-1.1.0.tar.gz", "has_sig": true, "md5_digest": "b83dda418760a564c920a0d29175e1aa", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 7033, "upload_time": "2018-02-23T20:38:29", "url": "https://files.pythonhosted.org/packages/79/cb/32ebb3122a7004764e0f755bf2e2244b967d0eb981a5d277cddb1d1237ca/Flask-ApiExceptions-1.1.0.tar.gz" } ], "1.1.1": [ { "comment_text": "", "digests": { "md5": "d1096fc459d4a2a32f2c507674054a99", "sha256": "9966c37df232c0f3713c90e34b318fbe96e78aade7a867c7be56e4a77193d1d9" }, "downloads": -1, "filename": "Flask-ApiExceptions-1.1.1.tar.gz", "has_sig": false, "md5_digest": "d1096fc459d4a2a32f2c507674054a99", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 8136, "upload_time": "2018-02-23T23:36:50", "url": "https://files.pythonhosted.org/packages/43/4f/5db6cac63bc1f1595521f678408f629c43ef333bb005f3e381f559bfbded/Flask-ApiExceptions-1.1.1.tar.gz" } ], "1.1.2": [ { "comment_text": "", "digests": { "md5": "f60bb010434af1ef678763cbc4d8a7a5", "sha256": "db62d5751626f47a50d560e9828432f438c8d709b8bb48f42185ffd86bde9828" }, "downloads": -1, "filename": "Flask_ApiExceptions-1.1.2-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "f60bb010434af1ef678763cbc4d8a7a5", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 8247, "upload_time": "2018-07-19T15:51:00", "url": "https://files.pythonhosted.org/packages/eb/8c/e1203e99bccb5cb549e0892502fd8708c278dac5168dd2b9322ad71e906d/Flask_ApiExceptions-1.1.2-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "bb9a3d7ea7f2aecf996f16df95956169", "sha256": "98a6fcfc3e835cba73fe188dc23b8b055af04165456c7136465234a4f20be169" }, "downloads": -1, "filename": "Flask-ApiExceptions-1.1.2.tar.gz", "has_sig": false, "md5_digest": "bb9a3d7ea7f2aecf996f16df95956169", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 8887, "upload_time": "2018-07-19T15:51:02", "url": "https://files.pythonhosted.org/packages/bc/b4/63e7039ba491353268335e04186ccecc3f2f0b3b31ea3f9eec911847710a/Flask-ApiExceptions-1.1.2.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "f60bb010434af1ef678763cbc4d8a7a5", "sha256": "db62d5751626f47a50d560e9828432f438c8d709b8bb48f42185ffd86bde9828" }, "downloads": -1, "filename": "Flask_ApiExceptions-1.1.2-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "f60bb010434af1ef678763cbc4d8a7a5", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 8247, "upload_time": "2018-07-19T15:51:00", "url": "https://files.pythonhosted.org/packages/eb/8c/e1203e99bccb5cb549e0892502fd8708c278dac5168dd2b9322ad71e906d/Flask_ApiExceptions-1.1.2-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "bb9a3d7ea7f2aecf996f16df95956169", "sha256": "98a6fcfc3e835cba73fe188dc23b8b055af04165456c7136465234a4f20be169" }, "downloads": -1, "filename": "Flask-ApiExceptions-1.1.2.tar.gz", "has_sig": false, "md5_digest": "bb9a3d7ea7f2aecf996f16df95956169", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 8887, "upload_time": "2018-07-19T15:51:02", "url": "https://files.pythonhosted.org/packages/bc/b4/63e7039ba491353268335e04186ccecc3f2f0b3b31ea3f9eec911847710a/Flask-ApiExceptions-1.1.2.tar.gz" } ] }