From 8c355a9e1852e63513340fa55186fdd0ccf92e04 Mon Sep 17 00:00:00 2001 From: Martastain Date: Fri, 2 Aug 2024 12:56:54 +0200 Subject: [PATCH 1/5] feat: reusable events (w.i.p.) --- ayon_server/events/eventstream.py | 67 ++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/ayon_server/events/eventstream.py b/ayon_server/events/eventstream.py index 6f1b2c0a..f1f82b42 100644 --- a/ayon_server/events/eventstream.py +++ b/ayon_server/events/eventstream.py @@ -36,6 +36,7 @@ async def dispatch( payload: dict[str, Any] | None = None, finished: bool = True, store: bool = True, + reuse: bool = False, recipients: list[str] | None = None, ) -> str: """ @@ -46,6 +47,9 @@ async def dispatch( store: whether to store the event in the database + reuse: + allow to reuse an existing event with the same hash + recipients: list of user names to notify via websocket (None for all users) """ @@ -79,32 +83,59 @@ async def dispatch( ) if store: - query = SQLTool.insert( - table="events", - id=event.id, - hash=event.hash, - sender=event.sender, - topic=event.topic, - project_name=event.project, - user_name=event.user, - depends_on=depends_on, - status=status, - description=description, - summary=event.summary, - payload=event.payload, - ) + query = """ + INSERT INTO + events ( + id, hash, sender, topic, project_name, user_name, + depends_on, status, description, summary, payload + ) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) + """ + if reuse: + query += """ + ON CONFLICT (hash) DO UPDATE SET + id = EXCLUDED.id, + sender = EXCLUDED.sender, + topic = EXCLUDED.topic, + project_name = EXCLUDED.project_name, + user_name = EXCLUDED.user_name, + depends_on = EXCLUDED.depends_on, + status = EXCLUDED.status, + description = EXCLUDED.description, + summary = EXCLUDED.summary, + payload = EXCLUDED.payload, + updated_at = NOW() + """ try: - await Postgres.execute(*query) + await Postgres.execute( + query, + event.id, + event.hash, + event.sender, + event.topic, + event.project, + event.user, + event.depends_on, + status, + description, + event.summary, + event.payload, + ) except Postgres.ForeignKeyViolationError as e: raise ConstraintViolationException( "Event depends on non-existing event", ) from e except Postgres.UniqueViolationError as e: - raise ConstraintViolationException( - "Event with same hash already exists", - ) from e + if reuse: + raise ConstraintViolationException( + "Unable to reuse the event. Another event depends on it", + ) from e + else: + raise ConstraintViolationException( + "Event with same hash already exists", + ) from e depends_on = ( str(event.depends_on).replace("-", "") if event.depends_on else None From 56c2f9e14569db7a5573868bd8f3321db532eea1 Mon Sep 17 00:00:00 2001 From: Martastain Date: Thu, 5 Dec 2024 09:29:25 +0100 Subject: [PATCH 2/5] chore: add deprecation notice, clean-up --- api/events/events.py | 3 +-- ayon_server/events/__init__.py | 17 +++++++++++++++++ ayon_server/events/eventstream.py | 5 +++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/api/events/events.py b/api/events/events.py index 3fcfb381..d2ea6867 100644 --- a/api/events/events.py +++ b/api/events/events.py @@ -193,6 +193,5 @@ async def delete_event(user: CurrentUser, event_id: EventID) -> EmptyResponse: if not user.is_admin: raise ForbiddenException("Not allowed to delete events") - await Postgres.execute("DELETE FROM events WHERE id = $1", event_id) - + await EventStream.delete(event_id) return EmptyResponse() diff --git a/ayon_server/events/__init__.py b/ayon_server/events/__init__.py index 56d2aa21..fbd3dbcb 100644 --- a/ayon_server/events/__init__.py +++ b/ayon_server/events/__init__.py @@ -26,7 +26,15 @@ async def dispatch_event( payload: dict[str, Any] | None = None, finished: bool = True, store: bool = True, + reuse: bool = False, ) -> str: + """Dispatch an event to the event stream. + + This function is deprecated and is provided only to + maintain backwards compatibility with older addons. + + Use `EventStream.dispatch` instead. + """ return await EventStream.dispatch( topic=topic, sender=sender, @@ -40,6 +48,7 @@ async def dispatch_event( payload=payload, finished=finished, store=store, + reuse=reuse, ) @@ -58,6 +67,14 @@ async def update_event( store: bool = True, retries: int | None = None, ) -> bool: + """Update an event in the event stream. + + This function is deprecated and is provided only to + maintain backwards compatibility with older addons. + + Use `EventStream.update` instead. + + """ return await EventStream.update( event_id=event_id, sender=sender, diff --git a/ayon_server/events/eventstream.py b/ayon_server/events/eventstream.py index 46b093ab..cdc0e268 100644 --- a/ayon_server/events/eventstream.py +++ b/ayon_server/events/eventstream.py @@ -315,3 +315,8 @@ async def get(cls, event_id: str) -> EventModel: if event is None: raise NotFoundException("Event not found") return event + + @classmethod + async def delete(cls, event_id: str) -> None: + await Postgres.execute("DELETE FROM events WHERE id = $1", event_id) + return None From c0d1a42faaa6fa8e3140501b6a4a000cd5654904 Mon Sep 17 00:00:00 2001 From: Martastain Date: Thu, 5 Dec 2024 09:53:55 +0100 Subject: [PATCH 3/5] feat: use reusable events for addon.install as P.O.C --- api/addons/install.py | 39 +++++++++---------------------- ayon_server/api/system.py | 17 ++++++-------- ayon_server/events/eventstream.py | 1 + 3 files changed, 19 insertions(+), 38 deletions(-) diff --git a/api/addons/install.py b/api/addons/install.py index 932939b1..1e19a130 100644 --- a/api/addons/install.py +++ b/api/addons/install.py @@ -14,6 +14,7 @@ from ayon_server.installer.addons import get_addon_zip_info from ayon_server.lib.postgres import Postgres from ayon_server.types import Field, OPModel +from ayon_server.utils import hash_data from .router import router @@ -68,34 +69,16 @@ async def upload_addon_zip_file( # and contains an addon. If it doesn't, an exception is raised before # we reach this point. - # Let's check if we installed this addon before - - query = """ - SELECT id FROM events - WHERE topic = 'addon.install' - AND summary->>'name' = $1 - AND summary->>'version' = $2 - LIMIT 1 - """ - - res = await Postgres.fetch(query, zip_info.name, zip_info.version) - if res: - event_id = res[0]["id"] - await EventStream.update( - event_id, - description="Reinstalling addon from zip file", - summary=zip_info.dict(exclude_none=True), - status="pending", - ) - else: - # If not, dispatch a new event - event_id = await EventStream.dispatch( - "addon.install", - description=f"Installing addon {zip_info.name} {zip_info.version}", - summary=zip_info.dict(exclude_none=True), - user=user.name, - finished=False, - ) + event_hash = hash_data(["addon.install", zip_info.name, zip_info.version]) + event_id = await EventStream.dispatch( + "addon.install", + hash=event_hash, + description=f"Installing addon {zip_info.name} {zip_info.version}", + summary=zip_info.dict(exclude_none=True), + user=user.name, + finished=False, + reuse=True, + ) # Start the installation in the background # And return the event ID to the client, diff --git a/ayon_server/api/system.py b/ayon_server/api/system.py index 052a3e27..1486df0e 100644 --- a/ayon_server/api/system.py +++ b/ayon_server/api/system.py @@ -4,7 +4,6 @@ from nxtools import logging from ayon_server.events import EventStream -from ayon_server.exceptions import ConstraintViolationException from ayon_server.lib.postgres import Postgres @@ -40,15 +39,13 @@ async def require_server_restart( if reason is None: reason = "Server restart is required" - try: - await EventStream.dispatch( - topic, hash=topic, description=reason, user=user_name - ) - except ConstraintViolationException: - # we don't need to do anything here. If the event fails, - # it means the event was already triggered, and the server - # is pending restart. - pass + await EventStream.dispatch( + topic, + hash=topic, + description=reason, + user=user_name, + reuse=True, + ) async def clear_server_restart_required(): diff --git a/ayon_server/events/eventstream.py b/ayon_server/events/eventstream.py index cdc0e268..01251482 100644 --- a/ayon_server/events/eventstream.py +++ b/ayon_server/events/eventstream.py @@ -140,6 +140,7 @@ async def dispatch( event.id, event.hash, event.sender, + event.sender_type, event.topic, event.project, event.user, From d34bb0b2f7bab5307cfd9bf982b8d786bde0e4a7 Mon Sep 17 00:00:00 2001 From: Martastain Date: Thu, 5 Dec 2024 10:03:18 +0100 Subject: [PATCH 4/5] fix: add missing reuse parameter in rest api --- api/events/events.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/api/events/events.py b/api/events/events.py index d2ea6867..4263b6f3 100644 --- a/api/events/events.py +++ b/api/events/events.py @@ -63,6 +63,12 @@ class DispatchEventRequestModel(OPModel): description="Set to False for fire-and-forget events", example=True, ) + reuse: bool = Field( + False, + title="Reuse", + description="Allow reusing events with the same hash", + example=False, + ) class UpdateEventRequestModel(OPModel): @@ -112,6 +118,7 @@ async def post_event( payload=request.payload, finished=request.finished, store=request.store, + reuse=request.reuse, ) return DispatchEventResponseModel(id=event_id) From f775e0038f54ac9a45cb48eff7f09c51e950a504 Mon Sep 17 00:00:00 2001 From: Martin Wacker Date: Thu, 5 Dec 2024 16:17:30 +0100 Subject: [PATCH 5/5] Update ayon_server/events/eventstream.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- ayon_server/events/eventstream.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ayon_server/events/eventstream.py b/ayon_server/events/eventstream.py index 01251482..a8527c5c 100644 --- a/ayon_server/events/eventstream.py +++ b/ayon_server/events/eventstream.py @@ -160,10 +160,9 @@ async def dispatch( raise ConstraintViolationException( "Unable to reuse the event. Another event depends on it", ) from e - else: - raise ConstraintViolationException( - "Event with same hash already exists", - ) from e + raise ConstraintViolationException( + "Event with same hash already exists", + ) from e depends_on = ( str(event.depends_on).replace("-", "") if event.depends_on else None