sqlmodel-object-helpers 0.0.2__tar.gz → 0.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. sqlmodel_object_helpers-0.0.4/.gitignore +34 -0
  2. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/PKG-INFO +133 -8
  3. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/README.md +132 -7
  4. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/src/sqlmodel_object_helpers/__init__.py +40 -44
  5. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/src/sqlmodel_object_helpers/constants.py +10 -2
  6. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/src/sqlmodel_object_helpers/exceptions.py +2 -1
  7. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/src/sqlmodel_object_helpers/filters.py +22 -11
  8. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/src/sqlmodel_object_helpers/loaders.py +36 -17
  9. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/src/sqlmodel_object_helpers/mutations.py +86 -78
  10. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/src/sqlmodel_object_helpers/operators.py +4 -2
  11. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/src/sqlmodel_object_helpers/query.py +94 -93
  12. sqlmodel_object_helpers-0.0.4/src/sqlmodel_object_helpers/session.py +153 -0
  13. sqlmodel_object_helpers-0.0.4/src/sqlmodel_object_helpers/standalone.py +221 -0
  14. sqlmodel_object_helpers-0.0.4/src/sqlmodel_object_helpers/types/__init__.py +3 -0
  15. sqlmodel_object_helpers-0.0.4/src/sqlmodel_object_helpers/types/datetime.py +38 -0
  16. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/src/sqlmodel_object_helpers/types/filters.py +24 -12
  17. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/tests/conftest.py +25 -21
  18. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/tests/test_bulk_mutations.py +2 -2
  19. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/tests/test_count_exists.py +5 -5
  20. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/tests/test_exceptions.py +2 -1
  21. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/tests/test_filters.py +20 -2
  22. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/tests/test_loaders.py +39 -11
  23. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/tests/test_operators.py +3 -4
  24. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/tests/test_query.py +9 -9
  25. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/tests/test_settings.py +0 -1
  26. sqlmodel_object_helpers-0.0.4/tests/test_standalone.py +1309 -0
  27. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/tests/test_time_filter.py +73 -35
  28. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/tests/test_types.py +136 -15
  29. sqlmodel_object_helpers-0.0.2/src/sqlmodel_object_helpers/session.py +0 -36
  30. sqlmodel_object_helpers-0.0.2/src/sqlmodel_object_helpers/types/__init__.py +0 -0
  31. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/.github/workflows/publish.yml +0 -0
  32. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/LICENSE +0 -0
  33. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/pyproject.toml +0 -0
  34. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/src/sqlmodel_object_helpers/types/pagination.py +0 -0
  35. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/src/sqlmodel_object_helpers/types/projections.py +0 -0
  36. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/tests/test_computed_columns.py +0 -0
  37. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/tests/test_for_update.py +0 -0
  38. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/tests/test_generated_columns_pg.py +0 -0
  39. {sqlmodel_object_helpers-0.0.2 → sqlmodel_object_helpers-0.0.4}/tests/test_mutations.py +0 -0
@@ -0,0 +1,34 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.pyo
5
+ *.pyd
6
+
7
+ # Distribution / packaging
8
+ dist/
9
+ build/
10
+ *.egg-info/
11
+ *.egg
12
+
13
+ # Virtual environments
14
+ .venv/
15
+ venv/
16
+
17
+ # Testing
18
+ .pytest_cache/
19
+ .coverage
20
+ htmlcov/
21
+
22
+ # IDEs
23
+ .idea/
24
+ .vscode/
25
+ *.swp
26
+ *.swo
27
+
28
+ # MCP config (local)
29
+ .mcp.json
30
+
31
+ # OS
32
+ Thumbs.db
33
+ Desktop.ini
34
+ .DS_Store
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlmodel-object-helpers
3
- Version: 0.0.2
3
+ Version: 0.0.4
4
4
  Summary: Generic async query helpers for SQLModel: filtering, eager loading, pagination
5
5
  Project-URL: Homepage, https://github.com/itstandart/sqlmodel-object-helpers
