Exemple #1
0
 def test_valid_json(self):
     self.assertEqual(
         pformat_json('{"ham": "spam", "eggs": "spam"}'),
         '{\n  "eggs": "spam",\n  "ham": "spam"\n}' if six.PY3 else '{\n  "eggs": "spam", \n  "ham": "spam"\n}'
     )
     self.assertEqual(
         pformat_json({'ham': 'spam', 'eggs': 'spam'}),
         '{\n  "eggs": "spam",\n  "ham": "spam"\n}' if six.PY3 else '{\n  "eggs": "spam", \n  "ham": "spam"\n}'
     )
Exemple #2
0
 def test_valid_json(self):
     self.assertEqual(
         pformat_json('{"ham": "spam", "eggs": "spam"}'),
         '{\n  "eggs": "spam",\n  "ham": "spam"\n}'
         if six.PY3 else '{\n  "eggs": "spam", \n  "ham": "spam"\n}')
     self.assertEqual(
         pformat_json({
             'ham': 'spam',
             'eggs': 'spam'
         }), '{\n  "eggs": "spam",\n  "ham": "spam"\n}'
         if six.PY3 else '{\n  "eggs": "spam", \n  "ham": "spam"\n}')
Exemple #3
0
 def request_wrapper(method, url, *args, **kwargs):
     log_level = logging.INFO
     request_error = ''
     response_status = None
     response_body = ''
     try:
         response = func(method, url, *args, **kwargs)
         response_status = response.status_code
         response_body = response.content
     except Exception as err:
         log_level = logging.ERROR
         request_error = str(err)
         if getattr(err, 'response', None) is not None:
             response_status = err.response.status_code
             response_body = pformat_json(err.response.text)
         raise
     else:
         return response
     finally:
         params, data, headers = unpack_request_args(method, args, kwargs)
         entry = RequestLogEntry(
             self.domain_name, self.payload_id, method, url, headers, params, data,
             request_error, response_status, response_body
         )
         logger(log_level, entry)
def send_dhis2_entities(requests, repeater, case_trigger_infos):
    """
    Send request to register / update tracked entities
    """
    errors = []
    for info in case_trigger_infos:
        assert isinstance(info, CaseTriggerInfo)
        case_config = get_case_config_for_case_type(
            info.type, repeater.dhis2_entity_config)
        if not case_config:
            # This payload includes a case of a case type that does not correspond to a tracked entity type
            continue

        try:
            tracked_entity, etag = get_tracked_entity_and_etag(
                requests, info, case_config)
            if tracked_entity:
                update_tracked_entity_instance(requests, tracked_entity, etag,
                                               info, case_config)
            else:
                register_tracked_entity_instance(requests, info, case_config)
        except (Dhis2Exception, HTTPError) as err:
            errors.append(str(err))

    if errors:
        errors_str = f"Errors sending to {repeater}: " + pformat_json(
            [str(e) for e in errors])
        requests.notify_error(errors_str)
        return RepeaterResponse(400, 'Bad Request', errors_str)
    return RepeaterResponse(200, "OK")
Exemple #5
0
 def request_wrapper(self, *args, **kwargs):
     log_level = logging.INFO
     request_error = ''
     response_status = None
     response_body = ''
     try:
         response = func(self, *args, **kwargs)
         response_status = response.status_code
         response_body = response.content
     except Exception as err:
         log_level = logging.ERROR
         request_error = str(err)
         if getattr(err, 'response', None) is not None:
             response_status = err.response.status_code
             response_body = pformat_json(err.response.text)
         raise
     else:
         return response
     finally:
         # args will be Requests method, url, and optionally params, data or json.
         # kwargs may include Requests method kwargs and raise_for_status.
         kwargs.pop('raise_for_status', None)
         RequestLog.log(log_level, self.domain_name, self.payload_id,
                        request_error, response_status, response_body,
                        *args, **kwargs)
Exemple #6
0
 def send_request(self, repeat_record, payload):
     value_source_configs: Iterable[JsonDict] = chain(
         self.openmrs_config.case_config.patient_identifiers.values(),
         self.openmrs_config.case_config.person_properties.values(),
         self.openmrs_config.case_config.person_preferred_name.values(),
         self.openmrs_config.case_config.person_preferred_address.values(),
         self.openmrs_config.case_config.person_attributes.values(),
     )
     case_trigger_infos = get_relevant_case_updates_from_form_json(
         self.domain,
         payload,
         case_types=self.white_listed_case_types,
         extra_fields=[
             conf["case_property"] for conf in value_source_configs
             if "case_property" in conf
         ],
         form_question_values=get_form_question_values(payload),
     )
     requests = self.get_requests(payload_id=repeat_record.payload_id)
     try:
         response = send_openmrs_data(
             requests,
             self.domain,
             payload,
             self.openmrs_config,
             case_trigger_infos,
         )
     except Exception as err:
         requests.notify_exception(str(err))
         return OpenmrsResponse(400, 'Bad Request', pformat_json(str(err)))
     return response
Exemple #7
0
    def send_request(self, repeat_record, payload):
        """
        Generates FHIR resources from ``payload``, and sends them as a
        FHIR transaction bundle. If there are patients that need to be
        registered, that is done first.

        Returns an HTTP response-like object. If the payload has nothing
        to send, returns True.
        """
        requests = self.connection_settings.get_requests(
            repeat_record.payload_id)
        infos, resource_types = self.get_infos_resource_types(
            payload,
            self.fhir_version,
        )
        try:
            resources = get_info_resource_list(infos, resource_types)
            resources = register_patients(
                requests,
                resources,
                self.patient_registration_enabled,
                self.patient_search_enabled,
                self._id,
            )
            response = send_resources(
                requests,
                resources,
                self.fhir_version,
                self._id,
            )
        except Exception as err:
            requests.notify_exception(str(err))
            return RepeaterResponse(400, 'Bad Request', pformat_json(str(err)))
        return response
Exemple #8
0
    def test_nonascii_json_bytes(self):
        pigs_and_eggs = (u'{"\U0001f416": "\U0001f416\U0001f416\U0001f416", '
                         u'"\U0001f95a\U0001f95a": "\U0001f416\U0001f416\U0001f416"}')
        json_string = pformat_json(pigs_and_eggs.encode("utf-8"))

        self.assertEqual(
            json_string,
            '{\n  "\\ud83d\\udc16": "\\ud83d\\udc16\\ud83d\\udc16\\ud83d\\udc16",\n  '
            '"\\ud83e\\udd5a\\ud83e\\udd5a": "\\ud83d\\udc16\\ud83d\\udc16\\ud83d\\udc16"\n}'
        )
Exemple #9
0
    def test_nonascii_dict(self):
        pigs_and_eggs = {"\U0001f416": "\U0001f416\U0001f416\U0001f416",
                         "\U0001f95a\U0001f95a": "\U0001f416\U0001f416\U0001f416"}
        json_string = pformat_json(pigs_and_eggs)

        self.assertEqual(
            json_string,
            '{\n  "\\ud83d\\udc16": "\\ud83d\\udc16\\ud83d\\udc16\\ud83d\\udc16",\n  '
            '"\\ud83e\\udd5a\\ud83e\\udd5a": "\\ud83d\\udc16\\ud83d\\udc16\\ud83d\\udc16"\n}'
        )
        assert json.loads(json_string) == pigs_and_eggs
Exemple #10
0
def parse_request_exception(err):
    """
    Parses an instance of RequestException and returns a request
    string and response string tuple
    """
    err_request = '{method} {url}\n\n{body}'.format(
        method=err.request.method, url=err.request.url,
        body=err.request.body) if err.request.body else ' '.join(
            (err.request.method, err.request.url))
    err_content = pformat_json(
        err.response.content)  # pformat_json returns non-JSON values unchanged
    err_response = '\n\n'.join((str(err), err_content))
    return err_request, err_response
Exemple #11
0
def parse_request_exception(err):
    """
    Parses an instance of RequestException and returns a request
    string and response string tuple
    """
    err_request = '{method} {url}\n\n{body}'.format(
        method=err.request.method,
        url=err.request.url,
        body=err.request.body
    ) if err.request.body else ' '.join((err.request.method, err.request.url))
    if err.response:
        err_content = pformat_json(err.response.content)  # pformat_json returns non-JSON values unchanged
        err_response = '\n\n'.join((str(err), err_content))
    else:
        err_response = str(err)
    return err_request, err_response
