From c38c1618ef0989759f47540137a2a34424e117f0 Mon Sep 17 00:00:00 2001 From: JunsongDu Date: Fri, 16 Feb 2024 14:57:03 +0100 Subject: [PATCH 1/4] feat: add entity update with keyValues --- filip/clients/ngsi_v2/cb.py | 76 ++++++++++++++++++++++++++++++++ tests/clients/test_ngsi_v2_cb.py | 41 ++++++++++++++++- 2 files changed, 116 insertions(+), 1 deletion(-) diff --git a/filip/clients/ngsi_v2/cb.py b/filip/clients/ngsi_v2/cb.py index 04e38a78..5eebdb17 100644 --- a/filip/clients/ngsi_v2/cb.py +++ b/filip/clients/ngsi_v2/cb.py @@ -722,6 +722,82 @@ def update_or_append_entity_attributes( self.log_error(err=err, msg=msg) raise + def update_entity_key_value(self, + entity: Union[ContextEntityKeyValues, dict],): + """ + The entity are updated with a ContextEntityKeyValues object or a + dictionary contain the simplified entity data. This corresponds to a + 'PATcH' request. + Only existing attribute can be updated! + + Args: + entity: A ContextEntityKeyValues object or a dictionary contain + the simplified entity data + + """ + if isinstance(entity, dict): + entity = ContextEntityKeyValues(**entity) + # TODO what to do with entity type? + url = urljoin(self.base_url, f'v2/entities/{entity.id}/attrs') + headers = self.headers.copy() + params = {"type": entity.type, + "options": AttrsFormat.KEY_VALUES.value + } + try: + res = self.patch(url=url, + headers=headers, + json=entity.model_dump(exclude={'id', 'type'}, + exclude_unset=True), + params=params) + if res.ok: + self.logger.info("Entity '%s' successfully " + "updated!", entity.id) + else: + res.raise_for_status() + except requests.RequestException as err: + msg = f"Could not update attributes of entity" \ + f" {entity.id} !" + self.log_error(err=err, msg=msg) + raise + + def update_entity_attributes_key_value(self, + entity_id: str, + attrs: dict, + entity_type: str = None, + ): + """ + Update entity with attributes in keyValues form. + This corresponds to a 'PATcH' request. + Only existing attribute can be updated! + + Args: + entity_id: Entity id to be updated + entity_type: Entity type, to avoid ambiguity in case there are + several entities with the same entity id. + attrs: a dictionary that contains the attribute values. + e.g. { + "temperature": 21.4, + "humidity": 50 + } + + Returns: + + """ + # TODO check entity type, and detect duplicated entities + if entity_type: + pass + else: + _entity = self.get_entity(entity_id=entity_id) + entity_type = _entity.type + + entity_dict = attrs.copy() + entity_dict.update({ + "id": entity_id, + "type": entity_type + }) + entity = ContextEntityKeyValues(**entity_dict) + self.update_entity_key_value(entity=entity) + def update_existing_entity_attributes( self, entity_id: str, diff --git a/tests/clients/test_ngsi_v2_cb.py b/tests/clients/test_ngsi_v2_cb.py index 1c7faa4e..4fe8a3f0 100644 --- a/tests/clients/test_ngsi_v2_cb.py +++ b/tests/clients/test_ngsi_v2_cb.py @@ -23,7 +23,8 @@ NamedContextAttribute, \ NamedCommand, \ Query, \ - ActionType + ActionType, \ + ContextEntityKeyValues from filip.models.ngsi_v2.base import AttrsFormat, EntityPattern, Status, \ NamedMetadata @@ -529,6 +530,44 @@ def on_disconnect(client, userdata, reasonCode, properties=None): mqtt_client.disconnect() time.sleep(1) + @clean_test(fiware_service=settings.FIWARE_SERVICE, + fiware_servicepath=settings.FIWARE_SERVICEPATH, + cb_url=settings.CB_URL) + def test_update_entity_keyvalues(self): + entity1 = self.entity.model_copy(deep=True) + # initial entity + self.client.post_entity(entity1) + + # key value + entity1_key_value = self.client.get_entity( + entity_id=entity1.id, + response_format=AttrsFormat.KEY_VALUES) + + # update entity with ContextEntityKeyValues + entity1_key_value.temperature = 30 + self.client.update_entity_key_value(entity=entity1_key_value) + self.assertEqual(entity1_key_value, + self.client.get_entity( + entity_id=entity1.id, + response_format=AttrsFormat.KEY_VALUES) + ) + + # update entity with dictionary + entity1_key_value_dict = entity1_key_value.model_dump() + entity1_key_value_dict["temperature"] = 40 + self.client.update_entity_key_value(entity=entity1_key_value_dict) + self.client.get_entity( + entity_id=entity1.id, + response_format=AttrsFormat.KEY_VALUES).model_dump() + self.assertEqual(entity1_key_value_dict, + self.client.get_entity( + entity_id=entity1.id, + response_format=AttrsFormat.KEY_VALUES).model_dump() + ) + entity1_key_value_dict.update({"humidity": 50}) + with self.assertRaises(RequestException): + self.client.update_entity_key_value(entity=entity1_key_value_dict) + @clean_test(fiware_service=settings.FIWARE_SERVICE, fiware_servicepath=settings.FIWARE_SERVICEPATH, cb_url=settings.CB_URL) From 46b645c6555747abc728dcbfe8e0766cbe15bcb0 Mon Sep 17 00:00:00 2001 From: JunsongDu Date: Tue, 20 Feb 2024 11:18:52 +0100 Subject: [PATCH 2/4] chore: add tests for data type --- tests/clients/test_ngsi_v2_cb.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/clients/test_ngsi_v2_cb.py b/tests/clients/test_ngsi_v2_cb.py index 4fe8a3f0..37ff4101 100644 --- a/tests/clients/test_ngsi_v2_cb.py +++ b/tests/clients/test_ngsi_v2_cb.py @@ -551,6 +551,9 @@ def test_update_entity_keyvalues(self): entity_id=entity1.id, response_format=AttrsFormat.KEY_VALUES) ) + entity2 = self.client.get_entity(entity_id=entity1.id) + self.assertEqual(entity1.temperature.type, + entity2.temperature.type) # update entity with dictionary entity1_key_value_dict = entity1_key_value.model_dump() @@ -564,6 +567,9 @@ def test_update_entity_keyvalues(self): entity_id=entity1.id, response_format=AttrsFormat.KEY_VALUES).model_dump() ) + entity3 = self.client.get_entity(entity_id=entity1.id) + self.assertEqual(entity1.temperature.type, + entity3.temperature.type) entity1_key_value_dict.update({"humidity": 50}) with self.assertRaises(RequestException): self.client.update_entity_key_value(entity=entity1_key_value_dict) From 31cc39e0f863f4de581fa94a6eda8dba497e958d Mon Sep 17 00:00:00 2001 From: JunsongDu Date: Tue, 20 Feb 2024 11:20:51 +0100 Subject: [PATCH 3/4] docs: update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e7de6c7..1cd3fce6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - fix: compare subscriptions to prevent duplicated notifications @FWuellhorst, @RCX112 ([#138](https://github.com/RWTH-EBC/FiLiP/pull/138)) - update pandas version to `~=2.1.4` for `python>=3.9` ([#231](https://github.com/RWTH-EBC/FiLiP/pull/231)) - fix: wrong msg in iotac post device ([#214](https://github.com/RWTH-EBC/FiLiP/pull/214)) +- add support to update entities with keyValues @djs0109 ([#245](https://github.com/RWTH-EBC/FiLiP/pull/245)) #### v0.3.0 - fix: bug in typePattern validation @richardmarston ([#180](https://github.com/RWTH-EBC/FiLiP/pull/180)) From e87476940207ef8a6c2d92fca7b0b7afdd79400c Mon Sep 17 00:00:00 2001 From: Saira Bano Date: Wed, 21 Feb 2024 15:10:48 +0100 Subject: [PATCH 4/4] refactor: remove unnecessary todos --- filip/clients/ngsi_v2/cb.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/filip/clients/ngsi_v2/cb.py b/filip/clients/ngsi_v2/cb.py index 5eebdb17..8734666a 100644 --- a/filip/clients/ngsi_v2/cb.py +++ b/filip/clients/ngsi_v2/cb.py @@ -737,7 +737,6 @@ def update_entity_key_value(self, """ if isinstance(entity, dict): entity = ContextEntityKeyValues(**entity) - # TODO what to do with entity type? url = urljoin(self.base_url, f'v2/entities/{entity.id}/attrs') headers = self.headers.copy() params = {"type": entity.type, @@ -783,7 +782,6 @@ def update_entity_attributes_key_value(self, Returns: """ - # TODO check entity type, and detect duplicated entities if entity_type: pass else: