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 39663cc746c2ff4192d185f3fb303f62e7ef1ac5..d168b98c6e488fd99ee4670c4495e07b62ab08d5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +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.
 
 ### Changed ###
 
@@ -17,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/Makefile b/Makefile
index 21ea40ac8a6eb34032aba75c089e278fa354a6f5..9e4d30dbf8dab85892c220136466360f48d89042 100644
--- a/Makefile
+++ b/Makefile
@@ -44,7 +44,7 @@ lint:
 .PHONY: lint
 
 mypy:
-	mypy src/linkahead/common unittests
+	mypy src/linkahead/common unittests --exclude high_level_api.py --exclude connection.py
 .PHONY: mypy
 
 unittest:
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 1d8e8832a3d215363af5ffcb3139d24cb6c27bc3..356a89c81fc8d91973c59e313555b188d09de7d3 100644
--- a/src/linkahead/common/models.py
+++ b/src/linkahead/common/models.py
@@ -54,6 +54,9 @@ if TYPE_CHECKING and sys.version_info > (3, 7):
     from datetime import datetime
     from typing import Any, Dict, Optional, Type, Union, List, TextIO, Tuple, Literal
     from .datatype import DATATYPE
+    from tempfile import _TemporaryFileWrapper
+    from io import BufferedWriter
+
 
 from warnings import warn
 
@@ -62,15 +65,26 @@ from lxml import etree
 from ..configuration import get_config
 from ..connection.connection import get_connection
 from ..connection.encode import MultipartParam, multipart_encode
-from ..exceptions import (AmbiguousEntityError, AuthorizationError,
-                          ConsistencyError, EmptyUniqueQueryError,
-                          EntityDoesNotExistError, EntityError,
-                          EntityHasNoDatatypeError, HTTPURITooLongError,
-                          LinkAheadConnectionError, LinkAheadException,
-                          MismatchingEntitiesError, PagingConsistencyError,
-                          QueryNotUniqueError, TransactionError,
-                          UniqueNamesError, UnqualifiedParentsError,
-                          UnqualifiedPropertiesError)
+from ..exceptions import (
+    AmbiguousEntityError,
+    AuthorizationError,
+    ConsistencyError,
+    EmptyUniqueQueryError,
+    EntityDoesNotExistError,
+    EntityError,
+    EntityHasNoAclError,
+    EntityHasNoDatatypeError,
+    HTTPURITooLongError,
+    LinkAheadConnectionError,
+    LinkAheadException,
+    MismatchingEntitiesError,
+    PagingConsistencyError,
+    QueryNotUniqueError,
+    TransactionError,
+    UniqueNamesError,
+    UnqualifiedParentsError,
+    UnqualifiedPropertiesError,
+)
 from .datatype import (
     BOOLEAN,
     DATETIME,
@@ -95,7 +109,8 @@ FIX = "FIX"
 ALL = "ALL"
 NONE = "NONE"
 if TYPE_CHECKING:
-    INHERITANCE = Literal["OBLIGATORY", "SUGGESTED", "RECOMMENDED", "FIX", "ALL", "NONE"]
+    INHERITANCE = Literal["OBLIGATORY", "SUGGESTED", "RECOMMENDED", "ALL", "NONE"]
+    IMPORTANCE = Literal["OBLIGATORY", "RECOMMENDED", "SUGGESTED", "FIX", "NONE"]
 
 SPECIAL_ATTRIBUTES = ["name", "role", "datatype", "description",
                       "id", "path", "checksum", "size", "value"]
@@ -387,6 +402,10 @@ class Entity:
             ACL will be revoked.
         """
         # @review Florian Spreckelsen 2022-03-17
+
+        if self.acl is None:
+            raise EntityHasNoAclError("This entity does not have an ACL (yet).")
+
         self.acl.grant(realm=realm, username=username, role=role,
                        permission=permission, priority=priority,
                        revoke_denial=revoke_denial)
@@ -429,6 +448,9 @@ class Entity:
             ACL will be revoked.
         """
         # @review Florian Spreckelsen 2022-03-17
+        if self.acl is None:
+            raise EntityHasNoAclError("This entity does not have an ACL (yet).")
+
         self.acl.deny(realm=realm, username=username, role=role,
                       permission=permission, priority=priority,
                       revoke_grant=revoke_grant)
@@ -452,12 +474,13 @@ class Entity:
             priority=priority)
 
     def is_permitted(self, permission: Permission, role: Optional[str] = None):