Exemple #12
0
    def get(self, request, domain):
        record_id = request.GET.get('record_id')
        record = self.get_record_or_404(domain, record_id)
        content_type = record.repeater.generator.content_type
        try:
            payload = record.get_payload()
        except XFormNotFound:
            return JsonResponse({
                'error': 'Odd, could not find payload for: {}'.format(record.payload_id)
            }, status=404)

        if content_type == 'text/xml':
            payload = indent_xml(payload)
        elif content_type == 'application/json':
            payload = pformat_json(payload)

        return JsonResponse({
            'payload': payload,
            'content_type': content_type,
        })
Exemple #13
0
    def get(self, request, domain):
        record_id = request.GET.get('record_id')
        record = self.get_record_or_404(request, domain, record_id)
        content_type = record.repeater.generator.content_type
        try:
            payload = record.get_payload()
        except XFormNotFound:
            return json_response({
                'error': 'Odd, could not find payload for: {}'.format(record.payload_id)
            }, status_code=404)

        if content_type == 'text/xml':
            payload = indent_xml(payload)
        elif content_type == 'application/json':
            payload = pformat_json(payload)

        return json_response({
            'payload': payload,
            'content_type': content_type,
        })
Exemple #14
0
def send_dhis2_entities(requests, repeater, case_trigger_infos):
    """
    Send request to register / update tracked entities
    """
    errors = []
    info_config_pairs = _get_info_config_pairs(repeater, case_trigger_infos)
    for info, case_config in info_config_pairs:
        try:
            tracked_entity, etag = get_tracked_entity_and_etag(
                requests, info, case_config)
            if tracked_entity:
                update_tracked_entity_instance(requests, tracked_entity, etag,
                                               info, case_config)
            else:
                register_tracked_entity_instance(requests, info, case_config)
        except (Dhis2Exception, HTTPError) as err:
            errors.append(str(err))

    # Create relationships after handling tracked entity instances, to
    # ensure that both the instances in the relationship have been created.
    for info, case_config in info_config_pairs:
        if not case_config.relationships_to_export:
            continue
        try:
            create_relationships(
                requests,
                info,
                case_config,
                repeater.dhis2_entity_config,
            )
        except (Dhis2Exception, HTTPError) as err:
            errors.append(str(err))

    if errors:
        errors_str = f"Errors sending to {repeater}: " + pformat_json(
            [str(e) for e in errors])
        requests.notify_error(errors_str)
        return RepeaterResponse(400, 'Bad Request', errors_str)
    return RepeaterResponse(200, "OK")
Exemple #15
0
 def request_wrapper(self, *args, **kwargs):
     log_level = logging.INFO
     request_error = ''
     response_status = None
     response_body = ''
     try:
         response = func(self, *args, **kwargs)
         response_status = response.status_code
         response_body = response.content
     except Exception as err:
         log_level = logging.ERROR
         request_error = str(err)
         if getattr(err, 'response', None) is not None:
             response_status = err.response.status_code
             response_body = pformat_json(err.response.content)
         raise
     else:
         return response
     finally:
         # args will be Requests method, url, and optionally params, data or json.
         # kwargs may include Requests method kwargs and raise_for_status.
         kwargs.pop('raise_for_status', None)
         RequestLog.log(log_level, self.domain_name, request_error, response_status, response_body,
                        *args, **kwargs)
Exemple #16
0
    def get(self, request, domain):
        record_id = request.GET.get('record_id')
        record = self.get_record_or_404(request, domain, record_id)
        content_type = record.repeater.generator.content_type
        try:
            payload = record.get_payload()
        except XFormNotFound:
            return json_response({
                'error': 'Odd, could not find payload for: {}'.format(record.payload_id)
            }, status_code=404)

        if content_type == 'text/xml':
            payload = indent_xml(payload)
        elif content_type == 'application/json':
            payload = pformat_json(payload)
        elif content_type == 'application/soap+xml':
            # we return a payload that is a dict, which is then converted to
            # XML by the zeep library before being sent along as a SOAP request.
            payload = json.dumps(payload, indent=4)

        return json_response({
            'payload': payload,
            'content_type': content_type,
        })
