Skip to content

Commit

Permalink
Merge branch 'develop' into enhancement/soft-crash-of-usd-plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
BigRoy authored Aug 28, 2024
2 parents f23926a + 7a89d22 commit cf6becd
Show file tree
Hide file tree
Showing 43 changed files with 2,728 additions and 1,861 deletions.
50 changes: 28 additions & 22 deletions client/ayon_core/addon/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@
from abc import ABC, abstractmethod
from typing import Optional

import appdirs
import ayon_api
from semver import VersionInfo

from ayon_core import AYON_CORE_ROOT
from ayon_core.lib import Logger, is_dev_mode_enabled
from ayon_core.lib import (
Logger,
is_dev_mode_enabled,
get_launcher_storage_dir,
is_headless_mode_enabled,
)
from ayon_core.settings import get_studio_settings

from .interfaces import (
Expand Down Expand Up @@ -77,36 +81,41 @@ class ProcessPreparationError(Exception):


class ProcessContext:
"""Context of child process.
"""Hold context of process that is going to be started.
Right now the context is simple, having information about addon that wants
to trigger preparation and possibly project name for which it should
happen.
Notes:
This class is used to pass context to child process. It can be used
to use different behavior of addon based on information in
the context.
The context can be enhanced in future versions.
Preparation for process can be required for ayon-core or any other addon.
It can be, change of environment variables, or request login to
a project management.
At the moment of creation is 'ProcessContext' only data holder, but that
might change in future if there will be need.
Args:
addon_name (Optional[str]): Addon name which triggered process.
addon_version (Optional[str]): Addon version which triggered process.
addon_name (str): Addon name which triggered process.
addon_version (str): Addon version which triggered process.
project_name (Optional[str]): Project name. Can be filled in case
process is triggered for specific project. Some addons can have
different behavior based on project.
headless (Optional[bool]): Is process running in headless mode.
different behavior based on project. Value is NOT autofilled.
headless (Optional[bool]): Is process running in headless mode. Value
is filled with value based on state set in AYON launcher.
"""
def __init__(
self,
addon_name: Optional[str] = None,
addon_version: Optional[str] = None,
addon_name: str,
addon_version: str,
project_name: Optional[str] = None,
headless: Optional[bool] = None,
**kwargs,
):
if headless is None:
# TODO use lib function to get headless mode
headless = os.getenv("AYON_HEADLESS_MODE") == "1"
self.addon_name: Optional[str] = addon_name
self.addon_version: Optional[str] = addon_version
headless = is_headless_mode_enabled()
self.addon_name: str = addon_name
self.addon_version: str = addon_version
self.project_name: Optional[str] = project_name
self.headless: bool = headless

Expand Down Expand Up @@ -327,10 +336,7 @@ def _load_ayon_addons(openpype_modules, modules_key, log):

addons_dir = os.environ.get("AYON_ADDONS_DIR")
if not addons_dir:
addons_dir = os.path.join(
appdirs.user_data_dir("AYON", "Ynput"),
"addons"
)
addons_dir = get_launcher_storage_dir("addons")

dev_mode_enabled = is_dev_mode_enabled()
dev_addons_info = {}
Expand Down
47 changes: 32 additions & 15 deletions client/ayon_core/addon/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,17 @@ def ensure_addons_are_process_context_ready(
process_context: ProcessContext,
addons_manager: Optional[AddonsManager] = None,
exit_on_failure: bool = True,
) -> Optional[Exception]:
) -> bool:
"""Ensure all enabled addons are ready to be used in the given context.
Call this method only in AYON launcher process and as first thing
to avoid possible clashes with preparation. For example 'QApplication'
should not be created.
Todos:
Run all preparations and allow to "ignore" failed preparations.
Right now single addon can block using certain actions.
Args:
process_context (ProcessContext): The context in which the
addons should be prepared.
Expand All @@ -88,14 +92,12 @@ def ensure_addons_are_process_context_ready(
if an error occurs. Defaults to True.
Returns:
Optional[Exception]: The exception that occurred during the
preparation, if any.
bool: True if all addons are ready, False otherwise.
"""
if addons_manager is None:
addons_manager = AddonsManager()

exception = None
message = None
failed = False
use_detail = False
Expand All @@ -112,13 +114,11 @@ def ensure_addons_are_process_context_ready(
addon.ensure_is_process_ready(process_context)
addon_failed = False
except ProcessPreparationError as exc:
exception = exc
message = str(exc)
print(f"Addon preparation failed: '{addon.name}'")
print(message)

except BaseException as exc:
exception = exc
except BaseException:
use_detail = True
message = "An unexpected error occurred."
formatted_traceback = "".join(traceback.format_exception(
Expand All @@ -140,7 +140,7 @@ def ensure_addons_are_process_context_ready(
if not failed:
if not process_context.headless:
_start_tray()
return None
return True

detail = None
if use_detail:
Expand All @@ -150,35 +150,52 @@ def ensure_addons_are_process_context_ready(
detail = output_str

_handle_error(process_context, message, detail)
if not exit_on_failure:
return exception
sys.exit(1)
if exit_on_failure:
sys.exit(1)
return False


def ensure_addons_are_process_ready(
addon_name: str,
addon_version: str,
project_name: Optional[str] = None,
headless: Optional[bool] = None,
*,
addons_manager: Optional[AddonsManager] = None,
exit_on_failure: bool = True,
**kwargs,
) -> Optional[Exception]:
) -> bool:
"""Ensure all enabled addons are ready to be used in the given context.
Call this method only in AYON launcher process and as first thing
to avoid possible clashes with preparation. For example 'QApplication'
should not be created.
Args:
addon_name (str): Addon name which triggered process.
addon_version (str): Addon version which triggered process.
project_name (Optional[str]): Project name. Can be filled in case
process is triggered for specific project. Some addons can have
different behavior based on project. Value is NOT autofilled.
headless (Optional[bool]): Is process running in headless mode. Value
is filled with value based on state set in AYON launcher.
addons_manager (Optional[AddonsManager]): The addons
manager to use. If not provided, a new one will be created.
exit_on_failure (bool, optional): If True, the process will exit
if an error occurs. Defaults to True.
kwargs: The keyword arguments to pass to the ProcessContext.
Returns:
Optional[Exception]: The exception that occurred during the
preparation, if any.
bool: True if all addons are ready, False otherwise.
"""
context: ProcessContext = ProcessContext(**kwargs)
context: ProcessContext = ProcessContext(
addon_name,
addon_version,
project_name,
headless,
**kwargs
)
return ensure_addons_are_process_context_ready(
context, addons_manager, exit_on_failure
)
6 changes: 6 additions & 0 deletions client/ayon_core/lib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
AYONSettingsRegistry,
OpenPypeSecureRegistry,
OpenPypeSettingsRegistry,
get_launcher_local_dir,
get_launcher_storage_dir,
get_local_site_id,
get_ayon_username,
get_openpype_username,
Expand Down Expand Up @@ -130,6 +132,7 @@
is_in_ayon_launcher_process,
is_running_from_build,
is_using_ayon_console,
is_headless_mode_enabled,
is_staging_enabled,
is_dev_mode_enabled,
is_in_tests,
Expand All @@ -144,6 +147,8 @@
"AYONSettingsRegistry",
"OpenPypeSecureRegistry",
"OpenPypeSettingsRegistry",
"get_launcher_local_dir",
"get_launcher_storage_dir",
"get_local_site_id",
"get_ayon_username",
"get_openpype_username",
Expand Down Expand Up @@ -241,6 +246,7 @@
"is_in_ayon_launcher_process",
"is_running_from_build",
"is_using_ayon_console",
"is_headless_mode_enabled",
"is_staging_enabled",
"is_dev_mode_enabled",
"is_in_tests",
Expand Down
2 changes: 1 addition & 1 deletion client/ayon_core/lib/attribute_definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,7 @@ def convert_value(self, value):
return self.default


class FileDefItem(object):
class FileDefItem:
def __init__(
self, directory, filenames, frames=None, template=None
):
Expand Down
4 changes: 4 additions & 0 deletions client/ayon_core/lib/ayon_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ def is_using_ayon_console():
return "ayon_console" in executable_filename


def is_headless_mode_enabled():
return os.getenv("AYON_HEADLESS_MODE") == "1"


def is_staging_enabled():
return os.getenv("AYON_USE_STAGING") == "1"

Expand Down
10 changes: 4 additions & 6 deletions client/ayon_core/lib/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import weakref
from uuid import uuid4

from .python_2_comp import WeakMethod
from .python_module_tools import is_func_signature_supported


Expand All @@ -18,7 +17,7 @@ class MissingEventSystem(Exception):

def _get_func_ref(func):
if inspect.ismethod(func):
return WeakMethod(func)
return weakref.WeakMethod(func)
return weakref.ref(func)


Expand Down Expand Up @@ -123,7 +122,7 @@ def validate_signature(self, *args, **kwargs):
)


class EventCallback(object):
class EventCallback:
"""Callback registered to a topic.
The callback function is registered to a topic. Topic is a string which
Expand Down Expand Up @@ -380,8 +379,7 @@ def _validate_ref(self):
self._partial_func = None


# Inherit from 'object' for Python 2 hosts
class Event(object):
class Event:
"""Base event object.
Can be used for any event because is not specific. Only required argument
Expand Down Expand Up @@ -488,7 +486,7 @@ def from_data(cls, event_data, event_system=None):
return obj


class EventSystem(object):
class EventSystem:
"""Encapsulate event handling into an object.
System wraps registered callbacks and triggered events into single object,
Expand Down
14 changes: 14 additions & 0 deletions client/ayon_core/lib/execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,20 @@ def run_subprocess(*args, **kwargs):
| getattr(subprocess, "CREATE_NO_WINDOW", 0)
)

# Escape parentheses for bash
if (
kwargs.get("shell") is True
and len(args) == 1
and isinstance(args[0], str)
and os.getenv("SHELL") in ("/bin/bash", "/bin/sh")
):
new_arg = (
args[0]
.replace("(", "\\(")
.replace(")", "\\)")
)
args = (new_arg, )

# Get environents from kwarg or use current process environments if were
# not passed.
env = kwargs.get("env") or os.environ
Expand Down
2 changes: 1 addition & 1 deletion client/ayon_core/lib/file_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class DuplicateDestinationError(ValueError):
"""


class FileTransaction(object):
class FileTransaction:
"""File transaction with rollback options.
The file transaction is a three-step process.
Expand Down
Loading

0 comments on commit cf6becd

Please sign in to comment.