def _get_start_stop_datetime(self, form_config): """ Returns a start datetime for the Visit and the Encounter, and a stop_datetime for the Visit """ if form_config.openmrs_start_datetime: cc_start_datetime_str = form_config.openmrs_start_datetime._get_commcare_value(self.info) if cc_start_datetime_str is None: raise ConfigurationError( 'A form config for form XMLNS "{}" uses "openmrs_start_datetime" to get the start of ' 'the visit but no value was found in the form.'.format(form_config.xmlns) ) try: cc_start_datetime = string_to_utc_datetime(cc_start_datetime_str) except ValueError: raise ConfigurationError( 'A form config for form XMLNS "{}" uses "openmrs_start_datetime" to get the start of ' 'the visit but an invalid value was found in the form.'.format(form_config.xmlns) ) cc_stop_datetime = cc_start_datetime + timedelta(days=1) - timedelta(seconds=1) # We need to use openmrs_start_datetime.serialize() # for both values because they could be either # OpenMRS datetimes or OpenMRS dates, and their data # types must match. start_datetime = form_config.openmrs_start_datetime.serialize(cc_start_datetime) stop_datetime = form_config.openmrs_start_datetime.serialize(cc_stop_datetime) else: cc_start_datetime = string_to_utc_datetime(self.form_json['form']['meta']['timeEnd']) cc_stop_datetime = cc_start_datetime + timedelta(days=1) - timedelta(seconds=1) start_datetime = to_omrs_datetime(cc_start_datetime) stop_datetime = to_omrs_datetime(cc_stop_datetime) return start_datetime, stop_datetime
def validate_parent_ref(parent_ref, parent_resource_type): """ Validates that ``parent_ref`` is a relative reference with an expected resource type. e.g. "Patient/12345" """ try: resource_type_name, resource_id = parent_ref.split('/') except (AttributeError, ValueError): raise ConfigurationError(f'Unexpected reference format {parent_ref!r}') if resource_type_name != parent_resource_type.name: raise ConfigurationError( 'Resource type does not match expected parent resource type')
def save(self, *args, **kwargs): if (self.case_property and self.case_property.case_type != self.resource_type.case_type): raise ConfigurationError( "Invalid FHIRResourceProperty: case_property case type " f"'{self.case_property.case_type}' does not match " f"resource_type case type '{self.resource_type.case_type}'.") if ((self.case_property or self.jsonpath or self.value_map) and self.value_source_config): raise ConfigurationError( "Invalid FHIRResourceProperty: Unable to set " "'value_source_config' when 'case_property', 'jsonpath' or " "'value_map' are set.") super().save(*args, **kwargs)
def get_case_properties(patient, importer): """ Returns case name and dictionary of case properties to update Raises ConfigurationError if a value cannot be deserialized using the data types given in a column mapping. """ name_columns = importer.name_columns.split(' ') case_name = ' '.join([patient[column] for column in name_columns]) errors = [] fields_to_update = {} tz = importer.get_timezone() for mapping in importer.column_map: value = patient[mapping.column] try: fields_to_update[mapping.property] = deserialize( mapping, value, tz) except (TypeError, ValueError) as err: errors.append( f'Unable to deserialize value {repr(value)} ' f'in column "{mapping.column}" for case property ' f'"{mapping.property}". OpenMRS data type is given as ' f'"{mapping.data_type}". CommCare data type is given as ' f'"{mapping.commcare_data_type}": {err}') if errors: raise ConfigurationError(f'Errors importing from {importer}:\n' + '\n'.join(errors)) return case_name, fields_to_update
def get_case_block_for_indexed_case(mapping, external_data, parent_case_id, parent_case_type, default_owner_id): relationship = mapping.indexed_case_mapping.relationship case_block_kwargs = { "index": { mapping.indexed_case_mapping.identifier: IndexAttrs( parent_case_type, parent_case_id, relationship, ) }, "update": {} } for value_source in mapping.indexed_case_mapping.case_properties: value = value_source.get_import_value(external_data) if value_source.case_property in CASE_BLOCK_ARGS: case_block_kwargs[value_source.case_property] = value else: case_block_kwargs["update"][value_source.case_property] = value case_id = uuid.uuid4().hex case_type = mapping.indexed_case_mapping.case_type case_block_kwargs.setdefault("owner_id", default_owner_id) if not case_block_kwargs["owner_id"]: raise ConfigurationError( _(f'Unable to determine mobile worker to own new "{case_type}" ' f'{relationship} case or parent case "{parent_case_id}"')) case_block = CaseBlock(create=True, case_id=case_id, case_type=case_type, **case_block_kwargs) return case_block
def import_patients_of_owner(requests, importer, domain_name, owner_id, location=None): try: openmrs_patients = get_openmrs_patients(requests, importer, location) except RequestException as err: requests.notify_exception( f'Unable to import patients for project space "{domain_name}" ' f'using {importer}: Error calling API: {err}') return except (KeyError, IndexError, TypeError, ValueError) as err: requests.notify_exception( f'Unable to import patients for project space "{domain_name}" ' f'using {importer}: Unexpected response format: {err}') return case_blocks = [] for i, patient in enumerate(openmrs_patients): try: patient_id = str(patient[importer.external_id_column]) except KeyError: raise ConfigurationError( f'Error importing patients for project space "{importer.domain}" ' f'from OpenMRS Importer "{importer}": External ID column ' f'"{importer.external_id_column}" not found in patient data.') case, error = importer_util.lookup_case(EXTERNAL_ID, patient_id, domain_name, importer.case_type) if error is None: case_block = get_updatepatient_caseblock(case, patient, importer) case_blocks.append(RowAndCase(i, case_block)) elif error == LookupErrors.NotFound: case_block = get_addpatient_caseblock(patient, importer, owner_id) case_blocks.append(RowAndCase(i, case_block)) elif error == LookupErrors.MultipleResults: raise ConfigurationError( f'Error importing patients for project space "{importer.domain}" ' f'from OpenMRS Importer "{importer}": {importer.case_type}' f'.{EXTERNAL_ID} "{patient_id}" is not unique.') submit_case_blocks( [cb.case.as_text() for cb in case_blocks], domain_name, device_id=f'{OPENMRS_IMPORTER_DEVICE_ID_PREFIX}{importer.get_id}', xmlns=XMLNS_OPENMRS, )
def update_patient(repeater, patient_uuid): """ Fetch patient from OpenMRS, submit case update for all mapped case properties. .. NOTE:: OpenMRS UUID must be saved to "external_id" case property """ if len(repeater.white_listed_case_types) != 1: raise ConfigurationError( _(f'{repeater.domain}: {repeater}: Error in settings: Unable to update ' f'patients from OpenMRS unless only one case type is specified.') ) case_type = repeater.white_listed_case_types[0] try: patient = get_patient_by_uuid(repeater.requests, patient_uuid) except (RequestException, ValueError) as err: raise OpenmrsException( _(f'{repeater.domain}: {repeater}: Error fetching Patient ' f'{patient_uuid!r}: {err}')) from err case, error = importer_util.lookup_case( EXTERNAL_ID, patient_uuid, repeater.domain, case_type=case_type, ) if error == LookupErrors.NotFound: if not repeater.openmrs_config.case_config.import_creates_cases: # We can't create cases via the Atom feed, just update them. # Nothing to do here. return default_owner: Optional[CommCareUser] = repeater.first_user case_block = get_addpatient_caseblock(case_type, default_owner, patient, repeater) elif error == LookupErrors.MultipleResults: # Multiple cases have been matched to the same patient. # Could be caused by: # * The cases were given the same identifier value. It could # be user error, or case config assumed identifier was # unique but it wasn't. # * PatientFinder matched badly. # * Race condition where a patient was previously added to # both CommCare and OpenMRS. raise DuplicateCaseMatch( _(f'{repeater.domain}: {repeater}: More than one case found ' f'matching unique OpenMRS UUID. case external_id: "{patient_uuid}"' )) else: case_block = get_updatepatient_caseblock(case, patient, repeater) if case_block: submit_case_blocks( [case_block.as_text()], repeater.domain, xmlns=XMLNS_OPENMRS, device_id=OPENMRS_ATOM_FEED_DEVICE_ID + repeater.get_id, )
def get_owner(self): if not self.owner_id: raise ConfigurationError('Owner ID missing') try: if self.owner_type == OWNER_TYPE_GROUP: return Group.get(self.owner_id) elif self.owner_type == OWNER_TYPE_LOCATION: return SQLLocation.objects.get(location_id=self.owner_id) elif self.owner_type == OWNER_TYPE_USER: user = CouchUser.get_by_user_id(self.owner_id) if user: return user else: raise ResourceNotFound() else: raise ConfigurationError(f'Unknown owner type {self.owner_type!r}') except (ResourceNotFound, SQLLocation.DoesNotExist): raise ConfigurationError(f'{self.owner_type.capitalize()} ' f'{self.owner_id!r} does not exist')
def validate_resource(self, fhir_resource): schema = self.get_json_schema() resolver = RefResolver(base_uri=f'file://{self._schema_file}', referrer=schema) try: validate(fhir_resource, schema, resolver=resolver) except JSONValidationError as err: raise ConfigurationError( f'Validation failed for resource {fhir_resource!r}: {err}' ) from err
def get_auth(self): if not self.last_token: raise ConfigurationError( _('OAuth1 authentication workflow has not been followed for ' f'Connection "{self.connection_settings}"')) resource_owner_key = self.last_token['oauth_token'] resource_owner_secret = self.last_token['oauth_token_secret'] return OAuth1(self.client_id, client_secret=self.client_secret, resource_owner_key=resource_owner_key, resource_owner_secret=resource_owner_secret)
def get_external_value(self, external_data): if self.jsonpath is not None: try: jsonpath = parse_jsonpath(self.jsonpath) except Exception as err: raise JsonpathError from err matches = jsonpath.find(external_data) values = [m.value for m in matches] if not values: return None elif len(values) == 1: return values[0] else: return values raise ConfigurationError(f"{self} is not configured to parse external data")
def get_value_source(self) -> ValueSource: """ Returns a ValueSource for building FHIR resources. """ if self.value_source_config: return as_value_source(self.value_source_config) if not (self.case_property and self.jsonpath): raise ConfigurationError( 'Unable to set FHIR resource property value without case ' 'property and JSONPath.') value_source_config = { 'case_property': self.case_property.name, 'jsonpath': self.jsonpath, } if self.value_map: value_source_config['value_map'] = self.value_map return as_value_source(value_source_config)
def set_external_value(self, external_data: dict, info: CaseTriggerInfo): """ Builds ``external_data`` by reference. Currently implemented for dicts using JSONPath but could be implemented for other types as long as they are mutable. """ if self.jsonpath is None: raise ConfigurationError(f"{self} is not configured to navigate " "external data") value = self.get_value(info) if value is None: # Don't set external value if CommCare has no value return try: jsonpath = parse_jsonpath(self.jsonpath) except Exception as err: raise JsonpathError from err jsonpath.update_or_create(external_data, value)
def get_addpatient_caseblock( case_type: str, default_owner: Optional[CommCareUser], patient: dict, repeater: OpenmrsRepeater, ) -> CaseBlock: case_block_kwargs = get_case_block_kwargs_from_patient(patient, repeater) if default_owner: case_block_kwargs.setdefault("owner_id", default_owner.user_id) if not case_block_kwargs.get("owner_id"): raise ConfigurationError( _(f'No users found at location "{repeater.location_id}" to own ' 'patients added from OpenMRS Atom feed.')) case_id = uuid.uuid4().hex return CaseBlock.deprecated_init(create=True, case_id=case_id, case_type=case_type, external_id=patient['uuid'], **case_block_kwargs)
def get_json_schema(self) -> dict: """ Returns the JSON schema of this resource type. >>> resource_type = FHIRResourceType( ... case_type=CaseType(name='mother'), ... name='Patient', ... ) >>> schema = resource_type.get_json_schema() >>> schema['$ref'] '#/definitions/Patient' """ try: with open(self._schema_file, 'r') as file: return json.load(file) except FileNotFoundError: raise ConfigurationError( f'Unknown resource type {self.name!r} for FHIR version ' f'{self.fhir_version}')
def create_parent_indices( importer: FHIRImportConfig, child_cases: List[ParentInfo], ): """ Creates parent-child relationships on imported cases. If ``ResourceTypeRelationship.related_resource_is_parent`` is ``True`` then this function will add an ``index`` on the child case to its parent case. """ if not child_cases: return case_blocks = [] for child_case_id, parent_ref, parent_resource_type in child_cases: resource_type_name, external_id = parent_ref.split('/') parent_case = get_case_by_external_id( parent_resource_type.domain, external_id, parent_resource_type.case_type.name, ) if not parent_case: raise ConfigurationError( f'Case not found with external_id {external_id!r}') case_blocks.append( CaseBlock( child_case_id, index={'parent': (parent_case.type, parent_case.case_id)}, )) submit_case_blocks( [cb.as_text() for cb in case_blocks], importer.domain, xmlns=XMLNS_FHIR, device_id=f'FHIRImportConfig-{importer.pk}', )