Skip to content

Commit 3093f33

Browse files
authored
Add pool_options argument (#13)
* Add pool_options argument * Introduce better naming
1 parent f9a7ed7 commit 3093f33

13 files changed

Lines changed: 434 additions & 162 deletions

File tree

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
[![PyPI - Version](https://img.shields.io/pypi/v/iron-sql)](https://pypi.org/project/iron-sql/)
66

77

8-
`iron_sql` is a typed SQL code generator and async runtime for PostgreSQL. Write SQL where you use it, run `generate_sql_package`, and get a module with typed dataclasses, query helpers, and pooled connections without hand-written boilerplate.
8+
`iron_sql` is a typed SQL code generator and async runtime for PostgreSQL. Write SQL where you use it, run `generate_sql_module`, and get a module with typed dataclasses, query helpers, and pooled connections without hand-written boilerplate.
99

1010
## Installation
1111

@@ -17,7 +17,7 @@ pip install iron-sql[codegen] # + inflection for code generation
1717
The `sqlc` binary is bundled automatically via the `sqlc` Python package.
1818

1919
## Key Features
20-
- **Query discovery.** `generate_sql_package` scans your codebase for calls like `<package>_sql("SELECT ...")`, runs `sqlc` for type analysis, and emits a typed module.
20+
- **Query discovery.** `generate_sql_module` scans your codebase for calls like `<module>_sql("SELECT ...")`, runs `sqlc` for type analysis, and emits a typed module.
2121
- **Strong typing.** Generated dataclasses and method signatures flow through your IDE and type checker.
2222
- **Async runtime.** Built on `psycopg` v3 with pooled connections, context-based connection reuse, and transaction helpers.
2323
- **Streaming.** `query_stream()` uses server-side cursors for memory-efficient iteration over large result sets.
@@ -44,12 +44,12 @@ The `sqlc` binary is bundled automatically via the `sqlc` Python package.
4444
```python
4545
from pathlib import Path
4646

47-
from iron_sql.codegen import generate_sql_package
47+
from iron_sql.codegen import generate_sql_module
4848

49-
generate_sql_package(
49+
generate_sql_module(
5050
schema_path=Path("schema.sql"),
51-
package_full_name="myapp.db.mydb",
52-
dsn_import="myapp.config:DSN",
51+
module_full_name="myapp.db.mydb",
52+
dsn_expr="myapp.config:DSN",
5353
src_path=Path("."),
5454
)
5555
```
@@ -66,7 +66,7 @@ The `sqlc` binary is bundled automatically via the `sqlc` Python package.
6666
- **Type overrides.** `type_overrides={"custom_type": "int"}` maps database type names to Python type strings.
6767
- **JSON model overrides.** `json_model_overrides={"users.metadata": "myapp.models:UserMeta"}` adds Pydantic validation for JSON/JSONB columns.
6868
- **Naming conventions.** Supply `to_pascal_fn` and `to_snake_fn` callables to control generated names.
69-
- **DSN configuration.** `dsn_import` is written verbatim into the generated module; point it at a config variable, env var lookup, or function call.
69+
- **Connection settings.** `dsn_expr` and `pool_options_expr` are written verbatim into the generated module; point them at config variables, env var lookups, or function calls.
7070
- **Debug artifacts.** Pass `debug_path` to save sqlc inputs and outputs for inspection.
7171

7272
## Runtime Highlights

example/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
import os
22

3+
from iron_sql import PoolOptions
4+
35
DSN = os.environ.get("DATABASE_URL", "")
6+
POOL_OPTIONS: PoolOptions = {"min_size": 1, "max_size": 10, "timeout": 15.0}

example/db/mydb.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,15 @@
2727
from iron_sql import runtime
2828

2929
from example.config import DSN
30-
30+
from example.config import POOL_OPTIONS
3131
import example.models
3232

33+
3334
MYDB_POOL = runtime.ConnectionPool(
3435
DSN,
3536
name="mydb",
3637
application_name=None,
38+
pool_options=POOL_OPTIONS,
3739
)
3840

3941
_mydb_connection = ContextVar[psycopg.AsyncConnection | None](
@@ -295,31 +297,31 @@ def query_stream(self, *, status: MydbTaskStatus) -> AbstractAsyncContextManager
295297

296298

297299
@overload
298-
def mydb_sql(stmt: Literal['\n INSERT INTO users (id, username, email)\n VALUES (@id, @username, @email)\n ']) -> Query_3ee53b6909da8b4496346dda36c9f442: ...
300+
def mydb_sql(sql: Literal['\n INSERT INTO users (id, username, email)\n VALUES (@id, @username, @email)\n ']) -> Query_3ee53b6909da8b4496346dda36c9f442: ...
299301
@overload
300-
def mydb_sql(stmt: Literal['\n INSERT INTO projects (id, name, owner_id, settings)\n VALUES (@id, @name, @owner_id, @settings)\n ']) -> Query_67ac0768d48a654b1a305124c92372e8: ...
302+
def mydb_sql(sql: Literal['\n INSERT INTO projects (id, name, owner_id, settings)\n VALUES (@id, @name, @owner_id, @settings)\n ']) -> Query_67ac0768d48a654b1a305124c92372e8: ...
301303
@overload
302-
def mydb_sql(stmt: Literal['\n INSERT INTO tasks (id, project_id, title, priority, assignee_id, metadata, due_date)\n VALUES (@id, @project_id, @title, @priority, @assignee_id?, @metadata?, @due_date?)\n ']) -> Query_bd4c62c78a942bfd1f087f87a19f2743: ...
304+
def mydb_sql(sql: Literal['\n INSERT INTO tasks (id, project_id, title, priority, assignee_id, metadata, due_date)\n VALUES (@id, @project_id, @title, @priority, @assignee_id?, @metadata?, @due_date?)\n ']) -> Query_bd4c62c78a942bfd1f087f87a19f2743: ...
303305
@overload
304-
def mydb_sql(stmt: Literal['UPDATE tasks SET status = @status WHERE id = @task_id']) -> Query_12e061f7aa94bf484295ab0018520059: ...
306+
def mydb_sql(sql: Literal['UPDATE tasks SET status = @status WHERE id = @task_id']) -> Query_12e061f7aa94bf484295ab0018520059: ...
305307
@overload
306-
def mydb_sql(stmt: Literal['SELECT id, username, email, created_at FROM users ORDER BY created_at']) -> Query_46242a02ffe365dc17851a034fdc1d30: ...
308+
def mydb_sql(sql: Literal['SELECT id, username, email, created_at FROM users ORDER BY created_at']) -> Query_46242a02ffe365dc17851a034fdc1d30: ...
307309
@overload
308-
def mydb_sql(stmt: Literal['SELECT id, username, email, created_at FROM users WHERE id = @user_id']) -> Query_41cb2f3cea216a76ba87b6ddb70e6be5: ...
310+
def mydb_sql(sql: Literal['SELECT id, username, email, created_at FROM users WHERE id = @user_id']) -> Query_41cb2f3cea216a76ba87b6ddb70e6be5: ...
309311
@overload
310-
def mydb_sql(stmt: Literal["\n SELECT id, project_id, assignee_id, title, status, priority, metadata, due_date, created_at\n FROM tasks\n WHERE project_id = @project_id AND (sqlc.narg('status')::task_status IS NULL OR status = @status?)\n "]) -> Query_ce9822661c2a7e0e716755087929ebd9: ...
312+
def mydb_sql(sql: Literal["\n SELECT id, project_id, assignee_id, title, status, priority, metadata, due_date, created_at\n FROM tasks\n WHERE project_id = @project_id AND (sqlc.narg('status')::task_status IS NULL OR status = @status?)\n "]) -> Query_ce9822661c2a7e0e716755087929ebd9: ...
311313
@overload
312-
def mydb_sql(stmt: Literal['\n SELECT status, count(*) AS task_count\n FROM tasks WHERE project_id = @project_id\n GROUP BY status ORDER BY status\n '], row_type: Literal['TaskStatusCount']) -> Query_cabe6d4d91163f6aadc739bf765777db_TaskStatusCount: ...
314+
def mydb_sql(sql: Literal['\n SELECT status, count(*) AS task_count\n FROM tasks WHERE project_id = @project_id\n GROUP BY status ORDER BY status\n '], row_type: Literal['TaskStatusCount']) -> Query_cabe6d4d91163f6aadc739bf765777db_TaskStatusCount: ...
313315
@overload
314-
def mydb_sql(stmt: Literal['SELECT id FROM tasks WHERE project_id = @project_id AND title = @title']) -> Query_07cbb3e5226e35adbd17171f38ab7216: ...
316+
def mydb_sql(sql: Literal['SELECT id FROM tasks WHERE project_id = @project_id AND title = @title']) -> Query_07cbb3e5226e35adbd17171f38ab7216: ...
315317
@overload
316-
def mydb_sql(stmt: Literal['SELECT count(*) FROM tasks WHERE status = @status']) -> Query_29c838280e39383dd6b0760431eb3e60: ...
318+
def mydb_sql(sql: Literal['SELECT count(*) FROM tasks WHERE status = @status']) -> Query_29c838280e39383dd6b0760431eb3e60: ...
317319
@overload
318-
def mydb_sql(stmt: str) -> Query: ...
320+
def mydb_sql(sql: str) -> Query: ...
319321

320322

321-
def mydb_sql(stmt: str, row_type: str | None = None) -> Query:
322-
if stmt in _QUERIES:
323-
return _QUERIES[stmt]()
324-
msg = f"Unknown statement: {stmt!r}"
323+
def mydb_sql(sql: str, row_type: str | None = None) -> Query:
324+
if sql in _QUERIES:
325+
return _QUERIES[sql]()
326+
msg = f"Unknown statement: {sql!r}"
325327
raise KeyError(msg)

example/generate.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,23 @@
44
import psycopg
55
from testcontainers.postgres import PostgresContainer
66

7-
from iron_sql.codegen import generate_sql_package
7+
from iron_sql.codegen import generate_sql_module
88

99

1010
def init_db(dsn: str, schema_path: Path):
1111
with psycopg.connect(dsn, autocommit=True) as conn:
1212
conn.execute(schema_path.read_text(encoding="utf-8")) # pyright: ignore[reportCallIssue, reportArgumentType]
1313

1414

15-
def generate_db_package(dsn: str, schema_path: Path, src_path: Path) -> bool:
15+
def generate_db_module(dsn: str, schema_path: Path, src_path: Path) -> bool:
1616
# For example.config:DSN
1717
os.environ["DATABASE_URL"] = dsn
1818

19-
return generate_sql_package(
19+
return generate_sql_module(
2020
schema_path=schema_path,
21-
package_full_name="example.db.mydb",
22-
dsn_import="example.config:DSN",
21+
module_full_name="example.db.mydb",
22+
dsn_expr="example.config:DSN",
23+
pool_options_expr="example.config:POOL_OPTIONS",
2324
src_path=src_path,
2425
json_model_overrides={
2526
"projects.settings": "example.models:ProjectSettings",
@@ -36,5 +37,5 @@ def generate_db_package(dsn: str, schema_path: Path, src_path: Path) -> bool:
3637
with PostgresContainer("postgres:17-alpine") as postgres:
3738
dsn = postgres.get_connection_url(driver=None)
3839
init_db(dsn, schema_path)
39-
changed = generate_db_package(dsn, schema_path, src_path)
40-
print("Updated SQL package:", changed)
40+
changed = generate_db_module(dsn, schema_path, src_path)
41+
print("Updated SQL module:", changed)

src/iron_sql/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
"""iron_sql: Typed SQL client generator for Python."""
22

33
from iron_sql.runtime import NoRowsError
4+
from iron_sql.runtime import PoolOptions
45
from iron_sql.runtime import TooManyRowsError
56

67
__all__ = [
78
"NoRowsError",
9+
"PoolOptions",
810
"TooManyRowsError",
911
]

src/iron_sql/codegen/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from iron_sql.codegen.generator import UnknownSQLTypeWarning
2-
from iron_sql.codegen.generator import generate_sql_package
2+
from iron_sql.codegen.generator import generate_sql_module
33

44
__all__ = [
55
"UnknownSQLTypeWarning",
6-
"generate_sql_package",
6+
"generate_sql_module",
77
]

0 commit comments

Comments
 (0)