def __get_kontaktdaten(self, modus: Modus) -> dict: """ Ladet die Kontakdaten aus dem in der GUI hinterlegten Pfad Args: modus (Modus): Abhängig vom Modus werden nicht alle Daten benötigt. Returns: dict: Kontakdaten """ if not os.path.isfile(self.pfad_kontaktdaten): if not self.kontaktdaten_erstellen(modus): return {} kontaktdaten = kontak_tools.get_kontaktdaten(self.pfad_kontaktdaten) kontak_tools.check_kontaktdaten(kontaktdaten, modus) if modus == Modus.TERMIN_SUCHEN: if not self.__check_old_kontakt_version(kontaktdaten): raise ValidationError("\"zeitrahmen\" fehlt -> Alte Version") if "codes" in kontaktdaten: if "XXXX-XXXX-XXXX" in kontaktdaten["codes"]: raise ValidationError( "Der Code is ungültig. Bitte trage einen korrekten Code ein!" ) return kontaktdaten
def validate_kontaktdaten(kontaktdaten: dict): """ Validiert Kontaktdaten. Leere Werte werden als Fehler angesehen. :raise ValidationError: Typ ist nicht dict :raise ValidationError: Einer der enthaltenen Keys ist unbekannt :raise ValidationError: Eine der enthaltenen Values ist ungültig """ if not isinstance(kontaktdaten, dict): raise ValidationError("Muss ein Dictionary sein") for key, value in kontaktdaten.items(): try: if key == "code": validate_code(value) elif key == "plz_impfzentren": validate_plz_impfzentren(value) elif key == "kontakt": validate_kontakt(value) elif key == "zeitrahmen": validate_zeitrahmen(value) else: raise ValidationError(f"Nicht unterstützter Key") except ValidationError as exc: raise ValidationError( f"Ungültiger Key {json.dumps(key)}:\n{str(exc)}")
def validate_pushover_app_token(pushover_app_token: str): """ Validiert den Pushover App Token. :raise ValidationError: Typ ist nicht str :raise ValidationError: Der Wert istz nicht genau 30 Zeichen lang """ if not isinstance(pushover_app_token, str): raise ValidationError("Muss eine Zeichenkette sein") if len(pushover_app_token) != 30: raise ValidationError("Der Pushover APP Token muss genau 30 Zeichen lang sein")
def validate_telegram_chat_id(telegram_chat_id: str): """ Validiert die Telegram Chat ID. :raise ValidationError: Typ ist nicht str :raise ValidationError: Die ID ist zu kurz (Länge der IDs variiert) """ if not isinstance(telegram_chat_id, str): raise ValidationError("Muss eine Zeichenkette sein") if len(telegram_chat_id) < 4: raise ValidationError("Die Chat ID ist zu kurz.")
def validate_telegram_api_token(telegram_api_token: str): """ Validiert den Telegram API Token. :raise ValidationError: Typ ist nicht str :raise ValidationError: Entspricht nicht dem Format \w+:\w+ """ if not isinstance(telegram_api_token, str): raise ValidationError("Muss eine Zeichenkette sein") if not re.search(r"\w+:\w+", telegram_api_token): raise ValidationError("Der Telegram API-Token besteht aus zwei Teilen welche durch \":\" getrennt sind.")
def validate_pushover_user_key(pushover_user_key: str): """ Validiert den Pushover User Key. :raise ValidationError: Typ ist nicht str :raise ValidationError: Der Wert istz nicht genau 30 Zeichen lang """ if not isinstance(pushover_user_key, str): raise ValidationError("Muss eine Zeichenkette sein") if len(pushover_user_key) != 30: raise ValidationError("Der Pushover User Key muss genau 30 Zeichen lang sein")
def validate_einhalten_bei(einhalten_bei: str): """ Validiert "zeitrahmen"."einhalten_bei"-Key aus Kontaktdaten. Erlaubte Parameter sind: "1", "2", "beide" :raise ValidationError: Parameter ist nicht erlaubt (s.o.) """ if not isinstance(einhalten_bei, str): raise ValidationError("Muss eine Zeichenkette sein") if einhalten_bei not in ["1", "2", "beide"]: raise ValidationError('Erlaubt sind: "1", "2", "beide"')
def validate_zeitrahmen(zeitrahmen: dict): """ Validiert "zeitrahmen"-Key aus Kontaktdaten. :raise ValidationError: Typ ist nicht dict :raise ValidationError: Einer der enthaltenen Keys ist unbekannt :raise ValidationError: Eine der enthaltenen Values ist ungültig """ if not isinstance(zeitrahmen, dict): raise ValidationError("Muss ein Dictionary sein") if zeitrahmen == {}: return # Ein ganz leerer Zeitrahmen ist zulässig (s.o.), aber ansonsten muss der # Key "einhalten_bei" vorhanden sein. if "einhalten_bei" not in zeitrahmen: raise ValidationError( 'Ein gesetzter Zeitrahmen braucht zwingend den Key "einhalten_bei"' ) for key, value in zeitrahmen.items(): try: if key in ["von_datum", "bis_datum"]: validate_datum(value) elif key in ["von_uhrzeit", "bis_uhrzeit"]: validate_uhrzeit(value) elif key == "wochentage": if not isinstance(value, list): raise ValidationError("Muss eine Liste sein") if not value: raise ValidationError("Darf keine leere Liste sein") for weekday in value: validate_wochentag(weekday) elif key == "einhalten_bei": validate_einhalten_bei(value) else: raise ValidationError(f"Nicht unterstützter Key") except ValidationError as exc: raise ValidationError( f"Ungültiger Key {json.dumps(key)}:\n{str(exc)}") if "von_datum" in zeitrahmen and "bis_datum" in zeitrahmen: von_datum = datetime.datetime.strptime(zeitrahmen["von_datum"], "%d.%m.%Y") bis_datum = datetime.datetime.strptime(zeitrahmen["bis_datum"], "%d.%m.%Y") if von_datum > bis_datum: raise ValidationError("Ungültige Kombination von Datums: " 'von_datum" liegt nach "bis_datum"') if "von_uhrzeit" in zeitrahmen and "bis_uhrzeit" in zeitrahmen: von_uhrzeit = datetime.datetime.strptime(zeitrahmen["von_uhrzeit"], "%H:%M") bis_uhrzeit = datetime.datetime.strptime(zeitrahmen["bis_uhrzeit"], "%H:%M") if von_uhrzeit > bis_uhrzeit: raise ValidationError("Ungültige Kombination von Uhrzeiten: " '"von_uhrzeit" liegt nach "bis_uhrzeit"')
def validate_email(email: str): """ Validiert eine E-Mail-Adresse. :raise ValidationError: Typ ist nicht str :raise ValidationError: Zeichenkette ist offensichtlich keine gültige E-Mail-Adresse """ if not isinstance(email, str): raise ValidationError("Muss eine Zeichenkette sein") # https://stackoverflow.com/a/14485817/7350842 if '@' not in parseaddr(email)[1]: raise ValidationError(f"Ungültige E-Mail-Adresse {json.dumps(email)}")
def validate_datum(date: str): """ Validiert ein Datum im erwarteten Format "30.11.1970". :raise ValidationError: Typ ist nicht str :raise ValidationError: Zeichenkette hat nicht das richtige Format """ if not isinstance(date, str): raise ValidationError("Muss eine Zeichenkette sein") try: datetime.datetime.strptime(date, "%d.%m.%Y") except ValueError as exc: raise ValidationError(str(exc)) from exc
def validate_uhrzeit(time: str): """ Validiert eine Uhrzeit im erwarteten Format "14:35". :raise ValidationError: Typ ist nicht str :raise ValidationError: Zeichenkette hat nicht das richtige Format """ if not isinstance(time, str): raise ValidationError("Muss eine Zeichenkette sein") try: datetime.datetime.strptime(time, "%H:%M") except ValueError as exc: raise ValidationError(str(exc)) from exc
def validate_telegram(telegram: dict): if not isinstance(telegram, dict): raise ValidationError("Muss ein Dictionary sein") for key, value in telegram.items(): try: if key == "api_token": validate_telegram_api_token(value) elif key == "chat_id": validate_telegram_chat_id(value) else: raise ValidationError(f"Nicht unterstützter Key") except ValidationError as exc: raise ValidationError( f"Ungültiger Key {json.dumps(key)}:\n{str(exc)}")
def validate_pushover(pushover: dict): if not isinstance(pushover, dict): raise ValidationError("Muss ein Dictionary sein") for key, value in pushover.items(): try: if key == "app_token": validate_pushover_app_token(value) elif key == "user_key": validate_pushover_user_key(value) else: raise ValidationError(f"Nicht unterstützter Key") except ValidationError as exc: raise ValidationError( f"Ungültiger Key {json.dumps(key)}:\n{str(exc)}")
def validate_notifications(notifications: dict): if not isinstance(notifications, dict): raise ValidationError("Muss ein Dictionary sein") for key, value in notifications.items(): try: if key == "pushover": validate_pushover(value) elif key == "telegram": validate_telegram(value) else: raise ValidationError(f"Nicht unterstützter Key") except ValidationError as exc: raise ValidationError( f"Ungültiger Key {json.dumps(key)}:\n{str(exc)}")
def __check_werte(self, kontaktdaten: dict): """ Prüft mithilfe von den kontakt_tools ob alle Daten da und gültig sind Args: kontaktdaten (dict): Kontaktdaten Raises: ValidationError: Daten Fehlerhaft MissingValuesError: Daten Fehlen """ kontakt_tools.check_kontaktdaten(kontaktdaten, self.modus) if self.modus == Modus.TERMIN_SUCHEN: kontakt_tools.validate_kontaktdaten(kontaktdaten) elif self.modus == Modus.CODE_GENERIEREN: kontakt_tools.validate_plz_impfzentren( kontaktdaten["plz_impfzentren"]) kontakt_tools.validate_email( kontaktdaten["kontakt"]["notificationReceiver"]) try: kontakt_tools.validate_phone(kontaktdaten["kontakt"]["phone"]) except ValidationError as error: raise ValidationError( "Telefonnummer: +49 nicht vergessen") from error
def validate_phone(phone: str): """ Validiert Telefonnummer auf: Typ, Präfix, "leer" Args: phone (str): Telefonnummer Raises: ValidationError: Typ ist nicht str ValidationError: Telefonnummer ungültig """ if not isinstance(phone, str): raise ValidationError("Muss eine Zeichenkette sein") if not re.match(r"^\+49[1-9][0-9]+$", phone): raise ValidationError(f"Ungültige Telefonnummer {json.dumps(phone)}")
def validate_wochentag(wochentag: str): """ Validiert einen Wochentag. Erlaubt sind "Montag" bis "Sonntag". Es sind auch Präfixe vom Wochentag-Namen zulässig, solange diese mindestens zwei Zeichen lang sind, z. B. "Mo", "Mon", "Mont", usw. :raise ValidationError: Typ ist nicht str :raise ValidationError: Zeichenkette hat nicht das richtige Format """ if not isinstance(wochentag, str): raise ValidationError("Muss eine Zeichenkette sein") try: decode_wochentag(wochentag) except ValueError as exc: raise ValidationError(str(exc)) from exc
def validate_plz(plz: str): """ Validiert die PLZ auf: Typ, Länge, "leer" Args: plz (str): PLZ Raises: ValidationError: PLZ ist kein String ValidationError: Besteht nicht aus genau 5 Ziffern """ if not isinstance(plz, str): raise ValidationError("Muss eine Zeichenkette sein") if not re.match(f"^{5 * '[0-9]'}$", plz): raise ValidationError( f"Ungültige PLZ {json.dumps(plz)} - muss aus genau 5 Ziffern bestehen")
def validate_code(code: str): """ Überprüft, ob der Code Valide ist Args: code (str): impf-code Raises: ValidationError: Code ist keine Zeichenkette oder entspricht nicht dem Schema """ if not isinstance(code, str): raise ValidationError("Muss eine Zeichenkette sein") c = "[0-9a-zA-Z]" if not re.match(f"^{4 * c}-{4 * c}-{4 * c}$", code): raise ValidationError( f"{json.dumps(code)} entspricht nicht dem Schema \"XXXX-XXXX-XXXX\"" )
def validate_email(email: str): """ Validiert eine E-Mail-Adresse. :raise ValidationError: Typ ist nicht str :raise ValidationError: Zeichenkette ist offensichtlich keine gültige E-Mail-Adresse """ if not isinstance(email, str): raise ValidationError("Muss eine Zeichenkette sein") # https://stackoverflow.com/a/14485817/7350842 parsed_email = parseaddr(email)[1] if '@' not in parsed_email: raise ValidationError(f"Ungültige E-Mail-Adresse {json.dumps(email)}") # Gmail erlaubt Plus-Zeichen (https://support.google.com/a/users/answer/9308648?hl=en), # der Impfterminservice leider nicht. if '+' in parsed_email: raise ValidationError( f"Ungültige E-Mail-Adresse {json.dumps(email)} (Plus-Zeichen nicht möglich)")
def validate_hausnummer(hausnummer: str): """ Validiert Hausnummer auf: Typ, Länge, "leer" Args: hausnummer (str): hausnummer Raises: ValidationError: Typ ist nicht str ValidationError: Zeichenkette ist länger als 20 Zeichen ValidationError: Zeichenkette ist leer """ if not isinstance(hausnummer, str): raise ValidationError("Muss eine Zeichenkette sein") if len(hausnummer) > 20: raise ValidationError( f"Hausnummer {json.dumps(hausnummer)} ist zu lang - maximal 20 Zeichen erlaubt") if hausnummer.strip() == "": raise ValidationError("Hausnummer ist leer")
def validate_codes(codes: list): """ Validiert eine Liste an Vermittlungscodes vom Schema XXXX-XXXX-XXXX :raise ValidationError: Typ ist nicht list :raise ValidationError: Liste ist leer :raise ValidationError: Liste enthält vom Schema abweichendes Element """ if not isinstance(codes, list): raise ValidationError("Muss eine Liste sein") if len(codes) == 0: raise ValidationError("Darf keine leere Liste sein") for code in codes: if not isinstance(code, str): raise ValidationError("Darf nur Zeichenketten enthalten") c = "[0-9a-zA-Z]" if not re.match(f"^{4 * c}-{4 * c}-{4 * c}$", code): raise ValidationError( f"{json.dumps(code)} entspricht nicht dem Schema \"XXXX-XXXX-XXXX\"")
def validate_plz_impfzentren(plz_impfzentren: list): """ Validiert eine Gruppe von PLZs mithilfe con validate_plz Args: plz_impfzentren (dict): PLZs Raises: ValidationError: PLZs ist keine Liste """ if not isinstance(plz_impfzentren, list): raise ValidationError("Muss eine Liste sein") for plz in plz_impfzentren: validate_plz(plz)
def __get_kontaktdaten(self, modus: Modus) -> dict: """ Ladet die Kontakdaten aus dem in der GUI hinterlegten Pfad Args: modus (Modus): Abhängig vom Modus werden nicht alle Daten benötigt. Returns: dict: Kontakdaten """ if not os.path.isfile(self.pfad_kontaktdaten): self.kontaktdaten_erstellen(modus) kontaktdaten = kontak_tools.get_kontaktdaten(self.pfad_kontaktdaten) if not self.__check_old_kontakt_version(kontaktdaten): raise ValidationError("\"zeitrahmen\" fehlt -> Alte Version") return kontaktdaten
def validate_kontakt(kontakt: dict): """ Validiert "kontakt"-Key aus Kontaktdaten. Leere Werte werden als Fehler angesehen. :raise ValidationError: Typ ist nicht dict :raise ValidationError: Einer der enthaltenen Keys ist unbekannt :raise ValidationError: Eine der enthaltenen Values ist ungültig """ if not isinstance(kontakt, dict): raise ValidationError("Muss ein Dictionary sein") for key, value in kontakt.items(): try: if key in ["anrede", "vorname", "nachname", "strasse", "ort"]: if not isinstance(value, str): raise ValidationError("Muss eine Zeichenkette sein") if value.strip() == "": raise ValidationError(f"Darf nicht leer sein") elif key == "plz": validate_plz(value) elif key == "hausnummer": validate_hausnummer(value) elif key == "phone": validate_phone(value) elif key == "notificationChannel": if value != "email": raise ValidationError("Muss auf \"email\" gesetzt werden") elif key == "notificationReceiver": validate_email(value) else: raise ValidationError(f"Nicht unterstützter Key") except ValidationError as exc: raise ValidationError( f"Ungültiger Key {json.dumps(key)}:\n{str(exc)}")