Source code for crate_anon.tools.print_crateweb_demo_config

#!/usr/bin/env python

"""
crate_anon/tools/print_crateweb_demo_config.py

===============================================================================

    Copyright (C) 2015, University of Cambridge, Department of Psychiatry.
    Created by Rudolf Cardinal (rnc1001@cam.ac.uk).

    This file is part of CRATE.

    CRATE is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    CRATE is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with CRATE. If not, see <https://www.gnu.org/licenses/>.

===============================================================================

**Print a demonstration CRATE web (Django) config file.**

"""
import argparse
import pprint
import re
import sys
from typing import Dict

from rich_argparse import RichHelpFormatter

from crate_anon.common.constants import EXIT_FAILURE

DEMO_CONFIG = r"""
# **Site-specific Django settings for CRATE web front end.**

# Put the secret stuff here.

# SPECIMEN FILE ONLY - edit to your own requirements.
# IT WILL NOT WORK until you've edited it.

# For help, see
# https://crateanon.readthedocs.io/en/latest/website_config/web_config_file.html

import logging
import os
import shutil
from typing import List, TYPE_CHECKING

# Include the following if you want to use it in CELERYBEAT_SCHEDULE
# from celery.schedules import crontab

from crate_anon.common.constants import mebibytes
from crate_anon.crateweb.config.constants import ResearchDbInfoKeys as RDIKeys
from crate_anon.crateweb.consent.constants import CPFTEthics2022

if TYPE_CHECKING:
    from django.http.request import HttpRequest

log = logging.getLogger(__name__)

log.critical(
    "Well done - CRATE has found your crate_local_settings.py file at {}. "
    "However, you need to configure it for your institution's set-up, and "
    "remove this line.".format(os.path.abspath(__file__))
)


# =============================================================================
# Site URL configuration
# =============================================================================
# See https://crateanon.readthedocs.io/en/latest/website_config/web_config_file.html  # noqa

# DJANGO_SITE_ROOT_ABSOLUTE_URL = "http://mymachine.mydomain"  # example for Apache  # noqa
# DJANGO_SITE_ROOT_ABSOLUTE_URL = "http://localhost:8000"  # for the Django dev server  # noqa
DJANGO_SITE_ROOT_ABSOLUTE_URL = "@@django_site_root_absolute_url@@"

FORCE_SCRIPT_NAME = "@@force_script_name@@"
# FORCE_SCRIPT_NAME = ""  # example for Apache root hosting
# FORCE_SCRIPT_NAME = "/crate"  # example for CherryPy or Apache non-root hosting  # noqa


# =============================================================================
# Site security
# =============================================================================
# See https://crateanon.readthedocs.io/en/latest/website_config/web_config_file.html  # noqa

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "@@secret_key@@"
# Run crate_generate_new_django_secret_key to generate a new one.

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
# ... when False, note that static files must be served properly

CRATE_HTTPS = @@crate_https@@  # True: require HTTPS and disallow plain HTTP


# noinspection PyUnusedLocal
def always_show_toolbar(request: "HttpRequest") -> bool:
    return True  # Always show toolbar, for debugging only.


if DEBUG:
    ALLOWED_HOSTS = []  # type: List[str]
    DEBUG_TOOLBAR_CONFIG = {
        "SHOW_TOOLBAR_CALLBACK": always_show_toolbar,
    }
else:
    ALLOWED_HOSTS = ["*"]


# =============================================================================
# Celery configuration
# =============================================================================
# See https://crateanon.readthedocs.io/en/latest/website_config/web_config_file.html  # noqa

BROKER_URL = "@@broker_url@@"

# =============================================================================
# Database configuration
# =============================================================================
# See https://crateanon.readthedocs.io/en/latest/website_config/web_config_file.html  # noqa

DATABASES = {
    # See https://docs.djangoproject.com/en/1.8/ref/settings/#databases
    # -------------------------------------------------------------------------
    # Django database for web site (inc. users, audit).
    # -------------------------------------------------------------------------
    # Quick SQLite example:
    # 'default': {
    #     'ENGINE': 'django.db.backends.sqlite3',
    #     'NAME': '/home/myuser/somewhere/crate_db.sqlite3',
    # },
    # Quick MySQL example:
    "default": {
        "ENGINE": "@@crate_db_engine@@",
        "HOST": "@@crate_db_host@@",  # e.g. 127.0.0.1
        "PORT": "@@crate_db_port@@",  # local e.g. 3306
        "NAME": "@@crate_db_name@@",
        "USER": "@@crate_db_user@@",
        "PASSWORD": "@@crate_db_password@@",
    },
    # -------------------------------------------------------------------------
    # Anonymised research database
    # -------------------------------------------------------------------------
    "research": {
        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        # IT IS CRITICALLY IMPORTANT THAT THIS CONNECTION (i.e. its user's
        # access) IS READ-ONLY FOR THE RESEARCH DATABASES [1] AND HAS NO
        # ACCESS WHATSOEVER TO SECRET DATABASES (like the 'default' or
        # 'secret' databases) [2]. RESEARCHERS ARE GIVEN FULL ABILITY TO
        # EXECUTE SQL VIA THIS CONNECTION, AND CAN DO SO FOR ANY DATABASES
        # THAT THE CONNECTION PERMITS, NOT JUST THE ONE YOU SPECIFY
        # EXPLICITLY.
        #
        # [1] ... so researchers can't alter/delete research data
        # [2] ... so researchers can't see secrets
        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        "ENGINE": "@@dest_db_engine@@",
        "HOST": "@@dest_db_host@@",  # e.g. 127.0.0.1
        "PORT": "@@dest_db_port@@",  # local, e.g. 3306
        "NAME": "@@dest_db_name@@",  # will be the default database; use None for no default database  # noqa
        "USER": "@@dest_db_user@@",
        "PASSWORD": "@@dest_db_password@@",
    },
    # -------------------------------------------------------------------------
    # One or more secret databases for RID/PID mapping
    # -------------------------------------------------------------------------
    "secret_1": {
        "ENGINE": "@@secret_db1_engine@@",
        "HOST": "@@secret_db1_host@@",  # e.g. 127.0.0.1
        "PORT": "@@secret_db1_port@@",
        "NAME": "@@secret_db1_name@@",
        "USER": "@@secret_db1_user@@",
        "PASSWORD": "@@secret_db1_password@@",
    },
    # -------------------------------------------------------------------------
    # Others, for consent lookup
    # -------------------------------------------------------------------------
    # Optional: 'cpft_crs'
    # Optional: 'cpft_pcmis'
    # Optional: 'cpft_rio_crate'
    # Optional: 'cpft_rio_datamart'
    # Optional: 'cpft_rio_raw'
    # Optional: 'cpft_rio_rcep'
    # Optional: 'cpft_systmone'
    # ... see ClinicalDatabaseType in crate_anon/crateweb/config/constants.py
}

# Which database should be used to look up demographic details?
CLINICAL_LOOKUP_DB = "dummy_clinical"

# Which database should be used to look up consent modes?
CLINICAL_LOOKUP_CONSENT_DB = "dummy_clinical"

# Research database title (displayed in web site)
RESEARCH_DB_TITLE = "My NHS Trust Research Database"

# Database structure information for CRATE's query builders.
# See https://crateanon.readthedocs.io/en/latest/website_config/web_config_file.html  # noqa
RESEARCH_DB_INFO = [
    {
        # Unique name e.g. "myresearchdb":
        RDIKeys.NAME: "@@rdi1_name@@",
        # Human-friendly description e.g. "My friendly research database":
        RDIKeys.DESCRIPTION: "@@rdi1_description@@",
        # Database name as seen by the database engine:
        # - BLANK, i.e. "", for MySQL.
        # - BLANK, i.e. "", for PostgreSQL.
        # - The database name, for SQL Server.
        RDIKeys.DATABASE: "@@rdi1_database@@",
        # Schema name:
        # - The database=schema name, for MySQL.
        # - The schema name, for PostgreSQL (usual default: "public").
        # - The schema name, for SQL Server (usual default: "dbo").
        RDIKeys.SCHEMA: "@@rdi1_schema@@",
        # Fields not in the database, but used for SELECT AS statements for
        # some clinician views:
        # e.g. "my_pid_field":
        RDIKeys.PID_PSEUDO_FIELD: "@@rdi1_pid_psuedo_field@@",
        # e.g. "my_mpid_field":
        RDIKeys.MPID_PSEUDO_FIELD: "@@rdi1_mpid_pseudo_field@@",
        # Fields and tables found within the database:
        # e.g. "trid"
        RDIKeys.TRID_FIELD: "@@rdi1_trid_field@@",
        # e.g. "brcid"
        RDIKeys.RID_FIELD: "@@rdi1_rid_field@@",
        # e.g. 1
        RDIKeys.RID_FAMILY: @@rdi1_rid_family@@,
        # e.g. "patients"
        RDIKeys.MRID_TABLE: "@@rdi1_mrid_table@@",
        # e.g. "nhshash"
        RDIKeys.MRID_FIELD: "@@rdi1_mrid_field@@",
        # Descriptions, used for PID lookup and the like
        # e.g. "Patient ID (My ID Num; PID) for database X"
        RDIKeys.PID_DESCRIPTION: "@@rdi1_pid_description@@",
        # e.g. "Master patient ID (NHS number; MPID)"
        RDIKeys.MPID_DESCRIPTION: "@@rdi1_mpid_description@@",
        # e.g. "Research ID (RID) for database X"
        RDIKeys.RID_DESCRIPTION: "@@rdi1_rid_description@@",
        # e.g. "Master research ID (MRID)"
        RDIKeys.MRID_DESCRIPTION: "@@rdi1_mrid_description@@",
        # e.g. "Transient research ID (TRID) for database X",
        RDIKeys.TRID_DESCRIPTION: (
            "@@rdi1_trid_description@@"
        ),
        # To look up PID/RID mappings, provide a key for "secret_lookup_db"
        # that is a database alias from DATABASES:
        # e.g. "secret_1"
        RDIKeys.SECRET_LOOKUP_DB: "@@rdi1_secret_lookup_db@@",
        # For the data finder: table-specific and default date column names
        RDIKeys.DATE_FIELDS_BY_TABLE: {@@rdi1_date_fields_by_table@@},
        # e.g. ["default_date_field"]
        RDIKeys.DEFAULT_DATE_FIELDS: [@@rdi1_default_date_fields@@],
        # Column name giving time that record was updated
        # e.g. "_when_fetched_utc"
        RDIKeys.UPDATE_DATE_FIELD: "@@rdi1_update_date_field@@",
    },
    # {
    #     RDIKeys.NAME: "similar_database",
    #     RDIKeys.DESCRIPTION: "A database sharing the RID with the first",
    #     RDIKeys.DATABASE: "similar_database",
    #     RDIKeys.SCHEMA: "similar_schema",
    #     RDIKeys.TRID_FIELD: "trid",
    #     RDIKeys.RID_FIELD: "same_rid",
    #     RDIKeys.RID_FAMILY: 1,
    #     RDIKeys.MRID_TABLE: "",
    #     RDIKeys.MRID_FIELD: "",
    #     RDIKeys.PID_DESCRIPTION: "",
    #     RDIKeys.MPID_DESCRIPTION: "",
    #     RDIKeys.RID_DESCRIPTION: "",
    #     RDIKeys.MRID_DESCRIPTION: "",
    #     RDIKeys.TRID_DESCRIPTION: "",
    #     RDIKeys.SECRET_LOOKUP_DB: "",
    #     RDIKeys.DATE_FIELDS_BY_TABLE: {},
    #     RDIKeys.DEFAULT_DATE_FIELDS: [],
    #     RDIKeys.UPDATE_DATE_FIELD: "_when_fetched_utc",
    # },
    # {
    #     RDIKeys.NAME: "different_database",
    #     RDIKeys.DESCRIPTION: "A database sharing only the MRID with the first",  # noqa: E501
    #     RDIKeys.DATABASE: "different_database",
    #     RDIKeys.SCHEMA: "different_schema",
    #     RDIKeys.TRID_FIELD: "trid",
    #     RDIKeys.RID_FIELD: "different_rid",
    #     RDIKeys.RID_FAMILY: 2,
    #     RDIKeys.MRID_TABLE: "hashed_nhs_numbers",
    #     RDIKeys.MRID_FIELD: "nhshash",
    #     RDIKeys.PID_DESCRIPTION: "",
    #     RDIKeys.MPID_DESCRIPTION: "",
    #     RDIKeys.RID_DESCRIPTION: "",
    #     RDIKeys.MRID_DESCRIPTION: "",
    #     RDIKeys.TRID_DESCRIPTION: "",
    #     RDIKeys.SECRET_LOOKUP_DB: "",
    #     RDIKeys.DATE_FIELDS_BY_TABLE: {},
    #     RDIKeys.DEFAULT_DATE_FIELDS: [],
    #     RDIKeys.UPDATE_DATE_FIELD: "_when_fetched_utc",
    # },
]

# Which database (from those defined in RESEARCH_DB_INFO above) should be used
# to look up patients when contact requests are made?
# Give the 'name' attribute of one of the databases in RESEARCH_DB_INFO.
# Its secret_lookup_db will be used for the actual lookup process.
RESEARCH_DB_FOR_CONTACT_LOOKUP = "@@research_db_for_contact_lookup@@"

# Definitions of source database names in CRATE NLP tables
NLP_SOURCEDB_MAP = {"SOURCE_DATABASE": "research"}

# For the automatic query generator, we need to know the underlying SQL dialect
# Options are
# - "mysql" => MySQL
# - "mssql" => Microsoft SQL Server
RESEARCH_DB_DIALECT = "mysql"

DISABLE_DJANGO_PYODBC_AZURE_CURSOR_FETCHONE_NEXTSET = True

# =============================================================================
# Archive views
# =============================================================================
# See https://crateanon.readthedocs.io/en/latest/website_config/web_config_file.html  # noqa

# e.g. /home/somewhere/my_archive_templates
ARCHIVE_TEMPLATE_DIR = "@@archive_template_dir@@"
# e.g. /home/somewhere/my_archive_templates/cache
ARCHIVE_TEMPLATE_CACHE_DIR = "@@archive_template_cache_dir@@"
# e.g. /home/somewhere/my_archive_templates/static
ARCHIVE_STATIC_DIR = "@@archive_static_dir@@"
ARCHIVE_ROOT_TEMPLATE = "root.mako"
# e.g. /home/somewhere/my_archive_attachments
ARCHIVE_ATTACHMENT_DIR = "@@archive_attachment_dir@@"
ARCHIVE_CONTEXT = {}
CACHE_CONTROL_MAX_AGE_ARCHIVE_ATTACHMENTS = 0
CACHE_CONTROL_MAX_AGE_ARCHIVE_TEMPLATES = 0
CACHE_CONTROL_MAX_AGE_ARCHIVE_STATIC = 0


# =============================================================================
# Database extra help file
# =============================================================================
# See https://crateanon.readthedocs.io/en/latest/website_config/web_config_file.html  # noqa

# If specified, this must be a string that is an absolute filename of TRUSTED
# HTML that will be included.
DATABASE_HELP_HTML_FILENAME = None


# =============================================================================
# Local file storage (for PDFs etc).
# =============================================================================
# See https://crateanon.readthedocs.io/en/latest/website_config/web_config_file.html  # noqa

# Where should we store the files? Make this directory (and don't let it
# be served by a generic web server that doesn't check permissions).
# e.g. /srv/crate_filestorage
PRIVATE_FILE_STORAGE_ROOT = "@@private_file_storage_root@@"

# Serve files via Django (inefficient but useful for testing) or via Apache
# with mod_xsendfile (or other web server configured for the X-SendFile
# directive)?
XSENDFILE = False

# How big will we accept?
MAX_UPLOAD_SIZE_BYTES = mebibytes(10)


# =============================================================================
# Outgoing e-mail
# =============================================================================
# See https://crateanon.readthedocs.io/en/latest/website_config/web_config_file.html  # noqa

# -----------------------------------------------------------------------------
# General settings for sending e-mail from Django
# -----------------------------------------------------------------------------
# https://docs.djangoproject.com/en/1.8/ref/settings/#email-backend

#   default backend:
# EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
#   bugfix for servers that only support TLSv1:
# EMAIL_BACKEND = "cardinal_pythonlib.django.mail.SmtpEmailBackendTls1"

EMAIL_HOST = "smtp.somewhere.nhs.uk"
EMAIL_PORT = 587  # usually 25 (plain SMTP) or 587 (STARTTLS)
# ... see https://www.fastmail.com/help/technical/ssltlsstarttls.html
EMAIL_HOST_USER = "myuser"
EMAIL_HOST_PASSWORD = "mypassword"
EMAIL_USE_TLS = True
EMAIL_USE_SSL = False

# Who will the e-mails appear to come from?
EMAIL_SENDER = "My NHS Trust Research Database - DO NOT REPLY <noreply@somewhere.nhs.uk>"  # noqa

# -----------------------------------------------------------------------------
# Additional settings
# -----------------------------------------------------------------------------
# See https://crateanon.readthedocs.io/en/latest/website_config/web_config_file.html  # noqa

# During development, we route all consent-related e-mails to the developer.
# Switch SAFETY_CATCH_ON to False for production mode.
SAFETY_CATCH_ON = True
DEVELOPER_EMAIL = "testuser@somewhere.nhs.uk"

VALID_RESEARCHER_EMAIL_DOMAINS = []  # type: List[str]
# ... if empty, no checks are performed (any address is accepted)


# =============================================================================
# Research Database Manager (RDBM) details
# =============================================================================
# See https://crateanon.readthedocs.io/en/latest/website_config/web_config_file.html  # noqa

RDBM_NAME = "John Doe"
RDBM_TITLE = "Research Database Manager"
RDBM_TELEPHONE = "01223-XXXXXX"
RDBM_EMAIL = "research.database@somewhere.nhs.uk"
RDBM_ADDRESS = [
    "FREEPOST SOMEWHERE_HOSPITAL RESEARCH DATABASE MANAGER"
]  # a list


# =============================================================================
# Administrators/managers to be notified of errors
# =============================================================================
# See https://crateanon.readthedocs.io/en/latest/website_config/web_config_file.html  # noqa

# Exceptions get sent to these people.
ADMINS = [
    ("Mr Administrator", "mr_admin@somewhere.domain"),
]


# =============================================================================
# PDF creation
# =============================================================================
# See https://crateanon.readthedocs.io/en/latest/website_config/web_config_file.html  # noqa
# Note that using headers/footers requires a version of wkhtmltopdf built using
# "patched Qt". See above.
# Fetch one from http://wkhtmltopdf.org/, e.g. v0.12.4 for your OS.

WKHTMLTOPDF_FILENAME = shutil.which("wkhtmltopdf")

WKHTMLTOPDF_OPTIONS = {  # dict for pdfkit
    "disable-smart-shrinking": "",  # --disable-smart-shrinking
    "dpi": "300",
    "enable-local-file-access": "",  # --enable-local-file-access
    "encoding": "UTF-8",
    "footer-spacing": "3",  # mm, from content down to top of footer
    "header-spacing": "3",  # mm, from content up to bottom of header
    "margin-bottom": "24mm",  # from paper edge up to bottom of content?
    "margin-left": "20mm",
    "margin-right": "20mm",
    "margin-top": "21mm",  # from paper edge down to top of content?
    "orientation": "portrait",
    "page-size": "A4",
}

PDF_LOGO_ABS_URL = "@@pdf_logo_abs_url@@"
# ... path on local machine, read by wkhtmltopdf
# Examples:
#   [if you're running a web server] "http://localhost/crate_logo"
#   [Linux root path] file:///home/myuser/myfile.png
#   [Windows root path] file:///c:/path/to/myfile.png

PDF_LOGO_WIDTH = "75%"
# ... must be suitable for an <img> tag, but "150mm" isn't working; "75%" is.
# ... tune this to your logo file (see PDF_LOGO_ABS_URL)

# The PDF generator also needs to be able to find the traffic-light pictures,
# on disk (not via your web site):
TRAFFIC_LIGHT_RED_ABS_URL = (
    "file:///somewhere/crate_anon/crateweb/static/red.png"
)
TRAFFIC_LIGHT_YELLOW_ABS_URL = (
    "file:///somewhere/crate_anon/crateweb/static/yellow.png"
)
TRAFFIC_LIGHT_GREEN_ABS_URL = (
    "file:///somewhere/crate_anon/crateweb/static/green.png"
)


# =============================================================================
# Consent-for-contact settings
# =============================================================================
# See https://crateanon.readthedocs.io/en/latest/website_config/web_config_file.html  # noqa

# For how long may we contact discharged patients without specific permission?
# Use 0 for "not at all".
PERMITTED_TO_CONTACT_DISCHARGED_PATIENTS_FOR_N_DAYS = 3 * 365

# Donation to charity for clinician response (regardless of the decision):
CHARITY_AMOUNT_CLINICIAN_RESPONSE = 1.0  # in local currency, e.g. GBP

# Address HTML for letter footers.
PDF_LETTER_FOOTER_ADDRESS_HTML = ""

# Ethics info for letter footers.
ETHICS_INFO = CPFTEthics2022()


# =============================================================================
# Local information links
# =============================================================================
# See https://crateanon.readthedocs.io/en/latest/website_config/web_config_file.html  # noqa

CHARITY_URL = "http://www.cpft.nhs.uk/research.htm"
CHARITY_URL_SHORT = "www.cpft.nhs.uk/research.htm"
LEAFLET_URL_CPFTRD_CLINRES_SHORT = (
    "www.cpft.nhs.uk/research.htm > CPFT Research Database"
)

ANONYMISE_API = {
    "HASH_KEY": "@@hash_key@@",
    "ALLOWLIST_FILENAMES": {},
    "DENYLIST_FILENAMES": {},
}

"""


