如何在 Django 中将同步视图转换为异步视图

问题描述 投票:0回答:1

我正在开发 django 视图来获取可用乘客的列表。我首先在同步上下文中创建,当我测试它时,它按预期工作。我需要它处于异步上下文中,但是当我尝试将 i 转换为异步上下文时,我收到以下错误消息:

   raise SynchronousOnlyOperation(message)
django.core.exceptions.SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.

这是我尝试转换为异步上下文时在异步上下文中的代码,但出现了上面的错误:

riderexpert_db = firestore.AsyncClient(project="riderexpert", database="riderexpert-db")

class GetAvailableRidersView(APIView): 
    permission_classes = [IsAuthenticated] 
    SEARCH_RADIUS_METERS = 10
    async def get_google_maps_client(self):
        """Initialize and return the asynchronous Google Maps API client."""
        return GoogleMapsClient(key=settings.GOOGLE_MAPS_API_KEY)

    async def validate_parameters(self, order_location, item_capacity, is_fragile):
        """Validate input parameters."""
        try:
            item_capacity = float(item_capacity)
            is_fragile = str_to_bool(is_fragile)
        except (ValueError, TypeError):
            return False, "Invalid or missing parameters"

        if not all(
            [
                order_location is not None and isinstance(order_location, str),
                item_capacity is not None and isinstance(item_capacity, (int, float)),
                is_fragile is not None and isinstance(is_fragile, bool),
            ]
        ):
            return False, "Invalid or missing parameters"
        return True, ""

    def handle_google_maps_api_error(self, e):
        """Handle Google Maps API errors."""
        return Response(
            {"status": "error", "message": f"Google Maps API error: {str(e)}"},
            status=400,
        )

    def handle_internal_error(self, e):
        """Handle internal server errors."""
        logger.error(str(e))
        return Response(
            {"status": "error", "message": "Error processing the request"}, status=500
        )

    async def get(self, request, *args, **kwargs):
        order_location = request.GET.get("origin")
        item_capacity = request.GET.get("item_capacity")
        is_fragile = request.GET.get("is_fragile")

        # Handle Missing or Invalid Parameters
        is_valid, validation_message = await self.validate_parameters(
            order_location, item_capacity, is_fragile
        )
        if not is_valid:
            return Response(
                {"status": "error", "message": validation_message}, status=400
            )

        try:
            # Initialize asynchronous Google Maps API client
            gmaps = await self.get_google_maps_client()

            # Optimize Database Queries
            available_riders = await self.get_available_riders(
                gmaps, order_location, item_capacity, is_fragile
            )

            return Response({"status": "success", "riders": available_riders})
        except ApiError as e:
            logger.error(f"Google Maps API error: {str(e)}")
            return self.handle_google_maps_api_error(e)
        except Exception as e:
            return self.handle_internal_error(e)
        finally:
            # Close the asynchronous Firestore client
            await riderexpert_db.close()

    async def get_available_riders(self, gmaps, order_location, item_capacity, is_fragile):
        try:
            # Query asynchronous Firestore for available riders locations
            riders_collection = await riderexpert_db.collection("riders").stream()

            riders_within_radius = []
            for firestore_rider in riders_collection:
                firestore_data = firestore_rider.to_dict()

                # Retrieve rider from Django model using email as identifier
                email = firestore_data.get("email")

                # Construct queryset to filter riders based on conditions
                queryset = Rider.objects.filter(
                    user__email=email,
                    is_available=True,
                    fragile_item_allowed=is_fragile,
                    min_capacity__lte=item_capacity,
                    max_capacity__gte=item_capacity,
                )
                # Check if the queryset is not empty
                if queryset.exists():
                    django_rider = queryset.first()
                    rider_location = (
                        firestore_data.get("current_latitude"),
                        firestore_data.get("current_longitude"),
                    )

                    distance_matrix_result = await gmaps.distance_matrix(
                        origins=order_location,
                        destinations=rider_location,
                        mode="driving",
                        units="metric",
                    )

                    distance = distance_matrix_result["rows"][0]["elements"][0][
                        "distance"
                    ]
                    duration = distance_matrix_result["rows"][0]["elements"][0][
                        "duration"
                    ]["text"]

                    if distance["value"] <= self.SEARCH_RADIUS_METERS * 1000:
                        # Include both distance and duration in the response
                        rider_data = {
                            "rider": RiderSerializer(django_rider).data,
                            "distance": distance,
                            "duration": duration,
                        }
                        riders_within_radius.append(rider_data)

            return riders_within_radius

        except ApiError as e:
            return self.handle_google_maps_api_error(e)`

所以我使用

sync_to_async from asgiref.sync
对其进行了修改,如下所示:

riderexpert_db = firestore.AsyncClient(project="riderexpert", database="riderexpert-db")


class GetAvailableRidersView(APIView):
    permission_classes = [IsAuthenticated]
    SEARCH_RADIUS_METERS = 10

    async def get_google_maps_client(self):
        """Initialize and return the asynchronous Google Maps API client."""
        return GoogleMapsClient(key=settings.GOOGLE_MAPS_API_KEY)

    async def validate_parameters(self, order_location, item_capacity, is_fragile):
        """Validate input parameters."""
        try:
            item_capacity = float(item_capacity)
            is_fragile = str_to_bool(is_fragile)
        except (ValueError, TypeError):
            return False, "Invalid or missing parameters"

        if not all(
            [
                order_location is not None and isinstance(order_location, str),
                item_capacity is not None and isinstance(item_capacity, (int, float)),
                is_fragile is not None and isinstance(is_fragile, bool),
            ]
        ):
            return False, "Invalid or missing parameters"
        return True, ""

    def handle_google_maps_api_error(self, e):
        """Handle Google Maps API errors."""
        return Response(
            {"status": "error", "message": f"Google Maps API error: {str(e)}"},
            status=400,
        )

    def handle_internal_error(self, e):
        """Handle internal server errors."""
        logger.error(str(e))
        return Response(
            {"status": "error", "message": "Error processing the request"}, status=500
        )

    async def get(self, request, *args, **kwargs):
        order_location = request.GET.get("origin")
        item_capacity = request.GET.get("item_capacity")
        is_fragile = request.GET.get("is_fragile")

        # Handle Missing or Invalid Parameters
        is_valid, validation_message = await self.validate_parameters(
            order_location, item_capacity, is_fragile
        )
        if not is_valid:
            return Response(
                {"status": "error", "message": validation_message}, status=400
            )

        try:
            # Initialize asynchronous Google Maps API client
            gmaps = await self.get_google_maps_client()

            # Optimize Database Queries
            available_riders = await self.get_available_riders(
                gmaps, order_location, item_capacity, is_fragile
            )

            return Response({"status": "success", "riders": available_riders})
        except ApiError as e:
            logger.error(f"Google Maps API error: {str(e)}")
            return self.handle_google_maps_api_error(e)
        except Exception as e:
            return self.handle_internal_error(e)
        finally:
            # Close the Firestore client
            await riderexpert_db.close()

    async def get_available_riders(self, gmaps, order_location, item_capacity, is_fragile):
        try:
            # Use sync_to_async for Firestore operations
            riders_collection = await sync_to_async(
                riderexpert_db.collection("riders").stream
            )()

            riders_within_radius = []
            for firestore_rider in riders_collection:
                firestore_data = firestore_rider.to_dict()

                # Retrieve rider from Django model using email as identifier
                email = firestore_data.get("email")

                # Construct queryset to filter riders based on conditions
                queryset = Rider.objects.filter(
                    user__email=email,
                    is_available=True,
                    fragile_item_allowed=is_fragile,
                    min_capacity__lte=item_capacity,
                    max_capacity__gte=item_capacity,
                )
                # Check if the queryset is not empty
                if queryset.exists():
                    django_rider = queryset.first()
                    rider_location = (
                        firestore_data.get("current_latitude"),
                        firestore_data.get("current_longitude"),
                    )

                    distance_matrix_result = await gmaps.distance_matrix(
                        origins=order_location,
                        destinations=rider_location,
                        mode="driving",
                        units="metric",
                    )

                    distance = distance_matrix_result["rows"][0]["elements"][0][
                        "distance"
                    ]
                    duration = distance_matrix_result["rows"][0]["elements"][0][
                        "duration"
                    ]["text"]

                    if distance["value"] <= self.SEARCH_RADIUS_METERS * 1000:
                        # Include both distance and duration in the response
                        rider_data = {
                            "rider": RiderSerializer(django_rider).data,
                            "distance": distance,
                            "duration": duration,
                        }
                        riders_within_radius.append(rider_data)

            return riders_within_radius

        except ApiError as e:
            return self.handle_google_maps_api_error(e)

但我仍然收到此错误:

result = await self.awaitable(*args, **kwargs) 
TypeError: object Response can't be used in 'await' expression

我该如何解决这个问题?

python django django-rest-framework django-views python-asyncio
1个回答
0
投票

当您在异步上下文中使用 django 的 orm 功能时,您可以有两个选项(https://docs.djangoproject.com/en/5.0/topics/async/):

  1. 在sync_to_async中包装orm方法调用
  2. 调用异步查询集方法变体(例如aget、aexist、acount等)并等待它。

在您的代码中,我在这里看到几个示例,您在异步函数中调用 orm ,这可能会导致您收到错误:

queryset = Rider.objects.filter(
   user__email=email,
   is_available=True,
   fragile_item_allowed=is_fragile,
   min_capacity__lte=item_capacity,
   max_capacity__gte=item_capacity,
)
# Check if the queryset is not empty
if queryset.exists():
   django_rider = queryset.first()
   rider_location = (
      firestore_data.get("current_latitude"),
      firestore_data.get("current_longitude"),
   )

这肯定会导致错误,因为您在异步函数中调用 orm 同步方法。正确的做法是:

queryset = Rider.objects.filter(
   user__email=email,
   is_available=True,
   fragile_item_allowed=is_fragile,
   min_capacity__lte=item_capacity,
   max_capacity__gte=item_capacity,
)
# Check if the queryset is not empty
if await queryset.aexists():
   django_rider = await queryset.afirst()
   rider_location = (
      # if firestore_data is queryset
      await firestore_data.aget("current_latitude"),
      await firestore_data.aget("current_longitude"),
   )

这只是您可能收到此错误的一个示例,您应该更正您提供的代码中的所有 orm 方法调用。

© www.soinside.com 2019 - 2024. All rights reserved.