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