Raw SQL¶
djox.Database runs SQL from async code, with or without Django.
Without Django¶
"""Quickstart: run SQL from async code with djox."""
import asyncio
import sys
from pathlib import Path
import djox
async def main(path: Path) -> None:
# One writer connection and a few reader connections, each on its own thread.
with djox.Database.sqlite(path) as database:
await database.execute("CREATE TABLE IF NOT EXISTS book (id INTEGER PRIMARY KEY, title TEXT, pages INTEGER)")
# Writes wait their turn on the writer: no "database is locked" errors.
await database.execute_many(
"INSERT INTO book (title, pages) VALUES (%s, %s)",
[["Dune", 412], ["Emma", 474], ["Beloved", 324]],
)
# Reads run in parallel on the readers. `%s` placeholders, like Django.
long_books, count = await asyncio.gather(
database.fetch_all("SELECT title FROM book WHERE pages > %s ORDER BY title", [400]),
database.fetch_one("SELECT count(*) FROM book"),
)
print(long_books) # [('Dune',), ('Emma',)]
print(count) # (3,)
if __name__ == "__main__":
asyncio.run(main(Path(sys.argv[1] if len(sys.argv) > 1 else "quickstart.sqlite3")))
- Open it:
djox.Database.sqlite(path, readers=4)(one writer, a few readers) ordjox.Database.postgres(url, pool_size=10)(a pool). Use it in awithblock, or callclose(). - TLS on Postgres: libpq's settings, as arguments:
djox.Database.postgres(url, sslmode="verify-full", sslrootcert="ca.pem")(see Postgres). - Methods:
execute(),execute_many(),fetch_all(),fetch_one(). - Placeholders are Django's:
%s(%%for a literal%). - Futures: each method sends the statement right away and returns an
asyncio.Future. Await it, or pass several toasyncio.gather(). - Rows are tuples of
None,int,float,strandbyteson SQLite, like Python'ssqlite3. Parameters can also bebool,datetime,date,time,DecimalandUUID, stored the way Django stores them.
Errors¶
They follow PEP 249, like Python's sqlite3:
| Error | When |
|---|---|
djox.IntegrityError |
a constraint failed |
djox.OperationalError |
the database couldn't run the statement |
djox.ProgrammingError |
a wrong number of parameters... |
djox.DataError |
a value doesn't fit its column |
djox.DatabaseClosedError |
after close() |
The database's errors are subclasses of djox.DatabaseError; all of them,
of djox.Error.
Next to Django¶
djox.connections[alias] is the async Database for the same settings as a
Django alias, shared by the whole process:
import djox
database = djox.connections["default"]
rows = await database.fetch_all("SELECT title FROM shop_book WHERE pages > %s", [300])
- Same SQL as Django: Django's SQL functions (
django_date_extract,REGEXP,STDDEV_POP...) are registered on every connection, so SQL from Django's compiler runs unchanged. - No hooks, no transaction: it is the layer below the ORM. It doesn't
see an open sync transaction (like
TestCase's), and hooks don't run.
SQLite: use djox's ENGINE next to Django
A djox.Database and Python's sqlite3 must never open the same file
(why). With
"ENGINE": "djox.backends.sqlite3", Django uses djox's SQLite too.