fastapi-sqla 3.0.2__py3-none-any.whl → 3.1.2__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.
fastapi_sqla/__init__.py CHANGED
@@ -1,7 +1,13 @@
1
1
  from fastapi_sqla.base import setup
2
- from fastapi_sqla.models import Base, Collection, Item, Page
2
+ from fastapi_sqla.models import Collection, Item, Page
3
3
  from fastapi_sqla.pagination import Paginate, PaginateSignature, Pagination
4
- from fastapi_sqla.sqla import Session, SessionDependency, SqlaSession, open_session
4
+ from fastapi_sqla.sqla import (
5
+ Base,
6
+ Session,
7
+ SessionDependency,
8
+ SqlaSession,
9
+ open_session,
10
+ )
5
11
 
6
12
  __all__ = [
7
13
  "Base",
@@ -6,7 +6,7 @@ from fastapi import Depends, Query
6
6
  from sqlalchemy.sql import Select, func, select
7
7
 
8
8
  from fastapi_sqla.async_sqla import AsyncSessionDependency, SqlaAsyncSession
9
- from fastapi_sqla.models import Page
9
+ from fastapi_sqla.models import Meta, Page
10
10
  from fastapi_sqla.sqla import _DEFAULT_SESSION_KEY
11
11
 
12
12
  QueryCountDependency = Callable[..., Awaitable[int]]
@@ -33,20 +33,20 @@ async def paginate_query(
33
33
  scalars: bool = True,
34
34
  ) -> Page:
35
35
  total_pages = math.ceil(total_items / limit)
36
- page_number = offset / limit + 1
36
+ page_number = math.floor(offset / limit + 1)
37
37
  query = query.offset(offset).limit(limit)
38
38
  result = await session.execute(query)
39
39
  data = iter(
40
40
  cast(Iterator, result.unique().scalars() if scalars else result.mappings())
41
41
  )
42
42
  return Page(
43
- data=data,
44
- meta={
45
- "offset": offset,
46
- "total_items": total_items,
47
- "total_pages": total_pages,
48
- "page_number": page_number,
49
- },
43
+ data=data, # type: ignore # Expected to be a list
44
+ meta=Meta(
45
+ offset=offset,
46
+ total_items=total_items,
47
+ total_pages=total_pages,
48
+ page_number=page_number,
49
+ ),
50
50
  )
51
51
 
52
52
 
@@ -11,8 +11,7 @@ from sqlalchemy.ext.asyncio import AsyncSession as SqlaAsyncSession
11
11
  from sqlalchemy.orm.session import sessionmaker
12
12
 
13
13
  from fastapi_sqla import aws_aurora_support, aws_rds_iam_support
14
- from fastapi_sqla.models import Base
15
- from fastapi_sqla.sqla import _DEFAULT_SESSION_KEY, new_engine
14
+ from fastapi_sqla.sqla import _DEFAULT_SESSION_KEY, Base, new_engine
16
15
 
17
16
  logger = structlog.get_logger(__name__)
18
17
 
fastapi_sqla/models.py CHANGED
@@ -2,14 +2,6 @@ from typing import Generic, TypeVar
2
2
 
3
3
  from pydantic import BaseModel, Field
4
4
  from pydantic import __version__ as pydantic_version
5
- from sqlalchemy.ext.declarative import DeferredReflection
6
-
7
- try:
8
- from sqlalchemy.orm import DeclarativeBase
9
- except ImportError:
10
- from sqlalchemy.ext.declarative import declarative_base
11
-
12
- DeclarativeBase = declarative_base() # type: ignore
13
5
 
14
6
  major, _, _ = [int(v) for v in pydantic_version.split(".")]
15
7
  is_pydantic2 = major == 2
@@ -19,10 +11,6 @@ else:
19
11
  from pydantic.generics import GenericModel # type:ignore
20
12
 
21
13
 
22
- class Base(DeclarativeBase, DeferredReflection):
23
- __abstract__ = True
24
-
25
-
26
14
  ItemT = TypeVar("ItemT")
27
15
 
28
16
 
@@ -7,7 +7,7 @@ from fastapi import Depends, Query
7
7
  from sqlalchemy.orm import Query as LegacyQuery
8
8
  from sqlalchemy.sql import Select, func, select
9
9
 
10
- from fastapi_sqla.models import Page
10
+ from fastapi_sqla.models import Meta, Page
11
11
  from fastapi_sqla.sqla import _DEFAULT_SESSION_KEY, SessionDependency, SqlaSession
12
12
 
13
13
  DbQuery = Union[LegacyQuery, Select]
@@ -66,15 +66,15 @@ def _paginate_legacy(
66
66
  scalars: bool = True,
67
67
  ) -> Page:
68
68
  total_pages = math.ceil(total_items / limit)
69
- page_number = offset / limit + 1
69
+ page_number = math.floor(offset / limit + 1)
70
70
  return Page(
71
71
  data=query.offset(offset).limit(limit).all(),
72
- meta={
73
- "offset": offset,
74
- "total_items": total_items,
75
- "total_pages": total_pages,
76
- "page_number": page_number,
77
- },
72
+ meta=Meta(
73
+ offset=offset,
74
+ total_items=total_items,
75
+ total_pages=total_pages,
76
+ page_number=page_number,
77
+ ),
78
78
  )
79
79
 
80
80
 
@@ -89,20 +89,20 @@ def _paginate(
89
89
  scalars: bool = True,
90
90
  ) -> Page:
91
91
  total_pages = math.ceil(total_items / limit)
92
- page_number = offset / limit + 1
92
+ page_number = math.floor(offset / limit + 1)
93
93
  query = query.offset(offset).limit(limit)
94
94
  result = session.execute(query)
95
95
  data = iter(
96
96
  cast(Iterator, result.unique().scalars() if scalars else result.mappings())
97
97
  )
98
98
  return Page(
99
- data=data,
100
- meta={
101
- "offset": offset,
102
- "total_items": total_items,
103
- "total_pages": total_pages,
104
- "page_number": page_number,
105
- },
99
+ data=data, # type: ignore # Expected to be a list
100
+ meta=Meta(
101
+ offset=offset,
102
+ total_items=total_items,
103
+ total_pages=total_pages,
104
+ page_number=page_number,
105
+ ),
106
106
  )
107
107
 
108
108
 
fastapi_sqla/sqla.py CHANGED
@@ -10,11 +10,25 @@ from fastapi.concurrency import contextmanager_in_threadpool
10
10
  from fastapi.responses import PlainTextResponse
11
11
  from sqlalchemy import engine_from_config, text
12
12
  from sqlalchemy.engine import Engine
13
+ from sqlalchemy.ext.declarative import DeferredReflection
13
14
  from sqlalchemy.orm.session import Session as SqlaSession
14
15
  from sqlalchemy.orm.session import sessionmaker
15
16
 
16
17
  from fastapi_sqla import aws_aurora_support, aws_rds_iam_support
17
- from fastapi_sqla.models import Base
18
+
19
+ try:
20
+ from sqlalchemy.orm import DeclarativeBase
21
+ except ImportError:
22
+ from sqlalchemy.ext.declarative import declarative_base
23
+
24
+ DeclarativeBase = declarative_base() # type: ignore
25
+
26
+ try:
27
+ from sqlmodel import Session as SqlaSession # type: ignore # noqa
28
+
29
+ except ImportError:
30
+ pass
31
+
18
32
 
19
33
  logger = structlog.get_logger(__name__)
20
34
 
@@ -23,6 +37,10 @@ _REQUEST_SESSION_KEY = "fastapi_sqla_session"
23
37
  _session_factories: dict[str, sessionmaker] = {}
24
38
 
25
39
 
40
+ class Base(DeclarativeBase, DeferredReflection):
41
+ __abstract__ = True
42
+
43
+
26
44
  def new_engine(key: str = _DEFAULT_SESSION_KEY) -> Engine:
27
45
  envvar_prefix = "sqlalchemy_"
28
46
  if key != _DEFAULT_SESSION_KEY:
@@ -50,7 +68,8 @@ def startup(key: str = _DEFAULT_SESSION_KEY):
50
68
  raise
51
69
 
52
70
  Base.prepare(engine)
53
- _session_factories[key] = sessionmaker(bind=engine)
71
+
72
+ _session_factories[key] = sessionmaker(bind=engine, class_=SqlaSession)
54
73
 
55
74
  logger.info("engine startup", engine_key=key, engine=engine)
56
75
 
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastapi-sqla
3
- Version: 3.0.2
4
- Summary: SQLAlchemy extension for FastAPI with support for pagination, asyncio, and pytest, ready for production.
3
+ Version: 3.1.2
4
+ Summary: SQLAlchemy extension for FastAPI with support for pagination, asyncio, SQLModel, and pytest, ready for production.
5
5
  Home-page: https://github.com/dialoguemd/fastapi-sqla
6
6
  License: MIT
7
7
  Keywords: FastAPI,SQLAlchemy,asyncio,pytest,alembic
@@ -34,6 +34,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
34
34
  Classifier: Typing :: Typed
35
35
  Provides-Extra: asyncpg
36
36
  Provides-Extra: aws-rds-iam
37
+ Provides-Extra: sqlmodel
37
38
  Provides-Extra: tests
38
39
  Requires-Dist: Faker (>=14.2.0,<15.0.0) ; extra == "tests"
39
40
  Requires-Dist: alembic (>=1.4.3,<2.0.0) ; extra == "tests"
@@ -45,7 +46,7 @@ Requires-Dist: fastapi (>=0.95.1)
45
46
  Requires-Dist: greenlet (>=1.1.3,<2.0.0) ; extra == "tests"
46
47
  Requires-Dist: httpx (>=0.23.0,<0.24.0) ; extra == "tests"
47
48
  Requires-Dist: isort (>=5.5.3,<6.0.0) ; extra == "tests"
48
- Requires-Dist: mypy[tests] (>=0.991,<0.992) ; extra == "tests"
49
+ Requires-Dist: mypy[tests] (>=1.0.0,<2.0.0) ; extra == "tests"
49
50
  Requires-Dist: pdbpp (>=0.10.2,<0.11.0) ; extra == "tests"
50
51
  Requires-Dist: psycopg2 (>=2.8.6,<3.0.0) ; extra == "tests"
51
52
  Requires-Dist: pydantic (>=1)
@@ -54,6 +55,7 @@ Requires-Dist: pytest (>=7.2.1,<8.0.0) ; extra == "tests"
54
55
  Requires-Dist: pytest-asyncio (>=0.19.0,<0.20.0) ; extra == "tests"
55
56
  Requires-Dist: pytest-cov (>=2.10.1,<3.0.0) ; extra == "tests"
56
57
  Requires-Dist: sqlalchemy (>=1.3)
58
+ Requires-Dist: sqlmodel (>=0.0.14,<0.0.15) ; extra == "sqlmodel"
57
59
  Requires-Dist: structlog (>=20)
58
60
  Requires-Dist: tox (>=3.26.0,<4.0.0) ; extra == "tests"
59
61
  Project-URL: Repository, https://github.com/dialoguemd/fastapi-sqla
@@ -69,7 +71,7 @@ Description-Content-Type: text/markdown
69
71
 
70
72
 
71
73
  Fastapi-SQLA is an [SQLAlchemy] extension for [FastAPI] easy to setup with support for
72
- pagination, asyncio, and [pytest].
74
+ pagination, asyncio, [SQLModel] and [pytest].
73
75
  It supports SQLAlchemy>=1.3 and is fully compliant with [SQLAlchemy 2.0].
74
76
  It is developped, maintained and used on production by the team at [@dialoguemd] with
75
77
  love from Montreal 🇨🇦.
@@ -600,6 +602,41 @@ async def async_all_users_alt(
600
602
  return await paginate(select(User))
601
603
  ```
602
604
 
605
+ # SQLModel support 🎉
606
+
607
+ If your project uses [SQLModel], then `Session` dependency is an SQLModel session::
608
+
609
+ ```python
610
+ from http import HTTPStatus
611
+
612
+ from fastapi import FastAPI, HTTPException
613
+ from fastapi_sqla import Item, Page, Paginate, Session, setup
614
+ from sqlmodel import Field, SQLModel, select
615
+
616
+ class Hero(SQLModel, table=True):
617
+ id: int | None = Field(default=None, primary_key=True)
618
+ name: str
619
+ secret_name: str
620
+ age: int | None = None
621
+
622
+
623
+ app = FastAPI()
624
+ setup(app)
625
+
626
+ @app.get("/heros", response_model=Page[Hero])
627
+ def list_hero(paginate: Paginate) -> Page[Hero]:
628
+ return paginate(select(Hero))
629
+
630
+
631
+ @app.get("/heros/{hero_id}", response_model=Item[Hero])
632
+ def get_hero(hero_id: int, session: Session) -> Item[Hero]:
633
+ hero = session.get(Hero, hero_id)
634
+ if hero is None:
635
+ raise HTTPException(HTTPStatus.NOT_FOUND)
636
+ return {"data": hero}
637
+
638
+ ```
639
+
603
640
  # Pytest fixtures
604
641
 
605
642
  This library provides a set of utility fixtures, through its PyTest plugin, which is
@@ -770,6 +807,7 @@ $ poetry run tox
770
807
  [FastAPI background tasks]: https://fastapi.tiangolo.com/tutorial/background-tasks/
771
808
  [SQLAlchemy]: http://sqlalchemy.org/
772
809
  [SQLAlchemy 2.0]: https://docs.sqlalchemy.org/en/20/changelog/migration_20.html
810
+ [SQLModel]: https://sqlmodel.tiangolo.com
773
811
  [`asyncpg`]: https://magicstack.github.io/asyncpg/current/
774
812
  [scalars]: https://docs.sqlalchemy.org/en/20/core/connections.html#sqlalchemy.engine.Result.scalars
775
813
  [alembic]: https://alembic.sqlalchemy.org/
@@ -0,0 +1,16 @@
1
+ fastapi_sqla/__init__.py,sha256=G9bKR6L9EnLgzelE_BLnHgc1cOm0Yu0wMzwLAdyA8iE,1153
2
+ fastapi_sqla/_pytest_plugin.py,sha256=bjPzOuwk32Zo4hoOa_bGjTGhtHGPImh3RyiORqz-J84,5430
3
+ fastapi_sqla/async_pagination.py,sha256=jYeZYtIi7mssM3MfwobOoaPYNZF0BE1EmCK8CqFnwOg,3164
4
+ fastapi_sqla/async_sqla.py,sha256=7SXRH3DMsQvpjCQdvCBEjQhDZ4-cPAg175MlgNVBnCg,5888
5
+ fastapi_sqla/aws_aurora_support.py,sha256=4dxLKOqDccgLwFqlz81L6f4HzrOXMZkY7Zuf4t_310U,838
6
+ fastapi_sqla/aws_rds_iam_support.py,sha256=YSJNhrxmhGN-GVk9PLMTmQSWTKZBvuorKkhc_XaoL44,1189
7
+ fastapi_sqla/base.py,sha256=0X7Gbt49rBHPiSFmNy5S2PT0dA4UBNnwrAesYSkaHBc,1606
8
+ fastapi_sqla/models.py,sha256=fesW7BqkwOA4iC345dbybzcV8Kz4-kLwREo38oqy_7A,1108
9
+ fastapi_sqla/pagination.py,sha256=IjbKFyUHly8jHyOO4pnyPK4RE_cFRDY03OOZeZEm3cY,4513
10
+ fastapi_sqla/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ fastapi_sqla/sqla.py,sha256=lOikCu_BjGQYfbbtcM59ZjwTL-66cM9L_yIbCEQasAQ,6356
12
+ fastapi_sqla-3.1.2.dist-info/LICENSE,sha256=8G0-nWLqi3xRYRrtRlTE8n1mkYJcnCRoZGUhv6ZE29c,1064
13
+ fastapi_sqla-3.1.2.dist-info/METADATA,sha256=xl5jfdozvDDEJGHW0fe9bFm_ITasdlzYb3NThMDveNk,20915
14
+ fastapi_sqla-3.1.2.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
15
+ fastapi_sqla-3.1.2.dist-info/entry_points.txt,sha256=haa0EueKcRo8-AlJTpHBMn08wMBiULNGA53nkvaDWj0,53
16
+ fastapi_sqla-3.1.2.dist-info/RECORD,,
@@ -1,16 +0,0 @@
1
- fastapi_sqla/__init__.py,sha256=4-MHiYF6TclnQpgs2Z1KDwYe2OPHLHGEu5oJzV3494s,1128
2
- fastapi_sqla/_pytest_plugin.py,sha256=bjPzOuwk32Zo4hoOa_bGjTGhtHGPImh3RyiORqz-J84,5430
3
- fastapi_sqla/async_pagination.py,sha256=UA3KxkTa48utBFDHeAkOFxTGUIvtNEWKm4uH-bqzQH4,3114
4
- fastapi_sqla/async_sqla.py,sha256=BERggjeIjBW9hPqVxE7ry3iQyjBFc9KLvDGwV_LOfK8,5919
5
- fastapi_sqla/aws_aurora_support.py,sha256=4dxLKOqDccgLwFqlz81L6f4HzrOXMZkY7Zuf4t_310U,838
6
- fastapi_sqla/aws_rds_iam_support.py,sha256=YSJNhrxmhGN-GVk9PLMTmQSWTKZBvuorKkhc_XaoL44,1189
7
- fastapi_sqla/base.py,sha256=0X7Gbt49rBHPiSFmNy5S2PT0dA4UBNnwrAesYSkaHBc,1606
8
- fastapi_sqla/models.py,sha256=QhnPCX-Gz5exAZfWyCRyYSaZ7SM_8QY0Eir6b4_4oI8,1432
9
- fastapi_sqla/pagination.py,sha256=NsI4ZeOkgbiNNDBjtqZL1rF6j1ya--jjXiyf0GlLaXU,4459
10
- fastapi_sqla/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- fastapi_sqla/sqla.py,sha256=ih4JBAQzn-lT86jK5ZOX3tewT1UOrw2NUJauOso0mNg,5939
12
- fastapi_sqla-3.0.2.dist-info/LICENSE,sha256=8G0-nWLqi3xRYRrtRlTE8n1mkYJcnCRoZGUhv6ZE29c,1064
13
- fastapi_sqla-3.0.2.dist-info/METADATA,sha256=hS61rcvqSFKrKWkfN6y5xpLOTRhnbvi-j0OTa5gVVAY,19809
14
- fastapi_sqla-3.0.2.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
15
- fastapi_sqla-3.0.2.dist-info/entry_points.txt,sha256=haa0EueKcRo8-AlJTpHBMn08wMBiULNGA53nkvaDWj0,53
16
- fastapi_sqla-3.0.2.dist-info/RECORD,,