Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

433 patch sensor #773

Merged
merged 28 commits into from Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
fa465c8
feat(sensors): adds fetch_one sensor endpoint to API
Jul 5, 2023
415f861
feat(sensors): adds post sensor to API
Jul 5, 2023
4e3f6c1
post sensor still needs work
Jul 18, 2023
714b1a0
feat(sensor): adds post sensor
Jul 20, 2023
058cb41
docs(sensor): changes the docstring of the post function
Jul 20, 2023
5691405
clearer names for the arguments to permission_required_for_context de…
nhoening Jul 20, 2023
ed53575
one more renaming
nhoening Jul 20, 2023
2376148
expanding possibilities in the require_permission_for_context decorat…
nhoening Jul 20, 2023
957c144
feat(sensor): post sensor without schema changes
Jul 24, 2023
4d87396
feat(sensor): adds patch sensor
Jul 24, 2023
c0ba110
feat(sensor): change importin users services
Jul 25, 2023
5e6caa6
docs(sensor): update changelogs and fix import
Jul 25, 2023
697270f
feat(sensor): update failing test
Jul 25, 2023
d7d1717
feat(sensor): Merge branch 'main' into 433-patch-sensor
Aug 1, 2023
33fa892
Merge branch 'main' into 433-patch-sensor
Aug 1, 2023
7c706e8
feat(sensor): tests for patching excluded attributes and no auth
Aug 1, 2023
5d425a3
feat(sensor): remove NewDurationField since it's unused
Aug 1, 2023
ddc9c78
feat(sensor): removes print statement
Aug 1, 2023
559593e
feat(sensor): test if database changed after patch
Aug 1, 2023
bbca0dc
Merge branch 'main' into 433-patch-sensor
GustaafL Aug 1, 2023
37d8359
feat(sensor): tests for patch response json
Aug 2, 2023
1cdf476
docs(sensor): updates docstrings patch sensor
Aug 2, 2023
8c684ab
tests(sensor): test for updating fields that are not allowed and impr…
Aug 2, 2023
c039fb9
docs(sensor): updated api changelog typo
Aug 2, 2023
c094558
feat(sensor): add id field(dump_only) to schema and response json
Aug 2, 2023
cf3f9b0
Merge branch 'main' into 433-patch-sensor
GustaafL Aug 2, 2023
3ca31cf
docs(sensor): edit docstrings to include id in example response
Aug 2, 2023
ad1c345
433 delete sensor (#784)
GustaafL Aug 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions documentation/api/change_log.rst
Expand Up @@ -5,6 +5,10 @@ API change log

.. note:: The FlexMeasures API follows its own versioning scheme. This is also reflected in the URL, allowing developers to upgrade at their own pace.

v3.0-8 | 2023-08-01
"""""""""""""""""""

- Added REST endpoint for patching a sensor: `/sensor<id>` (PATCH)
GustaafL marked this conversation as resolved.
Show resolved Hide resolved

v3.0-12 | 2023-07-31
"""""""""""""""""""
Expand Down
2 changes: 1 addition & 1 deletion documentation/changelog.rst
Expand Up @@ -18,7 +18,7 @@ New features
* Users on FlexMeasures servers in play mode (``FLEXMEASURES_MODE = "play"``) can use the ``sensors_to_show`` attribute to show any sensor on their asset pages, rather than only sensors registered to assets in their own account or to public assets [see `PR #740 <https://www.github.com/FlexMeasures/flexmeasures/pull/740>`_]
* Having percentages within the [0, 100] domain is such a common use case that we now always include it in sensor charts with % units, making it easier to read off individual charts and also to compare across charts [see `PR #739 <https://www.github.com/FlexMeasures/flexmeasures/pull/739>`_]
* DataSource table now allows storing arbitrary attributes as a JSON (without content validation), similar to the Sensor and GenericAsset tables [see `PR #750 <https://www.github.com/FlexMeasures/flexmeasures/pull/750>`_]
* Added API endpoints `/sensors/<id>` for fetching a single sensor and `/sensors` (POST) for adding a sensor. [see `PR #759 <https://www.github.com/FlexMeasures/flexmeasures/pull/759>`_] and [see `PR #767 <https://www.github.com/FlexMeasures/flexmeasures/pull/767>`_]
* Added API endpoints `/sensors/<id>` for fetching a single sensor, `/sensors` (POST) for adding a sensor and `/sensor/<id>` (PATCH) for updating a sensor. [see `PR #759 <https://www.github.com/FlexMeasures/flexmeasures/pull/759>`_] and [see `PR #767 <https://www.github.com/FlexMeasures/flexmeasures/pull/767>`_] and [see `PR #773 <https://www.github.com/FlexMeasures/flexmeasures/pull/773>`_]
* The CLI now allows to set lists and dicts as asset & sensor attributes (formerly only single values) [see `PR #762 <https://www.github.com/FlexMeasures/flexmeasures/pull/762>`_]
* Add `ProcessScheduler` class, which optimizes the starting time of processes using one of the following policies: INFLEXIBLE, SHIFTABLE and BREAKABLE [see `PR #729 <https://www.github.com/FlexMeasures/flexmeasures/pull/729>`_]

Expand Down
54 changes: 54 additions & 0 deletions flexmeasures/api/v3_0/sensors.py
Expand Up @@ -48,6 +48,7 @@
post_sensor_schema = PostSensorDataSchema()
sensors_schema = SensorSchema(many=True)
sensor_schema = SensorSchema()
partial_sensor_schema = SensorSchema(partial=True, exclude=["generic_asset_id"])


class SensorAPI(FlaskView):
Expand Down Expand Up @@ -576,3 +577,56 @@ def post(self, sensor_data: dict):
db.session.add(sensor)
db.session.commit()
return sensor_schema.dump(sensor), 201

@route("/<id>", methods=["PATCH"])
@use_args(partial_sensor_schema)
@use_kwargs({"db_sensor": SensorIdField(data_key="id")}, location="path")
@permission_required_for_context("update", ctx_arg_name="db_sensor")
@as_json
def patch(self, sensor_data: dict, id: int, db_sensor: Sensor):
"""Update an sensor given its identifier.
GustaafL marked this conversation as resolved.
Show resolved Hide resolved

.. :quickref: Sensor; Update a sensor

This endpoint sets data for an existing sensor.
Any subset of sensor fields can be sent.

The following fields are not allowed to be updated:
GustaafL marked this conversation as resolved.
Show resolved Hide resolved
- id
- generic_asset_id
GustaafL marked this conversation as resolved.
Show resolved Hide resolved

**Example request**

.. sourcecode:: json

{
"name": "POWER",
}

**Example response**

The whole sensor is returned in the response:

.. sourcecode:: json

{
GustaafL marked this conversation as resolved.
Show resolved Hide resolved
"name": "POWER",
"event_resolution": "PT1H",
"unit": "kWh",
"generic_asset_id": 1,
}

:reqheader Authorization: The authentication token
:reqheader Content-Type: application/json
:resheader Content-Type: application/json
:status 200: UPDATED
:status 400: INVALID_REQUEST, REQUIRED_INFO_MISSING, UNEXPECTED_PARAMS
:status 401: UNAUTHORIZED
:status 403: INVALID_SENDER
:status 422: UNPROCESSABLE_ENTITY
"""
for k, v in sensor_data.items():
setattr(db_sensor, k, v)
db.session.add(db_sensor)
db.session.commit()
return sensor_schema.dump(db_sensor), 200
54 changes: 54 additions & 0 deletions flexmeasures/api/v3_0/tests/test_sensors_api.py
Expand Up @@ -106,3 +106,57 @@ def test_post_sensor_to_asset_from_unrelated_account(client, setup_api_test_data
== "You cannot be authorized for this content or functionality."
)
assert response.json["status"] == "INVALID_SENDER"


def test_patch_sensor(client, setup_api_test_data):
auth_token = get_auth_token(client, "test_admin_user@seita.nl", "testtest")
sensor = Sensor.query.filter(Sensor.name == "some gas sensor").one_or_none()

response = client.patch(
url_for("SensorAPI:patch", id=sensor.id),
headers={"content-type": "application/json", "Authorization": auth_token},
json={
"name": "Changed name",
},
)
assert response.json["name"] == "Changed name"
new_sensor = Sensor.query.filter(Sensor.name == "Changed name").one_or_none()
assert new_sensor.name == "Changed name"
assert Sensor.query.filter(Sensor.name == "some gas sensor").one_or_none() is None


def test_patch_sensor_for_excluded_attribute(client, setup_api_test_data):
"""Test to change the generic_asset_id that should not be allowed.
The generic_asset_id is excluded in the partial_sensor_schema"""
auth_token = get_auth_token(client, "test_admin_user@seita.nl", "testtest")
sensor = Sensor.query.filter(Sensor.name == "some temperature sensor").one_or_none()

response = client.patch(
url_for("SensorAPI:patch", id=sensor.id),
headers={"content-type": "application/json", "Authorization": auth_token},
json={
"generic_asset_id": 8,
},
)

assert response.status_code == 422
assert response.json["status"] == "UNPROCESSABLE_ENTITY"


def test_patch_sensor_from_unrelated_account(client, setup_api_test_data):
"""Try to change the name of a sensor that is in an account the user does not
have access to"""
headers = make_headers_for("test_prosumer_user_2@seita.nl", client)

sensor = Sensor.query.filter(Sensor.name == "some temperature sensor").one_or_none()

response = client.patch(
url_for("SensorAPI:patch", id=sensor.id),
headers=headers,
json={
"name": "try to change the name",
},
)

assert response.status_code == 403
assert response.json["status"] == "INVALID_SENDER"