GithubHelp home page GithubHelp logo

fhir-py's Issues

Make fetch/fetch_all lazy

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.

Transform FHIRResource/FHIRReference in search lookups

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)

Setup fhir-py using base-fhir-py

  • Create classes FHIRClient, FHIRResource, FHIRReference, FHIRSearchSet based on classes from base-fhir-py
  • Override FHIRReference.id, FHIRReference.resource_type, FHIRReference.reference
  • Add validation using FHIRResource.raise_error_if_invalid_keys FHIRReference.raise_error_if_invalid_keys

Implement fetch_all() using pagination

  1. Create alias for execute - fetch()
  2. Create fetch_all() which fetches all instances with pagination
client.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

Move common functional to base-fhir-py

  • Rename FHIRClient -> Client, FHIRResource -> Resource, FHIRBaseResource -> AbstractResource, FHIRReference -> Reference, FHIRSearchSet -> SearchSet
  • Make them abstract
  • In Client specify searchset_class, resource_class, reference_class and use them in public API (resource, resources, reference)
  • Remove load_schema, validation, fhir_version, schema, root_keys make raise_error_if_invalid_keys public and empty by default
  • Make Reference.resource_type, Reference.id, Reference.reference abstract
  • Make AbstractResource.is_reference non static, abstract and move into Resource
  • In SearchSet.clone use self.class to instantiate a copy

Add support for Q() queries in .search()

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

Error in V1.0.0 of fhirpy

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

on 200 response JSON is not being properly decoded

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.

How to Handle List of Searches?

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?

Add license to repo

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.

https://choosealicense.com/no-permission/

Add better support for related resources via include/revinclude

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

fetch_all() Creating Infinite Loop.

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

searchset.py not in 1.0.0?

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?

Add API for calling resource operations

/Questionnaire/<id>/$populate
/Valuest/<id>/$expand
/Valuest/$expand

$ operation should be a string parameter and accept any string. It should work on both levels.

Lazy search sets

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()

No "extra headers" can be added to requests

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.

Recursively remove all null values

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

to_resource() error "Unknown search parameter 'id'"

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

self.resource_type).search(id=self.id).get()

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.

get should accept any search parameters

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.

Add support for _history

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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.