Advanced Plugin Topics

Relative Plugins

One of the most fundamental ideas about Plugin Sheets is that there are two types of plugins, relative plugins and absolute plugins.

The most bare-minimum absolute plugin looks like this:

plugins:
    absolute_plugin:
        hierarchy: fizz/buzz

The plugin contains just one item, “hierarchy”, which is the position of the Plugin for when it gets built into a Context.

A bare-minimum relative plugin looks like this

plugins:
    absolute_plugin:
        hierarchy: fizz
    relative_plugin:
        hierarchy: '{root}/buzz'
        uses:
            - fizz

A relative plugin can also refer to another relative plugin recursively, as long as the end of that chain of plugins is an absolute plugin.

Calling a plugin “relative” is a bit of a inaccurate. Relative plugins are not single plugins - they’re a group of plugins. Each hierarchy listed under “uses”, will create a separate Plugin object.

Note

{root} is only supported on a plugin’s hierarchy and mapping but it is also not required. If no {root} is given, Ways will just append the relative plugin’s mapping and hierarchy to its parent. If you do provide {root} though, you get to define different places for the parent’s data to be inserted, like this: “parent/{root}/library/{root}/hierarchy”.

“uses” has a couple details that are important to know before starting.

  1. uses should never give a relative plugin its own hierarchy. For example, these setups are invalid:
plugins:
    relative:
        mapping: something
        hierarchy: some/place
        uses:
            - some/place
plugins:
    absolute:
        mapping: whatever
        hierarchy: foo
    relative:
        mapping: "{root}/something"
        hierarchy: "{foo}/bar"
        uses:
            - foo/bar
  1. Relative plugins can be chained together, as long as one of the plugins is tied to an absolute plugin.
plugins:
    absolute_plugin:
        hierarchy: fizz
    relative_plugin1:
        hierarchy: '{root}/buzz'
        uses:
            - fizz
    relative_plugin2:
        hierarchy: '{root}/foo'
        uses:
            - fizz/buzz

The initial setup for relative plugins is a bit verbose but has its advantages. The main advantage is re-useability.

Here is an example of how absolute plugins and relative plugins differ.

Relative Absolute
plugins:
    absolute_plugin:
        hierarchy: fizz
        mapping: bar

    relative_plugin1:
        hierarchy: '{root}/buzz'
        mapping: '{root}/something'
        uses:
            - fizz

    absolute_plugin2:
        hierarchy: '{root}/pop'
        mapping: '{root}/another/thing'
        uses:
            - fizz/buzz

    absolute_plugin3:
        hierarchy: '{root}/fizz'
        mapping: '{root}/sets'
        uses:
            - fizz/buzz/pop

    library:
        hierarchy: '{root}/library'
        mapping: '{root}/library'
        uses:
            - fizz
            - fizz/buzz
            - fizz/buzz/pop
            - fizz/buzz/pop/fizz
plugins:
    absolute_plugin:
        hierarchy: fizz
        mapping: bar

    absolute_plugin1:
        hierarchy: fizz/buzz
        mapping: bar/something

    absolute_plugin1_library:
        hierarchy: fizz/buzz/library
        mapping: bar/something/library

    absolute_plugin2:
        hierarchy: fizz/buzz/pop
        mapping: bar/something/another/thing

    absolute_plugin2_library:
        hierarchy: fizz/buzz/pop/library
        mapping: bar/something/another/thing/library

    absolute_plugin3:
        hierarchy: fizz/buzz/pop/fizz
        mapping: bar/something/another/thing/sets

    absolute_plugin3_library:
        hierarchy: fizz/buzz/pop/fizz/library
        mapping: bar/something/another/thing/sets/library

Both examples create the same exact Plugins.

So to compare the two examples - the relative plugin example took more lines to create the absolute plugin version. If this example were longer however, the relative plugin version would come out shorter because each line in “uses” is 3 lines in the absolute version.

Also, if we needed to change something in “library”, we only need to change one plugin in the relative system, whereas in an absolute system, you would need to change it in 3 places.

Note

When Ways loads Plugins, all Plugins are “resolved” into absolute Plugin objects.

Designing For Cross-Platform Use

If you’re using Ways to build Context objects for your filesystem, you may have to consider supporting multiple operating systems.