-        if role is None:
+        if role is None and self.permissions is not None:
             # pylint: disable=unsupported-membership-test
-
             return permission in self.permissions
-        else:
-            self.acl.is_permitted(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()
@@ -534,20 +557,24 @@ class Entity:
 
         """
 
-        if self.get_property(property_name) is None:
+        property = self.get_property(property_name)
+        if property is None:
             return self
-        if self.get_property(property_name).value is None:
+
+        if property.value is None:
             remove_if_empty_afterwards = False
+
         empty_afterwards = False
-        if isinstance(self.get_property(property_name).value, list):
-            if value in self.get_property(property_name).value:
-                self.get_property(property_name).value.remove(value)
-                if self.get_property(property_name).value == []:
-                    self.get_property(property_name).value = None
+        if isinstance(property.value, list):
+            if value in property.value:
+                property.value.remove(value)
+                if property.value == []:
+                    property.value = None
                     empty_afterwards = True
-        elif self.get_property(property_name).value == value:
-            self.get_property(property_name).value = None
+        elif property.value == value:
+            property.value = None
             empty_afterwards = True
+
         if remove_if_empty_afterwards and empty_afterwards:
             self.remove_property(property_name)
 
@@ -576,10 +603,10 @@ class Entity:
         id: Optional[int] = None,
         name: Optional[str] = None,
         description: Optional[str] = None,
-        datatype: Optional[str] = None,
+        datatype: Optional[DATATYPE] = None,
         unit: Optional[str] = None,
-        importance: Optional[str] = None,
-        inheritance: Union[str, INHERITANCE, None] = None,
+        importance: Optional[IMPORTANCE] = None,
+        inheritance: Optional[INHERITANCE] = None,
     ) -> Entity:  # @ReservedAssignment
         """Add a property to this entity.
 
@@ -755,7 +782,7 @@ class Entity:
         parent: Union[Entity, int, str, None] = None,
         id: Optional[int] = None,
         name: Optional[str] = None,
-        inheritance: Union[INHERITANCE, str, None] = None,
+        inheritance: INHERITANCE = "NONE",
     ):  # @ReservedAssignment
         """Add a parent to this entity.
 
@@ -771,7 +798,7 @@ class Entity:
             Name of the parent entity. Ignored if `parent is not
             none`.
         inheritance : str, INHERITANCE
-            One of ``obligatory``, ``recommended``, ``suggested``, or ``fix``. Specifies the
+            One of ``obligatory``, ``recommended``, ``suggested``, or ``all``. Specifies the
             minimum importance which parent properties need to have to be inherited by this
             entity. If no `inheritance` is given, no properties will be inherited by the child.
             This parameter is case-insensitive.
@@ -1018,7 +1045,7 @@ out: List[Entity]
 
                     return p
         else:
-            raise ValueError("argument should be entity, int , string")
+            raise ValueError("`pattern` argument should be an Entity, int or str.")
 
         return None
 
@@ -1035,8 +1062,10 @@ out: List[Entity]
         """
         SPECIAL_SELECTORS = ["unit", "value", "description", "id", "name"]
 
-        if not isinstance(selector, (tuple, list)):
+        if isinstance(selector, str):
             selector = [selector]
+        elif isinstance(selector, tuple):
+            selector = list(selector)
 
         ref = self
 
@@ -1051,7 +1080,7 @@ out: List[Entity]
             special_selector = None
 
         # iterating through the entity tree according to the selector
-
+        prop: Optional[Property] = None
         for subselector in selector:
             # selector does not match the structure, we cannot get a
             # property of non-entity
@@ -1078,9 +1107,11 @@ out: List[Entity]
                 ref = prop
 
         # if we saved a special selector before, apply it
-
         if special_selector is None:
-            return prop.value
+            if prop is None:
+                return None
+            else:
+                return prop.value
         else:
             return getattr(ref, special_selector.lower())
 
