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

[BUG] Same path apis with different method and async sync are mixed then all considered as async when testing #1347

Open
LeeJB-48 opened this issue Nov 27, 2024 · 2 comments

Comments

@LeeJB-48
Copy link

Describe the bug
if same path with different method and async sync are mixed then they are all considered as async when testing

i use async for (GET) operation
and use sync for (POST,DELETE,PUT,PATCH) operations
but i got error when testing

example code is below

def test_bug():
    router = Router()
    @router.get("/test/")
    async def test_get(request):
        return {"test": "test"}
    @router.post("/test/")
    def test_post(request):
        return {"test": "test"}
    client = TestClient(router)
    response = client.post("/test/")

and it throws an error says

AttributeError sys:1: RuntimeWarning: coroutine 'PathView._async_view' was never awaited

so i found PathView._async_view from

client.urls[0].callback # also for client.urls[1].callback

but i found that both callbacks are all PathView._async_view , even for the sync view (POST method)

and the reason is that when operations are added to Router()
for same path , then even if one operation is async , then all considered async

class PathView:
    def __init__(self) -> None:
        self.operations: List[Operation] = []
        self.is_async = False  # if at least one operation is async - will become True    <---------- Here
        self.url_name: Optional[str] = None

    def add_operation(
        self,
        path: str,
        methods: List[str],
        view_func: Callable,
        *,
        auth: Optional[Union[Sequence[Callable], Callable, NOT_SET_TYPE]] = NOT_SET,
        throttle: Union[BaseThrottle, List[BaseThrottle], NOT_SET_TYPE] = NOT_SET,
        response: Any = NOT_SET,
        operation_id: Optional[str] = None,
        summary: Optional[str] = None,
        description: Optional[str] = None,
        tags: Optional[List[str]] = None,
        deprecated: Optional[bool] = None,
        by_alias: bool = False,
        exclude_unset: bool = False,
        exclude_defaults: bool = False,
        exclude_none: bool = False,
        url_name: Optional[str] = None,
        include_in_schema: bool = True,
        openapi_extra: Optional[Dict[str, Any]] = None,
    ) -> Operation:
        if url_name:
            self.url_name = url_name

        OperationClass = Operation
        if is_async(view_func):
            self.is_async = True   # <----------------------- Here
            OperationClass = AsyncOperation

        operation = OperationClass(
            path,
            methods,
            view_func,
            auth=auth,
            throttle=throttle,
            response=response,
            operation_id=operation_id,
            summary=summary,
            description=description,
            tags=tags,
            deprecated=deprecated,
            by_alias=by_alias,
            exclude_unset=exclude_unset,
            exclude_defaults=exclude_defaults,
            exclude_none=exclude_none,
            include_in_schema=include_in_schema,
            url_name=url_name,
            openapi_extra=openapi_extra,
        )

        self.operations.append(operation)
        view_func._ninja_operation = operation  # type: ignore
        return operation

i'm having a trouble because of that

is this a bug? or is there any purpose for that?

Versions (please complete the following information):

  • Python version: 3.11
  • Django version: 4.2.5
  • Django-Ninja version: 1.2.2
  • Pydantic version: 2.8.2
@vitalik
Copy link
Owner

vitalik commented Nov 28, 2024

Hi @LeeJB-48

You should use async test client for async views:

from ninja.testing import TestAsyncClient

client = TestAsyncClient(router)
response = await client.post("/test/")

@LeeJB-48
Copy link
Author

@vitalik

router = Router()
@router.get("/test/")
async def test_get(request):
    return {"test": "test"}
@router.post("/test/")
def test_post(request):
    return {"test": "test"}

then even if /test/ with POST method , then the view for that endpoint is considered "async" view even if that the exact POST method's function is sync? (reason is that the same endpoint for GET method is async?)

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