web development with flask - files.meetup.comfiles.meetup.com/1623444/757 python user group__11 aug...

56
WEB DEVELOPMENT WITH FLASK (HOPEFULLY IN 20 SOME ODD MINUTES) by John Boisselle

Upload: others

Post on 09-Jul-2020

39 views

Category:

Documents


1 download

TRANSCRIPT

WEB DEVELOPMENTWITH FLASK

(HOPEFULLY IN 20 SOME ODD MINUTES)by John Boisselle

ABOUT ME... $ whoami

nobody

srsly?!! I'm a freelance developer who likes to keep on the dl

I've worked on some interesting things

But this talk isn't about me...

It's about you + flask

SO WHAT IS FLASK?From flask's : website

Flask is a microframework for Pythonbased on Werkzeug, Jinja 2 and good

intentions.

$ pip install flask

WHAT DOES IT LOOK LIKE? from flask import Flaskapp = Flask(__name__)

@app.route("/")def hello(): return "Hello World!"

if __name__ == '__main__': app.run()

$ python hello.py

WELL, THAT LOOKS EASY!It really is!!!

The best thing about flask is YOU get to pick and choosewhat's BEST for the problem being solved.

The downside is you can also shoot yourself in the foot if youdon't think about security! Never trust your users!

BEFORE WE (REALLY) BEGIN...This is not so much of an introduction but more about the

lay of the land, alternatives, and some best practices.

There is no replacement for the fine documentation thatflask nor the packages mentioned in this presentation

provide.

PROJECT LAYOUTSDepending on your project, you could have just a simple

single script acting as a API service, a couple of filesseparating concerns, or a massive site with multiple

applications within.

Flask is flexible no matter what your project needs.

A SIMPLE SETUP/ project_root|­­+ templates| |­ layout.html| |­ index.html| |­ navigation.html (this would be included in many pages)||­­+ static (this would be akin to the public directory)| |­ favicon.ico| |­ something.js| |­ styles.css||­­ main.py

SIMPLISTIC SEPARATION OFCONCERNS

/ project_root|­­+ templates| |­ layout.html| |­ index.html| |­ navigation.html|­­+ static| |­ favicon.ico| |­ something.js| |­ styles.css|­­ app.py (application setup w/ configuring of extensions)|­­ forms.py (collection of classes for building forms)|­­ models.py (application data models)|­­ views.py (all view functions/class based views)|­­ main.py (main script importing the app and views)

SIMILAR TO DJANGO/ project_root|­­+ templates (site­wide templates)|­­+ static (site­wide static files)|­­+ blog (flask calls these blueprints)| |­­+ templates (template files specific to the blueprint)| |­­+ static (static files specific to the blog blueprint)| |­­ models.py| |­­ forms.py| |­­ commands.py| |­­ views.py|­­ app.py (factory function for creating the app)|­­ manage.py (cli/tasks performed on the application itself)|­­ settings.py (application configuration)

Django 'apps' => Flask 'blueprints'

CONFIGURATIONAt the heart of every project is your configuration settings.

Flask can be configured in so many ways (by python objects,external files, etc). Generally, extension setup would be in

here as well.

SINGLE FILE SETUPfrom flask import Flask...SQLALCHEMY_DATABASE_URI = "mysql://appuser:[email protected]/db"SECRET_KEY = "SuperSecretKeyShouldNOTBeHERE­­EVER"...app = Flask(__name__)app.config.from_object(__name__) # uses constants in file

When people first start off, they would end up doing this.Please don't do this.

EXTERNAL CONFIG FILEimport os...class Config(object): # py2 friendly ;P SECRET_KEY = os.environ.get('APP_ENV', 'SuperSecretKeyDefault') TESTING = False ...class DevelopmentConfig(Config): ... # key overrides specific to environment

class ProductionConfig(Config): ... # key overrides specific to environment

config = 'dev': DevelopmentConfig, 'prod': ProductionConfig, 'default'

This is an improvement over embedding your config keys inyour main app file for someone to see the gory details.

USE DOTENV$ pip install python­dotenv

