# Introduction

![Pyblish by Example](https://cloud.githubusercontent.com/assets/2152766/12489260/51843d38-c067-11e5-93c8-7b96c30ed37a.png)

Welcome to the Pyblish by Example tutorial.

## You will learn

* The basics of publishing in the production of film and games
* The fundamentals of [Pyblish](http://pyblish.com) and it's [API](http://api.pyblish.com)
* How to validate content
* How to guarantee valid content on export&#x20;
* How to properly position and name content according to convention
* How to visualise the results of one or more publishes
* Developer and Artist communication through the UI

## Introduction

Pyblish is an open source, cross-platform framework for test-driven content creation.

*Pyblish by Example* is a hands-on introduction to Pyblish using short example programs written like a book - to be read from top to bottom.

* [What is Publishing?](https://github.com/pyblish/pyblish/wiki/What-is-publishing)

## Installation

If you haven't already, go ahead and install Pyblish.

```bash
$ pip install pyblish-base
```

Any problems, have a look at the [extended installation guide](http://forums.pyblish.com/t/pyblish-1-4-released/239/2).

## How to use this guide

Start by confirming to yourself that you are indeed using version 1.4+ of Pyblish.

```python
>>> import pyblish
>>> pyblish.__version__
'1.4.3'
```

If not, see the top-left corner of this page for a dropdown of your version.

As you read through this guide it is recommended that you use the scripting API, accessible via `pyblish.util`.

```python
from pyblish import util
util.publish()
```

## Reporting issues

Should you happen to find errors or would like to contribute material to this guide, you can:

1. Click on the `+` button to the right of each paragraph to add a comment
2. Register on [GitBook](https://www.gitbook.com/book/pyblish/pyblish-by-example) to edit this book directly.
3. Fork the [GitHub repository](https://github.com/pyblish/pyblish-by-example) and submit a pull-request with your changes.

## Content

Have a look to the left for a table of contents, and below for related topics in the forums.

> **Note:** The below content was written at various versions of Pyblish and may not include current best practices, but all remain forwards compatible with version 1.4.
>
> For example, `.set_data("key", "value")` has been superseded by `.data["key"] = "value"` but will still work with newer plug-ins.

**Intermediate**

* [Responsibility of Extractors](http://forums.pyblish.com/t/responsibilities-of-extractors/266/9)
* [Multiple families workflow](http://forums.pyblish.com/t/multiple-families-workflow/205)
* [Per task plug-ins](http://forums.pyblish.com/t/task-specific-plugins/127)
* [Publishing with comments](http://forums.pyblish.com/t/publishing-with-comments/120)
* [Instances and Plain-Old-Data](http://forums.pyblish.com/t/instances-and-plain-old-data/136)
* [Cooperative Collection](http://forums.pyblish.com/t/cooperative-collection/137)
* [Search and Customisation](http://forums.pyblish.com/t/pyblish-search-and-customisation)
* [Good to know about Pyblish](http://forums.pyblish.com/t/good-to-know-about-pyblish)
* [`hosts` Attribute In-Depth](http://forums.pyblish.com/t/the-use-of-hosts-attribute/78/3)
* [Registration versus families](http://forums.pyblish.com/t/filtering-collected-instances-based-on-category-family/245/5)
* [Backwards compatibility](http://forums.pyblish.com/t/backwards-compatibility-and-breaking-changes/246)

**Advanced**

* [Magenta development thread](http://forums.pyblish.com/t/pyblish-magenta/79)
* [Developer Guide](http://forums.pyblish.com/t/developer-guide)
* [Pyblish in 100 lines](https://pyblish.gitbooks.io/developer-guide/content/pyblish_in_100_lines.html)

**Changelog**

* `2015-08-06 16:27` - Added [Quickstart](http://forums.pyblish.com/t/learning-pyblish-by-example/108/3)
* `2015-08-07 10:12` - Added [Architecture](http://forums.pyblish.com/t/learning-pyblish-by-example/108/6)
* `2015-08-07 10:14` - Updated [Quickstart](http://forums.pyblish.com/t/learning-pyblish-by-example/108/3)
* `2015-08-07 19:17` - Added [Good to know about Pyblish](http://forums.pyblish.com/t/good-to-know-about-pyblish)
* `2015-08-07 20:41` - Updated [Architecture](http://forums.pyblish.com/t/learning-pyblish-by-example/108/6)
* `2015-08-07 20:48` - Updated This
* `2015-08-09 10:51` - Updated [Validating II](http://forums.pyblish.com/t/learning-pyblish-by-example/108/16) with flow chart
* `2015-08-09 17:24` - Updated This, with installation instructions
* `2015-08-10 09:00` - Updated This
* `2015-08-11 07:28` - Updated [Quickstart](http://forums.pyblish.com/t/learning-pyblish-by-example/108/3)
* `2015-08-11 07:30` - Updated [Files](http://forums.pyblish.com/t/learning-pyblish-by-example/108/4) with comma-separated paths
* `2015-08-11 07:40` - Fixed typo in [Report IV](http://forums.pyblish.com/t/learning-pyblish-by-example/108/26)
* `2015-08-13 21:08` - Updated [Report I-IV](http://forums.pyblish.com/t/learning-pyblish-by-example/108/26)
* `2015-08-14 09:26` - Updated [Report I-II](http://forums.pyblish.com/t/learning-pyblish-by-example/108/26)
* `2015-08-18 07:02` - Added [host attribute in-depth](http://forums.pyblish.com/t/the-use-of-hosts-attribute/78/3)
* `2015-08-18 07:22` - Added "You will learn" to This
* `2015-10-19 14:38` - Updated use of "Asset" to "Instance"
* `2015-11-19 09:07` - Updated to pure-dict
* `2015-12-20 21:56` - Updated image of [Context + Instance](http://forums.pyblish.com/t/learning-pyblish-by-example/108/6).
* `2016-01-21 17:46` - Transitioned to GitBooks, removed section about custom test and services.
* `2016-06-07 09:48` - Defer use of GUI from guide.
* `2016-07-01 14:54` - Link to installation guide on Forum
* `2016-08-03 10:40` - Add intermediate and advanced links
* `2017-08-12 10:30` - Removed deprecated dependency injection link


# Hello World

Our first publish will be about printing the classic "Hello World" to the screen. Here is the full source code.

```python
import pyblish.api

class MyPlugin(pyblish.api.ContextPlugin):
  def process(self, context):
    print("hello python")

pyblish.api.register_plugin(MyPlugin)

import pyblish.util
pyblish.util.publish()
```

Running this in any Python environment, such as a standalone or built-in interpreter, will cause hello python to be printed to the screen.


# Quickstart

## Quickstart

Before we move on, let me throw you into the deep end and show you a full example.

Don't worry too much if it doesn't make sense just now, the rest of the examples are dedicated to explaining each feature in great detail.

The examples will be written primarily for Autodesk Maya, but should be easily readable and it's concepts applicable to any 3d content creation software. There is a slight simplification involved, for more clarity, but overall this represents a real-world implementation of *a fully featured publishing pipeline with Pyblish*.

## The Deep End

The example uses the 2 available superclasses in Pyblish - `ContextPlugin` and `InstancePlugin`. The order in which these plug-ins are run is controlled by an integer attribute called `order`, with 4 default values.

1. Collection
2. Validation
3. Extraction
4. Integration

![image](https://cloud.githubusercontent.com/assets/2152766/12515092/752725ea-c11e-11e5-923c-ace968721a38.png)

**1. collect\_rig.py**

Gather information to validate and export.

```python
import pyblish.api
from maya import cmds

class CollectRig(pyblish.api.ContextPlugin):
  """Discover and collect available rigs into the context"""

  order = pyblish.api.CollectorOrder

  def process(self, context):
    for node in cmds.ls(sets=True):
      if not node.endswith("_RIG"):
        continue

      name = node.rsplit("_", 1)[0]
      instance = context.create_instance(name, family="rig")

      # Collect associated nodes
      members = cmds.sets(node, query=True)
      cmds.select([node] + members, noExpand=True)
      instance[:] = cmds.file(
        constructionHistory=True,
        exportSelected=True,
        preview=True,
        force=True)
```

**2. validate\_rig.py**

Ensure the correctness of collected information.

```python
import pyblish.api

class ValidateRigContents(pyblish.api.InstancePlugin):
  """Ensure rig has the appropriate object sets"""

  order = pyblish.api.ValidatorOrder
  families = ["rig"]

  def process(self, instance):
    assert "controls_SEL" in instance, "%s is missing a controls set" % instance
    assert "pointcache_SEL" in instance, "%s is missing a pointcache set" % instance
```

**3. extract\_rig.py**

Write to disk.

```python
import os
import shutil
from datetime import datetime

import pyblish.api
from maya import cmds

class ExtractRig(pyblish.api.InstancePlugin):
  """Serialise valid rig"""

  order = pyblish.api.ExtractorOrder
  families = ["rig"]
  hosts = ["maya"]

  def process(self, instance):
    context = instance.context
    dirname = os.path.dirname(context.data["currentFile"])
    name, family = instance.data["name"], instance.data["family"]
    date = datetime.now().strftime("%Y%m%dT%H%M%SZ")

    # Find a temporary directory with support for publishing multiple times.
    tempdir = os.path.join(dirname, "temp", date, family, name)
    tempfile = os.path.join(tempdir, name + ".ma")

    self.log.info("Exporting %s to %s" % (instance, tempfile))

    if not os.path.exists(tempdir):
        os.makedirs(tempdir)

    cmds.select(instance, noExpand=True)  # `instance` a list
    cmds.file(tempfile,
              type="mayaAscii",
              exportSelected=True,
              constructionHistory=False,
              force=True)

    # Store reference for integration
    instance.set_data("tempdir", tempdir)
```

**4. integrate\_rig.py**

Integrate written information into the pipeline, as per convention.

```python
import os
import shutil

import pyblish.api

class IntegrateRig(pyblish.api.InstancePlugin):
  """Copy files to an appropriate location where others may reach it"""

  order = pyblish.api.IntegratorOrder
  families = ["rig"]

  def process(self, instance):
    assert instance.data("tempdir"), "Can't find rig on disk, aborting.."

    self.log.info("Computing output directory..")
    context = instance.context
    dirname = os.path.dirname(context.data("currentFile"))
    root = os.path.join(dirname, "public")

    if not os.path.exists(root):
        os.makedirs(root)

    version = "v%03d" % (len(os.listdir(root)) + 1)

    src = instance.data("tempdir")
    dst = os.path.join(root, version)

    self.log.info("Copying %s to %s.." % (src, dst))

    shutil.copytree(src, dst)
    self.log.info("Copied successfully!")
```

## That's a lot

I'm sure you have lots of questions, but don't worry. This is the part where we dig into exactly how we get to this point, and what all of this really means.

## Test it out

It isn't necessary to run this on your own, but if you want to give it a try, here's what you do.

1. Install [Pyblish for Maya](https://github.com/pyblish/pyblish-maya#installation)
2. Copy/paste the full source code [from here](https://gist.github.com/mottosso/93399862c94f0ab4314f) into your Maya script editor
3. Run it

This is what you should expect.

```bash
...
import pyblish.util
pyblish.util.publish()
pyblish: Registered C:\pythonpath\pyblish_maya\plugins
Pyblish loaded successfully.
# pyblish.ExtractRig : Exporting Bruce to C:\...\maya\scenes\temp\20180318T091805Z\rig\Bruce\Bruce.ma # 
# pyblish.IntegrateRig : Computing output directory.. # 
# pyblish.IntegrateRig : Copying C:\...\maya\scenes\temp\20180318T091805Z\rig\Bruce to C:\...\maya\scenes\public\v002.. # 
# pyblish.IntegrateRig : Copied successfully! #
```

If not, [let us know](https://forums.pyblish.com)!

## Alternative examples

Looking for a full example for your DCC?

These will turn into links as they become available, watch this space!

* 3ds Max
* Softimage
* Houdini
* Nuke
* Fusion
* Clarisse
* Modo
* Blender


# Files

Plug-ins can also be stored as files.

```bash
myplugins
├── myplugin1.py
└── myplugin2.py
```

Here is the full source code.

```python
# myplugin1.py
import pyblish.api

class MyPlugin1(pyblish.api.ContextPlugin):
  def process(self, context):
    print("hello from plugin1")
```

```python
# myplugin2.py
import pyblish.api

class MyPlugin2(pyblish.api.ContextPlugin):
  def process(self, context):
    print("hello from plugin2")
```

You then register their parent directory, similar to how you would normally register Python modules.

```bash
# Environment Variables: Windows
$ set PYBLISHPLUGINPATH=c:\myplugins;\\server\moreplugins

# Environment Variables: Unix
$ export PYBLISHPLUGINPATH=/myplugins:/moreplugins
```

You can also register from Python.

```python
import pyblish.api
pyblish.api.register_plugin_path(r"c:\myplugins")
```

Once registered, the plug-ins are triggered upon the next publish.

```python
import pyblish.util
pyblish.util.publish()
# hello from plugin1
# hello from plugin2
```

**See also**

* [Task-based plug-in registration](http://forums.pyblish.com/t/task-specific-plugins)


# Coordination

In the previous example, you might have gotten the reverse output.

```bash
# hello from plugin2
# hello from plugin1
```

That's because plug-ins are sorted by the class attribute `order`, and we didn't change it.

```python
import pyblish.api

class FirstPlugin(pyblish.api.ContextPlugin):
  order = 0

  def process(self, context):
    print("hello")

class SecondPlugin(pyblish.api.ContextPlugin):
  order = 1

  def process(self, context):
    print("world")

pyblish.api.register_plugin(FirstPlugin)
pyblish.api.register_plugin(SecondPlugin)

import pyblish.util
pyblish.util.publish()
# hello
# world
```

They now run in the expected order.


# Architecture

## Architecture

Let's take a moment to talk about the 10.000 feet view of Pyblish.

Besides plug-ins, there are two primary objects that are of interest to you.

1. `Context`
2. `Instance`

The `Context` represents the world, typically your current working file, and contains 1 or more `Instance`.

![image](https://cloud.githubusercontent.com/assets/2152766/12515123/ac0ec266-c11e-11e5-803f-8e83fac3b20d.png)

You can think of `Instance` as a *subdivision* of `Context`, each pertaining to a specific area of the bigger picture, such as an image sequence or a model. When publishing, you can choose to consider either the world or part of the world.

![image](https://cloud.githubusercontent.com/assets/2152766/12515132/b6693872-c11e-11e5-911d-43387571751a.png)

In some cases, it makes sense to only look at a small portion of a working file, either for precision or special treatment. For example, if your `Context` is a scene from Star Wars, you might treat Luke different from how you treat The Death Star.

In other cases, it makes more sense to look at the world as a whole, such as when identifying Luke midst other assets in your scene.

## Cooperation

Now that you're able to write, register and coordinate plug-ins, it's time to look at how to make them cooperate.

```python
import datetime
import pyblish.api

class CollectTime(pyblish.api.ContextPlugin):
  order = 0

  def process(self, context):
    time = datetime.datetime.now()
    context.data["time"] = time

class PrintTime(pyblish.api.ContextPlugin):
  order = 1

  def process(self, context):
    time = context.data["time"]
    print(time)

pyblish.api.register_plugin(CollectTime)
pyblish.api.register_plugin(PrintTime)

import pyblish.util
pyblish.util.publish()
```

You can use this object to pass information from one plug-in to another. The `Context` is also accessible from the return value of `publish()`.

```python
context = pyblish.util.publish()
```


# Branching I

Now that you know how to arrange plug-ins, let's have a look at how to manage the control flow of publishing.

```python
import pyblish.api

class MyPlugin(pyblish.api.ContextPlugin):
  hosts = ["maya"]

  def process(self, context):
    from maya import cmds
    cmds.headsUpMessage("Hello from Pyblish")

pyblish.api.register_plugin(MyPlugin)

import pyblish.util
pyblish.util.publish()
# Hello from Pyblish
```

`hosts` is a **requirement attribute** and limits plug-ins to a particular scenario.

In this case, the plug-in will only run when publishing from within Autodesk Maya. These are some examples of available values for the `hosts` attribute.

* `python`
* `maya`
* `houdini`
* `nuke`
* `modo`
* `unknown`

You can register your own host, if for example you would like to extend Pyblish for use in additional software, by using `register_host()`.

```python
import pyblish.api
pyblish.api.register_host("myhost")
```


# Branching II

Let's look at a more interesting branching technique.

```python
import pyblish.api

items = ["john", "door"]

class CollectInstances(pyblish.api.ContextPlugin):
  order = 0

  def process(self, context):
    for item in items:
      context.create_instance(item)

class PrintInstances(pyblish.api.InstancePlugin):
  order = 1

  def process(self, instance):
    print("Instance is: %s" % instance)

pyblish.api.register_plugin(CollectInstances)
pyblish.api.register_plugin(PrintInstances)

import pyblish.util
pyblish.util.publish()
# The instance is "john"
# The instance is "door"
```

In this case, `PrintInstances` will run once for every instance. That's because we subclassed [InstancePlugin](https://github.com/pyblish/pyblish.api/wiki/InstancePlugin) instead of [ContextPlugin](https://github.com/pyblish/pyblish.api/wiki/ContextPlugin).

These two superclasses form the foundation upon which all of Pyblish is built, we'll have a much closer look these at in the following examples.

\[Instance]: <https://github.com/pyblish/pyblish.api/wiki/Instance>


# Branching III

In addition to running a plug-in once for every instance as in the previous example, it can sometimes be useful to define "classes" of instances that a common set of plug-ins can operate on.

Here, a "class of instances" is known as a `family` and a plug-in may support one or more `families`, which is another **requirement attribute**, like `hosts`.

```python
import pyblish.api

items = ["john.person", "door.prop"]

class CollectInstances(pyblish.api.ContextPlugin):
  order = 0

  def process(self, context):
    for item in items:
      name, suffix = item.split(".")
      instance = context.create_instance(name)
      instance.data["families"] = [suffix]

class PrintPersons(pyblish.api.InstancePlugin):
  order = 1
  families = ["person"]

  def process(self, instance):
    print("Person is: %s" % instance)

class PrintProps(pyblish.api.InstancePlugin):
  order = 1
  families = ["prop"]

  def process(self, instance):
    print("The prop is: %s" % instance)

pyblish.api.register_plugin(CollectInstances)
pyblish.api.register_plugin(PrintPersons)
pyblish.api.register_plugin(PrintProps)

import pyblish.util
pyblish.util.publish()
# The person is "john"
# The prop is "door"
```

If you pay special attention to the final output, you can see that "john" was processed by `PrintPersons`, whereas "door" got processed by `PrintProps`.


# Sharing

Publishing is about sharing, so let's have a look at how to publish something other than by printing.

```python
import os
import datetime
import pyblish.api

class CollectUserDir(pyblish.api.ContextPlugin):
  order = 0

  def process(self, context):
    context.data["userDir"] = os.path.expanduser("~")

class WriteTime(pyblish.api.ContextPlugin):
  order = 1

  def process(self, context):
    user_dir = context.data["userDir"]
    destination_path = os.path.join(user_dir, "time.txt")

    print("Writing time to %s" % destination_path)
    with open(destination_path, "w") as f:
      f.write("The time is %s" % datetime.datetime.today().ctime())

pyblish.api.register_plugin(CollectUserDir)
pyblish.api.register_plugin(WriteTime)

import pyblish.util
pyblish.util.publish()
# Writing time to C:\Users\marcus\Documents\time.txt
```

And here's what `time.txt` looks like.

```bash
The time is Thu Jan 21 16:34:58 2016
```


# Validating I

Sometimes, sharing means to first agree on a format in which to share, such that you don't end up with one big mess.

```python
import pyblish.api

items = ["JOHN.person", "door.prop"]

class CollectInstances(pyblish.api.ContextPlugin):
  order = 0

  def process(self, context):
    for item in items:
      name, suffix = item.split(".")
      context.create_instance(name, family=suffix)

class ValidateNamingConvention(pyblish.api.InstancePlugin):
  order = 1

  def process(self, instance):
    name = instance.data["name"]
    assert name == name.title(), "Sorry, %s should have been %s" % (
      name, name.title())

pyblish.api.register_plugin(CollectInstances)
pyblish.api.register_plugin(ValidateNamingConvention)

import pyblish.util
pyblish.util.publish()
# Sorry, JOHN should have been John
# Sorry, door should have been Door
Stopped due to: failed validation
```

We indicate failure by throwing exceptions of any kind, including assertions. Making the change, we now "pass validation".

```python
...
items = ["John.person", "Door.prop"]
...
pyblish.util.publish()
# John is valid
# Door is valid
```


# Validating II

In the previous example, we ensured that instances are title-cased. But failure doesn't stop subsequent plug-ins from actually writing to disk.

```python
import pyblish.api

disk = {}
items = ["JOHN.person", "door.prop"]

class CollectInstances(pyblish.api.ContextPlugin):
  order = 0

  def process(self, context):
    for item in items:
      name, suffix = item.split(".")
      context.create_instance(name, family=suffix)

class ValidateNamingConvention(pyblish.api.InstancePlugin):
  order = 1

  def process(self, instance):
    name = instance.data["name"]
    assert name == name.title(), "Sorry, %s should have been %s" % (
      name, name.title())

class ExtractInstances(pyblish.api.InstancePlugin):
  order = 2

  def process(self, instance):
    disk[instance.data["name"]] = instance

pyblish.api.register_plugin(CollectInstances)
pyblish.api.register_plugin(ValidateNamingConvention)
pyblish.api.register_plugin(ExtractInstances)

import pyblish.util
pyblish.util.publish()
print("JOHN" in disk)
# True
```

To remedy this, we'll turn our attention to some of the pre-defined *orders* provided by Pyblish.

* Collection
* Validation
* Extraction
* Integration

Collection sets the stage for validation. Once validation is complete, Pyblish takes a moment to consider whether any of the plug-ins that ran threw an error. If it did, it stops processing and returns control to the user.

This behavior is paramount to publishing. If you think back to [Quickstart](/02-quickstart) earlies in this guide, you may remember the following visualisation of it.

![image](https://cloud.githubusercontent.com/assets/2152766/12515092/752725ea-c11e-11e5-923c-ace968721a38.png)

In the next example, we will dig deeper into this mechanism and find out more about what it can do for us.


# CVEI I

Because the order of the above technique of (1) collecting, (2) validating and (3) extract-if-valid is so common, they are provided as keywords you can use in-place of a hard-coded number.

```python
import pyblish.api

disk = {}
items = ["JOHN.person", "door.prop"]

class CollectInstances(pyblish.api.ContextPlugin):

  order = pyblish.api.CollectorOrder  # <-- This is new

  def process(self, context):
    for item in items:
      name, suffix = item.split(".")
      context.create_instance(name, family=suffix)

class ValidateNamingConvention(pyblish.api.InstancePlugin):

  order = pyblish.api.ValidatorOrder

  def process(self, instance):
    name = instance.data["name"]
    assert name == name.title(), "Sorry, %s should have been %s" % (
      name, name.title())

class ExtractInstances(pyblish.api.InstancePlugin):

  order = pyblish.api.ExtractorOrder

  def process(self, instance):
    disk[instance.data["name"]] = instance

pyblish.api.register_plugin(CollectInstances)
pyblish.api.register_plugin(ValidateNamingConvention)
pyblish.api.register_plugin(ExtractInstances)

import pyblish.util
pyblish.util.publish()
# Sorry, JOHN should have been John
# Sorry, door should have been Door
```

Notice that instead of picking a number at random, we instead utilised the built-in order of CVEI. This not only simplifies determining the role of each plug-in, it also allows Pyblish to make some basic assumptions about your plug-ins, such as when to stop.

These *constants* are nothing more than integer numbers.

```yaml
CollectorOrder: 0
ValidatorOrder: 1
ExtractorOrder: 2
```

Together, they form the first three letters of "CVEI". We'll look at the last letter next.


# CVEI II

Sometimes the file-management part of extraction is better kept separate.

In this example, we will:

1. Simulate an Autodesk Maya environment
2. Extract some data from it
3. Integrate this data with a server
4. Without making reference to the simulated environment

Our environment.

```python
import sys

disk = {}
server = {}

class cmds:
  @staticmethod
  def ls(type, assemblies):
    return maya.scene.keys()

  @staticmethod
  def file(path, exportSelected):
    disk[path] = maya.scene[maya.selected]

  @staticmethod
  def select(node):
    maya.selected = node

class maya:
  selected = None
  cmds = cmds
  scene = {
    "john": 0xb3513451, # Binary
    "door": 0x516b481f,
  }

sys.modules["maya"] = maya
```

We can now `from maya import cmds`, which we will use during collection and extraction.

```python
import pyblish.api
from maya import cmds

class CollectInstances(pyblish.api.ContextPlugin):
  order = pyblish.api.CollectorOrder

  def process(self, context):
    for name in cmds.ls(type="transform", assemblies=True):
      context.create_instance(name)

class ExtractInstances(pyblish.api.InstancePlugin):
  order = pyblish.api.ExtractorOrder

  def process(self, instance):
    # 1. Compute temporary output path
    name = instance.data["name"]
    transient_path = "c:\temp\%s.mb" % name

    # 2. Perform serialisation
    cmds.select(name)
    cmds.file(transient_path, exportSelected=True)

    # 3. Store reference for subsequent plug-ins
    instance.data["transientDest"] = transient_path
```

Now let's integrate the data from `temp` on `disk` into our `server`.

```python
class IntegrateInstances(pyblish.api.InstancePlugin):

  order = pyblish.api.IntegratorOrder

  def process(self, instance):
    transient_dest = instance.data["transientDest"]
    permanent_dest = "/instances/%s.mb" % instance
    server[permanent_dest] = disk[transient_dest]
```

Putting it all together, this is the full source code.

```python
import sys

disk = {}
server = {}

class cmds:
  @staticmethod
  def ls(type, assemblies):
    return maya.scene.keys()

  @staticmethod
  def file(path, exportSelected):
    disk[path] = maya.scene[maya.selected]

  @staticmethod
  def select(node):
    maya.selected = node

class maya:
  selected = None
  cmds = cmds
  scene = {
    "john": 0xb3513451, # Binary
    "door": 0x516b481f,
  }

sys.modules["maya"] = maya

import pyblish.api
from maya import cmds

class CollectInstances(pyblish.api.ContextPlugin):
  order = pyblish.api.CollectorOrder

  def process(self, context):
    for name in cmds.ls(type="transform", assemblies=True):
      context.create_instance(name)

class ExtractInstances(pyblish.api.InstancePlugin):
  order = pyblish.api.ExtractorOrder

  def process(self, instance):
    # 1. Compute temporary output path
    name = instance.data["name"]
    transient_path = "c:\temp\%s.mb" % name

    # 2. Perform serialisation
    cmds.select(name)
    cmds.file(transient_path, exportSelected=True)

    # 3. Store reference for subsequent plug-ins
    instance.data["transientDest"] = transient_path

class IntegrateInstances(pyblish.api.InstancePlugin):
  order = pyblish.api.IntegratorOrder

  def process(self, instance):
    transient_dest = instance.data["transientDest"]
    permanent_dest = "/instances/%s.mb" % instance
    server[permanent_dest] = disk[transient_dest]


pyblish.api.register_plugin(CollectInstances)
pyblish.api.register_plugin(ExtractInstances)
pyblish.api.register_plugin(IntegrateInstances)

import pyblish.util
pyblish.util.publish()
print disk
print server
# {'c:\temp\\john.mb': 3008443473L, 'c:\temp\\door.mb': 1365985311}
# {'/instances/john.mb': 3008443473L, '/instances/door.mb': 1365985311}
```

The key point to take away from this example is that file-management is independent of serialisation.


# CVEI III

As a final note - CVEI is a convention for which order to pick for a typical task.

The advantage of using these particular orders isn't as much functional as it is conventional. In fact, the order you assign is just an integer value.

```python
import pyblish.api

class MyValidator(pyblish.api.ContextPlugin):
  order = pyblish.api.ValidatorOrder  # == 1
```

The benefits of sticking with CVEI are two-fold.

1. The ordering provides a common language with which to discuss publishing.
2. And under the hood, they allow Pyblish to make assumptions about your plug-ins, such as when to abort.

This language fuels our community, and the assumptions are what fuels the mechanics of Pyblish.

With this in mind, saving plug-ins are typically named after what they are.

```yaml
plugins
├── collect_my_assets.py
├── validate_normals.py
├── extract_alembic.py
└── integrate_ftrack.py
```


# CVEI IV

Sometimes you need more control over ordering, and that's when it can be useful to know that you can *offset* the built-in orders.

```python
import pyblish.api

class PreCollector(pyblish.api.ContextPlugin):
  order = pyblish.api.CollectorOrder - 0.1

  def process(self, context):
    context.create_instance("SpecialInstance")


class Collector(pyblish.api.ContextPlugin):
  order = pyblish.api.CollectorOrder

  def process(self, context):
    special_instance = context["SpecialInstance"]
    special_instance.data["specialData"] = 42
```

You can offset any plug-in from negative 0.499.. to positive 0.499.. A value beyond that will inadvertently transition your plug-in into the next built-in order.

**Ranges**

```yaml
-0.5 to 0.499.. = Collection
0.5 to 1.499.. = Validation
1.5 to 2.499.. = Extraction
2.5 to 3.499.. = Integration
```


# Data

We've briefly touched upon the fact that `Context` and `Instance` objects can store and retrieve `data`.

```python
import pyblish.api
context = pyblish.api.Context()
context.data["key"] = "value"
```

Data is a regular Python dictionary stored in the `Context` and is meant for sharing information amongst plug-ins, whereas data in an `Instance` is specific to the instance. In both cases, data uses `mixedCase` by convention, whereas standard Python variables use `snake_case`.

```python
# wrong
context.data["my_key"] = True

# right
context.data["myKey"] = True
```


# Report I

Publishing is about finding problems and the only way to find them is by bringing them to the surface, to visualise them. So in this example, we'll have a look at how you can do just that.

We can use the results produced during publishing to pretty-print ourselves a report of how things went. This is what we will be producing in this example.

```bash
Success   Plug-in                                   -> Instance
----------------------------------------------------------------------
1         CollectCaptainAmerica                     -> None
0         ValidateCaptainAmerica                    -> Captain America
```

Let's dive into the plug-ins now.

```python
import pyblish.api

class CollectCaptainAmerica(pyblish.api.ContextPlugin):
  order = pyblish.api.CollectorOrder

  def process(self, context):
    context.create_instance("Captain America", isHero=False)

class ValidateCaptainAmerica(pyblish.api.InstancePlugin):
  order = pyblish.api.ValidatorOrder

  def process(self, instance):
    # Any raised exception will mark a plug-in as failed
    assert instance.data.get("isHero") == True, "%s must be a hero" % instance

pyblish.api.register_plugin(CollectCaptainAmerica)
pyblish.api.register_plugin(ValidateCaptainAmerica)

import pyblish.util
context = pyblish.util.publish()
```

With `context` at hand, we can now format the results using the `results` dictionary stored within.

```python
results = context.data.get("results")
header = "{:<10}{:<40} -> {}".format("Success", "Plug-in", "Instance")
result = "{success:<10}{plugin.__name__:<40} -> {instance}"
results = "\n".join(result.format(**r) for r in results)
report = """
{header}
{line}
{results}
"""
print(report.format(header=header,
                    results=results,
                    line="-" * 70))
```

The actual output is this.

```bash
Success   Plug-in                                   -> Instance
----------------------------------------------------------------------
1         CollectCurrentWorkingDirectory            -> None
1         CollectCaptainAmerica                     -> None
1         CollectCurrentUser                        -> None
0         ValidateCaptainAmerica                    -> Captain America
```

The plug-ins `CollectCurrentWorkingDirectory` and `CollectCurrentUser` are included by default with Pyblish and adds the following data to the context.

```yaml
cwd: The current working directory at the time of publish
user: The currently logged on user
```


# Report II

In addition to visualising which plug-in processed which instance, it would also be helpful to visualise error messages (if any). So that's what we'll do in this example.

```python
Success   Plug-in                                   -> Instance
----------------------------------------------------------------------
1         CollectCaptainAmerica                     -> None
0         ValidateCaptainAmerica                    -> Captain America
          +-- EXCEPTION: Captain America must be a hero
```

Building from our previous example, this is how to format it in order to end up with the above.

```python
header = "{:<10}{:<40} -> {}".format("Success", "Plug-in", "Instance")
result = "{success:<10}{plugin.__name__:<40} -> {instance}"
error = "{:<10}+-- EXCEPTION: {:<70}"

results = list()
for r in context.data["results"]:
  results.append(result.format(**r))
  if r["error"]:
    results.append(error.format("", r["error"]))

report = """
{header}
{line}
{results}
"""
print(report.format(header=header,
                    results="\n".join(results),
                    line="-" * 70))
```

Now all error messages are neatly printed in a tree-like fashion underneath each relevant result.


# Report III

Exception messages are useful, but what would make it even more useful is progress messages. Something that can tell us more about the *why* of an exception.

Progress messages are made using the standard Python logging mechanism.

Here is what we will be developing in this example.

```bash
1         CollectCaptainAmerica                     -> None
0         ValidateCaptainAmerica                    -> Captain America
          +-- INFO: Entering validator..
          +-- INFO: About to validate instance: "Captain America"
          +-- WARNING: Something is not right, aborting..
          +-- EXCEPTION: Captain America must be a hero
```

For this, we will utilise the built-in logger.

```python
class ValidateCaptainAmerica(pyblish.api.InstancePlugin):
  order = pyblish.api.ValidatorOrder

  def process(self, instance):
    self.log.info("Entering validator..")
    self.log.info("About to validate instance: %s" % instance)

    if not instance.data["isHero"]:
        self.log.warning("Something is not right.. aborting")
        raise Exception("%s must be a hero" % instance)
```

Now it's time to format the results.

```python
header = "{:<10}{:<40} -> {}".format("Success", "Plug-in", "Instance")
result = "{success:<10}{plugin.__name__:<40} -> {instance}"
error = "{:<10}+-- EXCEPTION: {:<70}"
record = "{:<10}+-- {level}: {message:<70}"

results = list()
for r in context.data["results"]:
  # Format summary
  results.append(result.format(**r))

  # Format log records
  for lr in r["records"]:
    results.append(record.format("", level=lr.levelname, message=lr.msg))

  # Format exception (if any)
  if r["error"]:
    results.append(error.format("", r["error"]))

report = """
{header}
{line}
{results}
"""
print(report.format(header=header,
                    results="\n".join(results),
                    line="-" * 70))
```

Putting it all together, here is the full source code.

```python
import pyblish.api

class CollectCaptainAmerica(pyblish.api.ContextPlugin):
  order = pyblish.api.CollectorOrder

  def process(self, context):
    context.create_instance("Captain America", isHero=False)

class ValidateCaptainAmerica(pyblish.api.InstancePlugin):
  order = pyblish.api.ValidatorOrder

  def process(self, instance):
    self.log.info("Entering validator..")
    self.log.info("About to validate instance: %s" % instance)

    if not instance.data.get("isHero"):
        self.log.warning("Something is not right.. aborting")
        raise Exception("%s must be a hero" % instance)

pyblish.api.register_plugin(CollectCaptainAmerica)
pyblish.api.register_plugin(ValidateCaptainAmerica)

import pyblish.util
context = pyblish.util.publish()

header = "{:<10}{:<40} -> {}".format("Success", "Plug-in", "Instance")
result = "{success:<10}{plugin.__name__:<40} -> {instance}"
error = "{:<10}+-- EXCEPTION: {:<70}"
record = "{:<10}+-- {level}: {message:<70}"

results = list()
for r in context.data["results"]:
  # Format summary
  results.append(result.format(**r))

  # Format log records
  for lr in r["records"]:
    results.append(record.format("", level=lr.levelname, message=lr.msg))

  # Format exception (if any)
  if r["error"]:
    results.append(error.format("", r["error"]))

report = """
{header}
{line}
{results}
"""
print(report.format(header=header,
                    results="\n".join(results),
                    line="-" * 70))
```


# Report IV

Visualising results right away is useful, but sometimes you need to go back in time. So how about instead of printing the report, we write it to a file? Better yet, why not develop a plug-in to write the file such that it happens along with every publish?

```bash
└── logs
    ├── 20150612-112011.txt
    ├── 20150612-112021.txt
    ├── 20150612-112051.txt
    ├── 20150612-112118.txt
    └── 20150612-112158.txt
```

Let's get cracking.

```python
import os
import datetime
import pyblish.api

class ArchiveValidation(pyblish.api.ContextPlugin):
  # Run after all validators have finished
  order = pyblish.api.ValidatorOrder + 0.1

  def process(self, context):
    formatted_results = self.format_results(context)

    # Compute output directory
    date = datetime.datetime.today().ctime()
    date = pyblish.api.format_filename(date)
    output_dir = os.path.join(os.path.expanduser("~"), "logs")
    output_path = os.path.join(output_dir, date + ".txt")

    # Write to disk
    if not os.path.exists(output_dir):
      os.makedirs(output_dir)

    with open(output_path, "w") as f:
      # E.g. c:\users\marcus\Documents\logs\20150612-110000.txt
      f.write(formatted_report)
```

We copy/paste the formatting from our previous example and chuck it into `format_results`, this time returning as opposed to printing.

```python
  def format_results(self, context):
    header = "{:<10}{:<40} -> {}".format("Success", "Plug-in", "Instance")
    result = "{success:<10}{plugin.__name__:<40} -> {instance}"
    error = "{:<10}+-- EXCEPTION: {:<70}"
    record = "{:<10}+-- {level}: {message:<70}"

    results = list()
    for r in context.data["results"]:
      # Format summary
      results.append(result.format(**r))

      # Format log records
      for lr in r["records"]:
        results.append(record.format("", level=lr.levelname, message=lr.msg))

      # Format exception (if any)
      if r["error"]:
        results.append(error.format("", r["error"]))

    report = """
{header}
{line}
{results}
    """

    return report.format(
      header=header,
      results="\n".join(results),
      line="-" * 70))
```

Putting it all together, here is the full source code.

```python
import os
import datetime
import pyblish.api

class CollectCaptainAmerica(pyblish.api.ContextPlugin):
  order = pyblish.api.CollectorOrder

  def process(self, context):
    context.create_instance("Captain America", isHero=False)

class ValidateCaptainAmerica(pyblish.api.InstancePlugin):
  order = pyblish.api.ValidatorOrder

  def process(self, instance):
    self.log.info("Entering validator..")
    self.log.info("About to validate instance: %s" % instance)

    if not instance.data["isHero"]:
        self.log.warning("Something is not right.. aborting")
        raise Exception("%s must be a hero" % instance)

class ArchiveValidation(pyblish.api.ContextPlugin):
  # Run after all validators have finished
  order = pyblish.api.ValidatorOrder + 0.1

  def process(self, context):
    formatted_results = self.format_results(context)

    # Compute output directory
    date = datetime.datetime.today().ctime()
    date = pyblish.api.format_filename(date)
    output_dir = os.path.join(os.path.expanduser("~"), "logs")
    output_path = os.path.join(output_dir, date + ".txt")

    # Write to disk
    if not os.path.exists(output_dir):
      os.makedirs(output_dir)

    with open(output_path, "w") as f:
      # E.g. c:\users\marcus\Documents\logs\20150612-110000.txt
      f.write(formatted_results)

    # Print rather than log, as this plug-in
    # won't be included in the results.
    print("Outputted to: %s" % output_path)

  def format_results(self, context):
    header = "{:<10}{:<40} -> {}".format("Success", "Plug-in", "Instance")
    result = "{success:<10}{plugin.__name__:<40} -> {instance}"
    error = "{:<10}+-- EXCEPTION: {:<70}"
    record = "{:<10}+-- {level}: {message:<70}"

    results = list()
    for r in context.data["results"]:
      # Format summary
      results.append(result.format(**r))

      # Format log records
      for lr in r["records"]:
        results.append(record.format("", level=lr.levelname, message=lr.msg))

      # Format exception (if any)
      if r["error"]:
        results.append(error.format("", r["error"]))

    report = """
{header}
{line}
{results}
    """

    return report.format(
      header=header,
      results="\n".join(results),
      line="-" * 70)


pyblish.api.register_plugin(CollectCaptainAmerica)
pyblish.api.register_plugin(ValidateCaptainAmerica)
pyblish.api.register_plugin(ArchiveValidation)

import pyblish.util
pyblish.util.publish()
```

And the results can be found in your home directory.


# Report V

Writing a report and archiving it is all well and good, but what would be really amazing is if we could visualise history with plots and charts. So let's do that now.

![image](https://cloud.githubusercontent.com/assets/2152766/12515462/626c08a6-c120-11e5-8036-74b0a6e6f96b.png)

For this example, I'll be using [pygal](http://pygal.org/) so go ahead and install this if you would like to follow along. Reusing `ArchiveValidators` from the previous example, let's make some history.

```python
import os
import random
import datetime
import pyblish.api

class FlipCoin(pyblish.api.ContextPlugin):
  def process(self, context):
    if random.random() > 0.5:
        raise Exception("Failed")

class ArchiveValidation(pyblish.api.ContextPlugin):
  ...

pyblishOpi.register_plugin(FlipCoin)
pyblish.api.register_plugin(ArchiveValidation)

import pyblish.util
pyblish.util.publish()
```

Run this at least 10 times so we have enough reports to work with. Next we will retrieve the last 10 publishes and draw a graph representing which time was successful and which was not.

Here is the graph-drawing source code.

```python
import os
import pygal

input_path = os.path.join(os.path.expanduser("~"), "logs")
output_path = os.path.join(input_path, "graph.svg")

results = list()
for fname in os.listdir(input_path)[-10:]:
    abspath = os.path.join(input_path, fname)
    with open(abspath) as f:
        lines = f.readlines()[2:]  # Top two are headers
        results.append([fname, any(line.startswith("0") for line in lines)])

chart = pygal.StackedLine(fill=True,
                          interpolate='cubic',
                          style=pygal.style.LightSolarizedStyle)

chart.title = 'Successful publishes over time'
chart.x_labels = [str(results.index(r)) for r in results]
chart.add("Publish", [r[1] for r in results])
chart.render_to_file(output_path)
```

Putting it all together, here is the final source code.

```python
import os
import random
import datetime

import pygal
import pyblish.api

class FlipCoin(pyblish.api.ContextPlugin):
  def process(self, context):
    if random.random() > 0.5:
        raise Exception("Failed")

class ArchiveValidation(pyblish.api.ContextPlugin):
  # Run after all validators have finished
  order = pyblish.api.ValidatorOrder + 0.1

  def process(self, context):
    formatted_results = self.format_results(context)

    # Compute output directory
    date = datetime.datetime.today().strftime("%Y%m%d-%H%M%S")
    output_dir = os.path.join(os.path.expanduser("~"), "logs")
    output_path = os.path.join(output_dir, date + ".txt")

    # Write to disk
    if not os.path.exists(output_dir):
      os.makedirs(output_dir)

    with open(output_path, "w") as f:
      # E.g. c:\users\marcus\Documents\logs\20150612-110000.txt
      f.write(formatted_results)

    # Print rather than log, as this plug-in
    # won't be included in the results.
    print("Outputted to: %s" % output_dir)
    context.data["archiveDir"] = output_dir

  def format_results(self, context):
    header = "{:<10}{:<40} -> {}".format("Success", "Plug-in", "Instance")
    result = "{success:<10}{plugin.__name__:<40} -> {instance}"
    error = "{:<10}+-- EXCEPTION: {:<70}"
    record = "{:<10}+-- {level}: {message:<70}"

    results = list()
    for r in context.data["results"]:
      # Format summary
      results.append(result.format(**r))

      # Format log records
      for lr in r["records"]:
        results.append(record.format("", level=lr.levelname, message=lr.message))

      # Format exception (if any)
      if r["error"]:
        results.append(error.format("", r["error"]))

    report = """
{header}
{line}
{results}
    """

    return report.format(
      header=header,
      results="\n".join(results),
      line="-" * 70)

class PlotArchive(pyblish.api.ContextPlugin):
    # Run after archival
    order = pyblish.api.ValidatorOrder + 0.2

    def process(self, context):
        input_path = context.data["archiveDir"]
        output_path = os.path.join(input_path, "graph.svg")

        results = list()
        for fname in os.listdir(input_path)[-10:]:
            abspath = os.path.join(input_path, fname)
            with open(abspath) as f:
                lines = f.readlines()[2:]  # Top two are headers
                results.append([fname, any(line.startswith("0") for line in lines)])

        chart = pygal.StackedLine(fill=True,
                                  interpolate='cubic',
                                  style=pygal.style.LightSolarizedStyle)

        chart.title = 'Successful publishes over time'
        chart.x_labels = [str(results.index(r)) for r in results]
        chart.add("Publish", [r[1] for r in results])
        chart.render_to_file(output_path)


pyblish.api.register_plugin(FlipCoin)
pyblish.api.register_plugin(ArchiveValidation)
pyblish.api.register_plugin(PlotArchive)

import pyblish.util
pyblish.util.publish()
# Outputted to: C:\Users\marcus\logs
```

Now, for every publish, results are archived and the graph updated. Go ahead, publish a few times and keep on refreshing your browser whilst looking at the graph. Animation is an exercise left to the reader.


# Callback I

Sometimes you may be interested in an event taking place at a particular location during processing.

```python
import pyblish.api

def on_published(context):
  has_error = any(result["error"] is not None for result in context.data["results"])
  print("Publishing %s" % ("failed" if has_error else "finished"))

pyblish.api.register_callback("published", on_published)
```

This will cause `"Publishing finished"` or `"Publishing failed"` to be printed upon completed publishing, based on whether it completed successfully or not.

The arguments provided by the signal must match those in your callback. See [here](https://pyblish.gitbooks.io/api/content/pages/Events.html) for a complete list of signals emitted in Pyblish.

* [Events](https://pyblish.gitbooks.io/api/content/pages/Events.html)


# Callback II

You can emit and catch your own signals.

```python
import pyblish.api

class MyCollector(pyblish.api.ContextPlugin):
    order = pyblish.api.CollectorOrder

    def process(self, context):
        pyblish.api.emit("myEvent", data="myData")
```

You can then catch this signal anywhere in your program.

```python
import pyblish.api

def on_my_event(data):
  print(data)

pyblish.api.register_callback("myEvent", on_my_event)
```

Callbacks can be useful for, amongst other things:

* Deep customisation
* Advanced logging
* Tighter integration

On a technical note, callbacks are run on the main thread and block until finished. This means that if for example one of your callbacks would take 10 seconds to finish, publishing overall will take 10 seconds longer. On the upside, it also means that the Context will accurately reflect the current state at the time of a signal emitting.


# Callback III

As a final, practical example of callbacks, consider the following.

```python
import pyblish.api
from maya import cmds

def on_instance_toggled(instance, new_value, old_value):
  node = instance.data["nodeName"]
  cmds.setAttr(node + ".publish", new_value)
```

Assuming that the publishable state of your instance in Maya is determined by the user-defined attribute "publish", this callback will cause the GUI to persist the toggled state of any given instance directly in your scene.

Now, both when you reset Pyblish, reload your scene or even save the file and restart Maya, the state at which it was visually toggled will persist.


# Logging

## Logging

Pyblish does not produce any output unless you explicitly tell it to.

If you enable the `DEBUG` level whilst developing plug-ins, Pyblish can tell you a few things about what is going on.

```python
import logging
import pyblish.api

# Step 0, this doesn't produce any warnings..
pyblish.api.discover()

# Step 1, bring out the debug level messages
logging.getLogger("pyblish").setLevel(logging.DEBUG)

# Step 2, unless there already is a handler, one must be setup.
logging.basicConfig()

# Step 3, done
pyblish.api.discover()
# DEBUG:pyblish.plugin:Skipped: "collect_myplugin" (name 'pybddlish' is not defined)
```

The handler can be customised to tailor the format and contents of the messages, like a timestamp and what not.

Read up on standard Python logging and corresponding handlers for more details.

## Formatting

In the above example, messages weren't very pretty. You can control the formatting of messages like this.

```python
# Fetch the logger Pyblish uses for all of its messages
log = logging.getLogger("pyblish")

# Do what `basicConfig` does, except explicitly
# and with control over where and how messages go
hnd = logging.StreamHandler()
fmt = logging.Formatter('%(message)s')
hnd.setFormatter(fmt)
log.addHandler(hnd)

pyblish.api.discover()
# Skipped: "collect_myplugin" (name 'pybddlish' is not defined)
```

That `%(message)s` is one of the many variables available during formatting, see here for a full list.

* <https://docs.python.org/2/library/logging.html#logrecord-attributes>

## Gotchas

* `basicConfig` will register a handler too, and it's possible to have many. See `log.handlers` for the ones currently registered, it's a plain Python `list` that you can clear to get rid of them.
* Autodesk Maya is special, it runs the equivalent of `basicConfig` under-the-hood at startup. Which means that you always see these messages whether you want to or not. You can remove handlers, and edit them despite this.
* Standalone Python doesn't have any handlers registered, and needs something like the above.
* See or start a conversation about this [on the forums](https://forums.pyblish.com/t/error-handling-on-plugins/596/3)


# Filtering

Filtering is handy when more control over your plugins is needed.

After registering a callback function, it'll run on all registered plugins during discovery. This is useful to overwrite the settings of registered plugins.

For example, here's how we can overwrite a hardcoded export path so as to reuse a plugin for another project, without having to write and register a new plugin.

```python
import pyblish.api

# The original plug-in
class MyExportPlugin(pyblish.api.ContextPlugin):
  export_path = "C:/project_alpha/models"
  def process(self, context):
    print("Exported to: '%s'" % self.export_path)

pyblish.api.register_plugin(MyExportPlugin)

# Our custom "filter"
def export_path_filter(plugins):
  for plugin in plugins:
    if hasattr(plugin, 'export_path'):
      plugin.export_path = 'D:/project_beta/models'

pyblish.api.register_discovery_filter(export_path_filter)

import pyblish.util
pyblish.util.publish()
# Exported to 'to:D:/project_beta/models'
```

You can register a filter using `api.register_discovery_filter.`

Note that functions stay registered and won't update when you re-register them with the same name. You have to either deregister the original one, or use `pyblish.api.deregister_all_discovery_filters()` to unregister all filters.


# Next Steps

So you've completed this guide on getting started with Pyblish., now what? Here are some options.

* Explore [Pyblish Starter](http://pyblish.com/pyblish-starter), a minimal yet complete example pipeline developed with Pyblish.
* Continue learning from [intermediate resources](http://learn.pyblish.com) (see bottom of page).
* Join the [forums](http://forums.pyblish.com/) and delve into advanced topics, such as [Pyblish Magenta](http://forums.pyblish.com/t/pyblish-magenta/79).
* Read the [Developer Guide](https://pyblish.gitbooks.io/developer-guide/content/) to gain an understanding of the underpinnings of the framework.
* Read the API Documentation at [api.pyblish.com](http://api.pyblish.com/).
* And most importantly, enjoy!

Yours truly,\
&#x20;The Pyblish team.


# Introduction

![image](https://cloud.githubusercontent.com/assets/2152766/12489260/51843d38-c067-11e5-93c8-7b96c30ed37a.png)

Welcome to the Pyblish By Example.

Choose a version of the library you would like to learn to the left.


# v1.2


# v1.3


# v1.4


