--
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+unsubscribe@googlegroups.com.
To post to this group, send email to django-developers@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/db158350-60a9-4950-b11c-83f2f7a9221c%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
--
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+unsubscribe@googlegroups.com.
To post to this group, send email to django-developers@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/16f87149-6896-40a2-a1fd-028446a171a3%40googlegroups.com.
Making some more progress - https://github.com/tomchristie/uvicornI'll look into adding streaming HTTP request bodies next, and then into adding a websocket protocol.I see that the consumer interface is part of the channels API reference, rather than part of the ASGI spec.Is the plan to eventually include the consumer interface as part of the ASGI spec, and make it more clearly separate to channels?
> The ability to just plug in a callable/coroutine that gets called with messages as they arrive, and which provides you with channel names to listen on as an attributeThis sort of interface is exactly what I'm looking for, yes. From my POV the consumer callable is the primary bit of interface, rather than the channel layers.The consumer interface as it currently stands *is* sufficient for writing alternative frameworks against, but it's possible that there could be a more limited, refined interface to be had here. (Eg. limiting the message argument to being something that contains only serializable data and channel interfaces.)What would be great would be to have *just* the ASGI consumer callable interface pulled into Django core, and have channels be one of the possible ways of deploying against that.
> persist state somewhere outside the locals() of the function (as you'll still have separate coroutines for connect/receive/disconnect).I assume you'd use the name of the reply_channel as the key to the state there, right?Incidentally asyncio implementations have less of a requirement here, as you could write a single coroutine handler that's called on `connect`,and that can then can non-blocking reads to a channel for incoming data, and could support broadcast via an asyncio interface onto redis pub/sub.
> Any interface like this would literally just be "this function gets called with every event, but you can't listen for events on your own"Gotcha, yes. Although that wouldn't be the case with asyncio frameworks, since the channel reader would be a coroutine.Which makes for interesting design thinking if you want to provide an consumer interface that's suitable for both framework styles.
> HTTP request bodies being not an event, but a stream, and they're fine as they are deliberately on a special channel per request.Which it turns out actually makes the cooperative-tasks in a single-process implementation slightly awkward. (Doable, but rather more complex)For the purposes of my implementation it makes sense that if you've got a synchronous HTTP callable, then the server should buffer the incoming dataand only dispatch a single message. (Not a problem wrt. Django, since ASGIHandler ends up effectively doing that in any case.)
Figure I may as well show the sort of thing I'm thinking wrt. a more constrained consumer callable interface...* A callable, taking two arguments, 'message' & 'channels'* Message being JSON-serializable python primitives.* Channels being a dictionary of str:channel* Channel instances expose `.send()`, `.receive()` and `.name` interfaces.Extensions such as groups/statistics/flush would get expressed instead as channel interfaces,eg. a chat example...def ws_connected(message, channels):channels['reply'].send({'accept': True})channels['groups'].send({'group': 'chat','add': channels['reply'].name})def ws_receive(message, channels):channels['groups'].send({'group': 'chat','send': message['text']})def ws_disconnect(message, channels):channels['groups'].send({'group': 'chat','discard': channels['reply'].name})
My thinking at the moment is that there isn't any great way of supporting both asyncio and sync implementations under the same interface.If you're in asyncio land, it makes sense to *only* expose awaitable channel operations as you don't ever want to be able to block the task pool.I think the best you can really do is express two distinct modes of interface.sync: (callable interface, blocking send/receive interface)asyncio (coroutine interface, coroutine send/receive interface)Presumably the equivalent would be true of eg. twisted.(There's a couple of diff things you can do to bridge from the asyncio interface -> sync interface if that's useful)
> def handler(channel_layer, channel_name, message):Oh great! That's not a million miles away from what I'm working towards on my side.Are you planning to eventually introduce something like that as part of the ASGI spec?
> So is the channels object just a place to stuff different function handlers?No, it's just a different interface style onto exactly the same set of channel send/receive functionality.It's the difference between this:def hello_world(channel_layer, channel_name, message):...channel_layer.send(message['reply_channel'], response)And this:def hello_world(message, channels):...channels['reply'].send(response)
> Why not just pass the channel layer there and use the API on that directly? e.g.: channel_layer.group_send("chat", message["text"])With the groups example I wanted to demonstrate that we don't need extra "extensions" API introduced onto the channel layer in order to support a broadcast interface.
> then frameworks can do per-channel-name dispatch if they likeYup, the channel name is certainly necessary.A possible alternative is including that in the message itself, which I also quite like as an option because you more naturally end up with a nice symmetry of having the signature of the child routes match the signature of the parent.def app(message, channels):channel = message['channel']if channel == 'http.request':http_request(message, channels)elif channel == 'websocket.connect':websocket_connect(message, channels)elif ...That's what I'm rolling with for the moment, but it's not something I'm necessarily wedded to.
I've done a bunch more work towards all this, so it'd worth checking out https://github.com/tomchristie/uvicorn in it's current state. That should make the interface style I'm considering more clear. (Focusing on asyncio there, but could equally present an equivalent-but-syncronous interface.)
There are also now initial implementations for both WSGI-to-ASGI and ASGI-to-WSGI adapters.
--
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+unsubscribe@googlegroups.com.
To post to this group, send email to django-developers@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/2da48b17-b47f-42e3-b1bb-4a0e5a9b5709%40googlegroups.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+unsubscribe@googlegroups.com.
To post to this group, send email to django-developers@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/3fe6c9c3-8cd2-4e22-bb0e-82f28355f866%40googlegroups.com.
To unsubscribe from this group and stop receiving emails from it, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
> If we end up with one format for channel names and messages that is spread across two consumption forms (in-process async and cross-process channel layers), I think that would still be a useful enough standard and make a lot more people happy.Yes. I'm not sure if there aren't also reasonable ways to have standard non-asyncio frameworks deployed directly behind a server, too. Personally I'm looking at using your work to fill the gap of having no WSGI equivalent in the asyncio framework space.
> I wish there was a nicer way to achieve this than having send_async() and send_group_async() methods (etc.), but the only other alternative I see is having the methods all mirrored on an .async object, as in "channel_layer.async.send()". I'm not sure how I feel about that - thoughts?First thought is that the same issue likely applies to a bunch of the extension methods. That's a good argument in favour of keeping the API surface area as minimal as possible. I'm still keen on an API that only exposes data and channel send/receive primitives (and associated error handling), and simply doesn't allow for anything beyond that.The naming aspect has plenty of bikeshedding potential. My current preference probably sounds a little counter-intuitive, in that I'd be happy to see synchronous and asyncio version of the interface be two incompatible takes on the same underlying interface. ie. name them send() and receive() in *both* cases. You don't ever want to expose the sync version to an asyncio framework, or vice versa.Asyncio essentially introduces a language within a language, eg. it wouldn't be unreasonable to see an `apython` interpreter in the future, that fully replaced all the incompatible parts of the standard library with coroutine equivalents, so I wouldn't have a problem with treating it almost as two separate language implementations against the same spec. I don't think it's worth getting hung up on resolving this aspect just yet tho. Less contentious would be at least asking the question "should we treat the interface as asyncio-first (eg. send/send_blocking) or sync-first (send/send_async)?"