You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 380b123
Browse filesBrowse the repository at this point in the historyBrowse files
* Add CaptureHolder docs
* Update API docs on CaptureHolder
* Document the new view annotations on Pipelines
Co-authored-by: Greg Caporaso <jgcap@fastmail.com>
Copy file name to clipboardExpand all lines: book/back-matter/glossary.md
+4Lines changed: 4 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -23,6 +23,10 @@ Artifact class
23
23
Artifact API
24
24
See {term}`Python 3 API`.
25
25
26
+
CaptureHolder
27
+
A class used as the Python type annotation for {term}`Parameters <Parameter>` whose default value is determined algorithmically (e.g., random number generator seeds).
28
+
Using this class ensures that the algorithmically set value is tracked in {term}`Provenance`.
29
+
26
30
Collection
27
31
An ordered list of `key: value` pairs. Think of an ordered [Python dictionary](https://docs.python.org/3/tutorial/datastructures.html#dictionaries).
28
32
These can be used as {term}`Input`, {term}`Parameter`, and {term}`Output` {term}`Types <Type>`.
Copy file name to clipboardExpand all lines: book/plugins/how-to-guides/create-register-pipeline.md
+62-19Lines changed: 62 additions & 19 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,32 +7,58 @@ This is accomplished by stitching together one or more `Methods` and/or `Visuali
7
7
8
8
Defining a function that can be registered as a `Pipeline` is very similar to defining one that can be registered as a `Method` with a few distinctions.
9
9
10
-
First, `Pipelines` do not use function annotations and instead receive `Artifact` objects as input and return `Artifact` and/or `Visualization` objects as output.
10
+
First, `Pipelines` are not required to use function annotations unless you are using the {term}`CaptureHolder` API documented [here](howto-track-the-value-of-auto-params-in-provenance). Instead they implicitly receive `Artifact` objects as input and return `Artifact` and/or `Visualization` objects as output.
11
+
12
+
If you choose to use function annotations on a `Pipeline` you must annotate all inputs, parameters, outputs, and the special `ctx` argument (described below). The parameters follow the same [mypy](http://mypy-lang.org/) syntax as `Methods` and `Visualizers`; however, the inputs and outputs are annotated simply as `Artifact` or `Visualization` in the case of singles or `list[Artifact]`, `dict[str, Artifact]`, `list[Visualization]`, or `dict[str, Visualization]` in the case of `Collections`. `ctx` must use `IContext` as its annotation.
11
13
12
14
Second, `Pipelines` must have `ctx` as their first parameter, which provides the following API:
13
15
-`ctx.get_action(plugin: str, action: str)`: returns a *sub-action* that can be called like a normal Artifact API call.
14
16
-`ctx.make_artifact(type, view, view_type=None)`: this has the same behavior as `Artifact.import_data`. It is wrapped by `ctx` for pipeline book-keeping.
15
17
16
-
Let's take a look at [`q2_diversity.core_metrics`](https://github.com/qiime2/q2-diversity/blob/99a0ccaaec14838b95845dbfe57f874d092b65c7/q2_diversity/_core_metrics.py#L10) for an example of a function that we can register as a `Pipeline`:
18
+
Let's take a look at [`q2_diversity.core_metrics`](https://github.com/qiime2/q2-diversity/blob/3fe491062b8a72939111ff66b2f4aeab8c12b16d/q2_diversity/_core_metrics.py#L14) for an example of a function that we can register as a `Pipeline`:
@@ -61,7 +88,7 @@ A description of this output should be included in `output_descriptions`
61
88
Citations do not need to be added for the pipeline unless unique citations are required for the pipeline that are not appropriate for the underlying `Methods` and `Visualizers` that it calls.
62
89
Citations for these underlying actions are automatically logged in citation provenance for this pipeline.
63
90
64
-
As an example for registering a `Pipeline`, we can look at `q2_diversity.core_metrics` (find the original source [here](https://github.com/qiime2/q2-diversity/blob/99a0ccaaec14838b95845dbfe57f874d092b65c7/q2_diversity/plugin_setup.py#L494)):
91
+
As an example for registering a `Pipeline`, we can look at `q2_diversity.core_metrics` (find the original source [here](https://github.com/qiime2/q2-diversity/blob/3fe491062b8a72939111ff66b2f4aeab8c12b16d/q2_diversity/plugin_setup.py#L496-L565)):
# Track the value of random seeds and other automated parameter settings in data provenance
3
+
4
+
There are cases where you might want an {term}`Action` to take a {term}`Parameter` that can be set to an explicit value by the user, or be algorithmically determined by the {term}`Action` itself.
5
+
The most common use case for this is a random seed, where you may allow the user to pass a set seed (usually because they are trying to reproduce a previous result) or have the {term}`Action` set a random seed for general use (usually the default approach, achieved by setting the default value to `None`).
6
+
This creates a challenge for reproducibility: if `None` is passed into the {term}`Action`, then `None` will be recorded in the output's {term}`Provenance`.
7
+
The value that the seed was set to internally is lost, making it impossible to exactly reproduce a prior {term}`Action` execution because the random seed is not known.
8
+
9
+
This problem is solved by using the {term}`CaptureHolder` object.
10
+
11
+
```{note}
12
+
The {term}`CaptureHolder` only works if the value of the algorithmically set parameter is actually accessible in the Python implementation of the function.
13
+
If you are passing a sentinel value into an underlying tool (e.g., R code that is used under the hood by your {term}`Action`) which sets its value, that value will be inaccessible in the {term}`Provenance`.
14
+
```
15
+
16
+
The {term}`Action` registration is unchanged:
17
+
18
+
```python
19
+
my_plugin.methods.register_function(
20
+
function=method_with_random_seed,
21
+
inputs={},
22
+
parameters={
23
+
'random_seed': Int
24
+
},
25
+
outputs=[('seed', SingleInt)],
26
+
name='Takes a random seed',
27
+
description='Takes an integer as a random seed and returns that same'
28
+
' integer. If no integer is provided, it generates one at'
29
+
' random and captures that randomly generated integer in'
30
+
' provenance.'
31
+
)
32
+
```
33
+
34
+
What changes is the implementation of the underlying Python function:
# Use the resolved integer value (guaranteed to never be None here)
47
+
my_value = my_function(random_int)
48
+
49
+
return my_value
50
+
```
51
+
52
+
The following rules must be followed to use the {term}`CaptureHolder` object:
53
+
54
+
1. The type annotation on the {term}`CaptureHolder` {term}`Parameter` must be `CaptureHolder[T]`, where `T` is the Python view type that corresponds to the QIIME 2 {term}`Semantic Type` used for the {term}`Parameter` at registration (e.g., `CaptureHolder[int]` for a parameter registered as `Int`).
55
+
2. The default value of the {term}`CaptureHolder` {term}`Parameter` must be `None`.
56
+
3.`CaptureHolder.get_or_set(<instance>, <callable>)` must be called exactly once per {term}`CaptureHolder` {term}`Parameter`, before the parameter is used. The return value is the resolved value that should be used in place of the {term}`CaptureHolder` going forward.
57
+
58
+
`CaptureHolder.get_or_set` takes two arguments: the {term}`CaptureHolder` {term}`Parameter` instance, and a zero-argument callable that generates a value when one is needed.
59
+
If the user passed `None`, the callable is invoked and its return value is used.
60
+
If the user passed an explicit value, that value is returned directly.
61
+
In both cases the resolved value is written back into the {term}`Action`'s {term}`Provenance` as though it had been passed in by the user originally.
62
+
63
+
```{note}
64
+
When calling the underlying function directly during testing (rather than through QIIME 2), `CaptureHolder.get_or_set` behaves correctly whether the parameter is a `CaptureHolder` instance, a plain value, or `None`.
65
+
This means you can write unit tests that call the function directly without any special handling.
0 commit comments