Python client for the Baby Tracker sync server, reverse engineered from the Android app (4.53), and a HACS-installable Home Assistant integration built on it. Each baby gets a log calendar and summary sensors, and actions add entries that sync back to the group. Reading has been run against the real server. Uploading and running inside real Home Assistant have not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
102 lines
2.3 KiB
Python
102 lines
2.3 KiB
Python
"""Just enough of voluptuous for the integration's schemas."""
|
|
|
|
|
|
class Invalid(Exception):
|
|
pass
|
|
|
|
|
|
class _Marker:
|
|
def __init__(self, key, default=None):
|
|
self.key = key
|
|
self.default = default
|
|
|
|
def __hash__(self):
|
|
return hash(self.key)
|
|
|
|
def __eq__(self, other):
|
|
return self.key == getattr(other, "key", other)
|
|
|
|
|
|
class Required(_Marker):
|
|
pass
|
|
|
|
|
|
class Optional(_Marker):
|
|
pass
|
|
|
|
|
|
def _check(validator, value):
|
|
if isinstance(validator, type):
|
|
if not isinstance(value, validator):
|
|
raise Invalid(f"expected {validator.__name__}")
|
|
return value
|
|
return validator(value)
|
|
|
|
|
|
class Schema:
|
|
def __init__(self, schema):
|
|
self.schema = schema
|
|
|
|
def __call__(self, data):
|
|
result = {}
|
|
known = set()
|
|
for marker, validator in self.schema.items():
|
|
known.add(marker.key)
|
|
if marker.key in data:
|
|
result[marker.key] = _check(validator, data[marker.key])
|
|
elif isinstance(marker, Required):
|
|
raise Invalid(f"required key not provided: {marker.key}")
|
|
elif marker.default is not None:
|
|
result[marker.key] = marker.default
|
|
extra = set(data) - known
|
|
if extra:
|
|
raise Invalid(f"extra keys not allowed: {sorted(extra)}")
|
|
return result
|
|
|
|
|
|
class All:
|
|
def __init__(self, *validators):
|
|
self.validators = validators
|
|
|
|
def __call__(self, value):
|
|
for validator in self.validators:
|
|
value = _check(validator, value)
|
|
return value
|
|
|
|
|
|
def In(options):
|
|
def validate(value):
|
|
if value not in options:
|
|
raise Invalid(f"{value} not in {options}")
|
|
return value
|
|
|
|
return validate
|
|
|
|
|
|
def Coerce(kind):
|
|
def validate(value):
|
|
try:
|
|
return kind(value)
|
|
except (TypeError, ValueError) as err:
|
|
raise Invalid(str(err)) from err
|
|
|
|
return validate
|
|
|
|
|
|
def Range(min=None):
|
|
def validate(value):
|
|
if min is not None and value < min:
|
|
raise Invalid(f"{value} below {min}")
|
|
return value
|
|
|
|
return validate
|
|
|
|
|
|
def Length(min=None):
|
|
def validate(value):
|
|
if min is not None and len(value) < min:
|
|
raise Invalid("too short")
|
|
return value
|
|
|
|
return validate
|