async def add_slave_device(properties: GenericJSONDict) -> slaves_devices.Slave: properties = dict(properties) # Work on copy, don't mess up incoming argument scheme = properties.pop('scheme') host = properties.pop('host') port = properties.pop('port') path = properties.pop('path') admin_password = properties.pop('admin_password', None) admin_password_hash = properties.pop('admin_password_hash', None) poll_interval = properties.pop('poll_interval', 0) listen_enabled = properties.pop('listen_enabled', None) # Look for slave duplicate for slave in slaves_devices.get_all(): if (slave.get_scheme() == scheme and slave.get_host() == host and slave.get_port() == port and slave.get_path() == path): raise core_api.APIError(400, 'duplicate-device') if poll_interval and listen_enabled: raise core_api.APIError(400, 'listening-and-polling') # Ensure admin password is supplied, in a way or another if admin_password is None and admin_password_hash is None: raise core_api.APIError(400, 'missing-field', field='admin_password') try: slave = await slaves_devices.add( scheme, host, port, path, poll_interval, listen_enabled, admin_password=admin_password, admin_password_hash=admin_password_hash, **properties ) except (core_responses.HostUnreachable, core_responses.NetworkUnreachable, core_responses.UnresolvableHostname) as e: raise core_api.APIError(502, 'unreachable') from e except core_responses.ConnectionRefused as e: raise core_api.APIError(502, 'connection-refused') from e except core_responses.InvalidJson as e: raise core_api.APIError(502, 'invalid-device') from e except core_responses.Timeout as e: raise core_api.APIError(504, 'device-timeout') from e except slaves_exceptions.InvalidDevice as e: raise core_api.APIError(502, 'invalid-device') from e except slaves_exceptions.NoListenSupport as e: raise core_api.APIError(400, 'no-listen-support') from e except slaves_exceptions.DeviceAlreadyExists as e: raise core_api.APIError(400, 'duplicate-device') from e except core_api.APIError: raise except core_responses.HTTPError as e: # We need to treat the 401/403 slave responses as a 400 if e.code in (401, 403): raise core_api.APIError(400, 'forbidden') from e raise core_api.APIError.from_http_error(e) from e except Exception as e: raise slaves_exceptions.adapt_api_error(e) from e return slave
async def set_port_attrs(port: core_ports.BasePort, attrs: GenericJSONDict, ignore_extra_attrs: bool) -> None: non_modifiable_attrs = await port.get_non_modifiable_attrs() def unexpected_field_code(field: str) -> str: if field in non_modifiable_attrs: return 'attribute-not-modifiable' else: return 'no-such-attribute' schema = await port.get_schema() if ignore_extra_attrs: schema = dict(schema) schema['additionalProperties'] = True # Ignore non-existent and non-modifiable attributes core_api_schema.validate( attrs, schema, unexpected_field_code=unexpected_field_code, unexpected_field_name='attribute' ) # Step validation attrdefs = await port.get_attrdefs() for name, value in attrs.items(): attrdef = attrdefs.get(name) if attrdef is None: continue step = attrdef.get('step') min_ = attrdef.get('min') if None not in (step, min_) and step != 0 and (value - min_) % step: raise core_api.APIError(400, 'invalid-field', field=name) errors_by_name = {} async def set_attr(attr_name: str, attr_value: Attribute) -> None: core_api.logger.debug('setting attribute %s = %s on %s', attr_name, json_utils.dumps(attr_value), port) try: await port.set_attr(attr_name, attr_value) except Exception as e1: errors_by_name[attr_name] = e1 value = attrs.pop('value', None) if attrs: await asyncio.wait([set_attr(n, v) for n, v in attrs.items()]) if errors_by_name: name, error = next(iter(errors_by_name.items())) if isinstance(error, core_api.APIError): raise error elif isinstance(error, core_ports.InvalidAttributeValue): raise core_api.APIError(400, 'invalid-field', field=name, details=error.details) elif isinstance(error, core_ports.PortTimeout): raise core_api.APIError(504, 'port-timeout') elif isinstance(error, core_ports.PortError): raise core_api.APIError(502, 'port-error', code=str(error)) else: # Transform any unhandled exception into APIError(500) raise core_api.APIError(500, 'unexpected-error', message=str(error)) from error # If value is supplied among attrs, use it to update port value, but in background and ignoring any errors if value is not None and port.is_enabled(): asyncio.create_task(port.write_transformed_value(value, reason=core_ports.CHANGE_REASON_API)) await port.save()