@@ -1195,7 +1226,7 @@ out: List[Entity]
     def to_xml(
         self,
         xml: Optional[etree._Element] = None,
-        add_properties: Optional[INHERITANCE] = ALL,
+        add_properties: INHERITANCE = "ALL",
         local_serialization: bool = False,
     ) -> etree._Element:
         """Generate an xml representation of this entity. If the parameter xml
@@ -1207,6 +1238,10 @@ out: List[Entity]
         @param xml: an xml element to which all attributes, parents,
             properties, and messages
             are to be added.
+
+        FIXME: Add documentation for the add_properties parameter.
+        FIXME: Add docuemntation for the local_serialization parameter.
+
         @return: xml representation of this entity.
         """
 
@@ -1675,7 +1710,13 @@ def _log_response(body):
 
 class QueryTemplate():
 
-    def __init__(self, id=None, name=None, query=None, description=None):  # @ReservedAssignment
+    def __init__(
+        self,
+        id: Optional[int] = None,
+        name: Optional[str] = None,
+        query: Optional[str] = None,
+        description: Optional[str] = None,
+    ):  # @ReservedAssignment
 
         self.id = (int(id) if id is not None else None)
         self.role = "QueryTemplate"
@@ -1694,15 +1735,20 @@ class QueryTemplate():
         self._size = None
         self._upload = None
         self.unit = None
-        self.acl = None
-        self.permissions = None
+        self.acl: Optional[ACL] = None
+        self.permissions: Optional[Permissions] = None
         self.is_valid = lambda: False
         self.is_deleted = lambda: False
         self.version = None
         self.state = None
 
-    def retrieve(self, raise_exception_on_error=True, unique=True, sync=True,
-                 flags=None):
+    def retrieve(
+        self,
+        raise_exception_on_error: bool = True,
+        unique: bool = True,
+        sync: bool = True,
+        flags: Optional[Dict[str, Optional[str]]] = None,
+    ):
 
         return Container().append(self).retrieve(
             raise_exception_on_error=raise_exception_on_error,
@@ -1710,8 +1756,14 @@ class QueryTemplate():
             sync=sync,
             flags=flags)[0]
 
-    def insert(self, strict=True, raise_exception_on_error=True,
-               unique=True, sync=True, flags=None):
+    def insert(
+        self,
+        strict: bool = True,
+        raise_exception_on_error: bool = True,
+        unique: bool = True,
+        sync: bool = True,
+        flags: Optional[Dict[str, Optional[str]]] = None,
+    ):
 
         return Container().append(self).insert(
             strict=strict,
@@ -1720,8 +1772,14 @@ class QueryTemplate():
             sync=sync,
             flags=flags)[0]
 
-    def update(self, strict=True, raise_exception_on_error=True,
-               unique=True, sync=True, flags=None):
+    def update(
+        self,
+        strict: bool = True,
+        raise_exception_on_error: bool = True,
+        unique: bool = True,
+        sync: bool = True,
+        flags: Optional[Dict[str, Optional[str]]] = None,
+    ):
 
         return Container().append(self).update(
             strict=strict,
@@ -1737,7 +1795,7 @@ class QueryTemplate():
     def __repr__(self):
         return xml2str(self.to_xml())
 
-    def to_xml(self, xml=None):
+    def to_xml(self, xml: Optional[etree._Element] = None):
         if xml is None:
             xml = etree.Element("QueryTemplate")
 
@@ -1767,7 +1825,7 @@ class QueryTemplate():
         return xml
 
     @staticmethod
-    def _from_xml(xml):
+    def _from_xml(xml: etree._Element):
         if xml.tag.lower() == "querytemplate":
             q = QueryTemplate(name=xml.get("name"),
                               description=xml.get("description"), query=None)
@@ -1777,16 +1835,18 @@ class QueryTemplate():
                     q.query = e.text
                 else:
                     child = _parse_single_xml_element(e)
-
+                    if child is None:
+                        continue
                     if isinstance(child, Message):
                         q.messages.append(child)
                     elif isinstance(child, ACL):
                         q.acl = child
                     elif isinstance(child, Version):
-                        q.version = child
+                        q.version = child  # type: ignore
                     elif isinstance(child, Permissions):
                         q.permissions = child
-            q.id = int(xml.get("id"))
+            id = xml.get("id")
+            q.id = int(id) if id is not None else None
 
             return q
         else:
@@ -1849,7 +1909,12 @@ class Parent(Entity):
             self.set_flag("inheritance", inheritance)
         self.__affiliation = None
 
-    def to_xml(self, xml: Optional[etree._Element] = None, add_properties=None):
+    def to_xml(
+        self,
+        xml: Optional[etree._Element] = None,
+        add_properties: INHERITANCE = "NONE",
+        local_serialization: bool = False,
+    ):
         if xml is None:
             xml = etree.Element("Parent")
 
@@ -1919,11 +1984,20 @@ class Property(Entity):
                         datatype=datatype, value=value, role="Property")
         self.unit = unit
 
