Coverage for cc_modules/cc_constants.py : 98%

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_constants.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**Various constants.**
29"""
31# Helpful UTF-8 characters: ‘’ “” – — × • ≤ ≥ ≠ ± →
33import logging
34import multiprocessing
35import os
37from cardinal_pythonlib.randomness import create_base64encoded_randomness
38from cardinal_pythonlib.sqlalchemy.session import make_mysql_url
40from camcops_server.cc_modules.cc_baseconstants import (
41 DEFAULT_EXTRA_STRINGS_DIR,
42 LINUX_DEFAULT_LOCK_DIR,
43 LINUX_DEFAULT_USER_DOWNLOAD_DIR,
44 STATIC_ROOT_DIR,
45)
46from camcops_server.cc_modules.cc_language import DEFAULT_LOCALE
49# =============================================================================
50# Number of ID numbers. Don't alter this lightly; influences database fields.
51# =============================================================================
53NUMBER_OF_IDNUMS_DEFUNCT = 8 # DEFUNCT BUT DO NOT REMOVE OR ALTER. EIGHT.
54# ... In older versions: determined number of ID number fields.
55# (Now this is arbitrary.) Still used to support old clients.
58# =============================================================================
59# File types
60# =============================================================================
62class FileType(object):
63 """
64 Used to represent output formats and their file extensions.
65 """
66 HTML = "html"
67 PDF = "pdf"
68 XML = "xml"
71# =============================================================================
72# Launching
73# =============================================================================
75DEFAULT_FLOWER_ADDRESS = "127.0.0.1"
76DEFAULT_FLOWER_PORT = 5555 # http://docs.celeryproject.org/en/latest/userguide/monitoring.html # noqa
79# =============================================================================
80# Webview constants
81# =============================================================================
83DEFAULT_ROWS_PER_PAGE = 25
84DEVICE_NAME_FOR_SERVER = "server" # Do not alter.
85USER_NAME_FOR_SYSTEM = "system" # Do not alter.
87# Address to download Windows/Mac versions:
88GITHUB_RELEASES_URL = "https://github.com/RudolfCardinal/camcops/releases/"
90MINIMUM_PASSWORD_LENGTH = 10
93# =============================================================================
94# Date formats
95# =============================================================================
97class DateFormat(object):
98 """
99 Assorted date/time formats.
100 """
101 SHORT_DATE = "%d %b %Y" # e.g. 24 Jul 2013
102 LONG_DATE = "%d %B %Y" # e.g. 24 July 2013
103 LONG_DATE_WITH_DAY = "%a %d %B %Y" # e.g. Wed 24 July 2013
104 LONG_DATETIME = "%d %B %Y, %H:%M %z" # e.g. 24 July 2013, 20:04 +0100
105 LONG_DATETIME_WITH_DAY = "%a %d %B %Y, %H:%M %z" # e.g. Wed 24 July 2013, 20:04 +0100 # noqa
106 LONG_DATETIME_WITH_DAY_NO_TZ = "%a %d %B %Y, %H:%M" # e.g. Wed 24 July 2013, 20:04 # noqa
107 SHORT_DATETIME_WITH_DAY_NO_TZ = "%a %d %b %Y, %H:%M" # e.g. Wed 24 Jul 2013, 20:04 # noqa
108 LONG_DATETIME_SECONDS = "%d %B %Y, %H:%M:%S %z"
109 SHORT_DATETIME = "%d %b %Y, %H:%M %z" # e.g. 24 Jul 2013, 20:04 +0100
110 SHORT_DATETIME_NO_TZ = "%d %b %Y, %H:%M" # e.g. 24 Jul 2013, 20:04
111 SHORT_DATETIME_SECONDS = "%d %b %Y, %H:%M:%S %z" # e.g. 24 Jul 2013, 20:04:23 +0100 # noqa
112 HOURS_MINUTES = "%H:%M" # e.g. 20:04
113 ISO8601 = "%Y-%m-%dT%H:%M:%S%z" # e.g. 2013-07-24T20:04:07+0100
114 ISO8601_HUMANIZED_TO_MINUTES = "%Y-%m-%d %H:%M" # e.g. 2013-07-24 20:04
115 ISO8601_HUMANIZED_TO_SECONDS = "%Y-%m-%d %H:%M:%S" # e.g. 2013-07-24 20:04:23 # noqa
116 ISO8601_HUMANIZED_TO_SECONDS_TZ = "%Y-%m-%d %H:%M:%S %z" # e.g. 2013-07-24 20:04:23 +0100 # noqa
117 ISO8601_DATE_ONLY = "%Y-%m-%d" # e.g. 2013-07-24
118 FILENAME = "%Y-%m-%dT%H%M%S" # e.g. 2013-07-24T200459
119 FILENAME_DATE_ONLY = "%Y-%m-%d" # e.g. 20130724
120 HL7_DATETIME = "%Y%m%d%H%M%S%z" # e.g. 20130724200407+0100
121 HL7_DATE = "%Y%m%d" # e.g. 20130724
122 ERA = "%Y-%m-%dT%H:%M:%SZ" # e.g. 2013-07-24T20:03:07Z
123 # http://www.hl7standards.com/blog/2008/07/25/hl7-time-zone-qualification/
124 RIO_EXPORT_UK = "%d/%m/%Y %H:%M" # e.g. 01/12/2014 09:45
127# =============================================================================
128# Permitted values in fields: some common settings
129# =============================================================================
131class PV(object):
132 """
133 Collections of permitted values.
134 """
135 BIT = [0, 1]
138NO_CHAR = 'N'
139YES_CHAR = 'Y'
141# Database values:
142SEX_FEMALE = "F"
143SEX_MALE = "M"
144SEX_OTHER_UNSPECIFIED = "X"
145POSSIBLE_SEX_VALUES = [SEX_FEMALE, SEX_MALE, SEX_OTHER_UNSPECIFIED]
148# =============================================================================
149# Field names/specifications
150# =============================================================================
152TABLET_ID_FIELD = "id"
153MOVE_OFF_TABLET_FIELD = "_move_off_tablet"
154CLIENT_DATE_FIELD = "when_last_modified"
156# Used for old client support, and TSV field names etc.:
157FP_ID_NUM = "idnum"
158FP_ID_DESC = "iddesc"
159FP_ID_SHORT_DESC = "idshortdesc"
161# Additional fields for some exports:
162EXTRA_IDNUM_FIELD_PREFIX = "_patient_idnum"
163EXTRA_TASK_TABLENAME_FIELD = "_task_tablename"
164EXTRA_TASK_SERVER_PK_FIELD = "_task_pk"
165EXTRA_COMMENT_PREFIX = "(EXTRA) "
168# =============================================================================
169# Other special values
170# =============================================================================
172# CAMCOPS_URL = "http://www.camcops.org/"
173CAMCOPS_URL = "https://camcops.readthedocs.io/"
174ERA_NOW = "NOW" # defines the current era in database records
177# =============================================================================
178# PDF engine: now always "pdfkit".
179# =============================================================================
181# PDF_ENGINE = "xhtml2pdf" # working
182PDF_ENGINE = "pdfkit" # working
183# PDF_ENGINE = "weasyprint" # working but table <tr> element bugs
184# ... value must be one of: xhtml2pdf, weasyprint, pdfkit
187# =============================================================================
188# Simple constants for HTML/plots/display
189# =============================================================================
191class PlotDefaults(object):
192 """
193 Defaults used with matplotlib plotting.
194 """
195 DEFAULT_PLOT_DPI = 300
197 FULLWIDTH_PLOT_WIDTH = 6.7 # inches: full width is ~170mm
199 # zorder parameter:
200 # - higher = on top
201 # - defaults relate to the type of thing being plotted:
202 # https://matplotlib.org/3.1.1/gallery/misc/zorder_demo.html
203 # - Patch / PatchCollection = 1
204 # - Line2D / LineCollection = 2
205 # - Text = 3
206 # - within a Line2D object (points and lines), the default is
207 # "markers on top of lines"
208 ZORDER_PRESET_LINES = 1
209 ZORDER_PRESET_LABELS = 2
210 ZORDER_DATA_LINES_POINTS = 3 # the default
213class MatplotlibConstants(object):
214 """
215 Constants used by matplotlib
216 """
217 # https://matplotlib.org/tutorials/colors/colors.html
218 COLOUR_BLACK = "k"
219 COLOUR_BLUE = "b"
220 COLOUR_GREEN = "g"
221 COLOUR_GREY_50 = "0.5"
222 COLOUR_GREY_90 = "0.9" # 0.9 is close to white (0 black, 1 white)
223 COLOUR_RED = "r"
225 # https://matplotlib.org/gallery/lines_bars_and_markers/line_styles_reference.html # noqa
226 # https://matplotlib.org/3.1.0/gallery/lines_bars_and_markers/linestyles.html # noqa
227 LINESTYLE_DOTTED = ":"
228 LINESTYLE_SOLID = "-"
229 LINESTYLE_NONE = "None"
231 # https://matplotlib.org/3.1.1/api/markers_api.html
232 MARKER_CIRCLE = "o"
233 MARKER_NONE = "" # also "None", " "
234 MARKER_PLUS = "+"
235 MARKER_STAR = "*"
237 WHOLE_PANEL = 111 # as in: ax = fig.add_subplot(111)
240# Debugging option
241USE_SVG_IN_HTML = True # set to False for PNG debugging
244# =============================================================================
245# CSS/HTML constants
246# =============================================================================
248CSS_PAGED_MEDIA = (PDF_ENGINE != "pdfkit")
250WKHTMLTOPDF_OPTIONS = { # dict for pdfkit
251 "page-size": "A4",
252 "margin-left": "20mm",
253 "margin-right": "20mm",
254 "margin-top": "21mm", # from paper edge down to top of content?
255 # ... inaccurate
256 "margin-bottom": "24mm", # from paper edge up to bottom of content?
257 # ... inaccurate
258 "header-spacing": "3", # mm, from content up to bottom of header
259 "footer-spacing": "3", # mm, from content down to top of footer
260 "quiet": "", # Suppress "Loading pages (1/6)" etc.
261 "enable-local-file-access": "",
262}
265class CssClass(object):
266 """
267 CSS names.
269 Values should match e.g. ``camcops_server/templates/css/css_base.mako``.
270 """
271 BAD_ID_POLICY_MILD = "badidpolicy_mild"
272 BAD_ID_POLICY_SEVERE = "badidpolicy_severe"
273 BANNER = "banner"
274 BANNER_REFERRAL_GENERAL_ADULT = "banner_referral_general_adult"
275 BANNER_REFERRAL_OLD_AGE = "banner_referral_old_age"
276 BANNER_REFERRAL_SUBSTANCE_MISUSE = "banner_referral_substance_misuse"
277 CENTREGAP_TD = "centregap_td"
278 CLINICIAN = "clinician"
279 COPYRIGHT = "copyright"
280 CTV_DATELIMIT_START = "ctv_datelimit_start"
281 CTV_DATELIMIT_END = "ctv_datelimit_end"
282 CTV_TASKHEADING = "ctv_taskheading"
283 CTV_FIELDHEADING = "ctv_fieldheading"
284 CTV_FIELDSUBHEADING = "ctv_fieldsubheading"
285 CTV_FIELDDESCRIPTION = "ctv_fielddescription"
286 CTV_FIELDCONTENT = "ctv_fieldcontent"
287 CTV_WARNINGS = "ctv_warnings"
288 ERROR = "error"
289 EXPLANATION = "explanation"
290 EXTRADETAIL = "extradetail"
291 EXTRADETAIL2 = "extradetail2"
292 FILTER = "filter"
293 FILTERS = "filters"
294 FIGURE = "figure"
295 FOOTNOTES = "footnotes"
296 FORMTITLE = "formtitle"
297 GENERAL = "general"
298 GREEN = "green"
299 HANGINGINDENT = "hangingindent"
300 HEADING = "heading"
301 HIGHLIGHT = "highlight"
302 IMAGE_TD = "image_td"
303 IMPORTANT = "important"
304 INCOMPLETE = "incomplete"
305 INDENT = "indent"
306 INDENTED = "indented"
307 LIVE_ON_TABLET = "live_on_tablet"
308 LOGO_LEFT = "logo_left"
309 LOGO_RIGHT = "logo_right"
310 NAVIGATION = "navigation"
311 NOBORDER = "noborder"
312 NOBORDERPHOTO = "noborderphoto"
313 OFFICE = "office"
314 PATIENT = "patient"
315 PHOTO = "photo"
316 PDF_LOGO_HEADER = "pdf_logo_header"
317 QA_TABLE_HEADING = "qa_tableheading"
318 RESPONDENT = "respondent"
319 SIGNATURE = "signature"
320 SIGNATURE_LABEL = "signature_label"
321 SMALLPRINT = "smallprint"
322 SPECIALNOTE = "specialnote"
323 SUBHEADING = "subheading"
324 SUBSUBHEADING = "subsubheading"
325 SUMMARY = "summary"
326 SUPERUSER = "superuser"
327 TASKCONFIG = "taskconfig"
328 TASKDETAIL = "taskdetail"
329 TASKHEADER = "taskheader"
330 TRACKERHEADER = "trackerheader"
331 TRACKER_ALL_CONSISTENT = "tracker_all_consistent"
332 WARNING = "warning"
333 WEB_LOGO_HEADER = "web_logo_header"
336# =============================================================================
337# Task constants
338# =============================================================================
340ANON_PATIENT = "XXXX"
341DATA_COLLECTION_ONLY_DIV = """
342 <div class="copyright">
343 Reproduction of the original task/scale is not permitted.
344 This is a data collection tool only; use it only in conjunction with
345 a licensed copy of the original task.
346 </div>
347"""
348DATA_COLLECTION_UNLESS_UPGRADED_DIV = """
349 <div class="copyright">
350 Reproduction of the original task/scale is not permitted as part of
351 CamCOPS. This is a data collection tool only, unless the hosting
352 institution has supplied task text via its own permissions. <b>Any such
353 text, if shown here, is not part of CamCOPS, and copyright in
354 it belongs to the original task’s copyright holder.</b> Use this data
355 collection tool only in conjunction with a licensed copy of the
356 original task.
357 </div>
358"""
359ICD10_COPYRIGHT_DIV = """
360 <div class="copyright">
361 ICD-10 criteria: Copyright © 1992 World Health Organization.
362 Used here with permission.
363 </div>
364"""
365INVALID_VALUE = "[invalid_value]"
367TSV_PATIENT_FIELD_PREFIX = "_patient_"
369QUESTION = "Question"
372# =============================================================================
373# Config constants
374# =============================================================================
376CONFIG_FILE_SITE_SECTION = "site"
377CONFIG_FILE_SERVER_SECTION = "server"
378CONFIG_FILE_EXPORT_SECTION = "export"
381class ConfigParamSite(object):
382 """
383 Parameters allowed in the main ``[site]`` section of the CamCOPS config
384 file.
385 """
386 ALLOW_INSECURE_COOKIES = "ALLOW_INSECURE_COOKIES"
387 CAMCOPS_LOGO_FILE_ABSOLUTE = "CAMCOPS_LOGO_FILE_ABSOLUTE"
388 CLIENT_API_LOGLEVEL = "CLIENT_API_LOGLEVEL"
389 CTV_FILENAME_SPEC = "CTV_FILENAME_SPEC"
390 DB_URL = "DB_URL"
391 DB_ECHO = "DB_ECHO"
392 DISABLE_PASSWORD_AUTOCOMPLETE = "DISABLE_PASSWORD_AUTOCOMPLETE"
393 EMAIL_FROM = "EMAIL_FROM"
394 EMAIL_HOST = "EMAIL_HOST"
395 EMAIL_HOST_PASSWORD = "EMAIL_HOST_PASSWORD"
396 EMAIL_HOST_USERNAME = "EMAIL_HOST_USERNAME"
397 EMAIL_PORT = "EMAIL_PORT"
398 EMAIL_REPLY_TO = "EMAIL_REPLY_TO"
399 EMAIL_SENDER = "EMAIL_SENDER"
400 EMAIL_USE_TLS = "EMAIL_USE_TLS"
401 EXTRA_STRING_FILES = "EXTRA_STRING_FILES"
402 LANGUAGE = "LANGUAGE"
403 LOCAL_INSTITUTION_URL = "LOCAL_INSTITUTION_URL"
404 LOCAL_LOGO_FILE_ABSOLUTE = "LOCAL_LOGO_FILE_ABSOLUTE"
405 LOCKOUT_DURATION_INCREMENT_MINUTES = "LOCKOUT_DURATION_INCREMENT_MINUTES"
406 LOCKOUT_THRESHOLD = "LOCKOUT_THRESHOLD"
407 PASSWORD_CHANGE_FREQUENCY_DAYS = "PASSWORD_CHANGE_FREQUENCY_DAYS"
408 PATIENT_SPEC = "PATIENT_SPEC"
409 PATIENT_SPEC_IF_ANONYMOUS = "PATIENT_SPEC_IF_ANONYMOUS"
410 PERMIT_IMMEDIATE_DOWNLOADS = "PERMIT_IMMEDIATE_DOWNLOADS"
411 RESTRICTED_TASKS = "RESTRICTED_TASKS"
412 SESSION_COOKIE_SECRET = "SESSION_COOKIE_SECRET"
413 SESSION_TIMEOUT_MINUTES = "SESSION_TIMEOUT_MINUTES"
414 SNOMED_TASK_XML_FILENAME = "SNOMED_TASK_XML_FILENAME"
415 SNOMED_ICD9_XML_FILENAME = "SNOMED_ICD9_XML_FILENAME"
416 SNOMED_ICD10_XML_FILENAME = "SNOMED_ICD10_XML_FILENAME"
417 TASK_FILENAME_SPEC = "TASK_FILENAME_SPEC"
418 TRACKER_FILENAME_SPEC = "TRACKER_FILENAME_SPEC"
419 USER_DOWNLOAD_DIR = "USER_DOWNLOAD_DIR"
420 USER_DOWNLOAD_FILE_LIFETIME_MIN = "USER_DOWNLOAD_FILE_LIFETIME_MIN"
421 USER_DOWNLOAD_MAX_SPACE_MB = "USER_DOWNLOAD_MAX_SPACE_MB"
422 WEBVIEW_LOGLEVEL = "WEBVIEW_LOGLEVEL"
423 WKHTMLTOPDF_FILENAME = "WKHTMLTOPDF_FILENAME"
426class ConfigParamServer(object):
427 """
428 Parameters allowed in the web server (``[server]``) section of the CamCOPS
429 config file.
430 """
431 CHERRYPY_LOG_SCREEN = "CHERRYPY_LOG_SCREEN"
432 CHERRYPY_ROOT_PATH = "CHERRYPY_ROOT_PATH"
433 CHERRYPY_SERVER_NAME = "CHERRYPY_SERVER_NAME"
434 CHERRYPY_THREADS_MAX = "CHERRYPY_THREADS_MAX"
435 CHERRYPY_THREADS_START = "CHERRYPY_THREADS_START"
436 DEBUG_REVERSE_PROXY = "DEBUG_REVERSE_PROXY"
437 DEBUG_SHOW_GUNICORN_OPTIONS = "DEBUG_SHOW_GUNICORN_OPTIONS"
438 DEBUG_TOOLBAR = "DEBUG_TOOLBAR"
439 GUNICORN_DEBUG_RELOAD = "GUNICORN_DEBUG_RELOAD"
440 GUNICORN_NUM_WORKERS = "GUNICORN_NUM_WORKERS"
441 GUNICORN_TIMEOUT_S = "GUNICORN_TIMEOUT_S"
442 HOST = "HOST"
443 PORT = "PORT"
444 PROXY_HTTP_HOST = "PROXY_HTTP_HOST"
445 PROXY_REMOTE_ADDR = "PROXY_REMOTE_ADDR"
446 PROXY_REWRITE_PATH_INFO = "PROXY_REWRITE_PATH_INFO"
447 PROXY_SCRIPT_NAME = "PROXY_SCRIPT_NAME"
448 PROXY_SERVER_NAME = "PROXY_SERVER_NAME"
449 PROXY_SERVER_PORT = "PROXY_SERVER_PORT"
450 PROXY_URL_SCHEME = "PROXY_URL_SCHEME"
451 SHOW_REQUEST_IMMEDIATELY = "SHOW_REQUEST_IMMEDIATELY"
452 SHOW_REQUESTS = "SHOW_REQUESTS"
453 SHOW_RESPONSE = "SHOW_RESPONSE"
454 SHOW_TIMING = "SHOW_TIMING"
455 SSL_CERTIFICATE = "SSL_CERTIFICATE"
456 SSL_PRIVATE_KEY = "SSL_PRIVATE_KEY"
457 STATIC_CACHE_DURATION_S = "STATIC_CACHE_DURATION_S"
458 TRUSTED_PROXY_HEADERS = "TRUSTED_PROXY_HEADERS"
459 UNIX_DOMAIN_SOCKET = "UNIX_DOMAIN_SOCKET"
462class ConfigParamExportGeneral(object):
463 """
464 Parameters allowed in the ``[export]`` section of the CamCOPS config file.
465 """
466 CELERY_BEAT_EXTRA_ARGS = "CELERY_BEAT_EXTRA_ARGS"
467 CELERY_BEAT_SCHEDULE_DATABASE = "CELERY_BEAT_SCHEDULE_DATABASE"
468 CELERY_BROKER_URL = "CELERY_BROKER_URL"
469 CELERY_WORKER_EXTRA_ARGS = "CELERY_WORKER_EXTRA_ARGS"
470 CELERY_EXPORT_TASK_RATE_LIMIT = "CELERY_EXPORT_TASK_RATE_LIMIT"
471 EXPORT_LOCKDIR = "EXPORT_LOCKDIR"
472 RECIPIENTS = "RECIPIENTS"
473 SCHEDULE = "SCHEDULE"
474 SCHEDULE_TIMEZONE = "SCHEDULE_TIMEZONE"
477class ConfigParamExportRecipient(object):
478 """
479 Possible configuration file parameters that relate to "export recipient"
480 definitions.
481 """
482 ALL_GROUPS = "ALL_GROUPS"
483 DB_ADD_SUMMARIES = "DB_ADD_SUMMARIES"
484 DB_ECHO = "DB_ECHO"
485 DB_INCLUDE_BLOBS = "DB_INCLUDE_BLOBS"
486 DB_PATIENT_ID_PER_ROW = "DB_PATIENT_ID_PER_ROW"
487 DB_URL = "DB_URL"
488 EMAIL_BCC = "EMAIL_BCC"
489 EMAIL_BODY = "EMAIL_BODY"
490 EMAIL_BODY_IS_HTML = "EMAIL_BODY_IS_HTML"
491 EMAIL_CC = "EMAIL_CC"
492 EMAIL_KEEP_MESSAGE = "EMAIL_KEEP_MESSAGE"
493 EMAIL_RECIPIENTS = "EMAIL_RECIPIENTS"
494 EMAIL_PATIENT_SPEC = "EMAIL_PATIENT_SPEC"
495 EMAIL_PATIENT_SPEC_IF_ANONYMOUS = "EMAIL_PATIENT_SPEC_IF_ANONYMOUS"
496 EMAIL_SUBJECT = "EMAIL_SUBJECT"
497 EMAIL_TIMEOUT = "EMAIL_TIMEOUT"
498 EMAIL_TO = "EMAIL_TO"
499 END_DATETIME_UTC = "END_DATETIME_UTC"
500 FHIR_API_URL = "FHIR_API_URL"
501 FHIR_APP_SECRET = "FHIR_APP_SECRET"
502 FHIR_LAUNCH_TOKEN = "FHIR_LAUNCH_TOKEN"
503 FILE_EXPORT_RIO_METADATA = "FILE_EXPORT_RIO_METADATA"
504 FILE_FILENAME_SPEC = "FILE_FILENAME_SPEC"
505 FILE_MAKE_DIRECTORY = "FILE_MAKE_DIRECTORY"
506 FILE_OVERWRITE_FILES = "FILE_OVERWRITE_FILES"
507 FILE_PATIENT_SPEC = "FILE_PATIENT_SPEC"
508 FILE_PATIENT_SPEC_IF_ANONYMOUS = "FILE_PATIENT_SPEC_IF_ANONYMOUS"
509 FILE_SCRIPT_AFTER_EXPORT = "FILE_SCRIPT_AFTER_EXPORT"
510 FINALIZED_ONLY = "FINALIZED_ONLY"
511 GROUPS = "GROUPS"
512 HL7_DEBUG_DIVERT_TO_FILE = "HL7_DEBUG_DIVERT_TO_FILE"
513 HL7_DEBUG_TREAT_DIVERTED_AS_SENT = "HL7_DEBUG_TREAT_DIVERTED_AS_SENT"
514 HL7_HOST = "HL7_HOST"
515 HL7_KEEP_MESSAGE = "HL7_KEEP_MESSAGE"
516 HL7_KEEP_REPLY = "HL7_KEEP_REPLY"
517 HL7_NETWORK_TIMEOUT_MS = "HL7_NETWORK_TIMEOUT_MS"
518 HL7_PING_FIRST = "HL7_PING_FIRST"
519 HL7_PORT = "HL7_PORT"
520 IDNUM_AA_PREFIX = "IDNUM_AA_" # unusual; prefix not parameter
521 IDNUM_TYPE_PREFIX = "IDNUM_TYPE_" # unusual; prefix not parameter
522 INCLUDE_ANONYMOUS = "INCLUDE_ANONYMOUS"
523 PRIMARY_IDNUM = "PRIMARY_IDNUM"
524 PUSH = "PUSH"
525 REDCAP_API_KEY = "REDCAP_API_KEY"
526 REDCAP_API_URL = "REDCAP_API_URL"
527 REDCAP_FIELDMAP_FILENAME = "REDCAP_FIELDMAP_FILENAME"
528 REQUIRE_PRIMARY_IDNUM_MANDATORY_IN_POLICY = "REQUIRE_PRIMARY_IDNUM_MANDATORY_IN_POLICY" # noqa
529 RIO_DOCUMENT_TYPE = "RIO_DOCUMENT_TYPE"
530 RIO_IDNUM = "RIO_IDNUM"
531 RIO_UPLOADING_USER = "RIO_UPLOADING_USER"
532 START_DATETIME_UTC = "START_DATETIME_UTC"
533 TASK_FORMAT = "TASK_FORMAT"
534 TASKS = "TASKS"
535 TRANSMISSION_METHOD = "TRANSMISSION_METHOD"
536 XML_FIELD_COMMENTS = "XML_FIELD_COMMENTS"
539class StandardPorts(object):
540 """
541 Standard TCP port numbers.
542 """
543 ALTERNATIVE_HTTP = 8000
544 AMQP = 5672
545 SMTP = 25
546 SMTP_TLS = 587
547 HL7_MLLP = 2575
548 MYSQL = 3306
551class DockerConstants(object):
552 """
553 Constants for the Docker environment.
554 """
555 # Directories
556 DOCKER_CAMCOPS_ROOT_DIR = "/camcops"
557 CONFIG_DIR = os.path.join(DOCKER_CAMCOPS_ROOT_DIR, "cfg")
558 TMP_DIR = os.path.join(DOCKER_CAMCOPS_ROOT_DIR, "tmp")
559 VENV_DIR = os.path.join(DOCKER_CAMCOPS_ROOT_DIR, "venv")
561 DEFAULT_USER_DOWNLOAD_DIR = os.path.join(TMP_DIR, "user_downloads")
562 DEFAULT_LOCKDIR = os.path.join(TMP_DIR, "lock")
564 # Container (internal) names
565 CONTAINER_RABBITMQ = "rabbitmq"
566 CONTAINER_MYSQL = "mysql"
568 # Other
569 CELERY_BROKER_URL = f"amqp://{CONTAINER_RABBITMQ}:{StandardPorts.AMQP}/"
570 DEFAULT_MYSQL_CAMCOPS_USER = "camcops"
571 HOST = "0.0.0.0"
572 # ... not "localhost" or "127.0.0.1"; see
573 # https://nickjanetakis.com/blog/docker-tip-54-fixing-connection-reset-by-peer-or-similar-errors # noqa
576# =============================================================================
577# Configuration defaults
578# =============================================================================
580class ConfigDefaults(object):
581 """
582 Contains default values for the config, plus some cosmetic defaults for
583 generating specimen config files.
585 - Re ``CHERRYPY_THREADS_MAX``: beware the default MySQL connection limit of
586 151; https://dev.mysql.com/doc/refman/5.7/en/too-many-connections.html
587 """
588 # [site] section
589 ALLOW_INSECURE_COOKIES = False
590 CAMCOPS_LOGO_FILE_ABSOLUTE = os.path.join(STATIC_ROOT_DIR,
591 "logo_camcops.png")
592 CLIENT_API_LOGLEVEL = logging.INFO
593 CLIENT_API_LOGLEVEL_TEXTFORMAT = "info" # should match CLIENT_API_LOGLEVEL
594 DB_DATABASE = "camcops" # for demo configs only
595 DB_ECHO = False
596 DB_PORT = StandardPorts.MYSQL # for demo configs only
597 DB_SERVER = "localhost" # for demo configs only
598 DB_USER = "YYY_USERNAME_REPLACE_ME" # cosmetic; for demo configs only
599 DB_PASSWORD = "ZZZ_PASSWORD_REPLACE_ME" # cosmetic; for demo configs only
600 DISABLE_PASSWORD_AUTOCOMPLETE = True
601 EMAIL_PORT = StandardPorts.SMTP_TLS
602 EMAIL_USE_TLS = True
603 EXTRA_STRING_FILES = os.path.join(DEFAULT_EXTRA_STRINGS_DIR, "*.xml") # cosmetic; for demo configs only # noqa
604 LANGUAGE = DEFAULT_LOCALE
605 LOCAL_INSTITUTION_URL = "http://www.camcops.org/"
606 LOCAL_LOGO_FILE_ABSOLUTE = os.path.join(STATIC_ROOT_DIR, "logo_local.png")
607 LOCKOUT_DURATION_INCREMENT_MINUTES = 10
608 LOCKOUT_THRESHOLD = 10
609 PASSWORD_CHANGE_FREQUENCY_DAYS = 0 # zero for never
610 PATIENT_SPEC_IF_ANONYMOUS = "anonymous"
611 PERMIT_IMMEDIATE_DOWNLOADS = False
612 SESSION_TIMEOUT_MINUTES = 30
613 USER_DOWNLOAD_DIR = LINUX_DEFAULT_USER_DOWNLOAD_DIR # for demo configs only # noqa
614 USER_DOWNLOAD_FILE_LIFETIME_MIN = 60
615 USER_DOWNLOAD_MAX_SPACE_MB = 100
616 WEBVIEW_LOGLEVEL = logging.INFO
617 WEBVIEW_LOGLEVEL_TEXTFORMAT = "info" # should match WEBVIEW_LOGLEVEL
619 # Not yet user-configurable
620 PLOT_FONTSIZE = 8
622 # [server] section
623 CHERRYPY_LOG_SCREEN = True
624 CHERRYPY_ROOT_PATH = "/"
625 CHERRYPY_SERVER_NAME = "localhost"
626 CHERRYPY_THREADS_MAX = 100
627 CHERRYPY_THREADS_START = 10
628 DEBUG_REVERSE_PROXY = False
629 DEBUG_SHOW_GUNICORN_OPTIONS = False
630 DEBUG_TOOLBAR = False
631 GUNICORN_DEBUG_RELOAD = False
632 GUNICORN_NUM_WORKERS = 2 * multiprocessing.cpu_count()
633 GUNICORN_TIMEOUT_S = 30
634 HOST = "127.0.0.1"
635 PORT = StandardPorts.ALTERNATIVE_HTTP
636 PROXY_REWRITE_PATH_INFO = False
637 SHOW_REQUEST_IMMEDIATELY = False
638 SHOW_REQUESTS = False
639 SHOW_RESPONSE = False
640 SHOW_TIMING = False
641 STATIC_CACHE_DURATION_S = 1 * 24 * 60 * 60 # 1 day, in seconds = 86400
643 # [export] section
644 CELERY_BROKER_URL = "amqp://"
645 CELERY_BEAT_SCHEDULE_DATABASE = os.path.join(
646 LINUX_DEFAULT_LOCK_DIR, "camcops_celerybeat_schedule") # for demo configs only # noqa
647 EXPORT_LOCKDIR = LINUX_DEFAULT_LOCK_DIR # for demo configs only
648 SCHEDULE_TIMEZONE = "UTC"
650 # Individual export recipients
651 # DB_ECHO: as above
652 ALL_GROUPS = False
653 DB_ADD_SUMMARIES = True
654 DB_INCLUDE_BLOBS = True
655 DB_PATIENT_ID_PER_ROW = False
656 EMAIL_BODY_IS_HTML = False
657 EMAIL_KEEP_MESSAGE = False
658 FILE_EXPORT_RIO_METADATA = False
659 FILE_MAKE_DIRECTORY = False
660 FILE_OVERWRITE_FILES = False
661 FILE_PATIENT_SPEC_IF_ANONYMOUS = "anonymous"
662 FINALIZED_ONLY = True
663 HL7_DEBUG_DIVERT_TO_FILE = False
664 HL7_DEBUG_TREAT_DIVERTED_AS_SENT = False
665 HL7_KEEP_MESSAGE = False
666 HL7_KEEP_REPLY = False
667 HL7_NETWORK_TIMEOUT_MS = 10000
668 HL7_PING_FIRST = True
669 HL7_PORT = StandardPorts.HL7_MLLP
670 INCLUDE_ANONYMOUS = False
671 PUSH = False
672 REQUIRE_PRIMARY_IDNUM_MANDATORY_IN_POLICY = True
673 TASK_FORMAT = FileType.PDF
674 XML_FIELD_COMMENTS = True
676 def __init__(self, docker: bool = False) -> None:
677 """
678 Args:
679 docker:
680 Amend defaults so it works within a Docker Compose application
681 without much fiddling?
683 Defaults for use within Docker:
685 - Note that a URL to another container/service looks like
686 ``protocol://container:port/``. Values here must match the Docker
687 Compose file.
688 """
689 self._docker = docker
690 if docker:
691 self.CELERY_BROKER_URL = DockerConstants.CELERY_BROKER_URL
692 self.CELERY_BEAT_SCHEDULE_DATABASE = os.path.join(
693 DockerConstants.DEFAULT_LOCKDIR, "camcops_celerybeat_schedule")
694 self.DB_SERVER = DockerConstants.CONTAINER_MYSQL
695 self.DB_USER = DockerConstants.DEFAULT_MYSQL_CAMCOPS_USER
696 self.EXPORT_LOCKDIR = DockerConstants.DEFAULT_LOCKDIR
697 self.HOST = DockerConstants.HOST
698 self.USER_DOWNLOAD_DIR = DockerConstants.DEFAULT_USER_DOWNLOAD_DIR
700 @property
701 def demo_db_url(self) -> str:
702 """
703 The demonstration SQLAlchemy URL.
704 """
705 # mysqlclient ("mysqldb") for Docker -- the C-based fast one
706 # pymysql for standard installations -- fewer dependencies
707 driver = "mysqldb" if self._docker else "pymysql"
708 return make_mysql_url(driver=driver,
709 host=self.DB_SERVER,
710 port=self.DB_PORT,
711 username=self.DB_USER,
712 password=self.DB_PASSWORD,
713 dbname=self.DB_DATABASE)
716# =============================================================================
717# String length limits
718# =============================================================================
719#
720# Note: "191" relates to MySQL indexing of VARCHAR fields using utf8mb4;
721# - https://stackoverflow.com/questions/6172798/
722# - https://dev.mysql.com/doc/refman/5.7/en/innodb-restrictions.html
723# There are alternative workarounds, but these fields are OK at 191.
724#
725# Re commenting variables in Sphinx:
726# - https://stackoverflow.com/questions/20227051/how-to-document-a-module-constant-in-python # noqa
727# - http://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#directive-autodata # noqa
728# - URLs are a bit tricky; the colons get re-interpreted sometimes; it seems
729# that inserting an extra colon after "#:", e.g. "#: : see http://somewhere"
730# works.
731# - If a comment needs "# noqa" for the linter, then make it a docstring,
732# because it will appear in the Sphinx string.
735class StringLengths:
736 # -------------------------------------------------------------------------
737 # Primary
738 # -------------------------------------------------------------------------
739 AUDIT_SOURCE_MAX_LEN = 20 #: our choice based on use in CamCOPS code
741 #: : See https://docs.python.org/3.7/library/codecs.html#standard-encodings.
742 #: Probably ~18 so give it some headroom.
743 CHARSET_MAX_LEN = 64
745 CURRENCY_MAX_LEN = 3 #: Can have Unicode symbols like € or text like "GBP"
747 DATABASE_TITLE_MIN_LEN = 1
748 DATABASE_TITLE_MAX_LEN = 255 #: our choice
750 #: 191 is the maximum for MySQL + InnoDB + VARCHAR + utf8mb4 + index;
751 #: must be compatible with tablet
752 DEVICE_NAME_MAX_LEN = 191
754 #: : See https://en.wikipedia.org/wiki/Email_address.
755 EMAIL_ADDRESS_MAX_LEN = 255
757 #: 191 is the maximum for MySQL + InnoDB + VARCHAR + utf8mb4 + index
758 EXPORT_RECIPIENT_NAME_MIN_LEN = 1
759 EXPORT_RECIPIENT_NAME_MAX_LEN = 191
761 #: Our choice
762 FILTER_TEXT_MAX_LEN = 255
764 #: Our choice; used for user full names on the server
765 FULLNAME_MAX_LEN = 255
767 #: Our choice
768 FILESPEC_MAX_LEN = 255
770 GROUP_DESCRIPTION_MIN_LEN = 1
771 #: Our choice
772 GROUP_DESCRIPTION_MAX_LEN = 255
774 GROUP_NAME_MIN_LEN = 1
775 #: 191 is the maximum for MySQL + InnoDB + VARCHAR + utf8mb4 + index
776 GROUP_NAME_MAX_LEN = 191
778 HASHED_PW_MAX_LEN = 60
779 """
780 :
781 We use ``bcrypt``. Empirically, the length of its hashed output is:
783 .. code-block:: none
785 "$2a$" (4)
786 cost parameter, e.g. "$09" for 9 rounds (3)
787 b64-enc 128-bit salt (22)
788 b64enc 184-bit hash (31)
790 ... total 60
792 See https://stackoverflow.com/questions/5881169/what-column-type-length-should-i-use-for-storing-a-bcrypt-hashed-password-in-a-d
793 """ # noqa
795 HL7_AA_MAX_LEN = 20
796 """
797 - The AA appears in Table 4.6 "Extended composite ID", p46-47 of
798 hl7guide-1-4-2012-08.pdf
799 - ... but is defined in Table 4.9 "Entity Identifier", p50, in which:
801 - component 2 is the Assigning Authority (see component 1)
802 - component 2 is also a Namespace ID with a length of 20
804 - ... and multiple other examples of an Assigning Authority being one
805 example of a Namespace ID
807 - ... and examples are in Table 0363 (p229 of the PDF), which are all
808 3-char.
810 - ... and several other examples of "Namespace ID" being of length 1..20
811 meaning 1-20.
812 """
814 HL7_ID_TYPE_MAX_LEN = 5
815 """
816 Table 4.6 "Extended composite ID", p46-47 of hl7guide-1-4-2012-08.pdf,
817 and Table 0203 "Identifier type", p204 of that PDF, in Appendix B.
818 """
820 HOSTNAME_MAX_LEN = 255
821 """
822 FQDN; see
823 https://stackoverflow.com/questions/8724954/what-is-the-maximum-number-of-characters-for-a-host-name-in-unix
824 """ # noqa
826 ICD9_CODE_MAX_LEN = 6
827 """
828 Longest is "xxx.xx"; thus, 6; see
829 https://www.cms.gov/Medicare/Quality-Initiatives-Patient-Assessment-Instruments/HospitalQualityInits/Downloads/HospitalAppendix_F.pdf
830 """ # noqa
832 #: longest is e.g. "F00.000"; "F10.202"; thus, 7
833 ICD10_CODE_MAX_LEN = 7
835 #: Our choice
836 ID_DESCRIPTOR_MAX_LEN = 255
838 #: Our choice
839 ID_POLICY_MAX_LEN = 255
841 #: : See http://stackoverflow.com/questions/166132
842 IP_ADDRESS_MAX_LEN = 45
844 ISO8601_DATETIME_STRING_MAX_LEN = 32
845 """
846 Max length e.g.
848 .. code-block:: none
850 2013-07-24T20:04:07.123456+01:00
851 1234567890123456789012345678901234567890
853 (with punctuation, T, microseconds, colon in timezone).
854 """
856 #: See :func:`cardinal_pythonlib.datetimefunc.duration_to_iso`
857 ISO8601_DURATION_STRING_MAX_LEN = 29
859 #: Two-letter language, hyphen, 2/3-letter country
860 LANGUAGE_CODE_MAX_LEN = 6
862 # LONGBLOB_LONGTEXT_MAX_LEN = (2 ** 32) - 1
863 # ... https://dev.mysql.com/doc/refman/8.0/en/storage-requirements.html
865 #: See https://stackoverflow.com/questions/643690
866 MIMETYPE_MAX_LEN = 255
868 #: For forename and surname, each; our choice but must match tablet
869 PATIENT_NAME_MAX_LEN = 255
871 RFC_2822_DATE_MAX_LEN = 31
872 """
873 e.g. ``Fri, 09 Nov 2001 01:08:47 -0000``; 3.3 in
874 https://tools.ietf.org/html/rfc2822, assuming extra white space not added
875 """
877 #: for export; our choice based on use in CamCOPS code
878 SENDING_FORMAT_MAX_LEN = 50
880 #: our choice; 64 bytes => 512 bits, which is a lot in 2017
881 SESSION_TOKEN_MAX_BYTES = 64
883 SQL_SEARCH_LITERAL_MIN_LENGTH = 0 # permits: LIKE ''
884 SQL_SEARCH_LITERAL_MAX_LENGTH = 255 # arbitrary
886 TABLENAME_MAX_LEN = 128
887 """
888 For
890 - MySQL: 64 -- https://dev.mysql.com/doc/refman/5.7/en/identifiers.html
891 - SQL Server: 128 -- https://msdn.microsoft.com/en-us/library/ms191240.aspx
892 - Oracle: 32, then 128 from v12.2 (2017)
893 """
895 TASK_SUMMARY_TEXT_FIELD_DEFAULT_MAX_LEN = 50
896 """
897 ... our choice, contains short strings like "normal", "abnormal", "severe".
898 Easy to change, since it's only used when exporting summaries, and not in
899 the core database.
900 """
902 #: Our choice
903 URL_MAX_LEN = 255
905 USERNAME_CAMCOPS_MIN_LEN = 1
906 #: 191 is the maximum for MySQL + InnoDB + VARCHAR + utf8mb4 + index
907 USERNAME_CAMCOPS_MAX_LEN = 191
909 #: Our choice
910 USERNAME_EXTERNAL_MAX_LEN = 255
912 # -------------------------------------------------------------------------
913 # Derived
914 # -------------------------------------------------------------------------
916 DIAGNOSTIC_CODE_MAX_LEN = max(ICD9_CODE_MAX_LEN, ICD10_CODE_MAX_LEN)
918 SESSION_TOKEN_MAX_LEN = len(
919 create_base64encoded_randomness(SESSION_TOKEN_MAX_BYTES))