FastSQLA 0.3.0__py3-none-any.whl → 0.4.4__py3-none-any.whl

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.
@@ -1,9 +1,8 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: FastSQLA
3
- Version: 0.3.0
3
+ Version: 0.4.4
4
4
  Summary: SQLAlchemy extension for FastAPI that supports asynchronous sessions and includes built-in pagination.
5
- Author-email: Hadrien David <bonjour@hadriendavid.com>
6
- License: MIT License
5
+ Author-email: Hadrien David <h@driendavid.com>
7
6
  Project-URL: Homepage, https://github.com/hadrien/fastsqla
8
7
  Project-URL: Documentation, https://github.com/hadrien/fastsqla
9
8
  Project-URL: Repository, https://github.com/hadrien/fastsqla
@@ -19,7 +18,6 @@ Classifier: Framework :: Pydantic
19
18
  Classifier: Intended Audience :: Developers
20
19
  Classifier: Intended Audience :: Information Technology
21
20
  Classifier: Intended Audience :: System Administrators
22
- Classifier: License :: OSI Approved :: MIT License
23
21
  Classifier: Operating System :: OS Independent
24
22
  Classifier: Programming Language :: Python :: 3 :: Only
25
23
  Classifier: Programming Language :: Python :: 3.12
@@ -36,12 +34,12 @@ Classifier: Topic :: Software Development
36
34
  Classifier: Typing :: Typed
37
35
  Requires-Python: >=3.12
38
36
  Description-Content-Type: text/markdown
39
- License-File: LICENSE
40
37
  Requires-Dist: fastapi>=0.115.6
41
38
  Requires-Dist: sqlalchemy[asyncio]>=2.0.37
42
39
  Requires-Dist: structlog>=24.4.0
43
40
  Provides-Extra: docs
44
41
  Requires-Dist: mkdocs-glightbox>=0.4.0; extra == "docs"
42
+ Requires-Dist: mkdocs-llmstxt>=0.2.0; extra == "docs"
45
43
  Requires-Dist: mkdocs-material>=9.5.50; extra == "docs"
46
44
  Requires-Dist: mkdocstrings[python]>=0.27.0; extra == "docs"
47
45
  Provides-Extra: sqlmodel
@@ -0,0 +1,5 @@
1
+ fastsqla.py,sha256=wO4KpgkA3jVOCtpbwORlaUuOu4YsEQoRslzg9wjDNBk,12364
2
+ fastsqla-0.4.4.dist-info/METADATA,sha256=lyIlPmzzZ99nG1uKAmrpi1lH7Zpclod8LUTmNj0OOuo,12425
3
+ fastsqla-0.4.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
4
+ fastsqla-0.4.4.dist-info/top_level.txt,sha256=Uh-1ssTtuSS4_SYCBeDoDVOxqWTrRAPEBZkuih5isSE,9
5
+ fastsqla-0.4.4.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
fastsqla.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import math
2
2
  import os
3
3
  from collections.abc import AsyncGenerator, Awaitable, Callable, Iterable
4
- from contextlib import asynccontextmanager
4
+ from contextlib import _AsyncGeneratorContextManager, asynccontextmanager
5
5
  from typing import Annotated, Generic, TypeVar, TypedDict
6
6
 
7
7
  from fastapi import Depends, FastAPI, Query
@@ -78,79 +78,118 @@ class State(TypedDict):
78
78
  fastsqla_engine: AsyncEngine
79
79
 
80
80
 
81
- @asynccontextmanager
82
- async def lifespan(app: FastAPI) -> AsyncGenerator[State, None]:
83
- """Use `fastsqla.lifespan` to set up SQLAlchemy.
84
-
85
- In an ASGI application, [lifespan events](https://asgi.readthedocs.io/en/latest/specs/lifespan.html)
86
- are used to communicate startup & shutdown events.
81
+ def new_lifespan(
82
+ url: str | None = None, **kw
83
+ ) -> Callable[[FastAPI], _AsyncGeneratorContextManager[State, None]]:
84
+ """Create a new lifespan async context manager.
87
85
 
