class FHIRClient(object): """ Instances of this class handle authorizing and talking to SMART on FHIR servers. The settings dictionary supports: - `app_id`: Your app/client-id, e.g. 'my_web_app' - `api_base`: The SMART service to connect to, e.g. 'https://fhir-api.smarthealthit.org' - `auth_type`: The authorization type, supports "oauth2" and "none". Defaults to "oauth2" if omitted - `redirect_uri`: The callback/redirect URL for your app, e.g. 'http://localhost:8000/fhir-app/' when testing locally """ def __init__(self, settings=None, state=None, save_func=lambda x:x): self.app_id = None self.server = None self.auth = None self.launch_context = None self._patient = None # init from state if state is not None: self.from_state(state) # init from settings dict elif settings is not None: self.app_id = settings['app_id'] self.server = FHIRServer(base_uri=settings['api_base']) scope = scope_default if 'launch_token' in settings: scope = ' launch:'.join([scope, settings['launch_token']]) else: scope = ' '.join([scope_nolaunch, scope]) auth_type = settings.get('auth_type') redirect = settings.get('redirect_uri') patient_id = settings.get('patient_id') if patient_id: self.auth = FHIRAuth.create(auth_type, app_id=self.app_id, patient_id=patient_id) else: self.auth = FHIRAuth.create(auth_type, app_id=self.app_id, scope=scope, redirect_uri=redirect) else: raise Exception("Must either supply settings or a state upon client initialization") self._save_func = save_func self.save_state() # MARK: Authorization @property def auth_type(self): return self.auth.auth_type if self.auth else None @property def ready(self): """ Returns True if the client is ready to make API calls (e.g. there is an access token). """ return self.auth.ready if self.auth is not None else False @property def authorize_url(self): """ The URL to use to receive an authorization token. """ auth_url = self.auth.authorize_url(self.server) if self.auth is not None else None self.save_state() return auth_url def handle_callback(self, url): """ You can call this to have the client automatically handle the auth callback after the user has logged in. :param str url: The complete callback URL """ self.launch_context = self.auth.handle_callback(url, self.server) self._set_authorized(True) self.save_state() def reauthorize(self): """ Try to reauthorize with the server; handled by our `auth` instance. :returns: A bool indicating reauthorization success """ ctx = self.auth.reauthorize(self.server) if self.auth is not None else None if ctx is not None: self.launch_context = ctx return True return False def _set_authorized(self, flag): """ Internal method used to sync server and auth. """ if flag: self.server.did_authorize(self.auth) else: self.server.did_authorize(None) self.auth.reset() # MARK: Current Patient @property def patient_id(self): return self.auth.patient_id @property def patient(self): if self._patient is None and self.ready: try: self._patient = patient.Patient.read(self.patient_id, self.server) except FHIRUnauthorizedException as e: if self.reauthorize(): self._patient = patient.Patient.read(self.patient_id, self.server) else: self._set_authorized(False) return self._patient def human_name(self, human_name_instance): """ Formats a `HumanName` instance into a string. """ if human_name_instance is None: return 'Unknown' parts = [] for n in [human_name_instance.prefix, human_name_instance.given, human_name_instance.family, human_name_instance.suffix]: if n is not None: parts.extend(n) return ' '.join(parts) if len(parts) > 0 else 'Unnamed' def string_gender(self, gender_concept): """ Takes a `CodeableConcept` instance and returns either 'male', 'female' or None. TODO: inspect coding system of the concepts and decide more thoroughly """ if gender_concept is not None \ and gender_concept.coding is not None \ and len(gender_concept.coding) > 0: if gender_concept.coding[0].code: # and 'http://hl7.org/fhir/v3/AdministrativeGender' == gender_concept.coding[0].system: return 'male' if 'M' == gender_concept.coding[0].code else 'female' return None # MARK: State @property def state(self): return { 'app_id': self.app_id, 'server': self.server.state, 'auth_type': self.auth_type, 'auth': self.auth.state, 'launch_context': self.launch_context, } def from_state(self, state): assert state self.app_id = state.get('app_id') or self.app_id self.launch_context = state.get('launch_context') or self.launch_context self.server = FHIRServer(state=state.get('server')) self.auth = FHIRAuth.create(state.get('auth_type'), app_id=self.app_id, state=state.get('auth')) if self.auth is not None and self.auth.ready is not None: self.server.did_authorize(self.auth) def save_state (self): self._save_func(self.state)