-    def to_xml(self, xml: Optional[etree._Element] = None, add_properties=ALL):
+    def to_xml(
+        self,
+        xml: Optional[etree._Element] = None,
+        add_properties: INHERITANCE = "ALL",
+        local_serialization: bool = False,
+    ):
         if xml is None:
             xml = etree.Element("Property")
 
-        return super(Property, self).to_xml(xml, add_properties)
+        return super(Property, self).to_xml(
+            xml=xml,
+            add_properties=add_properties,
+            local_serialization=local_serialization,
+        )
 
     def is_reference(self, server_retrieval=False):
         """Returns whether this Property is a reference
@@ -1974,7 +2048,7 @@ class Message(object):
         type: Optional[str] = None,
         code: Optional[int] = None,
         description: Optional[str] = None,
-        body: Optional[str] = None,
+        body: Union[str, etree._Attrib, None] = None,
     ):  # @ReservedAssignment
         self.description = description
         self.type = type if type is not None else "Info"
@@ -2028,7 +2102,7 @@ class RecordType(Entity):
         parent: Union[Entity, int, str, None] = None,
         id: Optional[int] = None,
         name: Optional[str] = None,
-        inheritance: Union[INHERITANCE, str, None] = OBLIGATORY,
+        inheritance: INHERITANCE = "OBLIGATORY",
     ):
         """Add a parent to this RecordType
 
@@ -2048,8 +2122,8 @@ class RecordType(Entity):
         name : str
             Name of the parent entity. Ignored if `parent is not
             none`.
-        inheritance : str, default OBLIGATORY
-            One of ``obligatory``, ``recommended``, ``suggested``, or ``fix``. Specifies the
+        inheritance : INHERITANCE, default OBLIGATORY
+            One of ``obligatory``, ``recommended``, ``suggested``, or ``all``. Specifies the
             minimum importance which parent properties need to have to be inherited by this
             entity. If no `inheritance` is given, no properties will be inherited by the child.
             This parameter is case-insensitive.
@@ -2075,12 +2149,18 @@ class RecordType(Entity):
     def to_xml(
         self,
         xml: Optional[etree._Element] = None,
-        add_properties: Optional[INHERITANCE] = ALL,
+        add_properties: INHERITANCE = "ALL",
+        local_serialization: bool = False,
     ) -> etree._Element:
         if xml is None:
             xml = etree.Element("RecordType")
 
-        return Entity.to_xml(self, xml, add_properties)
+        return Entity.to_xml(
+            self,
+            xml=xml,
+            add_properties=add_properties,
+            local_serialization=local_serialization,
+        )
 
 
 class Record(Entity):
@@ -2104,11 +2184,20 @@ class Record(Entity):
         Entity.__init__(self, name=name, id=id, description=description,
                         role="Record")
 
-    def to_xml(self, xml=None, add_properties=ALL):
+    def to_xml(
+        self,
+        xml: Optional[etree._Element] = None,
+        add_properties: INHERITANCE = "ALL",
+        local_serialization: bool = False,
+    ):
         if xml is None:
             xml = etree.Element("Record")
 
-        return Entity.to_xml(self, xml, add_properties=ALL)
+        return super().to_xml(
+            xml=xml,
+            add_properties=add_properties,
+            local_serialization=local_serialization,
+        )
 
 
 class File(Record):
