Coverage for cc_modules/cc_simpleobjects.py: 38%

73 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-08 23:14 +0000

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/cc_modules/cc_simpleobjects.py 

5 

6=============================================================================== 

7 

8 Copyright (C) 2012, University of Cambridge, Department of Psychiatry. 

9 Created by Rudolf Cardinal (rnc1001@cam.ac.uk). 

10 

11 This file is part of CamCOPS. 

12 

13 CamCOPS is free software: you can redistribute it and/or modify 

14 it under the terms of the GNU General Public License as published by 

15 the Free Software Foundation, either version 3 of the License, or 

16 (at your option) any later version. 

17 

18 CamCOPS is distributed in the hope that it will be useful, 

19 but WITHOUT ANY WARRANTY; without even the implied warranty of 

20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

21 GNU General Public License for more details. 

22 

23 You should have received a copy of the GNU General Public License 

24 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>. 

25 

26=============================================================================== 

27 

28**Simple struct-like classes.** 

29 

30""" 

31 

32import copy 

33from typing import List, TYPE_CHECKING 

34 

35from pendulum import Date 

36 

37from cardinal_pythonlib.datetimefunc import format_datetime 

38from cardinal_pythonlib.reprfunc import auto_repr 

39 

40from camcops_server.cc_modules.cc_constants import DateFormat 

41 

42if TYPE_CHECKING: 

43 from camcops_server.cc_modules.cc_request import CamcopsRequest 

44 

45# Prefer classes to collections.namedtuple; both support type checking but 

46# classes support better parameter checking (and refactoring) via PyCharm. 

47 

48 

49# ============================================================================= 

50# IdNumReference 

51# ============================================================================= 

52 

53 

54class IdNumReference(object): 

55 """ 

56 A simple way of referring to an ID number. 

57 

58 It's not stored in the database -- it's just an object to be passed around 

59 that encapsulates ``which_idnum`` and ``idnum_value``. 

60 

61 As an example, suppose our administrator has defined ID type 

62 (``which_idnum``) 7 to be "NHS number". Then if a patient has NHS number 

63 9999999999, we might represent this ID of theirs as 

64 ``IdNumReference(which_idnum=7, idnum_value=9999999999)``. 

65 """ 

66 

67 def __init__(self, which_idnum: int, idnum_value: int) -> None: 

68 self.which_idnum = which_idnum 

69 self.idnum_value = idnum_value 

70 

71 def __str__(self) -> str: 

72 return f"idnum{self.which_idnum}={self.idnum_value}" 

73 

74 def __repr__(self) -> str: 

75 return auto_repr(self) 

76 

77 def is_valid(self) -> bool: 

78 return ( 

79 self.which_idnum is not None 

80 and self.which_idnum > 0 

81 and self.idnum_value is not None 

82 and self.idnum_value > 0 

83 ) 

84 

85 def __eq__(self, other: "IdNumReference") -> bool: 

86 if not isinstance(other, IdNumReference): 

87 return False 

88 return ( 

89 self.which_idnum == other.which_idnum 

90 and self.idnum_value == other.idnum_value 

91 ) 

92 

93 def description(self, req: "CamcopsRequest") -> str: 

94 if not self.is_valid(): 

95 return "[invalid_IdNumReference]" 

96 return f"{req.get_id_shortdesc(self.which_idnum)} = {self.idnum_value}" 

97 

98 

99# ============================================================================= 

100# HL7PatientIdentifier 

101# ============================================================================= 

102 

103# noinspection PyShadowingBuiltins 

104class HL7PatientIdentifier(object): 

105 """ 

106 Represents a patient identifier for the HL7 protocol. 

107 """ 

108 

109 def __init__( 

110 self, pid: str, id_type: str, assigning_authority: str 

111 ) -> None: 

112 self.pid = pid 

113 # ... called "pid" not "id" as type checker sometimes thinks "id" must 

114 # be integer, as in Python's id(object). 

115 self.id_type = id_type 

116 self.assigning_authority = assigning_authority 

117 

118 

119# ============================================================================= 

120# BarePatientInfo 

121# ============================================================================= 

122 

123 

124class BarePatientInfo(object): 

125 """ 

126 Represents information about a patient using a simple object with no 

127 connection to a database. 

128 

129 In some situations we avoid using 

130 :class:`camcops_server.cc_modules.cc_patient.Patient`: specifically, when 

131 we would otherwise have to deal with mutual dependency problems and the use 

132 of the database (prior to full database initialization). 

