diff --git a/client/ayon_core/lib/local_settings.py b/client/ayon_core/lib/local_settings.py index 91b881cf57..85ece54d6f 100644 --- a/client/ayon_core/lib/local_settings.py +++ b/client/ayon_core/lib/local_settings.py @@ -8,6 +8,7 @@ import warnings from datetime import datetime from abc import ABC, abstractmethod from functools import lru_cache +from typing import Optional, Any import platformdirs import ayon_api @@ -15,26 +16,31 @@ import ayon_api _PLACEHOLDER = object() +# TODO should use 'KeyError' or 'Exception' as base +class RegistryItemNotFound(ValueError): + """Raised when the item is not found in the keyring.""" + + class _Cache: username = None -def _get_ayon_appdirs(*args): +def _get_ayon_appdirs(*args: str) -> str: return os.path.join( platformdirs.user_data_dir("AYON", "Ynput"), *args ) -def get_ayon_appdirs(*args): +def get_ayon_appdirs(*args: str) -> str: """Local app data directory of AYON client. Deprecated: Use 'get_launcher_local_dir' or 'get_launcher_storage_dir' based on - use-case. Deprecation added 24/08/09 (0.4.4-dev.1). + a use-case. Deprecation added 24/08/09 (0.4.4-dev.1). Args: - *args (Iterable[str]): Subdirectories/files in local app data dir. + *args (Iterable[str]): Subdirectories/files in the local app data dir. Returns: str: Path to directory/file in local app data dir. @@ -52,7 +58,7 @@ def get_ayon_appdirs(*args): def get_launcher_storage_dir(*subdirs: str) -> str: - """Get storage directory for launcher. + """Get a storage directory for launcher. Storage directory is used for storing shims, addons, dependencies, etc. @@ -77,14 +83,14 @@ def get_launcher_storage_dir(*subdirs: str) -> str: def get_launcher_local_dir(*subdirs: str) -> str: - """Get local directory for launcher. + """Get a local directory for launcher. - Local directory is used for storing machine or user specific data. + Local directory is used for storing machine or user-specific data. - The location is user specific. + The location is user-specific. Note: - This function should be called at least once on bootstrap. + This function should be called at least once on the bootstrap. Args: *subdirs (str): Subdirectories relative to local dir. @@ -101,7 +107,7 @@ def get_launcher_local_dir(*subdirs: str) -> str: def get_addons_resources_dir(addon_name: str, *args) -> str: - """Get directory for storing resources for addons. + """Get a directory for storing resources for addons. Some addons might need to store ad-hoc resources that are not part of addon client package (e.g. because of size). Studio might define @@ -111,7 +117,7 @@ def get_addons_resources_dir(addon_name: str, *args) -> str: Args: addon_name (str): Addon name. - *args (str): Subfolders in resources directory. + *args (str): Subfolders in the resources directory. Returns: str: Path to resources directory. @@ -134,9 +140,10 @@ class AYONSecureRegistry: identify which data were created by AYON. Args: - name(str): Name of registry used as identifier for data. + name(str): Name of registry used as the identifier for data. + """ - def __init__(self, name): + def __init__(self, name: str) -> None: try: import keyring @@ -152,13 +159,12 @@ class AYONSecureRegistry: keyring.set_keyring(Windows.WinVaultKeyring()) # Force "AYON" prefix - self._name = "/".join(("AYON", name)) + self._name = f"AYON/{name}" - def set_item(self, name, value): - # type: (str, str) -> None - """Set sensitive item into system's keyring. + def set_item(self, name: str, value: str) -> None: + """Set sensitive item into the system's keyring. - This uses `Keyring module`_ to save sensitive stuff into system's + This uses `Keyring module`_ to save sensitive stuff into the system's keyring. Args: @@ -172,22 +178,26 @@ class AYONSecureRegistry: import keyring keyring.set_password(self._name, name, value) + self.get_item.cache_clear() @lru_cache(maxsize=32) - def get_item(self, name, default=_PLACEHOLDER): - """Get value of sensitive item from system's keyring. + def get_item( + self, name: str, default: Any = _PLACEHOLDER + ) -> Optional[str]: + """Get value of sensitive item from the system's keyring. See also `Keyring module`_ Args: name (str): Name of the item. - default (Any): Default value if item is not available. + default (Any): Default value if the item is not available. Returns: value (str): Value of the item. Raises: - ValueError: If item doesn't exist and default is not defined. + RegistryItemNotFound: If the item doesn't exist and default + is not defined. .. _Keyring module: https://github.com/jaraco/keyring @@ -202,14 +212,12 @@ class AYONSecureRegistry: if default is not _PLACEHOLDER: return default - # NOTE Should raise `KeyError` - raise ValueError( - "Item {}:{} does not exist in keyring.".format(self._name, name) + raise RegistryItemNotFound( + f"Item {self._name}:{name} not found in keyring." ) - def delete_item(self, name): - # type: (str) -> None - """Delete value stored in system's keyring. + def delete_item(self, name: str) -> None: + """Delete value stored in the system's keyring. See also `Keyring module`_ @@ -227,47 +235,38 @@ class AYONSecureRegistry: class ASettingRegistry(ABC): - """Abstract class defining structure of **SettingRegistry** class. - - It is implementing methods to store secure items into keyring, otherwise - mechanism for storing common items must be implemented in abstract - methods. - - Attributes: - _name (str): Registry names. + """Abstract class to defining structure of registry class. """ - - def __init__(self, name): - # type: (str) -> ASettingRegistry - super(ASettingRegistry, self).__init__() - + def __init__(self, name: str) -> None: self._name = name - self._items = {} - - def set_item(self, name, value): - # type: (str, str) -> None - """Set item to settings registry. - - Args: - name (str): Name of the item. - value (str): Value of the item. - - """ - self._set_item(name, value) @abstractmethod - def _set_item(self, name, value): - # type: (str, str) -> None - # Implement it - pass + def _get_item(self, name: str) -> Any: + """Get item value from registry.""" - def __setitem__(self, name, value): - self._items[name] = value + @abstractmethod + def _set_item(self, name: str, value: str) -> None: + """Set item value to registry.""" + + @abstractmethod + def _delete_item(self, name: str) -> None: + """Delete item from registry.""" + + def __getitem__(self, name: str) -> Any: + return self._get_item(name) + + def __setitem__(self, name: str, value: str) -> None: self._set_item(name, value) - def get_item(self, name): - # type: (str) -> str + def __delitem__(self, name: str) -> None: + self._delete_item(name) + + @property + def name(self) -> str: + return self._name + + def get_item(self, name: str) -> str: """Get item from settings registry. Args: @@ -277,22 +276,22 @@ class ASettingRegistry(ABC): value (str): Value of the item. Raises: - ValueError: If item doesn't exist. + RegistryItemNotFound: If the item doesn't exist. """ return self._get_item(name) - @abstractmethod - def _get_item(self, name): - # type: (str) -> str - # Implement it - pass + def set_item(self, name: str, value: str) -> None: + """Set item to settings registry. - def __getitem__(self, name): - return self._get_item(name) + Args: + name (str): Name of the item. + value (str): Value of the item. - def delete_item(self, name): - # type: (str) -> None + """ + self._set_item(name, value) + + def delete_item(self, name: str) -> None: """Delete item from settings registry. Args: @@ -301,16 +300,6 @@ class ASettingRegistry(ABC): """ self._delete_item(name) - @abstractmethod - def _delete_item(self, name): - # type: (str) -> None - """Delete item from settings.""" - pass - - def __delitem__(self, name): - del self._items[name] - self._delete_item(name) - class IniSettingRegistry(ASettingRegistry): """Class using :mod:`configparser`. @@ -318,20 +307,17 @@ class IniSettingRegistry(ASettingRegistry): This class is using :mod:`configparser` (ini) files to store items. """ - - def __init__(self, name, path): - # type: (str, str) -> IniSettingRegistry - super(IniSettingRegistry, self).__init__(name) + def __init__(self, name: str, path: str) -> None: + super().__init__(name) # get registry file - self._registry_file = os.path.join(path, "{}.ini".format(name)) + self._registry_file = os.path.join(path, f"{name}.ini") if not os.path.exists(self._registry_file): with open(self._registry_file, mode="w") as cfg: print("# Settings registry", cfg) now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") - print("# {}".format(now), cfg) + print(f"# {now}", cfg) - def set_item_section(self, section, name, value): - # type: (str, str, str) -> None + def set_item_section(self, section: str, name: str, value: str) -> None: """Set item to specific section of ini registry. If section doesn't exists, it is created. @@ -354,12 +340,10 @@ class IniSettingRegistry(ASettingRegistry): with open(self._registry_file, mode="w") as cfg: config.write(cfg) - def _set_item(self, name, value): - # type: (str, str) -> None + def _set_item(self, name: str, value: str) -> None: self.set_item_section("MAIN", name, value) - def set_item(self, name, value): - # type: (str, str) -> None + def set_item(self, name: str, value: str) -> None: """Set item to settings ini file. This saves item to ``DEFAULT`` section of ini as each item there @@ -372,10 +356,9 @@ class IniSettingRegistry(ASettingRegistry): """ # this does the some, overridden just for different docstring. # we cast value to str as ini options values must be strings. - super(IniSettingRegistry, self).set_item(name, str(value)) + super().set_item(name, str(value)) - def get_item(self, name): - # type: (str) -> str + def get_item(self, name: str) -> str: """Gets item from settings ini file. This gets settings from ``DEFAULT`` section of ini file as each item @@ -388,19 +371,18 @@ class IniSettingRegistry(ASettingRegistry): str: Value of item. Raises: - ValueError: If value doesn't exist. + RegistryItemNotFound: If value doesn't exist. """ - return super(IniSettingRegistry, self).get_item(name) + return super().get_item(name) @lru_cache(maxsize=32) - def get_item_from_section(self, section, name): - # type: (str, str) -> str + def get_item_from_section(self, section: str, name: str) -> str: """Get item from section of ini file. This will read ini file and try to get item value from specified - section. If that section or item doesn't exist, :exc:`ValueError` - is risen. + section. If that section or item doesn't exist, + :exc:`RegistryItemNotFound` is risen. Args: section (str): Name of ini section. @@ -410,7 +392,7 @@ class IniSettingRegistry(ASettingRegistry): str: Item value. Raises: - ValueError: If value doesn't exist. + RegistryItemNotFound: If value doesn't exist. """ config = configparser.ConfigParser() @@ -418,16 +400,15 @@ class IniSettingRegistry(ASettingRegistry): try: value = config[section][name] except KeyError: - raise ValueError( - "Registry doesn't contain value {}:{}".format(section, name)) + raise RegistryItemNotFound( + f"Registry doesn't contain value {section}:{name}" + ) return value - def _get_item(self, name): - # type: (str) -> str + def _get_item(self, name: str) -> str: return self.get_item_from_section("MAIN", name) - def delete_item_from_section(self, section, name): - # type: (str, str) -> None + def delete_item_from_section(self, section: str, name: str) -> None: """Delete item from section in ini file. Args: @@ -435,7 +416,7 @@ class IniSettingRegistry(ASettingRegistry): name (str): Name of the item. Raises: - ValueError: If item doesn't exist. + RegistryItemNotFound: If the item doesn't exist. """ self.get_item_from_section.cache_clear() @@ -444,8 +425,9 @@ class IniSettingRegistry(ASettingRegistry): try: _ = config[section][name] except KeyError: - raise ValueError( - "Registry doesn't contain value {}:{}".format(section, name)) + raise RegistryItemNotFound( + f"Registry doesn't contain value {section}:{name}" + ) config.remove_option(section, name) # if section is empty, delete it @@ -461,29 +443,28 @@ class IniSettingRegistry(ASettingRegistry): class JSONSettingRegistry(ASettingRegistry): - """Class using json file as storage.""" + """Class using a json file as storage.""" - def __init__(self, name, path): - # type: (str, str) -> JSONSettingRegistry - super(JSONSettingRegistry, self).__init__(name) - #: str: name of registry file - self._registry_file = os.path.join(path, "{}.json".format(name)) + def __init__(self, name: str, path: str) -> None: + super().__init__(name) + self._registry_file = os.path.join(path, f"{name}.json") now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") header = { "__metadata__": {"generated": now}, "registry": {} } - if not os.path.exists(os.path.dirname(self._registry_file)): - os.makedirs(os.path.dirname(self._registry_file), exist_ok=True) + # Use 'os.path.dirname' in case someone uses slashes in 'name' + dirpath = os.path.dirname(self._registry_file) + if not os.path.exists(dirpath): + os.makedirs(dirpath, exist_ok=True) if not os.path.exists(self._registry_file): with open(self._registry_file, mode="w") as cfg: json.dump(header, cfg, indent=4) @lru_cache(maxsize=32) - def _get_item(self, name): - # type: (str) -> object - """Get item value from registry json. + def _get_item(self, name: str) -> str: + """Get item value from the registry. Note: See :meth:`ayon_core.lib.JSONSettingRegistry.get_item` @@ -494,29 +475,13 @@ class JSONSettingRegistry(ASettingRegistry): try: value = data["registry"][name] except KeyError: - raise ValueError( - "Registry doesn't contain value {}".format(name)) + raise RegistryItemNotFound( + f"Registry doesn't contain value {name}" + ) return value - def get_item(self, name): - # type: (str) -> object - """Get item value from registry json. - - Args: - name (str): Name of the item. - - Returns: - value of the item - - Raises: - ValueError: If item is not found in registry file. - - """ - return self._get_item(name) - - def _set_item(self, name, value): - # type: (str, object) -> None - """Set item value to registry json. + def _set_item(self, name: str, value: str) -> None: + """Set item value to the registry. Note: See :meth:`ayon_core.lib.JSONSettingRegistry.set_item` @@ -528,41 +493,39 @@ class JSONSettingRegistry(ASettingRegistry): cfg.truncate(0) cfg.seek(0) json.dump(data, cfg, indent=4) - - def set_item(self, name, value): - # type: (str, object) -> None - """Set item and its value into json registry file. - - Args: - name (str): name of the item. - value (Any): value of the item. - - """ - self._set_item(name, value) - - def _delete_item(self, name): - # type: (str) -> None self._get_item.cache_clear() + + def _delete_item(self, name: str) -> None: with open(self._registry_file, "r+") as cfg: data = json.load(cfg) del data["registry"][name] cfg.truncate(0) cfg.seek(0) json.dump(data, cfg, indent=4) + self._get_item.cache_clear() class AYONSettingsRegistry(JSONSettingRegistry): """Class handling AYON general settings registry. Args: - name (Optional[str]): Name of the registry. - """ + name (Optional[str]): Name of the registry. Using 'None' or not + passing name is deprecated. - def __init__(self, name=None): + """ + def __init__(self, name: Optional[str] = None) -> None: if not name: name = "AYON_settings" + warnings.warn( + ( + "Used 'AYONSettingsRegistry' without 'name' argument." + " The argument will be required in future versions." + ), + DeprecationWarning, + stacklevel=2, + ) path = get_launcher_storage_dir() - super(AYONSettingsRegistry, self).__init__(name, path) + super().__init__(name, path) def get_local_site_id():