@@ -2175,7 +2264,7 @@ class File(Record):
     def to_xml(
         self,
         xml: Optional[etree._Element] = None,
-        add_properties: Optional[INHERITANCE] = ALL,
+        add_properties: INHERITANCE = "ALL",
         local_serialization: bool = False,
     ):
         """Convert this file to an xml element.
@@ -2189,7 +2278,7 @@ class File(Record):
         return Entity.to_xml(self, xml=xml, add_properties=add_properties,
                              local_serialization=local_serialization)
 
-    def download(self, target=None):
+    def download(self, target: Optional[str] = None):
         """Download this file-entity's actual file from the file server. It
         will be stored to the target or will be hold as a temporary file.
 
@@ -2199,7 +2288,7 @@ class File(Record):
         self.clear_server_messages()
 
         if target:
-            file_ = open(target, 'wb')
+            file_: Union[BufferedWriter, _TemporaryFileWrapper] = open(target, "wb")
         else:
             file_ = NamedTemporaryFile(mode='wb', delete=False)
         checksum = File.download_from_path(file_, self.path)
@@ -2211,7 +2300,9 @@ class File(Record):
         return file_.name
 
     @staticmethod
-    def download_from_path(target_file, path):
+    def download_from_path(
+        target_file: Union[BufferedWriter, _TemporaryFileWrapper], path: str
+    ):
 
         _log_request("GET (download): " + path)
         response = get_connection().download_file(path)
@@ -2277,23 +2368,25 @@ class _Properties(list):
 
     def __init__(self):
         list.__init__(self)
-        self._importance = dict()
-        self._inheritance = dict()
-        self._element_by_name = dict()
-        self._element_by_id = dict()
+        self._importance: Dict[Entity, IMPORTANCE] = dict()
+        self._inheritance: Dict[Entity, INHERITANCE] = dict()
+        self._element_by_name: Dict[str, Entity] = dict()
+        self._element_by_id: Dict[str, Entity] = dict()
 
-    def get_importance(self, property):  # @ReservedAssignment
+    def get_importance(
+        self, property: Union[Property, Entity, str, None]
+    ):  # @ReservedAssignment
         if property is not None:
-            if hasattr(property, "encode"):
+            if isinstance(property, str):
                 property = self.get_by_name(property)  # @ReservedAssignment
 
             return self._importance.get(property)
 
-    def set_importance(self, property, importance):  # @ReservedAssignment
+    def set_importance(self, property: Optional[Property], importance: IMPORTANCE):  # @ReservedAssignment
         if property is not None:
             self._importance[property] = importance
 
