Skip to content

Commit

Permalink
[Docs] Add DR002 for versioning in codegen
Browse files Browse the repository at this point in the history
Part of OpenAssetIO#88. Consolidate the discussion, provoked by iterations of the
design proposal, into a decision record.

Signed-off-by: David Feltell <david.feltell@foundry.com>
  • Loading branch information
feltech committed May 15, 2024
1 parent 0d25667 commit e5669da
Showing 1 changed file with 151 additions and 0 deletions.
151 changes: 151 additions & 0 deletions decisions/DR002-Versioning-traits-and-specifications-codegen.md
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit e5669da

Please sign in to comment.