sqla-fancy-core 1.2.0__tar.gz → 1.2.3__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.

Potentially problematic release.


This version of sqla-fancy-core might be problematic. Click here for more details.

Files changed (23) hide show
  1. {sqla_fancy_core-1.2.0 → sqla_fancy_core-1.2.3}/PKG-INFO +39 -64
  2. {sqla_fancy_core-1.2.0 → sqla_fancy_core-1.2.3}/README.md +35 -60
  3. {sqla_fancy_core-1.2.0 → sqla_fancy_core-1.2.3}/pyproject.toml +13 -4
  4. {sqla_fancy_core-1.2.0 → sqla_fancy_core-1.2.3}/sqla_fancy_core/wrappers.py +17 -3
  5. {sqla_fancy_core-1.2.0 → sqla_fancy_core-1.2.3}/tests/test_async_atomic.py +0 -1
  6. {sqla_fancy_core-1.2.0 → sqla_fancy_core-1.2.3}/tests/test_transact.py +2 -1
  7. sqla_fancy_core-1.2.3/uv.lock +1399 -0
  8. sqla_fancy_core-1.2.0/uv.lock +0 -3503
  9. {sqla_fancy_core-1.2.0 → sqla_fancy_core-1.2.3}/.github/workflows/ci.yaml +0 -0
  10. {sqla_fancy_core-1.2.0 → sqla_fancy_core-1.2.3}/.gitignore +0 -0
  11. {sqla_fancy_core-1.2.0 → sqla_fancy_core-1.2.3}/LICENSE +0 -0
  12. {sqla_fancy_core-1.2.0 → sqla_fancy_core-1.2.3}/sqla_fancy_core/__init__.py +0 -0
  13. {sqla_fancy_core-1.2.0 → sqla_fancy_core-1.2.3}/sqla_fancy_core/decorators.py +0 -0
  14. {sqla_fancy_core-1.2.0 → sqla_fancy_core-1.2.3}/sqla_fancy_core/factories.py +0 -0
  15. {sqla_fancy_core-1.2.0 → sqla_fancy_core-1.2.3}/tests/__init__.py +0 -0
  16. {sqla_fancy_core-1.2.0 → sqla_fancy_core-1.2.3}/tests/test_async_fancy_engine.py +0 -0
  17. {sqla_fancy_core-1.2.0 → sqla_fancy_core-1.2.3}/tests/test_atomic.py +0 -0
  18. {sqla_fancy_core-1.2.0 → sqla_fancy_core-1.2.3}/tests/test_connect.py +0 -0
  19. {sqla_fancy_core-1.2.0 → sqla_fancy_core-1.2.3}/tests/test_decorators.py +0 -0
  20. {sqla_fancy_core-1.2.0 → sqla_fancy_core-1.2.3}/tests/test_fancy_engine.py +0 -0
  21. {sqla_fancy_core-1.2.0 → sqla_fancy_core-1.2.3}/tests/test_field.py +0 -0
  22. {sqla_fancy_core-1.2.0 → sqla_fancy_core-1.2.3}/tests/test_table_factory.py +0 -0
  23. {sqla_fancy_core-1.2.0 → sqla_fancy_core-1.2.3}/tests/test_table_factory_async.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqla-fancy-core
3
- Version: 1.2.0
4
- Summary: SQLAlchemy core, but fancier
3
+ Version: 1.2.3
4
+ Summary: A collection of type-safe, async friendly, and un-opinionated enhancements to SQLAlchemy Core that works well with mordern web servers.
5
5
  Project-URL: Homepage, https://github.com/sayanarijit/sqla-fancy-core
6
6
  Author-email: Arijit Basu <sayanarijit@gmail.com>
7
7
  Maintainer-email: Arijit Basu <sayanarijit@gmail.com>
@@ -27,11 +27,11 @@ License: MIT License
27
27
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
28
  SOFTWARE.
29
29
  License-File: LICENSE
30
- Keywords: sql,sqlalchemy,sqlalchemy-core
30
+ Keywords: async,database,fastapi,orm,sql,sqlalchemy,type-safe,web
31
31
  Classifier: Intended Audience :: Developers
