class GenericAssetTypeSchema(ma.SQLAlchemySchema): """ GenericAssetType schema, with validations. """ id = ma.auto_field() name = fields.Str() description = ma.auto_field() class Meta: model = GenericAssetType
class SensorSchemaMixin(Schema): """ Base sensor schema. Here we include all fields which are implemented by timely_beliefs.SensorDBMixin All classes inheriting from timely beliefs sensor don't need to repeat these. In a while, this schema can represent our unified Sensor class. When subclassing, also subclass from `ma.SQLAlchemySchema` and add your own DB model class, e.g.: class Meta: model = Asset """ name = ma.auto_field(required=True) unit = ma.auto_field(required=True) timezone = ma.auto_field() event_resolution = fields.TimeDelta(required=True, precision="minutes") entity_address = fields.String(dump_only=True) @validates("unit") def validate_unit(self, unit: str): if not is_valid_unit(unit): raise ValidationError(f"Unit '{unit}' cannot be handled.")
class UserSchema(ma.SQLAlchemySchema): """ This schema lists fields we support through this API (e.g. no password). """ class Meta: model = UserModel @validates("timezone") def validate_timezone(self, timezone): if timezone not in all_timezones: raise ValidationError(f"Timezone {timezone} doesn't exist.") id = ma.auto_field(dump_only=True) email = ma.auto_field(required=True, validate=validate.Email) username = ma.auto_field(required=True) account_id = ma.auto_field(dump_only=True) active = ma.auto_field() timezone = ma.auto_field() flexmeasures_roles = ma.auto_field() last_login_at = AwareDateTimeField()
class GenericAssetSchema(ma.SQLAlchemySchema): """ GenericAsset schema, with validations. """ id = ma.auto_field(dump_only=True) name = fields.Str(required=True) account_id = ma.auto_field() latitude = ma.auto_field() longitude = ma.auto_field() generic_asset_type_id = fields.Integer(required=True) class Meta: model = GenericAsset @validates_schema(skip_on_field_errors=False) def validate_name_is_unique_in_account(self, data, **kwargs): if "name" in data and "account_id" in data: asset = GenericAsset.query.filter( GenericAsset.name == data["name"] and GenericAsset.account_id == data["account_id"] ).one_or_none() if asset: raise ValidationError( f"An asset with the name {data['name']} already exists in this account.", "name", ) @validates("generic_asset_type_id") def validate_generic_asset_type(self, generic_asset_type_id: int): generic_asset_type = GenericAssetType.query.get(generic_asset_type_id) if not generic_asset_type: raise ValidationError( f"GenericAssetType with id {generic_asset_type_id} doesn't exist." ) @validates("account_id") def validate_account(self, account_id: int): account = Account.query.get(account_id) if not account: raise ValidationError(f"Account with Id {account_id} doesn't exist.") @validates("latitude") def validate_latitude(self, latitude: Optional[float]): """Validate optional latitude.""" if latitude is None: return if latitude < -90: raise ValidationError( f"Latitude {latitude} exceeds the minimum latitude of -90 degrees." ) if latitude > 90: raise ValidationError( f"Latitude {latitude} exceeds the maximum latitude of 90 degrees." ) @validates("longitude") def validate_longitude(self, longitude: Optional[float]): """Validate optional longitude.""" if longitude is None: return if longitude < -180: raise ValidationError( f"Longitude {longitude} exceeds the minimum longitude of -180 degrees." ) if longitude > 180: raise ValidationError( f"Longitude {longitude} exceeds the maximum longitude of 180 degrees." )
class AssetSchema(SensorSchemaMixin, ma.SQLAlchemySchema): """ Asset schema, with validations. TODO: deprecate, as it is based on legacy data model. Move some attributes to SensorSchema. """ class Meta: model = Asset @validates("name") def validate_name(self, name: str): asset = Asset.query.filter(Asset.name == name).one_or_none() if asset: raise ValidationError(f"An asset with the name {name} already exists.") @validates("owner_id") def validate_owner(self, owner_id: int): owner = User.query.get(owner_id) if not owner: raise ValidationError(f"Owner with id {owner_id} doesn't exist.") if not owner.account.has_role("Prosumer"): raise ValidationError( "Asset owner's account must have role 'Prosumer'." f" User {owner_id}'s account has roles: {'.'.join([r.name for r in owner.account.account_roles])}." ) @validates("market_id") def validate_market(self, market_id: int): sensor = Sensor.query.get(market_id) if not sensor: raise ValidationError(f"Market with id {market_id} doesn't exist.") @validates("asset_type_name") def validate_asset_type(self, asset_type_name: str): asset_type = AssetType.query.get(asset_type_name) if not asset_type: raise ValidationError(f"Asset type {asset_type_name} doesn't exist.") @validates_schema(skip_on_field_errors=False) def validate_soc_constraints(self, data, **kwargs): if "max_soc_in_mwh" in data and "min_soc_in_mwh" in data: if data["max_soc_in_mwh"] < data["min_soc_in_mwh"]: errors = { "max_soc_in_mwh": "This value must be equal or higher than the minimum soc." } raise ValidationError(errors) id = ma.auto_field() display_name = fields.Str(validate=validate.Length(min=4)) capacity_in_mw = fields.Float(required=True, validate=validate.Range(min=0.0001)) min_soc_in_mwh = fields.Float(validate=validate.Range(min=0)) max_soc_in_mwh = fields.Float(validate=validate.Range(min=0)) soc_in_mwh = ma.auto_field() soc_datetime = ma.auto_field() soc_udi_event_id = ma.auto_field() latitude = fields.Float(required=True, validate=validate.Range(min=-90, max=90)) longitude = fields.Float(required=True, validate=validate.Range(min=-180, max=180)) asset_type_name = ma.auto_field(required=True) owner_id = ma.auto_field(required=True) market_id = ma.auto_field(required=True)