-
Notifications
You must be signed in to change notification settings - Fork 51
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tethys Async Websocket Consumer with Permission Checks #1012
Conversation
…mentation also stubbed out other custom methods just in case they are needed in the future
not uses the decorator for permissions added additional args for login_required and permissions_use_or
I updated this PR after reviewing some feedback. The main thing was keeping user experience consistent with regards to decorators. Users can now use the new capabilities just like a controller by supplying arguments in the decorator. An example is below: The methods for connecting and disconnecting have now been separated according to user authorization. So there is now a "authorized_connect", "unauthorized_connect", "authorized_disconnect", and "unauthorized_disconnect" so that developers can handle whatever use case they experience. For the backend, the solution was using a mixin and dynamically adding new methods from the mixin to the decorated class. This works for both async and sync websockets as well. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love the updated approach! Excited for this to get merged. I just request one minor change.
tethys_apps/utilities.py
Outdated
""" | ||
base_class_name = inspect.getmro(function_or_class)[1].__name__.lower() | ||
function_or_class_name = function_or_class.__name__.lower() | ||
if "async" in function_or_class_name or "async" in base_class_name: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While it is not very likely to be a problem, there is a small chance that checking for "async"
in the function_or_class_name
could be miss-leading. Suppose someone were to create a function called: do_something_in_a_non_async_way
that was not an async function?
I think it might be safer to just check all of the base classes:
async_base_classes = [cls for cls in inspect.getmro(function_or_class)[1:]. if "async" in cls.__name__.lower()]
if async_base_classes:
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose this approach isn't foolproof either. Someone could do something like:
from channels.consumer import SyncConsumer, AsyncConsumer
class AnAsyncConsumer(AsyncConsumer):
pass
class ASyncConsumer(SyncConsumer):
pass
You never know! 🤷
According to the Tethys Docs:
The consumer decorator functions largely the same way as the controller decorator except that it is used to decorate a consumer class, which must be a subclass of either channels.consumer.AsyncConsumer or channels.consumer.SyncConsumer
I'm hoping that I was smarter when I wrote that than I am now. If it is indeed true that a consumer class must be a subclass of channels.consumer.AsyncConsumer
then:
if issubclass(function_or_class, channels.consumer.AsyncConsumer):
consumer_mixin = TethysAsyncWebsocketConsumerMixin
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks Scott. Those are valid points. I like the final proposed solution. Simple and clean for sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although, maybe that won't work. I just looked at my implementation and I am using the
AsyncWebsocketConsumer
class from channels.generic.websocket
. That is also what is suggested in the tutorials for the websockets.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The AsyncWebsocketConsumer
is a subclass of AsyncConsumer
. I think the issubclass
will look at the whole parent chain, but I'm not sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from channels.generic.websocket import AsyncWebsocketConsumer
class MyConsumer(AsyncWebsocketConsumer):
groups = ["broadcast"]
from channels.consumer import AsyncConsumer
issubclass(MyConsumer, AsyncConsumer)
True
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just implemented this. I had to do it the opposite way and checked if it is a subclass of SyncConsumer
because the WebsocketConsumer
and AsyncWebsocketConsumer
are both subclasses of AsyncConsumer
apparently.
@swainn It looks like the Docker build is failing because the |
I think we decided that the docker build won't work because this is being merged from a fork. Once other checks pass then I approve merging. |
This PR is to resolve issue #1009.
The newly created TethysAsyncWebsocketConsumer class will replace the AsyncWebsocketConsumer class. The new TethysAsyncWebsocketConsumer class has the following capabilities:
The websocket tutorial docs have been updated as well to show the new capabilities.