diff --git a/client/ayon_core/addon/base.py b/client/ayon_core/addon/base.py index b9ecff4233..308494b4d8 100644 --- a/client/ayon_core/addon/base.py +++ b/client/ayon_core/addon/base.py @@ -9,9 +9,8 @@ import logging import threading import collections from uuid import uuid4 -from abc import ABCMeta, abstractmethod +from abc import ABC, abstractmethod -import six import appdirs import ayon_api from semver import VersionInfo @@ -499,8 +498,7 @@ def is_func_marked(func): return getattr(func, _MARKING_ATTR, False) -@six.add_metaclass(ABCMeta) -class AYONAddon(object): +class AYONAddon(ABC): """Base class of AYON addon. Attributes: diff --git a/client/ayon_core/addon/interfaces.py b/client/ayon_core/addon/interfaces.py index 86e0c6e060..b273e7839b 100644 --- a/client/ayon_core/addon/interfaces.py +++ b/client/ayon_core/addon/interfaces.py @@ -1,7 +1,5 @@ from abc import ABCMeta, abstractmethod -import six - from ayon_core import resources @@ -15,8 +13,7 @@ class _AYONInterfaceMeta(ABCMeta): return str(self) -@six.add_metaclass(_AYONInterfaceMeta) -class AYONInterface: +class AYONInterface(metaclass=_AYONInterfaceMeta): """Base class of Interface that can be used as Mixin with abstract parts. This is way how AYON addon can define that contains specific predefined diff --git a/client/ayon_core/host/dirmap.py b/client/ayon_core/host/dirmap.py index b90b414240..19841845e7 100644 --- a/client/ayon_core/host/dirmap.py +++ b/client/ayon_core/host/dirmap.py @@ -7,18 +7,15 @@ exists is used. """ import os -from abc import ABCMeta, abstractmethod +from abc import ABC, abstractmethod import platform -import six - from ayon_core.lib import Logger from ayon_core.addon import AddonsManager from ayon_core.settings import get_project_settings -@six.add_metaclass(ABCMeta) -class HostDirmap(object): +class HostDirmap(ABC): """Abstract class for running dirmap on a workfile in a host. Dirmap is used to translate paths inside of host workfile from one diff --git a/client/ayon_core/host/host.py b/client/ayon_core/host/host.py index 081aafdbe3..5a29de6cd7 100644 --- a/client/ayon_core/host/host.py +++ b/client/ayon_core/host/host.py @@ -1,15 +1,13 @@ import os import logging import contextlib -from abc import ABCMeta, abstractproperty -import six +from abc import ABC, abstractproperty # NOTE can't import 'typing' because of issues in Maya 2020 # - shiboken crashes on 'typing' module import -@six.add_metaclass(ABCMeta) -class HostBase(object): +class HostBase(ABC): """Base of host implementation class. Host is pipeline implementation of DCC application. This class should help diff --git a/client/ayon_core/host/interfaces.py b/client/ayon_core/host/interfaces.py index 7157ad6f7e..c077dfeae9 100644 --- a/client/ayon_core/host/interfaces.py +++ b/client/ayon_core/host/interfaces.py @@ -1,5 +1,4 @@ -from abc import ABCMeta, abstractmethod -import six +from abc import ABC, abstractmethod class MissingMethodsError(ValueError): @@ -106,8 +105,7 @@ class ILoadHost: return self.get_containers() -@six.add_metaclass(ABCMeta) -class IWorkfileHost: +class IWorkfileHost(ABC): """Implementation requirements to be able use workfile utils and tool.""" @staticmethod diff --git a/client/ayon_core/lib/attribute_definitions.py b/client/ayon_core/lib/attribute_definitions.py index 0a9d38ab65..360d47ea17 100644 --- a/client/ayon_core/lib/attribute_definitions.py +++ b/client/ayon_core/lib/attribute_definitions.py @@ -6,7 +6,6 @@ import json import copy from abc import ABCMeta, abstractmethod, abstractproperty -import six import clique # Global variable which store attribute definitions by type @@ -91,8 +90,7 @@ class AbstractAttrDefMeta(ABCMeta): return obj -@six.add_metaclass(AbstractAttrDefMeta) -class AbstractAttrDef(object): +class AbstractAttrDef(metaclass=AbstractAttrDefMeta): """Abstraction of attribute definition. Each attribute definition must have implemented validation and @@ -349,7 +347,7 @@ class NumberDef(AbstractAttrDef): ) def convert_value(self, value): - if isinstance(value, six.string_types): + if isinstance(value, str): try: value = float(value) except Exception: @@ -396,12 +394,12 @@ class TextDef(AbstractAttrDef): if multiline is None: multiline = False - elif not isinstance(default, six.string_types): + elif not isinstance(default, str): raise TypeError(( - "'default' argument must be a {}, not '{}'" - ).format(six.string_types, type(default))) + f"'default' argument must be a str, not '{type(default)}'" + )) - if isinstance(regex, six.string_types): + if isinstance(regex, str): regex = re.compile(regex) self.multiline = multiline @@ -418,7 +416,7 @@ class TextDef(AbstractAttrDef): ) def convert_value(self, value): - if isinstance(value, six.string_types): + if isinstance(value, str): return value return self.default @@ -736,7 +734,7 @@ class FileDefItem(object): else: output.append(item) - elif isinstance(item, six.string_types): + elif isinstance(item, str): str_filepaths.append(item) else: raise TypeError( @@ -844,7 +842,7 @@ class FileDef(AbstractAttrDef): if isinstance(default, dict): FileDefItem.from_dict(default) - elif isinstance(default, six.string_types): + elif isinstance(default, str): default = FileDefItem.from_paths([default.strip()])[0] else: @@ -883,14 +881,14 @@ class FileDef(AbstractAttrDef): ) def convert_value(self, value): - if isinstance(value, six.string_types) or isinstance(value, dict): + if isinstance(value, (str, dict)): value = [value] if isinstance(value, (tuple, list, set)): string_paths = [] dict_items = [] for item in value: - if isinstance(item, six.string_types): + if isinstance(item, str): string_paths.append(item.strip()) elif isinstance(item, dict): try: diff --git a/client/ayon_core/lib/file_transaction.py b/client/ayon_core/lib/file_transaction.py index 81a3b386f6..47b10dd994 100644 --- a/client/ayon_core/lib/file_transaction.py +++ b/client/ayon_core/lib/file_transaction.py @@ -2,7 +2,6 @@ import os import logging import sys import errno -import six from ayon_core.lib import create_hard_link @@ -158,11 +157,13 @@ class FileTransaction(object): def rollback(self): errors = 0 + last_exc = None # Rollback any transferred files for path in self._transferred: try: os.remove(path) - except OSError: + except OSError as exc: + last_exc = exc errors += 1 self.log.error( "Failed to rollback created file: {}".format(path), @@ -172,7 +173,8 @@ class FileTransaction(object): for backup, original in self._backup_to_original.items(): try: os.rename(backup, original) - except OSError: + except OSError as exc: + last_exc = exc errors += 1 self.log.error( "Failed to restore original file: {} -> {}".format( @@ -183,7 +185,7 @@ class FileTransaction(object): self.log.error( "{} errors occurred during rollback.".format(errors), exc_info=True) - six.reraise(*sys.exc_info()) + raise last_exc @property def transferred(self): @@ -200,11 +202,9 @@ class FileTransaction(object): try: os.makedirs(dirname) except OSError as e: - if e.errno == errno.EEXIST: - pass - else: + if e.errno != errno.EEXIST: self.log.critical("An unexpected error occurred.") - six.reraise(*sys.exc_info()) + raise e def _same_paths(self, src, dst): # handles same paths but with C:/project vs c:/project diff --git a/client/ayon_core/lib/local_settings.py b/client/ayon_core/lib/local_settings.py index fd255c997f..54432265d9 100644 --- a/client/ayon_core/lib/local_settings.py +++ b/client/ayon_core/lib/local_settings.py @@ -4,7 +4,7 @@ import os import json import platform from datetime import datetime -from abc import ABCMeta, abstractmethod +from abc import ABC, abstractmethod # disable lru cache in Python 2 try: @@ -24,7 +24,6 @@ try: except ImportError: import ConfigParser as configparser -import six import appdirs import ayon_api @@ -133,8 +132,7 @@ class AYONSecureRegistry: keyring.delete_password(self._name, name) -@six.add_metaclass(ABCMeta) -class ASettingRegistry(): +class ASettingRegistry(ABC): """Abstract class defining structure of **SettingRegistry** class. It is implementing methods to store secure items into keyring, otherwise diff --git a/client/ayon_core/lib/path_templates.py b/client/ayon_core/lib/path_templates.py index a766dbd9c1..01a6985a25 100644 --- a/client/ayon_core/lib/path_templates.py +++ b/client/ayon_core/lib/path_templates.py @@ -2,8 +2,6 @@ import os import re import numbers -import six - KEY_PATTERN = re.compile(r"(\{.*?[^{0]*\})") KEY_PADDING_PATTERN = re.compile(r"([^:]+)\S+[><]\S+") SUB_DICT_PATTERN = re.compile(r"([^\[\]]+)") @@ -14,7 +12,7 @@ class TemplateUnsolved(Exception): """Exception for unsolved template when strict is set to True.""" msg = "Template \"{0}\" is unsolved.{1}{2}" - invalid_types_msg = " Keys with invalid DataType: `{0}`." + invalid_types_msg = " Keys with invalid data type: `{0}`." missing_keys_msg = " Missing keys: \"{0}\"." def __init__(self, template, missing_keys, invalid_types): @@ -43,7 +41,7 @@ class TemplateUnsolved(Exception): class StringTemplate(object): """String that can be formatted.""" def __init__(self, template): - if not isinstance(template, six.string_types): + if not isinstance(template, str): raise TypeError("<{}> argument must be a string, not {}.".format( self.__class__.__name__, str(type(template)) )) @@ -63,7 +61,7 @@ class StringTemplate(object): new_parts = [] for part in parts: - if not isinstance(part, six.string_types): + if not isinstance(part, str): new_parts.append(part) continue @@ -113,7 +111,7 @@ class StringTemplate(object): """ result = TemplatePartResult() for part in self._parts: - if isinstance(part, six.string_types): + if isinstance(part, str): result.add_output(part) else: part.format(data, result) @@ -176,7 +174,7 @@ class StringTemplate(object): value = "<>" elif ( len(parts) == 1 - and isinstance(parts[0], six.string_types) + and isinstance(parts[0], str) ): value = "<{}>".format(parts[0]) else: @@ -200,8 +198,9 @@ class StringTemplate(object): new_parts.extend(tmp_parts[idx]) return new_parts + class TemplateResult(str): - """Result of template format with most of information in. + """Result of template format with most of the information in. Args: used_values (dict): Dictionary of template filling data with @@ -299,7 +298,7 @@ class TemplatePartResult: self._optional = True def add_output(self, other): - if isinstance(other, six.string_types): + if isinstance(other, str): self._output += other elif isinstance(other, TemplatePartResult): @@ -457,7 +456,7 @@ class FormattingPart: return True for inh_class in type(value).mro(): - if inh_class in six.string_types: + if inh_class is str: return True return False @@ -568,7 +567,7 @@ class OptionalPart: def format(self, data, result): new_result = TemplatePartResult(True) for part in self._parts: - if isinstance(part, six.string_types): + if isinstance(part, str): new_result.add_output(part) else: part.format(data, new_result) diff --git a/client/ayon_core/pipeline/create/context.py b/client/ayon_core/pipeline/create/context.py index ca88b9b63c..1c64d22733 100644 --- a/client/ayon_core/pipeline/create/context.py +++ b/client/ayon_core/pipeline/create/context.py @@ -2043,7 +2043,8 @@ class CreateContext: variant, folder_entity=None, task_entity=None, - pre_create_data=None + pre_create_data=None, + active=None ): """Trigger create of plugins with standartized arguments. @@ -2061,6 +2062,8 @@ class CreateContext: of creation (possible context of created instance/s). task_entity (Dict[str, Any]): Task entity. pre_create_data (Dict[str, Any]): Pre-create attribute values. + active (Optional[bool]): Whether the created instance defaults + to be active or not. Returns: Any: Output of triggered creator's 'create' method. @@ -2126,6 +2129,14 @@ class CreateContext: "productType": creator.product_type, "variant": variant } + if active is not None: + if not isinstance(active, bool): + self.log.warning( + "CreateContext.create 'active' argument is not a bool. " + f"Converting {active} {type(active)} to bool.") + active = bool(active) + instance_data["active"] = active + return creator.create( product_name, instance_data, diff --git a/client/ayon_core/pipeline/create/creator_plugins.py b/client/ayon_core/pipeline/create/creator_plugins.py index 0e6025ad3b..624f1c9588 100644 --- a/client/ayon_core/pipeline/create/creator_plugins.py +++ b/client/ayon_core/pipeline/create/creator_plugins.py @@ -3,9 +3,7 @@ import copy import collections from typing import TYPE_CHECKING, Optional -from abc import ABCMeta, abstractmethod - -import six +from abc import ABC, abstractmethod from ayon_core.settings import get_project_settings from ayon_core.lib import Logger @@ -38,8 +36,7 @@ class CreatorError(Exception): super(CreatorError, self).__init__(message) -@six.add_metaclass(ABCMeta) -class ProductConvertorPlugin(object): +class ProductConvertorPlugin(ABC): """Helper for conversion of instances created using legacy creators. Conversion from legacy creators would mean to lose legacy instances, @@ -152,8 +149,7 @@ class ProductConvertorPlugin(object): self._create_context.remove_convertor_item(self.identifier) -@six.add_metaclass(ABCMeta) -class BaseCreator: +class BaseCreator(ABC): """Plugin that create and modify instance data before publishing process. We should maybe find better name as creation is only one part of its logic diff --git a/client/ayon_core/pipeline/project_folders.py b/client/ayon_core/pipeline/project_folders.py index 811a98ce4b..902b969457 100644 --- a/client/ayon_core/pipeline/project_folders.py +++ b/client/ayon_core/pipeline/project_folders.py @@ -2,8 +2,6 @@ import os import re import json -import six - from ayon_core.settings import get_project_settings from ayon_core.lib import Logger @@ -109,6 +107,6 @@ def get_project_basic_paths(project_name): if not folder_structure: return [] - if isinstance(folder_structure, six.string_types): + if isinstance(folder_structure, str): folder_structure = json.loads(folder_structure) return _list_path_items(folder_structure) diff --git a/client/ayon_core/pipeline/publish/abstract_collect_render.py b/client/ayon_core/pipeline/publish/abstract_collect_render.py index 17cab876b6..bd2d76c39c 100644 --- a/client/ayon_core/pipeline/publish/abstract_collect_render.py +++ b/client/ayon_core/pipeline/publish/abstract_collect_render.py @@ -7,8 +7,6 @@ TODO: use @dataclass when times come. from abc import abstractmethod import attr -import six - import pyblish.api from .publish_plugins import AbstractMetaContextPlugin @@ -122,8 +120,9 @@ class RenderInstance(object): raise ValueError("both tiles X a Y sizes are set to 1") -@six.add_metaclass(AbstractMetaContextPlugin) -class AbstractCollectRender(pyblish.api.ContextPlugin): +class AbstractCollectRender( + pyblish.api.ContextPlugin, metaclass=AbstractMetaContextPlugin +): """Gather all publishable render layers from renderSetup.""" order = pyblish.api.CollectorOrder + 0.01 diff --git a/client/ayon_core/pipeline/publish/abstract_expected_files.py b/client/ayon_core/pipeline/publish/abstract_expected_files.py index f9f3c17ef5..fffe723739 100644 --- a/client/ayon_core/pipeline/publish/abstract_expected_files.py +++ b/client/ayon_core/pipeline/publish/abstract_expected_files.py @@ -1,11 +1,9 @@ # -*- coding: utf-8 -*- """Abstract ExpectedFile class definition.""" -from abc import ABCMeta, abstractmethod -import six +from abc import ABC, abstractmethod -@six.add_metaclass(ABCMeta) -class ExpectedFiles: +class ExpectedFiles(ABC): """Class grouping functionality for all supported renderers. Attributes: diff --git a/client/ayon_core/pipeline/schema/__init__.py b/client/ayon_core/pipeline/schema/__init__.py index db98a6d080..d16755696d 100644 --- a/client/ayon_core/pipeline/schema/__init__.py +++ b/client/ayon_core/pipeline/schema/__init__.py @@ -17,7 +17,6 @@ import json import logging import jsonschema -import six log_ = logging.getLogger(__name__) @@ -44,7 +43,7 @@ def validate(data, schema=None): root, schema = data["schema"].rsplit(":", 1) - if isinstance(schema, six.string_types): + if isinstance(schema, str): schema = _cache[schema + ".json"] resolver = jsonschema.RefResolver( diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index 4a1f3a84da..7b15dff049 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -15,9 +15,8 @@ import os import re import collections import copy -from abc import ABCMeta, abstractmethod +from abc import ABC, abstractmethod -import six from ayon_api import ( get_folders, get_folder_by_path, @@ -82,8 +81,7 @@ class TemplateLoadFailed(Exception): pass -@six.add_metaclass(ABCMeta) -class AbstractTemplateBuilder(object): +class AbstractTemplateBuilder(ABC): """Abstraction of Template Builder. Builder cares about context, shared data, cache, discovery of plugins @@ -941,8 +939,7 @@ class AbstractTemplateBuilder(object): ) -@six.add_metaclass(ABCMeta) -class PlaceholderPlugin(object): +class PlaceholderPlugin(ABC): """Plugin which care about handling of placeholder items logic. Plugin create and update placeholders in scene and populate them on @@ -1797,6 +1794,16 @@ class PlaceholderCreateMixin(object): "\ncompiling of product name." ) ), + attribute_definitions.BoolDef( + "active", + label="Active", + default=options.get("active", True), + tooltip=( + "Active" + "\nDefines whether the created instance will default to " + "active or not." + ) + ), attribute_definitions.UISeparatorDef(), attribute_definitions.NumberDef( "order", @@ -1826,6 +1833,7 @@ class PlaceholderCreateMixin(object): legacy_create = self.builder.use_legacy_creators creator_name = placeholder.data["creator"] create_variant = placeholder.data["create_variant"] + active = placeholder.data.get("active") creator_plugin = self.builder.get_creators_by_name()[creator_name] @@ -1873,7 +1881,8 @@ class PlaceholderCreateMixin(object): create_variant, folder_entity, task_entity, - pre_create_data=pre_create_data + pre_create_data=pre_create_data, + active=active ) except: # noqa: E722 diff --git a/client/ayon_core/plugins/load/delete_old_versions.py b/client/ayon_core/plugins/load/delete_old_versions.py index 62302e7123..f8c45baff6 100644 --- a/client/ayon_core/plugins/load/delete_old_versions.py +++ b/client/ayon_core/plugins/load/delete_old_versions.py @@ -1,6 +1,7 @@ import collections import os import uuid +from typing import List, Dict, Any import clique import ayon_api @@ -41,11 +42,13 @@ class DeleteOldVersions(load.ProductLoaderPlugin): ) ] + requires_confirmation = True + def delete_whole_dir_paths(self, dir_paths, delete=True): size = 0 for dir_path in dir_paths: - # Delete all files and fodlers in dir path + # Delete all files and folders in dir path for root, dirs, files in os.walk(dir_path, topdown=False): for name in files: file_path = os.path.join(root, name) @@ -192,6 +195,42 @@ class DeleteOldVersions(load.ProductLoaderPlugin): ) msgBox.exec_() + def _confirm_delete(self, + contexts: List[Dict[str, Any]], + versions_to_keep: int) -> bool: + """Prompt user for a deletion confirmation""" + + contexts_list = "\n".join(sorted( + "- {folder[name]} > {product[name]}".format_map(context) + for context in contexts + )) + num_contexts = len(contexts) + s = "s" if num_contexts > 1 else "" + text = ( + "Are you sure you want to delete versions?\n\n" + f"This will keep only the last {versions_to_keep} " + f"versions for the {num_contexts} selected product{s}." + ) + informative_text="Warning: This will delete files from disk" + detailed_text = ( + f"Keep only {versions_to_keep} versions for:\n{contexts_list}" + ) + + messagebox = QtWidgets.QMessageBox() + messagebox.setIcon(QtWidgets.QMessageBox.Warning) + messagebox.setWindowTitle("Delete Old Versions") + messagebox.setText(text) + messagebox.setInformativeText(informative_text) + messagebox.setDetailedText(detailed_text) + messagebox.setStandardButtons( + QtWidgets.QMessageBox.Yes + | QtWidgets.QMessageBox.Cancel + ) + messagebox.setDefaultButton(QtWidgets.QMessageBox.Cancel) + messagebox.setStyleSheet(style.load_stylesheet()) + messagebox.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) + return messagebox.exec_() == QtWidgets.QMessageBox.Yes + def get_data(self, context, versions_count): product_entity = context["product"] folder_entity = context["folder"] @@ -365,19 +404,29 @@ class DeleteOldVersions(load.ProductLoaderPlugin): return size def load(self, contexts, name=None, namespace=None, options=None): + + # Get user options + versions_to_keep = 2 + remove_publish_folder = False + if options: + versions_to_keep = options.get( + "versions_to_keep", versions_to_keep + ) + remove_publish_folder = options.get( + "remove_publish_folder", remove_publish_folder + ) + + # Because we do not want this run by accident we will add an extra + # user confirmation + if ( + self.requires_confirmation + and not self._confirm_delete(contexts, versions_to_keep) + ): + return + try: size = 0 for count, context in enumerate(contexts): - versions_to_keep = 2 - remove_publish_folder = False - if options: - versions_to_keep = options.get( - "versions_to_keep", versions_to_keep - ) - remove_publish_folder = options.get( - "remove_publish_folder", remove_publish_folder - ) - data = self.get_data(context, versions_to_keep) if not data: continue @@ -408,6 +457,8 @@ class CalculateOldVersions(DeleteOldVersions): ) ] + requires_confirmation = False + def main(self, project_name, data, remove_publish_folder): size = 0 diff --git a/client/ayon_core/plugins/publish/extract_burnin.py b/client/ayon_core/plugins/publish/extract_burnin.py index 93774842ca..58a032a030 100644 --- a/client/ayon_core/plugins/publish/extract_burnin.py +++ b/client/ayon_core/plugins/publish/extract_burnin.py @@ -6,7 +6,6 @@ import platform import shutil import clique -import six import pyblish.api from ayon_core import resources, AYON_CORE_ROOT @@ -456,7 +455,7 @@ class ExtractBurnin(publish.Extractor): sys_name = platform.system().lower() font_filepath = font_filepath.get(sys_name) - if font_filepath and isinstance(font_filepath, six.string_types): + if font_filepath and isinstance(font_filepath, str): font_filepath = font_filepath.format(**os.environ) if not os.path.exists(font_filepath): font_filepath = None diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 1891c25521..c2793f98a2 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -4,9 +4,8 @@ import copy import json import shutil import subprocess -from abc import ABCMeta, abstractmethod +from abc import ABC, abstractmethod -import six import clique import speedcopy import pyblish.api @@ -1661,8 +1660,7 @@ class ExtractReview(pyblish.api.InstancePlugin): return vf_back -@six.add_metaclass(ABCMeta) -class _OverscanValue: +class _OverscanValue(ABC): def __repr__(self): return "<{}> {}".format(self.__class__.__name__, str(self)) diff --git a/client/ayon_core/plugins/publish/integrate.py b/client/ayon_core/plugins/publish/integrate.py index 2da33bfb19..a2cf910fa6 100644 --- a/client/ayon_core/plugins/publish/integrate.py +++ b/client/ayon_core/plugins/publish/integrate.py @@ -4,7 +4,6 @@ import sys import copy import clique -import six import pyblish.api from ayon_api import ( get_attributes_for_type, @@ -160,15 +159,14 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # Raise DuplicateDestinationError as KnownPublishError # and rollback the transactions file_transactions.rollback() - six.reraise(KnownPublishError, - KnownPublishError(exc), - sys.exc_info()[2]) - except Exception: + raise KnownPublishError(exc).with_traceback(sys.exc_info()[2]) + + except Exception as exc: # clean destination # todo: preferably we'd also rollback *any* changes to the database file_transactions.rollback() self.log.critical("Error when registering", exc_info=True) - six.reraise(*sys.exc_info()) + raise exc # Finalizing can't rollback safely so no use for moving it to # the try, except. diff --git a/client/ayon_core/style/__init__.py b/client/ayon_core/style/__init__.py index 8d3089ef86..064f527f2b 100644 --- a/client/ayon_core/style/__init__.py +++ b/client/ayon_core/style/__init__.py @@ -2,7 +2,6 @@ import os import copy import json import collections -import six from ayon_core import resources @@ -75,7 +74,7 @@ def _convert_color_values_to_objects(value): output[_key] = _convert_color_values_to_objects(_value) return output - if not isinstance(value, six.string_types): + if not isinstance(value, str): raise TypeError(( "Unexpected type in colors data '{}'. Expected 'str' or 'dict'." ).format(str(type(value)))) diff --git a/client/ayon_core/tools/common_models/hierarchy.py b/client/ayon_core/tools/common_models/hierarchy.py index f92563db20..6bccb0f468 100644 --- a/client/ayon_core/tools/common_models/hierarchy.py +++ b/client/ayon_core/tools/common_models/hierarchy.py @@ -1,18 +1,16 @@ import time import collections import contextlib -from abc import ABCMeta, abstractmethod +from abc import ABC, abstractmethod import ayon_api -import six from ayon_core.lib import NestedCacheItem HIERARCHY_MODEL_SENDER = "hierarchy.model" -@six.add_metaclass(ABCMeta) -class AbstractHierarchyController: +class AbstractHierarchyController(ABC): @abstractmethod def emit_event(self, topic, data, source): pass diff --git a/client/ayon_core/tools/launcher/abstract.py b/client/ayon_core/tools/launcher/abstract.py index 921fe7bc5b..63ba4cd717 100644 --- a/client/ayon_core/tools/launcher/abstract.py +++ b/client/ayon_core/tools/launcher/abstract.py @@ -1,10 +1,7 @@ -from abc import ABCMeta, abstractmethod - -import six +from abc import ABC, abstractmethod -@six.add_metaclass(ABCMeta) -class AbstractLauncherCommon(object): +class AbstractLauncherCommon(ABC): @abstractmethod def register_event_callback(self, topic, callback): """Register event callback. diff --git a/client/ayon_core/tools/loader/abstract.py b/client/ayon_core/tools/loader/abstract.py index 3a1a23edd7..6a68af1eb5 100644 --- a/client/ayon_core/tools/loader/abstract.py +++ b/client/ayon_core/tools/loader/abstract.py @@ -1,5 +1,4 @@ -from abc import ABCMeta, abstractmethod -import six +from abc import ABC, abstractmethod from ayon_core.lib.attribute_definitions import ( AbstractAttrDef, @@ -347,8 +346,7 @@ class ActionItem: return cls(**data) -@six.add_metaclass(ABCMeta) -class _BaseLoaderController(object): +class _BaseLoaderController(ABC): """Base loader controller abstraction. Abstract base class that is required for both frontend and backed. diff --git a/client/ayon_core/tools/loader/ui/products_delegates.py b/client/ayon_core/tools/loader/ui/products_delegates.py index 0ed8fe8fe7..9753da37af 100644 --- a/client/ayon_core/tools/loader/ui/products_delegates.py +++ b/client/ayon_core/tools/loader/ui/products_delegates.py @@ -140,12 +140,6 @@ class VersionComboBox(QtWidgets.QComboBox): self.value_changed.emit(self._product_id, value) -class EditorInfo: - def __init__(self, widget): - self.widget = widget - self.added = False - - class VersionDelegate(QtWidgets.QStyledItemDelegate): """A delegate that display version integer formatted as version string.""" @@ -154,7 +148,7 @@ class VersionDelegate(QtWidgets.QStyledItemDelegate): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self._editor_by_id: Dict[str, EditorInfo] = {} + self._editor_by_id: Dict[str, VersionComboBox] = {} self._statuses_filter = None def displayText(self, value, locale): @@ -164,8 +158,8 @@ class VersionDelegate(QtWidgets.QStyledItemDelegate): def set_statuses_filter(self, status_names): self._statuses_filter = set(status_names) - for info in self._editor_by_id.values(): - info.widget.set_statuses_filter(status_names) + for widget in self._editor_by_id.values(): + widget.set_statuses_filter(status_names) def paint(self, painter, option, index): fg_color = index.data(QtCore.Qt.ForegroundRole) @@ -229,11 +223,11 @@ class VersionDelegate(QtWidgets.QStyledItemDelegate): editor = VersionComboBox(product_id, parent) editor.setProperty("itemId", item_id) - self._editor_by_id[item_id] = EditorInfo(editor) - editor.value_changed.connect(self._on_editor_change) editor.destroyed.connect(self._on_destroy) + self._editor_by_id[item_id] = editor + return editor def setEditorData(self, editor, index): @@ -242,12 +236,10 @@ class VersionDelegate(QtWidgets.QStyledItemDelegate): # Current value of the index versions = index.data(VERSION_NAME_EDIT_ROLE) or [] version_id = index.data(VERSION_ID_ROLE) + editor.update_versions(versions, version_id) editor.set_statuses_filter(self._statuses_filter) - item_id = editor.property("itemId") - self._editor_by_id[item_id].added = True - def setModelData(self, editor, model, index): """Apply the integer version back in the model""" diff --git a/client/ayon_core/tools/loader/ui/products_model.py b/client/ayon_core/tools/loader/ui/products_model.py index 734be5dd90..bc24d4d7f7 100644 --- a/client/ayon_core/tools/loader/ui/products_model.py +++ b/client/ayon_core/tools/loader/ui/products_model.py @@ -132,7 +132,7 @@ class ProductsModel(QtGui.QStandardItemModel): def get_product_item_indexes(self): return [ - item.index() + self.indexFromItem(item) for item in self._items_by_id.values() ] @@ -156,16 +156,18 @@ class ProductsModel(QtGui.QStandardItemModel): if product_item is None: return - self.setData( - product_item.index(), version_id, VERSION_NAME_EDIT_ROLE - ) + index = self.indexFromItem(product_item) + self.setData(index, version_id, VERSION_NAME_EDIT_ROLE) def set_enable_grouping(self, enable_grouping): if enable_grouping is self._grouping_enabled: return self._grouping_enabled = enable_grouping # Ignore change if groups are not available - self.refresh(self._last_project_name, self._last_folder_ids) + self.refresh( + self._last_project_name, + self._last_folder_ids + ) def flags(self, index): # Make the version column editable @@ -454,7 +456,7 @@ class ProductsModel(QtGui.QStandardItemModel): def get_last_project_name(self): return self._last_project_name - def refresh(self, project_name, folder_ids, status_names): + def refresh(self, project_name, folder_ids): self._clear() self._last_project_name = project_name @@ -486,17 +488,9 @@ class ProductsModel(QtGui.QStandardItemModel): } last_version_by_product_id = {} for product_item in product_items: - all_versions = list(product_item.version_items.values()) - all_versions.sort() - versions = [ - version_item - for version_item in all_versions - if status_names is None or version_item.status in status_names - ] - if versions: - last_version = versions[-1] - else: - last_version = all_versions[-1] + versions = list(product_item.version_items.values()) + versions.sort() + last_version = versions[-1] last_version_by_product_id[product_item.product_id] = ( last_version ) @@ -543,10 +537,11 @@ class ProductsModel(QtGui.QStandardItemModel): for product_name, product_items in groups.items(): group_product_types |= {p.product_type for p in product_items} for product_item in product_items: - group_product_types |= { + group_status_names |= { version_item.status for version_item in product_item.version_items.values() } + group_product_types.add(product_item.product_type) if len(product_items) == 1: top_items.append(product_items[0]) @@ -589,13 +584,15 @@ class ProductsModel(QtGui.QStandardItemModel): product_name, product_items = path_info (merged_color_hex, merged_color_qt) = self._get_next_color() merged_color = qtawesome.icon( - "fa.circle", color=merged_color_qt) + "fa.circle", color=merged_color_qt + ) merged_item = self._get_merged_model_item( product_name, len(product_items), merged_color_hex) merged_item.setData(merged_color, QtCore.Qt.DecorationRole) new_items.append(merged_item) merged_product_types = set() + merged_status_names = set() new_merged_items = [] for product_item in product_items: item = self._get_product_model_item( @@ -608,9 +605,21 @@ class ProductsModel(QtGui.QStandardItemModel): ) new_merged_items.append(item) merged_product_types.add(product_item.product_type) + merged_status_names |= { + version_item.status + for version_item in ( + product_item.version_items.values() + ) + } merged_item.setData( - "|".join(merged_product_types), PRODUCT_TYPE_ROLE) + "|".join(merged_product_types), + PRODUCT_TYPE_ROLE + ) + merged_item.setData( + "|".join(merged_status_names), + STATUS_NAME_FILTER_ROLE + ) if new_merged_items: merged_item.appendRows(new_merged_items) diff --git a/client/ayon_core/tools/loader/ui/products_widget.py b/client/ayon_core/tools/loader/ui/products_widget.py index e37c327a17..748a1b5fb8 100644 --- a/client/ayon_core/tools/loader/ui/products_widget.py +++ b/client/ayon_core/tools/loader/ui/products_widget.py @@ -186,11 +186,12 @@ class ProductsWidget(QtWidgets.QWidget): products_proxy_model.rowsInserted.connect(self._on_rows_inserted) products_proxy_model.rowsMoved.connect(self._on_rows_moved) products_model.refreshed.connect(self._on_refresh) + products_model.version_changed.connect(self._on_version_change) products_view.customContextMenuRequested.connect( self._on_context_menu) - products_view.selectionModel().selectionChanged.connect( + products_view_sel_model = products_view.selectionModel() + products_view_sel_model.selectionChanged.connect( self._on_selection_change) - products_model.version_changed.connect(self._on_version_change) version_delegate.version_changed.connect( self._on_version_delegate_change ) @@ -321,8 +322,7 @@ class ProductsWidget(QtWidgets.QWidget): def _refresh_model(self): self._products_model.refresh( self._selected_project_name, - self._selected_folder_ids, - self._products_proxy_model.get_statuses_filter() + self._selected_folder_ids ) def _on_context_menu(self, point): diff --git a/client/ayon_core/tools/loader/ui/statuses_combo.py b/client/ayon_core/tools/loader/ui/statuses_combo.py index 5587853940..9fe7ab62a5 100644 --- a/client/ayon_core/tools/loader/ui/statuses_combo.py +++ b/client/ayon_core/tools/loader/ui/statuses_combo.py @@ -294,7 +294,7 @@ class StatusesCombobox(CustomPaintMultiselectComboBox): model=model, parent=parent ) - self.set_placeholder_text("Statuses...") + self.set_placeholder_text("Version status filter...") self._model = model self._last_project_name = None self._fully_disabled_filter = False diff --git a/client/ayon_core/tools/publisher/publish_report_viewer/window.py b/client/ayon_core/tools/publisher/publish_report_viewer/window.py index 3ee986e6f7..aedc3b9e31 100644 --- a/client/ayon_core/tools/publisher/publish_report_viewer/window.py +++ b/client/ayon_core/tools/publisher/publish_report_viewer/window.py @@ -1,6 +1,5 @@ import os import json -import six import uuid import appdirs @@ -387,7 +386,7 @@ class LoadedFilesModel(QtGui.QStandardItemModel): if not filepaths: return - if isinstance(filepaths, six.string_types): + if isinstance(filepaths, str): filepaths = [filepaths] filtered_paths = [] diff --git a/client/ayon_core/tools/workfiles/abstract.py b/client/ayon_core/tools/workfiles/abstract.py index e949915ab2..b78e987032 100644 --- a/client/ayon_core/tools/workfiles/abstract.py +++ b/client/ayon_core/tools/workfiles/abstract.py @@ -1,7 +1,6 @@ import os -from abc import ABCMeta, abstractmethod +from abc import ABC, abstractmethod -import six from ayon_core.style import get_default_entity_icon_color @@ -335,8 +334,7 @@ class WorkareaFilepathResult: self.filepath = filepath -@six.add_metaclass(ABCMeta) -class AbstractWorkfilesCommon(object): +class AbstractWorkfilesCommon(ABC): @abstractmethod def is_host_valid(self): """Host is valid for workfiles tool work. diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index 42fd6a5c72..a8c42ec80a 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON core addon version.""" -__version__ = "0.4.2-dev.1" +__version__ = "0.4.3-dev.1" diff --git a/package.py b/package.py index 5da8e36064..4f2d2b16b4 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "0.4.2-dev.1" +version = "0.4.3-dev.1" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index cdedd878a0..f8f840d2c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "0.4.2-dev.1" +version = "0.4.3-dev.1" description = "" authors = ["Ynput Team "] readme = "README.md"