Flask
Flask is a micro web framework written in Python and based on the Werkzeug toolkit and Jinja2 template engine.
Flask is classified as a microframework because it does not require particular tools or libraries. It has no database abstraction layer, form validation. Flask supports extensions that can add application features as if they were implemented in Flask itself.
Features
- Contains development server and debugger
- Integrated support for unit testing
- RESTful request dispatching
- Uses Jinja2 templating
- Support for secure cookies
- 100% WSGI 1.0 compliant
- Unicode-based
- Google App Engine compatibility
https://stxnext.com/blog/2018/09/27/beginners-introduction-python-frameworks
Request
request.dataContains the incoming request data as string in case it came with a mimetype Flask does not handle.
- request.args: the key/value pairs in the URL query string
- request.form: the key/value pairs in the body, from a HTML post form, or JavaScript request that isn't JSON encoded
- request.files: the files in the body, which Flask keeps separate fromform. HTML forms must useenctype=multipart/form-dataor files will not be uploaded.
- request.values: combinedargsandform, preferringargsif keys overlap
- request.json: parsed JSON data. The request must have theapplication/jsoncontent type, or userequest.get_json(force=True) to ignore the content type.
- request.data
- request.dict (type - dict)
- request.headers (type - dict)
- request.headers.get('device_id')
All of these areMultiDict instances (except forjson). You can access values using:
- request.form['name']: use indexing if you know the key exists
- request.form.get('name'): usegetif the key might not exist
- request.form.getlist('name'): usegetlistif the key is sent multiple times and you want a list of values.getonly returns the first value.
https://stackoverflow.com/questions/10434599/get-the-data-received-in-a-flask-request
Examples
https://gist.github.com/deepaksood619/99e790959f5eba6ba0815e056a8067d7
import logging
import os
import traceback
from logging.config import dictConfig
import sentry_sdk
from sentry_sdk.integrations.flask import FlaskIntegration
sentry_sdk.init(
dsn=SENTRY_DSN,
integrations=[FlaskIntegration()],
attach_stacktrace=True,
environment=ENVIRONMENT,
)
from flask import Flask, make_response, request, abort, jsonify, redirect, url_for
# logging settings
debug = eval(os.environ.get('DEBUG', 'False'))
# for sending error logs to slack
class HTTPSlackHandler(logging.Handler):
def emit(self, record):
log_entry = self.format(record)
json_text = json.dumps({"text": log_entry})
url = 'https://hooks.slack.com/services/<org_id>/<api_key>'
return requests.post(url, json_text, headers={"Content-type": "application/json"}).content
dictConfig({
"version": 1,
"disable_existing_loggers": True,
"formatters": {
"default": {
"format": "[%(asctime)s] %(levelname)s in %(module)s: %(message)s",
},
"access": {
"format": "%(message)s",
}
},
"handlers": {
"console": {
"level": "INFO",
"class": "logging.StreamHandler",
"formatter": "default",
"stream": "ext://sys.stdout",
},
"email": {
"class": "logging.handlers.SMTPHandler",
"formatter": "default",
"level": "ERROR",
"mailhost": ("smtp.example.com", 587),
"fromaddr": "devops@example.com",
"toaddrs": ["receiver@example.com", "receiver2@example.com"],
"subject": "Error Logs",
"credentials": ("username", "password"),
},
"slack": {
"class": "app.HTTPSlackHandler",
"formatter": "default",
"level": "ERROR",
},
"error_file": {
"class": "logging.handlers.RotatingFileHandler",
"formatter": "default",
"filename": "/var/log/gunicorn.error.log",
"maxBytes": 10000,
"backupCount": 10,
"delay": "True",
},
"access_file": {
"class": "logging.handlers.RotatingFileHandler",
"formatter": "access",
"filename": "/var/log/gunicorn.access.log",
"maxBytes": 10000,
"backupCount": 10,
"delay": "True",
}
},
"loggers": {
"gunicorn.error": {
"handlers": ["console"] if debug else ["console", "slack", "error_file"],
"level": "INFO",
"propagate": False,
},
"gunicorn.access": {
"handlers": ["console"] if debug else ["console", "access_file"],
"level": "INFO",
"propagate": False,
}
},
"root": {
"level": "DEBUG" if debug else "INFO",
"handlers": ["console"] if debug else ["console", "slack"],
}
})
app = Flask(__name__)
logging.warning('application started')
@app.errorhandler(404)
def resource_not_found(exception):
"""Returns exceptions as part of a json."""
return jsonify(error=str(exception)), 404
@app.route("/get_result")
def get_result():
"""Takes a job_id and returns the job's result."""
job_id = request.args["job_id"]
try:
job = Job.fetch(job_id, connection=redis_conn)
except Exception as exception:
abort(404, description=exception)
if not job.result:
abort(
404,
description=f"No result found for job_id {job.id}. Try checking the job's status.",
)
return jsonify(job.result)
@app.route('/add/<customer>', methods=["POST"])
def save_data(customer):
try:
logging.info(f'request.data: {request.data}')
logging.info(f'request.args: {request.args}')
logging.info(f'request.form: {request.form}')
logging.info(f'request.files: {request.files}')
logging.info(f'request.values: {request.values}')
logging.info(f'request.json: {request.json}')
logging.info(f'request.headers: {request.headers}')
logging.info(f'request.__dict__: {request.__dict__}')
request.method # request type
request.cookies.get('cookie_name') #cookies
if payload:
logging.info(f'payload: {payload}')
return make_response('OK', 200)
else:
return make_response('Payload Empty', 400)
except Exception as e:
logging.error(traceback.format_exc())
return make_response('FAIL', 500)
@app.route('/status', methods=["GET"])
def health_check():
return jsonify(success="OK"), 200
@app.route('/score', method=["GET"])
def score():
cust_id = request.args('cust_id')
if not cust_id:
return make_response('Pass cust_id', 400)
@app.route('/redirect')
def redirect_example():
return redirect(url_for('home')) @ sends user to /home
#set cookie
@app.route('/')
def index():
resp = make_response(render_template('index.html'))
resp.set_cookie('cookie_name', 'cookie_value')
return resp
#session handling
import session
app.config['SECRET_KEY'] = 'any random string' #must be set to use sessions
#set session
@app.route('/login_success')
def login_success():
session['key_name'] = 'key_value' #stores a secure cookie in browser
return redirect(url_for('index'))
#read session
@app.route('/')
def index():
if 'key_name' in session: #session exists and has key
session_var = session['key_value']
else:
#session does not exist
@app.before_first_request
def _run_on_start(a_string):
print "doing something important with %s" % a_string
app.add_url_rule("/userdevicesms", "userdevicesms", user_device_sms, methods=["POST"])
app.add_url_rule("/score", "score", score, methods=["GET"])
if __name__ == '__main__':
app.run(host='0.0.0.0', port='5000')
Serving
gunicorn kafka_flask_republisher:app -b 0.0.0.0:5000 --workers 2 -k gevent --timeout 300 --worker-connections 1000 --max-requests 1000000 --log-level info --limit-request-line 8190 --access-logfile -
# running
python app.py
Requirements.txt
Flask==1.1.1 gunicorn[gevent]==19.9.0
Flask
Theurl_for()function is very useful for dynamically building a URL for a specific function. The function accepts the name of a function as first argument, and one or more keyword arguments, each corresponding to the variable part of URL.
'web templating system'refers to designing an HTML script in which the variable data can be inserted dynamically. A web template system comprises of a template engine, some kind of data source and a template processor.
to_python()is used to convert the path in the URL to a Python object that will be passed to the view andto_url()is used byurl_for()to convert arguments to their appropriate forms in the URL.
https://www.tutorialspoint.com/flask
Debugging
https://blog.theodo.com/2020/05/debug-flask-vscode
Flask Extensions / Libraries / Plugins
Flask-admin - https://flask-admin.readthedocs.io/en/latest
https://www.youtube.com/watch?v=ysdShEL1HMM
Flask-blueprint
https://flask.palletsprojects.com/en/1.1.x/blueprints
https://realpython.com/flask-blueprint
Rest Libraries
Flask-Restful - https://github.com/flask-restful/flask-restful
Flask-restplus (DEAD) - https://flask-restplus.readthedocs.io/en/stable
Flask-Marshmallow - https://flask-marshmallow.readthedocs.io/en/latest
https://www.youtube.com/watch?v=Gl-5m1_eVjI
Flask-WTF - https://flask-wtf.readthedocs.io/en/stable
Security / Auth
Flask-security - https://pythonhosted.org/Flask-Security
Flask-Security allows you to quickly add common security mechanisms to your Flask application.
Flask-login - https://flask-login.readthedocs.io/en/latest
Flask-Login - https://github.com/maxcountryman/flask-login
Flask Praetorian - https://flask-praetorian.readthedocs.io/en/latest
This extesion offers a batteries-included approach to security for your API.
https://www.youtube.com/watch?v=WubG9iKXZ2g
Flask-User - https://github.com/lingthio/Flask-User
https://flask-oidc.readthedocs.io/en/latest/
Databases
Flask-SQLAlchemy - https://github.com/pallets/flask-sqlalchemy
https://towardsdatascience.com/use-flask-and-sqlalchemy-not-flask-sqlalchemy-5a64fafe22a4
geo-alchemy2 - https://geoalchemy-2.readthedocs.io/en/latest
Flask-PyMongo - https://github.com/dcrosta/flask-pymongo
Flask-mail - https://pythonhosted.org/Flask-Mail
Flask-principal - https://pythonhosted.org/Flask-Principal
Flask-sslify - https://github.com/kennethreitz-archive/flask-sslify
Flask Click
Click is a Python package for creating beautiful command line interfaces in a composable way with as little code as necessary. It's the "Command Line Interface Creation Kit". It's highly configurable but comes with sensible defaults out of the box.
https://click.palletsprojects.com/en/7.x
Flask-Uploads - https://github.com/maxcountryman/flask-uploads
Flask-Caching - https://flask-caching.readthedocs.io/en/latest
https://www.youtube.com/watch?v=iO0sL6Vyfps
https://www.youtube.com/c/PrettyPrintedTutorials/playlists
Commands
routes Show the routes for the app
run Run a development server
shell Run a shell in the app context
export FLASK_APP=flaskr
export FLASK_ENV=development
flask init-db
flask run
flask shell
from app import db
db.create_all()
from app import db, Product, Order, Customer
johndoe = Customer(first_name='John', last_name='Doe')
db.session.add(johndoe)
db.session.commit()
order = Order(coupon_code='FREE', customer_id=1, products=[computer, phone])
johndoe = Customer.query.filter_by(id=1).first().first_name
Customer.query.all()
Customer.query.filter_by(id=1).one().first_name
# for updating a column
johndoe.address = '456 fake street'
db.session.commit()
# for deleting a row
db.session.delete(johndoe)
db.session.commit()
# migrations
# directly sql queries
File structure
k8s/
src/
app/
main.py
constants.py
tests/
data/
tests.py
.dockerignore
.flake8
.gitignore
.isort.cfg
.pre-commit-config.yaml
config.yaml
credentials.json
credentials_sample.json
dev.env
docker-compose.yaml
Dockerfile
Jenkinsfile
README
requirements.txt
Coding Snippets
@backoff.on_exception(
backoff.expo, (requests.exceptions.Timeout, requests.exceptions.ConnectionError)
)
def populate(destination):
url = f"{destination}/populate"
requests.get(url)
if __name__ == "__main__":
destination = os.getenv("DESTINATION", "http://localhost:8000")
populate(destination)
while True:
send_requests(destination)
time.sleep(5)
Flask upload to s3
https://www.zabana.me/notes/flask-tutorial-upload-files-amazon-s3
Resources
https://www.freecodecamp.org/news/learn-the-flask-python-web-framework-by-building-a-market-platform