def update_ig_config(data_path, ig_control_filepath, add=True, rm_file=False): """ Add/remove the configuration entries to/from IG resource file for all resources in data_path. Optional - delete the resource file(s). Only applies if add=False. When a new resource file is added to the IG it will not be picked up for validation or site generation by the IG publisher unless the expected configuration for that resource is present. :param data_path: Path to directory or file containing resource(s) to remove from the IG configuration :param ig_control_filepath: Path to the implementation guide control file :type ig_control_filepath: str :param add: Whether to add the configuration versus remove it :type add: bool :param rm_file: Whether to delete the resource file(s). Only applies if add=False :type rm_file: bool """ # Load resource dicts resource_dicts = loader.load_resources(data_path) # Load IG resource dict ig_resource_dict = deepcopy(_load_ig_resource_dict(ig_control_filepath)) # Validate and add resource to IG configuration _custom_validate(resource_dicts) # Update the IG configuration _update_ig_config(resource_dicts, ig_resource_dict)
def validate(ig_control_filepath, clear_output=False, publisher_opts=''): """ Validate the FHIR data model (FHIR conformance and example resources) Run the HL7 FHIR implementation guide (IG) publisher in a Docker container to validate conformance resources and any example resources against the conformance resources. See https://confluence.hl7.org/display/FHIR/IG+Publisher+Documentation Validation fails if any of the following are true: - The publisher returns a non-zero exit code - QA report contains errors with the FHIR resources. - Any one of the resource files fail model validation in _custom_validate IG build errors are ignored since this method only validates the data model :param ig_control_filepath: Path to the implementation guide control file :type ig_control_filepath: str :param clear_output: Whether to clear all generated output before validating :type clear_output: boolean :param publisher_opts: IG publisher command line options forwarded directly to the publisher CLI :type publisher_opts: str """ logger.info('Begin validation of FHIR data model') ig_control_filepath = os.path.abspath( os.path.expanduser(ig_control_filepath)) # Clear previously generated output if clear_output: clear_ig_output(ig_control_filepath) # Read in ig resource file ig_resource_dict = _load_ig_resource_dict(ig_control_filepath) # Collect resource filepaths resource_dicts = [] site_root = os.path.dirname(ig_control_filepath) ig = ig_resource_dict['content'] for param in ig.get('definition', {}).get('parameter', []): if param.get('code') != 'path-resource': continue resource_dicts.extend( loader.load_resources(os.path.join(site_root, param.get('value')))) # Validate and add resource to IG configuration _custom_validate(resource_dicts) # Add entry to IG configuration _update_ig_config(resource_dicts, ig_resource_dict, add=True) # Do the standard HL7 FHIR validation via the IG Publisher _fhir_validate(ig_control_filepath, publisher_opts) logger.info('End validation of FHIR data model')
def _load_ig_resource_dict(ig_control_filepath): """ Load IG resource JSON into a dict Find the location of the IG resource file from the ig control file first :param ig_control_filepath: Path to the implementation guide control file :type ig_control_filepath: str :returns: IG resource dict """ # Read in ig control file ig_control_filepath = os.path.abspath( os.path.expanduser(ig_control_filepath)) ig_config = ConfigParser() ig_config.read(ig_control_filepath) # Read in ig resource file ig_filepath = os.path.join( os.path.split(ig_control_filepath)[0], dict(ig_config["IG"]).get("ig")) return loader.load_resources(ig_filepath)[0]
def test_search_params(client, search_param_filename, resource_type, search_value, expected_count): """ Test SearchParameters Query the test server at the /{resource_type} endpoint using the search parameter and specified search value. Check that the returned total matches specified expected count. """ sp = load_resources(os.path.join(SEARCH_PARAM_DIR, search_param_filename))[0]["content"] success, result = client.send_request( "get", f"{client.base_url}/{resource_type}", params={ sp["code"]: search_value, "_total": "accurate" }, ) assert success pprint(result) assert result["response"]["total"] == expected_count
def publish_to_server( resource_file_or_dir, base_url, username=None, password=None, fhir_version=None, submission_order=RESOURCE_SUBMISSION_ORDER, ): """ Push FHIR resources to a FHIR server Delete the resources if they exist on the server PUT any resources that have an `id` attribute defined POST any resources that do not have an `id` attribute defined :param resource_file_or_dir: path to a directory containing FHIR resource files or path to a single resource file :type resource_file_or_dir: str :param resource_type: directory containing FHIR resource files :type resource_type: str :param username: Server account username :type username: str :param password: Server account password :type password: str :param fhir_version: FHIR version number :type fhir_version: str """ logger.info( f"Begin publishing resources in {resource_file_or_dir} to {base_url}") if username and password: auth = HTTPBasicAuth(username, password) else: auth = None client = FhirApiClient(base_url=base_url, auth=auth, fhir_version=fhir_version) resources = loader.load_resources(resource_file_or_dir) # Re-order resources according to submission order resources_by_type = defaultdict(list) for r_dict in resources: resources_by_type[r_dict["resource_type"]].append(r_dict) resources = [] for r_type in submission_order: resources.extend(resources_by_type.pop(r_type, [])) for r_type, remaining in resources_by_type.items(): resources.extend(remaining) # Delete existing resources for r_dict in resources: r = r_dict["content"] if "url" in r: success = client.delete_all(f'{base_url}/{r["resourceType"]}', params={"url": r["url"]}) elif "id" in r: success, results = client.send_request( "delete", f'{base_url}/{r["resourceType"]}/{r["id"]}') else: logger.warning( f'⚠️ Could not delete {r_dict["filename"]}. No way to ' "identify the resource. Tried looking for `url` and `id` in " "payload.") # POST if no id is provided, PUT if id is provideds for r_dict in resources: r = r_dict["content"] id_ = r.get("id") if id_: success, results = client.send_request( "put", f'{base_url}/{r["resourceType"]}/{id_}', json=r) else: success, results = client.send_request( "post", f'{base_url}/{r["resourceType"]}', json=r) if not success: errors = [ r for r in results["response"]["issue"] if r["severity"] == "error" ] raise Exception(f"Publish failed! Caused by:\n{pformat(errors)}")
def publish_to_server(resource_file_or_dir, base_url, username=None, password=None, fhir_version=None): """ Push FHIR resources to a FHIR server Delete the resources if they exist on the server PUT any resources that have an `id` attribute defined POST any resources that do not have an `id` attribute defined :param resource_file_or_dir: path to a directory containing FHIR resource files or path to a single resource file :type resource_file_or_dir: str :param resource_type: directory containing FHIR resource files :type resource_type: str :param username: Server account username :type username: str :param password: Server account password :type password: str :param fhir_version: FHIR version number :type fhir_version: str """ logger.info( f'Begin publishing resources in {resource_file_or_dir} to {base_url}') if username and password: auth = HTTPBasicAuth(username, password) else: auth = None client = FhirApiClient(base_url=base_url, auth=auth, fhir_version=fhir_version) resources = loader.load_resources(resource_file_or_dir) # Delete existing resources for r_dict in resources: r = r_dict['content'] if 'url' in r: success = client.delete_all(f'{base_url}/{r["resourceType"]}', params={'url': r['url']}) elif 'id' in r: success, results = client.send_request( 'delete', f'{base_url}/{r["resourceType"]}/{r["id"]}') else: logger.warning( f'⚠️ Could not delete {r_dict["filename"]}. No way to ' 'identify the resource. Tried looking for `url` and `id` in ' 'payload.') # POST if no id is provided, PUT if id is provided for r_dict in resources: r = r_dict['content'] id_ = r.get('id') if id_: success, results = client.send_request( 'put', f'{base_url}/{r["resourceType"]}/{id_}', json=r) else: success, results = client.send_request( 'post', f'{base_url}/{r["resourceType"]}', json=r)