Add type annotations and improve coding style.
authorPhilipp Spitzer <philipp@spitzer.priv.at>
Mon, 26 Jul 2021 20:27:30 +0000 (22:27 +0200)
committerPhilipp Spitzer <philipp@spitzer.priv.at>
Mon, 26 Jul 2021 20:27:30 +0000 (22:27 +0200)
wrpylib/wrvalidators.py

index a4fbdbf578ff3e142060f2d9499d9de37b3239fe..48bc6b3cdabfe1fdab36e854153218ee8506987a 100644 (file)
@@ -12,12 +12,18 @@ import urllib.parse
 import re
 from collections import OrderedDict, namedtuple
 from email.errors import HeaderParseError
+from typing import Tuple, Optional, List, Any, Callable, Union, TypeVar, Dict
 
 import mwparserfromhell  # https://github.com/earwig/mwparserfromhell
 
 from wrpylib.mwmarkup import format_template_table
 
 
+T = TypeVar("T")  # use for generic type annotations
+E = TypeVar("E")  # use for generic type annotations
+N = TypeVar("N")  # use for generic type annotations
+
+
 # FromToConverter type
 # --------------------
 
@@ -28,22 +34,23 @@ FromToConverter = namedtuple('FromToConverter', ['from_str', 'to_str'])
 # optional converter
 # ------------------
 
-def opt_from_str(value, from_str, empty=None):
+def opt_from_str(value: str, from_str: Callable[[str], T], empty: E = None) -> Union[T, E]:
     """Makes the converter `from_str` "optional"
     by replacing the empty string with a predefined value (default: None)."""
     return empty if value == '' else from_str(value)
 
 
-def opt_to_str(value, to_str, empty=None):
+def opt_to_str(value: Union[T, E], to_str: Callable[[T], str], empty: E = None) -> str:
     return '' if value == empty else to_str(value)
 
 
 # "no" converter
 # --------------
 
-def no_german_from_str(value, from_str, use_tuple=True, no_value=None):
+def no_german_from_str(value: str, from_str: Callable[[str], T], use_tuple: bool = True, no_value: N = None) \
+        -> Union[Tuple[bool, Union[T, N]], T, N]:
     """Makes it possible to have "Nein" as special value. If use_tuple is True, a tuple is returned. The first
-    entry of the tuple is False in case the value is "Nein", otherwiese the first value is True. The second value is
+    entry of the tuple is False in case the value is "Nein", otherwise the first value is True. The second value is
     no_value in case of the value being "Nein", otherwise it is the result of from_str(value).
     If use_tuple is False, no_value is returned in case the value is "Nein", otherwise the result of from_str(value)."""
     if value == 'Nein':
@@ -51,7 +58,8 @@ def no_german_from_str(value, from_str, use_tuple=True, no_value=None):
     return (True, from_str(value)) if use_tuple else from_str(value)
 
 
-def no_german_to_str(value, to_str, use_tuple=True, no_value=None):
+def no_german_to_str(value: Union[Tuple[bool, Union[T, N]], T, N], to_str: Callable[[T], str], use_tuple: bool = True,
+                     no_value: N = None) -> str:
     if use_tuple:
         if not value[0]:
             return 'Nein'
@@ -65,11 +73,23 @@ def no_german_to_str(value, to_str, use_tuple=True, no_value=None):
 # "optional"/"no" converter
 # -------------------------
 
-def opt_no_german_from_str(value, from_str, use_tuple=True, no_value=None, empty=(None, None)):
+def opt_no_german_from_str(value: str, from_str: Callable[[str], T], use_tuple: bool = True, no_value: N = None,
+                           empty: E = (None, None)) -> Union[Tuple[bool, Union[T, N]], T, N, E]:
+    """
+    'abc' -> (True, from_str('abc')) or from_str('abc')
+    'Nein' -> (False, no_value) or no_value
+    '' -> empty
+    """
     return opt_from_str(value, lambda v: no_german_from_str(v, from_str, use_tuple, no_value), empty)
 
 
-def opt_no_german_to_str(value, to_str, use_tuple=True, no_value=None, empty=(None, None)):
+def opt_no_german_to_str(value: Union[Tuple[bool, Union[T, N]], T, N, E], to_str: Callable[[T], str], use_tuple=True,
+                         no_value: N = None, empty: E = (None, None)) -> str:
+    """
+    (True, 'abc') -> to_value('abc')
+    (False, no_value) -> 'Nein'
+    empty -> ''
+    """
     return opt_to_str(value, lambda v: no_german_to_str(v, to_str, use_tuple, no_value), empty)
 
 
