def test_grid_list(example_model_and_crs): model, crs = example_model_and_crs # create some grid objects grid_a, grid_b, grid_c = add_grids(model, crs, False) # access a grid part grid_1 = model.grid(title='GRID C') assert grid_1 is not None assert bu.matching_uuids(grid_1.uuid, grid_c.uuid) assert len(model.parts(obj_type='IjkGridRepresentation')) == 3 # check that the call to grid() has added the returned grid to the cache list assert len(model.grid_list_uuid_list()) == len(model.grid_list) == 1 assert bu.matching_uuids(model.grid_list[0].uuid, grid_c.uuid) assert model.grid_list[0] is grid_1 # access another grid by uuid and check that it is added to cached list grid_2 = model.grid(uuid=grid_a.uuid) assert bu.matching_uuids(grid_2.uuid, grid_a.uuid) assert len(model.grid_list_uuid_list()) == len(model.grid_list) == 2 # add all 3 grids to the grid cache list, checking for duplicates model.add_grid(grid_a, check_for_duplicates=True) model.add_grid(grid_b, check_for_duplicates=True) model.add_grid(grid_c, check_for_duplicates=True) assert len(model.parts(obj_type='IjkGridRepresentation')) == 3 assert len(model.grid_list_uuid_list()) == 3 # check use of cached grids grid_3a = model.grid_for_uuid_from_grid_list(grid_b.uuid) assert bu.matching_uuids(grid_3a.uuid, grid_b.uuid) assert grid_3a is grid_b grid_3b = model.grid_for_uuid_from_grid_list(grid_b.uuid) assert grid_3a is grid_3b assert tuple(grid_3a.extent_kji) == (3, 3, 3)
def test_crs_reuse(): model = rq.Model(new_epc = True, create_basics = True) crs_a = rqc.Crs(model) crs_a.create_xml() crs_b = rqc.Crs(model) crs_b.create_xml() assert len(model.parts(obj_type = 'LocalDepth3dCrs')) == 1 assert crs_a == crs_b assert bu.matching_uuids(crs_a.uuid, crs_b.uuid) crs_c = rqc.Crs(model, z_inc_down = False) crs_c.create_xml() assert len(model.parts(obj_type = 'LocalDepth3dCrs')) == 2 assert crs_c != crs_a assert not bu.matching_uuids(crs_c.uuid, crs_a.uuid) crs_d = rqc.Crs(model, z_units = 'ft') crs_d.create_xml() assert len(model.parts(obj_type = 'LocalDepth3dCrs')) == 3 crs_e = rqc.Crs(model, z_inc_down = False) crs_e.create_xml() assert len(model.uuids(obj_type = 'LocalDepth3dCrs')) == 3 assert crs_e == crs_c assert bu.matching_uuids(crs_e.uuid, crs_c.uuid) crs_f = rqc.Crs(model) crs_f.create_xml(reuse = False) assert len(model.parts(obj_type = 'LocalDepth3dCrs')) == 4 assert crs_f == crs_a assert not bu.matching_uuids(crs_f.uuid, crs_a.uuid)
def test_model_iterators(example_model_with_well): model, well_interp, datum, traj = example_model_with_well w = next(model.iter_wellbore_interpretations()) d = next(model.iter_md_datums()) t = next(model.iter_trajectories()) assert bu.matching_uuids(w.uuid, well_interp.uuid) assert bu.matching_uuids(d.uuid, datum.uuid) assert bu.matching_uuids(t.uuid, traj.uuid) assert w == well_interp assert d == datum assert t == traj
def test_model_copy_all_parts(example_model_with_properties): epc = example_model_with_properties.epc_file dir = example_model_with_properties.epc_directory copied_epc = os.path.join(dir, 'copied.epc') # test copying without consolidation original = rq.Model(epc) assert original is not None copied = rq.new_model(copied_epc) copied.copy_all_parts_from_other_model(original, consolidate=False) assert set(original.uuids()) == set(copied.uuids()) assert set(original.parts()) == set(copied.parts()) # test without consolidation of two crs objects copied = rq.new_model(copied_epc) new_crs = rqc.Crs(copied) new_crs.create_xml() copied.copy_all_parts_from_other_model(original, consolidate=False) assert len(copied.parts()) == len(original.parts()) + 1 assert set(original.parts()).issubset(set(copied.parts())) assert len(copied.parts(obj_type='LocalDepth3dCrs')) == 2 # test with consolidation of two crs objects copied = rq.new_model(copied_epc) new_crs = rqc.Crs(copied) new_crs.create_xml() copied.copy_all_parts_from_other_model(original, consolidate=True) assert len(copied.parts()) == len(original.parts()) assert len(copied.parts(obj_type='LocalDepth3dCrs')) == 1 crs_uuid = copied.uuid(obj_type='LocalDepth3dCrs') assert (bu.matching_uuids(crs_uuid, new_crs.uuid) or bu.matching_uuids( crs_uuid, original.uuid(obj_type='LocalDepth3dCrs'))) # test write and re-load of copied model copied.store_epc() re_opened = rq.Model(copied_epc) assert re_opened is not None assert len(copied.parts()) == len(original.parts()) crs_uuid = re_opened.uuid(obj_type='LocalDepth3dCrs') assert (bu.matching_uuids(crs_uuid, new_crs.uuid) or bu.matching_uuids( crs_uuid, original.uuid(obj_type='LocalDepth3dCrs')))
def extract_patches(self, surface_root): """Scan surface root for triangle patches, create TriangulatedPatch objects and build up patch_list.""" if len(self.patch_list) or surface_root is None: return paired_list = [] self.patch_list = [] for child in surface_root: if rqet.stripped_of_prefix(child.tag) != 'TrianglePatch': continue patch_index = rqet.find_tag_int(child, 'PatchIndex') assert patch_index is not None triangulated_patch = TriangulatedPatch(self.model, patch_index=patch_index, patch_node=child) assert triangulated_patch is not None if self.crs_uuid is None: self.crs_uuid = triangulated_patch.crs_uuid else: if not bu.matching_uuids(triangulated_patch.crs_uuid, self.crs_uuid): log.warning( 'mixed coordinate reference systems in use within a surface' ) paired_list.append((patch_index, triangulated_patch)) assert len( paired_list ), f'no triangulated patches found for surface: {self.title}' paired_list.sort() assert len(paired_list) and paired_list[0][0] == 0 and len( paired_list) == paired_list[-1][0] + 1 for _, patch in paired_list: self.patch_list.append(patch)
def test_model(tmp_path): epc = os.path.join(tmp_path, 'model.epc') model = rq.new_model(epc) assert model is not None crs = rqc.Crs(model) crs_root = crs.create_xml() model.store_epc() assert os.path.exists(epc) md_datum_1 = rqw.MdDatum(model, location=(0.0, 0.0, -50.0), crs_uuid=crs.uuid) md_datum_1.create_xml(title='Datum & 1') md_datum_2 = rqw.MdDatum(model, location=(3.0, 0.0, -50.0), crs_uuid=crs.uuid) md_datum_2.create_xml(title='Datum < 2') assert len(model.uuids(obj_type='MdDatum')) == 2 model.store_epc() model = rq.Model(epc) assert model is not None assert len(model.uuids(obj_type='MdDatum')) == 2 datum_part_1 = model.part(obj_type='MdDatum', title='1', title_mode='ends') datum_part_2 = model.part(obj_type='MdDatum', title='2', title_mode='ends') assert datum_part_1 is not None and datum_part_2 is not None and datum_part_1 != datum_part_2 datum_uuid_1 = rqet.uuid_in_part_name(datum_part_1) datum_uuid_2 = rqet.uuid_in_part_name(datum_part_2) assert not bu.matching_uuids(datum_uuid_1, datum_uuid_2) p1 = model.uuid_part_dict[bu.uuid_as_int(datum_uuid_1)] p2 = model.uuid_part_dict[bu.uuid_as_int(datum_uuid_2)] assert p1 == datum_part_1 and p2 == datum_part_2
def test_two_grid_gcs(tmp_path): epc = make_epc_with_abutting_grids(tmp_path) # re-open the model and establish the abutting grid connection set model = rq.Model(epc) gcs_uuid = model.uuid(obj_type='GridConnectionSetRepresentation') assert gcs_uuid is not None gcs = rqf.GridConnectionSet(model, uuid=gcs_uuid) assert gcs is not None gcs.cache_arrays() # check that attributes have been preserved assert gcs.number_of_grids() == 2 assert len(gcs.grid_list) == 2 assert not bu.matching_uuids(gcs.grid_list[0].uuid, gcs.grid_list[1].uuid) assert gcs.count == 6 assert gcs.grid_index_pairs.shape == (6, 2) assert np.all(gcs.grid_index_pairs[:, 0] == 0) assert np.all(gcs.grid_index_pairs[:, 1] == 1) assert gcs.face_index_pairs.shape == (6, 2) assert np.all(gcs.face_index_pairs[:, 0] == gcs.face_index_map[1, 1]) # J+ assert np.all(gcs.face_index_pairs[:, 1] == gcs.face_index_map[1, 0]) # J- assert tuple(gcs.face_index_inverse_map[gcs.face_index_pairs[0, 0]]) == (1, 1) assert tuple(gcs.face_index_inverse_map[gcs.face_index_pairs[0, 1]]) == (1, 0) assert gcs.cell_index_pairs.shape == (6, 2) assert np.all(gcs.cell_index_pairs >= 0) assert np.all(gcs.cell_index_pairs < 60)
def try_reuse(self): """Look for an equivalent existing RESQML object and modify the uuid of this object if found. returns: boolean: True if an equivalent object was found, False if not note: by design this method may change this object's uuid as a side effect """ assert self.uuid is not None if self.root is not None: return True uuid_list = self.model.uuids(obj_type=self.resqml_type) for other_uuid in uuid_list: if bu.matching_uuids(self.uuid, other_uuid): logger.debug(f'reusing existing xml for uuid {other_uuid}') return True try: other = self.__class__(self.model, uuid=other_uuid) except Exception: return False if self == other: logger.debug( f'reusing equivalent resqml object with uuid {other_uuid}') self.uuid = other_uuid # NB: change of uuid for this object assert self.root is not None return True return False
def is_equivalent(self, other_crs: 'Crs') -> bool: """Returns True if this crs is effectively the same as the other crs.""" # log.debug('testing crs equivalence') if other_crs is None: return False if self is other_crs: return True if bu.matching_uuids(self.uuid, other_crs.uuid): return True if self.resqml_type != other_crs.resqml_type: return False if self.xy_units != other_crs.xy_units or self.z_units != other_crs.z_units: return False if self.z_inc_down != other_crs.z_inc_down: return False if (self.time_units is not None or other_crs.time_units is not None) and self.time_units != other_crs.time_units: return False if self.axis_order != other_crs.axis_order: return False if not self.has_same_epsg_code(other_crs): return False if self.null_transform and other_crs.null_transform: return True if (maths.isclose(self.x_offset, other_crs.x_offset, abs_tol=1e-4) and maths.isclose(self.y_offset, other_crs.y_offset, abs_tol=1e-4) and maths.isclose( self.z_offset, other_crs.z_offset, abs_tol=1e-4) and maths.isclose( self.rotation, other_crs.rotation, abs_tol=1e-4)): return True # todo: handle and check rotation units; modularly equivalent rotations return False
def is_equivalent(self, other, check_extra_metadata=True): """Returns True if this interpretation is essentially the same as the other; otherwise False.""" if other is None or not isinstance(other, GeobodyInterpretation): return False if self is other or bu.matching_uuids(self.uuid, other.uuid): return True if self.geobody_feature is not None: if not self.geobody_feature.is_equivalent(other.geobody_feature): return False elif other.geobody_feature is not None: return False if self.root is not None and other.root is not None: if rqet.citation_title_for_node( self.root) != rqet.citation_title_for_node(other.root): return False elif self.root is not None or other.root is not None: return False if check_extra_metadata and not equivalent_extra_metadata(self, other): return False return (self.domain == other.domain and equivalent_chrono_pairs( self.has_occurred_during, other.has_occurred_during) and self.composition == other.composition and self.implacement == other.implacement and self.geobody_shape == other.geobody_shape)
def _grid_for_uuid_from_grid_list(model, uuid): """Returns the cached grid object matching the given uuid, if found in the grid list, otherwise None.""" for grid in model.grid_list: if bu.matching_uuids(uuid, grid.uuid): return grid return None
def is_equivalent(self, other, check_extra_metadata=True): """Returns True if this interpretation is essentially the same as the other; otherwise False.""" if other is None or not isinstance(other, HorizonInterpretation): return False if self is other or bu.matching_uuids(self.uuid, other.uuid): return True if self.genetic_boundary_feature is not None: if not self.genetic_boundary_feature.is_equivalent( other.genetic_boundary_feature): return False elif other.genetic_boundary_feature is not None: return False if self.root is not None and other.root is not None: if rqet.citation_title_for_node( self.root) != rqet.citation_title_for_node(other.root): return False elif self.root is not None or other.root is not None: return False if (self.domain != other.domain or not equivalent_chrono_pairs( self.has_occurred_during, other.has_occurred_during) or self.sequence_stratigraphy_surface != other.sequence_stratigraphy_surface): return False if check_extra_metadata and not equivalent_extra_metadata(self, other): return False if not self.boundary_relation_list and not other.boundary_relation_list: return True if not self.boundary_relation_list or not other.boundary_relation_list: return False return set(self.boundary_relation_list) == set( other.boundary_relation_list)
def is_equivalent(self, other, check_extra_metadata=True): """Returns True if this interpretation is essentially the same as the other; otherwise False. arguments: other (StratigraphicColumn): the other stratigraphic column to compare this one against check_extra_metadata (bool, default True): if True, then extra metadata items must match for the two columns to be deemed equivalent; if False, extra metadata is ignored in the comparison returns: bool: True if this stratigraphic column is essentially the same as the other; False otherwise """ if not isinstance(other, StratigraphicColumn): return False if self is other or bu.matching_uuids(self.uuid, other.uuid): return True if len(self.ranks) != len(other.ranks): return False for rank_a, rank_b in zip(self.ranks, other.ranks): if rank_a != rank_b: return False if check_extra_metadata and not rqo.equivalent_extra_metadata( self, other): return False return True
def _add_grid(model, grid_object, check_for_duplicates=False): """Add grid object to list of shareable grids for this model.""" if check_for_duplicates: for g in model.grid_list: if bu.matching_uuids(g.uuid, grid_object.uuid): return model.grid_list.append(grid_object)
def __eq__(self, other): """Implements equals operator; uses is_equivalent() otherwise compares class type and uuid.""" if hasattr(self, 'is_equivalent'): return self.is_equivalent(other) if not isinstance(other, self.__class__): return False other_uuid = getattr(other, "uuid", None) return bu.matching_uuids(self.uuid, other_uuid)
def _parts_list_filtered_by_related_uuid(model, parts_list, uuid, uuid_is_source=None): """From a list of parts, returns a list of those parts which have a relationship with the given uuid.""" if not model.rels_present or parts_list is None or uuid is None: return None filtered_list = [] this_part = _part_for_uuid(model, uuid) if this_part is not None: rels_part_root = _root_for_part( model, rqet.rels_part_name_for_part(this_part), is_rels=True) if rels_part_root is not None: for relation_node in rels_part_root: if rqet.stripped_of_prefix( relation_node.tag) != 'Relationship': continue target_part = relation_node.attrib['Target'] if target_part not in parts_list: continue if uuid_is_source is not None: source_dest = relation_node.attrib['Type'] if uuid_is_source: if 'source' not in source_dest: continue else: if 'source' in source_dest: continue filtered_list.append(target_part) for part in parts_list: if part in filtered_list: continue rels_part_root = _root_for_part(model, rqet.rels_part_name_for_part(part), is_rels=True) if rels_part_root is None: continue for relation_node in rels_part_root: if rqet.stripped_of_prefix(relation_node.tag) != 'Relationship': continue target_part = relation_node.attrib['Target'] relation_uuid = rqet.uuid_in_part_name(target_part) if bu.matching_uuids(uuid, relation_uuid): if uuid_is_source is not None: source_dest = relation_node.attrib['Type'] if uuid_is_source: if 'source' in source_dest: continue # relation is source, so uuid is not else: if 'source' not in source_dest: continue # relation is not source, so uuid is filtered_list.append(part) break return filtered_list
def is_equivalent(self, other, check_extra_metadata = True): """Returns True if this feature is essentially the same as the other; otherwise False.""" if other is None or not isinstance(other, GeneticBoundaryFeature): return False if self is other or bu.matching_uuids(self.uuid, other.uuid): return True if check_extra_metadata and not equivalent_extra_metadata(self, other): return False return self.feature_name == other.feature_name and self.kind == other.kind and self.absolute_age == other.absolute_age
def is_equivalent(self, other): """Implements equals operator, comparing metadata items deemed significant.""" if not isinstance(other, self.__class__): return False if self.md_reference != other.md_reference or not np.allclose( self.location, other.location): return False return bu.matching_uuids(self.crs_uuid, other.crs_uuid)
def create_xml(self, add_as_part=True, originator=None, reuse=True, add_relationships=True): """Creates xml for this stratigraphic unit feature. arguments: add_as_part (bool, default True): if True, the feature is added to the parent model as a high level part originator (str, optional): if present, is used as the originator field of the citation block reuse (bool, default True): if True, the parent model is inspected for any equivalent feature and, if found, the uuid of this feature is set to that of the equivalent part add_relationships (bool, default True): if True and add_as_part is True, relationships are created with the referenced top and bottom units, if present returns: lxml.etree._Element: the root node of the newly created xml tree for the feature """ if reuse and self.try_reuse(): return self.root # check for reusable (equivalent) object # create node with citation block suf = super().create_xml(add_as_part=False, originator=originator) if self.bottom_unit_uuid is not None: self.model.create_ref_node( 'ChronostratigraphicBottom', self.model.title(uuid=self.bottom_unit_uuid), self.bottom_unit_uuid, content_type=self.model.type_of_uuid(self.bottom_unit_uuid), root=suf) if self.top_unit_uuid is not None: self.model.create_ref_node( 'ChronostratigraphicTop', self.model.title(uuid=self.top_unit_uuid), self.top_unit_uuid, content_type=self.model.type_of_uuid(self.top_unit_uuid), root=suf) if add_as_part: self.model.add_part('obj_StratigraphicUnitFeature', self.uuid, suf) if add_relationships: if self.bottom_unit_uuid is not None: self.model.create_reciprocal_relationship( suf, 'destinationObject', self.model.root(uuid=self.bottom_unit_uuid), 'sourceObject') if self.top_unit_uuid is not None and not bu.matching_uuids( self.bottom_unit_uuid, self.top_unit_uuid): self.model.create_reciprocal_relationship( suf, 'destinationObject', self.model.root(uuid=self.top_unit_uuid), 'sourceObject') return suf
def _set_support_and_model_from_collection(collection, other, support_uuid, grid): _confirm_support_and_model_from_collection(collection, support_uuid, grid, other) assert collection.support_uuid is None or other.support_uuid is None or bu.matching_uuids( collection.support_uuid, other.support_uuid) if collection.support_uuid is None and collection.number_of_parts() == 0: collection.set_support(support_uuid=other.support_uuid, support=other.support)
def is_equivalent(self, other, check_extra_metadata=True): """Returns True if this feature is essentially the same as the other; otherwise False.""" if other is None or not isinstance(other, self.__class__): return False if self is other or bu.matching_uuids(self.uuid, other.uuid): return True if check_extra_metadata and not equivalent_extra_metadata(self, other): return False return self.feature_name == other.feature_name
def __eq__(self, other): """Implements equals operator. Compares class type and uuid """ # TODO: more detailed equality comparison other_uuid = getattr(other, "uuid", None) return isinstance(other, self.__class__) and bu.matching_uuids( self.uuid, other_uuid)
def is_equivalent(self, other, check_extra_metadata=True): """Returns True if this feature is essentially the same as the other; otherwise False.""" if not isinstance(other, OrganizationFeature): return False if self is other or bu.matching_uuids(self.uuid, other.uuid): return True return (self.feature_name == other.feature_name and self.organization_kind == other.organization_kind and ((not check_extra_metadata) or equivalent_extra_metadata(self, other)))
def is_equivalent(self, other, check_extra_metadata = True): """Returns True if this interpretation is essentially the same as the other; otherwise False.""" if other is None or not isinstance(other, FaultInterpretation): return False if self is other or bu.matching_uuids(self.uuid, other.uuid): return True attr_list = ['tectonic_boundary_feature', 'root', 'maximum_throw', 'mean_azimuth', 'mean_dip'] one_none_attr_list = [(getattr(self, v) is None) != (getattr(other, v) is None) for v in attr_list] if any(one_none_attr_list): # If only one of self or other has a None attribute return False # List of attributes that are not None in either self or other non_none_attr_list = [a for a in attr_list if getattr(self, a) is not None] # Additional tests for attributes that are not None check_dict = { 'tectonic_boundary_feature': lambda: self.tectonic_boundary_feature.is_equivalent(other.tectonic_boundary_feature), 'root': lambda: rqet.citation_title_for_node(self.root) != rqet.citation_title_for_node(other.root), 'maximum_throw': lambda: maths.isclose(self.maximum_throw, other.maximum_throw, rel_tol = 1e-3), 'mean_azimuth': lambda: maths.isclose(self.mean_azimuth, other.mean_azimuth, abs_tol = 0.5), 'mean_dip': lambda: maths.isclose(self.mean_dip, other.mean_dip, abs_tol = 0.5) } check_outcomes = [check_dict[v]() for v in non_none_attr_list] if not all(check_outcomes): # If any of the Additional tests fail then self and other are not equivalent return False if (not equivalent_chrono_pairs(self.main_has_occurred_during, other.main_has_occurred_during) or self.is_normal != other.is_normal or self.domain != other.domain or self.is_listric != other.is_listric): return False if check_extra_metadata and not equivalent_extra_metadata(self, other): return False if not self.throw_interpretation_list and not other.throw_interpretation_list: return True if not self.throw_interpretation_list or not other.throw_interpretation_list: return False if len(self.throw_interpretation_list) != len(other.throw_interpretation_list): return False for this_ti, other_ti in zip(self.throw_interpretation_list, other.throw_interpretation_list): if this_ti[0] != other_ti[0]: return False # throw kind if not equivalent_chrono_pairs(this_ti[1], other_ti[1]): return False return True
def is_equivalent(self, other): """Returns True if this lookup is the same as other (apart from uuid); False otherwise.""" if other is None: return False if self is other: return True if bu.matching_uuids(self.uuid, other.uuid): return True if self.title != other.title or self.min_index != other.min_index or self.max_index != other.max_index: return False return self.str_dict == other.str_dict
def _add_part_to_dict_get_support_uuid(collection, part): support_uuid = collection.model.supporting_representation_for_part(part) if support_uuid is None: support_uuid = collection.support_uuid elif collection.support_uuid is None: collection.set_support(support_uuid) elif not bu.matching_uuids( support_uuid, collection.support.uuid): # multi-support collection collection.set_support(None) if isinstance(support_uuid, str): support_uuid = bu.uuid_from_string(support_uuid) return support_uuid
def _confirm_support_and_model_from_collection(collection, support_uuid, grid, other): if support_uuid is None and grid is not None: support_uuid = grid.uuid if support_uuid is not None and collection.support_uuid is not None: assert bu.matching_uuids(support_uuid, collection.support_uuid) assert other is not None if collection.model is None: collection.model = other.model else: assert collection.model is other.model
def _parts_list_filtered_by_supporting_uuid(model, parts_list, uuid): """From a list of parts, returns a list of those parts which have the given uuid as supporting representation.""" if parts_list is None or uuid is None: return None filtered_list = [] for part in parts_list: support_ref_uuid = _supporting_representation_for_part(model, part) if support_ref_uuid is None: continue if bu.matching_uuids(support_ref_uuid, uuid): filtered_list.append(part) return filtered_list
def test_base_reuse_duplicate(tmp_model): # Create object and save dummy_1 = ReusableDummyObj(model = tmp_model) assert not dummy_1.try_reuse() dummy_1.create_xml(add_as_part = True) # Should be present in model uuids = tmp_model.uuids(obj_type = ReusableDummyObj.resqml_type) assert len(uuids) == 1 assert dummy_1.try_reuse() # after a part has had xml created and added to model, it is reusable # Create duplicate object dummy_2 = ReusableDummyObj(model = tmp_model) assert not bu.matching_uuids(dummy_1.uuid, dummy_2.uuid) assert dummy_2.try_reuse() # should have matched dummy_1 assert bu.matching_uuids(dummy_1.uuid, dummy_2.uuid) # Only one 'RESQML' object should exist in model, despite us having two resqpy objects uuids = tmp_model.uuids(obj_type = ReusableDummyObj.resqml_type) assert len(uuids) == 1 assert bu.matching_uuids(uuids[0], dummy_2.uuid)
def force_uuid_equivalence(self, immigrant_uuid, resident_uuid): """Forces immigrant object to be treated as equivalent to (same as) resident object, identified by uuids.""" assert immigrant_uuid is not None and resident_uuid is not None if isinstance(immigrant_uuid, str): immigrant_uuid = bu.uuid_from_string(immigrant_uuid) if isinstance(resident_uuid, str): resident_uuid = bu.uuid_from_string(resident_uuid) if bu.matching_uuids(immigrant_uuid, resident_uuid): return assert immigrant_uuid not in self.map.values() self.map[immigrant_uuid] = resident_uuid