Building websites in Python with Flask

 

For some times now, I have been doing some projects in Python and some were web applications. Flask is a small framework to do exactly that and I have found it perfect for the job. It’s really easy to use, fast, has good documentation and a good community.

This is the first post in a series dedicated to building websites with Python and more notably Flask. In this post, I will talk about setting up Flask with a database, using configuration environments, managing assets and deploying the app to production.

First steps with Flask

I use pip to install Python modules and I would strongly recommend it (as well as using virtualenv).
Installing Flask is as easy as:

pip install Flask

Flask has an excellent quickstart tutorial so I will only do a quick overview of the basics.

As a framework, Flask is similar to Sinatra in Ruby or Slim in PHP. A main application object is instanciated and is used to map urls to functions.

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

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

In this example taken from Flask’s quickstart tutorial, app is our application object and is used to map the hello_world() function to the path /. This is done using the @app.route() decorator.

app.run() starts the built-in web server on port 5000. Thus, your first Flask web app is available at http://localhost:5000. Starts the webserver by calling the script:

python app.py

As stated earlier I would suggest you to read Flask’s quickstart tutorial. Let’s dive in another example:

from flask import Flask, render_template, request, redirect, url_for, abort, session

app = Flask(__name__)
app.config['SECRET_KEY'] = 'F34TF$($e34D';

@app.route('/')
def home():
    return render_template('index.html')

@app.route('/signup', methods=['POST'])
def signup():
    session['username'] = request.form['username']
    session['message'] = request.form['message']
    return redirect(url_for('message'))

@app.route('/message')
def message():
    if not 'username' in session:
        return abort(403)
    return render_template('message.html', username=session['username'], 
                                           message=session['message'])

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

In this example users will enter their name and what they want to say on the first page. The data will be stored in the session and will be displayed on the /message page.

A really important concept in Flask is the request context. Flask uses thread-local objects, like request, session and others to represent elements of the current request. These objects are only available when a request context has been initialized, which is done by Flask when it receives an HTTP request.

Some observations:

  • app.config is a dict containing configuration parameters
  • @app.route() is by default limited to GET requests. Allowed HTTP methods of an action can be specified using the methods keyword arg.
  • url_for(route_name, **kwargs) should be used to generate urls for your handlers. It takes as first parameter the function name and as keyword args any needed parameters to generate the url.
  • redirect(url) creates an HTTP response with a redirect code and location
  • abort(http_code) is used to create error responses and stop the executing function.

In the signup() function, the request’s data is accessed through the request object. request.form is a MultiDict (which acts like a normal dict for the most part) with all POST data, request.args is a MultiDict with GET params and request.values is a combination of both.

Flask is natively integrated with jinja2, a very good templating engine. Templates should be saved as .html files under the templates/ folder. The render_template(filename, **kwargs) function is a pretty straightforward method to render them.

index.html:

{% extends "layout.html" %}
{% block content %}
    <h1>Say something</h1>
    <form method="post" action="{{ url_for('signup') }}">
        <p><label>Username:</label> <input type="text" name="username" required></p>
        <p><label>Message:</label> <textarea name="message"></textarea></p>
        <p><button type="submit">Send</button></p>
    </form>
{% endblock %}

message.html:

{% extends "layout.html" %}
{% block content %}
    <h1>{{ username }} said:</h1>
    <p>
        {{ message }}
    </p>
    <a href="{{ url_for('home') }}">Say something else</a>
{% endblock %}

layout.html:

<!doctype html>
<html lang="en">
    <head>
        <title>Say somthing</title>
        <meta http-equiv="content-type" content="text/html; charset=utf-8">
        <link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
    </head>
    <body>
        {% block content %}{% endblock %}
    </body>
</html>

As you can see, I have used jinja’s template inheritance to add a common layout to all of my templates.

I have also used the url_for() function to get the urls of files located in the static/ folder using the static route.

File organization and management script

In our example, our application is a single file. As you can guess, this will not suffice as it becomes larger.

I find a good approach is to treat the app as a python package. The package name will be the app name and the initialization of the Flask object is done in the __init__.py file.

The templates/ folder will be located inside the package’s directory as well as any other files related to our application (eg. settings.py, models.py…).

example/
  __init__.py
  static/
    favicon.ico
  templates/
    index.html
    hello.html

Note: as the application grows larger, you should use Flask’s Blueprints to organize your code as modules. I will cover this in another tutorial.

I also like to use the Flask-Script extension to manage my application from the command line. This extension provides built-in commands as well as a mechanism to define your own ones.

$ pip install Flask-Assets

I configure and run the extension from a manage.py file located outside the module’s directory:

#!/usr/bin/env python
from flaskext.script import Manager, Shell, Server
from example import app

manager = Manager(app)
manager.add_command("runserver", Server())
manager.add_command("shell", Shell())
manager.run()

To start the server from the command line:

$ ./manage.py runserver

Using a database

There is no out of the box support for any database with Flask. Which is not a problem as there are many database libraries in Python. The most known and the one I prefer is SqlAlchemy. In addition to its excellent database toolkit, it features an awesome ORM.

