beda-software / fhir-py

FHIR Client for python
MIT License
168 stars 31 forks source link

[FR] Support for FHIRPatch #68

Open ZuSe opened 3 years ago

ZuSe commented 3 years ago

The lib is missing support for proper PATCH operations as defined in the standard: http://hl7.org/implement/standards/fhir/http.html#patch

According to the docs update/save with a given list of fields should use this operation.

Tested with HAPI FHIR 5.2.0

mkizesov commented 3 years ago

This feature is already implemented. Could you provide an example of your code?

ZuSe commented 3 years ago

Hi @mkizesov

thanks for your help. Comes the code:

        client: SyncFHIRClient = get_client(async=False)
        # get or create FHIR object
        try:
            patient: SyncFHIRResource = client.reference('Patient', id=str(user.username)).to_resource()
            modifiedfields = [] #do a PATCH
        except exceptions.ResourceNotFound:
            modifiedfields = None #do a post
            patient = client.resource('Patient', id=str(user.username))

        # set telecom fields
        if any(x in update_fields for x in ["email", "mobile", "phone"]):
            if modifiedfields is not None:
                modifiedfields.append("telecom")
            try:
                # https://github.com/beda-software/fhir-py/issues/66
                getattr(patient, "telecom")
            except KeyError:
                patient.telecom = []

            def update_or_create(system, telelist, entry):
                updated = False
                # update if system already exists in FHIR object
                for i, t in enumerate(telelist):
                    if t["system"] == system:
                        updated = True
                        telelist[i] = entry
                        break
                if not updated:
                    telelist.append(entry)

            if "email" in update_fields:
                update_or_create("email", patient.telecom, entry={
                    "system": 'email',
                    "value": str(user.email),
                    "use": 'home'
                })
            if "mobile" in update_fields:
                update_or_create("sms", patient.telecom, entry={
                    "system": 'sms',
                    "value": str(user.mobile),
                    "use": 'mobile'
                })
            if "phone" in update_fields:
                update_or_create("phone", patient.telecom, entry={
                    "system": 'phone',
                    "value": str(user.phone),
                    "use": 'home'
                })

        if "first_name" in update_fields or "last_name" in update_fields:
            if modifiedfields is not None:
                modifiedfields.append("name")
            try:
                # https://github.com/beda-software/fhir-py/issues/66
                getattr(patient, "name")
            except KeyError:
                patient.name = [{}]
            patient.name[0]["use"] = 'official'
            if "first_name" in update_fields:
                patient.name[0]["given"] = str(user.first_name)
                patient.name[0]["text"] = f'{user.first_name} {patient.name[0].get("family", "")}'
            if "last_name" in update_fields:
                patient.name[0]["family"] = str(user.last_name)
                patient.name[0]["text"] = f'{patient.name[0].get("given", "")} {user.last_name}'
        if "birthdate" in update_fields:
            if modifiedfields is not None:
                modifiedfields.append("birthDate")
            patient.birthDate = user.birthdate.isoformat()
        if "gender" in update_fields:
            if modifiedfields is not None:
                modifiedfields.append("gender")
            patient.gender = user.gender
        if "is_active" in update_fields:
            if modifiedfields is not None:
                modifiedfields.append("active")
            patient.active = user.is_active    
        patient.save(fields=modifiedfields)

So basically the purpose of the code is to update only attributes that have been updated on our side. What we try achieve is to implement some kind of sync-adapter that keeps the FHIR Server in sync with the client. However, as many clients write to the server we want to limit the usage of PUT as much as possible ;)

Best Patrick

ruscoder commented 3 years ago

Hi! Maybe it will worth if we just allow passing not only field names in save, for example:

resource.save(fhirpatch=[{"op": "add", "path": "path expr here", "value": "value to add"}])

It will be more flexible. What do you think? Will it solve the issue?

ZuSe commented 3 years ago

yes, should work. Maybe it makes sense to compute as well. Basically

  1. Define FHIR related resource by id
  2. change fields via Dot-notation e.g. tmpPatient.name = "ABC"
  3. Save changed/added fields via patch tmpPatient.save(partial_update=True)

For me that's always the nice use case to use patch, when I know that I want to add/change some specific params but try to avoid loading the whole resource in advance and PUT everything back, just for a couple of small changes (and risk that I run into raise conditions because it changed in the meanwhile).

usr-av commented 2 years ago

https://github.com/beda-software/fhir-py/issues/68#issuecomment-866916629 is patch operation available ?

mkizesov commented 2 years ago

As I understand, fhir patch is not implemented yet. Only standard http PATCH operation (fhir update) is available using either resource.update(fieldToUpdate="") or resource.save(fields=['fieldsToUpdate']).