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

Document that the toolbar does not support async functionality #1845

Closed
tim-schilling opened this issue Oct 23, 2023 · 17 comments
Closed

Document that the toolbar does not support async functionality #1845

tim-schilling opened this issue Oct 23, 2023 · 17 comments

Comments

@tim-schilling
Copy link
Member

We should include in our readme that the application doesn't not support async functionality and that effort is under development. If possible, it'd be great to detect that async functionality is being used and warn the user that the toolbar won't be effective. @michaelurban suggested this in #1430 (comment)

@denisSurkov
Copy link

denisSurkov commented Jan 16, 2024

Hello!

I would like to implement showing warning message if DebugToolbarMiddleware is called in async environment.

I suppose the easiest way to check if async is enabled is something like this:

from django.core.handlers.asgi import ASGIRequest

...

class DebugToolbarMiddleware:

   ...   

   def __call__(self, request):
          # Decide whether the toolbar is active for this request.
          show_toolbar = get_show_toolbar()

          if not show_toolbar(request) or DebugToolbar.is_toolbar_request(request):
              return self.get_response(request)

          if type(request) == ASGIRequest:
             self._warn_djdt_asgi_uneffective()

          ...

What do you think about this approach? I guess there are some important thinks about compability, but general idea is the same.

What is the optimal way to warn developer? At this point I think about log a warning message, but may be it's something else.

@tim-schilling
Copy link
Member Author

@denisSurkov what do you think of using iscoroutinefunction(get_response) similar to https://docs.djangoproject.com/en/4.2/topics/http/middleware/#asynchronous-support?

@denisSurkov
Copy link

denisSurkov commented Jan 16, 2024

@tim-schilling , yes, this is an option

I can do something like this:

from inspect import iscoroutinefunction

...

class DebugToolbarMiddleware:

       def __init__(self, get_response):
        self.get_response = get_response   
       
        if iscoroutinefunction(get_response):
             self._warn_djdt_asgi_uneffective()

This seems cleaner I agree

@tim-schilling
Copy link
Member Author

What's the difference between asgiref's version and inspects?

@matthiask
Copy link
Member

I'm not completely sure that works, since Django automatically wraps get_response to be either a coroutine or not depending on whether the current middleware (and maybe other middlewares in the stack) support async or not.

The note here is especially relevant:

https://docs.djangoproject.com/en/5.0/topics/http/middleware/#async-middleware

If you declare a hybrid middleware that supports both synchronous and asynchronous calls, the kind of call you get may not match the underlying view. Django will optimize the middleware call stack to have as few sync/async transitions as possible.

So, as long as the DebugToolbarMiddleware doesn't officially support async we won't ever get a get_response. At least that's my understanding of where we are now.

@denisSurkov
Copy link

@matthiask , you are right.

Currently DegubToolbarMiddleware is sync_only. There are two attributes django checking (and does fallback) for every middleware:

sync_capable = True
async_capable = False

and DebugToolbarMiddleware is sync_capable only.

So async get_response would not be an option.


For this feature I see two options now:

  • Adding flags (and also refactoring for new way middlewares work with process_view and etc, but it may break compability) and checking with iscoroutinefunction;

  • Checking type of request - we could do it one time, because once ASGIRequest came to middleware next requests would have the same type I guess.

@matthiask
Copy link
Member

I think checking the type of the request may be fine?

I'm thinking if there are other signals such as the presence of an event loop (https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.get_running_loop), but it seems to me that "just" doing a type check may be the most straightforward option for now. I didn't spend much time thinking this through just now so that's not really a well researched opinion.

@tim-schilling
Copy link
Member Author

That's good for me. Sorry for the run around @denisSurkov!

@denisSurkov
Copy link

That's okey

I'll try to make an MVP in a couple of days.

@tim-schilling , I still have question about how to warn a developer? Just printing a message or logging it somehow? If DJDT has way to log it, can you point the place?

Also I think making tests for it would be challenging

@tim-schilling
Copy link
Member Author

That's a good question. I think there are a number of issues that would benefit from a warnings/alerts panel (#1435, #1682). This could also potentially go in there too.

I think for now let's keep it easy and log it out with logger.info().

To test it you should be able to patch logger, then use logger.info.assert_called_once_with("the warning goes here")

@tim-schilling
Copy link
Member Author

@matthiask what do you think about logging vs some sort of panel to render these messages?

@matthiask
Copy link
Member

matthiask commented Jan 18, 2024

I'm not sure. We have removed the logging panel some time ago and I'm not sure we want to reintroduce it since we had so many hard to find bugs around logging.

Re. warnings: Maybe it would be nice to show it inside the panel, but I also think something like a multiline and really loud and obnoxious warning (something with borders and ASCII art 😅) would be a good fit for this issue. It's not similar to a warning or anything of the sort: For the time being the toolbar is of very limited use when running an async server.

I just checked and Django also has a similar thing, when doing stuff which is probable to cause problems down the road:
https://github.com/django/django/blob/10c7c7320baf1c655fcb91202169d77725c9c4bd/setup.py#L34-L55

@tim-schilling
Copy link
Member Author

Hmm. I wasn't thinking something to show all calls to logger.log(). But instead there would be a series of checks/validations that the toolbar could perform and inform the user. Such as "Hey, this particular form has a file input, but the form doesn't have enctype="multipart/form-data"" set.

@matthiask
Copy link
Member

Ah yes, something like that would certainly be nice.

@denisSurkov
Copy link

@tim-schilling ,

To test it you should be able to patch logger, then use logger.info.assert_called_once_with("the warning goes here")

Testing logging is not a problem. I'm worried about how to pass ASGIRequest. I can construct a request directly, but this seems like a hack. So I was thinking about running ASGIHandler with async_to_sync in tests.

@tim-schilling
Copy link
Member Author

@denisSurkov I suspect we can get away with using the AsyncRequestFactory

@matthiask
Copy link
Member

The django-debug-toolbar 5.0.0a1 version should support async; not everything, but a good part should work. So, I'm going to close this since things have improved in this area.

@matthiask matthiask closed this as not planned Won't fix, can't repro, duplicate, stale Oct 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants