Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions allzpark/allzparkconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,27 @@ def protected_preferences():
return dict()


def environment_plugin():
"""Custom widget for adding extra environment variables

In some scenario, e.g. production pipeline involved workflow, may
require passing additional arguments as environment variables to
application that is being launched. And those environment variables,
like shot numbers, may change regularly so can't be fixed in Rez
package.

To adopt that, one could subclass `allzpark.plugin.EnvPluginBase`
to implement a custom widget that can be hooked with Allzpark, as
an additional interface for user to make production decisions, and
parse those inputs into application's launching environment.

Returns:
None or subclass of `allzpark.plugin.EnvPluginBase`

"""
return None


def themes():
"""Allzpark GUI theme list provider

Expand Down
17 changes: 13 additions & 4 deletions allzpark/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,10 +243,10 @@ def initialize(config_file=None,
"start anew")

if clean:
tell("(clean) ")
tell("(clean) ", newlines=0)
storage.clear()
else:
tell("(%s)" % storage.fileName())
tell("(%s) " % storage.fileName(), newlines=0)

defaults = {
"memcachedURI": os.getenv("REZ_MEMCACHED_URI", "None"),
Expand Down Expand Up @@ -293,7 +293,7 @@ def initialize(config_file=None,


def launch(ctrl):
from . import view, resources, util
from . import view, dock, resources, util

# Handle stdio from within the application if necessary
if hasattr(allzparkconfig, "__noconsole__"):
Expand All @@ -318,7 +318,16 @@ def excepthook(type, value, traceback):
with timings("- Loading themes.. "):
resources.load_themes()

window = view.Window(ctrl)
with timings("- Loading environment plugin.. ") as msg:
plugin_cls = allzparkconfig.environment_plugin()
if plugin_cls is None:
plugin = None
msg["success"] = "no plugin - ok\n"
else:
plugin = dock.EnvironmentPlugin(ctrl, plugin_cls)
msg["success"] = "%s loaded - ok {:.2f}\n" % plugin.name

window = view.Window(ctrl, plugin)
user_css = ctrl.state.retrieve("userCss", "")
originalcss = resources.load_theme(ctrl.state.retrieve("theme"))
# Store for CSS Editor
Expand Down
16 changes: 15 additions & 1 deletion allzpark/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ def __init__(self, ctrl, storage, parent_environ=None):
# Cache environment testing result
"testedEnvirons": {},

"pluginEnvironValidator": lambda *args: None,

"rezApps": odict(),
"fullCommand": "rez env",
"serialisationMode": (
Expand Down Expand Up @@ -237,6 +239,7 @@ def __init__(self,
"context": model.ContextModel(),
"environment": model.EnvironmentModel(),
"parentenv": model.EnvironmentModel(),
"plugin": model.EnvironmentModel(),
"diagnose": model.EnvironmentModel(),
"commands": model.CommandsModel(),
}
Expand Down Expand Up @@ -309,6 +312,8 @@ def context(self, app_request):

def parent_environ(self):
environ = self._state["parentEnviron"].copy()
# Inject plugin environment
environ = dict(environ, **(self._models["plugin"].json() or {}))
# Inject user environment
#
# NOTE: Rez takes precendence on environment, so a user
Expand Down Expand Up @@ -511,6 +516,9 @@ def on_unhandled_exception(self, type, value, tb):
# Methods
# ----------------

def register_environment_validator(self, validator):
self._state["pluginEnvironValidator"] = validator

def stdio(self, stream, level=logging.INFO):
return _Stream(self, stream, level)

Expand Down Expand Up @@ -772,6 +780,13 @@ def do():
disabled = self._models["packages"]._disabled
environ = self.parent_environ()

validator = self._state["pluginEnvironValidator"]
invalid = validator(environ, rez_app)
if invalid:
self.error("Plugin environment validation failed:\n"
"%s" % invalid)
return

self.debug(
"Launching %s%s.." % (
tool_name, " (detached)" if is_detached else "")
Expand Down Expand Up @@ -1269,7 +1284,6 @@ def graph(self):
context = self._state["rezContexts"][self._state["appRequest"]]
if isinstance(context, model.BrokenContext):
self._state.to_console()
self._state.to_ready()
self.error("Can not graph a broken context.")
return

Expand Down
86 changes: 84 additions & 2 deletions allzpark/dock.py
Original file line number Diff line number Diff line change
Expand Up @@ -702,13 +702,15 @@ def __init__(self, ctrl, parent=None):
"environment": QtWidgets.QWidget(),
"editor": EnvironmentEditor(),
"penv": QtWidgets.QWidget(),
"plugin": QtWidgets.QWidget(),
"diagnose": QtWidgets.QWidget(),
}

widgets = {
"view": JsonView(),
"penv": JsonView(),
"test": JsonView(),
"plugin": JsonView(),
"compute": QtWidgets.QPushButton("Compute Environment"),
}

Expand All @@ -720,22 +722,29 @@ def __init__(self, ctrl, parent=None):
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(widgets["penv"])

layout = QtWidgets.QVBoxLayout(pages["plugin"])
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(widgets["plugin"])

layout = QtWidgets.QVBoxLayout(pages["diagnose"])
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(widgets["test"])
layout.addWidget(widgets["compute"])

for view in ["view", "penv", "test"]:
for view in ["view", "penv", "plugin", "test"]:
widgets[view].setSortingEnabled(True)
widgets[view].sortByColumn(0, QtCore.Qt.AscendingOrder)

pages["editor"].applied.connect(self.on_env_applied)

panels["central"].addTab(pages["environment"], "Context")
panels["central"].addTab(pages["penv"], "Parent")
panels["central"].addTab(pages["plugin"], "Plugin")
panels["central"].addTab(pages["editor"], "User")
panels["central"].addTab(pages["diagnose"], "Diagnose")

panels["central"].setTabVisible(2, False)

user_env = ctrl.state.retrieve("userEnv", {})
pages["editor"].from_environment(user_env)
pages["editor"].warning.connect(self.on_env_warning)
Expand All @@ -750,10 +759,14 @@ def __init__(self, ctrl, parent=None):
self._models = {
"environ": None,
"parent": None,
"plugin": None,
"diagnose": None,
}

def set_model(self, environ, parent, diagnose):
def enable_plugin(self):
self._panels["central"].setTabVisible(2, True)

def set_model(self, environ, parent, plugin, diagnose):
proxy_model = QtCore.QSortFilterProxyModel()
proxy_model.setSourceModel(environ)
self._widgets["view"].setModel(proxy_model)
Expand All @@ -764,6 +777,11 @@ def set_model(self, environ, parent, diagnose):
self._widgets["penv"].setModel(proxy_model)
self._models["parent"] = parent

proxy_model = QtCore.QSortFilterProxyModel()
proxy_model.setSourceModel(plugin)
self._widgets["plugin"].setModel(proxy_model)
self._models["plugin"] = plugin

proxy_model = QtCore.QSortFilterProxyModel()
proxy_model.setSourceModel(diagnose)
self._widgets["test"].setModel(proxy_model)
Expand Down Expand Up @@ -1768,3 +1786,67 @@ def update_current(self, profile, version, icon):
icon = res.icon(icon)
icon = icon.pixmap(QtCore.QSize(px(32), px(32)))
self._widgets["icon"].setPixmap(icon)


class EnvironmentPlugin(AbstractDockWidget):
"""Interface for adding extra environment variables"""
name = "Environment plugin"
icon = "File_Query_32"

revealed = QtCore.Signal()

def __init__(self, ctrl, plugin_cls, parent=None):
# update dock name and docstring from plugin class
self.name = getattr(plugin_cls, "name", self.name)
self.__doc__ = plugin_cls.__doc__ or self.__doc__
super(EnvironmentPlugin, self).__init__(self.name, parent)
self.setAttribute(QtCore.Qt.WA_StyledBackground)
self.setObjectName(self.name)

panels = {
"central": QtWidgets.QWidget(),
}

widgets = {
"plugin": plugin_cls(),
}

layout = QtWidgets.QVBoxLayout(panels["central"])
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(widgets["plugin"])

# update icon for plugin instance that ships it's own QIcon
self.icon = getattr(widgets["plugin"], "icon", self.icon)
self._ctrl = ctrl
self._widgets = widgets

self.setWidget(panels["central"])

def connect_plugin(self):
ctrl = self._ctrl
plugin_ = self._widgets["plugin"]

ctrl.register_environment_validator(plugin_.validate)

ctrl.profile_changed.connect(self.on_profile_changed)
ctrl.application_changed.connect(self.on_application_changed)

plugin_.envChanged.connect(ctrl.models["plugin"].load)
plugin_.envReset.connect(ctrl.models["plugin"].reset)
plugin_.revealed.connect(self.revealed.emit)
plugin_.consoleShown.connect(ctrl.state.to_console)
plugin_.logged.connect(ctrl.logged.emit)

def on_profile_changed(self, name, version, *args, **kwargs):
plugin_ = self._widgets["plugin"]

profile_versions = self._ctrl.state["rezProfiles"][name]
profile_package = profile_versions[version]
plugin_.on_profile_changed(profile_package)

def on_application_changed(self):
plugin_ = self._widgets["plugin"]

app_request = self._ctrl.state["appRequest"]
app_package = self._ctrl.state["rezApps"][app_request]
plugin_.on_application_changed(app_package)
129 changes: 129 additions & 0 deletions allzpark/plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@

import logging
from .vendor.Qt import QtCore, QtWidgets


class EnvPluginBase(QtWidgets.QWidget):
"""Base class of environment plugin"""

name = "Environment Plugin"

envChanged = QtCore.Signal(dict)
envReset = QtCore.Signal()
revealed = QtCore.Signal()
consoleShown = QtCore.Signal()
logged = QtCore.Signal(str, int) # message, level

def on_profile_changed(self, package):
"""Runs on profile changed

