Skip to content

How to Access the Raw SQL Body in aiosql for Python

In a Python backend community I participate in, a developer recently asked: “I am using aiosql to manage my queries, but I need to log the actual SQL string being sent to the database for auditing. How do I get the ‘body’ of the query from the generated method?”

This is a common hurdle because aiosql dynamically maps SQL files to Python methods, making it feel like the underlying SQL is “hidden” inside the generated object.

The user had a SQL file like this:

-- name: get-user-by-id
-- Get a user by their ID
SELECT * FROM users WHERE id = :user_id;

In Python, they were calling queries.get_user_by_id(conn, user_id=1), but they couldn’t figure out how to access the string SELECT * FROM users WHERE id = :user_id; programmatically for logging purposes.


Immediate Fix: Accessing the .sql Attribute

Section titled “Immediate Fix: Accessing the .sql Attribute”

Every method generated by aiosql is actually a query object (specifically a Query instance) that contains metadata about the SQL it represents. You can access the raw SQL string using the .sql property on the method itself.

Illustrative Example — Python 3.9+ / aiosql 10.x

Section titled “Illustrative Example — Python 3.9+ / aiosql 10.x”
import aiosql
import sqlite3
# 1. Load queries
queries = aiosql.from_path("users.sql", "sqlite3")
# 2. Access the query body WITHOUT executing it
raw_sql = queries.get_user_by_id.sql
print(f"The SQL body is: {raw_sql}")
# Output: SELECT * FROM users WHERE id = :user_id;

When you call aiosql.from_path() or aiosql.from_str(), the library parses the SQL file and creates a Queries object. Each named query in your SQL file (e.g., -- name: get-user-by-id) becomes an attribute on that object.

However, these attributes are not just standard functions; they are instances of aiosql.queries.Query. These objects store:

  • .sql: The raw SQL string as written in your file.
  • .signature: The expected arguments.
  • .doc_comments: Any comments provided above the query.

If you need to log the query and the parameters together, you typically do this manually before the call:

# Standard pattern for logging
query_name = "get_user_by_id"
query_obj = getattr(queries, query_name)
print(f"Executing: {query_name}")
print(f"SQL: {query_obj.sql}")

Solution 2: Bulk Inspection via query_contents

Section titled “Solution 2: Bulk Inspection via query_contents”

If you are building a debugging dashboard or an automated documentation generator and need to see all query bodies at once, you can iterate over the loaded queries.

import aiosql
queries = aiosql.from_path("queries/", "psycopg2")
# aiosql stores query names in a list called available_queries
for query_name in queries.available_queries:
query_obj = getattr(queries, query_name)
print(f"Name: {query_name}")
print(f"Body: {query_obj.sql.strip()}")
print("-" * 20)

This approach is highly effective for “Dry Run” scenarios where you want to validate that all SQL files were parsed correctly by the library before the application starts.


1. Variables are not “Injected” in the String

Section titled “1. Variables are not “Injected” in the String”

It is important to remember that aiosql.get_user_by_id.sql will return the SQL with placeholders (like :user_id or ?), not the final string with values injected. This is because aiosql passes the string and the parameters separately to the database driver (like psycopg2 or sqlite3) to prevent SQL injection.

If you need to see the “rendered” SQL with values, you would need to use your database driver’s specific debugging tools (like cursor.mogrify() in psycopg2).

If you are using aiosql with a “loaded” connection (e.g., queries.load_methods(conn)), the attributes might behave more like bound methods. However, in the standard aiosql.from_path pattern, the .sql attribute remains the most reliable way to access the body.


Can I modify the SQL body at runtime? Technically, you can overwrite the .sql attribute on the query object, but this is discouraged. If you need dynamic SQL, it is better to use a dedicated query builder (like SQLAlchemy Core or HoneySQL patterns) rather than trying to mutate aiosql objects, which are designed to be static representations of your filesystem.

Does this work with asyncio drivers? Yes. Whether you are using aiopg, aiosqlite, or asyncpg, the aiosql query object structure remains the same. The .sql attribute is a synchronous string property and does not require an await to access.

How do I get the comments? If you want the description you wrote in the SQL file, use queries.your_query_name.doc_comments. This is great for generating automatic API documentation directly from your database layer.