Hide keyboard shortcuts

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. 

6 

7from . import fhirabstractbase 

8 

9 

10class FHIRAbstractResource(fhirabstractbase.FHIRAbstractBase): 

11 """ Extends the FHIRAbstractBase with server talking capabilities. 

12 """ 

13 resource_type = 'FHIRAbstractResource' 

14 

15 def __init__(self, jsondict=None, strict=True): 

16 self._server = None 

17 """ The server the instance was read from. """ 

18 

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'])) 

24 

25 super(FHIRAbstractResource, self).__init__(jsondict=jsondict, strict=strict) 

26 

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

35 

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) 

40 

41 def as_json(self): 

42 js = super(FHIRAbstractResource, self).as_json() 

43 js['resourceType'] = self.resource_type 

44 return js 

45 

46 

47 # MARK: Handling Paths 

48 

49 def relativeBase(self): 

50 return self.__class__.resource_type 

51 

52 def relativePath(self): 

53 if self.id is None: 

54 return self.relativeBase() 

55 return "{}/{}".format(self.relativeBase(), self.id) 

56 

57 

58 # MARK: - Server Connection 

59 

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 

70 

71 @origin_server.setter 

72 def origin_server(self, server): 

73 """ Sets the server on an element. """ 

74 self._server = server 

75 

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. 

81 

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

88 

89 path = '{}/{}'.format(cls.resource_type, rem_id) 

90 instance = cls.read_from(path, server) 

91 instance._local_id = rem_id 

92 

93 return instance 

94 

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. 

99 

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

108 

109 ret = server.request_json(path) 

110 instance = cls(jsondict=ret) 

111 instance.origin_server = server 

112 return instance 

113 

114 def createUrl(self): 

115 """ Get the URL on the server for creating the resource. 

116 

117 :returns: The resource endpoint or None for the root endpoint 

118 """ 

119 root_post_types = ("batch", "transaction") 

120 

121 if self.resource_type == "Bundle" and self.type in root_post_types: 

122 return None 

123 

124 return self.relativeBase() 

125 

126 def create(self, server): 

127 """ Attempt to create the receiver on the given server, using a POST 

128 command. 

129 

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

138 

139 ret = srv.post_json(self.createUrl(), self.as_json()) 

140 if len(ret.text) > 0: 

141 return ret.json() 

142 return None 

143 

144 def update(self, server=None): 

145 """ Update the receiver's representation on the given server, issuing 

146 a PUT command. 

147 

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

157 

158 ret = srv.put_json(self.relativePath(), self.as_json()) 

159 if len(ret.text) > 0: 

160 return ret.json() 

161 return None 

162 

163 def delete(self, server=None): 

164 """ Delete the receiver from the given server with a DELETE command. 

165 

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

175 

176 ret = srv.delete_json(self.relativePath()) 

177 if len(ret.text) > 0: 

178 return ret.json() 

179 return None 

180 

181 

182 # MARK: - Search 

183 

184 def search(self, struct=None): 

185 """ Search can be started via a dictionary containing a search 

186 construct. 

187 

188 Calling this method with a search struct will return a `FHIRSearch` 

189 object representing the search struct, with "$type" and "id" added. 

190 

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) 

199 

200 @classmethod 

201 def where(cls, struct): 

202 """ Search can be started via a dictionary containing a search 

203 construct. 

204 

205 Calling this method with a search struct will return a `FHIRSearch` 

206 object representing the search struct 

207 

208 :param dict struct: A search structure 

209 :returns: A FHIRSearch instance 

210 """ 

211 return fhirsearch.FHIRSearch(cls, struct) 

212 

213 

214from . import fhirdate 

215from . import fhirsearch 

216from . import fhirelementfactory