Skip to content

Commit

Permalink
Merge pull request #112 from beda-software/implement-conditional-oper…
Browse files Browse the repository at this point in the history
…ations

Implement conditional operations
  • Loading branch information
ir4y committed Aug 18, 2023
2 parents 19d636e + fa72910 commit 5587e08
Show file tree
Hide file tree
Showing 5 changed files with 438 additions and 44 deletions.
87 changes: 71 additions & 16 deletions fhirpy/base/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def execute(self, path, method=None, **kwargs):
pass

@abstractmethod # pragma: no cover
def _do_request(self, method, path, data=None, params=None):
def _do_request(self, method, path, data=None, params=None, returning_status=False):
pass

@abstractmethod # pragma: no cover
Expand Down Expand Up @@ -118,7 +118,7 @@ def __init__(
async def execute(self, path, method="post", **kwargs):
return await self._do_request(method, path, **kwargs)

async def _do_request(self, method, path, data=None, params=None):
async def _do_request(self, method, path, data=None, params=None, returning_status=False):
headers = self._build_request_headers()
url = self._build_request_url(path, params)
async with aiohttp.ClientSession(headers=headers) as session:
Expand All @@ -127,11 +127,15 @@ async def _do_request(self, method, path, data=None, params=None):
) as r:
if 200 <= r.status < 300:
data = await r.text()
return json.loads(data, object_hook=AttrDict) if data else None
r_data = json.loads(data, object_hook=AttrDict) if data else None
return (r_data, r.status) if returning_status else r_data

if r.status == 404 or r.status == 410:
raise ResourceNotFound(await r.text())

if r.status == 412:
raise MultipleResourcesFound(await r.text())

