Comparing Flask, Django, and Tornado
Django is a full-stack framework packed with built-in components including ORM, Forms, ModelForms, caching, sessions, middleware, signals, and more.
Flask is lightweight and minimal, offering core functionality without bundled components but with extensive third-party extension support. Its routing uses decorators but ultimately relies on add_url_rule internally.
Tornado is an asynchronous non-blocking framework built on IOLoop using coroutines for async operations.
The Foundation: WSGI Examples
Werkzeug Implementation
from werkzeug.wrappers import Request, Response
@Request.application
def handle_request(request):
return Response('Hello World!')
if __name__ == '__main__':
from werkzeug.serving import run_simple
run_simple('localhost', 4000, handle_request)
Using wsgiref
from wsgiref.simple_server import make_server
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return [b'<h1>Hello, web!</h1>']
if __name__ == '__main__':
server = make_server('', 8000, application)
server.serve_forever()
Raw Socket Implementation
import socket
def handle_connection(client):
data = client.recv(1024)
client.send("HTTP/1.1 200 OK\r\n\r\n")
client.send("Hello, World")
def main():
server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_sock.bind(('localhost', 8000))
server_sock.listen(5)
while True:
conn, addr = server_sock.accept()
handle_connection(conn)
conn.close()
if __name__ == '__main__':
main()
All three approaches demonstrate that web frameworks fundamentally operate over sockets.
Installation
pip3 install flask
Basic Flask Application
from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
return 'Hello World!'
if __name__ == '__main__':
app.run()
Building a Login System
Main application file:
from flask import Flask, render_template, request, redirect, session, url_for
app = Flask(__name__)
app.debug = True
app.secret_key = 'your_secret_key_here'
USERS = {
1: {'name': 'Alice', 'age': 25, 'gender': 'Female', 'text': 'Sample bio text here...'},
2: {'name': 'Bob', 'age': 30, 'gender': 'Male', 'text': 'Another sample bio...'},
3: {'name': 'Charlie', 'age': 22, 'gender': 'Male', 'text': 'More sample content...'},
}
def require_login(func):
def wrapper(*args, **kwargs):
if 'username' not in session:
return redirect(url_for('login_view'))
return func(*args, **kwargs)
return wrapper
@app.route('/user/<int:user_id>', methods=['GET'], endpoint='user_detail')
@require_login
def user_detail(user_id):
user = USERS.get(user_id)
return render_template('detail.html', user=user)
@app.route('/dashboard', methods=['GET'], endpoint='dashboard')
@require_login
def dashboard():
username = session.get('username')
return render_template('dashboard.html', users=USERS)
@app.route('/login', methods=['GET', 'POST'], endpoint='login_view')
def login_view():
if request.method == 'GET':
return render_template('login.html')
else:
username = request.form.get('username')
password = request.form.get('password')
if username == 'admin' and password == 'secret':
session['username'] = username
return redirect(url_for('dashboard'))
return render_template('login.html', error='Invalid credentials')
if __name__ == '__main__':
app.run()
login.html:
<html>
<head><meta charset="UTF-8"><title>Login</title></head>
<body>
<h1>Sign In</h1>
<form method="post">
<input type="text" name="username" placeholder="Username">
<input type="password" name="password" placeholder="Password">
<input type="submit" value="Login">
</form>
{{ error if error else '' }}
</body>
</html>
dashboard.html:
<html>
<head><meta charset="UTF-8"><title>Dashboard</title></head>
<body>
<h1>User Directory</h1>
| {{ id }} | {{ data.name }} | {{ data\['name'\] }} | {{ data.get('name') }} | [View Profile](</user/{{ id }}>) |
|---|---|---|---|---|
</body>
</html>
detail.html:
<html>
<head><meta charset="UTF-8"><title>User Profile</title></head>
<body>
<h1>{{ user.name }}'s Profile</h1>
<p>{{ user.text }}</p>
</body>
</html>
Test URLs:
- Login:
http://127.0.0.1:5000/login - Dashboard:
http://127.0.0.1:5000/dashboard - Detail page:
http://127.0.0.1:5000/user/1
Configuration Management
Flask configuration is stored in a dictionary-like config.Config object.
Default settings include:
DEBUG: Enable debug modeTESTING: Enable testing modeSECRET_KEY: Required for session managementPERMANENT_SESSION_LIFETIME: Session duration (default: 31 days)SESSION_COOKIE_HTTPONLY: Cookie security flagMAX_CONTENT_LENGTH: Maximum request size
Configuration Methods
Method 1 - Direct assignment:
app.config['DEBUG'] = True
app.config.update(DEBUG=True, SECRET_KEY='abc')
Method 2 - Load from Python file:
app.config.from_pyfile('settings.py')
Method 3 - Environment variable:
app.config.from_envvar('FLASK_CONFIG')
Method 4 - JSON file:
app.config.from_json('config.json')
Method 5 - Dictionary:
app.config.from_mapping({'DEBUG': True, 'SECRET_KEY': 'key'})
Method 6 - Class:
class BaseConfig:
DEBUG = False
SECRET_KEY = 'base_key'
class DevConfig(BaseConfig):
DEBUG = True
class ProdConfig(BaseConfig):
DATABASE_URI = 'mysql://user@localhost/db'
app.config.from_object(DevConfig)
config.py:
class Config:
DEBUG = False
TESTING = False
SECRET_KEY = 'production_secret'
DATABASE_URI = 'sqlite://:memory:'
class ProductionConfig(Config):
DATABASE_URI = 'mysql://user@db/prod'
class DevelopmentConfig(Config):
DEBUG = True
app.py:
from flask import Flask
from config import DevelopmentConfig
app = Flask(__name__)
app.config.from_object(DevelopmentConfig)
@app.route('/')
def index():
return 'Hello World!'
if __name__ == '__main__':
app.run()
Route Mechanics
The @app.route decorator is syntactic sugar for add_url_rule:
# This decorator:
@app.route('/', methods=['GET', 'POST'], endpoint='home')
def home():
return 'Home'
# Equates to:
def home():
return 'Home'
app.add_url_rule('/', 'home', home, methods=['GET', 'POST'])
Alternative approach:
from flask import Flask
app = Flask(__name__)
def login():
return 'Login Page'
app.add_url_rule('/login', 'login_page', login, methods=['GET', 'POST'])
if __name__ == '__main__':
app.run()
Class-Based Views
Class views offer inheritance and better organization compared to function-based views.
from flask import Flask, views
app = Flask(__name__)
app.debug = True
app.secret_key = 'secret'
def authentication(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
class APIView(views.MethodView):
methods = ['GET'] # Restrict to GET requests
decorators = [authentication] # Apply decorator
def get(self):
return 'API GET Response'
def post(self):
return 'API POST Response'
app.add_url_rule('/api', view_func=APIView.as_view(name='api'))
if __name__ == '__main__':
app.run()
URL Rule Parameters
Redirect Example
from flask import Flask
app = Flask(__name__)
@app.route('/old-page/', endpoint='old', redirect_to='/new-page/')
def old_page():
return 'Old Page (should redirect)'
@app.route('/new-page/', endpoint='new', defaults={'page_id': 1}, strict_slashes=True)
def new_page(page_id):
return f'New Page (ID: {page_id})'
if __name__ == '__main__':
app.run()
Host File Configuration
For local testing, modify your hosts file:
- Windows:
C:\Windows\System32\drivers\etc\hosts - Linux/Mac:
/etc/hosts
Add entries like:
127.0.0.1:5000 www.example.com
127.0.0.1 api.example.com
Subdomain Routing
from flask import Flask, url_for
app = Flask(__name__)
app.config['SERVER_NAME'] = 'example.com:5000'
@app.route('/', subdomain='admin')
def admin_static():
return 'Admin Dashboard'
@app.route('/profile', subdomain='<username>')
def user_profile(username):
return f'{username}\'s Profile'
if __name__ == '__main__':
app.run()
Access at http://admin.example.com:5000/ for static subdomain or http://john.example.com:5000/profile for dynamic subdomain.
Custom URL Converters
Regex Converter
Step 1: Create a converter class inheriting from BaseConverterStep 2: Override to_python for input processing Step 3: Override to_url for reverse URL generation
from flask import Flask, url_for
from werkzeug.routing import BaseConverter
app = Flask(__name__)
class NumericConverter(BaseConverter):
def __init__(self, url_map, regex_pattern):
super().__init__(url_map)
self.regex = regex_pattern
def to_python(self, value):
return int(value)
def to_url(self, value):
return super().to_url(value)
app.url_map.converters['digits'] = NumericConverter
@app.route('/item/<digits:"\\d+":item_id>')
def get_item(item_id):
print(f'Item ID type: {type(item_id)}')
url_for('get_item', item_id=42)
return f'Item #{item_id}'
if __name__ == '__main__':
app.run()
Template Features
Passing Functions to Templates
from flask import Flask, render_template, Markup
app = Flask(__name__)
def generate_input(field_name):
return Markup(f'<input type="text" name="{field_name}">')
@app.route('/')
def index():
return render_template('form.html', input_generator=generate_input)
if __name__ == '__main__':
app.run()
form.html:
<html>
<head><meta charset="UTF-8"><title>Form</title></head>
<body>
{{ input_generator('username') }}
{% macro create_field(name, field_type='text', default='') %}
<input type="{{ field_type }}" name="{{ name }}_1">
<input type="{{ field_type }}" name="{{ name }}_2">
<input type="{{ field_type }}" name="{{ name }}_3">
{% endmacro %}
{{ create_field('email') }}
</body>
</html>
Request and Response Handling
Request object (imported from flask): request
Response options:
jsonify(): Return JSON responsesmake_response(): Create custom responses
from flask import Flask, request, make_response, jsonify
app = Flask(__name__)
@app.route('/set-cookie')
def set_cookie():
resp = make_response('Cookie Set')
resp.set_cookie('user_id', '12345')
return resp
@app.route('/json')
def return_json():
return jsonify({'status': 'success', 'data': [1, 2, 3]})
if __name__ == '__main__':
app.run()
Sesion Management
from flask import Flask, session
app = Flask(__name__)
app.secret_key = 'secure_random_key'
@app.route('/save')
def save_data():
session['user'] = 'john_doe'
session['role'] = 'admin'
return 'Data saved to session'
@app.route('/read')
def read_data():
username = session.get('user', 'guest')
return f'Welcome, {username}'
if __name__ == '__main__':
app.run()
Sessions in Flask use signed cookies for storage, keeping data secure on the client side.