Skip to content

Commit a3be32e

Browse files
committed
Preserve null values for save with fields passed
1 parent 0a9ca27 commit a3be32e

File tree

6 files changed

+90
-78
lines changed

6 files changed

+90
-78
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 2.0.7
2+
3+
* Preserve null values for resource in save() with fields passed
4+
15
## 2.0.6
26

37
* Fix type inference for client.resource

fhirpy/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from .lib import AsyncFHIRClient, SyncFHIRClient
22

33
__title__ = "fhir-py"
4-
__version__ = "2.0.6"
4+
__version__ = "2.0.7"
55
__author__ = "beda.software"
66
__license__ = "None"
77
__copyright__ = "Copyright 2024 beda.software"

fhirpy/base/lib_async.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ async def save(
109109
# _as_dict is a private api used internally
110110
_as_dict: bool = False,
111111
) -> Union[TResource, Any]:
112-
data = serialize(resource)
112+
data = serialize(resource, drop_dict_null_values=fields is None)
113113
if fields:
114114
if not resource.id:
115115
raise TypeError("Resource `id` is required for update operation")
@@ -287,13 +287,9 @@ async def update(self) -> TResource: # type: ignore
287287

288288
async def patch(self, **kwargs) -> TResource:
289289
if not self.id:
290-
raise TypeError("Resource `id` is required for delete operation")
290+
raise TypeError("Resource `id` is required for patch operation")
291291
super(BaseResource, self).update(**kwargs)
292-
response_data = await self.__client__.patch(self.reference, **kwargs)
293-
294-
resource_type = self.resource_type
295-
super(BaseResource, self).clear()
296-
super(BaseResource, self).update(**self.__client__.resource(resource_type, **response_data))
292+
await self.save(fields=list(kwargs.keys()))
297293

298294
return cast(TResource, self)
299295

fhirpy/base/lib_sync.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def save(
109109
# _as_dict is a private api used internally
110110
_as_dict: bool = False,
111111
) -> Union[TResource, Any]:
112-
data = serialize(resource)
112+
data = serialize(resource, drop_dict_null_values=fields is None)
113113
if fields:
114114
if not resource.id:
115115
raise TypeError("Resource `id` is required for update operation")
@@ -281,13 +281,9 @@ def update(self) -> TResource: # type: ignore
281281

282282
def patch(self, **kwargs) -> TResource:
283283
if not self.id:
284-
raise TypeError("Resource `id` is required for delete operation")
284+
raise TypeError("Resource `id` is required for patch operation")
285285
super(BaseResource, self).update(**kwargs)
286-
response_data = self.__client__.patch(self.reference, **kwargs)
287-
288-
resource_type = self.resource_type
289-
super(BaseResource, self).clear()
290-
super(BaseResource, self).update(**self.__client__.resource(resource_type, **response_data))
286+
self.save(fields=list(kwargs.keys()))
291287

292288
return cast(TResource, self)
293289

tests/test_lib_async.py

Lines changed: 41 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -135,20 +135,26 @@ async def test_client_save_existing(self):
135135

136136
@pytest.mark.asyncio()
137137
async def test_client_save_partial_update(self):
138-
patient = await self.create_patient_model()
138+
patient = await self.create_patient_model(
139+
managingOrganization=Reference(reference="urn:organization")
140+
)
139141

140142
patient.identifier = [
141143
*patient.identifier,
142144
Identifier(system="url", value="value"),
143145
]
144146
patient.name[0].text = "New patient"
147+
patient.managingOrganization = None
145148

146-
updated_patient = await self.client.save(patient, fields=["identifier"])
149+
updated_patient = await self.client.save(
150+
patient, fields=["identifier", "managingOrganization"]
151+
)
147152

148153
assert isinstance(updated_patient, Patient)
149154
assert updated_patient.id == patient.id
150155
assert len(updated_patient.identifier) == 2 # noqa: PLR2004
151156
assert updated_patient.name[0].text == "My patient"
157+
assert updated_patient.managingOrganization is None
152158

153159
@pytest.mark.asyncio()
154160
async def test_client_save_partial_update_fails_without_id(self):
@@ -408,7 +414,7 @@ async def test_conditional_operations__fail_on_multiple_matches(self):
408414
)
409415

410416
@pytest.mark.asyncio()
411-
async def test_update_with_params__no_match(self):
417+
async def test_conditional_update__no_match(self):
412418
patient = await self.create_resource("Patient", id="patient", active=True)
413419

