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 Original Question
Section titled “The Original Question”The user had a SQL file like this:
-- name: get-user-by-id-- Get a user by their IDSELECT * 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 aiosqlimport sqlite3
# 1. Load queriesqueries = aiosql.from_path("users.sql", "sqlite3")
# 2. Access the query body WITHOUT executing itraw_sql = queries.get_user_by_id.sql
print(f"The SQL body is: {raw_sql}")# Output: SELECT * FROM users WHERE id = :user_id;Detailed Explanation
Section titled “Detailed Explanation”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 loggingquery_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.
Illustrative Example — Python 3.10+
Section titled “Illustrative Example — Python 3.10+”import aiosql
queries = aiosql.from_path("queries/", "psycopg2")
# aiosql stores query names in a list called available_queriesfor 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.
Edge Cases and Common Pitfalls
Section titled “Edge Cases and Common Pitfalls”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).
2. Methods vs. Query Objects
Section titled “2. Methods vs. Query Objects”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.
Related Follow-up Questions
Section titled “Related Follow-up Questions”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.