32
32
  Classifier: License :: OSI Approved :: MIT License
33
33
  Classifier: Programming Language :: Python :: 3
34
- Requires-Python: >=3.7
34
+ Requires-Python: >=3.10
35
35
  Requires-Dist: sqlalchemy
36
36
  Provides-Extra: dev
37
37
  Requires-Dist: build; extra == 'dev'
@@ -52,17 +52,27 @@ Description-Content-Type: text/markdown
52
52
 
53
53
  # sqla-fancy-core
54
54
 
55
- There are plenty of ORMs to choose from in Python world, but not many sql query makers for folks who prefer to stay close to the original SQL syntax, without sacrificing security and code readability. The closest, most mature and most flexible query maker you can find is SQLAlchemy core.
55
+ A collection of type-safe, async friendly, and un-opinionated enhancements to SQLAlchemy Core that works well with mordern web servers.
56
56
 
57
- But the syntax of defining tables and making queries has a lot of scope for improvement. For example, the `table.c.column` syntax is too dynamic, unreadable, and probably has performance impact too. It also doesn’t play along with static type checkers and linting tools.
57
+ **Why?**
58
58
 
59
- So here I present one attempt at getting the best out of SQLAlchemy core by changing the way we define tables.
59
+ - ORMs are magical, but it's not always a feature. Sometimes, we crave for familiar.
60
+ - SQLAlchemy Core is powerful but `table.c.column` breaks static type checking and has runtime overhead. This library provides a better way to define tables while keeping all of SQLAlchemy's flexibility. See [Table Factory](#table-factory).
61
+ - The idea of sessions can get feel too magical and opinionated. This library removes the magic and opinions and takes you to back to familiar transactions's territory, providing multiple un-opinionated APIs to deal with it. See [Wrappers](#fancy-engine-wrappers) and [Decorators](#decorators-inject-connect-transact).
60
62
 
61
- The table factory class it exposes, helps define tables in a way that eliminates the above drawbacks. Moreover, you can subclass it to add your preferred global defaults for columns (e.g. not null as default). Or specify custom column types with consistent naming (e.g. created_at).
63
+ **Demos:**
62
64
 