Say you have two paths that represent the same place on-disk in Windows and in Linux: /jobs/someJobName_123/library and Windows: \NETWORK\jobs\someJobName_123\library.

You might be tempted to write your plugins like this:

plugins:
    linux:
        mapping: /jobs
        hierarchy: job
    windows:
        mapping: \\NETWORK\jobs\someJobName_123\library
        hierarchy: job
    linux_library:
        mapping: /jobs/someJobName_123/library
        hierarchy: job/library
    windows_library:
        mapping: \\NETWORK\jobs\someJobName_123\library
        hierarchy: job/library
    linux_library_reference:
        mapping: /jobs/someJobName_123/library/reference
        hierarchy: job/library/reference
    windows_library_reference:
        mapping: \\NETWORK\jobs\someJobName_123\library\reference
        hierarchy: job/library/reference

This works but you wanted to keep data consistent across both plugins, you’d be forced to write separate plugins for each OS and each feature.

To make the process easier, just use relative plugins

plugins:
    job_root_linux:
        hierarchy: job
        mapping: /jobs
        platforms:
            - linux

    job_root_windows:
        hierarchy: job
        mapping: \\NETWORK\jobs
        platforms:
            - windows

    library:
        hierarchy: '{root}/library'
        mapping: '{root}/someJobName_123/library'
        uses:
            - job

    reference:
        hierarchy: '{root}/reference'
        mapping: '{root}/reference'
        uses:
            - job/library

Imagine a scenario where you have to maintain 100 separate plugins for Windows, Mac, and Linux. If it were written using absolute plugins only, that’d mean writing 300 plugins, total. And if you needed to change any plugin, you’d have to change it once for each OS. But if we write 3 absolute plugins, each with their own platform, we can write 99 relative plugins that just append to that root OS plugin. Ways will pick whichever root matches the system OS or what is defined in the WAYS_PLATFORM environment variable and then append all other 99 plugins onto it.

Note

If you’re designing plugins for cross-platform use and you’re dealing with filepaths, it’s best to write “path: true” in your hierarchies. That way, the path separator will always be correct. Examples of this are in path and in Common Patterns And Best Practices.

Appending To Plugins

Say for example you have a plugin in another file that you want to add to. You have two options to do this, an absolute append or a relative append.

You can do this using a relative plugin, but isn’t generally a good idea because its syntax is harder to follow

plugins:
    some_plugin:
        hierarchy: foo/bar
        mapping: something
    append_plugin:
        hierarchy: ''
        data:
            some_data: 8
        uses:
            - foo/bar

Appending with an absolute plugin is much simpler.

plugins:
    some_plugin:
        hierarchy: foo/bar
        mapping: something
    append_plugin:
        hierarchy: foo/bar
        data:
            some_data: 8

But if you need to append information to more than one plugin at once, relative plugins are very useful.

plugins:
    some_plugin:
        hierarchy: foo/bar
        mapping: something
    another_plugin:
        hierarchy: another
    another_plugin:
        hierarchy: another/tree
    append_plugin:
        hierarchy: ''
        data:
            some_data: 8
        uses:
            - foo/bar
            - another
            - another/tree

So in conclusion, absolute and relative plugins both have their pros and cons. Pick the right one for the right job.

Other than plugin platforms and relative plugins, there’s another way to affect the discovery and runtime of plugins in Ways called “assignments”.

Using Assignments

Whenever a Plugin is defined, its hierarchy is defined and if no assignment is given, ways.DEFAULT_ASSIGNMENT is used, instead.

Ways assignments allow users to change the way plugins resolve at runtime.

First lets explain the syntax of assignments and then explain how this works in a live environment.

There are 3 ways to define assignments to a plugin. Each one is a matter of convenience/preference and is no better than the other.

Assigning To Multiple Plugin Sheets

With the default Ways Descriptor classes, if you have a file called “.waypoint_plugin_info” in the same directory or above a Plugin Sheet, any assignment listed is used.

“.waypoint_plugin_info” can be JSON or YAML.

Examples:

>>> cat .waypoint_plugin_info.json
>>> {
>>>     "assignment": master,
>>>     "recursive": false
>>> }
>>> cat .waypoint_plugin_info.yml
>>> assignment: master
>>> recursive: false

Note

“recursive” defines if we will search for Ways Plugin Sheets in subfolders. For more information, seealso environment_setup.rst

