diff --git a/decisions/DR002-Versioning-traits-and-specifications-codegen.md b/decisions/DR002-Versioning-traits-and-specifications-codegen.md new file mode 100644 index 0000000..9794ec0 --- /dev/null +++ b/decisions/DR002-Versioning-traits-and-specifications-codegen.md @@ -0,0 +1,151 @@ +# DR025 Versioning Traits and Specification - generated view classes + +- **Status:** Proposed +- **Impact:** High +- **Driver:** @feltech +- **Approver:** @felltech @elliotcmorris @themissingcow +- **Outcome:** + + +## Background + +The medium of data exchange between a host and a manager is a logically +opaque data blob, i.e. a `TraitsData` object. In order to extract information +from this object, Trait and/or Specification view classes must be +used[^1]. These classes wrap a `TraitsData` instance, and provide a +suite of accessor and mutator methods that are relevant to the target +trait. The classes are generated from a YAML schema (e.g. see +[traits.yaml](../traits.yaml)). + +Hosts and managers may use different versions of the schema, and hence +different versions of the view classes, and yet still wish to work +together. + +This decision record follows on from a previous decision (OpenAssetIO +[DR023](https://github.com/OpenAssetIO/OpenAssetIO/blob/main/doc/decisions/DR023-Versioning-traits-and-specifications-method.md)) +that communicating a trait's version should be done by bundling the +version number with the data blob that is communicated across the API, +i.e. within `TraitsData`, most likely by appending the version number to +the unique trait ID. + +With this previous decision in mind, we then need to decide on how the +trait versions are represented in the high level interface, i.e. in +the definition and usage of Trait/Specification view classes. + +A motivating example should make this problem clear. + +[^1]: In reality, a `TraitsData` is a simple dictionary-like structure, +and the `TraitsData` type has a low-level interface for interacting with +it, but usage of this is discouraged. + +### Motivating example + +An example usage of the current form of these generated classes might +be: + +```python +url = LocatableContentTrait(trait_data).getLocation() +``` + +Imagine that we want to rename the LocatableContent trait's `"location"` +property to a more descriptive `"url"` property, hence changing the +generated view class's method from `getLocation` to `getUrl`. + +Given that hosts and managers are developed independently, we may end up +with a situation where one side is setting `"location"` (using +`setLocation`) in the data, handing it over to the other side, who then +attempts to read `"url"` (using `getUrl`). I.e. we have a version +mismatch. + +There is therefore a conflict at the data layer (i.e. field names differ +for the same semantic information). With C++, the data layer is where +the conflict ends. The Trait/Specification view classes are private +utility classes whose symbols should not be exported, so there will be +no source or binary incompatibility. + +However, with Python there is no such concept of a private, build-time +only, class. The manager plugin and host application must use the same +`openassetio-mediacreation` distribution package in the Python +environment (not considering, for the moment, custom vendoring). So one +side or the other will hit an `AttributeError` exception when trying to +use a method from the version they developed against, rather than the +version installed into the environment. + +### Assumptions + +We need a way for host and manager plugin authors to work with multiple +trait versions. + +* Hosts and manager plugins must have access to a Trait/Specification + view class for each version, such that they can detect that a + particular version of a trait is imbued in the data, and extract + property values specific to that version. +* Trait unique IDs will be suffixed with a version number whenever the + trait ID is used. This means two Trait view classes for the same + trait, but for different versions, will not be able to detect the + presence of a trait of the other version in trait data. Utility + functions to do this may be added in the future, but it is out of scope + for now. +* If a Specification view class is used to construct a trait set/data, + that data will _not_ have the Specification version encoded in the + data directly (only implicitly through the versioned IDs of the + composite traits). + +## Relevant data + +[OpenTimelineIO schema +versioning](https://opentimelineio.readthedocs.io/en/latest/tutorials/otio-file-format-specification.html#example) +is perhaps the closest analog. The version of the schema is appended to +the schema ID whenever it appears within a OTIO JSON document. + +The options presented were arrived at by sketching a proposal in [a Pull +Request](https://github.com/OpenAssetIO/OpenAssetIO-MediaCreation/pull/90), +soliciting feedback, and iterating. The final form of that PR reflects +the chosen option. + +## Options considered + +### Option 1 - Per schema versioning + +For example +```python +from openassetio_mediacreation.v1.traits import LocatableContent as LocatableContent_v1 +from openassetio_mediacreation.v2.traits import LocatableContent as LocatableContent_v2 +from openassetio_mediacreation.v2.specification import ImageSpecification +``` + +#### Pros + +- Tantalising possibility to use [Python namespace + packages](https://packaging.python.org/en/latest/guides/packaging-namespace-packages) + to allow different schema versions to be installed independently + side-by-side. +- The schema version a Specification comes from instantly tells you the + schema version of the consitituent traits. + +#### Cons + +- A source-incompatible breaking change without significant + special-casing. + +### Option 2 - Per Trait/Specification versioning + +For example +```python +from openassetio_mediacreation.traits import LocatableContent_v1 +from openassetio_mediacreation.traits import LocatableContent_v2 +from openassetio_mediacreation.specification import ImageSpecification_v2 +``` + +#### Pros + +- Fairly trivial to say "`_v0`" is equivalent to "" (blank), then e.g. + `import LocatableContent` continues to work as before, and this option + is fully source compatible. I.e. not a breaking change. + +#### Cons + +- No indication of the version of the constituent traits from the + version of a Schema + +## Outcome