You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
bazarr/libs/knowit/properties/general.py

149 lines
4.6 KiB

import re
import typing
from datetime import timedelta
from decimal import Decimal, InvalidOperation
import babelfish
from knowit.core import Configurable, Property
from knowit.utils import round_decimal
T = typing.TypeVar('T')
class Basic(Property[T]):
"""Basic property to handle int, Decimal and other basic types."""
def __init__(self, *args: str, data_type: typing.Type,
processor: typing.Optional[typing.Callable[[T], T]] = None,
allow_fallback: bool = False, **kwargs):
"""Init method."""
super().__init__(*args, **kwargs)
self.data_type = data_type
self.processor = processor or (lambda x: x)
self.allow_fallback = allow_fallback
def handle(self, value, context: typing.MutableMapping):
"""Handle value."""
if isinstance(value, self.data_type):
return self.processor(value)
try:
return self.processor(self.data_type(value))
except ValueError:
if not self.allow_fallback:
self.report(value, context)
class Duration(Property[timedelta]):
"""Duration property."""
duration_re = re.compile(r'(?P<hours>\d{1,2}):'
r'(?P<minutes>\d{1,2}):'
r'(?P<seconds>\d{1,2})(?:\.'
r'(?P<milliseconds>\d{3})'
r'(?P<microseconds>\d{3})?\d*)?')
def __init__(self, *args: str, resolution: typing.Union[int, Decimal] = 1, **kwargs):
"""Initialize a Duration."""
super().__init__(*args, **kwargs)
self.resolution = resolution
def handle(self, value, context: typing.MutableMapping):
"""Return duration as timedelta."""
if isinstance(value, timedelta):
return value
elif isinstance(value, int):
return timedelta(milliseconds=int(value * self.resolution))
try:
return timedelta(
milliseconds=int(Decimal(value) * self.resolution))
except (ValueError, InvalidOperation):
pass
match = self.duration_re.match(value)
if not match:
self.report(value, context)
return None
params = {
key: int(value)
for key, value in match.groupdict().items()
if value
}
return timedelta(**params)
class Language(Property[babelfish.Language]):
"""Language property."""
def handle(self, value, context: typing.MutableMapping):
"""Handle languages."""
try:
if len(value) == 3:
try:
return babelfish.Language.fromalpha3b(value)
except babelfish.Error:
# Try alpha3t if alpha3b fails
return babelfish.Language.fromalpha3t(value)
return babelfish.Language.fromietf(value)
except (babelfish.Error, ValueError):
pass
try:
return babelfish.Language.fromname(value)
except babelfish.Error:
pass
self.report(value, context)
return babelfish.Language('und')
class Quantity(Property):
"""Quantity is a property with unit."""
def __init__(self, *args: str, unit, data_type=int, **kwargs):
"""Init method."""
super().__init__(*args, **kwargs)
self.unit = unit
self.data_type = data_type
def handle(self, value, context):
"""Handle value with unit."""
if not isinstance(value, self.data_type):
try:
value = self.data_type(value)
except ValueError:
self.report(value, context)
return
if isinstance(value, Decimal):
value = round_decimal(value, min_digits=1, max_digits=3)
return value if context.get('no_units') else value * self.unit
class YesNo(Configurable[str]):
"""Yes or No handler."""
yes_values = ('yes', 'true', '1')
def __init__(self, *args: str, yes=True, no=False, hide_value=None,
config: typing.Optional[
typing.Mapping[str, typing.Mapping]] = None,
config_key: typing.Optional[str] = None,
**kwargs):
"""Init method."""
super().__init__(config or {}, config_key=config_key, *args, **kwargs)
self.yes = yes
self.no = no
self.hide_value = hide_value
def handle(self, value, context):
"""Handle boolean values."""
result = self.yes if str(value).lower() in self.yes_values else self.no
if result == self.hide_value:
return None
return super().handle(result, context) if self.mapping else result