Giter Site home page Giter Site logo

asgiref's Introduction

Django

Django is a high-level Python web framework that encourages rapid development and clean, pragmatic design. Thanks for checking it out.

All documentation is in the "docs" directory and online at https://docs.djangoproject.com/en/stable/. If you're just getting started, here's how we recommend you read the docs:

  • First, read docs/intro/install.txt for instructions on installing Django.
  • Next, work through the tutorials in order (docs/intro/tutorial01.txt, docs/intro/tutorial02.txt, etc.).
  • If you want to set up an actual deployment server, read docs/howto/deployment/index.txt for instructions.
  • You'll probably want to read through the topical guides (in docs/topics) next; from there you can jump to the HOWTOs (in docs/howto) for specific problems, and check out the reference (docs/ref) for gory details.
  • See docs/README for instructions on building an HTML version of the docs.

Docs are updated rigorously. If you find any problems in the docs, or think they should be clarified in any way, please take 30 seconds to fill out a ticket here: https://code.djangoproject.com/newticket

To get more help:

To contribute to Django:

To run Django's test suite:

Supporting the Development of Django

Django's development depends on your contributions.

If you depend on Django, remember to support the Django Software Foundation: https://www.djangoproject.com/fundraising/

asgiref's People

Contributors

abersheeran avatar adamchainz avatar adriangb avatar akx avatar andrewgodwin avatar asedeno avatar bellini666 avatar bjd183 avatar bluetech avatar blueyed avatar carltongibson avatar euri10 avatar felixxm avatar flaeppe avatar graingert avatar jdufresne avatar jlaine avatar kezabelle avatar kludex avatar leon0824 avatar patchwork-systems avatar pgjones avatar proofit404 avatar rixx avatar simonw avatar tiangolo avatar tobi-de avatar tomchristie avatar ttys0dev avatar untitaker avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

asgiref's Issues

Slashes excluded from group names

c2d93f5 added validators to limit the characters allowed in group names. In our application, it's very natural to use the message.content['path'] (i.e. the URL) as a group name. This was working great until we upgraded and found that / is no longer a valid group name character.

Is there a good reason to exclude / from group names? If so, we'll add a translation to some other character. But if not, we'd love to see slashes added to the character class in group_name_regex.

Exceptions from `send`, and expected behaviors on closed connections.

Before digging into the meat of this topic I want to start with this assumption:

  • Exceptions from send and receive should be treated as opaque, and should be allowed to bubble back up to the server in order to be logged. They may be intercepted by logging or 500 technical response middlewares, and subsequently re-raised, but applications shouldn't have any expectations about the type that may be raised, since these will differ from implementation to implementation.

That's pretty much how the spec is currently worded, and I think that's all as it should be. An alternative design could choose to have exceptions be used in a limited capacity to communicate information back to the application, but I don't think that's likely to be desirable. An exception raised by the server in send or receive is a black box indicating an error condition with an associated traceback, and we shouldn't expect to catch or handle particular subclasses in differing ways.

The core question in this issue is: 1. What expectations (if any) should the application have about the behavior when it makes a send() to a closed connection? 2. What behavior is most desirable from servers in that case?

There are three possible cases for the application here:

  • The application should expect an exception to be raised, indicating that the connection is closed.
  • The application should expect that no exception be raised.
  • An exception may or may not be raised, but the application cannot rely on either behavior.

In the case of HTTP, we've adopted "Note messages received by a server after the connection has been closed are not considered errors. In this case the send awaitable callable should act as a no-op." See #49

I don't recall where the conversation that lead to #49 is, but I think it's a reasonable behaviour because premature HTTP client disconnects once receiving the start of the response are valid behaviour, and not an error condition. If we raise an exception in that case then we end up with lots of erroneous exceptions being raised in response to perfectly valid server and client behavior.

If we're happy with that decision then there's two questions that it leads on to:

  1. What do we expect to happen in long-polling / SSE connections if send doesn't raise an exception once the connection has been closed?
  2. What should we expect in the case of websockets?

For the case of (1) I think that #49 means we have to have the expectation that the disconnect is communicated through the receive disconnect message. No exception should be raised from the server on send to the closed connection because that'd pollute server logs with false errors in the standard case, and we can't differentiate between long-polling connections and regular HTTP requests/responses.

Mature server implementations probably would want to enforce a timeout limit on the task once the connection has been closed, and that is the guardrail against applications that fail to properly listen for and act on the disconnect.

For the case of (2) it's less obvious. We could perfectly well raise exceptions in response to send on a disconnected connection. (Which I assume is what all current implementations do.) The issue here is that we'd be raising logged error conditions on what is actually perfectly correct behavior... there's no guarantee that an application will have yet "seen" the disconnect message at the point it sends any given outgoing message.

The upshot of all this is that I think the following would be the right sort of recommendations to make:

  • Applications must not assume that an exception will necessarily be raised when sending to a closed connection, or rely on the server doing so. They should listen to a disconnect message and terminate the task when that occurs.
  • Mature server implementations should prefer not to raise exceptions when a send occurs against a closed websocket, but should instead ignore the message, ensure that disconnect is sent on the "receive" channel, and should forcibly kill the task if it has not completed after a reasonable timeout. Server implementations that do raise exceptions in response to send on a closed channel are valid, but may end up falsely logging server errors in response to standard client disconnects.

Anyways, thorny issue, and more than happy to talk this through more, but I'm moderately sure that's the conclusion I'd come to.

Store/provide original call stack

For debugging it is useful to get/have the calling stack to a @database_sync_to_async wrapped function etc.

The following might do this, but should maybe be driven by some setting to enable it, or be done lazily (by storing the frame only, but that then typically loses its stack / f_back references):

diff --git i/asgiref/sync.py w/asgiref/sync.py
index 0652c9a..bea7cfc 100644
--- i/asgiref/sync.py
+++ w/asgiref/sync.py
@@ -9,6 +9,8 @@
     import contextvars  # Python 3.7+ only.
 except ImportError:
     contextvars = None
+else:
+    calling_stack = contextvars.ContextVar('calling_stack', default=None)


 class AsyncToSync:
@@ -137,6 +139,9 @@ def __init__(self, func):
         loop = asyncio.get_event_loop()

         if contextvars is not None:
+            import traceback
+            calling_stack.set(traceback.extract_stack(sys._getframe().f_back, limit=20))
+
             context = contextvars.copy_context()
             child = functools.partial(self.func, *args, **kwargs)
             func = context.run

This can be used like this then from a wrapped function:

        from asgiref.sync import calling_stack
        import traceback

        traceback.print_list(calling_stack.get())

