Python flask
Flask allows you to call on python to generate websites, or host a REST api.
Documentation
Official Docs | http://flask.pocoo.org/docs/0.12/ |
Testing Flask Apps | http://flask.pocoo.org/docs/0.12/testing/ |
Background Jobs with progress using Flask/Celery (polling) | https://blog.miguelgrinberg.com/post/using-celery-with-flask |
Celery progress without polling | https://stackoverflow.com/questions/31059888/monitor-a-celery-task-state-without-polling |
Plugins
Python flask-restful | like argparse for API routes (ex: curl 'http://api/user' -d 'name=will' -d 'age=30+' )
|
Python flask-restless | sqlalchemy-syntax in API route queries (ex: curl 'http://api/user?q{"filters": [{"name": "age", "op": ">", "val": 30}]}' )
|
Python flask-sqlalchemy | sqlalchemy integration within flask, (authentication, connection-pooling, etc). |
Serving Content
Dynamic Websites
A website is just a REST API that handles a http GET request, and replies with html as a payload.
Knowing this, you can use flask to either host very dynamic web contents, a rest api, or just about anything else.
URL
import flask app = flask.Flask(__name__) @app.route('/hello') def hello(): return '<html><header>My Webpage</header></html> if __name__ == '__main__': app.run(debug=True)Then run your website (using a quick n' dirty webserver):
python myflaskapp.py firefox http://127.0.0.1:5000/helloURL with params
Ex: http://website.com/user/wpittman
You can also parametrize your website generation using the URL:
@app.route('/user/<string:username>') def username(username): return 'hello, {}'.format(username)https://flask.palletsprojects.com/en/1.1.x/quickstart/#variable-rules for the other types you can use.
REST API
HTTP MethodTypes Method Allows Caching? Description GET retrieve existing data POST create new data PUT modify existing data DELETE delete data PATCH HEAD TRACE CONNECT Sample Data
import flask app = flask.Flask(__name__) user_data = { 'wpittman': {'first': 'will', 'last': 'pittman'}, 'cnelder': {'first': 'chris', 'last': 'nelder'}, }GET requests
# GET request @app.route('/user/info/<string:username>', methods=['GET']) def user_info(username): if username not in user_data: flask.abort(404) # file not found return flask.jsonify(user_data[username])curl -X GET 127.0.0.1:5000/user/info/wpittman{ "first": "will", "last": "pittman" }POST requests
# POST request @app.route('/user/create', methods=['POST']) def user_create(): r = flask.request # global var (one per-worker-thread), the payload if not r.json: flask.abort(400) # bad request user_data[r.json['username']] = { 'first': r.json['first'], 'last': r.json['last'], } return flask.jsonify(user_data[r.json['username']])curl -X POST \ -d '{"username": "dvader", "first": "darth", "last": "vader"}' \ -H 'Content-Type: application/json' \ 127.0.0.1:5000/user/createHandling Multiple RequestTypes
You can also handle multiple request types from one base url:
@app.route('/user', methods=['GET', 'POST']) def user(): r = flask.request if r.method == 'GET': # handle GET elif r.method == 'POST': # handle POST return flask.jsonify({'result': True})
Responses
default responses
Success
# flask/werkzeug does not expose it's error templates reply = flask.make_response('success', 204) # HTTP-204: no contentErrors
flask.abort(200) # success flask.abort(404) # file not found # these refer to werkzeug classes werkzeug.exceptions.BadGateway()jsonify
reply = flask.jsonify({'a': 1}) # default status 200 reply.status_code = 202 # change http statuscustom
reply = flask.Response('user already exists', status=409) flask.abort(reply)wrapping requests response
Forwarding/Redirecting
TODO:
wrapping requests responses
TODO:
redirects within flask, and status codes
TODO:
url_for() and how it works
TODO:
mention my issues with reverse proxy at sub-location, and workarounds
Raising/Handling Errors
serverside
Unhandled Exceptions
Unhandled exceptions are handled by default, and return 500 - server error to the client.
Abort using default HTTP status-code page
This can be in any part of the flask app. You do not need to carry a returnvalue throughout the call stack.
flask.abort(400)Abort using custom reply
# note that python's stdlib HTTPError cannot access payload. # 'requests' 3rd party lib can work. flask.abort( flask.Response( json.dumps({'success': False}).encode('utf-8'), status=404, headers={'Content-Type': 'application/json; charset=utf-8'}, ) )clientside
python stdlib
I would not use the stdlib unless you absolutely have to:
- Python stdlib will raise an exception, but will not give you the payload of the response. This is frustrating and hard to debug.
- Python stdlib also only implements GET/POST. You can override the HTTP method, but it will not work universally.
from six.moves.urllib import requests, error try: request = requests.Request( url='your.url.com', headers={'Content-Type': 'application/json; charset=utf-8'}, data=json.dumps({'a': 'b'}).encode('utf-8'), ) response = request.urlopen(request) except(error.HTTPError) as e: print(e.reason, e.code)requests 3rd party library
url = 'http://website.com/user' headers = {'Content-Type': 'application/json; charset=utf-8'} data = { 'id': 1234, 'columns': ['firstname', 'lastname', 'middlename'], } json_data = json.dumps(data) # query toolsets response = requests.get(url=url, headers=headers, data=json_data) if response.status_code != 200: msg = ( 'An Error occurred during load.\n' 'http status: {}\n' 'text: {}\n' ).format(response.status_code, response.text) raise RuntimeError(msg) return response.json()
Logging
If your flask app is being managed by a different WSGI server, you'll probably want your loggers to use flask's loghandler.
import logging from flask.logging import default_handler logging.root.addHandler(default_handler)
Hosting In Production
WSGI Server
In order to host a flask website, you'll need to run it using a WSGI server.
See python gunicorn .
SSL
See certbot.
Troubleshooting
Resources
rest api | https://blog.miguelgrinberg.com/post/designing-a-restful-api-with-python-and-flask |
custom login | https://pythonspot.com/login-authentication-with-flask/ |