Skip to content
This repository was archived by the owner on Aug 19, 2025. It is now read-only.

Commit b78e519

Browse files
authored
Add documentation about creating tables using sqlalchemy schemas; Close #234 (#515)
* Add docs about creating tables using sqlalchemy schemas; Close #234 * Add note about preferring alembic outside of experiments * Improve wording
1 parent b38cc4f commit b78e519

File tree

1 file changed

+48
-9
lines changed

1 file changed

+48
-9
lines changed

docs/database_queries.md

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,48 @@ notes = sqlalchemy.Table(
2424
)
2525
```
2626

27-
You can use any of the sqlalchemy column types such as `sqlalchemy.JSON`, or
27+
You can use any of the SQLAlchemy column types such as `sqlalchemy.JSON`, or
2828
custom column types.
2929

30+
## Creating tables
31+
32+
Databases doesn't use SQLAlchemy's engine for database access internally. [The usual SQLAlchemy core way to create tables with `create_all`](https://docs.sqlalchemy.org/en/20/core/metadata.html#sqlalchemy.schema.MetaData.create_all) is therefore not available. To work around this you can use SQLAlchemy to [compile the query to SQL](https://docs.sqlalchemy.org/en/20/faq/sqlexpressions.html#how-do-i-render-sql-expressions-as-strings-possibly-with-bound-parameters-inlined) and then execute it with databases:
33+
34+
```python
35+
from databases import Database
36+
import sqlalchemy
37+
38+
database = Database("postgresql+asyncpg://localhost/example")
39+
40+
# Establish the connection pool
41+
await database.connect()
42+
43+
metadata = sqlalchemy.MetaData()
44+
dialect = sqlalchemy.dialects.postgresql.dialect()
45+
46+
# Define your table(s)
47+
notes = sqlalchemy.Table(
48+
"notes",
49+
metadata,
50+
sqlalchemy.Column("id", sqlalchemy.Integer, primary_key=True),
51+
sqlalchemy.Column("text", sqlalchemy.String(length=100)),
52+
sqlalchemy.Column("completed", sqlalchemy.Boolean),
53+
)
54+
55+
# Create tables
56+
for table in metadata.tables.values():
57+
# Set `if_not_exists=False` if you want the query to throw an
58+
# exception when the table already exists
59+
schema = sqlalchemy.schema.CreateTable(table, if_not_exists=True)
60+
query = str(schema.compile(dialect=dialect))
61+
await database.execute(query=query)
62+
63+
# Close all connections in the connection pool
64+
await database.disconnect()
65+
```
66+
67+
Note that this way of creating tables is only useful for local experimentation. For serious projects, we recommend using a proper database schema migrations solution like [Alembic](https://alembic.sqlalchemy.org/en/latest/).
68+
3069
## Queries
3170

3271
You can now use any [SQLAlchemy core][sqlalchemy-core] queries ([official tutorial][sqlalchemy-core-tutorial]).
@@ -70,11 +109,11 @@ query = notes.select()
70109
async for row in database.iterate(query=query):
71110
...
72111

73-
# Close all connection in the connection pool
112+
# Close all connections in the connection pool
74113
await database.disconnect()
75114
```
76115

77-
Connections are managed as task-local state, with driver implementations
116+
Connections are managed as a task-local state, with driver implementations
78117
transparently using connection pooling behind the scenes.
79118

80119
## Raw queries
@@ -111,17 +150,17 @@ Note that query arguments should follow the `:query_arg` style.
111150

112151
## Query result
113152

114-
To keep in line with [SQLAlchemy 1.4 changes][sqlalchemy-mapping-changes]
115-
query result object no longer implements a mapping interface.
116-
To access query result as a mapping you should use the `_mapping` property.
117-
That way you can process both SQLAlchemy Rows and databases Records from raw queries
153+
To keep in line with [SQLAlchemy 1.4 changes][sqlalchemy-mapping-changes]
154+
query result object no longer implements a mapping interface.
155+
To access query result as a mapping you should use the `_mapping` property.
156+
That way you can process both SQLAlchemy Rows and databases Records from raw queries
118157
with the same function without any instance checks.
119158

120159
```python
121160
query = "SELECT * FROM notes WHERE id = :id"
122161
result = await database.fetch_one(query=query, values={"id": 1})
123-
result.id # access field via attribute
124-
result._mapping['id'] # access field via mapping
162+
result.id # Access field via attribute
163+
result._mapping['id'] # Access field via mapping
125164
```
126165

127166
[sqlalchemy-mapping-changes]: https://docs.sqlalchemy.org/en/14/changelog/migration_14.html#rowproxy-is-no-longer-a-proxy-is-now-called-row-and-behaves-like-an-enhanced-named-tuple

0 commit comments

Comments
 (0)