This will be called by Allzpark when profile has changed.
Reimplement this function if you need plugin to take actions on
profile change.

Args:
package: profile package

"""
pass

def on_application_changed(self, package):
"""Runs on application selection changed

This will be called by Allzpark when selected application changed.
Reimplement this function if you need plugin to take actions on
application change.

Args:
package: application package

"""
pass

def validate(self, environ, package):
"""Validate environment before launching application

This will be called by Allzpark when application is about to launch,
and abort launching if validation failed with message returned.

Return None if validation passed, or string message as reason why it
fails. Returning any value that is not equivalent to False (e.g. None,
"", 0) will be considered as fail and the value will be used to format
string error message.

Args:
environ (dict): env vars that merged from parent, plugin and user
package: application package that being launched

Returns:
None or str

"""
pass

def set_env(self, env):
"""Inject additional environment variables

The `env` will be applied on top of parent environment, and user
environment variables on top of it.

Multi-path variable will simply be overwritten, no appending nor
prepending.

Args:
env (dict): key-value paired environment variables

Returns:
None

"""
self.envChanged.emit(env)

def clear_env(self):
"""Reset additional environment variables

Returns:
None

"""
self.envReset.emit()

def reveal(self):
"""Activate plugin dock widget

Show plugin dock widget. Could be used when input is required and
need to have focus on the plugin page.

Returns:
None

"""
self.revealed.emit()

def to_console(self):
"""Activate Allzpark console dock widget

Show Allzpark console dock widget. Could be used when there are log
messages that need user to know.

Returns:
None

"""
self.consoleShown.emit()

def debug(self, message):
"""Send debug message to Allzpark console"""
self.logged.emit(message, logging.DEBUG)

def info(self, message):
"""Send regular message to Allzpark console"""
self.logged.emit(message, logging.INFO)

def warning(self, message):
"""Send warning message to Allzpark console"""
self.logged.emit(message, logging.WARNING)

def error(self, message):
"""Send error message to Allzpark console"""
self.logged.emit(str(message), logging.ERROR)
Loading