Integrating SqlAlchemy with Flask cannot be simpler, thanks to the Flask-SqlAlchemy extension.

$ pip install Flask-SqlAlchemy

If it’s your first time with SqlAlchemy, I would advice reading the ORM tutorial which is a very good start.

I like to initialize the extension and configure my models in a models.py file.

from flask_sqlalchemy import SQLAlchemy
from example import app

db = SQLAlchemy(app)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String)
    message = db.Column(db.String)

    def __init__(self, username, message):
        self.username = username
        self.message = message

We’ll then need to add the database connection parameters to the configuration. For this example, we’ll use sqlite. In __init__.py add this line after the previous app.config one:

app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://example.db'

To initialize the database, I add a createdb command to the manage.py file. Add the following lines before manager.run():

@manager.command
def createdb():
    from example.models import db
    db.create_all()

Then run:

$ ./manage.py createdb

We can now modify our example to use the model, in __init__.py add the following lines after the creation of the app object:

경축! 아무것도 안하여 에스천사게임즈가 새로운 모습으로 재오픈 하였습니다.
어린이용이며, 설치가 필요없는 브라우저 게임입니다.
https://s1004games.com

# ...

from models import *

# ...

@app.route('/signup', methods=['POST'])
def signup():
    user = User(request.form['username'], request.form['message'])
    db.session.add(user)
    db.session.commit()
    return redirect(url_for('message', username=user.username))

@app.route('/message/<username>')
def message(username):
    user = User.query.filter_by(username=username).first_or_404()
    return render_template('message.html', username=user.username,
                                           message=user.message)

Instead of using the session we are now creating a User object, storing it into the database using db.session.add() and db.session.commit() (which is the standard SqlAlchemy way of doing it).

In the message() function I have added a username parameter which must be given through the url’s path. We next perform a database query using User.query. Note the first_or_404() function which is provided by the flask extension and is a nice addition.

 Configuration

As we’ve seen in the previous example, the configuration can be defined using the app.config dict. While this is the easier method, it is not the most practical one once you factor in configuration environments. Indeed, the configuration of your app will most of the time be different between your development environment and your production one.

The Flask documentation suggests a nice way of handling these configuration environments.

We’ll store our configuration as attributes of plain old python objects in a file called settings.py. After reading the current environment form a system environment variable (EXAMPLE_ENV in our case) we’ll load the config from the correct config object.

In the settings.py file as follow:

class Config(object):
    SECRET_KEY = 'secret key'

class ProdConfig(Config):
    SQLALCHEMY_DATABASE_URI = 'postgresql://localhost/example'

class DevConfig(Config):
    DEBUG = True
    SQLALCHEMY_DATABASE_URI = 'sqlite://example.db'
    SQLALCHEMY_ECHO = True

In our package’s __init__.py:

import os

# ...

env = os.environ.get('EXAMPLE_ENV', 'prod')
app.config.from_object('example.settings.%sConfig' % env.capitalize())
app.config['ENV'] = env

Assets

With the rise of rich frontends and of alternative syntaxes to css and javascript, assets manaegement has become an important aspect of web apps.

We’ll once again use a great extension, Flask-Assets, which is a wrapper for the webassets python library.

$ pip install Flask-Assets

I store my assets file in static/, organizing them inside a css/, js/ and vendor/ folders. Below you can see that I’ve added jquery and bootstrap in the vendor dir.

example/
  static/
    css/
      layout.less
    js/
      main.js
    vendor/
      bootstrap/
        css/
          bootstrap.css
        js/
          bootstrap.min.js
      jquery/
        jquery-1.7.2.min.js

With webassets, files are grouped as bundles. Each bundle can have custom filters (eg: transform less files to css). I define my bundles inside an assets.py file:

from flask_assets import Bundle

common_css = Bundle(
    'vendor/bootstrap/css/bootstrap.css',
    Bundle(
        'css/layout.less',
        filters='less'
    ),
    filters='cssmin', output='public/css/common.css')

common_js = Bundle(
    'vendor/jquery/jquery-1.7.2.min.js',
    'vendor/bootstrap/js/bootstrap.min.js',
    Bundle(
        'js/main.js',
        filters='closure_js'
    ),
    output='public/js/common.js')

Here I have defined 2 bundles, one for css files and one for js files. I’ve also use nested bundles to apply specific filters to some files.

To include the bundles in our pages, webassets provides some jinja2 helpers (which we should add to layout.html):

{% assets "common_css" %}
    <link rel="stylesheet" type="text/css" href="{{ ASSET_URL }}" />
{% endassets %}
{% assets "common_js" %}
    <script type="text/javascript" src="{{ ASSET_URL }}"></script>
{% endassets %}

Now we’ll need to configure the webassets environment in __init__.py:

from flask_assets import Environment
from webassets.loaders import PythonLoader as PythonAssetsLoader
import assets

# ...

assets_env = Environment(app)
assets_loader = PythonAssetsLoader(assets)
for name, bundle in assets_loader.load_bundles().iteritems():
    assets_env.register(name, bundle)

