Skip to content

Commit 6dbd863

Browse files
authored
Merge pull request #203 from uriyyo/feature/sqlmodel
🔧 Add sqlmodel integration
2 parents cedb469 + 9de6954 commit 6dbd863

File tree

5 files changed

+120
-4
lines changed

5 files changed

+120
-4
lines changed

.github/workflows/test.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ on:
44
push:
55
pull_request:
66
workflow_dispatch:
7-
schedule:
8-
- cron: "0 0 * * *"
97

108
jobs:
119
build:
@@ -36,11 +34,12 @@ jobs:
3634
poetry install -E all
3735
3836
- name: Unit tests
39-
run: poetry run pytest tests --cov-report=xml --ignore=tests/ext/test_async_sqlalchemy.py --ignore=tests/ext/test_sqlalchemy_future.py
37+
run: poetry run pytest tests --cov-report=xml --ignore=tests/ext/test_async_sqlalchemy.py --ignore=tests/ext/test_sqlalchemy_future.py --ignore=tests/ext/test_sqlmodel.py
4038

4139
- name: Unit tests sqlalchemy 1.4
4240
run: |
43-
poetry run pip install -U "sqlalchemy>=1.4.11" # FIXME: dependency conflict must be solved
41+
poetry run pip install -U "sqlalchemy>=1.4.11" sqlmodel # FIXME: dependency conflict must be solved
42+
poetry run pytest tests/ext/test_sqlmodel.py --cov-append --cov-report=xml
4443
poetry run pytest tests/ext/test_async_sqlalchemy.py tests/ext/test_sqlalchemy_future.py --cov-append --cov-report=xml
4544
4645
- name: Upload coverage to Codecov

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Available integrations:
2727
* [tortoise](https://github.com/tortoise/tortoise-orm)
2828
* [django](https://github.com/django/django)
2929
* [piccolo](https://github.com/piccolo-orm/piccolo)
30+
* [sqlmodel](https://github.com/tiangolo/sqlmodel)
3031

3132
## Example
3233

fastapi_pagination/ext/sqlmodel.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from typing import Optional, TypeVar, Union
2+
3+
from sqlmodel import Session, SQLModel, func, select
4+
from sqlmodel.sql.expression import Select, SelectOfScalar
5+
6+
from ..api import create_page, resolve_params
7+
from ..bases import AbstractPage, AbstractParams
8+
9+
T = TypeVar("T", bound=SQLModel)
10+
11+
12+
def paginate(
13+
session: Session,
14+
query: Union[T, Select[T], SelectOfScalar[T]],
15+
params: Optional[AbstractParams] = None,
16+
) -> AbstractPage[T]:
17+
params = resolve_params(params)
18+
raw_params = params.to_raw_params()
19+
20+
if not isinstance(query, (Select, SelectOfScalar)):
21+
query = select(query)
22+
23+
total = session.scalar(select(func.count("*")).select_from(query.subquery()))
24+
items = session.exec(query.limit(raw_params.limit).offset(raw_params.offset)).unique().all()
25+
26+
return create_page(items, total, params)
27+
28+
29+
__all__ = ["paginate"]

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ ormar = { version = ">=0.10.5", optional = true}
3535
Django = { version = "<3.3.0", optional = true}
3636
piccolo = { version = ">=0.29,<0.35", optional = true}
3737

38+
# sqlmodel = { version = "^0.0.4", optional = true} Should be uncommented when gino will support sqlalchemy 1.4
39+
3840
[tool.poetry.dev-dependencies]
3941
pytest = "^6.2"
4042
pytest-cov = "^2.12.1"

tests/ext/test_sqlmodel.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
from functools import partial
2+
from typing import Iterator
3+
4+
from fastapi import Depends, FastAPI
5+
from pytest import fixture, mark
6+
from sqlmodel import Field, Session, SQLModel, create_engine, select
7+
8+
from fastapi_pagination import LimitOffsetPage, Page, add_pagination
9+
from fastapi_pagination.ext.sqlmodel import paginate
10+
11+
from ..base import BasePaginationTestCase, SafeTestClient, UserOut
12+
from ..utils import faker
13+
14+
15+
@fixture(scope="session")
16+
def engine(database_url):
17+
if database_url.startswith("sqlite"):
18+
connect_args = {"check_same_thread": False}
19+
else:
20+
connect_args = {}
21+
22+
return create_engine(database_url, connect_args=connect_args)
23+
24+
25+
@fixture(scope="session")
26+
def SessionLocal(engine):
27+
return partial(Session, engine)
28+
29+
30+
@fixture(scope="session")
31+
def User():
32+
class User(SQLModel, table=True):
33+
id: int = Field(primary_key=True)
34+
name: str
35+
36+
return User
37+
38+
39+
@fixture(
40+
scope="session",
41+
params=[True, False],
42+
ids=["model", "query"],
43+
)
44+
def query(request, User):
45+
if request.param:
46+
return User
47+
else:
48+
return select(User)
49+
50+
51+
@fixture(scope="session")
52+
def app(query, engine, User, SessionLocal):
53+
app = FastAPI()
54+
55+
@app.on_event("startup")
56+
def on_startup():
57+
SQLModel.metadata.create_all(engine)
58+
59+
with SessionLocal() as session:
60+
session.add_all([User(name=faker.name()) for _ in range(100)])
61+
62+
def get_db() -> Iterator[Session]:
63+
with SessionLocal() as db:
64+
yield db
65+
66+
@app.get("/default", response_model=Page[UserOut])
67+
@app.get("/limit-offset", response_model=LimitOffsetPage[UserOut])
68+
def route(db: Session = Depends(get_db)):
69+
return paginate(db, query)
70+
71+
add_pagination(app)
72+
return app
73+
74+
75+
@mark.future_sqlalchemy
76+
class TestSQLModel(BasePaginationTestCase):
77+
@fixture(scope="session")
78+
def client(self, app):
79+
with SafeTestClient(app) as c:
80+
yield c
81+
82+
@fixture(scope="session")
83+
def entities(self, SessionLocal, User):
84+
with SessionLocal() as session:
85+
return session.exec(select(User)).unique().all()

0 commit comments

Comments
 (0)