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
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-08 23:14 +0000
1#!/usr/bin/env python
3"""
4camcops_server/cc_modules/cc_simpleobjects.py
6===============================================================================
8 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
9 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
11 This file is part of CamCOPS.
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.
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.
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/>.
26===============================================================================
28**Simple struct-like classes.**
30"""
32import copy
33from typing import List, TYPE_CHECKING
35from pendulum import Date
37from cardinal_pythonlib.datetimefunc import format_datetime
38from cardinal_pythonlib.reprfunc import auto_repr
40from camcops_server.cc_modules.cc_constants import DateFormat
42if TYPE_CHECKING:
43 from camcops_server.cc_modules.cc_request import CamcopsRequest
45# Prefer classes to collections.namedtuple; both support type checking but
46# classes support better parameter checking (and refactoring) via PyCharm.
49# =============================================================================
50# IdNumReference
51# =============================================================================
54class IdNumReference(object):
55 """
56 A simple way of referring to an ID number.
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``.
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 """
67 def __init__(self, which_idnum: int, idnum_value: int) -> None:
68 self.which_idnum = which_idnum
69 self.idnum_value = idnum_value
71 def __str__(self) -> str:
72 return f"idnum{self.which_idnum}={self.idnum_value}"
74 def __repr__(self) -> str:
75 return auto_repr(self)
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 )
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 )
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}"
99# =============================================================================
100# HL7PatientIdentifier
101# =============================================================================
103# noinspection PyShadowingBuiltins
104class HL7PatientIdentifier(object):
105 """
106 Represents a patient identifier for the HL7 protocol.
107 """
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
119# =============================================================================
120# BarePatientInfo
121# =============================================================================
124class BarePatientInfo(object):
125 """
126 Represents information about a patient using a simple object with no
127 connection to a database.
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 """
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]
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 )
178 def __repr__(self) -> str:
179 return auto_repr(self)
181 def add_idnum(self, idref: IdNumReference) -> None:
182 """
183 Adds an ID number. No checks in relation to what's already present.
185 Args:
186 idref: a :class:`IdNumReference`
187 """
188 self.idnum_definitions.append(idref)
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 )
209# =============================================================================
210# Raw XML value
211# =============================================================================
214class XmlSimpleValue(object):
215 """
216 Represents XML lowest-level items. See functions in ``cc_xml.py``.
217 """
219 def __init__(self, value) -> None:
220 self.value = value
223# =============================================================================
224# TaskExportOptions
225# =============================================================================
228class TaskExportOptions(object):
229 """
230 Information-holding object for options controlling XML and other
231 representations of tasks.
232 """
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
260 include_blobs:
261 include binary large objects (BLOBs) (applies to several export
262 formats)
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
287 self.include_blobs = include_blobs
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
299 def clone(self) -> "TaskExportOptions":
300 """
301 Returns a copy of this object.
302 """
303 return copy.copy(self)