I wonder if there's a better way for this in general.
I could imagine to drive this through a custom ThreadPoolExecutor, which would store this information then on submit.
Also it might make sense to have support for this in Python itself directly.

Dealing with "async -> sync1 -> sync2 -> ... syncN -> async" call chains

The following raises RuntimeError: You cannot use AsyncToSync in the same thread as an async event loop - just await the async function directly.

import asyncio
import sys
from asgiref.sync import async_to_sync


async def sleepy_square(n):
    await asyncio.sleep(1)
    return n*n


def sync2(n):
    return async_to_sync(sleepy_square)(n)


def sync1(n):
    return sync2(n-1) + sync2(n+1)


async def async_main():
    await asyncio.sleep(1)
    return sync1(float(sys.argv[1]))


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    print(loop.run_until_complete(async_main()))

As this toy example demonstrates, it's not always trivial to "just await the async function directly"; there can be multiple layers of sync functions between the top level async function and the one to be wrapped with async_to_sync. Is there a better alternative than having to create almost duplicate async versions of sync1, sync2, ...?

Provide generic worker base class

There should be a generic worker framework that provides the basics of starting up a worker, listening on some channels for messages, handing them off to something to handle and then looping around again.

It should have built-in interrupt support, ideally, as well.

ChannelFull exception when using django-rest-framework Response

We want to return a 5MB file using django-rest-framework Response method:

from rest_framework.response import Response
...
data=open(_filename_, "rb").read()
return Response(data=ujson.loads(data), status=200)

But we're getting ChannelFull exception.

We tried to increase the capacity parameter and it worked. I also go back to version 0.11.2 of asgiref and it also worked (ChannelFull exception and parameter were not still developed).
Changing the chunk_size also makes the things work.

This is the traceback we get in the exception:
2016-07-21 17:16:57,101 - ERROR - worker - Error processing message with consumer channels.handler.ViewConsumer:
Traceback (most recent call last):
File "C:\Anaconda3\lib\site-packages\channels\worker.py", line 78, in run
consumer(message, **kwargs)
File "C:\Anaconda3\lib\site-packages\channels\handler.py", line 329, in call
message.reply_channel.send(reply_message)
File "C:\Anaconda3\lib\site-packages\channels\channel.py", line 38, in send
self.channel_layer.send(self.name, content)
File "C:\Anaconda3\lib\site-packages\asgiref\inmemory.py", line 50, in send
raise self.ChannelFull(channel)
asgiref.inmemory.ChannelLayer.ChannelFull: http.response!gMEQvWNb

WsgiToAsgi doesn’t handle more_body

WsgiToAsgi receives a single http.request event and invokes the WSGI app using the body key as the request body. This means that, if the body is delivered over multiple events by means of more_body, only the first piece will be passed on. WsgiToAsgi really ought to do one of the following, in decreasing order of preference:

  1. make wsgi.input and inter-thread filelike which is fed with body data as it arrives,
  2. loop over http.request events until not more_body, accumulating the body into a buffer, and call the WSGI application with the buffer, or
  3. assert that more_body is False on every event, so that at least the user knows this is a limitation of WsgiToAsgi and can install a request-body-buffering middleware in front of it, rather than having request bodies get silently truncated for no apparent reason.

WebSocket framing control

Currently ASGI says nothing about WebSocket frames, which is in my view the best approach. However I've been asked about support for this, and I wanted to ask for views about an ASGI extension to allow framing support. Something like,

{"type": "websocket.send", "text": "hello", "message_finished": False}
{"type": "websocket.send", "text": "world", "message_finished": True}

?

LICENSE file missing?

I think there should be a file stating the actual license for this project, clarifying if BSD means 2- or 3-clause license. This is a requirement before I can work on a Debian package.

ASGI resources list

Hey!

I'm opening this discussion after originally poking the idea over the Encode discussion board.

Context

ASGI has been establishing itself as a common interface enabling interoperability across a whole range of things in the Python async web space, including:

There are also all sorts of non-code resources out there, like talks and articles.

But, as the ASGI ecosystem is growing, I personally feel like it's hard to keep up on which things are available.

Listing ASGI resources

In this context, perhaps it could be worth setting up some sort of collaborative list of ASGI resources. This would allow better discoverability and more wide-spread usage of ASGI-related projects, and help getting a grasp of the scale of the ecosystem.

It seems a collaboratively-grown static list (e.g. an ASGI awesome list or an Are we … yet? list) would fill this needs quite well already.

Just to be clear: the ASGI list wouldn't list any async resources (the awesome-asyncio is already very well suited for that), but only those that are directly related to ASGI, such as those I listed earlier.

Location

Technically the list could be in the ASGI documentation itself, but I feel like the ASGI docs/spec should be part of the list, not the other way around. Giving the list its own space such as a GitHub repo is probably a more sustainable approach.

Digging further into this, maybe now would be a good time to give ASGI its own space, e.g. a python-asgi GitHub org. I suppose it'd contain this repository and the ASGI list at first (and maybe turn into some sort of umbrella organization for projects?).

Anyway, happy to read everyone's opinion on this. I'd love to help setup the list if this sounds like a good idea at all.

cc @tomchristie, @andrewgodwin, @simonw (who else?)

Lifespan spec still uses ASGI 2 in the example

Unless I'm mistaken, the example in the ASGI lifespan spec hasn't been updated to ASGI 3 yet:

class App:
def __init__(self, scope):
self.scope = scope
async def __call__(self, receive, send):
if self.scope['type'] == 'lifespan':

Should this be updated to ASGI 3.0 async def __call__(self, scope, receive, send)?

Who is responsible for implementing Chunked-Transfer-Encoding

I read the ASGI-Spec and it was not clear who is responsible for implementing Chunked-Transfer-Encoding for the event http.request.

Is the framework ultimately responsible for parsing whatever is sent over the raw socket or is the server responsible for supporting Transfer-Encoding?

Does the spec allow NaN and +-inf in messages?

The spec specifies that floats are allowed in messages:

Floating point numbers (within the IEEE 754 double precision range)

From the wording alone, it implies that NaN and +inf are allowed. From what I can tell, both current channel layers (redis and ipc) use msgpack for serialization, which explicitly supports NaN and infinities. However, the most likely msgpack alternative -- JSON -- does not support these. So I think it'd be useful if the spec specified one of the following:

  • Explicitly allow NaN/inf - in which case JSON implementations will need to handle them somehow. A note should be added to the spec similar to the existing "Strings and Unicode" note.

  • Explicitly disallow NaN/inf - in which case msgpack implementations should reject them for parity.

WebSocket spec speaks of "data frame" but would be better to use "message"