@@ -79,49 +99,50 @@ def opt_no_german_to_str(value, to_str, use_tuple=True, no_value=None, empty=(No
 def choice_from_str(value, choices):
     """Returns the value if it is a member of the choices iterable."""
     if value not in choices:
-        raise ValueError('{} is an invalid value')
+        raise ValueError(f"'{value}' is an invalid value")
     return value
 
 
 # dictkey converter
 # -----------------
 
-def dictkey_from_str(value, key_str_dict):
+def dictkey_from_str(value: str, key_str_dict: Dict[T, str]) -> T:
     """Returns the key of an entry in the key_str_dict if the value of the entry corresponds to the given value."""
     try:
         return dict(list(zip(key_str_dict.values(), key_str_dict.keys())))[value]
     except KeyError:
-        raise ValueError("Invalid value '{}'".format(value))
+        raise ValueError(f"Invalid value '{value}'")
 
 
-def dictkey_to_str(value, key_str_dict):
+def dictkey_to_str(value: T, key_str_dict: Dict[T, str]) -> str:
     try:
         return key_str_dict[value]
     except KeyError:
-        raise ValueError("Invalid value '{}'".format(value))
+        raise ValueError(f"Invalid value '{value}'")
 
 
 # enum/"list" converter
 # ---------------------
 
-def enum_from_str(value, from_str, separator=';', min_len=0):
+def enum_from_str(value: str, from_str: Callable[[str], T], separator: str = ';', min_len: int = 0) -> List[T]:
     """Semicolon separated list of entries with the same "type"."""
     values = value.split(separator)
     if len(values) == 1 and values[0] == '':
         values = []
     if len(values) < min_len:
-        raise ValueError('at least {} entry/entries have to be in the enumeration'.format(min_len))
+        raise ValueError(f'at least {min_len} entry/entries have to be in the enumeration')
     return list(map(from_str, map(str.strip, values)))
 
 
-def enum_to_str(value, to_str, separator='; '):
+def enum_to_str(value: List[T], to_str: Callable[[T], str], separator='; ') -> str:
     return separator.join(map(to_str, value))
 
 
 # value/comment converter
 # -----------------------
 
-def value_comment_from_str(value, value_from_str, comment_from_str, comment_optional=False):
+def value_comment_from_str(value: str, value_from_str: Callable[[str], T], comment_from_str: Callable[[str], E],
+                           comment_optional: bool = False) -> Tuple[T, E]:
     """Makes it possible to have a mandatory comment in parenthesis at the end of the string."""
     comment = ''
     if value.endswith(')'):
@@ -145,46 +166,47 @@ def value_comment_from_str(value, value_from_str, comment_from_str, comment_opti
                 raise ValueError('mandatory comment not found')
     else:
         if not comment_optional:
-            raise ValueError('mandatory comment not found in "{}"'.format(value))
+            raise ValueError(f'mandatory comment not found in "{value}"')
     return value_from_str(value), comment_from_str(comment)
 
 
-def value_comment_to_str(value, value_to_str, comment_to_str, comment_optional=False):
+def value_comment_to_str(value: Tuple[T, E], value_to_str: Callable[[T], str], comment_to_str: Callable[[E], str],
+                         comment_optional: bool = False) -> str:
     left = value_to_str(value[0])
     comment = comment_to_str(value[1])
     if len(comment) > 0 or not comment_optional:
-        comment = '({})'.format(comment)
+        comment = f'({comment})'
     if len(left) == 0:
         return comment
     if len(comment) == 0:
         return left
-    return '{} {}'.format(left, comment)
+    return f'{left} {comment}'
 
 
 # string converter
 # ----------------
 
-def str_from_str(value):
+def str_from_str(value: str) -> str:
     """Converter that takes any string and returns it as string without validation.
     In other words, this function does nothing and just returns its argument."""
     return value
 
 
-def str_to_str(value):
+def str_to_str(value: str) -> str:
     return value
 
 
-def req_str_from_str(value):
+def req_str_from_str(value: str) -> str:
     if value == '':
         raise ValueError('missing required value')
     return str_from_str(value)
 
 
-def opt_str_from_str(value):
+def opt_str_from_str(value: str) -> Optional[str]:
     return opt_from_str(value, str_from_str)
 
 
-def opt_str_to_str(value):
+def opt_str_to_str(value: Optional[str]) -> str:
     return opt_to_str(value, str_to_str)
 
 
@@ -194,13 +216,13 @@ opt_str_converter = FromToConverter(opt_str_from_str, opt_str_to_str)
 # optional no or string converter
 # -------------------------------
 
-def opt_no_or_str_from_str(value):
+def opt_no_or_str_from_str(value: str) -> Tuple[Optional[bool], Optional[str]]:
     """
     'Nein' => (False, None); 'Nur Wochenende' => (True, 'Nur Wochenende'); 'Ja' => (True, 'Ja'); '' => (None, None)"""
     return opt_no_german_from_str(value, req_str_from_str)
 
 
-def opt_no_or_str_to_str(value):
+def opt_no_or_str_to_str(value: Tuple[Optional[bool], Optional[str]]) -> str:
     return opt_no_german_to_str(value, str_to_str)
 
 
@@ -210,38 +232,38 @@ opt_no_or_str_converter = FromToConverter(opt_no_or_str_from_str, opt_no_or_str_
 # integer converter
 # -----------------
 
-def int_from_str(value, min=None, max=None):
+def int_from_str(value: str, minimum: Optional[int] = None, maximum: Optional[int] = None) -> int:
     """Converter that takes a string representation of an integer and returns the integer.
     :param value: string representation of an integer
-    :param min: If not None, the integer has to be at least min
-    :param max: If not None, the integer has to be no more than max
+    :param minimum: If not None, the integer has to be at least min
+    :param maximum: If not None, the integer has to be no more than max
     """
     value = int(value)
-    if min is not None and value < min:
-        raise ValueError('{} must be >= than {}'.format(value, min))
-    if max is not None and value > max:
-        raise ValueError('{} must be <= than {}'.format(value, max))
+    if minimum is not None and value < minimum:
+        raise ValueError(f'{value} must be >= than {minimum}')
+    if maximum is not None and value > maximum:
+        raise ValueError(f'{value} must be <= than {maximum}')
     return value
 
 
-def int_to_str(value):
+def int_to_str(value: int) -> str:
     return str(value)
 
 
-def opt_int_from_str(value, min=None, max=None):
-    return opt_from_str(value, lambda val: int_from_str(val, min, max))
+def opt_int_from_str(value: str, minimum: Optional[int] = None, maximum: Optional[int] = None) -> Optional[int]:
+    return opt_from_str(value, lambda val: int_from_str(val, minimum, maximum))
 
 
-def opt_int_to_str(value):
+def opt_int_to_str(value: Optional[int]) -> str:
     return opt_to_str(value, int_to_str)
 
 
-def opt_uint_from_str(value, min=0, max=None):
+def opt_uint_from_str(value: str, minimum: int = 0, maximum: Optional[int] = None) -> Optional[int]:
     """Optional positive integer."""
-    return opt_int_from_str(value, min, max)
+    return opt_int_from_str(value, minimum, maximum)
 
 
-def opt_uint_to_str(value):
+def opt_uint_to_str(value: Optional[int]) -> str:
     return opt_int_to_str(value)
 
 
@@ -254,19 +276,19 @@ opt_uint_converter = FromToConverter(opt_uint_from_str, opt_uint_to_str)
 BOOL_GERMAN = OrderedDict([(False, 'Nein'), (True, 'Ja')])
 
 
-def bool_german_from_str(value):
+def bool_german_from_str(value: str) -> bool:
     return dictkey_from_str(value, BOOL_GERMAN)
 
 
-def bool_german_to_str(value):
+def bool_german_to_str(value: bool) -> str:
     return dictkey_to_str(value, BOOL_GERMAN)
 
 
-def opt_bool_german_from_str(value):
+def opt_bool_german_from_str(value: str) -> Optional[bool]:
     return opt_from_str(value, bool_german_from_str)
 
 
-def opt_bool_german_to_str(value):
+def opt_bool_german_to_str(value: Optional[bool]) -> str:
     return opt_to_str(value, bool_german_to_str)
 
 
@@ -279,19 +301,19 @@ opt_bool_german_converter = FromToConverter(opt_bool_german_from_str, opt_bool_g
 TRISTATE_GERMAN = OrderedDict([(0.0, 'Nein'), (0.5, 'Teilweise'), (1.0, 'Ja')])
 
 
-def tristate_german_from_str(value):
+def tristate_german_from_str(value: str) -> float:
     return dictkey_from_str(value, TRISTATE_GERMAN)
 
 
-def tristate_german_to_str(value):
+def tristate_german_to_str(value: float) -> str:
     return dictkey_to_str(value, TRISTATE_GERMAN)
 
 
-def opt_tristate_german_from_str(value):
+def opt_tristate_german_from_str(value: str) -> Optional[float]:
     return opt_from_str(value, tristate_german_from_str)
 
 
-def opt_tristate_german_to_str(value):
+def opt_tristate_german_to_str(value: Optional[float]) -> str:
     return opt_to_str(value, tristate_german_to_str)
 
 
@@ -301,22 +323,23 @@ opt_tristate_german_converter = FromToConverter(opt_tristate_german_from_str, op
 # tristate with comment converter
 # -------------------------------
 
-def opt_tristate_german_comment_from_str(value):
+def opt_tristate_german_comment_from_str(value: str) -> Tuple[Optional[float], Optional[str]]:
     """Ja, Nein or Teilweise, optionally with comment in parenthesis."""
     return value_comment_from_str(value, opt_tristate_german_from_str, opt_str_from_str, True)
 
 
-def opt_tristate_german_comment_to_str(value):
+def opt_tristate_german_comment_to_str(value: Tuple[Optional[float], Optional[str]]) -> str:
     return value_comment_to_str(value, opt_tristate_german_to_str, opt_str_to_str, True)
 
 
-opt_tristate_german_comment_converter = FromToConverter(opt_tristate_german_comment_from_str, opt_tristate_german_comment_to_str)
+opt_tristate_german_comment_converter = FromToConverter(opt_tristate_german_comment_from_str,
+                                                        opt_tristate_german_comment_to_str)
 
 
 # url converter
 # -------------
 
-def url_from_str(value):
+def url_from_str(value: str) -> str:
     result = urllib.parse.urlparse(value)
     if result.scheme not in ['http', 'https']:
         raise ValueError('scheme has to be http or https')
@@ -325,14 +348,14 @@ def url_from_str(value):
     return value
 
 
-def url_to_str(value):
+def url_to_str(value: str) -> str:
     return value
 
 
 # webauskunft converter
 # ---------------------
 
-def webauskunft_from_str(value):
+def webauskunft_from_str(value: str) -> Tuple[Optional[bool], Optional[str]]:
     """Converts a URL or 'Nein' to a tuple
     'http://www.example.com/' -> (True, 'http://www.example.com/')
     'Nein' -> (False, None)
@@ -344,7 +367,7 @@ def webauskunft_from_str(value):
     return opt_no_german_from_str(value, url_from_str)
 
 
-def webauskunft_to_str(value):
+def webauskunft_to_str(value: Tuple[Optional[bool], Optional[str]]) -> str:
     return opt_no_german_to_str(value, url_to_str)
 
 
@@ -354,7 +377,7 @@ webauskunft_converter = FromToConverter(webauskunft_from_str, webauskunft_to_str
 # wikipage converter
 # ------------------
 
-def wikipage_from_str(value):
+def wikipage_from_str(value: str) -> str:
     """Validates wiki page name like '[[Birgitzer Alm]]'.
     The page is not checked for existence.
     An empty string is an error.
@@ -365,11 +388,11 @@ def wikipage_from_str(value):
     return value
 
 
-def wikipage_to_str(value):
+def wikipage_to_str(value: str) -> str:
     return value
 
 
-def opt_wikipage_enum_from_str(value):
+def opt_wikipage_enum_from_str(value: str) -> Optional[List[str]]:
     """Validates a list of wiki pages like '[[Birgitzer Alm]]; [[Kemater Alm]]'.
     '[[Birgitzer Alm]]; [[Kemater Alm]]' => ['[[Birgitzer Alm]]', '[[Kemater Alm]]']
     '[[Birgitzer Alm]]'                  => ['[[Birgitzer Alm]]']
@@ -379,7 +402,7 @@ def opt_wikipage_enum_from_str(value):
     return opt_no_german_from_str(value, lambda val: enum_from_str(val, wikipage_from_str), False, [], None)
 
 
-def opt_wikipage_enum_to_str(value):
+def opt_wikipage_enum_to_str(value: Optional[List[str]]) -> str:
     return opt_no_german_to_str(value, lambda val: enum_to_str(val, wikipage_to_str), False, [], None)
 
 
@@ -389,7 +412,7 @@ opt_wikipage_enum_converter = FromToConverter(opt_wikipage_enum_from_str, opt_wi
 # email converter
 # ---------------
 
-def email_from_str(value):
+def email_from_str(value: str) -> str:
     """Takes an email address like 'office@example.com', checks it for correctness and returns it again as string."""
     try:
         email.headerregistry.Address(addr_spec=value)
@@ -398,11 +421,11 @@ def email_from_str(value):
     return value
 
 
-def email_to_str(value):
+def email_to_str(value: str) -> str:
     return str(value)
 
 
-def masked_email_from_str(value, mask='(at)', masked_only=False):
+def masked_email_from_str(value: str, mask='(at)', masked_only=False) -> Tuple[str, bool]:
     """Converts an email address that is possibly masked. Returns a tuple. The first parameter is the un-masked
     email address as string, the second is a boolean telling whether the address was masked."""
     unmasked = value.replace(mask, '@')
@@ -412,7 +435,7 @@ def masked_email_from_str(value, mask='(at)', masked_only=False):
     return email_from_str(unmasked), was_masked
 
 
-def masked_email_to_str(value, mask='(at)'):
+def masked_email_to_str(value: Tuple[str, bool], mask='(at)') -> str:
     """Value is a tuple. The first entry is the email address, the second one is a boolean telling whether the
     email address should be masked."""
     email, do_masking = value
@@ -422,12 +445,19 @@ def masked_email_to_str(value, mask='(at)'):
     return email
 
 
-def emails_from_str(value):
-    return opt_no_german_from_str(value, lambda val: enum_from_str(val, lambda v: value_comment_from_str(v, masked_email_from_str, opt_str_from_str, True)), False, [], None)
+def emails_from_str(value: str) -> Optional[List[Tuple[str, str]]]:
+    return opt_no_german_from_str(
+        value,
+        lambda val:
+            enum_from_str(val, lambda v: value_comment_from_str(v, masked_email_from_str, opt_str_from_str, True)),
+        False, [], None)
 
 
-def emails_to_str(value):
-    return opt_no_german_to_str(value, lambda val: enum_to_str(val, lambda v: value_comment_to_str(v, masked_email_to_str, opt_str_to_str, True)), False, [], None)
+def emails_to_str(value: Optional[List[Tuple[str, str]]]) -> str:
+    return opt_no_german_to_str(
+        value,
+        lambda val: enum_to_str(val, lambda v: value_comment_to_str(v, masked_email_to_str, opt_str_to_str, True)),
+        False, [], None)
 
 
 emails_converter = FromToConverter(emails_from_str, emails_to_str)
@@ -436,29 +466,46 @@ emails_converter = FromToConverter(emails_from_str, emails_to_str)
 # phone converter
 # ---------------
 
-def phone_number_from_str(value):
+def phone_number_from_str(value: str) -> str:
     match = re.match(r'\+\d+(-\d+)*$', value)
     if match is None:
         raise ValueError('invalid format of phone number - use something like +43-699-1234567')
     return value
 
 
-def phone_number_to_str(value):
+def phone_number_to_str(value: str) -> str:
     return value
 
 
-def opt_phone_comment_enum_from_str(value, comment_optional=False):
-    return opt_no_german_from_str(value, lambda val: enum_from_str(val, lambda v: value_comment_from_str(v, phone_number_from_str, opt_str_from_str if comment_optional else req_str_from_str, comment_optional)), False, [], None)
+def opt_phone_comment_enum_from_str(value: str, comment_optional: bool = False) -> Optional[List[Tuple[str, str]]]:
+    return opt_no_german_from_str(
+        value,
+        lambda val: enum_from_str(
+            val,
+            lambda v:
+                value_comment_from_str(
+                    v,
+                    phone_number_from_str,
+                    opt_str_from_str if comment_optional else req_str_from_str,
+                    comment_optional
+                )
+        ), False, [], None)
 
 
-def opt_phone_comment_enum_to_str(value, comment_optional=False):
-    return opt_no_german_to_str(value, lambda val: enum_to_str(val, lambda v: value_comment_to_str(v, phone_number_to_str, opt_str_to_str if comment_optional else str_to_str, comment_optional)), False, [], None)
+def opt_phone_comment_enum_to_str(value: Optional[List[Tuple[str, str]]], comment_optional: bool = False) -> str:
+    return opt_no_german_to_str(
+        value,
+        lambda val: enum_to_str(
+            val, lambda v: value_comment_to_str(
+                v, phone_number_to_str, opt_str_to_str if comment_optional else str_to_str, comment_optional)),
+        False, [], None)
 
 
 opt_phone_comment_enum_converter = FromToConverter(opt_phone_comment_enum_from_str, opt_phone_comment_enum_to_str)
 
 
-opt_phone_comment_opt_enum_converter = FromToConverter(lambda value: opt_phone_comment_enum_from_str(value, True), lambda value: opt_phone_comment_enum_to_str(value, True))
+opt_phone_comment_opt_enum_converter = FromToConverter(lambda value: opt_phone_comment_enum_from_str(value, True),
+                                                       lambda value: opt_phone_comment_enum_to_str(value, True))
 
 
 # longitude/latitude converter
@@ -482,11 +529,11 @@ def lonlat_to_str(value: LonLat) -> str:
     return '{:.6f} N {:.6f} E'.format(value.lat, value.lon)
 
 
-def opt_lonlat_from_str(value):
+def opt_lonlat_from_str(value: str) -> LonLat:
     return opt_from_str(value, lonlat_from_str, lonlat_none)
 
 
-def opt_lonlat_to_str(value):
+def opt_lonlat_to_str(value: LonLat) -> str:
     return opt_to_str(value, lonlat_to_str, lonlat_none)
 
 
@@ -499,19 +546,19 @@ opt_lonlat_converter = FromToConverter(opt_lonlat_from_str, opt_lonlat_to_str)
 DIFFICULTY_GERMAN = OrderedDict([(1, 'leicht'), (2, 'mittel'), (3, 'schwer')])
 
 
-def difficulty_german_from_str(value):
+def difficulty_german_from_str(value: str) -> int:
     return dictkey_from_str(value, DIFFICULTY_GERMAN)
 
 
-def difficulty_german_to_str(value):
+def difficulty_german_to_str(value: int) -> str:
     return dictkey_to_str(value, DIFFICULTY_GERMAN)
 
 
-def opt_difficulty_german_from_str(value):
+def opt_difficulty_german_from_str(value: str) -> Optional[int]:
     return opt_from_str(value, difficulty_german_from_str)
 
 
-def opt_difficulty_german_to_str(value):
+def opt_difficulty_german_to_str(value: Optional[int]) -> str:
     return opt_to_str(value, difficulty_german_to_str)
 
 
@@ -524,19 +571,19 @@ opt_difficulty_german_converter = FromToConverter(opt_difficulty_german_from_str
 AVALANCHES_GERMAN = OrderedDict([(1, 'kaum'), (2, 'selten'), (3, 'gelegentlich'), (4, 'häufig')])
 
 
-def avalanches_german_from_str(value):
+def avalanches_german_from_str(value: str) -> int:
     return dictkey_from_str(value, AVALANCHES_GERMAN)
 
 
-def avalanches_german_to_str(value):
+def avalanches_german_to_str(value: int) -> str:
     return dictkey_to_str(value, AVALANCHES_GERMAN)
 
 
-def opt_avalanches_german_from_str(value):
+def opt_avalanches_german_from_str(value: str) -> Optional[int]:
     return opt_from_str(value, avalanches_german_from_str)
 
 
-def opt_avalanches_german_to_str(value):
+def opt_avalanches_german_to_str(value: Optional[int]) -> str:
     return opt_to_str(value, avalanches_german_to_str)
 
 
@@ -549,7 +596,7 @@ opt_avalanches_german_converter = FromToConverter(opt_avalanches_german_from_str
 LIFT_GERMAN = ['Sessellift', 'Gondel', 'Linienbus', 'Taxi', 'Sonstige']
 
 
-def lift_german_from_str(value):
+def lift_german_from_str(value) -> Optional[List[Tuple[str, Optional[str]]]]:
     """Checks a lift_details property. It is a value comment property with the following
     values allowed:
     'Sessellift'
@@ -568,11 +615,34 @@ def lift_german_from_str(value):
     'Sessellift; Taxi'                       <=> [('Sessellift', None), ('Taxi', None)]
     'Sessellift (Wochenende); Taxi (6 Euro)' <=> [('Sessellift', 'Wochenende'), ('Taxi', '6 Euro')]
     """
-    return opt_no_german_from_str(value, lambda value_enum: enum_from_str(value_enum, lambda value_comment: value_comment_from_str(value_comment, lambda v: choice_from_str(v, LIFT_GERMAN), opt_str_from_str, comment_optional=True)), use_tuple=False, no_value=[], empty=None)
-
-
-def lift_german_to_str(value):
-    return opt_no_german_to_str(value, lambda value_enum: enum_to_str(value_enum, lambda value_comment: value_comment_to_str(value_comment, str_to_str, opt_str_to_str, comment_optional=True)), use_tuple=False, no_value=[], empty=None)
+    return opt_no_german_from_str(
+        value,
+        lambda value_enum:
+            enum_from_str(
+                value_enum,
+                lambda value_comment:
+                    value_comment_from_str(
+                        value_comment,
+                        lambda v: choice_from_str(v, LIFT_GERMAN),
+                        opt_str_from_str,
+                        comment_optional=True)
+            ), use_tuple=False, no_value=[], empty=None)
+
+
+def lift_german_to_str(value: Optional[List[Tuple[str, Optional[str]]]]) -> str:
+    return opt_no_german_to_str(
+        value,
+        lambda value_enum:
+            enum_to_str(
+                value_enum,
+                lambda value_comment:
+                    value_comment_to_str(
+                        value_comment,
+                        str_to_str,
+                        opt_str_to_str,
+                        comment_optional=True)
+            ),
+        use_tuple=False, no_value=[], empty=None)
 
 
 lift_german_converter = FromToConverter(lift_german_from_str, lift_german_to_str)
@@ -581,26 +651,28 @@ lift_german_converter = FromToConverter(lift_german_from_str, lift_german_to_str
 # public transport converter
 # --------------------------
 
-PUBLIC_TRANSPORT_GERMAN = OrderedDict([(1, 'Sehr gut'), (2, 'Gut'), (3, 'Mittelmäßig'), (4, 'Schlecht'), (5, 'Nein'), (6, 'Ja')])
+PUBLIC_TRANSPORT_GERMAN = OrderedDict(
+    [(1, 'Sehr gut'), (2, 'Gut'), (3, 'Mittelmäßig'), (4, 'Schlecht'), (5, 'Nein'), (6, 'Ja')])
 
 
-def public_transport_german_from_str(value):
+def public_transport_german_from_str(value: str) -> int:
     return dictkey_from_str(value, PUBLIC_TRANSPORT_GERMAN)
 
 
-def public_transport_german_to_str(value):
+def public_transport_german_to_str(value: int) -> str:
     return dictkey_to_str(value, PUBLIC_TRANSPORT_GERMAN)
 
 
-def opt_public_transport_german_from_str(value):
+def opt_public_transport_german_from_str(value: str) -> Optional[int]:
     return opt_from_str(value, public_transport_german_from_str)
 
 
-def opt_public_transport_german_to_str(value):
+def opt_public_transport_german_to_str(value: Optional[int]) -> str:
     return opt_to_str(value, public_transport_german_to_str)
 
 
-opt_public_transport_german_converter = FromToConverter(opt_public_transport_german_from_str, opt_public_transport_german_to_str)
+opt_public_transport_german_converter = FromToConverter(opt_public_transport_german_from_str,
+                                                        opt_public_transport_german_to_str)
 
 
 # cachet converter
@@ -609,15 +681,15 @@ opt_public_transport_german_converter = FromToConverter(opt_public_transport_ger
 CACHET_REGEXP = [r'(Tiroler Naturrodelbahn-Gütesiegel) ([12]\d{3}) (leicht|mittel|schwer)$']
 
 
-def single_cachet_german_from_str(value):
+def single_cachet_german_from_str(value: str) -> Tuple[str, str, str]:
     for pattern in CACHET_REGEXP:
         match = re.match(pattern, value)
         if match:
             return match.groups()
-    raise ValueError("'{}' is no valid cachet".format(value))
+    raise ValueError(f"'{value}' is no valid cachet")
 
 
-def single_cachet_german_to_str(value):
+def single_cachet_german_to_str(value: Tuple[str, str, str]) -> str:
     return ' '.join(value)
 
 
@@ -639,11 +711,14 @@ cachet_german_converter = FromToConverter(cachet_german_from_str, cachet_german_
 # night light days converter
 # --------------------------
 
-def nightlightdays_from_str(value):
-    return value_comment_from_str(value, lambda val: opt_from_str(val, lambda v: int_from_str(v, min=0, max=7)), opt_str_from_str, comment_optional=True)
+def nightlightdays_from_str(value: str) -> Tuple[int, Optional[str]]:
+    return value_comment_from_str(
+        value,
+        lambda val:
+            opt_from_str(val, lambda v: int_from_str(v, minimum=0, maximum=7)), opt_str_from_str, comment_optional=True)
 
 
-def nightlightdays_to_str(value):
+def nightlightdays_to_str(value: Tuple[int, Optional[str]]) -> str:
     return value_comment_to_str(value, lambda val: opt_to_str(val, int_to_str), opt_str_to_str, comment_optional=True)
 
 
@@ -653,16 +728,29 @@ nightlightdays_converter = FromToConverter(nightlightdays_from_str, nightlightda
 # string with optional comment enum/list converter
 # ------------------------------------------------
 
-def opt_str_opt_comment_enum_from_str(value):
+def opt_str_opt_comment_enum_from_str(value: str) -> Optional[List[Tuple[str, Optional[str]]]]:
     """The value can be an empty string, 'Nein' or a semicolon-separated list of strings with optional comments.
     ''                                       => None
     'Nein'                                   => []
     'Talstation (nur mit Ticket); Schneealm' => [('Talstation', 'nur mit Ticket'), ('Schneealm', None)]"""
-    return opt_no_german_from_str(value, lambda val: enum_from_str(val, lambda v: value_comment_from_str(v, req_str_from_str, opt_str_from_str, True)), False, [], None)
-
-
-def opt_str_opt_comment_enum_to_str(value):
-    return opt_no_german_to_str(value, lambda val: enum_to_str(val, lambda v: value_comment_to_str(v, str_to_str, opt_str_to_str, True)), False, [], None)
+    return opt_no_german_from_str(
+        value,
+        lambda val:
+            enum_from_str(
+                val,
+                lambda v:
+                    value_comment_from_str(v, req_str_from_str, opt_str_from_str, True)
+            ), False, [], None)
+
+
+def opt_str_opt_comment_enum_to_str(value: Optional[List[Tuple[str, Optional[str]]]]) -> str:
+    return opt_no_german_to_str(
+        value,
+        lambda val:
+            enum_to_str(
+                val, lambda v:
+                    value_comment_to_str(v, str_to_str, opt_str_to_str, True)
+            ), False, [], None)
 
 
 opt_str_opt_comment_enum_converter = FromToConverter(opt_str_opt_comment_enum_from_str, opt_str_opt_comment_enum_to_str)
@@ -729,26 +817,26 @@ RODELBAHNBOX_TEMPLATE_NAME = 'Rodelbahnbox'
 
 
 RODELBAHNBOX_DICT = OrderedDict([
-    ('Position', opt_lonlat_converter), # '47.583333 N 15.75 E'
-    ('Position oben', opt_lonlat_converter), # '47.583333 N 15.75 E'
-    ('Höhe oben', opt_uint_converter), # '2000'
-    ('Position unten', opt_lonlat_converter), # '47.583333 N 15.75 E'
-    ('Höhe unten', opt_uint_converter), # '1200'
-    ('Länge', opt_uint_converter), # 3500
-    ('Schwierigkeit', opt_difficulty_german_converter), # 'mittel'
-    ('Lawinen', opt_avalanches_german_converter), # 'kaum'
-    ('Betreiber', opt_str_converter), # 'Max Mustermann'
-    ('Öffentliche Anreise', opt_public_transport_german_converter), # 'Mittelmäßig'
-    ('Aufstieg möglich', opt_bool_german_converter), # 'Ja'
-    ('Aufstieg getrennt', opt_tristate_german_comment_converter), # 'Ja'
-    ('Gehzeit', opt_uint_converter), # 90
-    ('Aufstiegshilfe', lift_german_converter), # 'Gondel (unterer Teil)'
+    ('Position', opt_lonlat_converter),  # '47.583333 N 15.75 E'
+    ('Position oben', opt_lonlat_converter),  # '47.583333 N 15.75 E'
+    ('Höhe oben', opt_uint_converter),  # '2000'
+    ('Position unten', opt_lonlat_converter),  # '47.583333 N 15.75 E'
+    ('Höhe unten', opt_uint_converter),  # '1200'
+    ('Länge', opt_uint_converter),  # 3500
+    ('Schwierigkeit', opt_difficulty_german_converter),  # 'mittel'
+    ('Lawinen', opt_avalanches_german_converter),  # 'kaum'
+    ('Betreiber', opt_str_converter),  # 'Max Mustermann'
+    ('Öffentliche Anreise', opt_public_transport_german_converter),  # 'Mittelmäßig'
+    ('Aufstieg möglich', opt_bool_german_converter),  # 'Ja'
+    ('Aufstieg getrennt', opt_tristate_german_comment_converter),  # 'Ja'
+    ('Gehzeit', opt_uint_converter),  # 90
+    ('Aufstiegshilfe', lift_german_converter),  # 'Gondel (unterer Teil)'
     ('Beleuchtungsanlage', opt_tristate_german_comment_converter),
-    ('Beleuchtungstage', nightlightdays_converter), # '3 (Montag, Mittwoch, Freitag)'
-    ('Rodelverleih', opt_str_opt_comment_enum_converter), # 'Talstation Serlesbahnan'
-    ('Gütesiegel', cachet_german_converter), # 'Tiroler Naturrodelbahn-Gütesiegel 2009 mittel'
-    ('Webauskunft', webauskunft_converter), # 'http://www.nösslachhütte.at/page9.php'
-    ('Telefonauskunft', opt_phone_comment_enum_converter), # '+43-664-5487520 (Mitterer Alm)'
+    ('Beleuchtungstage', nightlightdays_converter),  # '3 (Montag, Mittwoch, Freitag)'
+    ('Rodelverleih', opt_str_opt_comment_enum_converter),  # 'Talstation Serlesbahnan'
+    ('Gütesiegel', cachet_german_converter),  # 'Tiroler Naturrodelbahn-Gütesiegel 2009 mittel'
+    ('Webauskunft', webauskunft_converter),  # 'http://www.nösslachhütte.at/page9.php'
+    ('Telefonauskunft', opt_phone_comment_enum_converter),  # '+43-664-5487520 (Mitterer Alm)'
     ('Bild', opt_str_converter),
     ('In Übersichtskarte', opt_bool_german_converter),
     ('Forumid', opt_uint_converter)
@@ -799,7 +887,7 @@ GASTHAUSBOX_DICT = OrderedDict([
 
 def gasthausbox_from_template(template):
     """Returns an ordered dict."""
-    return wikibox_from_template(template, GASTHAUSBOX_TEMPLATE_NAME, GASTHAUSBOX_DICT)
+    return wikibox_from_template(template, GASTHAUSBOX_DICT)
 
 
 def gasthausbox_to_template(value):