Keywords: Flask | SQLAlchemy | Raw SQL | Python | Database
Abstract: This article provides a comprehensive guide on executing raw SQL queries in Flask applications using SQLAlchemy. It covers methods such as db.session.execute() with the text() function, parameterized queries for SQL injection prevention, result handling, and best practices. Practical code examples illustrate secure and efficient database operations.
Introduction
In Flask applications that utilize SQLAlchemy for database interactions, the Object-Relational Mapping (ORM) layer offers a convenient way to work with databases using Python objects. However, executing raw SQL queries becomes necessary in scenarios such as complex joins, performance optimizations, or database-specific operations. This article explores methods for executing raw SQL in Flask-SQLAlchemy, emphasizing security, transaction management, and result handling.
Methods for Executing Raw SQL
SQLAlchemy provides multiple approaches for executing raw SQL queries, primarily involving the session object or the engine object. It is recommended to use the session object for better transaction management, ensuring that multiple queries in a single request are handled as a unit to reduce data corruption risks.
A common method is db.session.execute(), which executes queries within the current transaction. Another approach is db.engine.execute(), but this is connectionless and deprecated in SQLAlchemy 2.0, so it should be avoided in new code.
Using the text() Function
To execute raw SQL safely, SQLAlchemy provides the text() function from the sqlalchemy module. This function wraps the SQL string and enables parameter binding, which helps prevent SQL injection attacks.
Example:
from sqlalchemy import text
sql = text('SELECT * FROM users WHERE email = :email')
result = db.session.execute(sql, {'email': 'user@example.com'})In this example, the :email parameter is bound to a value, making the query secure.
Parameterized Queries and SQL Injection Prevention
Using parameterized queries is essential for security. Instead of concatenating strings, always use placeholders like :param and pass a dictionary of values. This prevents malicious inputs from being executed as SQL code.
For instance, avoid:
query = "SELECT * FROM users WHERE email = '" + email + "'" # Vulnerable to SQL injectionInstead, use:
query = text('SELECT * FROM users WHERE email = :email')
result = db.session.execute(query, {'email': email})Handling Query Results
When a query is executed, it returns a result object that can be iterated over. Each row can be accessed using index or column names.
Example:
for row in result:
print(row[0]) # Access by index
print(row['email']) # Access by column nameAlternatively, you can convert the result to a list of dictionaries or named tuples for easier handling.
from collections import namedtuple
Record = namedtuple('Record', result.keys())
records = [Record(*r) for r in result.fetchall()]
for record in records:
print(record.email)Practical Examples
Consider a complete example within a Flask app context.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import text
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///example.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
with app.app_context():
# Create a table
db.session.execute(text('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT,
email TEXT
)
'''))
db.session.commit()
# Insert data
db.session.execute(text('INSERT INTO users (name, email) VALUES (:name, :email)'),
{'name': 'John Doe', 'email': 'john@example.com'})
db.session.commit()
# Query data
result = db.session.execute(text('SELECT * FROM users'))
for row in result:
print(f"ID: {row.id}, Name: {row.name}, Email: {row.email}")This example demonstrates table creation, data insertion, and querying within a Flask app context.
Best Practices
Always use the session object for executing queries to ensure proper transaction management. Prefer parameterized queries with the text() function to avoid SQL injection. Handle exceptions and commit changes appropriately. In Flask, use the application context to manage database connections.
For complex queries involving multiple joins or inline views, raw SQL may be more efficient, but ensure it is written securely and tested thoroughly.