diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4d66d9c..fea2a70 100755 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -33,7 +33,7 @@ repos: - id: trailing-whitespace # Leave black at the bottom so all touchups are done before it is run. - repo: https://github.com/ambv/black - rev: 19.10b0 + rev: 22.3.0 hooks: - id: black language_version: python3 diff --git a/README.md b/README.md index 6df0a85..05785a9 100755 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Python 2.6 2.7 3.7](https://img.shields.io/badge/python-2.6%20%7C%202.7%20%7C%203.7-blue.svg)](https://www.python.org/) +[![Python 2.7 3.7](https://img.shields.io/badge/python-2.7%20%7C%203.7-blue.svg)](https://www.python.org/) [![Build Status](https://secure.travis-ci.org/shotgunsoftware/tk-nuke.png?branch=master)](http://travis-ci.org/shotgunsoftware/tk-nuke) [![Build Status](https://dev.azure.com/shotgun-ecosystem/Toolkit/_apis/build/status/Engines/tk-nuke?branchName=master)](https://dev.azure.com/shotgun-ecosystem/Toolkit/_build/latest?definitionId=83&branchName=master) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) @@ -8,14 +8,14 @@ This repository is a part of the ShotGrid Pipeline Toolkit. - For more information about this app and for release notes, *see the wiki section*. -- For general information and documentation, click here: https://support.shotgunsoftware.com/entries/95441257 -- For information about ShotGrid in general, click here: http://www.shotgunsoftware.com/toolkit +- For general information and documentation, click here: https://developer.shotgridsoftware.com/d587be80/?title=Integrations+User+Guide +- For information about ShotGrid in general, click here: https://www.shotgridsoftware.com/integrations ## Using this app in your Setup All the apps that are part of our standard app suite are pushed to our App Store. This is where you typically go if you want to install an app into a project you are working on. For an overview of all the Apps and Engines in the Toolkit App Store, -click here: https://support.shotgunsoftware.com/entries/95441247. +click here: https://developer.shotgridsoftware.com/162eaa4b/?title=Pipeline+Integration+Components ## Have a Question? -Don't hesitate to contact us! You can find us on support@shotgunsoftware.com +Don't hesitate to contact us! You can find us on https://knowledge.autodesk.com/contact-support diff --git a/engine.py b/engine.py index e8282d5..2e90328 100755 --- a/engine.py +++ b/engine.py @@ -11,7 +11,6 @@ from __future__ import print_function import sgtk import nuke -import sys import os import nukescripts import logging @@ -180,10 +179,8 @@ def pre_app_init(self): self.logger.error(msg) return - # Versions > 13.1 have not yet been tested so show a message to that effect. - if nuke_version[0] > 13 or ( - nuke_version[0] == 13 and nuke_version[1] > 2 - ): + # Versions > 14.0 have not yet been tested so show a message to that effect. + if nuke_version[0] > 14 or (nuke_version[0] == 14 and nuke_version[1] > 0): # This is an untested version of Nuke. msg = ( "The SG Pipeline Toolkit has not yet been fully tested with Nuke %d.%dv%d. " @@ -264,6 +261,83 @@ def pre_app_init_nuke(self): os.environ["TANK_ENGINE"] = self.instance_name os.environ["TANK_CONTEXT"] = sgtk.context.serialize(self.context) + """ + https://jira.autodesk.com/browse/SG-25374 + Weblogin does not show up in Nuke 11 and makes Nuke 12 and 13 to crash + + To avoid Nuke crash, a monkeypatch of on_dialog_closed is required, + here the user is warned about restarted nuke is needed to continue. + """ + sgtk.authentication.sso_saml2.core.sso_saml2_core.SsoSaml2Core.on_dialog_closed = ( + self._on_dialog_closed_monkeypatch + ) + + @staticmethod + def _on_dialog_closed_monkeypatch(self, result): + """ + Called whenever the dialog is dismissed. + + This can be the result of a callback, a timeout or user interaction. + + :param result: Qt result following the closing of the dialog. + QtGui.QDialog.Accepted or QtGui.QDialog.Rejected + """ + self._logger.debug("SSO dialog closed") + # pylint: disable=invalid-name + QtGui = self._QtGui # noqa + + if self.is_handling_event(): + if result == QtGui.QDialog.Rejected and self._session.cookies != "": + # We got here because of a timeout attempting a GUI-less login. + # Let's clear the cookies, and force the use of the GUI. + self._session.cookies = "" + # Let's have another go, without any cookies this time ! + # This will force the GUI to be shown to the user. + self._logger.debug( + "Unable to login/renew claims automatically, presenting GUI " + "to user" + ) + """ + https://jira.autodesk.com/browse/SG-25374 + Weblogin does not show up in Nuke 11 and makes Nuke 12 and + 13 to crash + """ + base_dir = os.path.dirname(os.path.abspath(__file__)) + + msgbox_icon = os.path.join(base_dir, "resources", "alert_icon.png") + msgbox_parent = self._dialog + msgbox_title = "Nuke" + msgbox_text = [ + "The ShotGrid user session has expired.", + "To continue using ShotGrid in Nuke, please restart Nuke.", + ] + msgbox_buttons = self._QtGui.QMessageBox.Ok + + msgbox = self._QtGui.QMessageBox(msgbox_parent) + msgbox.setWindowTitle(msgbox_title) + msgbox.setText("\n\n".join(msgbox_text)) + msgbox.setStandardButtons(msgbox_buttons) + msgbox.setIconPixmap(self._QtGui.QPixmap(msgbox_icon)) + msgbox.activateWindow() # for Windows + msgbox.raise_() # for MacOS + msgbox.exec_() + + status = QtGui.QDialog.Rejected + self._logger.warn("Skipping web login dialog for Nuke DCC.") + self._login_status = self._login_status or status + else: + self.resolve_event() + else: + # Should we get a rejected dialog, then we have had a timeout. + if result == QtGui.QDialog.Rejected: + # @FIXME: Figure out exactly what to do when we have a timeout. + self._logger.warn( + "Our QDialog got canceled outside of an event handling" + ) + + # Clear the web page + self._view.page().mainFrame().load("about:blank") + def post_app_init(self): """ Called when all apps have initialized. @@ -309,9 +383,7 @@ def post_app_init_studio(self, menu_name="ShotGrid"): # No context switching in plugin mode. if self.in_plugin_mode: - self._context_switcher = tk_nuke.PluginStudioContextSwitcher( - self - ) + self._context_switcher = tk_nuke.PluginStudioContextSwitcher(self) else: hiero.core.events.registerInterest( "kAfterNewProjectCreated", @@ -323,9 +395,7 @@ def post_app_init_studio(self, menu_name="ShotGrid"): self._on_project_load_callback, ) - self._context_switcher = tk_nuke.ClassicStudioContextSwitcher( - self - ) + self._context_switcher = tk_nuke.ClassicStudioContextSwitcher(self) # On selection change we have to check what was selected and pre-load # the context if that environment (ie: shot_step) hasn't already been # processed. This ensure that all Nuke gizmos for the target environment @@ -423,9 +493,7 @@ def post_app_init_nuke(self, menu_name="NFA ShotGrid"): sgtk.util.append_path_to_env_var("NUKE_PATH", app_gizmo_folder) # Nuke Studio 9 really doesn't like us running commands at startup, so don't. - if not ( - nuke.env.get("NukeVersionMajor") == 9 and nuke.env.get("studio") - ): + if not (nuke.env.get("NukeVersionMajor") == 9 and nuke.env.get("studio")): self._run_commands_at_startup() @property @@ -511,10 +579,7 @@ def _run_commands_at_startup(self): else: if not setting_command_name: # Run all commands of the given app instance. - for ( - command_name, - command_function, - ) in command_dict.items(): + for (command_name, command_function) in command_dict.items(): self.logger.debug( "%s startup running app '%s' command '%s'.", self.name, @@ -667,9 +732,7 @@ def _emit_log_message(self, handler, record): ##################################################################################### # Panel Support - def show_panel( - self, panel_id, title, bundle, widget_class, *args, **kwargs - ): + def show_panel(self, panel_id, title, bundle, widget_class, *args, **kwargs): """ Shows a panel in Nuke. If the panel already exists, the previous panel is swapped out and replaced with a new one. In this case, the contents of the panel (e.g. the toolkit app) @@ -692,9 +755,7 @@ def show_panel( self.logger.info( "Panels are not supported in Hiero. Launching as a dialog..." ) - return self.show_dialog( - title, bundle, widget_class, *args, **kwargs - ) + return self.show_dialog(title, bundle, widget_class, *args, **kwargs) # Note! Not using the import_module call as this confuses nuke's callback system import tk_nuke_qt @@ -704,9 +765,7 @@ def show_panel( bundle, title, panel_id, widget_class, *args, **kwargs ) - self.logger.debug( - "Showing pane %s - %s from %s", panel_id, title, bundle.name - ) + self.logger.debug("Showing pane %s - %s from %s", panel_id, title, bundle.name) if hasattr(sgtk, "_callback_from_non_pane_menu"): self.logger.debug("Looking for a pane.") @@ -731,9 +790,7 @@ def show_panel( existing_pane = None for tab_name in built_in_tabs: - self.logger.debug( - "Parenting panel - looking for %s tab...", tab_name - ) + self.logger.debug("Parenting panel - looking for %s tab...", tab_name) existing_pane = nuke.getPaneFor(tab_name) if existing_pane: break @@ -885,15 +942,14 @@ def _handle_studio_selection_change(self, event): # If we've already seen this file selected before, or if it's # not a .nk file, then we don't need to do anything. - if ( - file_path not in self._processed_paths - and file_path.endswith(".nk") + if file_path not in self._processed_paths and file_path.endswith( + ".nk" ): self._processed_paths.append(file_path) self._context_change_menu_rebuild = False current_context = self.context - target_context = ( - self._context_switcher.get_new_context(file_path) + target_context = self._context_switcher.get_new_context( + file_path ) if target_context: @@ -908,9 +964,7 @@ def _handle_studio_selection_change(self, event): if env_name not in self._processed_environments: self._processed_environments.append(env_name) - self._context_switcher.change_context( - target_context - ) + self._context_switcher.change_context(target_context) except Exception as e: # If anything went wrong, we can just let the finally block # run, which will put things back to the way they were. @@ -958,9 +1012,7 @@ def _on_project_load_callback(self, event): if new_context != self.context: sgtk.platform.change_context(new_context) except Exception: - self.logger.debug( - "Unable to determine context for file: %s", script_path - ) + self.logger.debug("Unable to determine context for file: %s", script_path) def _define_qt_base(self): """ @@ -1015,13 +1067,7 @@ def __setup_favorite_dirs(self): ) # Ensure old favorites we used to use are removed. - supported_entity_types = [ - "Shot", - "Sequence", - "Scene", - "Asset", - "Project", - ] + supported_entity_types = ["Shot", "Sequence", "Scene", "Asset", "Project"] for x in supported_entity_types: nuke.removeFavoriteDir("Tank Current %s" % x) nuke.removeFavoriteDir("Tank Current Work") @@ -1057,9 +1103,7 @@ def __setup_favorite_dirs(self): # Remove old directory nuke.removeFavoriteDir(favorite["display_name"]) try: - template = self.get_template_by_name( - favorite["template_directory"] - ) + template = self.get_template_by_name(favorite["template_directory"]) fields = self.context.as_template_fields(template) path = template.apply_fields(fields) except Exception as e: diff --git a/hooks/tk-multi-publish2/basic/nuke_publish_script.py b/hooks/tk-multi-publish2/basic/nuke_publish_script.py index 751f7b0..8774880 100755 --- a/hooks/tk-multi-publish2/basic/nuke_publish_script.py +++ b/hooks/tk-multi-publish2/basic/nuke_publish_script.py @@ -37,7 +37,7 @@ def description(self): contain simple html for formatting. """ - loader_url = "https://support.shotgunsoftware.com/hc/en-us/articles/219033078" + loader_url = "https://developer.shotgridsoftware.com/a4c0a4f1/?title=Loader" return """ Publishes the file to ShotGrid. A Publish entry will be diff --git a/hooks/tk-multi-publish2/basic/nuke_start_version_control.py b/hooks/tk-multi-publish2/basic/nuke_start_version_control.py index a331365..b9f9431 100755 --- a/hooks/tk-multi-publish2/basic/nuke_start_version_control.py +++ b/hooks/tk-multi-publish2/basic/nuke_start_version_control.py @@ -313,6 +313,6 @@ def _get_version_docs_action(): "action_open_url": { "label": "Version Docs", "tooltip": "Show docs for version formats", - "url": "https://support.shotgunsoftware.com/hc/en-us/articles/115000068574-User-Guide-WIP-#What%20happens%20when%20you%20publish", + "url": "https://help.autodesk.com/view/SGSUB/ENU/?guid=SG_Supervisor_Artist_sa_integrations_sa_integrations_user_guide_html", } } diff --git a/hooks/tk-multi-publish2/basic/nuke_update_flame_clip.py b/hooks/tk-multi-publish2/basic/nuke_update_flame_clip.py index 59a8c58..945923b 100755 --- a/hooks/tk-multi-publish2/basic/nuke_update_flame_clip.py +++ b/hooks/tk-multi-publish2/basic/nuke_update_flame_clip.py @@ -638,7 +638,10 @@ def _update_flame_clip(self, item): # # date_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") - formatted_name = _generate_flame_clip_name(item, render_path_fields,) + formatted_name = _generate_flame_clip_name( + item, + render_path_fields, + ) # version_node = xml.createElement("version") @@ -775,7 +778,10 @@ def _get_flame_frame_spec_from_path(path): # We need to get all files that match the pattern from disk so that we # can determine what the min and max frame number is. We replace the # frame number or token with a * wildcard. - glob_path = "%s%s" % (re.sub(match.group(2), "*", root), ext,) + glob_path = "%s%s" % ( + re.sub(match.group(2), "*", root), + ext, + ) files = glob.glob(glob_path) # Our pattern from above matches against the file root, so we need @@ -850,7 +856,10 @@ def _generate_flame_clip_name(item, publish_fields): # foo1234 -> foo # foo_1234 -> foo default_name = re.sub(r"[._]*\d+$", "", default_name) - rp_name = publish_fields.get("name", default_name,) + rp_name = publish_fields.get( + "name", + default_name, + ) rp_channel = publish_fields.get("channel") if rp_name and rp_channel: diff --git a/hooks/tk-multi-publish2/basic/nukestudio_publish_project.py b/hooks/tk-multi-publish2/basic/nukestudio_publish_project.py index f885d58..cfb98ba 100755 --- a/hooks/tk-multi-publish2/basic/nukestudio_publish_project.py +++ b/hooks/tk-multi-publish2/basic/nukestudio_publish_project.py @@ -37,7 +37,7 @@ def description(self): contain simple html for formatting. """ - loader_url = "https://support.shotgunsoftware.com/hc/en-us/articles/219033078" + loader_url = "https://developer.shotgridsoftware.com/a4c0a4f1/?title=Loader" return """ Publishes the file to ShotGrid. A Publish entry will be diff --git a/hooks/tk-multi-publish2/basic/nukestudio_start_version_control.py b/hooks/tk-multi-publish2/basic/nukestudio_start_version_control.py index 66b99d4..bb02f33 100755 --- a/hooks/tk-multi-publish2/basic/nukestudio_start_version_control.py +++ b/hooks/tk-multi-publish2/basic/nukestudio_start_version_control.py @@ -312,7 +312,7 @@ def _get_version_docs_action(): "action_open_url": { "label": "Version Docs", "tooltip": "Show docs for version formats", - "url": "https://support.shotgunsoftware.com/hc/en-us/articles/115000068574-User-Guide-WIP-#What%20happens%20when%20you%20publish", + "url": "https://help.autodesk.com/view/SGSUB/ENU/?guid=SG_Supervisor_Artist_sa_integrations_sa_integrations_user_guide_html", } } diff --git a/hooks/tk-multi-publish2/basic/submit_for_review.py b/hooks/tk-multi-publish2/basic/submit_for_review.py index 3a1296f..8ca6f3f 100755 --- a/hooks/tk-multi-publish2/basic/submit_for_review.py +++ b/hooks/tk-multi-publish2/basic/submit_for_review.py @@ -46,7 +46,7 @@ def description(self): contain simple html for formatting. """ - review_url = "https://support.ShotGridsoftware.com/hc/en-us/articles/114094032014-The-review-workflow" + review_url = "https://help.autodesk.com/view/SGSUB/ENU/?guid=SG_Supervisor_Artist_sa_review_approval_sa_review_workflow_html" return """

