From 1f461bceaac7326fca4df971490762b2ccb24fe4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 13 Sep 2024 13:53:04 +0200 Subject: [PATCH 1/9] simplified 'get_handler_paths' --- services/processor/processor/server.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/services/processor/processor/server.py b/services/processor/processor/server.py index ca678b1..199be86 100644 --- a/services/processor/processor/server.py +++ b/services/processor/processor/server.py @@ -18,6 +18,8 @@ downloaded_event_handlers, ) +CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) + class _GlobalContext: stop_event = threading.Event() @@ -26,11 +28,9 @@ class _GlobalContext: def get_handler_paths() -> list[str]: - current_dir = os.path.dirname(os.path.abspath(__file__)) - handler_paths = [ - os.path.join(current_dir, "default_handlers"), + return [ + os.path.join(CURRENT_DIR, "default_handlers"), ] - return handler_paths def get_service_label(): From 5a9fa68bbf34b58c999fff4c6297ccd6c5c3febe Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 13 Sep 2024 13:55:04 +0200 Subject: [PATCH 2/9] break 'wait' loop if is not connected --- services/processor/processor/ftrack_session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/processor/processor/ftrack_session.py b/services/processor/processor/ftrack_session.py index 482e326..ef8a9c0 100644 --- a/services/processor/processor/ftrack_session.py +++ b/services/processor/processor/ftrack_session.py @@ -72,7 +72,7 @@ def wait(self, duration=None): started = time.time() while True: if not self.connected: - self.reconnect() + break job = None empty_queue = False From 1a4386ef6462e592cd64b84b72419a778462a00f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 13 Sep 2024 13:56:51 +0200 Subject: [PATCH 3/9] change message to correct information --- services/processor/processor/ftrack_session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/processor/processor/ftrack_session.py b/services/processor/processor/ftrack_session.py index ef8a9c0..8e7eb0b 100644 --- a/services/processor/processor/ftrack_session.py +++ b/services/processor/processor/ftrack_session.py @@ -42,7 +42,7 @@ def finish_job(self, job): event_id = job["id"] source_id = job["dependsOn"] source_event = get_event(event_id) - print(f"Processing event... {source_id}") + print(f"Processed event... {source_id}") description = f"Processed {source_event['description']}" From f3745cd65d605f7e6f5132ce252f21b44b119878 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 13 Sep 2024 13:57:59 +0200 Subject: [PATCH 4/9] preparation for better discovery of event handlers --- .../event_handlers/ftrack_action_handler.py | 3 +++ .../event_handlers/ftrack_base_handler.py | 19 +++++++++++++++++-- .../event_handlers/ftrack_event_handler.py | 1 + 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/client/ayon_ftrack/common/event_handlers/ftrack_action_handler.py b/client/ayon_ftrack/common/event_handlers/ftrack_action_handler.py index 008aa74..10c8dbc 100644 --- a/client/ayon_ftrack/common/event_handlers/ftrack_action_handler.py +++ b/client/ayon_ftrack/common/event_handlers/ftrack_action_handler.py @@ -29,6 +29,7 @@ class BaseAction(BaseHandler): Args: session (ftrack_api.Session): Connected ftrack session. """ + __ignore_handler_class = True label = None variant = None @@ -521,6 +522,7 @@ class LocalAction(BaseAction): Handy for actions where matters if is executed on specific machine. """ + __ignore_handler_class = True _full_launch_identifier = None @property @@ -616,5 +618,6 @@ class ServerAction(BaseAction): Unlike the `BaseAction` roles are not checked on register but on discover. For the same reason register is modified to not filter topics by username. """ + __ignore_handler_class = True settings_frack_subkey = "service_event_handlers" diff --git a/client/ayon_ftrack/common/event_handlers/ftrack_base_handler.py b/client/ayon_ftrack/common/event_handlers/ftrack_base_handler.py index ecd9d93..90935d9 100644 --- a/client/ayon_ftrack/common/event_handlers/ftrack_base_handler.py +++ b/client/ayon_ftrack/common/event_handlers/ftrack_base_handler.py @@ -15,7 +15,7 @@ from ayon_api import get_addons_settings, get_project -class BaseHandler(object, metaclass=ABCMeta): +class BaseHandler(metaclass=ABCMeta): """Base class for handling ftrack events. Attributes: @@ -26,8 +26,8 @@ class BaseHandler(object, metaclass=ABCMeta): Args: session (ftrack_api.Session): Connected ftrack session. - """ + """ _log = None _process_id = None # Default priority is 100 @@ -35,6 +35,8 @@ class BaseHandler(object, metaclass=ABCMeta): priority = 100 handler_type = "Base" _handler_label = None + # Mark base classes to be ignored for discovery + __ignore_handler_class = True def __init__(self, session): if not isinstance(session, ftrack_api.session.Session): @@ -46,6 +48,19 @@ def __init__(self, session): self.register = self.register_wrapper(self.register) + @classmethod + def ignore_handler_class(cls) -> bool: + """Check if handler class should be ignored. + + Do not touch implementation of this method, set + '__ignore_handler_class' to 'True' if you want to ignore class. + + """ + cls_name = cls.__name__ + if not cls_name.startswith("_"): + cls_name = f"_{cls_name}" + return getattr(cls, f"{cls_name}__ignore_handler_class", False) + @staticmethod def join_filter_values(values): return ",".join({'"{}"'.format(value) for value in values}) diff --git a/client/ayon_ftrack/common/event_handlers/ftrack_event_handler.py b/client/ayon_ftrack/common/event_handlers/ftrack_event_handler.py index b8a9c72..e2e30d1 100644 --- a/client/ayon_ftrack/common/event_handlers/ftrack_event_handler.py +++ b/client/ayon_ftrack/common/event_handlers/ftrack_event_handler.py @@ -9,6 +9,7 @@ class BaseEventHandler(BaseHandler): By default is listening to "ftrack.update". To change it override 'register' method of change 'subscription_topic' attribute. """ + __ignore_handler_class = True subscription_topic = "ftrack.update" handler_type = "Event" From e9b59d0283ded1a956ef41326443b407485898f5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 13 Sep 2024 13:58:15 +0200 Subject: [PATCH 5/9] enhance ftrack server logic --- client/ayon_ftrack/common/ftrack_server.py | 230 ++++++++++++++------- 1 file changed, 151 insertions(+), 79 deletions(-) diff --git a/client/ayon_ftrack/common/ftrack_server.py b/client/ayon_ftrack/common/ftrack_server.py index a7d8dbb..58c6410 100644 --- a/client/ayon_ftrack/common/ftrack_server.py +++ b/client/ayon_ftrack/common/ftrack_server.py @@ -3,25 +3,32 @@ import logging import traceback import types +import inspect import ftrack_api from .python_module_tools import modules_from_path +from .event_handlers import BaseHandler class FtrackServer: + """Helper wrapper to run ftrack server with event handlers. + + Handlers are discovered based on a list of paths. Each path is scanned for + python files which are imported as modules. Each module is checked for + 'register' function or classes inheriting from 'BaseHandler'. If class + inheriting from 'BaseHandler' is found it is instantiated and 'register' + method is called. If 'register' function is found it is called with + ftrack session as argument and 'BaseHandler' from the file are ignored. + + Function 'register' tells discovery system to skip looking for classes. + + Classes that start with '_' are ignored. It is possible to define + attribute `__ignore_handler_class = True` on class definition to mark + a "base class" that will be ignored on discovery, so you can safely import + custom base classes in the files. + """ def __init__(self, handler_paths=None): - """ - - 'type' is by default set to 'action' - Runs Action server - - enter 'event' for Event server - - EXAMPLE FOR EVENT SERVER: - ... - server = FtrackServer() - server.run_server() - .. - """ - # set Ftrack logging to Warning only - OPTIONAL ftrack_log = logging.getLogger("ftrack_api") ftrack_log.setLevel(logging.WARNING) @@ -31,68 +38,42 @@ def __init__(self, handler_paths=None): self._stopped = True self._is_running = False - self.handler_paths = handler_paths or [] + if handler_paths is None: + handler_paths = [] - def stop_session(self): - self._stopped = True - if self.session.event_hub.connected is True: - self.session.event_hub.disconnect() - self.session.close() - self.session = None + self._handler_paths = handler_paths - def set_files(self, paths): - # Iterate all paths - register_functions = [] - for path in paths: - # Try to format path with environments - try: - path = path.format(**os.environ) - except BaseException: - pass - - # Get all modules with functions - modules, crashed = modules_from_path(path) - for filepath, exc_info in crashed: - self.log.warning("Filepath load crashed {}.\n{}".format( - filepath, "".join(traceback.format_exception(*exc_info)) - )) + self._session = None + self._cached_modules = [] + self._cached_objects = [] - for filepath, module in modules: - register_function = None - for name, attr in module.__dict__.items(): - if ( - name == "register" - and isinstance(attr, types.FunctionType) - ): - register_function = attr - break + def stop_session(self): + session = self._session + self._session = None + self._stopped = True + if session.event_hub.connected is True: + session.event_hub.disconnect() + session.close() - if not register_function: - self.log.warning( - "\"{}\" - Missing register method".format(filepath) - ) - continue + def get_session(self): + return self._session - register_functions.append( - (filepath, register_function) - ) + def get_handler_paths(self): + return self._handler_paths - if not register_functions: - self.log.warning(( - "There are no events with `register` function" - " in registered paths: \"{}\"" - ).format("| ".join(paths))) + def set_handler_paths(self, paths): + if self._is_running: + raise ValueError( + "Cannot change handler paths when server is running." + ) + self._handler_paths = paths - for filepath, register_func in register_functions: - try: - register_func(self.session) - except Exception: - self.log.warning( - "\"{}\" - register was not successful".format(filepath), - exc_info=True - ) + session = property(get_session) + handler_paths = property(get_handler_paths, set_handler_paths) - def run_server(self, session=None, load_files=True): + def run_server(self, session=None): + if self._is_running: + raise ValueError("Server is already running.") self._stopped = False self._is_running = True if not session: @@ -115,24 +96,115 @@ def run_server(self, session=None, load_files=True): self.log.info("Connecting event hub") session.event_hub.connect() - self.session = session - if load_files: - if not self.handler_paths: - self.log.warning(( - "Paths to event handlers are not set." - " Ftrack server won't launch." - )) - self._is_running = False - return + self._session = session + if not self._handler_paths: + self.log.warning(( + "Paths to event handlers are not set." + " Ftrack server won't launch." + )) + self._is_running = False + return - self.set_files(self.handler_paths) + self._load_handlers() - msg = "Registration of event handlers has finished!" - self.log.info(len(msg) * "*") - self.log.info(msg) + msg = "Registration of event handlers has finished!" + self.log.info(len(msg) * "*") + self.log.info(msg) # keep event_hub on session running try: - self.session.event_hub.wait() + session.event_hub.wait() finally: self._is_running = False + self._cached_modules = [] + + def _load_handlers(self): + register_functions = [] + handler_classes = [] + + # Iterate all paths + paths = self._handler_paths + for path in paths: + # Try to format path with environments + try: + path = path.format(**os.environ) + except BaseException: + pass + + # Get all modules with functions + modules, crashed = modules_from_path(path) + for filepath, exc_info in crashed: + self.log.warning("Filepath load crashed {}.\n{}".format( + filepath, "".join(traceback.format_exception(*exc_info)) + )) + + for filepath, module in modules: + self._cached_modules.append(module) + register_function = getattr(module, "register", None) + if register_function is not None: + if isinstance(register_function, types.FunctionType): + register_functions.append( + (filepath, register_function) + ) + else: + self.log.warning( + f"\"{filepath}\"" + " - Found 'register' but it is not a function." + ) + continue + + for attr_name in dir(module): + if attr_name.startswith("_"): + self.log.debug( + f"Skipping private class '{attr_name}'" + ) + continue + + attr = getattr(module, attr_name, None) + if ( + not inspect.isclass(attr) + or not issubclass(attr, BaseHandler) + or attr.ignore_handler_class() + ): + continue + + if inspect.isabstract(attr): + self.log.warning( + f"Skipping abstract class '{attr_name}'." + ) + continue + handler_classes.append(attr) + + if not handler_classes: + self.log.warning( + f"\"{filepath}\"" + " - No 'register' function" + " or 'BaseHandler' classes found." + ) + + if not register_functions and not handler_classes: + self.log.warning(( + "There are no files with `register` function or 'BaseHandler'" + " classes in registered paths:\n- \"{}\"" + ).format("- \n".join(paths))) + + for filepath, register_func in register_functions: + try: + register_func(self._session) + except Exception: + self.log.warning( + f"\"{filepath}\" - register was not successful", + exc_info=True + ) + + for handler_class in handler_classes: + try: + obj = handler_class(self._session) + obj.register() + self._cached_objects.append(obj) + + except Exception: + self.log.warning( + f"\"{handler_class}\" - register was not successful", + exc_info=True + ) From 0d1975bc0631a9b2aef600bd294eeffa57f3f684 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 13 Sep 2024 13:59:32 +0200 Subject: [PATCH 6/9] remove register function from default event handlers --- .../action_delete_old_versions.py | 6 ------ .../action_fill_workfile_attr.py | 4 ---- .../action_store_thumbnails_to_avalon.py | 4 ---- .../event_handlers_user/action_applications.py | 5 ----- .../event_handlers_user/action_batch_task_creation.py | 6 ------ .../action_clean_hierarchical_attributes.py | 6 ------ .../event_handlers_user/action_client_review_sort.py | 4 ---- .../event_handlers_user/action_component_open.py | 4 ---- .../event_handlers_user/action_create_cust_attrs.py | 4 ---- .../event_handlers_user/action_create_folders.py | 5 ----- .../action_create_project_structure.py | 4 ---- .../ayon_ftrack/event_handlers_user/action_delivery.py | 6 ------ .../event_handlers_user/action_job_killer.py | 4 ---- .../event_handlers_user/action_multiple_notes.py | 4 ---- client/ayon_ftrack/event_handlers_user/action_test.py | 4 ---- .../action_thumbnail_to_childern.py | 4 ---- .../event_handlers_user/action_thumbnail_to_parent.py | 4 ---- .../event_handlers_user/action_where_run_ask.py | 4 ---- .../default_handlers/action_clone_review_session.py | 6 ------ .../processor/default_handlers/action_create_lists.py | 4 ---- .../default_handlers/action_delete_entities.py | 10 ---------- .../default_handlers/action_multiple_notes.py | 6 ------ .../default_handlers/action_prepare_project.py | 4 ---- .../action_private_project_detection.py | 6 ------ .../default_handlers/action_project_component_sizes.py | 5 ----- .../action_push_frame_values_to_task.py | 4 ---- .../default_handlers/action_sync_from_ftrack.py | 4 ---- .../action_tranfer_hierarchical_values.py | 6 ------ .../default_handlers/event_first_version_status.py | 6 ------ .../default_handlers/event_next_task_update.py | 4 ---- .../event_push_frame_values_to_task.py | 4 ---- .../default_handlers/event_sync_from_ftrack.py | 5 ----- .../default_handlers/event_task_to_parent_status.py | 4 ---- .../default_handlers/event_task_to_version_status.py | 4 ---- .../default_handlers/event_thumbnail_updates.py | 4 ---- .../default_handlers/event_version_to_task_statuses.py | 6 ------ .../action_create_review_session.py | 5 ----- .../event_del_avalon_id_from_new.py | 5 ----- .../processor/handlers_to_convert/event_sync_links.py | 5 ----- .../handlers_to_convert/event_sync_to_avalon.py | 5 ----- 40 files changed, 194 deletions(-) diff --git a/client/ayon_ftrack/event_handlers_to_convert/action_delete_old_versions.py b/client/ayon_ftrack/event_handlers_to_convert/action_delete_old_versions.py index d2d75a2..a0e3c84 100644 --- a/client/ayon_ftrack/event_handlers_to_convert/action_delete_old_versions.py +++ b/client/ayon_ftrack/event_handlers_to_convert/action_delete_old_versions.py @@ -559,9 +559,3 @@ def path_from_represenation(self, representation, anatomy): return (None, None) return (os.path.normpath(path), sequence_path) - - -def register(session): - '''Register plugin. Called when used as an plugin.''' - - DeleteOldVersions(session).register() diff --git a/client/ayon_ftrack/event_handlers_to_convert/action_fill_workfile_attr.py b/client/ayon_ftrack/event_handlers_to_convert/action_fill_workfile_attr.py index 05c5ea8..0e6c892 100644 --- a/client/ayon_ftrack/event_handlers_to_convert/action_fill_workfile_attr.py +++ b/client/ayon_ftrack/event_handlers_to_convert/action_fill_workfile_attr.py @@ -543,7 +543,3 @@ def _get_tasks_for_selection( report[NOT_SYNCHRONIZED_TITLE].append(task_path) return output - - -def register(session): - FillWorkfileAttributeAction(session).register() diff --git a/client/ayon_ftrack/event_handlers_to_convert/action_store_thumbnails_to_avalon.py b/client/ayon_ftrack/event_handlers_to_convert/action_store_thumbnails_to_avalon.py index 2741c5c..e28d68a 100644 --- a/client/ayon_ftrack/event_handlers_to_convert/action_store_thumbnails_to_avalon.py +++ b/client/ayon_ftrack/event_handlers_to_convert/action_store_thumbnails_to_avalon.py @@ -467,7 +467,3 @@ def get_avalon_entities_for_assetversion(self, asset_version, db_con): output["representations"] = repre_ents return output - - -def register(session): - StoreThumbnailsToAvalon(session).register() diff --git a/client/ayon_ftrack/event_handlers_user/action_applications.py b/client/ayon_ftrack/event_handlers_user/action_applications.py index 45bd703..b9e3d34 100644 --- a/client/ayon_ftrack/event_handlers_user/action_applications.py +++ b/client/ayon_ftrack/event_handlers_user/action_applications.py @@ -279,8 +279,3 @@ def launch(self, session, entities, event): def _get_folder_path(self, session, entity): entity_id = entity["id"] return get_folder_path_for_entities(session, [entity])[entity_id] - - -def register(session): - """Register action. Called when used as an event plugin.""" - AppplicationsAction(session).register() diff --git a/client/ayon_ftrack/event_handlers_user/action_batch_task_creation.py b/client/ayon_ftrack/event_handlers_user/action_batch_task_creation.py index c956546..8c69173 100644 --- a/client/ayon_ftrack/event_handlers_user/action_batch_task_creation.py +++ b/client/ayon_ftrack/event_handlers_user/action_batch_task_creation.py @@ -159,9 +159,3 @@ def launch(self, session, entities, event): } ] } - - -def register(session): - '''Register action. Called when used as an event plugin.''' - - BatchTasksAction(session).register() diff --git a/client/ayon_ftrack/event_handlers_user/action_clean_hierarchical_attributes.py b/client/ayon_ftrack/event_handlers_user/action_clean_hierarchical_attributes.py index 828a9cc..4466981 100644 --- a/client/ayon_ftrack/event_handlers_user/action_clean_hierarchical_attributes.py +++ b/client/ayon_ftrack/event_handlers_user/action_clean_hierarchical_attributes.py @@ -101,9 +101,3 @@ def launch(self, session, entities, event): session.commit() return True - - -def register(session): - '''Register plugin. Called when used as an plugin.''' - - CleanHierarchicalAttrsAction(session).register() diff --git a/client/ayon_ftrack/event_handlers_user/action_client_review_sort.py b/client/ayon_ftrack/event_handlers_user/action_client_review_sort.py index 70ec052..acf7c47 100644 --- a/client/ayon_ftrack/event_handlers_user/action_client_review_sort.py +++ b/client/ayon_ftrack/event_handlers_user/action_client_review_sort.py @@ -76,7 +76,3 @@ def launch(self, session, entities, event): "success": True, "message": "Client Review sorted!" } - - -def register(session): - ClientReviewSort(session).register() diff --git a/client/ayon_ftrack/event_handlers_user/action_component_open.py b/client/ayon_ftrack/event_handlers_user/action_component_open.py index 9ff57a6..361c7ed 100644 --- a/client/ayon_ftrack/event_handlers_user/action_component_open.py +++ b/client/ayon_ftrack/event_handlers_user/action_component_open.py @@ -66,7 +66,3 @@ def launch(self, session, entities, event): "success": True, "message": "Component folder Opened" } - - -def register(session): - ComponentOpen(session).register() diff --git a/client/ayon_ftrack/event_handlers_user/action_create_cust_attrs.py b/client/ayon_ftrack/event_handlers_user/action_create_cust_attrs.py index bf18aab..d5a87cb 100644 --- a/client/ayon_ftrack/event_handlers_user/action_create_cust_attrs.py +++ b/client/ayon_ftrack/event_handlers_user/action_create_cust_attrs.py @@ -875,7 +875,3 @@ def get_entity_type(self, context, attr): "entity_type": entity_type, "object_type_id": object_type["id"] } - - -def register(session): - CustomAttributes(session).register() diff --git a/client/ayon_ftrack/event_handlers_user/action_create_folders.py b/client/ayon_ftrack/event_handlers_user/action_create_folders.py index fce440e..a15e06d 100644 --- a/client/ayon_ftrack/event_handlers_user/action_create_folders.py +++ b/client/ayon_ftrack/event_handlers_user/action_create_folders.py @@ -327,8 +327,3 @@ def compute_template(self, data, template): ) ) return os.path.normpath(filled_template.split("{")[0]) - - -def register(session): - """Register plugin. Called when used as an plugin.""" - CreateFolders(session).register() diff --git a/client/ayon_ftrack/event_handlers_user/action_create_project_structure.py b/client/ayon_ftrack/event_handlers_user/action_create_project_structure.py index 0c3e476..93bae05 100644 --- a/client/ayon_ftrack/event_handlers_user/action_create_project_structure.py +++ b/client/ayon_ftrack/event_handlers_user/action_create_project_structure.py @@ -213,7 +213,3 @@ def create_ftrack_entity(self, name, ent_type, parent): new_ent = self.session.create(ent_type, data) self.session.commit() return new_ent - - -def register(session): - CreateProjectFolders(session).register() diff --git a/client/ayon_ftrack/event_handlers_user/action_delivery.py b/client/ayon_ftrack/event_handlers_user/action_delivery.py index 1ab8067..b7c043c 100644 --- a/client/ayon_ftrack/event_handlers_user/action_delivery.py +++ b/client/ayon_ftrack/event_handlers_user/action_delivery.py @@ -769,9 +769,3 @@ def _get_version_entities( filtered_versions.append(version_entity) return filtered_versions - - -def register(session): - """Register plugin. Called when used as a plugin.""" - - Delivery(session).register() diff --git a/client/ayon_ftrack/event_handlers_user/action_job_killer.py b/client/ayon_ftrack/event_handlers_user/action_job_killer.py index 530c989..9c52025 100644 --- a/client/ayon_ftrack/event_handlers_user/action_job_killer.py +++ b/client/ayon_ftrack/event_handlers_user/action_job_killer.py @@ -127,7 +127,3 @@ def launch(self, session, entities, event): "success": True, "message": "All selected jobs were killed Successfully!" } - - -def register(session): - JobKiller(session).register() diff --git a/client/ayon_ftrack/event_handlers_user/action_multiple_notes.py b/client/ayon_ftrack/event_handlers_user/action_multiple_notes.py index 39cac0e..e9fb57a 100644 --- a/client/ayon_ftrack/event_handlers_user/action_multiple_notes.py +++ b/client/ayon_ftrack/event_handlers_user/action_multiple_notes.py @@ -97,7 +97,3 @@ def launch(self, session, entities, event): entity["notes"].append(new_note) session.commit() return True - - -def register(session): - MultipleNotes(session).register() diff --git a/client/ayon_ftrack/event_handlers_user/action_test.py b/client/ayon_ftrack/event_handlers_user/action_test.py index 2662ac4..e11dbf3 100644 --- a/client/ayon_ftrack/event_handlers_user/action_test.py +++ b/client/ayon_ftrack/event_handlers_user/action_test.py @@ -20,7 +20,3 @@ def launch(self, session, entities, event): self.log.info(event) return True - - -def register(session): - TestAction(session).register() diff --git a/client/ayon_ftrack/event_handlers_user/action_thumbnail_to_childern.py b/client/ayon_ftrack/event_handlers_user/action_thumbnail_to_childern.py index 2ff89d0..8313663 100644 --- a/client/ayon_ftrack/event_handlers_user/action_thumbnail_to_childern.py +++ b/client/ayon_ftrack/event_handlers_user/action_thumbnail_to_childern.py @@ -48,7 +48,3 @@ def launch(self, session, entities, event): "success": True, "message": "Created job for updating thumbnails!" } - - -def register(session): - ThumbToChildren(session).register() diff --git a/client/ayon_ftrack/event_handlers_user/action_thumbnail_to_parent.py b/client/ayon_ftrack/event_handlers_user/action_thumbnail_to_parent.py index 0fa9aa2..0d25f79 100644 --- a/client/ayon_ftrack/event_handlers_user/action_thumbnail_to_parent.py +++ b/client/ayon_ftrack/event_handlers_user/action_thumbnail_to_parent.py @@ -73,7 +73,3 @@ def launch(self, session, entities, event): "success": True, "message": "Created job for updating thumbnails!" } - - -def register(session): - ThumbToParent(session).register() diff --git a/client/ayon_ftrack/event_handlers_user/action_where_run_ask.py b/client/ayon_ftrack/event_handlers_user/action_where_run_ask.py index 670d5ae..3b61441 100644 --- a/client/ayon_ftrack/event_handlers_user/action_where_run_ask.py +++ b/client/ayon_ftrack/event_handlers_user/action_where_run_ask.py @@ -89,7 +89,3 @@ def _show_info(self, event): items.append(message) self.show_interface(items, title, event=event) - - -def register(session): - ActionWhereIRun(session).register() diff --git a/services/processor/processor/default_handlers/action_clone_review_session.py b/services/processor/processor/default_handlers/action_clone_review_session.py index bce5445..0fe1b2a 100644 --- a/services/processor/processor/default_handlers/action_clone_review_session.py +++ b/services/processor/processor/default_handlers/action_clone_review_session.py @@ -122,9 +122,3 @@ def launch(self, session, entities, event): 'success': True, 'message': 'Action completed successfully' } - - -def register(session): - '''Register action. Called when used as an event plugin.''' - - CloneReviewSession(session).register() diff --git a/services/processor/processor/default_handlers/action_create_lists.py b/services/processor/processor/default_handlers/action_create_lists.py index bb5e153..ad59a13 100644 --- a/services/processor/processor/default_handlers/action_create_lists.py +++ b/services/processor/processor/default_handlers/action_create_lists.py @@ -836,7 +836,3 @@ def _fill_list_name_template( exc_info=True ) return output - - -def register(session): - CreateDailyListServerAction(session).register() diff --git a/services/processor/processor/default_handlers/action_delete_entities.py b/services/processor/processor/default_handlers/action_delete_entities.py index 3ffd804..c81b745 100644 --- a/services/processor/processor/default_handlers/action_delete_entities.py +++ b/services/processor/processor/default_handlers/action_delete_entities.py @@ -925,13 +925,3 @@ def _delete_products(self, session, entities, event, ftrack_ids): "success": True, "message": message } - - -def register(session): - """ - - Args: - session (ftrack_api.Session): Ftrack session. - """ - - DeleteEntitiesAction(session).register() diff --git a/services/processor/processor/default_handlers/action_multiple_notes.py b/services/processor/processor/default_handlers/action_multiple_notes.py index 6ee2141..c49f1ea 100644 --- a/services/processor/processor/default_handlers/action_multiple_notes.py +++ b/services/processor/processor/default_handlers/action_multiple_notes.py @@ -159,9 +159,3 @@ def launch(self, session, entities, event): entity["notes"].append(new_note) session.commit() return True - - -def register(session): - '''Register plugin. Called when used as an plugin.''' - - MultipleNotesServer(session).register() diff --git a/services/processor/processor/default_handlers/action_prepare_project.py b/services/processor/processor/default_handlers/action_prepare_project.py index 8a8b66e..cacdf3f 100644 --- a/services/processor/processor/default_handlers/action_prepare_project.py +++ b/services/processor/processor/default_handlers/action_prepare_project.py @@ -748,7 +748,3 @@ def launch(self, session, entities, event): "message": "Project created in AYON.", "success": True } - - -def register(session): - PrepareProjectServer(session).register() \ No newline at end of file diff --git a/services/processor/processor/default_handlers/action_private_project_detection.py b/services/processor/processor/default_handlers/action_private_project_detection.py index e4e8840..9849e56 100644 --- a/services/processor/processor/default_handlers/action_private_project_detection.py +++ b/services/processor/processor/default_handlers/action_private_project_detection.py @@ -53,9 +53,3 @@ def _launch(self, event): ], "submit_button_label": "Got it" } - - -def register(session): - '''Register plugin. Called when used as an plugin.''' - - PrivateProjectDetectionAction(session).register() diff --git a/services/processor/processor/default_handlers/action_project_component_sizes.py b/services/processor/processor/default_handlers/action_project_component_sizes.py index f645e01..38a962b 100644 --- a/services/processor/processor/default_handlers/action_project_component_sizes.py +++ b/services/processor/processor/default_handlers/action_project_component_sizes.py @@ -473,8 +473,3 @@ def _create_calculate_jobs( "This may take some time. Look into jobs to check progress." ) } - - -def register(session): - ProjectComponentsSizes(session).register() - ProjectComponentsSizesCalculator(session).register() diff --git a/services/processor/processor/default_handlers/action_push_frame_values_to_task.py b/services/processor/processor/default_handlers/action_push_frame_values_to_task.py index f0a4ffd..ecd8588 100644 --- a/services/processor/processor/default_handlers/action_push_frame_values_to_task.py +++ b/services/processor/processor/default_handlers/action_push_frame_values_to_task.py @@ -550,7 +550,3 @@ def push_values_to_entities( session.commit() session.commit() - - -def register(session): - PushHierValuesToNonHier(session).register() diff --git a/services/processor/processor/default_handlers/action_sync_from_ftrack.py b/services/processor/processor/default_handlers/action_sync_from_ftrack.py index 4400499..90b189c 100644 --- a/services/processor/processor/default_handlers/action_sync_from_ftrack.py +++ b/services/processor/processor/default_handlers/action_sync_from_ftrack.py @@ -110,7 +110,3 @@ def _on_leecher_start(self, event): ), on_error="ignore" ) - - -def register(session): - SyncFromFtrackAction(session).register() diff --git a/services/processor/processor/default_handlers/action_tranfer_hierarchical_values.py b/services/processor/processor/default_handlers/action_tranfer_hierarchical_values.py index a13ed26..59f82b3 100644 --- a/services/processor/processor/default_handlers/action_tranfer_hierarchical_values.py +++ b/services/processor/processor/default_handlers/action_tranfer_hierarchical_values.py @@ -338,9 +338,3 @@ def _get_attr_type(self, conf_def): return float return int return None - - -def register(session): - '''Register plugin. Called when used as an plugin.''' - - TransferHierarchicalValues(session).register() diff --git a/services/processor/processor/default_handlers/event_first_version_status.py b/services/processor/processor/default_handlers/event_first_version_status.py index 870358e..a74d4db 100644 --- a/services/processor/processor/default_handlers/event_first_version_status.py +++ b/services/processor/processor/default_handlers/event_first_version_status.py @@ -205,9 +205,3 @@ def filter_entities_info(self, event): filtered_entities_info[project_id].append(entity_info) return filtered_entities_info - - -def register(session): - '''Register plugin. Called when used as an plugin.''' - - FirstVersionStatus(session).register() diff --git a/services/processor/processor/default_handlers/event_next_task_update.py b/services/processor/processor/default_handlers/event_next_task_update.py index 38789ee..50579c8 100644 --- a/services/processor/processor/default_handlers/event_next_task_update.py +++ b/services/processor/processor/default_handlers/event_next_task_update.py @@ -453,7 +453,3 @@ def sort_by_name_task_entities_by_type(task_entities_by_type_id): # Override values in source object for type_id, value in _task_entities_by_type_id.items(): task_entities_by_type_id[type_id] = value - - -def register(session): - NextTaskUpdate(session).register() diff --git a/services/processor/processor/default_handlers/event_push_frame_values_to_task.py b/services/processor/processor/default_handlers/event_push_frame_values_to_task.py index 007d90c..ed76049 100644 --- a/services/processor/processor/default_handlers/event_push_frame_values_to_task.py +++ b/services/processor/processor/default_handlers/event_push_frame_values_to_task.py @@ -499,7 +499,3 @@ def launch(self, session, event): for project_id, entities_info in filtered_entities_info.items(): self.process_by_project(session, event, project_id, entities_info) - - -def register(session): - PushHierValuesToNonHierEvent(session).register() diff --git a/services/processor/processor/default_handlers/event_sync_from_ftrack.py b/services/processor/processor/default_handlers/event_sync_from_ftrack.py index 485c576..4f693e8 100644 --- a/services/processor/processor/default_handlers/event_sync_from_ftrack.py +++ b/services/processor/processor/default_handlers/event_sync_from_ftrack.py @@ -1838,8 +1838,3 @@ def _get_username(self, session, event): # event=self._cur_event # ) # return True - - -def register(session): - '''Register plugin. Called when used as an plugin.''' - AutoSyncFromFtrack(session).register() diff --git a/services/processor/processor/default_handlers/event_task_to_parent_status.py b/services/processor/processor/default_handlers/event_task_to_parent_status.py index 8125edc..e851424 100644 --- a/services/processor/processor/default_handlers/event_task_to_parent_status.py +++ b/services/processor/processor/default_handlers/event_task_to_parent_status.py @@ -434,7 +434,3 @@ def new_status_by_remainders( if best_order_status: output[parent_id] = best_order_status return output - - -def register(session): - TaskStatusToParent(session).register() diff --git a/services/processor/processor/default_handlers/event_task_to_version_status.py b/services/processor/processor/default_handlers/event_task_to_version_status.py index 141196e..4795002 100644 --- a/services/processor/processor/default_handlers/event_task_to_version_status.py +++ b/services/processor/processor/default_handlers/event_task_to_version_status.py @@ -407,7 +407,3 @@ def find_last_asset_versions_for_task_ids( last_asset_versions_by_task_id[task_id].append(asset_version) return last_asset_versions_by_task_id - - -def register(session): - TaskToVersionStatus(session).register() diff --git a/services/processor/processor/default_handlers/event_thumbnail_updates.py b/services/processor/processor/default_handlers/event_thumbnail_updates.py index d8940b9..914ac14 100644 --- a/services/processor/processor/default_handlers/event_thumbnail_updates.py +++ b/services/processor/processor/default_handlers/event_thumbnail_updates.py @@ -160,7 +160,3 @@ def filter_entities(self, event): filtered_entities_info[project_id] = [] filtered_entities_info[project_id].append(entity_info) return filtered_entities_info - - -def register(session): - ThumbnailEvents(session).register() diff --git a/services/processor/processor/default_handlers/event_version_to_task_statuses.py b/services/processor/processor/default_handlers/event_version_to_task_statuses.py index ff78052..3f1ff8b 100644 --- a/services/processor/processor/default_handlers/event_version_to_task_statuses.py +++ b/services/processor/processor/default_handlers/event_version_to_task_statuses.py @@ -246,9 +246,3 @@ def statuses_for_tasks(self, session, task_entities, project_id): } return output - - -def register(session): - '''Register plugin. Called when used as an plugin.''' - - VersionToTaskStatus(session).register() diff --git a/services/processor/processor/handlers_to_convert/action_create_review_session.py b/services/processor/processor/handlers_to_convert/action_create_review_session.py index 5bdff94..e036ae8 100644 --- a/services/processor/processor/handlers_to_convert/action_create_review_session.py +++ b/services/processor/processor/handlers_to_convert/action_create_review_session.py @@ -309,8 +309,3 @@ def _fill_review_template(self, template, data): exc_info=True ) return output - - -def register(session): - '''Register plugin. Called when used as an plugin.''' - CreateDailyReviewSessionServerAction(session).register() diff --git a/services/processor/processor/handlers_to_convert/event_del_avalon_id_from_new.py b/services/processor/processor/handlers_to_convert/event_del_avalon_id_from_new.py index 2f6aaa3..25df654 100644 --- a/services/processor/processor/handlers_to_convert/event_del_avalon_id_from_new.py +++ b/services/processor/processor/handlers_to_convert/event_del_avalon_id_from_new.py @@ -47,8 +47,3 @@ def launch(self, session, event): except Exception: session.rollback() continue - - -def register(session): - '''Register plugin. Called when used as an plugin.''' - DelAvalonIdFromNew(session).register() diff --git a/services/processor/processor/handlers_to_convert/event_sync_links.py b/services/processor/processor/handlers_to_convert/event_sync_links.py index e0f5d67..32ced7e 100644 --- a/services/processor/processor/handlers_to_convert/event_sync_links.py +++ b/services/processor/processor/handlers_to_convert/event_sync_links.py @@ -138,8 +138,3 @@ def _get_mongo_ids_by_ftrack_ids(self, session, attr_id, ftrack_ids): mongo_id_by_ftrack_id[ftrack_id] = mongo_id return mongo_id_by_ftrack_id - - -def register(session): - '''Register plugin. Called when used as an plugin.''' - SyncLinksToAYON(session).register() diff --git a/services/processor/processor/handlers_to_convert/event_sync_to_avalon.py b/services/processor/processor/handlers_to_convert/event_sync_to_avalon.py index 8993ee0..c988d96 100644 --- a/services/processor/processor/handlers_to_convert/event_sync_to_avalon.py +++ b/services/processor/processor/handlers_to_convert/event_sync_to_avalon.py @@ -2728,8 +2728,3 @@ def _mongo_id_configuration( temp_dict[entity_type] = mongo_id_configuration_id return mongo_id_configuration_id - - -def register(session): - '''Register plugin. Called when used as an plugin.''' - SyncToAvalonEvent(session).register() From eeb145fd282b9ba688cdfe4908f7657c84bf98f4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 13 Sep 2024 13:59:44 +0200 Subject: [PATCH 7/9] log error before handling session --- .../ayon_ftrack/common/event_handlers/ftrack_event_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_ftrack/common/event_handlers/ftrack_event_handler.py b/client/ayon_ftrack/common/event_handlers/ftrack_event_handler.py index e2e30d1..3aa92a1 100644 --- a/client/ayon_ftrack/common/event_handlers/ftrack_event_handler.py +++ b/client/ayon_ftrack/common/event_handlers/ftrack_event_handler.py @@ -66,14 +66,14 @@ def _launch(self, event): self.process(event) except Exception as exc: - self.session.rollback() - self.session._configure_locations() self.log.error( "Event \"{}\" Failed: {}".format( self.__class__.__name__, str(exc) ), exc_info=True ) + self.session.rollback() + self.session._configure_locations() def _translate_event(self, event, session=None): """Receive entity objects based on event. From 14379606e9ad2867783d414b45ff81693c2dec84 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 13 Sep 2024 14:49:42 +0200 Subject: [PATCH 8/9] typehints and formatting changes --- .../event_handlers/ftrack_action_handler.py | 180 ++++++++++------ .../event_handlers/ftrack_base_handler.py | 198 +++++++++++------- .../event_handlers/ftrack_event_handler.py | 38 ++-- 3 files changed, 266 insertions(+), 150 deletions(-) diff --git a/client/ayon_ftrack/common/event_handlers/ftrack_action_handler.py b/client/ayon_ftrack/common/event_handlers/ftrack_action_handler.py index 10c8dbc..e668971 100644 --- a/client/ayon_ftrack/common/event_handlers/ftrack_action_handler.py +++ b/client/ayon_ftrack/common/event_handlers/ftrack_action_handler.py @@ -1,4 +1,5 @@ import functools +from typing import Optional, List, Dict, Any, Union import ftrack_api @@ -28,25 +29,26 @@ class BaseAction(BaseHandler): Args: session (ftrack_api.Session): Connected ftrack session. + """ __ignore_handler_class = True - label = None - variant = None - identifier = None - description = None - icon = None - handler_type = "Action" - preactions = [] + label: Optional[str] = None + variant: Optional[str] = None + identifier: Optional[str] = None + description: Optional[str] = None + icon: Optional[str] = None + handler_type: str = "Action" + preactions: List[str] = [] - _full_label = None - _discover_identifier = None - _launch_identifier = None + _full_label: Optional[str] = None + _discover_identifier: Optional[str] = None + _launch_identifier: Optional[str] = None - settings_frack_subkey = "user_handlers" - settings_enabled_key = "enabled" + settings_frack_subkey: str = "user_handlers" + settings_enabled_key: str = "enabled" - def __init__(self, session): + def __init__(self, session: ftrack_api.Session): # Validate minimum requirements if not self.label: raise ValueError("Action missing 'label'.") @@ -61,27 +63,27 @@ def setup_launch_wrapper(self): self._launch = self.launch_wrapper(self._launch) @property - def discover_identifier(self): + def discover_identifier(self) -> str: return self.identifier @property - def launch_identifier(self): + def launch_identifier(self) -> str: return self.identifier @property - def handler_label(self): + def handler_label(self) -> str: return self.full_label @property - def full_label(self): + def full_label(self) -> str: """Full label of action. Value of full label is cached. Returns: str: Label created from 'label' and 'variant' attributes. - """ + """ if self._full_label is None: if self.variant: label = "{} {}".format(self.label, self.variant) @@ -92,7 +94,6 @@ def full_label(self): def register(self): """Register to ftrack topics to discover and launch action.""" - self.session.event_hub.subscribe( "topic=ftrack.action.discover", self._discover, @@ -104,9 +105,12 @@ def register(self): ).format(self.launch_identifier) self.session.event_hub.subscribe(launch_subscription, self._launch) - def _translate_event(self, event, session=None): + def _translate_event( + self, + event: ftrack_api.event.base.Event, + session: Optional[ftrack_api.Session] = None + ) -> List[ftrack_api.entity.base.Entity]: """Translate event to receive entities based on it's data.""" - if session is None: session = self.session @@ -127,7 +131,9 @@ def _translate_event(self, event, session=None): return _entities - def _discover(self, event): + def _discover( + self, event: ftrack_api.event.base.Event + ) -> Optional[Dict[str, Any]]: """Decide if and how will be action showed to user in ftrack. Args: @@ -138,8 +144,8 @@ def _discover(self, event): Union[None, Dict[str, Any]]: None if action is not returned otherwise returns items to show in UI (structure of items is defined by ftrack and can be found in documentation). - """ + """ entities = self._translate_event(event) if not entities: return None @@ -162,7 +168,12 @@ def _discover(self, event): }] } - def discover(self, session, entities, event): + def discover( + self, + session: ftrack_api.Session, + entities: List[ftrack_api.entity.base.Entity], + event: ftrack_api.event.base.Event, + ) -> bool: """Decide if action is showed to used based on event data. Action should override the method to implement logic to show the @@ -178,11 +189,13 @@ def discover(self, session, entities, event): Returns: bool: True if action should be returned. - """ + """ return False - def _handle_preactions(self, session, event): + def _handle_preactions( + self, session: ftrack_api.Session, event: ftrack_api.event.base.Event + ) -> bool: """Launch actions before launching this action. Concept came from Pype and got deprecated (and used) over time. Should @@ -204,8 +217,8 @@ def _handle_preactions(self, session, event): Preactions are marked as deprecated. Server actions should not use preactions and local actions use local identifier which is hard to handle automatically - """ + """ # If preactions are not set if len(self.preactions) == 0: return True @@ -261,7 +274,9 @@ def wrapper_func(*args, **kwargs): return output return wrapper_func - def _launch(self, event): + def _launch( + self, event: ftrack_api.event.base.Event + ) -> Optional[Dict[str, Any]]: entities = self._translate_event(event) if not entities: return @@ -278,7 +293,12 @@ def _launch(self, event): return self._handle_result(response) - def launch(self, session, entities, event): + def launch( + self, + session: ftrack_api.Session, + entities: List[ftrack_api.entity.base.Entity], + event: ftrack_api.event.base.Event + ) -> Optional[Union[bool, Dict[str, Any]]]: """Main part of handling event callback. Args: @@ -287,14 +307,18 @@ def launch(self, session, entities, event): event (ftrack_api.Event): Ftrack event to process. Returns: - Union[None, bool, Dict[str, Any]]: None if nothing should be showed - to user when done, 'True'/'False' if process succeded/failed - or more complex data strucure e.g. to show interface to user. - """ + Union[bool, Dict[str, Any]]: True or false for success or fail, + or more complex data structure e.g. to show interface to user. + """ raise NotImplementedError() - def _interface(self, session, entities, event): + def _interface( + self, + session: ftrack_api.Session, + entities: List[ftrack_api.entity.base.Entity], + event: ftrack_api.event.base.Event + ) -> Optional[Dict[str, Any]]: interface = self.interface(session, entities, event) if not interface: return @@ -320,7 +344,12 @@ def _interface(self, session, entities, event): ) ) - def interface(self, session, entities, event): + def interface( + self, + session: ftrack_api.Session, + entities: List[ftrack_api.entity.base.Entity], + event: ftrack_api.event.base.Event + ) -> Optional[Union[Dict[str, Any], List[Dict[str, Any]]]]: """Show an interface to user befor the action is processed. This is part of launch callback which gives option to return ftrack @@ -339,13 +368,12 @@ def interface(self, session, entities, event): should be showed, list of items to show or dictionary with 'items' key and possibly additional data (e.g. submit button label). - """ + """ return None - def _handle_result(self, result): + def _handle_result(self, result: Any) -> Optional[Dict[str, Any]]: """Validate the returned result from the action callback.""" - if not result: return None @@ -386,7 +414,11 @@ def _handle_result(self, result): return result @staticmethod - def roles_check(settings_roles, user_roles, default=True): + def roles_check( + settings_roles: List[str], + user_roles: List[str], + default: Optional[bool] = True + ) -> bool: """Compare roles from setting and user's roles. Args: @@ -397,8 +429,8 @@ def roles_check(settings_roles, user_roles, default=True): Returns: bool: 'True' if user has at least one role from settings or default if 'settings_roles' is empty. - """ + """ if not settings_roles: return default @@ -412,7 +444,11 @@ def roles_check(settings_roles, user_roles, default=True): return False @classmethod - def get_user_entity_from_event(cls, session, event): + def get_user_entity_from_event( + cls, + session: ftrack_api.Session, + event: ftrack_api.event.base.Event + ) -> Optional[ftrack_api.entity.user.User]: """Query user entity from event.""" not_set = object() @@ -437,15 +473,23 @@ def get_user_entity_from_event(cls, session, event): return user_entity @classmethod - def get_user_roles_from_event(cls, session, event, lower=False): + def get_user_roles_from_event( + cls, + session: ftrack_api.Session, + event: ftrack_api.event.base.Event, + lower: Optional[bool] = False + ) -> List[str]: """Get user roles based on data in event. Args: session (ftrack_api.Session): Prepared ftrack session. event (ftrack_api.event.Event): Event which is processed. lower (Optional[bool]): Lower the role names. Default 'False'. - """ + Returns: + List[str]: List of user roles. + + """ not_set = object() user_roles = event["data"].get("user_roles", not_set) @@ -461,8 +505,11 @@ def get_user_roles_from_event(cls, session, event, lower=False): return user_roles def get_project_name_from_event_with_entities( - self, session, event, entities - ): + self, + session: ftrack_api.Session, + event: ftrack_api.event.base.Event, + entities: List[ftrack_api.entity.base.Entity], + ) -> Optional[str]: """Load or query and fill project entity from/to event data. Project data are stored by ftrack id because in most cases it is @@ -472,8 +519,11 @@ def get_project_name_from_event_with_entities( session (ftrack_api.Session): Current session. event (ftrack_api.Event): Processed event by session. entities (List[Any]): Ftrack entities of selection. - """ + Returns: + Optional[str]: Project name from event data. + + """ # Try to get project entity from event project_name = event["data"].get("project_name") if not project_name: @@ -485,7 +535,12 @@ def get_project_name_from_event_with_entities( event["data"]["project_name"] = project_name return project_name - def get_ftrack_settings(self, session, event, entities): + def get_ftrack_settings( + self, + session: ftrack_api.Session, + event: ftrack_api.event.base.Event, + entities: List[ftrack_api.entity.base.Entity], + ) -> Dict[str, Any]: project_name = self.get_project_name_from_event_with_entities( session, event, entities ) @@ -494,12 +549,16 @@ def get_ftrack_settings(self, session, event, entities): ) return project_settings["ftrack"] - def valid_roles(self, session, entities, event): + def valid_roles( + self, + session: ftrack_api.Session, + entities: List[ftrack_api.entity.base.Entity], + event: ftrack_api.event.base.Event, + ) -> bool: """Validate user roles by settings. Method requires to have set `settings_key` attribute. """ - ftrack_settings = self.get_ftrack_settings(session, event, entities) settings = ( ftrack_settings[self.settings_frack_subkey][self.settings_key] @@ -522,11 +581,11 @@ class LocalAction(BaseAction): Handy for actions where matters if is executed on specific machine. """ - __ignore_handler_class = True - _full_launch_identifier = None + __ignore_handler_class: bool = True + _full_launch_identifier: bool = None @property - def discover_identifier(self): + def discover_identifier(self) -> str: if self._discover_identifier is None: self._discover_identifier = "{}.{}".format( self.identifier, self.process_identifier() @@ -534,7 +593,7 @@ def discover_identifier(self): return self._discover_identifier @property - def launch_identifier(self): + def launch_identifier(self) -> str: """Catch all topics with same identifier.""" if self._launch_identifier is None: self._launch_identifier = "{}.*".format(self.identifier) @@ -554,7 +613,6 @@ def register(self): Filter events to this session user. """ - # Subscribe to discover topic for user under this session self.session.event_hub.subscribe( "topic=ftrack.action.discover and source.user.username={}".format( @@ -574,7 +632,9 @@ def register(self): self._launch ) - def _discover(self, event): + def _discover( + self, event: ftrack_api.event.base.Event + ) -> Optional[Dict[str, Any]]: entities = self._translate_event(event) if not entities: return @@ -597,7 +657,9 @@ def _discover(self, event): }] } - def _launch(self, event): + def _launch( + self, event: ftrack_api.event.base.Event + ) -> Optional[Dict[str, Any]]: event_identifier = event["data"]["actionIdentifier"] # Check if identifier is same # - show message that acion may not be triggered on this machine @@ -609,7 +671,7 @@ def _launch(self, event): " where this action could be launched." ) } - return super(LocalAction, self)._launch(event) + return super()._launch(event) class ServerAction(BaseAction): @@ -618,6 +680,6 @@ class ServerAction(BaseAction): Unlike the `BaseAction` roles are not checked on register but on discover. For the same reason register is modified to not filter topics by username. """ - __ignore_handler_class = True + __ignore_handler_class: bool = True - settings_frack_subkey = "service_event_handlers" + settings_frack_subkey: str = "service_event_handlers" diff --git a/client/ayon_ftrack/common/event_handlers/ftrack_base_handler.py b/client/ayon_ftrack/common/event_handlers/ftrack_base_handler.py index 90935d9..d3d1c39 100644 --- a/client/ayon_ftrack/common/event_handlers/ftrack_base_handler.py +++ b/client/ayon_ftrack/common/event_handlers/ftrack_base_handler.py @@ -9,6 +9,7 @@ import time import logging from abc import ABCMeta, abstractmethod +from typing import Optional, Any, Union, Iterable, List, Dict, Tuple import ftrack_api @@ -28,15 +29,15 @@ class BaseHandler(metaclass=ABCMeta): session (ftrack_api.Session): Connected ftrack session. """ - _log = None - _process_id = None + _log: Optional[logging.Logger] = None + _process_id: Optional[str] = None # Default priority is 100 - enabled = True - priority = 100 - handler_type = "Base" - _handler_label = None + enabled: bool = True + priority: int = 100 + handler_type: str = "Base" + _handler_label: Optional[str] = None # Mark base classes to be ignored for discovery - __ignore_handler_class = True + __ignore_handler_class: bool = True def __init__(self, session): if not isinstance(session, ftrack_api.session.Session): @@ -62,21 +63,21 @@ def ignore_handler_class(cls) -> bool: return getattr(cls, f"{cls_name}__ignore_handler_class", False) @staticmethod - def join_filter_values(values): + def join_filter_values(values: Iterable[str]) -> str: return ",".join({'"{}"'.format(value) for value in values}) @classmethod - def join_query_keys(cls, keys): + def join_query_keys(cls, keys: Iterable[str]) -> str: return cls.join_filter_values(keys) @property - def log(self): + def log(self) -> logging.Logger: """Quick access to logger. Returns: logging.Logger: Logger that can be used for logging of handler. - """ + """ if self._log is None: # TODO better logging mechanism self._log = logging.getLogger(self.__class__.__name__) @@ -84,35 +85,34 @@ def log(self): return self._log @property - def handler_label(self): + def handler_label(self) -> str: if self._handler_label is None: self._handler_label = self.__class__.__name__ return self._handler_label @property - def session(self): + def session(self) -> ftrack_api.Session: """Fast access to session. Returns: session (ftrack_api.Session): Session which is source of events. - """ + """ return self._session def reset_session(self): """Reset session cache.""" - self.session.reset() @staticmethod - def process_identifier(): + def process_identifier() -> str: """Helper property to have unified access to process id. Todos: Use some global approach rather then implementation on 'BaseEntity'. - """ + """ if not BaseHandler._process_id: BaseHandler._process_id = str(uuid.uuid4()) return BaseHandler._process_id @@ -120,7 +120,6 @@ def process_identifier(): @abstractmethod def register(self): """Subscribe to event topics.""" - pass def register_wrapper(self, func): @@ -185,8 +184,8 @@ def _get_entity_type(self, entity, session=None): Todos: Use object id rather. - """ + """ # Get entity type and make sure it is lower cased. Most places except # the component tab in the Sidebar will use lower case notation. entity_type = entity.get("entityType").replace("_", "").lower() @@ -212,15 +211,21 @@ def _get_entity_type(self, entity, session=None): "Unable to translate entity type: {0}.".format(entity_type) ) - def show_message(self, event, message, success=False): + def show_message( + self, + event: ftrack_api.event.base.Event, + message: str, + success: Optional[bool]=False, + ): """Shows message to user who triggered event. Args: - event (ftrack_api.Event): Event used for source of user id. + event (ftrack_api.event.base.Event): Event used for source + of user id. message (str): Message that will be shown to user. success (bool): Define type (color) of message. False -> red color. - """ + """ if not isinstance(success, bool): success = False @@ -248,13 +253,13 @@ def show_message(self, event, message, success=False): def show_interface( self, - items, - title="", - user_id=None, - user=None, - event=None, - username=None, - submit_btn_label=None + items: List[Dict[str, Any]], + title: Optional[str] = "", + user_id: Optional[str] = None, + user: Optional[Any] = None, + event: Optional[ftrack_api.event.base.Event] = None, + username: Optional[str] = None, + submit_btn_label: Optional[str] = None, ): """Shows ftrack widgets interface to user. @@ -273,8 +278,8 @@ def show_interface( username (str): Username of user to get it's id. This is slowest way how user id is received. submit_btn_label (str): Label of submit button in ftrack widget. - """ + """ if user_id: pass @@ -319,7 +324,16 @@ def show_interface( on_error="ignore" ) - def show_interface_from_dict(self, messages, *args, **kwargs): + def show_interface_from_dict( + self, + messages: Dict[str, Union[str, List[str]]], + title: Optional[str] = "", + user_id: Optional[str] = None, + user: Optional[Any] = None, + event: Optional[ftrack_api.event.base.Event] = None, + username: Optional[str] = None, + submit_btn_label: Optional[str] = None, + ): # TODO Find out how and where is this used if not messages: self.log.debug("No messages to show! (messages dict is empty)") @@ -330,32 +344,35 @@ def show_interface_from_dict(self, messages, *args, **kwargs): for key, value in messages.items(): if not first: items.append(splitter) - else: - first = False + first = False items.append({"type": "label", "value": "
{}
".format(item) - } - items.append(message) - else: - message = {"type": "label", "value": "{}
".format(value)} - items.append(message) - - self.show_interface(items, *args, **kwargs) + if isinstance(value, str): + value = [value] + + for item in value: + items.append({"type": "label", "value": f"{item}
"}) + + self.show_interface( + items, + title=title, + user_id=user_id, + user=user, + event=event, + username=username, + submit_btn_label=submit_btn_label + ) def trigger_action( self, - action_identifier, - event=None, - session=None, - selection=None, - user_data=None, - topic="ftrack.action.launch", - additional_event_data={}, - on_error="ignore" + action_identifier: str, + event: Optional[ftrack_api.event.base.Event] = None, + session: Optional[ftrack_api.Session] = None, + selection: Optional[List[Dict[str, str]]] = None, + user_data: Optional[Dict[str, Any]] = None, + topic: Optional[str] = "ftrack.action.launch", + additional_event_data: Optional[Dict[str, Any]] = None, + on_error: Optional[str] = "ignore" ): self.log.debug( "Triggering action \"{}\" Begins".format(action_identifier)) @@ -403,12 +420,12 @@ def trigger_action( def trigger_event( self, - topic, - event_data=None, - session=None, - source=None, - event=None, - on_error="ignore" + topic: str, + event_data: Optional[Dict[str, Any]] = None, + session: Optional[ftrack_api.Session] = None, + source: Optional[Dict[str, Any]] = None, + event: Optional[ftrack_api.event.base.Event] = None, + on_error: Optional[str] = "ignore" ): if session is None: session = self.session @@ -430,7 +447,11 @@ def trigger_event( "Publishing event: {}" ).format(str(event.__dict__))) - def get_project_from_entity(self, entity, session=None): + def get_project_from_entity( + self, + entity: ftrack_api.entity.base.Entity, + session: Optional[ftrack_api.Session] = None + ): low_entity_type = entity.entity_type.lower() if low_entity_type == "project": return entity @@ -464,7 +485,12 @@ def get_project_from_entity(self, entity, session=None): "Project where id is {}".format(project_data["id"]) ).one() - def get_project_entity_from_event(self, session, event, project_id): + def get_project_entity_from_event( + self, + session: ftrack_api.Session, + event: ftrack_api.event.base.Event, + project_id: str, + ): """Load or query and fill project entity from/to event data. Project data are stored by ftrack id because in most cases it is @@ -478,8 +504,8 @@ def get_project_entity_from_event(self, session, event, project_id): Returns: Union[str, None]: Project name based on entities or None if project cannot be defined. - """ + """ if not project_id: raise ValueError( "Entered `project_id` is not valid. {} ({})".format( @@ -501,7 +527,12 @@ def get_project_entity_from_event(self, session, event, project_id): return project_entity - def get_project_name_from_event(self, session, event, project_id): + def get_project_name_from_event( + self, + session: ftrack_api.Session, + event: ftrack_api.event.base.Event, + project_id: str, + ): """Load or query and fill project entity from/to event data. Project data are stored by ftrack id because in most cases it is @@ -515,8 +546,8 @@ def get_project_name_from_event(self, session, event, project_id): Returns: Union[str, None]: Project name based on entities or None if project cannot be defined. - """ + """ if not project_id: raise ValueError( "Entered `project_id` is not valid. {} ({})".format( @@ -537,7 +568,11 @@ def get_project_name_from_event(self, session, event, project_id): project_id_mapping[project_id] = project_name return project_name - def get_ayon_project_from_event(self, event, project_name): + def get_ayon_project_from_event( + self, + event: ftrack_api.event.base.Event, + project_name: str + ): """Get AYON project from event. Args: @@ -546,8 +581,8 @@ def get_ayon_project_from_event(self, event, project_name): Returns: Union[dict[str, Any], None]: AYON project. - """ + """ ayon_projects = event["data"].setdefault("ayon_projects", {}) if project_name in ayon_projects: return ayon_projects[project_name] @@ -558,7 +593,11 @@ def get_ayon_project_from_event(self, event, project_name): ayon_projects[project_name] = project return project - def get_project_settings_from_event(self, event, project_name): + def get_project_settings_from_event( + self, + event: ftrack_api.event.base.Event, + project_name: str + ): """Load or fill AYON's project settings from event data. Project data are stored by ftrack id because in most cases it is @@ -567,8 +606,8 @@ def get_project_settings_from_event(self, event, project_name): Args: event (ftrack_api.Event): Processed event by session. project_name (str): Project name. - """ + """ project_settings_by_name = event["data"].setdefault( "project_settings", {} ) @@ -588,19 +627,21 @@ def get_project_settings_from_event(self, event, project_name): return copy.deepcopy(project_settings) @staticmethod - def get_entity_path(entity): + def get_entity_path(entity: ftrack_api.entity.base.Entity) -> str: """Return full hierarchical path to entity.""" - return "/".join( [ent["name"] for ent in entity["link"]] ) @classmethod def add_traceback_to_job( - cls, job, session, exc_info, - description=None, - component_name=None, - job_status=None + cls, + job: ftrack_api.entity.job.Job, + session: ftrack_api.Session, + exc_info: Tuple, + description: Optional[str] = None, + component_name: Optional[str] = None, + job_status: Optional[str] = None ): """Add traceback file to a job. @@ -617,8 +658,8 @@ def add_traceback_to_job( not specified. job_status (str): Status of job which will be set. By default is set to 'failed'. - """ + """ if description: job_data = { "description": description @@ -654,7 +695,12 @@ def add_traceback_to_job( os.remove(temp_filepath) @staticmethod - def add_file_component_to_job(job, session, filepath, basename=None): + def add_file_component_to_job( + job: ftrack_api.entity.job.Job, + session: ftrack_api.Session, + filepath: str, + basename: Optional[str] = None + ): """Add filepath as downloadable component to job. Args: @@ -667,8 +713,8 @@ def add_file_component_to_job(job, session, filepath, basename=None): user's side. Must be without extension otherwise extension will be duplicated in downloaded name. Basename from entered path used when not entered. - """ + """ # Make sure session's locations are configured # - they can be deconfigured e.g. using `rollback` method session._configure_locations() diff --git a/client/ayon_ftrack/common/event_handlers/ftrack_event_handler.py b/client/ayon_ftrack/common/event_handlers/ftrack_event_handler.py index 3aa92a1..bd4ae32 100644 --- a/client/ayon_ftrack/common/event_handlers/ftrack_event_handler.py +++ b/client/ayon_ftrack/common/event_handlers/ftrack_event_handler.py @@ -1,3 +1,7 @@ +from typing import Optional + +import ftrack_api + from .ftrack_base_handler import BaseHandler @@ -9,33 +13,34 @@ class BaseEventHandler(BaseHandler): By default is listening to "ftrack.update". To change it override 'register' method of change 'subscription_topic' attribute. """ - __ignore_handler_class = True + __ignore_handler_class: bool = True - subscription_topic = "ftrack.update" - handler_type = "Event" + subscription_topic: str = "ftrack.update" + handler_type: str = "Event" def register(self): """Register to subscription topic.""" - self.session.event_hub.subscribe( "topic={}".format(self.subscription_topic), self._process, priority=self.priority ) - def process(self, event): + def process(self, event: ftrack_api.event.base.Event): """Callback triggered on event with matching topic. Args: - session (ftrack_api.Session): Ftrack session which triggered - the event. event (ftrack_api.Event): Ftrack event to process. - """ + """ return self.launch(self.session, event) - def launch(self, session, event): + def launch( + self, + session: ftrack_api.Session, + event: ftrack_api.event.base.Event + ): """Deprecated method used for backwards compatibility. Override 'process' method rather then 'launch'. Method name 'launch' @@ -46,19 +51,18 @@ def launch(self, session, event): session (ftrack_api.Session): Ftrack session which triggered the event. event (ftrack_api.Event): Ftrack event to process. - """ + """ raise NotImplementedError() - def _process(self, event): + def _process(self, event: ftrack_api.event.base.Event): return self._launch(event) - def _launch(self, event): + def _launch(self, event: ftrack_api.event.base.Event): """Callback kept for backwards compatibility. Will be removed when default """ - self.session.rollback() self.session._local_cache.clear() @@ -75,7 +79,11 @@ def _launch(self, event): self.session.rollback() self.session._configure_locations() - def _translate_event(self, event, session=None): + def _translate_event( + self, + event: ftrack_api.event.base.Event, + session: Optional[ftrack_api.Session] = None + ): """Receive entity objects based on event. Args: @@ -84,8 +92,8 @@ def _translate_event(self, event, session=None): Returns: List[ftrack_api.Entity]: Queried entities based on event data. - """ + """ return self._get_entities( event, session, From f7690ffcc255ace6550853e0ac9fc3b31edcde49 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 13 Sep 2024 14:54:38 +0200 Subject: [PATCH 9/9] do not allow to get next event if not connected and break in empty queue --- services/processor/processor/ftrack_session.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/services/processor/processor/ftrack_session.py b/services/processor/processor/ftrack_session.py index 8e7eb0b..21d3434 100644 --- a/services/processor/processor/ftrack_session.py +++ b/services/processor/processor/ftrack_session.py @@ -30,6 +30,8 @@ class ProcessEventHub(ftrack_api.event.hub.EventHub): _server_con = None def get_next_ftrack_event(self): + if not self.connected: + return None return enroll_event_job( source_topic="ftrack.leech", target_topic="ftrack.proc", @@ -71,9 +73,6 @@ def wait(self, duration=None): started = time.time() while True: - if not self.connected: - break - job = None empty_queue = False try: @@ -89,6 +88,9 @@ def wait(self, duration=None): # Do not do this under except handling to avoid confusing # traceback if something happens if empty_queue: + if not self.connected: + break + if not self.load_event_from_jobs(): time.sleep(0.1) continue