/docs/source/training/extending/utilities.md

https://github.com/plone/guillotina · Markdown · 127 lines · 96 code · 31 blank · 0 comment · 0 complexity · f040f591c1fada59e6a5087382664915 MD5 · raw file

  1. # Async Utilities
  2. An async utility is an object instantiation with an async function that is run
  3. persistently on the asyncio event loop. It is useful for long running tasks.
  4. For our training, we're going to use an async utility with a queue to send
  5. messages to logged in users.
  6. Create a `utility.py` file and put the following code in it.
  7. ```python
  8. from guillotina.async_util import IAsyncUtility
  9. from guillotina.component import get_multi_adapter
  10. from guillotina.interfaces import IResourceSerializeToJsonSummary
  11. from guillotina.utils import get_authenticated_user_id, get_current_request
  12. import asyncio
  13. import orjson
  14. import logging
  15. logger = logging.getLogger('guillotina_chat')
  16. class IMessageSender(IAsyncUtility):
  17. pass
  18. class MessageSenderUtility:
  19. def __init__(self, settings=None, loop=None):
  20. self._loop = loop
  21. self._settings = {}
  22. self._webservices = []
  23. self._closed = False
  24. def register_ws(self, ws, request):
  25. ws.user_id = get_authenticated_user_id()
  26. self._webservices.append(ws)
  27. def unregister_ws(self, ws):
  28. self._webservices.remove(ws)
  29. async def send_message(self, message):
  30. summary = await get_multi_adapter(
  31. (message, get_current_request()),
  32. IResourceSerializeToJsonSummary)()
  33. await self._queue.put((message, summary))
  34. async def finalize(self):
  35. self._closed = True
  36. async def initialize(self, app=None):
  37. self._queue = asyncio.Queue()
  38. while not self._closed:
  39. try:
  40. message, summary = await asyncio.wait_for(self._queue.get(), 0.2)
  41. for user_id in message.__parent__.users:
  42. for ws in self._webservices:
  43. if ws.user_id == user_id:
  44. await ws.send_str(orjson.dumps(summary))
  45. except (RuntimeError, asyncio.CancelledError, asyncio.TimeoutError):
  46. pass
  47. except Exception:
  48. logger.warning(
  49. 'Error sending message',
  50. exc_info=True)
  51. await asyncio.sleep(1)
  52. ```
  53. Async utilities must implement an `initialize` method that performs the async
  54. task. In our case, it is creating a queue and waiting to process messages
  55. in the queue.
  56. We will use this to send messages to registered websockets.
  57. Like all other configured modules, make sure this file is scanned
  58. by the packages `__init__.py` file.
  59. Additionally, async utilities need to also be configured in `__init__.py`:
  60. ```python
  61. app_settings = {
  62. "load_utilities": {
  63. "guillotina_chat.message_sender": {
  64. "provides": "guillotina_chat.utility.IMessageSender",
  65. "factory": "guillotina_chat.utility.MessageSenderUtility",
  66. "settings": {}
  67. },
  68. }
  69. }
  70. ```
  71. ## Sending messages
  72. We'll need to add another event subscriber to the `subscribers.py` file
  73. in order for the utility to know to send out new messages to registered
  74. web services.So your `subscribers.py` file will now look like:
  75. ```
  76. from guillotina import configure
  77. from guillotina.component import get_utility
  78. from guillotina.interfaces import IObjectAddedEvent, IObjectModifiedEvent, IPrincipalRoleManager
  79. from guillotina.utils import get_authenticated_user_id, get_current_request
  80. from guillotina_chat.content import IConversation, IMessage
  81. from guillotina_chat.utility import IMessageSender
  82. @configure.subscriber(for_=(IConversation, IObjectAddedEvent))
  83. @configure.subscriber(for_=(IConversation, IObjectModifiedEvent))
  84. async def container_added(conversation, event):
  85. user_id = get_authenticated_user_id()
  86. if user_id not in conversation.users:
  87. conversation.users.append(user_id)
  88. manager = IPrincipalRoleManager(conversation)
  89. for user in conversation.users:
  90. manager.assign_role_to_principal(
  91. 'guillotina_chat.ConversationParticipant', user)
  92. @configure.subscriber(for_=(IMessage, IObjectAddedEvent))
  93. async def message_added(message, event):
  94. utility = get_utility(IMessageSender)
  95. await utility.send_message(message)
  96. ```