Werkzeug is a utility library for the Python programming language, in other words a toolkit for Web Server Gateway Interface (WSGI) applications, and is licensed under a BSD License. Werkzeug can realize software objects for request, response, and utility functions. It can be used to build a custom software framework on top of it
Clarifying Some Terms Before we jump in, we should address some confusing terminology associated with the interrelated concepts we will be dealing with. These three separate terms that appear interchangeable, but actually have distinct meanings: • WSGI: A Python spec that defines a standard interface for communication between an application or framework and an application/web server. This was created in order to simplify and standardize communication between these components for consistency and interchangeability. This basically defines an API interface that can be used over other protocols. • uWSGI: An application server container that aims to provide a full stack for developing and deploying web applications and services. The main component is an application server that can handle apps of different languages. It communicates with the application using the methods defined by the WSGI spec, and with other web servers over a variety of other protocols. This is the piece that translates requests from a conventional web server into a format that the application can process. • uwsgi: A fast, binary protocol implemented by the uWSGI server to communicate with a more full-featured web server. This is a wire protocol, not a transport protocol. It is the preferred way to speak to web servers that are proxying requests to uWSGI.
imported the Flask class. An instance of this class will be our WSGI application.
app = Flask(__name__)
The first argument is the name of the application’s module or package.
This is needed so that Flask knows where to look for templates, static files, and so on.
app will be the object created by the constructor Flask. __name__is a variable controlled by Python that tells code what module it's in. So __name__ is whatever you named this python file.
app.config['DEBUG'] = True
the DEBUG configuration setting for the Flask application will be enabled. This enables some behaviors that are helpful when developing Flask apps, such as displaying errors in the browser, and ensuring file changes are reloaded while the server is running (aka "host swapping")
Enables Automatic refresh of browser while modifying code
@app.route("/")
this is a decorator that creates a mapping between the path - in this case the root, or "/", and the function that we're about to define
app.run()
Pass control to the Flask object. The run function loops forever and never returns, so put it last. It carries out the responsibilities of a web server, listening for requests and sending responses over a network connection.
Setup & Installation
export FLASK_APP=flask_test.py
connects your flask python app to the flask server, then you can start the server (What is the difference between this and just starting the app with python flask_text.py ?
flask run
start flask web server
export FLASK_DEBUG=1
enter at command prompt to make Flask run in debug mode (so you don't have to stop and restart the flask server to reflect code changes)
Not mapped to specific file path a la regular HTML webpages. Instead, defined in your Python code, so use functionally descriptive and meaningful names
redirect
render_template
We are using the tools of Flask to specify that this function should receive HTTP requests at this url - /about
Handlers
The function that responds to the event?
Handlers are functions that respond to being triggered at a given URL/route? Or just the general section that includes the route?
Can name your function anything you want.
@app.before_request
To be run before any requests are executed, e.g., when you want to run something that doesn't have a route or doesn't render anything
to do checks before handling incoming requests
HTTP Requests & Flask Endpoints
- request
- Flask's way of creating objects that represent an HTTP request
- request.values
- Combined args and form, preferring args, if keys overlap
- if request.method == 'POST':
- request.endpoint
- the given route/view function, not the path or URL
- 'Note that when we use request.endpoint to match against our allowed_routes list, the endpoint is the name of the view function, not the url path. That is why in the list above we put 'login' in the allowed_routes list, rather than '/login''
- https://stackoverflow.com/questions/19261833/what-is-an-endpoint-in-flask
- Examples
- from flask import request @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': return do_the_login() else: return show_the_login_form()
- @app.route('/login', methods=['POST', 'GET']) def login(): error = None if request.method == 'POST': if valid_login(request.form['username'], request.form['password']): return log_the_user_in(request.form['username']) else: error = 'Invalid username/password' # the code below is executed if the request method # was GET or the credentials were invalid return render_template('login.html', error=error)
- Accessing Get Request Parameters
- Access GET parameters:
- A query (or GET request) parameter can be accessed via request.args:
- To access parameters submitted in the URL (?key=value) you can use the args attribute: searchword = request.args.get('key', '') We recommend accessing URL parameters with get or by catching the KeyError because users might change the URL and presenting them a 400 bad request page in that case is not user friendly. For a full list of methods and attributes of the request object, head over to the Request documentation.
- form_value = request.args.get('param_name')
- GET parameters are passed in the HTTP request as part of the URL. More specifically, they make up the query string--the portion after ?--which looked like this in the lesson:
- http://localhost:5000/hello?first_name=Chris
- Here, the query string is ?first_name=Chris. If there were multiple query parameters, they would be separated by the & (ampersand) character.
- http://localhost:5000/hello?first_name=Chris&last_name=Bay
- So, form_value = Chris
- Default Value
- request.args.get('username_error', default='')
- or just request.args.get('username_error', '')
- Accessing Post Request Parameters
- To enable a handler function to receive POST requests, we must add a methods parameter to the @app.route decorator:
- @app.route('/path', methods=['POST']) def my_handler(): # request handling code
- Can also use the form_value = request.form.get('param_name') method if you don't want it to error when there is no key present.
- Access POST parameters: request.form['param_name']
- a dictionary-like object
- request.form.keys()
- iterable of POST HTTP request
- Looping Through Post Request Parameters
- @app.route("/form-inputs", methods=['POST']) def print_form_values(): resp = "" for field in request.form.keys(): resp += "{key}: {value}
".format(key=field, value=request.form[field]) return resp
- Default Value
- title = request.form.get('title', '')
- Accessing Path Variables
- @app.route('/url_variables//') def url_variables(name: str, age: int): return name + age For the url: localhost/url_variables/38/evan
- Accessing Files
- request.files
- Access JSON
- Access Whole HTTP Message
- HTTP Error 405
- An HTTP status of 405 - Method Not Allowed will be received if a resource/path is requested that doesn't accept requests using the given method (usually, GET or POST). This can be a common mistake when setting up a form to POST to a given path, but failing to configure the handler function to accept POST requests.
- Sessions (& Cookies)
- In addition to the request object there is also a second object called session which allows you to store information specific to a user from one request to the next. This is implemented on top of cookies for you and signs the cookies cryptographically. What this means is that the user could look at the contents of your cookie but not modify it, unless they know the secret key used for signing.
- Flask will take the values you put into the session object and serialize them into a cookie. If you are finding some values do not persist across requests, cookies are indeed enabled, and you are not getting a clear error message, check the size of the cookie in your page responses compared to the size supported by web browsers.
- Besides the default client-side based sessions, if you want to handle sessions on the server-side instead, there are several Flask extensions that support this.
- check for whether this key is in the session object (and therefore if the user is logged in):
- @app.before_request def require_login(): allowed_routes = ['login', 'register'] if request.endpoint not in allowed_routes and 'email' not in session: return redirect('/login')
- session
- A session is an object (specifically, a dictionary) that you can use to store data that is associated with a specific user so that it is available for use across multiple requests.
- session['email'] = email. This creates a key called email that has a value of the user's email.
- del session['email']
- Secret Key
- app.secret_key = "#someSecretString"
- import os os.urandom(24) '\xfd{H\xe5<\x95\xf9\xe3\x96.5\xd1\x01O
- Just a one-time thing? One secret key per app? Not per user? Need to change it regularly?
- session.pop('username', None)
- remove the username from the session if it's there
- Forms & Validation
- cgi
- cgi.escape()
- quote=True
- also escapes quotes
- new_movie = cgi.escape(new_movie)
- Post/Redirect/Get Pattern
- Before I continue, I wanted to mention something important related to processing of web forms. Notice how after I process the form data, I end the request by issuing a redirect to the home page. I could have easily skipped the redirect and allowed the function to continue down into the template rendering part, since this is already the index view function. So, why the redirect? It is a standard practice to respond to a POST request generated by a web form submission with a redirect. This helps mitigate an annoyance with how the refresh command is implemented in web browsers. All the web browser does when you hit the refresh key is to re-issue the last request. If a POST request with a form submission returns a regular response, then a refresh will re-submit the form. Because this is unexpected, the browser is going to ask the user to confirm the duplicate submission, but most users will not understand what the browser is asking them. But if a POST request is answered with a redirect, the browser is now instructed to send a GET request to grab the page indicated in the redirect, so now the last request is not a POST request anymore, and the refresh command works in a more predictable way. This simple trick is called the Post/Redirect/Get pattern. It avoids inserting duplicate posts when a user inadvertently refreshes the page after submitting a web form. https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-ix-pagination
- Example
- LaunchCode
- Redirects
- redirect('/url/path')
- need to import redirect
- via HTTP status code 302
- function includes the url information you specified to redirect to in the HTML header
- redirect("/?error=" + error)
- with a GET query attached with a variable value, e.g. to pass along an error message
- url_for()
- http://flask.pocoo.org/docs/1.0/api/\#flask.url\_for
- from flask import url_for
- Application Factories
- http://flask.pocoo.org/docs/1.0/patterns/appfactories/
- Blueprints
- http://flask.pocoo.org/docs/1.0/blueprints/
- Flask-SQLAlchemy
- http://flask-sqlalchemy.pocoo.org/2.3/
- Usually done inside your virutal environment
- conda install -c conda-forge flask-sqlalchemy
- from flask_sqlalchemy import SQLAlchemy
- package name is flask-sqlalchemy but the module name is flask_sqlalchemy for some reason
- flask-sqlalchemy
- SQLAlchemy is a class that enables Python applications to "talk to" databases. It is able to work with several SQL-based database engines.
- The flask_sqlalchemy module is a "wrapper" of SQLAlchemy that makes it work more seemlessly with Flask applications.
- pymysql
- pymysql is the database driver
- conda install pymysql
- FlickList Example
- app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://flicklist:MyNewPass@localhost:8889/flicklist'
- app.config['SQLALCHEMY_ECHO'] = True
- db = SQLAlchemy(app)
- class Movie(db.Model): #options unique=True, id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(120)) watched = db.Column(db.Boolean) rating = db.Column(db.String(6)) def __init__(self, name): self.name = name self.watched = False def __repr__(self): return '' % self.name
- def get_current_watchlist(): return Movie.query.filter_by(watched=False).all()
- movie_id = request.form['movie_id'] rating = request.form['rating'] rated_movie = Movie.query.get(movie_id) rated_movie.rating = rating db.session.add(rated_movie) db.session.commit()
- Flask-SQLAlchemy Quickstart Example of One-to-many Relationship
- http://flask-sqlalchemy.pocoo.org/2.3/quickstart/
- from datetime import datetime class Post(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(80), nullable=False) body = db.Column(db.Text, nullable=False) pub_date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) category_id = db.Column(db.Integer, db.ForeignKey('category.id'), nullable=False) category = db.relationship('Category', backref=db.backref('posts', lazy=True)) def __repr__(self): return '' % self.title class Category(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(50), nullable=False) def __repr__(self): return '' % self.name
- >>> py = Category(name='Python') >>> Post(title='Hello Python!', body='Python is pretty cool', category=py) >>> p = Post(title='Snakes', body='Ssssssss') >>> py.posts.append(p) >>> db.session.add(py)
- As you can see, there is no need to add the Post objects to the session. Since the Category is part of the session all objects associated with it through relationships will be added too. It does not matter whether db.session.add() is called before or after creating these objects. The association can also be done on either side of the relationship - so a post can be created with a category or it can be added to the list of posts of the category.
- Let’s look at the posts. Accessing them will load them from the database since the relationship is lazy-loaded, but you will probably not notice the difference - loading a list is quite fast: >>> py.posts [, ] While lazy-loading a relationship is fast, it can easily become a major bottleneck when you end up triggering extra queries in a loop for more than a few objects. For this case, SQLAlchemy lets you override the loading strategy on the query level.
- Initial Configuration
- app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://flicklist:MyNewPass@localhost:8889/flicklist'
- Connection string used to connect to database
- type of database + driver + URI with username:password @ host:port (8889 is MySQL default from MAMP) then database name
- This line adds a new entry to the app.config dictionary, with key 'SQLALCHEMY_DATABASE_URI. The SQLAlchemy module will use this string to connect to our MySQL database.
- The components of this connection string are, from left to right:
- mysql - specifies that we'll be using a MySQL database
- pymysql - specifies that we'll be using the pymysqldatabase driver. A database driver is the mechanism by which an application can "talk to" a database.
- get-it-done:beproductive - specifies the database user and password to be used to connect. The user must already exist, and must have correct permissions on the given database.
- @localhost - specifies that we'll connect to a database on our local computer. If we were connecting to a remote database, we'd put the host name or IP address here.
- :8889 - specifies the port that the database is expected to be running on. Production MySQL databases typically run on port 3306, but our development database will run on a different port.
- Having the incorrect port is a common connection problem. You can check your port by looking at the Preferences or Settings pane of MAMP.
- /get-it-done - the name of the database that we'll use. This database must already exist (but can be empty) and the user we specified must have all (or most) permissions on the database.
- app.config['SQLALCHEMY_ECHO'] = True
- Good for debugging, generates the sql commands generated by sqlalchemy
- Enabling this setting will turn on query logging. In other words, when our app does anything that results in a database query being executed, the query that SQLAlchemy generates and executes will be logged to the terminal that our app is running within. This can be very useful in understanding how SQLAlchemy works, and what happens within an ORM library.
- db = SQLAlchemy(app)
- SQLAlchemy constructor with Flask app as argument, to bind them together in the 'db' instance/variable
- Create a database connection and interface for our app. We'll use the db object throughout our app, and it will allow us to interact with the database via our Flask/Python code.
- Model Class
- http://flask-sqlalchemy.pocoo.org/2.1/models/\#simple-example
- class Movie(db.Model):
- Create class that extends sqlalchemy's Model class and instantiated with db variable
- Creates a class that extends the db.Model class. By extending this class, we'll get a lot of functionality that will allow our class to be managed by SQLAlchemy, and thus stored in the database. We call a class like this a persistent class. When we run db.create_all(), a table named task will be created with columns corresponding to the properties configured on our class. We'll typically run db.create_all() in a Python shell, after running from main import db,Task.
- the table name is automatically set for you unless overridden. It’s derived from the class name converted to lowercase and with “CamelCase” converted to “camel_case”
- db.Column()
- id = db.Column(db.Integer, primary_key=True)
- Creates a new property of our class that will map to an integer column in the task table. The column name will be generated from the property name to be id as well. The column will be a primary key column on the table.
- db.ForeignKey()
- Column Data Types
- .Integer
- .String(120)
- .Boolean
- .DateTime
- as Python datetimeobject.
- Column Options
- primary_key=True
- default=False
- e.g.: default=datetime.utcnow
- unique=True
- nullable=False
- db.relationship()
- What does db.relationship() do? That function returns a new property that can do multiple things. In this case we told it to point to the Address class and load multiple of those. How does it know that this will return more than one address? Because SQLAlchemy guesses a useful default from your declaration. If you would want to have a one-to-one relationship you can pass uselist=False to relationship().
- So what do backref and lazy mean? backref is a simple way to also declare a new property on the Address class. You can then also use my_address.person to get to the person at that address. lazy defines when SQLAlchemy will load the data from the database: • 'select' (which is the default) means that SQLAlchemy will load the data as necessary in one go using a standard select statement. • 'joined' tells SQLAlchemy to load the relationship in the same query as the parent using a JOIN statement. • 'subquery' works like 'joined' but instead SQLAlchemy will use a subquery. • 'dynamic' is special and useful if you have many items. Instead of loading the items SQLAlchemy will return another query object which you can further refine before loading the items. This is usually what you want if you expect more than a handful of items for this relationship.
- Class.query.all()
- a list of Class objects/instances
- .query.filter_by(condition=value).all()
- returns iterable of objects?
- intermediate method - .filter_by() to only get objects with attribute property value pair
- Every class that extends db.Model will have a query property attached to it. This query object contains lots of useful methods for querying the database for data from the associate table(s). Here, Task.query.all() has the net effect of running SELECT * FROM task and then taking the results and turning them into a list of Task objects.
- .query.filter_by(email=email).first()
- returns first hit
- .query.get(object_id)
- gets an object with object_id
- will query for the specific object/row by it's primary key.
- .query.order_by("name desc")
- Setup Table Relations
- E.g. LaunchCode's Task List Example of one to many relationship:
- Refactor the Task class so that it has an owner_id property and takes an owner as an argument to its constructor:
- class Task(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(120)) completed = db.Column(db.Boolean) owner_id = db.Column(db.Integer, db.ForeignKey('user.id')) def __init__(self, name, owner): self.name = name self.completed = False self.owner = owner
- Specify that a user has many tasks and you want SQLAlchemy to look for matches between the two by adding this line in the User class:
- tasks = db.relationship('Task', backref='owner')
- In the POST handler of the index function, add code to get the owner of the task so that you can pass that into the Task constructor. Also, you'll want to modify the code so that it only grabs tasks from the database that belong to the current user:
- @app.route('/', methods=['POST', 'GET']) def index(): owner = User.query.filter_by(email=session['email']).first() if request.method == 'POST': task_name = request.form['task'] new_task = Task(task_name, owner) db.session.add(new_task) db.session.commit() tasks = Task.query.filter_by(completed=False,owner=owner).all() completed_tasks = Task.query.filter_by(completed=True,owner=owner).all() return render_template('todos.html', title="Get It Done!", tasks=tasks, completed_tasks=completed_tasks)
- Confused by the filter_by(owner=owner)
- Create Table in Database
- First, create user and database from your DBMS like phpMyAdmin
- Then define a class that will represent a table in your database like: class Movie(db.Model):
- From Python Interpreter: from python_program_name import SQL_object, Table_class db.create_all() db.session.commit() (is commit necessary for table creation?)
- If you need to delete existing tables: db.drop_all()
- If you modify the table, e.g. by adding a column, you need to drop the whole table and recreate it from scratch at the Python prompt. If you don't want to lose the existing table you can try Flask-migrate: https://flask-migrate.readthedocs.io/en/latest/
- Database Operations
- .session.add(object)
- Our ORM system, SQLAlchemy, does not know about our new object until we notify it that we want our object to be stored in the database. This is done by calling db.session.add(). A database session can be thought of as a collection of queries to be run all at once, when we ask the database to commit the session.
- .session.delete(object)
- .session.commit()
- commit to database everything that was added via .session.add
- Our changes and additions to the database aren't actually run against the database until we commit the session.
- Flash Messages
- The flashing system basically makes it possible to record a message at the end of a request and access it on the next (and only the next) request. This is usually combined with a layout template to expose the message.
- use flash messages to flexibly display messages to users based on their actions. Among other benefits, message flashing allows you to display messages to the user when you are redirecting them (i.e., not rendering a template).
- from flask import flash
- flash
- flash("message")
- adds a message to the queue/list
- get_flashed_messages()
- clears the messages
- using jinja2 syntax, including the newly-introduced "with" block:
- Message Categories
- Add categories to your flash function call so that you can add styling to your flash messages, e.g. flash("Logged In", "success"). You'll also need to modify your base template as follows:
- Then add whatever CSS style rules you wish for those categories
- CSS
- need to have a 'static' folder setup (for css/js files) unless you specifically override it during Flask initialization.
- Your directory structure for css should be like: /app - app_runner.py /services - app.py /templates - mainpage.html /static /styles - mainpage.css
- Then in base.html
- have to amend your allowed_routes list to include the folder name where your style file resides. You should have named that folder ‘static’. So just add ‘static’ to your allowed routes and it will work.
- Flask? Cookies
- request.cookies.get('visit-count', 0)
- returns a dictionary
- LaunchCode Example
- from flask, import Flask, request, make_response app = Flask(__name__) app.config['DEBUG'] = True @app.route('/') def index(): count = int(request.cookies.get('visit-count', 0)) count += 1 message = 'You have visited this page ' + str(count) + ' times' # make a response, set cookie, return resp = make_response(message) resp.set_cookie('visit-count', str(count)) return resp app.run()
- Pagination
- http://flask-sqlalchemy.pocoo.org/2.1/api/\#utilities
- https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-ix-pagination
- .paginate(page_number, number_of_posts, Error?).items
- the page number, starting from 1 the number of items per page an error flag. If True, when an out of range page is requested a 404 error will be automatically returned to the client. If False, an empty list will be returned for out of range pages.
- The return value from paginate is a Pagination object. The items attribute of this object contains the list of items in the requested page.
- this object has a few other attributes that are useful when building pagination links: has_next: True if there is at least one more page after the current one has_prev: True if there is at least one more page before the current one next_num: page number for the next page prev_num: page number for the previous page
- Example
- def get_posts(): return Blog.query.order_by("date desc").paginate(1, 2, False).items
- Email
- https://pythonhosted.org/Flask-Mail/
- Inbox - Flask
- https://stackoverflow.com/questions/13370317/sqlalchemy-default-datetime/13370382\#13370382
- lastChecked = db.Column(db.DateTime(), default=datetime.utcnow, onupdate=datetime.utcnow)
- http://flask.pocoo.org/docs/0.12/api/\#flask.make\_response
- http://flask.pocoo.org/docs/0.12/api/\#response-objects
- Flask-User package
- Flask marshmallow package
- Flask login package
- WTForms
- Sources
- http://flask.pocoo.org/
- http://flask.pocoo.org/docs/1.0/
- http://flask.pocoo.org/docs/1.0/quickstart/
- http://flask.pocoo.org/docs/1.0/tutorial/
- https://www.youtube.com/watch?v=MwZwr5Tvyxo&list=PL-osiE80TeTs4UjLw5MM6OjgkjFeUxCYH
- http://flask.pocoo.org/docs/1.0/tutorial/
- https://www.fullstackpython.com/flask.html
- http://exploreflask.com/en/latest/
- https://github.com/sloria/cookiecutter-flask
- https://realpython.com/flask-by-example-part-1-project-setup/
- https://testdriven.io/courses/microservices-with-docker-flask-and-react/