diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c2d071662c26322d44ed98e6e164c523edcae5af..0f9a258de99ba559d280fc5ace74a3f111a9e30e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -62,11 +62,11 @@ mypy: allow_failure: true # run unit tests -unittest_py3.7: +unittest_py3.8: tags: [ docker ] stage: test needs: [ ] - image: python:3.7 + image: python:3.8 script: &python_test_script # Python docker has problems with tox and pip so use plain pytest here - touch ~/.pylinkahead.ini @@ -74,13 +74,6 @@ unittest_py3.7: - pip install . - python -m pytest unittests -unittest_py3.8: - tags: [ docker ] - stage: test - needs: [ ] - image: python:3.8 - script: *python_test_script - # This needs to be changed once Python 3.9 isn't the standard Python in Debian # anymore. unittest_py3.9: @@ -121,8 +114,14 @@ unittest_py3.13: stage: test needs: [ ] image: python:3.13-rc - script: *python_test_script - + script: + # TODO: Replace by '*python_test_script' as soon as 3.13 has been officially released. + # Python docker has problems with tox and pip so use plain pytest here + - apt update && apt install -y cargo + - touch ~/.pylinkahead.ini + - pip install pynose pytest pytest-cov jsonschema>=4.4.0 setuptools + - pip install . + - python -m pytest unittests # Trigger building of server image and integration tests trigger_build: diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a1e39a852bfa5f7700cf5a59a57eb2cfe5b041c..d168b98c6e488fd99ee4670c4495e07b62ab08d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added ### * Support for Python 3.12 -* The `linkahead` module now opts into type checking and supports mypy. +* The `linkahead` module now opts into type checking and supports mypy. ### Changed ### @@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed ### +* Support for Python 3.7 + ### Fixed ### * [#104](https://gitlab.com/linkahead/linkahead-pylib/-/issues/104) Selecting diff --git a/setup.py b/setup.py index 27f305c28c70dccdbf1a27fd5a2a4aa9e153f006..ee2a5fb6fd7212acfc9ce9bc732fc9f2d4f345b4 100755 --- a/setup.py +++ b/setup.py @@ -179,7 +179,7 @@ def setup_package(): "Topic :: Scientific/Engineering :: Information Analysis", ], packages=find_packages('src'), - python_requires='>=3.7', + python_requires='>=3.8', package_dir={'': 'src'}, install_requires=['lxml>=4.6.3', "requests[socks]>=2.26", diff --git a/src/doc/high_level_api.rst b/src/doc/high_level_api.rst index 5f8ae7f9b998fd1205674250383f06ae25aaf460..df9f353bf95847b01dd753d90109f2ec30ec92ba 100644 --- a/src/doc/high_level_api.rst +++ b/src/doc/high_level_api.rst @@ -18,7 +18,7 @@ Or to speak it out directly in Python: r.get_property("alpha").value = 25 # setting properties (old api) print(r.get_property("alpha").value + 25) # getting properties (old api) - from linkahead.high_level_api import convert_to_python_entity + from linkahead.high_level_api import convert_to_python_object obj = convert_to_python_object(r) # create a high level entity obj.r = 25 # setting properties (new api) print(obj.r + 25) # getting properties (new api) diff --git a/src/linkahead/common/models.py b/src/linkahead/common/models.py index d1c1c264c6fbdfbfcdacc14232adecd4068eea96..412c48c306d23e2a52f058ff2e237d7ff7609621 100644 --- a/src/linkahead/common/models.py +++ b/src/linkahead/common/models.py @@ -477,10 +477,10 @@ class Entity: if role is None and self.permissions is not None: # pylint: disable=unsupported-membership-test return permission in self.permissions - else: - if self.acl is None: - raise EntityHasNoAclError("This entity does not have an ACL (yet).") - self.acl.is_permitted(role=role, permission=permission) + + if self.acl is None: + raise EntityHasNoAclError("This entity does not have an ACL (yet).") + return self.acl.is_permitted(role=role, permission=permission) def get_all_messages(self) -> Messages: ret = Messages() @@ -1045,7 +1045,7 @@ out: List[Entity] return p else: - raise ValueError("pattern argument should be Entity, int or str") + raise ValueError("`pattern` argument should be an Entity, int or str.") return None @@ -3365,7 +3365,7 @@ class Container(list): # which is to be synced with which: # sync_dict[local_entity]=sync_remote_enities - sync_dict: Dict[Entity, Optional[List[Entity]]] = dict() + sync_dict: Dict[Union[Container, Entity], Optional[List[Entity]]] = dict() # list of remote entities which already have a local equivalent used_remote_entities = [] @@ -3968,7 +3968,7 @@ class Container(list): else: entity._checksum = None - # FIXME: The signature of Conatiner.insert is completely different than the superclass' + # FIXME: The signature of Container.insert is completely different than the superclass' # list.insert method. This may be a problem in the future, but is ignored for now. def insert( # type: ignore self, @@ -3983,20 +3983,23 @@ class Container(list): identified, retrieved, updated, and deleted via this ID until it has been deleted. - If the insertion fails, a LinkAheadException will be raised. The server will have returned at - least one error-message describing the reason why it failed in that case (call + If the insertion fails, a LinkAheadException will be raised. The server will have returned + at least one error-message describing the reason why it failed in that case (call <this_entity>.get_all_messages() in order to get these error-messages). - Some insertions might cause warning-messages on the server-side, but the entities are inserted - anyway. Set the flag 'strict' to True in order to force the server to take all warnings as errors. - This prevents the server from inserting this entity if any warning occurs. + Some insertions might cause warning-messages on the server-side, but the entities are + inserted anyway. Set the flag 'strict' to True in order to force the server to take all + warnings as errors. This prevents the server from inserting this entity if any warning + occurs. @param strict=False: Flag for strict mode. @param sync=True: synchronize this container with the response from the server. Otherwise, - this method returns a new container with the inserted entities and leaves this container untouched. - @param unique=True: Flag for unique mode. If set to True, the server will check if the name of the - entity is unique. If not, the server will return an error. + this method returns a new container with the inserted entities and leaves + this container untouched. + @param unique=True: Flag for unique mode. If set to True, the server will check if the name + of the entity is unique. If not, the server will return an error. @param flags=None: Additional flags for the server. + """ self.clear_server_messages() @@ -4266,9 +4269,7 @@ class ACI(): e.set("role", self.role) else: if self.username is None: - raise LinkAheadException( - "An ACI must have either a role or a username." - ) + raise LinkAheadException("An ACI must have either a role or a username.") e.set("username", self.username) if self.realm is not None: @@ -4656,8 +4657,8 @@ class Query(): Attributes ---------- - q : str - The query string. + q : str, etree._Element + The query string, may also be a query XML snippet. flags : dict of str A dictionary of flags to be send with the query request. messages : Messages() @@ -4688,13 +4689,11 @@ class Query(): self.etag = None if isinstance(q, etree._Element): - string = q.get("string") - self.q = string if string is not None else "" + q.get("string") + self.q = q.get("string", "") results = q.get("results") if results is None: - raise LinkAheadException( - "The query result count is not available in the response." - ) + raise LinkAheadException("The query result count is not available in the response.") self.results = int(results) cached_value = q.get("cached") @@ -5097,9 +5096,9 @@ def _parse_single_xml_element(elem: etree._Element): return Message(type='History', description=elem.get("transaction")) elif elem.tag.lower() == 'stats': counts = elem.find("counts") - if counts is not None: - attrib: Union[str, etree._Attrib] = counts.attrib - return Message(type="Counts", description=None, body=attrib) + if counts is None: + raise LinkAheadException("'stats' element without a 'count' found.") + return Message(type="Counts", description=None, body=counts.attrib) elif elem.tag == "EntityACL": return ACL(xml=elem) elif elem.tag == "Permissions": diff --git a/tox.ini b/tox.ini index b87f6e8140dbc431d0b190301dbfa1125e4b8ede..bbaaa1fc9eec2aba87c247d783818d215d8a7d5e 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=py37, py38, py39, py310, py311, py312, py313 +envlist=py38, py39, py310, py311, py312, py313 skip_missing_interpreters = true [testenv]