def _assert_dt_equal( a: Union[datetime.datetime, Pendulum], b: Union[datetime.datetime, Pendulum], ) -> None: # Accept that one may have been truncated or rounded to milliseconds. a = coerce_to_pendulum(a) b = coerce_to_pendulum(b) diff = a - b assert diff.microseconds < 1000, f"{a!r} != {b!r}"
def datetime_to_nlprp_datetime(dt: datetime.datetime, assume_utc: bool = True) -> str: """ Converts a :class:`datetime.datetime` to the ISO string format (with timezone) used by the NLPRP. If the datetime.datetime object has no timezone info, then assume the local timezone if ``assume_local`` is true; otherwise, assume UTC. """ p = coerce_to_pendulum(dt, assume_local=not assume_utc) return pendulum_to_nlprp_datetime(p)
def deserialize(self, node: SchemaNode, cstruct: Union[str, ColanderNullType]) \ -> Optional[Pendulum]: """ Deserializes string representation to Python object. """ if not cstruct: return colander.null try: result = coerce_to_pendulum(cstruct, assume_local=self.use_local_tz) except (ValueError, ParserError) as e: raise Invalid( node, f"Invalid date/time: value={cstruct!r}, error={e!r}") return result
def serialize(self, node: SchemaNode, appstruct: Union[PotentialDatetimeType, ColanderNullType]) \ -> Union[str, ColanderNullType]: """ Serializes Python object to string representation. """ if not appstruct: return colander.null try: appstruct = coerce_to_pendulum(appstruct, assume_local=self.use_local_tz) except (ValueError, ParserError) as e: raise Invalid( node, f"{appstruct!r} is not a pendulum.DateTime object; " f"error was {e!r}") return appstruct.isoformat()
def read_from_config( cls, cfg: "CamcopsConfig", parser: configparser.ConfigParser, recipient_name: str, ) -> "ExportRecipientInfo": """ Reads from the config file and writes this instance's attributes. Args: cfg: a :class:`camcops_server.cc_modules.cc_config.CamcopsConfig` parser: configparser INI file object recipient_name: name of recipient and of INI file section Returns: an :class:`ExportRecipient` object, which is **not** currently in a database session """ assert recipient_name log.debug("Loading export config for recipient {!r}", recipient_name) section = CONFIG_RECIPIENT_PREFIX + recipient_name cps = ConfigParamSite cpr = ConfigParamExportRecipient cd = ConfigDefaults() r = cls() # type: ExportRecipientInfo def _get_str(paramname: str, default: str = None) -> Optional[str]: return get_config_parameter(parser, section, paramname, str, default) def _get_bool(paramname: str, default: bool) -> bool: return get_config_parameter_boolean(parser, section, paramname, default) def _get_int(paramname: str, default: int = None) -> Optional[int]: return get_config_parameter(parser, section, paramname, int, default) def _get_multiline(paramname: str) -> List[str]: return get_config_parameter_multiline(parser, section, paramname, []) def _get_site_str(paramname: str, default: str = None) -> Optional[str]: return get_config_parameter(parser, CONFIG_FILE_SITE_SECTION, paramname, str, default) # noinspection PyUnusedLocal def _get_site_bool(paramname: str, default: bool) -> bool: return get_config_parameter_boolean(parser, CONFIG_FILE_SITE_SECTION, paramname, default) # noinspection PyUnusedLocal def _get_site_int(paramname: str, default: int = None) -> Optional[int]: return get_config_parameter(parser, CONFIG_FILE_SITE_SECTION, paramname, int, default) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Identity # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ r.recipient_name = recipient_name # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # How to export # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ r.transmission_method = _get_str(cpr.TRANSMISSION_METHOD) r.transmission_method = str(r.transmission_method).lower() # Check this one immediately, since we use it in conditions below if r.transmission_method not in ALL_TRANSMISSION_METHODS: raise _Invalid( r.recipient_name, f"Missing/invalid " f"{ConfigParamExportRecipient.TRANSMISSION_METHOD}: " f"{r.transmission_method}", ) r.push = _get_bool(cpr.PUSH, cd.PUSH) r.task_format = _get_str(cpr.TASK_FORMAT, cd.TASK_FORMAT) r.xml_field_comments = _get_bool(cpr.XML_FIELD_COMMENTS, cd.XML_FIELD_COMMENTS) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # What to export # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ r.all_groups = _get_bool(cpr.ALL_GROUPS, cd.ALL_GROUPS) r.group_names = _get_multiline(cpr.GROUPS) r.group_ids = [] # ... read later by validate_db_dependent() r.tasks = sorted([x.lower() for x in _get_multiline(cpr.TASKS)]) sd = _get_str(cpr.START_DATETIME_UTC) r.start_datetime_utc = (pendulum_to_utc_datetime_without_tz( coerce_to_pendulum(sd, assume_local=False)) if sd else None) ed = _get_str(cpr.END_DATETIME_UTC) r.end_datetime_utc = (pendulum_to_utc_datetime_without_tz( coerce_to_pendulum(ed, assume_local=False)) if ed else None) r.finalized_only = _get_bool(cpr.FINALIZED_ONLY, cd.FINALIZED_ONLY) r.include_anonymous = _get_bool(cpr.INCLUDE_ANONYMOUS, cd.INCLUDE_ANONYMOUS) r.primary_idnum = _get_int(cpr.PRIMARY_IDNUM) r.require_idnum_mandatory = _get_bool( cpr.REQUIRE_PRIMARY_IDNUM_MANDATORY_IN_POLICY, cd.REQUIRE_PRIMARY_IDNUM_MANDATORY_IN_POLICY, ) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Database # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ if r.transmission_method == ExportTransmissionMethod.DATABASE: r.db_url = _get_str(cpr.DB_URL) r.db_echo = _get_bool(cpr.DB_ECHO, cd.DB_ECHO) r.db_include_blobs = _get_bool(cpr.DB_INCLUDE_BLOBS, cd.DB_INCLUDE_BLOBS) r.db_add_summaries = _get_bool(cpr.DB_ADD_SUMMARIES, cd.DB_ADD_SUMMARIES) r.db_patient_id_per_row = _get_bool(cpr.DB_PATIENT_ID_PER_ROW, cd.DB_PATIENT_ID_PER_ROW) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Email # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def _make_email_csv_list(paramname: str) -> str: return ", ".join(x for x in _get_multiline(paramname)) if r.transmission_method == ExportTransmissionMethod.EMAIL: r.email_host = cfg.email_host r.email_port = cfg.email_port r.email_use_tls = cfg.email_use_tls r.email_host_username = cfg.email_host_username r.email_host_password = cfg.email_host_password # Read from password safe using 'pass' # from subprocess import run, PIPE # output = run(["pass", "dept-of-psychiatry/Hermes"], stdout=PIPE) # r.email_host_password = output.stdout.decode("utf-8").split()[0] r.email_from = _get_site_str(cps.EMAIL_FROM, "") r.email_sender = _get_site_str(cps.EMAIL_SENDER, "") r.email_reply_to = _get_site_str(cps.EMAIL_REPLY_TO, "") r.email_to = _make_email_csv_list(cpr.EMAIL_TO) r.email_cc = _make_email_csv_list(cpr.EMAIL_CC) r.email_bcc = _make_email_csv_list(cpr.EMAIL_BCC) r.email_patient_spec_if_anonymous = _get_str( cpr.EMAIL_PATIENT_SPEC_IF_ANONYMOUS, "") r.email_patient_spec = _get_str(cpr.EMAIL_PATIENT_SPEC, "") r.email_subject = _get_str(cpr.EMAIL_SUBJECT, "") r.email_body_as_html = _get_bool(cpr.EMAIL_BODY_IS_HTML, cd.EMAIL_BODY_IS_HTML) r.email_body = _get_str(cpr.EMAIL_BODY, "") r.email_keep_message = _get_bool(cpr.EMAIL_KEEP_MESSAGE, cd.EMAIL_KEEP_MESSAGE) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # HL7 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ if r.transmission_method == ExportTransmissionMethod.HL7: r.hl7_host = _get_str(cpr.HL7_HOST) r.hl7_port = _get_int(cpr.HL7_PORT, cd.HL7_PORT) r.hl7_ping_first = _get_bool(cpr.HL7_PING_FIRST, cd.HL7_PING_FIRST) r.hl7_network_timeout_ms = _get_int(cpr.HL7_NETWORK_TIMEOUT_MS, cd.HL7_NETWORK_TIMEOUT_MS) r.hl7_keep_message = _get_bool(cpr.HL7_KEEP_MESSAGE, cd.HL7_KEEP_MESSAGE) r.hl7_keep_reply = _get_bool(cpr.HL7_KEEP_REPLY, cd.HL7_KEEP_REPLY) r.hl7_debug_divert_to_file = _get_bool( cpr.HL7_DEBUG_DIVERT_TO_FILE, cd.HL7_DEBUG_DIVERT_TO_FILE) r.hl7_debug_treat_diverted_as_sent = _get_bool( cpr.HL7_DEBUG_TREAT_DIVERTED_AS_SENT, cd.HL7_DEBUG_TREAT_DIVERTED_AS_SENT, ) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # File # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ if r._need_file_name(): r.file_patient_spec = _get_str(cpr.FILE_PATIENT_SPEC) r.file_patient_spec_if_anonymous = _get_str( cpr.FILE_PATIENT_SPEC_IF_ANONYMOUS, cd.FILE_PATIENT_SPEC_IF_ANONYMOUS, ) r.file_filename_spec = _get_str(cpr.FILE_FILENAME_SPEC) if r._need_file_disk_options(): r.file_make_directory = _get_bool(cpr.FILE_MAKE_DIRECTORY, cd.FILE_MAKE_DIRECTORY) r.file_overwrite_files = _get_bool(cpr.FILE_OVERWRITE_FILES, cd.FILE_OVERWRITE_FILES) if r.transmission_method == ExportTransmissionMethod.FILE: r.file_export_rio_metadata = _get_bool( cpr.FILE_EXPORT_RIO_METADATA, cd.FILE_EXPORT_RIO_METADATA) r.file_script_after_export = _get_str(cpr.FILE_SCRIPT_AFTER_EXPORT) if r._need_rio_metadata_options(): # RiO metadata r.rio_idnum = _get_int(cpr.RIO_IDNUM) r.rio_uploading_user = _get_str(cpr.RIO_UPLOADING_USER) r.rio_document_type = _get_str(cpr.RIO_DOCUMENT_TYPE) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # REDCap # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ if r.transmission_method == ExportTransmissionMethod.REDCAP: r.redcap_api_url = _get_str(cpr.REDCAP_API_URL) r.redcap_api_key = _get_str(cpr.REDCAP_API_KEY) r.redcap_fieldmap_filename = _get_str(cpr.REDCAP_FIELDMAP_FILENAME) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # FHIR # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ if r.transmission_method == ExportTransmissionMethod.FHIR: r.fhir_api_url = _get_str(cpr.FHIR_API_URL) r.fhir_app_id = _get_str(cpr.FHIR_APP_ID, CAMCOPS_DEFAULT_FHIR_APP_ID) r.fhir_app_secret = _get_str(cpr.FHIR_APP_SECRET) r.fhir_launch_token = _get_str(cpr.FHIR_LAUNCH_TOKEN) r.fhir_concurrent = _get_bool(cpr.FHIR_CONCURRENT, False) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Validate the basics and return # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ r.validate_db_independent() return r
def read_from_config(cls, parser: configparser.ConfigParser, recipient_name: str) -> "ExportRecipientInfo": """ Reads from the config file and writes this instance's attributes. Args: parser: configparser INI file object recipient_name: name of recipient and of INI file section Returns: an :class:`ExportRecipient` object, which is **not** currently in a database session """ assert recipient_name log.debug("Loading export config for recipient {!r}", recipient_name) section = CONFIG_RECIPIENT_PREFIX + recipient_name cpr = ConfigParamExportRecipient r = cls() def _get_str(paramname: str, default: str = None) -> Optional[str]: return get_config_parameter(parser, section, paramname, str, default) def _get_bool(paramname: str, default: bool) -> bool: return get_config_parameter_boolean(parser, section, paramname, default) def _get_int(paramname: str, default: int = None) -> Optional[int]: return get_config_parameter(parser, section, paramname, int, default) def _get_multiline(paramname: str) -> List[str]: return get_config_parameter_multiline(parser, section, paramname, []) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Identity # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ r.recipient_name = recipient_name # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # How to export # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ r.transmission_method = _get_str(cpr.TRANSMISSION_METHOD) r.transmission_method = str(r.transmission_method).lower() # Check this one immediately, since we use it in conditions below if r.transmission_method not in ALL_TRANSMISSION_METHODS: raise _Invalid( r.recipient_name, "Missing/invalid {}: {}".format( ConfigParamExportRecipient.TRANSMISSION_METHOD, r.transmission_method)) r.push = _get_bool(cpr.PUSH, False) r.task_format = _get_str(cpr.TASK_FORMAT, FileType.PDF) r.xml_field_comments = _get_bool(cpr.XML_FIELD_COMMENTS, True) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # What to export # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ r.all_groups = _get_bool(cpr.ALL_GROUPS, False) r.group_names = _get_multiline(cpr.GROUPS) r.group_ids = [] # type: List[int] # ... read later by validate_db_dependent() sd = _get_str(cpr.START_DATETIME_UTC) r.start_datetime_utc = pendulum_to_utc_datetime_without_tz( coerce_to_pendulum(sd, assume_local=False)) if sd else None ed = _get_str(cpr.END_DATETIME_UTC) r.end_datetime_utc = pendulum_to_utc_datetime_without_tz( coerce_to_pendulum(ed, assume_local=False)) if ed else None r.finalized_only = _get_bool(cpr.FINALIZED_ONLY, True) r.include_anonymous = _get_bool(cpr.INCLUDE_ANONYMOUS, False) r.primary_idnum = _get_int(cpr.PRIMARY_IDNUM) r.require_idnum_mandatory = _get_bool( cpr.REQUIRE_PRIMARY_IDNUM_MANDATORY_IN_POLICY, True) # noqa # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Database # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ if r.transmission_method == ExportTransmissionMethod.DATABASE: r.db_url = _get_str(cpr.DB_URL) r.db_echo = _get_bool(cpr.DB_ECHO, False) r.db_include_blobs = _get_bool(cpr.DB_INCLUDE_BLOBS, True) r.db_add_summaries = _get_bool(cpr.DB_ADD_SUMMARIES, True) r.db_patient_id_per_row = _get_bool(cpr.DB_PATIENT_ID_PER_ROW, False) # noqa # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Email # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def _make_email_csv_list(paramname: str) -> str: return ", ".join(x for x in _get_multiline(paramname)) if r.transmission_method == ExportTransmissionMethod.EMAIL: r.email_host = _get_str(cpr.EMAIL_HOST) r.email_port = _get_int(cpr.EMAIL_PORT, DEFAULT_SMTP_PORT) r.email_use_tls = _get_bool(cpr.EMAIL_USE_TLS, False) r.email_host_username = _get_str(cpr.EMAIL_HOST_USERNAME) r.email_host_password = _get_str(cpr.EMAIL_HOST_PASSWORD, "") r.email_from = _get_str(cpr.EMAIL_FROM, "") r.email_sender = _get_str(cpr.EMAIL_SENDER, "") r.email_reply_to = _get_str(cpr.EMAIL_REPLY_TO, "") r.email_to = _make_email_csv_list(cpr.EMAIL_TO) r.email_cc = _make_email_csv_list(cpr.EMAIL_CC) r.email_bcc = _make_email_csv_list(cpr.EMAIL_BCC) r.email_patient_spec_if_anonymous = _get_str( cpr.EMAIL_PATIENT_SPEC_IF_ANONYMOUS, "") # noqa r.email_patient_spec = _get_str(cpr.EMAIL_PATIENT_SPEC, "") r.email_subject = _get_str(cpr.EMAIL_SUBJECT, "") r.email_body_as_html = _get_bool(cpr.EMAIL_BODY_IS_HTML, False) r.email_body = _get_str(cpr.EMAIL_BODY, "") r.email_keep_message = _get_bool(cpr.EMAIL_KEEP_MESSAGE, False) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # HL7 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ if r.transmission_method == ExportTransmissionMethod.HL7: r.hl7_host = _get_str(cpr.HL7_HOST) r.hl7_port = _get_int(cpr.HL7_PORT, DEFAULT_HL7_MLLP_PORT) r.hl7_ping_first = _get_bool(cpr.HL7_PING_FIRST, True) r.hl7_network_timeout_ms = _get_int(cpr.HL7_NETWORK_TIMEOUT_MS, DEFAULT_HL7_TIMEOUT_MS) # noqa r.hl7_keep_message = _get_bool(cpr.HL7_KEEP_MESSAGE, False) r.hl7_keep_reply = _get_bool(cpr.HL7_KEEP_REPLY, False) r.hl7_debug_divert_to_file = _get_bool( cpr.HL7_DEBUG_DIVERT_TO_FILE, False) # noqa r.hl7_debug_treat_diverted_as_sent = _get_bool( cpr.HL7_DEBUG_TREAT_DIVERTED_AS_SENT, False) # noqa # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # File # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ if r._need_file_name(): r.file_patient_spec = _get_str(cpr.FILE_PATIENT_SPEC) r.file_patient_spec_if_anonymous = _get_str( cpr.FILE_PATIENT_SPEC_IF_ANONYMOUS, "anonymous") # noqa r.file_filename_spec = _get_str(cpr.FILE_FILENAME_SPEC) if r._need_file_disk_options(): r.file_make_directory = _get_bool(cpr.FILE_MAKE_DIRECTORY, False) r.file_overwrite_files = _get_bool(cpr.FILE_OVERWRITE_FILES, False) if r.transmission_method == ExportTransmissionMethod.FILE: r.file_export_rio_metadata = _get_bool( cpr.FILE_EXPORT_RIO_METADATA, False) # noqa r.file_script_after_export = _get_str(cpr.FILE_SCRIPT_AFTER_EXPORT) if r._need_rio_metadata_options(): # RiO metadata r.rio_idnum = _get_int(cpr.RIO_IDNUM) r.rio_uploading_user = _get_str(cpr.RIO_UPLOADING_USER) r.rio_document_type = _get_str(cpr.RIO_DOCUMENT_TYPE) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Validate the basics and return # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ r.validate_db_independent() return r
def datetime_submitted_pendulum(self) -> Optional[Pendulum]: return coerce_to_pendulum(self.datetime_submitted_utc, assume_local=False)