-
Notifications
You must be signed in to change notification settings - Fork 62
Expand file tree
/
Copy pathpublish_plugin_instance.py
More file actions
369 lines (296 loc) · 12 KB
/
Copy pathpublish_plugin_instance.py
File metadata and controls
369 lines (296 loc) · 12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
# Copyright (c) 2018 Shotgun Software Inc.
#
# CONFIDENTIAL AND PROPRIETARY
#
# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit
# Source Code License included in this distribution package. See LICENSE.
# By accessing, using, copying or modifying this work you indicate your
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
from contextlib import contextmanager
import traceback
try:
# In Python 3 the interface changed, getargspec is now deprecated.
# getfullargspec was deprecated in 3.5, but that was reversed in 3.6.
from inspect import getfullargspec as getargspec
except ImportError:
# Fallback for Python 2
from inspect import getargspec
import sgtk
from .instance_base import PluginInstanceBase
logger = sgtk.platform.get_logger(__name__)
class PublishPluginInstance(PluginInstanceBase):
"""
Class that wraps around a publishing plugin hook
Each plugin object reflects an instance in the app configuration.
"""
def __init__(self, name, path, settings, publish_logger=None):
"""
:param name: Name to be used for this plugin instance
:param path: Path to publish plugin hook
:param settings: Dictionary of plugin-specific settings
:param publish_logger: a logger object that will be used by the hook
"""
# all plugins need a hook and a name
self._name = name
self._icon_pixmap = None
super(PublishPluginInstance, self).__init__(path, settings, publish_logger)
def _create_hook_instance(self, path):
"""
Create the plugin's hook instance.
Injects the plugin base hook class in order to provide a default
implementation.
"""
bundle = sgtk.platform.current_bundle()
hook = bundle.create_hook_instance(
path, base_class=bundle.base_hooks.PublishPlugin
)
hook.id = path
return hook
@property
def name(self):
"""
The name of this publish plugin instance
"""
return self._name
@property
def plugin_name(self):
"""
The name of the publish plugin itself.
Always a string.
"""
value = None
try:
value = self._hook_instance.name
except AttributeError:
pass
return value or "Untitled Integration."
@property
def description(self):
"""
The description of the publish plugin.
Always a string.
"""
value = None
try:
value = self._hook_instance.description
except AttributeError:
pass
return value or "No detailed description provided."
@property
def icon(self):
"""
Returns the icon for this plugin instance.
.. warning:: This property will return ``None`` when run without a UI
present
"""
# nothing to do if running without a UI
if not sgtk.platform.current_engine().has_ui:
return None
if not self._icon_pixmap:
self._icon_pixmap = self._load_plugin_icon()
return self._icon_pixmap
@property
def item_filters(self):
"""
The item filters defined by this plugin
or [] if none have been defined.
"""
try:
return self._hook_instance.item_filters
except AttributeError:
return []
@property
def has_custom_ui(self):
"""
Checks if a plugin has a custom widget.
:returns: ``True`` if the plugin supports ``create_settings_widget``,
``get_ui_settings`` and ``set_ui_settings``,``False`` otherwise.
"""
return all(
hasattr(self._hook_instance, attr)
for attr in ["create_settings_widget", "get_ui_settings", "set_ui_settings"]
)
@property
def settings(self):
"""
returns a dict of resolved raw settings given the current state
"""
return self._settings
def run_accept(self, item):
"""
Executes the hook accept method for the given item
:param item: Item to analyze
:returns: dictionary with boolean keys accepted/visible/enabled/checked
"""
try:
return self._hook_instance.accept(self.settings, item)
except Exception:
error_msg = traceback.format_exc()
self._logger.error(
"Error running accept for %s" % self,
extra=_get_error_extra_info(error_msg),
)
return {"accepted": False}
finally:
if sgtk.platform.current_engine().has_ui:
from sgtk.platform.qt import QtCore
QtCore.QCoreApplication.processEvents()
def run_validate(self, settings, item):
"""
Executes the validation logic for this plugin instance.
:param settings: Dictionary of settings
:param item: Item to analyze
:return: True if validation passed, False otherwise.
"""
with self._handle_plugin_error(None, "Error Validating: %s"):
status = self._hook_instance.validate(settings, item)
# check that we are not trying to publish to a site level context
if item.context.project is None:
status = False
self.logger.error("Please link '%s' to a SG object and task!" % item.name)
if status:
self.logger.debug("Validation successful!")
else:
self.logger.error("Validation failed.")
return status
def run_publish(self, settings, item):
"""
Executes the publish logic for this plugin instance.
:param settings: Dictionary of settings
:param item: Item to analyze
"""
with self._handle_plugin_error("Publish complete!", "Error publishing: %s"):
self._hook_instance.publish(settings, item)
def run_finalize(self, settings, item):
"""
Executes the finalize logic for this plugin instance.
:param settings: Dictionary of settings
:param item: Item to analyze
"""
with self._handle_plugin_error("Finalize complete!", "Error finalizing: %s"):
self._hook_instance.finalize(settings, item)
############################################################################
# ui methods
def run_create_settings_widget(self, parent, items):
"""
Creates a custom widget to edit a plugin's settings.
.. note:: This method is a no-op if running without a UI present
:param parent: Parent widget
:type parent: :class:`QtGui.QWidget`
:param items: A list of PublishItems the selected tasks are parented to.
"""
# nothing to do if running without a UI
if not sgtk.platform.current_engine().has_ui:
return None
with self._handle_plugin_error(None, "Error laying out widgets: %s"):
if len(getargspec(self._hook_instance.create_settings_widget).args) == 3:
return self._hook_instance.create_settings_widget(parent, items)
else:
# Items is a newer attribute, which an older version of the hook
# might not implement, so fallback to passing just the parent.
return self._hook_instance.create_settings_widget(parent)
def run_get_ui_settings(self, parent, items):
"""
Retrieves the settings from the custom UI.
.. note:: This method is a no-op if running without a UI present
:param parent: Parent widget
:type parent: :class:`QtGui.QWidget`
"""
# nothing to do if running without a UI
if not sgtk.platform.current_engine().has_ui:
return None
with self._handle_plugin_error(None, "Error reading settings from UI: %s"):
if len(getargspec(self._hook_instance.get_ui_settings).args) == 3:
return self._hook_instance.get_ui_settings(parent, items)
else:
# Items is a newer attribute, which an older version of the hook
# might not implement, so fallback to passing just the parent.
return self._hook_instance.get_ui_settings(parent)
def run_set_ui_settings(self, parent, settings, items):
"""
Provides a list of settings from the custom UI. It is the responsibility of the UI
handle different values for the same setting.
.. note:: This method is a no-op if running without a UI present
:param parent: Parent widget
:type parent: :class:`QtGui.QWidget`
:param settings: List of dictionary of settings as python literals.
:param items: A list of PublishItems the selected tasks are parented to.
"""
# nothing to do if running without a UI
if not sgtk.platform.current_engine().has_ui:
return None
with self._handle_plugin_error(None, "Error writing settings to UI: %s"):
if len(getargspec(self._hook_instance.set_ui_settings).args) == 4:
self._hook_instance.set_ui_settings(parent, settings, items)
else:
# Items is a newer attribute, which an older version of the hook
# might not implement, so fallback to passing just the parent and settings.
self._hook_instance.set_ui_settings(parent, settings)
@contextmanager
def _handle_plugin_error(self, success_msg, error_msg):
"""
Creates a scope that will properly handle any error raised by the plugin
while the scope is executed.
.. note::
Any exception raised by the plugin is bubbled up to the caller.
:param str success_msg: Message to be displayed if there is no error.
:param str error_msg: Message to be displayed if there is an error.
"""
try:
# Execute's the code inside the with statement. Any errors will be
# caught and logged and the events will be processed
yield
except Exception as e:
exception_msg = traceback.format_exc()
self._logger.error(
error_msg % (e,), extra=_get_error_extra_info(exception_msg)
)
raise
else:
if success_msg:
self._logger.debug(success_msg)
finally:
if sgtk.platform.current_engine().has_ui:
# If we have a UI process the events so that the UI can update mid operation.
from sgtk.platform.qt import QtCore
QtCore.QCoreApplication.processEvents()
def _load_plugin_icon(self):
"""
Loads the icon defined by the plugin's hook.
:returns: QPixmap or None if not found
"""
# defer import until needed and to avoid issues when running without UI
from sgtk.platform.qt import QtGui
# load plugin icon
pixmap = None
try:
icon_path = self._hook_instance.icon
if icon_path:
try:
pixmap = QtGui.QPixmap(icon_path)
except Exception as e:
self._logger.warning(
"%r: Could not load icon '%s': %s" % (self, icon_path, e)
)
except AttributeError:
# plugin does not have an icon
pass
# load default pixmap if hook doesn't define one
if pixmap is None:
pixmap = QtGui.QPixmap(":/tk_multi_publish2/task.png")
return pixmap
def _get_error_extra_info(error_msg):
"""
A little wrapper to return a dictionary of data to show a button in the
publisher with the supplied error message.
:param error_msg: The error message to display.
:return: An logging "extra" dictionary to show the error message.
"""
return {
"action_show_more_info": {
"label": "Error Details",
"tooltip": "Show the full error tack trace",
"text": "<pre>%s</pre>" % (error_msg,),
}
}