Coverage for /home/martinb/workspace/client-py/fhirclient/models/fhirabstractresource.py : 47%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# Base class for FHIR resources.
5# 2014, SMART Health IT.
7from . import fhirabstractbase
10class FHIRAbstractResource(fhirabstractbase.FHIRAbstractBase):
11 """ Extends the FHIRAbstractBase with server talking capabilities.
12 """
13 resource_type = 'FHIRAbstractResource'
15 def __init__(self, jsondict=None, strict=True):
16 self._server = None
17 """ The server the instance was read from. """
19 # raise if "resourceType" does not match
20 if jsondict is not None and 'resourceType' in jsondict \
21 and jsondict['resourceType'] != self.resource_type:
22 raise Exception("Attempting to instantiate {} with resource data that defines a resourceType of \"{}\""
23 .format(self.__class__, jsondict['resourceType']))
25 super(FHIRAbstractResource, self).__init__(jsondict=jsondict, strict=strict)
27 @classmethod
28 def _with_json_dict(cls, jsondict):
29 """ Overridden to use a factory if called when "resourceType" is
30 defined in the JSON but does not match the receiver's resource_type.
31 """
32 if not isinstance(jsondict, dict):
33 raise Exception("Cannot use this method with anything but a JSON dictionary, got {}"
34 .format(jsondict))
36 res_type = jsondict.get('resourceType')
37 if res_type and res_type != cls.resource_type:
38 return fhirelementfactory.FHIRElementFactory.instantiate(res_type, jsondict)
39 return super(FHIRAbstractResource, cls)._with_json_dict(jsondict)
41 def as_json(self):
42 js = super(FHIRAbstractResource, self).as_json()
43 js['resourceType'] = self.resource_type
44 return js
47 # MARK: Handling Paths
49 def relativeBase(self):
50 return self.__class__.resource_type
52 def relativePath(self):
53 if self.id is None:
54 return self.relativeBase()
55 return "{}/{}".format(self.relativeBase(), self.id)
58 # MARK: - Server Connection
60 @property
61 def origin_server(self):
62 """ Walks the owner hierarchy until it finds an owner with a server.
63 """
64 server = self._server
65 owner = self._owner
66 while server is None and owner is not None:
67 server = getattr(owner, '_server', None)
68 owner = owner._owner
69 return server
71 @origin_server.setter
72 def origin_server(self, server):
73 """ Sets the server on an element. """
74 self._server = server
76 @classmethod
77 def read(cls, rem_id, server):
78 """ Read the resource with the given id from the given server. The
79 passed-in server instance must support a `request_json()` method call,
80 taking a relative path as first (and only mandatory) argument.
82 :param str rem_id: The id of the resource on the remote server
83 :param FHIRServer server: An instance of a FHIR server or compatible class
84 :returns: An instance of the receiving class
85 """
86 if not rem_id:
87 raise Exception("Cannot read resource without remote id")
89 path = '{}/{}'.format(cls.resource_type, rem_id)
90 instance = cls.read_from(path, server)
91 instance._local_id = rem_id
93 return instance
95 @classmethod
96 def read_from(cls, path, server):
97 """ Requests data from the given REST path on the server and creates
98 an instance of the receiving class.
100 :param str path: The REST path to read from
101 :param FHIRServer server: An instance of a FHIR server or compatible class
102 :returns: An instance of the receiving class
103 """
104 if not path:
105 raise Exception("Cannot read resource without REST path")
106 if server is None:
107 raise Exception("Cannot read resource without server instance")
109 ret = server.request_json(path)
110 instance = cls(jsondict=ret)
111 instance.origin_server = server
112 return instance
114 def createUrl(self):
115 """ Get the URL on the server for creating the resource.
117 :returns: The resource endpoint or None for the root endpoint
118 """
119 root_post_types = ("batch", "transaction")
121 if self.resource_type == "Bundle" and self.type in root_post_types:
122 return None
124 return self.relativeBase()
126 def create(self, server):
127 """ Attempt to create the receiver on the given server, using a POST
128 command.
130 :param FHIRServer server: The server to create the receiver on
131 :returns: None or the response JSON on success
132 """
133 srv = server or self.origin_server
134 if srv is None:
135 raise Exception("Cannot create a resource without a server")
136 if self.id:
137 raise Exception("This resource already has an id, cannot create")
139 ret = srv.post_json(self.createUrl(), self.as_json())
140 if len(ret.text) > 0:
141 return ret.json()
142 return None
144 def update(self, server=None):
145 """ Update the receiver's representation on the given server, issuing
146 a PUT command.
148 :param FHIRServer server: The server to update the receiver on;
149 optional, will use the instance's `server` if needed.
150 :returns: None or the response JSON on success
151 """
152 srv = server or self.origin_server
153 if srv is None:
154 raise Exception("Cannot update a resource that does not have a server")
155 if not self.id:
156 raise Exception("Cannot update a resource that does not have an id")
158 ret = srv.put_json(self.relativePath(), self.as_json())
159 if len(ret.text) > 0:
160 return ret.json()
161 return None
163 def delete(self, server=None):
164 """ Delete the receiver from the given server with a DELETE command.
166 :param FHIRServer server: The server to update the receiver on;
167 optional, will use the instance's `server` if needed.
168 :returns: None or the response JSON on success
169 """
170 srv = server or self.origin_server
171 if srv is None:
172 raise Exception("Cannot delete a resource that does not have a server")
173 if not self.id:
174 raise Exception("Cannot delete a resource that does not have an id")
176 ret = srv.delete_json(self.relativePath())
177 if len(ret.text) > 0:
178 return ret.json()
179 return None
182 # MARK: - Search
184 def search(self, struct=None):
185 """ Search can be started via a dictionary containing a search
186 construct.
188 Calling this method with a search struct will return a `FHIRSearch`
189 object representing the search struct, with "$type" and "id" added.
191 :param dict struct: An optional search structure
192 :returns: A FHIRSearch instance
193 """
194 if struct is None:
195 struct = {'$type': self.__class__.resource_type}
196 if self._local_id is not None or self.id is not None:
197 struct['id'] = self._local_id or self.id
198 return self.__class__.where(struct)
200 @classmethod
201 def where(cls, struct):
202 """ Search can be started via a dictionary containing a search
203 construct.
205 Calling this method with a search struct will return a `FHIRSearch`
206 object representing the search struct
208 :param dict struct: A search structure
209 :returns: A FHIRSearch instance
210 """
211 return fhirsearch.FHIRSearch(cls, struct)
214from . import fhirdate
215from . import fhirsearch
216from . import fhirelementfactory