Merge branch 'develop' of https://github.com/ynput/ayon-core into feature/AY-5998_DL-Search--replace-in-environment-values-requires-a-Value

This commit is contained in:
Petr Kalis 2024-07-22 16:40:39 +02:00
commit 8a956e3ce2
33 changed files with 200 additions and 171 deletions

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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,

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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:

View file

@ -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(

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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))

View file

@ -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.

View file

@ -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))))

View file

@ -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

View file

@ -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.

View file

@ -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.

View file

@ -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"""

View file

@ -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)

View file

@ -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):

View file

@ -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

View file

@ -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 = []

View file

@ -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.

View file

@ -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"

View file

@ -1,6 +1,6 @@
name = "core"
title = "Core"
version = "0.4.2-dev.1"
version = "0.4.3-dev.1"
client_dir = "ayon_core"

View file

@ -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 <team@ynput.io>"]
readme = "README.md"