AD JB Toolbox 01 EN

MariaDB Tutorial / Connect Python to MariaDB and Use Parameterized Queries

When connecting Python to MariaDB, pass SQL text and user-supplied values separately through the driver API. Parameterized queries keep values out of SQL string construction, while a least-privilege account and explicit commit or rollback control the database changes.

Prepare the environment and application account

This example uses Python 3, PyMySQL 1.1.2, and a local TCP connection to MariaDB. Its core behavior was checked with Python 3.12 and MariaDB 12.3.3. PyMySQL is a Python database driver; its value placeholders in this example are %s. Do not substitute another driver’s question-mark syntax. Use the least-privilege scope from Create Users and Grant Only Required Privileges and the transaction boundaries in Start InnoDB Transactions with COMMIT and ROLLBACK when committing a successful insert/read workflow.

Run the following setup through an administrator connection. Replace the public placeholder Replace_With_Your_Own_Lab_Password with your own lab password. The CREATE USER statement is for initial setup; inspect an existing account and its grants rather than attempting to recreate it blindly. The account and privilege tutorial explains these separate responsibilities.

CREATE DATABASE IF NOT EXISTS appops_lab
  CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE appops_lab;
CREATE TABLE app_notes (
  note_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  note_name VARCHAR(80) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE USER 'appops_app'@'127.0.0.1'
  IDENTIFIED BY 'Replace_With_Your_Own_Lab_Password';
GRANT SELECT, INSERT ON appops_lab.app_notes TO 'appops_app'@'127.0.0.1';

The account host 127.0.0.1 assumes TCP access to a native local server. Containers and remote databases may see a different client origin. Check account host matching and connection paths instead of solving connection problems by allowing administrator access from every host.

Create an isolated project and save the complete program

Create a new project folder and run these Windows PowerShell commands. Calling the environment’s Python directly avoids changing PowerShell’s activation-script policy.

python -m venv .venv
.venv\Scripts\python.exe -m pip install PyMySQL==1.1.2

On Linux or macOS, use .venv/bin/python for the same operations. Save the complete program below as UTF-8 notes_app.py. It asks for the password at runtime rather than embedding it in source code or SQL text.

import argparse
import getpass
import os
import pymysql

def save_and_find(conn, name, sort_key="id"):
    if not name or len(name) > 80:
        raise ValueError("Name must contain 1 to 80 characters")
    columns = {"id": "note_id", "name": "note_name, note_id"}
    if sort_key not in columns:
        raise ValueError("Unknown sort key")
    with conn.cursor() as cur:
        cur.execute("INSERT INTO app_notes (note_name) VALUES (%s)", (name,))
        cur.execute(
            "SELECT note_id, note_name FROM app_notes "
            "WHERE note_name = %s ORDER BY " + columns[sort_key],
            (name,),
        )
        return cur.fetchall()

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--name", default="O'Reilly")
    parser.add_argument("--sort", choices=("id", "name"), default="id")
    args = parser.parse_args()
    password = getpass.getpass("Database password: ")
    conn = pymysql.connect(
        host=os.getenv("DB_HOST", "127.0.0.1"),
        port=int(os.getenv("DB_PORT", "3306")),
        user=os.getenv("DB_USER", "appops_app"),
        password=password,
        database="appops_lab",
        charset="utf8mb4", autocommit=False,
        connect_timeout=5, read_timeout=10, write_timeout=10,
    )
    try:
        rows = save_and_find(conn, args.name, args.sort)
        conn.commit()
        for note_id, note_name in rows:
            print(note_id, note_name)
    except Exception:
        conn.rollback()
        raise
    finally:
        conn.close()

if __name__ == "__main__":
    main()

The connection specifies connect, read, and write timeouts and uses utf8mb4. This article covers a local PC lab. A connection across another network also needs a TLS configuration that verifies the server certificate; supplying a password alone does not protect the transport.

Run ordinary and apostrophe-containing names

.venv\Scripts\python.exe notes_app.py --name "Notebook"
.venv\Scripts\python.exe notes_app.py --name "O'Reilly" --sort name

These are separate program runs. Enter the database password for each. The first stores Notebook and the second stores O'Reilly, including its apostrophe, then queries matching names. On a fresh table, running each once returns its one corresponding row. Do not require a particular auto-increment ID: prior data and rolled-back inserts can affect numbering.

Running the same name again inserts another row and returns all rows matching that name. The example does not declare names unique. If a business process requires deduplication, define its identity rule and an appropriate database constraint rather than assuming the program has already enforced it.

Separate values from SQL syntax

The important call is cur.execute(sql, (name,)): SQL and a tuple of values are separate arguments. The comma creates a one-element tuple. Do not manually quote the %s placeholder or interpolate a user value with Python’s % operator, an f-string, or string concatenation. The official PyMySQL examples follow this separation.

A name such as x' OR 1=1 -- must remain one stored value and match that same string, rather than turning into a condition selecting every row. “Parameterized query” here describes the driver API. It does not claim that every driver necessarily uses the server-side prepared-statement protocol.

Identifiers and SQL keywords, including column names and ASC or DESC, cannot be substituted as ordinary value parameters. The ORDER BY fragment comes only from the developer-defined columns mapping. Both argparse and the function itself validate the choice, so another caller cannot bypass the allowlist. See the Cursor.execute argument reference.

Verify transactions and privileges together

With autocommit=False, a successful helper call does not automatically persist its insert. The main function commits after insertion and selection succeed, rolls back on an exception, and closes the connection in finally. Distinguish this commit boundary from what another connection can currently see; review transaction boundaries when testing.

The application account has only SELECT and INSERT. It does not create or drop tables, so it does not need CREATE or DROP. Least privilege does not replace correct parameter binding, but it reduces the operations available if the program has another defect. The connection option reference covers autocommit and timeout settings.

Diagnose errors and clean up the exercise

Symptom What to inspect
ModuleNotFoundError Run with the same .venv Python used to install PyMySQL
Access denied Username, password, account host, and the client address seen by the server
Connection refused or timeout Server state, DB_HOST and DB_PORT, firewall, and actual network path
No row after processing Whether an exception rolled back, and whether you queried the correct committed database
Failure only for apostrophes Whether a user value was manually interpolated instead of passed to execute

For non-secret connection settings, PowerShell can set an override such as $env:DB_PORT = "3307". Keep the interactive password prompt. Remove a no-longer-needed port override with Remove-Item Env:DB_PORT.

When finished, stop the program and have the administrator confirm these are the disposable lab account and table before removing them. Do not substitute the names of business accounts or tables.

DROP USER 'appops_app'@'127.0.0.1';
DROP TABLE appops_lab.app_notes;
More in This Category
MariaDB Tutorial / Break Queries into Named Steps with WITH and CTEs

MariaDB Tutorial / Break Queries into Named Steps with WITH and CTEs

Build a readable multi-step query with WITH, verify paid orders and per-order totals independently, and understand CTE scope, naming and execution-plan limitations.

MariaDB Tutorial / Create a Database with CREATE DATABASE, USE, and utf8mb4

MariaDB Tutorial / Create a Database with CREATE DATABASE, USE, and utf8mb4

Create a practice database with explicit utf8mb4 settings and verify its definition. Understand connection-local database selection, collation comparisons, and why IF NOT EXISTS preserves existing defaults.

MariaDB Tutorial / Store Dates and Times with DATE, DATETIME, and TIMESTAMP

MariaDB Tutorial / Store Dates and Times with DATE, DATETIME, and TIMESTAMP

Choose DATE, DATETIME, or TIMESTAMP by the meaning of a time value. Run a controlled time-zone comparison, check version-dependent limits and invalid dates, and restore the original session settings.

MariaDB Tutorial / Build a Local MariaDB Lab with Docker Compose

MariaDB Tutorial / Build a Local MariaDB Lab with Docker Compose

Configure a local MariaDB container with Compose, file-based secrets, a healthcheck, and a named volume. Verify connections and distinguish container replacement from deleting persistent data.

MariaDB Tutorial / Create a Product Table, Insert Data, and Query It

MariaDB Tutorial / Create a Product Table, Insert Data, and Query It

Build a complete product table, insert three records, and verify its definition and query results. Practice price and stock filters, understand rerun behavior, and reset only confirmed disposable tutorial data.

MariaDB Tutorial / Compare ROW_NUMBER, RANK, and DENSE_RANK

MariaDB Tutorial / Compare ROW_NUMBER, RANK, and DENSE_RANK

Compare ROW_NUMBER, RANK and DENSE_RANK on customer totals, then run regional rankings and an outer top-N filter. Check how a row limit differs from retaining tied ranks.

MariaDB Tutorial / Back Up an InnoDB Database with mariadb-dump

MariaDB Tutorial / Back Up an InnoDB Database with mariadb-dump

Create a scoped mariadb-dump backup with explicit consistency and file options. Check errors and hashes, inventory additional objects and define a separate-database restoration baseline.

MariaDB Tutorial / Calculate Date Intervals, Differences, and Display Formats

MariaDB Tutorial / Calculate Date Intervals, Differences, and Display Formats

Compare calendar months, day differences and complete-hour differences with fixed MariaDB examples. Format dates correctly and select full months using explicit interval boundaries.

MariaDB Tutorial / Build, Secure, Back Up, and Verify an Order Database

MariaDB Tutorial / Build, Secure, Back Up, and Verify an Order Database

Complete an order-database workflow using constrained tables, a limited app account and a checked stock transaction. Define and compare committed-state backup and restoration criteria, including denied operations.

MariaDB Tutorial / Design Single and Composite Indexes for Real Queries

MariaDB Tutorial / Design Single and Composite Indexes for Real Queries

Inspect existing keys, create a customer–time composite index and review leading columns, covering access and foreign-key dependencies. Preserve query results while evaluating read, write and storage costs.