Helpful for working with people on a project who don'tknow how to setup environment variablesComes with a CLI for editing keys but very easy to do byhandConfiguration file can be checked into version control but.env does notConfig file (settings.py -- by convention) changes slightly

DOTENV EXAMPLE.env (in same dir as settings.py)

DEV_SECRET_KEY="somerandomgibberish or os.urandom output ;)"BCRYPT_LEVEL=5000 # just kidding ­­ more like 13ish ­­ RTFM...

import osfrom os.path import dirname, joinfrom dotenv import load_dotenv

dotenv_path = join(dirname(__file__), '.env')load_dotenv(dotenv_path)

class Config(object): BCRYPT_LEVEL = os.environ.get('BCRYPT_LEVEL') # provide no default! ...

class DevelopemntConfig(Config): SECRET_KEY = os.environ.get('DEV_SECRET_KEY') # namespaced secret key

JINJA2 FOR TEMPLATES(MAIN HIGHLIGHTS)

Comes out of the boxSupports template inheritance, includes, and blocksEscapes output to prevent XSS by defaultHas a lot of filters, support for macros and isn't just forhtml

WHAT DOES IT LOOK LIKE% extends "layout.html" %% block body % <ul> % for user in users % <li><a href=" user.url "> user.username </a></li> % endfor % </ul>% endblock %

from flask import Flask, render_templateapp = Flask(__name__)

@app.route("/")def index(): ... return render_template("templates/example.html", user=user)

if __name__ == '__main__': app.run()

ALTERNATIVE TEMPLATINGMako Templates (which could also use Plim)PyJade (personal favorite)

supports django and mako templateseventual project name change due to 'jade' TM/SMissue

FORMSA majority of the time a form is used for interacting with a

website and its pretty easy to do with a jinja macro but is notideal

JINJA MACRO EXAMPLE RENDERINGA FORM INPUT

% macro input(name, value='', type='text', size=20) ­% <input type=" type " name=" name " value=" value|e " size% ­endmacro %

Jinja is used for the presentation layer of your applicationand does not deal with data coming in so I would not

recommend using this method.

NEVER TRUST DATA COMING IN OR OUT

WTFORMS / FLASK-WTFfrom flask_wtf import Formfrom wtforms.fields import StringFieldfrom wtforms.validators import DataRequired

class UserForm(Form): name = StringField(u"Name", validators=[DataRequired()])

<form method="post"> form.csrf_token form.name.label form.name(size=20) <input type="submit" value="Submit"></form>

Flask-WTF integrates WTForms with CSRF protection,Recaptcha support, uploads, html5 widgets, and

internationalization.

HONORABLE MENTIONFormEncode -- another python package for html forms

DATA STORAGEFlask is flexible in that you aren't tied to an RDBMS and can

choose which data store best fits your needs (althoughPostgres can solve a majority of them).

Python has many hooks into whatever data store you'reusing or haven't even considered.

ORMS + ODMSORMs (Object Relation Mapper) and ODMs (Object

Document Mapper) are pythonic objects mapping to theschema/structure of your data store. ORMs are for relational

databases and ODMs are for NoSQL databases.

BUT I DON'T NEED NO ORM/ODMSure you could write raw sql or use pymongo and no one

would stop you but you would be missing out on someimportant things...

business logic/validation pertaining to a model/unit (non-anemic models)testability

The options listed offer you a way to drop down to raw sql ifyou need to solve that problem.

SQLALCHEMY (ORM)from sqlalchemy import *from sqlalchemy.ext.declarative import declarative_basefrom sqlalchemy.orm import relation, sessionmaker

Base = declarative_base()

class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True) first_name = Column(String(20), nullable=False) last_name = Column(String(100), nullable=False) ...

SQLALCHEMY PROS + CONTRASPros Contras

Well Documented Documentation can be hard togrep

Very Mature & battletested

Pretty big code base if you areworking on an embeded device ormobile application

Supports all majordatabase vendors &custom types

Migrations viaAlembic

Alembic

FLASK-SQLALCHEMYThis package handles the setup, (sqlalchemy) sessions, and

teardown within the flask context$ pip install Flask­SQLAlchemy

Migrations are supported via Flask-Migrate$ pip install Flask­Migrate

