From 6df8e78932959ea7d3d4a32fd35e71bdedb8796c Mon Sep 17 00:00:00 2001 From: David Feltell Date: Wed, 6 Mar 2024 15:43:44 +0000 Subject: [PATCH] [Docs] Add generic republish notebook Signed-off-by: David Feltell --- examples/generic_republish.ipynb | 296 +++++++++++++++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100644 examples/generic_republish.ipynb diff --git a/examples/generic_republish.ipynb b/examples/generic_republish.ipynb new file mode 100644 index 0000000..6b714a6 --- /dev/null +++ b/examples/generic_republish.ipynb @@ -0,0 +1,296 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "07297a3d-048b-496b-adb0-8fdd67316021", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "# Hosts: Generic republishing\n" + ] + }, + { + "cell_type": "markdown", + "id": "60bd9de5-64c3-4f15-8824-ff1d94a894d7", + "metadata": { + "editable": true, + "jp-MarkdownHeadingCollapsed": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## Setup\n", + "\n", + "See \"Hello OpenAssetIO\" notebook for details on how to bootstrap OpenAssetIO. " + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "df703c44ad53b21c", + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2024-03-04T14:01:30.668184Z", + "start_time": "2024-03-04T14:01:30.628794Z" + } + }, + "outputs": [], + "source": [ + "from resources import helpers\n", + "\n", + "manager, context = helpers.bootstrap(\"resources/querying_entity_traits/openassetio_config.toml\")" + ] + }, + { + "cell_type": "markdown", + "id": "1a80594a-b171-49c5-8cfd-00e334759b1e", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## Getting started" + ] + }, + { + "cell_type": "markdown", + "source": [ + "In the following example we're going to ask the manager about the traits it supports with respect to an asset, and use that to determine a publishing workflow, without knowing anything about the entity.\n", + "\n", + "We've been given a URI by a colleague, which we need to turn into an `EntityReference` before we can use it to query the asset management system." + ], + "metadata": { + "collapsed": false + }, + "id": "22277554-b477-4600-8c2a-90ee8d5c064e" + }, + { + "cell_type": "code", + "outputs": [ + { + "data": { + "text/markdown": "> **Result:**\n> ``" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "logo_ref = manager.createEntityReference(\"bal:///project_artwork/logos/openassetio\")\n", + "\n", + "helpers.display_result(repr(logo_ref))" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2024-03-04T14:01:30.672906Z", + "start_time": "2024-03-04T14:01:30.669554Z" + } + }, + "id": "82c3962a-27b5-4375-b4fa-dbd14dcfc93b", + "execution_count": 2 + }, + { + "cell_type": "markdown", + "source": [ + "## Generic resolve and re-publish\n", + "\n", + "A rare use-case is for a tool that blindly resolves, updates, and re-publishes an entity, regardless what type of entity it is. \n", + "\n", + "Such generic re-publishing is discouraged and dangerous. For example, if \"approval\" status is blindly resolved and re-published, the manager may not know how it should handle this properly. A better approach to such a generic tool is to present the traits and their properties to the user to select before re-publishing.\n", + "\n", + "However, such workflows are possible and have their place in a pipeline. This can be accomplished by making use of `entityTraits`. " + ], + "metadata": { + "collapsed": false + }, + "id": "9c4274b9aa5f4965" + }, + { + "cell_type": "markdown", + "source": [ + "### Re-publishing a mutated entity\n", + "\n", + "The following presents an example of blindly updating the display name for any entity. Note that we make use of the fact that managers should silently ignore data that it cannot persist when publishing." + ], + "metadata": { + "collapsed": false + }, + "id": "92a3d36ca0b4a816" + }, + { + "cell_type": "code", + "outputs": [], + "source": [ + "from openassetio.access import EntityTraitsAccess, PolicyAccess, ResolveAccess, PublishingAccess\n", + "from openassetio_mediacreation.traits.identity import DisplayNameTrait\n", + "\n", + "# Get the complete trait set of the entity.\n", + "entity_trait_set = manager.entityTraits(logo_ref, EntityTraitsAccess.kRead, context)\n", + "\n", + "# Ensure the manager will accept a publish of this entity with an updated display name.\n", + "[policy_data] = manager.managementPolicy(\n", + " [entity_trait_set | {DisplayNameTrait.kId}], PolicyAccess.kWrite, context)\n", + "\n", + "if not DisplayNameTrait.kId in policy_data.traitSet():\n", + " raise Exception(\"Cannot update display name of this entity\")\n", + "\n", + "# Get all the properties of the given entity.\n", + "data_to_publish = manager.resolve(logo_ref, entity_trait_set, ResolveAccess.kRead, context)\n", + "\n", + "# Any traits without properties, or where the manager cannot provide them, will be missing from the data.\n", + "# We still need to imbue those traits, so that manager knows what kind of entity we are publishing.\n", + "data_to_publish.addTraits(entity_trait_set)\n", + "\n", + "# Create/update the name of the entity.\n", + "DisplayNameTrait(data_to_publish).setName(\"My New Name\")\n", + "\n", + "# Publish it. Any properties we `resolve`d that cannot be re-published will be silently ignored.\n", + "updated_ref = manager.register(logo_ref, data_to_publish, PublishingAccess.kWrite, context)" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2024-03-04T14:01:30.715862Z", + "start_time": "2024-03-04T14:01:30.673856Z" + } + }, + "id": "499f2040f821e2cd", + "execution_count": 3 + }, + { + "cell_type": "markdown", + "id": "b0f6750002fc94ee", + "metadata": { + "collapsed": false + }, + "source": [ + "### Re-publishing a minimal entity\n", + "\n", + "The following example dives deeper into the interaction between `entityTraits`, `managementPolicy` and `resolve` for a generic re-publisher. \n", + "\n", + "We want to re-publish a minimal entity (i.e. only the traits absolutely required for the given entity reference), with two new/updated traits. One of the traits, `BearerTokenTrait`, might not be supported, and the other trait, `LocatableContentTrait`, might have its properties dictated by the manager." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "beab2b9a9c071302", + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2024-03-04T14:01:30.950390Z", + "start_time": "2024-03-04T14:01:30.717329Z" + } + }, + "outputs": [ + { + "ename": "TypeError", + "evalue": "location must be a 'str'.", + "output_type": "error", + "traceback": [ + "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", + "\u001B[0;31mTypeError\u001B[0m Traceback (most recent call last)", + "Cell \u001B[0;32mIn[4], line 55\u001B[0m\n\u001B[1;32m 51\u001B[0m derived_data \u001B[38;5;241m=\u001B[39m manager\u001B[38;5;241m.\u001B[39mresolve(\n\u001B[1;32m 52\u001B[0m working_ref, {LocatableContentTrait\u001B[38;5;241m.\u001B[39mkId}, ResolveAccess\u001B[38;5;241m.\u001B[39mkManagerDriven, context)\n\u001B[1;32m 54\u001B[0m \u001B[38;5;66;03m# TODO(DF): `upsert` function for `TraitsData`.\u001B[39;00m\n\u001B[0;32m---> 55\u001B[0m \u001B[43mLocatableContentTrait\u001B[49m\u001B[43m(\u001B[49m\u001B[43mdata_to_publish\u001B[49m\u001B[43m)\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43msetLocation\u001B[49m\u001B[43m(\u001B[49m\n\u001B[1;32m 56\u001B[0m \u001B[43m \u001B[49m\u001B[43mLocatableContentTrait\u001B[49m\u001B[43m(\u001B[49m\u001B[43mderived_data\u001B[49m\u001B[43m)\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mgetLocation\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 58\u001B[0m \u001B[38;5;66;03m# [Do some work to write the new file...]\u001B[39;00m\n\u001B[1;32m 59\u001B[0m \n\u001B[1;32m 60\u001B[0m \u001B[38;5;66;03m# We can now finally publish\u001B[39;00m\n\u001B[1;32m 61\u001B[0m updated_ref \u001B[38;5;241m=\u001B[39m manager\u001B[38;5;241m.\u001B[39mregister(logo_ref, data_to_publish, PublishingAccess\u001B[38;5;241m.\u001B[39mkWrite, context)\n", + "File \u001B[0;32m~/workspace/cloud/assetapi/OpenAssetIO-MediaCreation/.venv3.9/lib/python3.9/site-packages/openassetio_mediacreation/traits/content.py:135\u001B[0m, in \u001B[0;36mLocatableContentTrait.setLocation\u001B[0;34m(self, location)\u001B[0m\n\u001B[1;32m 126\u001B[0m \u001B[38;5;250m\u001B[39m\u001B[38;5;124;03m\"\"\"\u001B[39;00m\n\u001B[1;32m 127\u001B[0m \u001B[38;5;124;03mSets the location property.\u001B[39;00m\n\u001B[1;32m 128\u001B[0m \n\u001B[0;32m (...)\u001B[0m\n\u001B[1;32m 132\u001B[0m \u001B[38;5;124;03mencoded accordingly.\u001B[39;00m\n\u001B[1;32m 133\u001B[0m \u001B[38;5;124;03m\"\"\"\u001B[39;00m\n\u001B[1;32m 134\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;28misinstance\u001B[39m(location, \u001B[38;5;28mstr\u001B[39m):\n\u001B[0;32m--> 135\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m \u001B[38;5;167;01mTypeError\u001B[39;00m(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mlocation must be a \u001B[39m\u001B[38;5;124m'\u001B[39m\u001B[38;5;124mstr\u001B[39m\u001B[38;5;124m'\u001B[39m\u001B[38;5;124m.\u001B[39m\u001B[38;5;124m\"\u001B[39m)\n\u001B[1;32m 136\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m__data\u001B[38;5;241m.\u001B[39msetTraitProperty(\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mkId, \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mlocation\u001B[39m\u001B[38;5;124m\"\u001B[39m, location)\n", + "\u001B[0;31mTypeError\u001B[0m: location must be a 'str'." + ] + } + ], + "source": [ + "from openassetio_mediacreation.traits.auth import BearerTokenTrait\n", + "from openassetio_mediacreation.traits.content import LocatableContentTrait\n", + "\n", + "# The minimum set of traits required to publish to this entity reference.\n", + "minimum_trait_set = manager.entityTraits(logo_ref, EntityTraitsAccess.kWrite, context)\n", + "\n", + "# Whatever the minimum trait set is, we know we want to publish a location and auth token.\n", + "desired_trait_set = minimum_trait_set | {BearerTokenTrait.kId, LocatableContentTrait.kId}\n", + "\n", + "# Get the set of traits that have properties the manager can persist.\n", + "[policy_for_desired_traits] = manager.managementPolicy(\n", + " [desired_trait_set], PolicyAccess.kWrite, context)\n", + "\n", + "# Filter down the desired traits to only those that are supported.\n", + "trait_set_to_publish = desired_trait_set & policy_for_desired_traits.traitSet()\n", + "\n", + "# We want to keep (the minimum amount of) data from the previous version, except for the values we're going to\n", + "# provide.\n", + "trait_set_to_keep = trait_set_to_publish - {BearerTokenTrait.kId, LocatableContentTrait.kId}\n", + "\n", + "# Get the properties that we wish to keep from the current version.\n", + "data_to_publish = manager.resolve(logo_ref, trait_set_to_keep, ResolveAccess.kManagerDriven, context)\n", + "\n", + "# Any traits without properties, or where the manager cannot provide them, will be missing from the data.\n", + "# We still need to imbue those traits, so that manager knows what kind of entity we are publishing.\n", + "data_to_publish.addTraits(minimum_trait_set)\n", + "\n", + "# Get the manager's policy for dictating trait properties, i.e. which traits the manager can `kDerive` for us.\n", + "[policy_for_derived_traits] = manager.managementPolicy(\n", + " [trait_set_to_publish], PolicyAccess.kManagerDriven, context)\n", + "\n", + "# Check if the manager can derive a location for us.\n", + "if LocatableContentTrait.kId in policy_for_derived_traits.traitSet():\n", + " # Imbue an empty LocatableContentTrait, so that the manager is aware in `preflight` that we intend to publish\n", + " # this trait. We will ask the manager to fill in the value for us before calling `register`.\n", + " LocatableContentTrait.imbueTo(data_to_publish)\n", + "else:\n", + " # If the manager doesn't want to provide a location for entities of this type, use a default.\n", + " LocatableContentTrait(data_to_publish).setLocation(\"file:///tmp/file\")\n", + "\n", + "# Manager might not support BearerTokenTrait.\n", + "if BearerTokenTrait.kId in trait_set_to_publish:\n", + " # BearerTokenTrait is supported, so imbue and configure.\n", + " BearerTokenTrait(data_to_publish).setToken(\"==ZxErn43G\")\n", + "\n", + "# We can now successfully begin the publishing process.\n", + "working_ref = manager.preflight(logo_ref, data_to_publish, PublishingAccess.kWrite, context)\n", + "\n", + "# Check if the manager can provide a location to us.\n", + "if LocatableContentTrait.kId in policy_for_derived_traits.traitSet():\n", + " derived_data = manager.resolve(\n", + " working_ref, {LocatableContentTrait.kId}, ResolveAccess.kManagerDriven, context)\n", + "\n", + " LocatableContentTrait(data_to_publish).setLocation(\n", + " LocatableContentTrait(derived_data).getLocation())\n", + " \n", + "# [Do some work to write the new file...]\n", + "\n", + "# We can now finally publish\n", + "updated_ref = manager.register(logo_ref, data_to_publish, PublishingAccess.kWrite, context)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.18" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}