-    def get_by_name(self, name: str) -> Property:
+    def get_by_name(self, name: str) -> Entity:
         """Get a property of this list via it's name. Raises a LinkAheadException
         if not exactly one property has this name.
 
@@ -2310,9 +2403,9 @@ class _Properties(list):
 
     def append(
         self,
-        property: Union[List[Entity], Entity],
-        importance=None,
-        inheritance: Union[str, INHERITANCE, None] = None,
+        property: Union[List[Entity], Entity, Property],
+        importance: Optional[IMPORTANCE] = None,
+        inheritance: Optional[INHERITANCE] = None,
     ):  # @ReservedAssignment
         if isinstance(property, list):
             for p in property:
@@ -2327,7 +2420,7 @@ class _Properties(list):
             if inheritance is not None:
                 self._inheritance[property] = inheritance
             else:
-                self._inheritance[property] = FIX
+                self._inheritance[property] = "ALL"
 
             if property.id is not None:
                 self._element_by_id[str(property.id)] = property
@@ -2340,9 +2433,7 @@ class _Properties(list):
 
         return self
 
-    def to_xml(
-        self, add_to_element: etree._Element, add_properties: Union[str, INHERITANCE]
-    ):
+    def to_xml(self, add_to_element: etree._Element, add_properties: INHERITANCE):
         for p in self:
             importance = self._importance.get(p)
 
@@ -3274,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 = []
@@ -3401,7 +3492,7 @@ class Container(list):
                 sync_remote_entities.append(remote_entity)
 
         if len(sync_remote_entities) > 0:
-            sync_dict[self] = sync_remote_entities
+            sync_dict[self] = sync_remote_entities  # FIXME: How is this supposed to work?
 
         if unique and len(sync_remote_entities) != 0:
             msg = "Request was not unique. There are " + \
@@ -3690,7 +3781,7 @@ class Container(list):
 
         return (entities[0:hl], entities[hl:len(entities)])
 
-    def _retrieve(self, entities, flags):
+    def _retrieve(self, entities, flags: Dict[str, Optional[str]]):
         c = get_connection()
         try:
             _log_request("GET: " + _ENTITY_URI_SEGMENT + str(entities) +
@@ -3744,8 +3835,14 @@ class Container(list):
 
         return ret
 
-    def update(self, strict=False, raise_exception_on_error=True,
-               unique=True, sync=True, flags=None):
+    def update(
+        self,
+        strict: bool = False,
+        raise_exception_on_error: bool = True,
+        unique: bool = True,
+        sync: bool = True,
+        flags: Optional[Dict[str, Any]] = None,
+    ):
         """Update these entites."""
 
         if len(self) < 1:
@@ -3756,7 +3853,7 @@ class Container(list):
 
         self.clear_server_messages()
         insert_xml = etree.Element("Update")
-        http_parts = []
+        http_parts: List[MultipartParam] = []
 
         if flags is None:
             flags = {}
@@ -3826,7 +3923,9 @@ class Container(list):
             return cresp
 
     @staticmethod
-    def _process_file_if_present_and_add_to_http_parts(http_parts, entity):
+    def _process_file_if_present_and_add_to_http_parts(
+        http_parts: List[MultipartParam], entity: Union[File, Entity]
+    ):
         if isinstance(entity, File) and hasattr(
                 entity, 'file') and entity.file is not None:
             new_checksum = File._get_checksum(entity.file)
@@ -3867,29 +3966,43 @@ class Container(list):
         else:
             entity._checksum = None
 
-    def insert(self, strict=False, raise_exception_on_error=True,
-               unique=True, sync=True, flags=None):
+    # 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,
+        strict: bool = False,
+        raise_exception_on_error: bool = True,
+        unique: bool = True,
+        sync: bool = True,
+        flags: Optional[Dict[str, Optional[str]]] = None,
+    ):
         """Insert this file entity into LinkAhead. A successful insertion will
         generate a new persistent ID for this entity. This entity can be
         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.
+                          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()
         insert_xml = etree.Element("Insert")
-        http_parts = []
+        http_parts: List[MultipartParam] = []
 
         if flags is None:
             flags = {}
@@ -3970,7 +4083,6 @@ class Container(list):
         cresp = Container._response_to_entities(http_response)
 
         if sync:
-
             self._sync(cresp, unique=unique,
                        raise_exception_on_error=raise_exception_on_error)
 
@@ -3985,7 +4097,7 @@ class Container(list):
             return cresp
 
     @staticmethod
-    def _get_smallest_tmpid(entity):
+    def _get_smallest_tmpid(entity: Entity):
         tmpid = 0
 
         if entity.id is not None:
@@ -4050,7 +4162,9 @@ class Container(list):
 
         return self
 
-    def get_property_values(self, *selectors):
+    def get_property_values(
+        self, *selectors: Union[str, Tuple[str]]
+    ) -> List[Tuple[str]]:
         """ Return a list of tuples with values of the given selectors.
 
         I.e. a tabular representation of the container's content.
@@ -4152,11 +4266,15 @@ class ACI():
         if self.role is not None:
             e.set("role", self.role)
         else:
+            if self.username is None:
+                raise LinkAheadException("An ACI must have either a role or a username.")
             e.set("username", self.username)
 
             if self.realm is not None:
                 e.set("realm", self.realm)
         p = etree.Element("Permission")
+        if self.permission is None:
+            raise LinkAheadException("An ACI must have a permission.")
         p.set("name", self.permission)
         e.append(p)
 
@@ -4206,7 +4324,7 @@ class ACL():
             role = e.get("role")
             username = e.get("username")
             realm = e.get("realm")
-            priority = e.get("priority")
+            priority = self._get_boolean_priority(e.get("priority"))
 
             for p in e:
                 if p.tag == "Permission":
@@ -4290,8 +4408,14 @@ class ACL():
         if item in self._grants:
             self._grants.remove(item)
 
-    def revoke_denial(self, username=None, realm=None,
-                      role=None, permission=None, priority=False):
+    def revoke_denial(
+        self,
+        username: Optional[str] = None,
+        realm: Optional[str] = None,
+        role: Optional[str] = None,
+        permission: Optional[str] = None,
+        priority: bool = False,
+    ):
         priority = self._get_boolean_priority(priority)
         item = ACI(role=role, username=username,
                    realm=realm, permission=permission)
@@ -4452,7 +4576,7 @@ class ACL():
 
         return ret
 
-    def get_acl_for_user(self, username, realm=None):
+    def get_acl_for_user(self, username: str, realm: Optional[str] = None):
         ret = ACL()
 
         for aci in self._grants:
@@ -4531,8 +4655,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()
@@ -4545,7 +4669,7 @@ class Query():
         with the resulting entities.
     """
 