In www.rst, WebSocket Send and Receive sections speak of "data frames": https://github.com/django/asgiref/blob/dfb76d4e23d0851021ee44529e90bac2453b50e4/specs/www.rst#receive

In WebSockets (as you probably know) there is a distinction between frames and messages: a message consists of one or more frames. Or more precisely it can be said that a complete data frame (= message) can be fragmented to multiple incomplete data frames.

The Daphne server is using messages, and does not expose details of fragmentation. So I assume the spec want to use messages too.


Some discussion on whether the spec should actually expose fragmentation.

Exposing frames to the application can be useful for implementing streaming messages. For example, I might want to send some huge file over a binary WebSocket message; the sending side breaks the message up to 64kb frames, and the received side appends them to the disk, thus there is no need to buffer the entire file in memory.

The obvious way to expose fragmentation in ASGI is to use a similar convention to the http more_body field - more_data: True if there are more frames to come, more_data: False for the last frame. However there is a little technicality which makes this difficult. From RFC 6555:

Note that a particular text frame might include a partial UTF-8 sequence; however, the whole message MUST contain valid UTF-8.

i.e. it is not possible to deliver text frames as str (as opposed to bytes), because it might not decode at the boundary.

Another way too enable streaming is to keep more_data but do it on arbitrarily-sized chunks and disregard frames.

But overall, it seems pretty clear to me that the 99% use case is to deliver full buffered messages, and that the spec should replace frames -> messages.

Note:

  • Many servers buffer frames anyway.
  • The original JavaScript WebSocket API doesn't expose frames.

Python 3.7: @async_to_sync unittest collection error.

On macOS (not sure about others yet) with Python 3.7.2.

  1. Create a new venv and install asgiref.
  2. Create a tests.py file:
import unittest

from asgiref.sync import async_to_sync


class ASGITest(unittest.TestCase):

    @async_to_sync
    async def test_wrapped_case_is_collected(self):
        self.assertTrue(True)
  1. python -m unitest tests.py

Output

(tmp-68415466ed791e9) ~/ve/tmp-68415466ed791e9 $ python -m unittest issue/tests.py
Traceback (most recent call last):
  File "/Users/carlton/.pyenv/versions/3.7.2/lib/python3.7/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/Users/carlton/.pyenv/versions/3.7.2/lib/python3.7/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/Users/carlton/.pyenv/versions/3.7.2/lib/python3.7/unittest/__main__.py", line 18, in <module>
    main(module=None)
  File "/Users/carlton/.pyenv/versions/3.7.2/lib/python3.7/unittest/main.py", line 100, in __init__
    self.parseArgs(argv)
  File "/Users/carlton/.pyenv/versions/3.7.2/lib/python3.7/unittest/main.py", line 147, in parseArgs
    self.createTests()
  File "/Users/carlton/.pyenv/versions/3.7.2/lib/python3.7/unittest/main.py", line 159, in createTests
    self.module)
  File "/Users/carlton/.pyenv/versions/3.7.2/lib/python3.7/unittest/loader.py", line 220, in loadTestsFromNames
    suites = [self.loadTestsFromName(name, module) for name in names]
  File "/Users/carlton/.pyenv/versions/3.7.2/lib/python3.7/unittest/loader.py", line 220, in <listcomp>
    suites = [self.loadTestsFromName(name, module) for name in names]
  File "/Users/carlton/.pyenv/versions/3.7.2/lib/python3.7/unittest/loader.py", line 191, in loadTestsFromName
    return self.loadTestsFromModule(obj)
  File "/Users/carlton/.pyenv/versions/3.7.2/lib/python3.7/unittest/loader.py", line 124, in loadTestsFromModule
    tests.append(self.loadTestsFromTestCase(obj))
  File "/Users/carlton/.pyenv/versions/3.7.2/lib/python3.7/unittest/loader.py", line 90, in loadTestsFromTestCase
    testCaseNames = self.getTestCaseNames(testCaseClass)
  File "/Users/carlton/.pyenv/versions/3.7.2/lib/python3.7/unittest/loader.py", line 235, in getTestCaseNames
    testFnNames = list(filter(shouldIncludeMethod, dir(testCaseClass)))
  File "/Users/carlton/.pyenv/versions/3.7.2/lib/python3.7/unittest/loader.py", line 232, in shouldIncludeMethod
    fullName = '%s.%s' % (testCaseClass.__module__, testFunc.__qualname__)
AttributeError: 'functools.partial' object has no attribute '__qualname__'

This works with Python 3.6.8.

I want to pin down if it's just macOS, and what change in Python triggers it, but opening now to see if people can test on their setup and/or have any thoughts.

Ran into this running the Django test suite (on Django master) against Python 3.7.2.

Update: failing test casefor Python 3.7 (and fix) in #104.

WsgiToAsgi doesn't send response.start on empty wsgi iterable

When the WSGI application produces an empty iterator the http.response.start event is never send, but a http.response.body event is sent. The following reproduces the issue:

def wsgi(environ, start_response):
    start_response("200 OK", [])
    return []

asgi = WsgiToAsgi(wsgi)

I think returning an empty iterable should be okay. Changing the end of WsgiToAsgiInstance.run_wsgi_app to the following fixed the problem for me:

# Close connection
if not self.response_started:
    self.response_started = True
    self.sync_send(self.response_start)
self.sync_send({"type": "http.response.body"})

Cannot use async_to_sync inside sync code scheduled from an event loop

This relates to #56 in that it's requesting the same thing, but for different reasons.
I am writing a SyncConsumer (this cannot be changed) but need to schedule some messages to be sent at some point in the future. The most obvious way to do this is to schedule them with the existing event loop - in particular it requires no extra threads. However, the synchronous function that is scheduled still needs to call the send method which ultimately needs to use async_to_sync which throws an error.

The error is at best incorrect - it is not possible to "just await the async function directly" because this is not async code. The implicit assumption that the only reason to have a running event loop is because the code is async is wrong.

The only real workaround I can see is reaching into the Consumer internals directly and scheduling AsyncConsumer.base_send.awaitable (because for some reason the class replaces its own method with a version wrapped in async_to_sync if it is subclassed into a SyncConsumer...) which is too brittle.

receive_many -> receive

In asgiref/worker.py:76 replace receive_many with receive.

Likewise in django/channels.git

channels/channel.py:13: a) Hide receive_many from end-users, as it is only for interface servers
channels/handler.py:132: _, chunk = message.channel_layer.receive_many(
channels/sessions.py:101: channel, content = message.channel_layer.receive_many([wait_channel], block=False)
channels/test/base.py:68: recv_channel, content = channel_layers[alias].receive_many([channel])
channels/test/base.py:105: recv_channel, content = channel_layers[self.alias].receive_many([channel])
channels/worker.py:87: channel, content = self.channel_layer.receive_many(channels, block=True)
docs/faqs.rst:137: yourproject.asgi.channel_layer and call send() and receive_many()
docs/testing.rst:36:you (if you want, you can call receive_many on the underlying channel layer,

so that the system can be started without generating warnings.

In asgiref/base_layer.py:77 replace "intepret" with "interpret".

PEP, roadmap, future of ASGI specification, etc.

I'm very interested in the future of the ASGI specification as a "spiritual successor to WSGI". Similarly to the sentiment expressed in #35, I think that the broader community using asyncio could benefit a lot from ASGI - not only by the utilities in this package, but through a more general adoption as a specification. With uvicorn recently becoming a viable ASGI server, I think there may be a need for a PEP document or some more formal guidance focusing on ASGI - particularly in regards to how it could/should be used outside of Django Channels.

I have been working on a micro-framework as well as various tools and examples to experiment with the spec and demonstrate the composability and potential uses of ASGI apps both with and without Django. ASGI apps are really flexible and quite enjoyable to develop, however the sources of information about ASGI seem to be, understandably, fragmented and inaccessible to those that aren't already familiar with Channels.

My questions at this point are:

Are there plans for a PEP document or any further ASGI-specific developments?
Is this the appropriate place to raise this issue? If not, where would this discussion be most useful?

Building the documentation

In #83 I mentioned that I needed to comment out a few lines in docs/conf.py to get them to build. conf.py currently specifies both html_theme = 'default' and html_sidebars = {...}, which I believe to be mutually exclusive. The simplest thing that would fix the build is commenting out html_sidebars, resulting in the default sphinx theme; alternatively, setting html_theme to 'sphinx_rtd_theme' and documenting the sphinx_rtd_theme package as a docs dependency would produce docs that look like those on https://asgi.readthedocs.io.

Earlier versions of sphinx shipped with sphinx_rtd_theme, but it now ships with alabaster. Setting the theme to sphinx_rtd_theme and trying to build without the dependency states:

sphinx_rtd_theme is no longer a hard dependency since version 1.4.0. Please install it manually.(pip install sphinx_rtd_theme) 

I can open a pull request with either solution along with a mention of the docs process in the README. Wanted to open this first to see which is preferable!

async_to_sync: support calling async_to_sync from an async loop.

This feature is needed for this common scenario:

  1. The application is async.

  2. The application has to override a class method in a library and the method is sync:

class Base:
    def method():
        pass
  1. I want writhe async code in the new method().

So I can write it as:

class Derived:
    @async_to_sync
    async def method():
        await some_thing()

Currently this commit prevent me from doing this: 9d42cb5

If I revert that commit, above code will hang due to race condition. That commit is correct.

To support this case, I think we can do this: when detected async_to_sync() is running on async thread and it's not under sync_to_async() (thread local), start a new thread and create a new event loop to run the async code.

Extending the WsgiToAsgi adapter, protocol routing, ASGI generics

Currently WsgiToAsgi only handles normal HTTP requests and builds sync WSGI responses. It may be useful to include a generic protocol router in asgiref that could be used with converted WSGI apps. The actual implementation of framework-specific details would make more sense in specific adapter implementations, but a generic protocol router that could be used with WsgiToAsgi could easily grant a WSGI application the benefit of ASGI async consumers without having to implement custom protocol routing for each case.

The class below modifies WsgiToAsgi with a dictionary mapping of protocol types to consumer handlers by path, similar to the class ProtocolTypeRouter in Channels. This is just an example, but if this kind of functionality would make sense to include in asgiref then it may be worth further thought and discussion.

Modified WsgiToAsgi class:

class WsgiToAsgi:

    def __init__(self, wsgi_application):
        self.wsgi_application = wsgi_application
        self.protocol_router = {"http": {}, "websocket": {}}

    def __call__(self, scope):
        protocol = scope["type"]
        path = scope["path"]
        # Check if a consumer protocol handles the request
        try:
            consumer = self.protocol_router[protocol][path]
        except KeyError:
            consumer = None
        if consumer is not None:
            return consumer(scope)
        return WsgiToAsgiInstance(self.wsgi_application, scope)

    def asgi(self, rule, protocol_type):
        """
        Route consumer handlers to the protocol router.
        """
        try:
            self.protocol_router[protocol_type]
        except KeyError:
            raise Exception("Invalid protocol type %s specified." % protocol_type)

        def _route(handler):
            self.protocol_router[protocol_type][rule] = handler
        return _route

Here is an example application that uses Flask:

from datetime import datetime
from flask import Flask, render_template
import asyncio

wsgi_app = Flask(__name__)
app = WsgiToAsgi(wsgi_app)
app.route = wsgi_app.route

# Handled by WSGI app

@app.route("/")
def hello():
    return render_template("hello.html")

# Handled by ASGI consumers

@app.asgi("/asgi", "http")
def hello_http(scope):
    async def asgi_instance(receive, send):
        while True:
            message = await receive()
            if message["type"] == "http.request":
                await send({
                    "type": "http.response.start",
                    "status": 200,
                    "headers": [(b"cache-control", b"no-cache"),
                                (b"content-type", b"text/html"),
                                (b"transfer-encoding", b"chunked")],
                })
                await send({
                    "type": "http.response.body",
                    "body": b"Starting...",
                    "more_body": True
                })
                for i in range(5):
                    payload = "data: %s\n\n" % datetime.now().isoformat()
                    await send({
                        "type": "http.response.body",
                        "body": payload.encode(),
                        "more_body": bool(i != 5)
                    })
                    await asyncio.sleep(1)
                return
            if message["type"] == "http.disconnect":
                return
    return asgi_instance


@app.asgi("/ws", "websocket")
def hello_websocket(scope):
    async def asgi_instance(receive, send):
        while True:
            message = await receive()
            if message["type"] == "websocket.connect":
                response = {"type": "websocket.accept"}
                await send(response)
            if message["type"] == "websocket.receive":
                response = {"type": "websocket.send"}
                if "text" in message:
                    response["text"] = message["text"]
                else:
                    response["bytes"] = message["bytes"]
                await send(response)
            if message["type"] == "websocket.disconnect":
                return
    return asgi_instance

ASGI spec: Connections, exceptions, and task management.

Some questions that may or may not necessarily need be part of the ASGI (http) spec, but that might need implementor guidelines...

Trying to be fairly complete here in thinking about potential edge cases around uvicorn's handling:

  • Should an ASGI server be responsible for detecting & handling applications that raise an exception?
  • Should an ASGI server be responsible for detecting & handling applications that complete without sending a response?
  • Should an ASGI server be responsible for handling applications that do not complete, after sending a response? (Within some given timeframe.)
  • Should an ASGI server be responsible for timing out slow-returning responses?
  • How should an ASGI server be expected to act if an application raises an exception once a response is partially sent?
  • How should an ASGI server be expected to act if an application raises an exception once a response is completely sent?
  • Should an ASGI application wrap and handle exceptions themselves as 500 errors, or just propagate them to the server?
  • Should an ASGI application wrap and handle exceptions that occur once a response is partially sent, and if so how should it communicate it to the server?
  • Should an ASGI application wrap and handle exceptions that occur once a response is already fully sent, and if so how should it communicate it to the server?
  • How should send be expected to behave in the event of a closed or closing connection?
  • How should receive be expected to behave in the event of a closed or closing connection?
  • Should a connection becoming closed cancel any outstanding applications, or allow them to run to completion?

Not sure which of these do or don't need to be explicit, but think it's worth at least opening this up for discussion here.

Should ASGI include an equivalent to `exc_info`?

WSGI includes an exc_info, which there's no equivalent of in ASGI.

There's a couple of things this means we can't do:

  • If an application framework catches exceptions and returns 500 responses (which it should) than the server framework cannot log the traceback as an error, since it never sees it.
  • Error logging middleware (eg. raven) can't work if the application framework catches and handles exceptions/500.
  • Test frameworks (eg. Starlette's TestClient, that plugs requests directly into an ASGI application) cannot raise the raw exception in 500 cases. (We can make sure that the application is responsible for not returning 500 responses under a test-case environment, but that ends up being a framework-by-framework approach.)

Not expecting any immediate resolutions here, but raising for consideration.

Bug from version 3.2.0

I'm using channels in my application, when I updated asgiref I started having tests that fail, especially one where I have a websocket that sends updates after a model gets updated using a receiver on the post_save signal of a model using the async_to_sync method. The issue is with a serializer that generates a field using a SerializeMethodField, but this does not matter since the serializer is tested in many other methods and those tests all pass. The odd thing is that the traceback has a asgiref call, so I tried all the versions in the middle of the update, (I was going from 3.1.2 to 3.2.1), I discovered that the version that breaks things is 3.2.0, I updated all the other packages related to channels and the tests pass.

This is the traceback that got me thinking something was wrong with asgiref.
It says that that there are other 1 connections to the database.

Destroying test database for alias 'default'...
Traceback (most recent call last):
  File "/Users/anas/.envs/timing_services/lib/python3.7/site-packages/django/db/backends/utils.py", line 82, in _execute
    return self.cursor.execute(sql)
psycopg2.errors.ObjectInUse: database "test_timing_services" is being accessed by other users
DETAIL:  There is 1 other session using the database.


The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Applications/PyCharm.app/Contents/helpers/pycharm/django_test_manage.py", line 168, in <module>
    utility.execute()
  File "/Applications/PyCharm.app/Contents/helpers/pycharm/django_test_manage.py", line 142, in execute
    _create_command().run_from_argv(self.argv)
  File "/Users/anas/.envs/timing_services/lib/python3.7/site-packages/django/core/management/commands/test.py", line 23, in run_from_argv
    super().run_from_argv(argv)
  File "/Users/anas/.envs/timing_services/lib/python3.7/site-packages/django/core/management/base.py", line 323, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/Users/anas/.envs/timing_services/lib/python3.7/site-packages/django/core/management/base.py", line 364, in execute
    output = self.handle(*args, **options)
  File "/Applications/PyCharm.app/Contents/helpers/pycharm/django_test_manage.py", line 104, in handle
    failures = TestRunner(test_labels, **options)
  File "/Applications/PyCharm.app/Contents/helpers/pycharm/django_test_runner.py", line 255, in run_tests
    extra_tests=extra_tests, **options)
  File "/Applications/PyCharm.app/Contents/helpers/pycharm/django_test_runner.py", line 156, in run_tests
    return super(DjangoTeamcityTestRunner, self).run_tests(test_labels, extra_tests, **kwargs)
  File "/Users/anas/.envs/timing_services/lib/python3.7/site-packages/django/test/runner.py", line 639, in run_tests
    self.teardown_databases(old_config)
  File "/Users/anas/.envs/timing_services/lib/python3.7/site-packages/django/test/runner.py", line 583, in teardown_databases
    keepdb=self.keepdb,
  File "/Users/anas/.envs/timing_services/lib/python3.7/site-packages/django/test/utils.py", line 299, in teardown_databases
    connection.creation.destroy_test_db(old_name, verbosity, keepdb)
  File "/Users/anas/.envs/timing_services/lib/python3.7/site-packages/django/db/backends/base/creation.py", line 259, in destroy_test_db
    self._destroy_test_db(test_database_name, verbosity)
  File "/Users/anas/.envs/timing_services/lib/python3.7/site-packages/django/db/backends/base/creation.py", line 276, in _destroy_test_db
    % self.connection.ops.quote_name(test_database_name))
  File "/Users/anas/.envs/timing_services/lib/python3.7/site-packages/django/db/backends/utils.py", line 67, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "/Users/anas/.envs/timing_services/lib/python3.7/site-packages/django/db/backends/utils.py", line 76, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/Users/anas/.envs/timing_services/lib/python3.7/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/Users/anas/.envs/timing_services/lib/python3.7/site-packages/django/db/utils.py", line 89, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/Users/anas/.envs/timing_services/lib/python3.7/site-packages/django/db/backends/utils.py", line 82, in _execute
    return self.cursor.execute(sql)
django.db.utils.OperationalError: database "test_timing_services" is being accessed by other users
DETAIL:  There is 1 other session using the database.

I'm sure that there is no other resource accessing the database except for the test runner.
I'm using python 3.7, and the following is the serializer that should be called just once while using the debugger the method of the serializer that fails is called twice, the first it has the cached field needed while the second it doesn't.

This is the stack trace from the async_to_sync call

File "/Users/anas/Projects/timing_services/timing/receivers.py", line 17, in lap_post_save
    async_to_sync(update_training_websocket)(instance, layer)
  File "/Users/anas/.envs/timing_services/lib/python3.7/site-packages/asgiref/sync.py", line 110, in __call__
    return call_result.result()
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/concurrent/futures/_base.py", line 425, in result
    return self.__get_result()
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/concurrent/futures/_base.py", line 384, in __get_result
    raise self._exception
  File "/Users/anas/.envs/timing_services/lib/python3.7/site-packages/asgiref/sync.py", line 142, in main_wrap
    result = await self.awaitable(*args, **kwargs)
  File "/Users/anas/Projects/timing_services/timing/ws/v1/utils.py", line 66, in update_training_websocket
    lap_json = LapSerializer(lap).data
  File "/Users/anas/.envs/timing_services/lib/python3.7/site-packages/rest_framework/serializers.py", line 559, in data
    ret = super().data
  File "/Users/anas/.envs/timing_services/lib/python3.7/site-packages/rest_framework/serializers.py", line 261, in data
    self._data = self.to_representation(self.instance)
  File "/Users/anas/.envs/timing_services/lib/python3.7/site-packages/rest_framework/serializers.py", line 526, in to_representation
    ret[field.field_name] = field.to_representation(attribute)
  File "/Users/anas/.envs/timing_services/lib/python3.7/site-packages/rest_framework/fields.py", line 1873, in to_representation
    return method(value)
  File "/Users/anas/Projects/timing_services/timing/api/v1/serializers.py", line 98, in get_tag_possession
    user = obj.round.training.tag.user
  File "/Users/anas/.envs/timing_services/lib/python3.7/site-packages/django/db/models/fields/related_descriptors.py", line 178, in __get__
    rel_obj = self.get_object(instance)
  File "/Users/anas/.envs/timing_services/lib/python3.7/site-packages/django/db/models/fields/related_descriptors.py", line 145, in get_object
    return qs.get(self.field.get_reverse_related_filter(instance))
  File "/Users/anas/.envs/timing_services/lib/python3.7/site-packages/django/db/models/query.py", line 408, in get
    self.model._meta.object_name
timing.models.Training.DoesNotExist: Training matching query does not exist.

Consider using asyncio.run implementation in AsyncToSync

Python 3.7 introduces new asyncio.run function. Would be nice to backport its implementation for standalone loop in AsyncToSync. It adds an extra level of reliability by wrapping loop.run_until_complete with try/finally, cancelling pending tasks and closing async generators.
On a side note, it seems that loop.create_task should be preferred to asyncio.ensure_future in most cases, according to Guido.

On ASGI's double-callable interface.

This isn't intended as an actionable point right now, rather just a point of conversation. I think it's worth bringing up given that ASGI is pre-PEP at this point. (And that we might still just about be at a point where we could adapt things if we felt it worthwhile enough.)

I'm starting to wonder (again) if the double-callable structure if neccessarily a better trade-off than a single-callable. eg. the difference between:

ASGI as it stands:

class App:
    def __init__(self, scope):
        self.scope = scope

    async def __call__(self, receive, send):
        await send({
            'type': 'http.response.start',
            'status': 200,
            'headers': [
                [b'content-type', b'text/plain'],
            ]
        })
        await send({
            'type': 'http.response.body',
            'body': b'Hello, world!',
        })

An alternative interface:

async def app(scope, receive, send):
    await send({
        'type': 'http.response.start',
        'status': 200,
        'headers': [
            [b'content-type', b'text/plain'],
        ]
    })
    await send({
        'type': 'http.response.body',
        'body': b'Hello, world!',
    })

The double-callable does allow classes to directly implement the ASGI interface, and for class based endpoint implementations it ends up being simpler.

However it's more complex for function based endpoint implementations, for middleware implementation patterns, and for instantiate-to-configure application implementations.

In each of those cases you end up either with a closure around an inner function, or partially-bound functools.partial. Both of those cases make for less easy-to-understand concepts and more awkward flows.

E.g. A typical middleware pattern with ASGI...

class Middleware:
    def __init__(self, app, **middleware_config):
        self.app = app
        ...

    def __call__(self, scope):
        return functools.partial(self.asgi, scope=scope)

    async def asgi(self, receive, send, scope):
        ...

An alternative:

class Middleware:
    def __init__(self, app, **middleware_config):
        self.app = app
        ...

    async def __call__(self, scope, receive, send):
        ...

Similarly with instantiate-to-configure ASGI applications, you currently end up having to return a partially bound method:

StaticFiles as ASGI...

class StaticFiles:
    def __init__(self, directory):
        self.directory = directory
        ...

    def __call__(self, scope):
        return functools.partial(self.asgi, scope=scope)

    async def asgi(self, receive, send, scope):
        ...

Alternative:

class StaticFiles:
    def __init__(self, directory):
        self.directory = directory
        ...

    async def __call__(self, scope, receive, send):
        ...

The flip side with a single-callable is that you can't point directly at a class as an ASGI interface. For endpoint implementations, you end up needing to do something like this pattern instead...

class App:
    def __init__(self, scope, receive, send):
        self.scope = scope
        self.receive = receive
        self.send = send

    async def dispatch(self):
        await self.send({'type': 'http.response.start', ...})
        await self.send({'type': 'http.response.body', ...})

    @classmethod
    async def asgi(cls, scope, receive, send):
        instance = cls(scope, receive, send)
        await instance.dispatch()

# app = App.asgi

This all starts matters a little in Starlette, where ASGI is the interface all the way through - you end up with a more complex flow, and less easy-to-understand primitives. It also ends up with more indirect tracebacks.

(I noticed this in particular when considering how to improve the stack traces for 500 responses. I was hoping to delimit each frame as "Inside the middleware stack", "Inside the router", "Inside the endpoint" - that's easy to do if the traceback frames match up directly to the ASGI functions themselves, but it's not do-able if the frames are from a function that's been returned from another function.)

Anyways, not asking for any action on this neccessarily, but it's something I've been thinking over that I wanted to make visible while ASGI is still pre-PEP.

Remove dependency on `async_timeout`

Given that asgiref is now a core Django dependency, it would be nice to remove this one forced dependency that we have, especially as it is for a small, single-feature library.

I believe the licenses are compatible, so I am tempted to just vendor it.

Is a double callable necessary?

So, here’s a silly question.

What’s the benefit of using the double callable style, over using this instead...

async def asgi(scope, receive, send)

Not necessarily suggesting the change, but occurred to me that it’d be an obvious API, easier to explain, and provide more simple implementation patterns.

Aside from the obvious issues of making an API change, am I missing any reasons why it’d be problematic?

Is ASGI asyncio only?

Both Uvicorn and Quart have recent trio support suggestions, yet I think the spec is asyncio based. Is there a view on this? As noted in the Uvicorn pull request if ASGI is for asyncio/trio/others then I think the servers and applications need some way to agree and hence use the same system throughout.

Provide raw path / URI

The path currently gets unquoted / percent-decoded, which results in unrecoverable loss of information: a request to /foo/bar is the same as /foo%2Fbar.

With WSGI there seems to exist workarounds via RAW_URI / REQUEST_URI (e.g. in gunicorn, etianen/aiohttp-wsgi#17).

I think it would be nice to have raw_path officially in the spec.
I am not sure if it should be a unicode string, or bytes (to avoid another field later on).

(I do not think that raw_uri (path + query info) is necessary, since query_string is already given as bytes)

Current spec: https://asgi.readthedocs.io/en/latest/specs/www.html#id1
(Via encode/uvicorn#354)

Empty response error

Running into an empty response error. Safari, Firefox, chrome, curl all report similar issues. Daphne starts up normally:

Running in development mode.
Running in development mode.
Performing system checks...

System check identified no issues (0 silenced).
April 03, 2017 - 04:59:19
Django version 1.10.6, using settings 'server.settings'
Starting Channels development server at http://0.0.0.0:8000/
Channel layer default (asgiref.inmemory.ChannelLayer)
Quit the server with CONTROL-C.
2017-04-03 04:59:19,932 - INFO - worker - Listening on channels binding.robot-states, http.request, websocket.connect, websocket.disconnect, websocket.receive
2017-04-03 04:59:19,932 - INFO - worker - Listening on channels binding.robot-states, http.request, websocket.connect, websocket.disconnect, websocket.receive
2017-04-03 04:59:19,933 - INFO - worker - Listening on channels binding.robot-states, http.request, websocket.connect, websocket.disconnect, websocket.receive
2017-04-03 04:59:19,936 - INFO - worker - Listening on channels binding.robot-states, http.request, websocket.connect, websocket.disconnect, websocket.receive
2017-04-03 04:59:19,937 - INFO - server - Using busy-loop synchronous mode on channel layer
2017-04-03 04:59:19,938 - INFO - server - Listening on endpoint tcp:port=8000:interface=0.0.0.0

I'm running into this issue on (macOS/Ubuntu).

The version of Django/Daphne I'm running is (from pip freeze):

daphne==1.0.3
Django==1.10.6
django-cors-headers==2.0.2
django-extensions==1.7.7
django-filter==1.0.1
django-gulp-rev==0.2
django-jsonfield==1.0.1
django-rest-multiple-models==1.8.2
django-reversion==2.0.8
django-ses==0.8.2
django-solo==1.1.2
django-storages==1.5.2
djangorestframework==3.5.4
asgiref==1.1.1

Downgrading asgiref resolved the issue 1.0.0 .

Does the ASGI specification require asyncio?

The specification uses the words "coroutine" and "awaitable" to describe the application callable and the send and receive parameters.

Python (the language) itself provides coroutines, but does not prescribe a way to drive them. The specification imposes one restriction: "Applications are instantiated with a connection scope, and then run in an event loop", so let's assume the application coroutine function is driven by an event loop.

Unlike other languages (Javascript, Go), Python does not prescribe a event loop driver either. Indeed, there are multiple incompatible options: asyncio, twisted, curio, trio, probably more.

The asgiref helpers, the Daphne server, and the Channels project are all built around asyncio. However, the specification doesn't require asyncio, nor does it explicitly not require asyncio. This major detail is unclear.

Following are some points regarding the two options. For the sake of discussion, let's say Django v17 will be fully async and switch from WSGI to ASGI.

Require asyncio

  • This is the status quo.
  • The specification is interoperable - every server works with every application.
  • Django must be written using asyncio libraries, using another (arguably better) framework like trio is not possible.
  • Django can assume it is run with asyncio; for example, if it wants to sleep, it can freely do asyncio.sleep(1); if it wants an async postgresql driver, it can use aiopg; etc.
  • An ASGI server must run the application with asyncio; writing a server in a language other than Python is a little dubious.

Don't require asyncio

  • Servers, applications and middleware must be event loop agnostic (not really possible), or no longer be interoperable with all others.
  • Servers can use the event loop that is most suitable for them, not necessarily asyncio.
  • Applications can use whatever IO framework they want, not necessarily asyncio.

Specify access to transport information

Use case: I would like to access the peer certificate sent by a TLS client within my ASGI app.

Issue: As far as I can tell, no information about the transport (including TLS session information) is available to the App.

In a fork of uvicorn, I have made a patch which exposes the full transport object as part of the scope passed to the app.

diff --git a/uvicorn/protocols/http/h11_impl.py b/uvicorn/protocols/http/h11_impl.py
index 240cb35..cf5fd67 100644
--- a/uvicorn/protocols/http/h11_impl.py
+++ b/uvicorn/protocols/http/h11_impl.py
@@ -191,6 +191,7 @@ class H11Protocol(asyncio.Protocol):
                     "raw_path": raw_path,
                     "query_string": query_string,
                     "headers": self.headers,
+                    "transport": self.transport,
                 }

Within my app code, I use scope['transport'].get_extra_info("ssl_object").getpeercert(binary_form=True) to access the relevant information.

Feature request: Specify a way for applications to fetch information about the transport they are communicating over.

See also: encode/uvicorn#400

Clarifying HTTP exception behavior wrt. 500 responses.

Related: #54

Having worked through the implementation implications in both uvicorn and starlette, I'm now confident that the behaviors should be:

  • Servers should log the exception. The must check if a response has been started, and must send a 500 if no response has been sent. They should not issue any particular warning if an exception is raised once a response has already been sent, as this represents normal behavior for a well-behaved application, although they may issue an explicit warning in the case of an exception where the response has started but not completed.
  • Applications may choose to catch exceptions, and send an appropriate 500 page (eg. debug traceback or user-facing 500 error page) if the response has not yet started. If they do so then they must subsequently still raise the exception. This behavior enables the server to continue to log the traceback as desired, and makes the exception visible to any error-monitoring middleware.

Both Uvicorn's WSGI middleware and asgiref's WsgiToAsgi appear to have incorrect behavior here, in that they raise exc_info immediately in both cases, rather than sending an application's 500 response that is returned alongside with the exc_info and then raising the exception. (End result - the server gets to log the exception and returns a 500 response, but we don't get any application-defined 500 responses, either traceback or user-facing 500s)

An example of what I've used to test server/application interaction for resolving this wrt uvicorn's middleware... Taken from the WSGI spec

import uvicorn
import sys


def app(environ, start_response):
    try:
        # regular application code here
        raise RuntimeError()
        status = "200 Froody"
        response_headers = [("content-type", "text/plain")]
        start_response(status, response_headers)
        return [b"normal body goes here"]
    except:
        # XXX should trap runtime issues like MemoryError, KeyboardInterrupt
        #     in a separate handler before this bare 'except:'...
        status = "500 Oops"
        response_headers = [("content-type", "text/plain")]
        start_response(status, response_headers, sys.exc_info())
        return [b"error body goes here"]


if __name__ == '__main__':
    uvicorn.run(app, wsgi=True)

Here's my resolution to the issue in uvicorn - note that exc_info needs to be raised not at the end of start_response (since we don't have the response body yet) but after all of the returned byte-iterator has been sent to the send channel.

Documenting advised practice for ASGI task lifetime

One of the corner areas that I think the docs ought to mention at some point is wrt. task lifetimes.

Server implementations and framework codebase ought to ensure that if they're creating sub-tasks, then main ASGI task does not finally return until all children have completed. (Similar in trio to how sub-tasks are always created within the scope of a nursery.)

This helps guard against resource leaks and ensures servers are able to perform clean shutdown/restart. (eg. servers can hard-limit the maximum number of supported concurrent tasks in order to provide better quality of service under over-resourced loads, can better monitor durations and number of any run-after-response background tasks, and can ensure clean shutdown behavior.)

This isn't something that can be enforced in the spec, but I think it's an implementation concern that's worth covering.

ThreadPool execution should preserve contextvar context.

This is an implementation pattern, rather than something strictly for the ASGI spec.

Something that cropped up while implementing https://github.com/encode/sentry-asgi is that Python's ThreadPoolExecutor does not default to preserving the contextvars context, which really is what you want.

If you're using something like the Sentry SDK, then you needs contextvars in order to properly tie otherwise unrelated events together into, and you need it to continue working properly when threadpools are used.

Here's my fix in Starlette: encode/starlette#192

Other ASGI tools and frameworks will want to use something similar when running sync code in threadpools.

Links to core issue here: encode/starlette#191

Genorators using database_sync_to_async are called on the main thread

This is probably true for the other decorators as well. Same issue if you call the decorators directly.

threading.get_ident() is the same as the async code when the function uses yield. If you change it to return a list it has a different ID.

Heres the code

    @sync_to_async
    def function_foo(self):
        print(threading.get_ident(), 'function')
        return 4

    @sync_to_async
    def generator_foo(self):
        print(threading.get_ident(), 'generator')
        yield 4

    async def receive_json(self, content):
        print(threading.get_ident(), 'loop')
        print(await self.function_foo())
        for i in await self.generator_foo():
            print(i)

Heres the output


139945948370688 loop
139945849284352 function
4
139945948370688 generator
4

Is there a standard way to convert an ASGI application to WSGI?

In the same way asgiref.wsgi.WsgiToAsgi converts a WSGI application to ASGI, is there a standard way to convert an ASGI application to WSGI?

My use case is that I want to leverage FastAPI to create my REST api, but I'm working on a legacy flask application. If I can convert my ASGI app to WSGI, I can easily mount it on the legacy application using werkzeug.middleware.dispatcher.DispatcherMiddleware.

Assert that there is no message to receive using ApplicationCommunicator

What do you think about having a method to assert that there is no message to receive using the ApplicationCommunicator?

Right now I'm doing it like this (using Channels' WebsocketCommunicator):

    with pytest.raises(futures.TimeoutError):
        await communicator.receive_from()

