From 6fcb3c46cf52ea5bf767cf0fb639fd7c057f7d69 Mon Sep 17 00:00:00 2001 From: JunsongDu Date: Wed, 20 Nov 2024 14:58:29 +0100 Subject: [PATCH 1/2] fix: remove unsupported geo type, geometrycollection --- filip/clients/ngsi_ld/cb.py | 8 ++++---- filip/models/ngsi_ld/context.py | 6 +++--- tests/models/test_ngsi_ld_context.py | 18 +++++++----------- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/filip/clients/ngsi_ld/cb.py b/filip/clients/ngsi_ld/cb.py index 7dca70df..2d6b1bda 100644 --- a/filip/clients/ngsi_ld/cb.py +++ b/filip/clients/ngsi_ld/cb.py @@ -198,7 +198,7 @@ def post_entity(self, """ Function registers an Object with the NGSI-LD Context Broker, if it already exists it can be automatically updated - if the overwrite bool is True + if the update flag bool is True. First a post request with the entity is tried, if the response code is 422 the entity is uncrossable, as it already exists there are two options, either overwrite it, if the attribute have changed @@ -223,14 +223,12 @@ def post_entity(self, return res.headers.get('Location') res.raise_for_status() except requests.RequestException as err: - if append and err.response.status_code == 409: + if append and err.response.status_code == 409: # 409 entity already exists return self.append_entity_attributes(entity=entity) msg = f"Could not post entity {entity.id}" self.log_error(err=err, msg=msg) raise - GeometryShape = Literal["Point", "MultiPoint", "LineString", "MultiLineString", "Polygon", "MultiPolygon"] - def get_entity(self, entity_id: str, entity_type: str = None, @@ -289,6 +287,8 @@ def get_entity(self, self.log_error(err=err, msg=msg) raise + GeometryShape = Literal["Point", "MultiPoint", "LineString", "MultiLineString", "Polygon", "MultiPolygon"] + def get_entity_list(self, entity_id: Optional[str] = None, id_pattern: Optional[str] = ".*", diff --git a/filip/models/ngsi_ld/context.py b/filip/models/ngsi_ld/context.py index 536cb7dd..39111b59 100644 --- a/filip/models/ngsi_ld/context.py +++ b/filip/models/ngsi_ld/context.py @@ -4,7 +4,7 @@ import logging from typing import Any, List, Dict, Union, Optional from geojson_pydantic import Point, MultiPoint, LineString, MultiLineString, Polygon, \ - MultiPolygon, GeometryCollection + MultiPolygon from typing_extensions import Self from aenum import Enum from pydantic import field_validator, ConfigDict, BaseModel, Field, model_validator @@ -190,7 +190,7 @@ def check_geoproperty_value(self) -> Self: elif self.model_dump().get("type") == "MultiPolygon": return MultiPolygon(**self.model_dump()) elif self.model_dump().get("type") == "GeometryCollection": - return GeometryCollection(**self.model_dump()) + raise ValueError("GeometryCollection is not supported") class ContextGeoProperty(BaseModel): @@ -221,7 +221,7 @@ class ContextGeoProperty(BaseModel): value: Optional[Union[ContextGeoPropertyValue, Point, LineString, Polygon, MultiPoint, MultiPolygon, - MultiLineString, GeometryCollection]] = Field( + MultiLineString]] = Field( default=None, title="GeoProperty value", description="the actual data" diff --git a/tests/models/test_ngsi_ld_context.py b/tests/models/test_ngsi_ld_context.py index 7a26dbb3..0eac1230 100644 --- a/tests/models/test_ngsi_ld_context.py +++ b/tests/models/test_ngsi_ld_context.py @@ -223,11 +223,6 @@ def setUp(self) -> None: "type": "GeoProperty", "value": self.testpolygon_value, "observedAt": "2023-09-12T12:36:00Z" - }, - "testgeometrycollection": { - "type": "GeoProperty", - "value": self.testgeometrycollection_value, - "observedAt": "2023-09-12T12:36:30Z" } } @@ -272,13 +267,14 @@ def test_geo_property(self) -> None: type="GeoProperty", value=Polygon(**self.testpolygon_value) ) - test_GeometryCollection = NamedContextGeoProperty( - name="testgeometrycollection", - type="GeoProperty", - value=GeometryCollection(**self.testgeometrycollection_value) - ) + with self.assertRaises(ValidationError): + test_GeometryCollection = NamedContextGeoProperty( + name="testgeometrycollection", + type="GeoProperty", + value=GeometryCollection(**self.testgeometrycollection_value) + ) new_entity.add_geo_properties([test_point, test_MultiPoint, test_LineString, - test_Polygon, test_GeometryCollection]) + test_Polygon]) def test_cb_entity(self) -> None: """ From f7a48d434cdfd0a5d625678694811dd3de45798d Mon Sep 17 00:00:00 2001 From: JunsongDu Date: Wed, 20 Nov 2024 15:39:07 +0100 Subject: [PATCH 2/2] feat: implement update entities option in post_entity to align with v2 client --- filip/clients/ngsi_ld/cb.py | 18 ++++++++-- tests/clients/test_ngsi_ld_cb.py | 58 ++++++++++++++++---------------- 2 files changed, 44 insertions(+), 32 deletions(-) diff --git a/filip/clients/ngsi_ld/cb.py b/filip/clients/ngsi_ld/cb.py index 2d6b1bda..8d7ffc66 100644 --- a/filip/clients/ngsi_ld/cb.py +++ b/filip/clients/ngsi_ld/cb.py @@ -223,12 +223,24 @@ def post_entity(self, return res.headers.get('Location') res.raise_for_status() except requests.RequestException as err: - if append and err.response.status_code == 409: # 409 entity already exists - return self.append_entity_attributes(entity=entity) + if err.response.status_code == 409: + if append: # 409 entity already exists + return self.append_entity_attributes(entity=entity) + elif update: + return self.override_entities(entities=[entity]) msg = f"Could not post entity {entity.id}" self.log_error(err=err, msg=msg) raise + def override_entities(self, entities: List[ContextLDEntity]): + """ + Function to create or override existing entites with the NGSI-LD Context Broker. + The batch operation with Upsert will be used. + """ + return self.entity_batch_operation(entities=entities, + action_type=ActionTypeLD.UPSERT, + options="replace") + def get_entity(self, entity_id: str, entity_type: str = None, @@ -296,7 +308,7 @@ def get_entity_list(self, attrs: Optional[List[str]] = None, q: Optional[str] = None, georel: Optional[str] = None, - geometry: Optional[GeometryShape] = None, # So machen oder wie auch für response_format + geometry: Optional[GeometryShape] = None, coordinates: Optional[str] = None, geoproperty: Optional[str] = None, csf: Optional[str] = None, diff --git a/tests/clients/test_ngsi_ld_cb.py b/tests/clients/test_ngsi_ld_cb.py index 4363a1ae..0387d243 100644 --- a/tests/clients/test_ngsi_ld_cb.py +++ b/tests/clients/test_ngsi_ld_cb.py @@ -190,33 +190,7 @@ def test_post_entity(self): - Post an entity again -> Does it return 409? - Post an entity without requires args -> Does it return 422? """ - """ - Test 1: - Post enitity with entity_ID and entity_type - if return != 201: - Raise Error - Get entity list - If entity with entity_ID is not on entity list: - Raise Error - Test 2: - Post entity with entity_ID and entity_type - Post entity with the same entity_ID and entity_type as before - If return != 409: - Raise Error - Get entity list - If there are duplicates on entity list: - Raise Error - Test 3: - Post an entity with an entity_ID and without an entity_type - If return != 422: - Raise Error - Get entity list - If the entity list does contain the posted entity: - Raise Error - Test Additonal: - post two entities with the same enitity id but different entity type-> should throw error. - """ - """Test1""" + # create entity self.client.post_entity(entity=self.entity) entity_list = self.client.get_entity_list(entity_type=self.entity.type) self.assertEqual(len(entity_list), 1) @@ -225,7 +199,7 @@ def test_post_entity(self): self.assertEqual(entity_list[0].testtemperature.value, self.entity.testtemperature.value) - """Test2""" + # existed entity self.entity_identical = self.entity.model_copy() with self.assertRaises(requests.exceptions.HTTPError) as contextmanager: self.client.post_entity(entity=self.entity_identical) @@ -236,7 +210,33 @@ def test_post_entity(self): entity_type=self.entity_identical.type) self.assertEqual(len(entity_list), 1) - """Test3""" + # append new attribute to existed entity + self.entity_append = self.entity.model_copy() + self.entity_append.delete_properties(['testtemperature']) + self.entity_append.add_properties( + {'humidity': ContextProperty(**{ + 'type': 'Property', + 'value': 50})}) + self.client.post_entity(entity=self.entity_append, append=True) + entity_append_res = self.client.get_entity(entity_id=self.entity_append.id) + self.assertEqual(entity_append_res.humidity.value, + self.entity_append.humidity.value) + self.assertEqual(entity_append_res.testtemperature.value, + self.entity.testtemperature.value) + + # override existed entity + new_attr = {'newattr': + {'type': 'Property', 'value': 999} + } + self.entity_override = ContextLDEntity( + id=self.entity.id, type=self.entity.type, **new_attr) + self.client.post_entity(entity=self.entity_override, update=True) + entity_override_res = self.client.get_entity(entity_id=self.entity.id) + self.assertEqual(entity_override_res.newattr.value, + self.entity_override.newattr.value) + self.assertNotIn('testtemperature', entity_override_res.model_dump()) + + # post without entity type is not allowed with self.assertRaises(Exception): self.client.post_entity(ContextLDEntity(id="room2")) entity_list = self.client.get_entity_list()