From d32823671a8d6e6cc1bf3711c9dc23c81aec9e9c Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Mon, 12 Sep 2022 11:07:48 +0200 Subject: [PATCH 01/17] Make it possible to show sensor data from other assets in the same account Signed-off-by: F.N. Claessen --- flexmeasures/data/models/generic_assets.py | 6 ++++-- flexmeasures/data/services/sensors.py | 11 +++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/flexmeasures/data/models/generic_assets.py b/flexmeasures/data/models/generic_assets.py index ae35ed90e..7e077366e 100644 --- a/flexmeasures/data/models/generic_assets.py +++ b/flexmeasures/data/models/generic_assets.py @@ -436,12 +436,14 @@ def sensors_to_show(self) -> List["Sensor"]: # noqa F821 if not self.has_attribute("sensors_to_show"): return self.sensors[:2] - from flexmeasures.data.services.sensors import get_public_sensors + from flexmeasures.data.services.sensors import get_sensors, get_public_sensors sensor_ids = self.get_attribute("sensors_to_show") sensor_map = { sensor.id: sensor - for sensor in self.sensors + get_public_sensors(sensor_ids) + for sensor in self.sensors + + get_sensors(self.account_id) + + get_public_sensors(sensor_ids) if sensor.id in sensor_ids } diff --git a/flexmeasures/data/services/sensors.py b/flexmeasures/data/services/sensors.py index e6e4bad65..a5d8fa71c 100644 --- a/flexmeasures/data/services/sensors.py +++ b/flexmeasures/data/services/sensors.py @@ -7,14 +7,25 @@ def get_sensors( + account_id: int | None = None, account_name: str | None = None, ) -> list[Sensor]: """Return a list of Sensor objects. + :param account_id: optionally, filter by account id. :param account_name: optionally, filter by account name. """ sensor_query = Sensor.query + if account_id is not None: + account = Account.query.filter_by(id=account_id).one_or_none() + if not account: + raise NotFound(f"There is no account with id {account_id}!") + sensor_query = ( + sensor_query.join(GenericAsset) + .filter(Sensor.generic_asset_id == GenericAsset.id) + .filter(GenericAsset.owner == account) + ) if account_name is not None: account = Account.query.filter(Account.name == account_name).one_or_none() if not account: From 4167241518d4126e6ca60888474ad81f7456216d Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Mon, 12 Sep 2022 11:10:49 +0200 Subject: [PATCH 02/17] Expand docstring Signed-off-by: F.N. Claessen --- flexmeasures/data/models/generic_assets.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/flexmeasures/data/models/generic_assets.py b/flexmeasures/data/models/generic_assets.py index 7e077366e..fad485cfe 100644 --- a/flexmeasures/data/models/generic_assets.py +++ b/flexmeasures/data/models/generic_assets.py @@ -431,6 +431,12 @@ def search_beliefs( def sensors_to_show(self) -> List["Sensor"]: # noqa F821 """Sensors to show, as defined by the sensors_to_show attribute. + Sensors to show are defined as a list of sensor ids, which + is set by the "sensors_to_show" field of the asset's "attributes" column. + Valid sensors either belong to the asset itself, to other assets in the same account, + or to public assets. + + Defaults to two of the asset's sensors. """ if not self.has_attribute("sensors_to_show"): From cb37002d09c46bafa6e19283dd2d4625322e8f41 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Mon, 12 Sep 2022 11:12:23 +0200 Subject: [PATCH 03/17] changelog entry Signed-off-by: F.N. Claessen --- documentation/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/changelog.rst b/documentation/changelog.rst index 2cc1e7613..03a997c0b 100644 --- a/documentation/changelog.rst +++ b/documentation/changelog.rst @@ -9,6 +9,7 @@ New features ------------- * Hit the replay button to replay what happened, available on the sensor and asset pages [see `PR #463 `_] +* The asset page also allows to show sensor data from other assets that belong to the same account [see `PR #500 `_] Bugfixes ----------- From fb40536790449e6e2a784f562b885cd165dd8351 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Mon, 12 Sep 2022 13:21:26 +0200 Subject: [PATCH 04/17] Refactor util function to get sensors for a given account (Account, id or name), and avoid duplicate queries of a known account Signed-off-by: F.N. Claessen --- flexmeasures/api/v3_0/sensors.py | 2 +- flexmeasures/data/models/generic_assets.py | 2 +- flexmeasures/data/services/sensors.py | 28 +++++++++------------- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/flexmeasures/api/v3_0/sensors.py b/flexmeasures/api/v3_0/sensors.py index 0ff900f0a..4449658f8 100644 --- a/flexmeasures/api/v3_0/sensors.py +++ b/flexmeasures/api/v3_0/sensors.py @@ -110,7 +110,7 @@ def index(self, account: Account): :status 403: INVALID_SENDER :status 422: UNPROCESSABLE_ENTITY """ - sensors = get_sensors(account_name=account.name) + sensors = get_sensors(account=account) return sensors_schema.dump(sensors), 200 @route("/data", methods=["POST"]) diff --git a/flexmeasures/data/models/generic_assets.py b/flexmeasures/data/models/generic_assets.py index fad485cfe..0b789d1bd 100644 --- a/flexmeasures/data/models/generic_assets.py +++ b/flexmeasures/data/models/generic_assets.py @@ -448,7 +448,7 @@ def sensors_to_show(self) -> List["Sensor"]: # noqa F821 sensor_map = { sensor.id: sensor for sensor in self.sensors - + get_sensors(self.account_id) + + get_sensors(self.owner) + get_public_sensors(sensor_ids) if sensor.id in sensor_ids } diff --git a/flexmeasures/data/services/sensors.py b/flexmeasures/data/services/sensors.py index a5d8fa71c..e433b2cd5 100644 --- a/flexmeasures/data/services/sensors.py +++ b/flexmeasures/data/services/sensors.py @@ -7,29 +7,23 @@ def get_sensors( - account_id: int | None = None, - account_name: str | None = None, + account: Account | int | str | None = None, ) -> list[Sensor]: """Return a list of Sensor objects. - :param account_id: optionally, filter by account id. - :param account_name: optionally, filter by account name. + :param account: optionally, filter by account by passing an Account, int (account id) or string (account name). """ sensor_query = Sensor.query - if account_id is not None: - account = Account.query.filter_by(id=account_id).one_or_none() - if not account: - raise NotFound(f"There is no account with id {account_id}!") - sensor_query = ( - sensor_query.join(GenericAsset) - .filter(Sensor.generic_asset_id == GenericAsset.id) - .filter(GenericAsset.owner == account) - ) - if account_name is not None: - account = Account.query.filter(Account.name == account_name).one_or_none() - if not account: - raise NotFound(f"There is no account named {account_name}!") + if account is not None: + if isinstance(account, int): + account = Account.query.filter_by(id=account_id).one_or_none() + if not account: + raise NotFound(f"There is no account with id {account_id}!") + elif isinstance(account, str): + account = Account.query.filter_by(name=account_name).one_or_none() + if not account: + raise NotFound(f"There is no account named {account_name}!") sensor_query = ( sensor_query.join(GenericAsset) .filter(Sensor.generic_asset_id == GenericAsset.id) From 386e78a1b4d1e61fbf210c0e6ad7712c284cc5d0 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Mon, 12 Sep 2022 13:26:21 +0200 Subject: [PATCH 05/17] Sensors on a given asset are a subset of sensors on assets with the same owner Signed-off-by: F.N. Claessen --- flexmeasures/data/models/generic_assets.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/flexmeasures/data/models/generic_assets.py b/flexmeasures/data/models/generic_assets.py index 0b789d1bd..76d6dc876 100644 --- a/flexmeasures/data/models/generic_assets.py +++ b/flexmeasures/data/models/generic_assets.py @@ -447,9 +447,7 @@ def sensors_to_show(self) -> List["Sensor"]: # noqa F821 sensor_ids = self.get_attribute("sensors_to_show") sensor_map = { sensor.id: sensor - for sensor in self.sensors - + get_sensors(self.owner) - + get_public_sensors(sensor_ids) + for sensor in get_sensors(self.owner) + get_public_sensors(sensor_ids) if sensor.id in sensor_ids } From 4bcfdf1b9505c9cefba933d8cf0dba2c12d9fc66 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Mon, 12 Sep 2022 13:29:57 +0200 Subject: [PATCH 06/17] Limit query to requested sensor ids (instead of returning all sensors from the same account, which may be a lot) Signed-off-by: F.N. Claessen --- flexmeasures/data/models/generic_assets.py | 3 ++- flexmeasures/data/services/sensors.py | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/flexmeasures/data/models/generic_assets.py b/flexmeasures/data/models/generic_assets.py index 76d6dc876..c99a6dcd6 100644 --- a/flexmeasures/data/models/generic_assets.py +++ b/flexmeasures/data/models/generic_assets.py @@ -447,7 +447,8 @@ def sensors_to_show(self) -> List["Sensor"]: # noqa F821 sensor_ids = self.get_attribute("sensors_to_show") sensor_map = { sensor.id: sensor - for sensor in get_sensors(self.owner) + get_public_sensors(sensor_ids) + for sensor in get_sensors(self.owner, sensor_ids) + + get_public_sensors(sensor_ids) if sensor.id in sensor_ids } diff --git a/flexmeasures/data/services/sensors.py b/flexmeasures/data/services/sensors.py index e433b2cd5..1ef930a0b 100644 --- a/flexmeasures/data/services/sensors.py +++ b/flexmeasures/data/services/sensors.py @@ -8,10 +8,12 @@ def get_sensors( account: Account | int | str | None = None, + sensor_ids: list[int] | None = None, ) -> list[Sensor]: """Return a list of Sensor objects. :param account: optionally, filter by account by passing an Account, int (account id) or string (account name). + :param sensor_ids: optionally, filter by sensor id. """ sensor_query = Sensor.query @@ -29,6 +31,8 @@ def get_sensors( .filter(Sensor.generic_asset_id == GenericAsset.id) .filter(GenericAsset.owner == account) ) + if sensor_ids: + sensor_query = sensor_query.filter(Sensor.id.in_(sensor_ids)) return sensor_query.all() From 983becfadd16afeec125dc669b98974a0758818c Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Mon, 12 Sep 2022 13:36:27 +0200 Subject: [PATCH 07/17] flake8 Signed-off-by: F.N. Claessen --- flexmeasures/data/services/sensors.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flexmeasures/data/services/sensors.py b/flexmeasures/data/services/sensors.py index 1ef930a0b..5b7da985a 100644 --- a/flexmeasures/data/services/sensors.py +++ b/flexmeasures/data/services/sensors.py @@ -19,13 +19,13 @@ def get_sensors( if account is not None: if isinstance(account, int): - account = Account.query.filter_by(id=account_id).one_or_none() + account = Account.query.filter_by(id=account).one_or_none() if not account: - raise NotFound(f"There is no account with id {account_id}!") + raise NotFound(f"There is no account with id {account}!") elif isinstance(account, str): - account = Account.query.filter_by(name=account_name).one_or_none() + account = Account.query.filter_by(name=account).one_or_none() if not account: - raise NotFound(f"There is no account named {account_name}!") + raise NotFound(f"There is no account named {account}!") sensor_query = ( sensor_query.join(GenericAsset) .filter(Sensor.generic_asset_id == GenericAsset.id) From 0993e34bb53ade6387d8bf84f4b648497d7675ac Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Mon, 12 Sep 2022 15:02:24 +0200 Subject: [PATCH 08/17] Remove redundant if statement Signed-off-by: F.N. Claessen --- flexmeasures/data/models/generic_assets.py | 1 - 1 file changed, 1 deletion(-) diff --git a/flexmeasures/data/models/generic_assets.py b/flexmeasures/data/models/generic_assets.py index c99a6dcd6..967dafa62 100644 --- a/flexmeasures/data/models/generic_assets.py +++ b/flexmeasures/data/models/generic_assets.py @@ -449,7 +449,6 @@ def sensors_to_show(self) -> List["Sensor"]: # noqa F821 sensor.id: sensor for sensor in get_sensors(self.owner, sensor_ids) + get_public_sensors(sensor_ids) - if sensor.id in sensor_ids } # Return sensors in the order given by the sensors_to_show attribute From fe2479aaa763bff03b32699349f7f77d81f92778 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Mon, 12 Sep 2022 15:04:13 +0200 Subject: [PATCH 09/17] Refactor: rename function Signed-off-by: F.N. Claessen --- flexmeasures/api/v3_0/sensors.py | 4 ++-- flexmeasures/data/models/generic_assets.py | 7 +++++-- flexmeasures/data/services/sensors.py | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/flexmeasures/api/v3_0/sensors.py b/flexmeasures/api/v3_0/sensors.py index 4449658f8..9cc48e34c 100644 --- a/flexmeasures/api/v3_0/sensors.py +++ b/flexmeasures/api/v3_0/sensors.py @@ -41,7 +41,7 @@ from flexmeasures.data.schemas.sensors import SensorSchema, SensorIdField from flexmeasures.data.schemas.units import QuantityField from flexmeasures.data.schemas import AwareDateTimeField -from flexmeasures.data.services.sensors import get_sensors +from flexmeasures.data.services.sensors import get_account_sensors from flexmeasures.data.services.scheduling import create_scheduling_job from flexmeasures.utils.time_utils import duration_isoformat from flexmeasures.utils.unit_utils import ur @@ -110,7 +110,7 @@ def index(self, account: Account): :status 403: INVALID_SENDER :status 422: UNPROCESSABLE_ENTITY """ - sensors = get_sensors(account=account) + sensors = get_account_sensors(account=account) return sensors_schema.dump(sensors), 200 @route("/data", methods=["POST"]) diff --git a/flexmeasures/data/models/generic_assets.py b/flexmeasures/data/models/generic_assets.py index 967dafa62..e5ab94590 100644 --- a/flexmeasures/data/models/generic_assets.py +++ b/flexmeasures/data/models/generic_assets.py @@ -442,12 +442,15 @@ def sensors_to_show(self) -> List["Sensor"]: # noqa F821 if not self.has_attribute("sensors_to_show"): return self.sensors[:2] - from flexmeasures.data.services.sensors import get_sensors, get_public_sensors + from flexmeasures.data.services.sensors import ( + get_account_sensors, + get_public_sensors, + ) sensor_ids = self.get_attribute("sensors_to_show") sensor_map = { sensor.id: sensor - for sensor in get_sensors(self.owner, sensor_ids) + for sensor in get_account_sensors(self.owner, sensor_ids) + get_public_sensors(sensor_ids) } diff --git a/flexmeasures/data/services/sensors.py b/flexmeasures/data/services/sensors.py index 5b7da985a..c5c1064e6 100644 --- a/flexmeasures/data/services/sensors.py +++ b/flexmeasures/data/services/sensors.py @@ -6,7 +6,7 @@ from flexmeasures.data.models.generic_assets import GenericAsset -def get_sensors( +def get_account_sensors( account: Account | int | str | None = None, sensor_ids: list[int] | None = None, ) -> list[Sensor]: From 9b6fec9d82074d1a04e69e2c7d5922e3c1681c32 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Mon, 12 Sep 2022 16:17:47 +0200 Subject: [PATCH 10/17] Repurpose function by making it mandatory to pass at least one account, or None for public sensors (which makes get_public_sensors obsolete) Signed-off-by: F.N. Claessen --- flexmeasures/api/v3_0/sensors.py | 2 +- flexmeasures/data/models/generic_assets.py | 11 +++-- flexmeasures/data/services/sensors.py | 52 +++++++--------------- 3 files changed, 23 insertions(+), 42 deletions(-) diff --git a/flexmeasures/api/v3_0/sensors.py b/flexmeasures/api/v3_0/sensors.py index 9cc48e34c..f0c963599 100644 --- a/flexmeasures/api/v3_0/sensors.py +++ b/flexmeasures/api/v3_0/sensors.py @@ -110,7 +110,7 @@ def index(self, account: Account): :status 403: INVALID_SENDER :status 422: UNPROCESSABLE_ENTITY """ - sensors = get_account_sensors(account=account) + sensors = get_account_sensors(accounts=[account]) return sensors_schema.dump(sensors), 200 @route("/data", methods=["POST"]) diff --git a/flexmeasures/data/models/generic_assets.py b/flexmeasures/data/models/generic_assets.py index e5ab94590..e53c6442d 100644 --- a/flexmeasures/data/models/generic_assets.py +++ b/flexmeasures/data/models/generic_assets.py @@ -442,16 +442,15 @@ def sensors_to_show(self) -> List["Sensor"]: # noqa F821 if not self.has_attribute("sensors_to_show"): return self.sensors[:2] - from flexmeasures.data.services.sensors import ( - get_account_sensors, - get_public_sensors, - ) + from flexmeasures.data.services.sensors import get_account_sensors sensor_ids = self.get_attribute("sensors_to_show") sensor_map = { sensor.id: sensor - for sensor in get_account_sensors(self.owner, sensor_ids) - + get_public_sensors(sensor_ids) + for sensor in get_account_sensors( + accounts=[self.owner, None], # include public sensors + sensor_ids=sensor_ids, + ) } # Return sensors in the order given by the sensors_to_show attribute diff --git a/flexmeasures/data/services/sensors.py b/flexmeasures/data/services/sensors.py index c5c1064e6..52d13562f 100644 --- a/flexmeasures/data/services/sensors.py +++ b/flexmeasures/data/services/sensors.py @@ -1,52 +1,34 @@ from __future__ import annotations -from werkzeug.exceptions import NotFound +import sqlalchemy as sa from flexmeasures import Sensor, Account from flexmeasures.data.models.generic_assets import GenericAsset def get_account_sensors( - account: Account | int | str | None = None, + accounts: list[Account | None], sensor_ids: list[int] | None = None, ) -> list[Sensor]: - """Return a list of Sensor objects. + """Return a list of Sensor objects that belong to any of the given accounts. - :param account: optionally, filter by account by passing an Account, int (account id) or string (account name). + :param accounts: select only sensors from this list of accounts + (include None to select sensors that belong to a public asset). :param sensor_ids: optionally, filter by sensor id. """ - sensor_query = Sensor.query - - if account is not None: - if isinstance(account, int): - account = Account.query.filter_by(id=account).one_or_none() - if not account: - raise NotFound(f"There is no account with id {account}!") - elif isinstance(account, str): - account = Account.query.filter_by(name=account).one_or_none() - if not account: - raise NotFound(f"There is no account named {account}!") - sensor_query = ( - sensor_query.join(GenericAsset) - .filter(Sensor.generic_asset_id == GenericAsset.id) - .filter(GenericAsset.owner == account) - ) - if sensor_ids: - sensor_query = sensor_query.filter(Sensor.id.in_(sensor_ids)) - - return sensor_query.all() - - -def get_public_sensors(sensor_ids: list[int] | None = None) -> list[Sensor]: - """Return a list of Sensor objects that belong to a public asset. - - :param sensor_ids: optionally, filter by sensor id. - """ - sensor_query = ( - Sensor.query.join(GenericAsset) - .filter(Sensor.generic_asset_id == GenericAsset.id) - .filter(GenericAsset.account_id.is_(None)) + account_ids = [account.id for account in accounts if account is not None] + sensor_query = Sensor.query.join(GenericAsset).filter( + Sensor.generic_asset_id == GenericAsset.id ) + if None in accounts: + sensor_query = sensor_query.filter( + sa.or_( + GenericAsset.account_id.in_(account_ids), + GenericAsset.account_id.is_(None), + ) + ) + else: + sensor_query = sensor_query.filter(GenericAsset.account_id.in_(account_ids)) if sensor_ids: sensor_query = sensor_query.filter(Sensor.id.in_(sensor_ids)) return sensor_query.all() From 327b6d87a93349b7854f70752602db62897c51f5 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Mon, 12 Sep 2022 22:26:05 +0200 Subject: [PATCH 11/17] Partially revert repurposing function: passing accounts is optional again, and rename filter argument Signed-off-by: F.N. Claessen --- flexmeasures/api/v3_0/sensors.py | 4 +- flexmeasures/data/models/generic_assets.py | 6 +-- flexmeasures/data/services/sensors.py | 44 +++++++++++----------- 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/flexmeasures/api/v3_0/sensors.py b/flexmeasures/api/v3_0/sensors.py index f0c963599..6cf8c0721 100644 --- a/flexmeasures/api/v3_0/sensors.py +++ b/flexmeasures/api/v3_0/sensors.py @@ -41,7 +41,7 @@ from flexmeasures.data.schemas.sensors import SensorSchema, SensorIdField from flexmeasures.data.schemas.units import QuantityField from flexmeasures.data.schemas import AwareDateTimeField -from flexmeasures.data.services.sensors import get_account_sensors +from flexmeasures.data.services.sensors import get_sensors from flexmeasures.data.services.scheduling import create_scheduling_job from flexmeasures.utils.time_utils import duration_isoformat from flexmeasures.utils.unit_utils import ur @@ -110,7 +110,7 @@ def index(self, account: Account): :status 403: INVALID_SENDER :status 422: UNPROCESSABLE_ENTITY """ - sensors = get_account_sensors(accounts=[account]) + sensors = get_sensors(accounts=[account]) return sensors_schema.dump(sensors), 200 @route("/data", methods=["POST"]) diff --git a/flexmeasures/data/models/generic_assets.py b/flexmeasures/data/models/generic_assets.py index e53c6442d..d9d2bd47a 100644 --- a/flexmeasures/data/models/generic_assets.py +++ b/flexmeasures/data/models/generic_assets.py @@ -442,14 +442,14 @@ def sensors_to_show(self) -> List["Sensor"]: # noqa F821 if not self.has_attribute("sensors_to_show"): return self.sensors[:2] - from flexmeasures.data.services.sensors import get_account_sensors + from flexmeasures.data.services.sensors import get_sensors sensor_ids = self.get_attribute("sensors_to_show") sensor_map = { sensor.id: sensor - for sensor in get_account_sensors( + for sensor in get_sensors( accounts=[self.owner, None], # include public sensors - sensor_ids=sensor_ids, + filter_by_sensor_ids=sensor_ids, ) } diff --git a/flexmeasures/data/services/sensors.py b/flexmeasures/data/services/sensors.py index 52d13562f..d02025713 100644 --- a/flexmeasures/data/services/sensors.py +++ b/flexmeasures/data/services/sensors.py @@ -6,29 +6,31 @@ from flexmeasures.data.models.generic_assets import GenericAsset -def get_account_sensors( - accounts: list[Account | None], - sensor_ids: list[int] | None = None, +def get_sensors( + accounts: list[Account | None] = None, + filter_by_sensor_ids: list[int] | None = None, ) -> list[Sensor]: - """Return a list of Sensor objects that belong to any of the given accounts. + """Return a list of Sensor objects that belong to any of the given accounts, and/or public sensors. - :param accounts: select only sensors from this list of accounts - (include None to select sensors that belong to a public asset). - :param sensor_ids: optionally, filter by sensor id. + :param accounts: optionally, select only sensors from this list of accounts + (include None to select sensors that belong to a public asset). + :param filter_by_sensor_ids: optionally, filter by sensor id. """ - account_ids = [account.id for account in accounts if account is not None] - sensor_query = Sensor.query.join(GenericAsset).filter( - Sensor.generic_asset_id == GenericAsset.id - ) - if None in accounts: - sensor_query = sensor_query.filter( - sa.or_( - GenericAsset.account_id.in_(account_ids), - GenericAsset.account_id.is_(None), - ) + sensor_query = Sensor.query + if accounts is not None: + account_ids = [account.id for account in accounts if account is not None] + sensor_query = sensor_query.join(GenericAsset).filter( + Sensor.generic_asset_id == GenericAsset.id ) - else: - sensor_query = sensor_query.filter(GenericAsset.account_id.in_(account_ids)) - if sensor_ids: - sensor_query = sensor_query.filter(Sensor.id.in_(sensor_ids)) + if None in accounts: + sensor_query = sensor_query.filter( + sa.or_( + GenericAsset.account_id.in_(account_ids), + GenericAsset.account_id.is_(None), + ) + ) + else: + sensor_query = sensor_query.filter(GenericAsset.account_id.in_(account_ids)) + if filter_by_sensor_ids: + sensor_query = sensor_query.filter(Sensor.id.in_(filter_by_sensor_ids)) return sensor_query.all() From 2546bca650e97fab72c3bb55fa88b7b71af35788 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Sun, 9 Oct 2022 11:04:42 +0200 Subject: [PATCH 12/17] Allow to filter sensors by name Signed-off-by: F.N. Claessen --- flexmeasures/data/services/sensors.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/flexmeasures/data/services/sensors.py b/flexmeasures/data/services/sensors.py index d02025713..32773b8be 100644 --- a/flexmeasures/data/services/sensors.py +++ b/flexmeasures/data/services/sensors.py @@ -9,12 +9,14 @@ def get_sensors( accounts: list[Account | None] = None, filter_by_sensor_ids: list[int] | None = None, + filter_by_sensor_names: list[str] | None = None, ) -> list[Sensor]: """Return a list of Sensor objects that belong to any of the given accounts, and/or public sensors. - :param accounts: optionally, select only sensors from this list of accounts - (include None to select sensors that belong to a public asset). - :param filter_by_sensor_ids: optionally, filter by sensor id. + :param accounts: optionally, select only sensors from this list of accounts + (include None to select sensors that belong to a public asset). + :param filter_by_sensor_ids: optionally, filter by sensor id. + :param filter_by_sensor_names: optionally, filter by sensor name. """ sensor_query = Sensor.query if accounts is not None: @@ -33,4 +35,6 @@ def get_sensors( sensor_query = sensor_query.filter(GenericAsset.account_id.in_(account_ids)) if filter_by_sensor_ids: sensor_query = sensor_query.filter(Sensor.id.in_(filter_by_sensor_ids)) + if filter_by_sensor_names: + sensor_query = sensor_query.filter(Sensor.name.in_(filter_by_sensor_names)) return sensor_query.all() From d64307214bd4e2348ce072bf753c8465be29e267 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Sun, 9 Oct 2022 19:29:25 +0200 Subject: [PATCH 13/17] Explicit parameter for including sensors of public assets Signed-off-by: F.N. Claessen --- flexmeasures/data/models/generic_assets.py | 3 ++- flexmeasures/data/services/sensors.py | 13 +++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/flexmeasures/data/models/generic_assets.py b/flexmeasures/data/models/generic_assets.py index d9d2bd47a..f4f66286f 100644 --- a/flexmeasures/data/models/generic_assets.py +++ b/flexmeasures/data/models/generic_assets.py @@ -448,7 +448,8 @@ def sensors_to_show(self) -> List["Sensor"]: # noqa F821 sensor_map = { sensor.id: sensor for sensor in get_sensors( - accounts=[self.owner, None], # include public sensors + accounts=[self.owner], + include_public_assets=True, filter_by_sensor_ids=sensor_ids, ) } diff --git a/flexmeasures/data/services/sensors.py b/flexmeasures/data/services/sensors.py index 32773b8be..8fac24d09 100644 --- a/flexmeasures/data/services/sensors.py +++ b/flexmeasures/data/services/sensors.py @@ -7,24 +7,25 @@ def get_sensors( - accounts: list[Account | None] = None, + accounts: list[Account] = None, + include_public_assets: bool = False, filter_by_sensor_ids: list[int] | None = None, filter_by_sensor_names: list[str] | None = None, ) -> list[Sensor]: """Return a list of Sensor objects that belong to any of the given accounts, and/or public sensors. :param accounts: optionally, select only sensors from this list of accounts - (include None to select sensors that belong to a public asset). - :param filter_by_sensor_ids: optionally, filter by sensor id. - :param filter_by_sensor_names: optionally, filter by sensor name. + :param include_public_assets: if True, include sensors that belong to a public asset + :param filter_by_sensor_ids: optionally, filter by sensor id + :param filter_by_sensor_names: optionally, filter by sensor name """ sensor_query = Sensor.query if accounts is not None: - account_ids = [account.id for account in accounts if account is not None] + account_ids = [account.id for account in accounts] sensor_query = sensor_query.join(GenericAsset).filter( Sensor.generic_asset_id == GenericAsset.id ) - if None in accounts: + if include_public_assets: sensor_query = sensor_query.filter( sa.or_( GenericAsset.account_id.in_(account_ids), From 433102bab6466b6285a22be93d7691978093c2e5 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Sun, 9 Oct 2022 19:39:45 +0200 Subject: [PATCH 14/17] Must pass a list of accounts Signed-off-by: F.N. Claessen --- flexmeasures/data/services/sensors.py | 29 +++++++++++++-------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/flexmeasures/data/services/sensors.py b/flexmeasures/data/services/sensors.py index 8fac24d09..a7ca03b38 100644 --- a/flexmeasures/data/services/sensors.py +++ b/flexmeasures/data/services/sensors.py @@ -7,33 +7,32 @@ def get_sensors( - accounts: list[Account] = None, + accounts: list[Account], include_public_assets: bool = False, filter_by_sensor_ids: list[int] | None = None, filter_by_sensor_names: list[str] | None = None, ) -> list[Sensor]: """Return a list of Sensor objects that belong to any of the given accounts, and/or public sensors. - :param accounts: optionally, select only sensors from this list of accounts + :param accounts: select only sensors from this list of accounts :param include_public_assets: if True, include sensors that belong to a public asset :param filter_by_sensor_ids: optionally, filter by sensor id :param filter_by_sensor_names: optionally, filter by sensor name """ sensor_query = Sensor.query - if accounts is not None: - account_ids = [account.id for account in accounts] - sensor_query = sensor_query.join(GenericAsset).filter( - Sensor.generic_asset_id == GenericAsset.id - ) - if include_public_assets: - sensor_query = sensor_query.filter( - sa.or_( - GenericAsset.account_id.in_(account_ids), - GenericAsset.account_id.is_(None), - ) + account_ids = [account.id for account in accounts] + sensor_query = sensor_query.join(GenericAsset).filter( + Sensor.generic_asset_id == GenericAsset.id + ) + if include_public_assets: + sensor_query = sensor_query.filter( + sa.or_( + GenericAsset.account_id.in_(account_ids), + GenericAsset.account_id.is_(None), ) - else: - sensor_query = sensor_query.filter(GenericAsset.account_id.in_(account_ids)) + ) + else: + sensor_query = sensor_query.filter(GenericAsset.account_id.in_(account_ids)) if filter_by_sensor_ids: sensor_query = sensor_query.filter(Sensor.id.in_(filter_by_sensor_ids)) if filter_by_sensor_names: From a0a0379dd1b517c0ef9082ce1944ceb077eb7b87 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Sun, 9 Oct 2022 19:42:43 +0200 Subject: [PATCH 15/17] The more common use case is looking up sensors for a single account Signed-off-by: F.N. Claessen --- flexmeasures/api/v3_0/sensors.py | 2 +- flexmeasures/data/models/generic_assets.py | 2 +- flexmeasures/data/services/sensors.py | 11 +++++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/flexmeasures/api/v3_0/sensors.py b/flexmeasures/api/v3_0/sensors.py index 6cf8c0721..4449658f8 100644 --- a/flexmeasures/api/v3_0/sensors.py +++ b/flexmeasures/api/v3_0/sensors.py @@ -110,7 +110,7 @@ def index(self, account: Account): :status 403: INVALID_SENDER :status 422: UNPROCESSABLE_ENTITY """ - sensors = get_sensors(accounts=[account]) + sensors = get_sensors(account=account) return sensors_schema.dump(sensors), 200 @route("/data", methods=["POST"]) diff --git a/flexmeasures/data/models/generic_assets.py b/flexmeasures/data/models/generic_assets.py index f4f66286f..c813395ab 100644 --- a/flexmeasures/data/models/generic_assets.py +++ b/flexmeasures/data/models/generic_assets.py @@ -448,7 +448,7 @@ def sensors_to_show(self) -> List["Sensor"]: # noqa F821 sensor_map = { sensor.id: sensor for sensor in get_sensors( - accounts=[self.owner], + account=self.owner, include_public_assets=True, filter_by_sensor_ids=sensor_ids, ) diff --git a/flexmeasures/data/services/sensors.py b/flexmeasures/data/services/sensors.py index a7ca03b38..6ac2c6a86 100644 --- a/flexmeasures/data/services/sensors.py +++ b/flexmeasures/data/services/sensors.py @@ -7,20 +7,23 @@ def get_sensors( - accounts: list[Account], + account: Account | list[Account], include_public_assets: bool = False, filter_by_sensor_ids: list[int] | None = None, filter_by_sensor_names: list[str] | None = None, ) -> list[Sensor]: - """Return a list of Sensor objects that belong to any of the given accounts, and/or public sensors. + """Return a list of Sensor objects that belong to the given account, and/or public sensors. - :param accounts: select only sensors from this list of accounts + :param account: select only sensors from this account (or list of accounts) :param include_public_assets: if True, include sensors that belong to a public asset :param filter_by_sensor_ids: optionally, filter by sensor id :param filter_by_sensor_names: optionally, filter by sensor name """ sensor_query = Sensor.query - account_ids = [account.id for account in accounts] + if isinstance(list, account): + account_ids = [account.id for account in account] + else: + account_ids = [account.id] sensor_query = sensor_query.join(GenericAsset).filter( Sensor.generic_asset_id == GenericAsset.id ) From 0ab4dabe7e282fc444134df0bf5ba769d5c5637e Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Sun, 9 Oct 2022 20:07:33 +0200 Subject: [PATCH 16/17] Rename variable Signed-off-by: F.N. Claessen --- flexmeasures/data/models/generic_assets.py | 2 +- flexmeasures/data/services/sensors.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/flexmeasures/data/models/generic_assets.py b/flexmeasures/data/models/generic_assets.py index c813395ab..8e62cca92 100644 --- a/flexmeasures/data/models/generic_assets.py +++ b/flexmeasures/data/models/generic_assets.py @@ -450,7 +450,7 @@ def sensors_to_show(self) -> List["Sensor"]: # noqa F821 for sensor in get_sensors( account=self.owner, include_public_assets=True, - filter_by_sensor_ids=sensor_ids, + sensor_id_allowlist=sensor_ids, ) } diff --git a/flexmeasures/data/services/sensors.py b/flexmeasures/data/services/sensors.py index 6ac2c6a86..37707719d 100644 --- a/flexmeasures/data/services/sensors.py +++ b/flexmeasures/data/services/sensors.py @@ -9,15 +9,15 @@ def get_sensors( account: Account | list[Account], include_public_assets: bool = False, - filter_by_sensor_ids: list[int] | None = None, - filter_by_sensor_names: list[str] | None = None, + sensor_id_allowlist: list[int] | None = None, + sensor_name_allowlist: list[str] | None = None, ) -> list[Sensor]: """Return a list of Sensor objects that belong to the given account, and/or public sensors. :param account: select only sensors from this account (or list of accounts) :param include_public_assets: if True, include sensors that belong to a public asset - :param filter_by_sensor_ids: optionally, filter by sensor id - :param filter_by_sensor_names: optionally, filter by sensor name + :param sensor_id_allowlist: optionally, allow only sensors whose id is in this list + :param sensor_name_allowlist: optionally, allow only sensors whose name is in this list """ sensor_query = Sensor.query if isinstance(list, account): @@ -36,8 +36,8 @@ def get_sensors( ) else: sensor_query = sensor_query.filter(GenericAsset.account_id.in_(account_ids)) - if filter_by_sensor_ids: - sensor_query = sensor_query.filter(Sensor.id.in_(filter_by_sensor_ids)) - if filter_by_sensor_names: - sensor_query = sensor_query.filter(Sensor.name.in_(filter_by_sensor_names)) + if sensor_id_allowlist: + sensor_query = sensor_query.filter(Sensor.id.in_(sensor_id_allowlist)) + if sensor_name_allowlist: + sensor_query = sensor_query.filter(Sensor.name.in_(sensor_name_allowlist)) return sensor_query.all() From f547cdd4839a79d0c6e490a646739e2557c76943 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Mon, 10 Oct 2022 14:19:37 +0200 Subject: [PATCH 17/17] fix variable order Signed-off-by: F.N. Claessen --- flexmeasures/data/services/sensors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flexmeasures/data/services/sensors.py b/flexmeasures/data/services/sensors.py index 37707719d..fd645e760 100644 --- a/flexmeasures/data/services/sensors.py +++ b/flexmeasures/data/services/sensors.py @@ -20,7 +20,7 @@ def get_sensors( :param sensor_name_allowlist: optionally, allow only sensors whose name is in this list """ sensor_query = Sensor.query - if isinstance(list, account): + if isinstance(account, list): account_ids = [account.id for account in account] else: account_ids = [account.id]