I'd like to have something like

    assert communicator.new_messages(timeout=1) == 0
    # or maybe
    assert communicator.received_nothing(timeout=1)

Actually the timeout should be 0 by default. Otherwise the tests might take quite long if you have a lot of these checks.

Documenting ASGI.

A few suggestions on how we might more prominently pitch ASGI as an independent interface, rather than leaning too heavily on it's Django-initiated heritage:

  • Configure the readthedocs URL as this repo's website in the GitHub interface.
  • Have the first paragraph in the README be an ASGI preamble and link to the readthedocs site.
  • Link Channels documentation to the readthedocs site, not the repo. Ensure that "ASGI" text in any documents is linked.
  • Add [Edit on GitHub] links to https://asgi.readthedocs.io/en/latest/

Those are all small changes, that I think would make a bit of a difference to how it's presented.

Something bigger that could be considered:

  • Move asgiref out of the django github org, and into an asgiref org.

I don't think that necessarily needs to be a big deal in terms of governance. It's only used for testing, (HttpCommunicator/WebSocketCommunicator) rather than part of the core stack. To go a step further those docs could get pushed into ASGI ref, and have the channels docs introduce and give examples of using them, but defer the full API docs to ASGI ref.

Support AsyncToSync outside of an async event loop

Right now, AsyncToSync assumes it's been nested inside SyncToAsync and tries to find the outer event loop, and fails if run at the top level in sync code. It should, instead, make its own tiny event loop and run it to completion.

