-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #97 from feltech/work/91-substitutionNotebook
Add string substitution notebook
- Loading branch information
Showing
1 changed file
with
247 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,247 @@ | ||
{ | ||
"cells": [ | ||
{ | ||
"metadata": {}, | ||
"cell_type": "markdown", | ||
"source": "# String token substitution", | ||
"id": "4c4a5a5ed39eb2e1" | ||
}, | ||
{ | ||
"metadata": {}, | ||
"cell_type": "markdown", | ||
"source": [ | ||
"When a host application `resolve`s a string from the manager, it might be that the string contains placeholder tokens, which must be substituted with values held by the host application before the string can be used. Similarly, the host may publish an entity with a string property that contains placeholder tokens, which the manager must substitute before the string can be used.\n", | ||
"\n", | ||
"This substitution mechanism is commonly used to represent dynamic content in an otherwise repeating pattern. For example, the path of images that make up an image sequence may be communicated as `path/to/image.{frame}`, expecting the host to substitute the frame number they desire, derived from their applications timeline or other selector.\n", | ||
"\n", | ||
"Given that, typically, the host application and manager plugin are independently developed, this presents a few problems\n", | ||
"\n", | ||
"* Hosts/managers need a way to find out if a string needs substitution at all.\n", | ||
"* Hosts/managers need a shared understanding of how placeholder tokens are encoded within a string.\n", | ||
"* Hosts/managers need to know which tokens are possible/allowed within a given string.\n", | ||
"* Hosts/managers need the ability to substitute values into the placeholder tokens in a string.\n", | ||
"\n", | ||
"OpenAssetIO solves these problems by a combination of documentation and a handy `substitute` utility function. The documentation of individual traits and their properties will tell hosts/managers whether they can place substitution tokens in a string, what tokens are allowed, and what values to map to each token. A common OpenAssetIO-dictated format for substitution tokens ensures that hosts/managers know how placeholders should be presented within strings. Once the host/manager has gathered the necessary values, they can use the `substitute` utility function to perform the substitution." | ||
], | ||
"id": "feec93d227a1e95e" | ||
}, | ||
{ | ||
"metadata": {}, | ||
"cell_type": "markdown", | ||
"source": [ | ||
"\n", | ||
"## Example - LocatableContent\n", | ||
"\n", | ||
"Before anything else, a host needs to know if they need to account for substitutions. \n", | ||
"\n", | ||
"The LocatableContent trait has an `\"isTemplated\"` property, which, if true, indicates that the `\"location\"` property contains substitution tokens.\n", | ||
"\n", | ||
"The `\"isTemplated\"` documentation says\n", | ||
"\n", | ||
"> The URL in the location property contains variables (post decoding) that must be expanded before any loading is attempted.\n", | ||
">\n", | ||
"> Variables use the OpenAssetIO syntax (eg: \"image.{frame:04}.{view}.exr\") see the OpenAssetIO documentation for more details.\n", | ||
">\n", | ||
"> The following well-known variables are defined within the MediaCreation ecosystem, and must be used where applicable to any specific workflow:\n", | ||
"> - frame: An integer frame number for the current time.\n", | ||
"> - view: A string representing the current view (eg. \"left\").\n", | ||
"\n", | ||
"This implies that a file name of `\"image.{frame}.{view}.exr\"` should be resolved to `\"image.0001.left.exr\"` if the frame number is 1 and the view is \"left\"." | ||
], | ||
"id": "e1cf4ef1d84c6d3c" | ||
}, | ||
{ | ||
"metadata": {}, | ||
"cell_type": "markdown", | ||
"source": [ | ||
"## The OpenAssetIO substitution syntax\n", | ||
"\n", | ||
"In general, the substitution tokens are of the form `{variable:format}` where `variable` is the name of the variable and `format` is a format string. The `format` part is optional. \n", | ||
"\n", | ||
"For maximum cross language compatibility, only basic substitutions are officially supported. Specifically\n", | ||
"\n", | ||
"* Substitution placeholders with no format string, e.g. `{frame}`.\n", | ||
"* Placeholders for integers with zero padding, e.g. `{frame:04}`.\n", | ||
"\n", | ||
"This may be revised in the future.\n", | ||
"\n", | ||
"The syntax is compatible with the C++ [libfmt](https://fmt.dev/latest/syntax.html) library, which in turn is broadly compatible with C++20's [`std::format`](https://en.cppreference.com/w/cpp/utility/format/format) and Python [format strings](https://docs.python.org/3.9/library/string.html#formatspec). " | ||
], | ||
"id": "f37a7c6450e02c50" | ||
}, | ||
{ | ||
"metadata": {}, | ||
"cell_type": "markdown", | ||
"source": [ | ||
"## The `substitute` utility function\n", | ||
"\n", | ||
"The `substitute` utility function is a simple function that takes a string and a dictionary of substitutions, and returns a new string with the substitutions made. The dictionary should map variable names to their values. Extra values will be ignored, and missing values is an error.\n", | ||
"\n", | ||
"When constructing a string containing placeholder substitution tokens and sending that string over the OpenAssetIO API, then - unless you know otherwise - you should assume that the receiving end will use the OpenAssetIO `substitute` function. As such, you should ensure that the string is formatted according to the OpenAssetIO syntax, described above.\n", | ||
"\n" | ||
], | ||
"id": "361171f45499c39e" | ||
}, | ||
{ | ||
"metadata": { | ||
"ExecuteTime": { | ||
"end_time": "2024-06-07T11:01:32.305620Z", | ||
"start_time": "2024-06-07T11:01:32.283123Z" | ||
} | ||
}, | ||
"cell_type": "code", | ||
"source": [ | ||
"from resources import helpers # helpers just for this notebook\n", | ||
"\n", | ||
"from openassetio import utils\n", | ||
"\n", | ||
"# A string with substitution tokens\n", | ||
"template_path = \"/mnt/show/sequences/image.{frame:04}.{view}.exr\"\n", | ||
"\n", | ||
"image_path = utils.substitute(template_path, {\"frame\": 12, \"view\": \"right\"})\n", | ||
"\n", | ||
"helpers.display_result(image_path)" | ||
], | ||
"id": "fa7bd39283d73254", | ||
"outputs": [ | ||
{ | ||
"data": { | ||
"text/markdown": "> **Result:**\n> `/mnt/show/sequences/image.0012.right.exr`" | ||
}, | ||
"metadata": {}, | ||
"output_type": "display_data" | ||
} | ||
], | ||
"execution_count": 1 | ||
}, | ||
{ | ||
"metadata": {}, | ||
"cell_type": "markdown", | ||
"source": "If a token is missing, an exception will be thrown (this behaviour may be revised in future):", | ||
"id": "79772a9e255db136" | ||
}, | ||
{ | ||
"metadata": { | ||
"ExecuteTime": { | ||
"end_time": "2024-06-07T11:01:32.310796Z", | ||
"start_time": "2024-06-07T11:01:32.306734Z" | ||
} | ||
}, | ||
"cell_type": "code", | ||
"source": [ | ||
"from openassetio import errors\n", | ||
"\n", | ||
"try:\n", | ||
" utils.substitute(template_path, {\"frame\": 12})\n", | ||
"except errors.InputValidationException as exc:\n", | ||
" helpers.display_result(str(exc))" | ||
], | ||
"id": "f9689e0762dae832", | ||
"outputs": [ | ||
{ | ||
"data": { | ||
"text/markdown": "> **Result:**\n> `substitute(): failed to process the input string '/mnt/show/sequences/image.{frame:04}.{view}.exr': argument not found`" | ||
}, | ||
"metadata": {}, | ||
"output_type": "display_data" | ||
} | ||
], | ||
"execution_count": 2 | ||
}, | ||
{ | ||
"metadata": {}, | ||
"cell_type": "markdown", | ||
"source": "If an extra token mapping is provided, it will be ignored:", | ||
"id": "51e67ec944b973d4" | ||
}, | ||
{ | ||
"metadata": { | ||
"ExecuteTime": { | ||
"end_time": "2024-06-07T11:01:32.315637Z", | ||
"start_time": "2024-06-07T11:01:32.311527Z" | ||
} | ||
}, | ||
"cell_type": "code", | ||
"source": [ | ||
"image_path = utils.substitute(template_path, {\"frame\": 12, \"view\": \"right\", \"extra\": \"value\"})\n", | ||
"\n", | ||
"helpers.display_result(image_path)" | ||
], | ||
"id": "b67b0418cf78e972", | ||
"outputs": [ | ||
{ | ||
"data": { | ||
"text/markdown": "> **Result:**\n> `/mnt/show/sequences/image.0012.right.exr`" | ||
}, | ||
"metadata": {}, | ||
"output_type": "display_data" | ||
} | ||
], | ||
"execution_count": 3 | ||
}, | ||
{ | ||
"metadata": {}, | ||
"cell_type": "markdown", | ||
"source": [ | ||
"The `substitute` function is also available in C++, where the input dictionary is of type `openassetio::InfoDictionary`. In fact, the Python implementation is just the Python bindings to the C++ implementation, meaning that the Python dictionary must be coerced to an `InfoDictionary` internally.\n", | ||
"\n", | ||
"If a value type is not supported by `InfoDictionary`, an exception will be thrown. For example, if we try to pass a nested dictionary as a value, an exception will be thrown:" | ||
], | ||
"id": "439baf43d9ac88c1" | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"id": "initial_id", | ||
"metadata": { | ||
"collapsed": true, | ||
"ExecuteTime": { | ||
"end_time": "2024-06-07T11:01:32.321619Z", | ||
"start_time": "2024-06-07T11:01:32.316946Z" | ||
} | ||
}, | ||
"source": [ | ||
"try:\n", | ||
" utils.substitute(template_path, {\"frame\": 12, \"view\": \"right\", \"nested\": {\"value\": 1}})\n", | ||
"except TypeError as exc:\n", | ||
" helpers.display_result(str(exc))" | ||
], | ||
"outputs": [ | ||
{ | ||
"data": { | ||
"text/markdown": "> **Result:**\n> `substitute(): incompatible function arguments. The following argument types are supported:\n 1. (input: str, substitutions: Dict[str, Union[bool, int, float, str]]) -> str\n\nInvoked with: '/mnt/show/sequences/image.{frame:04}.{view}.exr', {'frame': 12, 'view': 'right', 'nested': {'value': 1}}`" | ||
}, | ||
"metadata": {}, | ||
"output_type": "display_data" | ||
} | ||
], | ||
"execution_count": 4 | ||
}, | ||
{ | ||
"metadata": {}, | ||
"cell_type": "markdown", | ||
"source": "Note that the error message shows what types are available for use as substitution variables - `bool`, `int`, `float` and `str`.", | ||
"id": "51d06cf8cb65d6c4" | ||
} | ||
], | ||
"metadata": { | ||
"kernelspec": { | ||
"display_name": "Python 3", | ||
"language": "python", | ||
"name": "python3" | ||
}, | ||
"language_info": { | ||
"codemirror_mode": { | ||
"name": "ipython", | ||
"version": 2 | ||
}, | ||
"file_extension": ".py", | ||
"mimetype": "text/x-python", | ||
"name": "python", | ||
"nbconvert_exporter": "python", | ||
"pygments_lexer": "ipython2", | ||
"version": "2.7.6" | ||
} | ||
}, | ||
"nbformat": 4, | ||
"nbformat_minor": 5 | ||
} |