Exemple #17
0
def send_dataset(
    dataset_map: SQLDataSetMap,
    send_date: datetime.date
) -> dict:
    """
    Sends a data set of data values in the following format. "period" is
    determined from ``send_date``. ::

        {
            "dataSet": "dataSetID",
            "completeDate": "date",
            "period": "period",
            "orgUnit": "orgUnitID",
            "attributeOptionCombo", "aocID",
            "dataValues": [
                {
                    "dataElement": "dataElementID",
                    "categoryOptionCombo": "cocID",
                    "value": "1",
                    "comment": "comment1"
                },
                {
                    "dataElement": "dataElementID",
                    "categoryOptionCombo": "cocID",
                    "value": "2",
                    "comment": "comment2"
                },
                {
                    "dataElement": "dataElementID",
                    "categoryOptionCombo": "cocID",
                    "value": "3",
                    "comment": "comment3"
                }
            ]
        }

    See `DHIS2 API docs`_ for more details.


    .. _DHIS2 API docs: https://docs.dhis2.org/master/en/developer/html/webapi_data_values.html

    """
    # payload_id lets us filter API logs, and uniquely identifies the
    # dataset map, to help AEs and administrators link an API log back
    # to a dataset map.
    payload_id = f'dhis2/map/{dataset_map.pk}/'
    response_log_url = reverse(
        'motech_log_list_view',
        args=[dataset_map.domain],
        params={'filter_payload': payload_id}
    )

    with dataset_map.connection_settings.get_requests(payload_id) as requests:
        response = None
        try:
            datavalues_sets = parse_dataset_for_request(dataset_map, send_date)

            for datavalues_set in datavalues_sets:
                response = requests.post('/api/dataValueSets', json=datavalues_set,
                              raise_for_status=True)

        except DatabaseError as db_err:
            requests.notify_error(message=str(db_err),
                                  details=traceback.format_exc())
            return {
                'success': False,
                'error': _('There was an error retrieving some UCR data. '
                           'Try contacting support to help resolve this issue.'),
                'text': None,
                'log_url': response_log_url,
            }

        except Exception as err:
            requests.notify_error(message=str(err),
                                  details=traceback.format_exc())
            text = pformat_json(response.text if response else None)

            return {
                'success': False,
                'error': str(err),
                'status_code': response.status_code if response else None,
                'text': text,
                'log_url': response_log_url,
            }
        else:
            return {
                'success': True,
                'status_code': response.status_code,
                'text': pformat_json(response.text),
                'log_url': response_log_url,
            }
Exemple #18
0
def send_openmrs_data(requests, domain, form_json, openmrs_config,
                      case_trigger_infos):
    """
    Updates an OpenMRS patient and (optionally) creates visits.

    This involves several requests to the `OpenMRS REST Web Services`_. If any of those requests fail, we want to
    roll back previous changes to avoid inconsistencies in OpenMRS. To do this we define a workflow of tasks we
    want to do. Each workflow task has a rollback task. If a task fails, all previous tasks are rolled back in
    reverse order.

    :return: A response-like object that can be used by Repeater.handle_response(),
             RepeatRecord.handle_success() and RepeatRecord.handle_failure()


    .. _OpenMRS REST Web Services: https://wiki.openmrs.org/display/docs/REST+Web+Services+API+For+Clients
    """
    warnings = []
    errors = []
    for info in case_trigger_infos:
        assert isinstance(info, CaseTriggerInfo)
        try:
            patient = get_patient(requests, domain, info, openmrs_config)
        except (RequestException, HTTPError) as err:
            errors.append(
                _("Unable to create an OpenMRS patient for case "
                  f"{info.case_id!r}: {err}"))
            continue
        if patient is None:
            warnings.append(
                f"CommCare case '{info.case_id}' was not matched to a "
                f"patient in OpenMRS instance '{requests.base_url}'.")
            continue

        # case_trigger_infos are info about all of the cases
        # created/updated by the form. Execute a separate workflow to
        # update each patient.
        workflow = [
            # Update name first. If the current name in OpenMRS fails
            # validation, other API requests will be rejected.
            UpdatePersonNameTask(requests, info, openmrs_config,
                                 patient['person']),
            # Update identifiers second. If a current identifier fails
            # validation, other API requests will be rejected.
            SyncPatientIdentifiersTask(requests, info, openmrs_config,
                                       patient),
            # Now we should be able to update the rest.
            UpdatePersonPropertiesTask(requests, info, openmrs_config,
                                       patient['person']),
            SyncPersonAttributesTask(requests, info, openmrs_config,
                                     patient['person']['uuid'],
                                     patient['person']['attributes']),
        ]
        if patient['person']['preferredAddress']:
            workflow.append(
                UpdatePersonAddressTask(requests, info, openmrs_config,
                                        patient['person']))
        else:
            workflow.append(
                CreatePersonAddressTask(requests, info, openmrs_config,
                                        patient['person']))
        workflow.append(
            CreateVisitsEncountersObsTask(requests, domain, info, form_json,
                                          openmrs_config,
                                          patient['person']['uuid']), )

        errors.extend(execute_workflow(workflow))

    if errors:
        requests.notify_error(
            f'Errors encountered sending OpenMRS data: {errors}')
        # If the form included multiple patients, some workflows may
        # have succeeded, but don't say everything was OK if any
        # workflows failed. (Of course most forms will only involve one
        # case, so one workflow.)
        return OpenmrsResponse(
            400, 'Bad Request',
            "Errors: " + pformat_json([str(e) for e in errors]))

    if warnings:
        return OpenmrsResponse(
            201, "Accepted",
            "Warnings: " + pformat_json([str(e) for e in warnings]))

    return OpenmrsResponse(200, "OK")