88
- The [`lifespan`](https://fastapi.tiangolo.com/advanced/events/#lifespan) parameter of
89
- the `FastAPI` app can be assigned to a context manager, which is opened when the app
90
- starts and closed when the app stops.
86
+ It expects the exact same parameters as
87
+ [`sqlalchemy.ext.asyncio.create_async_engine`][sqlalchemy.ext.asyncio.create_async_engine]
91
88
 
92
- In order for `FastSQLA` to setup `SQLAlchemy` before the app is started, set
93
- `lifespan` parameter to `fastsqla.lifespan`:
89
+ Example:
94
90
 
95
91
  ```python
96
92
  from fastapi import FastAPI
97
- from fastsqla import lifespan
93
+ from fastsqla import new_lifespan
98
94
 
95
+ lifespan = new_lifespan(
96
+ "sqlite+aiosqlite:///app/db.sqlite", connect_args={"autocommit": False}
97
+ )
99
98
 
100
99
  app = FastAPI(lifespan=lifespan)
101
100
  ```
102
101
 
103
- If multiple lifespan contexts are required, create an async context manager function
104
- to handle them and set it as the app's lifespan:
102
+ Args:
103
+ url (str): Database url.
104
+ kw (dict): Configuration parameters as expected by [`sqlalchemy.ext.asyncio.create_async_engine`][sqlalchemy.ext.asyncio.create_async_engine]
105
+ """
105
106
 
106
- ```python
107
- from collections.abc import AsyncGenerator
108
- from contextlib import asynccontextmanager
107
+ has_config = url is not None
109
108
 
110
- from fastapi import FastAPI
111
- from fastsqla import lifespan as fastsqla_lifespan
112
- from this_other_library import another_lifespan
109
+ @asynccontextmanager
110
+ async def lifespan(app: FastAPI) -> AsyncGenerator[State, None]:
111
+ if has_config:
112
+ prefix = ""
113
+ sqla_config = {**kw, **{"url": url}}
113
114
 
115
+ else:
116
+ prefix = "sqlalchemy_"
117
+ sqla_config = {k.lower(): v for k, v in os.environ.items()}
114
118
 
115
- @asynccontextmanager
116
- async def lifespan(app:FastAPI) -> AsyncGenerator[dict, None]:
117
- async with AsyncExitStack() as stack:
118
- yield {
119
- **stack.enter_async_context(lifespan(app)),
120
- **stack.enter_async_context(another_lifespan(app)),
121
- }
119
+ try:
120
+ engine = async_engine_from_config(sqla_config, prefix=prefix)
122
121
 
122
+ except KeyError as exc:
123
+ raise Exception(f"Missing {prefix}{exc.args[0]} in environ.") from exc
123
124
 
124
- app = FastAPI(lifespan=lifespan)
125
- ```
125
+ async with engine.begin() as conn:
126
+ await conn.run_sync(Base.prepare)
126
127
 
127
- To learn more about lifespan protocol:
128
+ SessionFactory.configure(bind=engine)
128
129
 
129
- * [Lifespan Protocol](https://asgi.readthedocs.io/en/latest/specs/lifespan.html)
130
- * [Use Lifespan State instead of `app.state`](https://github.com/Kludex/fastapi-tips?tab=readme-ov-file#6-use-lifespan-state-instead-of-appstate)
131
- * [FastAPI lifespan documentation](https://fastapi.tiangolo.com/advanced/events/)
132
- """
133
- prefix = "sqlalchemy_"
134
- sqla_config = {k.lower(): v for k, v in os.environ.items()}
135
- try:
136
- engine = async_engine_from_config(sqla_config, prefix=prefix)
130
+ await logger.ainfo("Configured SQLAlchemy.")
137
131
 
138
- except KeyError as exc:
139
- raise Exception(f"Missing {prefix}{exc.args[0]} in environ.") from exc
132
+ yield {"fastsqla_engine": engine}
140
133
 
141
- async with engine.begin() as conn:
142
- await conn.run_sync(Base.prepare)
134
+ SessionFactory.configure(bind=None)
135
+ await engine.dispose()
143
136
 
144
- SessionFactory.configure(bind=engine)
137
+ await logger.ainfo("Cleared SQLAlchemy config.")
145
138
 
146
- await logger.ainfo("Configured SQLAlchemy.")
139
+ return lifespan
147
140
 
148
- yield {"fastsqla_engine": engine}
149
141
 
150
- SessionFactory.configure(bind=None)
151
- await engine.dispose()
142
+ lifespan = new_lifespan()
143
+ """Use `fastsqla.lifespan` to set up SQLAlchemy directly from environment variables.
152
144
 
153
- await logger.ainfo("Cleared SQLAlchemy config.")
145
+ In an ASGI application, [lifespan events](https://asgi.readthedocs.io/en/latest/specs/lifespan.html)
146
+ are used to communicate startup & shutdown events.
147
+
148
+ The [`lifespan`](https://fastapi.tiangolo.com/advanced/events/#lifespan) parameter of
149
+ the `FastAPI` app can be assigned to a context manager, which is opened when the app
150
+ starts and closed when the app stops.
151
+
152
+ In order for `FastSQLA` to setup `SQLAlchemy` before the app is started, set
153
+ `lifespan` parameter to `fastsqla.lifespan`:
154
+
155
+ ```python
156
+ from fastapi import FastAPI
157
+ from fastsqla import lifespan
158
+
159
+
160
+ app = FastAPI(lifespan=lifespan)
161
+ ```
162
+
163
+ If multiple lifespan contexts are required, create an async context manager function
164
+ to handle them and set it as the app's lifespan:
165
+
166
+ ```python
167
+ from collections.abc import AsyncGenerator
168
+ from contextlib import asynccontextmanager
169
+
170
+ from fastapi import FastAPI
171
+ from fastsqla import lifespan as fastsqla_lifespan
172
+ from this_other_library import another_lifespan
173
+
174
+
175
+ @asynccontextmanager
176
+ async def lifespan(app:FastAPI) -> AsyncGenerator[dict, None]:
177
+ async with AsyncExitStack() as stack:
178
+ yield {
179
+ **stack.enter_async_context(lifespan(app)),
180
+ **stack.enter_async_context(another_lifespan(app)),
181
+ }
182
+
183
+
184
+ app = FastAPI(lifespan=lifespan)
185
+ ```
186
+
187
+ To learn more about lifespan protocol:
188
+
189
+ * [Lifespan Protocol](https://asgi.readthedocs.io/en/latest/specs/lifespan.html)
190
+ * [Use Lifespan State instead of `app.state`](https://github.com/Kludex/fastapi-tips?tab=readme-ov-file#6-use-lifespan-state-instead-of-appstate)
191
+ * [FastAPI lifespan documentation](https://fastapi.tiangolo.com/advanced/events/)
192
+ """
154
193
 
155
194
 
156
195
  @asynccontextmanager
@@ -225,12 +264,15 @@ At the end of request handling:
225
264
 
226
265
  * If no exceptions are raised, the session is automatically committed.
227
266
  * If an exception is raised, the session is automatically rolled back.
228
- * In alls cases, the session is closed and the associated connection is returned to the
267
+ * In all cases, the session is closed and the associated connection is returned to the
229
268
  connection pool.
230
269
 
231
270
  Example:
232
271
 
233
- ``` py title="example.py" hl_lines="3"
272
+ ``` py title="example.py" hl_lines="6"
273
+ from fastsqla import Item, Session
274
+ ...
275
+
234
276
  @app.get("/heros/{hero_id}", response_model=Item[HeroItem])
235
277
  async def get_items(
236
278
  session: Session, # (1)!
@@ -252,6 +294,10 @@ If you need data generated by the database server, such as auto-incremented IDs,
252
294
  the session instead:
253
295
 
254
296
  ```python
297
+ from fastsqla import Item, Session
298
+ ...
299
+
300
+
255
301
  @app.post("/heros", response_model=Item[HeroItem])
256
302
  async def create_item(session: Session, new_hero: HeroBase):
257
303
  hero = Hero(**new_hero.model_dump())
@@ -283,6 +329,23 @@ class Collection(BaseModel, Generic[T]):
283
329
 
284
330
 
285
331
  class Page(Collection[T]):
332
+ """Generic container that contains collection data and page metadata.
333
+
334
+ The `Page` model is used to return paginated data in paginated endpoints:
335
+
336
+ ```json
337
+ {
338
+ "data": list[T],
339
+ "meta": {
340
+ "offset": int,
341
+ "total_items": int,
342
+ "total_pages": int,
343
+ "page_number": int,
344
+ }
345
+ }
346
+ ```
347
+ """
348
+
286
349
  meta: Meta
287
350
 
288
351
 
@@ -360,18 +423,5 @@ Paginate = Annotated[PaginateType[T], Depends(new_pagination())]
360
423
  """A dependency used in endpoints to paginate `SQLAlchemy` select queries.
361
424
 
362
425
  It adds **`offset`** and **`limit`** query parameters to the endpoint, which are used to
363
- paginate. The model returned by the endpoint is a `Page` model. It contains a page of
364
- data and metadata:
365
-
366
- ```json
367
- {
368
- "data": List[T],
369
- "meta": {
370
- "offset": int,
371
- "total_items": int,
372
- "total_pages": int,
373
- "page_number": int,
374
- }
375
- }
376
- ```
426
+ paginate. The model returned by the endpoint is a [`Page`][fastsqla.Page] model.
377
427
  """
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 Hadrien David
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,6 +0,0 @@
1
- fastsqla.py,sha256=yxfc96izBhaT8zobtm74OEigO5rfeCfmbRGZ5-jN3EM,11131
2
- FastSQLA-0.3.0.dist-info/LICENSE,sha256=uNKcyfhTq0YUZxgSDiDGBHoJfflKjGWecSfWxpYe_O4,1070
3
- FastSQLA-0.3.0.dist-info/METADATA,sha256=JuDDHA6vy8mzszl2C7iIqcPZvl8vLmQdNIPIKEMa5EM,12473
4
- FastSQLA-0.3.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
5
- FastSQLA-0.3.0.dist-info/top_level.txt,sha256=Uh-1ssTtuSS4_SYCBeDoDVOxqWTrRAPEBZkuih5isSE,9
6
- FastSQLA-0.3.0.dist-info/RECORD,,