from homeassistant.const import CONF_COMMAND from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow from ..models import Credentials, UserMeta CONF_ARGS = "args" CONF_META = "meta" CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend( { vol.Required(CONF_COMMAND): vol.All( str, os.path.normpath, msg="must be an absolute path" ), vol.Optional(CONF_ARGS, default=None): vol.Any(vol.DefaultTo(list), [str]), vol.Optional(CONF_META, default=False): bool, }, extra=vol.PREVENT_EXTRA, ) _LOGGER = logging.getLogger(__name__) class InvalidAuthError(HomeAssistantError): """Raised when authentication with given credentials fails.""" @AUTH_PROVIDERS.register("command_line") class CommandLineAuthProvider(AuthProvider): """Auth provider validating credentials by calling a command."""
from homeassistant.const import CONF_COMMAND from homeassistant.exceptions import HomeAssistantError from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow from ..models import Credentials, UserMeta CONF_ARGS = "args" CONF_META = "meta" CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend( { vol.Required(CONF_COMMAND): vol.All(str, os.path.normpath, msg="must be an absolute path"), vol.Optional(CONF_ARGS, default=None): vol.Any(vol.DefaultTo(list), [str]), vol.Optional(CONF_META, default=False): bool, }, extra=vol.PREVENT_EXTRA, ) _LOGGER = logging.getLogger(__name__) class InvalidAuthError(HomeAssistantError): """Raised when authentication with given credentials fails.""" @AUTH_PROVIDERS.register("command_line") class CommandLineAuthProvider(AuthProvider):
class Generic2Actor(ActorBase): """A configurable, generic actor for Schedy that can control multiple attributes at once.""" name = "generic2" config_schema_dict = { **ActorBase.config_schema_dict, vol.Optional("attributes", default=None): vol.All( vol.DefaultTo(list), [ vol.All( vol.DefaultTo(dict), { vol.Optional("attribute", default=None): vol.Any( str, None) }, ) ], ), vol.Optional("values", default=None): vol.All( vol.DefaultTo(list), [ vol.All( vol.DefaultTo(dict), { vol.Required("value"): vol.All( [vol.Any(*ALLOWED_VALUE_TYPES)], vol.Coerce(tuple), ), vol.Optional("calls", default=None): vol.All( vol.DefaultTo(list), [ vol.All( vol.DefaultTo(dict), { vol.Required("service"): vol.All( str, lambda v: v.replace(".", "/", 1)), vol.Optional("data", default=None): vol.All(vol.DefaultTo(dict), dict), vol.Optional("include_entity_id", default=True): bool, }, ) ], ), }, ) ], # Sort by number of attributes (descending) for longest prefix matching lambda v: sorted(v, key=lambda k: -len(k["value"])), ), vol.Optional("ignore_case", default=False): bool, } def _find_value_cfg(self, value: T.Tuple) -> T.Any: """Returns the config matching given value or ValueError if none found.""" for value_cfg in self.cfg["values"]: _value = value_cfg["value"] if len(_value) != len(value): continue for idx, attr_value in enumerate(_value): if attr_value not in (WILDCARD_ATTRIBUTE_VALUE, value[idx]): break else: return value_cfg raise ValueError("No configuration for value {!r}".format(value)) def _populate_service_data(self, data: T.Dict, fmt: T.Dict[str, T.Any]) -> None: """Fills in placeholders in the service data definition.""" # pylint: disable=too-many-nested-blocks memo = [data] # type: T.List[T.Union[T.Dict, T.List]] while memo: obj = memo.pop() if isinstance(obj, dict): _iter = obj.items() # type: T.Iterable[T.Tuple[T.Any, T.Any]] elif isinstance(obj, list): _iter = enumerate(obj) else: continue for key, value in _iter: if isinstance(value, str): try: formatted = value.format(fmt) # Convert special values to appropriate type if formatted == "None": obj[key] = None elif formatted == "True": obj[key] = True elif formatted == "False": obj[key] = False else: try: _float = float(formatted) _int = int(formatted) except ValueError: # It's a string value obj[key] = formatted else: # It's an int or float obj[key] = _int if _float == _int else _float except (IndexError, KeyError, ValueError) as err: self.log( "Couldn't format service data {!r} with values " "{!r}: {!r}, omitting data.".format( value, fmt, err), level="ERROR", ) elif isinstance(value, (dict, list)): memo.append(value) def do_send(self) -> None: """Executes the configured services for self._wanted_value.""" value = self._wanted_value # Build formatting data with values of all attributes fmt = {"entity_id": self.entity_id} for idx in range(len(self.cfg["attributes"])): fmt["attr{}".format(idx + 1)] = value[idx] if idx < len(value) else None for call_cfg in self._find_value_cfg(value)["calls"]: service = call_cfg["service"] data = copy.deepcopy(call_cfg["data"]) self._populate_service_data(data, fmt) if call_cfg["include_entity_id"]: data.setdefault("entity_id", self.entity_id) self.log( "Calling service {}, data = {}.".format( repr(service), repr(data)), level="DEBUG", prefix=common.LOG_PREFIX_OUTGOING, ) self.app.call_service(service, **data) def filter_set_value(self, value: T.Tuple) -> T.Any: """Checks whether the actor supports this value.""" if self.cfg["ignore_case"]: value = tuple(v.lower() if isinstance(v, str) else v for v in value) try: self._find_value_cfg(value) except ValueError: self.log("Value {!r} is not known by this actor.".format(value), level="ERROR") return None return value def notify_state_changed(self, attrs: dict) -> T.Any: """Is called when the entity's state changes.""" items = [] for attr_cfg in self.cfg["attributes"]: attr = attr_cfg["attribute"] if attr is None: self.log("Ignoring state change (write-only attribute).", level="DEBUG") return None state = attrs.get(attr) self.log( "Attribute {!r} is {!r}.".format(attr, state), level="DEBUG", prefix=common.LOG_PREFIX_INCOMING, ) if self.cfg["ignore_case"] and isinstance(state, str): state = state.lower() items.append(state) tpl = tuple(items) # Goes from len(tpl) down to 0 for size in range(len(tpl), -1, -1): value = tpl[:size] try: self._find_value_cfg(value) except ValueError: continue return value self.log( "Received state {!r} which is not configured as a value.".format( items), level="WARNING", ) return None @staticmethod def validate_value(value: T.Any) -> T.Any: """Converts lists to tuples.""" if isinstance(value, list): items = tuple(value) elif isinstance(value, tuple): items = value else: items = (value, ) for index, item in enumerate(items): if not isinstance(item, ALLOWED_VALUE_TYPES): raise ValueError( "Value {!r} for {}. attribute must be of one of these types: " "{}".format(item, index + 1, ALLOWED_VALUE_TYPES)) return items