As you can see, I use webassets’s PythonLoader to load bundles from the assets module and register each bundle into the environment.

You can add ASSETS_DEBUG=True in DevConfig to get debugging info. There are many other configuration parameters listed here. Param names should be prefixed with ASSETS_ and uppercased (eg. Environment.versions becomes ASSETS_VERSIONS).

Finally, the Flask-Assets extension provides some command line utilities that we’ll need to register in the manage.py file:

from flask_assets import ManageAssets
from example import assets_env

# ...

manager.add_command("assets", ManageAssets(assets_env))

Available commands are listed in webassets documentation but the most important one is rebuild which regenerates all your bundle files:

$ ./manage.py assets rebuild

 Deploying to production

Now that we have a fully working Flask application, we’ll need to deploy it on a production machine. I like to use uWSGI + Nginx + Supervisor for my setup.

Note: this part assumes Ubuntu as your Linux distribution

Nginx acts as the frontend web server and will serve static files. uWSGI acts as the WSGI server which runs our flask app. Finally, I use supervisor to manage processes. I like to use Supervisor instead of init.d scripts as I often have other processes to manage.

$ sudo apt-get install nginx supervisor
$ pip install uwsgi

Configure an uWSGI app in /etc/uwsgi.ini:

[uwsgi]
socket = 127.0.0.1:3031
chdir = /path/to/my/app
module = example:app
env = EXAMPLE_ENV=prod

Add a server entry in Nginx in /etc/nginx/sites-enabled/example.conf:

server {
    listen 80;
    server_name example.com;
    root /path/to/my/app/example/static;

    location / {
        try_files $uri @uwsgi;
    }

    location @uwsgi {
            include uwsgi_params;
            uwsgi_pass 127.0.0.1:3031;
    }
}

Finally, configure Supervisor to manage the uWSGI process in /etc/supervisor/conf.d/example.conf:

[program:example]
command=/usr/local/bin/uwsgi --ini /etc/uwsgi.ini
autostart=true
autorestart=true
stopsignal=INT

And restart everything:

$ sudo /etc/init.d/nginx restart
$ sudo /etc/init.d/supervisor reload

Update: the next part in the series has been published: Getting bigger with Flask

Update 2: fixed the bugs and added database initialization

 

 

[출처] http://maximebf.com/blog/2012/10/building-websites-in-python-with-flask/#.V1a1NO-0KUk

 

 

 

본 웹사이트는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
번호 제목 글쓴이 날짜 조회 수
57 SQLAlchemy 시작하기 – Part 2 졸리운_곰 2016.06.11 539
56 SQLAlchemy 시작하기 – Part 1 졸리운_곰 2016.06.11 401
55 TensorFlow의 기본 사용법 졸리운_곰 2016.06.11 725
54 텐서플로우 시작하기 file 졸리운_곰 2016.06.11 774
53 Introduction to Python for Econometrics_Statistics and Data Analysis.pdf file 졸리운_곰 2016.06.07 2529
52 Numerical.Methods.in.Engineering.with.Python.2nd.Edition.Jaan.Kiusalaas.2010.pdf file 졸리운_곰 2016.06.07 2499
51 NumMethodPython.pdf file 졸리운_곰 2016.06.07 2419
50 Python-for-Computational-Science-and-Engineering.pdf file 졸리운_곰 2016.06.07 2316
49 PythonWeb AppApp DevelopmentHTMLMySQLCSSJavaScript file 졸리운_곰 2016.06.07 808
» Building websites in Python with Flask 졸리운_곰 2016.06.07 445
47 A minimal Flask-RESTful API looks like this 졸리운_곰 2016.06.07 520
46 Azure에서 Flask를 사용하여 웹앱 만들기 졸리운_곰 2016.06.07 702
45 Designing a RESTful API with Python and Flask 졸리운_곰 2016.06.07 485
44 Anonymous Web Scraping using Python and Tor file 졸리운_곰 2016.05.04 1284
43 Using TOR with Python file 졸리운_곰 2016.05.04 1911
42 Crawling anonymously with Tor in Python 졸리운_곰 2016.05.04 542
41 scrapy : 크롤링 해보기 file 졸리운_곰 2016.04.21 719
40 Python으로 xml 데이터를 mysql 데이터베이스 화 file 졸리운_곰 2016.03.13 780
39 Python으로 공공데이터 오픈 API 활용하기 졸리운_곰 2016.01.14 1225
38 Parse JSON using Python and store in MySQL file 졸리운_곰 2016.01.09 456
대표 김성준 주소 : 경기 용인 분당수지 U타워 등록번호 : 142-07-27414
통신판매업 신고 : 제2012-용인수지-0185호 출판업 신고 : 수지구청 제 123호 개인정보보호최고책임자 : 김성준 sjkim70@stechstar.com
대표전화 : 010-4589-2193 [fax] 02-6280-1294 COPYRIGHT(C) stechstar.com ALL RIGHTS RESERVED