bedrock.external.decorators.websocket

 1import functools  # pragma: unit
 2
 3from bedrock.external.websocket import broadcast_websocket_message  # pragma: unit
 4
 5
 6class _WebsocketBroadcast:  # pragma: unit
 7    """
 8    The intended behaviour here is for the endpoint to receive a new attribute, __uses_websockets__, which is detected
 9    in our build scripts to determine whether to spin up websocket infrastructure.
10
11    When another decorator wraps a method before this decorator executes, it loses ownership of the endpoint class,
12    and hence cannot modify/add the __uses_websockets__ attribute.
13    """
14
15    def __init__(self, fn, topics: list[str]):
16        self.fn = fn
17        self.topics = topics
18        functools.update_wrapper(self, fn)
19
20    def __set_name__(self, owner, name):
21        owner.__uses_websockets__ = True
22        owner.__websocket_topics__ = list(set(owner.__websocket_topics__) | set(self.topics)) if hasattr(owner,
23                                                                                                         '__websocket_topics__') \
24            else self.topics
25
26    def __call__(self, *args, **kwargs):
27        return self.fn(*args, **kwargs)
28
29    def __get__(self, instance, owner=None):
30        if instance is None:
31            return self
32
33        @functools.wraps(self.fn)
34        def wrapper(*args, **kwargs):
35            status, content = self.fn(instance, *args, **kwargs)
36
37            if 200 <= int(status) < 300 and self.topics:
38                related_model = getattr(instance, "related_model", None)
39                broadcast_websocket_message(content, self.topics, related_model)
40
41            return status, content
42
43        return wrapper
44
45
46def websocket_broadcast(topics: str | list[str]):  # pragma: unit
47    """
48    WARNING: When multiple decorators are used to wrap an endpoint method, you need this decorator to be executed last.
49    To do this you must place it visually at the top of the list of decorator you use for a method.
50
51    For example:
52    ```python
53    @websocket_broadcast("entity") <- Place decorator at the top here (executed last)
54    @protected()
55    def method_a():
56        pass
57    ```
58
59    This behaviour is being verified in `tests/unit/external/decorators/test_websocket.py`
60    """
61    topic_list = [] if not topics else [topics] if isinstance(topics, str) else topics
62
63    def decorator(fn):
64        return _WebsocketBroadcast(fn, topic_list)
65
66    return decorator
def websocket_broadcast(topics: str | list[str]):
47def websocket_broadcast(topics: str | list[str]):  # pragma: unit
48    """
49    WARNING: When multiple decorators are used to wrap an endpoint method, you need this decorator to be executed last.
50    To do this you must place it visually at the top of the list of decorator you use for a method.
51
52    For example:
53    ```python
54    @websocket_broadcast("entity") <- Place decorator at the top here (executed last)
55    @protected()
56    def method_a():
57        pass
58    ```
59
60    This behaviour is being verified in `tests/unit/external/decorators/test_websocket.py`
61    """
62    topic_list = [] if not topics else [topics] if isinstance(topics, str) else topics
63
64    def decorator(fn):
65        return _WebsocketBroadcast(fn, topic_list)
66
67    return decorator

WARNING: When multiple decorators are used to wrap an endpoint method, you need this decorator to be executed last. To do this you must place it visually at the top of the list of decorator you use for a method.

For example:

@websocket_broadcast("entity") <- Place decorator at the top here (executed last)
@protected()
def method_a():
    pass

This behaviour is being verified in tests/unit/external/decorators/test_websocket.py