From bf37ce1458040091c466e25f81055d18bf766ce6 Mon Sep 17 00:00:00 2001 From: Tom Cowland Date: Thu, 10 Aug 2023 14:18:51 -0700 Subject: [PATCH 1/9] [Tests] Move to config/library relative setup Updates the test infrastructure to library relative media, and config relative settings. Pre-configures the resolver. This hopefully simplifies the setup steps, and removes the need for some validation test fixtures. Signed-off-by: Tom Cowland --- README.md | 11 ++-- tests/requirements.txt | 2 +- .../integration_test_data/bal_library.json | 8 +-- tests/resources/openassetio.conf | 2 - tests/resources/openassetio_config.toml | 5 ++ tests/test_resolver.py | 64 ++++--------------- 6 files changed, 28 insertions(+), 64 deletions(-) delete mode 100644 tests/resources/openassetio.conf create mode 100644 tests/resources/openassetio_config.toml diff --git a/README.md b/README.md index d7bba2a..9a95182 100644 --- a/README.md +++ b/README.md @@ -161,19 +161,20 @@ To enable debug logging from the resolver. ## Testing -To run tests, from the project root +To run tests, once built, from the project root: ```sh -export PXR_PLUGINPATH_NAME=$(pwd)/build/dist/resources/plugInfo.json -cd tests python -m pip install -r requirements.txt -pytest +python -m pytest ``` > **Note** > > Refer to the [Running](#running) section for the environmental -> prerequisites to run these tests. +> prerequisites to run these tests. The tests will set +> `OPENASSETIO_DEFAULT_CONFIG` appropriately, and unless otherwise +> defined, attempt to set `PXR_PLUGINPATH_NAME` if the standard `build` +> directory was used. ## A note on Python diff --git a/tests/requirements.txt b/tests/requirements.txt index 30f2918..2fa4ff4 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,2 +1,2 @@ pytest==6.2.4 -openassetio-manager-bal==1.0.0a6 +openassetio-manager-bal diff --git a/tests/resources/integration_test_data/bal_library.json b/tests/resources/integration_test_data/bal_library.json index c1d848e..8c433f8 100644 --- a/tests/resources/integration_test_data/bal_library.json +++ b/tests/resources/integration_test_data/bal_library.json @@ -5,7 +5,7 @@ { "traits": { "openassetio-mediacreation:content.LocatableContent": { - "location": "file://${TEST_DATA_ROOT}/assetized_child_ref_non_assetized_grandchild/floor1.usd" + "location": "file://${bal_library_dir}/assetized_child_ref_non_assetized_grandchild/floor1.usd" } } } @@ -16,7 +16,7 @@ { "traits": { "openassetio-mediacreation:content.LocatableContent": { - "location": "file://${TEST_DATA_ROOT}/non_assetized_child_ref_assetized_grandchild/car.usd" + "location": "file://${bal_library_dir}/non_assetized_child_ref_assetized_grandchild/car.usd" } } } @@ -27,7 +27,7 @@ { "traits": { "openassetio-mediacreation:content.LocatableContent": { - "location": "file://${TEST_DATA_ROOT}/recursive_assetized_resolve/floors/floor1.usd" + "location": "file://${bal_library_dir}/recursive_assetized_resolve/floors/floor1.usd" } } } @@ -38,7 +38,7 @@ { "traits": { "openassetio-mediacreation:content.LocatableContent": { - "location": "file://${TEST_DATA_ROOT}/recursive_assetized_resolve/cars/car.usd" + "location": "file://${bal_library_dir}/recursive_assetized_resolve/cars/car.usd" } } } diff --git a/tests/resources/openassetio.conf b/tests/resources/openassetio.conf deleted file mode 100644 index c379883..0000000 --- a/tests/resources/openassetio.conf +++ /dev/null @@ -1,2 +0,0 @@ -[manager] -identifier = "org.openassetio.examples.manager.bal" diff --git a/tests/resources/openassetio_config.toml b/tests/resources/openassetio_config.toml new file mode 100644 index 0000000..e584bca --- /dev/null +++ b/tests/resources/openassetio_config.toml @@ -0,0 +1,5 @@ +[manager] +identifier = "org.openassetio.examples.manager.bal" + +[manager.settings] +library_path = "${config_dir}/integration_test_data/bal_library.json" diff --git a/tests/test_resolver.py b/tests/test_resolver.py index 46b36e8..f8e9e90 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -10,40 +10,18 @@ # This environment var must be set before the usd imports. os.environ["TF_DEBUG"] = "OPENASSETIO_RESOLVER" -from pxr import Plug, Usd, Ar - +if "PXR_PLUGINPATH_NAME" not in os.environ: + # Set up our resolver plugin if not already set up, and we can + root_dir = os.path.realpath(os.path.dirname(os.path.dirname(__file__))) + plug_info = os.path.join(root_dir, "build", "dist", "resources", "pluginfo.json") + if os.path.exists(plug_info): + os.environ["PXR_PLUGINPATH_NAME"] = plug_info -# Assume OpenAssetIO is configured as the custom primary resolver for -# all tests. If you're wondering where this is configured, it may -# just be set via the `PXR_PLUGINPATH_NAME` environment variable. +from pxr import Plug, Usd, Ar # TODO(DF): More tests for error cases. -# This test can be removed once the logging transforms, alchemy like, -# into real functionality. -def test_open_stage_and_logging(capfd): - open_stage("resources/empty_shot.usda") - captured = capfd.readouterr() - - outputs = captured.out.split("UsdOpenAssetIOResolver::") - assert "UsdOpenAssetIOResolver" in outputs[1] - assert "_CreateIdentifier" in outputs[2] - assert "result" in outputs[2] - assert "_Resolve" in outputs[3] - assert "result" in outputs[3] - assert "_GetExtension" in outputs[4] - assert "result" in outputs[4] - assert "_GetAssetInfo" in outputs[5] - assert "result" in outputs[5] - assert "_OpenAsset" in outputs[6] - assert "result" in outputs[6] - assert "_GetModificationTimestamp" in outputs[7] - assert "result" in outputs[7] - assert "_GetExtension" in outputs[8] - assert "result" in outputs[8] - - # Given a USD document that references an asset via a direct relative # file path, then the asset is resolved to the file path as expected. def test_openassetio_resolver_has_no_effect_with_no_search_path(): @@ -126,16 +104,6 @@ def test_error_triggering_asset_ref(capfd): ##### Utility Functions ##### -# Verify OpenAssetIO configured as the AR resolver. -@pytest.fixture(autouse=True) -def openassetio_configured(): - plugin_registry = Plug.Registry() - plugin = plugin_registry.GetPluginWithName("usdOpenAssetIOResolver") - - assert ( - plugin is not None - ), "usdOpenAssetIOResolver plugin not loaded, please check PXR_PLUGINPATH_NAME env variable" - # Log openassetio resolver messages @pytest.fixture(autouse=True) @@ -183,22 +151,14 @@ def open_stage(path_relative_from_file, context=None): @pytest.fixture(autouse=True) -def bal_library(monkeypatch, test_data_root): +def openasssetio_default_config(monkeypatch, resources_dir): monkeypatch.setenv( - "BAL_LIBRARY_PATH", os.path.join(test_data_root, "bal_library.json") + "OPENASSETIO_DEFAULT_CONFIG", + os.path.join(resources_dir, "openassetio_config.toml"), ) -@pytest.fixture(autouse=True) -def test_data_root_env_var(monkeypatch, test_data_root): - """ - The TEST_DATA_ROOT env var is expanded in the various BAL JSON - libraries, to provide portable absolute paths to file assets. - """ - monkeypatch.setenv("TEST_DATA_ROOT", test_data_root) - - @pytest.fixture -def test_data_root(): +def resources_dir(): script_dir = os.path.realpath(os.path.dirname(__file__)) - return os.path.join(script_dir, "resources", "integration_test_data") + return os.path.join(script_dir, "resources") From 54d6ace1499728697ba9aeed5aab89c0a0880a91 Mon Sep 17 00:00:00 2001 From: Tom Cowland Date: Thu, 10 Aug 2023 15:45:59 -0700 Subject: [PATCH 2/9] [Core] Use OpenAssetIO-MediaCreation Migrates over to media creation, instead of the low-level TraitsData API with hard-coded trait strings. Adds basic decoding of %20 to space, as it is prob quite common. Signed-off-by: Tom Cowland --- .../action.yml | 33 ++++++++ .github/workflows/code-quality.yml | 19 ++++- .github/workflows/test.yml | 19 ++++- CMakeLists.txt | 1 + src/CMakeLists.txt | 1 + src/resolver.cpp | 79 ++++++++++--------- .../integration_test_data/bal_library.json | 14 +++- .../floors/{floor1.usd => floor 1.usd} | 0 tests/test_resolver.py | 15 ++-- 9 files changed, 135 insertions(+), 46 deletions(-) create mode 100644 .github/build_openassetio_mediacreation/action.yml rename tests/resources/integration_test_data/recursive_assetized_resolve/floors/{floor1.usd => floor 1.usd} (100%) diff --git a/.github/build_openassetio_mediacreation/action.yml b/.github/build_openassetio_mediacreation/action.yml new file mode 100644 index 0000000..a0f708c --- /dev/null +++ b/.github/build_openassetio_mediacreation/action.yml @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2023 The Foundry Visionmongers Ltd + +# Composite action for reuse within other workflows. +# Builds OpenAssetIO-MediaCreation. +# Should be run on a ghcr.io/openassetio/openassetio-build container. + +name: Build OpenAssetIO-MediaCreation +description: Builds OpenAssetIO-MediaCreation and publishes an artifact +runs: + using: "composite" + steps: + - name: Checkout OpenAssetIO-MediaCreation + uses: actions/checkout@v3 + with: + repository: OpenAssetIO/OpenAssetIO-MediaCreation + path: openassetio-mediacreation-checkout + + - name: Build OpenAssetIO-MediaCreation + shell: bash + run: | + cd openassetio-mediacreation-checkout + mkdir build + python -m pip install openassetio-traitgen + cmake -G Ninja -S . -B build + cmake --build build + cmake --install build + + - uses: actions/upload-artifact@v3 + with: + name: OpenAssetIO-MediaCreation Build + path: openassetio-mediacreation-checkout/build/dist + retention-days: 1 diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index a222d4f..f4d6d3f 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -54,6 +54,16 @@ jobs: - name: Build uses: ./.github/build_openassetio + build-openassetio-mediacreation: + name: Build OpenAssetIO-MediaCreation + runs-on: ubuntu-latest + container: + image: ghcr.io/openassetio/openassetio-build + steps: + - uses: actions/checkout@v3 + - name: Build + uses: ./.github/build_openassetio_mediacreation + cpp-linters: name: C++ linters runs-on: ubuntu-20.04 @@ -82,9 +92,16 @@ jobs: name: OpenAssetIO Build path: ./openassetio-build + - name: Get OpenAssetIO-MediaCreation + uses: actions/download-artifact@v3 + with: + name: OpenAssetIO-MediaCreation Build + path: ./openassetio-mediacreation-build + - name: Configure CMake build run: > - cmake -DCMAKE_PREFIX_PATH="./openassetio-build" -S . -B build -G Ninja + cmake -DCMAKE_PREFIX_PATH="./openassetio-build;./openassetio-mediacreation-build" + -S . -B build -G Ninja --install-prefix ${{ github.workspace }}/dist --preset lint diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 152a07b..0e1acf2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,6 +21,16 @@ jobs: - name: Build uses: ./.github/build_openassetio + build-openassetio-mediacreation: + name: Build OpenAssetIO-MediaCreation + runs-on: ubuntu-latest + container: + image: ghcr.io/openassetio/openassetio-build + steps: + - uses: actions/checkout@v3 + - name: Build + uses: ./.github/build_openassetio_mediacreation + test: name: Test-Resolver runs-on: ubuntu-latest @@ -36,14 +46,21 @@ jobs: name: OpenAssetIO Build path: ./openassetio-build + - name: Get OpenAssetIO-MediaCreation + uses: actions/download-artifact@v3 + with: + name: OpenAssetIO-MediaCreation Build + path: ./openassetio-mediacreation-build + - name: Build and install Resolver run: | - cmake -S . -DCMAKE_PREFIX_PATH="./openassetio-build" -B build + cmake -S . -DCMAKE_PREFIX_PATH="./openassetio-build;./openassetio-mediacreation-build" -B build cmake --build build cmake --install build - run: | python -m pip install -r tests/requirements.txt + python -m pip install importlib-metadata # PYTHONPATH here is extended with `/usr/local/lib/python` # because the USD install on this docker container is odd, and diff --git a/CMakeLists.txt b/CMakeLists.txt index 8552ca1..bb62e02 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,7 @@ find_package(pxr REQUIRED) # Find OpenAssetIO find_package(OpenAssetIO REQUIRED) +find_package(OpenAssetIO-MediaCreation REQUIRED) # Add Static analysis targets include(StaticAnalyzers) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cec6d06..64a9684 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -16,6 +16,7 @@ target_link_libraries(${PLUGIN_NAME} ar OpenAssetIO::openassetio-core OpenAssetIO::openassetio-python-bridge + OpenAssetIO-MediaCreation::openassetio-mediacreation ) #----------------------------------------------------------------------- diff --git a/src/resolver.cpp b/src/resolver.cpp index fd42cfe..f7283df 100644 --- a/src/resolver.cpp +++ b/src/resolver.cpp @@ -4,6 +4,7 @@ #include "resolver.h" #include +#include #include #include @@ -23,6 +24,8 @@ #include #include +#include + // NOLINTNEXTLINE PXR_NAMESPACE_USING_DIRECTIVE PXR_NAMESPACE_OPEN_SCOPE @@ -34,6 +37,21 @@ TF_DEBUG_CODES(OPENASSETIO_RESOLVER) PXR_NAMESPACE_CLOSE_SCOPE namespace { +/* + * Replaces all occurrences of the search string in the subject with + * the supplied replacement. + */ +void replaceAllInString(std::string &subject, const std::string &search, + const std::string &replace) { + const size_t searchLength = search.length(); + const size_t replaceLength = replace.length(); + size_t pos = 0; + while ((pos = subject.find(search, pos)) != std::string::npos) { + subject.replace(pos, searchLength, replace); + pos += replaceLength; + } +} + /// Converter logger from OpenAssetIO log framing to USD log outputs. class UsdOpenAssetIOResolverLogger : public openassetio::log::LoggerInterface { public: @@ -73,12 +91,6 @@ class UsdOpenAssetIOHostInterface : public openassetio::hostApi::HostInterface { } }; -// TODO(DF): Replace with C++ trait views, once they exist. -const openassetio::trait::TraitId kLocateableContentTraitId = // NOLINT - "openassetio-mediacreation:content.LocatableContent"; -const openassetio::trait::property::Key kLocateableContentLocationPropertyKey = // NOLINT - "location"; - /** * Retrieve the resolved file path of an entity reference. * @@ -100,39 +112,30 @@ template Ref locationInManagerContextForEntity(const openassetio::hostApi::ManagerPtr &manager, const openassetio::ContextConstPtr &context, Ref assetPath) { // Check if the assetPath is an OpenAssetIO entity reference. - if (auto maybeEntityReference = manager->createEntityReferenceIfValid(assetPath)) { - openassetio::TraitsDataPtr traitsData; - - // Resolve the locateableContent trait in order to get the - // (absolute) path to the asset. - manager->resolve( - {std::move(*maybeEntityReference)}, {kLocateableContentTraitId}, context, - [&traitsData]([[maybe_unused]] std::size_t idx, - const openassetio::TraitsDataPtr &resolvedTraitsData) { - // Success callback. - traitsData = resolvedTraitsData; - }, - []([[maybe_unused]] std::size_t idx, const openassetio::BatchElementError &error) { - // Error callback. - // TODO(DF): Better conversion of BatchElementError to - // appropriate exception type. - std::string errorMsg = "error code "; - errorMsg += std::to_string(static_cast(error.code)); - errorMsg += ": "; - errorMsg += error.message; - throw std::runtime_error{errorMsg}; - }); - - if (openassetio::trait::property::Value propValue; traitsData->getTraitProperty( - &propValue, kLocateableContentTraitId, kLocateableContentLocationPropertyKey)) { - // We've successfully got the locateableContent trait for the - // entity. - static constexpr std::size_t kProtocolSize = std::string_view{"file://"}.size(); - return Ref{std::get(propValue).substr(kProtocolSize)}; + if (auto entityReference = manager->createEntityReferenceIfValid(assetPath)) { + using openassetio_mediacreation::traits::content::LocatableContentTrait; + + // Resolve the locatable content trait, this will provide a URL + // that points to the final content + openassetio::TraitsDataPtr traitsData = + manager->resolve(std::move(*entityReference), {LocatableContentTrait::kId}, context); + + // OpenAssetIO is URL based, but we need a path, so check the + // scheme and decode into a path + + static constexpr std::string_view kFileURLScheme{"file://"}; + static constexpr std::size_t kProtocolSize = kFileURLScheme.size(); + + openassetio::Str url = LocatableContentTrait(traitsData).getLocation(); + if (url.rfind(kFileURLScheme, 0) == openassetio::Str::npos) { + std::string msg = "Only file URLs are supported: "; + msg += url; + throw std::runtime_error(msg); } - // TODO(DF): Here we fall through to returning `assetPath` verbatim - // if the "locateableContent" trait isn't found. We need to - // consider if this is the correct thing to do. + + // TODO(tc): Decode % escape sequences properly + replaceAllInString(url, "%20", " "); + return Ref{url.substr(kProtocolSize)}; } return assetPath; diff --git a/tests/resources/integration_test_data/bal_library.json b/tests/resources/integration_test_data/bal_library.json index 8c433f8..5621f02 100644 --- a/tests/resources/integration_test_data/bal_library.json +++ b/tests/resources/integration_test_data/bal_library.json @@ -27,7 +27,8 @@ { "traits": { "openassetio-mediacreation:content.LocatableContent": { - "location": "file://${bal_library_dir}/recursive_assetized_resolve/floors/floor1.usd" + "location": + "file://${bal_library_dir}/recursive_assetized_resolve/floors/floor%201.usd" } } } @@ -43,6 +44,17 @@ } } ] + }, + "not_a_file": { + "versions": [ + { + "traits": { + "openassetio-mediacreation:content.LocatableContent": { + "location": "https://stuffonmycat.com" + } + } + } + ] } } } diff --git a/tests/resources/integration_test_data/recursive_assetized_resolve/floors/floor1.usd b/tests/resources/integration_test_data/recursive_assetized_resolve/floors/floor 1.usd similarity index 100% rename from tests/resources/integration_test_data/recursive_assetized_resolve/floors/floor1.usd rename to tests/resources/integration_test_data/recursive_assetized_resolve/floors/floor 1.usd diff --git a/tests/test_resolver.py b/tests/test_resolver.py index f8e9e90..4e2fbde 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -17,7 +17,7 @@ if os.path.exists(plug_info): os.environ["PXR_PLUGINPATH_NAME"] = plug_info -from pxr import Plug, Usd, Ar +from pxr import Plug, Usd, Ar, Tf # TODO(DF): More tests for error cases. @@ -96,10 +96,15 @@ def test_error_triggering_asset_ref(capfd): ) logs = capfd.readouterr() - assert ( - "OpenAssetIO error in UsdOpenAssetIOResolver::_GetExtension: RuntimeError: error code 130:" - in logs.err - ) + assert "MalformedEntityReference" in logs.err + + +def test_when_resolves_to_non_file_url_then_error(): + with pytest.raises(Tf.ErrorException) as exc: + Usd.Stage.Open("bal:///not_a_file") + # The exception seems to be truncated which looses the specifics of + # our message (doh!). Check at least we were in the stack + assert "UsdOpenAssetIOResolverLogger" in str(exc) ##### Utility Functions ##### From d6318128525f9d88a7e4611370ef5b81190a6b43 Mon Sep 17 00:00:00 2001 From: Tom Cowland Date: Thu, 10 Aug 2023 19:00:39 -0700 Subject: [PATCH 3/9] [Core] Move resolution to _Resolve When we made this PoC, we noted that in order to support querying extension, mtime, from the manager, as well as to support publishing we'd need access to the entity reference in `_OpenAssetForWrite` and other methods. We opted to pass the ref through `_Resolve` so it would be available to these other methods, and actually do the resolve in `_OpenAsset` and friends. We know performance is important, and we need to ensure that early adopters don't see significantly increased overhead due to OpenAssetIO. Looking again, we can split behaviour, so that the old approach is only done in `_ResolveForNewAsset`. This means that the common case (read) won't end up with numerous redundant resolver per read. Signed-off-by: Tom Cowland --- src/resolver.cpp | 36 ++++++------------------------------ 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/src/resolver.cpp b/src/resolver.cpp index f7283df..c660422 100644 --- a/src/resolver.cpp +++ b/src/resolver.cpp @@ -241,9 +241,7 @@ std::string UsdOpenAssetIOResolver::_CreateIdentifier( // an "anchored" entity reference. identifier = assetPath; } else { - identifier = ArDefaultResolver::_CreateIdentifier( - assetPath, - locationInManagerContextForEntity(manager_, readContext_, anchorAssetPath)); + identifier = ArDefaultResolver::_CreateIdentifier(assetPath, anchorAssetPath); } return identifier; @@ -284,9 +282,8 @@ ArResolvedPath UsdOpenAssetIOResolver::_Resolve(const std::string &assetPath) co auto result = catchAndLogExceptions( [&] { if (manager_->isEntityReferenceString(assetPath)) { - // TODO(DF): We may wish to do more here, e.g. - // `finalizedEntityVersion()` - return ArResolvedPath{assetPath}; + return ArResolvedPath{ + locationInManagerContextForEntity(manager_, readContext_, assetPath)}; } return ArDefaultResolver::_Resolve(assetPath); }, @@ -323,13 +320,7 @@ std::string UsdOpenAssetIOResolver::_GetExtension(const std::string &assetPath) logger_->debug(logMsg.str()); } - auto result = catchAndLogExceptions( - [&] { - // TODO(DF): Give the manager a chance to provide the file extension. - return ArDefaultResolver::_GetExtension( - locationInManagerContextForEntity(manager_, readContext_, assetPath)); - }, - logger_, fnName); + auto result = ArDefaultResolver::_GetExtension(assetPath); { std::ostringstream logMsg; @@ -378,17 +369,7 @@ ArTimestamp UsdOpenAssetIOResolver::_GetModificationTimestamp( logger_->debug(logMsg.str()); } - auto result = catchAndLogExceptions( - [&] { - if (manager_->isEntityReferenceString(assetPath)) { - // Deliberately use a valid fixed timestamp, which will force caching - // until we implement a more considered method. - // TODO(DF): Consider how best to handle this via OpenAssetIO. - return ArTimestamp{0}; - } - return ArDefaultResolver::_GetModificationTimestamp(assetPath, resolvedPath); - }, - logger_, fnName); + auto result = ArDefaultResolver::_GetModificationTimestamp(assetPath, resolvedPath); { std::ostringstream logMsg; @@ -410,12 +391,7 @@ std::shared_ptr UsdOpenAssetIOResolver::_OpenAsset( logger_->debug(logMsg.str()); } - auto result = catchAndLogExceptions( - [&] { - return ArDefaultResolver::_OpenAsset( - locationInManagerContextForEntity(manager_, readContext_, resolvedPath)); - }, - logger_, fnName); + auto result = ArDefaultResolver::_OpenAsset(resolvedPath); { std::ostringstream logMsg; From 0991cbf635864135a31d595859a773dde907b874 Mon Sep 17 00:00:00 2001 From: Tom Cowland Date: Thu, 10 Aug 2023 19:18:45 -0700 Subject: [PATCH 4/9] [Core] Remove debug logging We put this in when we were learning about when and where USD invoked the various methods of the API. People have indicated that the overhead of the wrapper to OpenAssetIO would be a key factor during assessment. This commit removes this logging, to avoid any unnecessary overhead. It also makes the code somewhat easier to grok, which should help people using this as a reference implementation. Signed-off-by: Tom Cowland --- src/resolver.cpp | 158 +++++------------------------------------------ 1 file changed, 15 insertions(+), 143 deletions(-) diff --git a/src/resolver.cpp b/src/resolver.cpp index c660422..5973427 100644 --- a/src/resolver.cpp +++ b/src/resolver.cpp @@ -210,24 +210,13 @@ UsdOpenAssetIOResolver::UsdOpenAssetIOResolver() { // TODO(DF): Set appropriate locale. readContext_ = openassetio::Context::make(openassetio::Context::Access::kRead, openassetio::Context::Retention::kTransient); - - logger_->debug(TF_FUNC_NAME()); } -UsdOpenAssetIOResolver::~UsdOpenAssetIOResolver() { logger_->debug(TF_FUNC_NAME()); } +UsdOpenAssetIOResolver::~UsdOpenAssetIOResolver() = default; std::string UsdOpenAssetIOResolver::_CreateIdentifier( const std::string &assetPath, const ArResolvedPath &anchorAssetPath) const { - const std::string fnName = TF_FUNC_NAME(); - { - std::ostringstream logMsg; - logMsg << "[tid=" << std::this_thread::get_id() << "] " << fnName - << "\n assetPath: " << assetPath - << "\n anchorAssetPath: " << anchorAssetPath.GetPathString(); - logger_->debug(logMsg.str()); - } - - auto result = catchAndLogExceptions( + return catchAndLogExceptions( [&] { std::string identifier; @@ -246,40 +235,17 @@ std::string UsdOpenAssetIOResolver::_CreateIdentifier( return identifier; }, - logger_, fnName); - - { - std::ostringstream logMsg; - logMsg << "[tid=" << std::this_thread::get_id() << "] " - << "result: " << result; - logger_->debug(logMsg.str()); - } - return result; + logger_, TF_FUNC_NAME()); } // TODO(DF): Implement for publishing workflow. std::string UsdOpenAssetIOResolver::_CreateIdentifierForNewAsset( const std::string &assetPath, const ArResolvedPath &anchorAssetPath) const { - auto result = ArDefaultResolver::_CreateIdentifierForNewAsset(assetPath, anchorAssetPath); - logger_->debug(TF_FUNC_NAME()); - - logger_->debug(" assetPath: " + assetPath); - logger_->debug(" anchorAssetPath: " + anchorAssetPath.GetPathString()); - logger_->debug(" result: " + result); - - return result; + return ArDefaultResolver::_CreateIdentifierForNewAsset(assetPath, anchorAssetPath); } ArResolvedPath UsdOpenAssetIOResolver::_Resolve(const std::string &assetPath) const { - const std::string fnName = TF_FUNC_NAME(); - { - std::ostringstream logMsg; - logMsg << "[tid=" << std::this_thread::get_id() << "] " << fnName - << "\n assetPath: " << assetPath; - logger_->debug(logMsg.str()); - } - - auto result = catchAndLogExceptions( + return catchAndLogExceptions( [&] { if (manager_->isEntityReferenceString(assetPath)) { return ArResolvedPath{ @@ -287,138 +253,44 @@ ArResolvedPath UsdOpenAssetIOResolver::_Resolve(const std::string &assetPath) co } return ArDefaultResolver::_Resolve(assetPath); }, - logger_, fnName); - - { - std::ostringstream logMsg; - logMsg << "[tid=" << std::this_thread::get_id() << "] " - << "result: " << result.GetPathString(); - logger_->debug(logMsg.str()); - } - - return result; + logger_, TF_FUNC_NAME()); } // TODO(DF): Implement for publishing workflow. ArResolvedPath UsdOpenAssetIOResolver::_ResolveForNewAsset(const std::string &assetPath) const { - auto result = ArDefaultResolver::_ResolveForNewAsset(assetPath); - - logger_->debug(TF_FUNC_NAME()); - logger_->debug(" assetPath: " + assetPath); - logger_->debug(" result: " + result.GetPathString()); - - return result; + return ArDefaultResolver::_ResolveForNewAsset(assetPath); } -/* Asset Operations*/ +/* + * Pass-through asset operations + */ std::string UsdOpenAssetIOResolver::_GetExtension(const std::string &assetPath) const { - const std::string fnName = TF_FUNC_NAME(); - { - std::ostringstream logMsg; - logMsg << "[tid=" << std::this_thread::get_id() << "] " << fnName - << "\n assetPath: " << assetPath; - logger_->debug(logMsg.str()); - } - - auto result = ArDefaultResolver::_GetExtension(assetPath); - - { - std::ostringstream logMsg; - logMsg << "[tid=" << std::this_thread::get_id() << "] " - << "result: " << result; - logger_->debug(logMsg.str()); - } - - return result; + return ArDefaultResolver::_GetExtension(assetPath); } ArAssetInfo UsdOpenAssetIOResolver::_GetAssetInfo(const std::string &assetPath, const ArResolvedPath &resolvedPath) const { - const std::string fnName = TF_FUNC_NAME(); - { - std::ostringstream logMsg; - logMsg << "[tid=" << std::this_thread::get_id() << "] " << fnName - << "\n assetPath: " << assetPath - << "\n resolvedPath: " << resolvedPath.GetPathString(); - logger_->debug(logMsg.str()); - } - // TODO(DF): Determine if there is value in supporting this via the - // manager, including any useful data that can be stuffed into the - // generic `resolverInfo` VtValue member. - auto result = ArDefaultResolver::_GetAssetInfo(assetPath, resolvedPath); - - { - std::ostringstream logMsg; - logMsg << "[tid=" << std::this_thread::get_id() << "] " - << "\n result.assetName: " << result.assetName - << "\n result.repoPath: " << result.repoPath; - logger_->debug(logMsg.str()); - } - - return result; + return ArDefaultResolver::_GetAssetInfo(assetPath, resolvedPath); } ArTimestamp UsdOpenAssetIOResolver::_GetModificationTimestamp( const std::string &assetPath, const ArResolvedPath &resolvedPath) const { - const std::string fnName = TF_FUNC_NAME(); - { - std::ostringstream logMsg; - logMsg << "[tid=" << std::this_thread::get_id() << "] " << fnName - << "\n assetPath: " << assetPath - << "\n resolvedPath: " << resolvedPath.GetPathString(); - logger_->debug(logMsg.str()); - } - - auto result = ArDefaultResolver::_GetModificationTimestamp(assetPath, resolvedPath); - - { - std::ostringstream logMsg; - logMsg << "[tid=" << std::this_thread::get_id() << "] " - << "result: " << result.GetTime(); - logger_->debug(logMsg.str()); - } - - return result; + return ArDefaultResolver::_GetModificationTimestamp(assetPath, resolvedPath); } std::shared_ptr UsdOpenAssetIOResolver::_OpenAsset( const ArResolvedPath &resolvedPath) const { - const std::string fnName = TF_FUNC_NAME(); - { - std::ostringstream logMsg; - logMsg << "[tid=" << std::this_thread::get_id() << "] " << fnName - << "\n resolvedPath: " << resolvedPath.GetPathString(); - logger_->debug(logMsg.str()); - } - - auto result = ArDefaultResolver::_OpenAsset(resolvedPath); - - { - std::ostringstream logMsg; - logMsg << "[tid=" << std::this_thread::get_id() << "] " - << "result: " << result.get(); - logger_->debug(logMsg.str()); - } - return result; + return ArDefaultResolver::_OpenAsset(resolvedPath); } // TODO(DF): Implement for publishing workflow. bool UsdOpenAssetIOResolver::_CanWriteAssetToPath(const ArResolvedPath &resolvedPath, std::string *whyNot) const { - auto result = ArDefaultResolver::CanWriteAssetToPath(resolvedPath, whyNot); - - logger_->debug(TF_FUNC_NAME()); - logger_->debug(" resolvedPath: " + resolvedPath.GetPathString()); - logger_->debug(" result: " + std::to_string(static_cast(result))); - - return result; + return ArDefaultResolver::CanWriteAssetToPath(resolvedPath, whyNot); } // TODO(DF): Implement for publishing workflow. std::shared_ptr UsdOpenAssetIOResolver::_OpenAssetForWrite( const ArResolvedPath &resolvedPath, WriteMode writeMode) const { - logger_->debug(TF_FUNC_NAME()); - logger_->debug(" resolvedPath: " + resolvedPath.GetPathString()); - return ArDefaultResolver::_OpenAssetForWrite(resolvedPath, writeMode); } From b5169943776990d19ac5edfcbafb904d4ea648f0 Mon Sep 17 00:00:00 2001 From: Tom Cowland Date: Tue, 15 Aug 2023 15:24:27 +0100 Subject: [PATCH 5/9] [Core] Fix recursive CanWriteAssetToPath Signed-off-by: Tom Cowland --- src/resolver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resolver.cpp b/src/resolver.cpp index 5973427..8920eb4 100644 --- a/src/resolver.cpp +++ b/src/resolver.cpp @@ -286,7 +286,7 @@ std::shared_ptr UsdOpenAssetIOResolver::_OpenAsset( // TODO(DF): Implement for publishing workflow. bool UsdOpenAssetIOResolver::_CanWriteAssetToPath(const ArResolvedPath &resolvedPath, std::string *whyNot) const { - return ArDefaultResolver::CanWriteAssetToPath(resolvedPath, whyNot); + return ArDefaultResolver::_CanWriteAssetToPath(resolvedPath, whyNot); } // TODO(DF): Implement for publishing workflow. From 46b441b82e053df209c46a873a1027a49de5ae9b Mon Sep 17 00:00:00 2001 From: Tom Cowland Date: Thu, 10 Aug 2023 19:51:31 -0700 Subject: [PATCH 6/9] [Core] Refactor resolve helpers to avoid double `isEntityReferenceString` The previous implementation doubled up the `isEntityReferenceString` check. Though probably not a big deal, this code will serve as an integration example, and so should illustrate best practice. Tweaks names slightly to help with grokability. Signed-off-by: Tom Cowland --- src/resolver.cpp | 73 +++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 41 deletions(-) diff --git a/src/resolver.cpp b/src/resolver.cpp index 8920eb4..78eac3b 100644 --- a/src/resolver.cpp +++ b/src/resolver.cpp @@ -94,51 +94,41 @@ class UsdOpenAssetIOHostInterface : public openassetio::hostApi::HostInterface { /** * Retrieve the resolved file path of an entity reference. * - * If the given `assetPath` is not an entity reference, assume it's - * already a path and pass through unmodified. - * - * Otherwise, resolve the "locateableContent" trait of the entity and + * This will resolve the "locateableContent" trait of the entity and * return the "location" property of it, converted from a URL to a - * posix file path. + * file path. * * @param manager OpenAssetIO manager plugin wrapper. * @param context OpenAssetIO calling context. - * @param assetPath String-like asset path that may or may not be an - * entity reference. - * @return Resolved file path if `assetPath` is an entity reference, - * `assetPath` otherwise. + * @param entityReference The reference to resolve. + * @return Resolved file path. */ -template -Ref locationInManagerContextForEntity(const openassetio::hostApi::ManagerPtr &manager, - const openassetio::ContextConstPtr &context, Ref assetPath) { - // Check if the assetPath is an OpenAssetIO entity reference. - if (auto entityReference = manager->createEntityReferenceIfValid(assetPath)) { - using openassetio_mediacreation::traits::content::LocatableContentTrait; - - // Resolve the locatable content trait, this will provide a URL - // that points to the final content - openassetio::TraitsDataPtr traitsData = - manager->resolve(std::move(*entityReference), {LocatableContentTrait::kId}, context); - - // OpenAssetIO is URL based, but we need a path, so check the - // scheme and decode into a path - - static constexpr std::string_view kFileURLScheme{"file://"}; - static constexpr std::size_t kProtocolSize = kFileURLScheme.size(); - - openassetio::Str url = LocatableContentTrait(traitsData).getLocation(); - if (url.rfind(kFileURLScheme, 0) == openassetio::Str::npos) { - std::string msg = "Only file URLs are supported: "; - msg += url; - throw std::runtime_error(msg); - } - - // TODO(tc): Decode % escape sequences properly - replaceAllInString(url, "%20", " "); - return Ref{url.substr(kProtocolSize)}; +std::string resolveToPath(const openassetio::hostApi::ManagerPtr &manager, + const openassetio::ContextConstPtr &context, + const openassetio::EntityReference &entityReference) { + using openassetio_mediacreation::traits::content::LocatableContentTrait; + + // Resolve the locatable content trait, this will provide a URL + // that points to the final content + openassetio::TraitsDataPtr traitsData = + manager->resolve(entityReference, {LocatableContentTrait::kId}, context); + + // OpenAssetIO is URL based, but we need a path, so check the + // scheme and decode into a path + + static constexpr std::string_view kFileURLScheme{"file://"}; + static constexpr std::size_t kProtocolSize = kFileURLScheme.size(); + + openassetio::Str url = LocatableContentTrait(traitsData).getLocation(); + if (url.rfind(kFileURLScheme, 0) == openassetio::Str::npos) { + std::string msg = "Only file URLs are supported: "; + msg += url; + throw std::runtime_error(msg); } - return assetPath; + // TODO(tc): Decode % escape sequences properly + replaceAllInString(url, "%20", " "); + return url.substr(kProtocolSize); } /** @@ -183,8 +173,10 @@ auto catchAndLogExceptions(Fn &&fn, const openassetio::log::LoggerInterfacePtr & } } // namespace + // ------------------------------------------------------------ /* Ar Resolver Implementation */ + UsdOpenAssetIOResolver::UsdOpenAssetIOResolver() { // Note: it is safe to throw exceptions from the constructor. USD will // error gracefully, unlike exceptions from other member functions, @@ -247,9 +239,8 @@ std::string UsdOpenAssetIOResolver::_CreateIdentifierForNewAsset( ArResolvedPath UsdOpenAssetIOResolver::_Resolve(const std::string &assetPath) const { return catchAndLogExceptions( [&] { - if (manager_->isEntityReferenceString(assetPath)) { - return ArResolvedPath{ - locationInManagerContextForEntity(manager_, readContext_, assetPath)}; + if (auto entityReference = manager_->createEntityReferenceIfValid(assetPath)) { + return ArResolvedPath{resolveToPath(manager_, readContext_, *entityReference)}; } return ArDefaultResolver::_Resolve(assetPath); }, From 741f6e4e71d3d7bf4aa03ad7312ebaf853859786 Mon Sep 17 00:00:00 2001 From: Tom Cowland Date: Tue, 15 Aug 2023 16:10:34 +0100 Subject: [PATCH 7/9] [Core] Error for write operations to entity references Returning an empty result seems to be the way to ensure that an error is raised in a meaningful fashion. TF_ERROR doesn't abort, and TF_FATAL_ERROR aborts in a hard fashion in many situations. Signed-off-by: Tom Cowland --- src/resolver.cpp | 14 ++++++++++++-- tests/test_resolver.py | 19 ++++++++++++++++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/resolver.cpp b/src/resolver.cpp index 78eac3b..de1304f 100644 --- a/src/resolver.cpp +++ b/src/resolver.cpp @@ -230,9 +230,14 @@ std::string UsdOpenAssetIOResolver::_CreateIdentifier( logger_, TF_FUNC_NAME()); } -// TODO(DF): Implement for publishing workflow. std::string UsdOpenAssetIOResolver::_CreateIdentifierForNewAsset( const std::string &assetPath, const ArResolvedPath &anchorAssetPath) const { + if (manager_->isEntityReferenceString(assetPath)) { + std::string message = "Writes to OpenAssetIO entity references are not currently supported "; + message += assetPath; + logger_->critical(message); + return ""; + } return ArDefaultResolver::_CreateIdentifierForNewAsset(assetPath, anchorAssetPath); } @@ -247,8 +252,13 @@ ArResolvedPath UsdOpenAssetIOResolver::_Resolve(const std::string &assetPath) co logger_, TF_FUNC_NAME()); } -// TODO(DF): Implement for publishing workflow. ArResolvedPath UsdOpenAssetIOResolver::_ResolveForNewAsset(const std::string &assetPath) const { + if (manager_->isEntityReferenceString(assetPath)) { + std::string message = "Writes to OpenAssetIO entity references are not currently supported "; + message += assetPath; + logger_->critical(message); + return ArResolvedPath(""); + } return ArDefaultResolver::_ResolveForNewAsset(assetPath); } diff --git a/tests/test_resolver.py b/tests/test_resolver.py index 4e2fbde..7ff6120 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -17,7 +17,7 @@ if os.path.exists(plug_info): os.environ["PXR_PLUGINPATH_NAME"] = plug_info -from pxr import Plug, Usd, Ar, Tf +from pxr import Plug, Usd, Ar, Sdf, Tf # TODO(DF): More tests for error cases. @@ -107,6 +107,23 @@ def test_when_resolves_to_non_file_url_then_error(): assert "UsdOpenAssetIOResolverLogger" in str(exc) +def test_when_writing_to_file_then_ok(capfd, tmp_path): + Sdf.Layer.CreateNew(str(tmp_path / "newLayer.usda")) + logs = capfd.readouterr() + assert ( + "Writes to OpenAssetIO entity references are not currently supported" + not in logs.err + ) + + +def test_when_writing_to_entity_ref_then_error(): + with pytest.raises(Tf.ErrorException) as exc: + Sdf.Layer.CreateNew("bal:///newLayer") + assert "Writes to OpenAssetIO entity references are not currently supported" in str( + exc + ) + + ##### Utility Functions ##### From 06b03dd3c2f539650ee04743726d38b1af027ba6 Mon Sep 17 00:00:00 2001 From: Tom Cowland Date: Thu, 10 Aug 2023 20:27:36 -0700 Subject: [PATCH 8/9] [Docs] Add a little more info about the implementation Signed-off-by: Tom Cowland --- src/resolver.cpp | 69 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/src/resolver.cpp b/src/resolver.cpp index de1304f..a4e1cdf 100644 --- a/src/resolver.cpp +++ b/src/resolver.cpp @@ -52,7 +52,11 @@ void replaceAllInString(std::string &subject, const std::string &search, } } -/// Converter logger from OpenAssetIO log framing to USD log outputs. +/* + * OpenAssetIO LoggerInterface implementation + * + * Converter logger from OpenAssetIO log framing to USD log outputs. + */ class UsdOpenAssetIOResolverLogger : public openassetio::log::LoggerInterface { public: void log(Severity severity, const openassetio::Str &message) override { @@ -65,8 +69,6 @@ class UsdOpenAssetIOResolverLogger : public openassetio::log::LoggerInterface { TF_DEBUG(OPENASSETIO_RESOLVER).Msg(message + "\n"); break; case Severity::kError: - // TODO(EM) : Review to see which error types are most appropriate, - // are all errors (not criticals) non fatal? TF_ERROR(TfDiagnosticType::TF_DIAGNOSTIC_NONFATAL_ERROR_TYPE, message); break; case Severity::kInfo: @@ -80,6 +82,12 @@ class UsdOpenAssetIOResolverLogger : public openassetio::log::LoggerInterface { } }; +/** + * OpenAssetIO HostInterface implementation + * + * This identifies the Ar2 plugin uniquely should the manager plugin + * wish to adapt its behaviour. + */ class UsdOpenAssetIOHostInterface : public openassetio::hostApi::HostInterface { public: [[nodiscard]] openassetio::Identifier identifier() const override { @@ -173,9 +181,11 @@ auto catchAndLogExceptions(Fn &&fn, const openassetio::log::LoggerInterfacePtr & } } // namespace - // ------------------------------------------------------------ -/* Ar Resolver Implementation */ + +/* + * Ar Resolver Implementation + */ UsdOpenAssetIOResolver::UsdOpenAssetIOResolver() { // Note: it is safe to throw exceptions from the constructor. USD will @@ -199,13 +209,16 @@ UsdOpenAssetIOResolver::UsdOpenAssetIOResolver() { openassetio::hostApi::ManagerFactory::kDefaultManagerConfigEnvVarName}; } - // TODO(DF): Set appropriate locale. readContext_ = openassetio::Context::make(openassetio::Context::Access::kRead, openassetio::Context::Retention::kTransient); } UsdOpenAssetIOResolver::~UsdOpenAssetIOResolver() = default; +/* + * Read + */ + std::string UsdOpenAssetIOResolver::_CreateIdentifier( const std::string &assetPath, const ArResolvedPath &anchorAssetPath) const { return catchAndLogExceptions( @@ -219,7 +232,7 @@ std::string UsdOpenAssetIOResolver::_CreateIdentifier( // resolve to an absolute path, making the anchorAssetPath redundant // (for now). // TODO(DF): Allow the manager to provide an identifier representing - // an "anchored" entity reference. + // an "anchored" entity reference via getWithRelationship. identifier = assetPath; } else { identifier = ArDefaultResolver::_CreateIdentifier(assetPath, anchorAssetPath); @@ -230,17 +243,6 @@ std::string UsdOpenAssetIOResolver::_CreateIdentifier( logger_, TF_FUNC_NAME()); } -std::string UsdOpenAssetIOResolver::_CreateIdentifierForNewAsset( - const std::string &assetPath, const ArResolvedPath &anchorAssetPath) const { - if (manager_->isEntityReferenceString(assetPath)) { - std::string message = "Writes to OpenAssetIO entity references are not currently supported "; - message += assetPath; - logger_->critical(message); - return ""; - } - return ArDefaultResolver::_CreateIdentifierForNewAsset(assetPath, anchorAssetPath); -} - ArResolvedPath UsdOpenAssetIOResolver::_Resolve(const std::string &assetPath) const { return catchAndLogExceptions( [&] { @@ -252,6 +254,26 @@ ArResolvedPath UsdOpenAssetIOResolver::_Resolve(const std::string &assetPath) co logger_, TF_FUNC_NAME()); } +/* + * Write + * + * We don't currently support writes to OpenAssetIO entity references. + * In order to call register when the ArAsset is closed, we'll need to + * not resolve in _ResolveForNewAsset, and pass the entity reference + * through. + */ + +std::string UsdOpenAssetIOResolver::_CreateIdentifierForNewAsset( + const std::string &assetPath, const ArResolvedPath &anchorAssetPath) const { + if (manager_->isEntityReferenceString(assetPath)) { + std::string message = "Writes to OpenAssetIO entity references are not currently supported "; + message += assetPath; + logger_->critical(message); + return ""; + } + return ArDefaultResolver::_CreateIdentifierForNewAsset(assetPath, anchorAssetPath); +} + ArResolvedPath UsdOpenAssetIOResolver::_ResolveForNewAsset(const std::string &assetPath) const { if (manager_->isEntityReferenceString(assetPath)) { std::string message = "Writes to OpenAssetIO entity references are not currently supported "; @@ -264,6 +286,15 @@ ArResolvedPath UsdOpenAssetIOResolver::_ResolveForNewAsset(const std::string &as /* * Pass-through asset operations + * + * These methods are simply relayed to the ArDefaultResolver. There may + * be interest in fetching data for some of these from the manager, but + * we don't have a real use case just yet. Doing so increases complexity + * as we'd need to return both the resolved path _and_ the original + * entity reference from _Resolve, so we could make queries in these + * methods. We'll need this for publishing operations, but avoiding that + * overhead for the more common (and hot) read case is critical. + * */ std::string UsdOpenAssetIOResolver::_GetExtension(const std::string &assetPath) const { return ArDefaultResolver::_GetExtension(assetPath); @@ -284,13 +315,11 @@ std::shared_ptr UsdOpenAssetIOResolver::_OpenAsset( return ArDefaultResolver::_OpenAsset(resolvedPath); } -// TODO(DF): Implement for publishing workflow. bool UsdOpenAssetIOResolver::_CanWriteAssetToPath(const ArResolvedPath &resolvedPath, std::string *whyNot) const { return ArDefaultResolver::_CanWriteAssetToPath(resolvedPath, whyNot); } -// TODO(DF): Implement for publishing workflow. std::shared_ptr UsdOpenAssetIOResolver::_OpenAssetForWrite( const ArResolvedPath &resolvedPath, WriteMode writeMode) const { return ArDefaultResolver::_OpenAssetForWrite(resolvedPath, writeMode); From 6f853a476305e85eb814fe84b82384353463fefe Mon Sep 17 00:00:00 2001 From: Tom Cowland Date: Tue, 22 Aug 2023 17:59:40 +0100 Subject: [PATCH 9/9] [Core] Don't set Context retention This will shortly be removed from OpenAssetIO... Signed-off-by: Tom Cowland --- src/resolver.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/resolver.cpp b/src/resolver.cpp index a4e1cdf..bb5cf51 100644 --- a/src/resolver.cpp +++ b/src/resolver.cpp @@ -209,8 +209,7 @@ UsdOpenAssetIOResolver::UsdOpenAssetIOResolver() { openassetio::hostApi::ManagerFactory::kDefaultManagerConfigEnvVarName}; } - readContext_ = openassetio::Context::make(openassetio::Context::Access::kRead, - openassetio::Context::Retention::kTransient); + readContext_ = openassetio::Context::make(openassetio::Context::Access::kRead); } UsdOpenAssetIOResolver::~UsdOpenAssetIOResolver() = default;