def _base_entity_match( a: DatabaseEntity, b: DatabaseEntity, skip_fields: Set[str], allow_null_mismatch: bool = False ) -> bool: """Returns whether two objects of the same type are an entity match. Args: a: The first entity to match b: The second entity to match skip_fields: A list of names of fields that should be ignored when determining if two objects match based on flat fields. allow_null_mismatch: Allow for two objects to still match if one has a null value in a field where the other's is nonnull. """ # Placeholders never match if is_placeholder(a) or is_placeholder(b): return False # Compare external ids if one is present if a.get_external_id() or b.get_external_id(): return a.get_external_id() == b.get_external_id() # Compare all flat fields of the two entities all_set_flat_field_names = \ get_set_entity_field_names(a, EntityFieldType.FLAT_FIELD) | \ get_set_entity_field_names(b, EntityFieldType.FLAT_FIELD) for field_name in all_set_flat_field_names: # Skip primary key if field_name == a.get_class_id_name() or field_name in skip_fields: continue a_field = a.get_field(field_name) b_field = b.get_field(field_name) if allow_null_mismatch and (a_field is None or b_field is None): # Do not disqualify a match if one of the fields is null continue if a_field != b_field: return False return True
def default_merge_flat_fields( *, new_entity: DatabaseEntity, old_entity: DatabaseEntity) -> DatabaseEntity: """Merges all set non-relationship fields on the |new_entity| onto the |old_entity|. Returns the newly merged entity.""" for child_field_name in get_set_entity_field_names(new_entity, EntityFieldType.FLAT_FIELD): if child_field_name == old_entity.get_class_id_name(): continue # Do not overwrite with default status if child_field_name == 'status' and new_entity.has_default_status(): continue old_entity.set_field(child_field_name, new_entity.get_field(child_field_name)) return old_entity
def _base_entity_match(a: DatabaseEntity, b: DatabaseEntity) -> bool: # Placeholders never match if is_placeholder(a) or is_placeholder(b): return False # Compare external ids if one is present if a.get_external_id() or b.get_external_id(): return a.get_external_id() == b.get_external_id() # Compare all flat fields of the two entities all_set_flat_field_names = \ get_set_entity_field_names(a, EntityFieldType.FLAT_FIELD) | \ get_set_entity_field_names(b, EntityFieldType.FLAT_FIELD) for field_name in all_set_flat_field_names: # Skip primary key if field_name == a.get_class_id_name(): continue a_field = a.get_field(field_name) b_field = b.get_field(field_name) if a_field != b_field: return False return True
def remove_child_from_entity(*, entity: DatabaseEntity, child_field_name: str, child_to_remove: DatabaseEntity): """If present, removes the |child_to_remove| from the |child_field_name| field on the |entity|. """ child_field = entity.get_field(child_field_name) if isinstance(child_field, list): if child_to_remove in child_field: child_field.remove(child_to_remove) elif isinstance(child_field, DatabaseEntity): if child_field == child_to_remove: child_field = None entity.set_field(child_field_name, child_field)
def add_child_to_entity(*, entity: DatabaseEntity, child_field_name: str, child_to_add: DatabaseEntity): """Adds the |child_to_add| to the |child_field_name| field on the |entity|. """ child_field = entity.get_field(child_field_name) if isinstance(child_field, list): if child_to_add not in child_field: child_field.append(child_to_add) else: if child_field and child_field != child_to_add: raise EntityMatchingError( f"Attempting to add child {child_to_add} to entity {entity}, " f"but {child_field_name} already had different value " f"{child_field}", entity.get_entity_name()) child_field = child_to_add entity.set_field(child_field_name, child_field)
def _is_match(*, ingested_entity: DatabaseEntity, db_entity: DatabaseEntity) -> bool: """Returns true if the provided |ingested_entity| matches the provided |db_entity|. Otherwise returns False. """ if not ingested_entity or not db_entity: return ingested_entity == db_entity if ingested_entity.__class__ != db_entity.__class__: raise EntityMatchingError( f"is_match received entities of two different classes: " f"ingested entity {ingested_entity.__class__.__name__} and " f"db_entity {db_entity.__class__.__name__}", ingested_entity.get_entity_name()) if not isinstance(ingested_entity, DatabaseEntity): raise EntityMatchingError( f"Unexpected type for ingested entity[{type(ingested_entity)}]", 'unknown') if not isinstance(db_entity, DatabaseEntity): raise EntityMatchingError( f"Unexpected type for db entity[{type(db_entity)}]", 'unknown') if isinstance(ingested_entity, schema.StatePerson): db_entity = cast(schema.StatePerson, db_entity) for ingested_external_id in ingested_entity.external_ids: for db_external_id in db_entity.external_ids: if _is_match(ingested_entity=ingested_external_id, db_entity=db_external_id): return True return False # Aside from people, all entities are state specific. if ingested_entity.get_field('state_code') \ != db_entity.get_field('state_code'): return False # TODO(2671): Update all person attributes below to use complete entity # equality instead of just comparing individual fields. if isinstance(ingested_entity, schema.StatePersonExternalId): db_entity = cast(schema.StatePersonExternalId, db_entity) return ingested_entity.external_id == db_entity.external_id \ and ingested_entity.id_type == db_entity.id_type # As person has already been matched, assume that any of these 'person # attribute' entities are matches if specific attributes match. if isinstance(ingested_entity, schema.StatePersonAlias): db_entity = cast(schema.StatePersonAlias, db_entity) return ingested_entity.full_name == db_entity.full_name if isinstance(ingested_entity, schema.StatePersonRace): db_entity = cast(schema.StatePersonRace, db_entity) return ingested_entity.race == db_entity.race if isinstance(ingested_entity, schema.StatePersonEthnicity): db_entity = cast(schema.StatePersonEthnicity, db_entity) return ingested_entity.ethnicity == db_entity.ethnicity if isinstance(ingested_entity, (schema.StateSupervisionViolationResponseDecisionEntry, schema.StateSupervisionViolatedConditionEntry, schema.StateSupervisionViolationTypeEntry, schema.StateSupervisionCaseTypeEntry)): return _base_entity_match(ingested_entity, db_entity) # Placeholders entities are considered equal if ingested_entity.get_external_id() is None \ and db_entity.get_external_id() is None: return is_placeholder(ingested_entity) and is_placeholder(db_entity) return ingested_entity.get_external_id() == db_entity.get_external_id()