6
6
  Project-URL: Repository, https://github.com/itstandart/sqlmodel-object-helpers
@@ -55,6 +55,8 @@ Generic async query helpers for [SQLModel](https://sqlmodel.tiangolo.com/): filt
55
55
  - **Relationship Safety Check** - `check_for_related_records` pre-deletion inspection of ONETOMANY dependencies
56
56
  - **Security Limits** - Configurable depth, list size, and pagination caps to prevent abuse
57
57
  - **Type Safety** - Full type annotations with PEP 695 generics and `py.typed` marker (PEP 561)
58
+ - **Standalone Mode** - `configure()` + `import sqlmodel_object_helpers.standalone` for auto-session usage without DI
59
+ - **Session Lifecycle Logging** - Transparent session open/commit/rollback logging with hex session IDs and timing
58
60
 
59
61
  ## Installation
60
62
 
@@ -62,13 +64,20 @@ Generic async query helpers for [SQLModel](https://sqlmodel.tiangolo.com/): filt
62
64
  pip install sqlmodel-object-helpers
63
65
  ```
64
66
 
65
- One import, everything through dot notation:
67
+ Two usage modes:
66
68
 
67
69
  ```python
70
+ # DI mode — caller provides session (FastAPI Depends, etc.)
68
71
  import sqlmodel_object_helpers as soh
69
72
 
70
73
  soh.get_object(session, ...)
71
74
  soh.add_object(session, ...)
75
+
76
+ # Standalone mode — auto-creates session per call
77
+ import sqlmodel_object_helpers.standalone as soh_sa
78
+
79
+ soh_sa.get_object(User, pk={"id": 1})
80
+ soh_sa.add_object(User(name="Alice"))
72
81
  ```
73
82
 
74
83
  ## Quick Start
@@ -109,6 +118,107 @@ async def example(session: AsyncSession):
109
118
  # page.data -> list[User], page.pagination.total -> int
110
119
  ```
111
120
 
121
+ ## Standalone Mode
122
+
123
+ For projects that don't use FastAPI DI or need simple one-call-one-transaction semantics.
124
+
125
+ > **Important:** The session factory **must** use `expire_on_commit=False`.
126
+ > After each standalone call the session commits and closes — with the default `True`,
127
+ > all attributes on returned objects would be expired and inaccessible (`DetachedInstanceError`).
128
+
129
+ ```python
130
+ from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
131
+ import sqlmodel_object_helpers as soh
132
+ import sqlmodel_object_helpers.standalone as soh_sa
133
+
134
+ engine = create_async_engine("postgresql+asyncpg://...")
135
+ async_session_factory = async_sessionmaker(engine, expire_on_commit=False)
136
+
137
+ # 1. Configure the session factory once at startup
138
+ soh.configure(async_session_factory)
139
+
140
+ # 2. Use standalone wrappers — each call creates its own session and commits
141
+ user = await soh_sa.get_object(User, pk={"id": 1})
142
+ new_user = await soh_sa.add_object(User(name="Alice"))
143
+ await soh_sa.delete_object(User, pk={"id": 5})
144
+
145
+ # All 12 functions are available:
146
+ # Queries: get_object, get_objects, count_objects, exists_object, get_projection
147
+ # Mutations: add_object, add_objects, update_object, update_objects,
148
+ # delete_object, delete_objects, check_for_related_records
149
+ ```
150
+
151
+ Each standalone call creates a session, executes the operation, commits on success, and rolls back on error. No session parameter needed.
152
+
153
+ ### auto_session - multi-operation transactions
154
+
155
+ When you need multiple operations in a single atomic transaction:
156
+
157
+ ```python
158
+ import sqlmodel_object_helpers as soh
159
+
160
+ async with soh.auto_session() as session:
161
+ billing = await soh.add_object(session, Billing(...))
162
+ payment = await soh.add_object(session, Payment(...))
163
+ # commit happens automatically on exit
164
+ # if any operation fails — ALL are rolled back
165
+ ```
166
+
167
+ ## Session Management
168
+
169
+ ### DI mode — `create_session_dependency()`
170
+
171
+ For FastAPI projects with dependency injection:
172
+
173
+ ```python
174
+ import sqlmodel_object_helpers as soh
175
+ from typing import Annotated
176
+ from fastapi import Depends
177
+ from sqlalchemy.ext.asyncio import AsyncSession
178
+
179
+ get_session = soh.create_session_dependency(async_session_factory)
180
+ DbSession = Annotated[AsyncSession, Depends(get_session)]
181
+
182
+ @router.get("/users/{user_id}")
183
+ async def get_user(user_id: int, session: DbSession):
184
+ return await soh.get_object(session, User, pk={"id": user_id})
185
+ ```
186
+
187
+ One session per HTTP request. The dependency commits on success, rolls back on error.
188
+
189
+ ### Standalone mode — `configure()`
190
+
191
+ For projects without DI or for scripts/CLI:
192
+
193
+ ```python
194
+ import sqlmodel_object_helpers as soh
195
+
196
+ soh.configure(async_session_factory)
197
+ # Now standalone functions and auto_session() are available
198
+ ```
199
+
200
+ ### Session Lifecycle Logging
201
+
202
+ All session operations are logged via `logging.getLogger("sqlmodel_object_helpers")`:
203
+
204
+ ```
205
+ DEBUG auto_session[1a2b3c4d] opened
206
+ DEBUG auto_session[1a2b3c4d] committed (0.015s)
207
+ WARNING auto_session[1a2b3c4d] rollback (0.003s) — MutationError: ...
208
+ ```
209
+
210
+ - **Hex session ID** (`1a2b3c4d`) correlates all log lines for the same session
211
+ - **Prefix** distinguishes mode: `auto_session` (standalone/auto_session) vs `di_session` (DI dependency)
212
+ - **Timing** shows elapsed time from session open to commit/rollback
213
+ - **Level**: `DEBUG` for normal flow, `WARNING` for rollbacks and commit failures
214
+
215
+ Enable with:
216
+
217
+ ```python
218
+ import logging
219
+ logging.getLogger("sqlmodel_object_helpers").setLevel(logging.DEBUG)
220
+ ```
221
+
112
222
  ## Configuration
113
223
 
114
224
  Override security limits at application startup:
@@ -127,6 +237,7 @@ soh.settings.max_filter_depth = 50
127
237
  | `max_load_depth` | `10` | Maximum depth of eager-loading chains |
128
238
  | `max_in_list_size` | `1000` | Maximum elements in an IN(...) list |
129
239
  | `max_per_page` | `500` | Maximum value for per_page in pagination |
240
+ | `session_factory` | `None` | `async_sessionmaker` instance for standalone mode (set via `soh.configure()`) |
130
241
 
131
242
  ## Query Operations
132
243
 
@@ -194,13 +305,13 @@ users = await soh.get_objects(
194
305
  )
195
306
 
196
307
  # Time filtering
197
- from datetime import datetime
308
+ from datetime import datetime, timezone
198
309
 
199
310
  recent = await soh.get_objects(
200
311
  session, User,
201
312
  time_filter=soh.TimeFilter(
202
- created_after=datetime(2026, 1, 1),
203
- created_before=datetime(2026, 2, 1),
313
+ created_after=datetime(2026, 1, 1, tzinfo=timezone.utc),
314
+ created_before=datetime(2026, 2, 1, tzinfo=timezone.utc),
204
315
  ),
205
316
  )
206
317
  ```
@@ -221,7 +332,7 @@ active = await soh.count_objects(session, User, filters={"is_active": {soh.Opera
221
332
  # With time filter
222
333
  recent = await soh.count_objects(
223
334
  session, User,
224
- time_filter=soh.TimeFilter(created_after=datetime(2026, 1, 1)),
335
+ time_filter=soh.TimeFilter(created_after=datetime(2026, 1, 1, tzinfo=timezone.utc)),
225
336
  )
226
337
  ```
227
338
 
@@ -405,7 +516,7 @@ Used by `get_objects`. Nested dicts are auto-flattened via `flatten_filters`:
405
516
  | `Operator.ILIKE` | `ILIKE` | `{soh.Operator.ILIKE: "%test%"}` |
406
517
  | `Operator.BETWEEN` | `BETWEEN` | `{soh.Operator.BETWEEN: [1, 10]}` |
407
518
  | `Operator.IS` | `IS` | `{soh.Operator.IS: None}` |
408
- | `Operator.ISNOT` | `IS NOT` | `{soh.Operator.ISNOT: None}` |
519
+ | `Operator.IS_NOT` | `IS NOT` | `{soh.Operator.IS_NOT: None}` |
409
520
  | `Operator.MATCH` | `MATCH` | `{soh.Operator.MATCH: "query"}` |
410
521
  | `"exists"` | `IS NOT NULL` / `.any()` | `{"exists": True}` |
411
522
 
@@ -483,6 +594,16 @@ result = await soh.get_objects(session, User, order_by=order)
483
594
 
484
595
  ## API Reference
485
596
 
597
+ ### Session Management
598
+
599
+ - `soh.configure(factory)` -- Register `async_sessionmaker` for standalone mode
600
+ - `soh.auto_session()` -- Async context manager: creates session, commits on success, rolls back on error
601
+ - `soh.create_session_dependency(factory)` -- Create async generator for FastAPI `Depends`
602
+
603
+ ### Standalone Wrappers
604
+
605
+ - `import sqlmodel_object_helpers.standalone as soh_sa` -- All 12 query/mutation functions without `session` parameter
606
+
486
607
  ### Settings
487
608
 
488
609
  - `soh.settings` -- Module-level `QueryHelperSettings` instance (mutable at runtime)
@@ -521,7 +642,7 @@ result = await soh.get_objects(session, User, order_by=order)
521
642
  ### Loaders
522
643
 
523
644
  - `soh.build_load_chain(model, path, ...)` -- Build loader option from string/list path
524
- - `soh.build_load_options(model, attrs)` -- Build single loader option chain
645
+ - `soh.build_load_options(model, attrs, *, suspend_error=False)` -- Build single loader option chain
525
646
 
526
647
  ### Exceptions
527
648
 
@@ -539,6 +660,10 @@ result = await soh.get_objects(session, User, order_by=order)
539
660
  - `soh.LogicalFilter`
540
661
  - `soh.TimeFilter` -- `created_after`, `created_before`, `updated_after`, `updated_before` with half-open interval `[after, before)`
541
662
 
663
+ ### Datetime Types
664
+
665
+ - `soh.UTCDatetime` -- `Annotated[datetime, AfterValidator]` that rejects naive datetimes and converts aware datetimes to UTC
666
+
542
667
  ### Pagination Types
543
668
 
544
669
  - `soh.Pagination` -- Request model (page, per\_page)
@@ -21,6 +21,8 @@ Generic async query helpers for [SQLModel](https://sqlmodel.tiangolo.com/): filt
21
21
  - **Relationship Safety Check** - `check_for_related_records` pre-deletion inspection of ONETOMANY dependencies
22
22
  - **Security Limits** - Configurable depth, list size, and pagination caps to prevent abuse
23
23
  - **Type Safety** - Full type annotations with PEP 695 generics and `py.typed` marker (PEP 561)
24
+ - **Standalone Mode** - `configure()` + `import sqlmodel_object_helpers.standalone` for auto-session usage without DI
25
+ - **Session Lifecycle Logging** - Transparent session open/commit/rollback logging with hex session IDs and timing
24
26
 
25
27
  ## Installation
26
28
 
@@ -28,13 +30,20 @@ Generic async query helpers for [SQLModel](https://sqlmodel.tiangolo.com/): filt
28
30
  pip install sqlmodel-object-helpers
29
31
  ```
30
32
 
31
- One import, everything through dot notation:
33
+ Two usage modes:
32
34
 
33
35
  ```python
36
+ # DI mode — caller provides session (FastAPI Depends, etc.)
34
37
  import sqlmodel_object_helpers as soh
35
38
 
36
39
  soh.get_object(session, ...)
37
40
  soh.add_object(session, ...)
41
+
42
+ # Standalone mode — auto-creates session per call
43
+ import sqlmodel_object_helpers.standalone as soh_sa
44
+
45
+ soh_sa.get_object(User, pk={"id": 1})
46
+ soh_sa.add_object(User(name="Alice"))
38
47
  ```
39
48
 
40
49
  ## Quick Start
@@ -75,6 +84,107 @@ async def example(session: AsyncSession):
75
84
  # page.data -> list[User], page.pagination.total -> int
76
85
  ```
77
86
 
87
+ ## Standalone Mode
88
+
89
+ For projects that don't use FastAPI DI or need simple one-call-one-transaction semantics.
90
+
91
+ > **Important:** The session factory **must** use `expire_on_commit=False`.
92
+ > After each standalone call the session commits and closes — with the default `True`,
93
+ > all attributes on returned objects would be expired and inaccessible (`DetachedInstanceError`).
94
+
95
+ ```python
96
+ from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
97
+ import sqlmodel_object_helpers as soh
98
+ import sqlmodel_object_helpers.standalone as soh_sa
99
+
100
+ engine = create_async_engine("postgresql+asyncpg://...")
101
+ async_session_factory = async_sessionmaker(engine, expire_on_commit=False)
102
+
103
+ # 1. Configure the session factory once at startup
104
+ soh.configure(async_session_factory)
105
+
106
+ # 2. Use standalone wrappers — each call creates its own session and commits
107
+ user = await soh_sa.get_object(User, pk={"id": 1})
108
+ new_user = await soh_sa.add_object(User(name="Alice"))
109
+ await soh_sa.delete_object(User, pk={"id": 5})
110
+
111
+ # All 12 functions are available:
112
+ # Queries: get_object, get_objects, count_objects, exists_object, get_projection
113
+ # Mutations: add_object, add_objects, update_object, update_objects,
114
+ # delete_object, delete_objects, check_for_related_records
115
+ ```
116
+
117
+ Each standalone call creates a session, executes the operation, commits on success, and rolls back on error. No session parameter needed.
118
+
119
+ ### auto_session - multi-operation transactions
120
+
121
+ When you need multiple operations in a single atomic transaction:
122
+
123
+ ```python
124
+ import sqlmodel_object_helpers as soh
125
+
126
+ async with soh.auto_session() as session:
127
+ billing = await soh.add_object(session, Billing(...))
128
+ payment = await soh.add_object(session, Payment(...))
129
+ # commit happens automatically on exit
130
+ # if any operation fails — ALL are rolled back
131
+ ```
132
+
133
+ ## Session Management
134
+
135
+ ### DI mode — `create_session_dependency()`
136
+
137
+ For FastAPI projects with dependency injection:
138
+
139
+ ```python
140
+ import sqlmodel_object_helpers as soh
141
+ from typing import Annotated
142
+ from fastapi import Depends
143
+ from sqlalchemy.ext.asyncio import AsyncSession
144
+
145
+ get_session = soh.create_session_dependency(async_session_factory)
146
+ DbSession = Annotated[AsyncSession, Depends(get_session)]
147
+
148
+ @router.get("/users/{user_id}")
149
+ async def get_user(user_id: int, session: DbSession):
150
+ return await soh.get_object(session, User, pk={"id": user_id})
151
+ ```
152
+
153
+ One session per HTTP request. The dependency commits on success, rolls back on error.
154
+
155
+ ### Standalone mode — `configure()`
156
+
157
+ For projects without DI or for scripts/CLI:
158
+
159
+ ```python
160
+ import sqlmodel_object_helpers as soh
161
+
162
+ soh.configure(async_session_factory)
163
+ # Now standalone functions and auto_session() are available
164
+ ```
165
+
166
+ ### Session Lifecycle Logging
167
+
168
+ All session operations are logged via `logging.getLogger("sqlmodel_object_helpers")`:
169
+
170
+ ```
171
+ DEBUG auto_session[1a2b3c4d] opened
172
+ DEBUG auto_session[1a2b3c4d] committed (0.015s)
173
+ WARNING auto_session[1a2b3c4d] rollback (0.003s) — MutationError: ...
174
+ ```
175
+
176
+ - **Hex session ID** (`1a2b3c4d`) correlates all log lines for the same session
177
+ - **Prefix** distinguishes mode: `auto_session` (standalone/auto_session) vs `di_session` (DI dependency)
178
+ - **Timing** shows elapsed time from session open to commit/rollback
179
+ - **Level**: `DEBUG` for normal flow, `WARNING` for rollbacks and commit failures
180
+
181
+ Enable with:
182
+
183
+ ```python
184
+ import logging
185
+ logging.getLogger("sqlmodel_object_helpers").setLevel(logging.DEBUG)
186
+ ```
187
+
78
188
  ## Configuration
79
189
 
80
190
  Override security limits at application startup:
@@ -93,6 +203,7 @@ soh.settings.max_filter_depth = 50
93
203
  | `max_load_depth` | `10` | Maximum depth of eager-loading chains |
94
204
  | `max_in_list_size` | `1000` | Maximum elements in an IN(...) list |
95
205
  | `max_per_page` | `500` | Maximum value for per_page in pagination |
206
+ | `session_factory` | `None` | `async_sessionmaker` instance for standalone mode (set via `soh.configure()`) |
96
207
 
97
208
  ## Query Operations
98
209
 
@@ -160,13 +271,13 @@ users = await soh.get_objects(
160
271
  )
161
272
 
162
273
  # Time filtering
163
- from datetime import datetime
274
+ from datetime import datetime, timezone
164
275
 
165
276
  recent = await soh.get_objects(
166
277
  session, User,
167
278
  time_filter=soh.TimeFilter(
168
- created_after=datetime(2026, 1, 1),
169
- created_before=datetime(2026, 2, 1),
279
+ created_after=datetime(2026, 1, 1, tzinfo=timezone.utc),
280
+ created_before=datetime(2026, 2, 1, tzinfo=timezone.utc),
170
281
  ),
171
282
  )
172
283
  ```
@@ -187,7 +298,7 @@ active = await soh.count_objects(session, User, filters={"is_active": {soh.Opera
187
298
  # With time filter
188
299
  recent = await soh.count_objects(
189
300
  session, User,
190
- time_filter=soh.TimeFilter(created_after=datetime(2026, 1, 1)),
301
+ time_filter=soh.TimeFilter(created_after=datetime(2026, 1, 1, tzinfo=timezone.utc)),
191
302
  )
192
303
  ```
193
304
 
@@ -371,7 +482,7 @@ Used by `get_objects`. Nested dicts are auto-flattened via `flatten_filters`:
371
482
  | `Operator.ILIKE` | `ILIKE` | `{soh.Operator.ILIKE: "%test%"}` |
372
483
  | `Operator.BETWEEN` | `BETWEEN` | `{soh.Operator.BETWEEN: [1, 10]}` |
373
484
  | `Operator.IS` | `IS` | `{soh.Operator.IS: None}` |
374
- | `Operator.ISNOT` | `IS NOT` | `{soh.Operator.ISNOT: None}` |
485
+ | `Operator.IS_NOT` | `IS NOT` | `{soh.Operator.IS_NOT: None}` |
375
486
  | `Operator.MATCH` | `MATCH` | `{soh.Operator.MATCH: "query"}` |
376
487
  | `"exists"` | `IS NOT NULL` / `.any()` | `{"exists": True}` |
377
488
 
@@ -449,6 +560,16 @@ result = await soh.get_objects(session, User, order_by=order)
449
560
 
450
561
  ## API Reference
451
562
 
563
+ ### Session Management
564
+
565
+ - `soh.configure(factory)` -- Register `async_sessionmaker` for standalone mode
566
+ - `soh.auto_session()` -- Async context manager: creates session, commits on success, rolls back on error
567
+ - `soh.create_session_dependency(factory)` -- Create async generator for FastAPI `Depends`
568
+
569
+ ### Standalone Wrappers
570
+
571
+ - `import sqlmodel_object_helpers.standalone as soh_sa` -- All 12 query/mutation functions without `session` parameter
572
+
452
573
  ### Settings
453
574
 
454
575
  - `soh.settings` -- Module-level `QueryHelperSettings` instance (mutable at runtime)
@@ -487,7 +608,7 @@ result = await soh.get_objects(session, User, order_by=order)
487
608
  ### Loaders
488
609
 
489
610
  - `soh.build_load_chain(model, path, ...)` -- Build loader option from string/list path
490
- - `soh.build_load_options(model, attrs)` -- Build single loader option chain
611
+ - `soh.build_load_options(model, attrs, *, suspend_error=False)` -- Build single loader option chain
491
612
 
492
613
  ### Exceptions
493
614
 
@@ -505,6 +626,10 @@ result = await soh.get_objects(session, User, order_by=order)
505
626
  - `soh.LogicalFilter`
506
627
  - `soh.TimeFilter` -- `created_after`, `created_before`, `updated_after`, `updated_before` with half-open interval `[after, before)`
507
628
 
629
+ ### Datetime Types
630
+
631
+ - `soh.UTCDatetime` -- `Annotated[datetime, AfterValidator]` that rejects naive datetimes and converts aware datetimes to UTC
632
+
508
633
  ### Pagination Types
509
634
 
510
635
  - `soh.Pagination` -- Request model (page, per\_page)
@@ -1,6 +1,6 @@
1
1
  """sqlmodel-object-helpers — reusable query helpers for SQLModel projects."""
2
2
 
3
- __version__ = "0.0.2"
3
+ __version__ = "0.0.4"
4
4
 
5
5
  from .constants import QueryHelperSettings, settings
6
6
  from .exceptions import (
@@ -24,13 +24,15 @@ from .mutations import (
24
24
  update_objects,
25
25
  )
26
26
  from .query import count_objects, exists_object, get_object, get_objects, get_projection
27
- from .session import create_session_dependency
27
+ from .session import auto_session, configure, create_session_dependency
28
+ from .types.datetime import UTCDatetime
28
29
  from .types.filters import (
29
30
  FilterBool,
30
31
  FilterDate,
31
32
  FilterDatetime,
32
33
  FilterExists,
33
34
  FilterInt,
35
+ FilterNaiveDatetime,
34
36
  FilterStr,
35
37
  FilterTimedelta,
36
38
  LogicalFilter,
@@ -47,59 +49,53 @@ from .types.pagination import (
47
49
  from .types.projections import ColumnSpec
48
50
 
49
51
  __all__ = [
50
- # Settings
51
- "settings",
52
- "QueryHelperSettings",
53
- # Query functions (read)
54
- "get_object",
55
- "get_objects",
56
- "get_projection",
57
- "count_objects",
58
- "exists_object",
59
- # Mutation functions (create / update / delete)
60
52
  "add_object",
61
53
  "add_objects",
62
- "update_object",
63
- "update_objects",
64
- "delete_object",
65
- "delete_objects",
66
- "check_for_related_records",
67
- # Filters
54
+ "auto_session",
68
55
  "build_filter",
69
56
  "build_flat_filter",
70
- "flatten_filters",
71
- "SUPPORTED_OPERATORS",
72
- "SPECIAL_OPERATORS",
73
- "Operator",
74
- # Loaders
75
- "build_load_options",
76
57
  "build_load_chain",
77
- # Exceptions
78
- "QueryError",
79
- "ObjectNotFoundError",
80
- "InvalidFilterError",
81
- "InvalidLoadPathError",
58
+ "build_load_options",
59
+ "check_for_related_records",
60
+ "ColumnSpec",
61
+ "configure",
62
+ "count_objects",
63
+ "create_session_dependency",
82
64
  "DatabaseError",
83
- "MutationError",
84
- # Filter types
85
- "FilterInt",
86
- "FilterStr",
65
+ "delete_object",
66
+ "delete_objects",
67
+ "exists_object",
68
+ "FilterBool",
87
69
  "FilterDate",
88
70
  "FilterDatetime",
89
- "FilterTimedelta",
90
- "FilterBool",
91
71
  "FilterExists",
72
+ "FilterInt",
73
+ "FilterNaiveDatetime",
74
+ "FilterStr",
75
+ "FilterTimedelta",
76
+ "flatten_filters",
77
+ "get_object",
78
+ "get_objects",
79
+ "get_projection",
80
+ "GetAllPagination",
81
+ "InvalidFilterError",
82
+ "InvalidLoadPathError",
83
+ "LogicalFilter",
84
+ "MutationError",
85
+ "ObjectNotFoundError",
86
+ "Operator",
92
87
  "OrderAsc",
93
- "OrderDesc",
94
88
  "OrderBy",
95
- "LogicalFilter",
96
- "TimeFilter",
97
- # Pagination types
89
+ "OrderDesc",
98
90
  "Pagination",
99
91
  "PaginationR",
100
- "GetAllPagination",
101
- # Projection types
102
- "ColumnSpec",
103
- # Session
104
- "create_session_dependency",
92
+ "QueryError",
93
+ "QueryHelperSettings",
94
+ "settings",
95
+ "SPECIAL_OPERATORS",
96
+ "SUPPORTED_OPERATORS",
97
+ "TimeFilter",
98
+ "update_object",
99
+ "update_objects",
100
+ "UTCDatetime",
105
101
  ]
@@ -1,4 +1,5 @@
1
- """Security and performance limits for query building.
1
+ """
2
+ Security and performance limits for query building.
2
3
 
3
4
  All values have sensible defaults. Override at application startup:
4
5
 
@@ -7,11 +8,14 @@ All values have sensible defaults. Override at application startup:
7
8
  settings.max_filter_depth = 50
8
9
  """
9
10
 
11
+ from typing import Any
12
+
10
13
  from pydantic import BaseModel
11
14
 
12
15
 
13
16
  class QueryHelperSettings(BaseModel):
14
- """Mutable settings for query-helper limits.
17
+ """
18
+ Mutable settings for query-helper limits.
15
19
 
16
20
  Attributes:
17
21
  max_filter_depth: Maximum recursion depth for build_filter (AND/OR nesting).
@@ -22,6 +26,9 @@ class QueryHelperSettings(BaseModel):
22
26
  (e.g. "application.applicant.educations" = 3).
23
27
  max_in_list_size: Maximum number of elements in an IN(...) list.
24
28
  max_per_page: Maximum value for per_page in pagination.
29
+ session_factory: async_sessionmaker instance for standalone mode.
30
+ Set via ``soh.configure(factory)``.
31
+
25
32
  """
26
33
 
27
34
  max_filter_depth: int = 100
@@ -29,6 +36,7 @@ class QueryHelperSettings(BaseModel):
29
36
  max_load_depth: int = 10
30
37
  max_in_list_size: int = 1000
31
38
  max_per_page: int = 500
39
+ session_factory: Any = None
32
40
 
33
41
 
34
42
  settings = QueryHelperSettings()
@@ -42,7 +42,8 @@ class DatabaseError(QueryError):
42
42
 
43
43
 
44
44
  class MutationError(QueryError):
45
- """Raised when a create/update/delete operation fails.
45
+ """
46
+ Raised when a create/update/delete operation fails.
46
47
 
47
48
  Covers constraint violations, invalid field names in update data,
48
49
  and other mutation-specific errors.