Submits a movie file to ShotGrid for review. An entry will be diff --git a/hooks/tk-multi-publish2/icons/flame.png b/hooks/tk-multi-publish2/icons/flame.png index a13a565..b96a434 100755 Binary files a/hooks/tk-multi-publish2/icons/flame.png and b/hooks/tk-multi-publish2/icons/flame.png differ diff --git a/info.yml b/info.yml index 685e272..fd0e7f7 100755 --- a/info.yml +++ b/info.yml @@ -21,9 +21,9 @@ configuration: launch_builtin_plugins: type: list - description: Comma-separated list of tk-maya plugins to load when launching Maya. Use + description: Comma-separated list of tk-nuke plugins to load when launching Nuke. Use of this feature disables the classic mechanism for bootstrapping Toolkit - when Maya is launched. + when Nuke is launched. allows_empty: True default_value: [] values: @@ -148,7 +148,7 @@ configuration: type: int description: "Specify the minimum Application major version that will prompt a warning if it isn't yet fully supported and tested with Toolkit. To disable the warning - dialog for the version you are testing, it is recomended that you set this + dialog for the version you are testing, it is recommended that you set this value to the current major version + 1." default_value: 10 diff --git a/plugins/basic/info.yml b/plugins/basic/info.yml index d246c89..fdc367b 100755 --- a/plugins/basic/info.yml +++ b/plugins/basic/info.yml @@ -12,11 +12,11 @@ base_configuration: # The default configuration that the plugin should use. # For documentation and details, see - # http://developer.shotgunsoftware.com/tk-core/bootstrap.html#sgtk.bootstrap.ToolkitManager.base_configuration + # http://developer.shotgridsoftware.com/tk-core/bootstrap.html#sgtk.bootstrap.ToolkitManager.base_configuration # # This is expressed in the form of a Toolkit Descriptor. For more # information about Toolkit descriptors, see - # http://developer.shotgunsoftware.com/tk-core/descriptor.html + # http://developer.shotgridsoftware.com/tk-core/descriptor.html # # If your descriptor supports a version token and you want it to # always use the latest version available, simply omit the version token. @@ -25,7 +25,7 @@ base_configuration: # The Plugin Id helps uniquely identify this plugin and can be # used to override and customize it. For more information, see -# http://developer.shotgunsoftware.com/tk-core/bootstrap.html#sgtk.bootstrap.ToolkitManager.plugin_id +# http://developer.shotgridsoftware.com/tk-core/bootstrap.html#sgtk.bootstrap.ToolkitManager.plugin_id # # When the plugin is built, this file will be converted into a manifest.py file # and located in a python module named based on the plugin id in order to ensure diff --git a/python/tk_nuke/context.py b/python/tk_nuke/context.py index 8c2b8d3..e068b11 100755 --- a/python/tk_nuke/context.py +++ b/python/tk_nuke/context.py @@ -189,7 +189,10 @@ def _get_context_from_script(self, script): """ tk = tank.tank_from_path(script) - context = tk.context_from_path(script, previous_context=self.engine.context,) + context = tk.context_from_path( + script, + previous_context=self.engine.context, + ) if context.project is None: raise tank.TankError( @@ -233,7 +236,8 @@ def _on_save_callback(self): # Extract a new context based on the file and change to that # context. new_context = tk.context_from_path( - file_name, previous_context=self.context, + file_name, + previous_context=self.context, ) self.change_context(new_context) @@ -270,7 +274,8 @@ def _startup_node_callback(self): return new_ctx = tk.context_from_path( - file_name, previous_context=self.context, + file_name, + previous_context=self.context, ) # Now change the context for the engine and apps. @@ -346,7 +351,8 @@ def register_events(self, reregister=False): # Event for context switching from Hiero to Nuke. hiero.core.events.registerInterest( - hiero.core.events.EventType.kContextChanged, self._eventHandler, + hiero.core.events.EventType.kContextChanged, + self._eventHandler, ) for func_desc in self._event_desc: @@ -381,7 +387,8 @@ def unregister_events(self, only=None): import hiero.core hiero.core.events.unregisterInterest( - hiero.core.events.EventType.kContextChanged, self._eventHandler, + hiero.core.events.EventType.kContextChanged, + self._eventHandler, ) func_descs = only or self._event_desc diff --git a/python/tk_nuke/menu_generation.py b/python/tk_nuke/menu_generation.py index 5d87cfe..c159c70 100755 --- a/python/tk_nuke/menu_generation.py +++ b/python/tk_nuke/menu_generation.py @@ -251,16 +251,19 @@ def _create_hiero_menu(self, add_commands=True, commands=None): # Register for the interesting events. hiero.core.events.registerInterest( - "kShowContextMenu/kBin", self.eventHandler, + "kShowContextMenu/kBin", + self.eventHandler, ) hiero.core.events.registerInterest( - "kShowContextMenu/kTimeline", self.eventHandler, + "kShowContextMenu/kTimeline", + self.eventHandler, ) # Note that the kViewer works differently than the other things # (returns a hiero.ui.Viewer object: http://docs.thefoundry.co.uk/hiero/10/hieropythondevguide/api/api_ui.html#hiero.ui.Viewer) # so we cannot support this easily using the same principles as for the other things. hiero.core.events.registerInterest( - "kShowContextMenu/kSpreadsheet", self.eventHandler, + "kShowContextMenu/kSpreadsheet", + self.eventHandler, ) self._menu_handle.addSeparator() @@ -310,16 +313,19 @@ def destroy_menu(self): # Register for the interesting events. hiero.core.events.unregisterInterest( - "kShowContextMenu/kBin", self.eventHandler, + "kShowContextMenu/kBin", + self.eventHandler, ) hiero.core.events.unregisterInterest( - "kShowContextMenu/kTimeline", self.eventHandler, + "kShowContextMenu/kTimeline", + self.eventHandler, ) # Note that the kViewer works differently than the other things # (returns a hiero.ui.Viewer object: http://docs.thefoundry.co.uk/hiero/10/hieropythondevguide/api/api_ui.html#hiero.ui.Viewer) # so we cannot support this easily using the same principles as for the other things. hiero.core.events.unregisterInterest( - "kShowContextMenu/kSpreadsheet", self.eventHandler, + "kShowContextMenu/kSpreadsheet", + self.eventHandler, ) def eventHandler(self, event): @@ -494,7 +500,9 @@ def create_disabled_menu(self, cmd_name, msg): callback = lambda m=msg: nuke.message(m) cmd = HieroAppCommand( - self.engine, cmd_name, dict(properties=dict(), callback=callback), + self.engine, + cmd_name, + dict(properties=dict(), callback=callback), ) cmd.add_command_to_menu(self._menu_handle, icon=self._shotgun_logo_blue) @@ -612,7 +620,8 @@ def create_menu(self, add_commands=True): if cmd.type == "panel": # First make sure the Shotgun pane menu exists. pane_menu = nuke.menu("Pane").addMenu( - "ShotGrid", icon=self._shotgun_logo, + "ShotGrid", + icon=self._shotgun_logo, ) # Now set up the callback. cmd.add_command_to_pane_menu(pane_menu) @@ -634,7 +643,9 @@ def create_disabled_menu(self, cmd_name, msg): callback = lambda m=msg: nuke.message(m) cmd = NukeAppCommand( - self.engine, cmd_name, dict(properties=dict(), callback=callback), + self.engine, + cmd_name, + dict(properties=dict(), callback=callback), ) cmd.add_command_to_menu(self._menu_handle, icon=self._shotgun_logo_blue) diff --git a/python/tk_nuke_qt/panels.py b/python/tk_nuke_qt/panels.py index cc525b6..be09fc4 100755 --- a/python/tk_nuke_qt/panels.py +++ b/python/tk_nuke_qt/panels.py @@ -51,7 +51,12 @@ def __init__(self, bundle, dialog_name, panel_id, widget_class, *args, **kwargs) # we cannot pass parameters to the constructor of our wrapper class # directly, so instead pass them via a special class method ToolkitWidgetWrapper.set_init_parameters( - widget_class, panel_id, bundle, self, args, kwargs, + widget_class, + panel_id, + bundle, + self, + args, + kwargs, ) # Run parent constructor diff --git a/resources/alert_icon.png b/resources/alert_icon.png new file mode 100644 index 0000000..8b9dd05 Binary files /dev/null and b/resources/alert_icon.png differ diff --git a/resources/sg_logo_80px.png b/resources/sg_logo_80px.png new file mode 100644 index 0000000..f4a3629 Binary files /dev/null and b/resources/sg_logo_80px.png differ diff --git a/resources/sg_logo_blue_32px.png b/resources/sg_logo_blue_32px.png new file mode 100644 index 0000000..c404229 Binary files /dev/null and b/resources/sg_logo_blue_32px.png differ diff --git a/resources/sg_logo_dark_80px.png b/resources/sg_logo_dark_80px.png new file mode 100644 index 0000000..68cec57 Binary files /dev/null and b/resources/sg_logo_dark_80px.png differ diff --git a/tests/test_startup.py b/tests/test_startup.py index d84f5b1..119a505 100755 --- a/tests/test_startup.py +++ b/tests/test_startup.py @@ -11,6 +11,7 @@ from __future__ import with_statement from __future__ import print_function import os +import sys from tank_test.tank_test_base import TankTestBase from tank_test.tank_test_base import setUpModule # noqa @@ -151,14 +152,37 @@ def _recursive_split(self, path): directory, basename = os.path.split(path) return self._recursive_split(directory) + [basename] + def _glob_wrapper39(self, directory, dironly): + """ + This is a mocked implementation of glob._iterdir for Python >= 3.9. + This method fakes a folder hierarchy. + """ + tokens = self._recursive_split(directory) + # Start at the root of the mocked file system + current_depth = self._get_os_neutral_hierarchy() + for t in tokens: + # Unit test should not be asking for folders outside of the DCC hierarchy. + self.assertIn(t, current_depth) + # Remember where we are in the current hierarchy. + current_depth = current_depth[t] + current_depth_gen = (file for file in current_depth) + + # We've reached the folder we wanted, build a list. + # We're using dicts for intermediary folders and lists for leaf folders so iterate + # on the items to get all the names. + return current_depth_gen + def _glob_wrapper(self, directory, dironly): """ This is a mocked implementation of glob._iterdir. This method fakes a folder hierarchy. """ + tokens = self._recursive_split(directory) # Start at the root of the mocked file system current_depth = self._get_os_neutral_hierarchy() + # Ensure we are getting back the right variations. + for t in tokens: # Unit test should not be asking for folders outside of the DCC hierarchy. self.assertIn(t, current_depth) @@ -232,9 +256,15 @@ def _mock_folder_listing(self): if "TK_NO_FOLDER_MOCKING" not in os.environ: # In Python 3 glob doesn't use os.listdir to help iterate over the folders # It uses it's own _iterdir method, which still produces the same output. - if six.PY3: + + if sys.version_info[0:2] >= (3, 9): + with mock.patch("glob._iterdir", wraps=self._glob_wrapper39): + yield + + elif six.PY3: with mock.patch("glob._iterdir", wraps=self._glob_wrapper): yield + else: with mock.patch("os.listdir", wraps=self._os_listdir_wrapper): yield @@ -256,7 +286,8 @@ def _get_os_neutral_hierarchy(cls): return cls._mac_mock_hierarchy def _get_plugin_environment( - self, dcc_path, + self, + dcc_path, ): """ Returns the expected environment variables dictionary for a plugin.