Exemple #19
0
 def pp_request_headers(self):
     """
     Pretty-print the request headers
     """
     return pformat_json(self.request_headers)
Exemple #20
0
def pp_json(data):
    """
    Pretty-print data as JSON
    """
    return pformat_json(data)
Exemple #21
0
 def pp_request_params(self):
     """
     Pretty-print the request params
     """
     return pformat_json(self.request_params)
Exemple #22
0
 def test_invalid_json(self):
     self.assertEqual(pformat_json('ham spam eggs spam'),
                      'ham spam eggs spam')
Exemple #23
0
 def pp_response_body(self):
     """
     Pretty-print the response body
     """
     return pformat_json(self.response_body)
Exemple #24
0
 def test_invalid_json(self):
     self.assertEqual(
         pformat_json('ham spam eggs spam'),
         'ham spam eggs spam'
     )
Exemple #25
0
 def test_valid_json_string(self):
     self.assertEqual(
         pformat_json('{"ham": "spam", "eggs": "spam"}'),
         '{\n  "eggs": "spam",\n  "ham": "spam"\n}'
     )
Exemple #26
0
def send_openmrs_data(requests, domain, form_json, openmrs_config, case_trigger_infos, form_question_values):
    """
    Updates an OpenMRS patient and (optionally) creates visits.

    This involves several requests to the `OpenMRS REST Web Services`_. If any of those requests fail, we want to
    roll back previous changes to avoid inconsistencies in OpenMRS. To do this we define a workflow of tasks we
    want to do. Each workflow task has a rollback task. If a task fails, all previous tasks are rolled back in
    reverse order.

    :return: A response-like object that can be used by Repeater.handle_response


    .. _OpenMRS REST Web Services: https://wiki.openmrs.org/display/docs/REST+Web+Services+API+For+Clients
    """
    errors = []
    for info in case_trigger_infos:
        assert isinstance(info, CaseTriggerInfo)
        patient = get_patient(requests, domain, info, openmrs_config)
        if patient is None:
            errors.append('Warning: CommCare case "{}" was not found in OpenMRS'.format(info.case_id))
            continue

        # case_trigger_infos are info about all of the cases
        # created/updated by the form. Execute a separate workflow to
        # update each patient.
        workflow = [
            # Update name first. If the current name in OpenMRS fails
            # validation, other API requests will be rejected.
            UpdatePersonNameTask(requests, info, openmrs_config, patient['person']),
            # Update identifiers second. If a current identifier fails
            # validation, other API requests will be rejected.
            SyncPatientIdentifiersTask(requests, info, openmrs_config, patient),
            # Now we should be able to update the rest.
            UpdatePersonPropertiesTask(requests, info, openmrs_config, patient['person']),
            SyncPersonAttributesTask(
                requests, info, openmrs_config, patient['person']['uuid'], patient['person']['attributes']
            ),
        ]
        if patient['person']['preferredAddress']:
            workflow.append(
                UpdatePersonAddressTask(requests, info, openmrs_config, patient['person'])
            )
        else:
            workflow.append(
                CreatePersonAddressTask(requests, info, openmrs_config, patient['person'])
            )
        workflow.append(
            CreateVisitsEncountersObsTask(
                requests, domain, info, form_json, form_question_values, openmrs_config, patient['person']['uuid']
            ),
        )

        errors.extend(
            execute_workflow(workflow)
        )

    if errors:
        logger.error('Errors encountered sending OpenMRS data: %s', errors)
        # If the form included multiple patients, some workflows may
        # have succeeded, but don't say everything was OK if any
        # workflows failed. (Of course most forms will only involve one
        # case, so one workflow.)
        return OpenmrsResponse(400, 'Bad Request', pformat_json([str(e) for e in errors]))
    else:
        return OpenmrsResponse(200, 'OK', '')
