Coverage for common/constants.py: 87%
159 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-08-27 10:34 -0500
« prev ^ index » next coverage.py v7.8.0, created at 2025-08-27 10:34 -0500
1"""
2crate_anon/common/constants.py
4===============================================================================
6 Copyright (C) 2015, University of Cambridge, Department of Psychiatry.
7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
9 This file is part of CRATE.
11 CRATE is free software: you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation, either version 3 of the License, or
14 (at your option) any later version.
16 CRATE is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
21 You should have received a copy of the GNU General Public License
22 along with CRATE. If not, see <https://www.gnu.org/licenses/>.
24===============================================================================
26**Constants used throughout CRATE.**
28"""
30import os
33# =============================================================================
34# Plain constants
35# =============================================================================
37CRATE_DOCS_URL = "https://crateanon.readthedocs.io/"
39DEMO_NLP_INPUT_TERMINATOR = "STOP"
40DEMO_NLP_OUTPUT_TERMINATOR = "END_OF_NLP_OUTPUT_RECORD"
42EXIT_FAILURE = 1
43EXIT_SUCCESS = 0
45JSON_INDENT = 4
47JSON_SEPARATORS_COMPACT = (",", ":")
48# ... see https://docs.python.org/3/library/json.html
50LOWER_CASE_STRINGS_MEANING_TRUE = ["true", "1", "t", "y", "yes"]
52# Is this program running on readthedocs.org?
53ON_READTHEDOCS = os.environ.get("READTHEDOCS") == "True"
55NoneType = type(None) # for isinstance, sometimes
58# =============================================================================
59# Constant creation
60# =============================================================================
63def mebibytes(mb: int) -> int:
64 """
65 Takes a parameter in mebibytes (1024^2 bytes) and returns the number of
66 bytes.
67 """
68 return mb * 1024 * 1024
71# =============================================================================
72# Directories within CRATE
73# =============================================================================
76class CratePath:
77 """
78 Directories within the CRATE Python package.
79 """
81 CRATE_ANON_DIR = os.path.abspath(
82 os.path.join(
83 os.path.dirname(
84 os.path.abspath(__file__)
85 ), # this directory, common
86 os.pardir, # parent, crate_anon
87 )
88 )
89 CRATEWEB_DIR = os.path.join(CRATE_ANON_DIR, "crateweb")
90 STATIC_SRC_DIR = os.path.join(CRATEWEB_DIR, "static")
91 NLP_MANAGER_DIR = os.path.join(CRATE_ANON_DIR, "nlp_manager")
92 JAVA_CLASSES_DIR = os.path.join(NLP_MANAGER_DIR, "compiled_nlp_classes")
93 NLPRP_DIR = os.path.join(CRATE_ANON_DIR, "nlprp")
96# =============================================================================
97# DockerConstants
98# =============================================================================
101class DockerConstants:
102 """
103 Constants for the Docker environment.
104 """
106 # Directories
107 DOCKER_CRATE_ROOT_DIR = "/crate"
108 CONFIG_DIR = os.path.join(DOCKER_CRATE_ROOT_DIR, "cfg")
109 FILES_DIR = os.path.join(DOCKER_CRATE_ROOT_DIR, "files")
110 STATIC_DIR = os.path.join(DOCKER_CRATE_ROOT_DIR, "static")
111 TMP_DIR = os.path.join(DOCKER_CRATE_ROOT_DIR, "tmp")
112 VENV_DIR = os.path.join(DOCKER_CRATE_ROOT_DIR, "venv")
114 HOST = "0.0.0.0"
115 # ... not "localhost" or "127.0.0.1"; see
116 # https://nickjanetakis.com/blog/docker-tip-54-fixing-connection-reset-by-peer-or-similar-errors # noqa: E501
119# =============================================================================
120# Environment variables
121# =============================================================================
124class EnvVar:
125 """
126 Environment variable names.
127 """
129 CRATE_GATE_PLUGIN_FILE = "CRATE_GATE_PLUGIN_FILE"
130 # ... environment variable whose presence shows that we are generating
131 # docs.
132 GATE_HOME = "GATE_HOME"
133 GENERATING_CRATE_DOCS = "GENERATING_CRATE_DOCS"
134 JAVA_HOME = "JAVA_HOME"
135 KCL_KCONNECT_DIR = "KCL_KCONNECT_DIR"
136 KCL_LEWY_BODY_DIAGNOSIS_DIR = "KCL_LEWY_BODY_DIAGNOSIS_DIR"
137 KCL_PHARMACOTHERAPY_DIR = "KCL_PHARMACOTHERAPY_DIR"
138 MEDEX_HOME = "MEDEX_HOME"
139 PATH = "PATH"
140 RUNNING_TESTS = "RUNNING_TESTS"
141 RUN_WITHOUT_CONFIG = "CRATE_RUN_WITHOUT_LOCAL_SETTINGS"
144# =============================================================================
145# CRATE top-level commands
146# =============================================================================
149class CrateCommand:
150 """
151 Top-level commands within CRATE, recorded here to ensure consistency.
152 However, see also crate/installer/installer.py, which duplicates some
153 (because the full Python environment is not then available).
154 """
156 # Preprocessing
157 AUTOIMPORTDB = "crate_autoimport_db"
158 FETCH_WORDLISTS = "crate_fetch_wordlists"
159 POSTCODES = "crate_postcodes"
160 PREPROCESS_PCMIS = "crate_preprocess_pcmis"
161 PREPROCESS_RIO = "crate_preprocess_rio"
162 PREPROCESS_SYSTMONE = "crate_preprocess_systmone"
164 # Linkage
165 BULK_HASH = "crate_bulk_hash"
166 FUZZY_ID_MATCH = "crate_fuzzy_id_match"
168 # Anonymisation
169 ANON_CHECK_TEXT_EXTRACTOR = "crate_anon_check_text_extractor"
170 ANON_DEMO_CONFIG = "crate_anon_demo_config"
171 ANON_DRAFT_DD = "crate_anon_draft_dd"
172 ANON_SHOW_COUNTS = "crate_anon_show_counts"
173 ANON_SUMMARIZE_DD = "crate_anon_summarize_dd"
174 ANONYMISE = "crate_anonymise"
175 ANONYMISE_MULTIPROCESS = "crate_anonymise_multiprocess"
176 MAKE_DEMO_DATABASE = "crate_make_demo_database"
177 RESEARCHER_REPORT = "crate_researcher_report"
178 SUBSET_DB = "crate_subset_db"
179 TEST_ANONYMISATION = "crate_test_anonymisation"
180 TEST_EXTRACT_TEXT = "crate_test_extract_text"
182 # NLP
183 NLP = "crate_nlp"
184 NLP_BUILD_GATE_JAVA_INTERFACE = "crate_nlp_build_gate_java_interface"
185 NLP_BUILD_MEDEX_ITSELF = "crate_nlp_build_medex_itself"
186 NLP_BUILD_MEDEX_JAVA_INTERFACE = "crate_nlp_build_medex_java_interface"
187 NLP_MULTIPROCESS = "crate_nlp_multiprocess"
188 NLP_PREPARE_YMLS_FOR_BIOYODIE = "crate_nlp_prepare_ymls_for_bioyodie"
189 NLP_WRITE_GATE_AUTO_INSTALL_XML = "crate_nlp_write_gate_auto_install_xml"
190 RUN_CRATE_NLP_DEMO = "crate_run_crate_nlp_demo"
191 RUN_GATE_ANNIE_DEMO = "crate_run_gate_annie_demo"
192 RUN_GATE_KCL_KCONNECT_DEMO = "crate_run_gate_kcl_kconnect_demo"
193 RUN_GATE_KCL_LEWY_DEMO = "crate_run_gate_kcl_lewy_demo"
194 RUN_GATE_KCL_PHARMACOTHERAPY_DEMO = (
195 "crate_run_gate_kcl_pharmacotherapy_demo"
196 )
197 SHOW_CRATE_GATE_PIPELINE_OPTIONS = "crate_show_crate_gate_pipeline_options"
198 SHOW_CRATE_MEDEX_PIPELINE_OPTIONS = (
199 "crate_show_crate_medex_pipeline_options"
200 )
202 # Web site
203 CELERY_STATUS = "crate_celery_status"
204 DJANGO_MANAGE = "crate_django_manage"
205 EMAIL_RDBM = "crate_email_rdbm"
206 GENERATE_NEW_DJANGO_SECRET_KEY = "crate_generate_new_django_secret_key"
207 LAUNCH_CELERY = "crate_launch_celery"
208 LAUNCH_FLOWER = "crate_launch_flower"
209 PRINT_DEMO_CRATEWEB_CONFIG = "crate_print_demo_crateweb_config"
210 WINDOWS_SERVICE = "crate_windows_service"
211 LAUNCH_CHERRYPY_SERVER = "crate_launch_cherrypy_server"
212 LAUNCH_DJANGO_SERVER = "crate_launch_django_server"
214 # NLP web server
215 NLP_WEBSERVER_GENERATE_ENCRYPTION_KEY = (
216 "crate_nlp_webserver_generate_encryption_key"
217 )
218 NLP_WEBSERVER_INITIALIZE_DB = "crate_nlp_webserver_initialize_db"
219 NLP_WEBSERVER_LAUNCH_CELERY = "crate_nlp_webserver_launch_celery"
220 NLP_WEBSERVER_LAUNCH_FLOWER = "crate_nlp_webserver_launch_flower"
221 NLP_WEBSERVER_LAUNCH_GUNICORN = "crate_nlp_webserver_launch_gunicorn"
222 NLP_WEBSERVER_MANAGE_USERS = "crate_nlp_webserver_manage_users"
223 NLP_WEBSERVER_PRINT_DEMO = "crate_nlp_webserver_print_demo"
224 NLP_WEBSERVER_PSERVE = "crate_nlp_webserver_pserve"
226 # Testing
227 TEST_DATABASE_CONNECTION = "crate_test_database_connection"
230# =============================================================================
231# HelpUrl
232# =============================================================================
235class HelpUrl:
236 """
237 Makes help URLs, for an approximation to context-sensitive help within the
238 web site.
240 Note that in Django's template syntax,
242 .. code-block:: none
244 {{ HelpUrl.main }}
246 gets translated to
248 .. code-block:: python
250 HelpUrl.main()
252 i.e. further brackets are unnecessary (and an error). See:
254 - https://docs.djangoproject.com/en/2.2/topics/templates/#variables
256 ... "If a variable resolves to a callable, the template system will call it
257 with no arguments and use its result instead of the callable."
259 """
261 @staticmethod
262 def make_url(
263 location: str, language: str = "en", version: str = "latest"
264 ) -> str:
265 """
266 Make a CRATE help URL.
268 Args:
269 location: location within docs
270 language: language (default ``en``)
271 version: version (default ``latest``)
272 """
273 return f"{CRATE_DOCS_URL}{language}/{version}/{location}"
275 @classmethod
276 def main(cls) -> str:
277 return cls.make_url("")
279 @classmethod
280 def website(cls) -> str:
281 return cls.make_url("website_using/index.html")
283 @classmethod
284 def find_text_anywhere(cls) -> str:
285 return cls.make_url(
286 "website_using/clinician_privileged.html#clinician-privileged-find-text-anywhere" # noqa: E501
287 )
289 @classmethod
290 def clinician_lookup_rid(cls) -> str:
291 return cls.make_url(
292 "website_using/clinician_privileged.html#look-up-research-id-from-patient-id" # noqa: E501
293 )
295 @classmethod
296 def clinician_submit_contact_request(cls) -> str:
297 return cls.make_url(
298 "website_using/clinician_privileged.html#submit-patient-contact-request" # noqa: E501
299 )
301 @classmethod
302 def querybuilder(cls) -> str:
303 return cls.make_url(
304 "website_using/research_queries.html#query-builder"
305 )
307 @classmethod
308 def sql(cls) -> str:
309 return cls.make_url(
310 "website_using/research_queries.html#research-query-sql"
311 )
313 @classmethod
314 def highlighting(cls) -> str:
315 return cls.make_url(
316 "website_using/research_queries.html#highlighting-text-in-results"
317 )
319 @classmethod
320 def results(cls) -> str:
321 return cls.make_url(
322 "website_using/research_queries.html#results-table-view"
323 )
325 @classmethod
326 def patient_explorer(cls) -> str:
327 return cls.make_url("website_using/patient_explorer.html")
329 @classmethod
330 def sqlhelper_find_text_anywhere(cls) -> str:
331 return cls.make_url(
332 "website_using/sql_helpers.html#find-text-anywhere"
333 )
335 @classmethod
336 def sqlhelper_find_drugs_anywhere(cls) -> str:
337 return cls.make_url(
338 "website_using/sql_helpers.html#find-drugs-of-a-given-type-anywhere" # noqa: E501
339 )
341 @classmethod
342 def sitewide_queries(cls) -> str:
343 return cls.make_url("website_using/site_queries.html")
345 @classmethod
346 def research_db_structure(cls) -> str:
347 return cls.make_url("website_using/database_structure.html")
349 @classmethod
350 def submit_contact_request(cls) -> str:
351 return cls.make_url(
352 "website_using/contact_patients.html#submit-a-contact-request"
353 )
355 @classmethod
356 def rdbm(cls) -> str:
357 return cls.make_url("website_using/rdbm_admin.html")
359 @classmethod
360 def developer(cls) -> str:
361 return cls.make_url("website_using/developer_admin.html")
363 @classmethod
364 def user_settings(cls) -> str:
365 return cls.make_url(
366 "website_using/clinician_researcher_overview.html#your-settings"
367 )
369 @classmethod
370 def about_crate(cls) -> str:
371 return cls.make_url(
372 "website_using/clinician_researcher_overview.html#about-crate"
373 )
375 @classmethod
376 def archive(cls) -> str:
377 return cls.make_url("website_using/archive.html")
380# =============================================================================
381# More plain constants
382# =============================================================================
384# Will we run without a config file?
385RUNNING_WITHOUT_CONFIG = ON_READTHEDOCS or (
386 EnvVar.RUN_WITHOUT_CONFIG in os.environ
387 and os.environ[EnvVar.RUN_WITHOUT_CONFIG].lower()
388 in LOWER_CASE_STRINGS_MEANING_TRUE
389)