Skip to content

chores: Enhancing Async ModelService #281

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ doc-deploy:clean ## Run Deploy Documentation

doc-serve: ## Launch doc local server
mkdocs serve

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ Django Ninja Extra is a powerful extension for [Django Ninja](https://django-nin

## Requirements

- Python >= 3.6
- Django >= 2.1
- Python >= 3.8
- Django >= 4.0
- Pydantic >= 1.6
- Django-Ninja >= 0.16.1

Expand Down
74 changes: 54 additions & 20 deletions ninja_extra/controllers/model/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from pydantic import BaseModel as PydanticModel

from ninja_extra.exceptions import NotFound
from ninja_extra.shortcuts import get_object_or_exception
from ninja_extra.shortcuts import aget_object_or_exception, get_object_or_exception

from .interfaces import AsyncModelServiceBase, ModelServiceBase

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

# --- Synchonous Methods ---

def get_one(self, pk: t.Any, **kwargs: t.Any) -> t.Any:
obj = get_object_or_exception(
klass=self.model, error_message=None, exception=NotFound, pk=pk
)
return obj

async def get_one_async(self, pk: t.Any, **kwargs: t.Any) -> t.Any:
return await sync_to_async(self.get_one, thread_sensitive=True)(pk, **kwargs)

def get_all(self, **kwargs: t.Any) -> t.Union[QuerySet, t.List[t.Any]]:
return self.model.objects.all()

async def get_all_async(self, **kwargs: t.Any) -> t.Union[QuerySet, t.List[t.Any]]:
return await sync_to_async(self.get_all, thread_sensitive=True)(**kwargs)

def create(self, schema: PydanticModel, **kwargs: t.Any) -> t.Any:
data = schema.model_dump(by_alias=True)
data.update(kwargs)
Expand Down Expand Up @@ -63,9 +59,6 @@ def create(self, schema: PydanticModel, **kwargs: t.Any) -> t.Any:
)
raise TypeError(msg) from tex

async def create_async(self, schema: PydanticModel, **kwargs: t.Any) -> t.Any:
return await sync_to_async(self.create, thread_sensitive=True)(schema, **kwargs)

def update(self, instance: Model, schema: PydanticModel, **kwargs: t.Any) -> t.Any:
data = schema.model_dump(exclude_none=True)
data.update(kwargs)
Expand All @@ -74,23 +67,64 @@ def update(self, instance: Model, schema: PydanticModel, **kwargs: t.Any) -> t.A
instance.save()
return instance

def patch(self, instance: Model, schema: PydanticModel, **kwargs: t.Any) -> t.Any:
return self.update(instance=instance, schema=schema, **kwargs)

def delete(self, instance: Model, **kwargs: t.Any) -> t.Any:
instance.delete()

# --- Asynchronous Methods (using native async ORM where possible) ---

async def get_one_async(self, pk: t.Any, **kwargs: t.Any) -> t.Any:
obj = await aget_object_or_exception(
klass=self.model, error_message=None, exception=NotFound, pk=pk
)
return obj

async def get_all_async(self, **kwargs: t.Any) -> t.Union[QuerySet, t.List[t.Any]]:
return await sync_to_async(self.get_all, thread_sensitive=True)(**kwargs)

async def create_async(self, schema: PydanticModel, **kwargs: t.Any) -> t.Any:
data = schema.model_dump(by_alias=True)
data.update(kwargs)

try:
instance = await self.model._default_manager.acreate(**data)
return instance
except TypeError as tex: # pragma: no cover
tb = traceback.format_exc()
msg = (
"Got a `TypeError` when calling `%s.%s.create()`. "
"This may be because you have a writable field on the "
"serializer class that is not a valid argument to "
"`%s.%s.create()`. You may need to make the field "
"read-only, or override the %s.create() method to handle "
"this correctly.\nOriginal exception was:\n %s"
% (
self.model.__name__,
self.model._default_manager.name,
self.model.__name__,
self.model._default_manager.name,
self.__class__.__name__,
tb,
)
)
raise TypeError(msg) from tex

async def update_async(
self, instance: Model, schema: PydanticModel, **kwargs: t.Any
) -> t.Any:
return await sync_to_async(self.update, thread_sensitive=True)(
instance, schema, **kwargs
)

def patch(self, instance: Model, schema: PydanticModel, **kwargs: t.Any) -> t.Any:
return self.update(instance=instance, schema=schema, **kwargs)
data = schema.model_dump(exclude_none=True)
data.update(kwargs)
for attr, value in data.items():
setattr(instance, attr, value)
await instance.asave()
return instance

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

def delete(self, instance: Model, **kwargs: t.Any) -> t.Any:
instance.delete()

async def delete_async(self, instance: Model, **kwargs: t.Any) -> t.Any:
return await sync_to_async(self.delete, thread_sensitive=True)(instance)
await instance.adelete()
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ classifiers = [
]

requires = [
"Django >= 2.2",
"Django >= 4.0",
"django-ninja == 1.4.3",
"injector >= 0.19.0",
"asgiref",
"contextlib2"
]
description-file = "README.md"
requires-python = ">=3.7"
requires-python = ">=3.8"


[tool.flit.metadata.urls]
Expand Down
Loading