Skip to main content

An interface for using plain SQL, in files.

Does writing complex queries in an ORM feel like driving with the handbrake on? Embrace SQL! Put your SQL queries in regular .sql files, and embrace will load them.

Installation:

pip install embrace

Usage:

import embrace

# Connect to your database, using any db-api connector.
# If python supports it, so does embrace.
conn = psycopg2.connect("postgresql:///mydb")

# Create a module populated with queries from a collection of *.sql files:
queries = embrace.module("resources/sql")

# Run a query
users = queries.list_users(conn, order_by='created_at')

Your query would be specified like this:

-- :name list_users :many
select * from users where active = :active order by :identifier:order_by

Embrace returns rows using the underlying db-api cursor. Most db-api libraries have cursor types that return dicts or namedtuples. For example in Postgresql you could do this:

conn = psycopg2.connect(
    "postgresql:///mydb",
    cursor_factory=psycopg2.extras.NamedTupleCursor)
)

What is the format of a query SQL file?

Embrace-SQL tries to stick close to the format used by HugSQL and PugSQL. SQL files normally contain special comments to specify the query name and result type, and an SQL query:

-- :name get_user_count
-- :result :scalar
SELECT count(1) FROM users

If a result type is omitted, it will default to cursor. Also, the result type can be included directly after the name:

-- :name get_user_count :scalar

If :name is omitted, it will default to the filename without the file extension.

A single file may contain multiple queries, separated by a structured SQL comment. For example to create two query objects accessible as queries.list_users() and queries.get_user_by_id():

-- :name list_users :many
select * from users

-- :name get_user_by_id :one
select * from users where id=:id

But if you don’t have the separating comment, embrace-sql can run multiple statements in a single query call, returning the result from just the last one.

Why? Because it makes this possible in MySQL:

-- :result :column
insert into users (name, email) values (:name, :email);
select last_insert_id();

What can queries return?

The following result types are supported:

:affected, :n

The number of rows affected

:first

The first row, as returned by cursor.fetchone(), or None if no row is found.

:one, :1

A single row, as returned by cursor.fetchone(), usually as a tuple (but most db-api modules have extensions allowing you to access rows as dicts or named tuples.

If no row is generated by the query, embrace.exceptions.NoResultFound will be raised. If more than one row is generated by the query, embrace.exceptions.MultipleResultsFound will be raised.

exactly-one`, ``:=1

Synonyms for :one, retained for compatibility

:one-or-none

As one, but returns None if no row is returned by the query.

:many, :*

An iterator over a number of rows. Each row will be the value returned by cursor.fetchone(), usually a tuple.

:cursor, :raw

The cursor object.

:scalar

The value of the first column of the first row returned by the query.

If no row is generated by the query, a NoResultFound will be raised.

:column

An iterator over the values in the first column returned.

You can override the return type specified by the query from Python code by using one of the following methods on the Query object:

  • affected

  • one

  • exactlyone

  • many

  • cursor

  • scalar

  • column

How do parameters work?

Placeholders inserted using the :name syntax are escaped by the db-api driver:

-- Outputs `select * from user where name = 'o''brien'`;
select * from users where name = :name

You can interpolate lists and tuples too:

:tuple: creates a placeholder like this (?, ?, ?)

:value*: creates a placeholder like this ?, ?, ?

:tuple* creates a placeholder like this (?, ?, ?), (?, ?, ?), … (useful for multiple insert queries)

-- Call this with `queries.insert_foo(data=(1, 2, 3))`
INSERT INTO foo (a, b, c) VALUES :tuple:data

-- Call this with `queries.get_matching_users(names=("carolyn", "douglas"))`
SELECT * from users WHERE name in (:values*:names)

You can escape identifiers with :identifier:, like this:

-- Outputs `select * from "some random table"`
select * from :identifier:table_name

You can pass through raw sql too. This leaves you open to SQL injection attacks if you allow user input into such parameters:

-- Outputs `select * from users order by name desc`
select * from users order by :raw:order_clause

How do I handle connections? Transactions?

You must pass a db-api connection object every time you call a query. You can manage these connections yourself, but Embrace also offers a connection pooling module.

from embrace import pool

# Create a connection pool
connection_pool = pool(
    partial(psycopg2.connect, database='mydb'),
    limit=10
)

# Example 1 - explicit calls to getconn/release
conn = connection_pool.getconn()
try:
    queries.execute_some_query(conn)
finally:
    connection_pool.release(conn)

# Example 2 - context manager
with connection_pool.connect() as conn:
    queries.execute_some_query(conn)

Transaction handling may be handled manually by calling commit() or rollback() on the connection object, or you can also use the transaction context run to queries in a transaction:

with queries.transaction(conn) as q:
    q.increment_counter()

The transaction will be commited when the with block exits, or rolled back if an exception occurred.

How do I reload queries when the underlying files change?

Pass auto_reload=True when constructing a module:

m = module('resources/sql', auto_reload=True)

Exceptions

Exceptions raised from the underlying db-api connection are wrapped in exception classes from embrace.exceptions, with PEP-249 compliant names. You can use this to catch exceptions, for example:

try:
    queries.execute("SELECT 1.0 / 0.0")
except embrace.exceptions.DataError:
    pass

The original exception will be available in the __cause__ attribute of the embrace exception object.

Metadata

Release files for embrace 2.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for embrace 2.2.0
File Size Uploaded
embrace-2.2.0.tar.gz 15.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for embrace 2.2.0
File Interpreter ABI Platform
embrace-2.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 35.6 kB

Release files / embrace-2.2.0.tar.gz

Download URL embrace-2.2.0.tar.gz
Size 15.6 kB
Tags Source
SHA-256 checksum
How to use checksums
6ef0d25f8993d69a95981324d67369280859eab7f22586348e3b6c8a5713cd9f
BLAKE2b-256 checksum
How to use checksums
ff973e2d619ccfc04127355258b32149e988dffbab5bf4970dd4fa81225e8daf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.1.1 pkginfo/1.5.0.1 requests/2.22.0 setuptools/44.0.0 requests-toolbelt/0.8.0 tqdm/4.41.1 CPython/3.7.7

Release files / embrace-2.2.0-py3-none-any.whl

Download URL embrace-2.2.0-py3-none-any.whl
Size 20.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
69d6001b35ae97dfd2ce9ae93ecc4af316a276a8db6236b33c5f7df381c949a4
BLAKE2b-256 checksum
How to use checksums
2f9a01082504d6d3ac83162cdec42e0e1336a986c510e03fbd6b76acfb7b58db
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.1.1 pkginfo/1.5.0.1 requests/2.22.0 setuptools/44.0.0 requests-toolbelt/0.8.0 tqdm/4.41.1 CPython/3.7.7

Release history Release notifications | RSS feed

5.2.4

2 release files

5.2.3

2 release files

5.2.2

2 release files

5.2.1

2 release files

5.2.0

2 release files

5.1.0

2 release files

5.0.0

2 release files

4.4.0

1 release file

4.3.0

2 release files

4.2.2

2 release files

4.2.1

2 release files

4.2.0

2 release files

4.1.0

2 release files

4.0.0

2 release files

3.1.1

2 release files

3.1.0

2 release files

3.0.0

2 release files

2.3.1

2 release files

This release

2.2.0 This release

2 release files

2.1.0

2 release files

2.0.0

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page