From 3de2d65c5b2c759d2c2738c42f5da511208eeb73 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 24 Jun 2024 14:02:49 +0200 Subject: [PATCH] added typehints for controller --- .../ayon_core/tools/common_models/__init__.py | 4 + client/ayon_core/tools/publisher/abstract.py | 401 +++++++++++------- client/ayon_core/tools/publisher/constants.py | 3 + client/ayon_core/tools/publisher/control.py | 86 ++-- .../tools/publisher/models/__init__.py | 3 +- .../tools/publisher/models/create.py | 6 +- .../tools/publisher/models/publish.py | 4 +- .../publisher/widgets/card_view_widgets.py | 17 +- .../widgets/create_context_widgets.py | 15 +- .../tools/publisher/widgets/create_widget.py | 20 +- .../tools/publisher/widgets/folders_dialog.py | 5 +- .../tools/publisher/widgets/help_widget.py | 8 +- .../publisher/widgets/list_view_widgets.py | 15 +- .../publisher/widgets/overview_widget.py | 11 +- .../tools/publisher/widgets/publish_frame.py | 13 +- .../tools/publisher/widgets/report_page.py | 54 ++- .../tools/publisher/widgets/tasks_model.py | 16 +- .../publisher/widgets/thumbnail_widget.py | 11 +- .../tools/publisher/widgets/widgets.py | 76 ++-- client/ayon_core/tools/publisher/window.py | 24 +- 20 files changed, 484 insertions(+), 308 deletions(-) diff --git a/client/ayon_core/tools/common_models/__init__.py b/client/ayon_core/tools/common_models/__init__.py index f09edfeab2..7b644f5dba 100644 --- a/client/ayon_core/tools/common_models/__init__.py +++ b/client/ayon_core/tools/common_models/__init__.py @@ -5,6 +5,8 @@ from .projects import ( ProjectItem, ProjectsModel, PROJECTS_MODEL_SENDER, + FolderTypeItem, + TaskTypeItem, ) from .hierarchy import ( FolderItem, @@ -24,6 +26,8 @@ __all__ = ( "ProjectItem", "ProjectsModel", "PROJECTS_MODEL_SENDER", + "FolderTypeItem", + "TaskTypeItem", "FolderItem", "TaskItem", diff --git a/client/ayon_core/tools/publisher/abstract.py b/client/ayon_core/tools/publisher/abstract.py index 1562edb4dc..447fa5607f 100644 --- a/client/ayon_core/tools/publisher/abstract.py +++ b/client/ayon_core/tools/publisher/abstract.py @@ -3,9 +3,16 @@ from typing import Optional, Dict, List, Tuple, Any, Callable, Union, Iterable from ayon_core.lib import AbstractAttrDef from ayon_core.host import HostBase -from ayon_core.tools.common_models import FolderItem, TaskItem from ayon_core.pipeline.create import CreateContext, CreatedInstance from ayon_core.pipeline.create.context import ConvertorItem +from ayon_core.tools.common_models import ( + FolderItem, + TaskItem, + FolderTypeItem, + TaskTypeItem, +) + +from .models import CreatorItem class CardMessageTypes: @@ -14,22 +21,19 @@ class CardMessageTypes: error = "error" -class AbstractPublisherController(ABC): - """Publisher tool controller. - - Define what must be implemented to be able use Publisher functionality. - - Goal is to have "data driven" controller that can be used to control UI - running in different process. That lead to some disadvantages like UI can't - access objects directly but by using wrappers that can be serialized. - """ - +class AbstractPublisherCommon(ABC): @abstractmethod - def is_headless(self) -> bool: - pass + def register_event_callback(self, topic, callback): + """Register event callback. + + Listen for events with given topic. + + Args: + topic (str): Name of topic. + callback (Callable): Callback that will be called when event + is triggered. + """ - @abstractmethod - def get_host(self) -> HostBase: pass @abstractmethod @@ -38,11 +42,32 @@ class AbstractPublisherController(ABC): data: Optional[Dict[str, Any]] = None, source: Optional[str] = None ): - """Use implemented event system to trigger event.""" + """Emit event. + + Args: + topic (str): Event topic used for callbacks filtering. + data (Optional[dict[str, Any]]): Event data. + source (Optional[str]): Event source. + + """ pass @abstractmethod - def register_event_callback(self, topic: str, callback: Callable): + def emit_card_message( + self, + message: str, + message_type: Optional[str] = CardMessageTypes.standard + ): + """Emit a card message which can have a lifetime. + + This is for UI purposes. Method can be extended to more arguments + in future e.g. different message timeout or type (color). + + Args: + message (str): Message that will be showed. + message_type (Optional[str]): Message type. + """ + pass @abstractmethod @@ -88,34 +113,36 @@ class AbstractPublisherController(ABC): pass @abstractmethod - def is_host_valid(self) -> bool: - """Host is valid for creation part. + def reset(self): + """Reset whole controller. - Host must have implemented certain functionality to be able create - in Publisher tool. - - Returns: - bool: Host can handle creation of instances. + This should reset create context, publish context and all variables + that are related to it. """ pass + +class AbstractPublisherBackend(AbstractPublisherCommon): @abstractmethod - def get_folder_entity( - self, project_name: str, folder_id: str - ) -> Union[Dict[str, Any], None]: + def is_headless(self) -> bool: + """Controller is in headless mode. + + Notes: + Not sure if this method is relevant in UI tool? + + Returns: + bool: Headless mode. + + """ pass @abstractmethod - def get_task_entity( - self, project_name: str, task_id: str - ) -> Union[Dict[str, Any], None]: + def get_host(self) -> HostBase: pass @abstractmethod - def get_folder_item_by_path( - self, project_name: str, folder_path: str - ) -> Union[FolderItem, None]: + def get_create_context(self) -> CreateContext: pass @abstractmethod @@ -129,31 +156,42 @@ class AbstractPublisherController(ABC): pass @abstractmethod - def get_create_context(self) -> CreateContext: + def get_folder_entity( + self, project_name: str, folder_id: str + ) -> Union[Dict[str, Any], None]: pass @abstractmethod - def get_instances(self) -> List[CreatedInstance]: - """Collected/created instances. + def get_folder_item_by_path( + self, project_name: str, folder_path: str + ) -> Union[FolderItem, None]: + pass + + @abstractmethod + def get_task_entity( + self, project_name: str, task_id: str + ) -> Union[Dict[str, Any], None]: + pass + + +class AbstractPublisherFrontend(AbstractPublisherCommon): + @abstractmethod + def register_event_callback(self, topic: str, callback: Callable): + pass + + @abstractmethod + def is_host_valid(self) -> bool: + """Host is valid for creation part. + + Host must have implemented certain functionality to be able create + in Publisher tool. Returns: - List[CreatedInstance]: List of created instances. + bool: Host can handle creation of instances. """ pass - @abstractmethod - def get_instance_by_id( - self, instance_id: str - ) -> Union[CreatedInstance, None]: - pass - - @abstractmethod - def get_instances_by_id( - self, instance_ids: Optional[Iterable[str]] = None - ) -> Dict[str, Union[CreatedInstance, None]]: - pass - @abstractmethod def get_context_title(self) -> Union[str, None]: """Get context title for artist shown at the top of main window. @@ -166,19 +204,108 @@ class AbstractPublisherController(ABC): pass @abstractmethod - def get_existing_product_names(self, folder_path: str) -> List[str]: + def get_task_items_by_folder_paths( + self, folder_paths: Iterable[str] + ) -> Dict[str, List[TaskItem]]: pass @abstractmethod - def reset(self): - """Reset whole controller. + def get_folder_items( + self, project_name: str, sender: Optional[str] = None + ) -> List[FolderItem]: + pass - This should reset create context, publish context and all variables - that are related to it. + @abstractmethod + def get_task_items( + self, project_name: str, folder_id: str, sender: Optional[str] = None + ) -> List[TaskItem]: + pass + + @abstractmethod + def get_folder_type_items( + self, project_name: str, sender: Optional[str] = None + ) -> List[FolderTypeItem]: + pass + + @abstractmethod + def get_task_type_items( + self, project_name: str, sender: Optional[str] = None + ) -> List[TaskTypeItem]: + pass + + @abstractmethod + def are_folder_paths_valid(self, folder_paths: Iterable[str]) -> bool: + """Folder paths do exist in project. + + Args: + folder_paths (Iterable[str]): List of folder paths. + + Returns: + bool: All folder paths exist in project. + + """ + pass + + # --- Create --- + @abstractmethod + def get_creator_items(self) -> Dict[str, CreatorItem]: + """Creator items by identifier. + + Returns: + Dict[str, CreatorItem]: Creator items that will be shown to user. + + """ + pass + + @abstractmethod + def get_creator_icon( + self, identifier: str + ) -> Union[str, Dict[str, Any], None]: + """Receive creator's icon by identifier. + + Todos: + Icon should be part of 'CreatorItem'. + + Args: + identifier (str): Creator's identifier. + + Returns: + Union[str, None]: Creator's icon string. """ pass + @abstractmethod + def get_convertor_items(self) -> Dict[str, ConvertorItem]: + """Convertor items by identifier. + + Returns: + Dict[str, ConvertorItem]: Convertor items that can be triggered + by user. + + """ + pass + + @abstractmethod + def get_instances(self) -> List[CreatedInstance]: + """Collected/created instances. + + Returns: + List[CreatedInstance]: List of created instances. + + """ + pass + + @abstractmethod + def get_instances_by_id( + self, instance_ids: Optional[Iterable[str]] = None + ) -> Dict[str, Union[CreatedInstance, None]]: + pass + + @abstractmethod + def get_existing_product_names(self, folder_path: str) -> List[str]: + pass + @abstractmethod def get_creator_attribute_definitions( self, instances: List[CreatedInstance] @@ -197,21 +324,6 @@ class AbstractPublisherController(ABC): ]]: pass - @abstractmethod - def get_creator_icon( - self, identifier: str - ) -> Union[str, Dict[str, Any], None]: - """Receive creator's icon by identifier. - - Args: - identifier (str): Creator's identifier. - - Returns: - Union[str, None]: Creator's icon string. - """ - - pass - @abstractmethod def get_product_name( self, @@ -257,6 +369,17 @@ class AbstractPublisherController(ABC): pass + @abstractmethod + def trigger_convertor_items(self, convertor_identifiers: List[str]): + pass + + @abstractmethod + def remove_instances(self, instance_ids: Iterable[str]): + """Remove list of instances from create context.""" + # TODO expect instance ids + + pass + @abstractmethod def save_changes(self) -> bool: """Save changes in create context. @@ -269,10 +392,37 @@ class AbstractPublisherController(ABC): pass + # --- Publish --- @abstractmethod - def remove_instances(self, instance_ids: List[str]): - """Remove list of instances from create context.""" - # TODO expect instance ids + def publish(self): + """Trigger publishing without any order limitations.""" + + pass + + @abstractmethod + def validate(self): + """Trigger publishing which will stop after validation order.""" + + pass + + @abstractmethod + def stop_publish(self): + """Stop publishing can be also used to pause publishing. + + Pause of publishing is possible only if all plugins successfully + finished. + """ + + pass + + @abstractmethod + def run_action(self, plugin_id: str, action_id: str): + """Trigger pyblish action on a plugin. + + Args: + plugin_id (str): Id of publish plugin. + action_id (str): Id of publish action. + """ pass @@ -312,8 +462,18 @@ class AbstractPublisherController(ABC): Returns: bool: If publishing passed last possible validation order. - """ + """ + pass + + @abstractmethod + def publish_can_continue(self): + """Publish has still plugins to process and did not crash yet. + + Returns: + bool: Publishing can continue in processing. + + """ pass @abstractmethod @@ -336,16 +496,6 @@ class AbstractPublisherController(ABC): pass - @abstractmethod - def get_publish_max_progress(self) -> int: - """Get maximum possible progress number. - - Returns: - int: Number that can be used as 100% of publish progress bar. - """ - - pass - @abstractmethod def get_publish_progress(self) -> int: """Current progress number. @@ -356,6 +506,16 @@ class AbstractPublisherController(ABC): pass + @abstractmethod + def get_publish_max_progress(self) -> int: + """Get maximum possible progress number. + + Returns: + int: Number that can be used as 100% of publish progress bar. + """ + + pass + @abstractmethod def get_publish_error_msg(self) -> Union[str, None]: """Current error message which cause fail of publishing. @@ -375,59 +535,6 @@ class AbstractPublisherController(ABC): def get_validation_errors(self): pass - @abstractmethod - def publish(self): - """Trigger publishing without any order limitations.""" - - pass - - @abstractmethod - def validate(self): - """Trigger publishing which will stop after validation order.""" - - pass - - @abstractmethod - def stop_publish(self): - """Stop publishing can be also used to pause publishing. - - Pause of publishing is possible only if all plugins successfully - finished. - """ - - pass - - @abstractmethod - def run_action(self, plugin_id: str, action_id: str): - """Trigger pyblish action on a plugin. - - Args: - plugin_id (str): Id of publish plugin. - action_id (str): Id of publish action. - """ - - pass - - @abstractmethod - def get_convertor_items(self) -> Dict[str, ConvertorItem]: - pass - - @abstractmethod - def trigger_convertor_items(self, convertor_identifiers: List[str]): - pass - - @abstractmethod - def get_thumbnail_paths_for_instances( - self, instance_ids: List[str] - ) -> Dict[str, Union[str, None]]: - pass - - @abstractmethod - def set_thumbnail_paths_for_instances( - self, thumbnail_path_mapping: Dict[str, str] - ): - pass - @abstractmethod def set_comment(self, comment: str): """Set comment on pyblish context. @@ -441,21 +548,15 @@ class AbstractPublisherController(ABC): pass @abstractmethod - def emit_card_message( - self, - message: str, - message_type: Optional[str] = CardMessageTypes.standard + def get_thumbnail_paths_for_instances( + self, instance_ids: List[str] + ) -> Dict[str, Union[str, None]]: + pass + + @abstractmethod + def set_thumbnail_paths_for_instances( + self, thumbnail_path_mapping: Dict[str, Optional[str]] ): - """Emit a card message which can have a lifetime. - - This is for UI purposes. Method can be extended to more arguments - in future e.g. different message timeout or type (color). - - Args: - message (str): Message that will be showed. - message_type (Optional[str]): Message type. - """ - pass @abstractmethod diff --git a/client/ayon_core/tools/publisher/constants.py b/client/ayon_core/tools/publisher/constants.py index 6676f14c3d..285724727d 100644 --- a/client/ayon_core/tools/publisher/constants.py +++ b/client/ayon_core/tools/publisher/constants.py @@ -37,6 +37,9 @@ __all__ = ( "CONTEXT_ID", "CONTEXT_LABEL", + "CONTEXT_GROUP", + "CONVERTOR_ITEM_GROUP", + "VARIANT_TOOLTIP", "INPUTS_LAYOUT_HSPACING", diff --git a/client/ayon_core/tools/publisher/control.py b/client/ayon_core/tools/publisher/control.py index 4f8e7e8582..f26f8fc524 100644 --- a/client/ayon_core/tools/publisher/control.py +++ b/client/ayon_core/tools/publisher/control.py @@ -17,43 +17,17 @@ from .models import ( PublishModel, CreateModel, ) -from .abstract import AbstractPublisherController, CardMessageTypes +from .abstract import ( + AbstractPublisherBackend, + AbstractPublisherFrontend, + CardMessageTypes +) -class BasePublisherController(AbstractPublisherController): - """Implement common logic for controllers. - - Implement event system, logger and common attributes. Attributes are - triggering value changes so anyone can listen to their topics. - - Prepare implementation for creator items. Controller must implement just - their filling by '_collect_creator_items'. - - All prepared implementation is based on calling super '__init__'. - """ - - def get_thumbnail_temp_dir_path(self): - """Return path to directory where thumbnails can be temporary stored. - - Returns: - str: Path to a directory. - """ - - return os.path.join( - tempfile.gettempdir(), - "publisher_thumbnails", - get_process_id() - ) - - def clear_thumbnail_temp_dir_path(self): - """Remove content of thumbnail temp directory.""" - - dirpath = self.get_thumbnail_temp_dir_path() - if os.path.exists(dirpath): - shutil.rmtree(dirpath) - - -class PublisherController(BasePublisherController): +class PublisherController( + AbstractPublisherBackend, + AbstractPublisherFrontend, +): """Middleware between UI, CreateContext and publish Context. Handle both creation and publishing parts. @@ -137,6 +111,17 @@ class PublisherController(BasePublisherController): data = {} self._event_system.emit(topic, data, source) + def emit_card_message( + self, message, message_type=CardMessageTypes.standard + ): + self._emit_event( + "show.card.message", + { + "message": message, + "message_type": message_type + } + ) + def register_event_callback(self, topic, callback): self._event_system.add_callback(topic, callback) @@ -183,6 +168,7 @@ class PublisherController(BasePublisherController): Args: identifier (str): Creator's identifier for which should be icon returned. + """ return self._create_model.get_creator_icon(identifier) @@ -201,9 +187,6 @@ class PublisherController(BasePublisherController): """Current instances in create context.""" return self._create_model.get_instances() - def get_instance_by_id(self, instance_id): - return self._create_model.get_instance_by_id(instance_id) - def get_instances_by_id(self, instance_ids=None): return self._create_model.get_instances_by_id(instance_ids) @@ -356,17 +339,26 @@ class PublisherController(BasePublisherController): thumbnail_path_mapping ) - def emit_card_message( - self, message, message_type=CardMessageTypes.standard - ): - self._emit_event( - "show.card.message", - { - "message": message, - "message_type": message_type - } + def get_thumbnail_temp_dir_path(self): + """Return path to directory where thumbnails can be temporary stored. + + Returns: + str: Path to a directory. + """ + + return os.path.join( + tempfile.gettempdir(), + "publisher_thumbnails", + get_process_id() ) + def clear_thumbnail_temp_dir_path(self): + """Remove content of thumbnail temp directory.""" + + dirpath = self.get_thumbnail_temp_dir_path() + if os.path.exists(dirpath): + shutil.rmtree(dirpath) + def get_creator_attribute_definitions(self, instances): """Collect creator attribute definitions for multuple instances. diff --git a/client/ayon_core/tools/publisher/models/__init__.py b/client/ayon_core/tools/publisher/models/__init__.py index 194ea944ef..bd593be29b 100644 --- a/client/ayon_core/tools/publisher/models/__init__.py +++ b/client/ayon_core/tools/publisher/models/__init__.py @@ -1,9 +1,10 @@ -from .create import CreateModel +from .create import CreateModel, CreatorItem from .publish import PublishModel __all__ = ( "CreateModel", + "CreatorItem", "PublishModel", ) diff --git a/client/ayon_core/tools/publisher/models/create.py b/client/ayon_core/tools/publisher/models/create.py index fc561cf6d0..6da3a51a31 100644 --- a/client/ayon_core/tools/publisher/models/create.py +++ b/client/ayon_core/tools/publisher/models/create.py @@ -23,7 +23,7 @@ from ayon_core.pipeline.create.context import ( ConvertorItem, ) from ayon_core.tools.publisher.abstract import ( - AbstractPublisherController, + AbstractPublisherBackend, CardMessageTypes, ) CREATE_EVENT_SOURCE = "publisher.create.model" @@ -191,9 +191,9 @@ class CreatorItem: class CreateModel: - def __init__(self, controller: AbstractPublisherController): + def __init__(self, controller: AbstractPublisherBackend): self._log = None - self._controller = controller + self._controller: AbstractPublisherBackend = controller self._create_context = CreateContext( controller.get_host(), diff --git a/client/ayon_core/tools/publisher/models/publish.py b/client/ayon_core/tools/publisher/models/publish.py index 7f32e8c057..da7b64ceae 100644 --- a/client/ayon_core/tools/publisher/models/publish.py +++ b/client/ayon_core/tools/publisher/models/publish.py @@ -16,7 +16,7 @@ from ayon_core.pipeline import ( ) from ayon_core.pipeline.plugin_discover import DiscoverResult from ayon_core.pipeline.publish import get_publish_instance_label -from ayon_core.tools.publisher.abstract import AbstractPublisherController +from ayon_core.tools.publisher.abstract import AbstractPublisherBackend PUBLISH_EVENT_SOURCE = "publisher.publish.model" # Define constant for plugin orders offset @@ -788,7 +788,7 @@ def collect_families_from_instances( class PublishModel: - def __init__(self, controller: AbstractPublisherController): + def __init__(self, controller: AbstractPublisherBackend): self._controller = controller # Publishing should stop at validation stage diff --git a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py index 5c12f0fbb0..3178f0c591 100644 --- a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py @@ -29,18 +29,21 @@ from ayon_core.tools.utils import NiceCheckbox from ayon_core.tools.utils import BaseClickableFrame from ayon_core.tools.utils.lib import html_escape + +from ayon_core.tools.publisher.abstract import AbstractPublisherFrontend +from ayon_core.tools.publisher.constants import ( + CONTEXT_ID, + CONTEXT_LABEL, + CONTEXT_GROUP, + CONVERTOR_ITEM_GROUP, +) + from .widgets import ( AbstractInstanceView, ContextWarningLabel, IconValuePixmapLabel, PublishPixmapLabel ) -from ..constants import ( - CONTEXT_ID, - CONTEXT_LABEL, - CONTEXT_GROUP, - CONVERTOR_ITEM_GROUP, -) class SelectionTypes: @@ -560,7 +563,7 @@ class InstanceCardView(AbstractInstanceView): def __init__(self, controller, parent): super(InstanceCardView, self).__init__(parent) - self._controller = controller + self._controller: AbstractPublisherFrontend = controller scroll_area = QtWidgets.QScrollArea(self) scroll_area.setWidgetResizable(True) diff --git a/client/ayon_core/tools/publisher/widgets/create_context_widgets.py b/client/ayon_core/tools/publisher/widgets/create_context_widgets.py index 6e665bc963..faf2248181 100644 --- a/client/ayon_core/tools/publisher/widgets/create_context_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/create_context_widgets.py @@ -5,6 +5,7 @@ from ayon_core.tools.utils import PlaceholderLineEdit, GoToCurrentButton from ayon_core.tools.common_models import HierarchyExpectedSelection from ayon_core.tools.utils import FoldersWidget, TasksWidget +from ayon_core.tools.publisher.abstract import AbstractPublisherFrontend class CreateSelectionModel(object): @@ -18,8 +19,8 @@ class CreateSelectionModel(object): event_source = "publisher.create.selection.model" - def __init__(self, controller): - self._controller = controller + def __init__(self, controller: "CreateHierarchyController"): + self._controller: CreateHierarchyController = controller self._project_name = None self._folder_id = None @@ -94,9 +95,9 @@ class CreateHierarchyController: controller (PublisherController): Publisher controller. """ - def __init__(self, controller): + def __init__(self, controller: AbstractPublisherFrontend): self._event_system = QueuedEventSystem() - self._controller = controller + self._controller: AbstractPublisherFrontend = controller self._selection_model = CreateSelectionModel(self) self._expected_selection = HierarchyExpectedSelection( self, handle_project=False @@ -168,10 +169,10 @@ class CreateContextWidget(QtWidgets.QWidget): folder_changed = QtCore.Signal() task_changed = QtCore.Signal() - def __init__(self, controller, parent): - super(CreateContextWidget, self).__init__(parent) + def __init__(self, controller: AbstractPublisherFrontend, parent): + super().__init__(parent) - self._controller = controller + self._controller: AbstractPublisherFrontend = controller self._enabled = True self._last_project_name = None self._last_folder_id = None diff --git a/client/ayon_core/tools/publisher/widgets/create_widget.py b/client/ayon_core/tools/publisher/widgets/create_widget.py index 72859a4ad2..af6fee533e 100644 --- a/client/ayon_core/tools/publisher/widgets/create_widget.py +++ b/client/ayon_core/tools/publisher/widgets/create_widget.py @@ -9,14 +9,8 @@ from ayon_core.pipeline.create import ( TaskNotSetError, ) -from .thumbnail_widget import ThumbnailWidget -from .widgets import ( - IconValuePixmapLabel, - CreateBtn, -) -from .create_context_widgets import CreateContextWidget -from .precreate_widget import PreCreateWidget -from ..constants import ( +from ayon_core.tools.publisher.abstract import AbstractPublisherFrontend +from ayon_core.tools.publisher.constants import ( VARIANT_TOOLTIP, PRODUCT_TYPE_ROLE, CREATOR_IDENTIFIER_ROLE, @@ -26,6 +20,14 @@ from ..constants import ( INPUTS_LAYOUT_VSPACING, ) +from .thumbnail_widget import ThumbnailWidget +from .widgets import ( + IconValuePixmapLabel, + CreateBtn, +) +from .create_context_widgets import CreateContextWidget +from .precreate_widget import PreCreateWidget + SEPARATORS = ("---separator---", "---") @@ -106,7 +108,7 @@ class CreateWidget(QtWidgets.QWidget): def __init__(self, controller, parent=None): super(CreateWidget, self).__init__(parent) - self._controller = controller + self._controller: AbstractPublisherFrontend = controller self._folder_path = None self._product_names = None diff --git a/client/ayon_core/tools/publisher/widgets/folders_dialog.py b/client/ayon_core/tools/publisher/widgets/folders_dialog.py index baf229290d..fa9a174c9c 100644 --- a/client/ayon_core/tools/publisher/widgets/folders_dialog.py +++ b/client/ayon_core/tools/publisher/widgets/folders_dialog.py @@ -2,12 +2,13 @@ from qtpy import QtWidgets from ayon_core.lib.events import QueuedEventSystem from ayon_core.tools.utils import PlaceholderLineEdit, FoldersWidget +from ayon_core.tools.publisher.abstract import AbstractPublisherFrontend class FoldersDialogController: - def __init__(self, controller): + def __init__(self, controller: AbstractPublisherFrontend): self._event_system = QueuedEventSystem() - self._controller = controller + self._controller: AbstractPublisherFrontend = controller @property def event_system(self): diff --git a/client/ayon_core/tools/publisher/widgets/help_widget.py b/client/ayon_core/tools/publisher/widgets/help_widget.py index aaff60acba..062b117220 100644 --- a/client/ayon_core/tools/publisher/widgets/help_widget.py +++ b/client/ayon_core/tools/publisher/widgets/help_widget.py @@ -5,6 +5,8 @@ except Exception: from qtpy import QtWidgets, QtCore +from ayon_core.tools.publisher.abstract import AbstractPublisherFrontend + class HelpButton(QtWidgets.QPushButton): """Button used to trigger help dialog.""" @@ -54,7 +56,9 @@ class HelpDialog(QtWidgets.QDialog): default_width = 530 default_height = 340 - def __init__(self, controller, parent): + def __init__( + self, controller: AbstractPublisherFrontend, parent: QtWidgets.QWidget + ): super(HelpDialog, self).__init__(parent) self.setWindowTitle("Help dialog") @@ -68,7 +72,7 @@ class HelpDialog(QtWidgets.QDialog): "show.detailed.help", self._on_help_request ) - self._controller = controller + self._controller: AbstractPublisherFrontend = controller self._help_content = help_content diff --git a/client/ayon_core/tools/publisher/widgets/list_view_widgets.py b/client/ayon_core/tools/publisher/widgets/list_view_widgets.py index 714edb5eef..74fd3265cc 100644 --- a/client/ayon_core/tools/publisher/widgets/list_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/list_view_widgets.py @@ -29,8 +29,9 @@ from qtpy import QtWidgets, QtCore, QtGui from ayon_core.style import get_objected_colors from ayon_core.tools.utils import NiceCheckbox from ayon_core.tools.utils.lib import html_escape, checkstate_int_to_enum -from .widgets import AbstractInstanceView -from ..constants import ( + +from ayon_core.tools.publisher.abstract import AbstractPublisherFrontend +from ayon_core.tools.publisher.constants import ( INSTANCE_ID_ROLE, SORT_VALUE_ROLE, IS_GROUP_ROLE, @@ -41,6 +42,8 @@ from ..constants import ( CONVERTOR_ITEM_GROUP, ) +from .widgets import AbstractInstanceView + class ListItemDelegate(QtWidgets.QStyledItemDelegate): """Generic delegate for instance group. @@ -442,10 +445,12 @@ class InstanceListView(AbstractInstanceView): double_clicked = QtCore.Signal() - def __init__(self, controller, parent): - super(InstanceListView, self).__init__(parent) + def __init__( + self, controller: AbstractPublisherFrontend, parent: QtWidgets.QWidget + ): + super().__init__(parent) - self._controller = controller + self._controller: AbstractPublisherFrontend = controller instance_view = InstanceTreeView(self) instance_delegate = ListItemDelegate(instance_view) diff --git a/client/ayon_core/tools/publisher/widgets/overview_widget.py b/client/ayon_core/tools/publisher/widgets/overview_widget.py index a8eb4c7116..52a45d0881 100644 --- a/client/ayon_core/tools/publisher/widgets/overview_widget.py +++ b/client/ayon_core/tools/publisher/widgets/overview_widget.py @@ -1,7 +1,8 @@ from qtpy import QtWidgets, QtCore -from .border_label_widget import BorderedLabelWidget +from ayon_core.tools.publisher.abstract import AbstractPublisherFrontend +from .border_label_widget import BorderedLabelWidget from .card_view_widgets import InstanceCardView from .list_view_widgets import InstanceListView from .widgets import ( @@ -23,11 +24,13 @@ class OverviewWidget(QtWidgets.QFrame): anim_end_value = 200 anim_duration = 200 - def __init__(self, controller, parent): - super(OverviewWidget, self).__init__(parent) + def __init__( + self, controller: AbstractPublisherFrontend, parent: QtWidgets.QWidget + ): + super().__init__(parent) self._refreshing_instances = False - self._controller = controller + self._controller: AbstractPublisherFrontend = controller product_content_widget = QtWidgets.QWidget(self) diff --git a/client/ayon_core/tools/publisher/widgets/publish_frame.py b/client/ayon_core/tools/publisher/widgets/publish_frame.py index 264b0a2e02..4041a58ec7 100644 --- a/client/ayon_core/tools/publisher/widgets/publish_frame.py +++ b/client/ayon_core/tools/publisher/widgets/publish_frame.py @@ -1,5 +1,7 @@ from qtpy import QtWidgets, QtCore +from ayon_core.tools.publisher.abstract import AbstractPublisherFrontend + from .widgets import ( StopBtn, ResetBtn, @@ -31,8 +33,13 @@ class PublishFrame(QtWidgets.QWidget): details_page_requested = QtCore.Signal() - def __init__(self, controller, borders, parent): - super(PublishFrame, self).__init__(parent) + def __init__( + self, + controller: AbstractPublisherFrontend, + borders: int, + parent: QtWidgets.QWidget + ): + super().__init__(parent) # Bottom part of widget where process and callback buttons are showed # - QFrame used to be able set background using stylesheets easily @@ -179,7 +186,7 @@ class PublishFrame(QtWidgets.QWidget): self._shrunk_anim = shrunk_anim - self._controller = controller + self._controller: AbstractPublisherFrontend = controller self._content_frame = content_frame self._content_layout = content_layout diff --git a/client/ayon_core/tools/publisher/widgets/report_page.py b/client/ayon_core/tools/publisher/widgets/report_page.py index e8b29aefc2..896e8f93af 100644 --- a/client/ayon_core/tools/publisher/widgets/report_page.py +++ b/client/ayon_core/tools/publisher/widgets/report_page.py @@ -19,16 +19,18 @@ from ayon_core.tools.utils import ( paint_image_with_color, SeparatorWidget, ) +from ayon_core.tools.publisher.abstract import AbstractPublisherFrontend +from ayon_core.tools.publisher.constants import ( + INSTANCE_ID_ROLE, + CONTEXT_ID, + CONTEXT_LABEL, +) + from .widgets import IconValuePixmapLabel from .icons import ( get_pixmap, get_image, ) -from ..constants import ( - INSTANCE_ID_ROLE, - CONTEXT_ID, - CONTEXT_LABEL, -) LOG_DEBUG_VISIBLE = 1 << 0 LOG_INFO_VISIBLE = 1 << 1 @@ -159,8 +161,10 @@ class ValidateActionsWidget(QtWidgets.QFrame): Change actions based on selected validation error. """ - def __init__(self, controller, parent): - super(ValidateActionsWidget, self).__init__(parent) + def __init__( + self, controller: AbstractPublisherFrontend, parent: QtWidgets.QWidget + ): + super().__init__(parent) self.setAttribute(QtCore.Qt.WA_TranslucentBackground) @@ -172,7 +176,7 @@ class ValidateActionsWidget(QtWidgets.QFrame): layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(content_widget) - self._controller = controller + self._controller: AbstractPublisherFrontend = controller self._content_widget = content_widget self._content_layout = content_layout @@ -874,8 +878,10 @@ class PublishInstancesViewWidget(QtWidgets.QWidget): _min_width_measure_string = 24 * "O" selection_changed = QtCore.Signal() - def __init__(self, controller, parent): - super(PublishInstancesViewWidget, self).__init__(parent) + def __init__( + self, controller: AbstractPublisherFrontend, parent: QtWidgets.QWidget + ): + super().__init__(parent) scroll_area = VerticalScrollArea(self) scroll_area.setWidgetResizable(True) @@ -898,7 +904,7 @@ class PublishInstancesViewWidget(QtWidgets.QWidget): layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(scroll_area, 1) - self._controller = controller + self._controller: AbstractPublisherFrontend = controller self._scroll_area = scroll_area self._instance_view = instance_view self._instance_layout = instance_layout @@ -1357,7 +1363,7 @@ class InstancesLogsView(QtWidgets.QFrame): self._is_showed = False def closeEvent(self, event): - super(InstancesLogsView, self).closeEvent(event) + super().closeEvent(event) self._is_showed = False def _update_instances(self): @@ -1455,8 +1461,10 @@ class CrashWidget(QtWidgets.QWidget): actions. """ - def __init__(self, controller, parent): - super(CrashWidget, self).__init__(parent) + def __init__( + self, controller: AbstractPublisherFrontend, parent: QtWidgets.QWidget + ): + super().__init__(parent) main_label = QtWidgets.QLabel("This is not your fault", self) main_label.setAlignment(QtCore.Qt.AlignCenter) @@ -1498,7 +1506,7 @@ class CrashWidget(QtWidgets.QWidget): copy_clipboard_btn.clicked.connect(self._on_copy_to_clipboard) save_to_disk_btn.clicked.connect(self._on_save_to_disk_click) - self._controller = controller + self._controller: AbstractPublisherFrontend = controller def _on_copy_to_clipboard(self): self._controller.emit_event( @@ -1623,8 +1631,10 @@ class ReportsWidget(QtWidgets.QWidget): └──────┴─────────┴─────────┘ """ - def __init__(self, controller, parent): - super(ReportsWidget, self).__init__(parent) + def __init__( + self, controller: AbstractPublisherFrontend, parent: QtWidgets.QWidget + ): + super().__init__(parent) # Instances view views_widget = QtWidgets.QWidget(self) @@ -1708,7 +1718,7 @@ class ReportsWidget(QtWidgets.QWidget): self._detail_input_scroll = detail_input_scroll self._crash_widget = crash_widget - self._controller = controller + self._controller: AbstractPublisherFrontend = controller self._validation_errors_by_id = {} @@ -1818,8 +1828,10 @@ class ReportPageWidget(QtWidgets.QFrame): and validation error detail with possible actions (repair). """ - def __init__(self, controller, parent): - super(ReportPageWidget, self).__init__(parent) + def __init__( + self, controller: AbstractPublisherFrontend, parent: QtWidgets.QWidget + ): + super().__init__(parent) header_label = QtWidgets.QLabel(self) header_label.setAlignment(QtCore.Qt.AlignCenter) @@ -1845,7 +1857,7 @@ class ReportPageWidget(QtWidgets.QFrame): self._header_label = header_label self._publish_instances_widget = publish_instances_widget - self._controller = controller + self._controller: AbstractPublisherFrontend = controller def _update_label(self): if not self._controller.publish_has_started(): diff --git a/client/ayon_core/tools/publisher/widgets/tasks_model.py b/client/ayon_core/tools/publisher/widgets/tasks_model.py index a7db953821..16a4111f59 100644 --- a/client/ayon_core/tools/publisher/widgets/tasks_model.py +++ b/client/ayon_core/tools/publisher/widgets/tasks_model.py @@ -1,7 +1,10 @@ +from typing import Optional + from qtpy import QtCore, QtGui from ayon_core.style import get_default_entity_icon_color from ayon_core.tools.utils import get_qt_icon +from ayon_core.tools.publisher.abstract import AbstractPublisherFrontend TASK_NAME_ROLE = QtCore.Qt.UserRole + 1 TASK_TYPE_ROLE = QtCore.Qt.UserRole + 2 @@ -19,14 +22,19 @@ class TasksModel(QtGui.QStandardItemModel): tasks with same names then model is empty too. Args: - controller (PublisherController): Controller which handles creation and + controller (AbstractPublisherFrontend): Controller which handles creation and publishing. + """ - def __init__(self, controller, allow_empty_task=False): - super(TasksModel, self).__init__() + def __init__( + self, + controller: AbstractPublisherFrontend, + allow_empty_task: Optional[bool] = False + ): + super().__init__() self._allow_empty_task = allow_empty_task - self._controller = controller + self._controller: AbstractPublisherFrontend = controller self._items_by_name = {} self._folder_paths = [] self._task_names_by_folder_path = {} diff --git a/client/ayon_core/tools/publisher/widgets/thumbnail_widget.py b/client/ayon_core/tools/publisher/widgets/thumbnail_widget.py index 07dc532534..de7af80d50 100644 --- a/client/ayon_core/tools/publisher/widgets/thumbnail_widget.py +++ b/client/ayon_core/tools/publisher/widgets/thumbnail_widget.py @@ -19,7 +19,10 @@ from ayon_core.tools.utils import ( paint_image_with_color, PixmapButton, ) -from ayon_core.tools.publisher.control import CardMessageTypes +from ayon_core.tools.publisher.abstract import ( + CardMessageTypes, + AbstractPublisherFrontend, +) from .icons import get_image from .screenshot_widget import capture_to_file @@ -299,7 +302,9 @@ class ThumbnailWidget(QtWidgets.QWidget): thumbnail_created = QtCore.Signal(str) thumbnail_cleared = QtCore.Signal() - def __init__(self, controller, parent): + def __init__( + self, controller: AbstractPublisherFrontend, parent: QtWidgets.QWidget + ): # Missing implementation for thumbnail # - widget kept to make a visial offset of global attr widget offset super(ThumbnailWidget, self).__init__(parent) @@ -355,7 +360,7 @@ class ThumbnailWidget(QtWidgets.QWidget): paste_btn.clicked.connect(self._on_paste_from_clipboard) browse_btn.clicked.connect(self._on_browse_clicked) - self._controller = controller + self._controller: AbstractPublisherFrontend = controller self._output_dir = controller.get_thumbnail_temp_dir_path() self._review_extensions = set(IMAGE_EXTENSIONS) | set(VIDEO_EXTENSIONS) diff --git a/client/ayon_core/tools/publisher/widgets/widgets.py b/client/ayon_core/tools/publisher/widgets/widgets.py index 4299a572b3..9e1d2d0525 100644 --- a/client/ayon_core/tools/publisher/widgets/widgets.py +++ b/client/ayon_core/tools/publisher/widgets/widgets.py @@ -10,6 +10,11 @@ from qtpy import QtWidgets, QtCore, QtGui import qtawesome from ayon_core.lib.attribute_definitions import UnknownDef +from ayon_core.style import get_objected_colors +from ayon_core.pipeline.create import ( + PRODUCT_NAME_ALLOWED_SYMBOLS, + TaskNotSetError, +) from ayon_core.tools.attribute_defs import create_widget_for_attr_def from ayon_core.tools import resources from ayon_core.tools.flickcharm import FlickCharm @@ -20,11 +25,14 @@ from ayon_core.tools.utils import ( BaseClickableFrame, set_style_property, ) -from ayon_core.style import get_objected_colors -from ayon_core.pipeline.create import ( - PRODUCT_NAME_ALLOWED_SYMBOLS, - TaskNotSetError, +from ayon_core.tools.publisher.abstract import AbstractPublisherFrontend +from ayon_core.tools.publisher.constants import ( + VARIANT_TOOLTIP, + ResetKeySequence, + INPUTS_LAYOUT_HSPACING, + INPUTS_LAYOUT_VSPACING, ) + from .thumbnail_widget import ThumbnailWidget from .folders_dialog import FoldersDialog from .tasks_model import TasksModel @@ -33,13 +41,6 @@ from .icons import ( get_icon_path ) -from ..constants import ( - VARIANT_TOOLTIP, - ResetKeySequence, - INPUTS_LAYOUT_HSPACING, - INPUTS_LAYOUT_VSPACING, -) - FA_PREFIXES = ["", "fa.", "fa5.", "fa5b.", "fa5s.", "ei.", "mdi."] @@ -363,7 +364,9 @@ class AbstractInstanceView(QtWidgets.QWidget): "{} Method 'get_selected_items' is not implemented." ).format(self.__class__.__name__)) - def set_selected_items(self, instance_ids, context_selected): + def set_selected_items( + self, instance_ids, context_selected, convertor_identifiers + ): """Change selection for instances and context. Used to applying selection from one view to other. @@ -371,8 +374,9 @@ class AbstractInstanceView(QtWidgets.QWidget): Args: instance_ids (List[str]): Selected instance ids. context_selected (bool): Context is selected. - """ + convertor_identifiers (List[str]): Selected convertor identifiers. + """ raise NotImplementedError(( "{} Method 'set_selected_items' is not implemented." ).format(self.__class__.__name__)) @@ -429,8 +433,10 @@ class FoldersFields(BaseClickableFrame): """ value_changed = QtCore.Signal() - def __init__(self, controller, parent): - super(FoldersFields, self).__init__(parent) + def __init__( + self, controller: AbstractPublisherFrontend, parent: QtWidgets.QWidget + ): + super().__init__(parent) self.setObjectName("FolderPathInputWidget") # Don't use 'self' for parent! @@ -465,7 +471,7 @@ class FoldersFields(BaseClickableFrame): icon_btn.clicked.connect(self._mouse_release_callback) dialog.finished.connect(self._on_dialog_finish) - self._controller = controller + self._controller: AbstractPublisherFrontend = controller self._dialog = dialog self._name_input = name_input self._icon_btn = icon_btn @@ -613,8 +619,10 @@ class TasksCombobox(QtWidgets.QComboBox): """ value_changed = QtCore.Signal() - def __init__(self, controller, parent): - super(TasksCombobox, self).__init__(parent) + def __init__( + self, controller: AbstractPublisherFrontend, parent: QtWidgets.QWidget + ): + super().__init__(parent) self.setObjectName("TasksCombobox") # Set empty delegate to propagate stylesheet to a combobox @@ -1095,10 +1103,12 @@ class GlobalAttrsWidget(QtWidgets.QWidget): multiselection_text = "< Multiselection >" unknown_value = "N/A" - def __init__(self, controller, parent): - super(GlobalAttrsWidget, self).__init__(parent) + def __init__( + self, controller: AbstractPublisherFrontend, parent: QtWidgets.QWidget + ): + super().__init__(parent) - self._controller = controller + self._controller: AbstractPublisherFrontend = controller self._current_instances = [] variant_input = VariantInputWidget(self) @@ -1338,8 +1348,10 @@ class CreatorAttrsWidget(QtWidgets.QWidget): widgets are merged into one (different label does not count). """ - def __init__(self, controller, parent): - super(CreatorAttrsWidget, self).__init__(parent) + def __init__( + self, controller: AbstractPublisherFrontend, parent: QtWidgets.QWidget + ): + super().__init__(parent) scroll_area = QtWidgets.QScrollArea(self) scroll_area.setWidgetResizable(True) @@ -1351,7 +1363,7 @@ class CreatorAttrsWidget(QtWidgets.QWidget): self._main_layout = main_layout - self._controller = controller + self._controller: AbstractPublisherFrontend = controller self._scroll_area = scroll_area self._attr_def_id_to_instances = {} @@ -1476,8 +1488,10 @@ class PublishPluginAttrsWidget(QtWidgets.QWidget): does not count). """ - def __init__(self, controller, parent): - super(PublishPluginAttrsWidget, self).__init__(parent) + def __init__( + self, controller: AbstractPublisherFrontend, parent: QtWidgets.QWidget + ): + super().__init__(parent) scroll_area = QtWidgets.QScrollArea(self) scroll_area.setWidgetResizable(True) @@ -1489,7 +1503,7 @@ class PublishPluginAttrsWidget(QtWidgets.QWidget): self._main_layout = main_layout - self._controller = controller + self._controller: AbstractPublisherFrontend = controller self._scroll_area = scroll_area self._attr_def_id_to_instances = {} @@ -1635,8 +1649,10 @@ class ProductAttributesWidget(QtWidgets.QWidget): instance_context_changed = QtCore.Signal() convert_requested = QtCore.Signal() - def __init__(self, controller, parent): - super(ProductAttributesWidget, self).__init__(parent) + def __init__( + self, controller: AbstractPublisherFrontend, parent: QtWidgets.QWidget + ): + super().__init__(parent) # TOP PART top_widget = QtWidgets.QWidget(self) @@ -1738,7 +1754,7 @@ class ProductAttributesWidget(QtWidgets.QWidget): "instance.thumbnail.changed", self._on_thumbnail_changed ) - self._controller = controller + self._controller: AbstractPublisherFrontend = controller self._convert_widget = convert_widget diff --git a/client/ayon_core/tools/publisher/window.py b/client/ayon_core/tools/publisher/window.py index 14938b145f..9d3a033acd 100644 --- a/client/ayon_core/tools/publisher/window.py +++ b/client/ayon_core/tools/publisher/window.py @@ -3,6 +3,8 @@ import json import time import collections import copy +from typing import Optional + from qtpy import QtWidgets, QtCore, QtGui from ayon_core import ( @@ -19,7 +21,7 @@ from ayon_core.tools.utils.lib import center_window from .constants import ResetKeySequence from .publish_report_viewer import PublishReportViewerWidget -from .control import CardMessageTypes +from .abstract import CardMessageTypes, AbstractPublisherFrontend from .control_qt import QtPublisherController from .widgets import ( OverviewWidget, @@ -48,8 +50,13 @@ class PublisherWindow(QtWidgets.QDialog): footer_border = 8 publish_footer_spacer = 2 - def __init__(self, parent=None, controller=None, reset_on_show=None): - super(PublisherWindow, self).__init__(parent) + def __init__( + self, + parent: Optional[QtWidgets.QWidget] = None, + controller: Optional[AbstractPublisherFrontend] = None, + reset_on_show: Optional[bool] = None + ): + super().__init__(parent) self.setObjectName("PublishWindow") @@ -362,7 +369,7 @@ class PublisherWindow(QtWidgets.QDialog): self._overlay_object = overlay_object - self._controller = controller + self._controller: AbstractPublisherFrontend = controller self._first_show = True self._first_reset = True @@ -386,7 +393,8 @@ class PublisherWindow(QtWidgets.QDialog): self._window_is_visible = False @property - def controller(self): + def controller(self) -> AbstractPublisherFrontend: + """Kept for compatibility with traypublisher.""" return self._controller def show_and_publish(self, comment=None): @@ -520,7 +528,7 @@ class PublisherWindow(QtWidgets.QDialog): ) if reset_match_result == QtGui.QKeySequence.ExactMatch: - if not self.controller.publish_is_running: + if not self._controller.publish_is_running: self.reset() event.accept() return @@ -643,7 +651,7 @@ class PublisherWindow(QtWidgets.QDialog): if not force and not self._is_on_details_tab(): return - report_data = self.controller.get_publish_report() + report_data = self._controller.get_publish_report() self._publish_details_widget.set_report_data(report_data) def _on_help_click(self): @@ -921,7 +929,7 @@ class PublisherWindow(QtWidgets.QDialog): def _on_instances_refresh(self): self._validate_create_instances() - context_title = self.controller.get_context_title() + context_title = self._controller.get_context_title() self.set_context_label(context_title) self._update_publish_details_widget()