|
8 | 8 | """ |
9 | 9 |
|
10 | 10 | import time |
| 11 | +import asyncio |
11 | 12 | from typing import Any, Callable, Dict, List, TYPE_CHECKING |
12 | 13 | from functools import partial |
13 | 14 | import re |
@@ -117,6 +118,25 @@ def __init__(self, *args, **kwargs): |
117 | 118 |
|
118 | 119 | self.event_loop.create_task(self._start_observing_global_awareness()) |
119 | 120 |
|
| 121 | + def _invoke_callback(self, callback: Callable, *args, **kwargs) -> None: |
| 122 | + """ |
| 123 | + Invoke a callback, handling both sync and async callbacks. |
| 124 | +
|
| 125 | + Args: |
| 126 | + callback: The callback function to invoke |
| 127 | + *args: Positional arguments to pass to the callback |
| 128 | + **kwargs: Keyword arguments to pass to the callback |
| 129 | + """ |
| 130 | + try: |
| 131 | + if asyncio.iscoroutinefunction(callback): |
| 132 | + # For async callbacks, schedule them as a task |
| 133 | + self.event_loop.create_task(callback(*args, **kwargs)) |
| 134 | + else: |
| 135 | + # For sync callbacks, call directly |
| 136 | + callback(*args, **kwargs) |
| 137 | + except Exception as e: |
| 138 | + self.log.error(f"Error invoking callback: {e}") |
| 139 | + |
120 | 140 | async def _room_id_from_path(self, path: str) -> str | None: |
121 | 141 | room_id = await self.parent._room_id_from_path(path) |
122 | 142 | return room_id |
@@ -531,10 +551,7 @@ def _notify_notebook_activity_observers( |
531 | 551 | for observer_id in observer_ids: |
532 | 552 | if observer_id in self._observer_callbacks: |
533 | 553 | callback = self._observer_callbacks[observer_id]["callback"] |
534 | | - try: |
535 | | - callback(username, prev_active_cell, notebook_path) |
536 | | - except Exception as e: |
537 | | - self.log.error(f"Notebook activity observer error for {username}: {e}") |
| 554 | + self._invoke_callback(callback, username, prev_active_cell, notebook_path) |
538 | 555 |
|
539 | 556 |
|
540 | 557 | def connect_chat(self, room_id: str, ychat: "YChat") -> None: |
@@ -635,58 +652,43 @@ def _notify_slash_cmd_observers(self, room_id: str, message: Message, clean_comm |
635 | 652 | for registered_pattern, callbacks in room_observers.items(): |
636 | 653 | if matches_pattern(clean_command, registered_pattern): |
637 | 654 | for callback in callbacks: |
638 | | - try: |
639 | | - callback(room_id, clean_command, message) |
640 | | - except Exception as e: |
641 | | - self.log.error(f"Slash command observer error for pattern '{registered_pattern}': {e}") |
| 655 | + self._invoke_callback(callback, room_id, clean_command, message) |
642 | 656 |
|
643 | 657 | def _notify_chat_init_observers(self, room_id: str, ychat: "YChat") -> None: |
644 | 658 | """Notify all new chat observers.""" |
645 | 659 | for callback in self.chat_init_observers: |
646 | | - try: |
647 | | - callback(room_id, ychat) |
648 | | - except Exception as e: |
649 | | - self.log.error(f"New chat observer error for {room_id}: {e}") |
| 660 | + self._invoke_callback(callback, room_id, ychat) |
650 | 661 |
|
651 | 662 | def _notify_msg_observers(self, room_id: str, message: Message) -> None: |
652 | 663 | """Notify all message observers.""" |
653 | 664 | callbacks = self.chat_msg_observers.get(room_id, []) |
654 | 665 | for callback in callbacks: |
655 | | - try: |
656 | | - callback(room_id, message) |
657 | | - except Exception as e: |
658 | | - self.log.error(f"Message observer error for {room_id}: {e}") |
| 666 | + self._invoke_callback(callback, room_id, message) |
659 | 667 |
|
660 | 668 | def _on_chat_reset(self, room_id, ychat: "YChat") -> None: |
661 | 669 | """ |
662 | 670 | Method to call when the YChat undergoes a document reset, e.g. when the |
663 | 671 | `.chat` file is modified directly on disk. |
664 | | - |
| 672 | +
|
665 | 673 | NOTE: Document resets will only occur when `jupyter_server_documents` is |
666 | 674 | installed. |
667 | 675 | """ |
668 | 676 | self.log.warning(f"Detected `YChat` document reset in room '{room_id}'.") |
669 | 677 | self.active_chats[room_id] = ychat |
670 | 678 | for callback in self.chat_reset_observers: |
671 | | - try: |
672 | | - callback(room_id, ychat) |
673 | | - except Exception as e: |
674 | | - self.log.error(f"Reset chat observer error for {room_id}: {e}") |
| 679 | + self._invoke_callback(callback, room_id, ychat) |
675 | 680 |
|
676 | 681 | def _on_notebook_reset(self, room_id, ydoc: YBaseDoc) -> None: |
677 | 682 | """ |
678 | 683 | Method to call when the YDoc undergoes a document reset, e.g. when the |
679 | 684 | `.ipynb` file is modified directly on disk. |
680 | | - |
| 685 | +
|
681 | 686 | NOTE: Document resets will only occur when `jupyter_server_documents` is |
682 | 687 | installed. |
683 | 688 | """ |
684 | 689 | self.log.warning(f"Detected `YDoc` document reset in room '{room_id}'.") |
685 | 690 | for callback in self.notebook_reset_observers: |
686 | | - try: |
687 | | - callback(room_id, ydoc) |
688 | | - except Exception as e: |
689 | | - self.log.error(f"Reset notebook observer error for {room_id}: {e}") |
| 691 | + self._invoke_callback(callback, room_id, ydoc) |
690 | 692 |
|
691 | 693 | def _cleanup_rooms(self) -> None: |
692 | 694 | """Clean up all room trackers and their subscriptions.""" |
|
0 commit comments