From fd55ecfdefeb8ffb7096312bd9832b40b6644865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20tom=20W=C3=B6rden?= <h.tomwoerden@indiscale.com> Date: Thu, 14 Mar 2024 20:08:46 +0100 Subject: [PATCH 01/33] MAINT: remove error when comparing different roles --- src/linkahead/apiutils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/linkahead/apiutils.py b/src/linkahead/apiutils.py index e2ed0fac..06547198 100644 --- a/src/linkahead/apiutils.py +++ b/src/linkahead/apiutils.py @@ -215,9 +215,9 @@ def compare_entities(old_entity: Entity, new_entity: Entity, compare_referenced_ if old_entity is new_entity: return (olddiff, newdiff) - if type(old_entity) is not type(new_entity): - raise ValueError( - "Comparison of different Entity types is not supported.") + #if type(old_entity) is not type(new_entity): + # raise ValueError( + # "Comparison of different Entity types is not supported.") for attr in SPECIAL_ATTRIBUTES: try: -- GitLab From 5f1b683541119becabfffb44a0dd7be3b58f5458 Mon Sep 17 00:00:00 2001 From: Daniel Hornung <d.hornung@indiscale.com> Date: Mon, 25 Mar 2024 10:13:51 +0100 Subject: [PATCH 02/33] DOC: Typo --- src/linkahead/common/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linkahead/common/models.py b/src/linkahead/common/models.py index 6f6e4c8f..d7bc7a47 100644 --- a/src/linkahead/common/models.py +++ b/src/linkahead/common/models.py @@ -2286,7 +2286,7 @@ class _Properties(list): class _ParentList(list): - # TODO unclear why this class is private. Isn't it use full for users? + # TODO unclear why this class is private. Isn't it useful for users? def _get_entity_by_cuid(self, cuid): ''' -- GitLab From 6a39b7348ef05682e98e2aa6a7a7506c63d66bcf Mon Sep 17 00:00:00 2001 From: Daniel <d.hornung@indiscale.com> Date: Thu, 2 May 2024 14:22:22 +0200 Subject: [PATCH 03/33] WIP: Plain json serialization for high level api. --- src/linkahead/high_level_api.py | 91 ++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 35 deletions(-) diff --git a/src/linkahead/high_level_api.py b/src/linkahead/high_level_api.py index 18d219c7..5b1682f3 100644 --- a/src/linkahead/high_level_api.py +++ b/src/linkahead/high_level_api.py @@ -471,8 +471,7 @@ class CaosDBPythonEntity(object): if isinstance(att, list): return att - else: - return [att] + return [att] def add_parent(self, parent: Union[ CaosDBPythonUnresolvedParent, "CaosDBPythonRecordType", str]): @@ -679,53 +678,68 @@ class CaosDBPythonEntity(object): return entity - def serialize(self, without_metadata: bool = False, visited: dict = None): - """ - Serialize necessary information into a dict. - - without_metadata: bool - If True don't set the metadata field in order to increase - readability. Not recommended if deserialization is needed. + def serialize(self, without_metadata: bool = None, plain_json: bool = False, + visited: dict = None): + """Serialize necessary information into a dict. + +Parameters +---------- +without_metadata: bool, optional + If True don't set the metadata field in order to increase + readability. Not recommended if deserialization is needed. +plain_json: bool, optional + If True, serialize to a plain dict without any additional information besides the property values, + name and id. This should conform to the format as specified by the json schema generated by the + advanced user tools. This implies ``without_metadata = True``. """ + if plain_json: + if without_metadata is None: + without_metadata = True + if not without_metadata: + raise ValueError("`plain_json` implies `without_metadata`.") + if without_metadata is None: + without_metadata = False if visited is None: - visited = dict() + visited = {} if self in visited: return visited[self] - metadata: Dict[str, Any] = dict() - properties = dict() - parents = list() + metadata: Dict[str, Any] = {} + properties = {} + parents = [] # The full information to be returned: - fulldict = dict() + fulldict = {} visited[self] = fulldict - # Add CaosDB role: - fulldict["role"] = standard_type_for_high_level_type(self, True) - for parent in self._parents: if isinstance(parent, CaosDBPythonEntity): - parents.append(parent.serialize(without_metadata, visited)) + parents.append(parent.serialize(without_metadata=without_metadata, + plain_json=plain_json, + visited=visited)) elif isinstance(parent, CaosDBPythonUnresolvedParent): parents.append({"name": parent.name, "id": parent.id, "unresolved": True}) else: raise RuntimeError("Incompatible class used as parent.") - for baseprop in ("name", "id", "description", "version"): - val = self.__getattribute__(baseprop) - if val is not None: - fulldict[baseprop] = val + if not plain_json: + # Add LinkAhead role: + fulldict["role"] = standard_type_for_high_level_type(self, True) + for baseprop in ("name", "id", "description", "version"): + val = self.__getattribute__(baseprop) + if val is not None: + fulldict[baseprop] = val - if type(self) == CaosDBPythonFile: - fulldict["file"] = self.file - fulldict["path"] = self.path + if isinstance(self, CaosDBPythonFile): + fulldict["file"] = self.file + fulldict["path"] = self.path for p in self.get_properties(): m = self.get_property_metadata(p) - metadata[p] = dict() + metadata[p] = {} for f in fields(m): val = m.__getattribute__(f.name) if val is not None: @@ -735,30 +749,37 @@ class CaosDBPythonEntity(object): if isinstance(val, CaosDBPythonUnresolvedReference): properties[p] = {"id": val.id, "unresolved": True} elif isinstance(val, CaosDBPythonEntity): - properties[p] = val.serialize(without_metadata, visited) + properties[p] = val.serialize(without_metadata=without_metadata, + plain_json=plain_json, + visited=visited) elif isinstance(val, list): serializedelements = [] for element in val: if isinstance(element, CaosDBPythonUnresolvedReference): - elm = dict() + elm = {} elm["id"] = element.id elm["unresolved"] = True serializedelements.append(elm) elif isinstance(element, CaosDBPythonEntity): serializedelements.append( - element.serialize(without_metadata, - visited)) + element.serialize(without_metadata=without_metadata, + plain_json=plain_json, + visited=visited)) else: serializedelements.append(element) properties[p] = serializedelements else: properties[p] = val - fulldict["properties"] = properties - fulldict["parents"] = parents - - if not without_metadata: - fulldict["metadata"] = metadata + if plain_json: + fulldict["id"] = getattr(self, "id") + fulldict["name"] = getattr(self, "name") + fulldict.update(properties) + else: + fulldict["properties"] = properties + fulldict["parents"] = parents + if not without_metadata: + fulldict["metadata"] = metadata return fulldict def __str__(self): -- GitLab From da7a10d5124a02fee57ee86cef9fac4db626206f Mon Sep 17 00:00:00 2001 From: Daniel <d.hornung@indiscale.com> Date: Fri, 3 May 2024 13:16:55 +0200 Subject: [PATCH 04/33] MAINT: Formatting. --- src/linkahead/high_level_api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/linkahead/high_level_api.py b/src/linkahead/high_level_api.py index 5b1682f3..cdbd81e3 100644 --- a/src/linkahead/high_level_api.py +++ b/src/linkahead/high_level_api.py @@ -44,7 +44,8 @@ from .common.datatype import (BOOLEAN, DATETIME, DOUBLE, FILE, INTEGER, REFERENCE, TEXT, get_list_datatype, is_list_datatype, is_reference) -warnings.warn("""EXPERIMENTAL! The high_level_api module is experimental and may be changed or +warnings.warn(""" +EXPERIMENTAL! The high_level_api module is experimental and may be changed or removed in the future. Its purpose is to give an impression on how the Python client user interface might be changed.""") -- GitLab From fd0650aef3de47be12112937a09ca0380c8c1bb2 Mon Sep 17 00:00:00 2001 From: Daniel <d.hornung@indiscale.com> Date: Wed, 22 May 2024 14:00:29 +0200 Subject: [PATCH 05/33] FIX: No infinite recursion for 1-character strings. --- src/linkahead/common/models.py | 4 ++-- unittests/test_issues.py | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/linkahead/common/models.py b/src/linkahead/common/models.py index 483c5231..9df9788e 100644 --- a/src/linkahead/common/models.py +++ b/src/linkahead/common/models.py @@ -1709,8 +1709,8 @@ def _parse_value(datatype, value): return ret # This is for a special case, where the xml parser could not differentiate - # between single values and lists with one element. As - if hasattr(value, "__len__") and len(value) == 1: + # between single values and lists with one element. + if hasattr(value, "__len__") and not isinstance(value, str) and len(value) == 1: return _parse_value(datatype, value[0]) # deal with references diff --git a/unittests/test_issues.py b/unittests/test_issues.py index 7472f710..ba934009 100644 --- a/unittests/test_issues.py +++ b/unittests/test_issues.py @@ -64,3 +64,9 @@ def test_issue_156(): # </ParentList> assert value is project assert parents[0].name == "RTName" + + +def test_parse_datatype(): + """No infinite recursion.""" + from linkahead.common.models import _parse_value + assert 1 == _parse_value("labels0", "1") -- GitLab From e7efcb316825a0b788cb8dc375792372aeaf7424 Mon Sep 17 00:00:00 2001 From: Daniel Hornung <d.hornung@indiscale.com> Date: Wed, 18 Sep 2024 14:43:52 +0200 Subject: [PATCH 06/33] MAINT: Added deprecation warning to DropOffBox related options. --- src/linkahead/common/models.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/linkahead/common/models.py b/src/linkahead/common/models.py index a8144286..0cc0f11e 100644 --- a/src/linkahead/common/models.py +++ b/src/linkahead/common/models.py @@ -2279,12 +2279,12 @@ class File(Record): server's "caosroot" file system. @param file: A local path or python file object. The file designated by this argument will be uploaded to the server via HTTP. - @param pickup: A file/folder in the DropOffBox (the server will move that + @param pickup: Deprecated: A file/folder in the DropOffBox (the server will move that file into its "caosroot" file system). @param thumbnail: (Local) filename to a thumbnail for this file. @param properties: A list of properties for this file record. @todo is this implemented? - @param from_location: Deprecated, use `pickup` instead. + @param from_location: Deprecated x 2, use `pickup` instead. """ @@ -2311,10 +2311,12 @@ class File(Record): self.thumbnail = thumbnail self.pickup = pickup + if self.pickup is not None: + warn(DeprecationWarning("The DropOffBox is deprecated, do not use `pickup`.")) if from_location is not None: warn(DeprecationWarning( - "Param `from_location` is deprecated, use `pickup instead`.")) + "Param `from_location` is deprecated, as everything DropOffBox related.")) if self.pickup is None: self.pickup = from_location -- GitLab From 2600dacb6a3193f90d6174c84d29229a90bd2776 Mon Sep 17 00:00:00 2001 From: Daniel Hornung <d.hornung@indiscale.com> Date: Wed, 18 Sep 2024 15:40:40 +0200 Subject: [PATCH 07/33] MAINT: Removced DropOffBox class. --- src/linkahead/__init__.py | 2 +- src/linkahead/common/models.py | 36 ---------------------------------- 2 files changed, 1 insertion(+), 37 deletions(-) diff --git a/src/linkahead/__init__.py b/src/linkahead/__init__.py index cd54f8f4..2c34c252 100644 --- a/src/linkahead/__init__.py +++ b/src/linkahead/__init__.py @@ -42,7 +42,7 @@ from .common.datatype import (BOOLEAN, DATETIME, DOUBLE, FILE, INTEGER, LIST, REFERENCE, TEXT) # Import of the basic API classes: from .common.models import (ACL, ALL, FIX, NONE, OBLIGATORY, RECOMMENDED, - SUGGESTED, Container, DropOffBox, Entity, File, + SUGGESTED, Container, Entity, File, Info, Message, Permissions, Property, Query, QueryTemplate, Record, RecordType, delete, execute_query, get_global_acl, diff --git a/src/linkahead/common/models.py b/src/linkahead/common/models.py index 0cc0f11e..c4b4d15e 100644 --- a/src/linkahead/common/models.py +++ b/src/linkahead/common/models.py @@ -4999,42 +4999,6 @@ def execute_query( cache=cache, page_length=page_length) -class DropOffBox(list): - def __init__(self, *args, **kwargs): - warn(DeprecationWarning("The DropOffBox is deprecated and will be removed in future.")) - super().__init__(*args, **kwargs) - - path = None - - def sync(self): - c = get_connection() - _log_request("GET: Info") - http_response = c.retrieve(["Info"]) - body = http_response.read() - _log_response(body) - - xml = etree.fromstring(body) - - for child in xml: - if child.tag.lower() == "stats": - infoelem = child - - break - - for child in infoelem: - if child.tag.lower() == "dropoffbox": - dropoffboxelem = child - - break - del self[:] - self.path = dropoffboxelem.get('path') - - for f in dropoffboxelem: - self.append(f.get('path')) - - return self - - class UserInfo(): """User information from a server response. -- GitLab From 85d209eb825e8192c8e18527c0ea8777d1a79d8f Mon Sep 17 00:00:00 2001 From: Daniel Hornung <d.hornung@indiscale.com> Date: Wed, 25 Sep 2024 16:09:55 +0200 Subject: [PATCH 08/33] WIP: DropOffBox removal --- CHANGELOG.md | 2 ++ src/linkahead/common/models.py | 35 +--------------------------------- 2 files changed, 3 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b792cc1..78b4b206 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed ### +- `DropOffBox` class and related parameters (`pickup` for file uploading). + ### Fixed ### ### Security ### diff --git a/src/linkahead/common/models.py b/src/linkahead/common/models.py index c4b4d15e..6683ed11 100644 --- a/src/linkahead/common/models.py +++ b/src/linkahead/common/models.py @@ -358,17 +358,6 @@ class Entity: def file(self, new_file): self.__file = new_file - @property - def pickup(self): - if self.__pickup is not None or self._wrapped_entity is None: - return self.__pickup - - return self._wrapped_entity.pickup - - @pickup.setter - def pickup(self, new_pickup): - self.__pickup = new_pickup - def grant( self, realm: Optional[str] = None, @@ -2263,8 +2252,7 @@ class File(Record): """This class represents LinkAhead's file entities. For inserting a new file to the server, `path` gives the new location, and - (exactly?) one of `file` and `pickup` should (must?) be given to specify the - source of the file. + `file` specifies the source of the file. Symlinking from the "extroot" file system is not supported by this API yet, it can be done manually using the `InsertFilesInDir` flag. For sample code, @@ -2279,13 +2267,9 @@ class File(Record): server's "caosroot" file system. @param file: A local path or python file object. The file designated by this argument will be uploaded to the server via HTTP. - @param pickup: Deprecated: A file/folder in the DropOffBox (the server will move that - file into its "caosroot" file system). @param thumbnail: (Local) filename to a thumbnail for this file. @param properties: A list of properties for this file record. @todo is this implemented? - @param from_location: Deprecated x 2, use `pickup` instead. - """ def __init__( @@ -2295,9 +2279,7 @@ class File(Record): description: Optional[str] = None, # @ReservedAssignment path: Optional[str] = None, file: Union[str, TextIO, None] = None, - pickup: Optional[str] = None, # @ReservedAssignment thumbnail: Optional[str] = None, - from_location=None, ): Record.__init__(self, id=id, name=name, description=description) self.role = "File" @@ -2310,17 +2292,6 @@ class File(Record): self.file = file self.thumbnail = thumbnail - self.pickup = pickup - if self.pickup is not None: - warn(DeprecationWarning("The DropOffBox is deprecated, do not use `pickup`.")) - - if from_location is not None: - warn(DeprecationWarning( - "Param `from_location` is deprecated, as everything DropOffBox related.")) - - if self.pickup is None: - self.pickup = from_location - def to_xml( self, xml: Optional[etree._Element] = None, @@ -3975,8 +3946,6 @@ class Container(list): if hasattr(entity, '_upload') and entity._upload is not None: entity_xml.set("upload", entity._upload) - elif hasattr(entity, 'pickup') and entity.pickup is not None: - entity_xml.set("pickup", entity.pickup) insert_xml.append(entity_xml) @@ -4149,8 +4118,6 @@ class Container(list): if hasattr(entity, '_upload') and entity._upload is not None: entity_xml.set("upload", entity._upload) - elif hasattr(entity, 'pickup') and entity.pickup is not None: - entity_xml.set("pickup", entity.pickup) insert_xml.append(entity_xml) if len(self) > 0 and len(insert_xml) < 1: -- GitLab From b1a00123707c72e256f97fb7ca34aff7ee97401c Mon Sep 17 00:00:00 2001 From: Daniel <d.hornung@indiscale.com> Date: Mon, 9 Dec 2024 16:59:32 +0100 Subject: [PATCH 09/33] WIP: Better diff in case of different types. --- src/linkahead/apiutils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/linkahead/apiutils.py b/src/linkahead/apiutils.py index 1aa127d3..b0b65d8d 100644 --- a/src/linkahead/apiutils.py +++ b/src/linkahead/apiutils.py @@ -291,6 +291,7 @@ def compare_entities(entity0: Optional[Entity] = None, if entity0 is entity1: return diff + # FIXME Why not simply return a diff which says that the types are different?g if type(entity0) is not type(entity1): raise ValueError( "Comparison of different Entity types is not supported.") -- GitLab From b70d0f98bce6f7accfaffb9a2f159605d5d710df Mon Sep 17 00:00:00 2001 From: Daniel <d.hornung@indiscale.com> Date: Mon, 9 Dec 2024 17:04:46 +0100 Subject: [PATCH 10/33] WIP: Better diff in case of different types. --- src/linkahead/apiutils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/linkahead/apiutils.py b/src/linkahead/apiutils.py index b0b65d8d..53336a4a 100644 --- a/src/linkahead/apiutils.py +++ b/src/linkahead/apiutils.py @@ -291,10 +291,10 @@ def compare_entities(entity0: Optional[Entity] = None, if entity0 is entity1: return diff - # FIXME Why not simply return a diff which says that the types are different?g + # FIXME Why not simply return a diff which says that the types are different? if type(entity0) is not type(entity1): - raise ValueError( - "Comparison of different Entity types is not supported.") + diff[0]["type"] = type(entity0) + diff[1]["type"] = type(entity1) # compare special attributes for attr in SPECIAL_ATTRIBUTES: -- GitLab From 0dbb1a1943ce1c6984ce296462db0578a7e0b4b9 Mon Sep 17 00:00:00 2001 From: Florian Spreckelsen <f.spreckelsen@indiscale.com> Date: Tue, 14 Jan 2025 15:50:42 +0100 Subject: [PATCH 11/33] REL: Begin next release cycle --- CHANGELOG.md | 16 ++++++++++++++++ setup.py | 4 ++-- src/doc/conf.py | 4 ++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f56bc3ab..e780ce25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] ## + +### Added ### + +### Changed ### + +### Deprecated ### + +### Removed ### + +### Fixed ### + +### Security ### + +### Documentation ### + ## [0.17.0] - 2025-01-14 ## ### Added ### diff --git a/setup.py b/setup.py index 75bcf0c7..ab8555b8 100755 --- a/setup.py +++ b/setup.py @@ -46,10 +46,10 @@ from setuptools import find_packages, setup # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ######################################################################## -ISRELEASED = True +ISRELEASED = False MAJOR = 0 MINOR = 17 -MICRO = 0 +MICRO = 1 # Do not tag as pre-release until this commit # https://github.com/pypa/packaging/pull/515 # has made it into a release. Probably we should wait for pypa/packaging>=21.4 diff --git a/src/doc/conf.py b/src/doc/conf.py index 65600678..ce1aa8ff 100644 --- a/src/doc/conf.py +++ b/src/doc/conf.py @@ -29,10 +29,10 @@ copyright = '2024, IndiScale GmbH' author = 'Daniel Hornung' # The short X.Y version -version = '0.17.0' +version = '0.17.1' # The full version, including alpha/beta/rc tags # release = '0.5.2-rc2' -release = '0.17.0' +release = '0.17.1-dev' # -- General configuration --------------------------------------------------- -- GitLab From cc8c254e8145f45720a29f9fd58f3e4f6542100a Mon Sep 17 00:00:00 2001 From: Florian Spreckelsen <f.spreckelsen@indiscale.com> Date: Thu, 16 Jan 2025 13:05:55 +0100 Subject: [PATCH 12/33] DOC: Fix and extend docstrings of linkahead.utils.register_tests --- src/linkahead/utils/register_tests.py | 72 +++++++++++++++++---------- 1 file changed, 47 insertions(+), 25 deletions(-) diff --git a/src/linkahead/utils/register_tests.py b/src/linkahead/utils/register_tests.py index 6909544f..66fd4553 100644 --- a/src/linkahead/utils/register_tests.py +++ b/src/linkahead/utils/register_tests.py @@ -18,44 +18,62 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>. - -import linkahead as db -from linkahead import administration as admin - -""" -This module implements a registration procedure for integration tests which +"""This module implements a registration procedure for integration tests which need a running LinkAhead instance. -It ensures that tests do not accidentally overwrite data in real LinkAhead -instances, as it checks whether the running LinkAhead instance is actually the -correct one, that -should be used for these tests. - -The test files have to define a global variable TEST_KEY which must be unique -for each test using +It ensures that tests do not accidentally overwrite data in real +LinkAhead instances, as it checks whether the running LinkAhead +instance is actually the correct one, that should be used for these +tests. -set_test_key("ABCDE") +The test files have to define a global variable ``TEST_KEY`` which +must be unique for each test using +:py:meth:`~linkahead.utils.register_tests.set_test_key`. The test procedure (invoked by pytest) checks whether a registration information is stored in one of the server properties or otherwise -- offers to register this test in the currently running database ONLY if this - is empty. + +- offers to register this test in the currently running database ONLY if this is + empty. - fails otherwise with a RuntimeError -NOTE: you probably need to use pytest with the -s option to be able to - register the test interactively. Otherwise, the server property has to be - set before server start-up in the server.conf of the LinkAhead server. +.. note:: + + you probably need to use pytest with the -s option to be able to + register the test interactively. Otherwise, the server property + has to be set before server start-up in the server.conf of the + LinkAhead server. This module is intended to be used with pytest. -There is a pytest fixture "clear_database" that performs the above mentioned -checks and clears the database in case of success. +There is a pytest fixture +:py:meth:`~linkahead.utils.register_tests.clear_database` that +performs the above mentioned checks and clears the database in case of +success. + """ +import linkahead as db +from linkahead import administration as admin + TEST_KEY = None -def set_test_key(KEY): +def set_test_key(KEY: str): + """Set the global ``TEST_KEY`` variable to `KEY`. Afterwards, if + `KEY` matches the ``_CAOSDB_INTEGRATION_TEST_SUITE_KEY`` server + environment variable, mehtods like :py:meth:`clear_database` can + be used. Call this function in the beginning of your test file. + + Parameters + ---------- + KEY : str + key with which the test using this function is registered and + which is checked against the + ``_CAOSDB_INTEGRATION_TEST_SUITE_KEY`` server environment + variable. + + """ global TEST_KEY TEST_KEY = KEY @@ -122,10 +140,14 @@ try: @pytest.fixture def clear_database(): - """Remove Records, RecordTypes, Properties, and Files ONLY IF the LinkAhead - server the current connection points to was registered with the appropriate key. + """Remove Records, RecordTypes, Properties, and Files ONLY IF + the LinkAhead server the current connection points to was + registered with the appropriate key using + :py:meth:`set_test_key`. + + PyTestInfo Records and the corresponding RecordType and + Property are preserved. - PyTestInfo Records and the corresponding RecordType and Property are preserved. """ _assure_test_is_registered() yield _clear_database() # called before the test function -- GitLab From 90a8df2132508069cb949cb40ba4f63e0254b4ce Mon Sep 17 00:00:00 2001 From: Florian Spreckelsen <f.spreckelsen@indiscale.com> Date: Thu, 16 Jan 2025 13:07:55 +0100 Subject: [PATCH 13/33] DOC: Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e780ce25..23ddf860 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Documentation ### +* [#78](https://gitlab.com/linkahead/linkahead-pylib/-/issues/78) Fix + and extend test-registration docstrings. + ## [0.17.0] - 2025-01-14 ## ### Added ### -- GitLab From 6b4062059f34c8cdc87172487f9604a9d7b5c42d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20tom=20W=C3=B6rden?= <h.tomwoerden@indiscale.com> Date: Thu, 16 Jan 2025 17:12:15 +0100 Subject: [PATCH 14/33] FIX: run linting on all linkahead --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 21ea40ac..7490c5d5 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ style: .PHONY: style lint: - pylint --unsafe-load-any-extension=y -d all -e E,F src/linkahead/common + pylint --unsafe-load-any-extension=y -d all -e E,F src/linkahead .PHONY: lint mypy: -- GitLab From f6c3fba01eaad2f2e282a423e775ea60b53477c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20tom=20W=C3=B6rden?= <h.tomwoerden@indiscale.com> Date: Fri, 24 Jan 2025 10:52:05 +0100 Subject: [PATCH 15/33] ENH: add convenience functions `value_matches_versionid`, `get_id_from_versionid` and `get_versionid` --- CHANGELOG.md | 1 + src/linkahead/common/models.py | 21 +++++++++++++++++++++ unittests/test_entity.py | 28 +++++++++++++++++++++++++++- 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23ddf860..20f2498a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ## ### Added ### +- convenience functions `value_matches_versionid`, `get_id_from_versionid` and `get_versionid` ### Changed ### diff --git a/src/linkahead/common/models.py b/src/linkahead/common/models.py index 75b03b70..0912647a 100644 --- a/src/linkahead/common/models.py +++ b/src/linkahead/common/models.py @@ -505,6 +505,9 @@ class Entity: return self + def get_versionid(self): + return str(self.id)+"@"+str(self.version.id) + def get_importance(self, property): # @ReservedAssignment """Get the importance of a given property regarding this entity.""" @@ -1954,6 +1957,7 @@ class QueryTemplate(): return len(self.get_errors()) > 0 + class Parent(Entity): """The parent entities.""" @@ -2126,6 +2130,12 @@ class Property(Entity): return is_reference(self.datatype) + def value_matches_versionid(self): + return value_matches_versionid(self.value) + + def get_id_from_versionid_value(self): + return get_id_from_versionid(self.value) + class Message(object): def __init__( @@ -5670,3 +5680,14 @@ def _filter_entity_list_by_identity(listobject: list[Entity], if pid_none and name_match: matches.append(candidate) return matches + +def value_matches_versionid(value: Union[int, str]): + if isinstance(value, int): + return False + if not isinstance(value, str): + raise ValueError(f"A reference value needs to be int or str. It was {type(value)}. " + "Did you call value_matches_versionid on a non reference value?") + return "@" in value + +def get_id_from_versionid(versionid: str): + return versionid.split("@")[0] diff --git a/unittests/test_entity.py b/unittests/test_entity.py index 855e5a39..722930b2 100644 --- a/unittests/test_entity.py +++ b/unittests/test_entity.py @@ -30,7 +30,9 @@ import linkahead from linkahead import (INTEGER, Entity, Parent, Property, Record, RecordType, configure_connection) import warnings -from linkahead.common.models import SPECIAL_ATTRIBUTES +from linkahead.common.models import (SPECIAL_ATTRIBUTES, get_id_from_versionid, +value_matches_versionid) +from linkahead.common.versioning import Version from linkahead.connection.mockup import MockUpServerConnection from lxml import etree from pytest import raises @@ -295,3 +297,27 @@ def test_filter_by_identity(): t.parents.filter(pid=234) assert issubclass(w[-1].category, DeprecationWarning) assert "This function was renamed" in str(w[-1].message) + + +def test_value_matches_versionid(): + assert value_matches_versionid(234) is False, "integer is no version id" + assert value_matches_versionid("234") is False, ("string that only contains an integer is no " + "version id") + assert value_matches_versionid("234@bfe1a42cb37aae8ac625a757715d38814c274158") is True, ( + "integer is no version id") is True + with raises(ValueError): + value_matches_versionid(234.0) + p = Property(value=234) + assert p.value_matches_versionid() is False + p = Property(value="234@bfe1a42cb37aae8ac625a757715d38814c274158") + assert p.value_matches_versionid() is True + +def test_get_id_from_versionid(): + assert get_id_from_versionid("234@bfe1a42cb37aae8ac625a757715d38814c274158") == "234" + p = Property(value="234@bfe1a42cb37aae8ac625a757715d38814c274158") + assert p.get_id_from_versionid_value() == "234" + +def test_get_versionid(): + e = Entity(id=234) + e.version = Version(id="bfe1a42cb37aae8ac625a757715d38814c274158") + assert e.get_versionid() =="234@bfe1a42cb37aae8ac625a757715d38814c274158" -- GitLab From 85d807d332542485f12a3cdfd235a6b587f54c85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20tom=20W=C3=B6rden?= <h.tomwoerden@indiscale.com> Date: Fri, 24 Jan 2025 11:05:06 +0100 Subject: [PATCH 16/33] DOC: add docstrings --- src/linkahead/common/models.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/linkahead/common/models.py b/src/linkahead/common/models.py index 0912647a..12d36179 100644 --- a/src/linkahead/common/models.py +++ b/src/linkahead/common/models.py @@ -506,6 +506,7 @@ class Entity: return self def get_versionid(self): + """Returns the concatenation of ID and version""" return str(self.id)+"@"+str(self.version.id) def get_importance(self, property): # @ReservedAssignment @@ -2131,9 +2132,11 @@ class Property(Entity): def value_matches_versionid(self): + """Returns True if the value matches the pattern <id>@<version>""" return value_matches_versionid(self.value) def get_id_from_versionid_value(self): + """Returns the ID part of the versionid with the pattern <id>@<version>""" return get_id_from_versionid(self.value) class Message(object): @@ -5682,6 +5685,7 @@ def _filter_entity_list_by_identity(listobject: list[Entity], return matches def value_matches_versionid(value: Union[int, str]): + """Returns True if the value matches the pattern <id>@<version>""" if isinstance(value, int): return False if not isinstance(value, str): @@ -5690,4 +5694,5 @@ def value_matches_versionid(value: Union[int, str]): return "@" in value def get_id_from_versionid(versionid: str): + """Returns the ID part of the versionid with the pattern <id>@<version>""" return versionid.split("@")[0] -- GitLab From 1a8c63724874076b633cce93e7d636c33ff0c125 Mon Sep 17 00:00:00 2001 From: Florian Spreckelsen <f.spreckelsen@indiscale.com> Date: Fri, 24 Jan 2025 14:08:53 +0100 Subject: [PATCH 17/33] STY: autopep8'd --- src/linkahead/common/models.py | 5 +++-- unittests/test_entity.py | 8 +++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/linkahead/common/models.py b/src/linkahead/common/models.py index 12d36179..bfe43b01 100644 --- a/src/linkahead/common/models.py +++ b/src/linkahead/common/models.py @@ -1958,7 +1958,6 @@ class QueryTemplate(): return len(self.get_errors()) > 0 - class Parent(Entity): """The parent entities.""" @@ -2130,7 +2129,6 @@ class Property(Entity): else: return is_reference(self.datatype) - def value_matches_versionid(self): """Returns True if the value matches the pattern <id>@<version>""" return value_matches_versionid(self.value) @@ -2139,6 +2137,7 @@ class Property(Entity): """Returns the ID part of the versionid with the pattern <id>@<version>""" return get_id_from_versionid(self.value) + class Message(object): def __init__( @@ -5684,6 +5683,7 @@ def _filter_entity_list_by_identity(listobject: list[Entity], matches.append(candidate) return matches + def value_matches_versionid(value: Union[int, str]): """Returns True if the value matches the pattern <id>@<version>""" if isinstance(value, int): @@ -5693,6 +5693,7 @@ def value_matches_versionid(value: Union[int, str]): "Did you call value_matches_versionid on a non reference value?") return "@" in value + def get_id_from_versionid(versionid: str): """Returns the ID part of the versionid with the pattern <id>@<version>""" return versionid.split("@")[0] diff --git a/unittests/test_entity.py b/unittests/test_entity.py index 722930b2..2f413717 100644 --- a/unittests/test_entity.py +++ b/unittests/test_entity.py @@ -31,7 +31,7 @@ from linkahead import (INTEGER, Entity, Parent, Property, Record, RecordType, configure_connection) import warnings from linkahead.common.models import (SPECIAL_ATTRIBUTES, get_id_from_versionid, -value_matches_versionid) + value_matches_versionid) from linkahead.common.versioning import Version from linkahead.connection.mockup import MockUpServerConnection from lxml import etree @@ -302,7 +302,7 @@ def test_filter_by_identity(): def test_value_matches_versionid(): assert value_matches_versionid(234) is False, "integer is no version id" assert value_matches_versionid("234") is False, ("string that only contains an integer is no " - "version id") + "version id") assert value_matches_versionid("234@bfe1a42cb37aae8ac625a757715d38814c274158") is True, ( "integer is no version id") is True with raises(ValueError): @@ -312,12 +312,14 @@ def test_value_matches_versionid(): p = Property(value="234@bfe1a42cb37aae8ac625a757715d38814c274158") assert p.value_matches_versionid() is True + def test_get_id_from_versionid(): assert get_id_from_versionid("234@bfe1a42cb37aae8ac625a757715d38814c274158") == "234" p = Property(value="234@bfe1a42cb37aae8ac625a757715d38814c274158") assert p.get_id_from_versionid_value() == "234" + def test_get_versionid(): e = Entity(id=234) e.version = Version(id="bfe1a42cb37aae8ac625a757715d38814c274158") - assert e.get_versionid() =="234@bfe1a42cb37aae8ac625a757715d38814c274158" + assert e.get_versionid() == "234@bfe1a42cb37aae8ac625a757715d38814c274158" -- GitLab From 82ce5c08c7f35dedee7622660c28e211bf17db63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20tom=20W=C3=B6rden?= <h.tomwoerden@indiscale.com> Date: Fri, 24 Jan 2025 14:34:35 +0100 Subject: [PATCH 18/33] FIX: remove value_matches_versionid and get_id_from_versionid_value member functions --- src/linkahead/common/models.py | 8 -------- unittests/test_entity.py | 6 ------ 2 files changed, 14 deletions(-) diff --git a/src/linkahead/common/models.py b/src/linkahead/common/models.py index bfe43b01..8b8141be 100644 --- a/src/linkahead/common/models.py +++ b/src/linkahead/common/models.py @@ -2129,14 +2129,6 @@ class Property(Entity): else: return is_reference(self.datatype) - def value_matches_versionid(self): - """Returns True if the value matches the pattern <id>@<version>""" - return value_matches_versionid(self.value) - - def get_id_from_versionid_value(self): - """Returns the ID part of the versionid with the pattern <id>@<version>""" - return get_id_from_versionid(self.value) - class Message(object): diff --git a/unittests/test_entity.py b/unittests/test_entity.py index 2f413717..f2164d96 100644 --- a/unittests/test_entity.py +++ b/unittests/test_entity.py @@ -307,16 +307,10 @@ def test_value_matches_versionid(): "integer is no version id") is True with raises(ValueError): value_matches_versionid(234.0) - p = Property(value=234) - assert p.value_matches_versionid() is False - p = Property(value="234@bfe1a42cb37aae8ac625a757715d38814c274158") - assert p.value_matches_versionid() is True def test_get_id_from_versionid(): assert get_id_from_versionid("234@bfe1a42cb37aae8ac625a757715d38814c274158") == "234" - p = Property(value="234@bfe1a42cb37aae8ac625a757715d38814c274158") - assert p.get_id_from_versionid_value() == "234" def test_get_versionid(): -- GitLab From 1459d6c612da49d21aefb59f57ba3674626007c3 Mon Sep 17 00:00:00 2001 From: "i.nueske" <i.nueske@indiscale.com> Date: Fri, 28 Feb 2025 11:12:49 +0100 Subject: [PATCH 19/33] CI: Add python 3.14 to tests --- .gitlab-ci.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index db600343..a773c677 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -101,13 +101,26 @@ unittest_py3.12: script: *python_test_script unittest_py3.13: - allow_failure: true tags: [ docker ] stage: test needs: [ ] image: python:3.13 script: *python_test_script +unittest_py3.14: + allow_failure: true # remove on release + tags: [ docker ] + stage: test + needs: [ ] + image: python:3.14-rc + script: # replace by '*python_test_script' on release + # Install cargo manually, source its env, and set it to accept 3.14 as interpreter + - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + - . "$HOME/.cargo/env" + - export PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1 + # Continue normally + - *python_test_script + # Trigger building of server image and integration tests trigger_build: stage: deploy -- GitLab From d1cf1b9e3b2853c0d353c582aff5f515ea2815ed Mon Sep 17 00:00:00 2001 From: "i.nueske" <i.nueske@indiscale.com> Date: Tue, 11 Mar 2025 15:21:41 +0100 Subject: [PATCH 20/33] MNT: Change warnings.warn to logger.warning to enable warning suppression, and add it to the module docstring --- src/linkahead/high_level_api.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/linkahead/high_level_api.py b/src/linkahead/high_level_api.py index 18d219c7..9aa59fb9 100644 --- a/src/linkahead/high_level_api.py +++ b/src/linkahead/high_level_api.py @@ -26,11 +26,12 @@ # type: ignore """ A high level API for accessing LinkAhead entities from within python. +This module is experimental, and may be changed or removed in the future. This is refactored from apiutils. """ -import warnings +import logging from dataclasses import dataclass, fields from datetime import datetime from typing import Any, Dict, List, Optional, Union @@ -44,7 +45,10 @@ from .common.datatype import (BOOLEAN, DATETIME, DOUBLE, FILE, INTEGER, REFERENCE, TEXT, get_list_datatype, is_list_datatype, is_reference) -warnings.warn("""EXPERIMENTAL! The high_level_api module is experimental and may be changed or +logger = logging.getLogger(__name__) + + +logger.warning("""EXPERIMENTAL! The high_level_api module is experimental and may be changed or removed in the future. Its purpose is to give an impression on how the Python client user interface might be changed.""") -- GitLab From dc339849eaedfd911ce2fd321dbd09df8a2d5f07 Mon Sep 17 00:00:00 2001 From: "i.nueske" <i.nueske@indiscale.com> Date: Tue, 1 Apr 2025 13:15:09 +0200 Subject: [PATCH 21/33] FIX: Always import CredentialsAuthenticator, ignore linting errors on version import --- src/linkahead/__init__.py | 2 +- src/linkahead/connection/connection.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/linkahead/__init__.py b/src/linkahead/__init__.py index 567748e3..97203a20 100644 --- a/src/linkahead/__init__.py +++ b/src/linkahead/__init__.py @@ -55,7 +55,7 @@ from .utils.get_entity import (get_entity_by_id, get_entity_by_name, get_entity_by_path) try: - from .version import version as __version__ + from .version import version as __version_ # pylint: disable=import-error except ModuleNotFoundError: version = "uninstalled" __version__ = version diff --git a/src/linkahead/connection/connection.py b/src/linkahead/connection/connection.py index 74dd2317..1bee0b77 100644 --- a/src/linkahead/connection/connection.py +++ b/src/linkahead/connection/connection.py @@ -47,7 +47,7 @@ from ..exceptions import (ConfigurationError, HTTPClientError, LoginFailedError) try: - from ..version import version + from ..version import version # pylint: disable=import-error except ModuleNotFoundError: version = "uninstalled" @@ -56,11 +56,12 @@ from .interface import CaosDBHTTPResponse, CaosDBServerConnection from .utils import make_uri_path, urlencode from typing import TYPE_CHECKING +from .authentication.interface import CredentialsAuthenticator if TYPE_CHECKING: from typing import Optional, Any, Iterator, Union from requests.models import Response from ssl import _SSLMethod - from .authentication.interface import AbstractAuthenticator, CredentialsAuthenticator + from .authentication.interface import AbstractAuthenticator _LOGGER = logging.getLogger(__name__) -- GitLab From dfc66218e2d5c213344093d84f376a7116eb8521 Mon Sep 17 00:00:00 2001 From: "i.nueske" <i.nueske@indiscale.com> Date: Tue, 1 Apr 2025 13:34:33 +0200 Subject: [PATCH 22/33] FIX: Ignore false positive linting warnings --- src/linkahead/connection/connection.py | 2 +- src/linkahead/connection/encode.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/linkahead/connection/connection.py b/src/linkahead/connection/connection.py index 1bee0b77..fe99b421 100644 --- a/src/linkahead/connection/connection.py +++ b/src/linkahead/connection/connection.py @@ -60,7 +60,7 @@ from .authentication.interface import CredentialsAuthenticator if TYPE_CHECKING: from typing import Optional, Any, Iterator, Union from requests.models import Response - from ssl import _SSLMethod + from ssl import _SSLMethod # pylint: disable=no-name-in-module from .authentication.interface import AbstractAuthenticator diff --git a/src/linkahead/connection/encode.py b/src/linkahead/connection/encode.py index a7619780..0cbb0b69 100644 --- a/src/linkahead/connection/encode.py +++ b/src/linkahead/connection/encode.py @@ -384,7 +384,7 @@ class MultipartYielder(object): # since python 3 def __next__(self): - return self.next() + return self.next() # pylint: disable=not-callable def next(self): """generator function to yield multipart/form-data representation of -- GitLab From d3b90636156c8d99bf9e6357387bf42e1bd3b1e2 Mon Sep 17 00:00:00 2001 From: "i.nueske" <i.nueske@indiscale.com> Date: Tue, 1 Apr 2025 13:45:35 +0200 Subject: [PATCH 23/33] FIX: Revert accidental rename --- src/linkahead/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linkahead/__init__.py b/src/linkahead/__init__.py index 97203a20..ac8df123 100644 --- a/src/linkahead/__init__.py +++ b/src/linkahead/__init__.py @@ -55,7 +55,7 @@ from .utils.get_entity import (get_entity_by_id, get_entity_by_name, get_entity_by_path) try: - from .version import version as __version_ # pylint: disable=import-error + from .version import version as __version__ # pylint: disable=import-error except ModuleNotFoundError: version = "uninstalled" __version__ = version -- GitLab From 49382a8fa5f0be460fd3fe34b7718021d25cf336 Mon Sep 17 00:00:00 2001 From: Daniel <d.hornung@indiscale.com> Date: Tue, 8 Apr 2025 11:13:20 +0200 Subject: [PATCH 24/33] STYLE: Whitespace only. --- src/linkahead/high_level_api.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/linkahead/high_level_api.py b/src/linkahead/high_level_api.py index 44686a42..a835fbb3 100644 --- a/src/linkahead/high_level_api.py +++ b/src/linkahead/high_level_api.py @@ -688,9 +688,11 @@ class CaosDBPythonEntity(object): Parameters ---------- + without_metadata: bool, optional If True don't set the metadata field in order to increase readability. Not recommended if deserialization is needed. + plain_json: bool, optional If True, serialize to a plain dict without any additional information besides the property values, name and id. This should conform to the format as specified by the json schema generated by the -- GitLab From af843d65c6de11fea91f01728d2659c18d312b8f Mon Sep 17 00:00:00 2001 From: Daniel <d.hornung@indiscale.com> Date: Tue, 8 Apr 2025 14:36:30 +0200 Subject: [PATCH 25/33] TEST: Added unit test for high level api serialization. --- unittests/test_high_level_api.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/unittests/test_high_level_api.py b/unittests/test_high_level_api.py index 82c1a5ca..e35dc678 100644 --- a/unittests/test_high_level_api.py +++ b/unittests/test_high_level_api.py @@ -322,6 +322,7 @@ def test_wrong_entity_for_file(): def test_serialization(): + # With ID r = db.Record(id=5, name="test", description="ok") r.add_property(name="v", value=15, datatype=db.INTEGER, unit="kpx", importance="RECOMMENDED") @@ -333,6 +334,22 @@ def test_serialization(): for teststr in teststrs: assert teststr in text + serialized = convert_to_python_object(r).serialize() + assert serialized == {'role': 'Record', + 'name': 'test', + 'id': 5, + 'description': 'ok', + 'properties': {'v': 15}, + 'parents': [], + 'metadata': {'v': {'unit': 'kpx', + 'datatype': 'INTEGER', + 'importance': 'RECOMMENDED'}}} + + serialized_plain = convert_to_python_object(r).serialize(plain_json=True) + assert serialized_plain == {'id': 5, 'name': 'test', 'v': 15} + + # Without ID + r = db.Record(description="ok") r.add_property(name="v", value=15, datatype=db.INTEGER, unit="kpx", importance="RECOMMENDED") @@ -341,6 +358,18 @@ def test_serialization(): assert "name" not in text assert "id" not in text + serialized = convert_to_python_object(r).serialize() + assert serialized == {'role': 'Record', + 'description': 'ok', + 'properties': {'v': 15}, + 'parents': [], + 'metadata': {'v': {'unit': 'kpx', + 'datatype': 'INTEGER', + 'importance': 'RECOMMENDED'}}} + + serialized_plain = convert_to_python_object(r).serialize(plain_json=True) + assert serialized_plain == {'id': None, 'name': None, 'v': 15} + def test_files(): # empty file: -- GitLab From 70588ba60165370b1262581b1fc620e3bbde3d94 Mon Sep 17 00:00:00 2001 From: Daniel <d.hornung@indiscale.com> Date: Tue, 8 Apr 2025 14:40:34 +0200 Subject: [PATCH 26/33] DOCS: More documentation for the high level api serialization. --- CHANGELOG.md | 2 ++ src/linkahead/high_level_api.py | 28 ++++++++++++++++++---------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20f2498a..33142766 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ## ### Added ### + - convenience functions `value_matches_versionid`, `get_id_from_versionid` and `get_versionid` +- Parameter for high level API serialization to output a plain JSON. ### Changed ### diff --git a/src/linkahead/high_level_api.py b/src/linkahead/high_level_api.py index a835fbb3..a0137dac 100644 --- a/src/linkahead/high_level_api.py +++ b/src/linkahead/high_level_api.py @@ -683,20 +683,28 @@ class CaosDBPythonEntity(object): return entity def serialize(self, without_metadata: bool = None, plain_json: bool = False, - visited: dict = None): + visited: dict = None) -> dict: """Serialize necessary information into a dict. -Parameters ----------- + Parameters + ---------- -without_metadata: bool, optional - If True don't set the metadata field in order to increase - readability. Not recommended if deserialization is needed. + without_metadata: bool, optional + If True don't set the metadata field in order to increase + readability. Not recommended if deserialization is needed. -plain_json: bool, optional - If True, serialize to a plain dict without any additional information besides the property values, - name and id. This should conform to the format as specified by the json schema generated by the - advanced user tools. This implies ``without_metadata = True``. + plain_json: bool, optional + If True, serialize to a plain dict without any additional information besides the property values, + name and id. This should conform to the format as specified by the json schema generated by the + advanced user tools. It also sets all properties as top level items of the resulting dict. This + implies ``without_metadata = True + + Returns + ------- + + out: dict + A dict corresponding to this entity. + ``. """ if plain_json: if without_metadata is None: -- GitLab From 7934fb3a7a638fe2eb32ef36a421f7e51fd645b4 Mon Sep 17 00:00:00 2001 From: Daniel <d.hornung@indiscale.com> Date: Wed, 9 Apr 2025 15:16:58 +0200 Subject: [PATCH 27/33] DOCS: Small docstring fix. --- src/linkahead/common/models.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/linkahead/common/models.py b/src/linkahead/common/models.py index 6de27153..a3b4683a 100644 --- a/src/linkahead/common/models.py +++ b/src/linkahead/common/models.py @@ -2315,21 +2315,23 @@ class File(Record): look at `test_files.py` in the Python integration tests of the `load_files.py` script in the advanced user tools. - @param name: A name for this file record (That's an entity name - not to be - confused with the last segment of the files path). - @param id: An ID. - @param description: A description for this file record. - @param path: The complete path, including the file name, of the file in the - server's "caosroot" file system. - @param file: A local path or python file object. The file designated by - this argument will be uploaded to the server via HTTP. - @param pickup: A file/folder in the DropOffBox (the server will move that - file into its "caosroot" file system). - @param thumbnail: (Local) filename to a thumbnail for this file. - @param properties: A list of properties for this file record. @todo is this - implemented? - @param from_location: Deprecated, use `pickup` instead. - + @param name + A name for this file *Record* (That's an entity name - not to be confused with the last + segment of the files path). + @param id + An ID. + @param description + A description for this file record. + @param path + The complete path, including the file name, of the file in the server's "caosroot" file + system. + @param file + A local path or python file object. The file designated by this argument will be uploaded + to the server via HTTP. + @param thumbnail + (Local) filename to a thumbnail for this file. + @param properties + A list of properties for this file record. @todo is this implemented? """ def __init__( -- GitLab From 9639455f558fbc0120c4ac9a58de6810b974d996 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20tom=20W=C3=B6rden?= <h.tomwoerden@indiscale.com> Date: Thu, 10 Apr 2025 09:24:11 +0200 Subject: [PATCH 28/33] FIX: treat None case in depenency search --- src/linkahead/common/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/linkahead/common/models.py b/src/linkahead/common/models.py index a3b4683a..69ac403b 100644 --- a/src/linkahead/common/models.py +++ b/src/linkahead/common/models.py @@ -3824,6 +3824,8 @@ class Container(list): is_being_referenced.add(prop.value) elif is_list_datatype(prop_dt): for list_item in prop.value: + if list_item is None: + continue if isinstance(list_item, int): is_being_referenced.add(list_item) else: -- GitLab From cdd23d0ca8a9ff54e0bd1d21f2b5b152cd222b46 Mon Sep 17 00:00:00 2001 From: Daniel <d.hornung@indiscale.com> Date: Tue, 15 Apr 2025 17:52:13 +0200 Subject: [PATCH 29/33] FEAT: `resolve_references` parameter In `high_level_api.convert_to_python_object()` --- CHANGELOG.md | 3 ++- src/linkahead/high_level_api.py | 28 ++++++++++++++++------------ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33142766..35b6a480 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added ### - convenience functions `value_matches_versionid`, `get_id_from_versionid` and `get_versionid` -- Parameter for high level API serialization to output a plain JSON. +- High level API: Parameter for serialization to output a plain JSON. +- High level API: Parameter to resolve references when converting to Python object. ### Changed ### diff --git a/src/linkahead/high_level_api.py b/src/linkahead/high_level_api.py index a0137dac..45839d9b 100644 --- a/src/linkahead/high_level_api.py +++ b/src/linkahead/high_level_api.py @@ -4,9 +4,10 @@ # # Copyright (C) 2018 Research Group Biomedical Physics, # Max-Planck-Institute for Dynamics and Self-Organization Göttingen +# Copyright (C) 2020-2022,2025 IndiScale GmbH <info@indiscale.com> # Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com> -# Copyright (C) 2020-2022 IndiScale GmbH <info@indiscale.com> # Copyright (C) 2022 Alexander Schlemmer <alexander.schlemmer@ds.mpg.de> +# Copyright (C) 2025 Daniel Hornung <d.hornung@indiscale.com> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -21,8 +22,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>. # -# ** end header -# + # type: ignore """ A high level API for accessing LinkAhead entities from within python. @@ -974,26 +974,32 @@ def convert_to_entity(python_object): def convert_to_python_object(entity: Union[db.Container, db.Entity], references: Optional[db.Container] = None, visited: Optional[Dict[int, - "CaosDBPythonEntity"]] = None): + "CaosDBPythonEntity"]] = None, + resolve_references: Optional[bool] = False, + ): """ Convert either a container of CaosDB entities or a single CaosDB entity into the high level representation. - The optional second parameter can be used + The optional ``references`` parameter can be used to resolve references that occur in the converted entities and resolve them to their correct representations. (Entities that are not found remain as - CaosDBPythonUnresolvedReferences.) + CaosDBPythonUnresolvedReferences, unless ``resolve_references`` is given and True.) """ if isinstance(entity, db.Container): # Create a list of objects: - return [convert_to_python_object(i, references, visited) for i in entity] + return [convert_to_python_object(ent, references=references, visited=visited, + resolve_references=resolve_references) for ent in entity] # TODO: recursion problems? - return _single_convert_to_python_object( + converted = _single_convert_to_python_object( high_level_type_for_standard_type(entity)(), entity, references, visited) + if resolve_references: + converted.resolve_references(True, references) + return converted def new_high_level_entity(entity: db.RecordType, @@ -1077,8 +1083,6 @@ def query(query: str, """ res = db.execute_query(query) - objects = convert_to_python_object(res) - if resolve_references: - for obj in objects: - obj.resolve_references(True, references) + objects = convert_to_python_object(res, references=references, + resolve_references=resolve_references) return objects -- GitLab From 34f72e35e1f4a6d9c30877360552a45b919e7b8f Mon Sep 17 00:00:00 2001 From: Daniel <d.hornung@indiscale.com> Date: Thu, 24 Apr 2025 08:58:32 +0200 Subject: [PATCH 30/33] FIX: Reverted some changes. --- src/linkahead/common/models.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/linkahead/common/models.py b/src/linkahead/common/models.py index 0985d8f0..da46c563 100644 --- a/src/linkahead/common/models.py +++ b/src/linkahead/common/models.py @@ -338,6 +338,17 @@ class Entity: def file(self, new_file): self.__file = new_file + # FIXME Add test. + @property # getter for _cuid + def cuid(self): + # Set if None? + return self._cuid + + # FIXME Add test. + @property # getter for _flags + def flags(self): + return self._flags.copy() # for dict[str, str] shallow copy is enough + def grant( self, realm: Optional[str] = None, -- GitLab From 10db17e97bc78d0183c87f23f9d4b082388c4981 Mon Sep 17 00:00:00 2001 From: Daniel <d.hornung@indiscale.com> Date: Wed, 7 May 2025 09:34:46 +0200 Subject: [PATCH 31/33] DOCS: Added note to docstring. --- src/linkahead/common/models.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/linkahead/common/models.py b/src/linkahead/common/models.py index da46c563..8d1afbac 100644 --- a/src/linkahead/common/models.py +++ b/src/linkahead/common/models.py @@ -3338,6 +3338,11 @@ class Container(list): Returns ------- xml_element : etree._Element + + Note + ---- + Calling this method has the side effect that all entities without ID will get a negative + integer ID. """ tmpid = 0 -- GitLab From 17be5bafafc45084d5fd0dd131429de22af219ee Mon Sep 17 00:00:00 2001 From: Daniel <d.hornung@indiscale.com> Date: Fri, 9 May 2025 13:35:28 +0200 Subject: [PATCH 32/33] DOCS: Fixed typo. --- src/linkahead/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linkahead/exceptions.py b/src/linkahead/exceptions.py index 7d4dc085..0904929c 100644 --- a/src/linkahead/exceptions.py +++ b/src/linkahead/exceptions.py @@ -190,7 +190,7 @@ class QueryNotUniqueError(BadQueryError): class EmptyUniqueQueryError(BadQueryError): - """A unique query or retrieve dound no result.""" + """A unique query or retrieve found no result.""" # ######################### Transaction errors ######################### -- GitLab From 8952c3a8ca7bdd92fba8f8e994f839002b29eb25 Mon Sep 17 00:00:00 2001 From: Florian Spreckelsen <f.spreckelsen@indiscale.com> Date: Tue, 27 May 2025 17:37:28 +0200 Subject: [PATCH 33/33] BUILD: Bump versions for release --- CHANGELOG.md | 10 +--------- CITATION.cff | 4 ++-- setup.py | 6 +++--- src/doc/conf.py | 4 ++-- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd0c7524..790b8aba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] ## +## [0.18.0] - 2025-05-27 ## ### Added ### @@ -13,18 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - High level API: Parameter for serialization to output a plain JSON. - High level API: Parameter to resolve references when converting to Python object. -### Changed ### - -### Deprecated ### - ### Removed ### - `DropOffBox` class and related parameters (`pickup` for file uploading). -### Fixed ### - -### Security ### - ### Documentation ### * [#78](https://gitlab.com/linkahead/linkahead-pylib/-/issues/78) Fix diff --git a/CITATION.cff b/CITATION.cff index bcecc2fd..e685ff0c 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -20,6 +20,6 @@ authors: given-names: Stefan orcid: https://orcid.org/0000-0001-7214-8125 title: CaosDB - Pylib -version: 0.17.0 +version: 0.18.0 doi: 10.3390/data4020083 -date-released: 2025-01-14 +date-released: 2025-05-27 diff --git a/setup.py b/setup.py index ab8555b8..9047cbd0 100755 --- a/setup.py +++ b/setup.py @@ -46,10 +46,10 @@ from setuptools import find_packages, setup # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ######################################################################## -ISRELEASED = False +ISRELEASED = True MAJOR = 0 -MINOR = 17 -MICRO = 1 +MINOR = 18 +MICRO = 0 # Do not tag as pre-release until this commit # https://github.com/pypa/packaging/pull/515 # has made it into a release. Probably we should wait for pypa/packaging>=21.4 diff --git a/src/doc/conf.py b/src/doc/conf.py index ce1aa8ff..46c1645c 100644 --- a/src/doc/conf.py +++ b/src/doc/conf.py @@ -29,10 +29,10 @@ copyright = '2024, IndiScale GmbH' author = 'Daniel Hornung' # The short X.Y version -version = '0.17.1' +version = '0.18.0' # The full version, including alpha/beta/rc tags # release = '0.5.2-rc2' -release = '0.17.1-dev' +release = '0.18.0' # -- General configuration --------------------------------------------------- -- GitLab