414420
patient_to_update = self.client.resource(
@@ -427,7 +433,7 @@ async def test_update_with_params__no_match(self):
427433
assert created is True
428434

429435
@pytest.mark.asyncio()
430-
async def test_update_with_params__one_match(self):
436+
async def test_conditional_update__one_match(self):
431437
patient = await self.create_resource("Patient", id="patient", active=True)
432438

433439
patient_to_update = self.client.resource(
@@ -450,7 +456,7 @@ async def test_update_with_params__one_match(self):
450456
assert patient.get("active") is None
451457

452458
@pytest.mark.asyncio()
453-
async def test_patch_with_params__no_match(self):
459+
async def test_conditional_patch__no_match(self):
454460
patient_to_patch = self.client.resource(
455461
"Patient",
456462
identifier=[{"system": "http://example.com/env", "value": "other"}, self.identifier[0]],
@@ -462,7 +468,7 @@ async def test_patch_with_params__no_match(self):
462468
)
463469

464470
@pytest.mark.asyncio()
465-
async def test_patch_with_params__one_match(self):
471+
async def test_conditional_patch__one_match(self):
466472
patient = await self.create_resource(
467473
"Patient",
468474
id="patient",
@@ -494,7 +500,7 @@ async def test_patch_with_params__one_match(self):
494500
assert patient.get("managingOrganization") is None
495501

496502
@pytest.mark.asyncio()
497-
async def test_patch_with_params__one_match_deprecated(self):
503+
async def test_conditional_patch__one_match_deprecated(self):
498504
patient = await self.create_resource("Patient", id="patient", active=True)
499505

500506
patient_to_patch = self.client.resource(
@@ -569,7 +575,7 @@ async def test_delete_without_id_failed(self):
569575
await patient.delete()
570576

571577
@pytest.mark.asyncio()
572-
async def test_delete_with_params__no_match(self):
578+
async def test_conditional_delete__no_match(self):
573579
await self.create_resource("Patient", id="patient")
574580

575581
_, status_code = await self.client.resources("Patient").search(identifier="other").delete()
@@ -578,7 +584,7 @@ async def test_delete_with_params__no_match(self):
578584
assert status_code == 204 # noqa: PLR2004
579585

580586
@pytest.mark.asyncio()
581-
async def test_delete_with_params__one_match(self):
587+
async def test_conditional_delete__one_match(self):
582588
patient = self.client.resource(
583589
"Patient",
584590
id="patient",
@@ -595,7 +601,7 @@ async def test_delete_with_params__one_match(self):
595601
assert status_code == 200 # noqa: PLR2004
596602

597603
@pytest.mark.asyncio()
598-
async def test_delete_with_params__multiple_matches(self):
604+
async def test_conditional_delete__multiple_matches(self):
599605
await self.create_resource("Patient", id="patient-1")
600606
await self.create_resource("Patient", id="patient-2")
601607

@@ -859,18 +865,42 @@ async def test_save_fields(self):
859865
active=False,
860866
birthDate="1998-01-01",
861867
name=[{"text": "Abc"}],
868+
managingOrganization={"reference": "urn:organization"},
862869
)
863870
patient["gender"] = "male"
864871
patient["birthDate"] = "1998-02-02"
865872
patient["active"] = True
866873
patient["name"] = [{"text": "Bcd"}]
867-
await patient.save(fields=["gender", "birthDate"])
874+
patient["managingOrganization"] = None
875+
await patient.save(fields=["gender", "birthDate", "managingOrganization"])
868876

869877
patient_refreshed = await patient.to_reference().to_resource()
870878
assert patient_refreshed["gender"] == patient["gender"]
871879
assert patient_refreshed["birthDate"] == patient["birthDate"]
872880
assert patient_refreshed["active"] is False
873881
assert patient_refreshed["name"] == [{"text": "Abc"}]
882+
assert patient_refreshed.get("managingOrganization") is None
883+
884+
@pytest.mark.asyncio()
885+
async def test_update_patch_without_id(self):
886+
patient = self.client.resource(
887+
"Patient", identifier=self.identifier, name=[{"text": "J London"}]
888+
)
889+
new_name = [
890+
{
891+
"text": "Jack London",
892+
"family": "London",
893+
"given": ["Jack"],
894+
}
895+
]
896+
with pytest.raises(TypeError):
897+
await patient.update()
898+
with pytest.raises(TypeError):
899+
await patient.patch(active=True, name=new_name)
900+
patient["name"] = new_name
901+
with pytest.raises(TypeError):
902+
await patient.save(fields=["name"])
903+
await patient.save()
874904

875905
@pytest.mark.asyncio()
876906
async def test_update(self):
@@ -918,27 +948,6 @@ async def test_patch(self):
918948
assert patient_instance_1_refreshed["name"] == new_name
919949
assert patient_instance_1_refreshed.get("managingOrganization") is None
920950

921-
@pytest.mark.asyncio()
922-
async def test_update_without_id(self):
923-
patient = self.client.resource(
924-
"Patient", identifier=self.identifier, name=[{"text": "J London"}]
925-
)
926-
new_name = [
927-
{
928-
"text": "Jack London",
929-
"family": "London",
930-
"given": ["Jack"],
931-
}
932-
]
933-
with pytest.raises(TypeError):
934-
await patient.update()
935-
with pytest.raises(TypeError):
936-
await patient.patch(active=True, name=new_name)
937-
patient["name"] = new_name
938-
with pytest.raises(TypeError):
939-
await patient.save(fields=["name"])
940-
await patient.save()
941-
942951
@pytest.mark.asyncio()
943952
async def test_refresh(self):
944953
patient_id = "refresh-patient-id"

0 commit comments

Comments
 (0)