1. Introduction to Flask What is Flask? A micro web framework for Python. It's lightweight, flexible, and easy to get started with, providing core functionalities without forcing specific dependencies or project layouts. Why Flask? Minimalistic Core: Provides just the essentials (routing, request/response handling) and lets you choose extensions for other features (databases, forms, authentication). Extensible: A rich ecosystem of Flask extensions exists for almost any need. Easy to Learn: Simple API and Pythonic design make it quick to pick up. Flexible: No strict project structure, allowing developers to organize their code as they see fit. Good for APIs: Excellent for building RESTful APIs due to its lightweight nature. Scalable: Can be used for small personal projects to large-scale applications. 2. Installation and Setup 2.1. Basic Installation pip install Flask This installs Flask and its core dependencies (Jinja2, Werkzeug, MarkupSafe, click, itsdangerous). 2.2. Virtual Environments (Recommended) Isolate your project dependencies to avoid conflicts. # Create a virtual environment python -m venv venv # Activate the virtual environment # On macOS/Linux: source venv/bin/activate # On Windows (Command Prompt): venv\Scripts\activate # On Windows (PowerShell): venv\Scripts\Activate.ps1 # Install Flask within the virtual environment pip install Flask To deactivate: deactivate 3. Basic Flask Application 3.1. Hello World! from flask import Flask app = Flask(__name__) # Initialize the Flask application @app.route('/') # Decorator to associate URL '/' with the function def hello_world(): """Returns a simple greeting.""" return 'Hello, World!' if __name__ == '__main__': # Run the development server # debug=True enables debug mode (auto-reloader, debugger) app.run(debug=True, port=5000) app = Flask(__name__) : __name__ is a special Python variable that gets the name of the current module. Flask uses it to know where to look for resources like templates and static files. app.run(debug=True) : Starts a local development server. In production, you'd use a WSGI server like Gunicorn or uWSGI. 3.2. Running the App Save the code as app.py (or wsgi.py , main.py , etc.). From the terminal (with virtual environment activated): python app.py Access in browser: http://127.0.0.1:5000/ 4. Routing and URL Handling 4.1. Basic Routes @app.route('/about') def about(): return 'This is the about page of our application.' @app.route('/contact') def contact(): return 'Contact us at info@example.com' 4.2. Variable Rules Capture parts of the URL as variables to pass to your view function. Converters: <string:name> : (Default) Accepts any text without a slash. <int:id> : Accepts positive integers. <float:price> : Accepts floating point values. <path:filepath> : Accepts paths, including slashes. <uuid:id> : Accepts UUID strings. @app.route('/user/<string:username>') def show_user_profile(username): return f'User Profile for: {username.upper()}' @app.route('/post/<int:post_id>') def show_post(post_id): # post_id will be an integer return f'Displaying Post Number: {post_id}' @app.route('/file/<path:subpath>') def show_file_path(subpath): # subpath can contain slashes, e.g., /file/docs/report.pdf return f'File requested from path: {subpath}' 4.3. HTTP Methods Specify which HTTP methods a route should respond to using the methods argument. Default is GET . from flask import request @app.route('/submit_data', methods=['GET', 'POST']) def submit_data(): if request.method == 'POST': # Data was sent via POST request (e.g., from a form) return 'Data received via POST!' else: # Request was a GET request (e.g., direct URL access) return 'Please use the form to submit data.' 5. Request Object The request object (imported from flask ) contains all incoming request data from the client. 5.1. Common Attributes and Methods Attribute/Method Description request.method The HTTP method of the request (e.g., 'GET', 'POST', 'PUT'). request.args A dictionary-like object for URL query parameters (e.g., ?key=value¶m=other ). request.form A dictionary-like object for form data ( application/x-www-form-urlencoded or multipart/form-data ). request.json Parsed JSON data (if Content-Type is application/json ). Returns None if not JSON. request.data Raw incoming data as bytes. request.files A dictionary-like object for uploaded files. Each item is a FileStorage object. request.headers A dictionary-like object for request headers. request.cookies A dictionary of cookies sent by the client. request.remote_addr The IP address of the client. request.url The full URL of the request. request.path The path component of the URL. request.is_json True if the request's Content-Type indicates JSON data. 5.2. Request Examples @app.route('/search') def search(): query = request.args.get('q') # Get query parameter 'q' page = request.args.get('page', 1, type=int) # Get 'page', default 1, convert to int if query: return f'Searching for "{query}" on page {page}.' return 'Please provide a search query using ?q=your_query' @app.route('/profile_update', methods=['POST']) def profile_update(): username = request.form.get('username') email = request.form.get('email') if username and email: return f'Profile updated for {username} with email {email}.' return 'Missing username or email.', 400 @app.route('/api/create_resource', methods=['POST']) def create_resource(): if request.is_json: data = request.json name = data.get('name') value = data.get('value') if name and value: return jsonify({'message': f'Resource "{name}" created with value "{value}"'}), 201 return jsonify({'error': 'Missing name or value in JSON'}), 400 return jsonify({'error': 'Request must be JSON'}), 400 @app.route('/upload', methods=['POST']) def upload_file(): if 'file' not in request.files: return 'No file part', 400 file = request.files['file'] if file.filename == '': return 'No selected file', 400 if file: # Save file to a specific path file.save(f'/tmp/{file.filename}') return f'File {file.filename} uploaded successfully!' 6. Responses View functions must return a response object, a string, a tuple, or a WSGI callable. Common return types: String: Automatically converted to a response object with text/html content type. Tuple: (response, status_code, headers) . E.g., ('Not Found', 404) or ('{"key": "value"}', 200, {'Content-Type': 'application/json'}) . Response Object: Created using make_response() or jsonify() . 6.1. JSON Responses from flask import jsonify @app.route('/api/status') def get_status(): status = {'service_status': 'operational', 'version': '1.0'} return jsonify(status) # Automatically sets Content-Type to application/json 6.2. Custom Responses & Headers from flask import make_response @app.route('/custom_header') def custom_header_response(): response = make_response('This response has a custom header!') response.headers['X-Custom-Header'] = 'Flask-Rocks' return response @app.route('/set_cookie') def set_cookie(): response = make_response('Cookie has been set!') response.set_cookie('my_cookie', 'my_value', max_age=3600) # Expires in 1 hour return response 7. Templates (Jinja2) Flask uses the powerful Jinja2 templating engine. Templates are typically stored in a templates/ directory in your application root. 7.1. Rendering a Template from flask import render_template @app.route('/dashboard') def dashboard(): user_name = "Alice" items = ['Report A', 'Report B', 'Chart C'] return render_template('dashboard.html', user=user_name, reports=items) render_template('template_name.html', **context) : Renders the specified template, passing keyword arguments as context variables available inside the template. 7.2. Example templates/dashboard.html <!DOCTYPE html> <html> <head> <title>Dashboard</title> </head> <body> <h1>Welcome, {{ user }}!</h1> <h2>Your Reports:</h2> {% if reports %} <ul> {% for report in reports %} <li>{{ report }}</li> {% endfor %} </ul> {% else %} <p>No reports available.</p> {% endif %} <p>Current time: {{ moment().format('LLL') }}</p> <!-- Example with a hypothetical moment.js --> </body> <!-- Jinja2 comments: {# This is a comment and will not be in the output #} --> </html> 7.3. Jinja2 Syntax Summary Variables: {{ variable_name }} . Escaped by default (prevents XSS). Statements: {% control_structure %} (e.g., if , for , extends , block , set ). Comments: {# this is a Jinja2 comment #} (not rendered in output HTML). Filters: Modify variables. Usage: {{ variable | filter_name(arg1, arg2) }} {{ "hello" | upper }} $\rightarrow$ HELLO {{ " whitespace " | trim }} $\rightarrow$ "whitespace" {{ my_list | length }} $\rightarrow$ number of items {{ " raw html " | safe }} $\rightarrow$ renders as HTML (use with caution!) Tests: Check conditions. Usage: {% if variable is test_name %} {% if user is none %} {% if users is iterable %} Macros: Reusable code blocks, similar to functions. {% macro input(name, value='', type='text', size=20) %} <input type="{{ type }}" name="{{ name }}" value="{{ value|e }}" size="{{ size }}"> {% endmacro %} {{ input('username', 'John Doe') }} Includes: Insert one template into another. {% include 'header.html' %} 7.4. Template Inheritance Define a base template with common layout and blocks, then extend it in child templates. templates/base.html : <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{% block title %}My Awesome App{% endblock %}</title> <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}"> </head> <body> <header> <nav> <a href="{{ url_for('index') }}">Home</a> <a href="{{ url_for('about') }}">About</a> </nav> </header> <main> {% block content %}{% endblock %} </main> <footer> <p>© 2023 My Awesome App</p> </footer> <script src="{{ url_for('static', filename='js/main.js') }}"></script> {% block scripts %}{% endblock %} </body> </html> templates/home.html (extends base): {% extends "base.html" %} {% block title %}Home - My Awesome App{% endblock %} {% block content %} <h1>Welcome to the Home Page!</h1> <p>This is the main content area of the home page.</p> <p>Feel free to explore our features.</p> {% endblock %} {% block scripts %} <script> console.log("Home page specific script loaded!"); </script> {% endblock %} 8. Static Files Static files (CSS, JavaScript, images, fonts) are served directly by the web server. Flask serves them from a static/ directory by default, located in your application root. Always use url_for('static', filename='path/to/file') to generate URLs for static files. <!-- In templates/base.html --> <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}"> <script src="{{ url_for('static', filename='js/main.js') }}"></script> <img src="{{ url_for('static', filename='img/logo.png') }}" alt="App Logo"> Structure example: your_project/ ├── app.py ├── static/ │ ├── css/ │ │ └── style.css │ ├── js/ │ │ └── main.js │ └── img/ │ └── logo.png └── templates/ ├── base.html └── home.html 9. URL Building ( url_for ) The url_for() function is essential for dynamically building URLs. Benefits: Avoids hardcoding URLs, making your application more flexible. Automatically handles special characters and URL escaping. Updates URLs automatically if route names change. 9.1. Basic Usage from flask import url_for, redirect @app.route('/') def index(): return 'Welcome to the index page.' @app.route('/user_profile/<username>') def user_profile(username): return f'Viewing profile for {username}' @app.route('/go_to_profile') def go_to_profile(): # Builds URL for 'user_profile' function with 'john_doe' as username return redirect(url_for('user_profile', username='john_doe')) # Example in template: # <a href="{{ url_for('index') }}">Home</a> # <a href="{{ url_for('user_profile', username='admin') }}">Admin Profile</a> # <a href="{{ url_for('static', filename='images/favicon.ico') }}">Favicon</a> 9.2. External URLs You can generate external URLs by passing _external=True . @app.route('/external_link') def external_link(): # Will generate a full URL like http://127.0.0.1:5000/user_profile/jane_doe external_profile_url = url_for('user_profile', username='jane_doe', _external=True) return f'Check out Jane Doe\'s profile: <a href="{external_profile_url}">Link</a>' 10. Redirects and Errors 10.1. Redirects Used to send the client to a different URL. redirect(location, code=302) : code can be 301 (permanent), 302 (found, default), 303 (see other), 307 (temporary redirect), 308 (permanent redirect). from flask import redirect, url_for @app.route('/old-path') def old_path_redirect(): # Permanent redirect to a new, better path return redirect(url_for('new_path_route'), code=301) @app.route('/new-path') def new_path_route(): return 'You have been redirected to the new path!' @app.route('/login_required') def login_required(): # If user is not logged in, redirect them to the login page # (Assuming is_logged_in is a function that checks session/auth) if not is_logged_in(): # Placeholder for actual auth check return redirect(url_for('login_page')) return 'Welcome, logged-in user!' 10.2. Aborting Requests (Errors) abort(status_code) : Raises an HTTPException , which Flask then handles by rendering an error page. from flask import abort, render_template @app.route('/admin_panel') def admin_panel(): # Example: check if user has admin privileges if not current_user.is_admin: # Hypothetical current_user object abort(403) # Forbidden return 'Welcome to the Admin Panel!' @app.route('/item/<int:item_id>') def get_item(item_id): item = find_item_by_id(item_id) # Hypothetical database query if item is None: abort(404) # Not Found return f'Item details for: {item.name}' 10.3. Custom Error Pages Register functions to handle specific HTTP error codes. @app.errorhandler(404) def page_not_found(error): # 'error' argument contains the HTTPException object return render_template('errors/404.html'), 404 @app.errorhandler(403) def forbidden_access(error): return render_template('errors/403.html'), 403 @app.errorhandler(500) def internal_server_error(error): # Log the error for debugging in production app.logger.error(f"Server Error: {error}") return render_template('errors/500.html'), 500 Example templates/errors/404.html : {% extends "base.html" %} {% block title %}Page Not Found{% endblock %} {% block content %} <h1>404 - Page Not Found</h1> <p>The page you are looking for does not exist.</p> <p><a href="{{ url_for('index') }}">Go back to home</a></p> {% endblock %} 11. Sessions Flask provides a way to store information specific to a user from one request to the next using sessions. Sessions are implemented using signed cookies. The data is stored on the client side, but it's cryptographically signed to prevent tampering. Requires a SECRET_KEY to sign the session cookie. 11.1. Configuration from flask import Flask, session import os app = Flask(__name__) # Generate a strong, random key. In production, use environment variables. app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', os.urandom(24)) # Optional: session cookie settings app.config['SESSION_COOKIE_NAME'] = 'my_app_session' app.config['PERMANENT_SESSION_LIFETIME'] = 86400 # 1 day in seconds SECURITY WARNING: Never hardcode your SECRET_KEY in production. Use environment variables. 11.2. Usage @app.route('/login_session', methods=['POST']) def login_session(): username = request.form.get('username') if username == 'admin': # Simple check session['username'] = username session['logged_in'] = True session.permanent = True # Make the session last for PERMANENT_SESSION_LIFETIME flash('Logged in successfully!', 'success') return redirect(url_for('dashboard')) flash('Invalid username!', 'danger') return redirect(url_for('login_form')) @app.route('/dashboard_session') def dashboard_session(): if 'logged_in' in session and session['logged_in']: return f'Hello, {session["username"]}! Your session is active.' return redirect(url_for('login_form')) @app.route('/logout_session') def logout_session(): session.pop('username', None) # Remove specific item session.clear() # Clear all session data flash('You have been logged out.', 'info') return redirect(url_for('index')) `session` behaves like a dictionary. session.pop(key, default) : Removes key, returns its value or default if not found. session.clear() : Clears the entire session. 12. Messages (Flash) The flashing system provides a way to record a message at the end of a request and access it on the next request. Ideal for one-time notifications to the user (e.g., "Item added to cart", "Login successful"). 12.1. Usage from flask import flash, get_flashed_messages @app.route('/add_to_cart', methods=['POST']) def add_to_cart(): item_id = request.form.get('item_id') # ... logic to add item to cart (e.g., update session) ... flash(f'Item {item_id} added to your cart!', 'success') # 'success' is a category return redirect(url_for('view_cart')) @app.route('/view_cart') def view_cart(): # get_flashed_messages() retrieves messages and clears them from session # with_categories=True returns a list of (category, message) tuples messages = get_flashed_messages(with_categories=True) return render_template('cart.html', messages=messages) 12.2. Displaying in Template <!-- templates/cart.html --> {% extends "base.html" %} {% block content %} <h1>Your Shopping Cart</h1> {% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} <div class="flash-messages"> {% for category, message in messages %} <div class="alert alert-{{ category }}"> <!-- Using Bootstrap-like classes --> {{ message }} </div> {% endfor %} </div> {% endif %} {% endwith %} <!-- Cart content here --> <p>... list of items ...</p> {% endblock %} 13. Blueprints Blueprints allow you to organize your Flask application into smaller, reusable components. Each blueprint can define its own views, templates, static files, and error handlers. Ideal for structuring larger applications, separating features (e.g., authentication, blog, admin), or creating reusable components for different projects. 13.1. Creating a Blueprint (e.g., auth/routes.py ) # auth/routes.py from flask import Blueprint, render_template, request, redirect, url_for, flash, session # Create a Blueprint instance # 'auth_bp' is the name of the blueprint # __name__ is the import name for the blueprint # url_prefix will prefix all routes defined in this blueprint auth_bp = Blueprint('auth', __name__, template_folder='templates', static_folder='static', url_prefix='/auth') @auth_bp.route('/register', methods=['GET', 'POST']) def register(): if request.method == 'POST': username = request.form['username'] password = request.form['password'] # ... logic to save user ... flash(f'User {username} registered successfully!', 'success') return redirect(url_for('auth.login')) # Note 'auth.login' return render_template('auth/register.html') # Relative to blueprint's template_folder @auth_bp.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': username = request.form['username'] password = request.form['password'] # ... logic to authenticate user ... if username == 'test' and password == 'password': session['logged_in'] = True session['username'] = username flash('Logged in!', 'info') return redirect(url_for('main.dashboard')) # Redirect to a route in another blueprint ('main') flash('Invalid credentials!', 'danger') return render_template('auth/login.html') 13.2. Registering a Blueprint (e.g., in app.py ) # app.py from flask import Flask, render_template, session, redirect, url_for, flash from auth.routes import auth_bp # Import your blueprint app = Flask(__name__) app.config['SECRET_KEY'] = 'super_secret_key_for_app' # Required for sessions/flashing # Register the blueprint with the application app.register_blueprint(auth_bp) # Define a main blueprint or just main routes directly in app.py @app.route('/') def index(): return render_template('index.html') @app.route('/dashboard') def dashboard(): if not session.get('logged_in'): flash('Please log in to view the dashboard.', 'warning') return redirect(url_for('auth.login')) return render_template('dashboard.html', username=session.get('username')) # To demonstrate url_for from main to blueprint and vice-versa # You can define a 'main' blueprint too if your app is large # main_bp = Blueprint('main', __name__) # @main_bp.route('/dashboard') ... # app.register_blueprint(main_bp) Notice how url_for uses 'blueprint_name.view_function_name' (e.g., url_for('auth.login') ). Blueprint templates are found in blueprint_folder/templates/ . You can structure them further, e.g., auth/templates/auth/login.html . 14. Application Context and Request Context Flask manages two contexts: Application Context: Holds the current application object ( current_app ). Pushed when the application starts or when a CLI command is run. Request Context: Holds the current request-specific objects ( request , session , g ). Pushed at the beginning of each request. These contexts are crucial for thread-safe access to global objects during a request. You typically don't need to manage them manually within view functions, as Flask handles it automatically. Manual pushing is needed for code running outside a request (e.g., custom CLI commands, background tasks). from flask import current_app, request, g # Example: Accessing current_app outside a request with app.app_context(): print(current_app.name) # 'app' # Example: Accessing request data outside a request (for testing/CLI) with app.test_request_context('/hello?name=World', method='GET'): print(request.path) # '/hello' print(request.args['name']) # 'World' g.user = 'System' # 'g' object is specific to the current context print(g.user) @app.route('/context_demo') def context_demo(): # Inside a request, current_app, request, session, g are available automatically print(f"App name: {current_app.name}") print(f"Request method: {request.method}") g.request_start_time = 'some_timestamp' # Store data for the duration of this request return 'Context objects accessed.' 15. Database Integration (Example: SQLAlchemy with Flask-SQLAlchemy) Flask is database-agnostic. Flask-SQLAlchemy is the most popular extension for integrating SQLAlchemy. 15.1. Installation pip install Flask-SQLAlchemy # Add a database driver like psycopg2-binary for PostgreSQL, pymysql for MySQL, or leave for SQLite (built-in) 15.2. Configuration & Model Definition from flask import Flask from flask_sqlalchemy import SQLAlchemy import os app = Flask(__name__) # Configure database URI # Example for SQLite: 'sqlite:///site.db' # Example for PostgreSQL: 'postgresql://user:password@host:port/database' # Example for MySQL: 'mysql+pymysql://user:password@host:port/database' app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL', 'sqlite:///site.db') app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # Suppress warning for event system db = SQLAlchemy(app) class User(db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), unique=True, nullable=False) email = db.Column(db.String(120), unique=True, nullable=False) password_hash = db.Column(db.String(128)) def __repr__(self): return f'<User {self.username}>' class Post(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(100), nullable=False) content = db.Column(db.Text, nullable=False) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) # Define a relationship to User author = db.relationship('User', backref=db.backref('posts', lazy=True)) def __repr__(self): return f'<Post {self.title}>' 15.3. Creating Tables Run this once to create your database tables based on models. # In your app.py or a separate script # Make sure to run this inside an application context with app.app_context(): db.create_all() # To drop all tables (USE WITH CAUTION!): # db.drop_all() 15.4. Basic CRUD Operations @app.route('/db/add_user/<username>/<email>') def add_user_db(username, email): with app.app_context(): new_user = User(username=username, email=email) db.session.add(new_user) # Add to session db.session.commit() # Commit changes to DB return f'User {username} ({email}) added!' @app.route('/db/users') def list_users_db(): with app.app_context(): users = User.query.all() # Fetch all users user_list_html = '<ul>' for user in users: user_list_html += f'<li>{user.username} - {user.email}</li>' user_list_html += '</ul>' return user_list_html @app.route('/db/user/<int:user_id>') def get_user_db(user_id): with app.app_context(): user = User.query.get_or_404(user_id) # Get by primary key, or 404 return f'Found user: {user.username}, Email: {user.email}' @app.route('/db/update_email/<int:user_id>/<new_email>') def update_email_db(user_id, new_email): with app.app_context(): user = User.query.get_or_404(user_id) user.email = new_email db.session.commit() return f'User {user.username} email updated to {new_email}' @app.route('/db/delete_user/<int:user_id>') def delete_user_db(user_id): with app.app_context(): user = User.query.get_or_404(user_id) db.session.delete(user) db.session.commit() return f'User {user.username} deleted.' 15.5. Querying Query Description User.query.all() Get all users. User.query.get(id) Get user by primary key (deprecated, use get_or_404 or get from db.session ). User.query.filter_by(username='Alice').first() Get the first user with username 'Alice'. User.query.filter(User.email.like('%@example.com%')).all() Get users whose email contains '@example.com'. User.query.order_by(User.username.asc()).all() Order users by username ascending. User.query.count() Count total number of users. User.query.paginate(page=1, per_page=10) Paginate query results. 16. Forms (Example: Flask-WTF) Flask-WTF is an integration of WTForms with Flask, providing powerful form handling, validation, and CSRF protection. 16.1. Installation pip install Flask-WTF 16.2. Form Definition (e.g., forms.py ) # forms.py from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, SubmitField, BooleanField, TextAreaField from wtforms.validators import DataRequired, Email, EqualTo, Length, ValidationError from app import User # Assuming User model is defined in app.py class LoginForm(FlaskForm): username = StringField('Username', validators=[DataRequired(), Length(min=2, max=20)]) password = PasswordField('Password', validators=[DataRequired()]) remember_me = BooleanField('Remember Me') submit = SubmitField('Login') class RegistrationForm(FlaskForm): username = StringField('Username', validators=[DataRequired(), Length(min=2, max=20)]) email = StringField('Email', validators=[DataRequired(), Email()]) password = PasswordField('Password', validators=[DataRequired()]) confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')]) submit = SubmitField('Register') # Custom validation methods def validate_username(self, username): user = User.query.filter_by(username=username.data).first() if user: raise ValidationError('That username is taken. Please choose a different one.') def validate_email(self, email): user = User.query.filter_by(email=email.data).first() if user: raise ValidationError('That email is taken. Please choose a different one.') class PostForm(FlaskForm): title = StringField('Title', validators=[DataRequired(), Length(max=100)]) content = TextAreaField('Content', validators=[DataRequired()]) submit = SubmitField('Post') 16.3. Usage in App (e.g., app.py ) # app.py from flask import render_template, flash, redirect, url_for, request from forms import LoginForm, RegistrationForm, PostForm # Import forms from app import db, User, Post # Assuming DB and User/Post models are available # ... app setup (SECRET_KEY, SQLAlchemy) ... @app.route('/login_form', methods=['GET', 'POST']) def login_form(): form = LoginForm() if form.validate_on_submit(): # Checks if POST request and all validators pass # Hypothetical user authentication logic user = User.query.filter_by(username=form.username.data).first() if user and user.password_hash == form.password.data: # In real app, hash and verify password session['user_id'] = user.id session['logged_in'] = True flash(f'Login successful for {form.username.data}!', 'success') return redirect(url_for('dashboard')) else: flash('Login Unsuccessful. Please check username and password', 'danger') return render_template('login_form.html', form=form) @app.route('/register_form', methods=['GET', 'POST']) def register_form(): form = RegistrationForm() if form.validate_on_submit(): # In a real app, hash password before saving hashed_password = form.password.data # Replace with generate_password_hash user = User(username=form.username.data, email=form.email.data, password_hash=hashed_password) db.session.add(user) db.session.commit() flash('Your account has been created! You are now able to log in', 'success') return redirect(url_for('login_form')) return render_template('register_form.html', form=form) @app.route('/create_post', methods=['GET', 'POST']) def create_post(): form = PostForm() if form.validate_on_submit(): if not session.get('logged_in'): flash('You must be logged in to create a post.', 'warning') return redirect(url_for('login_form')) # Assuming current_user is available via Flask-Login or similar user_id = session.get('user_id') post = Post(title=form.title.data, content=form.content.data, user_id=user_id) db.session.add(post) db.session.commit() flash('Your post has been created!', 'success') return redirect(url_for('dashboard')) return render_template('create_post.html', form=form) 16.4. Template Rendering <!-- templates/login_form.html --> {% extends "base.html" %} {% block content %} <h1>Login</h1> <form method="POST"> {{ form.hidden_tag() }} <!-- Renders hidden CSRF token field --> <div class="form-group"> {{ form.username.label(class="form-control-label") }} {{ form.username(class="form-control") }} {% if form.username.errors %} <div class="alert alert-danger"> {% for error in form.username.errors %} <span>{{ error }}</span> {% endfor %} </div> {% endif %} </div> <div class="form-group"> {{ form.password.label(class="form-control-label") }} {{ form.password(class="form-control") }} {% if form.password.errors %} <div class="alert alert-danger"> {% for error in form.password.errors %} <span>{{ error }}</span> {% endfor %} </div> {% endif %} </div> <div class="form-check"> {{ form.remember_me(class="form-check-input") }} {{ form.remember_me.label(class="form-check-label") }} </div> <div class="form-group"> {{ form.submit(class="btn btn-primary") }} </div> </form> {% endblock %} 17. RESTful APIs with Flask Flask is an excellent choice for building RESTful APIs due to its minimalist nature. Key principles: Stateless, client-server, cacheable, layered system, uniform interface. 17.1. Basic API Endpoints from flask import jsonify, request, abort # In-memory data store for demonstration users_data = { 1: {'id': 1, 'name': 'Alice', 'email': 'alice@example.com'}, 2: {'id': 2, 'name': 'Bob', 'email': 'bob@example.com'} } next_user_id = 3 @app.route('/api/users', methods=['GET']) def get_all_users(): return jsonify(list(users_data.values())) @app.route('/api/users/<int:user_id>', methods=['GET']) def get_user_by_id(user_id): user = users_data.get(user_id) if user is None: abort(404, description="User not found") return jsonify(user) @app.route('/api/users', methods=['POST']) def create_user(): global next_user_id if not request.is_json: abort(400, description="Request must be JSON") data = request.json name = data.get('name') email = data.get('email') if not name or not email: abort(400, description="Missing 'name' or 'email'") new_user = {'id': next_user_id, 'name': name, 'email': email} users_data[next_user_id] = new_user next_user_id += 1 return jsonify(new_user), 201 # 201 Created @app.route('/api/users/<int:user_id>', methods=['PUT']) def update_user(user_id): user = users_data.get(user_id) if user is None: abort(404, description="User not found") if not request.is_json: abort(400, description="Request must be JSON") data = request.json user['name'] = data.get('name', user['name']) user['email'] = data.get('email', user['email']) return jsonify(user) @app.route('/api/users/<int:user_id>', methods=['DELETE']) def delete_user(user_id): if user_id not in users_data: abort(404, description="User not found") del users_data[user_id] return jsonify({'message': 'User deleted'}), 204 # 204 No Content 17.2. Using Flask-RESTful (Extension for more structured APIs) pip install Flask-RESTful from flask import Flask from flask_restful import Resource, Api, reqparse, fields, marshal_with, abort app = Flask(__name__) api = Api(app) # In-memory data store TODOS = { 'todo1': {'task': 'build an API'}, 'todo2': {'task': '?????'}, 'todo3': {'task': 'profit!'}, } # Request parser for POST/PUT requests parser = reqparse.RequestParser() parser.add_argument('task', type=str, required=True, help='Task is required', location='json') # Define how a Todo object should be serialized resource_fields = { 'task': fields.String, 'uri': fields.Url('todo_ep') # Dynamically generate URL for this resource } class Todo(Resource): @marshal_with(resource_fields) # Apply fields for serialization def get(self, todo_id): if todo_id not in TODOS: abort(404, message=f"Todo {todo_id} doesn't exist") return TODOS[todo_id] def delete(self, todo_id): if todo_id not in TODOS: abort(404, message=f"Todo {todo_id} doesn't exist") del TODOS[todo_id] return '', 204 @marshal_with(resource_fields) def put(self, todo_id): args = parser.parse_args() task = {'task': args['task']} TODOS[todo_id] = task return task, 200 class TodoList(Resource): @marshal_with(resource_fields) def get(self): return TODOS @marshal_with(resource_fields) def post(self): args = parser.parse_args() todo_id = 'todo%d' % (len(TODOS) + 1) TODOS[todo_id] = {'task': args['task']} return TODOS[todo_id], 201 # Register resources with API api.add_resource(TodoList, '/todos') api.add_resource(Todo, '/todos/<string:todo_id>', endpoint='todo_ep') # endpoint for url_for 18. CLI Commands (Flask CLI) Flask comes with a powerful command-line interface (CLI) for common tasks. You can add your own custom commands. 18.1. Basic Commands flask run : Runs the development server (same as app.run() ). flask shell : Opens a Python shell with application context loaded ( app , db , etc., are available). # Start the app FLASK_APP=app.py FLASK_ENV=development flask run # Open a shell FLASK_APP=app.py flask shell # >>> from app import db, User # >>> with app.app_context(): # ... user = User.query.first() # ... print(user.username) 18.2. Custom Commands Use the @app.cli.command() decorator. import click @app.cli.command('create-admin') @click.argument('username') @click.option('--password', prompt=True, hide_input=True, confirmation_prompt=True) def create_admin(username, password): """Creates a new admin user.""" with app.app_context(): # In a real app, hash the password new_admin = User(username=username, email=f'{username}@admin.com', password_hash=password) db.session.add(new_admin) db.session.commit() click.echo(f'Admin user {username} created successfully!') @app.cli.command('seed-db') def seed_db_command(): """Seeds the database with initial data.""" with app.app_context(): # Example seeding logic if User.query.count() == 0: user1 = User(username='testuser', email='test@example.com', password_hash='testpass') user2 = User(username='another', email='another@example.com', password_hash='anotherpass') db.session.add_all([user1, user2]) db.session.commit() click.echo('Database seeded with initial users.') else: click.echo('Database already contains users. Skipping seeding.') Run custom commands: FLASK_APP=app.py flask create-admin myadminuser FLASK_APP=app.py flask seed-db 19. Deployment The Flask development server is not designed for production use. Use a production-ready WSGI (Web Server Gateway Interface) server. Common setup: Nginx (reverse proxy) + Gunicorn/uWSGI (WSGI server) + Flask app. 19.1. Gunicorn Example # Install Gunicorn pip install gunicorn # Run Gunicorn. 'app:app' means the 'app' variable in 'app.py' gunicorn -w 4 -b 0.0.0.0:8000 app:app # -w N: Number of worker processes # -b ADDRESS:PORT: Bind to this address and port 19.2. Environment Variables Essential for configuration, especially sensitive data. FLASK_APP : Specifies the Python file or module where your Flask application instance is found (e.g., app.py ). FLASK_ENV : Sets the environment ( development , production ). Affects debug mode, error handling, etc. # .env file (for development, use python-dotenv) FLASK_APP=app.py FLASK_ENV=development SECRET_KEY=a_very_long_random_string_for_production_only DATABASE_URL=postgresql://user:pass@db_host/my_prod_db # In your app.py: import os app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY') app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL') # Access 'FLASK_ENV' directly via app.env: # if app.env == 'development': # app.config['DEBUG'] = True 20. Useful Flask Extensions Flask-Login: Manages user sessions, authentication, and protects views. Flask-Bcrypt: Provides strong password hashing for security. Flask-Mail: Simplifies sending emails from your Flask application. Flask-Migrate: Integrates Alembic for database migrations (essential for production). Flask-CORS: Handles Cross-Origin Resource Sharing (CORS) for API access from different domains. Flask-Caching: Adds caching support to your application. Flask-Limiter: Implements rate limiting to protect against abuse. Flask-DebugToolbar: Adds a configurable debug toolbar to your Flask app for detailed request information. Flask-Admin: Build a powerful administrative interface for your models. 21. Debugging and Logging Debug Mode (Development): app.run(debug=True) or FLASK_ENV=development flask run Provides an interactive debugger in the browser for unhandled exceptions. Automatically reloads the server on code changes. Logging: Flask uses Python's standard logging module. import logging from logging.handlers import RotatingFileHandler import os # Basic logging configuration for development if app.env == 'development': app.logger.setLevel(logging.DEBUG) else: # Production logging: log to file or external service handler = RotatingFileHandler('app.log', maxBytes=10000, backupCount=1) handler.setLevel(logging.INFO) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') handler.setFormatter(formatter) app.logger.addHandler(handler) app.logger.setLevel(logging.INFO) @app.route('/log_test') def log_test(): app.logger.debug('This is a DEBUG message.') app.logger.info('This is an INFO message.') app.logger.warning('This is a WARNING message.') app.logger.error('This is an ERROR message.') app.logger.critical('This is a CRITICAL message.') try: 1 / 0 except ZeroDivisionError: app.logger.exception('A ZeroDivisionError occurred!') # Logs traceback return 'Check your logs!' 22. Testing Flask makes testing easy by providing a test client. The test client simulates requests without running a live server. # test_app.py import unittest from app import app, db, User, Post # Import your app and models class FlaskTestCase(unittest.TestCase): def setUp(self): # Set up a test application context app.config['TESTING'] = True app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:' # Use in-memory SQLite for tests app.config['WTF_CSRF_ENABLED'] = False # Disable CSRF for easier testing of forms self.app = app.test_client() with app.app_context(): db.create_all() # Seed test data user = User(username='testuser', email='test@example.com', password_hash='password') db.session.add(user) db.session.commit() self.user_id = user.id def tearDown(self): # Clean up after each test with app.app_context(): db.session.remove() db.drop_all() def test_index_page(self): response = self.app.get('/') self.assertEqual(response.status_code, 200) self.assertIn(b'Welcome', response.data) # Check for content in HTML def test_user_profile_get(self): response = self.app.get(f'/db/user/{self.user_id}') self.assertEqual(response.status_code, 200) self.assertIn(b'testuser', response.data) def test_user_profile_not_found(self): response = self.app.get('/db/user/999') self.assertEqual(response.status_code, 404) def test_login_form_post_success(self): # Simulate a form submission response = self.app.post('/login_form', data={ 'username': 'testuser', 'password': 'password' }, follow_redirects=True) # Follow redirects to check final page self.assertEqual(response.status_code, 200) self.assertIn(b'Login successful', response.data) self.assertIn(b'Hello, testuser', response.data) # Check dashboard content def test_api_create_user(self): response = self.app.post('/api/users', json={ 'name': 'Charlie', 'email': 'charlie@example.com' }) self.assertEqual(response.status_code, 201) self.assertIn(b'Charlie', response.data) if __name__ == '__main__': unittest.main()