-    def putFlag(self, key, value=None):
+    def putFlag(self, key: str, value: Optional[str] = None):
         self.flags[key] = value
 
         return self
@@ -4557,19 +4681,24 @@ class Query():
         return self.flags.get(key)
 
     def __init__(self, q: Union[str, etree._Element]):
-        self.flags: Dict[str, str] = dict()
+        self.flags: Dict[str, Optional[str]] = dict()
         self.messages = Messages()
         self.cached: Optional[bool] = None
         self.etag = None
 
         if isinstance(q, etree._Element):
-            self.q = q.get("string")
-            self.results = int(q.get("results"))
-
-            if q.get("cached") is None:
+            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.")
+            self.results = int(results)
+
+            cached_value = q.get("cached")
+            if cached_value is None:
                 self.cached = False
             else:
-                self.cached = q.get("cached").lower() == "true"
+                self.cached = cached_value.lower() == "true"
             self.etag = q.get("etag")
 
             for m in q:
@@ -4578,7 +4707,7 @@ class Query():
         else:
             self.q = q
 
-    def _query_request(self, query_dict):
+    def _query_request(self, query_dict: Dict[str, Optional[str]]):
         """Used internally to execute the query request..."""
         _log_request("GET Entity?" + str(query_dict), None)
         connection = get_connection()
@@ -4588,7 +4717,12 @@ class Query():
         cresp = Container._response_to_entities(http_response)
         return cresp
 
-    def _paging_generator(self, first_page, query_dict, page_length):
+    def _paging_generator(
+        self,
+        first_page: Container,
+        query_dict: Dict[str, Optional[str]],
+        page_length: int,
+    ):
         """Used internally to create a generator of pages instead instead of a
         container which contais all the results."""
         if len(first_page) == 0:
@@ -4691,7 +4825,7 @@ class Query():
                 return r
         self.messages = cresp.messages
 
-        if has_paging:
+        if has_paging and page_length is not None:
             return self._paging_generator(cresp, query_dict, page_length)
         else:
             return cresp