[docs]def main() -> None: """ Command-line entry point. """ parser = argparse.ArgumentParser(formatter_class=RichHelpFormatter) parser.add_argument( "--leave_placeholders", action="store_true", help="Don't substitute @@ placeholders with examples", default=False, ) args = parser.parse_args() if args.leave_placeholders: print(DEMO_CONFIG.strip()) return replace_dict = { "archive_attachment_dir": "/home/somewhere/my_archive_attachments", "archive_static_dir": "/home/somewhere/my_archive_templates/static", "archive_template_cache_dir": "/tmp/somewhere/my_archive_template_cache", # noqa: E501 "archive_template_dir": "/home/somewhere/my_archive_templates", "broker_url": "", "crate_https": "True", "crate_install_dir": "somewhere", "dest_db_engine": "django.db.backends.mysql", "dest_db_host": "127.0.0.1", "dest_db_name": "anonymous_output", "dest_db_password": "somepassword", "dest_db_port": "3306", "dest_db_user": "researcher", "django_site_root_absolute_url": "http://mymachine.mydomain", "force_script_name": "", "hash_key": "aaaa CHANGE THIS! aaaa", "crate_db_engine": "django.db.backends.mysql", "crate_db_name": "crate_db", "crate_db_host": "127.0.0.1", "crate_db_password": "somepassword", "crate_db_port": "3306", "crate_db_user": "someuser", "pdf_logo_abs_url": "http://localhost/crate_logo", "private_file_storage_root": "/srv/crate_filestorage", "rdi1_database": "", "rdi1_date_fields_by_table": "", "rdi1_default_date_fields": '"default_date_field"', "rdi1_description": "My friendly research database", "rdi1_mpid_description": "Master patient ID (NHS number; MPID)", "rdi1_mpid_pseudo_field": "my_mpid_field", "rdi1_mrid_description": "Master research ID (MRID)", "rdi1_mrid_field": "nhshash", "rdi1_mrid_table": "patients", "rdi1_name": "myresearchdb", "rdi1_pid_description": "Patient ID (My ID Num; PID) for database X", "rdi1_pid_psuedo_field": "my_pid_field", "rdi1_rid_description": "Research ID (RID) for database X", "rdi1_rid_family": "1", "rdi1_rid_field": "brcid", "rdi1_schema": "dbo", "rdi1_secret_lookup_db": "secret_1", "rdi1_trid_description": "Transient research ID (TRID) for database X", "rdi1_trid_field": "trid", "rdi1_update_date_field": "_when_fetched_utc", "research_db_for_contact_lookup": "myresearchdb", "secret_db1_engine": "django.db.backends.mysql", "secret_db1_host": "127.0.0.1", "secret_db1_name": "anonymous_mapping", "secret_db1_password": "somepassword", "secret_db1_port": "3306", "secret_db1_user": "anonymiser_system", "secret_key": "aaaaaaaaaaaaaaaaaa CHANGE THIS! aaaaaaaaaaaaaaaaaa", } config = search_replace_text(DEMO_CONFIG, replace_dict) missing_dict = {} regex = r"@@([^@]*)@@" for match in re.finditer(regex, config): missing_dict[f"{match.group(1)}"] = "" if missing_dict: print( "@@ Placeholders not substituted in DEMO_CONFIG:", file=sys.stderr ) pprint.pprint(missing_dict, stream=sys.stderr) sys.exit(EXIT_FAILURE) print(config.strip())
def search_replace_text(text: str, replace_dict: Dict[str, str]) -> str: for search, replace in replace_dict.items(): if replace is None: print(f"Can't replace '{search}' with None") sys.exit(EXIT_FAILURE) text = text.replace(f"@@{search}@@", replace) return text if __name__ == "__main__": main()