Skip to content

Commit 41f3f5d

Browse files
authored
Merge pull request #276 from Ahmed1813/master
feat: Enhance ModelService with native async ORM
2 parents b5db1a2 + beb595f commit 41f3f5d

File tree

3 files changed

+58
-24
lines changed

3 files changed

+58
-24
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ Django Ninja Extra is a powerful extension for [Django Ninja](https://django-nin
4343

4444
## Requirements
4545

46-
- Python >= 3.6
47-
- Django >= 2.1
46+
- Python >= 3.8
47+
- Django >= 4.0
4848
- Pydantic >= 1.6
4949
- Django-Ninja >= 0.16.1
5050

ninja_extra/controllers/model/service.py

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from pydantic import BaseModel as PydanticModel
77

88
from ninja_extra.exceptions import NotFound
9-
from ninja_extra.shortcuts import get_object_or_exception
9+
from ninja_extra.shortcuts import aget_object_or_exception, get_object_or_exception
1010

1111
from .interfaces import AsyncModelServiceBase, ModelServiceBase
1212

@@ -21,21 +21,17 @@ class ModelService(ModelServiceBase, AsyncModelServiceBase):
2121
def __init__(self, model: t.Type[Model]) -> None:
2222
self.model = model
2323

24+
# --- Synchonous Methods ---
25+
2426
def get_one(self, pk: t.Any, **kwargs: t.Any) -> t.Any:
2527
obj = get_object_or_exception(
2628
klass=self.model, error_message=None, exception=NotFound, pk=pk
2729
)
2830
return obj
2931

30-
async def get_one_async(self, pk: t.Any, **kwargs: t.Any) -> t.Any:
31-
return await sync_to_async(self.get_one, thread_sensitive=True)(pk, **kwargs)
32-
3332
def get_all(self, **kwargs: t.Any) -> t.Union[QuerySet, t.List[t.Any]]:
3433
return self.model.objects.all()
3534

36-
async def get_all_async(self, **kwargs: t.Any) -> t.Union[QuerySet, t.List[t.Any]]:
37-
return await sync_to_async(self.get_all, thread_sensitive=True)(**kwargs)
38-
3935
def create(self, schema: PydanticModel, **kwargs: t.Any) -> t.Any:
4036
data = schema.model_dump(by_alias=True)
4137
data.update(kwargs)
@@ -63,9 +59,6 @@ def create(self, schema: PydanticModel, **kwargs: t.Any) -> t.Any:
6359
)
6460
raise TypeError(msg) from tex
6561

66-
async def create_async(self, schema: PydanticModel, **kwargs: t.Any) -> t.Any:
67-
return await sync_to_async(self.create, thread_sensitive=True)(schema, **kwargs)
68-
6962
def update(self, instance: Model, schema: PydanticModel, **kwargs: t.Any) -> t.Any:
7063
data = schema.model_dump(exclude_none=True)
7164
data.update(kwargs)
@@ -74,23 +67,64 @@ def update(self, instance: Model, schema: PydanticModel, **kwargs: t.Any) -> t.A
7467
instance.save()
7568
return instance
7669

70+
def patch(self, instance: Model, schema: PydanticModel, **kwargs: t.Any) -> t.Any:
71+
return self.update(instance=instance, schema=schema, **kwargs)
72+
73+
def delete(self, instance: Model, **kwargs: t.Any) -> t.Any:
74+
instance.delete()
75+
76+
# --- Asynchronous Methods (using native async ORM where possible) ---
77+
78+
async def get_one_async(self, pk: t.Any, **kwargs: t.Any) -> t.Any:
79+
obj = await aget_object_or_exception(
80+
klass=self.model, error_message=None, exception=NotFound, pk=pk
81+
)
82+
return obj
83+
84+
async def get_all_async(self, **kwargs: t.Any) -> t.Union[QuerySet, t.List[t.Any]]:
85+
return await sync_to_async(self.get_all, thread_sensitive=True)(**kwargs)
86+
87+
async def create_async(self, schema: PydanticModel, **kwargs: t.Any) -> t.Any:
88+
data = schema.model_dump(by_alias=True)
89+
data.update(kwargs)
90+
91+
try:
92+
instance = await self.model._default_manager.acreate(**data)
93+
return instance
94+
except TypeError as tex: # pragma: no cover
95+
tb = traceback.format_exc()
96+
msg = (
97+
"Got a `TypeError` when calling `%s.%s.create()`. "
98+
"This may be because you have a writable field on the "
99+
"serializer class that is not a valid argument to "
100+
"`%s.%s.create()`. You may need to make the field "
101+
"read-only, or override the %s.create() method to handle "
102+
"this correctly.\nOriginal exception was:\n %s"
103+
% (
104+
self.model.__name__,
105+
self.model._default_manager.name,
106+
self.model.__name__,
107+
self.model._default_manager.name,
108+
self.__class__.__name__,
109+
tb,
110+
)
111+
)
112+
raise TypeError(msg) from tex
113+
77114
async def update_async(
78115
self, instance: Model, schema: PydanticModel, **kwargs: t.Any
79116
) -> t.Any:
80-
return await sync_to_async(self.update, thread_sensitive=True)(
81-
instance, schema, **kwargs
82-
)
83-
84-
def patch(self, instance: Model, schema: PydanticModel, **kwargs: t.Any) -> t.Any:
85-
return self.update(instance=instance, schema=schema, **kwargs)
117+
data = schema.model_dump(exclude_none=True)
118+
data.update(kwargs)
119+
for attr, value in data.items():
120+
setattr(instance, attr, value)
121+
await instance.asave()
122+
return instance
86123

87124
async def patch_async(
88125
self, instance: Model, schema: PydanticModel, **kwargs: t.Any
89126
) -> t.Any:
90127
return await self.update_async(instance=instance, schema=schema, **kwargs)
91128

92-
def delete(self, instance: Model, **kwargs: t.Any) -> t.Any:
93-
instance.delete()
94-
95129
async def delete_async(self, instance: Model, **kwargs: t.Any) -> t.Any:
96-
return await sync_to_async(self.delete, thread_sensitive=True)(instance)
130+
await instance.adelete()

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,14 @@ classifiers = [
4141
]
4242

4343
requires = [
44-
"Django >= 2.2",
44+
"Django >= 4.0",
4545
"django-ninja == 1.4.3",
4646
"injector >= 0.19.0",
4747
"asgiref",
4848
"contextlib2"
4949
]
5050
description-file = "README.md"
51-
requires-python = ">=3.7"
51+
requires-python = ">=3.8"
5252

5353

5454
[tool.flit.metadata.urls]

0 commit comments

Comments
 (0)