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

Async reverse relationship #1142

Open
carvalhochris opened this issue Apr 26, 2024 · 4 comments
Open

Async reverse relationship #1142

carvalhochris opened this issue Apr 26, 2024 · 4 comments

Comments

@carvalhochris
Copy link

carvalhochris commented Apr 26, 2024

Expectation: To be able to run this view async, should this be possible?

Output:

Traceback (most recent call last):
  File "/Users/christophercarvalho/ninja/env/lib/python3.11/site-packages/ninja/operation.py", line 282, in run
    return self._result_to_response(request, result, temporal_response)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/christophercarvalho/ninja/env/lib/python3.11/site-packages/ninja/operation.py", line 208, in _result_to_response
    validated_object = response_model.model_validate(
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/christophercarvalho/ninja/env/lib/python3.11/site-packages/pydantic/main.py", line 532, in model_validate
    return cls.__pydantic_validator__.validate_python(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 1 validation error for NinjaResponseSchema
response
  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: <ninja.ope... object at 0x10a4fbe10>>, input_type=DjangoGetter]
    For further information visit https://errors.pydantic.dev/2.7/v/get_attribute_error

Schema

class CardOut(Schema):
    id: int
    title: str
    uuid: uuid.UUID
    price: float | None
    ext_file: str | None
    comp_file: str | None
    # image: str | None
    image_file: str | None
    quantity: int | None
    # includes: str | None
    inc_list: list[str]
    desc: str | None
    song: SongOut | None
    cardhold_set: list[CardToCardHoldOut] | None
    artist_set: List[ArtistOut] | None

View

@sync_to_async
def get_cards():
    queryset = (
        Card.objects.prefetch_related("cardhold_set")
    )
    return queryset


@api.get("/get-cards", response=List[CardOut])
@decorate_view(cache_page(7 * 24 * 60 * 60))
async def list_cards(request):
    cards = await get_cards()
    return cards

Models

class Card(models.Model):
    title = models.CharField(max_length=128)
    uuid = models.UUIDField(default=uuid.uuid4, editable=False)
    image = models.FileField(null=True, blank=True)
    desc = models.TextField(null=True, blank=True)
    includes = models.CharField(max_length=256, null=True, blank=True)
    song = models.ForeignKey(Song, null=True, on_delete=models.CASCADE)
    inc_media = models.URLField(null=True, blank=True)
    g_folder_id = models.CharField(max_length=42, blank=True)
    price = models.FloatField(null=True, blank=True)
    quantity = models.IntegerField(default=1)
    ext_file = models.URLField(blank=True, null=True)
    comp_file = models.URLField(blank=True, null=True)

    def __str__(self):
        return self.title

class Artist(models.Model):
    name = models.CharField(max_length=128)
    slug = models.SlugField(unique=True)
    about = models.TextField(null=True, blank=True)
    email = models.EmailField(null=True)
    instagram = models.CharField(max_length=30, blank=True)
    cards = models.ManyToManyField(Card, null=True, blank=True)

    def __str__(self):
        return self.name

class CardHold(models.Model):
    collector = models.ForeignKey(Collector, on_delete=models.CASCADE)
    card = models.ForeignKey(Card, on_delete=models.CASCADE)
    updated = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f"{self.collector.name} collected {self.card.title} at {self.updated}"
@vitalik
Copy link
Owner

vitalik commented Apr 26, 2024

@carvalhochris

try pre fetching also artist_set

Card.objects.prefetch_related("cardhold_set", "artist_set")

@carvalhochris
Copy link
Author

carvalhochris commented Apr 26, 2024

@carvalhochris

try pre fetching also artist_set

Card.objects.prefetch_related("cardhold_set", "artist_set")

Hi @vitalik thanks for your response.

I tried that and get the following:

Traceback (most recent call last):
  File "/Users/christophercarvalho/ninja/env/lib/python3.11/site-packages/ninja/operation.py", line 282, in run
    return self._result_to_response(request, result, temporal_response)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/christophercarvalho/ninja/env/lib/python3.11/site-packages/ninja/operation.py", line 208, in _result_to_response
    validated_object = response_model.model_validate(
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/christophercarvalho/ninja/env/lib/python3.11/site-packages/pydantic/main.py", line 532, in model_validate
    return cls.__pydantic_validator__.validate_python(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 1 validation error for NinjaResponseSchema
response
  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: <ninja.ope... object at 0x1136c23d0>>, input_type=DjangoGetter]
    For further information visit https://errors.pydantic.dev/2.7/v/get_attribute_error

@vitalik
Copy link
Owner

vitalik commented Apr 26, 2024

yeah, maybe prefetch_related does not support async yet..

try:

@api.get("/get-cards", response=List[CardOut])
@decorate_view(cache_page(7 * 24 * 60 * 60))
async def list_cards(request):
    queryset = Card.objects.prefetch_related("cardhold_set", "artist_set")
    cards = await sync_to_async(list)(queryset) # force executing db query here by converting to list
    return cards

@carvalhochris
Copy link
Author

yeah, maybe prefetch_related does not support async yet..

try:

@api.get("/get-cards", response=List[CardOut])
@decorate_view(cache_page(7 * 24 * 60 * 60))
async def list_cards(request):
    queryset = Card.objects.prefetch_related("cardhold_set", "artist_set")
    cards = await sync_to_async(list)(queryset) # force executing db query here by converting to list
    return cards

I think you might be right, it doesn't work when I include artist_set, song, or cardhold_set in the schema

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

2 participants