strawberry-graphql / strawberry-django

Strawberry GraphQL Django extension
https://strawberry.rocks/docs/django
MIT License
406 stars 118 forks source link

Self is coroutine - there is no data being awaited. #496

Closed rafaelhbarros closed 6 months ago

rafaelhbarros commented 6 months ago

A mutation was written in django with a payload that returns multiple values, defined as:

@strawberry.input()
class RecordingInput:
    record_id: strawberry.ID = strawberry.field()

@strawberry.type()
class RecordingInfoPayload:
    _streaming_url: strawberry.Private[Optional[WebUrl]] = None
    _expiration_time: strawberry.Private[Optional[str]] = None
    _is_expired: strawberry.Private[bool] = False
    _is_error: strawberry.Private[bool] = False
    _record: strawberry.Private[models.Record]

    @strawberry.field()
    def recording_streaming_url(self) -> Optional[WebUrl]:
        return self._streaming_url

    @strawberry.field()
    def expiration_date(self) -> Optional[str]:
        if self._expiration_time is not None:
            return get_date_timezone_aware(
                self._expiration_time,
                self._record.rsvp.event.timezone,
                date_format="long",
            )
        return None

    @strawberry.field()
    def is_expired(self) -> bool:
        return self._is_expired

    @strawberry.field()
    def is_error(self) -> bool:
        return self._is_error

    @strawberry.field()
    def end_time(self) -> Optional[datetime.datetime]:
        start_time = self._record.actual_start_time
        duration = self._record.duration
        if duration and start_time:
            return start_time + datetime.timedelta(seconds=duration)
        return None

async def create_recording_streaming_info(
    input: RecordingInput, info: Info
) -> Optional[RecordingInfoPayload]:
    record = await models.Record.objects.filter(uuid=input.record_id).afirst()

    # some logic

    return RecordingInfoPayload(
        _streaming_url=record._streaming_url,
        _expiration_time=record._expiration_time,
        _record =record
    )

from asgiref.sync import sync_to_async

create_recording_streaming_info_mutation = strawberry.field(
    name="createRecordingStreamingInfo",
    resolver=create_recording_streaming_info,
    description="",
)

And I get this error:

{
  "data": {
    "createRecordingStreamingInfo": null
  },
  "errors": [
    {
      "message": "'coroutine' object has no attribute '_is_error'",
      "locations": [
        {
          "line": 6,
          "column": 5
        }
      ],
      "path": [
        "createRecordingStreamingInfo",
        "isError"
      ]
    }
  ]
}

If I write: (await self)._is_error and (await self)._is_expired for instance, it says coroutine is already being awaited.

Why is self a coroutine?

Upvote & Fund

Fund with Polar

bellini666 commented 6 months ago

self inside resolvers can sometimes not be what you are expecting: https://strawberry.rocks/docs/types/resolvers#defining-resolvers-as-methods

That being said, I'm not actually seeing anything wrong with your example. It seems that something failed to await the result of create_recording_streaming_info and passed the coroutine itself as the root to the resolver below it.

Could you try to provide a MRE of the issue?

rafaelhbarros commented 6 months ago

I'll produce a reproducible example today. The documentation is peculiar too.

Okay, I wrote an example but it works in this version. I have no idea why. I'll keep seeing if there is something I am missing on one of these.

rafaelhbarros commented 6 months ago

@bellini666 I found the issue, it's an extension we have for auth. I'll close this ticket.

bellini666 commented 6 months ago

@rafaelhbarros awesome! :)

Feel free to open more issues in case you find anything else.

Also:

The documentation is peculiar too.

Let us know how we can improve the docs. Also feel free to open any PRs for docs improvements if you want/can. That's something I really need to do in this lib...