@@ -4702,7 +4836,7 @@ def execute_query(
     unique: bool = False,
     raise_exception_on_error: bool = True,
     cache: bool = True,
-    flags: Optional[Dict[str, str]] = None,
+    flags: Optional[Dict[str, Optional[str]]] = None,
     page_length: Optional[int] = None,
 ) -> Union[Container, int]:
     """Execute a query (via a server-requests) and return the results.
@@ -4806,8 +4940,8 @@ class Info():
 
     def __init__(self):
         self.messages = Messages()
-        self.user_info = None
-        self.time_zone = None
+        self.user_info: Optional[UserInfo] = None
+        self.time_zone: Optional[TimeZone] = None
         self.sync()
 
     def sync(self):
@@ -4870,7 +5004,7 @@ class Permission():
 
 class Permissions():
 
-    known_permissions = None
+    known_permissions: Optional[List[Permissions]] = None
 
     def __init__(self, xml: etree._Element):
         self.parse_xml(xml)
@@ -4883,8 +5017,12 @@ class Permissions():
 
         for e in xml:
             if e.tag == "Permission":
-                self._perms.add(Permission(name=e.get("name"),
-                                           description=e.get("description")))
+                name = e.get("name")
+                if name is None:
+                    raise LinkAheadException(
+                        "The permission element has no name attribute."
+                    )
+                self._perms.add(Permission(name=name, description=e.get("description")))
 
     def __contains__(self, p):
         if isinstance(p, Permission):
@@ -4917,15 +5055,18 @@ def parse_xml(xml: Union[str, etree._Element]):
 
 def _parse_single_xml_element(elem: etree._Element):
     classmap = {
-        'record': Record,
-        'recordtype': RecordType,
-        'property': Property,
-        'file': File,
-        'parent': Parent,
-        'entity': Entity}
+        "record": Record,
+        "recordtype": RecordType,
+        "property": Property,
+        "file": File,
+        "parent": Parent,
+        "entity": Entity,
+    }
 
     if elem.tag.lower() in classmap:
         klass = classmap.get(elem.tag.lower())
+        if klass is None:
+            raise LinkAheadException("No class for tag '{}' found.".format(elem.tag))
         entity = klass()
         Entity._from_xml(entity, elem)
 
@@ -4953,7 +5094,8 @@ 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 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)
@@ -4962,14 +5104,22 @@ def _parse_single_xml_element(elem: etree._Element):
     elif elem.tag == "UserInfo":
         return UserInfo(xml=elem)
     elif elem.tag == "TimeZone":
-        return TimeZone(zone_id=elem.get("id"), offset=elem.get("offset"),
-                        display_name=elem.text.strip())
+        return TimeZone(
+            zone_id=elem.get("id"),
+            offset=elem.get("offset"),
+            display_name=elem.text.strip() if elem.text is not None else "",
+        )
     else:
-        return Message(type=elem.tag, code=elem.get(
-            "code"), description=elem.get("description"), body=elem.text)
+        code = elem.get("code")
+        return Message(
+            type=elem.tag,
+            code=int(code) if code is not None else None,
+            description=elem.get("description"),
+            body=elem.text,
+        )
 
 
-def _evaluate_and_add_error(parent_error: TransactionError, ent: Union[Entity, Container]):
+def _evaluate_and_add_error(parent_error: TransactionError, ent: Union[Entity, QueryTemplate, Container]):
     """Evaluate the error message(s) attached to entity and add a
     corresponding exception to parent_error.
 
@@ -4978,7 +5128,7 @@ def _evaluate_and_add_error(parent_error: TransactionError, ent: Union[Entity, C
     parent_error : TransactionError
         Parent error to which the new exception will be attached. This
         exception will be a direct child.
-    ent : Entity or Container
+    ent : Entity or Container or QueryTemplate
         Entity that caused the TransactionError. An exception is
         created depending on its error message(s).
 
@@ -5000,8 +5150,8 @@ def _evaluate_and_add_error(parent_error: TransactionError, ent: Union[Entity, C
 
             if err.code is not None:
                 if int(err.code) == 101:  # ent doesn't exist
-                    new_exc = EntityDoesNotExistError(entity=ent,
-                                                      error=err)
+                    new_exc: EntityError = EntityDoesNotExistError(entity=ent,
+                                                                   error=err)
                 elif int(err.code) == 110:  # ent has no data type
                     new_exc = EntityHasNoDatatypeError(entity=ent,
                                                        error=err)
@@ -5099,7 +5249,7 @@ def raise_errors(arg0: Union[Entity, QueryTemplate, Container]):
         raise transaction_error
 
 
-def delete(ids: Union[List[int], range], raise_exception_on_error=True):
+def delete(ids: Union[List[int], range], raise_exception_on_error: bool = True):
     c = Container()
 
     if isinstance(ids, list) or isinstance(ids, range):
diff --git a/src/linkahead/exceptions.py b/src/linkahead/exceptions.py
index a6abe09edbbece2a38bdc6c5e1296a2b3dd81bde..609d3654ac670a993185ba1faa33db921c44409c 100644
--- a/src/linkahead/exceptions.py
+++ b/src/linkahead/exceptions.py
@@ -354,6 +354,10 @@ class UnqualifiedPropertiesError(EntityError):
     """
 
 
+class EntityHasNoAclError(EntityError):
+    """This entity has no ACL (yet)."""
+
+
 class EntityDoesNotExistError(EntityError):
     """This entity does not exist."""
 
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]