|
| 1 | +# How-to: Work with Object References |
| 2 | + |
| 3 | +This how-to guide shows you how to work with properties whose data type is OBJECT in order to reference other objects in openBIS. |
| 4 | + |
| 5 | +## What are object references? |
| 6 | + |
| 7 | +Some object types have properties with `data_type="OBJECT"` that create references to other objects. For example: |
| 8 | + |
| 9 | +- An `Instrument` might have a `responsible_person` property that references a `Person` object |
| 10 | +- A `Calibration` might have an `instrument` property that references an `Instrument` object |
| 11 | +- A `Sample` might have a `parent_sample` property that references another `Sample` object |
| 12 | + |
| 13 | +These are properties defined to reference other existing objects in openBIS. Their purpose is to link between objects, similar to what a parent-child relationship do. However, these links have a semantic meaning, while parent-child relationships are only connecting inputs with outputs. |
| 14 | + |
| 15 | +??? note "Semantic meaning of object referencing" |
| 16 | + The semantic meaning of object referencing is given by the name of the property (e.g., a person responsible for operating an instrument). Nevertheless, openBIS will soon allow for adding more metainformation to these properties to create a more complete description of objects and their relationships. |
| 17 | + |
| 18 | +## Option 1: Reference by Object Instance |
| 19 | + |
| 20 | +When creating multiple related objects in the same operation, you can reference them directly: |
| 21 | + |
| 22 | +```python |
| 23 | +from bam_masterdata.datamodel.object_types import Person, Instrument |
| 24 | +from bam_masterdata.metadata.entities import CollectionType |
| 25 | + |
| 26 | +# Create a collection |
| 27 | +collection = CollectionType() |
| 28 | + |
| 29 | +# Create a person |
| 30 | +person = Person(name="Dr. Jane Smith") |
| 31 | +person.code = "PERSON_001" # ⚠️ Must set a code! |
| 32 | +person_id = collection.add(person) |
| 33 | + |
| 34 | +# Create an instrument and reference the person |
| 35 | +instrument = Instrument(name="High-Resolution Microscope") |
| 36 | +instrument.responsible_person = person # Direct reference |
| 37 | +instrument_id = collection.add(instrument) |
| 38 | +``` |
| 39 | + |
| 40 | +!!! warning "Object must have a code" |
| 41 | + When referencing an object instance, it **must** have a `code` attribute set. If not, you'll get a `ValueError`: |
| 42 | + |
| 43 | + ``` |
| 44 | + ValueError: Object instance for 'responsible_person' must have a 'code' attribute set |
| 45 | + ``` |
| 46 | + |
| 47 | +## Option 2: Reference by Path String |
| 48 | + |
| 49 | +If the object already exists in openBIS, you can reference it using its identifier path: |
| 50 | + |
| 51 | +```python |
| 52 | +from bam_masterdata.datamodel.object_types import Instrument |
| 53 | + |
| 54 | +# Create an instrument |
| 55 | +instrument = Instrument(name="Spectrometer X500") |
| 56 | + |
| 57 | +# Reference an existing person using the path format |
| 58 | +# Format: /{space}/{project}/{collection}/{object} |
| 59 | +instrument.responsible_person = "/LAB_SPACE/INSTRUMENTS/STAFF/PERSON_001" |
| 60 | + |
| 61 | +# Or without collection: /{space}/{project}/{object} |
| 62 | +instrument.responsible_person = "/LAB_SPACE/INSTRUMENTS/PERSON_001" |
| 63 | +``` |
| 64 | + |
| 65 | +!!! note "Path validation" |
| 66 | + The path must: |
| 67 | + |
| 68 | + - Start with `/` |
| 69 | + - Have either 3 parts (space/project/object) or 4 parts (space/project/collection/object) |
| 70 | + - Point to an existing object in openBIS |
| 71 | + |
| 72 | +## Combining Both Approaches |
| 73 | + |
| 74 | +You can mix both approaches in the same parser: |
| 75 | + |
| 76 | +```python |
| 77 | +from bam_masterdata.parsing import AbstractParser |
| 78 | +from bam_masterdata.datamodel.object_types import Person, Instrument |
| 79 | + |
| 80 | +class InstrumentParser(AbstractParser): |
| 81 | + def parse(self, files, collection, logger): |
| 82 | + # Create a new person |
| 83 | + new_person = Person(name="Dr. Alice Johnson") |
| 84 | + new_person.code = "PERSON_NEW_001" |
| 85 | + collection.add(new_person) |
| 86 | + |
| 87 | + # Instrument 1: References the newly created person |
| 88 | + instrument1 = Instrument(name="Microscope A") |
| 89 | + instrument1.responsible_person = new_person # Object instance |
| 90 | + collection.add(instrument1) |
| 91 | + |
| 92 | + # Instrument 2: References an existing person in openBIS |
| 93 | + instrument2 = Instrument(name="Microscope B") |
| 94 | + instrument2.responsible_person = "/LAB_SPACE/PROJECT/PERSON_EXISTING" # Path |
| 95 | + collection.add(instrument2) |
| 96 | +``` |
| 97 | + |
| 98 | +## Troubleshooting |
| 99 | + |
| 100 | +### Error: "Object instance must have a 'code' attribute set" |
| 101 | + |
| 102 | +**Cause**: You're trying to reference an object instance that doesn't have a code. |
| 103 | + |
| 104 | +**Solution**: Set the `code` attribute before using the object as a reference: |
| 105 | + |
| 106 | +```python |
| 107 | +person = Person(name="Dr. Smith") |
| 108 | +person.code = "PERSON_001" # ✓ Set the code |
| 109 | +instrument.responsible_person = person |
| 110 | +``` |
| 111 | + |
| 112 | +### Error: "Invalid OBJECT path format" |
| 113 | + |
| 114 | +**Cause**: The path string doesn't follow the required format. |
| 115 | + |
| 116 | +**Solution**: Ensure your path: |
| 117 | + |
| 118 | +- Starts with `/` |
| 119 | +- Has 3 or 4 parts separated by `/` |
| 120 | + |
| 121 | +```python |
| 122 | +# ✗ Wrong |
| 123 | +instrument.responsible_person = "PERSON_001" |
| 124 | +instrument.responsible_person = "SPACE/PROJECT/PERSON_001" |
| 125 | + |
| 126 | +# ✓ Correct |
| 127 | +instrument.responsible_person = "/SPACE/PROJECT/PERSON_001" |
| 128 | +instrument.responsible_person = "/SPACE/PROJECT/COLLECTION/PERSON_001" |
| 129 | +``` |
| 130 | + |
| 131 | +### Error: "Failed to resolve OBJECT reference" |
| 132 | + |
| 133 | +**Cause**: The path references an object that doesn't exist in openBIS. |
| 134 | + |
| 135 | +**Solution**: Verify the object exists in openBIS at the specified path, or create it first: |
| 136 | + |
| 137 | +```python |
| 138 | +# Check if the object exists in openBIS before referencing |
| 139 | +openbis = ologin(...) |
| 140 | +try: |
| 141 | + obj = openbis.get_object("/SPACE/PROJECT/PERSON_001") |
| 142 | + print(f"Object exists: {obj.identifier}") |
| 143 | +except: |
| 144 | + print("Object not found - create it first or use a different path") |
| 145 | +``` |
0 commit comments