PEEWEE (ORM)from peewee import *

db = SqliteDatabase('products.db')

class Product(Model): name = CharField(max_length=40, unique=True) price = DecimalField(constraints=[Check("price < 10000")]) created = DateTimeField()

class Meta: database = db

$ pip install peewee

PEEWEE PROS + CONTRASPros Contras

Very small and expressive(1 module)

Not as popular asSQLAlchemy

Excellent choice forrestricted resourceenvironments (IoT /Mobile apps)

Only supports MySQL, Sqlite,Postgres. SQLCipher andBerkleyDB via extensions

Similar to Django ORM

FLASK-PEEWEE?There used to be an extension which provided an api and

admin interface but is now used via peewee's playhouse (setof extensions that come with the package)

from playhouse.flask_utils import FlaskDB...database = FlaskDB(app)

class User(database.Model): username = CharField(max_length=40, unique=True)

Migrations are supported but not as nice as alembic. Usearnold for migration generation. Migrations always have to

be configured.$ pip install arnold

FLASK-MONGOALCHEMY (ODM)...from flask.ext.mongoalchemy import MongoAlchemy...db = MongoAlchemy(app)

class Author(db.Document): name = db.StringField()

class Book(db.Document): title = db.StringField() author = db.DocumentField(Author) year = db.IntField()

$ pip install Flask­MongoAlchemy

NoSQL databases don't need/use migrations since theschema/document is dynamic in nature

HONORABLE MENTIONALCHY (ORM)

Use Alchy if you are trying to use your models outside of aflask app context as this provides some extra enhancements

to the model classes, sessions and queries.

A drop in replacement for Flask-SQLAlchemy.$ pip install alchy

HONORABLE MENTIONMONGOENGINE (ODM)

Flask-MongoEngine is the extension for integratingMongoEngine into your flask project. MongoEngine also

works with Django.$ pip install flask­mongoengine

USER MANAGEMENTWeb applications have users and you have to be careful with

authentication. Since flask can use various packagesinterchagably you have to know what you are trying to

achieve.

FLASK-LOGINThe de facto flask extension in user session management.

store active user ID in session with log in and outrestrict views whether the user is logged in or nothandles 'remember me' functionalityhelps protect user sessions by 'cookie thieves'possible integration with Flask-Principal or otherauthorization packages$ pip install Flask­Login

ROLL YOUR OWN USER CLASSfrom flask_login import UserMixinfrom ..extensions import db, flask_bcrypt

class User(UserMixin, db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), unique=True, nullable=False) ... # These methods are overridden by your custom User class def is_authenticated(self): ... def is_active(self): ... def is_anonymous(self): ... def get_id(self): ...

A WARNING TO THOSE WHO AREN'TAWARE OF THE DARK ARTS

I would not recommend storing user information that issensitive whatsoever! Store only what is pertinent to your

application and use an external service for handlingauthentication like OpenID, LDAP, etc.

If you really have to store passwords you can use passlib orbcrypt, create the hash, store it and check the hashes when

authenticating.$ pip install passlib

$ pip install flask­bcrypt # or pip install flask­scrypt

OR USE SOMEONE ELSE'S$ pip install flask­user

This package offers a lot of features that would be good forsomeone starting out and uses a lot of packages that I

have/will talk about.

Sometimes an app doesn't require all of these bells andwhistles and you end up being dependent upon an

implementation.

ROLE MANAGEMENTUsers can have many types of roles within an application.

You can have various roles associated with a user.

There are a couple of packages that can be tied into yourUser class implementation along with Flask-Login to fine

tune resource management.

FLASK-PRINCIPALThis package provides very fine tuned role management and

splits things up into Permissions and needs for theRole/User.

Read the documentation thoroughly when setting this up.

You will also have to create a relation/join table betweenvarious Role classes to the User class.

EXAMPLE MODELS.PY...roles_users = db.Table( 'roles_users', db.Column('id', db.Integer(), primary_key=True), db.Column('user_id', db.Integer(), db.ForeignKey('users.id')), db.Column('role_id', db.Integer(), db.ForeignKey('roles.id')))