Provide the un-%-decoded path

As far as I can tell, ASGI follows the precedent set by WSGI in providing only a %-decoded string representation of the path. I believe this is a mistake. In particular, %2F is not a path separator by the URI RFC, but an application can't tell it apart from /.

I suggest either providing a raw_path byte string, or deserializing the path properly and passing it as a list.

Usage of errors inside recieve/send callbacks

When an application calls one of these callbacks, there's a chance that an error can be raised (if the server closes out from underneath the application, for example). The spec is unclear on how errors like these should be handled - if they should be handled by the application framework itself, or if they should bubble down into the main server application and be sent to the void in there; this is especially a problem because different ASGI servers can give out different types of errors that cannot reasonably be caught by every application. Maybe the errors coming from these callbacks should be defined as specific stdlib errors, or that no errors are allowed to be caught from recieve/send, or similar.

Clarify HTTP/2 and HTTP/3 pseudo-headers interaction with ASGI

In addition to "normal" HTTP headers, HTTP/2 and HTTP/3 use pseudo-headers starting with ":", some of which have natural mappings to ASGI scope variables :

  • :method: => this maps to scope["method"]
  • :path: => this maps to scope["raw_path"]
  • :scheme: => this maps to scope["scheme"]

A more problematic pseudo-header is :authority which is the equivalent of HTTP/1.1's Host header.

It would be good to clarify in the ASGI specs:

  • how should the :authority pseudo-header be conveyed by the server to the ASGI app: should servers append a synthetic Host header?

  • my feeling is that pseudo-headers should be purge from what is passed into scope["headers"], do we want to specify this in the specs?

Another interesting pseudo-header is the :protocol pseudo-header which is used to handshake WebSockets over HTTP/2, and very likely also over HTTP/3.

issue with PUT method

I am having issue with PUT method, I am tring to update fields by using PUT method in as HTTP request. UNfotuantely. message I get 'AsgiRequest' object has no attribute 'PUT'.

can you please give some hints or I am doing something wrong here. photo attached.

Screenshot from 2019-07-12 18-36-52
Screenshot from 2019-07-12 18-37-09

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.