Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Issue 6 entity address scheme improvements 2 (#81)
* Rename entity type for weather sensors * Update domain registration year in docstring * Build and parse sensor entity addresses * Fix pass-through of error response * Add entity address properties to Market and WeatherSensor * Make test util function more flexible * Add marshmallow schema for sensors * Improve test legibility * Move setup of test WeatherSensors to higher conftest.py * Better regex for date specification * Test marshmallow schema for sensors * mypy * Fix variable naming of test util * Update CLI command * Fix tests with deprecation of sqlalchemy RowProxy in 1.4 * Prefer localhost over 127.0.0.1 in entity addresses and strip www. * Introduce fm1 scheme for local part of entity addresses * add docstrings to our API documentation, more explicit handling of no regex matches in parsing ea addresses * also test parsing a fm1 type address * review comments: typos, nomenclature * implement review comments Co-authored-by: F.N. Claessen <felix@seita.nl> Co-authored-by: Nicolas Höning <nicolas@seita.nl> Co-authored-by: Nicolas Höning <iam@nicolashoening.de>
- Loading branch information
1 parent
908f8bb
commit 927aba9
Showing
31 changed files
with
775 additions
and
279 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
from typing import Union | ||
|
||
from marshmallow import fields | ||
|
||
from flexmeasures.api import FMValidationError | ||
from flexmeasures.api.common.utils.api_utils import get_weather_sensor_by | ||
from flexmeasures.utils.entity_address_utils import ( | ||
parse_entity_address, | ||
EntityAddressException, | ||
) | ||
from flexmeasures.data.models.assets import Asset | ||
from flexmeasures.data.models.markets import Market | ||
from flexmeasures.data.models.weather import WeatherSensor | ||
from flexmeasures.data.models.time_series import Sensor | ||
|
||
|
||
class EntityAddressValidationError(FMValidationError): | ||
status = "INVALID_DOMAIN" # USEF error status | ||
|
||
|
||
class SensorField(fields.Str): | ||
"""Field that de-serializes to a Sensor, Asset, Market or WeatherSensor | ||
and serializes back to an entity address (string).""" | ||
|
||
# todo: when Actuators also get an entity address, refactor this class to EntityField, | ||
# where an Entity represents anything with an entity address: we currently foresee Sensors and Actuators | ||
|
||
def __init__( | ||
self, | ||
entity_type: str, | ||
fm_scheme: str, | ||
*args, | ||
**kwargs, | ||
): | ||
""" | ||
:param entity_type: "sensor", "connection", "market" or "weather_sensor" | ||
:param fm_scheme: "fm0" or "fm1" | ||
""" | ||
self.entity_type = entity_type | ||
self.fm_scheme = fm_scheme | ||
super().__init__(*args, **kwargs) | ||
|
||
def _deserialize( # noqa: C901 todo: the noqa can probably be removed after refactoring Asset/Market/WeatherSensor to Sensor | ||
self, value, attr, obj, **kwargs | ||
) -> Union[Sensor, Asset, Market, WeatherSensor]: | ||
"""De-serialize to a Sensor, Asset, Market or WeatherSensor.""" | ||
# TODO: After refactoring, unify 3 generic_asset cases -> 1 sensor case | ||
try: | ||
ea = parse_entity_address(value, self.entity_type, self.fm_scheme) | ||
if self.fm_scheme == "fm0": | ||
if self.entity_type == "connection": | ||
asset = Asset.query.filter(Asset.id == ea["asset_id"]).one_or_none() | ||
if asset is not None: | ||
return asset | ||
else: | ||
raise EntityAddressValidationError( | ||
f"Asset with entity address {value} doesn't exist." | ||
) | ||
elif self.entity_type == "market": | ||
market = Market.query.filter( | ||
Market.name == ea["market_name"] | ||
).one_or_none() | ||
if market is not None: | ||
return market | ||
else: | ||
raise EntityAddressValidationError( | ||
f"Market with entity address {value} doesn't exist." | ||
) | ||
elif self.entity_type == "weather_sensor": | ||
weather_sensor = get_weather_sensor_by( | ||
ea["weather_sensor_type_name"], ea["latitude"], ea["longitude"] | ||
) | ||
if weather_sensor is not None and isinstance( | ||
weather_sensor, WeatherSensor | ||
): | ||
return weather_sensor | ||
else: | ||
raise EntityAddressValidationError( | ||
f"Weather sensor with entity address {value} doesn't exist." | ||
) | ||
else: | ||
if self.entity_type == "sensor": | ||
sensor = Sensor.query.filter( | ||
Sensor.id == ea["sensor_id"] | ||
).one_or_none() | ||
if sensor is not None: | ||
return sensor | ||
else: | ||
raise EntityAddressValidationError( | ||
f"Sensor with entity address {value} doesn't exist." | ||
) | ||
elif self.entity_type == "connection": | ||
asset = Asset.query.filter( | ||
Asset.id == ea["sensor_id"] | ||
).one_or_none() | ||
if asset is not None: | ||
return asset | ||
else: | ||
raise EntityAddressValidationError( | ||
f"Asset with entity address {value} doesn't exist." | ||
) | ||
elif self.entity_type == "market": | ||
market = Market.query.filter( | ||
Market.id == ea["sensor_id"] | ||
).one_or_none() | ||
if market is not None: | ||
return market | ||
else: | ||
raise EntityAddressValidationError( | ||
f"Market with entity address {value} doesn't exist." | ||
) | ||
elif self.entity_type == "weather_sensor": | ||
weather_sensor = WeatherSensor.query.filter( | ||
WeatherSensor.id == ea["sensor_id"] | ||
).one_or_none() | ||
if weather_sensor is not None and isinstance( | ||
weather_sensor, WeatherSensor | ||
): | ||
return weather_sensor | ||
else: | ||
raise EntityAddressValidationError( | ||
f"Weather sensor with entity address {value} doesn't exist." | ||
) | ||
except EntityAddressException as eae: | ||
raise EntityAddressValidationError(str(eae)) | ||
return NotImplemented | ||
|
||
def _serialize( | ||
self, value: Union[Sensor, Asset, Market, WeatherSensor], attr, data, **kwargs | ||
): | ||
"""Serialize to an entity address.""" | ||
if self.fm_scheme == "fm0": | ||
return value.entity_address_fm0 | ||
else: | ||
return value.entity_address |
Oops, something went wrong.