beda-software / fhir-py Goto Github PK
View Code? Open in Web Editor NEWFHIR Client for python
License: MIT License
FHIR Client for python
License: MIT License
Now, FHIRReference returns None for .id
/.resourceType
attributes.
But we can extract them from URL: http://another.fhir.server/fhir/Patient/id
Sometimes we need to iterate over thousands of entries and I think it will be very useful if we can have fetch_all() as a generator. Now if we call fetch_all for thousands of entries we firstly wait until all entries are fetched.
Consider implementing lazy fetch for searchset iterating.
Repeat Django's behaviour, just check resource_type and id
It would be great if we could use conditional delete (as described here) to clean up linked resources.
Use Bundle resource from #7
We should transform resource/reference instance in search lookups, for example:
practitioner = clients.resources('Practitioner').first()
# This line should be transformed to
client.resources('Schedule').search(actor=practitioner)
# Something like that
client.resources('Schedule').search(actor=practitioner.reference)
Something like resource.update({'attr': 'new value'})
or client.resources('Patient').update(id=1, {'birthDate': '1905-08-23'})
execute
- fetch()
fetch_all()
which fetches all instances with paginationclient.resources('Patient').limit(10).fetch_all()
Possible implementation:
def fetch_all(self):
resources = []
page = 0
while True:
new_resources = self.page(page).fetch()
if not new_resources:
break
resources.extend(new_resources)
page += 1
return new_resources
It will be very useful to combine complex queries using Q() like it made in Django.
Examples:
OR
client.resources('Slot').search(Q(schedule='id1') | Q(schedule='id2'))
transforms to =>
client.resources('Schedule').search(actor='id1,id2')
and generates =>
?schedule=id1,id2
AND
client.resources('Schedule').search(Q(actor='id1') & Q(actor='id2'))
transforms to =>
client.resources('Schedule').search(actor=['id1', 'id2'])
and generates =>
?actor=id1&actor=id2
Hi
I created a repl.it program using fhirpy. You can test it here
https://repl.it/@fhirinterm/TryFhirPy
It just runs this:
import asyncio
from fhirpy import AsyncFHIRClient
async def main():
# Create an instance
client = AsyncFHIRClient(
'http://test.fhir.org/r4',
fhir_version='4.0.0',
authorization='Bearer TOKEN',
)
# Iterate over search set
org_resources = client.resources('Patient')
async for org_resource in org_resources:
print(org_resource.serialize())
if name == 'main':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
I got this error message:
Traceback (most recent call last):
File "main.py", line 22, in
loop.run_until_complete(main())
File "/usr/local/lib/python3.7/asyncio/base_events.py", line 579, in run_until_complete
return future.result()
File "main.py", line 16, in main
async for org_resource in org_resources:
File "/home/runner/.local/share/virtualenvs/python3/lib/python3.7/site-packages/fhirpy/base/lib.py", line496, in aiter
items = await iterable_coroutine
File "/home/runner/.local/share/virtualenvs/python3/lib/python3.7/site-packages/fhirpy/base/lib.py", line522, in fetch
resource = self._perform_resource(data, skip_caching)
File "/home/runner/.local/share/virtualenvs/python3/lib/python3.7/site-packages/fhirpy/base/lib.py", line282, in _perform_resource
resource = self.client.resource(resource_type, **data)
File "/home/runner/.local/share/virtualenvs/python3/lib/python3.7/site-packages/fhirpy/base/lib.py", line90, in resource
return self.resource_class(self, resource_type=resource_type, **kwargs)
File "/home/runner/.local/share/virtualenvs/python3/lib/python3.7/site-packages/fhirpy/base/lib.py", line709, in init
super(BaseResource, self).init(client, **converted_kwargs)
File "/home/runner/.local/share/virtualenvs/python3/lib/python3.7/site-packages/fhirpy/base/lib.py", line597, in init
self._raise_error_if_invalid_keys(kwargs.keys())
File "/home/runner/.local/share/virtualenvs/python3/lib/python3.7/site-packages/fhirpy/base/lib.py", line684, in _raise_error_if_invalid_keys
key, ', '.join(root_attrs)
KeyError: 'Invalid key _birthDate
. Possible keys are multipleBirthInteger, text, active, deceasedBoolean, identifier, multipleBirthBoolean, language, extension, contained, address, gender, modifierExtension, id, communication, managingOrganization, resourceType, maritalStatus, photo, telecom, birthDate, name, generalPractitioner, meta, contact, implicitRules, deceasedDateTime, link
'
Interestingly, if you replace 'Patient' with 'Organization'
It works perfectly
KInd regards
DK
When making a get call on multiple end points, both open (http://hapi.fhir.org/baseDstu3) and EMR Vendor based (Epic 2018, Epic August 2018) receive error
the JSON object must be str, not 'bytes'
This is caused by a missing .decode() call on r.content, here's the broken piece of code. Notice the 200 call is the only one without a .decode()
if 200 <= r.status_code < 300:
return json.loads(r.content) if r.content else None
if r.status_code == 404:
raise FHIRResourceNotFound(r.content.decode())
raise FHIROperationOutcome(r.content.decode())
This simple example shows the issue
from fhirpy import FHIRClient
client = FHIRClient(url='http://hapi.fhir.org/baseDstu3')
resources = client.resources('Practitioner').get(id=1700173)
I have a fix in place I'm using currently and will submit a pull request.
For example, if I have a list of Patient Id's I want to look up, how can I iterate the list and make an asynchronous request for each one? Here is what I tried so far:
import asyncio
from fhirpy import AsyncFHIRClient
async def main():
# Create an instance
client = AsyncFHIRClient(
'https://myfhirendpoint.com',
fhir_version='4.0.0',
)
client.schema = None
pat_ids = [
"patient1",
"patient2",
"patient3",
"patient4",
"patient5",
]
resources = client.resources('Patient')
tasks = [
resources.search(_id=pat_id).limit(10)
for pat_id in pat_ids
]
patients = [await task.fetch() for task in tasks]
return patients
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
The above works, but I don't think it is executing asynchronously. What is the proper way to do this using fhirpy?
I've started adding issues and pull requests, though the project's license file is empty. While the beda software site speaks to a commitment to open source, it would make folks, including myself, more comfortable contributing if the licensing was clearly stated in the repo.
When we use revinclude
or include
we have a bundle with multiple types of resources, but fetch/fetch_all returns only main resource.
We can add fetch_raw/fetch_all_raw that should always return a dict with fetched resources.
Example:
client.resources('Schedule').revinclude('Slot', 'schedule').fetch_all_raw()
returns
{'Slot': [SlotResource, SlotResource, ...],
'Schedule': [ScheduleResource, ScheduleResource, ...],}
And
client.resources('Schedule').fetch_all_raw()
returns
{'Schedule': [ScheduleResource, ScheduleResource, ...],}
TBD
Not sure if this problem is specific to the FHIR Server I am pulling from, but supplying a page number will always return a bundle with resources. The current fetch_all() code uses this:
while True:
new_resources = await self.page(page).fetch()
if not new_resources:
break
Since my server always returns something regardless of the page number, it gets stuck in the loop above. The way I've gotten around this in the past is to refer to the next link in the Bundle .
I suppose a quick and dirty solution would be something like the following:
async def fetch(self):
bundle_data = await self.client._fetch_resource(
self.resource_type, self.params
)
bundle_resource_type = bundle_data.get('resourceType', None)
...
next_link = None
for link in bundle_data.get('link', []):
if link['relation'] == 'next':
next_link = link['url']
return resources, next_link
async def fetch_all(self):
page = 1
resources = []
while True:
new_resources, next_link = await self.page(page).fetch()
resources.extend(new_resources)
if not next_link:
break
page += 1
return resources
The documentation mentions you can use Raw Parameters using the following:
from fhirpy.base.searchset import Raw
patients = client.resources('Patient')
patients.search(Raw('general-practitioner.name=Hospital'))
# /Patient?general-practitioner.name=Hospital
However, after downloading the source code for 1.0.0 it appears that searchset.py was not included in the release. Is this a mistake?
/Questionnaire/<id>/$populate
/Valuest/<id>/$expand
/Valuest/$expand
$ operation should be a string parameter and accept any string. It should work on both levels.
When using a bearer token, is there a way to refresh it when it expires?
Or will I have to create a new client instance each time?
The following examples should work:
client.resources('Patient').search(general_practitioner__name='test')
client.resources('Patient').search(general_practitioner__Practitioner__name='test')
In #35 we've done lazy fetching using __iter__
/__aiter__
. I think In 2.0.0 we can remove fetch/fetch_all/fetch_raw due to __iter__
/__aiter__
(always returns all results) and some methods such as to_list()
and to_bundle()
Several EHR vendors, including Epic, require headers that cannot be passed in, or make no sense to be passed in, with the current state of the object constructor. These are typically used for things like client IDs and additional authorization for access.
I have a solution currently implemented and being used with a large EHR installation. I add an extra_headers attribute to the object, defaulted to None. When not None, the extra_headers attribute is unpacked into the headers variable.
If you can please assign this bug to me, I will submit a pull request for the solution within the next few days.
datetime.date
should be transformed into %Y-%m-%d
, and datetime.datetime
into %Y-%m-%dT%H:%M:%S
Now we have a bug when using client.resources('...').search(active=True)
It creates a query ?active=True
, but it should be ?active=true
TBD
Example:
p = client.resource('Patient', managingOrganization=[...])
p['managingOrganization'] = None
p.save()
should remove managingOrganization key for POST/PUT query.
For PATCH - TBD, but it hasn't implemented yet
Just re-write FHIRResource._get_path
It will be useful if we can provide this API for search()
with prefixies and modifiers.
See:
Examples:
client.resources('Schedule').search(date__eq='2000-01-01')
transforms to =>
client.resources('Schedule').search(date='eq2000-01-01')
generates =>
?date=eq2000-01-01
Let's remove self.client to self._client
I am trying to resolve references from a resource, with AsyncReference.to_resource()
This seems to fail.
example
import asyncio
from fhirpy import AsyncFHIRClient
from py2neo import Graph, Node, Relationship
async def main():
# Create an instance
client = AsyncFHIRClient(
"http://hapi.fhir.org/baseR4",
# "http://localhost:8080/fhir",
authorization="Bearer TOKEN",
)
obser_client = client.resources("Observation") # Return lazy search set
obser_query = obser_client.search().limit(5)
observations = await obser_query.fetch()
for obser in observations:
if "subject" in obser:
# This will fail
patient = await obser.subject.to_resource()
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
It looks like a typo on line
Line 355 in f85b7d5
because when changing the line
self.resource_type).search(id=self.id).get()
to
self.resource_type).search(_id=self.id).get()
it seems to work.
Im just starting to work with fhir, and do not have a good overview of the subject matter.
Do I use the function in a wrong way?
Is the fhir test server data corrupt?
If i found a bug, i can provide a merge request.
Now it is hard to extract details
p = client.resource('Observation', code={'coding': [{'code': '123'}]})
p['code']
should return wrapped dict and provide get_by_path method
Now it accepts only id
. get should accept any search parameters and assert that exactly one result was returned. Otherwise, an exception should be fired.
Now we update only id and meta. But sometimes the resource can be mutated
Critical fix for get_by_path beda-software/base-fhir-py@cf02ff9
It should make a POST request to the /resourceType/$validate and return OperationOutcome if raise_exception arg passed or just True/False.
TBD
GET /EpisodeOfCare?patient:Patient.name=Ivan
We need to support both sync and async interfaces
We should add a new SearchSet (e.g. HistorySearchSet) and provide methods .history()
for Resource/Reference, SearchSet and Client.
It should be something like that:
client.history('User') - returns HistorySearchSet for /User/_history/
client.history('User', 'id') - returns HistorySearchSet for /User/id/_history/
clients.resources('User').history() - returns HistorySearchSet for /User/_history/
clients.resources('User').history('id') - returns HistorySearchSet for /User/id/_history/
And resource/reference methods:
resource = client.resource('User').get('id')
resource.history() - returns HistorySearchSet for /User/id/_history/
And HistorySearchSet should provide:
history = client.history('User', 'id')
history.fetch()/history.fetch_all() - executes and returns list of resources
history.count() - executes and returns history count
history.limit()/history.page() - the same as in the SearchSet
history.at('date')/history.since('date') - returns new HistorySearchSet
history.get('vid') - executes and returns /User/id/_history/vid
And also we can support some useful helpers:
resource.history().get_previous() - returns the previous version of the resource if possible or None
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.