From 0212d80389483f8ea2930bfa80175ff2d64b3763 Mon Sep 17 00:00:00 2001 From: Bedram Tamang Date: Thu, 21 May 2026 16:01:01 -0700 Subject: [PATCH 1/2] feat: add first_or_fail method to QueryBuilder and Model Raises ModelNotFoundException when no record matches the query chain, mirroring find_or_fail but for arbitrary where conditions. Co-Authored-By: Claude Sonnet 4.6 --- .../masoniteorm/models/builder.py | 10 ++++++++++ .../masoniteorm/models/model.py | 4 ++++ .../sqlite/models/test_sqlite_model.py | 20 +++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/builder.py b/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/builder.py index 9edfda5c..21764173 100644 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/builder.py +++ b/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/builder.py @@ -97,6 +97,16 @@ async def find_or_fail(self, primary_key: str | int, columns=None): ) return result + async def first_or_fail(self, columns=None): + from fastapi_startkit.masoniteorm.exceptions import ModelNotFoundException + + result = await self.first(columns) + if result is None: + raise ModelNotFoundException( + f"{self._model.__name__} not found." + ) + return result + async def first(self, columns=None): if not columns: columns = [] diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/model.py b/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/model.py index 98f078f7..ab746cb8 100644 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/model.py +++ b/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/model.py @@ -107,6 +107,10 @@ async def find(cls, primary_key: str | int, columns=None): async def find_or_fail(cls, primary_key: str | int, columns=None): return await cls.query().find_or_fail(primary_key, columns) + @classmethod + async def first_or_fail(cls, columns=None): + return await cls.query().first_or_fail(columns) + @classmethod async def first(cls, columns=None): return await cls.query().first(columns) diff --git a/fastapi_startkit/tests/masoniteorm/sqlite/models/test_sqlite_model.py b/fastapi_startkit/tests/masoniteorm/sqlite/models/test_sqlite_model.py index facd4be0..e4877688 100644 --- a/fastapi_startkit/tests/masoniteorm/sqlite/models/test_sqlite_model.py +++ b/fastapi_startkit/tests/masoniteorm/sqlite/models/test_sqlite_model.py @@ -65,6 +65,26 @@ async def test_find_or_fail_exception_message_contains_key(self): except ModelNotFoundException as e: self.assertIn("99999", str(e)) + async def test_first_or_fail_returns_model_when_found(self): + user = await User.where("id", 1).first_or_fail() + self.assertIsInstance(user, User) + self.assertEqual(user.id, 1) + + async def test_first_or_fail_raises_when_no_match(self): + with self.assertRaises(ModelNotFoundException): + await User.where("name", "NonExistent").first_or_fail() + + async def test_first_or_fail_exception_has_404_status(self): + try: + await User.where("name", "NonExistent").first_or_fail() + except ModelNotFoundException as e: + self.assertEqual(e.get_status(), 404) + + async def test_first_or_fail_class_method_raises_when_table_empty(self): + await User.query().delete() + with self.assertRaises(ModelNotFoundException): + await User.first_or_fail() + async def test_can_set_and_retrieve_attribute(self): user = await User.first() user.name = "updated" From 7410fbebe7405fc5dcb62695ef12625da664b0e9 Mon Sep 17 00:00:00 2001 From: Bedram Tamang Date: Thu, 21 May 2026 16:03:03 -0700 Subject: [PATCH 2/2] chore: apply ruff formatting to builder.py Co-Authored-By: Claude Sonnet 4.6 --- .../src/fastapi_startkit/masoniteorm/models/builder.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/builder.py b/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/builder.py index 21764173..90b74497 100644 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/builder.py +++ b/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/builder.py @@ -102,9 +102,7 @@ async def first_or_fail(self, columns=None): result = await self.first(columns) if result is None: - raise ModelNotFoundException( - f"{self._model.__name__} not found." - ) + raise ModelNotFoundException(f"{self._model.__name__} not found.") return result async def first(self, columns=None):