class Role(db.Model): __tablename__ = 'roles' id = db.Column(db.Integer, primary_key=True) ...

class User(db.Model): __tablename__ = 'users' ...

EXAMPLE FLASK-PRINCIPAL SETUP...from flask_login import LoginManager, current_userfrom flask_principal import ( Principal, Permission, RoleNeed, UserNeed, identity_loaded)from .models import User...# flask­login initialization should happen before flask­principal# as an instance of LoginManager is used for managing user sessions...principals = Principal()...admin_permission = Permission(RoleNeed('admin'))

@identity_loaded.connectdef on_identity_loaded(sender, identity): identity.user = current_user

HONORABLE MENTIONFLASK-AUTH

from flaskext.auth.permissions import Permission, Role

user_create = Permission('user', 'create')user_view = Permission('user', 'view')

roles = 'admin': Role('admin', [user_create, user_view]), 'userview': Role('userview', [user_view]),

def load_role(role_name): return roles.get(role_name)

auth.load_role = load_role

$ pip install flask­auth

HONORABLE MENTIONFLASK-BOUNCER (CANCAN INSPIRED)

from flask.ext.bouncer import requires, ensure, Bouncerapp = Flask()bouncer = Bouncer(app)

@bouncer.authorization_methoddef define_authorization(user, they): if user.is_admin: they.can(MANAGE, ALL) else: they.can(READ, ('Article', 'BlogPost')) they.can(EDIT, 'Article', lambda a: a.author_id == user.id)

@app.route("/articles")@requires(READ, Article)def articles_index(): return "A bunch of articles"

$ pip install flask­bouncer

ADMIN INTERFACEWith flask you can definitely roll your own admin section forall of your CRUD operations, but why do that when someone

else has done the heavy liing?

FLASK-ADMINThis is a popular admin interface and has excellent support

for SQLAlchemy, MongoEngine, Peewee and PyMongo.

Easily customizable and has a lot of features like CSRFprotection, localization, can manage files and folders and

can add model backends if its not supported.$ pip install Flask­Admin

HONORABLE MENTIONFLASK-SUPERADMIN

Has a very nice interface and supports only MongoEngine,Django and SQLAlchemy models. Also has an optional File

admin. This project was originally forked from Flask-Admin.$ pip install Flask­SuperAdmin

TASK MANAGEMENTTasks are for things that need to happen with your flask appbut outside of the request-response life cycle. Simple tasks

could be setting up your database, seeding it or evenrunning your tests. There are 2 options for running CLI

scripts with your flask application right now.

FLASK-SCRIPT (0.10 UP TO 1.0)This is the current option that some of the pre-existing flask-

extensions use as an interface with the CLI and is trivial touse. It takes an app instance and you can even have a shell

for inspecting your flask app.

FLASK-SCRIPT EXAMPLEmanage.py -- by convention

...from flask_script import Managerfrom flask_script.commands import Shellfrom flask_migrate import MigrateCommandfrom sample_app.app import create_appfrom sample_app.extensions import db

app = create_app(os.environ.get('SAMPLE_APP_ENV', 'default'))

manager = Manager(app)manager.add_command('db', MigrateCommand)

@manager.commanddef test(): "Run unittests" import unittest

CLICK (0.11 ONWARDS)Flask 0.11 comes with a dependency of the Click package. It

has a similar style of using decorators declaring clicommands which is different if you are used to using the

standard argparse.

Flask now provides a 'flask' command to run your scripts.

Want to run a shell with your flask app? Run `flask shell` atthe command line

CLICK EXAMPLE# win32/64 users should use 'set' instead of exportexport FLASK_APP=/path/to/main/script.py

import clickfrom flask import Flask

app = Flask(__name__)

@app.cli.commanddef hello(): """Saying hello""" click.echo("Saying hello...")

$ flask helloSaying hello...

This is as simple as it gets but should be discussed aer

ODDS + ENDSThere is just way too much to cover and I have only covered

the basics without even going into anything. Flask haspackages for almost everything from webassets,

websockets, A/B testing and even celery ;P

My goal was to give you a roadmap to help you choosewhich packages could benefit you in the future.

THANK YOU