Exemple #27
0
 def test_none(self):
     self.assertEqual(
         pformat_json(None),
         ''
     )
Exemple #28
0
 def test_empty_string(self):
     self.assertEqual(
         pformat_json(''),
         ''
     )
Exemple #29
0
def send_openmrs_data(requests, domain, form_json, openmrs_config, case_trigger_infos, form_question_values):
    """
    Updates an OpenMRS patient and (optionally) creates visits.

    This involves several requests to the `OpenMRS REST Web Services`_. If any of those requests fail, we want to
    roll back previous changes to avoid inconsistencies in OpenMRS. To do this we define a workflow of tasks we
    want to do. Each workflow task has a rollback task. If a task fails, all previous tasks are rolled back in
    reverse order.

    :return: A response-like object that can be used by Repeater.handle_response


    .. _OpenMRS REST Web Services: https://wiki.openmrs.org/display/docs/REST+Web+Services+API+For+Clients
    """
    errors = []
    for info in case_trigger_infos:
        assert isinstance(info, CaseTriggerInfo)
        patient = get_patient(requests, domain, info, openmrs_config)
        if patient is None:
            errors.append('Warning: CommCare case "{}" was not found in OpenMRS'.format(info.case_id))
            continue

        # case_trigger_infos are info about all of the cases
        # created/updated by the form. Execute a separate workflow to
        # update each patient.
        workflow = [
            # Update name first. If the current name in OpenMRS fails
            # validation, other API requests will be rejected.
            UpdatePersonNameTask(requests, info, openmrs_config, patient['person']),
            # Update identifiers second. If a current identifier fails
            # validation, other API requests will be rejected.
            SyncPatientIdentifiersTask(requests, info, openmrs_config, patient),
            # Now we should be able to update the rest.
            UpdatePersonPropertiesTask(requests, info, openmrs_config, patient['person']),
            SyncPersonAttributesTask(
                requests, info, openmrs_config, patient['person']['uuid'], patient['person']['attributes']
            ),
        ]
        if patient['person']['preferredAddress']:
            workflow.append(
                UpdatePersonAddressTask(requests, info, openmrs_config, patient['person'])
            )
        else:
            workflow.append(
                CreatePersonAddressTask(requests, info, openmrs_config, patient['person'])
            )
        workflow.append(
            CreateVisitsEncountersObsTask(
                requests, domain, info, form_json, form_question_values, openmrs_config, patient['person']['uuid']
            ),
        )

        errors.extend(
            execute_workflow(workflow)
        )

    if errors:
        logger.error('Errors encountered sending OpenMRS data: %s', errors)
        # If the form included multiple patients, some workflows may
        # have succeeded, but don't say everything was OK if any
        # workflows failed. (Of course most forms will only involve one
        # case, so one workflow.)
        return OpenmrsResponse(400, 'Bad Request', pformat_json([str(e) for e in errors]))
    else:
        return OpenmrsResponse(200, 'OK', '')
Exemple #30
0
def pp_json(data):
    """
    Pretty-print data as JSON
    """
    return pformat_json(data)
Exemple #31
0
 def pp_request_body(self):
     """
     Pretty-print the request body
     """
     return pformat_json(self.request_body)
Exemple #32
0
 def test_invalid_json_bytes(self):
     self.assertEqual(
         pformat_json(b'{"ham": "spam", "spam", "spa'),
         b'{"ham": "spam", "spam", "spa'
     )
Exemple #33
0
 def test_dict(self):
     self.assertEqual(
         pformat_json({'ham': 'spam', 'eggs': 'spam'}),
         '{\n  "eggs": "spam",\n  "ham": "spam"\n}'
     )