From c52be307c758bc8a22f52b75785664cca8df1e33 Mon Sep 17 00:00:00 2001 From: "i.nueske" <i.nueske@indiscale.com> Date: Sat, 11 Jan 2025 14:57:44 +0100 Subject: [PATCH 1/4] ENH: Updated add_property to support special attributes: - if given name is a special attributes, update corresponding attribute instead of creating a new property - Add test for https://gitlab.com/linkahead/linkahead-pylib/-/issues/134 - Update changelog --- CHANGELOG.md | 4 +++- src/linkahead/common/models.py | 5 +++++ unittests/test_apiutils.py | 12 ++++++------ unittests/test_issues.py | 15 +++++++++++++++ 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a32e16d..e64b496 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,9 +37,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [#87](https://gitlab.com/linkahead/linkahead-pylib/-/issues/87) `XMLSyntaxError` messages when parsing (incomplete) responses in case of certain connection timeouts. - The diff returned by compare_entities now uses id instead of name as key if either property does not have a name * [#127](https://gitlab.com/linkahead/linkahead-pylib/-/issues/127) pylinkahead.ini now supports None and tuples as values for the `timeout` keyword +* [#134](https://gitlab.com/linkahead/linkahead-pylib/-/issues/134) + Setting special attributes using add_property now updates the attribute + instead of adding a new property with the same name ### Security ### diff --git a/src/linkahead/common/models.py b/src/linkahead/common/models.py index 8bd8eac..38a485f 100644 --- a/src/linkahead/common/models.py +++ b/src/linkahead/common/models.py @@ -749,6 +749,11 @@ class Entity: raise UserWarning( "This method expects you to pass at least an entity, a name or an id.") + # If the name is a special attribute, set the attribute instead + if name in SPECIAL_ATTRIBUTES and name in dir(self): + setattr(self, name, value) + return self + new_property = Property(name=name, id=id, description=description, datatype=datatype, value=value, unit=unit) diff --git a/unittests/test_apiutils.py b/unittests/test_apiutils.py index 6667089..4b1bbf2 100644 --- a/unittests/test_apiutils.py +++ b/unittests/test_apiutils.py @@ -115,8 +115,8 @@ def test_compare_entities(): r2.add_property("tester", ) r1.add_property("tests_234234", value=45) r2.add_property("tests_TT", value=45) - r1.add_property("datatype", value=45, datatype=db.INTEGER) - r2.add_property("datatype", value=45) + r1.add_property("datatype_prop", value=45, datatype=db.INTEGER) + r2.add_property("datatype_prop", value=45) r1.add_property("entity_id", value=2) r2.add_property("entity_id", value=24) r1.add_property("entity_mix_e", value=2) @@ -152,10 +152,10 @@ def test_compare_entities(): assert "tests_234234" in diff_r1["properties"] assert "tests_TT" in diff_r2["properties"] - assert "datatype" in diff_r1["properties"] - assert "datatype" in diff_r1["properties"]["datatype"] - assert "datatype" in diff_r2["properties"] - assert "datatype" in diff_r2["properties"]["datatype"] + assert "datatype_prop" in diff_r1["properties"] + assert "datatype" in diff_r1["properties"]["datatype_prop"] + assert "datatype_prop" in diff_r2["properties"] + assert "datatype" in diff_r2["properties"]["datatype_prop"] assert "entity_id" in diff_r1["properties"] assert "entity_id" in diff_r2["properties"] diff --git a/unittests/test_issues.py b/unittests/test_issues.py index 3b0117b..5f44a90 100644 --- a/unittests/test_issues.py +++ b/unittests/test_issues.py @@ -128,3 +128,18 @@ def test_issue_73(): assert "datatype=" in xml_str assert "Recursive reference" in xml_str assert len(xml_str) < 500 + + +def test_issue_134(): + """ + Test setting special attributes using add_property. + https://gitlab.com/linkahead/linkahead-pylib/-/issues/134 + """ + for attr, val in [("name", "TestRecord"), ("datatype", db.TEXT), + ("description", "desc"), ("id", 1000), + ("value", "Val"), ("unit", "°C")]: + rec = db.Record() + assert rec.__getattribute__(attr) is None + rec.add_property(name=attr, value=val) + assert rec.__getattribute__(attr) == val + assert rec.get_property(attr) is None -- GitLab From bbbc0f3ef39ef8cf2540ff148ebcfbf0b6e166e4 Mon Sep 17 00:00:00 2001 From: Florian Spreckelsen <f.spreckelsen@indiscale.com> Date: Tue, 14 Jan 2025 10:21:19 +0100 Subject: [PATCH 2/4] DOC: Update changelog --- CHANGELOG.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6b8774..19b6a1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,12 +10,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added ### * New setup extra `test` which installs the dependencies for testing. -* The Container class has a new member function `filter` which is based o `_filter_entity_list`. +* The Container class has a new member function `filter_by_identity` + which is based on `_filter_entity_list`. * The `Entity` properties `_cuid` and `_flags` are now available for read-only access as `cuid` and `flags`, respectively. ### Changed ### -* Renamed the `filter` function of Container, ParentList and PropertyList to `filter_by_identity`. + +* Renamed the `filter` function of Container, ParentList and + PropertyList to `filter_by_identity`. +* [#134](https://gitlab.com/linkahead/linkahead-pylib/-/issues/134) + Setting special attributes using add_property now updates the attribute + instead of adding a new property with the same name ### Deprecated ### @@ -40,9 +46,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 case of certain connection timeouts. * [#127](https://gitlab.com/linkahead/linkahead-pylib/-/issues/127) pylinkahead.ini now supports None and tuples as values for the `timeout` keyword -* [#134](https://gitlab.com/linkahead/linkahead-pylib/-/issues/134) - Setting special attributes using add_property now updates the attribute - instead of adding a new property with the same name ### Security ### -- GitLab From 092b1863d5a033d4bd490c3d1fd33c1cbb7379e7 Mon Sep 17 00:00:00 2001 From: "i.nueske" <i.nueske@indiscale.com> Date: Tue, 14 Jan 2025 10:59:13 +0100 Subject: [PATCH 3/4] ENH: Update add_property to prevent overwriting special attributes --- src/linkahead/common/models.py | 13 +++++++++++-- unittests/test_issues.py | 5 +++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/linkahead/common/models.py b/src/linkahead/common/models.py index 0b66acd..35db9f6 100644 --- a/src/linkahead/common/models.py +++ b/src/linkahead/common/models.py @@ -751,8 +751,17 @@ class Entity: # If the name is a special attribute, set the attribute instead if name in SPECIAL_ATTRIBUTES and name in dir(self): - setattr(self, name, value) - return self + if getattr(self, name) is None: + setattr(self, name, value) + return self + else: + raise ValueError(f"'{name}' is a special attribute and does not " + f"support multi-property. It is already set " + f"and cannot be overwritten using this method. " + f"Please use direct assignment for setting this " + f"property after the first time.") + # ToDo: Implement the same behaviour for special attribute ids, + # https://gitlab.indiscale.com/caosdb/src/caosdb-pylib/-/issues/219 new_property = Property(name=name, id=id, description=description, datatype=datatype, value=value, unit=unit) diff --git a/unittests/test_issues.py b/unittests/test_issues.py index 5f44a90..33fdc9e 100644 --- a/unittests/test_issues.py +++ b/unittests/test_issues.py @@ -143,3 +143,8 @@ def test_issue_134(): rec.add_property(name=attr, value=val) assert rec.__getattribute__(attr) == val assert rec.get_property(attr) is None + + exp_str = f"'{attr}' is a special attribute and does not support" + with raises(ValueError) as e: + rec.add_property(name=attr, value=val) + assert exp_str in str(e.value) -- GitLab From 2e69dca0c1d0f2844e27945d6a92b0193557b875 Mon Sep 17 00:00:00 2001 From: "i.nueske" <i.nueske@indiscale.com> Date: Tue, 14 Jan 2025 11:50:04 +0100 Subject: [PATCH 4/4] ENH: Update add_property docstring, make special attribute case-insensitive --- src/linkahead/common/models.py | 10 +++++++++- unittests/test_issues.py | 11 ++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/linkahead/common/models.py b/src/linkahead/common/models.py index 35db9f6..5f6f7ec 100644 --- a/src/linkahead/common/models.py +++ b/src/linkahead/common/models.py @@ -622,6 +622,10 @@ class Entity: If you want to add a property to an already existing entity, the property ``id`` of that property needs to be specified before you send the updated entity to the server. + If the specified name matches the name of a special attribute in + lowercase, such as 'name' or 'description', the attribute is set + instead. If the attribute already has a value, this fails with a + ValueError. Parameters ---------- @@ -671,6 +675,9 @@ class Entity: If you try to add an ``Entity`` object with File or Record role (or, equivalently, a ``File`` or ``Record`` object) as a property, a ``ValueError`` is raised. + ValueError: + If the property to be added is a special attribute and already has + a value. Examples -------- @@ -750,7 +757,8 @@ class Entity: "This method expects you to pass at least an entity, a name or an id.") # If the name is a special attribute, set the attribute instead - if name in SPECIAL_ATTRIBUTES and name in dir(self): + if name and name.lower() in SPECIAL_ATTRIBUTES and name.lower() in dir(self): + name = name.lower() if getattr(self, name) is None: setattr(self, name, value) return self diff --git a/unittests/test_issues.py b/unittests/test_issues.py index 33fdc9e..31c09ed 100644 --- a/unittests/test_issues.py +++ b/unittests/test_issues.py @@ -135,16 +135,17 @@ def test_issue_134(): Test setting special attributes using add_property. https://gitlab.com/linkahead/linkahead-pylib/-/issues/134 """ - for attr, val in [("name", "TestRecord"), ("datatype", db.TEXT), - ("description", "desc"), ("id", 1000), + for attr, val in [("nAme", "TestRecord"), ("datatype", db.TEXT), + ("descriptIon", "desc"), ("id", 1000), ("value", "Val"), ("unit", "°C")]: rec = db.Record() - assert rec.__getattribute__(attr) is None + assert rec.__getattribute__(attr.lower()) is None rec.add_property(name=attr, value=val) - assert rec.__getattribute__(attr) == val + assert rec.__getattribute__(attr.lower()) == val assert rec.get_property(attr) is None + assert rec.get_property(attr.lower()) is None - exp_str = f"'{attr}' is a special attribute and does not support" + exp_str = f"'{attr.lower()}' is a special attribute and does not" with raises(ValueError) as e: rec.add_property(name=attr, value=val) assert exp_str in str(e.value) -- GitLab