The assignment in this file will apply to all plugins in all Plugins Sheets at the same directory or below the “.waypoint_plugin_info” file.

Assigning To A Plugin Sheet

You can add an assignment to every plugin in a Plugin Sheet, using “globals”

globals:
    assignment: bar
plugins:
    some_plugin:
        hierarchy: some/hierarchy
    another_plugin:
        hierarchy: another/hierarchy

All plugins listed now have “job” assigned to them. Using “globals” takes priority over any assignment in a “.waypoint_plugin_info” file.

Assigning To A Plugin

If an assignment is directly applied to a plugin, then it is used over any other assignment method.

plugins:
    another_plugin:
        hierarchy: another/hierarchy
        assignment: job

Applied Assignments - Live Environments

Whenever you call a Context, you must give a hierarchy and an assignment. If no assignment is given, Ways “searches” for plugins in every assignment that it knows about, defined in the WAYS_PRIORITY environment variable.

export WAYS_PRIORITY=master:shot:job

In the above example, “master” plugins are loaded first, then “job” plugins, and then “shot” plugins.

To take advantage of this in a live environment, here is a short example.

master.yml

plugins:
    job:
        hierarchy: job
        mapping: '/jobs/{JOB}'
    shot:
        hierarchy: '{root}/shot'
        mapping: '{root}/{SCENE}/{SHOT}'
        uses:
            - job
    plates:
        hierarchy: '{root}/plates'
        mapping: '{root}/library/graded/plates'
        uses:
            - job/shot
    client_plates:
        hierarchy: '{root}/client'
        mapping: '{root}/clientinfo'
        uses:
            - job/shot/plates
    compositing:
        hierarchy: '{root}/comp'
        mapping: '{root}/compwork'
        uses:
            - job/shot/plates

Here, we didn’t define an assignment and we have no “.waypoint_plugin_info.(yml|json)” file, so ways.DEFAULT_ASSIGNMENT (master) is given to every Plugin.

Now define the WAYS_PRIORITY

sh/bash

export WAYS_PRIORITY=master:job

csh/tcsh

setenv WAYS_PRIORITY master:job

Add a folder or file location to the WAYS_DESCRIPTORS environment variable where we’re going to look for “job-specific” Plugin Sheets.

export WAYS_DESCRIPTORS=/path/to/master.yml:/path/to/job/plugins

The last step is to add a ‘job’-assigned Plugin Sheet to the /path/to/job/plugins folder.

jobber.yml

globals:
    assignment: job
plugins:
    job_plugin:
        hierarchy: '{root}/plates'
        mapping: '{root}/archive/plates'
        uses:
            - job/shot

Now let’s see this in a live Python environment

# Both get_context versions do the same thing, because assignment='' by default
context = ways.api.get_context('job/shot/plates/client', assignment='')
context = ways.api.get_context('job/shot/plates/client')
context.get_mapping()
# Result: "/jobs/{JOB}/{SCENE}/{SHOT}/archive/plates/clientinfo"

Before adding jobber.yml to our system, the mapping for “job.shot/plates/client” was “/jobs/{JOB}/{SCENE}/{SHOT}/library/graded/plates/clientinfo”.

Now, it’s “/jobs/{JOB}/{SCENE}/{SHOT}/archive/plates/clientinfo”.

This works because the “job_plugin” key in jobber.yml matches the same hierarchy as the “plates” key in master.yml.

jobber.yml comes after master.yml and its assignment loads after, so it overwrote the hierarchy plugins in master.yml. All of the relative plugins that depend on “job/shot/plates” now have a completely different mapping.

Now consider this

If one project has their WAYS_DESCRIPTORS set to this:

export WAYS_DESCRIPTORS=/path/to/master.yml

And another project includes the job-assignment folder:

export WAYS_DESCRIPTORS=/path/to/master.yml:/path/to/job/plugins/jobber.yml

The two projects could have completely different runtime behaviors despite having the exact same Python code.

Or maybe instead of having projects point to different files on disk, you have a job-based environment like this.

export WAYS_DESCRIPTORS=/jobs/$JOB/config/ways

Maybe one job is called “foo” and another is called “bar”.

/jobs/foo/config/ways and /jobs/bar/config/ways could have different Plugin Sheet files customized for each job’s needs.

With just a single, 8 line file, Ways’s plugin structure can completely change.