Hey Andrew,

Thanks for looking through all that, and for the reply.

I like the simplicity of your updated examples. I started to make a 
counter-example to suggest that `include` be inside of a `route` 
(https://gist.github.com/orokusaki/c0c934013ee7911071ef).

But then, as I thought through this, I think I like your example almost* 
exactly like it is, but I'm afraid there might be a problem:

Given your example, any message how would a 'websocket.connect' message at 
the path `notifications/foo/` be routed, giving this example:

routing = [
    route("websocket.connect", ChatConnect),
    include(path="^notifications", "notification.routing.routing"),
]

Given that the chat route is the first match, wouldn't the notifications 
route never be used? Would path need to be required, so that the matching 
would be similar to `urlpatterns`? Otherwise, we're allowing routing based 
on channel name or path again? Maybe I'm misunderstanding.

On Friday, March 18, 2016 at 10:58:41 AM UTC-4, Andrew Godwin wrote:
>
> Hi Vincent,
>
> I think you make some good points - in particular, I think includes are 
> probably a necessity, as you say. However, I disagree we should let people 
> have either channel->url or url->channel; I'd like us not to try and give 
> people multiple ways to do things if we can help it, especially in an 
> ecosystem of third-party apps.
>
> You can't rename routing.py to channels.py, however, as then the import 
> names will clash with the `channels` third-party app if the user hasn't 
> turned on absolute imports.
>
> I'd build on your proposal and say the following:
>
> ---
>
> Routing should be a list of pattern matches rather than a dict, so you can 
> spread it across multiple includes sensibly. Each entry in the list will be 
> a route() match object that takes a channel name (it has to be a name, no 
> regular expressions here) as a required argument, and an optional `path` 
> argument that's a regular expression matched against the incoming message 
> `path`.
>
> In fact, the routing will be generic, so you can regex against any primary 
> message attribute you know will be present - for example, `method` for 
> HTTP, or in the SMS example, by country code. It'll work like URL routing 
> too and let you capture groups to be passed to the consumer (but only by 
> name, to avoid confusion when a user matches against multiple message 
> attributes and there's no defined ordering).
>
> The include() function will take any keyword argument and do prefixing 
> like it does now for URLs.
>
> Example:
>
> routing = [
>     route("http.request", ViewConsumer),
>     route("websocket.connect", path="^chat/(?P<room>[^/]+)/$", 
> ChatConnect),
>     route("sms.receive", sender="+44(?P<local_number>[0-9]+)$", 
> UkSmsConsumer),
>     include(path="^notifications", "notification.routing.routing"),
> ]
>
> It also means that the simple example case stays quite readable:
>
> routing = [
>     route("websocket.connect", ws_connect),
>     route("websocket.receive", ws_receive),
>     route("websocket.disconnect", ws_disconnect),
> ]
>
> We can also have channels upconvert the old dict format into this 
> trivially to preserve all the existing code and examples, like Jacob's 
> article.
>
> Andrew
>
> On Fri, Mar 18, 2016 at 3:48 AM, Vincent <thebe...@gmail.com <javascript:>
> > wrote:
>
>> Jacob, Florian, Andrew,
>>
>> I've spent the last 200 minutes thinking this through and writing, and 
>> here's what I've come up with:
>>
>> https://gist.github.com/orokusaki/c67d46965a4ebeb3035a
>>
>> Below are the full contents of that Gist (but I recommend the Gist for 
>> formatting).
>>
>> I also created https://github.com/andrewgodwin/channels/issues/87 last 
>> weekend (re: your static files point above, Jacob).
>>
>> ### Problem
>>
>>   1. Channel / URL routing is 2-dimensional (compared with 1D URL 
>> handling in Django)
>>   2. This creates a chicken / egg problem between channel name and URL 
>> path
>>   2. That's illustrated by the discrepancy between Florian's URL -> 
>> channel and Andrew's channel -> URL
>>   3. Put a Channel on the Y axis and URL are on the X axis, and the 
>> intersection is where a Consumer comes into play
>>
>> ### Considerations
>>
>> Here are some design considerations for my API proposal:
>>
>>   1. Includes - because nobody wants to all of their channels / URLs in a 
>> project-level module
>>   2. URL generation for channels with URLs
>>   3. The following duplicate include functionality works perfectly in 
>> Django currently with URLs
>>   4. `urlpatterns` are kepts in `urls.py`
>>   5. So, I've renamed `routing.py` to `channels.py` - after all, we're 
>> defining `channelpatterns`
>>   6. Either channel name OR URL has to come first, so that we don't need 
>> a 2D graph in Python for routing consumers
>>
>> Project-level channels:
>>
>>     # channels.py
>>     channelpatterns = [
>>         channel(r'^websocket\.', include('chat.channels', 
>> namespace='chat')),
>>         channel(r'^websocket\.', include('game.channels', 
>> namespace='game')),
>>         channel(
>>             r'^websocket',
>>             name='active-visitors',
>>             urls=[
>>                 url(r'^active-visitors/$', VisitorCount.as_consumer()),
>>             ]
>>         ),
>>     ]
>>
>> App-level channels:
>>
>>     # game/channels.py
>>     channelpatterns = [
>>         channel(
>>             r'^receive',
>>             name='receive',
>>             urls=[
>>                 url(r'^game/moves/up/$', 
>> Move.as_consumer(direction='up'), name='move-up'),
>>                 url(r'^game/moves/down/$', 
>> Move.as_consumer(direction='down'), name='move-down'),
>>             ]
>>         ),
>>     ]
>>
>> Channel routing would be handled similar to URLs in Django.
>>
>> Given the above, getting the Channel URL for "moving up" in game could 
>> be: `{% channel 'game:receive:move-up' %}`
>>
>> Here's an example of `websocket.connect` @ `/game/moves/up/`
>>
>>   1. Encounter first match (the WebSocket channel named `chat`)
>>   2. Include `chat.channels`
>>   3. Determine there is no match
>>   4. Encounter second match (the WebSocket channel named `game`)
>>   5. Include `game.channels`
>>   6. Encounter first URL match (the `/game/moves/up/` pattern)
>>   7. Delegate to `Move` consumer with default `direction='up'`
>>
>> But wait, there's more :)
>>
>> Since Channel + URL is 2-dimensional, I propose we allow `include` at the 
>> `url` level and at the `channel` level.
>>
>>     # channels.py
>>     channelpatterns = [
>>         channel(
>>             r'^websocket\.receive$',
>>             urls=[
>>                 url(r'^game/', include('chat.channels', 
>> namespace='game')),
>>             ]
>>         ),
>>     ]
>>
>> Then:
>>
>>     # game/channels.py
>>     channelpatterns = [
>>         channel(
>>             r'^$',
>>             name='moves',
>>             urls=[
>>                 url(r'^moves/up$', Move.as_consumer(direction='up'), 
>> name='move-up'),
>>                 url(r'^moves/down$', Move.as_consumer(direction='down'), 
>> name='move-down'),
>>             ]
>>         )
>>     ]
>>
>> Since I'm allowing `include` in a `channel` or any of its `urls`, either 
>> breadth-first search (giving URls / URL includes precedence) or depth-first 
>> search (giving channel includes precedence) could be used. I'd argue for 
>> breadth-first.
>>
>>
>> On Thursday, March 17, 2016 at 12:42:05 PM UTC-4, Jacob Kaplan-Moss wrote:
>>>
>>> Hi folks (and especially Andrew):
>>>
>>> I've just completed writing an example Channels app [1] for an article 
>>> about Channels [2]. Overall it was a super-pleasant experience: Channels 
>>> seems pretty solid, the APIs make sense to me, and I couldn't be more 
>>> excited about the new things this'll let me do! 
>>>
>>> In the interests of making this thing as solid as possible before we 
>>> merge it into Django, I do have some feedback on some of the hiccups I 
>>> encountered. Roughly in order of severity (as I perceive it), they are:
>>>
>>> 1. Debugging errors:
>>>
>>> I found debugging errors that happen in a consumer to be *really* 
>>> difficult -- errors mostly presented as things just silently not working. 
>>> It took a ton of messing around with logging setups before I could get 
>>> anything of use dumped to the console. This isn't strictly a Channels issue 
>>> (I've noted similar problems with AJAX views and errors in Celery tasks), 
>>> but I think Channels sorta brings the issue to a head. 
>>>
>>> I think we need some better defaults, and simple, clear documentation, 
>>> to make sure that exceptions go somewhere useful.
>>>
>>> 2. Static files:
>>>
>>> I had trouble getting static files served. I'm used to using Whitenoise (
>>> http://whitenoise.evans.io/en/stable/) for small-to-medium-ish sites 
>>> that don't need a proper static server, but of course it doesn't work with 
>>> Channels since Channels doesn't use WSGI! I found the (undocumented) 
>>> StaticFilesConsumer (
>>> https://github.com/jacobian/channels-example/blob/master/chat/routing.py#L5-L8),
>>>  
>>> but that feels less than ideal.
>>>
>>> I think this might be an opportunity, however. If Daphne learns how to 
>>> serve static files (perhaps via optional integration with Whitenoise?), 
>>> this would actually make static media in Django a bit easier by default.
>>>
>>> [I would be happy to work on this if I get a thumbsup.]
>>>
>>> 3. WebSocket routing:
>>>
>>> Channels routes all WebSocket connections to a single set of consumers 
>>> (the `websocket.*` consumers). This means that if you want multiple 
>>> WebSocket URLs in a single app you need to manually parse the path. And, to 
>>> make things more complicated, you only get the WebSocket path passed in the 
>>> connection message, so you have to also use a channel session to keep track.
>>>
>>> This feels like a distinct step back from the URL routing we already 
>>> have in Django, and it was surprising to me to have to do this by hand. It 
>>> definitely felt like Channels is missing some sort of WebSocket URL router. 
>>>
>>> I had a brief chat with Andrew, who indicates that he'd planned for this 
>>> to be a post-1.0 feature. I'm not sure I agree - it feels pretty 
>>> fundamental - but I'd like to hear other thoughts. 
>>>
>>> [This is another thing I'd be interested in working on, assuming a 
>>> thumbs.]
>>>
>>> ---
>>>
>>> Has anyone else here played with Channels? Are there other things I'm 
>>> missing that might need to be included before we merge this?
>>>
>>> Jacob
>>>
>>> [1] https://github.com/jacobian/channels-example
>>>
>>> [2] 
>>> https://blog.heroku.com/archives/2016/3/17/in_deep_with_django_channels_the_future_of_real_time_apps_in_django
>>> ?
>>>
>> -- 
>> 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-develop...@googlegroups.com <javascript:>.
>> To post to this group, send email to django-d...@googlegroups.com 
>> <javascript:>.
>> 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/747fcf54-ff68-470c-ba0d-9e52b4cb85f6%40googlegroups.com
>>  
>> <https://groups.google.com/d/msgid/django-developers/747fcf54-ff68-470c-ba0d-9e52b4cb85f6%40googlegroups.com?utm_medium=email&utm_source=footer>
>> .
>>
>> 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+unsubscr...@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/e0ccf0b7-c7d9-4f29-b37e-6513b857f490%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to