data = await r.text()
try:
parsed_data = json.loads(data)
Expand All @@ -158,23 +162,23 @@ def __init__(
def execute(self, path, method="post", **kwargs):
return self._do_request(method, path, **kwargs)

def _do_request(self, method, path, data=None, params=None):
def _do_request(self, method, path, data=None, params=None, returning_status=False):
headers = self._build_request_headers()
url = self._build_request_url(path, params)
r = requests.request(
method, url, json=data, headers=headers, **self.requests_config
)

if 200 <= r.status_code < 300:
return (
json.loads(r.content.decode(), object_hook=AttrDict)
if r.content
else None
)
r_data = json.loads(r.content.decode(), object_hook=AttrDict) if r.content else None
return (r_data, r.status_code) if returning_status else r_data

if r.status_code == 404 or r.status_code == 410:
raise ResourceNotFound(r.content.decode())

if r.status_code == 412:
raise MultipleResourcesFound(r.content.decode())

data = r.content.decode()
try:
parsed_data = json.loads(data)
Expand Down Expand Up @@ -240,6 +244,24 @@ def first(self):

return result[0] if result else None

def get_or_create(self, resource):
assert resource.resource_type == self.resource_type
data, status_code = self.client._do_request("POST", self.resource_type, resource.serialize(), self.params, True)
return data, (True if status_code == 201 else False)

def update(self, resource):
# TODO: Support cases where resource with id is provided
# accordingly to the https://build.fhir.org/http.html#cond-update
assert resource.resource_type == self.resource_type
data, status_code = self.client._do_request("PUT", self.resource_type, resource.serialize(), self.params, True)
return data, (True if status_code == 201 else False)

def patch(self, resource):
# TODO: Handle cases where resource with id is provided
assert resource.resource_type == self.resource_type
# TODO: Should we omit resourceType after serialization? (not to pollute history)
return self.client._do_request("PATCH", self.resource_type, resource.serialize(), self.params)

def __iter__(self):
next_link = None
while True:
Expand All @@ -260,7 +282,6 @@ def __iter__(self):
if not next_link:
break


class AsyncSearchSet(AbstractSearchSet, ABC):
async def fetch(self):
bundle_data = await self.client._fetch_resource(self.resource_type, self.params)
Expand Down Expand Up @@ -313,6 +334,24 @@ async def first(self):

return result[0] if result else None

async def get_or_create(self, resource):
assert resource.resource_type == self.resource_type
data, status_code = await self.client._do_request("POST", self.resource_type, resource.serialize(), self.params, True)
return data, (True if status_code == 201 else False)

async def update(self, resource):
# TODO: Support cases where resource with id is provided
# accordingly to the https://build.fhir.org/http.html#cond-update
assert resource.resource_type == self.resource_type
data, status_code = await self.client._do_request("PUT", self.resource_type, resource.serialize(), self.params, True)
return data, (True if status_code == 201 else False)

async def patch(self, resource):
# TODO: Handle cases where resource with id is provided
assert resource.resource_type == self.resource_type
# TODO: Should we omit resourceType after serialization? (not to pollute history)
return await self.client._do_request("PATCH", self.resource_type, resource.serialize(), self.params)

async def __aiter__(self):
next_link = None
while True:
Expand All @@ -335,7 +374,7 @@ async def __aiter__(self):


class SyncResource(BaseResource, ABC):
def save(self, fields=None):
def save(self, fields=None, search_params=None):
data = self.serialize()
if fields:
if not self.id:
Expand All @@ -344,14 +383,22 @@ def save(self, fields=None):
method = "patch"
else:
method = "put" if self.id else "post"
response_data = self.client._do_request(method, self._get_path(), data=data)
response_data = self.client._do_request(method, self._get_path(), data=data, params=search_params)
if response_data:
super(BaseResource, self).clear()
super(BaseResource, self).update(
**self.client.resource(self.resource_type, **response_data)
)

def update(self, **kwargs):
def create(self, **kwargs):
self.save(search_params=kwargs)
return self

def update(self):
if not self.id:
raise TypeError("Resource `id` is required for update operation")
self.save()
def patch(self, **kwargs):
super(BaseResource, self).update(**kwargs)
self.save(fields=kwargs.keys())

Expand Down Expand Up @@ -383,7 +430,7 @@ def execute(self, operation, method="post", data=None, params=None):


class AsyncResource(BaseResource, ABC):
async def save(self, fields=None):
async def save(self, fields=None, search_params=None):
data = self.serialize()
if fields:
if not self.id:
Expand All @@ -394,15 +441,23 @@ async def save(self, fields=None):
method = "put" if self.id else "post"

response_data = await self.client._do_request(
method, self._get_path(), data=data
method, self._get_path(), data=data, params=search_params
)
if response_data:
super(BaseResource, self).clear()
super(BaseResource, self).update(
**self.client.resource(self.resource_type, **response_data)
)

async def update(self, **kwargs):
async def create(self, **kwargs):
await self.save(search_params=kwargs)
return self

async def update(self):
if not self.id:
raise TypeError("Resource `id` is required for update operation")
await self.save()
async def patch(self, **kwargs):
super(BaseResource, self).update(**kwargs)
await self.save(fields=kwargs.keys())

Expand Down
7 changes: 5 additions & 2 deletions fhirpy/base/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,17 @@ def __repr__(self): # pragma: no cover
return self.__str__()

@abstractmethod # pragma: no cover
def save(self, fields=None):
def save(self, fields=None, search_params=None):
pass

@abstractmethod # pragma: no cover
def update(self, **kwargs):
def patch(self, **kwargs):
pass

@abstractmethod # pragma: no cover
def update(self):
pass
@abstractmethod # pragma: no cover
def delete(self):
pass

Expand Down
12 changes: 12 additions & 0 deletions fhirpy/base/searchset.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,18 @@ def count(self):
def first(self):
pass

@abstractmethod
async def get_or_create(self, resource):
pass

@abstractmethod
def update(self, resource):
pass

@abstractmethod
def patch(self, resource):
pass

def clone(self, override=False, **kwargs):
new_params = copy.deepcopy(self.params)
for key, value in kwargs.items():
Expand Down

0 comments on commit 5587e08

Please sign in to comment.