From 77e214d8e6c2ece97ef3243c8e374a4c854f12f9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 17 Apr 2024 10:31:41 +0200 Subject: [PATCH 01/19] add launch command to applications addon --- .../client/ayon_applications/addon.py | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/server_addon/applications/client/ayon_applications/addon.py b/server_addon/applications/client/ayon_applications/addon.py index 0f1b68af0e..624f158baa 100644 --- a/server_addon/applications/client/ayon_applications/addon.py +++ b/server_addon/applications/client/ayon_applications/addon.py @@ -110,6 +110,26 @@ class ApplicationsAddon(AYONAddon, IPluginPaths): ] } + def launch_application( + self, app_name, project_name, folder_path, task_name + ): + """Launch application. + + Args: + app_name (str): Full application name e.g. 'maya/2024'. + project_name (str): Project name. + folder_path (str): Folder path. + task_name (str): Task name. + + """ + app_manager = self.get_applications_manager() + return app_manager.launch( + app_name, + project_name=project_name, + folder_path=folder_path, + task_name=task_name, + ) + # --- CLI --- def cli(self, addon_click_group): main_group = click_wrap.group( @@ -134,6 +154,17 @@ class ApplicationsAddon(AYONAddon, IPluginPaths): default=None ) ) + ( + main_group.command( + self._cli_launch_applications, + name="launch", + help="Launch application" + ) + .option("--app", help="Application name") + .option("--project", help="Project name") + .option("--folder", help="Folder path") + .option("--task", help="Task name") + ) # Convert main command to click object and add it to parent group addon_click_group.add_command( main_group.to_click_obj() @@ -171,3 +202,17 @@ class ApplicationsAddon(AYONAddon, IPluginPaths): with open(output_json_path, "w") as file_stream: json.dump(env, file_stream, indent=4) + + def _cli_launch_applications(self, project, folder, task, app): + """Produces json file with environment based on project and app. + + Called by farm integration to propagate environment into farm jobs. + + Args: + project (str): Project name. + folder (str): Folder path. + task (str): Task name. + app (str): Full application name e.g. 'maya/2024'. + + """ + self.launch_application(app, project, folder, task,) From 7f4292c797a66204cad817ec765ef232a755f035 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 17 Apr 2024 10:32:14 +0200 Subject: [PATCH 02/19] bump version '0.2.1' --- server_addon/applications/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/applications/package.py b/server_addon/applications/package.py index ce312ed662..bcc91f1d84 100644 --- a/server_addon/applications/package.py +++ b/server_addon/applications/package.py @@ -1,3 +1,3 @@ name = "applications" title = "Applications" -version = "0.2.0" +version = "0.2.1" From 787e3ad90d557dd8853151d7093c7ce4a72d5964 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 17 Apr 2024 11:27:13 +0200 Subject: [PATCH 03/19] remove trailing comma Co-authored-by: Roy Nieterau --- server_addon/applications/client/ayon_applications/addon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/applications/client/ayon_applications/addon.py b/server_addon/applications/client/ayon_applications/addon.py index 624f158baa..a573ee666a 100644 --- a/server_addon/applications/client/ayon_applications/addon.py +++ b/server_addon/applications/client/ayon_applications/addon.py @@ -215,4 +215,4 @@ class ApplicationsAddon(AYONAddon, IPluginPaths): app (str): Full application name e.g. 'maya/2024'. """ - self.launch_application(app, project, folder, task,) + self.launch_application(app, project, folder, task) From 24c3b2c5c8d9a2f725049b7c7bdb699dadd2d37a Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 17 Apr 2024 11:27:35 +0200 Subject: [PATCH 04/19] fix docstring Co-authored-by: Roy Nieterau --- server_addon/applications/client/ayon_applications/addon.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/server_addon/applications/client/ayon_applications/addon.py b/server_addon/applications/client/ayon_applications/addon.py index a573ee666a..f3ebedd364 100644 --- a/server_addon/applications/client/ayon_applications/addon.py +++ b/server_addon/applications/client/ayon_applications/addon.py @@ -204,9 +204,7 @@ class ApplicationsAddon(AYONAddon, IPluginPaths): json.dump(env, file_stream, indent=4) def _cli_launch_applications(self, project, folder, task, app): - """Produces json file with environment based on project and app. - - Called by farm integration to propagate environment into farm jobs. + """Launch application. Args: project (str): Project name. From d8081868d740dae87829de5e61988f41884186db Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 17 Apr 2024 11:31:05 +0200 Subject: [PATCH 05/19] require arguments for application launch --- .../applications/client/ayon_applications/addon.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server_addon/applications/client/ayon_applications/addon.py b/server_addon/applications/client/ayon_applications/addon.py index f3ebedd364..a8eaa46cad 100644 --- a/server_addon/applications/client/ayon_applications/addon.py +++ b/server_addon/applications/client/ayon_applications/addon.py @@ -160,10 +160,10 @@ class ApplicationsAddon(AYONAddon, IPluginPaths): name="launch", help="Launch application" ) - .option("--app", help="Application name") - .option("--project", help="Project name") - .option("--folder", help="Folder path") - .option("--task", help="Task name") + .option("--app", required=True, help="Application name") + .option("--project", required=True, help="Project name") + .option("--folder", required=True, help="Folder path") + .option("--task", required=True, help="Task name") ) # Convert main command to click object and add it to parent group addon_click_group.add_command( From 02f0994903f05429a54b7e24953a2710516433d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 25 Apr 2024 14:57:19 +0200 Subject: [PATCH 06/19] :bug: fix support of PySide6 in Unreal 5.4 --- client/ayon_core/hosts/unreal/ue_workers.py | 32 +++++++++++++++------ pyproject.toml | 2 +- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/client/ayon_core/hosts/unreal/ue_workers.py b/client/ayon_core/hosts/unreal/ue_workers.py index e3f8729c2e..a987abcc83 100644 --- a/client/ayon_core/hosts/unreal/ue_workers.py +++ b/client/ayon_core/hosts/unreal/ue_workers.py @@ -260,11 +260,11 @@ class UEProjectGenerationWorker(UEWorker): self.failed.emit(msg, return_code) raise RuntimeError(msg) - # ensure we have PySide2 installed in engine + # ensure we have PySide2/6 installed in engine self.progress.emit(0) self.stage_begin.emit( - (f"Checking PySide2 installation... {stage_count} " + (f"Checking Qt bindings installation... {stage_count} " f" out of {stage_count}")) python_path = None if platform.system().lower() == "windows": @@ -287,11 +287,25 @@ class UEProjectGenerationWorker(UEWorker): msg = f"Unreal Python not found at {python_path}" self.failed.emit(msg, 1) raise RuntimeError(msg) - pyside_cmd = [python_path.as_posix(), - "-m", - "pip", - "install", - "pyside2"] + + pyside_version = "PySide2" + ue_version = self.ue_version.split(".") + if int(ue_version[0]) == 5 and int(ue_version[1]) >= 4: + pyside_version = "PySide6" + + site_packages_prefix = python_path.parent.as_posix() + + pyside_cmd = [ + python_path.as_posix(), + "-m", "pip", + "install", + "--ignore-installed", + pyside_version, + "--prefix", site_packages_prefix, + ] + + print(f"--- Installing {pyside_version} ...") + print(" ".join(pyside_cmd)) pyside_install = subprocess.Popen(pyside_cmd, stdout=subprocess.PIPE, @@ -306,8 +320,8 @@ class UEProjectGenerationWorker(UEWorker): return_code = pyside_install.wait() if return_code and return_code != 0: - msg = ("Failed to create the project! " - "The installation of PySide2 has failed!") + msg = (f"Failed to create the project! {return_code} " + f"The installation of {pyside_version} has failed!: {pyside_install}") self.failed.emit(msg, return_code) raise RuntimeError(msg) diff --git a/pyproject.toml b/pyproject.toml index c1f6ddfb0b..4726bef41a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -106,7 +106,7 @@ line-ending = "auto" [tool.codespell] # Ignore words that are not in the dictionary. -ignore-words-list = "ayon,ynput,parms,parm,hda,developpement" +ignore-words-list = "ayon,ynput,parms,parm,hda,developpement,ue" skip = "./.*,./package/*,*/vendor/*,*/unreal/integration/*,*/aftereffects/api/extension/js/libs/*" count = true From 7c20ec0b564d5d3392dc558b48400144c56be8c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 25 Apr 2024 15:31:15 +0200 Subject: [PATCH 07/19] :bug: downgrade PySide6 because of the bug --- client/ayon_core/hosts/unreal/ue_workers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/unreal/ue_workers.py b/client/ayon_core/hosts/unreal/ue_workers.py index a987abcc83..cdac2c28af 100644 --- a/client/ayon_core/hosts/unreal/ue_workers.py +++ b/client/ayon_core/hosts/unreal/ue_workers.py @@ -291,7 +291,9 @@ class UEProjectGenerationWorker(UEWorker): pyside_version = "PySide2" ue_version = self.ue_version.split(".") if int(ue_version[0]) == 5 and int(ue_version[1]) >= 4: - pyside_version = "PySide6" + # Use PySide6 6.6.3 because 6.7.0 had a bug + # - 'QPushButton' can't be added to 'QBoxLayout' + pyside_version = "PySide6==6.6.3" site_packages_prefix = python_path.parent.as_posix() From 32c538d5c53ef3440d906822871fef8b67c0b8d3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:09:00 +0200 Subject: [PATCH 08/19] copied cache items from tools to lib --- client/ayon_core/lib/__init__.py | 7 + client/ayon_core/lib/cache.py | 243 +++++++++++++++++++++++++++++++ 2 files changed, 250 insertions(+) create mode 100644 client/ayon_core/lib/cache.py diff --git a/client/ayon_core/lib/__init__.py b/client/ayon_core/lib/__init__.py index 408262ca42..e436396c6c 100644 --- a/client/ayon_core/lib/__init__.py +++ b/client/ayon_core/lib/__init__.py @@ -27,6 +27,10 @@ from .local_settings import ( get_openpype_username, ) from .ayon_connection import initialize_ayon_connection +from .cache import ( + CacheItem, + NestedCacheItem, +) from .events import ( emit_event, register_event_callback @@ -157,6 +161,9 @@ __all__ = [ "initialize_ayon_connection", + "CacheItem", + "NestedCacheItem", + "emit_event", "register_event_callback", diff --git a/client/ayon_core/lib/cache.py b/client/ayon_core/lib/cache.py new file mode 100644 index 0000000000..170b9853fe --- /dev/null +++ b/client/ayon_core/lib/cache.py @@ -0,0 +1,243 @@ +import time +import collections + +InitInfo = collections.namedtuple( + "InitInfo", + ["default_factory", "lifetime"] +) + + +def _default_factory_func(): + return None + + +class CacheItem: + """Simple cache item with lifetime and default factory for default value. + + + Default factory should return default value that is used on init + and on reset. + + Args: + default_factory (Optional[callable]): Function that returns default + value used on init and on reset. + lifetime (Optional[int]): Lifetime of the cache data in seconds. + """ + + def __init__(self, default_factory=None, lifetime=None): + if lifetime is None: + lifetime = 120 + self._lifetime = lifetime + self._last_update = None + if default_factory is None: + default_factory = _default_factory_func + self._default_factory = default_factory + self._data = default_factory() + + @property + def is_valid(self): + """Is cache valid to use. + + Return: + bool: True if cache is valid, False otherwise. + """ + + if self._last_update is None: + return False + + return (time.time() - self._last_update) < self._lifetime + + def set_lifetime(self, lifetime): + """Change lifetime of cache item. + + Args: + lifetime (int): Lifetime of the cache data in seconds. + """ + + self._lifetime = lifetime + + def set_invalid(self): + """Set cache as invalid.""" + + self._last_update = None + + def reset(self): + """Set cache as invalid and reset data.""" + + self._last_update = None + self._data = self._default_factory() + + def get_data(self): + """Receive cached data. + + Returns: + Any: Any data that are cached. + """ + + return self._data + + def update_data(self, data): + self._data = data + self._last_update = time.time() + + +class NestedCacheItem: + """Helper for cached items stored in nested structure. + + Example: + >>> cache = NestedCacheItem(levels=2, default_factory=lambda: 0) + >>> cache["a"]["b"].is_valid + False + >>> cache["a"]["b"].get_data() + 0 + >>> cache["a"]["b"] = 1 + >>> cache["a"]["b"].is_valid + True + >>> cache["a"]["b"].get_data() + 1 + >>> cache.reset() + >>> cache["a"]["b"].is_valid + False + + Args: + levels (int): Number of nested levels where read cache is stored. + default_factory (Optional[callable]): Function that returns default + value used on init and on reset. + lifetime (Optional[int]): Lifetime of the cache data in seconds. + _init_info (Optional[InitInfo]): Private argument. Init info for + nested cache where created from parent item. + """ + + def __init__( + self, levels=1, default_factory=None, lifetime=None, _init_info=None + ): + if levels < 1: + raise ValueError("Nested levels must be greater than 0") + self._data_by_key = {} + if _init_info is None: + _init_info = InitInfo(default_factory, lifetime) + self._init_info = _init_info + self._levels = levels + + def __getitem__(self, key): + """Get cached data. + + Args: + key (str): Key of the cache item. + + Returns: + Union[NestedCacheItem, CacheItem]: Cache item. + """ + + cache = self._data_by_key.get(key) + if cache is None: + if self._levels > 1: + cache = NestedCacheItem( + levels=self._levels - 1, + _init_info=self._init_info + ) + else: + cache = CacheItem( + self._init_info.default_factory, + self._init_info.lifetime + ) + self._data_by_key[key] = cache + return cache + + def __setitem__(self, key, value): + """Update cached data. + + Args: + key (str): Key of the cache item. + value (Any): Any data that are cached. + """ + + if self._levels > 1: + raise AttributeError(( + "{} does not support '__setitem__'. Lower nested level by {}" + ).format(self.__class__.__name__, self._levels - 1)) + cache = self[key] + cache.update_data(value) + + def get(self, key): + """Get cached data. + + Args: + key (str): Key of the cache item. + + Returns: + Union[NestedCacheItem, CacheItem]: Cache item. + """ + + return self[key] + + def cached_count(self): + """Amount of cached items. + + Returns: + int: Amount of cached items. + """ + + return len(self._data_by_key) + + def clear_key(self, key): + """Clear cached item by key. + + Args: + key (str): Key of the cache item. + """ + + self._data_by_key.pop(key, None) + + def clear_invalid(self): + """Clear all invalid cache items. + + Note: + To clear all cache items use 'reset'. + """ + + changed = {} + children_are_nested = self._levels > 1 + for key, cache in tuple(self._data_by_key.items()): + if children_are_nested: + output = cache.clear_invalid() + if output: + changed[key] = output + if not cache.cached_count(): + self._data_by_key.pop(key) + elif not cache.is_valid: + changed[key] = cache.get_data() + self._data_by_key.pop(key) + return changed + + def reset(self): + """Reset cache. + + Note: + To clear only invalid cache items use 'clear_invalid'. + """ + + self._data_by_key = {} + + def set_lifetime(self, lifetime): + """Change lifetime of all children cache items. + + Args: + lifetime (int): Lifetime of the cache data in seconds. + """ + + self._init_info.lifetime = lifetime + for cache in self._data_by_key.values(): + cache.set_lifetime(lifetime) + + @property + def is_valid(self): + """Raise reasonable error when called on wrong level. + + Raises: + AttributeError: If called on nested cache item. + """ + + raise AttributeError(( + "{} does not support 'is_valid'. Lower nested level by '{}'" + ).format(self.__class__.__name__, self._levels)) From 8f9ae0669d1b7b86b867a1ff685ebe3b2300b015 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:09:15 +0200 Subject: [PATCH 09/19] import cache items from new location --- client/ayon_core/tools/common_models/hierarchy.py | 3 +-- client/ayon_core/tools/common_models/projects.py | 3 +-- client/ayon_core/tools/common_models/thumbnails.py | 2 +- client/ayon_core/tools/loader/models/actions.py | 2 +- client/ayon_core/tools/loader/models/products.py | 2 +- client/ayon_core/tools/loader/models/sitesync.py | 3 +-- 6 files changed, 6 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/tools/common_models/hierarchy.py b/client/ayon_core/tools/common_models/hierarchy.py index d8b28f020d..78b8a7f492 100644 --- a/client/ayon_core/tools/common_models/hierarchy.py +++ b/client/ayon_core/tools/common_models/hierarchy.py @@ -6,8 +6,7 @@ import ayon_api import six from ayon_core.style import get_default_entity_icon_color - -from .cache import NestedCacheItem +from ayon_core.lib import NestedCacheItem HIERARCHY_MODEL_SENDER = "hierarchy.model" diff --git a/client/ayon_core/tools/common_models/projects.py b/client/ayon_core/tools/common_models/projects.py index e30561000e..19a38bee21 100644 --- a/client/ayon_core/tools/common_models/projects.py +++ b/client/ayon_core/tools/common_models/projects.py @@ -5,8 +5,7 @@ import ayon_api import six from ayon_core.style import get_default_entity_icon_color - -from .cache import CacheItem +from ayon_core.lib import CacheItem PROJECTS_MODEL_SENDER = "projects.model" diff --git a/client/ayon_core/tools/common_models/thumbnails.py b/client/ayon_core/tools/common_models/thumbnails.py index 1c3aadc49f..6d14783b9a 100644 --- a/client/ayon_core/tools/common_models/thumbnails.py +++ b/client/ayon_core/tools/common_models/thumbnails.py @@ -5,7 +5,7 @@ import collections import ayon_api import appdirs -from .cache import NestedCacheItem +from ayon_core.lib import NestedCacheItem FileInfo = collections.namedtuple( "FileInfo", diff --git a/client/ayon_core/tools/loader/models/actions.py b/client/ayon_core/tools/loader/models/actions.py index ad2993af50..cfe91cadab 100644 --- a/client/ayon_core/tools/loader/models/actions.py +++ b/client/ayon_core/tools/loader/models/actions.py @@ -6,6 +6,7 @@ import uuid import ayon_api +from ayon_core.lib import NestedCacheItem from ayon_core.pipeline.load import ( discover_loader_plugins, ProductLoaderPlugin, @@ -17,7 +18,6 @@ from ayon_core.pipeline.load import ( LoadError, IncompatibleLoaderError, ) -from ayon_core.tools.common_models import NestedCacheItem from ayon_core.tools.loader.abstract import ActionItem ACTIONS_MODEL_SENDER = "actions.model" diff --git a/client/ayon_core/tools/loader/models/products.py b/client/ayon_core/tools/loader/models/products.py index 812446a012..a3bbc30a09 100644 --- a/client/ayon_core/tools/loader/models/products.py +++ b/client/ayon_core/tools/loader/models/products.py @@ -5,8 +5,8 @@ import arrow import ayon_api from ayon_api.operations import OperationsSession +from ayon_core.lib import NestedCacheItem from ayon_core.style import get_default_entity_icon_color -from ayon_core.tools.common_models import NestedCacheItem from ayon_core.tools.loader.abstract import ( ProductTypeItem, ProductItem, diff --git a/client/ayon_core/tools/loader/models/sitesync.py b/client/ayon_core/tools/loader/models/sitesync.py index 987510905b..02504c2ad3 100644 --- a/client/ayon_core/tools/loader/models/sitesync.py +++ b/client/ayon_core/tools/loader/models/sitesync.py @@ -2,9 +2,8 @@ import collections from ayon_api import get_representations, get_versions_links -from ayon_core.lib import Logger +from ayon_core.lib import Logger, NestedCacheItem from ayon_core.addon import AddonsManager -from ayon_core.tools.common_models import NestedCacheItem from ayon_core.tools.loader.abstract import ActionItem DOWNLOAD_IDENTIFIER = "sitesync.download" From 734ce367fa3177ac4fd4272a16a00c5e43104d3f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:10:09 +0200 Subject: [PATCH 10/19] added deprecation warnings to classes on previous location --- client/ayon_core/tools/common_models/cache.py | 266 ++---------------- 1 file changed, 29 insertions(+), 237 deletions(-) diff --git a/client/ayon_core/tools/common_models/cache.py b/client/ayon_core/tools/common_models/cache.py index 221a14160c..59b727728f 100644 --- a/client/ayon_core/tools/common_models/cache.py +++ b/client/ayon_core/tools/common_models/cache.py @@ -1,239 +1,31 @@ -import time -import collections +import warnings -InitInfo = collections.namedtuple( - "InitInfo", - ["default_factory", "lifetime"] +from ayon_core.lib import CacheItem as _CacheItem +from ayon_core.lib import NestedCacheItem as _NestedCacheItem + + +# Cache classes were moved to `ayon_core.lib.cache` +class CacheItem(_CacheItem): + def __init__(self, *args, **kwargs): + warnings.warn( + "Used 'CacheItem' from deprecated location " + "'ayon_core.tools.common_models', use 'ayon_core.lib' instead.", + DeprecationWarning, + ) + super().__init__(*args, **kwargs) + + +class NestedCacheItem(_NestedCacheItem): + def __init__(self, *args, **kwargs): + warnings.warn( + "Used 'NestedCacheItem' from deprecated location " + "'ayon_core.tools.common_models', use 'ayon_core.lib' instead.", + DeprecationWarning, + ) + super().__init__(*args, **kwargs) + + +__all__ = ( + "CacheItem", + "NestedCacheItem", ) - - -def _default_factory_func(): - return None - - -class CacheItem: - """Simple cache item with lifetime and default value. - - Args: - default_factory (Optional[callable]): Function that returns default - value used on init and on reset. - lifetime (Optional[int]): Lifetime of the cache data in seconds. - """ - - def __init__(self, default_factory=None, lifetime=None): - if lifetime is None: - lifetime = 120 - self._lifetime = lifetime - self._last_update = None - if default_factory is None: - default_factory = _default_factory_func - self._default_factory = default_factory - self._data = default_factory() - - @property - def is_valid(self): - """Is cache valid to use. - - Return: - bool: True if cache is valid, False otherwise. - """ - - if self._last_update is None: - return False - - return (time.time() - self._last_update) < self._lifetime - - def set_lifetime(self, lifetime): - """Change lifetime of cache item. - - Args: - lifetime (int): Lifetime of the cache data in seconds. - """ - - self._lifetime = lifetime - - def set_invalid(self): - """Set cache as invalid.""" - - self._last_update = None - - def reset(self): - """Set cache as invalid and reset data.""" - - self._last_update = None - self._data = self._default_factory() - - def get_data(self): - """Receive cached data. - - Returns: - Any: Any data that are cached. - """ - - return self._data - - def update_data(self, data): - self._data = data - self._last_update = time.time() - - -class NestedCacheItem: - """Helper for cached items stored in nested structure. - - Example: - >>> cache = NestedCacheItem(levels=2, default_factory=lambda: 0) - >>> cache["a"]["b"].is_valid - False - >>> cache["a"]["b"].get_data() - 0 - >>> cache["a"]["b"] = 1 - >>> cache["a"]["b"].is_valid - True - >>> cache["a"]["b"].get_data() - 1 - >>> cache.reset() - >>> cache["a"]["b"].is_valid - False - - Args: - levels (int): Number of nested levels where read cache is stored. - default_factory (Optional[callable]): Function that returns default - value used on init and on reset. - lifetime (Optional[int]): Lifetime of the cache data in seconds. - _init_info (Optional[InitInfo]): Private argument. Init info for - nested cache where created from parent item. - """ - - def __init__( - self, levels=1, default_factory=None, lifetime=None, _init_info=None - ): - if levels < 1: - raise ValueError("Nested levels must be greater than 0") - self._data_by_key = {} - if _init_info is None: - _init_info = InitInfo(default_factory, lifetime) - self._init_info = _init_info - self._levels = levels - - def __getitem__(self, key): - """Get cached data. - - Args: - key (str): Key of the cache item. - - Returns: - Union[NestedCacheItem, CacheItem]: Cache item. - """ - - cache = self._data_by_key.get(key) - if cache is None: - if self._levels > 1: - cache = NestedCacheItem( - levels=self._levels - 1, - _init_info=self._init_info - ) - else: - cache = CacheItem( - self._init_info.default_factory, - self._init_info.lifetime - ) - self._data_by_key[key] = cache - return cache - - def __setitem__(self, key, value): - """Update cached data. - - Args: - key (str): Key of the cache item. - value (Any): Any data that are cached. - """ - - if self._levels > 1: - raise AttributeError(( - "{} does not support '__setitem__'. Lower nested level by {}" - ).format(self.__class__.__name__, self._levels - 1)) - cache = self[key] - cache.update_data(value) - - def get(self, key): - """Get cached data. - - Args: - key (str): Key of the cache item. - - Returns: - Union[NestedCacheItem, CacheItem]: Cache item. - """ - - return self[key] - - def cached_count(self): - """Amount of cached items. - - Returns: - int: Amount of cached items. - """ - - return len(self._data_by_key) - - def clear_key(self, key): - """Clear cached item by key. - - Args: - key (str): Key of the cache item. - """ - - self._data_by_key.pop(key, None) - - def clear_invalid(self): - """Clear all invalid cache items. - - Note: - To clear all cache items use 'reset'. - """ - - changed = {} - children_are_nested = self._levels > 1 - for key, cache in tuple(self._data_by_key.items()): - if children_are_nested: - output = cache.clear_invalid() - if output: - changed[key] = output - if not cache.cached_count(): - self._data_by_key.pop(key) - elif not cache.is_valid: - changed[key] = cache.get_data() - self._data_by_key.pop(key) - return changed - - def reset(self): - """Reset cache. - - Note: - To clear only invalid cache items use 'clear_invalid'. - """ - - self._data_by_key = {} - - def set_lifetime(self, lifetime): - """Change lifetime of all children cache items. - - Args: - lifetime (int): Lifetime of the cache data in seconds. - """ - - self._init_info.lifetime = lifetime - for cache in self._data_by_key.values(): - cache.set_lifetime(lifetime) - - @property - def is_valid(self): - """Raise reasonable error when called on wront level. - - Raises: - AttributeError: If called on nested cache item. - """ - - raise AttributeError(( - "{} does not support 'is_valid'. Lower nested level by '{}'" - ).format(self.__class__.__name__, self._levels)) From 86bbd24a9a7666d1475fbc22bf61597da3eef25d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:15:46 +0200 Subject: [PATCH 11/19] use cache items in anatomy --- client/ayon_core/pipeline/anatomy/anatomy.py | 76 ++++---------------- 1 file changed, 15 insertions(+), 61 deletions(-) diff --git a/client/ayon_core/pipeline/anatomy/anatomy.py b/client/ayon_core/pipeline/anatomy/anatomy.py index 2aa8eeddbc..35599e85d6 100644 --- a/client/ayon_core/pipeline/anatomy/anatomy.py +++ b/client/ayon_core/pipeline/anatomy/anatomy.py @@ -3,11 +3,16 @@ import re import copy import platform import collections -import time import ayon_api -from ayon_core.lib import Logger, get_local_site_id, StringTemplate +from ayon_core.lib import ( + Logger, + get_local_site_id, + StringTemplate, + CacheItem, + NestedCacheItem, +) from ayon_core.addon import AddonsManager from .exceptions import RootCombinationError, ProjectNotSet @@ -397,62 +402,11 @@ class BaseAnatomy(object): ) -class CacheItem: - """Helper to cache data. - - Helper does not handle refresh of data and does not mark data as outdated. - Who uses the object should check of outdated state on his own will. - """ - - default_lifetime = 10 - - def __init__(self, lifetime=None): - self._data = None - self._cached = None - self._lifetime = lifetime or self.default_lifetime - - @property - def data(self): - """Cached data/object. - - Returns: - Any: Whatever was cached. - """ - - return self._data - - @property - def is_outdated(self): - """Item has outdated cache. - - Lifetime of cache item expired or was not yet set. - - Returns: - bool: Item is outdated. - """ - - if self._cached is None: - return True - return (time.time() - self._cached) > self._lifetime - - def update_data(self, data): - """Update cache of data. - - Args: - data (Any): Data to cache. - """ - - self._data = data - self._cached = time.time() - - class Anatomy(BaseAnatomy): - _sitesync_addon_cache = CacheItem() - _project_cache = collections.defaultdict(CacheItem) - _default_site_id_cache = collections.defaultdict(CacheItem) - _root_overrides_cache = collections.defaultdict( - lambda: collections.defaultdict(CacheItem) - ) + _sitesync_addon_cache = CacheItem(lifetime=10) + _project_cache = NestedCacheItem(lifetime=10) + _default_site_id_cache = NestedCacheItem(lifetime=10) + _root_overrides_cache = NestedCacheItem(2, lifetime=10) def __init__( self, project_name=None, site_name=None, project_entity=None @@ -479,7 +433,7 @@ class Anatomy(BaseAnatomy): project_cache = cls._project_cache[project_name] if project_cache.is_outdated: project_cache.update_data(ayon_api.get_project(project_name)) - return copy.deepcopy(project_cache.data) + return copy.deepcopy(project_cache.get_data()) @classmethod def get_sitesync_addon(cls): @@ -488,7 +442,7 @@ class Anatomy(BaseAnatomy): cls._sitesync_addon_cache.update_data( manager.get_enabled_addon("sitesync") ) - return cls._sitesync_addon_cache.data + return cls._sitesync_addon_cache.get_data() @classmethod def _get_studio_roots_overrides(cls, project_name): @@ -537,7 +491,7 @@ class Anatomy(BaseAnatomy): project_cache.update_data( sitesync_addon.get_active_site_type(project_name) ) - site_name = project_cache.data + site_name = project_cache.get_data() site_cache = cls._root_overrides_cache[project_name][site_name] if site_cache.is_outdated: @@ -553,4 +507,4 @@ class Anatomy(BaseAnatomy): project_name, site_name ) site_cache.update_data(roots_overrides) - return site_cache.data + return site_cache.get_data() From 27c3a7bcaa2e3df0e2075624bc99d483c770b0b9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:15:56 +0200 Subject: [PATCH 12/19] change defautlt timeouts --- client/ayon_core/pipeline/anatomy/anatomy.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/pipeline/anatomy/anatomy.py b/client/ayon_core/pipeline/anatomy/anatomy.py index 35599e85d6..db8a00fa08 100644 --- a/client/ayon_core/pipeline/anatomy/anatomy.py +++ b/client/ayon_core/pipeline/anatomy/anatomy.py @@ -403,10 +403,10 @@ class BaseAnatomy(object): class Anatomy(BaseAnatomy): - _sitesync_addon_cache = CacheItem(lifetime=10) _project_cache = NestedCacheItem(lifetime=10) - _default_site_id_cache = NestedCacheItem(lifetime=10) - _root_overrides_cache = NestedCacheItem(2, lifetime=10) + _sitesync_addon_cache = CacheItem(lifetime=60) + _default_site_id_cache = NestedCacheItem(lifetime=60) + _root_overrides_cache = NestedCacheItem(2, lifetime=60) def __init__( self, project_name=None, site_name=None, project_entity=None From 3980f59211a389c7d1a11b4d9874d0f2147898c2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:07:28 +0200 Subject: [PATCH 13/19] fix docstring lines --- client/ayon_core/lib/cache.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/client/ayon_core/lib/cache.py b/client/ayon_core/lib/cache.py index 170b9853fe..be6fd4f4d7 100644 --- a/client/ayon_core/lib/cache.py +++ b/client/ayon_core/lib/cache.py @@ -14,7 +14,6 @@ def _default_factory_func(): class CacheItem: """Simple cache item with lifetime and default factory for default value. - Default factory should return default value that is used on init and on reset. @@ -22,8 +21,8 @@ class CacheItem: default_factory (Optional[callable]): Function that returns default value used on init and on reset. lifetime (Optional[int]): Lifetime of the cache data in seconds. - """ + """ def __init__(self, default_factory=None, lifetime=None): if lifetime is None: lifetime = 120 @@ -40,8 +39,8 @@ class CacheItem: Return: bool: True if cache is valid, False otherwise. - """ + """ if self._last_update is None: return False @@ -72,8 +71,8 @@ class CacheItem: Returns: Any: Any data that are cached. - """ + """ return self._data def update_data(self, data): @@ -106,8 +105,8 @@ class NestedCacheItem: lifetime (Optional[int]): Lifetime of the cache data in seconds. _init_info (Optional[InitInfo]): Private argument. Init info for nested cache where created from parent item. - """ + """ def __init__( self, levels=1, default_factory=None, lifetime=None, _init_info=None ): @@ -127,8 +126,8 @@ class NestedCacheItem: Returns: Union[NestedCacheItem, CacheItem]: Cache item. - """ + """ cache = self._data_by_key.get(key) if cache is None: if self._levels > 1: @@ -150,8 +149,8 @@ class NestedCacheItem: Args: key (str): Key of the cache item. value (Any): Any data that are cached. - """ + """ if self._levels > 1: raise AttributeError(( "{} does not support '__setitem__'. Lower nested level by {}" @@ -167,8 +166,8 @@ class NestedCacheItem: Returns: Union[NestedCacheItem, CacheItem]: Cache item. - """ + """ return self[key] def cached_count(self): @@ -176,8 +175,8 @@ class NestedCacheItem: Returns: int: Amount of cached items. - """ + """ return len(self._data_by_key) def clear_key(self, key): @@ -185,8 +184,8 @@ class NestedCacheItem: Args: key (str): Key of the cache item. - """ + """ self._data_by_key.pop(key, None) def clear_invalid(self): @@ -194,8 +193,8 @@ class NestedCacheItem: Note: To clear all cache items use 'reset'. - """ + """ changed = {} children_are_nested = self._levels > 1 for key, cache in tuple(self._data_by_key.items()): @@ -215,8 +214,8 @@ class NestedCacheItem: Note: To clear only invalid cache items use 'clear_invalid'. - """ + """ self._data_by_key = {} def set_lifetime(self, lifetime): @@ -224,8 +223,8 @@ class NestedCacheItem: Args: lifetime (int): Lifetime of the cache data in seconds. - """ + """ self._init_info.lifetime = lifetime for cache in self._data_by_key.values(): cache.set_lifetime(lifetime) @@ -236,8 +235,8 @@ class NestedCacheItem: Raises: AttributeError: If called on nested cache item. - """ + """ raise AttributeError(( "{} does not support 'is_valid'. Lower nested level by '{}'" ).format(self.__class__.__name__, self._levels)) From f10d2660297224720dcc12aa57340d4cd648985d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:07:35 +0200 Subject: [PATCH 14/19] add missing docstring --- client/ayon_core/lib/cache.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/ayon_core/lib/cache.py b/client/ayon_core/lib/cache.py index be6fd4f4d7..c661a16f7b 100644 --- a/client/ayon_core/lib/cache.py +++ b/client/ayon_core/lib/cache.py @@ -76,6 +76,12 @@ class CacheItem: return self._data def update_data(self, data): + """Update cache data. + + Args: + data (Any): Any data that are cached. + + """ self._data = data self._last_update = time.time() From 2594fc82b061ceacee66765b402b1148057e0960 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:07:50 +0200 Subject: [PATCH 15/19] added default value of lifetime to docstring --- client/ayon_core/lib/cache.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/lib/cache.py b/client/ayon_core/lib/cache.py index c661a16f7b..005c900c9f 100644 --- a/client/ayon_core/lib/cache.py +++ b/client/ayon_core/lib/cache.py @@ -21,6 +21,7 @@ class CacheItem: default_factory (Optional[callable]): Function that returns default value used on init and on reset. lifetime (Optional[int]): Lifetime of the cache data in seconds. + Default lifetime is 120 seconds. """ def __init__(self, default_factory=None, lifetime=None): From 907fa5de934aeea15c9706bed1d3cb46f496a199 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:08:43 +0200 Subject: [PATCH 16/19] added default value to 'NestedCacheItem' --- client/ayon_core/lib/cache.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/lib/cache.py b/client/ayon_core/lib/cache.py index 005c900c9f..dc83520f76 100644 --- a/client/ayon_core/lib/cache.py +++ b/client/ayon_core/lib/cache.py @@ -110,6 +110,7 @@ class NestedCacheItem: default_factory (Optional[callable]): Function that returns default value used on init and on reset. lifetime (Optional[int]): Lifetime of the cache data in seconds. + Default value is based on default value of 'CacheItem'. _init_info (Optional[InitInfo]): Private argument. Init info for nested cache where created from parent item. From 64ff24dbf12c9979d23f3558e7f619ba02fa8b54 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:45:07 +0200 Subject: [PATCH 17/19] use 'is_valid' instead of 'is_outdated' --- client/ayon_core/pipeline/anatomy/anatomy.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/pipeline/anatomy/anatomy.py b/client/ayon_core/pipeline/anatomy/anatomy.py index db8a00fa08..98bbaa9bdc 100644 --- a/client/ayon_core/pipeline/anatomy/anatomy.py +++ b/client/ayon_core/pipeline/anatomy/anatomy.py @@ -431,13 +431,13 @@ class Anatomy(BaseAnatomy): @classmethod def get_project_entity_from_cache(cls, project_name): project_cache = cls._project_cache[project_name] - if project_cache.is_outdated: + if not project_cache.is_valid: project_cache.update_data(ayon_api.get_project(project_name)) return copy.deepcopy(project_cache.get_data()) @classmethod def get_sitesync_addon(cls): - if cls._sitesync_addon_cache.is_outdated: + if not cls._sitesync_addon_cache.is_valid: manager = AddonsManager() cls._sitesync_addon_cache.update_data( manager.get_enabled_addon("sitesync") @@ -487,14 +487,14 @@ class Anatomy(BaseAnatomy): elif not site_name: # Use sync server to receive active site name project_cache = cls._default_site_id_cache[project_name] - if project_cache.is_outdated: + if not project_cache.is_valid: project_cache.update_data( sitesync_addon.get_active_site_type(project_name) ) site_name = project_cache.get_data() site_cache = cls._root_overrides_cache[project_name][site_name] - if site_cache.is_outdated: + if not site_cache.is_valid: if site_name == "studio": # Handle studio root overrides without sync server # - studio root overrides can be done even without sync server From 84dc455bbe6aadce32361d154c3014a95951f998 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 1 May 2024 01:36:15 +0200 Subject: [PATCH 18/19] Fix `extract_alembic` imports --- client/ayon_core/hosts/maya/plugins/publish/extract_assembly.py | 2 +- .../ayon_core/hosts/maya/plugins/publish/extract_proxy_abc.py | 2 +- .../maya/plugins/publish/extract_unreal_skeletalmesh_abc.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_assembly.py b/client/ayon_core/hosts/maya/plugins/publish/extract_assembly.py index 2c23f9b752..5f51dc38cb 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_assembly.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_assembly.py @@ -2,7 +2,7 @@ import os import json from ayon_core.pipeline import publish -from ayon_core.hosts.maya.api.lib import extract_alembic +from ayon_core.hosts.maya.api.alembic import extract_alembic from maya import cmds diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_proxy_abc.py b/client/ayon_core/hosts/maya/plugins/publish/extract_proxy_abc.py index 3637a58614..5aefdfc33a 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_proxy_abc.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_proxy_abc.py @@ -3,8 +3,8 @@ import os from maya import cmds from ayon_core.pipeline import publish +from ayon_core.hosts.maya.api.alembic import extract_alembic from ayon_core.hosts.maya.api.lib import ( - extract_alembic, suspended_refresh, maintained_selection, iter_visible_nodes_in_range diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_abc.py b/client/ayon_core/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_abc.py index 1a389f3d33..b5cc7745a1 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_abc.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_abc.py @@ -5,8 +5,8 @@ import os from maya import cmds # noqa from ayon_core.pipeline import publish +from ayon_core.hosts.maya.api.alembic import extract_alembic from ayon_core.hosts.maya.api.lib import ( - extract_alembic, suspended_refresh, maintained_selection ) From 2c5a43dc931737c2e1aed2dfe6614545bb6c275f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 2 May 2024 11:42:46 +0200 Subject: [PATCH 19/19] :recycle: limit the site prefix only to windows --- client/ayon_core/hosts/unreal/ue_workers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/unreal/ue_workers.py b/client/ayon_core/hosts/unreal/ue_workers.py index cdac2c28af..256c0557be 100644 --- a/client/ayon_core/hosts/unreal/ue_workers.py +++ b/client/ayon_core/hosts/unreal/ue_workers.py @@ -303,9 +303,12 @@ class UEProjectGenerationWorker(UEWorker): "install", "--ignore-installed", pyside_version, - "--prefix", site_packages_prefix, + ] + if platform.system().lower() == "windows": + pyside_cmd += ["--target", site_packages_prefix] + print(f"--- Installing {pyside_version} ...") print(" ".join(pyside_cmd))