{"id":21134080,"url":"https://github.com/horgix/willish","last_synced_at":"2025-10-08T16:03:53.895Z","repository":{"id":95051432,"uuid":"108045571","full_name":"Horgix/willish","owner":"Horgix","description":"A simple wishlist API","archived":false,"fork":false,"pushed_at":"2017-10-24T22:00:57.000Z","size":42,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-08T16:02:46.549Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Horgix.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-10-23T22:06:47.000Z","updated_at":"2017-10-23T22:07:15.000Z","dependencies_parsed_at":"2023-06-11T23:15:28.958Z","dependency_job_id":null,"html_url":"https://github.com/Horgix/willish","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Horgix/willish","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Horgix%2Fwillish","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Horgix%2Fwillish/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Horgix%2Fwillish/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Horgix%2Fwillish/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Horgix","download_url":"https://codeload.github.com/Horgix/willish/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Horgix%2Fwillish/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278972327,"owners_count":26078019,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-08T02:00:06.501Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-20T06:23:01.385Z","updated_at":"2025-10-08T16:03:53.847Z","avatar_url":"https://github.com/Horgix.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"Warning, this is being developed, not even alpha yet.\n\n\u003chttp://flask.pocoo.org/docs/0.12/patterns/errorpages/\u003e\n\u003chttp://flask.pocoo.org/docs/0.12/errorhandling/\u003e\n\u003chttp://flask.pocoo.org/snippets/83/\u003e\n\n# Introduction\n\nThis `README.md` covers all the development process of this appplication.\nIt is intended to later be transformed into a blog post of some kind, giving\npeople a real world example of the development process of a simple but clean\napp.\n\n# Methods\n\nWe're going to define the following endpoints on ou API:\n\n- `GET   http://example.com/wishes`\n    - Obtain information about a resource\n    - Retrieve wishes list\n\n- `GET   http://example.com/wishes/123`\n    - Obtain information about a resource\n    - Retrieve wish #123\n\n- `POST  http://example.com/wishes`\n    - Create a new resource\n    - Create a new wish, from data provided with the request\n\n- `PUT   http://example.com/wishes/123`\n    - Update a resource\n    - Update wish #123, from data provided with the request\n\n- `DELETE http://example.com/wishes/123`\n    - Delete a resource\n    - Delete wish #123\n\n# API versioning\n\nThis will be taken care of in a later release, with proper version in\nAccepted-Content, etc., not in URL\n\n# Methods\n\n- GET     `http://[hostname]/wishes`            Retrieve list of wishes\n- GET     `http://[hostname]/wishes/[wish_id]`  Retrieve a wish based on its ID\n- POST    `http://[hostname]/wishes`            Create a new wish, from data\n  provided with the request\n- PUT     `http://[hostname]/wishes/[wish_id]`  Update an existing wish based\n  on its ID, from data provided with the request\n- DELETE  `http://[hostname]/wishes/[wish_id]`  Delete a wish based on its ID\n\n# What's a wish?\n\nFirst question we have to ask ourselves: **what is a wish**?\n\nIt's nice and everything to talk about API, versionning, bla, bla, bla, but we\nhave to start from the functionnal (some would say \"business\") side: what is\nthe purpose and the needs of what we're building?\n\nA *Wish* is something we thrive to acquire. Well, you get the point of a\nwishlist I guess.\n\nLet's define a *Wish* as follow:\n\n- `id`: Numeric. Unique identifier. Generated.\n- `name`: String. *Wish* item name.\n- `link`: String. Link to somewhere the wish can be found/bought.\n- `acquired`: Bool. *Wish* obtention status.\n\nThis may, of course, be expanded later one. Here are some examples:\n\n- Price\n- Multiple links\n\n# Virtualenv\n\nLet's dive a bit in the code!\n\n```\nmake venv\n```\n\n# Dependencies\n\n`requirements.txt` contains a pip freeze\n\n# What I did\n\n`pip install flask`\n\n```raw\n$ pip install flask\nCollecting flask\n  Downloading Flask-0.12.1-py2.py3-none-any.whl (82kB)\n    100% |████████████████████████████████| 92kB 4.6MB/s\nCollecting click\u003e=2.0 (from flask)\n  Downloading click-6.7-py2.py3-none-any.whl (71kB)\n    100% |████████████████████████████████| 71kB 7.2MB/s\nCollecting itsdangerous\u003e=0.21 (from flask)\n  Downloading itsdangerous-0.24.tar.gz (46kB)\n    100% |████████████████████████████████| 51kB 9.2MB/s\nCollecting Werkzeug\u003e=0.7 (from flask)\n  Downloading Werkzeug-0.12.1-py2.py3-none-any.whl (312kB)\n    100% |████████████████████████████████| 317kB 1.5MB/s\nCollecting Jinja2\u003e=2.4 (from flask)\n  Using cached Jinja2-2.9.6-py2.py3-none-any.whl\nCollecting MarkupSafe\u003e=0.23 (from Jinja2\u003e=2.4-\u003eflask)\nBuilding wheels for collected packages: itsdangerous\n  Running setup.py bdist_wheel for itsdangerous ... done\n  Stored in directory: /home/horgix/.cache/pip/wheels/fc/a8/66/24d655233c757e178d45dea2de22a04c6d92766abfb741129a\nSuccessfully built itsdangerous\nInstalling collected packages: click, itsdangerous, Werkzeug, MarkupSafe, Jinja2, flask\nSuccessfully installed Jinja2-2.9.6 MarkupSafe-1.0 Werkzeug-0.12.1 click-6.7 flask-0.12.1 itsdangerous-0.24\n```\n\n`afdd9879e7dc104ab6fa707103e957c553624467`\n\n`chmod +x willish.py`\n\n```raw\n$ ./willish.py\n * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n * Restarting with stat\n * Debugger is active!\n * Debugger PIN: 191-272-850\n127.0.0.1 - - [03/May/2017 13:31:17] \"GET / HTTP/1.1\" 200 -\n```\n\n```raw\n$ curl localhost:5000\nHello, World!%\n```\n\n\u003chttp://flask.pocoo.org/docs/0.12/quickstart/#http-methods\u003e\nand it's already handling HEAD for us :D\n\n# GET /wishes\n\nAdd details about `curl -i`\n\n```raw\n$ curl -i localhost:5000/wishes\nHTTP/1.0 200 OK\nContent-Type: application/json\nContent-Length: 357\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Wed, 03 May 2017 13:37:17 GMT\n```\n```json\n{\n  \"wishes\": [\n    {\n      \"acquired\": true, \n      \"id\": 1, \n      \"link\": \"https://duck-duck-go.myshopify.com/collections/frontpage/products/duckduckgo-t-shirt\", \n      \"name\": \"DuckDuckGo t-shirt\"\n    }, \n    {\n      \"acquired\": false, \n      \"id\": 2, \n      \"link\": \"https://supporters.eff.org/shop/eff-lapel-pin\", \n      \"name\": \"EFF pin\"\n    }\n  ]\n}\n```\n\n```raw\n$ ./willish.py\n * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n * Restarting with stat\n * Debugger is active!\n * Debugger PIN: 191-272-850\n127.0.0.1 - - [03/May/2017 15:37:03] \"GET /wishes/1 HTTP/1.1\" 404 -\n127.0.0.1 - - [03/May/2017 15:37:17] \"GET /wishes HTTP/1.1\" 200 -\n```\n\n\nbtw 404\n\n```raw\n$ curl -i localhost:5000/wishes/1\nHTTP/1.0 404 NOT FOUND\nContent-Type: text/html\nContent-Length: 233\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Wed, 03 May 2017 13:37:03 GMT\n\n\u003c!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\"\u003e\n\u003ctitle\u003e404 Not Found\u003c/title\u003e\n\u003ch1\u003eNot Found\u003c/h1\u003e\n\u003cp\u003eThe requested URL was not found on the server.  If you entered the URL manually please check your spelling and try again.\u003c/p\u003e\n```\n\n# Add GET /wishes/\u003cwish_id\u003e\n\n\u003chttp://flask.pocoo.org/docs/0.12/quickstart/#variable-rules\u003e\n\n```python\n@app.route('/wishes/\u003cint:wish_id\u003e', methods=['GET'])\ndef get_wish(wish_id):\n    return jsonify({'wish': wishes[wish_id]})\n```\n\n```raw\n$ curl -i localhost:5000/wishes/1\nHTTP/1.0 200 OK\nContent-Type: application/json\nContent-Length: 142\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Wed, 03 May 2017 13:57:24 GMT\n```\n```json\n{\n  \"wish\": {\n    \"acquired\": false,\n    \"id\": 2,\n    \"link\": \"https://supporters.eff.org/shop/eff-lapel-pin\",\n    \"name\": \"EFF pin\"\n  }\n}\n```\n\nFine ! (At least we believe it. I intentionally introduced a bug to be fixed\nlater and which will give us a reason for writing good tests)\nNow an unknown id...\n\n```raw\n$ curl -i localhost:5000/wishes/2\n\nHTTP/1.0 500 INTERNAL SERVER ERROR\nContent-Type: text/html; charset=utf-8\nX-XSS-Protection: 0\nConnection: close\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Wed, 03 May 2017 13:57:44 GMT\n```\n```html\n\u003c!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n  \"http://www.w3.org/TR/html4/loose.dtd\"\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003ctitle\u003eIndexError: list index out of range // Werkzeug Debugger\u003c/title\u003e\n    \u003clink rel=\"stylesheet\" href=\"?__debugger__=yes\u0026amp;cmd=resource\u0026amp;f=style.css\"\n        type=\"text/css\"\u003e\n    \u003c!-- We need to make sure this has a favicon so that the debugger does\n         not by accident trigger a request to /favicon.ico which might\n         change the application state. --\u003e\n    \u003clink rel=\"shortcut icon\"\n        href=\"?__debugger__=yes\u0026amp;cmd=resource\u0026amp;f=console.png\"\u003e\n    \u003cscript src=\"?__debugger__=yes\u0026amp;cmd=resource\u0026amp;f=jquery.js\"\u003e\u003c/script\u003e\n    \u003cscript src=\"?__debugger__=yes\u0026amp;cmd=resource\u0026amp;f=debugger.js\"\u003e\u003c/script\u003e\n    \u003cscript type=\"text/javascript\"\u003e\n      var TRACEBACK = 140000030783080,\n          CONSOLE_MODE = false,\n          EVALEX = true,\n          EVALEX_TRUSTED = false,\n          SECRET = \"GeFqnZX5UMpgchZHapHh\";\n    \u003c/script\u003e\n  \u003c/head\u003e\n  \u003cbody style=\"background-color: #fff\"\u003e\n    \u003cdiv class=\"debugger\"\u003e\n\u003ch1\u003ebuiltins.IndexError\u003c/h1\u003e\n\u003cdiv class=\"detail\"\u003e\n  \u003cp class=\"errormsg\"\u003eIndexError: list index out of range\u003c/p\u003e\n\u003c/div\u003e\n\u003ch2 class=\"traceback\"\u003eTraceback \u003cem\u003e(most recent call last)\u003c/em\u003e\u003c/h2\u003e\n\u003cdiv class=\"traceback\"\u003e\n  \n  \u003cul\u003e\u003cli\u003e\u003cdiv class=\"frame\" id=\"frame-140000030783416\"\u003e\n  \u003ch4\u003eFile \u003ccite class=\"filename\"\u003e\"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\"\u003c/cite\u003e,\n      line \u003cem class=\"line\"\u003e1997\u003c/em\u003e,\n      in \u003ccode class=\"function\"\u003e__call__\u003c/code\u003e\u003c/h4\u003e\n  \u003cdiv class=\"source\"\u003e\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e                \u003c/span\u003eerror = None\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003ectx.auto_pop(error)\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e\u003c/span\u003e \u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e    \u003c/span\u003edef __call__(self, environ, start_response):\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003e\u0026quot;\u0026quot;\u0026quot;Shortcut for :attr:`wsgi_app`.\u0026quot;\u0026quot;\u0026quot;\u003c/pre\u003e\n\u003cpre class=\"line current\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003ereturn self.wsgi_app(environ, start_response)\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e\u003c/span\u003e \u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e    \u003c/span\u003edef __repr__(self):\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003ereturn '\u0026lt;%s %r\u0026gt;' % (\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003eself.__class__.__name__,\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003eself.name,\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cli\u003e\u003cdiv class=\"frame\" id=\"frame-140000030783864\"\u003e\n  \u003ch4\u003eFile \u003ccite class=\"filename\"\u003e\"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\"\u003c/cite\u003e,\n      line \u003cem class=\"line\"\u003e1985\u003c/em\u003e,\n      in \u003ccode class=\"function\"\u003ewsgi_app\u003c/code\u003e\u003c/h4\u003e\n  \u003cdiv class=\"source\"\u003e\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003etry:\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003etry:\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e                \u003c/span\u003eresponse = self.full_dispatch_request()\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003eexcept Exception as e:\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e                \u003c/span\u003eerror = e\u003c/pre\u003e\n\u003cpre class=\"line current\"\u003e\u003cspan class=\"ws\"\u003e                \u003c/span\u003eresponse = self.handle_exception(e)\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003eexcept:\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e                \u003c/span\u003eerror = sys.exc_info()[1]\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e                \u003c/span\u003eraise\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003ereturn response(environ, start_response)\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003efinally:\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cli\u003e\u003cdiv class=\"frame\" id=\"frame-140000030783920\"\u003e\n  \u003ch4\u003eFile \u003ccite class=\"filename\"\u003e\"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\"\u003c/cite\u003e,\n      line \u003cem class=\"line\"\u003e1540\u003c/em\u003e,\n      in \u003ccode class=\"function\"\u003ehandle_exception\u003c/code\u003e\u003c/h4\u003e\n  \u003cdiv class=\"source\"\u003e\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003e# if we want to repropagate the exception, we can attempt to\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003e# raise it with the whole traceback in case we can do that\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003e# (the function was actually called from the except part)\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003e# otherwise, we just raise the error again\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003eif exc_value is e:\u003c/pre\u003e\n\u003cpre class=\"line current\"\u003e\u003cspan class=\"ws\"\u003e                \u003c/span\u003ereraise(exc_type, exc_value, tb)\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003eelse:\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e                \u003c/span\u003eraise e\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e\u003c/span\u003e \u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003eself.log_exception((exc_type, exc_value, tb))\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003eif handler is None:\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cli\u003e\u003cdiv class=\"frame\" id=\"frame-140000030783976\"\u003e\n  \u003ch4\u003eFile \u003ccite class=\"filename\"\u003e\"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/_compat.py\"\u003c/cite\u003e,\n      line \u003cem class=\"line\"\u003e33\u003c/em\u003e,\n      in \u003ccode class=\"function\"\u003ereraise\u003c/code\u003e\u003c/h4\u003e\n  \u003cdiv class=\"source\"\u003e\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e    \u003c/span\u003efrom io import StringIO\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e\u003c/span\u003e \u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e    \u003c/span\u003edef reraise(tp, value, tb=None):\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003eif value.__traceback__ is not tb:\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003eraise value.with_traceback(tb)\u003c/pre\u003e\n\u003cpre class=\"line current\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003eraise value\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e\u003c/span\u003e \u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e    \u003c/span\u003eimplements_to_string = _identity\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e\u003c/span\u003e \u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e\u003c/span\u003eelse:\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e    \u003c/span\u003etext_type = unicode\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cli\u003e\u003cdiv class=\"frame\" id=\"frame-140000030783640\"\u003e\n  \u003ch4\u003eFile \u003ccite class=\"filename\"\u003e\"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\"\u003c/cite\u003e,\n      line \u003cem class=\"line\"\u003e1982\u003c/em\u003e,\n      in \u003ccode class=\"function\"\u003ewsgi_app\u003c/code\u003e\u003c/h4\u003e\n  \u003cdiv class=\"source\"\u003e\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003ectx = self.request_context(environ)\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003ectx.push()\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003eerror = None\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003etry:\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003etry:\u003c/pre\u003e\n\u003cpre class=\"line current\"\u003e\u003cspan class=\"ws\"\u003e                \u003c/span\u003eresponse = self.full_dispatch_request()\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003eexcept Exception as e:\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e                \u003c/span\u003eerror = e\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e                \u003c/span\u003eresponse = self.handle_exception(e)\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003eexcept:\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e                \u003c/span\u003eerror = sys.exc_info()[1]\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cli\u003e\u003cdiv class=\"frame\" id=\"frame-140000030784088\"\u003e\n  \u003ch4\u003eFile \u003ccite class=\"filename\"\u003e\"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\"\u003c/cite\u003e,\n      line \u003cem class=\"line\"\u003e1614\u003c/em\u003e,\n      in \u003ccode class=\"function\"\u003efull_dispatch_request\u003c/code\u003e\u003c/h4\u003e\n  \u003cdiv class=\"source\"\u003e\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003erequest_started.send(self)\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003erv = self.preprocess_request()\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003eif rv is None:\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e                \u003c/span\u003erv = self.dispatch_request()\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003eexcept Exception as e:\u003c/pre\u003e\n\u003cpre class=\"line current\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003erv = self.handle_user_exception(e)\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003ereturn self.finalize_request(rv)\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e\u003c/span\u003e \u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e    \u003c/span\u003edef finalize_request(self, rv, from_error_handler=False):\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003e\u0026quot;\u0026quot;\u0026quot;Given the return value from a view function this finalizes\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003ethe request by converting it into a response and invoking the\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cli\u003e\u003cdiv class=\"frame\" id=\"frame-140000030784144\"\u003e\n  \u003ch4\u003eFile \u003ccite class=\"filename\"\u003e\"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\"\u003c/cite\u003e,\n      line \u003cem class=\"line\"\u003e1517\u003c/em\u003e,\n      in \u003ccode class=\"function\"\u003ehandle_user_exception\u003c/code\u003e\u003c/h4\u003e\n  \u003cdiv class=\"source\"\u003e\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003ereturn self.handle_http_exception(e)\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e\u003c/span\u003e \u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003ehandler = self._find_error_handler(e)\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e\u003c/span\u003e \u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003eif handler is None:\u003c/pre\u003e\n\u003cpre class=\"line current\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003ereraise(exc_type, exc_value, tb)\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003ereturn handler(e)\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e\u003c/span\u003e \u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e    \u003c/span\u003edef handle_exception(self, e):\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003e\u0026quot;\u0026quot;\u0026quot;Default exception handling that kicks in when an exception\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003eoccurs that is not caught.  In debug mode the exception will\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cli\u003e\u003cdiv class=\"frame\" id=\"frame-140000030784200\"\u003e\n  \u003ch4\u003eFile \u003ccite class=\"filename\"\u003e\"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/_compat.py\"\u003c/cite\u003e,\n      line \u003cem class=\"line\"\u003e33\u003c/em\u003e,\n      in \u003ccode class=\"function\"\u003ereraise\u003c/code\u003e\u003c/h4\u003e\n  \u003cdiv class=\"source\"\u003e\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e    \u003c/span\u003efrom io import StringIO\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e\u003c/span\u003e \u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e    \u003c/span\u003edef reraise(tp, value, tb=None):\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003eif value.__traceback__ is not tb:\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003eraise value.with_traceback(tb)\u003c/pre\u003e\n\u003cpre class=\"line current\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003eraise value\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e\u003c/span\u003e \u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e    \u003c/span\u003eimplements_to_string = _identity\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e\u003c/span\u003e \u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e\u003c/span\u003eelse:\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e    \u003c/span\u003etext_type = unicode\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cli\u003e\u003cdiv class=\"frame\" id=\"frame-140000030783696\"\u003e\n  \u003ch4\u003eFile \u003ccite class=\"filename\"\u003e\"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\"\u003c/cite\u003e,\n      line \u003cem class=\"line\"\u003e1612\u003c/em\u003e,\n      in \u003ccode class=\"function\"\u003efull_dispatch_request\u003c/code\u003e\u003c/h4\u003e\n  \u003cdiv class=\"source\"\u003e\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003eself.try_trigger_before_first_request_functions()\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003etry:\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003erequest_started.send(self)\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003erv = self.preprocess_request()\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003eif rv is None:\u003c/pre\u003e\n\u003cpre class=\"line current\"\u003e\u003cspan class=\"ws\"\u003e                \u003c/span\u003erv = self.dispatch_request()\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003eexcept Exception as e:\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003erv = self.handle_user_exception(e)\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003ereturn self.finalize_request(rv)\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e\u003c/span\u003e \u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e    \u003c/span\u003edef finalize_request(self, rv, from_error_handler=False):\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cli\u003e\u003cdiv class=\"frame\" id=\"frame-140000030784312\"\u003e\n  \u003ch4\u003eFile \u003ccite class=\"filename\"\u003e\"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\"\u003c/cite\u003e,\n      line \u003cem class=\"line\"\u003e1598\u003c/em\u003e,\n      in \u003ccode class=\"function\"\u003edispatch_request\u003c/code\u003e\u003c/h4\u003e\n  \u003cdiv class=\"source\"\u003e\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003e# request came with the OPTIONS method, reply automatically\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003eif getattr(rule, 'provide_automatic_options', False) \\\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e           \u003c/span\u003eand req.method == 'OPTIONS':\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e            \u003c/span\u003ereturn self.make_default_options_response()\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003e# otherwise dispatch to the handler for that endpoint\u003c/pre\u003e\n\u003cpre class=\"line current\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003ereturn self.view_functions[rule.endpoint](**req.view_args)\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e\u003c/span\u003e \u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e    \u003c/span\u003edef full_dispatch_request(self):\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003e\u0026quot;\u0026quot;\u0026quot;Dispatches the request and on top of that performs request\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003epre and postprocessing as well as HTTP exception catching and\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e        \u003c/span\u003eerror handling.\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cli\u003e\u003cdiv class=\"frame\" id=\"frame-140000030784368\"\u003e\n  \u003ch4\u003eFile \u003ccite class=\"filename\"\u003e\"/home/horgix/work/willish/willish.py\"\u003c/cite\u003e,\n      line \u003cem class=\"line\"\u003e28\u003c/em\u003e,\n      in \u003ccode class=\"function\"\u003eget_wish\u003c/code\u003e\u003c/h4\u003e\n  \u003cdiv class=\"source\"\u003e\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e\u003c/span\u003edef get_wishes():\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e    \u003c/span\u003ereturn jsonify({'wishes': wishes})\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e\u003c/span\u003e \u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e\u003c/span\u003e@app.route('/wishes/\u0026lt;int:wish_id\u0026gt;', methods=['GET'])\u003c/pre\u003e\n\u003cpre class=\"line before\"\u003e\u003cspan class=\"ws\"\u003e\u003c/span\u003edef get_wish(wish_id):\u003c/pre\u003e\n\u003cpre class=\"line current\"\u003e\u003cspan class=\"ws\"\u003e    \u003c/span\u003ereturn jsonify({'wish': wishes[wish_id]})\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e\u003c/span\u003e \u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e\u003c/span\u003e@app.route('/')\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e\u003c/span\u003edef index():\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e    \u003c/span\u003ereturn \u0026quot;Hello, World!\u0026quot;\u003c/pre\u003e\n\u003cpre class=\"line after\"\u003e\u003cspan class=\"ws\"\u003e\u003c/span\u003e \u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003c/ul\u003e\n  \u003cblockquote\u003eIndexError: list index out of range\u003c/blockquote\u003e\n\u003c/div\u003e\n\n\u003cdiv class=\"plain\"\u003e\n  \u003cform action=\"/?__debugger__=yes\u0026amp;cmd=paste\" method=\"post\"\u003e\n    \u003cp\u003e\n      \u003cinput type=\"hidden\" name=\"language\" value=\"pytb\"\u003e\n      This is the Copy/Paste friendly version of the traceback.  \u003cspan\n      class=\"pastemessage\"\u003eYou can also paste this traceback into\n      a \u003ca href=\"https://gist.github.com/\"\u003egist\u003c/a\u003e:\n      \u003cinput type=\"submit\" value=\"create paste\"\u003e\u003c/span\u003e\n    \u003c/p\u003e\n    \u003ctextarea cols=\"50\" rows=\"10\" name=\"code\" readonly\u003eTraceback (most recent call last):\n  File \u0026quot;/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\u0026quot;, line 1997, in __call__\n    return self.wsgi_app(environ, start_response)\n  File \u0026quot;/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\u0026quot;, line 1985, in wsgi_app\n    response = self.handle_exception(e)\n  File \u0026quot;/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\u0026quot;, line 1540, in handle_exception\n    reraise(exc_type, exc_value, tb)\n  File \u0026quot;/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/_compat.py\u0026quot;, line 33, in reraise\n    raise value\n  File \u0026quot;/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\u0026quot;, line 1982, in wsgi_app\n    response = self.full_dispatch_request()\n  File \u0026quot;/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\u0026quot;, line 1614, in full_dispatch_request\n    rv = self.handle_user_exception(e)\n  File \u0026quot;/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\u0026quot;, line 1517, in handle_user_exception\n    reraise(exc_type, exc_value, tb)\n  File \u0026quot;/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/_compat.py\u0026quot;, line 33, in reraise\n    raise value\n  File \u0026quot;/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\u0026quot;, line 1612, in full_dispatch_request\n    rv = self.dispatch_request()\n  File \u0026quot;/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\u0026quot;, line 1598, in dispatch_request\n    return self.view_functions[rule.endpoint](**req.view_args)\n  File \u0026quot;/home/horgix/work/willish/willish.py\u0026quot;, line 28, in get_wish\n    return jsonify({'wish': wishes[wish_id]})\nIndexError: list index out of range\u003c/textarea\u003e\n  \u003c/form\u003e\n\u003c/div\u003e\n\u003cdiv class=\"explanation\"\u003e\n  The debugger caught an exception in your WSGI application.  You can now\n  look at the traceback which led to the error.  \u003cspan class=\"nojavascript\"\u003e\n  If you enable JavaScript you can also use additional features such as code\n  execution (if the evalex feature is enabled), automatic pasting of the\n  exceptions and much more.\u003c/span\u003e\n\u003c/div\u003e\n      \u003cdiv class=\"footer\"\u003e\n        Brought to you by \u003cstrong class=\"arthur\"\u003eDON'T PANIC\u003c/strong\u003e, your\n        friendly Werkzeug powered traceback interpreter.\n      \u003c/div\u003e\n    \u003c/div\u003e\n\n    \u003cdiv class=\"pin-prompt\"\u003e\n      \u003cdiv class=\"inner\"\u003e\n        \u003ch3\u003eConsole Locked\u003c/h3\u003e\n        \u003cp\u003e\n          The console is locked and needs to be unlocked by entering the PIN.\n          You can find the PIN printed out on the standard output of your\n          shell that runs the server.\n        \u003cform\u003e\n          \u003cp\u003ePIN:\n            \u003cinput type=text name=pin size=14\u003e\n            \u003cinput type=submit name=btn value=\"Confirm Pin\"\u003e\n        \u003c/form\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n\n\u003c!--\n\nTraceback (most recent call last):\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1997, in __call__\n    return self.wsgi_app(environ, start_response)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1985, in wsgi_app\n    response = self.handle_exception(e)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1540, in handle_exception\n    reraise(exc_type, exc_value, tb)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/_compat.py\", line 33, in reraise\n    raise value\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1982, in wsgi_app\n    response = self.full_dispatch_request()\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1614, in full_dispatch_request\n    rv = self.handle_user_exception(e)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1517, in handle_user_exception\n    reraise(exc_type, exc_value, tb)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/_compat.py\", line 33, in reraise\n    raise value\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1612, in full_dispatch_request\n    rv = self.dispatch_request()\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1598, in dispatch_request\n    return self.view_functions[rule.endpoint](**req.view_args)\n  File \"/home/horgix/work/willish/willish.py\", line 28, in get_wish\n    return jsonify({'wish': wishes[wish_id]})\nIndexError: list index out of range\n\n--\u003e\n```\n\nIMAGE1\n\nand on server side :\n\n```\n$ ./willish.py\n\n * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n * Restarting with stat\n * Debugger is active!\n * Debugger PIN: 191-272-850\n127.0.0.1 - - [03/May/2017 15:57:24] \"GET /wishes/1 HTTP/1.1\" 200 -\n127.0.0.1 - - [03/May/2017 15:57:44] \"GET /wishes/2 HTTP/1.1\" 500 -\nTraceback (most recent call last):\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1997, in __call__\n    return self.wsgi_app(environ, start_response)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1985, in wsgi_app\n    response = self.handle_exception(e)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1540, in handle_exception\n    reraise(exc_type, exc_value, tb)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/_compat.py\", line 33, in reraise\n    raise value\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1982, in wsgi_app\n    response = self.full_dispatch_request()\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1614, in full_dispatch_request\n    rv = self.handle_user_exception(e)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1517, in handle_user_exception\n    reraise(exc_type, exc_value, tb)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/_compat.py\", line 33, in reraise\n    raise value\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1612, in full_dispatch_request\n    rv = self.dispatch_request()\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1598, in dispatch_request\n    return self.view_functions[rule.endpoint](**req.view_args)\n  File \"/home/horgix/work/willish/willish.py\", line 28, in get_wish\n    return jsonify({'wish': wishes[wish_id]})\nIndexError: list index out of range\n```\n\nBut if we change this:\n\n```python\nif __name__ == '__main__':\n    app.run(debug=True)\n```\n\nto that:\n\n```python\nif __name__ == '__main__':\n    app.run(debug=False)\n```\n\nWe just get the bare Error :\n\n```\n$ curl -i localhost:5000/wishes/2\n\nHTTP/1.0 500 INTERNAL SERVER ERROR\nContent-Type: text/html\nContent-Length: 291\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Wed, 03 May 2017 13:59:28 GMT\n\n\u003c!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\"\u003e\n\u003ctitle\u003e500 Internal Server Error\u003c/title\u003e\n\u003ch1\u003eInternal Server Error\u003c/h1\u003e\n\u003cp\u003eThe server encountered an internal error and was unable to complete your request.  Either the server is overloaded or there is an error in the application.\u003c/p\u003e\n```\n\nOne more reason to tune errors sent by Flask when working on an API!\n\n\n\nWhy did it fail?\nWell, we aren't checking that the ID is valid.\n\n\n# Adding error handling on this\n\n```python\n@app.route('/wishes/\u003cint:wish_id\u003e', methods=['GET'])\ndef get_wish(wish_id):\n    try:\n        return jsonify({'wish': wishes[wish_id]})\n    except IndexError:\n        return jsonify({'error': 'index not found'})\n```\n\n```raw\n$ ./willish.py\n\n * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n * Restarting with stat\n * Debugger is active!\n * Debugger PIN: 191-272-850\n127.0.0.1 - - [03/May/2017 16:03:13] \"GET /wishes/2 HTTP/1.1\" 200 -\n```\n\nAnd it's working :\n\n```raw\n$ curl -i localhost:5000/wishes/2\n\nHTTP/1.0 200 OK\nContent-Type: application/json\nContent-Length: 33\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Wed, 03 May 2017 14:03:13 GMT\n```\n```json\n{\n  \"error\": \"index not found\"\n}\n```\n\nBut it's not really HTTP compliant... 200 code for an error...\n\n# Handling error better\n\nFlask to the rescue! `from flask import abort`\n\nChange this:\n\n```python\nreturn jsonify({'error': 'index not found'})\n```\n\ninto that:\n\n```python\nabort(404)\n```\n\nand you're done :)\n\n```raw\n$ ./willish.py\n\n * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n * Restarting with stat\n * Debugger is active!\n * Debugger PIN: 191-272-850\n127.0.0.1 - - [03/May/2017 16:16:51] \"GET /wishes/2 HTTP/1.1\" 404 -\n```\n\n```raw\n$ curl -i localhost:5000/wishes/2\n\nHTTP/1.0 404 NOT FOUND\nContent-Type: text/html\nContent-Length: 233\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Wed, 03 May 2017 14:16:51 GMT\n```\n\n```html\n\u003c!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\"\u003e\n\u003ctitle\u003e404 Not Found\u003c/title\u003e\n\u003ch1\u003eNot Found\u003c/h1\u003e\n\u003cp\u003eThe requested URL was not found on the server.  If you entered the URL manually please check your spelling and try again.\u003c/p\u003e\n```\n\nYay, 404\n\nBut sadly, it's still returning HTML, which is bad for a REST API.\n\n# JSON 404\n\nThanks to Flask, we can override the error handler that gets called by\n`abort()` :)\n\n```python\n@app.errorhandler(404)\ndef not_found(error):\n    return jsonify({'error': 'Not found'})\n```\n\nwhich seems to be sending JSON:\n\n```raw\n$ curl -i localhost:5000/wishes/2\n\nHTTP/1.0 200 OK\nContent-Type: application/json\nContent-Length: 27\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Wed, 03 May 2017 14:39:25 GMT\n```\n```json\n{\n  \"error\": \"Not found\"\n}\n```\n\nBut we lost the 404 status again :(\nHow can we send it?\n\nWe'll be using `make_response` in our error handler as suggested in\n\u003chttp://flask.pocoo.org/docs/0.12/quickstart/#about-responses\u003e\n\nDocumentation:\n\u003chttp://flask.pocoo.org/docs/0.12/api/#flask.Flask.make_response\u003e\n\n\n```python\n@app.errorhandler(404)\ndef not_found(error):\n    return make_response(jsonify({'error': 'Not found'}), 404)\n```\n\nand adding the `make_response` to the `from flask import ...` stuff\n\nand it's working fine :\n\n```raw\n$ curl -i localhost:5000/wishes/2\n\nHTTP/1.0 404 NOT FOUND\nContent-Type: application/json\nContent-Length: 27\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Wed, 03 May 2017 15:42:33 GMT\n```\n```json\n{\n  \"error\": \"Not found\"\n}\n```\n\nNote the HTTP status code, the Content-Type header and the body. No longer 200,\nno longer HTML: we won!\n\nFor more information on error handling and JSON outputs:\n\n- \u003chttp://flask.pocoo.org/docs/0.12/patterns/apierrors/\u003e\n- \u003chttp://flask.pocoo.org/snippets/83/\u003e\n\nWe'll come back on that later I guess.\n\n# POST\n\nThis will be a long part, but we're entering the really useful part!\n\nFirst we need to ask ourselves this question : what is mandatory in order to\ncreate a wish? Reminder, we handle the following attributes right now :\n\n- `id` : this will be generated automatically so we'll not take it from POST\n  request data\n- `name` : having a name but no link probably makes sense\n- `link` : having a link but no name probably makes sense too\n- `acquired` : we'll set it to False at creation time, this will not be taken\n  either from POST request data. It would be non sense to add an item we\n  already acquired to a wishlist, wouldn't it?\n\nSooo, at the end, we require either `name` or `link`, and we should fail if\nnone of them is provided in the request. For the rest/REST (what a joke.), it's\nup to us to decide what we do with parameter we don't need, i.e. if a client\nsend us a `cake_taste` field or the `id` or `acquired` field which we'll not\ntake into account anyway. Should we send an error, forcing the client to send\nperfectly conform requests? Should we just ignore it? **I'm going for the\nignore thing**, it will be way easier, but we'll have to document it with our\nAPI so people can expect it.\n\n## Implementation\n\n- add request to from flask import ...\n\nRequest doc : http://flask.pocoo.org/docs/0.12/api/#incoming-request-data\n\n### First\n\nLet's implement something really dumb simple: we'll just check that provided JSON is\nvalid before trying to do anything with the JSON data.\n\nFlask provides the `is_json` field with following description\n\u003chttp://flask.pocoo.org/docs/0.12/api/#flask.Request.is_json\u003e :\n\n\u003e Indicates if this request is JSON or not. By default a request is considered\n\u003e to include JSON data if the mimetype is `application/json` or\n\u003e `application/*+json`.\n\n```python\n@app.route('/wishes', methods=['POST'])\ndef add_wish():\n    print(request.is_json)\n    return jsonify({})\n```\n\nLet's see how it behaves:\n\n```\n$ curl -i -H \"Content-Type: application/json\" -X POST -d '{\"name\":\"New keyboard\"}' http://localhost:5000/wishes\n\nHTTP/1.0 200 OK\nContent-Type: application/json\nContent-Length: 3\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Thu, 04 May 2017 16:56:17 GMT\n\n{}\n```\n\n\n```\n$ ./willish.py\n\n * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n * Restarting with stat\n * Debugger is active!\n * Debugger PIN: 191-272-850\nTrue\n127.0.0.1 - - [04/May/2017 18:56:17] \"POST /wishes HTTP/1.1\" 200 -\n```\n\nYay, fine! `is_json` returns `True`.\n\nHowever, I'm a bit worried about the `Content-Type`-based check. What if\nsomeone sends us a request with `application/json` as `Content-Type` but\nwithout a real JSON object in data? Or an invalid one? Let's try!\n\n```\n$ curl -i -H \"Content-Type: application/json\" -X POST -d '{\"name\":\"New keyboard' http://localhost:5000/wishes\nHTTP/1.0 200 OK\nContent-Type: application/json\nContent-Length: 3\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Thu, 04 May 2017 16:54:29 GMT\n\n{}\n```\n\n```\n$ ./willish.py\n * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n * Restarting with stat\n * Debugger is active!\n * Debugger PIN: 191-272-850\nTrue\n127.0.0.1 - - [04/May/2017 18:58:04] \"POST /wishes HTTP/1.1\" 200 -\n```\n\nWell, `True` too :(\n\nBut there is another field, `json`, that can saves us !\n\u003chttp://flask.pocoo.org/docs/0.12/api/#flask.Request.json\u003e\n\n\n\u003e If the mimetype is application/json this will contain the parsed JSON data. Otherwise this will be None.\n\u003e\n\u003e The get_json() method should be used instead.\n\nOk, we'll use `get_json()` to access fields, but  looks like we can use `json`\nto assert for parse-able json.\n\nLet's verify that.\n\n```python\n@app.route('/wishes', methods=['POST'])\ndef add_wish():\n    print(request.json)\n    return jsonify({})\n```\n\n```\n$ curl -i -H \"Content-Type: application/json\" -X POST -d '{\"name\":\"New keyboard\"}' http://localhost:5000/wishes\n\nHTTP/1.0 200 OK\nContent-Type: application/json\nContent-Length: 3\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Thu, 04 May 2017 17:02:42 GMT\n\n{}\n```\n\n```\n$ ./willish.py\n\n * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n * Restarting with stat\n * Debugger is active!\n * Debugger PIN: 191-272-850\n{'name': 'New keyboard'}\n127.0.0.1 - - [04/May/2017 19:02:42] \"POST /wishes HTTP/1.1\" 200 -\n```\n\nOk so valid JSON works as expected.\n\nLet's try invalid JSON:\n\n```\n$ curl -i -H \"Content-Type: application/json\" -X POST -d '{\"name\":\"New keyboard' http://localhost:5000/wishes\n\nHTTP/1.0 400 BAD REQUEST\nContent-Type: text/html\nContent-Length: 203\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Thu, 04 May 2017 17:03:34 GMT\n\n\u003c!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\"\u003e\n\u003ctitle\u003e400 Bad Request\u003c/title\u003e\n\u003ch1\u003eBad Request\u003c/h1\u003e\n\u003cp\u003eFailed to decode JSON object: Unterminated string starting at: line 1 column 9 (char 8)\u003c/p\u003e\n```\n\n```\n$ ./willish.py\n\n * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n * Restarting with stat\n * Debugger is active!\n * Debugger PIN: 191-272-850\n127.0.0.1 - - [04/May/2017 19:03:34] \"POST /wishes HTTP/1.1\" 400 -\n```\n\nNice!\nHowever, once more, we're getting a HTML body, just like for the 404 stuff. You\nknow how this ends up; let's override the handler for the 400 error !\n\nhttp://flask.pocoo.org/docs/0.12/errorhandling/#registering\n\n\n```python\n@app.errorhandler(400)\ndef bad_request(error):\n    return make_response(jsonify({'error': 'Bad Request'}), 400)\n```\n\n```raw\n$ curl -i -H \"Content-Type: application/json\" -X POST -d '{\"name\":\"New keyboard' http://localhost:5000/wishes\n\nHTTP/1.0 400 BAD REQUEST\nContent-Type: application/json\nContent-Length: 29\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Thu, 04 May 2017 17:11:09 GMT\n```\n```json\n{\n  \"error\": \"Bad Request\"\n}\n```\n\n```\n$ ./willish.py\n * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n * Restarting with stat\n * Debugger is active!\n * Debugger PIN: 191-272-850\n127.0.0.1 - - [04/May/2017 19:12:00] \"POST /wishes HTTP/1.1\" 400 -\n```\n\nIt works... but we lost an information here. If you take a look at the HTML\nprecedently returned, you'll see `\u003cp\u003eFailed to decode JSON object: Unterminated\nstring starting at: line 1 column 9 (char 8)\u003c/p\u003e`. This was returning us the\nerror details!\n\nWait. Maybe this is because we run the app with `app.run(debug=False)` ? Let's\ntry with `debug=True` and without our errorhandler.\n\n```raw\n$ curl -i -H \"Content-Type: application/json\" -X POST -d '{\"name\":\"New keyboard' http://localhost:5000/wishes\n\nHTTP/1.0 400 BAD REQUEST\nContent-Type: text/html\nContent-Length: 192\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Thu, 04 May 2017 17:13:16 GMT\n\n\u003c!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\"\u003e\n\u003ctitle\u003e400 Bad Request\u003c/title\u003e\n\u003ch1\u003eBad Request\u003c/h1\u003e\n\u003cp\u003eThe browser (or proxy) sent a request that this server could not understand.\u003c/p\u003e\n```\n\n```raw\n$ ./willish.py\n\n * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n127.0.0.1 - - [04/May/2017 19:14:28] \"POST /wishes HTTP/1.1\" 400 -\n\n```\n\nOk so the error is handled differently if the debug mode is enabled or not,\nwhich makes sense.\n\nThe difference can be found in the `flask/wrappers.py` file, line 167 :\n\n```python\ndef on_json_loading_failed(self, e):\n    \"\"\"Called if decoding of the JSON data failed.  The return value of\n    this method is used by :meth:`get_json` when an error occurred.  The\n    default implementation just raises a :class:`BadRequest` exception.\n\n    .. versionchanged:: 0.10\n       Removed buggy previous behavior of generating a random JSON\n       response.  If you want that behavior back you can trivially\n       add it by subclassing.\n\n    .. versionadded:: 0.8\n    \"\"\"\n    ctx = _request_ctx_stack.top\n    if ctx is not None and ctx.app.config.get('DEBUG', False):\n        raise BadRequest('Failed to decode JSON object: {0}'.format(e))\n    raise BadRequest()\n```\n\nI actually have to thank JetBrain's PyCharm for finding this, the debugger with\nstep-by-step run was immensely useful.\n\nSo, the BadRequest returned is directly modified when raised according to the\ndebug mode. So we actually will not to have to handle that by ourselves, cool\n:)\n\nLet's see how it goes :\n\n```python\n@app.errorhandler(400)\ndef bad_request(error):\n    return make_response(jsonify({'error': str(error)}), 400)\n```\n\nIn `debug=False`:\n\n```raw\n$ curl -i -H \"Content-Type: application/json\" -X POST -d '{\"name\":\"New keyboard' http://localhost:5000/wishes\n\nHTTP/1.0 400 BAD REQUEST\nContent-Type: application/json\nContent-Length: 111\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Thu, 04 May 2017 21:57:23 GMT\n```\n```json\n{\n  \"error\": \"400 Bad Request: The browser (or proxy) sent a request that this server could not understand.\"\n}\n\n```\n\nIn `debug=True`:\n\n```raw\n$ curl -i -H \"Content-Type: application/json\" -X POST -d '{\"name\":\"New keyboard' http://localhost:5000/wishes\n\nHTTP/1.0 400 BAD REQUEST\nContent-Type: application/json\nContent-Length: 122\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Thu, 04 May 2017 21:57:32 GMT\n```\n```json\n{\n  \"error\": \"400 Bad Request: Failed to decode JSON object: Unterminated string starting at: line 1 column 9 (char 8)\"\n}\n```\n\nExactly what we wanted!\n\n- JSON `Content-Type`\n- JSON body in answer\n- Real error message, with details in debug mode\n\nLet's do the same for the 404 error and transform this:\n\n```python\n@app.errorhandler(404)\ndef not_found(error):\n    return make_response(jsonify({'error': 'Not found'}), 404)\n```\n\nin that:\n\n```python\n@app.errorhandler(404)\ndef not_found(error):\n    return make_response(jsonify({'error': str(error)}), 404)\n```\n\nSo now instead of:\n\n```raw\n$ curl -i localhost:5000/wishes/4\nHTTP/1.0 404 NOT FOUND\nContent-Type: application/json\nContent-Length: 27\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Thu, 04 May 2017 22:02:00 GMT\n```\n```json\n{\n  \"error\": \"Not found\"\n}\n```\n\nwe get:\n\n```raw\n$ curl -i localhost:5000/wishes/4\nHTTP/1.0 404 NOT FOUND\nContent-Type: application/json\nContent-Length: 154\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Thu, 04 May 2017 22:03:43 GMT\n```\n```json\n{\n  \"error\": \"404 Not Found: The requested URL was not found on the server.  If you entered the URL manually please check your spelling and try again.\"\n}\n```\n\nWe'll take a look later at how we could generalize this error handling for JSON.\n\ncommit `2c588f1ce864e94ff393d9001d6808891f5f15f5` here\n\n### Second\n\nWell. At the beginning we wanted to implement the POST request and ended up\ntuning errors. Let's get back to the POST itself.\n\nAfter thinking about it, we'll need the JSON data anyway and we'll have to call\n`get_json()` so let's substitute `request.json` by `request.get_json()` right\naway.\nReminder: we were just checking that JSON was parse-able, but we also need to\nassert that the `Content-Type` is `application/json`. If it's not, `get_json()`\nwill return None so let's check that:\n\n\nWith something like that:\n\n```\n@app.route('/wishes', methods=['POST'])\ndef add_wish():\n    json = request.get_json()\n    name = json['name']\n    return jsonify({'name': name})\n```\n\nwe end up with\n\n```\n$ curl -i -H \"Content-Type: application/json\" -X POST -d '{\"name\":\"New keyboard\"}' http://localhost:5000/wishes\nHTTP/1.0 200 OK\nContent-Type: application/json\nContent-Length: 29\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Fri, 05 May 2017 10:05:09 GMT\n\n{\n  \"name\": \"New keyboard\"\n}\n```\n\nand in case of error in the JSON\n\n```\n$ curl -i -H \"Content-Type: application/json\" -X POST -d '{\"name\":\"New keyboard' http://localhost:5000/wishes\nHTTP/1.0 400 BAD REQUEST\nContent-Type: application/json\nContent-Length: 122\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Fri, 05 May 2017 10:05:36 GMT\n\n{\n  \"error\": \"400 Bad Request: Failed to decode JSON object: Unterminated string starting at: line 1 column 9 (char 8)\"\n}\n```\nNow let's check with a bad content-type:\n\n```\n$ curl -i -H \"Content-Type: application/json2\" -X POST -d '{\"name\":\"New keyboard\"}' http://localhost:5000/wishes\nHTTP/1.0 500 INTERNAL SERVER ERROR\nContent-Type: text/html; charset=utf-8\nX-XSS-Protection: 0\nConnection: close\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Fri, 05 May 2017 10:06:17 GMT\n\n\u003c!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n[...]\n```\n\n```\n/home/horgix/work/willish/venv/bin/python /home/horgix/work/willish/willish.py\n * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n * Restarting with stat\n * Debugger is active!\n * Debugger PIN: 191-272-850\n127.0.0.1 - - [05/May/2017 12:06:17] \"POST /wishes HTTP/1.1\" 500 -\nTraceback (most recent call last):\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1997, in __call__\n    return self.wsgi_app(environ, start_response)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1985, in wsgi_app\n    response = self.handle_exception(e)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1540, in handle_exception\n    reraise(exc_type, exc_value, tb)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/_compat.py\", line 33, in reraise\n    raise value\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1982, in wsgi_app\n    response = self.full_dispatch_request()\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1614, in full_dispatch_request\n    rv = self.handle_user_exception(e)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1517, in handle_user_exception\n    reraise(exc_type, exc_value, tb)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/_compat.py\", line 33, in reraise\n    raise value\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1612, in full_dispatch_request\n    rv = self.dispatch_request()\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1598, in dispatch_request\n    return self.view_functions[rule.endpoint](**req.view_args)\n  File \"/home/horgix/work/willish/willish.py\", line 40, in add_wish\n    name = json['name']\nTypeError: 'NoneType' object is not subscriptable\n```\n\nFail :( Let's handle when that `get_json()` returns `None`:\n\nAdd a check:\n\n```\n@app.route('/wishes', methods=['POST'])\ndef add_wish():\n    json = request.get_json()\n    name = json['name']\n    if json is None:\n        abort(400)\n    return jsonify({'name': name})\n```\n\n```\n$ curl -i -H \"Content-Type: application/json2\" -X POST -d '{\"name\":\"New keyboard\"}' http://localhost:5000/wishes\nHTTP/1.0 400 BAD REQUEST\nContent-Type: application/json\nContent-Length: 111\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Fri, 05 May 2017 10:07:57 GMT\n\n{\n  \"error\": \"400 Bad Request: The browser (or proxy) sent a request that this server could not understand.\"\n}\n```\n\n```\n/home/horgix/work/willish/venv/bin/python /home/horgix/work/willish/willish.py\n * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n * Restarting with stat\n * Debugger is active!\n * Debugger PIN: 191-272-850\n127.0.0.1 - - [05/May/2017 12:07:57] \"POST /wishes HTTP/1.1\" 400 -\n```\n\nGood !\n\nNow we are properly handling:\n\n- Invalid Content-Type\n- Invalid JSON\n- Other benefits already listed above\n\n### Third\n\nNow let's ensure that the json contains either the name or the title\nNow let's make sure we have `name` or `link` defined. Should be\nstraightforward. Before implementing it, I want to make a note in the HTTP code\nthat we have to return in case of error here. \n\nFrom \u003chttps://en.wikipedia.org/wiki/List_of_HTTP_status_codes\u003e :\n\u003e 422 Unprocessable Entity (WebDAV; RFC 4918)\n\u003e     The request was well-formed but was unable to be followed due to semantic errors\n\nIndeed, 400 Bad Request is fine for bad json, malformed stuff, etc. But for\nwell formatted requests but with bad data, the 422 fits better.\n\nLet's implement the check.\n\n```\n@app.route('/wishes', methods=['POST'])\ndef add_wish():\n    json = request.get_json()\n    if json is None:\n        abort(400)\n    if 'name' not in json and 'link' not in json:\n        abort(422)\n    return jsonify({'name': 'placeholder'})\n```\n\nOf course, just like we did until now, we have to override the error handler\nfor 422 to make it send JSON :\n\n```\n@app.errorhandler(422)\ndef unprocessable_entity(error):\n    return make_response(jsonify({'error': str(error)}), 422)\n```\n\nLet's test it:\n\n```\n$ curl -i -H \"Content-Type: application/json\" -X POST -d '{\"name2\":\"New keyboard\"}' http://localhost:5000/wishes\nHTTP/1.0 422 UNPROCESSABLE ENTITY\nContent-Type: application/json\nContent-Length: 125\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Fri, 05 May 2017 13:04:37 GMT\n\n{\n  \"error\": \"422 Unprocessable Entity: The request was well-formed but was unable to be followed due to semantic errors.\"\n}\n```\n\n```\n127.0.0.1 - - [05/May/2017 15:04:37] \"POST /wishes HTTP/1.1\" 422 -\n```\n\nNice!\n\nNow we want to add the real wish in the list, and return it in the data and\nwith HTTP 201 that stands for \"Resource created\"\n\n```\n@app.route('/wishes', methods=['POST'])\ndef add_wish():\n    json = request.get_json()\n    if json is None:\n        abort(400)\n    if 'name' not in json and 'link' not in json:\n        abort(422)\n    new_wish = {\n            'id': 42,\n            'name': json.get('name'),\n            'link': json.get('link'),\n            'acquired': False\n            }\n    wishes.append(new_wish)\n    return jsonify(new_wish), 201\n```\n\nNote a few things:\n\n- We use '42' as a placeholder for the ID right now\n- the `json.get('name')` and `json.get('link')` will return null if a the field\n  is not present in the dict, which is fine\n- We're setting acquired to False right away, as noted earlier\n- We append it to the list of existing wishes before returning anything\n- We are returning 201\n- We are returning the object we just created\n\n```\n$ curl -i -H \"Content-Type: application/json\" -X POST -d '{\"name\":\"New keyboard\"}' http://localhost:5000/wishes\nHTTP/1.0 201 CREATED\nContent-Type: application/json\nContent-Length: 81\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Fri, 05 May 2017 13:24:14 GMT\n\n\n{\n  \"acquired\": false,\n  \"id\": 42,\n  \"link\": null,\n  \"name\": \"New keyboard\"\n}\n\n```\n\nWow, it's working! And we should be able to get it by GET-ing /wishes :\n\n```\n$ curl -i http://localhost:5000/wishes\nHTTP/1.0 200 OK\nContent-Type: application/json\nContent-Length: 464\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Fri, 05 May 2017 13:24:27 GMT\n\n{\n  \"wishes\": [\n    {\n      \"acquired\": true,\n      \"id\": 1,\n      \"link\": \"https://duck-duck-go.myshopify.com/collections/frontpage/products/duckduckgo-t-shirt\",\n      \"name\": \"DuckDuckGo t-shirt\"\n    },\n    {\n      \"acquired\": false,\n      \"id\": 2,\n      \"link\": \"https://supporters.eff.org/shop/eff-lapel-pin\",\n      \"name\": \"EFF pin\"\n    },\n    {\n      \"acquired\": false,\n      \"id\": 42,\n      \"link\": null,\n      \"name\": \"New keyboard\"\n    }\n  ]\n}\n```\n\nYay, it works! However, note that there is absolutely no check Done on the ID\nhere and its just a placeholder, which allow stuff like that to happen (not\nstopping  the server from the previous run):\n\n```\n$ curl -i -H \"Content-Type: application/json\" -X POST -d '{\"link\":\"http://schwag.archlinux.ca/\"}' http://localhost:5000/wishes\nHTTP/1.0 201 CREATED\nContent-Type: application/json\nContent-Length: 96\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Fri, 05 May 2017 13:30:52 GMT\n\n{\n  \"acquired\": false,\n  \"id\": 42,\n  \"link\": \"http://schwag.archlinux.ca/\",\n  \"name\": null\n}\n```\n\n```\n$ curl -i http://localhost:5000/wishes\nHTTP/1.0 200 OK\nContent-Type: application/json\nContent-Length: 586\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Fri, 05 May 2017 13:30:54 GMT\n\n{\n  \"wishes\": [\n    {\n      \"acquired\": true,\n      \"id\": 1,\n      \"link\": \"https://duck-duck-go.myshopify.com/collections/frontpage/products/duckduckgo-t-shirt\",\n      \"name\": \"DuckDuckGo t-shirt\"\n    },\n    {\n      \"acquired\": false,\n      \"id\": 2,\n      \"link\": \"https://supporters.eff.org/shop/eff-lapel-pin\",\n      \"name\": \"EFF pin\"\n    },\n    {\n      \"acquired\": false,\n      \"id\": 42,\n      \"link\": null,\n      \"name\": \"New keyboard\"\n    },\n    {\n      \"acquired\": false,\n      \"id\": 42,\n      \"link\": \"http://schwag.archlinux.ca/\",\n      \"name\": null\n    }\n  ]\n}\n```\n\nOne good thing, one bad!\n\n- Good : we just validated that we can also add tasks with only a link\n- Bad : we just created two tasks with the same ID\n\nLong story short, we'll have to handle the ID smartly. And this is not the kind\nof things you want to do, we are here just because we're using a poor in memory\n\"database\". In real world, the DB itself would handle that id generation and\nuniqueness (and we'll come to this, don't worry).\n\nWe're going to do something dirty (sorryyy).\n\nAdd a global variable `max_id`:\n\n```\nmax_id = 2\n```\n\n2 since we have 2 wishes in our base \"database\"\n\nand then increment it and assign the new value to the new wish:\n\n```\n    max_id += 1\n    new_id = max_id\n    new_wish = {\n            'id': max_id,\n            'name': json.get('name'),\n            'link': json.get('link'),\n            'acquired': False\n            }\n```\n\nThis doesn't work :\n\n```\n127.0.0.1 - - [05/May/2017 15:37:53] \"POST /wishes HTTP/1.1\" 500 -\nTraceback (most recent call last):\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1997, in __call__\n    return self.wsgi_app(environ, start_response)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1985, in wsgi_app\n    response = self.handle_exception(e)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1540, in handle_exception\n    reraise(exc_type, exc_value, tb)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/_compat.py\", line 33, in reraise\n    raise value\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1982, in wsgi_app\n    response = self.full_dispatch_request()\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1614, in full_dispatch_request\n    rv = self.handle_user_exception(e)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1517, in handle_user_exception\n    reraise(exc_type, exc_value, tb)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/_compat.py\", line 33, in reraise\n    raise value\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1612, in full_dispatch_request\n    rv = self.dispatch_request()\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1598, in dispatch_request\n    return self.view_functions[rule.endpoint](**req.view_args)\n  File \"/home/horgix/work/willish/willish.py\", line 48, in add_wish\n    max_id += 1\nUnboundLocalError: local variable 'max_id' referenced before assignment\n```\n\nHere, it's more global python knowledge than anything else. Long story short\n(again), it's due to python scoping and to the fact that we're trying to\nreassign a global variable so we need to specify it explicitly by flagging it\nas `global`:\n\n```\n    global max_id\n    max_id += 1\n    new_id = max_id\n    new_wish = {\n            'id': max_id,\n            'name': json.get('name'),\n            'link': json.get('link'),\n            'acquired': False\n            }\n```\n\nLet's try this :\n\n```\n$ curl -i -H \"Content-Type: application/json\" -X POST -d '{\"name\":\"New keyboard\"}' http://localhost:5000/wishes\nHTTP/1.0 201 CREATED\nContent-Type: application/json\nContent-Length: 80\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Fri, 05 May 2017 13:41:52 GMT\n\n{\n  \"acquired\": false,\n  \"id\": 3,\n  \"link\": null,\n  \"name\": \"New keyboard\"\n}\n\n$ curl -i -H \"Content-Type: application/json\" -X POST -d '{\"link\":\"http://schwag.archlinux.ca/\"}' http://localhost:5000/wishes\nHTTP/1.0 201 CREATED\nContent-Type: application/json\nContent-Length: 95\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Fri, 05 May 2017 13:41:55 GMT\n\n{\n  \"acquired\": false,\n  \"id\": 4,\n  \"link\": \"http://schwag.archlinux.ca/\",\n  \"name\": null\n}\n\n$ curl -i http://localhost:5000/wishes\nHTTP/1.0 200 OK\nContent-Type: application/json\nContent-Length: 584\nServer: Werkzeug/0.12.1 Python/3.6.0\nDate: Fri, 05 May 2017 13:41:59 GMT\n\n{\n  \"wishes\": [\n    {\n      \"acquired\": true,\n      \"id\": 1,\n      \"link\": \"https://duck-duck-go.myshopify.com/collections/frontpage/products/duckduckgo-t-shirt\",\n      \"name\": \"DuckDuckGo t-shirt\"\n    },\n    {\n      \"acquired\": false,\n      \"id\": 2,\n      \"link\": \"https://supporters.eff.org/shop/eff-lapel-pin\",\n      \"name\": \"EFF pin\"\n    },\n    {\n      \"acquired\": false,\n      \"id\": 3,\n      \"link\": null,\n      \"name\": \"New keyboard\"\n    },\n    {\n      \"acquired\": false,\n      \"id\": 4,\n      \"link\": \"http://schwag.archlinux.ca/\",\n      \"name\": null\n    }\n  ]\n}\n```\n\nYay,it works :)\nWe can't just take the greater ID and increment it, else in case of POST, then\nDELETE, then POST, we could re-attribute  the same ID but to a different wish,\nwhich would be a non-sense.\n\ncommit 32f988d16d0533f17149b1efb86742360ab75e33\n\n\n\nWe'll let the PUT for modification and DELETE for deletion on the side for now.\nWe already have some stuff to test, so let's go for testing !\n\nBelow are a few notes on ideas for later :\n\n## Misc\n\n- What about using/defining some `status.HTTP_NOT_FOUND` to 404 to ensure we\n  get type right ?\n- Initiated after this post : https://blog.miguelgrinberg.com/post/designing-a-restful-api-with-python-and-flask\n- Add something about trailing slashes\n- Flask capitalizes http status :(\n- http://flask.pocoo.org/snippets/83/\n- Should we send details of failure reason with error 400 ?\n- 422 in case of unsupported Content-Type ?\n- Old ids reusability\n\n## Tests ideas\n\n- without trailing slash = trailing slash or redirect\n- uneeded post params\n- id and acquired params\n- Add item with name only\n- Add item with link only\n- Add item with link and name\n- Add, Remove, Add, check ID\n- Content type\n- JSON badly formatted\n\n\n# Tests\n\nLet's test!\n\nAfter searching for a while, I found Chakram \u003chttp://dareid.github.io/chakram/\u003e\n:\n\nFrom http://dareid.github.io/chakram/  :\n\u003e Chakram allows you to write clear and comprehensive tests,\n\u003e ensuring JSON REST endpoints work correctly as you develop and in the future\n\nFrom https://github.com/dareid/chakram  :\n\u003e Chakram is an API testing framework designed to perform end to end tests on JSON REST endpoints.\n\nThis is exactly what we want!\n\nyeah, JSON, I know, but I want to learn more about it, and it looks like this\ndoes the job.\n\nit's based on :\n\n- http://mochajs.org/ , a test runner (it will run the tests and report them,\n  and allow multiple test \"executors\" to be managed)\n- http://chaijs.com/ , an assertion library which is the \"executor\" managed by\n  mocha in context of chakram\n\n## Beginning\n\nlet's create a directory \"tests\"\n\n    npm install --save-dev chakram mocha  \n\n```\n$ npm list                                                                             [130]~16:28 Fri,May 05┘\n/home/horgix/work/willish/tests\n├─┬ chakram@1.5.0\n│ ├─┬ chai@3.5.0\n│ │ ├── assertion-error@1.0.2\n│ │ ├─┬ deep-eql@0.1.3\n│ │ │ └── type-detect@0.1.1\n│ │ └── type-detect@1.0.0\n│ ├── chai-as-promised@5.3.0\n│ ├── chai-subset@1.5.0\n│ ├── extend-object@1.0.0\n│ ├── q@1.5.0\n│ ├─┬ request@2.81.0\n│ │ ├── aws-sign2@0.6.0\n│ │ ├── aws4@1.6.0\n│ │ ├── caseless@0.12.0\n│ │ ├─┬ combined-stream@1.0.5\n│ │ │ └── delayed-stream@1.0.0\n│ │ ├── extend@3.0.1\n│ │ ├── forever-agent@0.6.1\n│ │ ├─┬ form-data@2.1.4\n│ │ │ ├── asynckit@0.4.0\n│ │ │ ├── combined-stream@1.0.5 deduped\n│ │ │ └── mime-types@2.1.15 deduped\n│ │ ├─┬ har-validator@4.2.1\n│ │ │ ├─┬ ajv@4.11.8\n│ │ │ │ ├── co@4.6.0\n│ │ │ │ └─┬ json-stable-stringify@1.0.1\n│ │ │ │   └── jsonify@0.0.0\n│ │ │ └── har-schema@1.0.5\n│ │ ├─┬ hawk@3.1.3\n│ │ │ ├─┬ boom@2.10.1\n│ │ │ │ └── hoek@2.16.3 deduped\n│ │ │ ├─┬ cryptiles@2.0.5\n│ │ │ │ └── boom@2.10.1 deduped\n│ │ │ ├── hoek@2.16.3\n│ │ │ └─┬ sntp@1.0.9\n│ │ │   └── hoek@2.16.3 deduped\n│ │ ├─┬ http-signature@1.1.1\n│ │ │ ├── assert-plus@0.2.0\n│ │ │ ├─┬ jsprim@1.4.0\n│ │ │ │ ├── assert-plus@1.0.0\n│ │ │ │ ├── extsprintf@1.0.2\n│ │ │ │ ├── json-schema@0.2.3\n│ │ │ │ └─┬ verror@1.3.6\n│ │ │ │   └── extsprintf@1.0.2 deduped\n│ │ │ └─┬ sshpk@1.13.0\n│ │ │   ├── asn1@0.2.3\n│ │ │   ├── assert-plus@1.0.0\n│ │ │   ├─┬ bcrypt-pbkdf@1.0.1\n│ │ │   │ └── tweetnacl@0.14.5 deduped\n│ │ │   ├─┬ dashdash@1.14.1\n│ │ │   │ └── assert-plus@1.0.0\n│ │ │   ├─┬ ecc-jsbn@0.1.1\n│ │ │   │ └── jsbn@0.1.1 deduped\n│ │ │   ├─┬ getpass@0.1.7\n│ │ │   │ └── assert-plus@1.0.0\n│ │ │   ├─┬ jodid25519@1.0.2\n│ │ │   │ └── jsbn@0.1.1 deduped\n│ │ │   ├── jsbn@0.1.1\n│ │ │   └── tweetnacl@0.14.5\n│ │ ├── is-typedarray@1.0.0\n│ │ ├── isstream@0.1.2\n│ │ ├── json-stringify-safe@5.0.1\n│ │ ├─┬ mime-types@2.1.15\n│ │ │ └── mime-db@1.27.0\n│ │ ├── oauth-sign@0.8.2\n│ │ ├── performance-now@0.2.0\n│ │ ├── qs@6.4.0\n│ │ ├── safe-buffer@5.0.1\n│ │ ├── stringstream@0.0.5\n│ │ ├─┬ tough-cookie@2.3.2\n│ │ │ └── punycode@1.4.1\n│ │ ├─┬ tunnel-agent@0.6.0\n│ │ │ └── safe-buffer@5.0.1 deduped\n│ │ └── uuid@3.0.1\n│ ├─┬ request-debug@0.2.0\n│ │ └── stringify-clone@1.1.1\n│ └── tv4@1.3.0\n└─┬ mocha@3.3.0\n  ├── browser-stdout@1.3.0\n  ├─┬ commander@2.9.0\n  │ └── graceful-readlink@1.0.1\n  ├─┬ debug@2.6.0\n  │ └── ms@0.7.2\n  ├── diff@3.2.0\n  ├── escape-string-regexp@1.0.5\n  ├─┬ glob@7.1.1\n  │ ├── fs.realpath@1.0.0\n  │ ├─┬ inflight@1.0.6\n  │ │ ├── once@1.4.0 deduped\n  │ │ └── wrappy@1.0.2\n  │ ├── inherits@2.0.3\n  │ ├─┬ minimatch@3.0.3\n  │ │ └─┬ brace-expansion@1.1.7\n  │ │   ├── balanced-match@0.4.2\n  │ │   └── concat-map@0.0.1\n  │ ├─┬ once@1.4.0\n  │ │ └── wrappy@1.0.2 deduped\n  │ └── path-is-absolute@1.0.1\n  ├── growl@1.9.2\n  ├── json3@3.3.2\n  ├─┬ lodash.create@3.1.1\n  │ ├─┬ lodash._baseassign@3.2.0\n  │ │ ├── lodash._basecopy@3.0.1\n  │ │ └─┬ lodash.keys@3.1.2\n  │ │   ├── lodash._getnative@3.9.1\n  │ │   ├── lodash.isarguments@3.1.0\n  │ │   └── lodash.isarray@3.0.4\n  │ ├── lodash._basecreate@3.0.3\n  │ └── lodash._isiterateecall@3.0.9\n  ├─┬ mkdirp@0.5.1\n  │ └── minimist@0.0.8\n  └─┬ supports-color@3.1.2\n    └── has-flag@1.0.0\n```\n\nWow, that's a lot.\nBut the npm install fails at the end with:\n\n```\nnpm WARN enoent ENOENT: no such file or directory, open '/home/horgix/work/willish/tests/package.json'\nnpm WARN tests No description\nnpm WARN tests No repository field.\nnpm WARN tests No README data\nnpm WARN tests No license field.\n```\n\nOk, looks like the package.json is quite similar to the requirements.txt for\npython and that npm can directly append to it, but it doesn't exist yet\n\nturns out you have to `npm init` it before, kind of like `virtualenv` creation;\nmakes sense.\n\n```\n$ npm init\nThis utility will walk you through creating a package.json file.\nIt only covers the most common items, and tries to guess sensible defaults.\n\nSee `npm help json` for definitive documentation on these fields\nand exactly what they do.\n\nUse `npm install \u003cpkg\u003e --save` afterwards to install a package and\nsave it as a dependency in the package.json file.\n\nPress ^C at any time to quit.\nname: (tests) willish-test\nversion: (1.0.0)\ndescription: Tests for Willish API\nentry point: (test.js)\ntest command: (mocha)\ngit repository:\nkeywords:\nauthor: Alexis \"Horgix\" Chotard\nlicense: (ISC) Beerware\nAbout to write to /home/horgix/work/willish/tests/package.json:\n\n{\n  \"name\": \"willish-test\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Tests for Willish API\",\n  \"main\": \"test.js\",\n  \"dependencies\": {\n    \"chakram\": \"^1.5.0\"\n  },\n  \"devDependencies\": {\n    \"mocha\": \"^3.3.0\"\n  },\n  \"scripts\": {\n    \"test\": \"mocha\"\n  },\n  \"author\": \"Alexis \\\"Horgix\\\" Chotard\",\n  \"license\": \"Beerware\"\n}\n\n\nIs this ok? (yes) yes\n```\n\nNow if we reinstall chakram and mocha, it only warn like this :\n\n```\nnpm WARN willish-test@1.0.0 No repository field.\nnpm WARN The package chakram is included as both a dev and production dependency.\n```\n\nwhich is fine for now.\nThe `package.json` now automatically contains this:\n\n```\n{\n  \"name\": \"willish-test\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Tests for Willish API\",\n  \"main\": \"test.js\",\n  \"dependencies\": {\n    \"chakram\": \"^1.5.0\"\n  },\n  \"devDependencies\": {\n    \"chakram\": \"^1.5.0\",\n    \"mocha\": \"^3.3.0\"\n  },\n  \"scripts\": {\n    \"test\": \"mocha\"\n  },\n  \"author\": \"Alexis \\\"Horgix\\\" Chotard\",\n  \"license\": \"Beerware\"\n}\n```\n\nd81c0233a97a365872942b18988e8def1053e383\n\nLet's try the chakram example:\n\n```\n$ cat test.js\nvar chakram = require('chakram');\n\ndescribe(\"Chakram\", function() {\n    it(\"should offer simple HTTP request capabilities\", function () {\n        return chakram.get(\"http://httpbin.org/get\");\n    });\n});\n```\n\n```\n$ mocha .\nzsh: command not found: mocha\n```\n\nWell, contrary to virtualenv, nothing has been set in my PATH so of course it's\nnot going to find a new command. Thanks StackOverflow here ! \nhttp://stackoverflow.com/a/24497202/2781800\n\nAnd you know what ? When we answered \"mocha\" to the \"test command\" in \"npm\ninit\", it already put it in the package.json for us! Let's call it\n\n```\n$ npm test\n\n\u003e willish-test@1.0.0 test /home/horgix/work/willish/tests\n\u003e mocha\n\n\n\n  Chakram\n    ✓ should offer simple HTTP request capabilities (322ms)\n\n\n  1 passing (332ms)\n```\n\nnice.\nLet's write out first test!\n\nChai offers 3 assertions type : expect, should, and assert\nThey cover the differences really well here : ADD LINK\n\ntl;d\n  - assert should be avoided if possible\n  - should is just like expect but with a bit more restrictions\n\nso, as in the example, we'll just use expect for now.\n\n```\nvar chakram = require('chakram'),\n    expect = chakram.expect;\n\ndescribe(\"Chakram\", function() {\n    it(\"should answer with success to a basic GET on /wishes\", function () {\n      var response = chakram.get(\"http://127.0.0.1:5000/wishes\");\n      expect(response).to.have.status(200);\n      return chakram.wait();\n    });\n});\n```\n\n- Do not forget to declare `expect`\n- We are just querying our test application directly on `127.0.0.1`, port\n  `5000`\n- We are asserting that the status code is 200, which is OK\n\n```\n\u003e npm test\n\n\u003e willish-test@1.0.0 test /home/horgix/work/willish/tests\n\u003e mocha\n\n\n\n  Chakram\n    ✓ should answer with success to a basic GET on /wishes\n\n\n  1 passing (46ms)\n```\n\nSo it's succeeding!\n\nLet's add a check to ensure the header Content-Type is here\n\n```\nvar chakram = require(\"chakram\"),\n    expect = chakram.expect;\n\ndescribe(\"Chakram\", function() {\n    it(\"should answer with success to a basic GET on /wishes\", function () {\n      var response = chakram.get(\"http://127.0.0.1:5000/wishes\");\n      expect(response).to.have.status(200);\n      expect(response).to.have.header(\"Content-Type\")\n      return chakram.wait();\n    });\n});\n```\n\n```\n\u003e npm test\n\n willish-test@1.0.0 test /home/horgix/work/willish/tests\n mocha\n\n\n\n Chakram\n   ✓ should answer with success to a basic GET on /wishes (40ms)\n\n\n 1 passing (49ms)\n```\n\nNice. However, something bothers me: if something is going to fail, I'll know\nit one fail after the other\n\nfor example, let's update the test to this:\n\n```\nvar chakram = require(\"chakram\"),\n    expect = chakram.expect;\n\ndescribe(\"Chakram\", function() {\n    it(\"should answer with success to a basic GET on /wishes\", function () {\n      var response = chakram.get(\"http://127.0.0.1:5000/wishes\");\n      expect(response).to.have.status(500);\n      expect(response).to.have.header(\"Content-Type-Wrong-Header\")\n      return chakram.wait();\n    });\n});\n```\n\nnotice the 500 and Content-Type-Wrong-Header. Both checks will fail\n\n```\n$ npm test\n\n\u003e willish-test@1.0.0 test /home/horgix/work/willish/tests\n\u003e mocha\n\n\n\n  Chakram\n    1) should answer with success to a basic GET on /wishes\n\n\n  0 passing (48ms)\n  1 failing\n\n  1) Chakram should answer with success to a basic GET on /wishes:\n     AssertionError: expected status code 200 to equal 500\n\n\n\n\nnpm ERR! Test failed.  See above for more details.\n```\n\nand yet we are only getting the information that the status code is wrong, not\nthe header. It's really oriented business tests more than sort of unit tests.\nBut I want precise tests! let's try something.\n\n```\nvar chakram = require(\"chakram\"),\n    expect = chakram.expect;\n\ndescribe(\"GET /wishes\", function() {\n    var apiResponse = chakram.get(\"http://127.0.0.1:5000/wishes\");\n    it(\"should return success status code\", function () {\n      return expect(apiResponse).to.have.status(200);\n    });\n    it(\"should have correct 'Content-Type' header\", function () {\n      return expect(apiResponse).to.have.header(\"Content-Type\")\n    });\n});\n```\n\n```\n\u003e npm test\n\n\u003e willish-test@1.0.0 test /home/horgix/work/willish/tests\n\u003e mocha\n\n\n\n  GET /wishes\n    ✓ should return success status code\n    ✓ should have correct 'Content-Type' header\n\n\n  2 passing (44ms)\n```\n\nNice. We do the request only once, but check the result in separate assertions,\ngiving us clear logs\n\n\nHowever, the following style can be found on Chakram official example\n(randomuser):\n\n\n```\nvar chakram = require(\"chakram\"),\n    expect = chakram.expect;\n\ndescribe(\"GET /wishes\", function() {\n    var apiResponse;\n\n    before(function (){\n      apiResponse = chakram.get(\"http://127.0.0.1:5000/wishes\");\n      return apiResponse;\n    });\n\n    it(\"should return success status code\", function () {\n      return expect(apiResponse).to.have.status(200);\n    });\n    it(\"should have correct 'Content-Type' header\", function () {\n      return expect(apiResponse).to.have.header(\"Content-Type\")\n    });\n});\n```\n\nThis \"before\" function is called once before the tests, not before each, so it\nbehaves exactly as what I did before without this function. What's the\ndifference ? No idea right now. TODO : check\n\nHere is were we are :\n\n```\nvar chakram = require(\"chakram\"),\n    expect = chakram.expect;\n\ndescribe(\"GET /wishes\", function() {\n    var apiResponse;\n\n    before(function (){\n      apiResponse = chakram.get(\"http://127.0.0.1:5000/wishes\");\n      return apiResponse;\n    });\n\n    it(\"should have success status code\", function () {\n      return expect(apiResponse).to.have.status(200);\n    });\n    it(\"should have correct 'Content-Type' header\", function () {\n      return expect(apiResponse).to.have.header(\"Content-Type\",\n        \"application/json\")\n    });\n    it(\"should have 'Server' header\", function () {\n      return expect(apiResponse).to.have.header(\"Server\");\n    });\n    it(\"should have 'Date' header\", function () {\n      return expect(apiResponse).to.have.header(\"Date\");\n    });\n    it(\"should have 'Content-Length' header\", function () {\n      return expect(apiResponse).to.have.header(\"Content-Length\");\n    });\n    // TODO : shouldn't leak too much server info\n});\n```\n\n\n```\n$ npm test\n\n\u003e willish-test@1.0.0 test /home/horgix/work/willish/tests\n\u003e mocha\n\n\n\n  GET /wishes\n    ✓ should have success status code\n    ✓ should have correct 'Content-Type' header\n    ✓ should have 'Server' header\n    ✓ should have 'Date' header\n    ✓ should have 'Content-Length' header\n\n\n  5 passing (47ms)\n```\n\nnote the 'before' part btw\nOk so we're checking header presence, and that the content-type is\napplication/json\n\ncommit 9b02264160426e13d1c9b713fd455e321b4e9bd5\nNow let's check the JSON itself.\nTake a look at JSON schema, link doc; etc\n\nJSON schema:\n\n\nhttps://spacetelescope.github.io/understanding-json-schema/reference/object.html#required\nhttp://json-schema.org/examples.html\n\n```\n    it(\"should have valid JSON as answer\", function () {\n    return expect(apiResponse).to.have.schema({\n      \"type\": \"object\",\n      \"required\": [\"wishes\"],\n      \"properties\": {\n        \"wishes\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"properties\": {\n              \"acquired\": { \"type\": \"boolean\" },\n              \"id\": { \"type\": \"integer\" },\n              \"link\": { \"type\": \"string\" },\n              \"name\": { \"type\": \"string\" }\n            }\n          }\n        }\n      }\n    });\n  });\n});\n```\n\nHere we go. This should do the job. Note the \"required\" part!\nTODO: we need to extend it later, after adding some wishes, to make sure they\nappear in the list by imposing the wishes array length on the test.\n\nWe'll now add tests for the POST function.\nThe http://dareid.github.io/chakram/example/dweet/ example provides some POST\nexample.\n\n\nNow we're onto something really interesting.\nI added the following test:\n\n```\ndescribe(\"POST /wishes\", function() {\n  var apiResponse;\n\n  before(function (){\n    newWish = {\n      name: \"New Keyboard\"\n    };\n    apiResponse = chakram.post(\"http://127.0.0.1:5000/wishes\", newWish);\n    return apiResponse;\n  });\n\n  it(\"should have 'Created' status code\", function () {\n    console.log(apiResponse)\n    return expect(apiResponse).to.have.status(201);\n  });\n});\n```\n\nSimple enough uh?\nWell... remember that we can omit name or link?\nThat's what we just did. And it works!\n\n```\n$ npm test\n\n\u003e willish-test@1.0.0 test /home/horgix/work/willish/tests\n\u003e mocha\n\n\n\n  GET /wishes\n    ✓ should have success status code\n    ✓ should have correct 'Content-Type' header\n    ✓ should have 'Server' header\n    ✓ should have 'Date' header\n    ✓ should have 'Content-Length' header\n    ✓ should have valid JSON as answer\n\n  POST /wishes\n{ state: 'fulfilled',\n  value:\n   { error: null,\n     response:\n      IncomingMessage {\n        _readableState: [Object],\n        readable: false,\n        domain: null,\n        _events: [Object],\n        _eventsCount: 4,\n        _maxListeners: undefined,\n        socket: [Object],\n        connection: [Object],\n        httpVersionMajor: 1,\n        httpVersionMinor: 0,\n        httpVersion: '1.0',\n        complete: true,\n        headers: [Object],\n        rawHeaders: [Object],\n        trailers: {},\n        rawTrailers: [],\n        upgrade: false,\n        url: '',\n        method: null,\n        statusCode: 201,\n        statusMessage: 'CREATED',\n        client: [Object],\n        _consuming: true,\n        _dumped: false,\n        req: [Object],\n        request: [Object],\n        toJSON: [Function: responseToJSON],\n        caseless: [Object],\n        read: [Function],\n        body: [Object] },\n     body: { acquired: false, id: 3, link: null, name: 'New Keyboard' },\n     jar: RequestJar { _jar: [Object] },\n     url: 'http://127.0.0.1:5000/wishes',\n     responseTime: 4.134612 } }\n    ✓ should have 'Created' status code\n\n\n  7 passing (61ms)\n```\n\nSo, where's the problem?\nif we re-run exactly the same tests (the whole tests, GET /wishes + POST)... it\nfails!\n\n```\n$ npm test                                                                                   19:53 Sat,May 13┘\n\n\u003e willish-test@1.0.0 test /home/horgix/work/willish/tests\n\u003e mocha\n\n\n\n  GET /wishes\n    ✓ should have success status code\n    ✓ should have correct 'Content-Type' header\n    ✓ should have 'Server' header\n    ✓ should have 'Date' header\n    ✓ should have 'Content-Length' header\n    1) should have valid JSON as answer\n\n  POST /wishes\n{ state: 'fulfilled',\n  value:\n   { error: null,\n     response:\n      IncomingMessage {\n        _readableState: [Object],\n        readable: false,\n        domain: null,\n        _events: [Object],\n        _eventsCount: 4,\n        _maxListeners: undefined,\n        socket: [Object],\n        connection: [Object],\n        httpVersionMajor: 1,\n        httpVersionMinor: 0,\n        httpVersion: '1.0',\n        complete: true,\n        headers: [Object],\n        rawHeaders: [Object],\n        trailers: {},\n        rawTrailers: [],\n        upgrade: false,\n        url: '',\n        method: null,\n        statusCode: 201,\n        statusMessage: 'CREATED',\n        client: [Object],\n        _consuming: true,\n        _dumped: false,\n        req: [Object],\n        request: [Object],\n        toJSON: [Function: responseToJSON],\n        caseless: [Object],\n        read: [Function],\n        body: [Object] },\n     body: { acquired: false, id: 4, link: null, name: 'New Keyboard' },\n     jar: RequestJar { _jar: [Object] },\n     url: 'http://127.0.0.1:5000/wishes',\n     responseTime: 4.086063 } }\n    ✓ should have 'Created' status code\n\n\n  6 passing (60ms)\n  1 failing\n\n  1) GET /wishes should have valid JSON as answer:\n     AssertionError: expected body to match JSON schema {\n  \"type\": \"object\",\n  \"required\": [\n    \"wishes\"\n  ],\n  \"properties\": {\n    \"wishes\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"acquired\": {\n            \"type\": \"boolean\"\n          },\n          \"id\": {\n            \"type\": \"integer\"\n          },\n          \"link\": {\n            \"type\": \"string\"\n          },\n          \"name\": {\n            \"type\": \"string\"\n          }\n        }\n      }\n    }\n  }\n}.\n-----\n error: Invalid type: null (expected string).\n data path: /wishes/2/link.\n schema path: /properties/wishes/items/properties/link/type.\n\n\n\n\nnpm ERR! Test failed.  See above for more details.\n```\n\nThe interesting part is this:\n\n\u003e 1) GET /wishes should have valid JSON as answer:\n\u003e data path: /wishes/2/link.\n\u003e error: Invalid type: null (expected string).\n\nSo the test on JSON schema is failing because `link` is null (we didn't specify\nany in our POST, remember!), and we told the schema it need to be a string.\nWow, honestly, tests are really powerful even when we're simply writing them!\n\nThanks StackOverflow again: \u003chttp://stackoverflow.com/questions/16241333/specify-a-value-can-be-a-string-or-null-with-json-schema#16241482\u003e\n\nIf we change the previous test to:\n\n```\n  it(\"should have valid JSON as answer\", function () {\n    return expect(apiResponse).to.have.schema({\n      \"type\": \"object\",\n      \"required\": [\"wishes\"],\n      \"properties\": {\n        \"wishes\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"properties\": {\n              \"acquired\": { \"type\": \"boolean\" },\n              \"id\": { \"type\": \"integer\" },\n              \"link\": { \"type\": [\"string\",\"null\"] },\n              \"name\": { \"type\": [\"string\",\"null\"] }\n            }\n          }\n        }\n      }\n    });\n  });\n```\n\nThe test passes again, because we specified that link and name can be a string\nOR null. Yet it doesn't ensure that we have one or the other; both shouldn't be\nable to be null, and that makes up for another test that we'll see later.\n\nJust to make sure, we're going to add \"name\" to the main test case and test\nedge cases later. Add to this Header and JSON checking to check the \"correct\"\ncase fully.\n\nlet's also add strict checks on fields that we can predict\n\n```\ndescribe(\"Basic POST on /wishes\", function() {\n  var apiResponse;\n\n  before(function (){\n    newWish = {\n      name: \"New Keyboard\",\n      link: \"http://www.thekeyboardwaffleiron.com/\"\n    };\n    apiResponse = chakram.post(\"http://127.0.0.1:5000/wishes\", newWish);\n    return apiResponse;\n  });\n\n  it(\"should have status code 201\", function () {\n    return expect(apiResponse).to.have.status(201);\n  });\n  it(\"should have 'application/json' as Content-Type\", function () {\n    return expect(apiResponse).to.have.header(\"Content-Type\",\n      \"application/json\")\n  });\n  it(\"should have a 'Server' header\", function () {\n    return expect(apiResponse).to.have.header(\"Server\");\n  });\n  it(\"should have a 'Date' header\", function () {\n    return expect(apiResponse).to.have.header(\"Date\");\n  });\n  it(\"should have a 'Content-Length' header\", function () {\n    return expect(apiResponse).to.have.header(\"Content-Length\");\n  });\n  it(\"should have valid JSON in body\", function () {\n    return expect(apiResponse).to.have.schema({\n      \"type\": \"object\",\n      \"required\": [\"acquired\", \"id\", \"link\", \"name\"],\n      \"properties\": {\n        \"acquired\": { \"type\": \"boolean\" },\n        \"id\":       { \"type\": \"integer\" },\n        \"link\":     { \"type\": [\"string\"] },\n        \"name\":     { \"type\": [\"string\"] }\n      }\n    });\n  });\n  it(\"should have correct name\", function () {\n    return expect(apiResponse).to.have.json(\"name\", \"New Keyboard\");\n  });\n  it(\"should have correct link\", function () {\n    return expect(apiResponse).to.have.json(\"link\", \"http://www.thekeyboardwaffleiron.com/\");\n  });\n  it(\"should have a non-acquired status\", function () {\n    return expect(apiResponse).to.have.json(\"acquired\", false);\n  });\n});\n```\n\nWe're starting to have a really nice test suite (I renamed a few):\n\n```\n$ npm test\n\n\u003e willish-test@1.0.0 test /home/horgix/work/willish/tests\n\u003e mocha\n\n\n\n  GET on /wishes\n    ✓ should have status code 200\n    ✓ should have 'application/json' as Content-Type\n    ✓ should have 'Server' header\n    ✓ should have 'Date' header\n    ✓ should have 'Content-Length' header\n    ✓ should have valid JSON in body\n\n  Basic POST on /wishes\n    ✓ should have status code 201\n    ✓ should have 'application/json' as Content-Type\n    ✓ should have a 'Server' header\n    ✓ should have a 'Date' header\n    ✓ should have a 'Content-Length' header\n    ✓ should have valid JSON in body\n    ✓ should have correct name\n    ✓ should have correct link\n    ✓ should have a non-acquired status\n\n\n  15 passing (71ms)\n```\n\nlet's now check for bad cases.\nLet's start with the case were we provide neither name or link, which should\nerror with 422\n\n```\ndescribe(\" POST on /wishes without name or link\", function() {\n  var apiResponse;\n\n  before(function (){\n    newWish = {};\n    apiResponse = chakram.post(\"http://127.0.0.1:5000/wishes\", newWish);\n    return apiResponse;\n  });\n\n  it(\"should have status code 422\", function () {\n    return expect(apiResponse).to.have.status(422);\n  });\n  it(\"should have 'application/json' as Content-Type\", function () {\n    return expect(apiResponse).to.have.header(\"Content-Type\",\n      \"application/json\")\n  });\n  it(\"should have error declaration as JSON in body\", function () {\n    return expect(apiResponse).to.have.schema({\n      \"type\": \"object\",\n      \"required\": [\"error\"],\n      \"properties\": {\n        \"error\": { \"type\": \"string\" }\n      }\n    });\n  });\n});\n```\n\nAnd with the case were we do not send \"application/json\" as Content-Type, which\nshould result in a 400 error\n\n```\ndescribe(\"POST on /wishes without 'application/json' Content-Type\", function() {\n  var apiResponse;\n\n  before(function (){\n    newWish = {\n      name: \"New Keyboard\",\n      link: \"http://www.thekeyboardwaffleiron.com/\"\n    };\n    apiResponse = chakram.post(\n      \"http://127.0.0.1:5000/wishes\",\n      newWish,\n      { headers: {\"Content-Type\": \"invalid/contenttype\"}}\n    );\n    return apiResponse;\n  });\n\n  it(\"should have status code 400\", function () {\n    return expect(apiResponse).to.have.status(400);\n  });\n  it(\"should have 'application/json' as Content-Type\", function () {\n    return expect(apiResponse).to.have.header(\"Content-Type\",\n      \"application/json\")\n  });\n  it(\"should have error declaration as JSON in body\", function () {\n    return expect(apiResponse).to.have.schema({\n      \"type\": \"object\",\n      \"required\": [\"error\"],\n      \"properties\": {\n        \"error\": { \"type\": \"string\" }\n      }\n    });\n  });\n});\n```\n\nNice! Our tests are starting to look good:\n\n```\n$ npm test\n\n\u003e willish-test@1.0.0 test /home/horgix/work/willish/tests\n\u003e mocha\n\n\n\n  GET on /wishes\n    ✓ should have status code 200\n    ✓ should have 'application/json' as Content-Type\n    ✓ should have 'Server' header\n    ✓ should have 'Date' header\n    ✓ should have 'Content-Length' header\n    ✓ should have valid JSON in body\n\n  Basic POST on /wishes\n    ✓ should have status code 201\n    ✓ should have 'application/json' as Content-Type\n    ✓ should have a 'Server' header\n    ✓ should have a 'Date' header\n    ✓ should have a 'Content-Length' header\n    ✓ should have valid JSON in body\n    ✓ should have correct name\n    ✓ should have correct link\n    ✓ should have a non-acquired status\n\n  POST on /wishes without name or link\n    ✓ should have status code 422\n    ✓ should have 'application/json' as Content-Type\n    ✓ should have error declaration as JSON in body\n\n  POST on /wishes without 'application/json' Content-Type\n    ✓ should have status code 400\n    ✓ should have 'application/json' as Content-Type\n    ✓ should have error declaration as JSON in body\n\n\n  21 passing (73ms)\n```\n\ncommit `47a24a4c147abf9e3ca5efa3f82b510161badca7`\n\nAs I went to implement the test of unparseable JSON, something interesting\nhappened:\n\n```\ndescribe(\"POST on /wishes with unparseable JSON\", function() {\n  var apiResponse;\n\n  before(function (){\n    // Note the missing closing curly bracket and the fact that it's a string\n    newWish = '{ name: \"New Keyboard\", link: \"http://www.thekeyboardwaffleiron.com/\"';\n    apiResponse = chakram.post(\n      \"http://127.0.0.1:5000/wishes\",\n      newWish\n    );\n    return apiResponse;\n  });\n\n  it(\"should have status code 400\", function () {\n    return expect(apiResponse).to.have.status(400);\n  });\n  it(\"should have 'application/json' as Content-Type\", function () {\n    return expect(apiResponse).to.have.header(\"Content-Type\",\n      \"application/json\")\n  });\n  it(\"should have error declaration as JSON in body\", function () {\n    return expect(apiResponse).to.have.schema({\n      \"type\": \"object\",\n      \"required\": [\"error\"],\n      \"properties\": {\n        \"error\": { \"type\": \"string\" }\n      }\n    });\n  });\n});\n```\n\nMake sure to note the \"newWish\" content : it's a string that is a JSON without\na closing curly bracket. Invalid and unparseable JSON!\nLet's run it\n\n```\n  POST on /wishes with unparseable JSON\n    1) should have status code 400\n    2) should have 'application/json' as Content-Type\n    3) should have error declaration as JSON in body\n\n[...]\n\n  1) POST on /wishes with unparseable JSON should have status code 400:\n     AssertionError: expected status code 500 to equal 400\n\n\n  2) POST on /wishes with unparseable JSON should have 'application/json' as Content-Type:\n     AssertionError: expected header Content-Type with value text/html; charset=utf-8 to match application/json\n\n\n  3) POST on /wishes with unparseable JSON should have error declaration as JSON in body:\n     AssertionError: expected body to match JSON schema {\n  \"type\": \"object\",\n  \"required\": [\n    \"error\"\n  ],\n  \"properties\": {\n    \"error\": {\n      \"type\": \"string\"\n    }\n  }\n}.\n-----\n error: Invalid type: string (expected object).\n data path: .\n schema path: /type.\n```\n\nWow, 3 fails on 3 tests, that hurts!\n\n1. We got a 500 error back instead of the expected 400\n2. We got some content in text/html instead of application/json\n3. We didn't get a JSON that matched what we wanted as a schema since, well,\n   it's html.\n\nIf you analyze it a bit... we have a 500 error (1), which we didn't rewrite the\nhandler for yet, which generates the default error message in html (2) which\nfails to match the JSON schema (3).\n\nWhy the hell do we have a 500 error?\n\nThe logs for the server are the following ones:\n\n```\n127.0.0.1 - - [16/May/2017 19:06:28] \"POST /wishes HTTP/1.1\" 500 -\nTraceback (most recent call last):\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1997, in __call__\n    return self.wsgi_app(environ, start_response)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1985, in wsgi_app\n    response = self.handle_exception(e)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1540, in handle_exception\n    reraise(exc_type, exc_value, tb)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/_compat.py\", line 33, in reraise\n    raise value\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1982, in wsgi_app\n    response = self.full_dispatch_request()\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1614, in full_dispatch_request\n    rv = self.handle_user_exception(e)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1517, in handle_user_exception\n    reraise(exc_type, exc_value, tb)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/_compat.py\", line 33, in reraise\n    raise value\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1612, in full_dispatch_request\n    rv = self.dispatch_request()\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1598, in dispatch_request\n    return self.view_functions[rule.endpoint](**req.view_args)\n  File \"/home/horgix/work/willish/willish.py\", line 54, in add_wish\n    'name': json.get('name'),\nAttributeError: 'str' object has no attribute 'get'\n```\n\nwhy the hell is it calling the json.get('name')? As a reminder, here's the code\natm:\n\n```\n@app.route('/wishes', methods=['POST'])\ndef add_wish():\n    json = request.get_json()\n    if json is None:\n        abort(400)\n    if 'name' not in json and 'link' not in json:\n        abort(422)\n    global max_id\n    max_id += 1\n    new_id = max_id\n    new_wish = {\n            'id': max_id,\n            'name': json.get('name'),\n            'link': json.get('link'),\n            'acquired': False\n            }\n    wishes.append(new_wish)\n    return jsonify(new_wish), 201\n```\n\nWe should never reach the part where json.get('name') gets called, it should\nexit straight away on this:\n\n```\n    json = request.get_json()\n    if json is None:\n        abort(400)\n```\n\nBut it doesn't. if we add a `print(json)` just before the condition it shows...\n\n```\n{ name: \"New Keyboard\", link: \"http://www.thekeyboardwaffleiron.com/\"```\n```\n\nThe `get_json()` didn't fail, and sent us a plain string!\n\njust to be sure...\n\n```\n@app.route('/wishes', methods=['POST'])\ndef add_wish():\n    json = request.get_json()\n    print(\"Failed wih type: \" + str(type(json)))\n```\n\n```\nFailed wih type: \u003cclass 'str'\u003e\n```\n\nWait. We checked that kind of stuff when we were still testing the API with\ncurl, so what changed?\n\nlet's print a bit more.\n\n\n```\n@app.route('/wishes', methods=['POST'])\ndef add_wish():\n    print(\"Full request: \" + str(request))\n    print(\"Request data: \" + str(request.data))\n    print(\"Request data type: \" + str(type(request.data)))\n    json = request.get_json()\n    print(\"Parsed json data type: \" + str(type(json)))\n    if json is None:\n        abort(400)\n[...]\n```\n\nAnd test with curl and chakram:\n\n```raw\n$ curl -i -H \"Content-Type: application/json\" -X POST -d '{\"name\":\"New keyboard' http://localhost:5000/wishes\nHTTP/1.0 400 BAD REQUEST\nContent-Type: application/json\nContent-Length: 122\nServer: Werkzeug/0.12.1 Python/3.6.1\nDate: Sun, 28 May 2017 19:10:04 GMT\n```\n```json\n{\n  \"error\": \"400 Bad Request: Failed to decode JSON object: Unterminated string starting at: line 1 column 9 (char 8)\"\n}\n```\n\nand server side:\n\n```raw\nFull request: \u003cRequest 'http://localhost:5000/wishes' [POST]\u003e\nRequest data: b'{\"name\":\"New keyboard'\nRequest data type: \u003cclass 'bytes'\u003e\n127.0.0.1 - - [28/May/2017 21:10:38] \"POST /wishes HTTP/1.1\" 400 -\n```\n\nNote that the json parsed stuff never got printed since it failed correctly and\nstopped there\n\n```\n$ npm test\n  POST on /wishes with unparseable JSON\n    1) should have status code 400\n    2) should have 'application/json' as Content-Type\n    3) should have error declaration as JSON in body\n\n\n  0 passing (56ms)\n  3 failing\n\n  1) POST on /wishes with unparseable JSON should have status code 400:\n     AssertionError: expected status code 500 to equal 400\n  \n\n  2) POST on /wishes with unparseable JSON should have 'application/json' as Content-Type:\n     AssertionError: expected header Content-Type with value text/html; charset=utf-8 to match application/json\n  \n\n  3) POST on /wishes with unparseable JSON should have error declaration as JSON in body:\n     AssertionError: expected body to match JSON schema {\n  \"type\": \"object\",\n  \"required\": [\n    \"error\"\n  ],\n  \"properties\": {\n    \"error\": {\n      \"type\": \"string\"\n    }\n  }\n}.\n-----\n error: Invalid type: string (expected object).\n data path: .\n schema path: /type.\n```\n\n\n```raw\nFull request: \u003cRequest 'http://127.0.0.1:5000/wishes' [POST]\u003e\nRequest data: b'\"{\\\\\"name\\\\\": \\\\\"New Keyboard\\\\\", \\\\\"link\\\\\": \\\\\"http://www.thekeyboardwaffleiron.com/\\\\\"\"'\nRequest data type: \u003cclass 'bytes'\u003e\nParsed json data type: \u003cclass 'str'\u003e\n127.0.0.1 - - [28/May/2017 21:11:18] \"POST /wishes HTTP/1.1\" 500 -\nTraceback (most recent call last):\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1997, in __call__\n    return self.wsgi_app(environ, start_response)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1985, in wsgi_app\n    response = self.handle_exception(e)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1540, in handle_exception\n    reraise(exc_type, exc_value, tb)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/_compat.py\", line 33, in reraise\n    raise value\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1982, in wsgi_app\n    response = self.full_dispatch_request()\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1614, in full_dispatch_request\n    rv = self.handle_user_exception(e)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1517, in handle_user_exception\n    reraise(exc_type, exc_value, tb)\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/_compat.py\", line 33, in reraise\n    raise value\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1612, in full_dispatch_request\n    rv = self.dispatch_request()\n  File \"/home/horgix/work/willish/venv/lib/python3.6/site-packages/flask/app.py\", line 1598, in dispatch_request\n    return self.view_functions[rule.endpoint](**req.view_args)\n  File \"/home/horgix/work/willish/willish.py\", line 57, in add_wish\n    'name': json.get('name'),\nAttributeError: 'str' object has no attribute 'get'\n```\n\nThe difference is quite interesting: chakram (requests actually) chose to make\nthe string a valid JSON type by enclosing my invalid json in double quotes.\nAnd actually, since it's valid JSON, `get_json()` from flask it totally able to\nparse it; hey, it's just a string which is a valid JSON object!\n\nSo. 2 things to learn here:\n\n1. We need to address this unpredicted case in our code and throw a proper error\n  (422) when we get **valid JSON** but which **cannot be parsed as a dict** and\n  thus cannot be checked against `'name' not in json` and `'link' not in json`\n  since the parsed object resulting of the `get_json()` operation is just a\n  string that doesn't implement the `get()` operator. So we need to check if\n  `json` is actually an instance of a dict, since it can also be a plain bool\n  (valid json type too)\n2. We need to split our test case in 2:\n    - One that will send only a plain string or bool or whatever, which is\n      valid json but unexpected parsed type (that will result in a 422)\n    - One that will send unparseable, invalid JSON (that will result in a 400\n      and that we were looking to test in the first place)\n\nLet's address 1:\n\n```python\n@app.route('/wishes', methods=['POST'])\ndef add_wish():\n    json = request.get_json()\n    if json is None:\n        abort(400)\n    if not isinstance(json, dict) or ('name' not in json and 'link' not in json):\n        # TODO : Maybe add error details if Debug is enabled?\n        abort(422)\n```\n\nI actually thing that json-schema (as used in chakram to check JSON format in\nrequest) may be of use here in the app to check if the JSON received has\nrequested fields, since this condition is becoming quite long. However I'm not\nsure it's able to define stuff like \"ensure there is either this field or this\none\" like we do with name and link.\n\nNow address 2:\n\nFirst, the test with a plain string, valid JSON\n\n```node\ndescribe(\"POST on /wishes with only a string in JSON\", function() {\n  var apiResponse;\n\n  before(function (){\n    // Note the missing closing curly bracket and the fact that it's a string\n    newWish = 'This is valid JSON since it is only a string, but is not a valid data format for this POST';\n    apiResponse = chakram.post(\n      \"http://127.0.0.1:5000/wishes\",\n      newWish\n    );\n    return apiResponse;\n  });\n\n  it(\"should have status code 422\", function () {\n    return expect(apiResponse).to.have.status(422);\n  });\n  it(\"should have 'application/json' as Content-Type\", function () {\n    return expect(apiResponse).to.have.header(\"Content-Type\",\n      \"application/json\")\n  });\n  it(\"should have error declaration as JSON in body\", function () {\n    return expect(apiResponse).to.have.schema({\n      \"type\": \"object\",\n      \"required\": [\"error\"],\n      \"properties\": {\n        \"error\": { \"type\": \"string\" }\n      }\n    });\n  });\n});\n```\n\n```raw\n  POST on /wishes with only a string in JSON\n    ✓ should have status code 422\n    ✓ should have 'application/json' as Content-Type\n    ✓ should have error declaration as JSON in body\n```\n\nYay. Now the second test, with a totally unparseable JSON object.\n\nHow can we build it? Or more precisely, why in the first place this request:\n\n\n```node\n    newWish = '{\"name\": \"New Keyboard\", \"link\": \"http://www.thekeyboardwaffleiron.com/\"';\n    apiResponse = chakram.post(\n      \"http://127.0.0.1:5000/wishes\",\n      newWish\n    );\n```\n\nwas transformed in:\n\n```raw\nFull request: \u003cRequest 'http://127.0.0.1:5000/wishes' [POST]\u003e\nRequest data: b'\"{\\\\\"name\\\\\": \\\\\"New Keyboard\\\\\", \\\\\"link\\\\\": \\\\\"http://www.thekeyboardwaffleiron.com/\\\\\"\"'\n```\n\nLet's take a look at the chakram documentation:\n\n\u003chttp://dareid.github.io/chakram/jsdoc/module-chakram.html#.post\u003e\n\n```\nstaticmodule:chakram.post(url, data, params){Promise}\nmethods.js, line 116\n    Perform HTTP POST request\n    Name \tType \tDescription\n    url \tstring \tfully qualified url\n    data \tObject \ta JSON serializable object (unless json is set to false in params, in which case this should be a buffer or string)\n    params \tObject \toptional additional request options, see the popular request library for options\n```\n\nHmm, I guess that \"JSON serializable\" means it will serialize it into valid\njson, hence the addition of double quotes an","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhorgix%2Fwillish","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhorgix%2Fwillish","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhorgix%2Fwillish/lists"}