This thread got too long too fast: it's a hell of a lot to catch up on. In the future, please can we all let things sit for a bit so other voices can chime in, especially when the issue has concretely affected very few users.
In the past I have also been frustrated at particular bug fixes not being backported. But I've gradually come to appreciate just how valuable the backport policy is. It keeps Django forward-facing, and prevents the fellows spending much of their precious time backporting. Also, I’ve learned several techniques to backport within projects when required (e.g. https://adamj.eu/tech/2020/07/29/backporting-a-django-orm-feature-with-database-instrumentation/ ). I encourage others to do the same. We could perhaps document some of these techniques (one source: Paul Ganssle’s talk ”What to Do When the Bug is in Someone Else’s Code ”: https://ganssle.io/talks/#upstream-bugs ). I also believe that there is no overriding reason to backport in this case. Self-backporting the fix within a project is very feasible—there’s no need for "the request.body workaround" (which is an understandable source of frustration). I have created a repo demonstrating three different ways to self-backport the fix: https://github.com/adamchainz/django-ticket-34063-backport (client only, not request factory). The code for those three techniques follows. I hope you can use one of these, James. *1. Monkey-patching* import django from django import test if django.VERSION < (4, 2): # Backport of https://code.djangoproject.com/ticket/34063 from django.core.handlers.wsgi import LimitedStream from django.test.client import AsyncClientHandler orig_get_response_async = AsyncClientHandler.get_response_async async def get_response_async(self, request): request._stream = LimitedStream( request._stream, len(request._stream), ) return await orig_get_response_async(self, request) AsyncClientHandler.get_response_async = get_response_async class IndexTests(test.TestCase): async def test_post(self): await self.async_client.post("/") *2. Subclassing* import django from django import test if django.VERSION < (4, 2): # Backport of https://code.djangoproject.com/ticket/34063 from django.core.handlers.wsgi import LimitedStream from django.test.client import AsyncClient, AsyncClientHandler class FixedAsyncClientHandler(AsyncClientHandler): async def get_response_async(self, request): request._stream = LimitedStream( request._stream, len(request._stream), ) return await super().get_response_async(request) class FixedAsyncClient(AsyncClient): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.handler.__class__ = FixedAsyncClientHandler class TestCase(test.TestCase): if django.VERSION < (4, 2): async_client_class = FixedAsyncClient class IndexTests(TestCase): async def test_post(self): await self.async_client.post("/") *3. patchy *( My package: https://pypi.org/project/patchy/ ) import django import patchy from django import test if django.VERSION < (4, 2): # Backport of https://code.djangoproject.com/ticket/34063 from django.test.client import AsyncClientHandler patchy.patch( AsyncClientHandler.__call__, """\ @@ -14,7 +14,8 @@ sender=self.__class__, scope=scope ) request_started.connect(close_old_connections) - request = ASGIRequest(scope, body_file) + from django.core.handlers.wsgi import LimitedStream + request = ASGIRequest(scope, LimitedStream(body_file, len(body_file))) # Sneaky little hack so that we can easily get round # CsrfViewMiddleware. This makes life easier, and is probably required # for backwards compatibility with external tests against admin views. """ ) class IndexTests(test.TestCase): async def test_post(self): await self.async_client.post("/") On Sat, Dec 31, 2022 at 8:39 AM James Bennett <ubernost...@gmail.com> wrote: > On Fri, Dec 30, 2022 at 11:22 PM Carlton Gibson > <carlton.gib...@gmail.com> wrote: > > Under normal circumstances you just use the sync Client, as you've > always done. `response = client.get(`/my-async-view/`)`. > > Django handles that the **view** is async for you. > > > > It's only if you need to write an actual **async test**, i.e. an `async > def` test case that you need the async client and factory. > > Well, no. And I have been over this at least once in my explanations. > But I will explain it again as succinctly as I can. > > First: the Django testing documentation steers readers toward > AsyncClient for asynchronous testing, especially for a consistently > async codebase. Which is something that has become table stakes, or > perhaps even more basic than that, for any Python framework claiming > to support async. Given that Python itself imposes limitations on use > of async code in synchronous contexts (with "await" literally being a > syntax error in any but an async context), telling people to just > write sync tests with the sync client is neither an acceptable > substitute for just fixing the AsyncClient, nor is it compatible with > the fact that the documentation currently prominently advertises > AsyncClient and its ability to let you write a full async codebase, > tests and all. > > Second: as I *know* I have explained, it can be difficult to predict > in advance which code path Django will choose for mixed sync/async > code, because of all the work Django does to try to make them coexist > nicely. Any developer who wants to ensure async code paths are > actually tested has no useful alternative to AsyncClient, because only > AsyncClient is natively an ASGI client and thus will reliably trigger > async code paths through Django. > > > The factory can use the suggested `request.body` workaround if needed. > > Again I point out that this presumes the ability to easily communicate > both the workaround and the need for it to casual readers of the > documentation. If no exception can be made to the backport policy for > fixing the async test support, then no exception should be made to > change the documentation to tell people to use the workaround. And if > an exception is to be made, the exception which causes the least > damage to Django and the least overall ongoing burden is just to *fix > the async test support*. As I have been advocating. > > > The `AsyncClient` is affected, yes. The question is whether that > justifies a backport at this very late hour. > > I remind everyone that earlier in this thread I provided some initial > longer explanations, and then over on the forum I wrote over two > thousand words explaining in detail why I believe the backport is in > fact justified, and which anticipated and addressed several arguments > now being raised here. So I ask that people actually read and engage > with that in future replies. This is the bare minimum of respect and > good faith that can be expected of participants in discussions on this > and other forums concerning Django and its development. > > -- > You received this message because you are subscribed to the Google Groups > "Django developers (Contributions to Django itself)" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to django-developers+unsubscr...@googlegroups.com. > To view this discussion on the web visit > https://groups.google.com/d/msgid/django-developers/CAL13Cg9LYH2CW-TNi_DuJvCWZk4CZ9aS_aKVAPhqgNzndW%2B5pw%40mail.gmail.com > . > -- You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group. To unsubscribe from this group and stop receiving emails from it, send an email to django-developers+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/CAMyDDM16%2BwJEA1NjfdsWJK3Pd-pRv_FrxyjrrKNcgNupTwvbVg%40mail.gmail.com.