Coverage for cc_modules/cc_simpleobjects.py : 38%

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
3"""
4camcops_server/cc_modules/cc_simpleobjects.py
6===============================================================================
8 Copyright (C) 2012-2020 Rudolf Cardinal (rudolf@pobox.com).
10 This file is part of CamCOPS.
12 CamCOPS is free software: you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation, either version 3 of the License, or
15 (at your option) any later version.
17 CamCOPS is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
22 You should have received a copy of the GNU General Public License
23 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
25===============================================================================
27**Simple struct-like classes.**
29"""
31import copy
32# import logging
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# log = logging.getLogger(__name__)
47# Prefer classes to collections.namedtuple; both support type checking but
48# classes support better parameter checking (and refactoring) via PyCharm.
51# =============================================================================
52# IdNumReference
53# =============================================================================
55class IdNumReference(object):
56 """
57 A simple way of referring to an ID number.
59 It's not stored in the database -- it's just an object to be passed around
60 that encapsulates ``which_idnum`` and ``idnum_value``.
62 As an example, suppose our administrator has defined ID type
63 (``which_idnum``) 7 to be "NHS number". Then if a patient has NHS number
64 9999999999, we might represent this ID of theirs as
65 ``IdNumReference(which_idnum=7, idnum_value=9999999999)``.
66 """
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 and self.which_idnum > 0 and
80 self.idnum_value is not None and self.idnum_value > 0
81 )
83 def __eq__(self, other: "IdNumReference") -> bool:
84 if not isinstance(other, IdNumReference):
85 return False
86 return (
87 self.which_idnum == other.which_idnum and
88 self.idnum_value == other.idnum_value
89 )
91 def description(self, req: "CamcopsRequest") -> str:
92 if not self.is_valid():
93 return "[invalid_IdNumReference]"
94 return f"{req.get_id_shortdesc(self.which_idnum)} = {self.idnum_value}"
97# =============================================================================
98# HL7PatientIdentifier
99# =============================================================================
101# noinspection PyShadowingBuiltins
102class HL7PatientIdentifier(object):
103 """
104 Represents a patient identifier for the HL7 protocol.
105 """
106 def __init__(self, pid: str, id_type: str,
107 assigning_authority: str) -> None:
108 self.pid = pid
109 # ... called "pid" not "id" as type checker sometimes thinks "id" must
110 # be integer, as in Python's id(object).
111 self.id_type = id_type
112 self.assigning_authority = assigning_authority
115# =============================================================================
116# BarePatientInfo
117# =============================================================================
119class BarePatientInfo(object):
120 """
121 Represents information about a patient using a simple object with no
122 connection to a database.
124 In some situations we avoid using
125 :class:`camcops_server.cc_modules.cc_patient.Patient`: specifically, when
126 we would otherwise have to deal with mutual dependency problems and the use
127 of the database (prior to full database initialization).
128 """
129 def __init__(self,
130 forename: str = None,
131 surname: str = None,
132 sex: str = None,
133 dob: Date = None,
134 address: str = None,
135 email: str = None,
136 gp: str = None,
137 otherdetails: str = None,
138 idnum_definitions: List[IdNumReference] = None) -> None:
139 self.forename = forename
140 self.surname = surname
141 self.sex = sex
142 self.dob = dob
143 self.address = address
144 self.email = email
145 self.gp = gp
146 self.otherdetails = otherdetails
147 self.idnum_definitions = idnum_definitions or [] # type: List[IdNumReference] # noqa
149 def __str__(self) -> str:
150 return (
151 "Patient(forename={f!r}, surname={sur!r}, sex={sex!r}, DOB={dob}, "
152 "address={a!r}, email={email!r}, gp={gp!r}, otherdetails={o!r}, "
153 "idnums={i})".format(
154 f=self.forename,
155 sur=self.surname,
156 sex=self.sex,
157 dob=format_datetime(self.dob, DateFormat.ISO8601_DATE_ONLY),
158 a=self.address,
159 email=self.email,
160 gp=self.gp,
161 o=self.otherdetails,
162 i="[{}]".format(", ".join(
163 str(idnum) for idnum in self.idnum_definitions)),
164 )
165 )
167 def __repr__(self) -> str:
168 return auto_repr(self)
170 def add_idnum(self, idref: IdNumReference) -> None:
171 """
172 Adds an ID number. No checks in relation to what's already present.
174 Args:
175 idref: a :class:`IdNumReference`
176 """
177 self.idnum_definitions.append(idref)
179 def __eq__(self, other: "BarePatientInfo") -> bool:
180 """
181 Do all data elements match those of ``other``?
182 """
183 if not isinstance(other, BarePatientInfo):
184 return False
185 return (
186 self.forename == other.forename and
187 self.surname == other.surname and
188 self.sex == other.sex and
189 self.dob == other.dob and
190 self.address == other.address and
191 self.email == other.email and
192 self.gp == other.gp and
193 self.otherdetails == other.otherdetails and
194 self.idnum_definitions == other.idnum_definitions
195 )
198# =============================================================================
199# Raw XML value
200# =============================================================================
202class XmlSimpleValue(object):
203 """
204 Represents XML lowest-level items. See functions in ``cc_xml.py``.
205 """
206 def __init__(self, value) -> None:
207 self.value = value
210# =============================================================================
211# TaskExportOptions
212# =============================================================================
214class TaskExportOptions(object):
215 """
216 Information-holding object for options controlling XML and other
217 representations of tasks.
218 """
219 def __init__(self,
220 db_patient_id_per_row: bool = False,
221 db_make_all_tables_even_empty: bool = False,
222 db_include_summaries: bool = False,
223 include_blobs: bool = False,
224 xml_include_ancillary: bool = False,
225 xml_include_calculated: bool = False,
226 xml_include_comments: bool = True,
227 xml_include_patient: bool = False,
228 xml_include_plain_columns: bool = False,
229 xml_include_snomed: bool = False,
230 xml_skip_fields: List[str] = None,
231 xml_sort_by_name: bool = True,
232 xml_with_header_comments: bool = False) -> None:
233 """
234 Args:
235 db_patient_id_per_row:
236 generates an anonymisation staging database -- that is, a
237 database with patient IDs in every row of every table, suitable
238 for feeding into an anonymisation system like CRATE
239 (https://doi.org/10.1186%2Fs12911-017-0437-1).
240 db_make_all_tables_even_empty:
241 create all tables, even empty ones
243 include_blobs:
244 include binary large objects (BLOBs) (applies to several export
245 formats)
247 xml_include_ancillary:
248 include ancillary tables as well as the main?
249 xml_include_calculated:
250 include fields calculated by the task
251 xml_include_comments:
252 include comments in XML?
253 xml_include_patient:
254 include patient details?
255 xml_include_plain_columns:
256 include the base columns
257 xml_include_snomed:
258 include SNOMED-CT codes, if available?
259 xml_skip_fields:
260 fieldnames to skip
261 xml_sort_by_name:
262 sort by field/attribute names?
263 xml_with_header_comments:
264 include header-style comments?
265 """
266 self.db_patient_id_in_each_row = db_patient_id_per_row
267 self.db_make_all_tables_even_empty = db_make_all_tables_even_empty
268 self.db_include_summaries = db_include_summaries
270 self.include_blobs = include_blobs
272 self.xml_include_ancillary = xml_include_ancillary
273 self.xml_include_calculated = xml_include_calculated
274 self.xml_include_comments = xml_include_comments
275 self.xml_include_patient = xml_include_patient
276 self.xml_include_plain_columns = xml_include_plain_columns
277 self.xml_include_snomed = xml_include_snomed
278 self.xml_skip_fields = xml_skip_fields or [] # type: List[str]
279 self.xml_sort_by_name = xml_sort_by_name
280 self.xml_with_header_comments = xml_with_header_comments
282 def clone(self) -> "TaskExportOptions":
283 """
284 Returns a copy of this object.
285 """
286 return copy.copy(self)