Skip to content
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

How can I use resolve_<field> for async api? #1277

Open
BRANYA43 opened this issue Aug 23, 2024 · 0 comments
Open

How can I use resolve_<field> for async api? #1277

BRANYA43 opened this issue Aug 23, 2024 · 0 comments

Comments

@BRANYA43
Copy link

BRANYA43 commented Aug 23, 2024

When I tested my API to get the country list I got a following error when pydantic tried to serialize a info field asynchronously:

Traceback

Traceback (most recent call last):
  File "/home/branya/Документи/My Projects/Django/guide_self_api/.venv/lib/python3.11/site-packages/asgiref/sync.py", line 254, in __call__
    return call_result.result()
           ^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/concurrent/futures/_base.py", line 449, in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/concurrent/futures/_base.py", line 401, in __get_result
    raise self._exception
  File "/home/branya/Документи/My Projects/Django/guide_self_api/.venv/lib/python3.11/site-packages/asgiref/sync.py", line 331, in main_wrap
    result = await self.awaitable(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/branya/Документи/My Projects/Django/guide_self_api/src/travels/tests/test_api.py", line 43, in test_api_returns_country_list_data
    response = await self.client.get('/countries', headers={'Accept-Language': 'en'})
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/branya/Документи/My Projects/Django/guide_self_api/.venv/lib/python3.11/site-packages/ninja/testing/client.py", line 169, in _call
    return NinjaResponse(await func(request, **kwargs))
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/branya/Документи/My Projects/Django/guide_self_api/.venv/lib/python3.11/site-packages/ninja/operation.py", line 473, in _async_view
    return await cast(AsyncOperation, operation).run(request, *a, **kw)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/branya/Документи/My Projects/Django/guide_self_api/.venv/lib/python3.11/site-packages/ninja/operation.py", line 336, in run
    return self.api.on_exception(request, e)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/branya/Документи/My Projects/Django/guide_self_api/.venv/lib/python3.11/site-packages/ninja/main.py", line 515, in on_exception
    return handler(request, exc)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/home/branya/Документи/My Projects/Django/guide_self_api/.venv/lib/python3.11/site-packages/ninja/errors.py", line 114, in _default_exception
    raise exc  # let django deal with it
    ^^^^^^^^^
  File "/home/branya/Документи/My Projects/Django/guide_self_api/.venv/lib/python3.11/site-packages/ninja/operation.py", line 334, in run
    return self._result_to_response(request, result, temporal_response)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/branya/Документи/My Projects/Django/guide_self_api/.venv/lib/python3.11/site-packages/ninja/operation.py", line 260, in _result_to_response
    validated_object = response_model.model_validate(
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/branya/Документи/My Projects/Django/guide_self_api/.venv/lib/python3.11/site-packages/pydantic/main.py", line 568, in model_validate
    return cls.__pydantic_validator__.validate_python(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 6 validation errors for NinjaResponseSchema
response.0.info
  Error extracting attribute: SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async. [type=get_attribute_error, input_value=<DjangoGetter: <Country: england>>, input_type=DjangoGetter]
    For further information visit https://errors.pydantic.dev/2.8/v/get_attribute_error

Code

# Models
class Country(models.Model):
    id = models.UUIDField(
        primary_key=True,
        default=uuid4,
        db_index=True,
        editable=False,
    )
    info = GenericRelation(
        verbose_name='Localized Information',
        to='Info',
    )

class Info(models.Model):
    slug = models.SlugField()
    name = models.CharField(max_length=100)
    short_descr = models.TextField(
        null=True, 
        blank=True, 
    )
    content_type = models.ForeignKey(
        to=ContentType,
        on_delete=models.PROTECT,
    )
    object_id = models.UUIDField()
    content_obj = GenericForeignKey()

# Schemas
class InfoSummaryField(ModelSchema):
    class Meta:
        model = Info
        fields = ('name', 'short_descr')
        
class CountrySummarySchema(ModelSchema):
    id: UUID4
    info: InfoSummaryField | None
    @staticmethod
    def resolve_info(obj) -> Info | None:
        return obj.info.first()

    class Meta:
        model = Country
        fields = ('id', 'info')


# API Rout
@router.get('/countries', response=list[CountrySummarySchema])
async def get_country_list(request):
    countries = await sync_to_async(list)(Country.objects.prefetch_related('info'))
    return countries

I tried to use async\await in the resolve_info method, but pydantic cannot work asynchronously. I'm beginner in the asynchronous, so I could miss or don't know some thing to solve this issue.

Please help me. Thank you for your help earlier

Possible solution

I'm not sure this is good solution, because the data is serialized twice.

class CountrySummarySchema(ModelSchema):
    id: UUID4
    info: InfoSummaryField | None
    @staticmethod
    def resolve_info(obj) -> Info | dict | None:
         if isinstance(obj, dict):
            return obj.get('info')
        return obj.info.first()

    class Meta:
        model = Country
        fields = ('id', 'info')

@sync_to_async
def get_country_list_with_info_and_main_image():
    return Country.objects.prefetch_related('info')


@sync_to_async
def serialize_country_data(data):
    return [CountrySummarySchema.from_orm(country).dict() for country in data]


@router.get('/countries', response=list[CountrySummarySchema])
async def get_country_list(request):
    countries = await get_country_list_with_info_and_main_image()
    countries = await serialize_country_data(countries)
    return countries
@BRANYA43 BRANYA43 changed the title How I can to use resolve_<field> for async api? How can I use resolve_<field> for async api? Aug 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant