From 3c14b3e356e0c2cb95e44a1618849564666a310b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 2 Apr 2024 17:36:21 +0200 Subject: [PATCH 1/5] live update of applications and tools attribute --- server_addon/applications/server/__init__.py | 167 ++++++++++++++----- 1 file changed, 128 insertions(+), 39 deletions(-) diff --git a/server_addon/applications/server/__init__.py b/server_addon/applications/server/__init__.py index d5c2de3df3..a199325475 100644 --- a/server_addon/applications/server/__init__.py +++ b/server_addon/applications/server/__init__.py @@ -118,9 +118,28 @@ class ApplicationsAddon(BaseServerAddon): ) async def setup(self): - need_restart = await self.create_applications_attribute() + need_restart = await self.create_required_attributes() if need_restart: self.request_server_restart() + await self.update_enums() + + def _get_applications_def(self): + return { + "name": "applications", + "type": "list_of_strings", + "title": "Applications", + "scope": ["project"], + "enum":[], + } + + def _get_tools_def(self): + return { + "name": "tools", + "type": "list_of_strings", + "title": "Tools", + "scope": ["project", "folder", "task"], + "enum":[], + } async def create_applications_attribute(self) -> bool: """Make sure there are required attributes which ftrack addon needs. @@ -129,6 +148,85 @@ class ApplicationsAddon(BaseServerAddon): bool: 'True' if an attribute was created or updated. """ + need_restart = await self.create_required_attributes() + await self.update_enums() + return need_restart + + async def create_required_attributes(self) -> bool: + """Make sure there are required 'applications' and 'tools' attributes. + This only checks for the existence of the attributes, it does not populate + them with any data. When an attribute is added, server needs to be restarted, + while adding enum data to the attribute does not require a restart. + Returns: + bool: 'True' if an attribute was created or updated. + """ + + # keep track of the last attribute position (for adding new attributes) + apps_present = False + tools_present = False + apps_attribute_data = self._get_applications_def() + apps_attrib_name = apps_attribute_data["name"] + + tools_attribute_data = self._get_tools_def() + tools_attrib_name = tools_attribute_data["name"] + + last_index = -1 + async for row in Postgres.iterate( + "SELECT name, position FROM attributes ORDER BY position" + ): + # check if the required attributes are present + # (in that case, we don't need to add them) + # also keep track of the last attribute position + if row["name"] == apps_attrib_name: + apps_present = True + elif row["name"] == tools_attrib_name: + tools_present = True + last_index = row["position"] + + attributes_to_create = {} + if not apps_present: + attributes_to_create[apps_attrib_name] = { + "scope": apps_attribute_data["scope"], + "data": { + "title": apps_attribute_data["title"], + "type": apps_attribute_data["type"], + "enum": [], + } + } + + if not tools_present: + attributes_to_create[tools_attrib_name] = { + "scope": tools_attribute_data["scope"], + "data": { + "title": tools_attribute_data["title"], + "type": tools_attribute_data["type"], + "enum": [], + }, + } + + # when any of the required attributes are not present, add them + # and return 'True' to indicate that server needs to be restarted + needs_restart = False + for name, payload in attributes_to_create.items(): + await Postgres.execute( + "INSERT INTO attributes (name, position, scope, data) VALUES ($1, $2, $3, $4)", + name, + last_index + 1, + payload["scope"], + payload["data"], + ) + last_index += 1 + needs_restart = True + + return needs_restart + + async def update_enums(self): + """Updates applications and tools enums based on the addon settings. + This method is called when the addon is started (after we are sure that the + 'applications' and 'tools' attributes exist) and when the addon settings are + updated (using on_settings_updated method). + """ + instance = AddonLibrary.getinstance() app_defs = instance.data.get(self.name) all_applications = [] @@ -148,33 +246,32 @@ class ApplicationsAddon(BaseServerAddon): merge_groups(all_applications, app_groups) merge_groups(all_tools, studio_settings["tool_groups"]) - query = "SELECT name, position, scope, data from public.attributes" - apps_attrib_name = "applications" tools_attrib_name = "tools" apps_enum = get_enum_items_from_groups(all_applications) tools_enum = get_enum_items_from_groups(all_tools) + apps_attribute_data = { "type": "list_of_strings", "title": "Applications", - "enum": apps_enum + "enum": apps_enum, } tools_attribute_data = { "type": "list_of_strings", "title": "Tools", - "enum": tools_enum + "enum": tools_enum, } + apps_scope = ["project"] tools_scope = ["project", "folder", "task"] - apps_match_position = None apps_matches = False - tools_match_position = None tools_matches = False - position = 1 - async for row in Postgres.iterate(query): - position += 1 + + async for row in Postgres.iterate( + "SELECT name, position, scope, data from public.attributes" + ): if row["name"] == apps_attrib_name: # Check if scope is matching ftrack addon requirements if ( @@ -182,7 +279,6 @@ class ApplicationsAddon(BaseServerAddon): and row["data"].get("enum") == apps_enum ): apps_matches = True - apps_match_position = row["position"] elif row["name"] == tools_attrib_name: if ( @@ -190,45 +286,38 @@ class ApplicationsAddon(BaseServerAddon): and row["data"].get("enum") == tools_enum ): tools_matches = True - tools_match_position = row["position"] if apps_matches and tools_matches: - return False + return - postgre_query = "\n".join(( - "INSERT INTO public.attributes", - " (name, position, scope, data)", - "VALUES", - " ($1, $2, $3, $4)", - "ON CONFLICT (name)", - "DO UPDATE SET", - " scope = $3,", - " data = $4", - )) if not apps_matches: - # Reuse position from found attribute - if apps_match_position is None: - apps_match_position = position - position += 1 - await Postgres.execute( - postgre_query, - apps_attrib_name, - apps_match_position, + """ + UPDATE attributes SET + scope = $1, + data = $2 + WHERE + name = $3 + """, apps_scope, apps_attribute_data, + apps_attrib_name, ) if not tools_matches: - if tools_match_position is None: - tools_match_position = position - position += 1 - await Postgres.execute( - postgre_query, - tools_attrib_name, - tools_match_position, + """ + UPDATE attributes SET + scope = $1, + data = $2 + WHERE + name = $3 + """, tools_scope, tools_attribute_data, + tools_attrib_name, ) - return True + + async def on_settings_changed(self, *args, **kwargs): + _ = args, kwargs + await self.update_enums() \ No newline at end of file From 769bcbaf2e39b4a67d8f6504e8860b53d46b93df Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 2 Apr 2024 17:36:43 +0200 Subject: [PATCH 2/5] bump version to 0.1.9 --- server_addon/applications/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/applications/server/version.py b/server_addon/applications/server/version.py index 9cb17e7976..c11f861afb 100644 --- a/server_addon/applications/server/version.py +++ b/server_addon/applications/server/version.py @@ -1 +1 @@ -__version__ = "0.1.8" +__version__ = "0.1.9" From 72aeb63ef974cfc80bd1ff0d22b1f51448336939 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 3 Apr 2024 14:12:13 +0200 Subject: [PATCH 3/5] reset cache of attributes on server when enums are updated --- server_addon/applications/server/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server_addon/applications/server/__init__.py b/server_addon/applications/server/__init__.py index a199325475..07e6c46cc3 100644 --- a/server_addon/applications/server/__init__.py +++ b/server_addon/applications/server/__init__.py @@ -3,6 +3,7 @@ import json import copy from ayon_server.addons import BaseServerAddon, AddonLibrary +from ayon_server.entities.core import attribute_library from ayon_server.lib.postgres import Postgres from .version import __version__ @@ -318,6 +319,9 @@ class ApplicationsAddon(BaseServerAddon): tools_attrib_name, ) + # Reset attributes cache on server + attribute_library.load() + async def on_settings_changed(self, *args, **kwargs): _ = args, kwargs await self.update_enums() \ No newline at end of file From 16bc2e601dd448a836e4289aed5e107e76803512 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 3 Apr 2024 14:12:28 +0200 Subject: [PATCH 4/5] make 'update_enums' private method '_update_enums' --- server_addon/applications/server/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server_addon/applications/server/__init__.py b/server_addon/applications/server/__init__.py index 07e6c46cc3..30181285fb 100644 --- a/server_addon/applications/server/__init__.py +++ b/server_addon/applications/server/__init__.py @@ -122,7 +122,7 @@ class ApplicationsAddon(BaseServerAddon): need_restart = await self.create_required_attributes() if need_restart: self.request_server_restart() - await self.update_enums() + await self._update_enums() def _get_applications_def(self): return { @@ -150,7 +150,7 @@ class ApplicationsAddon(BaseServerAddon): """ need_restart = await self.create_required_attributes() - await self.update_enums() + await self._update_enums() return need_restart async def create_required_attributes(self) -> bool: @@ -221,7 +221,7 @@ class ApplicationsAddon(BaseServerAddon): return needs_restart - async def update_enums(self): + async def _update_enums(self): """Updates applications and tools enums based on the addon settings. This method is called when the addon is started (after we are sure that the 'applications' and 'tools' attributes exist) and when the addon settings are @@ -324,4 +324,4 @@ class ApplicationsAddon(BaseServerAddon): async def on_settings_changed(self, *args, **kwargs): _ = args, kwargs - await self.update_enums() \ No newline at end of file + await self._update_enums() From 5a4f374e921ef05e91abfb0eedfeb3fdae920b7a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 3 Apr 2024 15:46:17 +0200 Subject: [PATCH 5/5] better sql queries --- server_addon/applications/server/__init__.py | 84 +++++++++----------- 1 file changed, 36 insertions(+), 48 deletions(-) diff --git a/server_addon/applications/server/__init__.py b/server_addon/applications/server/__init__.py index 30181285fb..2668589cbe 100644 --- a/server_addon/applications/server/__init__.py +++ b/server_addon/applications/server/__init__.py @@ -163,61 +163,49 @@ class ApplicationsAddon(BaseServerAddon): """ # keep track of the last attribute position (for adding new attributes) - apps_present = False - tools_present = False apps_attribute_data = self._get_applications_def() - apps_attrib_name = apps_attribute_data["name"] - tools_attribute_data = self._get_tools_def() + + apps_attrib_name = apps_attribute_data["name"] tools_attrib_name = tools_attribute_data["name"] - last_index = -1 - async for row in Postgres.iterate( - "SELECT name, position FROM attributes ORDER BY position" - ): - # check if the required attributes are present - # (in that case, we don't need to add them) - # also keep track of the last attribute position - if row["name"] == apps_attrib_name: - apps_present = True - elif row["name"] == tools_attrib_name: - tools_present = True - last_index = row["position"] + async with Postgres.acquire() as conn, conn.transaction(): + query = "SELECT BOOL_OR(name = 'applications') AS has_applications, BOOL_OR(name = 'tools') AS has_tools FROM attributes;" + result = (await conn.fetch(query))[0] - attributes_to_create = {} - if not apps_present: - attributes_to_create[apps_attrib_name] = { - "scope": apps_attribute_data["scope"], - "data": { - "title": apps_attribute_data["title"], - "type": apps_attribute_data["type"], - "enum": [], + attributes_to_create = {} + if not result["has_applications"]: + attributes_to_create[apps_attrib_name] = { + "scope": apps_attribute_data["scope"], + "data": { + "title": apps_attribute_data["title"], + "type": apps_attribute_data["type"], + "enum": [], + } } - } - if not tools_present: - attributes_to_create[tools_attrib_name] = { - "scope": tools_attribute_data["scope"], - "data": { - "title": tools_attribute_data["title"], - "type": tools_attribute_data["type"], - "enum": [], - }, - } + if not result["has_tools"]: + attributes_to_create[tools_attrib_name] = { + "scope": tools_attribute_data["scope"], + "data": { + "title": tools_attribute_data["title"], + "type": tools_attribute_data["type"], + "enum": [], + }, + } - # when any of the required attributes are not present, add them - # and return 'True' to indicate that server needs to be restarted - needs_restart = False - for name, payload in attributes_to_create.items(): - await Postgres.execute( - "INSERT INTO attributes (name, position, scope, data) VALUES ($1, $2, $3, $4)", - name, - last_index + 1, - payload["scope"], - payload["data"], - ) - last_index += 1 - needs_restart = True + needs_restart = False + # when any of the required attributes are not present, add them + # and return 'True' to indicate that server needs to be restarted + for name, payload in attributes_to_create.items(): + insert_query = "INSERT INTO attributes (name, scope, data, position) VALUES ($1, $2, $3, (SELECT COALESCE(MAX(position), 0) + 1 FROM attributes)) ON CONFLICT DO NOTHING" + await conn.execute( + insert_query, + name, + payload["scope"], + payload["data"], + ) + needs_restart = True return needs_restart @@ -320,7 +308,7 @@ class ApplicationsAddon(BaseServerAddon): ) # Reset attributes cache on server - attribute_library.load() + await attribute_library.load() async def on_settings_changed(self, *args, **kwargs): _ = args, kwargs