diff --git a/CHANGELOG.md b/CHANGELOG.md index a3a63db..32988f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ * It's a private API, there's a small chance that it should not affect the code * Deprecate conditional patch with resource argument, use kwargs instead * Fix pickling error for BaseResource instances #77 +* Add .execute for search set #74 ## 1.4.2 * Conditional delete @pavlushkin diff --git a/fhirpy/base/lib_async.py b/fhirpy/base/lib_async.py index bef3031..762a2f8 100644 --- a/fhirpy/base/lib_async.py +++ b/fhirpy/base/lib_async.py @@ -272,8 +272,8 @@ async def to_resource(self) -> TResource: return cast(TResource, self) async def is_valid(self, raise_exception=False) -> bool: - data = await self.__client__._do_request( - "post", f"{self.resource_type}/$validate", data=self.serialize() + data = await self.__client__.execute( + f"{self.resource_type}/$validate", method="post", data=self.serialize() ) if any(issue["severity"] in ["fatal", "error"] for issue in data["issue"]): if raise_exception: @@ -288,9 +288,9 @@ async def execute( data: Union[dict, None] = None, params: Union[dict, None] = None, ) -> Any: - return await self.__client__._do_request( - method, + return await self.__client__.execute( f"{self._get_path()}/{operation}", + method, data=data, params=params, ) @@ -308,16 +308,22 @@ async def to_resource(self) -> TResource: """ if not self.is_local: raise ResourceNotFound("Can not resolve not local resource") - resource_data = await self.__client__._do_request("get", f"{self.resource_type}/{self.id}") + resource_data = await self.__client__.execute( + f"{self.resource_type}/{self.id}", method="get" + ) return self._dict_to_resource(resource_data) - async def execute(self, operation, method="post", **kwargs): + async def execute( + self, + operation, + method="post", + data: Union[dict, None] = None, + params: Union[dict, None] = None, + ): if not self.is_local: raise ResourceNotFound("Can not execute on not local resource") - return await self.__client__._do_request( - method, - f"{self.resource_type}/{self.id}/{operation}", - **kwargs, + return await self.__client__.execute( + f"{self.resource_type}/{self.id}/{operation}", method=method, data=data, params=params ) async def patch(self, **kwargs) -> TResource: @@ -331,6 +337,20 @@ async def delete(self): class AsyncSearchSet( Generic[TAsyncClient, TResource], AbstractSearchSet[TAsyncClient, TResource], ABC ): + async def execute( + self, + operation: str, + method: str = "post", + data: Union[dict, None] = None, + params: Union[dict, None] = None, + ) -> Any: + return await self.client.execute( + f"{self.resource_type}/{operation}", + method=method, + data=data, + params=params, + ) + async def fetch(self) -> list[TResource]: bundle_data = await self.client._fetch_resource(self.resource_type, self.params) @@ -383,7 +403,7 @@ async def first(self) -> Union[TResource, None]: async def get_or_create(self, resource: TResource) -> tuple[TResource, bool]: assert resource.resourceType == self.resource_type response_data, status_code = await self.client._do_request( - "POST", + "post", self.resource_type, serialize(resource), self.params, @@ -396,7 +416,7 @@ async def update(self, resource: TResource) -> tuple[TResource, bool]: # accordingly to the https://build.fhir.org/http.html#cond-update assert resource.resourceType == self.resource_type response_data, status_code = await self.client._do_request( - "PUT", + "put", self.resource_type, serialize(resource), self.params, @@ -420,7 +440,7 @@ async def patch(self, _resource: Any = None, **kwargs) -> TResource: async def delete(self) -> Any: return await self.client._do_request( - "DELETE", self.resource_type, params=self.params, returning_status=True + "delete", self.resource_type, params=self.params, returning_status=True ) async def __aiter__(self) -> AsyncGenerator[TResource, None]: diff --git a/fhirpy/base/lib_sync.py b/fhirpy/base/lib_sync.py index 9e75d84..103559c 100644 --- a/fhirpy/base/lib_sync.py +++ b/fhirpy/base/lib_sync.py @@ -266,8 +266,8 @@ def to_resource(self) -> TResource: return cast(TResource, self) def is_valid(self, raise_exception=False) -> bool: - data = self.__client__._do_request( - "post", f"{self.resource_type}/$validate", data=self.serialize() + data = self.__client__.execute( + f"{self.resource_type}/$validate", method="post", data=self.serialize() ) if any(issue["severity"] in ["fatal", "error"] for issue in data["issue"]): if raise_exception: @@ -282,9 +282,9 @@ def execute( data: Union[dict, None] = None, params: Union[dict, None] = None, ) -> Any: - return self.__client__._do_request( - method, + return self.__client__.execute( f"{self._get_path()}/{operation}", + method=method, data=data, params=params, ) @@ -302,16 +302,26 @@ def to_resource(self) -> TResource: """ if not self.is_local: raise ResourceNotFound("Can not resolve not local resource") - resource_data = self.__client__._do_request("get", f"{self.resource_type}/{self.id}") + resource_data = self.__client__.execute( + f"{self.resource_type}/{self.id}", + method="get", + ) return self._dict_to_resource(resource_data) - def execute(self, operation, method="post", **kwargs): + def execute( + self, + operation, + method="post", + data: Union[dict, None] = None, + params: Union[dict, None] = None, + ): if not self.is_local: raise ResourceNotFound("Can not execute on not local resource") - return self.__client__._do_request( - method, + return self.__client__.execute( f"{self.resource_type}/{self.id}/{operation}", - **kwargs, + method=method, + data=data, + params=params, ) def patch(self, **kwargs) -> TResource: @@ -325,6 +335,20 @@ def delete(self): class SyncSearchSet( Generic[TSyncClient, TResource], AbstractSearchSet[TSyncClient, TResource], ABC ): + def execute( + self, + operation: str, + method: str = "post", + data: Union[dict, None] = None, + params: Union[dict, None] = None, + ) -> Any: + return self.client.execute( + f"{self.resource_type}/{operation}", + method=method, + data=data, + params=params, + ) + def fetch(self) -> list[TResource]: bundle_data = self.client._fetch_resource(self.resource_type, self.params) @@ -377,7 +401,7 @@ def first(self) -> Union[TResource, None]: def get_or_create(self, resource: TResource) -> tuple[TResource, int]: assert resource.resourceType == self.resource_type response_data, status_code = self.client._do_request( - "POST", + "post", self.resource_type, serialize(resource), self.params, @@ -390,7 +414,7 @@ def update(self, resource: TResource) -> tuple[TResource, int]: # accordingly to the https://build.fhir.org/http.html#cond-update assert resource.resourceType == self.resource_type response_data, status_code = self.client._do_request( - "PUT", + "put", self.resource_type, serialize(resource), self.params, @@ -409,12 +433,12 @@ def patch(self, _resource: Any = None, **kwargs) -> TResource: ) data = serialize(_resource if _resource is not None else kwargs) - response_data = self.client._do_request("PATCH", self.resource_type, data, self.params) + response_data = self.client._do_request("patch", self.resource_type, data, self.params) return self._dict_to_resource(response_data) def delete(self) -> Any: return self.client._do_request( - "DELETE", self.resource_type, params=self.params, returning_status=True + "delete", self.resource_type, params=self.params, returning_status=True ) def __iter__(self) -> Generator[TResource, None, None]: diff --git a/fhirpy/base/resource.py b/fhirpy/base/resource.py index c1ede9f..0f07520 100644 --- a/fhirpy/base/resource.py +++ b/fhirpy/base/resource.py @@ -207,7 +207,13 @@ def to_resource(self): pass @abstractmethod - def execute(self, operation, method=None, **kwargs): + def execute( + self, + operation, + method=None, + data: Union[dict, None] = None, + params: Union[dict, None] = None, + ): pass @abstractmethod diff --git a/fhirpy/base/searchset.py b/fhirpy/base/searchset.py index 9fd1dd7..631f3fe 100644 --- a/fhirpy/base/searchset.py +++ b/fhirpy/base/searchset.py @@ -198,6 +198,16 @@ def _dict_to_resource(self, data) -> TResource: return self.custom_resource_class(**data) return self.client.resource(data["resourceType"], **data) + @abstractmethod + def execute( + self, + path: str, + method: str = "post", + data: Union[dict, None] = None, + params: Union[dict, None] = None, + ): + pass + @abstractmethod def fetch(self): pass diff --git a/tests/test_lib_async.py b/tests/test_lib_async.py index b299d5c..baa7b4f 100644 --- a/tests/test_lib_async.py +++ b/tests/test_lib_async.py @@ -928,6 +928,35 @@ async def test_client_execute_lastn(self): assert response["total"] == 1 assert response["entry"][0]["resource"]["id"] == observation["id"] + @pytest.mark.asyncio() + async def test_searchset_execute_lastn(self): + patient = await self.create_resource("Patient", name=[{"text": "John First"}]) + observation = await self.create_resource( + "Observation", + status="registered", + subject=patient, + category=[ + { + "coding": [ + { + "code": "vital-signs", + "system": "http://terminology.hl7.org/CodeSystem/observation-category", + "display": "Vital Signs", + } + ] + } + ], + code={"coding": [{"code": "10000-8", "system": "http://loinc.org"}]}, + ) + response = await self.client.resources("Observation").execute( + "$lastn", + method="get", + params={"patient": f"Patient/{patient.id}", "category": "vital-signs"}, + ) + assert response["resourceType"] == "Bundle" + assert response["total"] == 1 + assert response["entry"][0]["resource"]["id"] == observation["id"] + @pytest.mark.asyncio() async def test_resource_execute_lastn(self): patient = await self.create_resource("Patient", name=[{"text": "John First"}]) diff --git a/tests/test_lib_sync.py b/tests/test_lib_sync.py index 8d26b94..61d4dd6 100644 --- a/tests/test_lib_sync.py +++ b/tests/test_lib_sync.py @@ -863,6 +863,34 @@ def test_client_execute_lastn(self): assert response["total"] == 1 assert response["entry"][0]["resource"]["id"] == observation["id"] + def test_searchset_execute_lastn(self): + patient = self.create_resource("Patient", name=[{"text": "John First"}]) + observation = self.create_resource( + "Observation", + status="registered", + subject=patient, + category=[ + { + "coding": [ + { + "code": "vital-signs", + "system": "http://terminology.hl7.org/CodeSystem/observation-category", + "display": "Vital Signs", + } + ] + } + ], + code={"coding": [{"code": "10000-8", "system": "http://loinc.org"}]}, + ) + response = self.client.resources("Observation").execute( + "$lastn", + method="get", + params={"patient": f"Patient/{patient.id}", "category": "vital-signs"}, + ) + assert response["resourceType"] == "Bundle" + assert response["total"] == 1 + assert response["entry"][0]["resource"]["id"] == observation["id"] + def test_resource_execute_lastn(self): patient = self.create_resource("Patient", name=[{"text": "John First"}]) observation = self.create_resource(