nazrulworld / fhir.resources

FHIR Resources https://www.hl7.org/fhir/resourcelist.html
https://pypi.org/project/fhir.resources/
Other
372 stars 104 forks source link

Support for FastAPI pydantic response models #46

Open garry-jeromson opened 3 years ago

garry-jeromson commented 3 years ago

FastAPI supports using pydantic models to define JSON response bodies for REST API endpoints; making FHIR resources compatible with this support would take the usability of the library to a higher level.

I'm new to the library so I don't know more about how it uses pydantic other than what is stated in the README; in any case, however pydantic is currently used, it doesn't seem to be compatible on first look. The only requirement for compatibility with FastAPI is that the response model class is derived from pydantic's BaseModel.

A super basic example using the Practitioner resource from release 5.1.1, with FastAPI 0.62.0 and pydantic 1.7.3, is as follows:


from fastapi import APIRouter
from fhir.resources.practitioner import Practitioner

router = APIRouter()

@router.get(
    "/practitioner",
    response_model=Practitioner,
)
def get_practitioner(
) -> Any:
    return Practitioner()

This gives a couple of runtime errors on endpoint setup:

RuntimeError: no validator found for <class 'fhir.resources.practitioner.Practitioner'>, see `arbitrary_types_allowed` in Config
Invalid args for response field! Hint: check that <class 'fhir.resources.practitioner.Practitioner'> is a valid pydantic field type

This makes some sense, as the Practitioner resource (and most other resources, it would seem) derive from FHIRAbstractBase, and not pydantic's BaseModel.

Is FastAPI support something that's been considered already? Am I missing anything obvious regarding the use of pydantic?

nazrulworld commented 3 years ago

I think we have support the FastAPI typing methodology. try

from fastapi import APIRouter
from fhir.resources.practitioner import Practitioner
from fhir.resources.fhirtypes import PractitionerType

router = APIRouter()

@router.get(
    "/practitioner",
    response_model=PractitionerType,
)
def get_practitioner(
) -> Any:
    return Practitioner()

Let us know if you face any problem.

garry-jeromson commented 3 years ago

Thanks! This kind of works, in that the code runs, but the response model is incorrect and it doesn't play nice with FastAPI's OpenAPI documentation generation:

{
  "fhir_comments": null,
  "id": null,
  "_id": null,
  "implicitRules": null,
  "_implicitRules": null,
  "language": null,
  "_language": null,
  "meta": null,
  "contained": null,
  "extension": null,
  "modifierExtension": null,
  "text": null,
  "active": null,
  "_active": null,
  "address": null,
  "birthDate": null,
  "_birthDate": null,
  "communication": null,
  "gender": null,
  "_gender": null,
  "identifier": null,
  "name": null,
  "photo": null,
  "qualification": null,
  "telecom": null,
  "resourceType": "Practitioner"
}

If improved FastAPI support is something you'd be open to adding to the library, I'm happy to work on a pull request to see if we can fix these issues.

nazrulworld commented 3 years ago

@garry-jeromson you are welcome to make your pull requests. we are fully open to add any new feature. Just keep in mind that the new feature should not make any new mandatory package dependency as well as not violating FHIR Resource specification.

pcorazao commented 3 years ago

if we add this to here: https://github.com/nazrulworld/fhir.resources/blob/main/fhir/resources/fhirabstractmodel.py#L516 We can remove all the _ properties from showing up on the Schema for FastAPI Swagger.

@staticmethod
        def schema_extra(schema: Dict[str, Any], model: Type['FHIRAbstractModel']) -> None:
            current_schema = schema.get('properties', {})

            for key in list(current_schema.keys()):
                if "_" in key:
                    current_schema.pop(key)

My next question is.. how do we get the sub models to render in FastAPI? if we do something like this: ` name: HumanName = Field( None, alias="name", title="A name associated with the patient", description="A name associated with the individual.",

if property is element of this resource.

    element_property=True,
)` i.e. removing the typing.List[fhirtypes.HumanNameType] we are nearly there, you get both the HumanName added to the Schema, but it also plugs in the full contract into the name field as well.  Which might be problematic for some of the larger resources.

I don't know if there is a way to keep HumanNameType and then like.. simply add the HumanName to the Schema somehow?

gilmorera commented 1 year ago

What's the status on FastAPI? Is there any inherit functionality for fastAPI in this repository or is there a 3rd party tool now?