async def load_from_data(self, data: GenericJSONDict) -> None: attrs_start = ['enabled'] # These will be loaded first, in this order attrs_end = ['expression'] # These will be loaded last, in this order attr_items = data.items() attr_items = [a for a in attr_items if (a[0] not in attrs_start) and (a[0] not in attrs_end)] attr_items_start = [] for n in attrs_start: v = data.get(n) if v is not None: attr_items_start.append((n, v)) # Sort the rest of the attributes alphabetically attr_items.sort(key=lambda i: i[0]) attr_items_end = [] for n in attrs_end: v = data.get(n) if v is not None: attr_items_end.append((n, v)) attr_items = attr_items_start + attr_items + attr_items_end for name, value in attr_items: if name in ('id', 'value'): continue # Value is also among the persisted fields try: self.debug('loading %s = %s', name, json_utils.dumps(value)) await self.set_attr(name, value) except Exception as e: self.error('failed to set attribute %s = %s: %s', name, json_utils.dumps(value), e) # Value if await self.is_persisted() and data.get('value') is not None: self._value = data['value'] self.debug('loaded value = %s', json_utils.dumps(self._value)) if await self.is_writable(): # Write the just-loaded value to the port value = self._value if self._transform_write: value = await self.adapt_value_type(self._transform_write.eval()) await self.write_value(value) elif self.is_enabled(): try: value = await self.read_transformed_value() if value is not None: self._value = value self.debug('read value = %s', json_utils.dumps(self._value)) except Exception as e: self.error('failed to read value: %s', e, exc_info=True)
def _record_from_db(cls, db_record: GenericJSONDict, fields: Optional[Set[str]] = None) -> Record: if fields is not None: return {k: (cls._value_from_db(v) if k != 'id' else v) for k, v in db_record.items() if k in fields} else: return {k: (cls._value_from_db(v) if k != 'id' else v) for k, v in db_record.items()}
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()
def _record_from_db(cls, db_record: GenericJSONDict) -> Record: return { k: (cls._value_from_db(v) if k != 'id' else v) for k, v in db_record.items() }