63
- ## Basic Usage
65
+ - [FastAPI - sqla-fancy-core example app](https://github.com/sayanarijit/fastapi-sqla-fancy-core-example-app).
64
66
 
65
- First, let's define a table using the `TableFactory`.
67
+ ## Table factory
68
+
69
+ Define tables with static column references## Target audience
70
+
71
+ For production use by developers who prefer query builders over ORMs, need robust sync/async support, and want type-safe, readable code.
72
+
73
+ **Example:**
74
+
75
+ Define tables:
66
76
 
67
77
  ```python
68
78
  import sqlalchemy as sa
@@ -79,7 +89,7 @@ class Author:
79
89
  Table = tf("author")
80
90
  ```
81
91
 
82
- The `TableFactory` provides a convenient way to define columns with common attributes. For more complex scenarios, you can define tables without losing type hints:
92
+ For complex scenarios, define columns explicitly:
83
93
 
84
94
  ```python
85
95
  class Book:
@@ -107,7 +117,7 @@ class Book:
107
117
  Table = tf(sa.Table("book", sa.MetaData()))
108
118
  ```
109
119
 
110
- Now, let's create an engine and the tables.
120
+ Create tables:
111
121
 
112
122
  ```python
113
123
  from sqlalchemy.ext.asyncio import create_async_engine
@@ -120,11 +130,7 @@ async with engine.begin() as conn:
120
130
  await conn.run_sync(tf.metadata.create_all)
121
131
  ```
122
132
 
123
- With the tables created, you can perform CRUD operations.
124
-
125
- ### CRUD Operations
126
-
127
- Here's how you can interact with the database using the defined tables.
133
+ Perform CRUD operations:
128
134
 
129
135
  ```python
130
136
  async with engine.begin() as txn:
@@ -158,15 +164,13 @@ async with engine.begin() as txn:
158
164
 
159
165
  ## Fancy Engine Wrappers
160
166
 
161
- `sqla-fancy-core` provides `fancy` engine wrappers that simplify database interactions by automatically managing connections and transactions. The `fancy` function wraps a SQLAlchemy `Engine` or `AsyncEngine` and returns a wrapper object with the following methods:
162
-
163
- - `x(conn, query)`: Executes a query. It uses the provided `conn` if available, otherwise it creates a new connection.
164
- - `tx(conn, query)`: Executes a query within a transaction. It uses the provided `conn` if available, otherwise it tries to use the atomic context if within one, else creates a new connection and begins a transaction.
165
- - `atomic()`: A context manager for grouping multiple operations in a single transaction scope.
166
- - `ax(query)`: Executes a query using the connection from the active `atomic()` context. Raises `AtomicContextError` if called outside an `atomic()` block.
167
- - `atx(query)`: Executes a query inside a transaction automatically. If already inside `atomic()`, it reuses the same connection and transaction; otherwise it opens a new transaction just for this call.
167
+ Simplify connection and transaction management. The `fancy()` function wraps a SQLAlchemy engine and provides:
168
168
 
169
- This is particularly useful for writing connection-agnostic query functions.
169
+ - `x(conn, query)`: Execute query with optional connection
170
+ - `tx(conn, query)`: Execute query in transaction
171
+ - `atomic()`: Context manager for transaction scope
172
+ - `ax(query)`: Execute inside `atomic()` context (raises `AtomicContextError` outside)
173
+ - `atx(query)`: Auto-transactional (reuses `atomic()` if present, or creates new transaction)
170
174
 
171
175
  ### Basic Examples
172
176
 
@@ -215,7 +219,7 @@ async def main():
215
219
 
216
220
  ### Using the atomic() Context Manager
217
221
 
218
- The `atomic()` context manager lets you group several database operations within one transactional scope. Queries executed with `ax()` inside this context all use the same connection. Nested `atomic()` contexts reuse the outer connection automatically.
222
+ Group operations in a single transaction. Nested `atomic()` contexts share the outer connection.
219
223
 
220
224
  **Sync Example:**
221
225
 
@@ -272,36 +276,17 @@ async def run_example():
272
276
  assert count == 2
273
277
  ```
274
278
 
275
- **Key Points:**
276
-
277
- - `ax()` must be called inside an `atomic()` context. Calling it elsewhere raises `AtomicContextError`.
278
- - `atx()` is a safe/ergonomic helper: it will run inside the current `atomic()` transaction when present, or create a short-lived transaction otherwise.
279
- - Nesting `atomic()` contexts is safe. Inner contexts share the outer connection instead of creating a new transaction.
280
- - On normal exit, the transaction commits automatically. On exception, it rolls back.
281
-
282
- ### ax vs atx vs tx
283
-
284
- - `ax(q)`: Only valid inside `atomic()`. Uses the ambient transactional connection. Great for batch operations grouped by an outer context.
285
- - `atx(q)`: Fire-and-forget in a transaction. Reuses the ambient `atomic()` connection if present; otherwise starts and commits its own transaction.
286
- - `tx(conn, q)`: Low-level primitive. If `conn` is provided, it executes within it, creating a transaction when needed; if `None`, it prefers the `atomic()` connection when active or opens a new transactional connection.
287
-
288
279
  ## Decorators: Inject, connect, transact
289
280
 
290
- When writing plain SQLAlchemy Core code, you often pass connections around and manage transactions manually. The decorators in `sqla-fancy-core` help you keep functions connection-agnostic and composable, while remaining explicit and safe.
291
-
292
- At the heart of it is `Inject(engine)`, a tiny marker used as a default parameter value to tell decorators where to inject a connection.
293
-
294
- - `Inject(engine)`: marks which parameter should receive a connection derived from the given engine.
295
- - `@connect`: ensures the injected parameter is a live connection. If you passed a connection explicitly, it will use that one as-is. Otherwise, it will open a new connection for the call and close it afterwards. No transaction is created by default.
296
- - `@transact`: ensures the injected parameter is inside a transaction. If you pass a connection already in a transaction, it reuses it; if you pass a connection outside a transaction, it starts one; if you pass nothing, it opens a new connection and begins a transaction for the duration of the call.
281
+ Keep functions connection-agnostic with decorator-based injection.
297
282
 
298
- All three work both for sync and async engines. The signatures remain the same — you only change the default value to `Inject(engine)`.
283
+ **Components:**
299
284
 
300
- ### Quick reference
285
+ - `Inject(engine)`: Marks parameter for connection injection
286
+ - `@connect`: Ensures live connection (no transaction by default)
287
+ - `@transact`: Ensures transactional connection
301
288
 
302
- - Prefer `@connect` for read-only operations or when you want to control commit/rollback yourself.
303
- - Prefer `@transact` to wrap a function in a transaction automatically and consistently.
304
- - You can still pass `conn=...` explicitly to either decorator to reuse an existing connection/transaction.
289
+ Use `@connect` for read-only operations. Use `@transact` for writes.
305
290
 
306
291
  ### Sync examples
307
292
 
@@ -378,11 +363,7 @@ async with engine.connect() as conn:
378
363
  assert await get_user_count(conn=conn) == 2
379
364
  ```
380
365
 
381
- ### Works with dependency injection frameworks
382
-
383
- These decorators pair nicely with frameworks like FastAPI. You can keep a single function that works both inside DI (with an injected connection) and outside it (self-managed).
384
-
385
- Sync example with FastAPI:
366
+ Works with dependency injection frameworks like FastAPI:
386
367
 
387
368
  ```python
388
369
  from typing import Annotated
@@ -408,7 +389,7 @@ def create_user(
408
389
  create_user(name="outside fastapi")
409
390
  ```
410
391
 
411
- Async example with FastAPI:
392
+ Async with FastAPI:
412
393
 
413
394
  ```python
414
395
  from typing import Annotated
@@ -432,15 +413,9 @@ async def create_user(
432
413
  await conn.execute(sa.insert(users).values(name=name))
433
414
  ```
434
415
 
435
- Notes:
436
-
437
- - `@connect` never starts a transaction by itself; `@transact` ensures one.
438
- - Passing an explicit `conn` always wins — the decorators simply adapt to what you give them.
439
- - The injection marker keeps your function signatures clean and type-checker friendly.
440
-
441
416
  ## With Pydantic Validation
442
417
 
443
- You can integrate `sqla-fancy-core` with Pydantic for data validation.
418
+ Integrate with Pydantic for validation:
444
419
 
445
420
  ```python
446
421
  from typing import Any
@@ -1,16 +1,26 @@
1
1
  # sqla-fancy-core
2
2
 
3
- There are plenty of ORMs to choose from in Python world, but not many sql query makers for folks who prefer to stay close to the original SQL syntax, without sacrificing security and code readability. The closest, most mature and most flexible query maker you can find is SQLAlchemy core.
3
+ A collection of type-safe, async friendly, and un-opinionated enhancements to SQLAlchemy Core that works well with mordern web servers.
4
4
 
5
- But the syntax of defining tables and making queries has a lot of scope for improvement. For example, the `table.c.column` syntax is too dynamic, unreadable, and probably has performance impact too. It also doesn’t play along with static type checkers and linting tools.
5
+ **Why?**
6
6
 
7
- So here I present one attempt at getting the best out of SQLAlchemy core by changing the way we define tables.
7
+ - ORMs are magical, but it's not always a feature. Sometimes, we crave for familiar.
8
+ - SQLAlchemy Core is powerful but `table.c.column` breaks static type checking and has runtime overhead. This library provides a better way to define tables while keeping all of SQLAlchemy's flexibility. See [Table Factory](#table-factory).
9
+ - The idea of sessions can get feel too magical and opinionated. This library removes the magic and opinions and takes you to back to familiar transactions's territory, providing multiple un-opinionated APIs to deal with it. See [Wrappers](#fancy-engine-wrappers) and [Decorators](#decorators-inject-connect-transact).
8
10
 
9
- The table factory class it exposes, helps define tables in a way that eliminates the above drawbacks. Moreover, you can subclass it to add your preferred global defaults for columns (e.g. not null as default). Or specify custom column types with consistent naming (e.g. created_at).
11
+ **Demos:**
10
12
 
11
- ## Basic Usage
13
+ - [FastAPI - sqla-fancy-core example app](https://github.com/sayanarijit/fastapi-sqla-fancy-core-example-app).
12
14
 
13
- First, let's define a table using the `TableFactory`.
15
+ ## Table factory
16
+
17
+ Define tables with static column references## Target audience
18
+
19
+ For production use by developers who prefer query builders over ORMs, need robust sync/async support, and want type-safe, readable code.
20
+
21
+ **Example:**
22
+
23
+ Define tables:
14
24
 
15
25
  ```python
16
26
  import sqlalchemy as sa
@@ -27,7 +37,7 @@ class Author:
27
37
  Table = tf("author")
28
38
  ```
29
39
 
30
- The `TableFactory` provides a convenient way to define columns with common attributes. For more complex scenarios, you can define tables without losing type hints:
40
+ For complex scenarios, define columns explicitly:
31
41
 
32
42
  ```python
33
43
  class Book:
@@ -55,7 +65,7 @@ class Book:
55
65
  Table = tf(sa.Table("book", sa.MetaData()))
56
66
  ```
57
67
 
58
- Now, let's create an engine and the tables.
68
+ Create tables:
59
69
 
60
70
  ```python
61
71
  from sqlalchemy.ext.asyncio import create_async_engine
@@ -68,11 +78,7 @@ async with engine.begin() as conn:
68
78
  await conn.run_sync(tf.metadata.create_all)
69
79
  ```
70
80
 
71
- With the tables created, you can perform CRUD operations.
72
-
73
- ### CRUD Operations
74
-
75
- Here's how you can interact with the database using the defined tables.
81
+ Perform CRUD operations:
76
82
 
77
83
  ```python
78
84
  async with engine.begin() as txn:
@@ -106,15 +112,13 @@ async with engine.begin() as txn:
106
112
 
107
113
  ## Fancy Engine Wrappers
108
114
 
109
- `sqla-fancy-core` provides `fancy` engine wrappers that simplify database interactions by automatically managing connections and transactions. The `fancy` function wraps a SQLAlchemy `Engine` or `AsyncEngine` and returns a wrapper object with the following methods:
110
-
111
- - `x(conn, query)`: Executes a query. It uses the provided `conn` if available, otherwise it creates a new connection.
112
- - `tx(conn, query)`: Executes a query within a transaction. It uses the provided `conn` if available, otherwise it tries to use the atomic context if within one, else creates a new connection and begins a transaction.
113
- - `atomic()`: A context manager for grouping multiple operations in a single transaction scope.
114
- - `ax(query)`: Executes a query using the connection from the active `atomic()` context. Raises `AtomicContextError` if called outside an `atomic()` block.
115
- - `atx(query)`: Executes a query inside a transaction automatically. If already inside `atomic()`, it reuses the same connection and transaction; otherwise it opens a new transaction just for this call.
115
+ Simplify connection and transaction management. The `fancy()` function wraps a SQLAlchemy engine and provides:
116
116
 
117
- This is particularly useful for writing connection-agnostic query functions.
117
+ - `x(conn, query)`: Execute query with optional connection
118
+ - `tx(conn, query)`: Execute query in transaction
119
+ - `atomic()`: Context manager for transaction scope
120
+ - `ax(query)`: Execute inside `atomic()` context (raises `AtomicContextError` outside)
121
+ - `atx(query)`: Auto-transactional (reuses `atomic()` if present, or creates new transaction)
118
122
 
119
123
  ### Basic Examples
120
124
 
@@ -163,7 +167,7 @@ async def main():
163
167
 
164
168
  ### Using the atomic() Context Manager
165
169
 
166
- The `atomic()` context manager lets you group several database operations within one transactional scope. Queries executed with `ax()` inside this context all use the same connection. Nested `atomic()` contexts reuse the outer connection automatically.
170
+ Group operations in a single transaction. Nested `atomic()` contexts share the outer connection.
167
171
 
168
172
  **Sync Example:**
169
173
 
@@ -220,36 +224,17 @@ async def run_example():
220
224
  assert count == 2
221
225
  ```
222
226
 
223
- **Key Points:**
224
-
225
- - `ax()` must be called inside an `atomic()` context. Calling it elsewhere raises `AtomicContextError`.
226
- - `atx()` is a safe/ergonomic helper: it will run inside the current `atomic()` transaction when present, or create a short-lived transaction otherwise.
227
- - Nesting `atomic()` contexts is safe. Inner contexts share the outer connection instead of creating a new transaction.
228
- - On normal exit, the transaction commits automatically. On exception, it rolls back.
229
-
230
- ### ax vs atx vs tx
231
-
232
- - `ax(q)`: Only valid inside `atomic()`. Uses the ambient transactional connection. Great for batch operations grouped by an outer context.
233
- - `atx(q)`: Fire-and-forget in a transaction. Reuses the ambient `atomic()` connection if present; otherwise starts and commits its own transaction.
234
- - `tx(conn, q)`: Low-level primitive. If `conn` is provided, it executes within it, creating a transaction when needed; if `None`, it prefers the `atomic()` connection when active or opens a new transactional connection.
235
-
236
227
  ## Decorators: Inject, connect, transact
237
228
 
238
- When writing plain SQLAlchemy Core code, you often pass connections around and manage transactions manually. The decorators in `sqla-fancy-core` help you keep functions connection-agnostic and composable, while remaining explicit and safe.
239
-
240
- At the heart of it is `Inject(engine)`, a tiny marker used as a default parameter value to tell decorators where to inject a connection.
241
-
242
- - `Inject(engine)`: marks which parameter should receive a connection derived from the given engine.
243
- - `@connect`: ensures the injected parameter is a live connection. If you passed a connection explicitly, it will use that one as-is. Otherwise, it will open a new connection for the call and close it afterwards. No transaction is created by default.
244
- - `@transact`: ensures the injected parameter is inside a transaction. If you pass a connection already in a transaction, it reuses it; if you pass a connection outside a transaction, it starts one; if you pass nothing, it opens a new connection and begins a transaction for the duration of the call.
229
+ Keep functions connection-agnostic with decorator-based injection.
245
230
 
246
- All three work both for sync and async engines. The signatures remain the same — you only change the default value to `Inject(engine)`.
231
+ **Components:**
247
232
 
248
- ### Quick reference
233
+ - `Inject(engine)`: Marks parameter for connection injection
234
+ - `@connect`: Ensures live connection (no transaction by default)
235
+ - `@transact`: Ensures transactional connection
249
236
 
250
- - Prefer `@connect` for read-only operations or when you want to control commit/rollback yourself.
251
- - Prefer `@transact` to wrap a function in a transaction automatically and consistently.
252
- - You can still pass `conn=...` explicitly to either decorator to reuse an existing connection/transaction.
237
+ Use `@connect` for read-only operations. Use `@transact` for writes.
253
238
 
254
239
  ### Sync examples
255
240
 
@@ -326,11 +311,7 @@ async with engine.connect() as conn:
326
311
  assert await get_user_count(conn=conn) == 2
327
312
  ```
328
313
 
329
- ### Works with dependency injection frameworks
330
-
331
- These decorators pair nicely with frameworks like FastAPI. You can keep a single function that works both inside DI (with an injected connection) and outside it (self-managed).
332
-
333
- Sync example with FastAPI:
314
+ Works with dependency injection frameworks like FastAPI:
334
315
 
335
316
  ```python
336
317
  from typing import Annotated
@@ -356,7 +337,7 @@ def create_user(
356
337
  create_user(name="outside fastapi")
357
338
  ```
358
339
 
359
- Async example with FastAPI:
340
+ Async with FastAPI:
360
341
 
361
342
  ```python
362
343
  from typing import Annotated
@@ -380,15 +361,9 @@ async def create_user(
380
361
  await conn.execute(sa.insert(users).values(name=name))
381
362
  ```
382
363
 
383
- Notes:
384
-
385
- - `@connect` never starts a transaction by itself; `@transact` ensures one.
386
- - Passing an explicit `conn` always wins — the decorators simply adapt to what you give them.
387
- - The injection marker keeps your function signatures clean and type-checker friendly.
388
-
389
364
  ## With Pydantic Validation
390
365
 
391
- You can integrate `sqla-fancy-core` with Pydantic for data validation.
366
+ Integrate with Pydantic for validation:
392
367
 
393
368
  ```python
394
369
  from typing import Any
@@ -1,11 +1,20 @@
1
1
  [project]
2
2
  name = 'sqla-fancy-core'
3
- version = '1.2.0'
4
- description = 'SQLAlchemy core, but fancier'
3
+ version = '1.2.3'
4
+ description = 'A collection of type-safe, async friendly, and un-opinionated enhancements to SQLAlchemy Core that works well with mordern web servers.'
5
5
  readme = 'README.md'
6
- requires-python = ">=3.7"
6
+ requires-python = ">=3.10"
7
7
  license = { file = "LICENSE" }
8
- keywords = ["sql", "sqlalchemy", "sqlalchemy-core"]
8
+ keywords = [
9
+ "sql",
10
+ "sqlalchemy",
11
+ "database",
12
+ "async",
13
+ "type-safe",
14
+ "orm",
15
+ "web",
16
+ "fastapi",
17
+ ]
9
18
  authors = [{ name = "Arijit Basu", email = "sayanarijit@gmail.com" }]
10
19
  maintainers = [{ name = "Arijit Basu", email = "sayanarijit@gmail.com" }]
11
20
  classifiers = [
@@ -206,10 +206,17 @@ class FancyEngineWrapper:
206
206
  execution_options: Optional[CoreExecuteOptionsParameter] = None,
207
207
  ) -> CursorResult[Any]:
208
208
  """If within an atomic context, execute the query there; else, create a new transaction."""
209
- with self.atomic() as connection:
210
- return connection.execute(
209
+
210
+ conn = self._ATOMIC_TX_CONN.get()
211
+ if conn:
212
+ return conn.execute(
211
213
  statement, parameters, execution_options=execution_options
212
214
  )
215
+ else:
216
+ with self.engine.begin() as conn:
217
+ return conn.execute(
218
+ statement, parameters, execution_options=execution_options
219
+ )
213
220
 
214
221
 
215
222
  class AsyncFancyEngineWrapper:
@@ -383,10 +390,17 @@ class AsyncFancyEngineWrapper:
383
390
  execution_options: Optional[CoreExecuteOptionsParameter] = None,
384
391
  ) -> CursorResult[Any]:
385
392
  """If within an atomic context, execute the query there; else, create a new transaction."""
386
- async with self.atomic() as connection:
393
+
394
+ connection = self._ATOMIC_TX_CONN.get()
395
+ if connection:
387
396
  return await connection.execute(
388
397
  statement, parameters, execution_options=execution_options
389
398
  )
399
+ else:
400
+ async with self.engine.begin() as connection:
401
+ return await connection.execute(
402
+ statement, parameters, execution_options=execution_options
403
+ )
390
404
 
391
405
 
392
406
  @overload
@@ -6,7 +6,6 @@ from sqlalchemy.ext.asyncio import create_async_engine
6
6
  from sqla_fancy_core import TableFactory, fancy
7
7
  from sqla_fancy_core.wrappers import AtomicContextError
8
8
 
9
-
10
9
  tf = TableFactory()
11
10
 
12
11
 
@@ -5,6 +5,7 @@ import pytest_asyncio
5
5
  import sqlalchemy as sa
6
6
  from fastapi import Form
7
7
  from sqlalchemy.ext.asyncio import AsyncConnection, create_async_engine
8
+ from sqlalchemy.pool import StaticPool
8
9
 
9
10
  from sqla_fancy_core.decorators import Inject, transact
10
11
 
@@ -24,7 +25,7 @@ def sync_engine():
24
25
  engine = sa.create_engine(
25
26
  "sqlite+pysqlite:///:memory:",
26
27
  connect_args={"check_same_thread": False},
27
- poolclass=sa.pool.StaticPool,
28
+ poolclass=StaticPool,
28
29
  )
29
30
  metadata.create_all(engine)
30
31
  yield engine