133 """ 

134 

135 def __init__( 

136 self, 

137 forename: str = None, 

138 surname: str = None, 

139 sex: str = None, 

140 dob: Date = None, 

141 address: str = None, 

142 email: str = None, 

143 gp: str = None, 

144 otherdetails: str = None, 

145 idnum_definitions: List[IdNumReference] = None, 

146 ) -> None: 

147 self.forename = forename 

148 self.surname = surname 

149 self.sex = sex 

150 self.dob = dob 

151 self.address = address 

152 self.email = email 

153 self.gp = gp 

154 self.otherdetails = otherdetails 

155 self.idnum_definitions = ( 

156 idnum_definitions or [] 

157 ) # type: List[IdNumReference] 

158 

159 def __str__(self) -> str: 

160 return ( 

161 "Patient(forename={f!r}, surname={sur!r}, sex={sex!r}, DOB={dob}, " 

162 "address={a!r}, email={email!r}, gp={gp!r}, otherdetails={o!r}, " 

163 "idnums={i})".format( 

164 f=self.forename, 

165 sur=self.surname, 

166 sex=self.sex, 

167 dob=format_datetime(self.dob, DateFormat.ISO8601_DATE_ONLY), 

168 a=self.address, 

169 email=self.email, 

170 gp=self.gp, 

171 o=self.otherdetails, 

172 i="[{}]".format( 

173 ", ".join(str(idnum) for idnum in self.idnum_definitions) 

174 ), 

175 ) 

176 ) 

177 

178 def __repr__(self) -> str: 

179 return auto_repr(self) 

180 

181 def add_idnum(self, idref: IdNumReference) -> None: 

182 """ 

183 Adds an ID number. No checks in relation to what's already present. 

184 

185 Args: 

186 idref: a :class:`IdNumReference` 

187 """ 

188 self.idnum_definitions.append(idref) 

189 

190 def __eq__(self, other: "BarePatientInfo") -> bool: 

191 """ 

192 Do all data elements match those of ``other``? 

193 """ 

194 if not isinstance(other, BarePatientInfo): 

195 return False 

196 return ( 

197 self.forename == other.forename 

198 and self.surname == other.surname 

199 and self.sex == other.sex 

200 and self.dob == other.dob 

201 and self.address == other.address 

202 and self.email == other.email 

203 and self.gp == other.gp 

204 and self.otherdetails == other.otherdetails 

205 and self.idnum_definitions == other.idnum_definitions 

206 ) 

207 

208 

209# ============================================================================= 

210# Raw XML value 

211# ============================================================================= 

212 

213 

214class XmlSimpleValue(object): 

215 """ 

216 Represents XML lowest-level items. See functions in ``cc_xml.py``. 

217 """ 

218 

219 def __init__(self, value) -> None: 

220 self.value = value 

221 

222 

223# ============================================================================= 

224# TaskExportOptions 

225# ============================================================================= 

226 

227 

228class TaskExportOptions(object): 

229 """ 

230 Information-holding object for options controlling XML and other 

231 representations of tasks. 

232 """ 

233 

234 def __init__( 

235 self, 

236 db_patient_id_per_row: bool = False, 

237 db_make_all_tables_even_empty: bool = False, 

238 db_include_summaries: bool = False, 

239 include_blobs: bool = False, 

240 xml_include_ancillary: bool = False, 

241 xml_include_calculated: bool = False, 

242 xml_include_comments: bool = True, 

243 xml_include_patient: bool = False, 

244 xml_include_plain_columns: bool = False, 

245 xml_include_snomed: bool = False, 

246 xml_skip_fields: List[str] = None, 

247 xml_sort_by_name: bool = True, 

248 xml_with_header_comments: bool = False, 

249 ) -> None: 

250 """ 

251 Args: 

252 db_patient_id_per_row: 

253 generates an anonymisation staging database -- that is, a 

254 database with patient IDs in every row of every table, suitable 

255 for feeding into an anonymisation system like CRATE 

256 (https://doi.org/10.1186%2Fs12911-017-0437-1). 

257 db_make_all_tables_even_empty: 

258 create all tables, even empty ones 

259 

260 include_blobs: 

261 include binary large objects (BLOBs) (applies to several export 

262 formats) 

263 

264 xml_include_ancillary: 

265 include ancillary tables as well as the main? 

266 xml_include_calculated: 

267 include fields calculated by the task 

268 xml_include_comments: 

269 include comments in XML? 

270 xml_include_patient: 

271 include patient details? 

272 xml_include_plain_columns: 

273 include the base columns 

274 xml_include_snomed: 

275 include SNOMED-CT codes, if available? 

276 xml_skip_fields: 

277 fieldnames to skip 

278 xml_sort_by_name: 

279 sort by field/attribute names? 

280 xml_with_header_comments: 

281 include header-style comments? 

282 """ 

283 self.db_patient_id_in_each_row = db_patient_id_per_row 

284 self.db_make_all_tables_even_empty = db_make_all_tables_even_empty 

285 self.db_include_summaries = db_include_summaries 

286 

287 self.include_blobs = include_blobs 

288 

289 self.xml_include_ancillary = xml_include_ancillary 

290 self.xml_include_calculated = xml_include_calculated 

291 self.xml_include_comments = xml_include_comments 

292 self.xml_include_patient = xml_include_patient 

293 self.xml_include_plain_columns = xml_include_plain_columns 

294 self.xml_include_snomed = xml_include_snomed 

295 self.xml_skip_fields = xml_skip_fields or [] # type: List[str] 

296 self.xml_sort_by_name = xml_sort_by_name 

297 self.xml_with_header_comments = xml_with_header_comments 

298 

299 def clone(self) -> "TaskExportOptions": 

300 """ 

301 Returns a copy of this object. 

302 """ 

303 return copy.copy(self)