Skip to content

Commit 4ce8b8f

Browse files
authored
Merge branch '3.0.x' into #1205
2 parents c0cc66f + 4d1cc1a commit 4ce8b8f

File tree

5 files changed

+98
-4
lines changed

5 files changed

+98
-4
lines changed

CHANGES.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
Version 3.0.4
2+
-------------
3+
4+
Unreleased
5+
6+
- Fix type hint for ``get_or_404`` return value. :pr:`1208`
7+
8+
19
Version 3.0.3
210
-------------
311

src/flask_sqlalchemy/extension.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
from .session import Session
2424
from .table import _Table
2525

26+
_O = t.TypeVar("_O", bound=object) # Based on sqlalchemy.orm._typing.py
27+
2628

2729
class SQLAlchemy:
2830
"""Integrates SQLAlchemy with Flask. This handles setting up one or more engines,
@@ -731,8 +733,8 @@ def get_binds(self) -> dict[sa.Table, sa.engine.Engine]:
731733
}
732734

733735
def get_or_404(
734-
self, entity: type[t.Any], ident: t.Any, *, description: str | None = None
735-
) -> t.Any:
736+
self, entity: type[_O], ident: t.Any, *, description: str | None = None
737+
) -> t.Optional[_O]:
736738
"""Like :meth:`session.get() <sqlalchemy.orm.Session.get>` but aborts with a
737739
``404 Not Found`` error instead of returning ``None``.
738740

tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def db(app: Flask) -> SQLAlchemy:
3131

3232

3333
@pytest.fixture
34-
def Todo(app: Flask, db: SQLAlchemy) -> t.Any:
34+
def Todo(app: Flask, db: SQLAlchemy) -> t.Generator[t.Any, None, None]:
3535
class Todo(db.Model):
3636
id = sa.Column(sa.Integer, primary_key=True)
3737
title = sa.Column(sa.String)

tests/test_view_query.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
from __future__ import annotations
2+
3+
import typing as t
4+
5+
import pytest
6+
import sqlalchemy as sa
7+
from flask import Flask
8+
from werkzeug.exceptions import NotFound
9+
10+
from flask_sqlalchemy import SQLAlchemy
11+
12+
13+
@pytest.mark.usefixtures("app_ctx")
14+
def test_view_get_or_404(db: SQLAlchemy, Todo: t.Any) -> None:
15+
item = Todo()
16+
db.session.add(item)
17+
db.session.commit()
18+
assert db.get_or_404(Todo, 1) is item
19+
with pytest.raises(NotFound):
20+
assert db.get_or_404(Todo, 2)
21+
22+
23+
@pytest.mark.usefixtures("app_ctx")
24+
def test_first_or_404(db: SQLAlchemy, Todo: t.Any) -> None:
25+
db.session.add(Todo(title="a"))
26+
db.session.commit()
27+
result = db.first_or_404(db.select(Todo).filter_by(title="a"))
28+
assert result.title == "a"
29+
30+
with pytest.raises(NotFound):
31+
db.first_or_404(db.select(Todo).filter_by(title="b"))
32+
33+
34+
@pytest.mark.usefixtures("app_ctx")
35+
def test_view_one_or_404(db: SQLAlchemy, Todo: t.Any) -> None:
36+
db.session.add(Todo(title="a"))
37+
db.session.add(Todo(title="b"))
38+
db.session.add(Todo(title="b"))
39+
db.session.commit()
40+
result = db.one_or_404(db.select(Todo).filter_by(title="a"))
41+
assert result.title == "a"
42+
43+
with pytest.raises(NotFound):
44+
# MultipleResultsFound
45+
db.one_or_404(db.select(Todo).filter_by(title="b"))
46+
47+
with pytest.raises(NotFound):
48+
# NoResultFound
49+
db.one_or_404(db.select(Todo).filter_by(title="c"))
50+
51+
52+
@pytest.mark.usefixtures("app_ctx")
53+
def test_paginate(db: SQLAlchemy, Todo: t.Any) -> None:
54+
db.session.add_all(Todo() for _ in range(150))
55+
db.session.commit()
56+
p = db.paginate(db.select(Todo))
57+
assert p.total == 150
58+
assert len(p.items) == 20
59+
p2 = p.next()
60+
assert p2.page == 2
61+
assert p2.total == 150
62+
63+
64+
# This test creates its own inline model so that it can use that as the type
65+
@pytest.mark.usefixtures("app_ctx")
66+
def test_view_get_or_404_typed(db: SQLAlchemy, app: Flask) -> None:
67+
class Quiz(db.Model):
68+
id = sa.Column(sa.Integer, primary_key=True)
69+
topic = sa.Column(sa.String)
70+
71+
db.create_all()
72+
73+
item: Quiz = Quiz()
74+
db.session.add(item)
75+
db.session.commit()
76+
result = db.get_or_404(Quiz, 1)
77+
assert result is item
78+
if hasattr(t, "assert_type"):
79+
t.assert_type(result, Quiz)
80+
with pytest.raises(NotFound):
81+
assert db.get_or_404(Quiz, 2)
82+
db.drop_all()

tox.ini

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ wheel_build_env = .pkg
2929
constrain_package_deps = true
3030
use_frozen_constraints = true
3131
deps = -r requirements/mypy.txt
32-
commands = mypy
32+
commands =
33+
mypy --python-version 3.7
34+
mypy --python-version 3.11
3335

3436
[testenv:docs]
3537
package = wheel

0 commit comments

Comments
 (0)