Repository: SINTEF/dlite (language: python)

Issue:
Add datatype to collection
# Description
Add datatype to collections. This is needed for representing RDF literals.

Also added more tests and fixed bugs.

Closes #741

## Type of change
- [x] Bug fix & code cleanup
- [x] New feature
- [ ] Documentation update
- [x] Test update

## Checklist for the reviewer
This checklist should be used as a help for the reviewer.

- [ ] Is the change limited to one issue?
- [ ] Does this PR close the issue?
- [ ] Is the code easy to read and understand?
- [ ] Do all new feature have an accompanying new test?
- [ ] Has the documentation been updated as necessary?

Add datatype to collection
Collections are currently only a list of s,p,o triples, represented as three strings. However, in order to represent RDF graphs as relations in a collection, we also have to store the datatype of literals. 

This issue relates closely to issue #174 
Add unit support in property access and assignment
Issue suggested by @terence-coudert.

Suggested interface for assignment:
```
inst.stress = "3 MPa"  # this will convert the str to a Quantity and store the value in the right unit
inst.stress = 3   # this is already implemented
inst.stress = ([3.0, 4.0, 5.0], "MPa")  # this convert the tuple into a Quantity
inst.stress = Quantity(70, "GPa")
```

Suggested interface for access:
```
inst.stress.to("MPa") -> 70e3
```



Discussion / hints:

Fix patch:
diff --git a/.dockerignore b/.dockerignore
old mode 100755
new mode 100644
diff --git a/.gitattributes b/.gitattributes
old mode 100755
new mode 100644
diff --git a/.github/.gitignore b/.github/.gitignore
old mode 100755
new mode 100644
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
old mode 100755
new mode 100644
diff --git a/.github/docker/Dockerfile-manylinux.template b/.github/docker/Dockerfile-manylinux.template
old mode 100755
new mode 100644
diff --git a/.github/docker/Dockerfile-mingw b/.github/docker/Dockerfile-mingw
old mode 100755
new mode 100644
diff --git a/.github/docker/Dockerfile-musllinux.template b/.github/docker/Dockerfile-musllinux.template
old mode 100755
new mode 100644
diff --git a/.github/docker/pgdg-91_i686.repo b/.github/docker/pgdg-91_i686.repo
old mode 100755
new mode 100644
diff --git a/.github/docker/pgdg-91_x86_64.repo b/.github/docker/pgdg-91_x86_64.repo
old mode 100755
new mode 100644
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
old mode 100755
new mode 100644
diff --git a/.github/utils/release_tag_msg.txt b/.github/utils/release_tag_msg.txt
old mode 100755
new mode 100644
diff --git a/.github/workflows/cd_docs.yml b/.github/workflows/cd_docs.yml
old mode 100755
new mode 100644
index 6b4bdba8b..2de1e6fb9
--- a/.github/workflows/cd_docs.yml
+++ b/.github/workflows/cd_docs.yml
@@ -32,12 +32,12 @@ jobs:
         uses: actions/checkout@v4
 
       - name: Setup Python 3.9
-        uses: actions/setup-python@v4
+        uses: actions/setup-python@v5
         with:
           python-version: "3.9"
 
       - name: Setup Pages
-        uses: actions/configure-pages@v3
+        uses: actions/configure-pages@v4
 
       - name: Install system dependencies
         run: |
@@ -82,7 +82,7 @@ jobs:
         working-directory: build
 
       - name: Upload artifact
-        uses: actions/upload-pages-artifact@v2
+        uses: actions/upload-pages-artifact@v3
         with:
           path: 'build/doc/html/'
 
@@ -99,4 +99,4 @@ jobs:
     steps:
       - name: Deploy to GitHub Pages
         id: deployment
-        uses: actions/deploy-pages@v2
+        uses: actions/deploy-pages@v4
diff --git a/.github/workflows/cd_release.yml b/.github/workflows/cd_release.yml
old mode 100755
new mode 100644
index 9db566794..0332d32a5
--- a/.github/workflows/cd_release.yml
+++ b/.github/workflows/cd_release.yml
@@ -153,7 +153,7 @@ jobs:
       shell: bash  # Enforce bash shell to make `sed` work also on Windows
 
     - name: Set up Python 3
-      uses: actions/setup-python@v4
+      uses: actions/setup-python@v5
       with:
         python-version: "3.${{ contains(matrix.py_minors, ',') && 'x' || matrix.py_minors }}"
         architecture: ${{ matrix.arch == '' && 'x86' || 'x64' }}
@@ -208,7 +208,7 @@ jobs:
       run: sed -i -E -e "s|^ +VERSION +[0-9.]+$|  VERSION ${GITHUB_REF#refs/tags/v}|" CMakeLists.txt
 
     - name: Set up Python 3.9
-      uses: actions/setup-python@v4
+      uses: actions/setup-python@v5
       with:
         python-version: "3.9"
 
diff --git a/.github/workflows/ci_automerge_dependabot.yml b/.github/workflows/ci_automerge_dependabot.yml
index e3b54d87a..7e723ea91 100644
--- a/.github/workflows/ci_automerge_dependabot.yml
+++ b/.github/workflows/ci_automerge_dependabot.yml
@@ -7,7 +7,7 @@ on:
 jobs:
   update-dependabot-branch:
     name: External
-    uses: SINTEF/ci-cd/.github/workflows/ci_automerge_prs.yml@v2.6.0
+    uses: SINTEF/ci-cd/.github/workflows/ci_automerge_prs.yml@v2.7.2
     if: github.repository_owner == 'SINTEF' && startsWith(github.event.pull_request.head.ref, 'dependabot/') && github.actor == 'dependabot[bot]'
     secrets:
       PAT: ${{ secrets.RELEASE_PAT }}
diff --git a/.github/workflows/ci_build_wheels.yml b/.github/workflows/ci_build_wheels.yml
old mode 100755
new mode 100644
index aac267d3d..4d4c99fbe
--- a/.github/workflows/ci_build_wheels.yml
+++ b/.github/workflows/ci_build_wheels.yml
@@ -80,7 +80,7 @@ jobs:
       - name: Checkout repository
         uses: actions/checkout@v4
 
-      - uses: actions/setup-python@v4
+      - uses: actions/setup-python@v5
         with:
           python-version: "3.${{ contains(matrix.py_minors, ',') && 'x' || matrix.py_minors }}"
           architecture: ${{ matrix.arch == '' && 'x86' || 'x64' }}
diff --git a/.github/workflows/ci_cd_updated_master.yml b/.github/workflows/ci_cd_updated_master.yml
index 14c4a043a..d7b2dbaa2 100644
--- a/.github/workflows/ci_cd_updated_master.yml
+++ b/.github/workflows/ci_cd_updated_master.yml
@@ -7,7 +7,7 @@ on:
 jobs:
   updates-to-master:
     name: External
-    uses: SINTEF/ci-cd/.github/workflows/ci_cd_updated_default_branch.yml@v2.6.0
+    uses: SINTEF/ci-cd/.github/workflows/ci_cd_updated_default_branch.yml@v2.7.2
     if: github.repository_owner == 'SINTEF'
     with:
       # General
diff --git a/.github/workflows/ci_dependabot.yml b/.github/workflows/ci_dependabot.yml
index 2a25699a9..3eff5506a 100644
--- a/.github/workflows/ci_dependabot.yml
+++ b/.github/workflows/ci_dependabot.yml
@@ -9,7 +9,7 @@ on:
 jobs:
   create-collected-pr:
     name: External
-    uses: SINTEF/ci-cd/.github/workflows/ci_update_dependencies.yml@v2.6.0
+    uses: SINTEF/ci-cd/.github/workflows/ci_update_dependencies.yml@v2.7.2
     if: github.repository_owner == 'sintef'
     with:
       git_username: DLite Developers
diff --git a/.github/workflows/container_builds_weekly.yml b/.github/workflows/container_builds_weekly.yml
old mode 100755
new mode 100644
index 5effc5891..fbbe04c01
--- a/.github/workflows/container_builds_weekly.yml
+++ b/.github/workflows/container_builds_weekly.yml
@@ -70,7 +70,7 @@ jobs:
 
       - name: Setup cache
         if: github.event_name != 'workflow_dispatch' || fromJSON(github.event.inputs.use_cache)
-        uses: actions/cache@v3
+        uses: actions/cache@v4
         with:
           path: .buildx-cache-${{ matrix.system }}${{ matrix.type }}_${{ matrix.arch }}
           key: buildx-cache-${{ matrix.system }}${{ matrix.type }}-${{ matrix.arch }}-${{ github.sha }}
diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml
old mode 100755
new mode 100644
diff --git a/.gitignore b/.gitignore
old mode 100755
new mode 100644
diff --git a/CMakeLists.txt b/CMakeLists.txt
old mode 100755
new mode 100644
index 64158ab56..24e03c9d5
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,7 +3,7 @@
 cmake_minimum_required(VERSION 3.14)
 
 project(dlite
-  VERSION 0.4.6
+  VERSION 0.5.1
   DESCRIPTION "Lightweight data-centric framework for semantic interoperability"
   HOMEPAGE_URL "https://github.com/SINTEF/dlite"
   LANGUAGES C
diff --git a/Dockerfile b/Dockerfile
old mode 100755
new mode 100644
diff --git a/README.md b/README.md
old mode 100755
new mode 100644
index 64693110e..6cd4d95aa
--- a/README.md
+++ b/README.md
@@ -128,6 +128,16 @@ In this case we didn't initialised the age
 >>> homes.age = 34  # Assign the age
 ```
 
+If you have [Pint] installed, you can also specify or access the age
+as a quantity with unit
+```python
+>>> homes.q.age = "34year"
+>>> homes.q.age
+<Quantity(34, 'year')>
+>>> homes.q.age.to("century").m
+0.34
+```
+
 We can view (a JSON representation of) the instance with
 
 ```python
@@ -213,3 +223,4 @@ DLite is developed with the hope that it will be a delight to work with.
 [documentation]: https://sintef.github.io/dlite/index.html
 [SOFT]: https://www.sintef.no/en/publications/publication/1553408/
 [UUID]: https://en.wikipedia.org/wiki/Universally_unique_identifier
+[Pint]: https://pint.readthedocs.io/
diff --git a/bindings/fortran/CMakeLists.txt b/bindings/fortran/CMakeLists.txt
old mode 100755
new mode 100644
diff --git a/bindings/fortran/c_interface.f90 b/bindings/fortran/c_interface.f90
old mode 100755
new mode 100644
diff --git a/bindings/fortran/dlite.f90 b/bindings/fortran/dlite.f90
old mode 100755
new mode 100644
diff --git a/bindings/python/.gitignore b/bindings/python/.gitignore
old mode 100755
new mode 100644
diff --git a/bindings/python/CMakeLists.txt b/bindings/python/CMakeLists.txt
old mode 100755
new mode 100644
index 979877d51..54d8e3811
--- a/bindings/python/CMakeLists.txt
+++ b/bindings/python/CMakeLists.txt
@@ -8,6 +8,7 @@ set(py_sources
   mappings.py
   datamodel.py
   rdf.py
+  quantity.py
   testutils.py
   )
 
diff --git a/bindings/python/__init__inbuild.py.in b/bindings/python/__init__inbuild.py.in
old mode 100755
new mode 100644
diff --git a/bindings/python/__init__standalone.py.in b/bindings/python/__init__standalone.py.in
old mode 100755
new mode 100644
diff --git a/bindings/python/datamodel.py b/bindings/python/datamodel.py
old mode 100755
new mode 100644
diff --git a/bindings/python/dlite-collection-python.i b/bindings/python/dlite-collection-python.i
old mode 100755
new mode 100644
index a8392dc00..68a77961b
--- a/bindings/python/dlite-collection-python.i
+++ b/bindings/python/dlite-collection-python.i
@@ -11,15 +11,21 @@
             v = self.next()
             if v and v.is_meta:
                 v.__class__ = Metadata
-        elif self.rettype in 'RTspo':  # relation
+        elif self.rettype in 'RTtspod':  # relation
             v = self.next_relation()
         else:
-            raise ValueError('`rettype` must one of "IRTspo"')
+            raise ValueError('`rettype` must one of "IRTtspod"')
 
         if v is None:
             raise StopIteration()
 
-        if self.rettype == 'T':  # return (s,p,o) tuple
+        if self.rettype == 'I':  # return instance
+            return v
+        if self.rettype == 'R':  # return relation object
+            return v.copy()
+        if self.rettype == 'T':  # return (s,p,o,d) tuple
+            return (v.s, v.p, v.o, v.d)
+        if self.rettype == 't':  # return (s,p,o) tuple
             return (v.s, v.p, v.o)
         elif self.rettype == 's':  # return subject
             return v.s
@@ -27,8 +33,10 @@
             return v.p
         elif self.rettype == 'o':  # return object
             return v.o
+        elif self.rettype == 'd':  # return datatype
+            return v.d
 
-        return v
+        raise _dlite.DLiteRuntimeError()  # should never be reached
 %}
 
 }
@@ -49,9 +57,17 @@ class Collection(Instance):
     of instances.
 
     Use the get_relations() method (or the convenience methods
-    get_subjects(), get_predicates() and get_predicates()) to iterate
-    over relations.  The number of relations is available via the `nrelations`
-    property.
+    get_subjects(), get_predicates() and get_objects()) to iterate
+    over relations.  The number of relations is available via the
+    `nrelations` property.
+
+    Relations are (s, p, o, d=None)-triples with an optional fourth field
+    `d`, specifying the datatype of the object.  The datatype may have the
+    following values:
+      - None: object is an IRI.
+      - Starts with '@': object is a language-tagged plain literal.
+        The language identifier follows the '@'-sign.
+      - Otherwise: object is a literal with datatype `d`.
     """
     def __new__(cls, id=None):
         """Creates an empty collection."""
@@ -191,21 +207,22 @@ class Collection(Instance):
         b = _collection_has_id(self._coll, id)
         return bool(b)
 
-    def add_relation(self, s, p, o):
+    def add_relation(self, s, p, o, d=None):
         """Add (subject, predicate, object) RDF triple to collection."""
-        if _collection_add_relation(self._coll, s, p, o) != 0:
-            raise _dlite.DLiteError(f'Error adding relation ({s}, {p}, {o})')
+        if _collection_add_relation(self._coll, s, p, o, d) != 0:
+            raise _dlite.DLiteError(
+                f'Error adding relation ({s}, {p}, {o}, {d})')
 
-    def remove_relations(self, s=None, p=None, o=None):
+    def remove_relations(self, s=None, p=None, o=None, d=None):
         """Remove all relations matching `s`, `p` and `o`."""
-        if _collection_remove_relations(self._coll, s, p, o) < 0:
+        if _collection_remove_relations(self._coll, s, p, o, d) < 0:
             raise _dlite.DLiteError(
-                f'Error removing relations matching ({s}, {p}, {o})')
+                f'Error removing relations matching ({s}, {p}, {o}, {d})')
 
-    def get_first_relation(self, s=None, p=None, o=None):
+    def get_first_relation(self, s=None, p=None, o=None, d=None):
         """Returns the first relation matching `s`, `p` and `o`.
         None is returned if there are no matching relations."""
-        return _collection_find_first(self._coll, s, p, o)
+        return _collection_find_first(self._coll, s, p, o, d)
 
     def get_instances(self, metaid=None, property_mappings=False,
                       allow_incomplete=False, ureg=None, function_repo=None,
@@ -295,26 +312,58 @@ class Collection(Instance):
         """Returns a generator over all labels."""
         return self.get_subjects(p='_is-a', o='Instance')
 
-    def get_relations(self, s=None, p=None, o=None, rettype='T'):
+    def get_relations(self, s=None, p=None, o=None, d=None, rettype='t'):
         """Returns a generator over all relations matching the given
-        values of `s`, `p` and `o`."""
-        return _CollectionIter(self, s=s, p=p, o=o, rettype=rettype)
+        values of `s` (subject), `p` (predicate), `o` (object) and `d`
+        (datatype). See docstring for dlite.Collection for more info.
+
+        The following values for `rettype` are supported:
+        - 'I': Iterate over instances.
+        - 'R': Iterate over relation objects.
+        - 'T': Iterate over (s,p,o,d) tuples.
+        - 't': Iterate over (s,p,o) tuples.
+        - 's': Iterate over subjects.
+        - 'p': Iterate over predicates.
+        - 'o': Iterate over objects.
+        - 'd': Iterate over datatypes.
+        """
+        return _CollectionIter(self, s=s, p=p, o=o, d=d, rettype=rettype)
 
-    def get_subjects(self, p=None, o=None):
+    def get_subjects(self, p=None, o=None, d=None):
         """Returns a generator over all subjects of relations matching the
-        given values of `p` and `o`."""
-        return _CollectionIter(self, s=None, p=p, o=o, rettype='s')
+        given values of `p`, `o` and `d`."""
+        return _CollectionIter(self, s=None, p=p, o=o, d=d, rettype='s')
 
-    def get_predicates(self, s=None, o=None):
+    def get_predicates(self, s=None, o=None, d=None):
         """Returns a generator over all predicates of relations matching the
-        given values of `s` and `o`."""
-        return _CollectionIter(self, s=s, p=None, o=o, rettype='p')
+        given values of `s`, `o` and `d`."""
+        return _CollectionIter(self, s=s, p=None, o=o, d=d, rettype='p')
 
-    def get_objects(self, s=None, p=None):
+    def get_objects(self, s=None, p=None, d=None):
         """Returns a generator over all subjects of relations matching the
-        given values of `s` and `p`."""
-        return _CollectionIter(self, s=s, p=p, o=None, rettype='o')
+        given values of `s`, `p` and `d`."""
+        return _CollectionIter(self, s=s, p=p, o=None, d=d, rettype='o')
+
+    def value(self, s=None, p=None, o=None, d=None, default=None, any=False):
+        """Return pointer to the value for a pair of two criteria.
+
+        Useful if one knows that there may only be one value.
 
+        Parameters:
+            s, p, o: Criteria to match. Two of these must not be None.
+            d: If not None, the required datatype of literal objects.
+            default: Value to return if no matches are found.
+            any: If true, return first matching value.
+
+        Returns:
+            A pointer to the value of the `s`, `p` or `o` that is None.
+
+        Raises:
+            DLiteTypeError: Not exactly two of `s`, `p` and `o` are None.
+            DLiteSearchError: No match can be found or more that one match
+                if `any` is true.
+        """
+        return _dlite._collection_value(self, s, p, o, d, default, any)
 
 def get_collection(id):
     """Returns a new reference to a collection with given id."""
diff --git a/bindings/python/dlite-collection.i b/bindings/python/dlite-collection.i
old mode 100755
new mode 100644
index 80b043798..441472c2d
--- a/bindings/python/dlite-collection.i
+++ b/bindings/python/dlite-collection.i
@@ -9,12 +9,27 @@
 struct _DLiteCollection *_get_collection(const char *id)
 {
   struct _DLiteInstance *inst = dlite_instance_get(id);
-  if (!inst) return dlite_err(1, "no instance with this id: %s", id), NULL;
+  if (!inst) return dlite_err(dliteTypeError,
+                              "no instance with this id: %s", id), NULL;
   if (strcmp(inst->meta->uri, DLITE_COLLECTION_ENTITY) != 0)
     return dlite_err(1, "not a collection: %s", id), NULL;
   return (DLiteCollection *)inst;
 }
 
+char *_collection_value(DLiteInstance *inst,
+                        const char *s, const char *p,
+                        const char *o, const char *d,
+                        const char *fallback, int any) {
+  if (!inst) return dlite_err(dliteTypeError,
+                              "first argument must be provided"), NULL;
+  if (strcmp(inst->meta->uri, DLITE_COLLECTION_ENTITY) != 0)
+    return dlite_err(dliteTypeError,
+                     "first argument must be a collection"), NULL;
+  DLiteCollection *coll = (DLiteCollection *)inst;
+  const char *v = triplestore_value(coll->rstore, s, p, o, d, fallback, any);
+  return (v) ? strdup(v) : NULL;
+ }
+
 %}
 
 
@@ -26,13 +41,15 @@ int dlite_collection_save(struct _DLiteCollection *coll,
                           struct _DLiteStorage *s);
 int dlite_collection_save_url(struct _DLiteCollection *coll, const char *url);
 int dlite_collection_add_relation(struct _DLiteCollection *coll, const char *s,
-                                  const char *p, const char *o);
+                                  const char *p, const char *o,
+                                  const char *d=NULL);
 int dlite_collection_remove_relations(struct _DLiteCollection *coll,
-                                      const char *s,
-                                      const char *p, const char *o);
+                                      const char *s, const char *p,
+                                      const char *o, const char *d=NULL);
 const struct _Triple *
   dlite_collection_find_first(const struct _DLiteCollection *coll,
-                              const char *s, const char *p, const char *o);
+                              const char *s, const char *p, const char *o,
+                              const char *d=NULL);
 int dlite_collection_add(struct _DLiteCollection *coll, const char *label,
                          struct _DLiteInstance *inst);
 int dlite_collection_remove(struct _DLiteCollection *coll, const char *label);
@@ -86,6 +103,7 @@ struct _DLiteCollection {
     dlite_instance_incref(inst);
     return inst;
   }
+
 }
 
 
@@ -97,8 +115,11 @@ struct _DLiteCollection {
   struct _CollectionIter {
     DLiteCollection *coll;
     DLiteCollectionState state;
-    char *s, *p, *o;  /* search pattern */
-    char rettype;     /* return type: I=Instance, R=Relation, T=tuple */
+    char *s, *p, *o, *d;  /* search pattern */
+    char rettype;         /* return type: I=Instance; R=Relation;
+                             T=(s,p,o,d)-tuple; t=(s,p,o)-tuple;
+                             s=subject; p=predicate; o=object; d=datatype */
+
   };
 %}
 
@@ -108,7 +129,7 @@ struct _DLiteCollection {
 %extend struct _CollectionIter {
   _CollectionIter(struct _DLiteInstance *inst,
                   const char *s=NULL, const char *p=NULL, const char *o=NULL,
-                  const char rettype='T') {
+                  const char *d=NULL, const char rettype='t') {
     if (strcmp(inst->meta->uri, DLITE_COLLECTION_ENTITY) != 0)
       return dlite_err(1, "not a collection: %s", inst->uuid), NULL;
     struct _CollectionIter *iter = calloc(1, sizeof(struct _CollectionIter));
@@ -117,6 +138,7 @@ struct _DLiteCollection {
     iter->s = (s) ? strdup(s) : NULL;
     iter->p = (p) ? strdup(p) : NULL;
     iter->o = (o) ? strdup(o) : NULL;
+    iter->d = (d) ? strdup(d) : NULL;
     iter->rettype = rettype;
     dlite_collection_init_state(coll, &iter->state);
     return iter;
@@ -127,6 +149,7 @@ struct _DLiteCollection {
     if ($self->s) free($self->s);
     if ($self->p) free($self->p);
     if ($self->o) free($self->o);
+    if ($self->d) free($self->d);
     free($self);
   }
 
@@ -142,7 +165,7 @@ struct _DLiteCollection {
            ) next;
   const struct _Triple *next_relation(void) {
     return dlite_collection_find($self->coll, &$self->state,
-                                 $self->s, $self->p, $self->o);
+                                 $self->s, $self->p, $self->o, $self->d);
   }
 
   %feature("docstring",
@@ -171,6 +194,13 @@ struct _DLiteCollection {
 %newobject _get_collection;
 struct _DLiteCollection *_get_collection(const char *id);
 
+%feature("docstring", "Return the value for a pair of two criteria.") _value;
+%newobject _value;
+char *_collection_value(struct _DLiteInstance *inst,
+                        const char *s=NULL, const char *p=NULL,
+                        const char *o=NULL, const char *d=NULL,
+                        const char *fallback=NULL, int any=0);
+
 
 /* -----------------------------------
  * Target language-spesific extensions
diff --git a/bindings/python/dlite-entity-python.i b/bindings/python/dlite-entity-python.i
old mode 100755
new mode 100644
index 45cd52156..d07c34f38
--- a/bindings/python/dlite-entity-python.i
+++ b/bindings/python/dlite-entity-python.i
@@ -22,11 +22,11 @@ class Metadata(Instance):
         description: Description of metadata.
     """
     def __new__(
-            cls,
-            uri: str,
-            dimensions: "Sequence[Dimension]",
-            properties: "Sequence[Property]",
-            description: str = ''
+        cls,
+        uri: str,
+        dimensions: "Sequence[Dimension]",
+        properties: "Sequence[Property]",
+        description: str = ''
     ):
         return Instance.create_metadata(
             uri, dimensions, properties, description)
@@ -97,7 +97,7 @@ class Metadata(Instance):
         lst = [p for p in self.properties["properties"] if p.name == name]
         if lst:
             return lst[0]
-        raise _dlite.DLiteError(
+        raise _dlite.DLiteKeyError(
             f"Metadata {self.uri} has no such property: {name}")
 
     def dimnames(self):
@@ -161,12 +161,8 @@ def get_instance(id: str, metaid: str = None, check_storages: bool = True) -> "I
         inst = _dlite.get_instance(id, metaid, check_storages)
 
     if inst is None:
-        raise _dlite.DLiteError(f"no such instance: {id}")
-    elif inst.is_meta:
-        inst.__class__ = Metadata
-    elif inst.meta.uri == COLLECTION_ENTITY:
-        inst.__class__ = Collection
-    return inst
+        raise _dlite.DLiteMissingInstanceError(f"no such instance: {id}")
+    return instance_cast(inst)
 
 %}
 
@@ -266,8 +262,22 @@ def get_instance(id: str, metaid: str = None, check_storages: bool = True) -> "I
 %extend _Triple {
   %pythoncode %{
     def __repr__(self):
-        return 'Relation(s=%r, p=%r, o=%r, id=%r)' % (
-            self.s, self.p, self.o, self.id)
+        args = [f"s='{self.s}', p='{self.p}', o='{self.o}'"]
+        if self.d:
+            args.append(f"d='{self.d}'")
+        if self.id:
+            args.append(f"id='{self.id}'")
+        return f"Relation({', '.join(args)})"
+
+    def __eq__(self, other):
+        if isinstance(other, Relation):
+            return (self.s == other.s and self.p == other.p and
+                    self.o == other.o and self.d == other.d)
+        return NotImplemented
+
+    def copy(self):
+        """Returns a copy of self."""
+        return Relation(s=self.s, p=self.p, o=self.o, d=self.d, id=self.id)
 
     def aspreferred(self):
         """Returns preferred Python representation."""
@@ -278,7 +288,6 @@ def get_instance(id: str, metaid: str = None, check_storages: bool = True) -> "I
         d = dict(s=self.s, p=self.p, o=self.o)
         if self.id:
             d[id] = self.id
-
         return d
 
     def asstrings(self):
@@ -291,7 +300,6 @@ def get_instance(id: str, metaid: str = None, check_storages: bool = True) -> "I
 /* --------
  * Instance
  * -------- */
-
 %extend _DLiteInstance {
 
   int __len__(void) {
@@ -339,10 +347,7 @@ def get_instance(id: str, metaid: str = None, check_storages: bool = True) -> "I
 
         if not hasattr(self, 'this') or not getattr(self, 'this'):
             raise _dlite.DLitePythonError(f"cannot initiate dlite.Instance")
-        elif self.is_meta:
-            self.__class__ = Metadata
-        elif self.meta.uri == COLLECTION_ENTITY:
-            self.__class__ = Collection
+        instance_cast(self)
 
     def get_meta(self):
         """Returns reference to metadata."""
@@ -356,7 +361,10 @@ def get_instance(id: str, metaid: str = None, check_storages: bool = True) -> "I
         for p in self.meta['properties']:
             if p.name == name:
                 return p
-        raise ValueError(f'No property "{name}" in "{self.uri}"')
+        raise ValueError(
+            f'No property "{name}" in '
+            f'"{self.uri if self.uri else self.meta.uri}"'
+        )
 
     meta = property(get_meta, doc="Reference to the metadata of this instance.")
     dimensions = property(
@@ -571,33 +579,40 @@ def get_instance(id: str, metaid: str = None, check_storages: bool = True) -> "I
         elif isinstance(dest, str):
             self.save_to_url(dest)
         else:
-            raise _dlite.DLiteError('Arguments do not match any call signature')
+            raise _dlite.DLiteTypeError(
+                'Arguments to save() do not match any of the call signatures'
+            )
 
     def __getitem__(self, ind):
         if isinstance(ind, int):
             value = self.get_property_by_index(ind)
         elif self.has_property(ind):
-            value = self.get_property(ind)
+            value = _get_property(self, ind)
         elif isinstance(ind, int):
-            raise IndexError('instance property index out of range: %d' % ind)
+            raise _dlite.DLiteIndexError(
+                'instance property index out of range: %d' % ind
+            )
         else:
-            raise KeyError('no such property: %s' % ind)
+            raise _dlite.DLiteKeyError('no such property: %s' % ind)
         if isinstance(value, np.ndarray) and self.is_frozen():
             value.flags.writeable = False  # ensure immutability
         return value
 
     def __setitem__(self, ind, value):
         if self.is_frozen():
-            raise _dlite.DLiteError(
-                'frozen instance does not support item assignment')
+            raise _dlite.DLiteUnsupportedError(
+                f'frozen instance does not support assignment of property '
+                f'{ind}')
         if isinstance(ind, int):
             self.set_property_by_index(ind, value)
         elif self.has_property(ind):
-            self.set_property(ind, value)
+            _set_property(self, ind, value)
         elif isinstance(ind, int):
-            raise IndexError('instance property index out of range: %d' % ind)
+            raise _dlite.DLiteIndexError(
+                'instance property index out of range: %d' % ind
+            )
         else:
-            raise KeyError('no such property: %s' % ind)
+            raise _dlite.DLiteKeyError('no such property: %s' % ind)
 
     def __contains__(self, item):
         return item in self.properties.keys()
@@ -613,7 +628,9 @@ def get_instance(id: str, metaid: str = None, check_storages: bool = True) -> "I
         elif self.has_dimension(name):
             value = self.get_dimension_size(name)
         else:
-            raise AttributeError('Instance object has no attribute %r' % name)
+            raise _dlite.DLiteAttributeError(
+                'Instance object has no attribute %r' % name
+            )
         if isinstance(value, np.ndarray) and self.is_frozen():
             value.flags.writeable = False  # ensure immutability
         return value
@@ -622,8 +639,10 @@ def get_instance(id: str, metaid: str = None, check_storages: bool = True) -> "I
         if name == 'this':
             object.__setattr__(self, name, value)
         elif self.is_frozen():
-            raise _dlite.DLiteError(
-                'frozen instance does not support attribute assignment')
+            raise _dlite.DLiteUnsupportedError(
+                f"frozen instance does not support assignment of property "
+                f"'{name}'"
+            )
         elif _has_property(self, name):
             _set_property(self, name, value)
         else:
@@ -648,11 +667,7 @@ def get_instance(id: str, metaid: str = None, check_storages: bool = True) -> "I
         will be the id of the new instance, otherwise it will be given
         a random UUID."""
         newinst = self._copy(newid=newid)
-        if newinst.is_meta:
-            newinst.__class__ = Metadata
-        elif newinst.meta.uri == COLLECTION_ENTITY:
-            newinst.__class__ = Collection
-        return newinst
+        return instance_cast(newinst)
 
     def __reduce__(self):
         # ensures that instances can be pickled
@@ -782,7 +797,17 @@ def get_instance(id: str, metaid: str = None, check_storages: bool = True) -> "I
             DeprecationWarning, stacklevel=2)
         return self.copy()
 
-%}
+    @property
+    def q(self):
+        """ to work with quantities """
+        from dlite.quantity import get_quantity_helper
+        return get_quantity_helper(self)
+
+    def get_quantity(self, name):
+        return self.q[name]
 
+    def set_quantity(self, name, value, unit):
+        self.q[name] = (value, unit)
 
+%}
 }
diff --git a/bindings/python/dlite-entity.i b/bindings/python/dlite-entity.i
old mode 100755
new mode 100644
index e14ac5cb5..57d7c15b4
--- a/bindings/python/dlite-entity.i
+++ b/bindings/python/dlite-entity.i
@@ -192,19 +192,38 @@ struct _DLiteProperty {
 /* --------
  * Relation
  * -------- */
+%feature("docstring", "\
+Relations in DLite corresponds to RDF-triples, but are internally 4 fields:
+  - s: subject
+  - p: predicate
+  - o: object
+  - d: datatype
+
+The datatype is the datatype for literal objects. It may have three forms:
+  - None: object is an IRI (rdfs:Resource).
+  - Starts with '@': object is a language-tagged plain literal
+    (rdf:langString). The language identifier follows the '@'-sign.
+    Ex: '@en' for english.
+  - Otherwise: object is a literal with datatype `d`. Ex: 'xsd:int'.
+
+As an internal implementation detail, relations also have an `id` field.
+It may change in the future, so please don't rely on it.
+") _Triple;
 %rename(Relation) _Triple;
 struct _Triple {
   char *s;     /*!< subject */
   char *p;     /*!< predicate */
   char *o;     /*!< object */
+  char *d;     /*!< datatype */
   char *id;    /*!< unique ID identifying this triple */
 };
 
 %extend _Triple {
-  _Triple(const char *s, const char *p, const char *o, const char *id=NULL) {
+  _Triple(const char *s, const char *p, const char *o,
+          const char *d=NULL, const char *id=NULL) {
     Triple *t;
     if (!(t =  calloc(1, sizeof(Triple)))) FAIL("allocation failure");
-    if (triple_set(t, s, p, o, id)) FAIL("cannot set relation");
+    if (triple_set(t, s, p, o, d, id)) FAIL("cannot set relation");
     return t;
   fail:
     if (t) {
@@ -220,19 +239,6 @@ struct _Triple {
   }
 }
 
-%{
-char *triple_get_id2(const char *s, const char *p, const char *o,
-                      const char *namespace) {
-  return triple_get_id(namespace, s, p, o);
-}
-%}
- %rename(triple_get_id) triple_get_id2;
-%newobject triple_get_id;
-char *triple_get_id(const char *s, const char *p, const char *o,
-                     const char *namespace=NULL);
-
-void triple_set_default_namespace(const char *namespace);
-
 
 /* --------
  * Instance
@@ -292,11 +298,15 @@ struct _DLiteInstance {
       DLiteMeta *meta;
       size_t i, *d, n=ndims;
       if (!(meta = dlite_meta_get(metaid)))
-        return dlite_err(1, "cannot find metadata '%s'", metaid), NULL;
+        return dlite_err(dliteMissingMetadataError,
+                         "cannot find metadata '%s'", metaid), NULL;
       if (n != meta->_ndimensions) {
+        dlite_err(dliteValueError, "ndims=%d, but %s has %u dimension(s)",
+                  ndims, metaid, (unsigned)meta->_ndimensions);
         dlite_meta_decref(meta);
-        return dlite_err(1, "%s has %u dimensions",
-                          metaid, (unsigned)meta->_ndimensions), NULL;
+        return dlite_err(dliteParseError, "%s has %u dimensions (%u given)",
+                         metaid, (unsigned)meta->_ndimensions,
+                         (unsigned)n), NULL;
       }
       d = malloc(n * sizeof(size_t));
       for (i=0; i<n; i++) d[i] = dims[i];
diff --git a/bindings/python/dlite-macros.i b/bindings/python/dlite-macros.i
old mode 100755
new mode 100644
diff --git a/bindings/python/dlite-mapping.i b/bindings/python/dlite-mapping.i
old mode 100755
new mode 100644
diff --git a/bindings/python/dlite-misc-python.i b/bindings/python/dlite-misc-python.i
old mode 100755
new mode 100644
diff --git a/bindings/python/dlite-misc.i b/bindings/python/dlite-misc.i
old mode 100755
new mode 100644
diff --git a/bindings/python/dlite-path-python.i b/bindings/python/dlite-path-python.i
old mode 100755
new mode 100644
diff --git a/bindings/python/dlite-path.i b/bindings/python/dlite-path.i
old mode 100755
new mode 100644
diff --git a/bindings/python/dlite-python-docstring.i b/bindings/python/dlite-python-docstring.i
old mode 100755
new mode 100644
diff --git a/bindings/python/dlite-python.i b/bindings/python/dlite-python.i
old mode 100755
new mode 100644
index a9343d142..1bdaa6481
--- a/bindings/python/dlite-python.i
+++ b/bindings/python/dlite-python.i
@@ -931,33 +931,39 @@ int dlite_swig_set_scalar(void *ptr, DLiteType type, size_t size, obj_t *obj)
       void *p;
       if (SWIG_IsOK(SWIG_ConvertPtr(obj, &p, SWIGTYPE_p__Triple, 0))) {
         DLiteRelation *src = (DLiteRelation *)p;
-        triple_reset(ptr, src->s, src->p, src->o, src->id);
+        triple_reset(ptr, src->s, src->p, src->o, src->d, src->id);
 
       } else if (PySequence_Check(obj) || PyIter_Check(obj)) {
         int i;
         Py_ssize_t n;
         PyObject *lst;
-        char **s = ptr;  /* cast DLiteRelation to 4 string pointers */
-        assert(sizeof(DLiteRelation) == 4*sizeof(char *));
+        char **s = ptr;  /* cast DLiteRelation to 5 string pointers */
+        assert(sizeof(DLiteRelation) == 5*sizeof(char *));
 
-        /* Check that `obj` is a sequence of 3 or 4 strings */
+        /* Check that `obj` is a sequence of 3 to 5 strings */
         if (!(lst = PySequence_Fast(obj, "not a sequence or iterable")))
           FAIL("expected relation to be represented by a sequence or iterable");
 
-        if ((n = PySequence_Fast_GET_SIZE(lst)) < 3 || n > 4) {
+        if ((n = PySequence_Fast_GET_SIZE(lst)) < 3 || n > 5) {
           Py_DECREF(lst);
-          FAIL1("relations must be 3 or 4 strings, got %ld", (long)n);
+          FAIL1("relations must be 3 to 5 strings, got %ld", (long)n);
         }
         for (i=0; i<n; i++) {
           PyObject *item = PySequence_Fast_GET_ITEM(lst, i);
           if (!PyUnicode_Check(item)) {
             Py_DECREF(lst);
-            FAIL("relation subject, predicate and object must be strings");
+            FAIL("all elements in a (subject(s), predicate(p), object(o)[, "
+                 "datatype(d)[, id]]) relation provided as a sequence must "
+                 "be strings");
           }
         }
 
         /* Free old values stored in the relation */
-        for (i=0; i<4; i++) if (s[i]) free(s[i]);
+        for (i=0; i<5; i++)
+          if (s[i]) {
+            free(s[i]);
+            s[i] = NULL;
+          }
 
         /* Assign new values */
         for (i=0; i<n; i++) {
@@ -969,14 +975,14 @@ int dlite_swig_set_scalar(void *ptr, DLiteType type, size_t size, obj_t *obj)
         }
 
         /* Assign id if not already provided */
-        if (n < 4)
-          s[3] = triple_get_id(NULL, s[0], s[1], s[2]);
+        if (n < 5)
+          s[4] = triple_get_id(NULL, s[0], s[1], s[2], s[3]);
         Py_DECREF(lst);
 
       } else if (PyMapping_Check(obj)) {
         char *msg=NULL;
-        PyObject *s=NULL, *p=NULL, *o=NULL, *id=NULL;
-        const char *ss, *sp, *so, *sid;
+        PyObject *s=NULL, *p=NULL, *o=NULL, *d=NULL, *id=NULL;
+        const char *ss, *sp, *so, *sd, *sid;
         if (!(s = PyMapping_GetItemString(obj, "s")) ||
             !(p = PyMapping_GetItemString(obj, "p")) ||
             !(o = PyMapping_GetItemString(obj, "o")))
@@ -985,15 +991,21 @@ int dlite_swig_set_scalar(void *ptr, DLiteType type, size_t size, obj_t *obj)
                       PyUnicode_Check(p) && (sp = PyUnicode_AsUTF8(p)) &&
                       PyUnicode_Check(o) && (so = PyUnicode_AsUTF8(o))))
           msg = "Relation 's', 'p', 'o' items must be strings";
+        if (!msg && PyMapping_HasKeyString(obj, "d") &&
+            (!(d = PyMapping_GetItemString(obj, "d")) ||
+             !PyUnicode_Check(d) || !(sd = PyUnicode_AsUTF8(d))))
+          msg = "If given, relation d (datatype) must be a string";
         if (!msg && PyMapping_HasKeyString(obj, "id") &&
             (!(id = PyMapping_GetItemString(obj, "id")) ||
              !PyUnicode_Check(id) || !(sid = PyUnicode_AsUTF8(id))))
           msg = "If given, relation id must be a string";
         if (!msg)
-          triple_reset(ptr, ss, sp, so, (id) ? sid : NULL);
+          triple_reset(ptr, ss, sp, so,
+                       (d) ? sd : NULL, (id) ? sid : NULL);
         Py_XDECREF(s);
         Py_XDECREF(p);
         Py_XDECREF(o);
+        Py_XDECREF(d);
         Py_XDECREF(id);
         if (msg) FAIL1("%s", msg);
 
@@ -1370,10 +1382,35 @@ PyObject *dlite_python_module_error(int code);
 int _get_number_of_errors(void);
 
 %pythoncode %{
-  for n in range(_dlite._get_number_of_errors()):
-      exc = errgetexc(-n)
-      setattr(_dlite, exc.__name__, exc)
-  DLiteStorageBase = _dlite._get_storage_base()
-  DLiteMappingBase = _dlite._get_mapping_base()
-  del n, exc
+for n in range(_dlite._get_number_of_errors()):
+    exc = errgetexc(-n)
+    setattr(_dlite, exc.__name__, exc)
+DLiteStorageBase = _dlite._get_storage_base()
+DLiteMappingBase = _dlite._get_mapping_base()
+del n, exc
+
+def instance_cast(inst, newtype=None):
+    """Return instance converted to a new instance subclass.
+
+    By default the convertion is done to the type of the underlying
+    instance object.
+
+    Any subclass of `dlite.Instance` can be provided as `newtype`.
+    Only downcasting to subclasses of `inst` are permitted.  For
+    casting to other types, `dlite.DLiteTypeError` is raised.
+    """
+    if newtype:
+        subclasses = getattr(newtype, "__subclasses__")
+        if type(inst) in subclasses():
+            inst.__class__ = newtype
+        else:
+            raise _dlite.DLiteTypeError(
+                f"cannot upcast {type(inst)} to {newtype}"
+            )
+    elif inst.meta.uri == COLLECTION_ENTITY:
+        inst.__class__ = Collection
+    elif inst.is_meta:
+        inst.__class__ = Metadata
+    return inst
+
 %}
diff --git a/bindings/python/dlite-storage-python.i b/bindings/python/dlite-storage-python.i
old mode 100755
new mode 100644
index 3f7360788..d13e97841
--- a/bindings/python/dlite-storage-python.i
+++ b/bindings/python/dlite-storage-python.i
@@ -103,7 +103,7 @@
               # Delete reference to iterator object stored away in __init__()
               _dlite._storage_iters.pop(id(self.this), None)
               raise StopIteration()
-          return inst
+          return instance_cast(inst)
   %}
 }
 
diff --git a/bindings/python/dlite-storage.i b/bindings/python/dlite-storage.i
old mode 100755
new mode 100644
index 780767ce8..c7aced65e
--- a/bindings/python/dlite-storage.i
+++ b/bindings/python/dlite-storage.i
@@ -52,7 +52,7 @@ Returns next instance or None if exhausted.") next;
     if (dlite_storage_iter_next($self->s, $self->state, uuid) == 0) {
       DLiteInstance *inst = dlite_instance_load($self->s, uuid);
       // Why isn't the refcount already increased?
-      dlite_instance_incref(inst);
+      if (inst) dlite_instance_incref(inst);
       return inst;
     }
     return NULL;
diff --git a/bindings/python/dlite-type.i b/bindings/python/dlite-type.i
old mode 100755
new mode 100644
diff --git a/bindings/python/dlite.i b/bindings/python/dlite.i
old mode 100755
new mode 100644
diff --git a/bindings/python/factory.py b/bindings/python/factory.py
old mode 100755
new mode 100644
diff --git a/bindings/python/mappings.py b/bindings/python/mappings.py
old mode 100755
new mode 100644
diff --git a/bindings/python/numpy.i b/bindings/python/numpy.i
old mode 100755
new mode 100644
diff --git a/bindings/python/options.py b/bindings/python/options.py
old mode 100755
new mode 100644
index b12a96c4e..9ea4b052d
--- a/bindings/python/options.py
+++ b/bindings/python/options.py
@@ -1,6 +1,8 @@
 """A module for parsing standard options intended to be used by Python
 storage plugins."""
 
+import json
+
 
 class Options(dict):
     """A dict representation of the options string `options`.
@@ -19,23 +21,24 @@ class Options(dict):
 
     def __init__(self, options, defaults=None):
         dict.__init__(self)
-        if options is None:
-            options = ""
-        options = options.split("#")[0]  # strip hash and everything following
         if isinstance(defaults, str):
             defaults = Options(defaults)
         if defaults:
             self.update(defaults)
-
-        if ";" in options:
-            tokens = options.split(";")
-        elif "&" in options:
-            tokens = options.split("&")
-        else:
-            tokens = [options]
-
-        if tokens and tokens != [""]:
-            self.update([t.split("=", 1) for t in tokens])
+        if isinstance(options, str):
+            if options.startswith("{"):
+                self.update(json.loads(options))
+            else:
+                # strip hash and everything following
+                options = options.split("#")[0]
+                if ";" in options:
+                    tokens = options.split(";")
+                elif "&" in options:
+                    tokens = options.split("&")
+                else:
+                    tokens = [options]
+                if tokens and tokens != [""]:
+                    self.update([t.split("=", 1) for t in tokens])
 
     def __getattr__(self, name):
         if name in self:
@@ -45,3 +48,18 @@ def __getattr__(self, name):
 
     def __setattr__(self, name, value):
         self[name] = value
+
+    def __str__(self):
+        encode = False
+        for value in self.values():
+            if isinstance(value, (bool, int, float)):
+                encode = True
+                break
+            elif isinstance(value, str):
+                if ("&" in value) | (";" in value):
+                    encode = True
+                    break
+        if encode:
+            return json.dumps(self, separators=(",", ":"))
+        else:
+            return ";".join([f"{k}={v}" for k, v in self.items()])
diff --git a/bindings/python/paths.py.in b/bindings/python/paths.py.in
old mode 100755
new mode 100644
diff --git a/bindings/python/python-mapping-plugins/.gitignore b/bindings/python/python-mapping-plugins/.gitignore
old mode 100755
new mode 100644
diff --git a/bindings/python/python-mapping-plugins/README.md b/bindings/python/python-mapping-plugins/README.md
old mode 100755
new mode 100644
diff --git a/bindings/python/quantity.py b/bindings/python/quantity.py
new file mode 100755
index 000000000..8ce31cd1c
--- /dev/null
+++ b/bindings/python/quantity.py
@@ -0,0 +1,182 @@
+
+""" Define the singleton QuantityHelper to work with pint Quantity and dlite 
+    instance properties.
+"""
+
+from typing import Any
+import numpy as np
+HAS_PINT = True
+try:
+    import pint
+except Exception:
+    HAS_PINT = False
+
+DLITE_QUANTITY_TYPES = [
+    'int', 'float', 'double', 'uint',
+    'int8', 'int16', 'int32', 'int64',
+    'uint8', 'uint16', 'uint32', 'uint64',
+    'float32', 'float64', 'float80', 'float96', 'float128'
+]
+quantity_helper = None
+
+
+class QuantityHelper:
+
+    def __init__(self):
+        self.__dict__['_instance'] = None
+        self.__dict__['_registry'] = None
+
+    def _get_property(self, name: str):
+        """ Return the property if it exists and if it can be a quantity """
+        p = self._instance.get_property_descr(name)
+        if p.type in DLITE_QUANTITY_TYPES:
+            return p
+        else:
+            raise TypeError(
+                f'The type "{p.type}" of property "{name}" is not a type for'
+                ' a number, cannot convert the property value to a quantity.'
+            )
+
+    def _get_unit_as_string(self, unit: Any) -> str:
+        """ Returns the given unit as a string """
+        unit_string = ''
+        if isinstance(unit, str):
+            unit_string = unit.strip()
+        elif isinstance(unit, self._registry.Unit):
+            unit_string = str(unit)
+        return unit_string.replace('%', 'percent')
+
+    def __getitem__(self, name: str) -> pint.Quantity:
+        p = self._get_property(name)
+        u = self._get_unit_as_string(p.unit)
+        return self.quantity(self._instance[name], u)
+
+    def __setitem__(self, name: str, value: Any):
+        p = self._get_property(name)
+        q = self.parse(value)
+        self._instance[name] = q.m if q.units.dimensionless else q.to(p.unit).m
+
+    def __getattr__(self, name: str):
+        if name in self.__dict__:
+            return self.__dict__[name]
+        else:
+            return self[name]
+
+    def __setattr__(self, name: str, value: Any):
+        if name in self.__dict__:
+            self.__dict__[name] = value
+        else:
+            self[name] = value
+
+    def __call__(self, inst) -> 'QuantityHelper':
+        self._instance = inst
+        self._registry = pint.get_application_registry()
+        if not hasattr(self._registry.get(), 'percent'):
+            self._registry.get().define('percent = 0.01*count')
+        return self
+
+    def names(self):
+        """ Return the names of each property which has a quantity type """
+        prop = self._instance.meta['properties']
+        return [p.name for p in prop if p.type in DLITE_QUANTITY_TYPES]
+
+    def units(self):
+        """ Return the units of each property which has a quantity type """
+        prop = self._instance.meta['properties']
+        return [p.unit for p in prop if p.type in DLITE_QUANTITY_TYPES]
+
+    def values(self):
+        """ Return the quantity of each property which has a quantity type """
+        return [self[name] for name in self.names()]
+
+    def items(self):
+        """ Return a zip iterator (name, quantity) """
+        names = self.names()
+        values = [self[name] for name in names]
+        return zip(names, values)
+
+    def get(self, *names):
+        """ Return the quantity of each given property name """
+        if names:
+            if len(names) == 1:
+                return self[names[0]]
+            else:
+                return [self[name] for name in names]
+        return None
+
+    @property
+    def unit_registry(self) -> pint.UnitRegistry:
+        """ Returns the current pint UnitRegistry object """
+        return self._registry.get()
+
+    def quantity(self, magnitude, unit) -> pint.Quantity:
+        """ Return a pint.Quantity object """
+        return self._registry.Quantity(magnitude, unit)
+
+    def parse(self, value: Any) -> pint.Quantity:
+        """ Parse the given value and return a pint.Quantity object """
+        if isinstance(value, (pint.Quantity, self._registry.Quantity)):
+            return value
+        elif isinstance(value, tuple):
+            if len(value) >= 2:
+                return self.quantity(value[0], value[1])
+            else:
+                return self.quantity(value[0], '')
+        elif isinstance(value, str):
+            return self.parse_expression(value)
+        elif value is None:
+            return self.quantity(np.nan, '')
+        else:
+            return self.quantity(value, '')
+
+    def parse_expression(self, value: str) -> pint.Quantity:
+        """ Parse an expression (str) and return a pint.Quantity object """
+        result = None
+        if value:
+            value_str = self._get_unit_as_string(value)
+            ph = pint.util.ParserHelper.from_string(value_str)
+            if len(ph._d) > 0:
+                un = '*'.join([f'({k}**{v})' for k, v in ph._d.items()])
+                try:
+                    unit = self.unit_registry.parse_units(un)
+                except Exception:
+                    unit = None
+                if unit is not None:
+                    result = self.quantity(ph.scale, un)
+            else:
+                result = self.quantity(ph.scale, '')
+        return self.quantity(np.nan, '') if result is None else result
+
+    def update(self, **kwargs):
+        for key, val in kwargs.items():
+            self[key] = val
+
+    def to_dict(self, names=None, value_type='quantity', fmt=''):
+        if not isinstance(names, (list, tuple, dict)):
+            prop = self._instance.meta['properties']
+            names = [p.name for p in prop if p.type in DLITE_QUANTITY_TYPES]
+        values = {}
+        for name in names:
+            q = self[name]
+            if value_type.startswith('q'):
+                values[name] = q
+            elif value_type.startswith('m'):
+                values[name] = q.m
+            elif value_type.startswith('s'):
+                if fmt:
+                    q.default_format = fmt
+                values[name] = f'{q}'
+        return values
+
+
+def get_quantity_helper(instance):
+    global quantity_helper
+    if HAS_PINT:
+        if quantity_helper is None:
+            quantity_helper = QuantityHelper()
+        return quantity_helper(instance)
+    else:
+        raise RuntimeError(
+            'you must install "pint" to work with quantities, '
+            'try: pip install pint'
+        )
diff --git a/bindings/python/rdf.py b/bindings/python/rdf.py
old mode 100755
new mode 100644
diff --git a/bindings/python/scripts/CMakeLists.txt b/bindings/python/scripts/CMakeLists.txt
old mode 100755
new mode 100644
index d93eb9a3d..5ebdd5afb
--- a/bindings/python/scripts/CMakeLists.txt
+++ b/bindings/python/scripts/CMakeLists.txt
@@ -32,26 +32,30 @@ install(
 # Tests
 test_success(
   dlite-validate-Person
-  dlite-validate ../tests/Person.json
+  dlite-validate ../tests/entities/Person.json
 )
 test_success(
   dlite-validate-persons
-  dlite-validate -p ../tests/Person.json -i a1d8d35f-723c-5ea1-abaf-8fc8f1d0982f
-    ../tests/persons.json
+  dlite-validate
+    -p ../tests/entities/Person.json
+    -i a1d8d35f-723c-5ea1-abaf-8fc8f1d0982f
+    ../tests/input/persons.json
 )
 test_success(
   dlite-validate-persons2
   dlite-validate
-    --storage-path=../tests
+    --storage-path=../tests/entities
     --id=Ada
     --show
-    ../tests/persons.json
+    ../tests/input/persons.json
   )
 
 # No --id for storage with multiple instances
 test_failure(
   dlite-validate-persons-fail2
-  dlite-validate --storage-path ../tests/Person.json --show ../tests/persons.json
+  dlite-validate
+    --storage-path ../tests/entities/Person.json
+    --show ../tests/input/persons.json
 )
 
 # The "dimensions" keyword is required
diff --git a/bindings/python/scripts/Empty.json b/bindings/python/scripts/Empty.json
old mode 100755
new mode 100644
diff --git a/bindings/python/scripts/NoDimensions.json b/bindings/python/scripts/NoDimensions.json
old mode 100755
new mode 100644
diff --git a/bindings/python/scripts/setup.py b/bindings/python/scripts/setup.py
old mode 100755
new mode 100644
diff --git a/bindings/python/triplestore/README.md b/bindings/python/triplestore/README.md
old mode 100755
new mode 100644
diff --git a/bindings/python/triplestore/__init__.py b/bindings/python/triplestore/__init__.py
old mode 100755
new mode 100644
diff --git a/bindings/python/triplestore/units.py b/bindings/python/triplestore/units.py
old mode 100755
new mode 100644
diff --git a/bindings/python/triplestore/units_README.md b/bindings/python/triplestore/units_README.md
old mode 100755
new mode 100644
diff --git a/bindings/python/utils.py b/bindings/python/utils.py
old mode 100755
new mode 100644
index 0aa18e361..386b70f4a
--- a/bindings/python/utils.py
+++ b/bindings/python/utils.py
@@ -199,7 +199,7 @@ def instance_from_dict(d, id=None, single=None, check_storages=True):
         inst = dlite.Instance.from_metaid(meta.uri, dimensions=dims, id=inst_id)
         for p in meta["properties"]:
             value = d["properties"][p.name]
-            inst[p.name] = value
+            inst.set_property(p.name, value)
 
     return inst
 
diff --git a/cmake/FindNumPy.cmake b/cmake/FindNumPy.cmake
old mode 100755
new mode 100644
diff --git a/cmake/FindPackageHandleStandardArgs314.cmake b/cmake/FindPackageHandleStandardArgs314.cmake
old mode 100755
new mode 100644
diff --git a/cmake/FindPackageMessage314.cmake b/cmake/FindPackageMessage314.cmake
old mode 100755
new mode 100644
diff --git a/cmake/FindPython3-Support.diff b/cmake/FindPython3-Support.diff
old mode 100755
new mode 100644
diff --git a/cmake/FindPythonAnaconda.cmake b/cmake/FindPythonAnaconda.cmake
old mode 100755
new mode 100644
diff --git a/cmake/FindPythonModule.cmake b/cmake/FindPythonModule.cmake
old mode 100755
new mode 100644
diff --git a/cmake/FindRaptor.cmake b/cmake/FindRaptor.cmake
old mode 100755
new mode 100644
diff --git a/cmake/FindRasqal.cmake b/cmake/FindRasqal.cmake
old mode 100755
new mode 100644
diff --git a/cmake/FindRedland.cmake b/cmake/FindRedland.cmake
old mode 100755
new mode 100644
diff --git a/cmake/FindSWIG314.cmake b/cmake/FindSWIG314.cmake
old mode 100755
new mode 100644
diff --git a/cmake/FindSphinx.cmake b/cmake/FindSphinx.cmake
old mode 100755
new mode 100644
diff --git a/cmake/Finddlite.cmake b/cmake/Finddlite.cmake
old mode 100755
new mode 100644
diff --git a/cmake/Finduuid.cmake b/cmake/Finduuid.cmake
old mode 100755
new mode 100644
diff --git a/cmake/GenerateDef.cmake b/cmake/GenerateDef.cmake
old mode 100755
new mode 100644
diff --git a/cmake/GenerateDef.py b/cmake/GenerateDef.py
old mode 100755
new mode 100644
diff --git a/cmake/ListToPaths.cmake b/cmake/ListToPaths.cmake
old mode 100755
new mode 100644
diff --git a/cmake/MakePlatformPaths.cmake b/cmake/MakePlatformPaths.cmake
old mode 100755
new mode 100644
diff --git a/cmake/README.md b/cmake/README.md
old mode 100755
new mode 100644
diff --git a/cmake/SetCompilerFlags.cmake b/cmake/SetCompilerFlags.cmake
old mode 100755
new mode 100644
diff --git a/cmake/SetDefaults.cmake b/cmake/SetDefaults.cmake
old mode 100755
new mode 100644
diff --git a/cmake/UseSWIG314.cmake b/cmake/UseSWIG314.cmake
old mode 100755
new mode 100644
diff --git a/cmake/build-python3.7.cmake b/cmake/build-python3.7.cmake
old mode 100755
new mode 100644
diff --git a/cmake/compare_files.cmake b/cmake/compare_files.cmake
old mode 100755
new mode 100644
diff --git a/cmake/dlite-env.sh.in b/cmake/dlite-env.sh.in
old mode 100755
new mode 100644
diff --git a/cmake/dliteCodeGen.bat.in b/cmake/dliteCodeGen.bat.in
old mode 100755
new mode 100644
diff --git a/cmake/dliteCodeGen.cmake b/cmake/dliteCodeGen.cmake
old mode 100755
new mode 100644
diff --git a/cmake/dliteConfig.cmake.in b/cmake/dliteConfig.cmake.in
old mode 100755
new mode 100644
diff --git a/cmake/dliteGenEnv-sub.cmake b/cmake/dliteGenEnv-sub.cmake
old mode 100755
new mode 100644
diff --git a/cmake/dliteGenEnv.cmake b/cmake/dliteGenEnv.cmake
old mode 100755
new mode 100644
diff --git a/cmake/toolchain-win64.cmake b/cmake/toolchain-win64.cmake
old mode 100755
new mode 100644
diff --git a/cmake/valgrind-dlite.supp b/cmake/valgrind-dlite.supp
old mode 100755
new mode 100644
diff --git a/cmake/valgrind-python.supp b/cmake/valgrind-python.supp
old mode 100755
new mode 100644
diff --git a/cmake/valgrind_python.py b/cmake/valgrind_python.py
old mode 100755
new mode 100644
diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt
old mode 100755
new mode 100644
index 925aac288..4c1b4b934
--- a/doc/CMakeLists.txt
+++ b/doc/CMakeLists.txt
@@ -24,7 +24,6 @@ set(DOXYGEN_DOXYFILE_OUT "${BINARY_BUILD_DIR}/Doxyfile")
 
 set(DOXYGEN_INDEX_FILE ${DOXYGEN_OUTPUT_XML_DIR}/index.xml)
 
-message (${DOXYGEN_DOXYFILE_IN})
 configure_file(
     "${CMAKE_CURRENT_SOURCE_DIR}/conf.py.in"
     "${BINARY_BUILD_DIR}/conf.py"
@@ -79,4 +78,3 @@ add_custom_command(OUTPUT ${SPHINX_INDEX_FILE}
   COMMENT "Building HTML documentation with Sphinx")
 
 add_custom_target(Sphinx ALL DEPENDS ${SPHINX_INDEX_FILE})
-
diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in
old mode 100755
new mode 100644
index 3ecda1361..202ecaf1e
--- a/doc/Doxyfile.in
+++ b/doc/Doxyfile.in
@@ -1986,7 +1986,17 @@ INCLUDE_FILE_PATTERNS  = config.h
 # This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
 
 PREDEFINED             = __declspec(x)= \
-                         __attribute__(x)=
+                         __attribute__(x)= \
+                         STRINGIFY(s)="s" \
+                         UNUSED(x)= \
+                         JSMN_API= \
+                         DSL_EXPORT= \
+                         DLLEXTERN= \
+                         DLiteInstance_HEAD= \
+                         DLiteMeta_HEAD= \
+                         PluginAPI_HEAD= \
+                         DLiteStorage_HEAD= \
+                         DLiteDataModel_HEAD=
 
 # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
 # tag can be used to specify a list of macro names that should be expanded. The
@@ -1995,7 +2005,15 @@ PREDEFINED             = __declspec(x)= \
 # definition found in the source code.
 # This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
 
-EXPAND_AS_DEFINED      =
+EXPAND_AS_DEFINED      = map_t
+
+# These should ideally be expanded, but it seems that doxygen isn't able to do that
+# properly. Instead we remove them using PREDEFINED.
+#                         DLiteInstance_HEAD \
+#                         DLiteMeta_HEAD \
+#                         PluginAPI_HEAD \
+#                         DLiteStorage_HEAD \
+#                         DLiteDataModel_HEAD
 
 # If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
 # remove all references to function-like macros that are alone on a line, have
diff --git a/doc/Makefile b/doc/Makefile
old mode 100755
new mode 100644
diff --git a/doc/_static/SOFT-metadata-structure.png b/doc/_static/SOFT-metadata-structure.png
old mode 100755
new mode 100644
diff --git a/doc/_static/agenda.png b/doc/_static/agenda.png
old mode 100755
new mode 100644
diff --git a/doc/_static/api-reference.svg b/doc/_static/api-reference.svg
old mode 100755
new mode 100644
diff --git a/doc/_static/api.png b/doc/_static/api.png
old mode 100755
new mode 100644
diff --git a/doc/_static/architecture.svg b/doc/_static/architecture.svg
old mode 100755
new mode 100644
diff --git a/doc/_static/contribute.svg b/doc/_static/contribute.svg
old mode 100755
new mode 100644
diff --git a/doc/_static/datamodel-hierarchy.svg b/doc/_static/datamodel-hierarchy.svg
old mode 100755
new mode 100644
diff --git a/doc/_static/datamodel.svg b/doc/_static/datamodel.svg
old mode 100755
new mode 100644
diff --git a/doc/_static/datamodel.xml b/doc/_static/datamodel.xml
old mode 100755
new mode 100644
diff --git a/doc/_static/dlite-metadata_model.png b/doc/_static/dlite-metadata_model.png
old mode 100755
new mode 100644
diff --git a/doc/_static/favicon.ico b/doc/_static/favicon.ico
old mode 100755
new mode 100644
diff --git a/doc/_static/getting-started.svg b/doc/_static/getting-started.svg
old mode 100755
new mode 100644
diff --git a/doc/_static/logo-circle.ico b/doc/_static/logo-circle.ico
old mode 100755
new mode 100644
diff --git a/doc/_static/logo-circle.png b/doc/_static/logo-circle.png
old mode 100755
new mode 100644
diff --git a/doc/_static/logo-circle.svg b/doc/_static/logo-circle.svg
old mode 100755
new mode 100644
diff --git a/doc/_static/logo-small.png b/doc/_static/logo-small.png
old mode 100755
new mode 100644
diff --git a/doc/_static/logo.png b/doc/_static/logo.png
old mode 100755
new mode 100644
diff --git a/doc/_static/logo.svg b/doc/_static/logo.svg
old mode 100755
new mode 100644
diff --git a/doc/_static/manual.png b/doc/_static/manual.png
old mode 100755
new mode 100644
diff --git a/doc/_static/overview.svg b/doc/_static/overview.svg
old mode 100755
new mode 100644
diff --git a/doc/_static/startup.png b/doc/_static/startup.png
old mode 100755
new mode 100644
diff --git a/doc/_static/transactions-branch.png b/doc/_static/transactions-branch.png
old mode 100755
new mode 100644
diff --git a/doc/_static/transactions.drawio b/doc/_static/transactions.drawio
old mode 100755
new mode 100644
diff --git a/doc/_static/transactions.png b/doc/_static/transactions.png
old mode 100755
new mode 100644
diff --git a/doc/_static/user-guide.svg b/doc/_static/user-guide.svg
old mode 100755
new mode 100644
diff --git a/doc/_templates/autoapi/base/base.rst b/doc/_templates/autoapi/base/base.rst
old mode 100755
new mode 100644
diff --git a/doc/_templates/autoapi/index.rst b/doc/_templates/autoapi/index.rst
old mode 100755
new mode 100644
diff --git a/doc/_templates/autoapi/python/attribute.rst b/doc/_templates/autoapi/python/attribute.rst
old mode 100755
new mode 100644
diff --git a/doc/_templates/autoapi/python/class.rst b/doc/_templates/autoapi/python/class.rst
old mode 100755
new mode 100644
diff --git a/doc/_templates/autoapi/python/data.rst b/doc/_templates/autoapi/python/data.rst
old mode 100755
new mode 100644
diff --git a/doc/_templates/autoapi/python/exception.rst b/doc/_templates/autoapi/python/exception.rst
old mode 100755
new mode 100644
diff --git a/doc/_templates/autoapi/python/function.rst b/doc/_templates/autoapi/python/function.rst
old mode 100755
new mode 100644
diff --git a/doc/_templates/autoapi/python/method.rst b/doc/_templates/autoapi/python/method.rst
old mode 100755
new mode 100644
diff --git a/doc/_templates/autoapi/python/module.rst b/doc/_templates/autoapi/python/module.rst
old mode 100755
new mode 100644
diff --git a/doc/_templates/autoapi/python/package.rst b/doc/_templates/autoapi/python/package.rst
old mode 100755
new mode 100644
diff --git a/doc/_templates/autoapi/python/property.rst b/doc/_templates/autoapi/python/property.rst
old mode 100755
new mode 100644
diff --git a/doc/_templates/respirator/doxygenfile.rst.j2 b/doc/_templates/respirator/doxygenfile.rst.j2
old mode 100755
new mode 100644
diff --git a/doc/_templates/respirator/src.rst.j2 b/doc/_templates/respirator/src.rst.j2
old mode 100755
new mode 100644
diff --git a/doc/_templates/respirator/src/utils.rst.j2 b/doc/_templates/respirator/src/utils.rst.j2
old mode 100755
new mode 100644
diff --git a/doc/_templates/respirator/toc.rst.j2 b/doc/_templates/respirator/toc.rst.j2
old mode 100755
new mode 100644
diff --git a/doc/api.rst b/doc/api.rst
old mode 100755
new mode 100644
diff --git a/doc/conf.py.in b/doc/conf.py.in
old mode 100755
new mode 100644
index 2b8de8dbb..f9bada93e
--- a/doc/conf.py.in
+++ b/doc/conf.py.in
@@ -163,6 +163,21 @@ if os.getenv("CI"):
 plantuml = "java -jar lib/plantuml.jar"
 plantuml_output_format = "svg_img"
 
+####
+
+# Options for the C domain
+c_extra_keywords = [
+    'alignas',
+    #'alignof',  # defined in dlite-type.h
+    'bool',
+    'complex',
+    'imaginary',
+    'noreturn',
+    'static_assert',
+    'thread_local',
+]
+
+
 ####
 
 # Jinja2 custom filters
diff --git a/doc/contributors_guide/coding_guideline.md b/doc/contributors_guide/coding_guideline.md
old mode 100755
new mode 100644
diff --git a/doc/contributors_guide/documentation_contributors.md b/doc/contributors_guide/documentation_contributors.md
old mode 100755
new mode 100644
diff --git a/doc/contributors_guide/index.rst b/doc/contributors_guide/index.rst
old mode 100755
new mode 100644
diff --git a/doc/contributors_guide/release_instructions.md b/doc/contributors_guide/release_instructions.md
old mode 100755
new mode 100644
diff --git a/doc/contributors_guide/tips_and_tricks.md b/doc/contributors_guide/tips_and_tricks.md
old mode 100755
new mode 100644
diff --git a/doc/getting_started/about.md b/doc/getting_started/about.md
old mode 100755
new mode 100644
diff --git a/doc/getting_started/build/build.md b/doc/getting_started/build/build.md
old mode 100755
new mode 100644
diff --git a/doc/getting_started/build/build_against_python_env.md b/doc/getting_started/build/build_against_python_env.md
old mode 100755
new mode 100644
diff --git a/doc/getting_started/build/build_dependencies.md b/doc/getting_started/build/build_dependencies.md
old mode 100755
new mode 100644
diff --git a/doc/getting_started/build/build_with_vs.md b/doc/getting_started/build/build_with_vs.md
old mode 100755
new mode 100644
diff --git a/doc/getting_started/build/cmake_variables.md b/doc/getting_started/build/cmake_variables.md
old mode 100755
new mode 100644
diff --git a/doc/getting_started/build/index.rst b/doc/getting_started/build/index.rst
old mode 100755
new mode 100644
diff --git a/doc/getting_started/build/runtime_dependencies.md b/doc/getting_started/build/runtime_dependencies.md
old mode 100755
new mode 100644
diff --git a/doc/getting_started/features.md b/doc/getting_started/features.md
old mode 100755
new mode 100644
diff --git a/doc/getting_started/index.rst b/doc/getting_started/index.rst
old mode 100755
new mode 100644
diff --git a/doc/getting_started/installation.md b/doc/getting_started/installation.md
old mode 100755
new mode 100644
diff --git a/doc/getting_started/more_advanced_example.md b/doc/getting_started/more_advanced_example.md
old mode 100755
new mode 100644
diff --git a/doc/getting_started/tutorial.md b/doc/getting_started/tutorial.md
old mode 100755
new mode 100644
index c1a9f2bed..4495c475a
--- a/doc/getting_started/tutorial.md
+++ b/doc/getting_started/tutorial.md
@@ -230,3 +230,119 @@ for key in mapping:
 
 
 More examples of how to use DLite are available in the [examples](https://github.com/SINTEF/dlite/tree/master/examples) directory.
+
+
+## Working with physical quantities
+
+As you can read in the previous sections, a property of a dlite entity can be
+defined with a **unit** (a unit of measurement). The dlite.Instance object can
+set (or get) their properties from (or to) physical quanities (the product of a
+numerical value and a unit of measurement). The dlite library uses the physical
+quantities defined and implemented by the Python package
+[pint](https://pint.readthedocs.io/en/stable/getting/overview.html).
+
+> You must install pint if you want to to work with physical quantities,
+> try to run the command ```pip install pint```, see the page
+> [pint installation](https://pint.readthedocs.io/en/stable/getting/index.html#installation).
+
+This section illustrates how to work with physical quantities when manipulating
+the dlite **instance** objects. The code example will use the following data model:
+
+```yaml
+uri: http://onto-ns.com/meta/0.1/BodyMassIndex
+description: The body mass index of a person.
+properties:
+  name:
+    type: string
+    description: Name of the person.
+  age:
+    type: float32
+    unit: year
+    description: Age of the person.
+  height:
+    type: float
+    unit: cm
+    description: Height of the person.
+  weight:
+    type: float
+    unit: kg
+    description: Weight of the person.
+  bmi:
+    type: float
+    unit: kg/m^2
+    description: Body mass index.
+  category:
+    type: string
+    description: Underweight, normal, or overweight.
+```
+
+The code below will create one BodyMassIndex instance object (variable ```person```) and assign the properties of the ```person``` using physical quantities. You can use the method ```person.set_quantity``` to set a property with a quantity or you can use the shortcut ```person.q``` like shown below:
+
+```python
+BodyMassIndex = dlite.get_instance("http://onto-ns.com/meta/0.1/BodyMassIndex")
+# create a BodyMassIndex instance
+person = BodyMassIndex()
+# assign the name of the person
+person.name = "John Doe"
+# assign the age of the person without a physical quantity
+person.age = 5
+assert person.age == 5.0
+# assign the age of the person with a physical quantity
+person.set_quantity("age", 3.0, "years")
+assert person.age == 3.0
+person.q.age = (1461, "days")
+assert person.age == 4.0
+# assign the height and the weight
+person.q.height = "1.72m"
+person.q.weight = "63kg"
+assert person.height == 172.0
+assert person.weight == 63.0
+# compute the BMI
+person.q.bmi = person.q.weight.to("kg") / person.q.height.to("m") ** 2
+assert np.round(person.get_quantity("bmi").magnitude) == 21.0
+# the following line will give the same result as the line above
+person.q.bmi = person.q.weight / person.q.height ** 2
+# the following line will raise a TypeError exception, because the property
+# "category" with the type "string" cannot be converted into a quantity.
+category = person.q.category
+# assign the category
+person.category = "normal"
+if person.bmi < 18.5:
+    person.category = = "underweight"
+elif person.bmi >= 25.0:
+    person.category = "overweight"
+```
+
+> If you need in your program to use a specific units registry, use the function
+> [pint.set_application_registry](https://pint.readthedocs.io/en/0.10.1/tutorial.html#using-pint-in-your-projects)
+
+The Python property ```q``` of the dlite.Instance objects as some other methods:
+
+```python
+assert person.q.names() == ["age", "height", "weight", "bmi"]
+assert person.q.units() == ["year", "cm", "kg", "kg/m^2"]
+for name, quantity in persion.q.items():
+    quantity.defaut_format = "~"  # format the unit with a compact format
+    print(f"{name} = {quantity}")
+```
+
+> see [quantity formatting](https://pint.readthedocs.io/en/stable/user/formatting.html)
+
+You should not keep the Python property ```q``` as a local variable, it will result with wrong assigment if you work with several instances.
+
+```python
+# >>> don't do that
+p1 = person1.q  
+p2 = person2.q
+# this will assign the weight to person2, and not to person1
+p1.weight = "34kg"  
+# <<< 
+# you must write this code
+person1.q.weight = "34kg"
+person2.q.weight = "32kg"
+# for more complex formula, you can get the quantities in local variables
+w1, h1 = person1.q.get("weight", "height")
+w2, h2, a2 = person2.q.get("weight", "height", "age")
+```
+
+The first line of the code above prepare the [singleton](https://www.geeksforgeeks.org/singleton-pattern-in-python-a-complete-guide/) dlite.quantity.QuantityHelper for the person1 and return the singleton. The second line do the same as the first line, so the variable ```p2``` is the singleton and is prepared to work on ```person2```.
\ No newline at end of file
diff --git a/doc/index.rst b/doc/index.rst
old mode 100755
new mode 100644
diff --git a/doc/license.md b/doc/license.md
old mode 100755
new mode 100644
diff --git a/doc/make.bat b/doc/make.bat
old mode 100755
new mode 100644
diff --git a/doc/respirator.py b/doc/respirator.py
old mode 100755
new mode 100644
diff --git a/doc/user_guide/code_generation.md b/doc/user_guide/code_generation.md
old mode 100755
new mode 100644
diff --git a/doc/user_guide/collections.md b/doc/user_guide/collections.md
old mode 100755
new mode 100644
diff --git a/doc/user_guide/concepts.md b/doc/user_guide/concepts.md
old mode 100755
new mode 100644
diff --git a/doc/user_guide/environment_variables.md b/doc/user_guide/environment_variables.md
old mode 100755
new mode 100644
diff --git a/doc/user_guide/features.md b/doc/user_guide/features.md
old mode 100755
new mode 100644
diff --git a/doc/user_guide/index.rst b/doc/user_guide/index.rst
old mode 100755
new mode 100644
index 2767421dc..57b01cb68
--- a/doc/user_guide/index.rst
+++ b/doc/user_guide/index.rst
@@ -14,7 +14,7 @@ User Guide
    tools
    code_generation
    storage_plugins
-   storage_plugins_mongodb-atlas
+   storage_plugins_mongodb
    environment_variables
    features
    vocabulary
diff --git a/doc/user_guide/mappings.md b/doc/user_guide/mappings.md
old mode 100755
new mode 100644
diff --git a/doc/user_guide/storage_plugin.py b/doc/user_guide/storage_plugin.py
old mode 100755
new mode 100644
diff --git a/doc/user_guide/storage_plugins.md b/doc/user_guide/storage_plugins.md
old mode 100755
new mode 100644
diff --git a/doc/user_guide/storage_plugins_mongodb-atlas.md b/doc/user_guide/storage_plugins_mongodb-atlas.md
deleted file mode 100755
index 7c3ebbc4b..000000000
--- a/doc/user_guide/storage_plugins_mongodb-atlas.md
+++ /dev/null
@@ -1,42 +0,0 @@
-Mongodb Storage plugin
-===============
-
-Quick Guide
--------
-
-This is a quickguide for users wishing to connect to a mongodb atlas server. It is assumed that they have access
-to a server and have their own username and password.
-
-First, set up the connection information:
-
-```python
-    >>> user = "USERNAME"
-    >>> password = "PASSWORD"
-    >>> server = "MYSERVERADDRESS"
-    >>> uri =  f"mongodb://{server}"
-
-```
-
-With this information one can then create a dlite storage instance.
-
-```python
-    >>> import dlite
-
-    >>> storage = dlite.Storage(
-    ...     'mongodb', uri,
-    ...     options=f'user={user};password={password};schema=mongodb+srv'
-    ... )
-
-```
-
-This storage instance can then be used like any other dlite storage instance.
-For example saving and loading instances:
-
-```python
-    >>> inst1 = dlite.Instance.from_location("json", "MY/INSTANCE/PATH.JSON")
-    >>> storage.save(inst1)
-    >>> storage.load(inst1.uuid)
-
-```
-
-Replace "MY/INSTANCE/PATH.JSON" with the path to your instance file, and make sure to use the correct username, password, and server address for your MongoDB Atlas server.
diff --git a/doc/user_guide/storage_plugins_mongodb.md b/doc/user_guide/storage_plugins_mongodb.md
new file mode 100644
index 000000000..f17723a58
--- /dev/null
+++ b/doc/user_guide/storage_plugins_mongodb.md
@@ -0,0 +1,91 @@
+MongoDB storage plugin
+======================
+
+Quick guide
+-----------
+
+This is a quickguide for users wishing to connect to a mongodb server.
+It is assumed that they have access to a server and have their own username
+and password (or they have access to a connection string).
+
+The MongoDB dlite plugin used the Python client
+[pymongo.MongoClient](https://pymongo.readthedocs.io/en/stable/api/pymongo/mongo_client.html#pymongo.mongo_client.MongoClient)
+to connect to the server. The main parameter to set-up a connection
+to a MongoDB database is the URI, many options like the user name and the
+password can be defined in the URI (see
+[mongodb URI](https://www.mongodb.com/docs/manual/reference/connection-string/#connection-string-formats)
+for more details).
+
+First, set up the connection string:
+
+```python
+>>> user = "USERNAME"
+>>> password = "PASSWORD"
+>>> server = "MYSERVERADDRESS"
+>>> uri =  f"mongodb+srv://{user}:{password}@{server}"
+```
+
+Then, you must define the name of the database and the name of the collection.
+The dlite storage will store and retireve instances from a collection
+(see the definitions of [Databases and Collections](https://www.mongodb.com/docs/manual/core/databases-and-collections/)).
+
+```python
+>>> database = "test-database"
+>>> collection = "test-collection"
+```
+
+With those information you can create a dlite storage instance.
+
+```python
+>>> import dlite
+>>> options = f"mode=w,database={database},collection={collection}"
+>>> storage = dlite.Storage("mongodb", uri, options)
+```
+
+This storage instance can then be used like any other dlite storage instance.
+For example saving and loading instances:
+
+```python
+>>> inst1 = dlite.Instance.from_location("json", "MY/INSTANCE/PATH.JSON")
+>>> storage.save(inst1)
+>>> storage.load(inst1.uuid)
+```
+
+Replace "MY/INSTANCE/PATH.JSON" with the path to your instance file,
+and make sure to use the correct username, password, and server address
+for your MongoDB server.
+
+Initialize the MongoClient with more options
+--------------------------------------------
+
+All options/parameters of the MongoClient
+[constructor](https://pymongo.readthedocs.io/en/stable/api/pymongo/mongo_client.html#pymongo.mongo_client.MongoClient)
+can be passed via the dlite storage parameters ```options```, like shown below.
+
+```python
+>>> import dlite
+>>> from dlite.options import Options
+>>> options = Options(f"mode=w,database={database},collection={collection}")
+>>> options.update(connect=False, maxPoolSize=200)
+>>> storage = dlite.Storage("mongodb", uri, str(options))
+```
+
+Connect to Azure MongoDB
+------------------------
+
+In the [Azure portal](https://portal.azure.com/), create an **Azure CosmosDB for MongoDB** account, then open the new account, and open the menu Settings > Connection strings.
+In the **Connection strings** page, show and copy to the clipboard the **PRIMARY CONNECTION STRING**. Then paste the string to a *.env* file.
+
+```sh
+# ".env" file
+AZURE_MONGODB=mongodb://...
+```
+
+Then, in your Python code, load the connection string and use it to create a dlite storage.
+
+```python
+>>> from dotenv import dotenv_values
+>>> config = dotenv_values(".env")
+>>> options = "mode=w,database=test-database,collection=test-collection"
+>>> storage = dlite.Storage("mongodb", config["AZURE_MONGODB"], options)
+```
diff --git a/doc/user_guide/tools.md b/doc/user_guide/tools.md
old mode 100755
new mode 100644
diff --git a/doc/user_guide/transactions.md b/doc/user_guide/transactions.md
old mode 100755
new mode 100644
diff --git a/doc/user_guide/type-system.md b/doc/user_guide/type-system.md
old mode 100755
new mode 100644
diff --git a/doc/user_guide/vocabulary.md b/doc/user_guide/vocabulary.md
old mode 100755
new mode 100644
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
old mode 100755
new mode 100644
diff --git a/examples/TEM_data/__pycache__/tem_paths.cpython-38.pyc b/examples/TEM_data/__pycache__/tem_paths.cpython-38.pyc
deleted file mode 100644
index fb2ef4433..000000000
Binary files a/examples/TEM_data/__pycache__/tem_paths.cpython-38.pyc and /dev/null differ
diff --git a/examples/datamodel_as_rdf/__pycache__/dataresource_pydantic2.cpython-38.pyc b/examples/datamodel_as_rdf/__pycache__/dataresource_pydantic2.cpython-38.pyc
deleted file mode 100644
index 3e1ac845d..000000000
Binary files a/examples/datamodel_as_rdf/__pycache__/dataresource_pydantic2.cpython-38.pyc and /dev/null differ
diff --git a/examples/datamodel_as_rdf/__pycache__/pydantic_nested.cpython-38.pyc b/examples/datamodel_as_rdf/__pycache__/pydantic_nested.cpython-38.pyc
deleted file mode 100644
index f42f9494b..000000000
Binary files a/examples/datamodel_as_rdf/__pycache__/pydantic_nested.cpython-38.pyc and /dev/null differ
diff --git a/examples/dehydrogenation/.gitignore b/examples/dehydrogenation/.gitignore
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/1-simple-workflow/.gitignore b/examples/dehydrogenation/1-simple-workflow/.gitignore
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/1-simple-workflow/calculate_reaction.py b/examples/dehydrogenation/1-simple-workflow/calculate_reaction.py
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/1-simple-workflow/molecular_energies.py b/examples/dehydrogenation/1-simple-workflow/molecular_energies.py
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/2-instance-mappings/.gitignore b/examples/dehydrogenation/2-instance-mappings/.gitignore
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/3-property-mappings/mappings_from_ontology/.gitignore b/examples/dehydrogenation/3-property-mappings/mappings_from_ontology/.gitignore
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/3-property-mappings/mappings_from_ontology/calculate_reaction_energy.py b/examples/dehydrogenation/3-property-mappings/mappings_from_ontology/calculate_reaction_energy.py
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/3-property-mappings/mappings_from_ontology/map_molecules.py b/examples/dehydrogenation/3-property-mappings/mappings_from_ontology/map_molecules.py
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/3-property-mappings/mappings_from_ontology/map_substance.py b/examples/dehydrogenation/3-property-mappings/mappings_from_ontology/map_substance.py
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/3-property-mappings/mappings_from_ontology/run_w_onto.py b/examples/dehydrogenation/3-property-mappings/mappings_from_ontology/run_w_onto.py
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/3-property-mappings/mappings_hard_coded/run.py b/examples/dehydrogenation/3-property-mappings/mappings_hard_coded/run.py
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/README.md b/examples/dehydrogenation/README.md
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/auto_data_model_creation/make_metadata.py b/examples/dehydrogenation/auto_data_model_creation/make_metadata.py
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/auto_data_model_creation/nwchem_result1.nwout b/examples/dehydrogenation/auto_data_model_creation/nwchem_result1.nwout
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/auto_data_model_creation/nwchem_result2.nwout b/examples/dehydrogenation/auto_data_model_creation/nwchem_result2.nwout
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/entities/Molecule.json b/examples/dehydrogenation/entities/Molecule.json
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/entities/Reaction.json b/examples/dehydrogenation/entities/Reaction.json
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/entities/Substance.json b/examples/dehydrogenation/entities/Substance.json
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/figs/c2h6.png b/examples/dehydrogenation/figs/c2h6.png
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/figs/c4h4.png b/examples/dehydrogenation/figs/c4h4.png
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/figs/dehydrogenation-mappings.png b/examples/dehydrogenation/figs/dehydrogenation-mappings.png
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/figs/dehydrogenation-mappings2.png b/examples/dehydrogenation/figs/dehydrogenation-mappings2.png
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/figs/dehydrogenation-overall-workflow.png b/examples/dehydrogenation/figs/dehydrogenation-overall-workflow.png
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/figs/dehydrogenation-workflow1.png b/examples/dehydrogenation/figs/dehydrogenation-workflow1.png
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/figs/dehydrogenation-workflow2.png b/examples/dehydrogenation/figs/dehydrogenation-workflow2.png
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/figs/dehydrogenation-workflow3.png b/examples/dehydrogenation/figs/dehydrogenation-workflow3.png
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/figs/dehydrogenation-workflow4.png b/examples/dehydrogenation/figs/dehydrogenation-workflow4.png
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/figs/dehydrogenation.drawio b/examples/dehydrogenation/figs/dehydrogenation.drawio
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/figs/h2.png b/examples/dehydrogenation/figs/h2.png
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/figs/reaction.png b/examples/dehydrogenation/figs/reaction.png
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/molecules/C2H4.xyz b/examples/dehydrogenation/molecules/C2H4.xyz
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/molecules/C2H6.xyz b/examples/dehydrogenation/molecules/C2H6.xyz
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/molecules/C3H6.xyz b/examples/dehydrogenation/molecules/C3H6.xyz
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/molecules/C3H8.xyz b/examples/dehydrogenation/molecules/C3H8.xyz
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/molecules/CH4.xyz b/examples/dehydrogenation/molecules/CH4.xyz
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/molecules/H2.xyz b/examples/dehydrogenation/molecules/H2.xyz
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/ontology/catalog-v001.xml b/examples/dehydrogenation/ontology/catalog-v001.xml
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/ontology/chemistry.ttl b/examples/dehydrogenation/ontology/chemistry.ttl
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/ontology/mapsTo.ttl b/examples/dehydrogenation/ontology/mapsTo.ttl
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/ontology/ontology.py b/examples/dehydrogenation/ontology/ontology.py
old mode 100755
new mode 100644
diff --git a/examples/dehydrogenation/python-mapping-plugins/molecule2substance.py b/examples/dehydrogenation/python-mapping-plugins/molecule2substance.py
old mode 100755
new mode 100644
diff --git a/examples/entities/Chemistry-0.1.json b/examples/entities/Chemistry-0.1.json
old mode 100755
new mode 100644
diff --git a/examples/entities/aa6060.json b/examples/entities/aa6060.json
old mode 100755
new mode 100644
diff --git a/examples/entities/aa6082.json b/examples/entities/aa6082.json
old mode 100755
new mode 100644
diff --git a/examples/entity_service/.gitignore b/examples/entity_service/.gitignore
old mode 100755
new mode 100644
diff --git a/examples/entity_service/README.md b/examples/entity_service/README.md
old mode 100755
new mode 100644
diff --git a/examples/entity_service/get_instance.py b/examples/entity_service/get_instance.py
old mode 100755
new mode 100644
diff --git a/examples/entity_service/main.py b/examples/entity_service/main.py
old mode 100755
new mode 100644
diff --git a/examples/entity_service/populate_database.py b/examples/entity_service/populate_database.py
old mode 100755
new mode 100644
diff --git a/examples/entity_service/try_service.py b/examples/entity_service/try_service.py
old mode 100755
new mode 100644
diff --git a/examples/ex1/CMakeLists.txt b/examples/ex1/CMakeLists.txt
old mode 100755
new mode 100644
diff --git a/examples/ex1/Chemistry-0.1.json b/examples/ex1/Chemistry-0.1.json
old mode 100755
new mode 100644
diff --git a/examples/ex1/Finddlite.cmake b/examples/ex1/Finddlite.cmake
old mode 100755
new mode 100644
diff --git a/examples/ex1/README.md b/examples/ex1/README.md
old mode 100755
new mode 100644
diff --git a/examples/ex1/main.c b/examples/ex1/main.c
old mode 100755
new mode 100644
diff --git a/examples/ex2/.gitignore b/examples/ex2/.gitignore
old mode 100755
new mode 100644
diff --git a/examples/ex2/CMakeLists.txt b/examples/ex2/CMakeLists.txt
old mode 100755
new mode 100644
diff --git a/examples/ex2/Chemistry-0.1.json b/examples/ex2/Chemistry-0.1.json
old mode 100755
new mode 100644
diff --git a/examples/ex2/Finddlite.cmake b/examples/ex2/Finddlite.cmake
old mode 100755
new mode 100644
diff --git a/examples/ex2/README.md b/examples/ex2/README.md
old mode 100755
new mode 100644
diff --git a/examples/ex2/main.c b/examples/ex2/main.c
old mode 100755
new mode 100644
diff --git a/examples/ex3/.gitignore b/examples/ex3/.gitignore
old mode 100755
new mode 100644
diff --git a/examples/ex3/CMakeLists.txt b/examples/ex3/CMakeLists.txt
old mode 100755
new mode 100644
diff --git a/examples/ex3/PhilibTable.json b/examples/ex3/PhilibTable.json
old mode 100755
new mode 100644
diff --git a/examples/ex3/README.md b/examples/ex3/README.md
old mode 100755
new mode 100644
diff --git a/examples/ex3/main.c b/examples/ex3/main.c
old mode 100755
new mode 100644
diff --git a/examples/ex4/CMakeLists.txt b/examples/ex4/CMakeLists.txt
old mode 100755
new mode 100644
diff --git a/examples/ex4/Chemistry-0.1.json b/examples/ex4/Chemistry-0.1.json
old mode 100755
new mode 100644
diff --git a/examples/ex4/README.md b/examples/ex4/README.md
old mode 100755
new mode 100644
diff --git a/examples/ex4/main.f90 b/examples/ex4/main.f90
old mode 100755
new mode 100644
diff --git a/examples/ex5d/PlotPhilibTable.ipynb b/examples/ex5d/PlotPhilibTable.ipynb
old mode 100755
new mode 100644
diff --git a/examples/ex5d/README.md b/examples/ex5d/README.md
old mode 100755
new mode 100644
diff --git a/examples/ex5d/example5d.c b/examples/ex5d/example5d.c
old mode 100755
new mode 100644
diff --git a/examples/ex5d/philibtable.h b/examples/ex5d/philibtable.h
old mode 100755
new mode 100644
diff --git a/examples/ex5d/tc_data_defs.h b/examples/ex5d/tc_data_defs.h
old mode 100755
new mode 100644
diff --git a/examples/ex5d/tqroot.h b/examples/ex5d/tqroot.h
old mode 100755
new mode 100644
diff --git a/examples/mappings/README.md b/examples/mappings/README.md
old mode 100755
new mode 100644
diff --git a/examples/mappings/__pycache__/mappingfunc.cpython-38.pyc b/examples/mappings/__pycache__/mappingfunc.cpython-38.pyc
deleted file mode 100644
index f7dbcc34b..000000000
Binary files a/examples/mappings/__pycache__/mappingfunc.cpython-38.pyc and /dev/null differ
diff --git a/examples/mappings/__pycache__/simple.cpython-38.pyc b/examples/mappings/__pycache__/simple.cpython-38.pyc
deleted file mode 100644
index 8d759dfad..000000000
Binary files a/examples/mappings/__pycache__/simple.cpython-38.pyc and /dev/null differ
diff --git a/examples/mappings/asedemo.svg b/examples/mappings/asedemo.svg
old mode 100755
new mode 100644
diff --git a/examples/mappings/data/C3H6.json b/examples/mappings/data/C3H6.json
old mode 100755
new mode 100644
diff --git a/examples/mappings/data/result.json b/examples/mappings/data/result.json
old mode 100755
new mode 100644
diff --git a/examples/mappings/entities/CalcResult.json b/examples/mappings/entities/CalcResult.json
old mode 100755
new mode 100644
diff --git a/examples/mappings/entities/Energy.json b/examples/mappings/entities/Energy.json
old mode 100755
new mode 100644
diff --git a/examples/mappings/entities/Forces.json b/examples/mappings/entities/Forces.json
old mode 100755
new mode 100644
diff --git a/examples/mappings/entities/Molecule.json b/examples/mappings/entities/Molecule.json
old mode 100755
new mode 100644
diff --git a/examples/mappings/entities/Structure.json b/examples/mappings/entities/Structure.json
old mode 100755
new mode 100644
diff --git a/examples/mappings/main.py b/examples/mappings/main.py
old mode 100755
new mode 100644
diff --git a/examples/mappings/mappingfunc.py b/examples/mappings/mappingfunc.py
old mode 100755
new mode 100644
diff --git a/examples/mappings/simple.py b/examples/mappings/simple.py
old mode 100755
new mode 100644
diff --git a/examples/minio_storage/README.md b/examples/minio_storage/README.md
old mode 100755
new mode 100644
diff --git a/examples/minio_storage/fetch.py b/examples/minio_storage/fetch.py
old mode 100755
new mode 100644
diff --git a/examples/minio_storage/main.py b/examples/minio_storage/main.py
old mode 100755
new mode 100644
diff --git a/examples/minio_storage/store.py b/examples/minio_storage/store.py
old mode 100755
new mode 100644
diff --git a/examples/read-csv/.gitignore b/examples/read-csv/.gitignore
old mode 100755
new mode 100644
diff --git a/examples/read-csv/README.md b/examples/read-csv/README.md
old mode 100755
new mode 100644
diff --git a/examples/read-csv/faithful.csv b/examples/read-csv/faithful.csv
old mode 100755
new mode 100644
diff --git a/examples/read-csv/main.py b/examples/read-csv/main.py
old mode 100755
new mode 100644
diff --git a/examples/read-csv/readcsv.py b/examples/read-csv/readcsv.py
old mode 100755
new mode 100644
diff --git a/examples/storage_plugin/.gitignore b/examples/storage_plugin/.gitignore
old mode 100755
new mode 100644
diff --git a/examples/storage_plugin/README.md b/examples/storage_plugin/README.md
old mode 100755
new mode 100644
diff --git a/examples/storage_plugin/dataset.txt b/examples/storage_plugin/dataset.txt
old mode 100755
new mode 100644
diff --git a/examples/storage_plugin/entities/TempProfile.json b/examples/storage_plugin/entities/TempProfile.json
old mode 100755
new mode 100644
diff --git a/examples/storage_plugin/main.py b/examples/storage_plugin/main.py
old mode 100755
new mode 100644
diff --git a/examples/storage_plugin/plugins/tempprofile.py b/examples/storage_plugin/plugins/tempprofile.py
old mode 100755
new mode 100644
diff --git a/examples/storages/Chemistry-0.1.json b/examples/storages/Chemistry-0.1.json
old mode 100755
new mode 100644
diff --git a/examples/storages/Person.json b/examples/storages/Person.json
old mode 100755
new mode 100644
diff --git a/examples/storages/persons.json b/examples/storages/persons.json
old mode 100755
new mode 100644
diff --git a/examples/storages/persons2.json b/examples/storages/persons2.json
old mode 100755
new mode 100644
diff --git a/python/.gitignore b/python/.gitignore
old mode 100755
new mode 100644
diff --git a/python/MANIFEST.in b/python/MANIFEST.in
old mode 100755
new mode 100644
diff --git a/python/dlite/.gitignore b/python/dlite/.gitignore
old mode 100755
new mode 100644
diff --git a/python/dlite/__init__.py b/python/dlite/__init__.py
old mode 100755
new mode 100644
diff --git a/python/pyproject.toml b/python/pyproject.toml
old mode 100755
new mode 100644
diff --git a/python/setup.py b/python/setup.py
old mode 100755
new mode 100644
diff --git a/requirements.txt b/requirements.txt
old mode 100755
new mode 100644
diff --git a/requirements_dev.txt b/requirements_dev.txt
old mode 100755
new mode 100644
diff --git a/requirements_doc.txt b/requirements_doc.txt
old mode 100755
new mode 100644
index 9be131da1..6e92cf2ca
--- a/requirements_doc.txt
+++ b/requirements_doc.txt
@@ -14,4 +14,4 @@ sphinx-copybutton~=0.5.1  # does not work well with toggleprompt
 breathe>=4.34.0
 myst-nb~=0.17.1
 nbclient>=0.2,<0.10
-docutils>=0.14,<0.21
+docutils~=0.17.1
diff --git a/src/.gitignore b/src/.gitignore
old mode 100755
new mode 100644
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
old mode 100755
new mode 100644
diff --git a/src/config-paths.h.in b/src/config-paths.h.in
old mode 100755
new mode 100644
diff --git a/src/config.h.in b/src/config.h.in
old mode 100755
new mode 100644
diff --git a/src/dlite-arrays.c b/src/dlite-arrays.c
old mode 100755
new mode 100644
diff --git a/src/dlite-arrays.h b/src/dlite-arrays.h
old mode 100755
new mode 100644
diff --git a/src/dlite-bson.c b/src/dlite-bson.c
old mode 100755
new mode 100644
diff --git a/src/dlite-bson.h b/src/dlite-bson.h
old mode 100755
new mode 100644
diff --git a/src/dlite-codegen.c b/src/dlite-codegen.c
old mode 100755
new mode 100644
diff --git a/src/dlite-codegen.h b/src/dlite-codegen.h
old mode 100755
new mode 100644
diff --git a/src/dlite-collection.c b/src/dlite-collection.c
old mode 100755
new mode 100644
index 3e678bbf6..87a0c0ca7
--- a/src/dlite-collection.c
+++ b/src/dlite-collection.c
@@ -42,7 +42,8 @@ int dlite_collection_deinit(DLiteInstance *inst)
 
   /* Release references to all instances. */
   dlite_collection_init_state(coll, &state);
-  while ((r=dlite_collection_find(coll,&state, NULL, "_has-uuid", NULL))) {
+  while ((r=dlite_collection_find(coll,&state, NULL, "_has-uuid", NULL,
+                                  NULL))) {
     if ((inst2 = dlite_instance_get(r->o))) {
       dlite_instance_decref(inst2);
     } else {
@@ -63,6 +64,9 @@ int _cmp_triple(const void *a, const void *b) {
   if ((v = strcmp(t1->s, t2->s))) return v;
   if ((v = strcmp(t1->p, t2->p))) return v;
   if ((v = strcmp(t1->o, t2->o))) return v;
+  if (t1->d && !t2->d) return 1;
+  if (!t1->d && t2->d) return -1;
+  if (t1->d && t2->d && (v = strcmp(t1->d, t2->d))) return v;
   return 0;
 }
 
@@ -73,6 +77,7 @@ void _sha3_update_triple(sha3_context *c, const Triple *t)
   sha3_Update(c, t->s, strlen(t->s));
   sha3_Update(c, t->p, strlen(t->p));
   sha3_Update(c, t->o, strlen(t->o));
+  if (t->d) sha3_Update(c, t->d, strlen(t->d));
 }
 
 /* Calculate hash of a collection. */
@@ -111,7 +116,8 @@ int dlite_collection_gethash(const DLiteInstance *inst, uint8_t *hash,
     if (strcmp(triples[i]->p, "_has-hash") == 0) continue;
     if (strcmp(triples[i]->p, "_has-uuid") == 0) {
       if ((t = triplestore_find_first(coll->rstore,
-                                      triples[i]->s, "_has-hash", NULL))) {
+                                      triples[i]->s, "_has-hash", NULL,
+                                      NULL))) {
         _sha3_update_triple(&c, t);
       } else {
         uint8_t hash[DLITE_HASH_SIZE];
@@ -166,7 +172,7 @@ int dlite_collection_loadprop(const DLiteInstance *inst, size_t i)
   /* Iterate over all newly added triples and load instances corresponding
      to "_has-uuid" relations. */
   triplestore_init_state(coll->rstore, &state);
-  while ((t = triplestore_find(&state, NULL, "_has-uuid", NULL))) {
+  while ((t = triplestore_find(&state, NULL, "_has-uuid", NULL, NULL))) {
     DLiteInstance *inst2 = dlite_instance_get(t->o);
     if (!inst2) retval = errx(dliteStorageLoadError,
                               "cannot get instance \"%s\" labeled \"%s\" "
@@ -194,6 +200,7 @@ int dlite_collection_saveprop(DLiteInstance *inst, size_t i)
   triplestore_init_state(coll->rstore, &state);
   while ((t = triplestore_next(&state))) {
     assert(j < (int)coll->nrelations);
+    triple_clean(coll->relations + j);
     triple_copy(coll->relations + j, t);
     j++;
   }
@@ -281,8 +288,10 @@ DLiteCollection *dlite_collection_load(DLiteStorage *s, const char *id,
   }
 
   dlite_collection_init_state(coll, &state);
-  while ((t = dlite_collection_find(coll, &state, NULL, "_has-uuid", NULL))) {
-    if (!(t2 = dlite_collection_find_first(coll, t->s, "_has-meta", NULL)))
+  while ((t = dlite_collection_find(coll, &state, NULL, "_has-uuid", NULL,
+                                    NULL))) {
+    if (!(t2 = dlite_collection_find_first(coll, t->s, "_has-meta", NULL,
+                                           NULL)))
       FAILCODE1(dliteInconsistentDataError,
                 "collection inconsistency - no \"_has-meta\" relation for "
                 "instance: %s", t->s);
@@ -369,13 +378,13 @@ int dlite_collection_save_url(DLiteCollection *coll, const char *url)
 }
 
 /*
-  Adds subject-predicate-object relation to collection.  Returns non-zero
-  on error.
+  Adds subject-predicate-object relation to collection.  `d` is the
+  datatype of literal object. Returns non-zero on error.
  */
 int dlite_collection_add_relation(DLiteCollection *coll, const char *s,
-                                  const char *p, const char *o)
+                                  const char *p, const char *o, const char *d)
 {
-  int stat = triplestore_add(coll->rstore, s, p, o);
+  int stat = triplestore_add(coll->rstore, s, p, o, d);
   if (!stat)
     DLITE_PROP_DIM(coll, 0, 0) = coll->nrelations;
   return stat;
@@ -387,9 +396,10 @@ int dlite_collection_add_relation(DLiteCollection *coll, const char *s,
   multiple matches.  Returns the number of relations removed, or -1 on error.
  */
 int dlite_collection_remove_relations(DLiteCollection *coll, const char *s,
-                                      const char *p, const char *o)
+                                      const char *p, const char *o,
+                                      const char *d)
 {
-  int retval = triplestore_remove(coll->rstore, s, p, o);
+  int retval = triplestore_remove(coll->rstore, s, p, o, d);
   if (retval > -1)
     DLITE_PROP_DIM(coll, 0, 0) = coll->nrelations;
   return retval;
@@ -417,6 +427,15 @@ void dlite_collection_deinit_state(DLiteCollectionState *state)
 }
 
 
+/*
+  Resets `state` already initialised with dlite_collection_init_state().
+*/
+void dlite_collection_reset_state(DLiteCollectionState *state)
+{
+  triplestore_reset_state((TripleState *)state);
+}
+
+
 /*
   Finds matching relations.
 
@@ -436,12 +455,12 @@ void dlite_collection_deinit_state(DLiteCollectionState *state)
 const DLiteRelation *dlite_collection_find(const DLiteCollection *coll,
                                            DLiteCollectionState *state,
                                            const char *s, const char *p,
-                                           const char *o)
+                                           const char *o, const char *d)
 {
   if (state)
-    return (DLiteRelation *)triplestore_find((TripleState *)state, s, p, o);
+    return (DLiteRelation *)triplestore_find((TripleState *)state, s, p, o, d);
   else
-    return (DLiteRelation *)triplestore_find_first(coll->rstore, s, p, o);
+    return (DLiteRelation *)triplestore_find_first(coll->rstore, s, p, o, d);
 }
 
 /*
@@ -450,14 +469,9 @@ const DLiteRelation *dlite_collection_find(const DLiteCollection *coll,
  */
 const DLiteRelation *dlite_collection_find_first(const DLiteCollection *coll,
                                                  const char *s, const char *p,
-                                                 const char *o)
+                                                 const char *o, const char *d)
 {
-  DLiteCollectionState state;
-  const DLiteRelation *r;
-  dlite_collection_init_state(coll, &state);
-  r = dlite_collection_find(coll, &state, s, p, o);
-  dlite_collection_deinit_state(&state);
-  return r;
+  return dlite_collection_find(coll, NULL, s, p, o, d);
 }
 
 /*
@@ -469,14 +483,15 @@ const DLiteRelation *dlite_collection_find_first(const DLiteCollection *coll,
 int dlite_collection_add_new(DLiteCollection *coll, const char *label,
                              DLiteInstance *inst)
 {
-  if (dlite_collection_find(coll, NULL, label, "_is-a", "Instance"))
+  if (dlite_collection_find(coll, NULL, label, "_is-a", "Instance", NULL))
     return errx(dliteValueError,
                 "instance with label '%s' is already in the collection",
                 label);
 
-  dlite_collection_add_relation(coll, label, "_is-a", "Instance");
-  dlite_collection_add_relation(coll, label, "_has-uuid", inst->uuid);
-  dlite_collection_add_relation(coll, label, "_has-meta", inst->meta->uri);
+  dlite_collection_add_relation(coll, label, "_is-a", "Instance", NULL);
+  dlite_collection_add_relation(coll, label, "_has-uuid", inst->uuid,
+                                "xsd:anyURI");
+  dlite_collection_add_relation(coll, label, "_has-meta", inst->meta->uri, NULL);
   return 0;
 }
 
@@ -502,8 +517,8 @@ int dlite_collection_remove(DLiteCollection *coll, const char *label)
   DLiteCollectionState state;
   DLiteInstance *inst;
   const DLiteRelation *r;
-  if (dlite_collection_remove_relations(coll, label, "_is-a", "Instance") > 0) {
-    r = dlite_collection_find(coll, NULL, label, "_has-uuid", NULL);
+  if (dlite_collection_remove_relations(coll, label, "_is-a", "Instance", NULL) > 0) {
+    r = dlite_collection_find(coll, NULL, label, "_has-uuid", NULL, NULL);
     assert(r);
 
     /* Removes reference hold by collection to the instance. We have
@@ -523,9 +538,9 @@ int dlite_collection_remove(DLiteCollection *coll, const char *label)
     //*dlite_collection_deinit_state(&state);
     UNUSED(state);
 
-    dlite_collection_remove_relations(coll, label, "_has-uuid", NULL);
-    dlite_collection_remove_relations(coll, label, "_has-meta", NULL);
-    dlite_collection_remove_relations(coll, label, "_has-dimmap", NULL);
+    dlite_collection_remove_relations(coll, label, "_has-uuid", NULL, NULL);
+    dlite_collection_remove_relations(coll, label, "_has-meta", NULL, NULL);
+    dlite_collection_remove_relations(coll, label, "_has-dimmap", NULL, NULL);
     return 0;
   }
   return 1;
@@ -539,7 +554,8 @@ const DLiteInstance *dlite_collection_get(const DLiteCollection *coll,
                                           const char *label)
 {
   const DLiteRelation *r;
-  if ((r = dlite_collection_find(coll, NULL, label, "_has-uuid", NULL))) {
+  if ((r = dlite_collection_find(coll, NULL, label, "_has-uuid", NULL,
+                                 NULL))) {
     DLiteInstance *inst = dlite_instance_get(r->o);
     if (!inst) FAILCODE2(dliteKeyError,
                          "no such instance '%s' in collection '%s'",
@@ -566,7 +582,7 @@ const DLiteInstance *dlite_collection_get_id(const DLiteCollection *coll,
   const DLiteRelation *r;
   char uuid[DLITE_UUID_LENGTH+1];
   if (dlite_get_uuid(uuid, id) < 0) return NULL;
-  if ((r = dlite_collection_find(coll, NULL, NULL, "_has-uuid", uuid)))
+  if ((r = dlite_collection_find(coll, NULL, NULL, "_has-uuid", uuid, NULL)))
     return dlite_instance_get(id);
   return NULL;
 }
@@ -598,7 +614,8 @@ DLiteInstance *dlite_collection_get_new(const DLiteCollection *coll,
  */
 int dlite_collection_has(const DLiteCollection *coll, const char *label)
 {
-  return (dlite_collection_find_first(coll, label, "_has-uuid", NULL)) ? 1 : 0;
+  return (dlite_collection_find_first(coll, label, "_has-uuid", NULL,
+                                      NULL)) ? 1 : 0;
 }
 
 /*
@@ -609,7 +626,8 @@ int dlite_collection_has_id(const DLiteCollection *coll, const char *id)
 {
   char uuid[DLITE_UUID_LENGTH+1];
   if (dlite_get_uuid(uuid, id) < 0) return 0;
-  return (dlite_collection_find_first(coll, NULL, "_has-uuid", uuid)) ? 1 : 0;
+  return (dlite_collection_find_first(coll, NULL, "_has-uuid", uuid,
+                                      NULL)) ? 1 : 0;
 }
 
 
@@ -641,7 +659,7 @@ DLiteInstance *dlite_collection_next_new(DLiteCollection *coll,
 {
   UNUSED(coll);
   const Triple *t;
-  while ((t = triplestore_find(state, NULL, "_has-uuid", NULL)))
+  while ((t = triplestore_find(state, NULL, "_has-uuid", NULL, NULL)))
     return dlite_instance_get(t->o);
   return NULL;
 }
@@ -656,7 +674,31 @@ int dlite_collection_count(DLiteCollection *coll)
   int count=0;
   TripleState state;
   triplestore_init_state(coll->rstore, &state);
-  while (triplestore_find(&state, NULL, "_is-a", "Instance")) count++;
+  while (triplestore_find(&state, NULL, "_is-a", "Instance", NULL)) count++;
   triplestore_deinit_state(&state);
   return count;
 }
+
+
+/*
+  Return pointer to the value for a pair of two criteria.
+
+  Useful if one knows that there may only be one value.  The returned
+  value is held by the collection and should be copied by the user
+  since it may be overwritten by later calls to the collection.
+
+  Parameters:
+      s, p, o: Criteria to match. Two of these must be non-NULL.
+      d: If not NULL, the required datatype of literal objects.
+      fallback: Value to return if no matches are found.
+      any: If non-zero, return first matching value.
+
+  Returns a pointer to the value of the `s`, `p` or `o` that is NULL.
+  On error NULL is returned.
+ */
+const char *dlite_collection_value(DLiteCollection *coll, const char *s,
+                                   const char *p, const char *o, const char *d,
+                                   const char *fallback, int any)
+{
+  return triplestore_value(coll->rstore, s, p, o, d, fallback, any);
+}
diff --git a/src/dlite-collection.h b/src/dlite-collection.h
old mode 100755
new mode 100644
index 077b7e0d6..49c802c2d
--- a/src/dlite-collection.h
+++ b/src/dlite-collection.h
@@ -78,6 +78,11 @@ int dlite_collection_init(DLiteInstance *inst);
  */
 int dlite_collection_deinit(DLiteInstance *inst);
 
+/**
+  Resets `state` already initialised with dlite_collection_init_state().
+*/
+void dlite_collection_reset_state(DLiteCollectionState *state);
+
 /**
   Calculate hash of a collection.
 
@@ -168,11 +173,12 @@ int dlite_collection_save_url(DLiteCollection *coll, const char *url);
 
 
 /**
-  Adds subject-predicate-object relation to collection.  Returns non-zero
-  on error.
+  Adds subject-predicate-object relation to collection.  `d` is the
+  datatype of literal object. Returns non-zero on error.
  */
 int dlite_collection_add_relation(DLiteCollection *coll, const char *s,
-                                  const char *p, const char *o);
+                                  const char *p, const char *o,
+                                  const char *d);
 
 
 /**
@@ -180,7 +186,8 @@ int dlite_collection_add_relation(DLiteCollection *coll, const char *s,
   multiple matches.  Returns the number of relations removed, or -1 on error.
  */
 int dlite_collection_remove_relations(DLiteCollection *coll, const char *s,
-                                      const char *p, const char *o);
+                                      const char *p, const char *o,
+                                      const char *d);
 
 
 /**
@@ -215,7 +222,7 @@ void dlite_collection_deinit_state(DLiteCollectionState *state);
 const DLiteRelation *dlite_collection_find(const DLiteCollection *coll,
 					   DLiteCollectionState *state,
 					   const char *s, const char *p,
-					   const char *o);
+					   const char *o, const char *d);
 
 /**
   Like dlite_collection_find(), but returns only a pointer to the
@@ -223,7 +230,7 @@ const DLiteRelation *dlite_collection_find(const DLiteCollection *coll,
  */
 const DLiteRelation *dlite_collection_find_first(const DLiteCollection *coll,
                                                  const char *s, const char *p,
-                                                 const char *o);
+                                                 const char *o, const char *d);
 
 
 /**
@@ -309,4 +316,24 @@ DLiteInstance *dlite_collection_next_new(DLiteCollection *coll,
 int dlite_collection_count(DLiteCollection *coll);
 
 
+/**
+  Return pointer to the value for a pair of two criteria.
+
+  Useful if one knows that there may only be one value.  The returned
+  value is held by the collection and should be copied by the user
+  since it may be overwritten by later calls to the collection.
+
+  Parameters:
+      s, p, o: Criteria to match. Two of these must be non-NULL.
+      d: If not NULL, the required datatype of literal objects.
+      fallback: Value to return if no matches are found.
+      any: If non-zero, return first matching value.
+
+  Returns a pointer to the value of the `s`, `p` or `o` that is NULL.
+  On error NULL is returned.
+ */
+const char *dlite_collection_value(DLiteCollection *coll, const char *s,
+                                   const char *p, const char *o, const char *d,
+                                   const char *fallback, int any);
+
 #endif /* _DLITE_COLLECTION_H */
diff --git a/src/dlite-datamodel.c b/src/dlite-datamodel.c
old mode 100755
new mode 100644
diff --git a/src/dlite-datamodel.h b/src/dlite-datamodel.h
old mode 100755
new mode 100644
diff --git a/src/dlite-entity.c b/src/dlite-entity.c
old mode 100755
new mode 100644
index 088f21fb2..7d941437a
--- a/src/dlite-entity.c
+++ b/src/dlite-entity.c
@@ -169,7 +169,8 @@ static DLiteInstance *_instance_store_get(const char *id)
   char uuid[DLITE_UUID_LENGTH+1];
   DLiteInstance **instp;
   if ((uuidver = dlite_get_uuid(uuid, id)) != 0 && uuidver != 5)
-    return errx(dliteValueError, "id '%s' is neither a valid UUID or a convertible string",
+    return errx(dliteValueError,
+                "id '%s' is neither a valid UUID or a convertable string",
                 id), NULL;
   if (!(instp = map_get(istore, uuid))) return NULL;
   return *instp;
@@ -709,8 +710,10 @@ DLiteInstance *dlite_instance_get(const char *id)
     DLiteStorage *s;
     char *copy, *driver, *location, *options;
 
-    if (!(copy = strdup(url)))
-     return err(dliteMemoryError, "allocation failure"), NULL;
+    if (!(copy = strdup(url))) {
+      err(dliteMemoryError, "allocation failure");
+      break;
+    }
 #ifdef _WIN32
     /* Hack: on Window, don't interpreat the "C" in urls starting with
        "C:\" or "C:/" as a driver, but rather as a part of the location...
diff --git a/src/dlite-entity.h b/src/dlite-entity.h
old mode 100755
new mode 100644
diff --git a/src/dlite-errors.c b/src/dlite-errors.c
old mode 100755
new mode 100644
index 00a970d1f..bcb6bdc1f
--- a/src/dlite-errors.c
+++ b/src/dlite-errors.c
@@ -28,6 +28,7 @@ const char *dlite_errname(DLiteErrCode code)
 
   case dliteOSError:                return "DLiteOS";
   case dliteKeyError:               return "DLiteKey";
+  case dliteSearchError:            return "DLiteSearch";
   case dliteParseError:             return "DLiteParse";
   case dliteSerialiseError:         return "DLiteSerialise";
   case dliteUnsupportedError:       return "DLiteUnsupported";
@@ -73,6 +74,7 @@ const char *dlite_errdescr(DLiteErrCode code)
 
   case dliteOSError:                return "Error calling a system function";
   case dliteKeyError:               return "Mapping key is not found";
+  case dliteSearchError:            return "Error in search";
   case dliteParseError:             return "Cannot parse input";
   case dliteSerialiseError:         return "Cannot serialise output";
   case dliteUnsupportedError:       return "Feature is not implemented/supported";
diff --git a/src/dlite-errors.h b/src/dlite-errors.h
old mode 100755
new mode 100644
index 773013c1b..d8a0f57b0
--- a/src/dlite-errors.h
+++ b/src/dlite-errors.h
@@ -23,23 +23,24 @@ typedef enum {
   /* Additional DLite-specific errors */
   dliteOSError=-14,              /*!< Error calling a system function */
   dliteKeyError=-15,             /*!< Mapping key not found */
-  dliteParseError=-16,           /*!< Cannot parse input */
-  dliteSerialiseError=-17,       /*!< Cannot serialise output */
-  dliteUnsupportedError=-18,     /*!< Feature is not implemented/supported  */
-  dliteVerifyError=-19,          /*!< Object cannot be verified */
-  dliteInconsistentDataError=-20,/*!< Inconsistent data */
-  dliteStorageOpenError=-21,     /*!< Cannot open storage plugin */
-  dliteStorageLoadError=-22,     /*!< Cannot load storage plugin */
-  dliteStorageSaveError=-23,     /*!< Cannot save storage plugin */
-  dliteOptionError=-24,          /*!< Invalid storage plugin option */
-  dliteMissingInstanceError=-25, /*!< No instance with given id can be found */
-  dliteMissingMetadataError=-26, /*!< No metadata with given id can be found */
-  dliteMetadataExistError=-27,   /*!< Metadata with given id already exists */
-  dliteMappingError=-28,         /*!< Error in instance mappings */
-  dlitePythonError=-29,          /*!< Error calling Python API */
+  dliteSearchError=-16,          /*!< Error in search */
+  dliteParseError=-17,           /*!< Cannot parse input */
+  dliteSerialiseError=-18,       /*!< Cannot serialise output */
+  dliteUnsupportedError=-19,     /*!< Feature is not implemented/supported  */
+  dliteVerifyError=-20,          /*!< Object cannot be verified */
+  dliteInconsistentDataError=-21,/*!< Inconsistent data */
+  dliteStorageOpenError=-22,     /*!< Cannot open storage plugin */
+  dliteStorageLoadError=-23,     /*!< Cannot load storage plugin */
+  dliteStorageSaveError=-24,     /*!< Cannot save storage plugin */
+  dliteOptionError=-25,          /*!< Invalid storage plugin option */
+  dliteMissingInstanceError=-26, /*!< No instance with given id can be found */
+  dliteMissingMetadataError=-27, /*!< No metadata with given id can be found */
+  dliteMetadataExistError=-28,   /*!< Metadata with given id already exists */
+  dliteMappingError=-29,         /*!< Error in instance mappings */
+  dlitePythonError=-30,          /*!< Error calling Python API */
 
   /* Should always be the last error */
-  dliteLastError=-30
+  dliteLastError=-31
 } DLiteErrCode;
 
 
diff --git a/src/dlite-getlicense.h b/src/dlite-getlicense.h
old mode 100755
new mode 100644
diff --git a/src/dlite-json.c b/src/dlite-json.c
old mode 100755
new mode 100644
index 3fd48f080..554b9fcfb
--- a/src/dlite-json.c
+++ b/src/dlite-json.c
@@ -612,8 +612,9 @@ static DLiteInstance *parse_instance(const char *src, jsmntok_t *obj,
     if ((t = jsmn_item(src, obj, "properties"))) dims[n++] = t->size;
     if ((t = jsmn_item(src, obj, "relations")))  dims[n++] = t->size;
     if (n != meta->_ndimensions)
-      FAIL1("metadata does not confirm to schema, please check dimensions, "
-            "properties and/or relations: %s", id);
+      FAILCODE1(dliteParseError, "metadata does not confirm to schema, "
+                "please check dimensions, properties and/or relations: %s",
+                id);
 
   } else {
     if (meta->_ndimensions > 0) {
diff --git a/src/dlite-json.h b/src/dlite-json.h
old mode 100755
new mode 100644
diff --git a/src/dlite-macros.h b/src/dlite-macros.h
old mode 100755
new mode 100644
diff --git a/src/dlite-mapping-plugins.c b/src/dlite-mapping-plugins.c
old mode 100755
new mode 100644
diff --git a/src/dlite-mapping-plugins.h b/src/dlite-mapping-plugins.h
old mode 100755
new mode 100644
diff --git a/src/dlite-mapping.c b/src/dlite-mapping.c
old mode 100755
new mode 100644
diff --git a/src/dlite-mapping.h b/src/dlite-mapping.h
old mode 100755
new mode 100644
diff --git a/src/dlite-misc.c b/src/dlite-misc.c
old mode 100755
new mode 100644
index 919e022f1..ffb67a389
--- a/src/dlite-misc.c
+++ b/src/dlite-misc.c
@@ -460,44 +460,44 @@ int dlite_add_dll_path(void)
 
 #define ERR_STATE_ID "err-globals-id"
 #define ERR_MASK_ID "err-ignored-id"
-#define LOCALS_ID "dlite-misc-locals-id"
 
 
-/* A cache pointing to the current session handler */
-static DLiteGlobals *_globals_handler=NULL;
-
 /* Local state for this module. */
 typedef struct {
   int in_atexit;
 } Locals;
 
 
+/* A cache pointing to the current session handler */
+static DLiteGlobals *_globals_handler=NULL;
+
+/* Pointer to local state */
+Locals *_locals=NULL;
+
 /* Free variables in local state. */
-static void free_locals(void *locals)
+static void free_locals()
 {
-  UNUSED(locals);
+  _locals = NULL;
 }
 
 /* Return a pointer to local state. */
 static Locals *get_locals(void)
 {
-  static Locals *locals=NULL;
-  if (!locals) {
-    locals = dlite_globals_get_state(LOCALS_ID);
-    if (!locals) {
-      static Locals _locals;
-      locals = &_locals;
-      memset(locals, 0, sizeof(Locals));
-      dlite_globals_add_state(LOCALS_ID, locals, free_locals);
-    }
+  if (!_locals) {
+    static Locals locals;
+    _locals = &locals;
+    memset(_locals, 0, sizeof(Locals));
   }
-  return locals;
+  return _locals;
 }
 
 
 /* Called by atexit(). */
 static void _handle_atexit(void) {
 
+  /* No extra finalisation is needed if we already are in an atexit handler */
+  if (dlite_globals_in_atexit()) return;
+
   /* Mark that we are in an atexit handler */
   dlite_globals_set_atexit();
 
@@ -512,11 +512,12 @@ DLiteGlobals *dlite_globals_get(void)
   if (!_globals_handler) {
     _globals_handler = session_get_default();
 
+    dlite_init();
+
     /* Make valgrind and other memory leak detectors happy by freeing
        up all globals at exit. */
-    atexit(_handle_atexit);
-
-    dlite_init();
+    if (!dlite_globals_in_atexit())
+      atexit(_handle_atexit);
   }
   return _globals_handler;
 }
@@ -583,13 +584,16 @@ void dlite_finalize(void)
   Session *s = session_get_default();
 
   /* Don't free anything if we are in an atexit handler */
-  if (dlite_globals_in_atexit() || !getenv("DLITE_ATEXIT_FREE")) return;
+  if (dlite_globals_in_atexit() && !getenv("DLITE_ATEXIT_FREE")) return;
 
   /* Reset error handling */
   err_set_handler(NULL);
   err_set_nameconv(NULL);
 
   session_free(s);
+  _globals_handler = NULL;
+
+  free_locals();
 }
 
 
@@ -693,6 +697,7 @@ int dlite_err_ignored_get(DLiteErrCode code)
 {
   DLiteErrMask *mask = _dlite_err_mask_get();
   if (!mask) return 0;
+  if (code > 0 && (*mask & DLITE_ERRBIT(dliteUnknownError))) return 1;
   return *mask & DLITE_ERRBIT(code);
 }
 
diff --git a/src/dlite-misc.h b/src/dlite-misc.h
old mode 100755
new mode 100644
diff --git a/src/dlite-schemas.c b/src/dlite-schemas.c
old mode 100755
new mode 100644
index cdd2c64a9..26b6c615a
--- a/src/dlite-schemas.c
+++ b/src/dlite-schemas.c
@@ -451,7 +451,7 @@ const DLiteMeta *dlite_get_entity_schema()
 const DLiteMeta *dlite_get_collection_entity()
 {
   dlite_get_uuid(collection_entity.uuid, DLITE_COLLECTION_ENTITY);
-  if (!collection_entity._headersize)
+  if (!collection_entity._npropdims)
     dlite_meta_init((DLiteMeta *)&collection_entity);
   return (DLiteMeta *)&collection_entity;
 }
diff --git a/src/dlite-schemas.h b/src/dlite-schemas.h
old mode 100755
new mode 100644
diff --git a/src/dlite-storage-plugins.c b/src/dlite-storage-plugins.c
old mode 100755
new mode 100644
diff --git a/src/dlite-storage-plugins.h b/src/dlite-storage-plugins.h
old mode 100755
new mode 100644
diff --git a/src/dlite-storage.c b/src/dlite-storage.c
old mode 100755
new mode 100644
diff --git a/src/dlite-storage.h b/src/dlite-storage.h
old mode 100755
new mode 100644
diff --git a/src/dlite-store.c b/src/dlite-store.c
old mode 100755
new mode 100644
diff --git a/src/dlite-store.h b/src/dlite-store.h
old mode 100755
new mode 100644
diff --git a/src/dlite-type-cast.c b/src/dlite-type-cast.c
old mode 100755
new mode 100644
diff --git a/src/dlite-type-cast.h b/src/dlite-type-cast.h
old mode 100755
new mode 100644
diff --git a/src/dlite-type.c b/src/dlite-type.c
old mode 100755
new mode 100644
index 4fb8b2831..9a5179011
--- a/src/dlite-type.c
+++ b/src/dlite-type.c
@@ -670,6 +670,7 @@ void *dlite_type_clear(void *p, DLiteType dtype, size_t size)
     if (((DLiteRelation *)p)->s)  free(((DLiteRelation *)p)->s);
     if (((DLiteRelation *)p)->p)  free(((DLiteRelation *)p)->p);
     if (((DLiteRelation *)p)->o)  free(((DLiteRelation *)p)->o);
+    if (((DLiteRelation *)p)->d)  free(((DLiteRelation *)p)->d);
     if (((DLiteRelation *)p)->id) free(((DLiteRelation *)p)->id);
     break;
   }
@@ -868,6 +869,10 @@ int dlite_type_print(char *dest, size_t n, const void *p, DLiteType dtype,
       m += strquote(dest+m, PDIFF(n, m), r->p);
       m += snprintf(dest+m, PDIFF(n, m), ", ");
       m += strquote(dest+m, PDIFF(n, m), r->o);
+      if (r->d) {
+        m += snprintf(dest+m, PDIFF(n, m), ", ");
+        m += strquote(dest+m, PDIFF(n, m), r->d);
+      }
       m += snprintf(dest+m, PDIFF(n, m), "]");
     }
     break;
@@ -920,7 +925,7 @@ int dlite_type_aprint(char **dest, size_t *n, size_t pos, const void *p,
 /* Maximum number of jsmn tokens in a dimension, property and relation */
 #define MAX_DIMENSION_TOKENS  5
 #define MAX_PROPERTY_TOKENS  64  // this supports at least 50 dimensions...
-#define MAX_RELATION_TOKENS   9
+#define MAX_RELATION_TOKENS  11
 
 /* Macro used by dlite_type_scan() to asign `target` when scanning a
    relation. */
@@ -928,7 +933,7 @@ int dlite_type_aprint(char **dest, size_t *n, size_t pos, const void *p,
   if (strnput_unquote(&buf, &bufsize, 0, src + t->start,                \
                       t->end - t->start, NULL, strquoteNoQuote) < 0)    \
     return -1;                                                          \
-  target = strndup(buf, t->end - t->start);
+  target = (buf[0]) ? strndup(buf, t->end - t->start) : NULL;
 
 
 /*
@@ -1209,8 +1214,8 @@ int dlite_type_scan(const char *src, int len, void *p, DLiteType dtype,
       if ((r = jsmn_parse(&parser, src, len, tokens, MAX_RELATION_TOKENS)) < 0)
         return err(dliteParseError, "cannot parse relation: %s: '%s'",
                    jsmn_strerror(r), src);
-      if (tokens->size < 3 || tokens->size > 4)
-        return errx(dliteParseError, "relation should have 3 (optionally 4) elements");
+      if (tokens->size < 3 || tokens->size > 5)
+        return errx(dliteParseError, "relation should have 3 (optionally 5) elements");
       m = tokens->end - tokens->start;
       if (tokens->type == JSMN_ARRAY) {
         size_t bufsize=0;
@@ -1221,8 +1226,13 @@ int dlite_type_scan(const char *src, int len, void *p, DLiteType dtype,
         SET_RELATION(rel->p, buf, bufsize, t, src);
         if (!(t = jsmn_element(src, tokens, 2))) return -1;
         SET_RELATION(rel->o, buf, bufsize, t, src);
-        if (tokens->size > 3 && (t = jsmn_element(src, tokens, 3)))
-          rel->id = strndup(src + t->start, t->end - t->start);
+        if (tokens->size > 3 && (t = jsmn_element(src, tokens, 3))) {
+          SET_RELATION(rel->d, buf, bufsize, t, src);
+        }
+        if (tokens->size > 4 && (t = jsmn_element(src, tokens, 4))) {
+          rel->id = (t->end > t->start) ?
+            strndup(src + t->start, t->end - t->start) : NULL;
+        }
         free(buf);
       } else if (tokens->type == JSMN_OBJECT) {
         size_t bufsize=0;
@@ -1233,8 +1243,12 @@ int dlite_type_scan(const char *src, int len, void *p, DLiteType dtype,
         SET_RELATION(rel->p, buf, bufsize, t, src);
         if (!(t = jsmn_item(src, tokens, "o"))) return -1;
         SET_RELATION(rel->o, buf, bufsize, t, src);
+        if ((t = jsmn_item(src, tokens, "d"))) {
+          SET_RELATION(rel->d, buf, bufsize, t, src);
+        }
         if ((t = jsmn_item(src, tokens, "id")))
-          rel->id = strndup(src + t->start, t->end - t->start);
+          rel->id = (t->end > t->start) ?
+            strndup(src + t->start, t->end - t->start) : NULL;
         free(buf);
       } else {
         return errx(dliteValueError, "relation should be a JSON array");
diff --git a/src/dlite-type.h b/src/dlite-type.h
old mode 100755
new mode 100644
diff --git a/src/dlite.h b/src/dlite.h
old mode 100755
new mode 100644
diff --git a/src/getuuid.c b/src/getuuid.c
old mode 100755
new mode 100644
diff --git a/src/getuuid.h b/src/getuuid.h
old mode 100755
new mode 100644
diff --git a/src/pathshash.c b/src/pathshash.c
old mode 100755
new mode 100644
diff --git a/src/pathshash.h b/src/pathshash.h
old mode 100755
new mode 100644
diff --git a/src/pyembed/.gitignore b/src/pyembed/.gitignore
old mode 100755
new mode 100644
diff --git a/src/pyembed/CMakeLists.txt b/src/pyembed/CMakeLists.txt
old mode 100755
new mode 100644
diff --git a/src/pyembed/README.md b/src/pyembed/README.md
old mode 100755
new mode 100644
diff --git a/src/pyembed/dlite-pyembed.c b/src/pyembed/dlite-pyembed.c
old mode 100755
new mode 100644
diff --git a/src/pyembed/dlite-pyembed.h b/src/pyembed/dlite-pyembed.h
old mode 100755
new mode 100644
diff --git a/src/pyembed/dlite-python-mapping.c b/src/pyembed/dlite-python-mapping.c
old mode 100755
new mode 100644
diff --git a/src/pyembed/dlite-python-mapping.h b/src/pyembed/dlite-python-mapping.h
old mode 100755
new mode 100644
diff --git a/src/pyembed/dlite-python-singletons.c b/src/pyembed/dlite-python-singletons.c
old mode 100755
new mode 100644
diff --git a/src/pyembed/dlite-python-singletons.h b/src/pyembed/dlite-python-singletons.h
old mode 100755
new mode 100644
diff --git a/src/pyembed/dlite-python-storage.c b/src/pyembed/dlite-python-storage.c
old mode 100755
new mode 100644
diff --git a/src/pyembed/dlite-python-storage.h b/src/pyembed/dlite-python-storage.h
old mode 100755
new mode 100644
diff --git a/src/triple.c b/src/triple.c
old mode 100755
new mode 100644
index f2e3c64ef..691470794
--- a/src/triple.c
+++ b/src/triple.c
@@ -58,9 +58,10 @@ const char *triple_get_default_namespace(void)
 */
 void triple_clean(Triple *t)
 {
-  free(t->s);
-  free(t->p);
-  free(t->o);
+  if (t->s) free(t->s);
+  if (t->p) free(t->p);
+  if (t->o) free(t->o);
+  if (t->d) free(t->d);
   if (t->id) free(t->id);
   memset(t, 0, sizeof(Triple));
 }
@@ -71,12 +72,13 @@ void triple_clean(Triple *t)
   NULL, a new id will be generated bases on `s`, `p` and `o`.
  */
 int triple_set(Triple *t, const char *s, const char *p, const char *o,
-                const char *id)
+               const char *d, const char *id)
 {
   t->s = strdup((s) ? s : "");
   t->p = strdup((p) ? p : "");
   t->o = strdup((o) ? o : "");
-  t->id = (id) ? strdup(id) : triple_get_id(NULL, s, p, o);
+  t->d = (d) ? strdup(d) : NULL;
+  t->id = (id) ? strdup(id) : triple_get_id(NULL, s, p, o, d);
   return 0;
 }
 
@@ -85,13 +87,10 @@ int triple_set(Triple *t, const char *s, const char *p, const char *o,
   it.  Don't use this function if `t` has not been initiated.
  */
 int triple_reset(Triple *t, const char *s, const char *p, const char *o,
-                const char *id)
+                 const char *d, const char *id)
 {
-  if (t->s)  free(t->s);
-  if (t->p)  free(t->p);
-  if (t->o)  free(t->o);
-  if (t->id) free(t->id);
-  return triple_set(t, s, p, o, id);
+  triple_clean(t);
+  return triple_set(t, s, p, o, d, id);
 }
 
 /*
@@ -99,7 +98,7 @@ int triple_reset(Triple *t, const char *s, const char *p, const char *o,
   Returns NULL on error, for instance if any of `s`, `p` or `o` are NULL.
 */
 char *triple_get_id(const char *namespace, const char *s, const char *p,
-                      const char *o)
+                    const char *o, const char *d)
 {
   SHA1_CTX context;
   unsigned char digest[20];
@@ -111,6 +110,7 @@ char *triple_get_id(const char *namespace, const char *s, const char *p,
   SHA1Update(&context, (unsigned char *)s, strlen(s));
   SHA1Update(&context, (unsigned char *)p, strlen(p));
   SHA1Update(&context, (unsigned char *)o, strlen(o));
+  if (d) SHA1Update(&context, (unsigned char *)d, strlen(d));
   SHA1Final(digest, &context);
   if (!namespace) namespace = triple_get_default_namespace();
   if (namespace) size += strlen(namespace);
@@ -123,19 +123,20 @@ char *triple_get_id(const char *namespace, const char *s, const char *p,
 
 /*
   Copies triple `src` to `dest` and returns a pointer to `dest`.
+
+  Existing memory hold by `dest` is not free'ed.  So if `dest` may
+  hold some memory, call `triple_clean()` before calling this
+  function.
  */
 Triple *triple_copy(Triple *dest, const Triple *src)
 {
   assert(src);
   assert(dest);
-  if (dest->s) free(dest->s);
-  if (dest->p) free(dest->p);
-  if (dest->o) free(dest->o);
-  if (dest->id) free(dest->id);
   memset(dest, 0, sizeof(Triple));
   if (src->s  && !(dest->s = strdup(src->s)))   goto fail;
   if (src->p  && !(dest->p = strdup(src->p)))   goto fail;
   if (src->o  && !(dest->o = strdup(src->o)))   goto fail;
+  if (src->d  && !(dest->d = strdup(src->d)))   goto fail;
   if (src->id && !(dest->id = strdup(src->id))) goto fail;
   return dest;
  fail:
diff --git a/src/triple.h b/src/triple.h
old mode 100755
new mode 100644
index 77fcede54..24809cd10
--- a/src/triple.h
+++ b/src/triple.h
@@ -21,6 +21,7 @@ typedef struct _Triple {
   char *s;     /*!< subject */
   char *p;     /*!< predicate */
   char *o;     /*!< object */
+  char *d;     /*!< datatype */
   char *id;    /*!< unique ID identifying this triple */
 } Triple;
 
@@ -46,16 +47,17 @@ void triple_clean(Triple *t);
   Convinient function to assign a triple. This allocates new memory
   for the internal s, p, o and id pointers.  If `id` is
   NULL, a new id will be generated bases on `s`, `p` and `o`.
+  For literals, the datatype can be provided with `d`.
  */
 int triple_set(Triple *t, const char *s, const char *p, const char *o,
-                const char *id);
+               const char *d, const char *id);
 
 /**
   Like triple_set(), but free's allocated memory in `t` before re-assigning
   it.  Don't use this function if `t` has not been initiated.
  */
 int triple_reset(Triple *t, const char *s, const char *p, const char *o,
-                  const char *id);
+                 const char *d, const char *id);
 
 /**
   Returns an newly malloc'ed unique id calculated from triple.
@@ -66,11 +68,15 @@ int triple_reset(Triple *t, const char *s, const char *p, const char *o,
   Returns NULL on error.
 */
 char *triple_get_id(const char *namespace, const char *s, const char *p,
-                      const char *o);
+                    const char *o, const char *d);
 
 /**
   Copies triple `src` to `dest` and returns a pointer to `dest`.
- */
+
+  Existing memory hold by `dest` is not free'ed.  So if `dest` may
+  hold some memory, call `triple_clean()` before calling this
+  function.
+*/
 Triple *triple_copy(Triple *dest, const Triple *src);
 
 
diff --git a/src/triplestore-builtin.c b/src/triplestore-builtin.c
old mode 100755
new mode 100644
index ea80d2255..455be0baf
--- a/src/triplestore-builtin.c
+++ b/src/triplestore-builtin.c
@@ -34,8 +34,8 @@
 
 
 
-/* Allocate triplestore memory in chunks of TRIPLESTORE_BUFFSIZE */
-#define TRIPLESTORE_BUFFSIZE 1024
+/* Allocate triplestore memory in chunks of TRIPLESTORE_CHUNKSIZE */
+#define TRIPLESTORE_CHUNKSIZE 1024
 
 
 /* Prototype for cleanup-function */
@@ -43,7 +43,7 @@ typedef void (*Freer)(void *ptr);
 
 /* Triple store. */
 struct _TripleStore {
-  Triple *triples;  /*!< array of triples */
+  Triple *triples;    /*!< array of triples */
   size_t length;      /*!< logically number of triples (excluding pending
                            removes */
   size_t true_length; /*!< number of triples (including pending removes) */
@@ -54,6 +54,7 @@ struct _TripleStore {
   size_t niter;       /*!< counter for number of running iterators */
   int freed;          /*!< set to non-zero when this store is supposed to
                            be freed, but kept alive due to existing iterators */
+  const char *ns;     /*!< Namespace */
 };
 
 
@@ -104,28 +105,17 @@ size_t triplestore_length(TripleStore *ts)
 }
 
 
-///* Compare triples (in s-o-p order) */
-//static int compar(const void *p1, const void *p2)
-//{
-//  int v;
-//  Triple *t1 = (Triple *)p1;
-//  Triple *t2 = (Triple *)p2;
-//  if ((v = strcmp(t1->s, t2->s))) return v;
-//  if ((v = strcmp(t1->o, t2->o))) return v;
-//  return strcmp(t1->p, t2->p);
-//}
-
-
 /*
   Adds a single triple to store.  Returns non-zero on error.
  */
 int triplestore_add(TripleStore *ts, const char *s, const char *p,
-                    const char *o)
+                    const char *o, const char *d)
 {
   Triple t;
   t.s = (char *)s;
   t.p = (char *)p;
   t.o = (char *)o;
+  t.d = (char *)d;
   t.id = NULL;
   return triplestore_add_triples(ts, &t, 1);
 }
@@ -141,8 +131,8 @@ int triplestore_add_triples(TripleStore *ts, const Triple *triples,
 
   /* make space for new triples */
   if (ts->size < ts->true_length + n) {
-    size_t m = (ts->true_length + n - ts->size) / TRIPLESTORE_BUFFSIZE;
-    size_t size = ts->size + (m + 1) * TRIPLESTORE_BUFFSIZE;
+    size_t m = (ts->true_length + n - ts->size) / TRIPLESTORE_CHUNKSIZE;
+    size_t size = ts->size + (m + 1) * TRIPLESTORE_CHUNKSIZE;
     void *ptr;
     assert(size >= ts->true_length + n);
     if (!(ptr = realloc(ts->triples, size * sizeof(Triple))))
@@ -160,14 +150,16 @@ int triplestore_add_triples(TripleStore *ts, const Triple *triples,
     if (triples[i].id) {
       if (!(id = strdup(triples[i].id))) return err(1, "allocation error");
     } else {
-      if (!(id = triple_get_id(NULL, triples[i].s,
-                                triples[i].p, triples[i].o)))
+      if (!(id = triple_get_id(NULL, triples[i].s, triples[i].p,
+                               triples[i].o, triples[i].d)))
+
         return 1;
     }
     if (!map_get(&ts->map, id)) {
       if (!(t->s = strdup(triples[i].s))) return err(1, "allocation error");
       if (!(t->p = strdup(triples[i].p))) return err(1, "allocation error");
       if (!(t->o = strdup(triples[i].o))) return err(1, "allocation error");
+      t->d = (triples[i].d) ? strdup(triples[i].d) : NULL;
       t->id = id;
       ts->length++;
       ts->true_length++;
@@ -208,6 +200,7 @@ static int _remove_by_index(TripleStore *ts, size_t n)
   return 0;
 }
 
+
 /*
   Removes a triple identified by it's `id`.  Returns non-zero on
   error or if no such triple can be found.
@@ -225,8 +218,8 @@ int triplestore_remove_by_id(TripleStore *ts, const char *id)
   be NULL, allowing for multiple matches.  Returns the number of
   triples removed.
 */
-int triplestore_remove(TripleStore *ts, const char *s,
-                       const char *p, const char *o)
+int triplestore_remove(TripleStore *ts, const char *s, const char *p,
+                       const char *o, const char *d)
 {
   int i=ts->true_length, n=0;
   while (--i >= 0) {
@@ -234,7 +227,8 @@ int triplestore_remove(TripleStore *ts, const char *s,
     if (!t->id) continue;
     if ((!s || strcmp(s, t->s) == 0) &&
         (!p || strcmp(p, t->p) == 0) &&
-        (!o || strcmp(o, t->o) == 0)) {
+        (!o || strcmp(o, t->o) == 0) &&
+        (!d || (t->d && strcmp(d, t->d) == 0))) {
       if (_remove_by_index(ts, i) == 0) n++;
     }
   }
@@ -256,6 +250,7 @@ void triplestore_clear(TripleStore *ts)
   map_deinit(&ts->map);
   memset(ts, 0, sizeof(TripleStore));
   ts->niter = niter;
+  if (ts->ns) free((char *)ts->ns);
 }
 
 
@@ -278,7 +273,8 @@ const Triple *triplestore_get(const TripleStore *ts, const char *id)
   if no match can be found.  Any of `s`, `p` or `o` may be NULL.
  */
 const Triple *triplestore_find_first(const TripleStore *ts, const char *s,
-                                      const char *p, const char *o)
+                                     const char *p, const char *o,
+                                     const char *d)
 {
   size_t i;
   for (i=0; i<ts->true_length; i++) {
@@ -286,7 +282,8 @@ const Triple *triplestore_find_first(const TripleStore *ts, const char *s,
     if (t->id &&
         (!s || strcmp(s, t->s) == 0) &&
         (!p || strcmp(p, t->p) == 0) &&
-        (!o || strcmp(o, t->o) == 0))
+        (!o || strcmp(o, t->o) == 0) &&
+        (!d || (t->d && strcmp(d, t->d) == 0)))
       return t;
   }
   return NULL;
@@ -334,8 +331,8 @@ void triplestore_deinit_state(TripleState *state)
       }
     }
     assert(ts->true_length == ts->length);
-    if (ts->size > ts->length + TRIPLESTORE_BUFFSIZE) {
-      ts->size = ts->length + ts->length % TRIPLESTORE_BUFFSIZE;
+    if (ts->size > ts->length + TRIPLESTORE_CHUNKSIZE) {
+      ts->size = ts->length + ts->length % TRIPLESTORE_CHUNKSIZE;
       ts->triples = realloc(ts->triples, ts->size*sizeof(Triple));
     }
   }
@@ -382,15 +379,28 @@ const Triple *triplestore_poll(TripleState *state)
 
 
 /*
-  This function should be called iteratively.  Before the first call
-  it should be provided a `state` initialised with triplestore_init_state().
+  Return next triple matching s-p-o triple, where `d` is the datatype
+  of the object. Any of `s`, `p`, `o` or `d` may be NULL, in which case
+  they will match anything.
+
+  If `d` starts with '@', it will match language-tagged plain text
+  literal objects whos XML language abbreviation matches the string
+  following the '@'-sign.
+
+  if `d` is NUL (empty string), it will match non-literal objects.
+
+  Any other non-NULL `d` will match literal objects whos datatype are `d`.
 
-  For each call it will return a pointer to triple matching `s`, `p`
-  and `o`.  Any of `s`, `p` or `o` may be NULL.  When no more matches
-  can be found, NULL is returned.
+  When no more matches can be found, NULL is returned.  NULL is also
+  returned on error.
+
+  This function should be called iteratively.  Before the first call
+  it should be provided a `state` initialised with triplestore_init_state()
+  and deinitialised with triplestore_deinit_state().
  */
 const Triple *triplestore_find(TripleState *state,
-                                const char *s, const char *p, const char *o)
+                               const char *s, const char *p, const char *o,
+                               const char *d)
 {
   TripleStore *ts = state->ts;
   while (state->pos < ts->true_length) {
@@ -398,20 +408,9 @@ const Triple *triplestore_find(TripleState *state,
     if (t->id &&
         (!s || strcmp(s, t->s) == 0) &&
         (!p || strcmp(p, t->p) == 0) &&
-        (!o || strcmp(o, t->o) == 0))
+        (!o || strcmp(o, t->o) == 0) &&
+        (!d || (!d[0] && !t->d) || (t->d && strcmp(d, t->d) == 0)))
       return t;
   }
   return NULL;
 }
-
-/*
-  Default implementation...
- */
-const Triple *triplestore_find2(TripleState *state,
-                                const char *s, const char *p, const char *o,
-                                int literal, const char *lang)
-{
-  UNUSED(literal);
-  UNUSED(lang);
-  return triplestore_find(state, s, p, o);
-}
diff --git a/src/triplestore-redland.c b/src/triplestore-redland.c
old mode 100755
new mode 100644
index a316924c9..f23b5a2f4
--- a/src/triplestore-redland.c
+++ b/src/triplestore-redland.c
@@ -122,50 +122,118 @@ static void finalize_check()
 }
 
 
+/* Create context for stream filter. */
+typedef struct { const char *o, *d; } StreamContext;
+
+static void *stream_get_context(const char *o, const char *d)
+{
+  StreamContext *c;
+  if (!(c = calloc(1, sizeof(StreamContext))))
+    return err(dliteMemoryError, "allocation failure"), NULL;
+  c->o = o;
+  c->d = d;
+  return (void *)c;
+}
+
+/* Free context for stream filter. */
+static void stream_free(void *context)
+{
+  free(context);
+}
+
+/* Filter for the stream returned by find. Used to handle object and
+   datatype. */
+static librdf_statement *stream_map(librdf_stream *stream, void *context,
+                                    librdf_statement *item)
+{
+  UNUSED(stream);
+  librdf_node *no = librdf_statement_get_object(item);
+  StreamContext *c = (StreamContext *)context;
+
+  if (librdf_node_is_literal(no)) {
+    if (c->o) {
+      char *value = (char *)librdf_node_get_literal_value(no);
+      if (strcmp(value, c->o) != 0) return NULL;
+    }
+    if (c->d) {
+      char *lang = librdf_node_get_literal_value_language(no);
+      librdf_uri *uri = librdf_node_get_literal_value_datatype_uri(no);
+      if (lang) {
+        if ((c->d[0] != '@') || (strcmp(c->d+1, lang) != 0)) return NULL;
+      }
+      if (uri) {
+        if (strcmp(c->d, (char *)librdf_uri_as_string(uri)) != 0) return NULL;
+      }
+      if (c->d[0] == '\0' && (lang || uri)) return NULL;
+    }
+  } else {
+    librdf_uri *iri = librdf_node_get_uri(no);
+    if (c->d && c->d[0]) return NULL;
+    if (c->o && iri && strcmp(c->o, (char *)librdf_uri_as_string(iri)) != 0)
+      return NULL;
+  }
+  return item;
+}
+
+
+
 /*
   Returns a new librdf stream with all triples matching `s`, `p` and
   `o`.  Any of these may be NULL, allowing for multiple matches.
 
-  If `literal` is non-zero the object will be considered to be a
-  literal, otherwise it is considered to be an URI.
-
-  If `lang` is not NULL, it must be a valid XML language abbreviation,
-  like "en". Only used if `literal` is non-zero.
+  If `o` is non-NULL and `d` is NULL, the object is first assumed to
+  be an IRI. If there are no matches, the object is assumed to be a
+  literal with no specified language or type.
 
   Returns NULL on error.
 */
 static librdf_stream *find(TripleStore *ts, const char *s, const char *p,
-                           const char *o, int literal, const char *lang)
+                           const char *o, const char *d)
 {
   librdf_node *ns=NULL, *np=NULL, *no=NULL;
+  librdf_uri *dt=NULL;
   librdf_statement *statement=NULL;
   librdf_stream *stream=NULL;
   int failed = 1;
+
   if (s &&!(ns = librdf_new_node_from_uri_string(ts->world,
                                                  (const unsigned char *)s)))
     FAIL1("error creating node for subject: '%s'", s);
   if (p && !(np = librdf_new_node_from_uri_string(ts->world,
                                                   (const unsigned char *)p)))
     FAIL1("error creating node for predicate: '%s'", p);
-  if (literal) {
-    if (o && !(no = librdf_new_node_from_literal(ts->world,
-                                                 (const unsigned char *)o,
-                                                 lang, 0)))
-      FAIL1("error creating node for literal object: '%s'", o);
-  } else {
-    if (o && !(no = librdf_new_node_from_uri_string(ts->world,
-                                                    (const unsigned char *)o)))
-      FAIL1("error creating node for object: '%s'", o);
+
+  if (o && d && *d) {
+    const char *lang=NULL;
+    if (d[0] == '@')
+      lang = d+1;
+    else if (!(dt = librdf_new_uri(ts->world, (const unsigned char *)d)))
+      FAIL1("error datatype '%s'", d);
+    if (!(no = librdf_new_node_from_typed_literal(ts->world,
+                                                  (const unsigned char *)o,
+                                                  lang, dt)))
+      FAIL2("error creating node for literal object: '%s' of type '%s'", o, d);
   }
+
   if (!(statement = librdf_new_statement_from_nodes(ts->world, ns, np, no))) {
-    ns = np = no = NULL;
-    FAIL3("error creating statement: (%s, %s, %s)", s, p, o);
+    ns = np = no = NULL;  // now owned by statement
+    FAIL4("error creating statement: (%s, %s, %s) (d=%s)", s, p, o, d);
   }
   if (!(stream = librdf_model_find_statements(ts->model, statement)))
-    FAIL3("error finding statements matching (%s, %s, %s)", s, p, o);
+    FAIL4("error finding statements matching (%s, %s, %s) (d=%s)", s, p, o, d);
+
+  if ((o || d) && !(o && d && *d)) {
+    void *context = stream_get_context(o, d);
+    if (librdf_stream_add_map(stream, stream_map, stream_free, context))
+      FAIL("error adding mapping function to stream");
+  }
 
   failed = 0;
  fail:
+  if (failed) {
+    if (stream) librdf_free_stream(stream);
+    stream = NULL;
+  }
   if (statement) {
     librdf_free_statement(statement);
   } else if (failed) {
@@ -173,10 +241,7 @@ static librdf_stream *find(TripleStore *ts, const char *s, const char *p,
     if (np) librdf_free_node(np);
     if (no) librdf_free_node(no);
   }
-  if (failed) {
-    if (stream) librdf_free_stream(stream);
-    stream = NULL;
-  }
+  if (dt) librdf_free_uri(dt);
   return stream;
 }
 
@@ -194,20 +259,33 @@ static int assign_triple_from_statement(Triple *t,
                                          librdf_statement *statement)
 {
   librdf_node *node;
-  unsigned char *s=NULL, *p=NULL, *o=NULL;
+  unsigned char *s=NULL, *p=NULL, *o=NULL, *d=NULL;
+  char *lang;
+  librdf_uri *datatype;
   errno = 0;
   s = librdf_uri_to_string(librdf_node_get_uri(librdf_statement_get_subject(statement)));
   p = librdf_uri_to_string(librdf_node_get_uri(librdf_statement_get_predicate(statement)));
   node = librdf_statement_get_object(statement);
   switch (librdf_node_get_type(node)) {
   case LIBRDF_NODE_TYPE_UNKNOWN:
-    return err(1, "unknown node type");
+    FAIL("unknown node type");
   case LIBRDF_NODE_TYPE_RESOURCE:
     o = librdf_uri_to_string(librdf_node_get_uri(node));
     break;
   case LIBRDF_NODE_TYPE_LITERAL:
     o = librdf_node_get_literal_value(node);
     if (o) o = (unsigned char *)strdup((char *)o);
+
+    if ((datatype = librdf_node_get_literal_value_datatype_uri(node))) {
+      if (!(d = librdf_uri_to_string(datatype)))
+        FAIL("cannot convert datatype URI to string");
+    } else if ((lang = librdf_node_get_literal_value_language(node))) {
+      size_t n = strlen(lang);
+      if (!(d = calloc(1, n+2)))
+        FAILCODE(dliteMemoryError, "allocation failure");
+      d[0] = '@';
+      strncpy((char *)d+1, lang, n+1);
+    }
     break;
   case LIBRDF_NODE_TYPE_BLANK:
     o = librdf_node_get_blank_identifier(node);
@@ -215,15 +293,21 @@ static int assign_triple_from_statement(Triple *t,
     break;
   }
   if (s && p && o) {
-    if (t->s) free(t->s);
-    if (t->p) free(t->p);
-    if (t->o) free(t->o);
+    triple_clean(t);
     t->s = (char *)s;
     t->p = (char *)p;
     t->o = (char *)o;
+    t->d = (char *)d;
+    t->id = NULL;
     return 0;
   }
-  return err(1, "error in assign_triple_from_statement");
+  FAIL("error in assign_triple_from_statement");
+ fail:
+  if (s) free(s);
+  if (p) free(p);
+  if (o) free(o);
+  if (d) free(d);
+  return 1;
 }
 
 
@@ -301,20 +385,6 @@ int triplestore_set_default_storage(const char *name)
   return err(1, "no such triplestore storage: %s", name);
 }
 
-/* Set default namespace */
-void triplestore_set_namespace(TripleStore *ts, const char *ns)
-{
-  if (ts->ns) free((char *)ts->ns);
-  ts->ns = (ns) ? strdup(ns) : NULL;
-}
-
-/* Returns a pointer to default namespace.
-   It may be NULL if it hasn't been set */
-const char *triplestore_get_namespace(TripleStore *ts)
-{
-  return ts->ns;
-}
-
 /* Returns a pointer to the name of default storage or NULL on error. */
 const char *triplestore_get_default_storage()
 {
@@ -404,9 +474,7 @@ void triplestore_free(TripleStore *ts)
   if (ts->name)          free((char *)ts->name);
   if (ts->options)       free((char *)ts->options);
   if (ts->ns)            free((char *)ts->ns);
-  if (ts->triple.s)      free(ts->triple.s);
-  if (ts->triple.p)      free(ts->triple.p);
-  if (ts->triple.o)      free(ts->triple.o);
+  triple_clean(&ts->triple);
   free(ts);
   finalize_check();
 }
@@ -445,23 +513,15 @@ static librdf_node *new_uri_node(TripleStore *ts, const char *uri)
   return node;
 }
 
-/*
-  Adds a single (s,p,o) triple to store.
-
-  If `literal` is non-zero the object will be considered to be a
-  literal, otherwise it is considered to be an URI.
-
-  If `lang` is not NULL, it must be a valid XML language abbreviation,
-  like "en". Only used if `literal` is non-zero.
 
-  If `datatype_uri` is not NULL, it should be an uri for the literal
-  datatype. Ex: "xsd:integer".
+/*
+  Adds a single (s,p,o,d) triple (of URIs) to store.  If datatype `d` is NULL,
+  is the object is considered to be an IRI. Otherwise it is a literal.
 
   Returns non-zero on error.
  */
-int triplestore_add2(TripleStore *ts, const char *s, const char *p,
-                     const char *o, int literal, const char *lang,
-                     const char *datatype_uri)
+int triplestore_add(TripleStore *ts, const char *s, const char *p,
+                    const char *o, const char *d)
 {
   librdf_node *ns=NULL, *np=NULL, *no=NULL;
   librdf_uri *uri=NULL;
@@ -469,63 +529,34 @@ int triplestore_add2(TripleStore *ts, const char *s, const char *p,
     FAIL1("error creating node for subject: '%s'", s);
   if (!(np = new_uri_node(ts, p)))
     FAIL1("error creating node for predicate: '%s'", p);
-  if (literal) {
-    if (datatype_uri &&
-        !(uri = librdf_new_uri(ts->world, (unsigned char *)datatype_uri)))
-      FAIL1("error creating uri from '%s'", datatype_uri);
+
+  if (d && d[0] == '@') {
+    if (!(no = librdf_new_node_from_typed_literal(ts->world, (unsigned char *)o,
+                                                  d+1, NULL)))
+      FAIL2("error creating language-tagged (%s) node for object: '%s'", d, o);
+  } else if (d) {
+    if (!(uri = librdf_new_uri(ts->world, (unsigned char *)d)))
+      FAIL1("error creating datatype URI from: '%s'", d);
     if (!(no = librdf_new_node_from_typed_literal(ts->world, (unsigned char *)o,
-                                                  lang, uri)))
-      FAIL1("error creating node for object: '%s'", o);
+                                                  NULL, uri)))
+      FAIL2("error creating typed (%s) literal node for object: '%s'", d, o);
   } else {
     if (!(no = new_uri_node(ts, o)))
-      FAIL1("error creating node for object: '%s'", o);
+      FAIL1("error creating IRI node for object: '%s'", o);
   }
   if (librdf_model_add(ts->model, ns, np, no))
-    FAIL("error adding triple");
+    FAIL4("error adding triple (%s, %s, %s, %s)", s, p, o, d);
   if (uri) librdf_free_uri(uri);
   return 0;
  fail:
-  if (uri) librdf_free_uri(uri);
   if (ns) librdf_free_node(ns);
   if (np) librdf_free_node(np);
   if (no) librdf_free_node(no);
+  if (uri) librdf_free_uri(uri);
   return 1;
 }
 
 
-/*
-  Adds a single (s,p,o) triple (of URIs) to store.  The object is
-  considered to be a literal with no language.
-
-  Returns non-zero on error.
- */
-int triplestore_add(TripleStore *ts, const char *s, const char *p,
-                    const char *o)
-{
-  return triplestore_add2(ts, s, p, o, 1, NULL, NULL);
-}
-
-/*
-  Adds a single triple to store.  The object is considered to be an
-  english literal.  Returns non-zero on error.
- */
-int triplestore_add_en(TripleStore *ts, const char *s, const char *p,
-                    const char *o)
-{
-  return triplestore_add2(ts, s, p, o, 1, "en", NULL);
-}
-
-/*
-  Adds a single triple to store.  The object is considered to be an URI.
-  Returns non-zero on error.
- */
-int triplestore_add_uri(TripleStore *ts, const char *s, const char *p,
-                        const char *o)
-{
-  return triplestore_add2(ts, s, p, o, 0, NULL, NULL);
-}
-
-
 /*
   Adds `n` triples to store.  Returns non-zero on error.
  */
@@ -534,7 +565,7 @@ int triplestore_add_triples(TripleStore *ts, const Triple *triples, size_t n)
   size_t i;
   for (i=0; i<n; i++) {
     const Triple *t = triples + i;
-    if (triplestore_add(ts, t->s, t->p, t->o)) return 1;
+    if (triplestore_add(ts, t->s, t->p, t->o, t->d)) return 1;
   }
   return 0;
 }
@@ -542,22 +573,17 @@ int triplestore_add_triples(TripleStore *ts, const Triple *triples, size_t n)
 
 /*
   Removes a triple identified by `s`, `p` and `o`.  Any of these may
-  be NULL, allowing for multiple matches.
-
-  If `literal` is non-zero the object will be considered to be a
-  literal, otherwise it is considered to be an URI.
-
-  If `lang` is not NULL, it must be a valid XML language abbreviation,
-  like "en". Only used if `literal` is non-zero.
+  be NULL, allowing for multiple matches.  The object is assumed to
+  be a literal with no language.
 
   Returns the number of triples removed or -1 on error.
 */
-int triplestore_remove2(TripleStore *ts, const char *s, const char *p,
-                        const char *o, int literal, const char *lang)
+int triplestore_remove(TripleStore *ts, const char *s,
+                       const char *p, const char *o, const char *d)
 {
   librdf_stream *stream;
   int failed=0, removed=0;
-  if (!(stream = find(ts, s, p, o, literal, lang))) return -1;
+  if (!(stream = find(ts, s, p, o, d))) return -1;
   do {
     librdf_statement *statement;
     if (!(statement = librdf_stream_get_object(stream))) break;
@@ -569,27 +595,13 @@ int triplestore_remove2(TripleStore *ts, const char *s, const char *p,
 }
 
 
-/*
-  Removes a triple identified by `s`, `p` and `o`.  Any of these may
-  be NULL, allowing for multiple matches.  The object is assumed to
-  be a literal with no language.
-
-  Returns the number of triples removed or -1 on error.
-*/
-int triplestore_remove(TripleStore *ts, const char *s,
-                       const char *p, const char *o)
-{
-  return triplestore_remove2(ts, s, p, o, 1, NULL);
-}
-
-
 /*
   Removes all relations in triplestore and releases all references to
   external memory.
  */
 void triplestore_clear(TripleStore *ts)
 {
-  triplestore_remove(ts, NULL, NULL, NULL);
+  triplestore_remove(ts, NULL, NULL, NULL, NULL);
 }
 
 
@@ -677,30 +689,34 @@ const Triple *triplestore_poll(TripleState *state)
 
 
 /*
-  This function should be called iteratively.  Before the first call
-  it should be provided a `state` initialised with triplestore_init_state().
+  Return next triple matching s-p-o triple, where `d` is the datatype
+  of the object. Any of `s`, `p`, `o` or `d` may be NULL, in which case
+  they will match anything.
 
-  If `literal` is non-zero the object will be considered to be a
-  literal, otherwise it is considered to be an URI.
+  If `d` starts with '@', it will match language-tagged plain text
+  literal objects whos XML language abbreviation matches the string
+  following the '@'-sign.
 
-  If `lang` is not NULL, it must be a valid XML language abbreviation,
-  like "en". Only used if `literal` is non-zero.
+  if `d` is NUL (empty string), it will match non-literal objects.
 
-  For each call it will return a pointer to triple matching `s`, `p`
-  and `o`.  Any of `s`, `p` or `o` may be NULL.  When no more matches
-  can be found, NULL is returned.
+  Any other non-NULL `d` will match literal objects whos datatype are `d`.
 
-  NULL is also returned on error.
- */
-const Triple *triplestore_find2(TripleState *state,
-                                 const char *s, const char *p, const char *o,
-                                 int literal, const char *lang)
+  When no more matches can be found, NULL is returned.  NULL is also
+  returned on error.
+
+  This function should be called iteratively.  Before the first call
+  it should be provided a `state` initialised with triplestore_init_state()
+  and deinitialised with triplestore_deinit_state().
+*/
+const Triple *triplestore_find(TripleState *state,
+                               const char *s, const char *p, const char *o,
+                               const char *d)
 {
   librdf_stream *stream;
   librdf_statement *statement;
   Triple *t = &state->ts->triple;
   assert(t);
-  if (!state->data && !(state->data = find(state->ts, s, p, o, literal, lang)))
+  if (!state->data && !(state->data = find(state->ts, s, p, o, d)))
     return err(1, "cannot create model stream"), NULL;
   stream = (librdf_stream *)state->data;
   if (!(statement = librdf_stream_get_object(stream))) return NULL;
@@ -710,75 +726,18 @@ const Triple *triplestore_find2(TripleState *state,
 }
 
 
-/*
-   Equivalent to `triplestore_find2(state, s, p, o, 1, NULL)`.
-*/
-const Triple *triplestore_find(TripleState *state,
-                                const char *s, const char *p, const char *o)
-{
-  return triplestore_find2(state, s, p, o, 1, NULL);
-}
-
-
 /*
   Returns a pointer to first triple matching `s`, `p` and `o` or NULL
   if no match can be found.  Any of `s`, `p` or `o` may be NULL.
  */
-const Triple *triplestore_find_first2(const TripleStore *ts, const char *s,
-                                       const char *p, const char *o,
-                                       int literal, const char *lang)
+const Triple *triplestore_find_first(const TripleStore *ts, const char *s,
+                                     const char *p, const char *o,
+                                     const char *d)
 {
   TripleState state;
   const Triple *t;
   triplestore_init_state((TripleStore *)ts, &state);
-  t = triplestore_find2(&state, s, p, o, literal, lang);
+  t = triplestore_find(&state, s, p, o, d);
   triplestore_deinit_state(&state);
   return t;
 }
-
-
-/*
-  Returns a pointer to first triple matching `s`, `p` and `o` or NULL
-  if no match can be found.  Any of `s`, `p` or `o` may be NULL.
- */
-const Triple *triplestore_find_first(const TripleStore *ts, const char *s,
-                                      const char *p, const char *o)
-{
-  return triplestore_find_first2(ts, s, p, o, 1, NULL);
-}
-
-
-#if 0
-
-
-/*
-  Removes a triple identified by it's `id`.  Returns non-zero on
-  error or if no such triple can be found.
-*/
-int triplestore_remove_by_id(TripleStore *ts, const char *id)
-{
-  int *n;
-  if (!(n = map_get(&ts->map, id)))
-    return err(1, "no such triple id: \"%s\"", id);
-  return _remove_by_index(ts, *n);
-}
-
-
-
-/*
-  Returns a pointer to triple with given id or NULL if no match can be found.
-*/
-const Triple *triplestore_get(const TripleStore *ts, const char *id)
-{
-  int *n = map_get(&((TripleStore *)ts)->map, id);
-  if (!n)
-    return errx(1, "no triple with id \"%s\"", id), NULL;
-  if (!ts->triples[*n].id)
-    return errx(1, "triple \"%s\" has been removed", id), NULL;
-  return ts->triples + *n;
-}
-
-
-
-
-#endif
diff --git a/src/triplestore.c b/src/triplestore.c
old mode 100755
new mode 100644
index d1fb4549b..93e6450be
--- a/src/triplestore.c
+++ b/src/triplestore.c
@@ -6,4 +6,95 @@
 #include "triplestore-redland.c"
 #else
 #include "triplestore-builtin.c"
+#include "dlite-macros.h"
 #endif
+
+
+/*
+  Adds a single triple to store.  The object is considered to be an
+  english literal.  Returns non-zero on error.
+ */
+int triplestore_add_en(TripleStore *ts, const char *s, const char *p,
+                    const char *o)
+{
+  return triplestore_add(ts, s, p, o, "@en");
+}
+
+/*
+  Adds a single triple to store.  The object is considered to be an URI.
+  Returns non-zero on error.
+ */
+int triplestore_add_uri(TripleStore *ts, const char *s, const char *p,
+                        const char *o)
+{
+  return triplestore_add(ts, s, p, o, NULL);
+}
+
+/* Set default namespace */
+void triplestore_set_namespace(TripleStore *ts, const char *ns)
+{
+  if (ts->ns) free((char *)ts->ns);
+  ts->ns = (ns) ? strdup(ns) : NULL;
+}
+
+/* Returns a pointer to default namespace.
+   It may be NULL if it hasn't been set */
+const char *triplestore_get_namespace(TripleStore *ts)
+{
+  return ts->ns;
+}
+
+/*
+  Return pointer to the value for a pair of two criteria.
+
+  Useful if one knows that there may only be one value.  The returned
+  value is held by the triplestore and should be copied by the user
+  since it may be overwritten by later calls to the triplestore.
+
+  Parameters:
+      s, p, o: Criteria to match. Two of these must be non-NULL.
+      d: If not NULL, the required datatype of literal objects.
+      fallback: Value to return if no matches are found.
+      any: If non-zero, return first matching value.
+
+  Returns a pointer to the value of the `s`, `p` or `o` that is NULL.
+  On error NULL is returned.
+ */
+const char *triplestore_value(TripleStore *ts, const char *s, const char *p,
+                              const char *o, const char *d,
+                              const char *fallback, int any)
+{
+  int n=0, i=-1;
+  const char *value=NULL;
+  TripleState state;
+  const Triple *t;
+  triplestore_init_state(ts, &state);
+  if (s) n++; else i=0;
+  if (p) n++; else i=1;
+  if (o) n++; else i=2;
+  if (n != 2) FAILCODE3(dliteTypeError,
+                        "triplestore_value() expects exact two of "
+                        "s='%s', p='%s', o='%s' to be non-NULL", s, p, o);
+  assert(i >= 0);
+  if (!(t = triplestore_find(&state, s, p, o, d))) {
+    if (!fallback)
+      FAILCODE4(dliteSearchError, "no values matching the criteria: "
+                "s='%s', p='%s', o='%s', d='%s'", s, p, o, d);
+    value = fallback;
+  } else {
+    switch (i) {
+    case 0: value = t->s; break;
+    case 1: value = t->p; break;
+    case 2: value = t->o; break;
+    }
+  }
+  if (!any && triplestore_find(&state, s, p, o, d))
+    FAILCODE4(dliteSearchError, "more than one value matching the criteria: "
+              "s='%s', p='%s', o='%s', d='%s'.  Maybe you want to set `any` "
+              "to true?", s, p, o, d);
+  triplestore_deinit_state(&state);
+  return value;
+ fail:
+  triplestore_deinit_state(&state);
+  return NULL;
+}
diff --git a/src/triplestore.h b/src/triplestore.h
old mode 100755
new mode 100644
index aa9467a9c..51142cbf1
--- a/src/triplestore.h
+++ b/src/triplestore.h
@@ -55,8 +55,24 @@ librdf_world *triplestore_get_world(TripleStore *ts);
 /** Returns the internal librdf model. */
 librdf_model *triplestore_get_model(TripleStore *ts);
 
-#endif
+/** Like triplestore_create_with_storage(), but also takes a librdf world
+    as argument. */
+TripleStore *triplestore_create_with_world(librdf_world *world,
+                                           const char *storage_name,
+                                           const char *name,
+                                           const char *options);
+
 
+/* ================================== */
+/* Builtin-specific functions         */
+/* ================================== */
+#else  /* !HAVE_REDLAND */
+
+/** Removes a triple identified by it's `id`.  Returns non-zero if no
+  such triple can be found. */
+int triplestore_remove_by_id(TripleStore *ts, const char *id);
+
+#endif
 
 
 /* ================================== */
@@ -71,6 +87,8 @@ TripleStore *triplestore_create();
 /**
   Returns a new empty triplestore.
 
+  Without librdf, this behaves like triplestore_create().
+
   Arguments:
     storage_name: Name of storage module. If NULL, the default storage will
                   be used.
@@ -93,7 +111,7 @@ void triplestore_free(TripleStore *ts);
 
 
 /**
-  Set default namespace
+  Set default namespace.
 */
 void triplestore_set_namespace(TripleStore *ts, const char *ns);
 
@@ -111,30 +129,19 @@ size_t triplestore_length(TripleStore *ts);
 
 
 /**
-  Adds a single (s,p,o) triple to store.
-
-  If `literal` is non-zero the object will be considered to be a
-  literal, otherwise it is considered to be an URI.
+  Adds triple to store.
 
-  If `lang` is not NULL, it must be a valid XML language abbreviation,
-  like "en". Only used if `literal` is non-zero.
+  If `d` is NULL, the object is considered to be an IRI.
 
-  If `datatype_uri` is not NULL, it should be an uri for the literal
-  datatype. Ex: "xsd:integer".
+  If `d` starts with '@', the object is considered to be a
+  language-tagged plain text literal.  The language code should follow
+  directly after the '@' sign.  Ex: "@en".
 
-  Returns non-zero on error.
- */
-int triplestore_add2(TripleStore *ts, const char *s, const char *p,
-                     const char *o, int literal, const char *lang,
-                     const char *datatype_uri);
-
-
-/**
-  Adds a single triple to store.  The object is considered to be a
-  literal with no language.  Returns non-zero on error.
+  Otherwise, the object is considered to be a literal with datatype
+  specified by `d`.  Ex: "xsd:integer".
  */
 int triplestore_add(TripleStore *ts, const char *s, const char *p,
-                    const char *o);
+                    const char *o, const char *d);
 
 
 /**
@@ -159,20 +166,13 @@ int triplestore_add_uri(TripleStore *ts, const char *s, const char *p,
 int triplestore_add_triples(TripleStore *ts, const Triple *triples, size_t n);
 
 
-/**
-  Removes a triple identified by it's `id`.  Returns non-zero if no such
-  triple can be found.
-*/
-int triplestore_remove_by_id(TripleStore *ts, const char *id);
-
-
 /**
   Removes a triple identified by `s`, `p` and `o`.  Any of these may
   be NULL, allowing for multiple matches.  Returns the number of
   triples removed.
 */
 int triplestore_remove(TripleStore *ts, const char *s,
-                       const char *p, const char *o);
+                       const char *p, const char *o, const char *d);
 
 
 /**
@@ -192,7 +192,8 @@ const Triple *triplestore_get(const TripleStore *ts, const char *id);
   if no match can be found.  Any of `s`, `p` or `o` may be NULL.
  */
 const Triple *triplestore_find_first(const TripleStore *ts, const char *s,
-                             const char *p, const char *o);
+                                     const char *p, const char *o,
+                                     const char *d);
 
 
 /**
@@ -224,36 +225,49 @@ const Triple *triplestore_next(TripleState *state);
 const Triple *triplestore_poll(TripleState *state);
 
 /**
-  This function should be called iteratively.  Before the first call
-  it should be provided a `state` initialised with triplestore_init_state().
+  Return next triple matching s-p-o triple, where `d` is the datatype
+  of the object. Any of `s`, `p`, `o` or `d` may be NULL, in which case
+  they will match anything.
 
-  For each call it will return a pointer to triple matching `s`, `p`
-  and `o`.  Any of `s`, `p` or `o` may be NULL.  When no more matches
-  can be found, NULL is returned.
+  If `d` starts with '@', it will match language-tagged plain text
+  literal objects whos XML language abbreviation matches the string
+  following the '@'-sign.
 
-  No other calls to triplestore_add() or triplestore_find() should be
-  done while searching.
+  if `d` is NUL (empty string), it will match non-literal objects.
 
-  NULL is also returned on error.
+  Any other non-NULL `d` will match literal objects whos datatype are `d`.
+
+  When no more matches can be found, NULL is returned.  NULL is also
+  returned on error.
+
+  This function should be called iteratively.  Before the first call
+  it should be provided a `state` initialised with triplestore_init_state()
+  and deinitialised with triplestore_deinit_state().
  */
 const Triple *triplestore_find(TripleState *state,
-                                const char *s, const char *p, const char *o);
+                               const char *s, const char *p, const char *o,
+                               const char *d);
 
 
 /**
-  Like triplestore_find(), but has two additional arguments.
+  Return pointer to the value for a pair of two criteria.
 
-  If `literal` is non-zero the object will be considered to be a
-  literal, otherwise it is considered to be an URI.
+  Useful if one knows that there may only be one value.  The returned
+  value is held by the triplestore and should be copied by the user
+  since it may be overwritten by later calls to the triplestore.
 
-  If `lang` is not NULL, it must be a valid XML language abbreviation,
-  like "en". Only used if `literal` is non-zero.
+  Parameters:
+      s, p, o: Criteria to match. Two of these must be non-NULL.
+      d: If not NULL, the required datatype of literal objects.
+      fallback: Value to return if no matches are found.
+      any: If non-zero, return first matching value.
 
-  If redland is not available, it is equivalent to triplestore_find().
+  Returns a pointer to the value of the `s`, `p` or `o` that is NULL.
+  On error NULL is returned.
  */
-const Triple *triplestore_find2(TripleState *state,
-                                const char *s, const char *p, const char *o,
-                                int literal, const char *lang);
+const char *triplestore_value(TripleStore *ts, const char *s, const char *p,
+                              const char *o, const char *d,
+                              const char *fallback, int any);
 
 
 #endif /* _TRIPLESTORE_H */
diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt
old mode 100755
new mode 100644
diff --git a/src/utils/LICENSES.txt b/src/utils/LICENSES.txt
old mode 100755
new mode 100644
diff --git a/src/utils/README.md b/src/utils/README.md
old mode 100755
new mode 100644
diff --git a/src/utils/boolean.h.in b/src/utils/boolean.h.in
old mode 100755
new mode 100644
diff --git a/src/utils/bson.c b/src/utils/bson.c
old mode 100755
new mode 100644
diff --git a/src/utils/bson.h b/src/utils/bson.h
old mode 100755
new mode 100644
diff --git a/src/utils/byteorder.h.in b/src/utils/byteorder.h.in
old mode 100755
new mode 100644
diff --git a/src/utils/clp2.c b/src/utils/clp2.c
old mode 100755
new mode 100644
diff --git a/src/utils/clp2.h b/src/utils/clp2.h
old mode 100755
new mode 100644
diff --git a/src/utils/compat.c b/src/utils/compat.c
old mode 100755
new mode 100644
diff --git a/src/utils/compat.h b/src/utils/compat.h
old mode 100755
new mode 100644
diff --git a/src/utils/compat/config_snprintf.h.in b/src/utils/compat/config_snprintf.h.in
old mode 100755
new mode 100644
diff --git a/src/utils/compat/dirent.h b/src/utils/compat/dirent.h
old mode 100755
new mode 100644
diff --git a/src/utils/compat/getopt.c b/src/utils/compat/getopt.c
old mode 100755
new mode 100644
diff --git a/src/utils/compat/getopt.h b/src/utils/compat/getopt.h
old mode 100755
new mode 100644
diff --git a/src/utils/compat/realpath.c b/src/utils/compat/realpath.c
old mode 100755
new mode 100644
diff --git a/src/utils/compat/snprintf.c b/src/utils/compat/snprintf.c
old mode 100755
new mode 100644
diff --git a/src/utils/config.h.in b/src/utils/config.h.in
old mode 100755
new mode 100644
diff --git a/src/utils/dsl.h b/src/utils/dsl.h
old mode 100755
new mode 100644
diff --git a/src/utils/err.c b/src/utils/err.c
old mode 100755
new mode 100644
diff --git a/src/utils/err.h b/src/utils/err.h
old mode 100755
new mode 100644
diff --git a/src/utils/execprocess.c b/src/utils/execprocess.c
old mode 100755
new mode 100644
diff --git a/src/utils/execprocess.h b/src/utils/execprocess.h
old mode 100755
new mode 100644
diff --git a/src/utils/fileinfo.c b/src/utils/fileinfo.c
old mode 100755
new mode 100644
diff --git a/src/utils/fileinfo.h b/src/utils/fileinfo.h
old mode 100755
new mode 100644
diff --git a/src/utils/fileutils.c b/src/utils/fileutils.c
old mode 100755
new mode 100644
diff --git a/src/utils/fileutils.h b/src/utils/fileutils.h
old mode 100755
new mode 100644
diff --git a/src/utils/floats.h.in b/src/utils/floats.h.in
old mode 100755
new mode 100644
diff --git a/src/utils/gencfile.c b/src/utils/gencfile.c
old mode 100755
new mode 100644
diff --git a/src/utils/globmatch.c b/src/utils/globmatch.c
old mode 100755
new mode 100644
diff --git a/src/utils/globmatch.h b/src/utils/globmatch.h
old mode 100755
new mode 100644
diff --git a/src/utils/infixcalc.c b/src/utils/infixcalc.c
old mode 100755
new mode 100644
diff --git a/src/utils/infixcalc.h b/src/utils/infixcalc.h
old mode 100755
new mode 100644
diff --git a/src/utils/integers.h.in b/src/utils/integers.h.in
old mode 100755
new mode 100644
diff --git a/src/utils/jsmn.h b/src/utils/jsmn.h
old mode 100755
new mode 100644
diff --git a/src/utils/jsmnx.c b/src/utils/jsmnx.c
old mode 100755
new mode 100644
diff --git a/src/utils/jsmnx.h b/src/utils/jsmnx.h
old mode 100755
new mode 100644
diff --git a/src/utils/jstore.c b/src/utils/jstore.c
old mode 100755
new mode 100644
diff --git a/src/utils/jstore.h b/src/utils/jstore.h
old mode 100755
new mode 100644
diff --git a/src/utils/map.c b/src/utils/map.c
old mode 100755
new mode 100644
diff --git a/src/utils/map.h b/src/utils/map.h
old mode 100755
new mode 100644
diff --git a/src/utils/md5.c b/src/utils/md5.c
old mode 100755
new mode 100644
diff --git a/src/utils/md5.h b/src/utils/md5.h
old mode 100755
new mode 100644
diff --git a/src/utils/plugin.c b/src/utils/plugin.c
old mode 100755
new mode 100644
index 901d96235..817861b79
--- a/src/utils/plugin.c
+++ b/src/utils/plugin.c
@@ -249,7 +249,8 @@ const PluginAPI *plugin_load(PluginInfo *info, const char *name,
            info->symbol, filepath, dsl_error());
 
     if (!registered_api && handle) {
-      if (handle) (void)dsl_close(handle);
+      if (handle && dsl_close(handle))
+        err(1, "error closing \"%s\": %s", filepath, dsl_error());
       handle = NULL;
     }
   }
diff --git a/src/utils/plugin.h b/src/utils/plugin.h
old mode 100755
new mode 100644
diff --git a/src/utils/session.c b/src/utils/session.c
old mode 100755
new mode 100644
diff --git a/src/utils/session.h b/src/utils/session.h
old mode 100755
new mode 100644
diff --git a/src/utils/sha1.c b/src/utils/sha1.c
old mode 100755
new mode 100644
diff --git a/src/utils/sha1.h b/src/utils/sha1.h
old mode 100755
new mode 100644
diff --git a/src/utils/sha3.c b/src/utils/sha3.c
old mode 100755
new mode 100644
diff --git a/src/utils/sha3.h b/src/utils/sha3.h
old mode 100755
new mode 100644
diff --git a/src/utils/strtob.c b/src/utils/strtob.c
old mode 100755
new mode 100644
diff --git a/src/utils/strtob.h b/src/utils/strtob.h
old mode 100755
new mode 100644
diff --git a/src/utils/strutils.c b/src/utils/strutils.c
old mode 100755
new mode 100644
diff --git a/src/utils/strutils.h b/src/utils/strutils.h
old mode 100755
new mode 100644
diff --git a/src/utils/tgen.c b/src/utils/tgen.c
old mode 100755
new mode 100644
diff --git a/src/utils/tgen.h b/src/utils/tgen.h
old mode 100755
new mode 100644
diff --git a/src/utils/tmpfileplus.c b/src/utils/tmpfileplus.c
old mode 100755
new mode 100644
diff --git a/src/utils/tmpfileplus.h b/src/utils/tmpfileplus.h
old mode 100755
new mode 100644
diff --git a/src/utils/unicode.c b/src/utils/unicode.c
old mode 100755
new mode 100644
diff --git a/src/utils/unicode.h b/src/utils/unicode.h
old mode 100755
new mode 100644
diff --git a/src/utils/urlsplit.c b/src/utils/urlsplit.c
old mode 100755
new mode 100644
diff --git a/src/utils/urlsplit.h b/src/utils/urlsplit.h
old mode 100755
new mode 100644
diff --git a/src/utils/uuid.c b/src/utils/uuid.c
old mode 100755
new mode 100644
diff --git a/src/utils/uuid.h b/src/utils/uuid.h
old mode 100755
new mode 100644
diff --git a/src/utils/uuid4.c b/src/utils/uuid4.c
old mode 100755
new mode 100644
diff --git a/src/utils/uuid4.h b/src/utils/uuid4.h
old mode 100755
new mode 100644
diff --git a/storages/hdf5/CMakeLists.txt b/storages/hdf5/CMakeLists.txt
old mode 100755
new mode 100644
diff --git a/storages/hdf5/dh5lite.c b/storages/hdf5/dh5lite.c
old mode 100755
new mode 100644
diff --git a/storages/json/CMakeLists.txt b/storages/json/CMakeLists.txt
old mode 100755
new mode 100644
diff --git a/storages/json/dlite-json-storage.c b/storages/json/dlite-json-storage.c
old mode 100755
new mode 100644
diff --git a/storages/python/CMakeLists.txt b/storages/python/CMakeLists.txt
old mode 100755
new mode 100644
diff --git a/storages/python/README.md b/storages/python/README.md
old mode 100755
new mode 100644
diff --git a/storages/python/dlite-plugins-python.c b/storages/python/dlite-plugins-python.c
old mode 100755
new mode 100644
diff --git a/storages/python/python-storage-plugins/README.md b/storages/python/python-storage-plugins/README.md
old mode 100755
new mode 100644
diff --git a/storages/python/python-storage-plugins/blob.json b/storages/python/python-storage-plugins/blob.json
old mode 100755
new mode 100644
diff --git a/storages/python/python-storage-plugins/blob.py b/storages/python/python-storage-plugins/blob.py
old mode 100755
new mode 100644
diff --git a/storages/python/python-storage-plugins/bson.py b/storages/python/python-storage-plugins/bson.py
old mode 100755
new mode 100644
diff --git a/storages/python/python-storage-plugins/csv.py b/storages/python/python-storage-plugins/csv.py
old mode 100755
new mode 100644
diff --git a/storages/python/python-storage-plugins/http.py b/storages/python/python-storage-plugins/http.py
old mode 100755
new mode 100644
diff --git a/storages/python/python-storage-plugins/minio.py b/storages/python/python-storage-plugins/minio.py
old mode 100755
new mode 100644
diff --git a/storages/python/python-storage-plugins/mongodb.py b/storages/python/python-storage-plugins/mongodb.py
old mode 100755
new mode 100644
index a528eed8d..22fb8b6a8
--- a/storages/python/python-storage-plugins/mongodb.py
+++ b/storages/python/python-storage-plugins/mongodb.py
@@ -1,10 +1,9 @@
+
+"""DLite storage plugin for MongoDB."""
+
 import fnmatch
 import json
-import sys
-from urllib.parse import quote_plus, urlparse
-
 import pymongo
-
 import dlite
 from dlite.options import Options
 
@@ -13,7 +12,12 @@ class mongodb(dlite.DLiteStorageBase):
     """DLite storage plugin for MongoDB."""
 
     def open(self, uri, options=None):
-        """Opens `uri`.
+        """Opens `uri`, connection string of the MongoDB
+
+        uri = mongodb://{account}:{password}@{account}.{hostname}:{port}/
+
+        see more details of the connection string:
+        https://www.mongodb.com/docs/manual/reference/connection-string/#connection-string-formats
 
         The `options` argument provies additional input to the driver.
         Which options that are supported varies between the plugins.  It
@@ -23,93 +27,32 @@ def open(self, uri, options=None):
 
         An ampersand (&) may be used instead of the semicolon (;).
 
-        All options that begin with MONGOCLIENT_<keyword> will be
-        passed directly to the mongodb client.
-
         Options:
-        - database: Name of database to use (default: "dlite")
-        - collection: Name of collection to use (default: "dlite_coll")
-        - user: User name.
-        - password: Password.
         - mode: "r" for opening in read-only mode, "w" for read/write mode.
-        - schema: Schema to use when connecting to MongoDB server.  Defaults
-              to schema in `uri`.
-        - authMechanism: Authentication mechanism
-        - mock: Whether to use mongomock.
-        """
-        parsed_options = self._parse_options(options)
-        self._configure(parsed_options, uri)
-
-    def _parse_options(self, options):
-        """Parse and validate input options."""
-        parsed_options = Options(
-            options,
-            defaults="database=dlite;collection=dlite_coll;mode=r;mock=no;"
-            "user=guest;password=guest",
-        )
-        parsed_options.setdefault("password", None)
-        return parsed_options
-
-    def _configure(self, parsed_options, uri):
-        """Configure and connect to the MongoDB database."""
-        self.writable = True if "w" in parsed_options.mode else False
-
-        client_options = {
-            k: parsed_options[k] for k in parsed_options if "MONGOCLIENT_" in k
-        }
-
-        user = quote_plus(parsed_options.user) if parsed_options.user else None
-        password = (
-            quote_plus(parsed_options.password)
-            if parsed_options.password
-            else None
-        )
-
-        # Determine the schema based on the presence of "localhost" or "127.0.0.1" in the URI
-        schema = parsed_options.get("schema", None)
-        if schema is None:
-            if "localhost" in uri or "127.0.0.1" in uri:
-                schema = "mongodb"
-            else:
-                schema = "mongodb+srv"
-
-        # Remove any existing schema from the URI
-        if not uri.startswith(schema + "://"):
-            uri = uri.replace("mongodb+srv://", "")
-            uri = uri.replace("mongodb://", "")
+        - database: Name of database to use (default: "test")
+        - collection: Name of collection to use (default: "test_coll")
+        - options for the constructor of the pymongo.MongoClient could pass by
+          using the dlite.Options object, for example:
 
-        # Construct the final URI with the correct schema
-        final_uri = f"{schema}://{uri}"
+            opt = Options("mode=r;database=test;collection=testc")
+            opt.update(directConnection=False, maxPoolSize=200)
+            storage = dlite.Storage("mongodb", "localhost", str(opt))
 
-        if dlite.asbool(parsed_options.mock):
-            import mongomock
+        see more details of the pymongo.MongoClient options
+        https://pymongo.readthedocs.io/en/stable/api/pymongo/mongo_client.html#pymongo.mongo_client.MongoClient:
 
-            mongo_url = urlparse(f"mongodb://{uri}")
-            port = mongo_url.port if mongo_url.port else 27017
-
-            @mongomock.patch(servers=((mongo_url.hostname, port),))
-            def get_client():
-                return open_client()
-
-        else:
-
-            def get_client():
-                return open_client()
-
-        def open_client():
-            client = pymongo.MongoClient(
-                host=final_uri,
-                username=user,
-                password=password,
-                **client_options,
-            )
-            return client
-
-        self.client = get_client()
-        self.collection = self.client[parsed_options.database][
-            parsed_options.collection
-        ]
-        self.options = parsed_options
+        """
+        opt = Options(
+            options,
+            defaults="mode=r;database=test;collection=test_coll"
+        )
+        keys = ["mode", "database", "collection"]
+        client_options = {k: v for k, v in opt.items() if k not in keys}
+        self.client = pymongo.MongoClient(uri, **client_options)
+        self.collection = self.client[opt.database][opt.collection]
+        self.options = opt
+        self.readable = ("r" in opt.mode) | ("w" in opt.mode)
+        self.writable = "w" in opt.mode
 
     def close(self):
         """Closes the MongoDB connection."""
@@ -121,7 +64,7 @@ def load(self, id):
         document = self.collection.find_one({"uuid": uuid})
         if not document:
             raise IOError(
-                f"No instance with uuid={uuid} in MongoDB database "
+                f"No instance with {uuid} in MongoDB database "
                 f'"{self.collection.database.name}" and collection '
                 f'"{self.collection.name}"'
             )
@@ -140,9 +83,17 @@ def queue(self, pattern=None):
         if pattern:
             # MongoDB supports PCRE, which is created by fnmatch.translate()
             mongo_regex = {"$regex": fnmatch.translate(pattern)}
-            filter = {"meta": mongo_regex}
+            filters = {"meta": mongo_regex}
         else:
-            filter = {}
-
-        for doc in self.collection.find(filter=filter, projection=["uuid"]):
+            filters = {}
+        for doc in self.collection.find(filter=filters, projection=["uuid"]):
             yield doc["uuid"]
+
+    def delete(self, uid):
+        """Delete instance with given `uid` from storage.
+
+        Arguments:
+            id: UUID of instance to delete.
+        """
+        result = self.collection.delete_one({"uuid": uid})
+        return result.deleted_count == 1
diff --git a/storages/python/python-storage-plugins/postgresql.py b/storages/python/python-storage-plugins/postgresql.py
old mode 100755
new mode 100644
diff --git a/storages/python/python-storage-plugins/pyrdf.py b/storages/python/python-storage-plugins/pyrdf.py
old mode 100755
new mode 100644
diff --git a/storages/python/python-storage-plugins/redis.py b/storages/python/python-storage-plugins/redis.py
old mode 100755
new mode 100644
diff --git a/storages/python/python-storage-plugins/yaml.py b/storages/python/python-storage-plugins/yaml.py
old mode 100755
new mode 100644
diff --git a/storages/rdf/CMakeLists.txt b/storages/rdf/CMakeLists.txt
old mode 100755
new mode 100644
diff --git a/storages/rdf/dlite-rdf.c b/storages/rdf/dlite-rdf.c
old mode 100755
new mode 100644
index 86be74578..99b345ced
--- a/storages/rdf/dlite-rdf.c
+++ b/storages/rdf/dlite-rdf.c
@@ -232,6 +232,7 @@ DLiteStorage *rdf_open(const DLiteStoragePlugin *api, const char *uri,
 int rdf_close(DLiteStorage *storage)
 {
   RdfStorage *s = (RdfStorage *)storage;
+  int retval = 0;
 
   if (s->flags & dliteWritable) {
     librdf_world *world = triplestore_get_world(s->ts);
@@ -247,6 +248,7 @@ int rdf_close(DLiteStorage *storage)
     if (s->filename) {
       unsigned char *buf;
       librdf_uri *base_uri=NULL, *type_uri=NULL;
+      FILE *fp;
       if (s->base_uri)
         base_uri = librdf_new_uri(world, (unsigned char *)s->base_uri);
       if (s->type_uri)
@@ -257,10 +259,11 @@ int rdf_close(DLiteStorage *storage)
 
       if (strcmp(s->filename, "-") == 0) {
         fprintf(stdout, "%s", buf);
-      } else {
-        FILE *fp = fopen(s->filename, "w");
+      } else if ((fp = fopen(s->filename, "w"))) {
         fprintf(fp, "%s", buf);
         fclose(fp);
+      } else {
+        retval = err(dliteIOError, "cannot write rdf file: %s", s->filename);
       }
 
       if (base_uri) librdf_free_uri(base_uri);
@@ -276,7 +279,7 @@ int rdf_close(DLiteStorage *storage)
   if (s->format) free(s->format);
   if (s->mime_type) free(s->mime_type);
   if (s->type_uri) free(s->type_uri);
-  return 0;
+  return retval;
 }
 
 
@@ -289,7 +292,7 @@ static const char *getobj(RdfStorage *rdf, const char *s, const char *p,
 {
   TripleStore *ts = rdf->ts;
   const Triple *t;
-  if (!(t = triplestore_find_first(ts, s, p, NULL))) {
+  if (!(t = triplestore_find_first(ts, s, p, NULL, NULL))) {
     if (verbose) err(1, "missing s='%s' p='%s': %s", s, p, rdf->location);
     return NULL;
   }
@@ -302,7 +305,7 @@ static int count(TripleStore *ts, const char *s, const char *p, const char *o)
   TripleState state;
   int n=0;
   triplestore_init_state(ts, &state);
-  while (triplestore_find(&state, s, p, o)) n++;
+  while (triplestore_find(&state, s, p, o, NULL)) n++;
   triplestore_deinit_state(&state);
   return n;
 }
@@ -323,32 +326,39 @@ DLiteInstance *rdf_load_instance(const DLiteStorage *storage, const char *id)
   int ok=0, n, j;
   char uuid[DLITE_UUID_LENGTH+1], muuid[DLITE_UUID_LENGTH+1];
   char *pid=NULL, *mid=NULL, *propiri=NULL;
+  const char *value;
 
   errno = 0;
-  dlite_get_uuid(uuid, id);
-  pid = (s->base_uri) ? aprintf("%s:%s", s->base_uri, uuid) : NULL;
 
   /* find instance and metadata UUIDs */
-  triplestore_init_state(ts, &state);
-  while ((t2 = triplestore_find(&state, pid, _P ":hasMeta", NULL))) {
-    if (t) FAIL1("ID must be provided if storage holds "
-                 "more than one instance: %s", s->location);
-    t = t2;
-  }
-  triplestore_deinit_state(&state);
-  if (t) {
-    dlite_get_uuid(muuid, t->o);
+  if (id) {
+    dlite_get_uuid(uuid, id);
+    pid = (s->base_uri) ? aprintf("%s:%s", s->base_uri, uuid) : NULL;
+    if (!(value = triplestore_value(ts, pid, _P ":hasMeta", NULL, NULL,
+                                  NULL, 0)))
+      FAILCODE2(dliteSearchError,
+                "cannot find instance '%s' in RDF storage: %s",
+                pid, s->location);
+    dlite_get_uuid(muuid, value);
   } else {
     triplestore_init_state(ts, &state);
-    while ((t2 = triplestore_find(&state, pid, _P ":hasURI", NULL))) {
-      if (t) FAIL1("ID must be provided if storage holds "
-                   "more than one instance: %s", s->location);
-      t = t2;
+    if ((t = triplestore_find(&state, NULL, _P ":hasMeta", NULL, NULL))) {
+      pid = strdup(t->s);
+      dlite_get_uuid(muuid, t->o);
     }
+    t2 = triplestore_find(&state, NULL, _P ":hasMeta", NULL, NULL);
     triplestore_deinit_state(&state);
-    dlite_get_uuid(muuid, DLITE_ENTITY_SCHEMA);
+    if (!t) FAILCODE1(dliteSearchError,
+                      "no instances in RDF storage: %s", s->location);
+
+    if (t2) FAILCODE1(dliteSearchError, "ID must be provided if storage "
+                      "holds more than one instance: %s", s->location);
+    if (!(value = triplestore_value(ts, pid, _P ":hasUUID", NULL, NULL,
+                                    NULL, 0)))
+      FAILCODE2(dliteInconsistentDataError, "instance '%s' has no "
+                _P ":hasUUID relation in RDF storage: %s", pid, s->location);
+    dlite_get_uuid(uuid, value);
   }
-  if (!t) FAIL2("no instance with id '%s' in store: %s", id, s->location);
 
   /* get/load metadata */
   mid = (s->base_uri) ? aprintf("%s:%s", s->base_uri, muuid) : NULL;
@@ -361,12 +371,12 @@ DLiteInstance *rdf_load_instance(const DLiteStorage *storage, const char *id)
     const char *name, *val;
     if (!(dims = calloc(meta->_ndimensions, sizeof(size_t))))
       FAILCODE(dliteMemoryError, "allocation failure");
-    if (triplestore_find_first(ts, pid, _P ":hasDimensionValue", NULL)) {
+    if (triplestore_find_first(ts, pid, _P ":hasDimensionValue", NULL, NULL)) {
       /* -- read dimension values */
       n = 0;
       triplestore_init_state(ts, &state);
       while ((t = triplestore_find(&state, pid, _P ":hasDimensionValue",
-                                   NULL))) {
+                                   NULL, NULL))) {
         char *dimval = strdup(t->o);
         if (!(name = getobj(s, dimval, _P ":hasLabel", 1))) goto fail;
         j = dlite_meta_get_dimension_index(meta, name);
@@ -390,15 +400,18 @@ DLiteInstance *rdf_load_instance(const DLiteStorage *storage, const char *id)
     }
   }
 
-  if (!(inst = dlite_instance_create(meta, shape, (id) ? id : uuid))) goto fail;
-  if (!inst->uri && (t = triplestore_find_first(ts, pid, _P ":hasURI", NULL)))
+  if (!(inst = dlite_instance_create(meta, dims, (id) ? id : uuid))) goto fail;
+  if (!inst->uri && (t = triplestore_find_first(ts, pid, _P ":hasURI", NULL,
+                                                NULL)))
     inst->uri = strdup(t->o);
 
   /* FIXME - should have been called by dlite_instance_create() */
   if (dlite_instance_is_meta(inst)) dlite_meta_init((DLiteMeta *)inst);
 
   n = 0;
-  while ((t = triplestore_find(&state, pid, _P ":hasPropertyValue", NULL))) {
+  triplestore_init_state(ts, &state);
+  while ((t = triplestore_find(&state, pid, _P ":hasPropertyValue", NULL,
+                               NULL))) {
     /* -- read property values */
     DLiteProperty *p;
     const char *name, *val;
@@ -444,7 +457,8 @@ DLiteInstance *rdf_load_instance(const DLiteStorage *storage, const char *id)
     /* -- read dimensions */
     d = dlite_instance_get_property(inst, "dimensions");
     triplestore_init_state(ts, &state);
-    while ((t = triplestore_find(&state, pid, _P ":hasDimension", NULL))) {
+    while ((t = triplestore_find(&state, pid, _P ":hasDimension", NULL,
+                                 NULL))) {
       propiri = strdup(t->o);
       if (!(str = getobj(s, propiri, _P ":hasLabel", 1))) goto fail;
       d->name = strdup(str);
@@ -460,7 +474,8 @@ DLiteInstance *rdf_load_instance(const DLiteStorage *storage, const char *id)
     /* -- read properties */
     p = dlite_instance_get_property(inst, "properties");
     triplestore_init_state(ts, &state);
-    while ((t = triplestore_find(&state, pid, _P ":hasProperty", NULL))) {
+    while ((t = triplestore_find(&state, pid, _P ":hasProperty", NULL,
+                                 NULL))) {
       const char *name, *typename, *shape, *unit, *descr;
 
       /* save propiri so it is not overwritten by getobj() */
@@ -517,7 +532,6 @@ DLiteInstance *rdf_load_instance(const DLiteStorage *storage, const char *id)
   if (propiri) free(propiri);
   if (dims) free(dims);
   if (!ok && inst) dlite_instance_decref(inst);
-  triplestore_deinit_state(&state);
   return (ok) ? inst : NULL;
 }
 
@@ -556,10 +570,10 @@ int rdf_save_instance(DLiteStorage *storage, const DLiteInstance *inst)
     triplestore_add_uri(ts, inst->uuid, "rdf:type", _P ":Entity");
   else
     triplestore_add_uri(ts, inst->uuid, "rdf:type", _P ":Object");
-  triplestore_add(ts, inst->uuid, _P ":hasUUID", inst->uuid);
-  triplestore_add(ts, inst->uuid, _P ":hasMeta", inst->meta->uri);
+  triplestore_add(ts, inst->uuid, _P ":hasUUID", inst->uuid, "xsd:anyURI");
+  triplestore_add(ts, inst->uuid, _P ":hasMeta", inst->meta->uri, NULL);
   if (inst->uri)
-    triplestore_add(ts, inst->uuid, _P ":hasURI", inst->uri);
+    triplestore_add(ts, inst->uuid, _P ":hasURI", inst->uri, NULL);
 
   /* Describe metadata with spesialised properties */
   if (meta && s->fmtflags & fmtMetaAnnot) {
@@ -573,7 +587,7 @@ int rdf_save_instance(DLiteStorage *storage, const DLiteInstance *inst)
       if (!(b1 = get_blank_node(ts, buf))) goto fail;
       triplestore_add_uri(ts, inst->uuid, _P ":hasDimension", b1);
       triplestore_add_uri(ts, b1, "rdf:type", _P ":Dimension");
-      triplestore_add(ts, b1, _P ":hasLabel", d->name);
+      triplestore_add(ts, b1, _P ":hasLabel", d->name, "xsd:Name");
       triplestore_add_en(ts, b1, _P ":hasDescription", d->description);
       free(b1);
     }
@@ -588,18 +602,19 @@ int rdf_save_instance(DLiteStorage *storage, const DLiteInstance *inst)
       if (!(b2 = get_blank_node(ts, buf2))) goto fail;
       triplestore_add_uri(ts, inst->uuid, _P ":hasProperty", b1);
       triplestore_add_uri(ts, b1, "rdf:type", _P ":Property");
-      triplestore_add(ts, b1, _P ":hasLabel", p->name);
-      triplestore_add(ts, b1, _P ":hasType", typename);
+      triplestore_add(ts, b1, _P ":hasLabel", p->name, "xsd:Name");
+      triplestore_add(ts, b1, _P ":hasType", typename, "xsd:Name");
       if (p->ndims)
         triplestore_add_uri(ts, b1, _P ":hasFirstShape", b2);
       if (p->unit)
-        triplestore_add(ts, b1, _P ":hasUnit", p->unit);
+        triplestore_add(ts, b1, _P ":hasUnit", p->unit, "xsd:Name");
       if (p->description)
         triplestore_add_en(ts, b1, _P ":hasDescription", p->description);
 
       if (p->shape) {
         triplestore_add_uri(ts, b2, "rdf:type", _P ":Shape");
-        triplestore_add(ts, b2, _P ":hasDimensionExpression", p->shape[0]);
+        triplestore_add(ts, b2, _P ":hasDimensionExpression", p->shape[0],
+                        "xsd:string");
       }
       for (j=1; j < p->ndims; j++) {
         char *b;
@@ -607,7 +622,8 @@ int rdf_save_instance(DLiteStorage *storage, const DLiteInstance *inst)
         if (!(b = get_blank_node(ts, buf2))) goto fail;
         triplestore_add_uri(ts, b2, _P ":hasNextShape", b);
         triplestore_add_uri(ts, b, "rdf:type", _P ":Shape");
-        triplestore_add(ts, b, _P ":hasDimensionExpression", p->shape[j]);
+        triplestore_add(ts, b, _P ":hasDimensionExpression", p->shape[j],
+                        "xsd:string");
         free(b2);
         b2 = b;
       }
@@ -625,9 +641,8 @@ int rdf_save_instance(DLiteStorage *storage, const DLiteInstance *inst)
       asnprintf(&buf, &bufsize, "%d",
                 (int)dlite_instance_get_dimension_size_by_index(inst, i));
       triplestore_add_uri(ts, inst->uuid, _P ":hasDimensionValue", b1);
-      triplestore_add(ts, b1, _P ":hasLabel", name);
-      triplestore_add2(ts, b1, _P ":hasDimensionSize", buf,
-                       1, NULL, "xsd:integer");
+      triplestore_add(ts, b1, _P ":hasLabel", name, "xsd:Name");
+      triplestore_add(ts, b1, _P ":hasDimensionSize", buf, "xsd:integer");
       free(b1);
     }
 
@@ -642,11 +657,10 @@ int rdf_save_instance(DLiteStorage *storage, const DLiteInstance *inst)
       triplestore_add_uri(ts, inst->uuid, _P ":hasPropertyValue", b1);
       triplestore_add_uri(ts, b1, "rdf:type", "owl:NamedIndividual");
       triplestore_add_uri(ts, b1, "rdf:type", _P ":PropertyValue");
-      triplestore_add(ts, b1, _P ":hasLabel", name);
+      triplestore_add(ts, b1, _P ":hasLabel", name, "xsd:Name");
       dlite_property_aprint(&buf, &bufsize, 0, ptr, p, shape, 0, -2,
                             dliteFlagRaw | dliteFlagStrip);
-      triplestore_add2(ts, b1, _P ":hasValue", buf, 1, NULL,
-                       "rdf:PlainLiteral");
+      triplestore_add(ts, b1, _P ":hasValue", buf, "rdf:PlainLiteral");
       free(b1);
     }
   }
@@ -700,7 +714,7 @@ int rdf_iter_next(void *iter, char *buf)
   RdfIter *riter = iter;
   const Triple *t;
   while (1) {
-    t = triplestore_find(&riter->state, NULL, _P ":hasMeta", NULL);
+    t = triplestore_find(&riter->state, NULL, _P ":hasMeta", NULL, NULL);
     if (!t) return 1;
     if (!riter->pattern) break;
     if (globmatch(riter->pattern, t->o) == 0) break;
diff --git a/tools/.gitignore b/tools/.gitignore
old mode 100755
new mode 100644
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
old mode 100755
new mode 100644
diff --git a/tools/bindgen/bindgen.py b/tools/bindgen/bindgen.py
old mode 100755
new mode 100644
diff --git a/tools/bindgen/template-dlite.f90 b/tools/bindgen/template-dlite.f90
old mode 100755
new mode 100644
diff --git a/tools/dlite-codegen.c b/tools/dlite-codegen.c
old mode 100755
new mode 100644
diff --git a/tools/dlite-env.c b/tools/dlite-env.c
old mode 100755
new mode 100644
diff --git a/tools/dlite-getuuid.c b/tools/dlite-getuuid.c
old mode 100755
new mode 100644
diff --git a/tools/templates/c-header.txt b/tools/templates/c-header.txt
old mode 100755
new mode 100644
diff --git a/tools/templates/c-meta-header.txt b/tools/templates/c-meta-header.txt
old mode 100755
new mode 100644
diff --git a/tools/templates/c-source.txt b/tools/templates/c-source.txt
old mode 100755
new mode 100644
diff --git a/tools/templates/fortran-module.txt b/tools/templates/fortran-module.txt
old mode 100755
new mode 100644


Test patch:
diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml
old mode 100755
new mode 100644
index b170cbc92..55ed8ce59
--- a/.github/workflows/ci_tests.yml
+++ b/.github/workflows/ci_tests.yml
@@ -10,6 +10,10 @@ jobs:
     - name: Checkout repository
       uses: actions/checkout@v4
 
+      # We do not install rdflib0-dev here, since we want to test
+      # DLite both with and without rdflib (Redland) support and the
+      # case with rdflib is already covered by the ci_build_wheel
+      # workflow for Linux systems.
     - name: Install dependencies
       run: |
         sudo apt-get update --fix-missing && \
diff --git a/bindings/fortran/tests/Animal.json b/bindings/fortran/tests/Animal.json
old mode 100755
new mode 100644
diff --git a/bindings/fortran/tests/CMakeLists.txt b/bindings/fortran/tests/CMakeLists.txt
old mode 100755
new mode 100644
diff --git a/bindings/fortran/tests/Person.json b/bindings/fortran/tests/Person.json
old mode 100755
new mode 100644
diff --git a/bindings/fortran/tests/Scan3D.json b/bindings/fortran/tests/Scan3D.json
old mode 100755
new mode 100644
diff --git a/bindings/fortran/tests/persons.json b/bindings/fortran/tests/persons.json
old mode 100755
new mode 100644
diff --git a/bindings/fortran/tests/persons_names.json b/bindings/fortran/tests/persons_names.json
old mode 100755
new mode 100644
diff --git a/bindings/fortran/tests/test_animal.f90 b/bindings/fortran/tests/test_animal.f90
old mode 100755
new mode 100644
diff --git a/bindings/fortran/tests/test_person.f90 b/bindings/fortran/tests/test_person.f90
old mode 100755
new mode 100644
diff --git a/bindings/python/tests/.gitignore b/bindings/python/tests/.gitignore
old mode 100755
new mode 100644
index 41706a5db..7d0735cd2
--- a/bindings/python/tests/.gitignore
+++ b/bindings/python/tests/.gitignore
@@ -1,16 +1,2 @@
 .gdb_history
 __pycache__
-
-inst.ttl
-inst.yaml
-test.json
-test.yaml
-
-xxx.json
-xxx2.json
-yyy.json
-
-myentity.json
-persons2.json
-
-db.xml
diff --git a/bindings/python/tests/CMakeLists.txt b/bindings/python/tests/CMakeLists.txt
old mode 100755
new mode 100644
index ec0a357f9..a9746189f
--- a/bindings/python/tests/CMakeLists.txt
+++ b/bindings/python/tests/CMakeLists.txt
@@ -4,6 +4,7 @@
 set(tests
   test_python_bindings
   test_collection
+  test_collection_load
   test_entity
   test_factory
   test_misc
@@ -22,6 +23,8 @@ set(tests
   test_postgresql2_read
   test_ref_type
   test_pydantic
+  test_relation
+  test_iri
 )
 
 foreach(test ${tests})
@@ -50,3 +53,5 @@ endforeach()
 # Additional properties
 set_property(TEST test_postgresql2_read-py PROPERTY
   DEPENDS test_postgresql1_write-py)
+set_property(TEST test_collection_load-py APPEND PROPERTY
+  DEPENDS test_collection-py)
diff --git a/bindings/python/tests/Person.json b/bindings/python/tests/Person.json
deleted file mode 100755
index 97c608791..000000000
--- a/bindings/python/tests/Person.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
-  "uri": "http://onto-ns.com/meta/0.1/Person",
-  "description": "A person.",
-  "dimensions": {
-    "N": "Number of skills."
-  },
-  "properties": {
-    "name": {
-      "type": "string",
-      "description": "Full name."
-    },
-    "age": {
-      "type": "float",
-      "unit": "years",
-      "description": "Age of person."
-    },
-    "skills": {
-      "type": "string",
-      "shape": ["N"],
-      "description": "List of skills."
-    }
-  }
-}
diff --git a/bindings/python/tests/__main__.py b/bindings/python/tests/__main__.py
old mode 100755
new mode 100644
diff --git a/bindings/python/tests/check_import.py b/bindings/python/tests/check_import.py
old mode 100755
new mode 100644
diff --git a/bindings/python/tests/entities/Empty.json b/bindings/python/tests/entities/Empty.json
old mode 100755
new mode 100644
diff --git a/bindings/python/tests/entities/Empty2.json b/bindings/python/tests/entities/Empty2.json
old mode 100755
new mode 100644
diff --git a/bindings/python/tests/entities/Invalid1.json b/bindings/python/tests/entities/Invalid1.json
old mode 100755
new mode 100644
index 8401f9f18..d7693cff0
--- a/bindings/python/tests/entities/Invalid1.json
+++ b/bindings/python/tests/entities/Invalid1.json
@@ -1,7 +1,19 @@
 {
-  "name": "Missing version",
-  "namespace": "http://onto-ns.com/meta",
-  "meta": "http://onto-ns.com/meta/0.3/EntitySchema",
-  "dimensions": {}
-  "properties": {}
+  "uri": "http://onto-ns.com/meta/0.1/Invalid1",
+  "description": "An datamodel for an item with float type and invalid shape name.",
+  "dimensions": {
+      "nf": "Number of eigen-frequencies."
+  },
+  "properties": {
+    "name": {
+      "type": "str",
+      "description": "Name of the item."
+    },
+    "f": {
+      "type": "float64",
+      "shape": ["nf_MISPELLED"],
+      "unit": "Hz",
+      "description": "The magic eigen-frequencies of the item."
+    }
+  }
 }
diff --git a/bindings/python/tests/entities/Invalid2.json b/bindings/python/tests/entities/Invalid2.json
old mode 100755
new mode 100644
index 4792bc587..e0a51fdf3
--- a/bindings/python/tests/entities/Invalid2.json
+++ b/bindings/python/tests/entities/Invalid2.json
@@ -1,7 +1,15 @@
 {
-  "name": "Missing namespace",
-  "version": "0.0.1",
-  "meta": "http://onto-ns.com/meta/0.3/EntitySchema",
-  "dimensions": {}
-  "properties": {}
+  "uri": "http://onto-ns.com/meta/0.1/Invalid2",
+  "description": "A datamodel with ref-types with invalid shape name...",
+  "dimensions": {
+    "nrefs": "Number of references to Ref datamodels."
+  },
+  "properties": {
+    "items": {
+      "type": "ref",
+      "$ref": "http://onto-ns.com/meta/0.1/Item",
+      "shape": ["INVALID_nrefs"],
+      "description": "List of Item datamodels."
+    }
+  }
 }
diff --git a/bindings/python/tests/entities/Invalid3.json b/bindings/python/tests/entities/Invalid3.json
old mode 100755
new mode 100644
index 4f91de558..cb1ed38bb
--- a/bindings/python/tests/entities/Invalid3.json
+++ b/bindings/python/tests/entities/Invalid3.json
@@ -1,5 +1,19 @@
 {
-  "url": "http://onto-ns.com/meta/0.1/Invalid3",
-  "description": "Missing properties",
-  "dimensions": {}
+  "uri": "http://onto-ns.com/meta/0.1/Invalid3",
+  "description": "Invalid JSON (square bracket for dimensions instead of braces).",
+  "dimensions": [
+    "nfrequencies": "Number of eigen-frequencies."
+  ],
+  "properties": {
+    "name": {
+      "type": "str",
+      "description": "Name of the item."
+    },
+    "f": {
+      "type": "float64",
+      "shape": ["nf"],
+      "unit": "Hz",
+      "description": "The magic eigen-frequencies of the item."
+    }
+  }
 }
diff --git a/bindings/python/tests/entities/Invalid4.json b/bindings/python/tests/entities/Invalid4.json
old mode 100755
new mode 100644
diff --git a/bindings/python/tests/entities/Invalid5.json b/bindings/python/tests/entities/Invalid5.json
new file mode 100644
index 000000000..8401f9f18
--- /dev/null
+++ b/bindings/python/tests/entities/Invalid5.json
@@ -0,0 +1,7 @@
+{
+  "name": "Missing version",
+  "namespace": "http://onto-ns.com/meta",
+  "meta": "http://onto-ns.com/meta/0.3/EntitySchema",
+  "dimensions": {}
+  "properties": {}
+}
diff --git a/bindings/python/tests/entities/Invalid6.json b/bindings/python/tests/entities/Invalid6.json
new file mode 100644
index 000000000..4792bc587
--- /dev/null
+++ b/bindings/python/tests/entities/Invalid6.json
@@ -0,0 +1,7 @@
+{
+  "name": "Missing namespace",
+  "version": "0.0.1",
+  "meta": "http://onto-ns.com/meta/0.3/EntitySchema",
+  "dimensions": {}
+  "properties": {}
+}
diff --git a/bindings/python/tests/entities/Invalid7.json b/bindings/python/tests/entities/Invalid7.json
new file mode 100644
index 000000000..a1d112bc7
--- /dev/null
+++ b/bindings/python/tests/entities/Invalid7.json
@@ -0,0 +1,5 @@
+{
+  "url": "http://onto-ns.com/meta/0.1/Invalid7",
+  "description": "Missing properties",
+  "dimensions": {}
+}
diff --git a/bindings/python/tests/entities/InvalidJson.json b/bindings/python/tests/entities/InvalidJson.json
old mode 100755
new mode 100644
diff --git a/bindings/python/tests/input/Item.json b/bindings/python/tests/entities/Item.json
similarity index 100%
rename from bindings/python/tests/input/Item.json
rename to bindings/python/tests/entities/Item.json
diff --git a/bindings/python/tests/MyEntity.json b/bindings/python/tests/entities/MyEntity.json
old mode 100755
new mode 100644
similarity index 100%
rename from bindings/python/tests/MyEntity.json
rename to bindings/python/tests/entities/MyEntity.json
diff --git a/bindings/python/tests/entities/Person.json b/bindings/python/tests/entities/Person.json
old mode 100755
new mode 100644
index 1788c5a80..97c608791
--- a/bindings/python/tests/entities/Person.json
+++ b/bindings/python/tests/entities/Person.json
@@ -1,34 +1,23 @@
 {
-  "name": "Person",
-  "version": "0.1",
-  "namespace": "http://onto-ns.com/meta",
-  "meta": "http://onto-ns.com/meta/0.3/EntitySchema",
+  "uri": "http://onto-ns.com/meta/0.1/Person",
   "description": "A person.",
-  "dimensions": [
-    {
-      "name": "N",
-      "description": "Number of skills."
-    }
-  ],
-  "properties": [
-    {
-      "name": "name",
+  "dimensions": {
+    "N": "Number of skills."
+  },
+  "properties": {
+    "name": {
       "type": "string",
       "description": "Full name."
     },
-    {
-      "name": "age",
+    "age": {
       "type": "float",
       "unit": "years",
       "description": "Age of person."
     },
-    {
-      "name": "skills",
+    "skills": {
       "type": "string",
-      " shape": [
-        "N"
-      ],
+      "shape": ["N"],
       "description": "List of skills."
     }
-  ]
+  }
 }
diff --git a/bindings/python/tests/entities/PersonQuantity.json b/bindings/python/tests/entities/PersonQuantity.json
new file mode 100644
index 000000000..a1c411d8a
--- /dev/null
+++ b/bindings/python/tests/entities/PersonQuantity.json
@@ -0,0 +1,36 @@
+{
+  "uri": "http://onto-ns.com/meta/0.1/PersonQuantity",
+  "description": "A example to work with quantity.",
+  "dimensions": {
+    "N": "Number of points."
+  },
+  "properties": {
+    "name": {
+      "type": "string",
+      "description": "Name."
+    },
+    "age": {
+      "type": "float",
+      "unit": "years",
+      "description": "Age of person."
+    },
+    "length": {
+      "type": "float",
+      "unit": "cm",
+      "description": "Length."
+    },
+    "temperature": {
+      "type": "float",
+      "unit": "degC",
+      "description": "Temperature."
+    },
+    "stress": {
+      "type": "float",
+      "unit": "MPa",
+      "shape": [
+        "N"
+      ],
+      "description": "Stress curve."
+    }
+  }
+}
\ No newline at end of file
diff --git a/bindings/python/tests/input/Ref.json b/bindings/python/tests/entities/Ref.json
similarity index 100%
rename from bindings/python/tests/input/Ref.json
rename to bindings/python/tests/entities/Ref.json
diff --git a/bindings/python/tests/SimplePerson.json b/bindings/python/tests/entities/SimplePerson.json
old mode 100755
new mode 100644
similarity index 100%
rename from bindings/python/tests/SimplePerson.json
rename to bindings/python/tests/entities/SimplePerson.json
diff --git a/bindings/python/tests/global_dlite_state_mod1.py b/bindings/python/tests/global_dlite_state_mod1.py
old mode 100755
new mode 100644
diff --git a/bindings/python/tests/global_dlite_state_mod2.py b/bindings/python/tests/global_dlite_state_mod2.py
old mode 100755
new mode 100644
index 1476774cb..de8a8d826
--- a/bindings/python/tests/global_dlite_state_mod2.py
+++ b/bindings/python/tests/global_dlite_state_mod2.py
@@ -1,15 +1,16 @@
 #!/usr/bin/env python
-# -*- coding: utf-8 -*-
-import os
+from pathlib import Path
 
 import dlite
 from dlite import Instance, Dimension, Property, Relation
 
 assert len(dlite.istore_get_uuids()) == 3 + 3
 
-thisdir = os.path.abspath(os.path.dirname(__file__))
 
-url = "json://" + thisdir + "/MyEntity.json"
+thisdir = Path(__file__).absolute().parent
+entitydir = thisdir / "entities"
+
+url = f"json://{entitydir}/MyEntity.json"
 
 # myentity is already defined via test_global_dlite_state, no new instance is added to istore
 myentity = Instance.from_url(url)
diff --git a/bindings/python/tests/global_dlite_state_mod3.py b/bindings/python/tests/global_dlite_state_mod3.py
old mode 100755
new mode 100644
diff --git a/bindings/python/tests/global_dlite_state_mod4.py b/bindings/python/tests/global_dlite_state_mod4.py
old mode 100755
new mode 100644
diff --git a/bindings/python/tests/input/Invalid1.json b/bindings/python/tests/input/Invalid1.json
deleted file mode 100644
index d7693cff0..000000000
--- a/bindings/python/tests/input/Invalid1.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
-  "uri": "http://onto-ns.com/meta/0.1/Invalid1",
-  "description": "An datamodel for an item with float type and invalid shape name.",
-  "dimensions": {
-      "nf": "Number of eigen-frequencies."
-  },
-  "properties": {
-    "name": {
-      "type": "str",
-      "description": "Name of the item."
-    },
-    "f": {
-      "type": "float64",
-      "shape": ["nf_MISPELLED"],
-      "unit": "Hz",
-      "description": "The magic eigen-frequencies of the item."
-    }
-  }
-}
diff --git a/bindings/python/tests/input/Invalid2.json b/bindings/python/tests/input/Invalid2.json
deleted file mode 100644
index e0a51fdf3..000000000
--- a/bindings/python/tests/input/Invalid2.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-  "uri": "http://onto-ns.com/meta/0.1/Invalid2",
-  "description": "A datamodel with ref-types with invalid shape name...",
-  "dimensions": {
-    "nrefs": "Number of references to Ref datamodels."
-  },
-  "properties": {
-    "items": {
-      "type": "ref",
-      "$ref": "http://onto-ns.com/meta/0.1/Item",
-      "shape": ["INVALID_nrefs"],
-      "description": "List of Item datamodels."
-    }
-  }
-}
diff --git a/bindings/python/tests/input/Invalid3.json b/bindings/python/tests/input/Invalid3.json
deleted file mode 100644
index cb1ed38bb..000000000
--- a/bindings/python/tests/input/Invalid3.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
-  "uri": "http://onto-ns.com/meta/0.1/Invalid3",
-  "description": "Invalid JSON (square bracket for dimensions instead of braces).",
-  "dimensions": [
-    "nfrequencies": "Number of eigen-frequencies."
-  ],
-  "properties": {
-    "name": {
-      "type": "str",
-      "description": "Name of the item."
-    },
-    "f": {
-      "type": "float64",
-      "shape": ["nf"],
-      "unit": "Hz",
-      "description": "The magic eigen-frequencies of the item."
-    }
-  }
-}
diff --git a/bindings/python/tests/input/coll.json b/bindings/python/tests/input/coll.json
new file mode 100644
index 000000000..13fc63845
--- /dev/null
+++ b/bindings/python/tests/input/coll.json
@@ -0,0 +1,37 @@
+{
+  "90ba2cb5-d242-4fd4-978f-e0e1c80006cf":   {
+    "meta": "http://onto-ns.com/meta/0.1/Collection",
+    "dimensions": {
+      "nrelations": 6
+    },
+    "properties": {
+      "relations": [
+        [
+          "http://onto-ns.com/meta/0.1/TypeOne#dpOne",
+          "http://emmo.info/domain-mappings#mapsTo",
+          "http://www.osp-core.com/ex#dpOne"
+        ], [
+          "http://onto-ns.com/meta/0.1/TypeOne#dpTwo",
+          "http://emmo.info/domain-mappings#mapsTo",
+          "http://www.osp-core.com/ex#dpTwo"
+        ], [
+          "http://onto-ns.com/meta/0.1/TypeTwo#dpOne",
+          "http://emmo.info/domain-mappings#mapsTo",
+          "http://www.osp-core.com/ex#dpOne"
+        ], [
+          "http://onto-ns.com/meta/0.1/TypeTwo#dpTwo",
+          "http://emmo.info/domain-mappings#mapsTo",
+          "http://www.osp-core.com/ex#dpTwo"
+        ], [
+          "http://onto-ns.com/meta/0.1/TypeTwo#dpThree",
+          "http://emmo.info/domain-mappings#mapsTo",
+          "http://www.osp-core.com/ex#dpThree"
+        ], [
+          "http://onto-ns.com/meta/0.1/TypeThree#dpTwo",
+          "http://emmo.info/domain-mappings#mapsTo",
+          "http://www.osp-core.com/ex#dpTwo"
+        ]
+      ]
+    }
+  }
+}
diff --git a/bindings/python/tests/persons.json b/bindings/python/tests/input/persons.json
old mode 100755
new mode 100644
similarity index 100%
rename from bindings/python/tests/persons.json
rename to bindings/python/tests/input/persons.json
diff --git a/bindings/python/tests/rdf.ttl b/bindings/python/tests/input/rdf.ttl
old mode 100755
new mode 100644
similarity index 100%
rename from bindings/python/tests/rdf.ttl
rename to bindings/python/tests/input/rdf.ttl
diff --git a/bindings/python/tests/test_ref_type.json b/bindings/python/tests/input/test_ref_type.json
old mode 100755
new mode 100644
similarity index 100%
rename from bindings/python/tests/test_ref_type.json
rename to bindings/python/tests/input/test_ref_type.json
diff --git a/bindings/python/tests/inst.json b/bindings/python/tests/inst.json
deleted file mode 100755
index 1aaefb7e3..000000000
--- a/bindings/python/tests/inst.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
-  "ce592ff9-a7dc-548a-bbe5-3c53800afaf3":   {
-    "uri": "my-data",
-    "meta": "http://onto-ns.com/meta/0.1/MyEntity",
-    "dimensions": {
-      "N": 2,
-      "M": 3
-    },
-    "properties": {
-      "a-blob": "000102030405060708090a0b0c0d0e0f",
-      "a-blob-array": [["00aa00ff", "00bb00dd"], ["001100aa", "0000cc00"]],
-      "a-bool": false,
-      "a-bool-array": [true, false],
-      "an-int": 42,
-      "an-int-array": [0, 1, -1],
-      "a-float": 0,
-      "a-float64-array": [0, 3.14, -1e-6],
-      "a-fixstring": "",
-      "a-fixstring-array": [["abc", "def"], ["ghi", "jkl"]],
-      "a-string": "en af dem der red med fane",
-      "a-string-array": [[null, "a", "b"], ["abc", "a longer string", "and one that is not shorter..."]],
-      "a-relation": ["Cat", "isA", "Mammal"],
-      "a-relation-array": [["Shark", "isA", "Fish"], ["Fish", "isA", "Animal"]]
-    }
-  }
-}
diff --git a/bindings/python/tests/ontologies/family.ttl b/bindings/python/tests/ontologies/family.ttl
old mode 100755
new mode 100644
diff --git a/bindings/python/tests/ontologies/food.ttl b/bindings/python/tests/ontologies/food.ttl
old mode 100755
new mode 100644
diff --git a/bindings/python/tests/output/.gitignore b/bindings/python/tests/output/.gitignore
old mode 100755
new mode 100644
index 5c0c65f19..5b880214c
--- a/bindings/python/tests/output/.gitignore
+++ b/bindings/python/tests/output/.gitignore
@@ -1,2 +1,4 @@
 *.json
 *.yaml
+*.ttl
+db.xml
diff --git a/bindings/python/tests/python-mapping-plugins/plugin1.py b/bindings/python/tests/python-mapping-plugins/plugin1.py
old mode 100755
new mode 100644
diff --git a/bindings/python/tests/test_collection.py b/bindings/python/tests/test_collection.py
old mode 100755
new mode 100644
index ff0f43e12..3d9dee18b
--- a/bindings/python/tests/test_collection.py
+++ b/bindings/python/tests/test_collection.py
@@ -3,9 +3,14 @@
 from pathlib import Path
 
 import dlite
+from dlite.testutils import raises
+
 
 thisdir = Path(__file__).resolve().parent
 outdir = thisdir / "output"
+indir = thisdir / "input"
+entitydir = thisdir / "entities"
+
 
 # Create collection
 coll = dlite.Collection("mycoll")
@@ -22,7 +27,7 @@
 assert rel is None
 
 # Create instances
-url = f"json://{thisdir}/MyEntity.json?mode=r"
+url = f"json://{entitydir}/MyEntity.json?mode=r"
 e = dlite.Instance.from_url(url)
 inst1 = dlite.Instance.from_metaid(e.uri, [3, 2])
 inst2 = dlite.Instance.from_metaid(e.uri, (3, 4), "myinst")
@@ -159,6 +164,30 @@
 assert isinstance(coll["meta"], dlite.Metadata)
 
 
+# Test Collection.value()
+assert coll.value(s="dog", p="is-a") == "animal"
+assert coll.value(p="_is-a", o="Instance", any=1) == "inst1"
+assert coll.value(p="is-a", o="x", default="y", any=1) == "y"
+assert coll.value(s="meta", p="_has-uuid", d="xsd:anyURI") == (
+    "96f31fc3-3838-5cb8-8d90-eddee6ff59ca"
+)
+
+with raises(dlite.DLiteTypeError):
+    coll.value(s="dog", p="is-a", o="animal")
+
+with raises(dlite.DLiteTypeError):
+    coll.value()
+
+with raises(dlite.DLiteSearchError):
+    coll.value(s="dog", p="x")
+
+with raises(dlite.DLiteSearchError):
+    coll.value(p="_is-a", o="Instance")
+
+with raises(dlite.DLiteSearchError):
+    coll.value(s="meta", p="_has-uuid", d="xsd:int")
+
+
 # String representation
 s = str(coll)
 print(s)
diff --git a/bindings/python/tests/test_collection_load.py b/bindings/python/tests/test_collection_load.py
new file mode 100644
index 000000000..9cb9b527c
--- /dev/null
+++ b/bindings/python/tests/test_collection_load.py
@@ -0,0 +1,25 @@
+# This test assumes that test_collection.py has been run first
+import json
+from pathlib import Path
+
+import dlite
+from dlite.utils import instance_from_dict
+
+
+thisdir = Path(__file__).resolve().parent
+indir = thisdir / "input"
+outdir = thisdir / "output"
+dlite.storage_path.append(thisdir)
+dlite.storage_path.append(thisdir / "entities")
+
+
+# -- Test for PR #750
+
+# check initialisation of collection
+Coll = dlite.get_instance(dlite.COLLECTION_ENTITY)
+assert Coll["dimensions"].size
+
+# Check that we can load collection from dict
+d = json.load(open(indir / "coll.json", "r"))
+coll = instance_from_dict(d)
+assert coll.nrelations == 6
diff --git a/bindings/python/tests/test_datamodel.py b/bindings/python/tests/test_datamodel.py
old mode 100755
new mode 100644
diff --git a/bindings/python/tests/test_entity.py b/bindings/python/tests/test_entity.py
old mode 100755
new mode 100644
index f7e010ba7..297b0e336
--- a/bindings/python/tests/test_entity.py
+++ b/bindings/python/tests/test_entity.py
@@ -11,16 +11,15 @@
 
 
 thisdir = Path(__file__).absolute().parent
+outdir = thisdir / "output"
 indir = thisdir / "input"
 entitydir = thisdir / "entities"
 dlite.storage_path.append(indir / "*.json")
 dlite.storage_path.append(entitydir / "*.json")
 
-url = f"json://{thisdir}/MyEntity.json"
-
 
 # Load metadata (i.e. an instance of meta-metadata) from url
-myentity = Instance.from_url(url)
+myentity = Instance.from_url(f"json://{entitydir}/MyEntity.json")
 print(myentity.uuid)
 
 # Check some properties of the entity
@@ -32,15 +31,11 @@
 assert not myentity.is_metameta
 
 # Store the entity to a new file
-myentity.save("json://xxx.json?mode=w")
+myentity.save(f"json://{outdir}/test_entity.json?mode=w")
 
 # Try to overwrite without mode - should fail because metadata is immutable
-try:
-    myentity.save("json://xxx.json")
-except dlite.DLiteError:
-    dlite.errclr()
-else:
-    assert False, "overwriting single-entity formatted file"
+with raises(dlite.DLiteStorageSaveError):
+    myentity.save(f"json://{outdir}/test_entity.json")
 
 # Create an instance of `myentity` with dimensions 2, 3
 # For convinience, we give it an unique label "myid" that can be used
@@ -86,61 +81,51 @@
 # print(inst)
 
 # Check save and load
-inst.save("json://inst.json?mode=w")
-inst2 = Instance.from_url("json://inst.json")
+inst.save(f"json://{outdir}/test_entity_inst.json?mode=w")
+inst2 = Instance.from_url(f"json://{outdir}/test_entity_inst.json")
 blob = inst2["a-blob"]
 
 del inst2
 inst2 = Instance.from_url(
-    "json://inst.json?mode=r#46a67765-3d8b-5764-9583-3aec59a17983"
+    f"json://{outdir}/test_entity_inst.json?"
+    "mode=r#46a67765-3d8b-5764-9583-3aec59a17983"
 )
 assert inst2["a-blob"] == blob
 
 del inst2
-inst2 = Instance.from_location("json", "inst.json")
+inst2 = Instance.from_location("json", outdir / "test_entity_inst.json")
 assert inst2["a-blob"] == blob
 
 del inst2
 inst2 = Instance.from_location(
-    "json", "inst.json", id="46a67765-3d8b-5764-9583-3aec59a17983"
+    "json", outdir / "test_entity_inst.json",
+    id="46a67765-3d8b-5764-9583-3aec59a17983"
 )
 assert inst2["a-blob"] == blob
 del inst2
 
-with dlite.Storage("json", "inst.json") as s:
+with dlite.Storage("json", outdir / "test_entity_inst.json") as s:
     inst2 = dlite.Instance.from_storage(s)
 assert inst2["a-blob"] == blob
 del inst2
 
-with dlite.Storage("json", "inst.json") as s:
+with dlite.Storage("json", outdir / "test_entity_inst.json") as s:
     inst2 = s.load(id="46a67765-3d8b-5764-9583-3aec59a17983")
 assert inst2["a-blob"] == blob
 del inst2
 
 # Make sure we fail with an exception for pathetic cases
-try:
+with raises(dlite.DLiteStorageOpenError):
     Instance.from_location("json", "/", "mode=r")
-except dlite.DLiteError:
-    print('*** catched error loading "/" in read mode')
-    dlite.errclr()
 
-try:
+with raises(dlite.DLiteStorageLoadError):
     Instance.from_location("json", "/", "mode=w")
-except dlite.DLiteError:
-    print('*** catched error loading "/" in write mode')
-    dlite.errclr()
 
-try:
+with raises(dlite.DLiteStorageLoadError):
     Instance.from_location("json", "")
-except dlite.DLiteError:
-    print('*** catched error loading ""')
-    dlite.errclr()
 
-try:
+with raises(dlite.DLiteStorageLoadError):
     Instance.from_location("json", "non-existing-path...")
-except dlite.DLiteError:
-    print('*** catched error loading "non-existing-path..."')
-    dlite.errclr()
 
 
 # Test for issue #352 - improved error message for missing dimensions
@@ -154,17 +139,8 @@
   }
 }
 """
-try:
-    entity = dlite.Instance.from_json(json_repr)
-except dlite.DLiteError as exc:
-    assert str(exc) == (
-        "DLiteOtherError: metadata does not confirm to schema, please check "
-        "dimensions, properties and/or relations: "
-        "http://onto-ns.com/ex/0.1/test"
-    )
-    dlite.errclr()
-else:
-    assert False  # missing dimensions should raise an exception
+with raises(dlite.DLiteParseError):
+    dlite.Instance.from_json(json_repr)
 
 
 # Test copy
@@ -252,14 +228,14 @@
 
 
 # Test save
-inst.save("json://yyy.json?mode=w")
+inst.save(f"json://{outdir}/test_entity2.json?mode=w")
 
 try:
     import yaml
 except ImportError:
     pass
 else:
-    inst.save("yaml://yyy.yaml?mode=w")
+    inst.save(f"yaml://{outdir}/test_entity.yaml?mode=w")
 
 
 # Test metadata
@@ -304,10 +280,12 @@
 
 # Metadata schema
 schema = dlite.get_instance(dlite.ENTITY_SCHEMA)
-schema.save("entity_schema.json?mode=w;arrays=false")
-schema.meta.save("basic_metadata_schema.json?mode=w;arrays=false")
+schema.save(f"json://{outdir}/entity_schema.json?mode=w;arrays=false")
+schema.meta.save(
+    f"json://{outdir}/basic_metadata_schema.json?mode=w;arrays=false"
+)
 
-mm = dlite.Instance.from_url("json://entity_schema.json")
+mm = dlite.Instance.from_url(f"json://{outdir}/entity_schema.json")
 assert mm.uri == dlite.ENTITY_SCHEMA
 
 
@@ -317,7 +295,6 @@
 with raises(dlite.DLiteMissingInstanceError, dlite.DLiteSyntaxError):
     invalid1 = Invalid1([2], properties={"name": "a", "f": [3.14, 2.72]})
 
-
 # For issue #686
 Invalid2 = dlite.get_instance("http://onto-ns.com/meta/0.1/Invalid2")
 with raises(dlite.DLiteMissingInstanceError, dlite.DLiteSyntaxError):
@@ -325,8 +302,20 @@
 
 
 # For issue #691
-with raises(dlite.DLiteStorageOpenError):
-    Invalid3 = dlite.Instance.from_location("json", indir / "Invalid3.json")
+with raises(dlite.DLiteStorageOpenError, dlite.DLiteUnknownError):
+    dlite.Instance.from_location("json", entitydir / "Invalid3.json")
+
+with raises(dlite.DLiteStorageOpenError, dlite.DLiteUnknownError):
+    dlite.Instance.from_location("json", entitydir / "Invalid4.json")
+
+with raises(dlite.DLiteStorageOpenError, dlite.DLiteUnknownError):
+    dlite.Instance.from_location("json", entitydir / "Invalid5.json")
+
+with raises(dlite.DLiteStorageOpenError, dlite.DLiteUnknownError):
+    dlite.Instance.from_location("json", entitydir / "Invalid6.json")
+
+with raises(dlite.DLiteStorageOpenError, dlite.DLiteUnknownError):
+    dlite.Instance.from_location("json", entitydir / "Invalid7.json")
 
 
 # For issue #702
@@ -334,3 +323,30 @@
 PersonNew = dlite.get_instance("http://onto-ns.com/meta/0.1/PersonNew")
 assert PersonOld.props == PersonNew.props
 assert PersonOld.dimnames() == PersonNew.dimnames()
+
+
+# For issue #743
+Item = dlite.get_instance("http://onto-ns.com/meta/0.1/Item")
+item = Item(dimensions={"nf": 3}, properties={"name": "A", "f": [1, 2, 3]})
+try:
+    import pint
+except ModuleNotFoundError:
+    pass
+else:
+    item.q.f = pint.Quantity([0.1, 0.2, 0.3], "kHz")
+    assert item.f.tolist() == [100., 200., 300.]
+    item.q.f = ([0.5, 0.2, 0.3], "kHz")
+    assert item.f.tolist() == [500., 200., 300.]
+
+    item.set_quantity("f", [0, 3600, 3.6], "hour**-1")
+    assert item.f.tolist() == [0., 1., 0.001]
+    assert item.q.f.m.tolist() == [0., 1., 0.001]
+    assert item.q.f.to("1/hour").m.tolist() == [0, 3600, 3.6]
+
+    
+# For issue #750 - test instance_cast()
+with raises(dlite.DLiteTypeError):
+    dlite.instance_cast(inst, dlite.Metadata)
+castinst = dlite.instance_cast(inst.meta, dlite.Instance)
+assert type(castinst) == dlite.Instance
+assert type(dlite.instance_cast(castinst)) == dlite.Metadata
diff --git a/bindings/python/tests/test_factory.py b/bindings/python/tests/test_factory.py
old mode 100755
new mode 100644
index 044e2abb0..6c0187dd3
--- a/bindings/python/tests/test_factory.py
+++ b/bindings/python/tests/test_factory.py
@@ -7,7 +7,9 @@
 
 thisdir = Path(__file__).resolve().parent
 rootdir = thisdir.parent.parent.parent
+indir = thisdir / "input"
 outdir = thisdir / "output"
+entitydir = thisdir / "entities"
 
 class Person:
     def __init__(self, name, age, skills):
@@ -19,17 +21,15 @@ def __repr__(self):
         return "Person(%r, %r, %r)" % (self.name, self.age, list(self.skills))
 
 
-url = f"json://{thisdir}/Person.json"
-
 print("-- create: ExPerson")
-ExPerson = dlite.classfactory(Person, url=url)
+ExPerson = dlite.classfactory(Person, url=f"json://{entitydir}/Person.json")
 
 print("-- create: person1")
 person1 = Person("Jack Daniel", 42, ["distilling", "tasting"])
 
 print("-- create: person2")
 person2 = ExPerson("Jack Daniel", 42, ["distilling", "tasting"])
-person2.dlite_inst.save("json", f"{outdir}/persons.json", "mode=w")
+person2.dlite_inst.save("json", outdir / "persons.json", "mode=w")
 
 # Print json-representation of person2 using dlite
 print(person2.dlite_inst.asjson(indent=2))
diff --git a/bindings/python/tests/test_global_dlite_state.py b/bindings/python/tests/test_global_dlite_state.py
old mode 100755
new mode 100644
index 849fcc351..bae1b2ac5
--- a/bindings/python/tests/test_global_dlite_state.py
+++ b/bindings/python/tests/test_global_dlite_state.py
@@ -1,11 +1,16 @@
-import os
+from pathlib import Path
 import importlib
 import dlite
 from dlite import Instance
 
 from global_dlite_state_mod1 import assert_exists_in_module
 
-thisdir = os.path.abspath(os.path.dirname(__file__))
+
+thisdir = Path(__file__).absolute().parent
+outdir = thisdir / "output"
+indir = thisdir / "input"
+entitydir = thisdir / "entities"
+#dlite.storage_path.append(entitydir / "*.json")
 
 assert len(dlite.istore_get_uuids()) == 3  # 3 Hardcoded dlite instances
 
@@ -17,7 +22,7 @@
 # Must exist in imported dlite in different module (mod1)
 assert_exists_in_module(coll.uuid)
 
-url = "json://" + thisdir + "/MyEntity.json" + "?mode=r"
+url = f"json://{entitydir}/MyEntity.json" + "?mode=r"
 e = Instance.from_url(url)  # (2)
 assert len(dlite.istore_get_uuids()) == 3 + 2
 
@@ -32,7 +37,7 @@
 
 # Use compile and exec with dlite defined in globals
 env = globals().copy()
-filename = os.path.join(thisdir, "global_dlite_state_mod2.py")
+filename = thisdir / "global_dlite_state_mod2.py"
 
 with open(filename) as fd:
     exec(compile(fd.read(), filename, "exec"), env)
diff --git a/bindings/python/tests/test_iri.py b/bindings/python/tests/test_iri.py
old mode 100755
new mode 100644
index 0ce438a09..869faed68
--- a/bindings/python/tests/test_iri.py
+++ b/bindings/python/tests/test_iri.py
@@ -1,14 +1,14 @@
 #!/usr/bin/env python
-# -*- coding: utf-8 -*-
-import os
+from pathlib import Path
 
 import dlite
 
-thisdir = os.path.abspath(os.path.dirname(__file__))
 
-url = "json://" + thisdir + "/MyEntity.json" + "?mode=r"
+thisdir = Path(__file__).parent.absolute()
+entitydir = thisdir / "entities"
+dlite.storage_path.append(f"{entitydir}/*.json")
 
-E = dlite.Instance.from_url(url)
+E = dlite.Instance.from_url(url=f"json://{entitydir}/MyEntity.json?mode=r")
 E.iri = "http://emmo.info/emmo/EMMO_Physical"
 E.iri = None
 E.iri = "http://emmo.info/emmo/EMMO_Physical"
diff --git a/bindings/python/tests/test_mapping.py b/bindings/python/tests/test_mapping.py
index f58e61886..168f0b555 100755
--- a/bindings/python/tests/test_mapping.py
+++ b/bindings/python/tests/test_mapping.py
@@ -6,11 +6,12 @@
 
 # Configure search paths
 thisdir = Path(__file__).parent.absolute()
-dlite.storage_path.append(f"{thisdir}/*.json")
+entitydir = thisdir / "entities"
+dlite.storage_path.append(f"{entitydir}/*.json")
 dlite.python_mapping_plugin_path.append(f"{thisdir}/python-mapping-plugins")
 
 # Create an instance of Person
-Person = dlite.Instance.from_url(f"json:{thisdir}/Person.json?mode=r")
+Person = dlite.Instance.from_url(f"json:{entitydir}/Person.json?mode=r")
 person = Person(dimensions=[2])
 person.name = "Neil Armstrong"
 person.age = 39
diff --git a/bindings/python/tests/test_paths.py b/bindings/python/tests/test_paths.py
old mode 100755
new mode 100644
diff --git a/bindings/python/tests/test_postgresql1_write.py b/bindings/python/tests/test_postgresql1_write.py
old mode 100755
new mode 100644
index 88825ed20..62e9398cd
--- a/bindings/python/tests/test_postgresql1_write.py
+++ b/bindings/python/tests/test_postgresql1_write.py
@@ -9,6 +9,9 @@
 
 # Paths
 thisdir = Path(__file__).resolve().parent
+indir = thisdir / "input"
+outdir = thisdir / "output"
+entitydir = thisdir / "entities"
 
 
 def parse_pgconf():
@@ -54,14 +57,14 @@ def ping_server(server="localhost", port=5432, timeout=3):
 ping_server()
 
 # Add metadata to search path
-dlite.storage_path.append(f"{thisdir}/Person.json")
-dlite.storage_path.append(f"{thisdir}/SimplePerson.json")
+dlite.storage_path.append(f"{entitydir}/Person.json")
+dlite.storage_path.append(f"{entitydir}/SimplePerson.json")
 
 # Load dataset
 host, user, database, password = parse_pgconf()
 inst = dlite.Instance.from_location(
     "json",
-    f"{thisdir}/persons.json",
+    f"{indir}/persons.json",
     id="Cleopatra",
 )
 
diff --git a/bindings/python/tests/test_postgresql2_read.py b/bindings/python/tests/test_postgresql2_read.py
old mode 100755
new mode 100644
diff --git a/bindings/python/tests/test_pydantic.py b/bindings/python/tests/test_pydantic.py
old mode 100755
new mode 100644
diff --git a/bindings/python/tests/test_python_storage.py b/bindings/python/tests/test_python_storage.py
old mode 100755
new mode 100644
index 06477597e..6ad829702
--- a/bindings/python/tests/test_python_storage.py
+++ b/bindings/python/tests/test_python_storage.py
@@ -29,6 +29,8 @@
 
 # thisdir = os.path.abspath(os.path.dirname(__file__))
 thisdir = Path(__file__).resolve().parent
+outdir = thisdir / "output"
+entitydir = thisdir / "entities"
 
 
 def equal_rdf_files(path1, path2):
@@ -46,21 +48,18 @@ def equal_rdf_files(path1, path2):
 
 
 # Test JSON
-
-url = "json://" + os.path.join(thisdir, "Person.json")
-Person = dlite.Instance.from_url(url)
-
-person = Person(dimensions=[2])
+Person = dlite.Instance.from_url(f"json://{entitydir}/Person.json")
+person = Person(dims=[2])
 person.name = "Ada"
 person.age = 12.5
 person.skills = ["skiing", "jumping"]
 
 print("=== saving...")
-with dlite.Storage("json", "test.json", "mode=w") as s:
+with dlite.Storage("json", outdir / "test_python_storage.json", "mode=w") as s:
     s.save(person)
 
 print("=== loading...", person.uuid)
-with dlite.Storage("json", "test.json", "mode=r") as s:
+with dlite.Storage("json", outdir / "test_python_storage.json", "mode=r") as s:
     inst = s.load(id=person.uuid)
 
 
@@ -68,11 +67,11 @@ def equal_rdf_files(path1, path2):
 person2.name = "Berry"
 person2.age = 24.3
 person2.skills = ["eating", "sleeping", "reading"]
-with dlite.Storage("json://test.json") as s:
+with dlite.Storage(f"json://{outdir}/test_python_storage.json") as s:
     s.save(person2)
 
 
-s = dlite.Storage("json://test.json")
+s = dlite.Storage(f"json://{outdir}/test_python_storage.json")
 uuids = s.get_uuids()
 del s
 del uuids
diff --git a/bindings/python/tests/test_quantity.py b/bindings/python/tests/test_quantity.py
new file mode 100755
index 000000000..e15ca5240
--- /dev/null
+++ b/bindings/python/tests/test_quantity.py
@@ -0,0 +1,122 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+""" Unit Tests on the quantity module """
+try:
+    import pint
+except ImportError:
+    exit(44)  # skip test
+
+from pathlib import Path
+import numpy as np
+import dlite
+from dlite.testutils import raises
+from dlite.quantity import get_quantity_helper
+
+thisdir = Path(__file__).absolute().parent
+entitydir = thisdir / "entities"
+dlite.storage_path.append(f'{entitydir}/*.json')
+
+Person = dlite.get_instance("http://onto-ns.com/meta/0.1/PersonQuantity")
+
+
+def assertQuantity(q, m, u):
+    if np.isnan(m):
+        assert np.isnan(q.m)
+    else:
+        assert q.m == m
+    assert q.u == pint.Unit(u)
+
+
+def assertParse(h, value, m, u):
+    assertQuantity(h.parse(value), m, u)
+
+
+# Test the quantity helper methods: names, units, values, items
+inst = Person(dimensions={'N': 3})
+names = inst.q.names()
+assert ['age', 'length', 'temperature', 'stress'] == names
+assert ['years', 'cm', 'degC', 'MPa'] == inst.q.units()
+inst.q.update(
+    temperature='20 degC',
+    length=(22.3, 'mm'),
+    age='1461 days',
+    stress=([1, 2, 3], 'GPa')
+)
+values = inst.q.values()
+assertQuantity(values[0], 4.0, 'year')
+assertQuantity(values[1], 2.23, 'cm')
+assertQuantity(values[2], 20.0, 'degC')
+assert np.allclose(values[3].m, [1000.0, 2000.0, 3000.0])
+assert values[3].u == pint.Unit('MPa')
+items = list(inst.q.items())
+names = [item[0] for item in items]
+assert ['age', 'length', 'temperature', 'stress'] == names
+
+# Test the quantity helper for dlite instances
+q = inst.q
+# raise a ValueError if property does not exists
+with raises(ValueError):
+    q.unknown
+# raise a TypeError if the property has not quantity type
+with raises(TypeError):
+    inst.q.name
+# modify the value of the property "temperature"
+inst.temperature = 37.0
+# get temperature as quantity
+assertQuantity(q.temperature, 37.0, 'degC')
+assertQuantity(inst.get_quantity('temperature'), 37.0, 'degC')
+# use the quantity to scale the magnitude
+assertQuantity(q.temperature.to('K'), 273.15 + 37.0, 'K')
+
+# Test to set quantity into dlite instance
+inst.name = 'Hello'
+assert inst.name == 'Hello'
+with raises(TypeError):
+    inst.q.name = 3
+inst.q.temperature = '20 degC'
+assertQuantity(inst.q.temperature, 20, 'degC')
+inst.q['length'] = (10, 'mm')
+assertQuantity(inst.q.length, 1, 'cm')
+inst.q.age = inst.q.quantity(1461, 'days')
+assertQuantity(inst.q.age, 4, 'years')
+surface = inst.q.length ** 2
+assertQuantity(surface, 1, 'cm²')
+force = inst.q.quantity([1, 2, 3], 'kN')
+inst.q.stress = force / surface
+assert np.allclose([10, 20, 30], inst.q.stress.m)
+assert pint.Unit('MPa') == inst.q.stress.u
+inst.set_quantity('age', 365.25, 'days')
+assertQuantity(inst.get_quantity('age'), 1.0, 'year')
+
+# test parse expression
+p = get_quantity_helper(None)
+assertParse(p, '15', 15, '')
+assertParse(p, '273.15K', 273.15, 'K')
+assertParse(p, 'degC', 1.0, 'degC')
+assertParse(p, 'degC/cm', 1.0, 'degC/cm')
+assertParse(p, '', np.nan, '')
+assertParse(p, None, np.nan, '')
+assertParse(p, 'None', np.nan, '')
+assertParse(p, 'none', np.nan, '')
+assertParse(p, 'Text', np.nan, '')
+assertParse(p, 'degC/28', 1 / 28, 'degC')
+assertParse(p, '3.0', 3.0, '')
+assertParse(p, '10cm', 10, 'cm')
+assertParse(p, (10.0, 'km'), 10, 'km')
+assertParse(p, 10.0, 10.0, '')
+arr = [1.2, 2.3, 3.4]
+q = p.quantity(arr, 'nm')
+assert np.allclose(arr, q.m)
+assert q.u == pint.Unit('nm')
+
+# test unit "percent"
+assertParse(p, '1.1', 1.1, '')
+assertParse(p, '11%', 11, 'percent')
+q = p.parse('11%').to('')
+assert q.m == 0.11
+q = p.parse('11%').to('percent')
+assert q.m == 11
+q = p.parse('101 percent').to('')
+assert q.m == 1.01
+q = p.parse('101 1/%').to('')
+assert q.m == 10100.0
diff --git a/bindings/python/tests/test_rdf.py b/bindings/python/tests/test_rdf.py
old mode 100755
new mode 100644
index ab35e88f5..485ab633c
--- a/bindings/python/tests/test_rdf.py
+++ b/bindings/python/tests/test_rdf.py
@@ -12,9 +12,10 @@
 
 
 thisdir = Path(__file__).resolve().parent
+indir = thisdir / "input"
 
 id = "http://onto-ns.com/data#my_test_instance"
-inst = from_rdf(thisdir / "rdf.ttl", id=id)
+inst = from_rdf(indir / "rdf.ttl", id=id)
 
 # Serialise `inst` to string `s`
 s = to_rdf(
@@ -25,6 +26,6 @@
 )
 
 # Check that content matches original serialisation
-with open(thisdir / "rdf.ttl", "r") as f:
+with open(indir / "rdf.ttl", "r") as f:
     orig = f.read()
 assert s == orig
diff --git a/bindings/python/tests/test_ref_type.py b/bindings/python/tests/test_ref_type.py
old mode 100755
new mode 100644
index d970c2f3e..5d7e62c5b
--- a/bindings/python/tests/test_ref_type.py
+++ b/bindings/python/tests/test_ref_type.py
@@ -4,8 +4,9 @@
 
 
 thisdir = Path(__file__).resolve().parent
+indir = thisdir / "input"
 
-dlite.storage_path.append(thisdir / "test_ref_type.json")
+dlite.storage_path.append(indir / "test_ref_type.json")
 
 Top = dlite.get_instance("http://onto-ns.com/meta/0.1/Top")
 Middle = dlite.get_instance("http://onto-ns.com/meta/0.1/Middle")
diff --git a/bindings/python/tests/test_relation.py b/bindings/python/tests/test_relation.py
new file mode 100644
index 000000000..34db39817
--- /dev/null
+++ b/bindings/python/tests/test_relation.py
@@ -0,0 +1,18 @@
+import dlite
+from dlite.testutils import raises
+
+
+rel1 = dlite.Relation("s1", "p1", "o1")
+assert (rel1.s, rel1.p, rel1.o) == ("s1", "p1", "o1")
+assert rel1.d == None
+
+rel2 = dlite.Relation("s2", "p2", "o2", "d2")
+assert (rel2.s, rel2.p, rel2.o) == ("s2", "p2", "o2")
+assert rel2.d == "d2"
+
+rel = dlite.Relation("s", "p", "3")
+with raises(TypeError):
+    rel = dlite.Relation("s", "p", 3)
+
+with raises(TypeError):
+    rel = dlite.Relation("s", "p", "o", "d", "id", "non-expected-argument")
diff --git a/bindings/python/tests/test_storage.py b/bindings/python/tests/test_storage.py
index 7c7be74ba..6a6f2ef58 100755
--- a/bindings/python/tests/test_storage.py
+++ b/bindings/python/tests/test_storage.py
@@ -1,6 +1,7 @@
 #!/usr/bin/env python
-# -*- coding: utf-8 -*-
-import os
+from pathlib import Path
+
+import numpy as np
 
 import dlite
 from dlite.testutils import raises
@@ -12,10 +13,13 @@
     HAVE_YAML = False
 
 
-thisdir = os.path.abspath(os.path.dirname(__file__))
-outdir = f"{thisdir}/output"
+thisdir = Path(__file__).absolute().parent
+outdir = thisdir / "output"
+indir = thisdir / "input"
+entitydir = thisdir / "entities"
+dlite.storage_path.append(entitydir / "*.json")
 
-url = "json://" + thisdir + "/MyEntity.json"
+url = f"json://{entitydir}/MyEntity.json"
 
 
 # Load metadata (i.e. an instance of meta-metadata) from url
@@ -34,9 +38,20 @@
 # Create an instance
 inst = myentity(dimensions=[2, 3], id="my-data")
 inst["a-bool-array"] = True, False
+inst["a-relation"] = "subj", "pred", "obj"
+inst["a-relation-array"] = [
+    ("s1", "p1", "o1"),
+    ("s2", "p2", "o2"),
+]
+assert np.all(inst["a-bool-array"] == [True, False])
+assert inst["a-relation"] == dlite.Relation("subj", "pred", "obj")
+assert np.all(inst["a-relation-array"] == [
+    dlite.Relation("s1", "p1", "o1"),
+    dlite.Relation("s2", "p2", "o2"),
+])
 
 # Test Storage.save()
-with dlite.Storage("json", f"{outdir}/tmp.json", "mode=w") as s:
+with dlite.Storage("json", outdir / "test_storage_tmp.json", "mode=w") as s:
     s.save(inst)
 
 # Test query
@@ -44,18 +59,17 @@
 
 # Test json
 print("--- testing json")
-myentity.save(f"json://{outdir}/myentity.json?mode=w")
-inst.save(f"json://{outdir}/inst.json?mode=w")
+myentity.save(f"json://{outdir}/test_storage_myentity.json?mode=w")
+inst.save(f"json://{outdir}/test_storage_inst.json?mode=w")
 del inst
-inst = dlite.Instance.from_url(f"json://{outdir}/inst.json#my-data")
-
+inst = dlite.Instance.from_url(f"json://{outdir}/test_storage_inst.json#my-data")
 
 # Test yaml
 if HAVE_YAML:
     print('--- testing yaml')
-    inst.save(f"yaml://{outdir}/inst.yaml?mode=w")
+    inst.save(f"yaml://{outdir}/test_storage_inst.yaml?mode=w")
     del inst
-    inst = dlite.Instance.from_url(f"yaml://{outdir}/inst.yaml#my-data")
+    inst = dlite.Instance.from_url(f"yaml://{outdir}/test_storage_inst.yaml#my-data")
 
     # test help()
     expected = """\
@@ -74,7 +88,9 @@
             - `single`: Whether the input is assumed to be in single-entity form.
                 If "auto" (default) the form will be inferred automatically.
 """
-    s = dlite.Storage("yaml", f"{outdir}/inst.yaml", options="mode=a")
+    s = dlite.Storage(
+        "yaml", outdir / "test_storage_inst.yaml", options="mode=a"
+    )
     assert s.help().strip() == expected.strip()
 
     # Test delete()
@@ -95,13 +111,27 @@
 # Test rdf
 try:
     print("--- testing rdf")
-    inst.save("rdf:db.xml?mode=w;store=file;filename=inst.ttl;format=turtle")
-except dlite.DLiteError:
+    inst.save(
+        f"rdf:{outdir}/db.xml?mode=w;"
+        "store=file;"
+        f"filename={outdir}/test_storage_inst.ttl;"
+        "format=turtle"
+    )
+except (dlite.DLiteUnsupportedError, dlite.DLiteStorageOpenError):
     print("    skipping rdf test")
 else:
     # del inst
     # FIXME: read from inst.ttl not db.xml
-    inst3 = dlite.Instance.from_url("rdf://db.xml#my-data")
+    inst3 = dlite.Instance.from_url("rdf://{outdir}/db.xml#my-data")
+
+    with raises(dlite.DLiteIOError):
+        inst.save(
+            f"rdf://{outdir}/db.xml?mode=w;"
+            "store=file;"
+            f"filename={outdir}/non-existing/test_storage_inst.ttl;"
+            "format=turtle"
+        )
+
 
 
 # Tests for issue #587
@@ -113,8 +143,7 @@
     inst.to_bytes("json")
 
 
-dlite.storage_path.append(thisdir)
-s = dlite.Storage("json", f"{thisdir}/persons.json", "mode=r")
+s = dlite.Storage("json", f"{indir}/persons.json", "mode=r")
 
 
 #assert len(list(s.instances())) == 5
diff --git a/bindings/python/tests/test_storage_path.py b/bindings/python/tests/test_storage_path.py
old mode 100755
new mode 100644
diff --git a/bindings/python/tests/test_transaction.py b/bindings/python/tests/test_transaction.py
old mode 100755
new mode 100644
index 87509d422..88c75b824
--- a/bindings/python/tests/test_transaction.py
+++ b/bindings/python/tests/test_transaction.py
@@ -1,6 +1,7 @@
 from pathlib import Path
 
 import dlite
+from dlite.testutils import raises
 
 
 # Configure paths
@@ -40,19 +41,12 @@
 
 frozen = person.get_snapshot(1)
 assert frozen.is_frozen() == True
-try:
+
+with raises(dlite.DLiteUnsupportedError):
     frozen.age = 77
-except dlite.DLiteError as exc:
-    assert str(exc) == "frozen instance does not support attribute assignment"
-else:
-    raise Exception("frozen instance should not accept attribute assignment")
 
-try:
+with raises(ValueError):
     frozen.skills[0] = "skiing"
-except ValueError:
-    pass
-else:
-    raise Exception("frozen property array should not accept assignment")
 
 # Create branch
 inst = person.copy()
diff --git a/bindings/python/tests/test_utils.py b/bindings/python/tests/test_utils.py
old mode 100755
new mode 100644
index 8fddbd654..fd5d1ccff
--- a/bindings/python/tests/test_utils.py
+++ b/bindings/python/tests/test_utils.py
@@ -14,9 +14,10 @@
 
 
 thisdir = Path(__file__).absolute().parent
-dlite.storage_path.append(thisdir / "input" / "*.json")
+entitydir = thisdir / "entities"
+dlite.storage_path.append(entitydir / "*.json")
 
-with open(thisdir / "Person.json", "rt") as f:
+with open(entitydir / "Person.json", "rt") as f:
     d = json.load(f)
 Person = dlite.utils.instance_from_dict(d)
 
@@ -67,9 +68,7 @@
 print(inst)
 
 
-url = "json://" + os.path.join(thisdir, "Person.json")
-Person = dlite.Instance.from_url(url)
-
+Person = dlite.Instance.from_url(f"json://{entitydir}/Person.json")
 person = Person([2])
 person.name = "Ada"
 person.age = 12.5
diff --git a/bindings/python/testutils.py b/bindings/python/testutils.py
index b60da1309..0e09156a9 100644
--- a/bindings/python/testutils.py
+++ b/bindings/python/testutils.py
@@ -49,6 +49,7 @@ def __exit__(self, exc_type, exc_value, tb):
         # was raised
         for exc in self.exceptions:
             if issubclass(exc_type, exc):
+                dlite.errclr()
                 return True
 
         raise UnexpectedExceptionError(
diff --git a/bindings/python/triplestore/test_units.py b/bindings/python/triplestore/test_units.py
old mode 100755
new mode 100644
diff --git a/doc/contributors_guide/documentation_testing.md b/doc/contributors_guide/documentation_testing.md
old mode 100755
new mode 100644
diff --git a/src/tests/.gitignore b/src/tests/.gitignore
old mode 100755
new mode 100644
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
old mode 100755
new mode 100644
index 47bb42cbd..6bbe9b8d0
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -13,6 +13,7 @@ set(tests
   test_dlitebson
   test_metamodel
   test_store
+  test_triple
   test_triplestore
   test_collection
   test_schemas
diff --git a/src/tests/Chemistry.bson b/src/tests/Chemistry.bson
old mode 100755
new mode 100644
diff --git a/src/tests/alloys.json b/src/tests/alloys.json
old mode 100755
new mode 100644
diff --git a/src/tests/array.json b/src/tests/array.json
old mode 100755
new mode 100644
diff --git a/src/tests/coll.json b/src/tests/coll.json
old mode 100755
new mode 100644
diff --git a/src/tests/mappings/CMakeLists.txt b/src/tests/mappings/CMakeLists.txt
old mode 100755
new mode 100644
diff --git a/src/tests/mappings/ent1.json b/src/tests/mappings/ent1.json
old mode 100755
new mode 100644
diff --git a/src/tests/mappings/ent2.json b/src/tests/mappings/ent2.json
old mode 100755
new mode 100644
diff --git a/src/tests/mappings/ent3.json b/src/tests/mappings/ent3.json
old mode 100755
new mode 100644
diff --git a/src/tests/mappings/instances.json b/src/tests/mappings/instances.json
old mode 100755
new mode 100644
diff --git a/src/tests/mappings/mapA.c b/src/tests/mappings/mapA.c
old mode 100755
new mode 100644
diff --git a/src/tests/minunit/MIT-LICENSE.txt b/src/tests/minunit/MIT-LICENSE.txt
old mode 100755
new mode 100644
diff --git a/src/tests/minunit/README.md b/src/tests/minunit/README.md
old mode 100755
new mode 100644
diff --git a/src/tests/minunit/ci/Makefile b/src/tests/minunit/ci/Makefile
old mode 100755
new mode 100644
diff --git a/src/tests/minunit/ci/check_example.bat b/src/tests/minunit/ci/check_example.bat
old mode 100755
new mode 100644
diff --git a/src/tests/minunit/minunit.h b/src/tests/minunit/minunit.h
old mode 100755
new mode 100644
diff --git a/src/tests/minunit/minunit_example.c b/src/tests/minunit/minunit_example.c
old mode 100755
new mode 100644
diff --git a/src/tests/python/CMakeLists.txt b/src/tests/python/CMakeLists.txt
old mode 100755
new mode 100644
diff --git a/src/tests/python/plugin1.py b/src/tests/python/plugin1.py
old mode 100755
new mode 100644
diff --git a/src/tests/python/test_pyembed.c b/src/tests/python/test_pyembed.c
old mode 100755
new mode 100644
diff --git a/src/tests/python/test_python_mapping.c b/src/tests/python/test_python_mapping.c
old mode 100755
new mode 100644
diff --git a/src/tests/test-data.json b/src/tests/test-data.json
old mode 100755
new mode 100644
diff --git a/src/tests/test-entity.json b/src/tests/test-entity.json
old mode 100755
new mode 100644
diff --git a/src/tests/test-read-data.json b/src/tests/test-read-data.json
old mode 100755
new mode 100644
diff --git a/src/tests/test_arrays.c b/src/tests/test_arrays.c
old mode 100755
new mode 100644
diff --git a/src/tests/test_collection.c b/src/tests/test_collection.c
old mode 100755
new mode 100644
index fa34fbd1f..5bcc2dd11
--- a/src/tests/test_collection.c
+++ b/src/tests/test_collection.c
@@ -6,6 +6,7 @@
 #include <string.h>
 
 #include "minunit/minunit.h"
+#include "utils/err.h"
 #include "dlite.h"
 #include "dlite-collection.h"
 #include "dlite-macros.h"
@@ -26,15 +27,19 @@ MU_TEST(test_collection_add_relation)
   mu_assert_int_eq(0, dlite_instance_get_dimension_size((DLiteInstance *)coll,
 							"nrelations"));
 
-  mu_check(!dlite_collection_add_relation(coll, "dog", "is_a", "animal"));
-  mu_check(!dlite_collection_add_relation(coll, "cat", "is_a", "animal"));
-  mu_check(!dlite_collection_add_relation(coll, "terrier", "is_a", "dog"));
+  mu_check(!dlite_collection_add_relation(coll, "dog", "is_a", "animal",
+                                          NULL));
+  mu_check(!dlite_collection_add_relation(coll, "cat", "is_a", "animal",
+                                          NULL));
+  mu_check(!dlite_collection_add_relation(coll, "terrier", "is_a", "dog",
+                                          NULL));
   mu_assert_int_eq(3, dlite_instance_get_dimension_size((DLiteInstance *)coll,
 							"nrelations"));
   mu_assert_int_eq(3, coll->nrelations);
 
   /* dublicate... */
-  mu_check(!dlite_collection_add_relation(coll, "terrier", "is_a", "dog"));
+  mu_check(!dlite_collection_add_relation(coll, "terrier", "is_a", "dog",
+                                          NULL));
   mu_assert_int_eq(3, dlite_instance_get_dimension_size((DLiteInstance *)coll,
 							"nrelations"));
 }
@@ -42,8 +47,8 @@ MU_TEST(test_collection_add_relation)
 
 MU_TEST(test_collection_remove_relations)
 {
-  mu_assert_int_eq(2, dlite_collection_remove_relations(coll, NULL,
-							"is_a", "animal"));
+  mu_assert_int_eq(2, dlite_collection_remove_relations(coll, NULL, "is_a",
+                                                        "animal", NULL));
   mu_assert_int_eq(1, dlite_instance_get_dimension_size((DLiteInstance *)coll,
 							"nrelations"));
 }
@@ -53,26 +58,94 @@ MU_TEST(test_collection_find)
 {
   const DLiteRelation *r;
   DLiteCollectionState state;
-  int nanimals=0;
-
-  mu_check(!dlite_collection_add_relation(coll, "dog", "is_a", "animal"));
-  mu_check(!dlite_collection_add_relation(coll, "cat", "is_a", "animal"));
-  mu_check(!dlite_collection_add_relation(coll, "terrier", "is_a", "dog"));
-  mu_check(!dlite_collection_add_relation(coll, "car", "is_a", "vehicle"));
-  mu_assert_int_eq(4, dlite_instance_get_dimension_size((DLiteInstance *)coll,
+  int nanimals=0, n=0;
+
+  mu_check(!dlite_collection_add_relation(coll, "dog", "is_a", "animal",
+                                          NULL));
+  mu_check(!dlite_collection_add_relation(coll, "cat", "is_a", "animal",
+                                          NULL));
+  mu_check(!dlite_collection_add_relation(coll, "terrier", "is_a", "dog",
+                                          NULL));
+  mu_check(!dlite_collection_add_relation(coll, "car", "is_a", "vehicle",
+                                          NULL));
+  mu_check(!dlite_collection_add_relation(coll, "cat", "has_color", "black",
+                                          "xsd:string"));
+  mu_check(!dlite_collection_add_relation(coll, "car", "has_color", "black",
+                                          "xsd:string"));
+  mu_assert_int_eq(6, dlite_instance_get_dimension_size((DLiteInstance *)coll,
 							"nrelations"));
 
   dlite_collection_init_state(coll, &state);
+  printf("\nRelations:\n");
+  while ((r = dlite_collection_find(coll, &state, NULL, NULL, NULL, NULL)))
+    printf("  %-10s %-10s %-10s %s\n", r->s, r->p, r->o, r->d);
+
+  dlite_collection_reset_state(&state);
   printf("\nAnimals:\n");
-  while ((r = dlite_collection_find(coll, &state, NULL, "is_a", "animal"))) {
+  while ((r = dlite_collection_find(coll, &state, NULL, "is_a", "animal",
+                                    NULL))) {
     printf("  %s\n", r->s);
     nanimals++;
   }
   mu_assert_int_eq(2, nanimals);
+
+  dlite_collection_reset_state(&state);
+  n = 0;
+  while (dlite_collection_find(coll, &state, NULL, NULL, NULL, "xsd:int")) n++;
+  mu_assert_int_eq(0, n);
+
+  dlite_collection_reset_state(&state);
+  n = 0;
+  while (dlite_collection_find(coll, &state, NULL, NULL, NULL, "xsd:string"))
+    n++;
+  mu_assert_int_eq(2, n);
+
+  dlite_collection_reset_state(&state);
+  n = 0;
+  while (dlite_collection_find(coll, &state, NULL, NULL, "black", "xsd:string"))
+    n++;
+  mu_assert_int_eq(2, n);
+
+  dlite_collection_reset_state(&state);
+  n = 0;
+  while (dlite_collection_find(coll, &state, NULL, NULL, "red", "xsd:string"))
+    n++;
+  mu_assert_int_eq(0, n);
+
+  dlite_collection_reset_state(&state);
+  n = 0;
+  while (dlite_collection_find(coll, &state, "car", NULL, NULL, "xsd:string"))
+    n++;
+  mu_assert_int_eq(1, n);
+
+  dlite_collection_reset_state(&state);
+  n = 0;
+  while (dlite_collection_find(coll, &state, "car", NULL, NULL, ""))
+    n++;
+  mu_assert_int_eq(1, n);
+
   dlite_collection_deinit_state(&state);
 }
 
 
+MU_TEST(test_collection_value)
+{
+  mu_assert_string_eq("terrier", dlite_collection_value(coll, NULL, "is_a",
+                                                        "dog", NULL, NULL, 0));
+  mu_assert_string_eq("dog", dlite_collection_value(coll, NULL, "is_a",
+                                                    "animal", NULL, NULL, 1));
+
+ ErrTry:
+  mu_assert_string_eq(NULL, dlite_collection_value(coll, NULL, "is_a",
+                                                   "animal", NULL, NULL, 0));
+  mu_assert_string_eq(NULL, dlite_collection_value(coll, NULL, "is_a",
+                                                   "mammal", NULL, NULL, 0));
+ ErrCatch(dliteSearchError):
+  break;
+ ErrEnd;
+}
+
+
 MU_TEST(test_collection_add)
 {
   char *path, *uri;
@@ -165,29 +238,30 @@ MU_TEST(test_collection_load)
 {
   DLiteCollection *coll2;
   char *collpath = STRINGIFY(dlite_SOURCE_DIR) "/src/tests/coll.json";
-  DLiteStoragePathIter *iter;
-  const char *path;
+  //DLiteStoragePathIter *iter;
+  //const char *path;
 
   dlite_storage_paths_append(STRINGIFY(dlite_SOURCE_DIR) "/src/tests/*.json");
 
-  printf("\n\nStorage paths:\n");
-  iter = dlite_storage_paths_iter_start();
-  while ((path = dlite_storage_paths_iter_next(iter)))
-    printf("  - %s\n", path);
-  printf("\n");
-  dlite_storage_paths_iter_stop(iter);
+  //printf("\n\nStorage paths:\n");
+  //iter = dlite_storage_paths_iter_start();
+  //while ((path = dlite_storage_paths_iter_next(iter)))
+  //  printf("  - %s\n", path);
+  //printf("\n");
+  //dlite_storage_paths_iter_stop(iter);
 
   FILE *fp = fopen(collpath, "r");
   coll2 = (DLiteCollection *)
     dlite_json_fscan(fp, NULL, "http://onto-ns.com/meta/0.1/Collection");
   fclose(fp);
-  printf("\n\n--- coll2: %p ---\n", (void *)coll2);
-  dlite_json_print((DLiteInstance *)coll2);
-  printf("----------------------\n");
+  //printf("\n\n--- coll2: %p ---\n", (void *)coll2);
+  //dlite_json_print((DLiteInstance *)coll2);
+  //printf("----------------------\n");
+
   const DLiteInstance *inst = dlite_collection_get(coll2, "inst");
-  printf("\n--- inst: %p ---\n", (void *)inst);
-  dlite_json_print((DLiteInstance *)inst);
-  printf("----------------------\n");
+  //printf("\n--- inst: %p ---\n", (void *)inst);
+  //dlite_json_print((DLiteInstance *)inst);
+  //printf("----------------------\n");
 
   dlite_instance_decref((DLiteInstance *)inst);
   dlite_collection_decref(coll2);
@@ -197,6 +271,8 @@ MU_TEST(test_collection_load)
 MU_TEST(test_collection_free)
 {
   dlite_collection_decref(coll);
+
+  dlite_finalize();  // for checking memory leaks
 }
 
 
@@ -212,8 +288,8 @@ MU_TEST_SUITE(test_suite)
 
   MU_RUN_TEST(test_collection_add_relation);
   MU_RUN_TEST(test_collection_remove_relations);
-
   MU_RUN_TEST(test_collection_find);
+  MU_RUN_TEST(test_collection_value);
 
   MU_RUN_TEST(test_collection_add);
   MU_RUN_TEST(test_collection_get);
diff --git a/src/tests/test_datamodel.c b/src/tests/test_datamodel.c
old mode 100755
new mode 100644
diff --git a/src/tests/test_dlitebson.c b/src/tests/test_dlitebson.c
old mode 100755
new mode 100644
diff --git a/src/tests/test_entity.c b/src/tests/test_entity.c
old mode 100755
new mode 100644
index 0a7d11f74..aa1851bda
--- a/src/tests/test_entity.c
+++ b/src/tests/test_entity.c
@@ -379,7 +379,8 @@ MU_TEST(test_instance_get_hash)
   mu_assert_string_eq(hash, gethash(s, (DLiteInstance *)inst->meta->meta));
 
   // basic metadata schema
-  hash = "fc7a634b6f98306b04bfd44f94bddb1d2a29970e8c0314e4d6cc977e8d6920da";
+  //hash = "fc7a634b6f98306b04bfd44f94bddb1d2a29970e8c0314e4d6cc977e8d6920da";
+  hash = "0e48cc58a1298ef87c9826e4978923c26e5369c1f87fc6216c67de358d41596b";
   mu_assert_string_eq(hash, gethash(s, (DLiteInstance *)inst->meta->meta->meta));
 
   // basic metadata schema
diff --git a/src/tests/test_getuuid.c b/src/tests/test_getuuid.c
old mode 100755
new mode 100644
diff --git a/src/tests/test_json.c b/src/tests/test_json.c
old mode 100755
new mode 100644
index c908cc247..cc82aeb14
--- a/src/tests/test_json.c
+++ b/src/tests/test_json.c
@@ -105,9 +105,10 @@ MU_TEST(test_sprint)
 
   /* Tests for proper quoting */
   DLiteCollection *coll = dlite_collection_create(NULL);
-  dlite_collection_add_relation(coll, "s", "p", "\"o\"");
+  dlite_collection_add_relation(coll, "s", "p", "\"o\"", NULL);
   m = dlite_json_sprint(buf, sizeof(buf), (DLiteInstance *)coll, 2, 0);
-  const DLiteRelation *rel = dlite_collection_find_first(coll, "s", "p", NULL);
+  const DLiteRelation *rel = dlite_collection_find_first(coll, "s", "p", NULL,
+                                                         NULL);
   mu_assert_string_eq("\"o\"", rel->o);
   dlite_instance_decref((DLiteInstance *)coll);
   //printf("\n--------------------------------------------------------\n");
diff --git a/src/tests/test_json_entity.c b/src/tests/test_json_entity.c
old mode 100755
new mode 100644
diff --git a/src/tests/test_mapping.c b/src/tests/test_mapping.c
old mode 100755
new mode 100644
diff --git a/src/tests/test_metamodel.c b/src/tests/test_metamodel.c
old mode 100755
new mode 100644
diff --git a/src/tests/test_misc.c b/src/tests/test_misc.c
old mode 100755
new mode 100644
diff --git a/src/tests/test_postgresql_storage.c b/src/tests/test_postgresql_storage.c
old mode 100755
new mode 100644
diff --git a/src/tests/test_property.c b/src/tests/test_property.c
old mode 100755
new mode 100644
index d59a5367e..30e207590
--- a/src/tests/test_property.c
+++ b/src/tests/test_property.c
@@ -90,12 +90,13 @@ MU_TEST(test_print)
                       "\"shape\": [\"M\", \"N\"], \"unit\": \"m\", "
                       "\"description\": \"about x...\"}", s);
 
-  DLiteRelation rel = {"subject", "predicate", "object", "id"};
+  DLiteRelation rel = {"subject", "predicate", "object", "datatype", NULL};
   prop.type = dliteRelation;
   prop.size = sizeof(rel);
   m = dlite_property_aprint(&s, &size, n, &rel, &prop, NULL, 0, -2, 0);
-  mu_assert_int_eq(34, m);
-  mu_assert_string_eq("[\"subject\", \"predicate\", \"object\"]", s);
+  mu_assert_int_eq(46, m);
+  mu_assert_string_eq("[\"subject\", \"predicate\", \"object\", \"datatype\"]",
+                      s);
 
   free(s);
 }
diff --git a/src/tests/test_ref.c b/src/tests/test_ref.c
old mode 100755
new mode 100644
diff --git a/src/tests/test_ref.json b/src/tests/test_ref.json
old mode 100755
new mode 100644
diff --git a/src/tests/test_schemas.c b/src/tests/test_schemas.c
old mode 100755
new mode 100644
diff --git a/src/tests/test_storage.c b/src/tests/test_storage.c
old mode 100755
new mode 100644
diff --git a/src/tests/test_storage_lookup.c b/src/tests/test_storage_lookup.c
old mode 100755
new mode 100644
diff --git a/src/tests/test_store.c b/src/tests/test_store.c
old mode 100755
new mode 100644
diff --git a/src/tests/test_triple.c b/src/tests/test_triple.c
new file mode 100644
index 000000000..64ecafb29
--- /dev/null
+++ b/src/tests/test_triple.c
@@ -0,0 +1,77 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include "utils/session.h"
+#include "minunit/minunit.h"
+#include "dlite-misc.h"
+#include "triple.h"
+
+
+
+MU_TEST(test_set)
+{
+  Triple t;
+  triple_set(&t, "book", "is-a", "thing", NULL, NULL);
+  mu_assert_string_eq("book", t.s);
+  mu_assert_string_eq("is-a", t.p);
+  mu_assert_string_eq("thing", t.o);
+  mu_assert_string_eq(NULL, t.d);
+  mu_assert_string_eq("e86ddacd5fd2f3f8f46543fc8096eab96a12c440", t.id);
+
+  triple_reset(&t, "subject", "predicate", "object", "datatype", NULL);
+  mu_assert_string_eq("subject", t.s);
+  mu_assert_string_eq("predicate", t.p);
+  mu_assert_string_eq("object", t.o);
+  mu_assert_string_eq("datatype", t.d);
+  triple_clean(&t);
+}
+
+
+MU_TEST(test_get_id)
+{
+  char *id;
+  Triple t = {"s", "p", "o", "@en", NULL};
+  id = triple_get_id(NULL, t.s, t.p, t.o, t.d);
+  mu_assert_string_eq("fac793e0cf9731b05a1554d16f834f03bbfe8306", id);
+  free(id);
+}
+
+
+MU_TEST(test_copy)
+{
+  Triple t = {"s", "p", "o", "@en", NULL}, t2, *tp;
+  tp = triple_copy(&t2, &t);
+  mu_assert_ptr_eq(tp, &t2);
+  mu_assert_string_eq("s", t2.s);
+  mu_assert_string_eq("p", t2.p);
+  mu_assert_string_eq("o", t2.o);
+  mu_assert_string_eq("@en", t2.d);
+  mu_assert_string_eq(NULL, t2.id);
+  triple_clean(&t2);
+}
+
+
+MU_TEST(test_finalize)
+{
+  dlite_finalize();  // for memory checking
+}
+
+
+/***********************************************************************/
+
+MU_TEST_SUITE(test_suite)
+{
+  MU_RUN_TEST(test_set);
+  MU_RUN_TEST(test_get_id);
+  MU_RUN_TEST(test_copy);
+  MU_RUN_TEST(test_finalize);
+}
+
+
+int main()
+{
+  MU_RUN_SUITE(test_suite);
+  MU_REPORT();
+  return (minunit_fail) ? 1 : 0;
+}
diff --git a/src/tests/test_triplestore.c b/src/tests/test_triplestore.c
old mode 100755
new mode 100644
index 1f8809997..4877daa2a
--- a/src/tests/test_triplestore.c
+++ b/src/tests/test_triplestore.c
@@ -2,8 +2,10 @@
 #include <stdio.h>
 #include <errno.h>
 
+#include "utils/err.h"
 #include "utils/session.h"
 #include "minunit/minunit.h"
+#include "dlite-misc.h"
 #include "triplestore.h"
 
 
@@ -16,36 +18,33 @@ MU_TEST(test_create)
   errno = 0;
 }
 
-MU_TEST(test_triple)
-{
-  Triple t;
-  char *id;
-  triple_set(&t, "book", "is-a", "thing", NULL);
-  id = triple_get_id(NULL, t.s, t.p, t.o);
-  mu_assert_string_eq("e86ddacd5fd2f3f8f46543fc8096eab96a12c440", id);
-  triple_clean(&t);
-  free(id);
-}
 
 MU_TEST(test_add)
 {
   Triple t[] = {
-    {"book", "is-a", "thing", NULL},
-    {"table", "is-a", "thing", NULL},
-    {"book", "is-ontop-of", "table", NULL},
-    {"write", "is-a", "action", NULL},
-    {"walk", "is-a", "action", NULL},
-    {"write", "is-a", "action", NULL}  /* dublicate */
+    {"book", "is-a", "thing", NULL, NULL},
+    {"table", "is-a", "thing", NULL, NULL},
+    {"table", "is-a", "furniture", NULL, NULL},
+    {"book", "is-ontop-of", "table", NULL, NULL},
+    {"write", "is-a", "action", NULL, NULL},
+    {"walk", "is-a", "action", NULL, NULL},
+    {"write", "is-a", "action", NULL, NULL}  /* dublicate */
   };
   size_t n = sizeof(t) / sizeof(t[0]);
 
-  mu_check(0 == triplestore_length(ts));
+  mu_assert_int_eq(0, triplestore_length(ts));
   mu_assert_int_eq(0, triplestore_add_triples(ts, t, n));
-  mu_check(5 == triplestore_length(ts));
-
-  mu_assert_int_eq(0, triplestore_add(ts, "read", "is-a", "action"));
-  mu_check(6 == triplestore_length(ts));
-
+  mu_assert_int_eq(6, triplestore_length(ts));
+
+  mu_assert_int_eq(0, triplestore_add_en(ts, "book", "has-title",
+                                         "The Infinite Book"));
+  mu_assert_int_eq(0, triplestore_add_uri(ts, "book", "has-weight",
+                                          "book-weight"));
+  mu_assert_int_eq(0, triplestore_add(ts, "book-weight", "has-value",
+                                      "0.6", "xsd:double"));
+  mu_assert_int_eq(0, triplestore_add(ts, "book-weight", "has-unit",
+                                      "kg", "xsd:string"));
+  mu_assert_int_eq(10, triplestore_length(ts));
 }
 
 
@@ -57,50 +56,145 @@ MU_TEST(test_next)
   triplestore_init_state(ts, &state);
   printf("\n");
   while ((t = triplestore_next(&state)))
-    printf("  %-11s %-11s %-11s %s\n", t->s, t->p, t->o, t->id);
+    printf("  %-11s %-11s %-20s %s\n", t->s, t->p, t->o, t->d);
   triplestore_deinit_state(&state);
 }
 
 
+MU_TEST(test_poll)
+{
+  TripleState state;
+  const Triple *t;
+
+  triplestore_init_state(ts, &state);
+  t = triplestore_poll(&state);
+  mu_assert_string_eq("book", t->s);
+
+  t = triplestore_next(&state);
+  mu_assert_string_eq("book", t->s);
+
+  t = triplestore_poll(&state);
+  mu_assert_string_eq("table", t->s);
+
+  triplestore_reset_state(&state);
+  t = triplestore_poll(&state);
+  mu_assert_string_eq("book", t->s);
+
+  triplestore_deinit_state(&state);
+}
+
 
 MU_TEST(test_find)
 {
   int n;
-  const Triple *t;
   TripleState state;
+  const Triple *t;
 
-  t = triplestore_find_first(ts, NULL, "is-a", "table");
+  t = triplestore_find_first(ts, NULL, "is-a", "table", NULL);
   mu_check(t == NULL);
 
-  t = triplestore_find_first(ts, NULL, "is-ontop-of", "table");
+  t = triplestore_find_first(ts, NULL, "is-ontop-of", "table", NULL);
   mu_check(t);
   mu_assert_string_eq("book", t->s);
   mu_assert_string_eq("is-ontop-of", t->p);
   mu_assert_string_eq("table", t->o);
+  mu_assert_string_eq(NULL, t->d);
 
   triplestore_init_state(ts, &state);
   n = 0;
-  while (triplestore_find(&state, NULL, "is-a", "thing")) n++;
-  mu_assert_int_eq(2, n);
+  while (triplestore_find(&state, NULL, NULL, NULL, NULL)) n++;
+  mu_assert_int_eq(10, n);
   triplestore_deinit_state(&state);
 
   triplestore_init_state(ts, &state);
   n = 0;
-  while (triplestore_find(&state, NULL, "is-a", NULL)) n++;
+  while (triplestore_find(&state, NULL, "is-a", NULL, NULL)) n++;
   mu_assert_int_eq(5, n);
   triplestore_deinit_state(&state);
+
+  triplestore_init_state(ts, &state);
+  n = 0;
+  while (triplestore_find(&state, NULL, "is-a", "thing", NULL)) n++;
+  mu_assert_int_eq(2, n);
+  triplestore_deinit_state(&state);
+
+  /* Count IRIs */
+  triplestore_init_state(ts, &state);
+  n = 0;
+  while (triplestore_find(&state, NULL, NULL, NULL, "")) n++;
+  mu_assert_int_eq(7, n);
+  triplestore_deinit_state(&state);
+
+  triplestore_init_state(ts, &state);
+  n = 0;
+  while (triplestore_find(&state, NULL, NULL, NULL, "xsd:double")) n++;
+  mu_assert_int_eq(1, n);
+  triplestore_deinit_state(&state);
+
+  t = triplestore_find_first(ts, NULL, NULL, NULL, "xsd:double");
+  mu_check(t);
+  mu_assert_string_eq("book-weight", t->s);
+  mu_assert_string_eq("has-value", t->p);
+  mu_assert_string_eq("0.6", t->o);
+  mu_assert_string_eq("xsd:double", t->d);
+}
+
+
+MU_TEST(test_value)
+{
+  mu_assert_string_eq("action", triplestore_value(ts, "write", "is-a", NULL,
+                                                  NULL, NULL, 0));
+  mu_assert_string_eq("thing", triplestore_value(ts, "table", "is-a", NULL,
+                                                  NULL, NULL, 1));
+  mu_assert_string_eq("kg", triplestore_value(ts, "book-weight", "has-unit",
+                                              NULL, NULL, NULL, 0));
+  mu_assert_string_eq("book-weight", triplestore_value(ts, NULL, "has-unit",
+                                              "kg", NULL, NULL, 0));
+  mu_assert_string_eq("some-weight", triplestore_value(ts, NULL, "has-unit",
+                                              "µg", NULL, "some-weight", 0));
+
+
+  /* Check some failures */
+ ErrTry:
+  // more than one match
+  mu_assert_string_eq(NULL, triplestore_value(ts, "table", "is-a", NULL,
+                                              NULL, NULL, 0));
+  // no match (datatype does not match)
+  mu_assert_string_eq(NULL, triplestore_value(ts, "book-weight", "has-unit",
+                                              NULL, "xsd:float", NULL, 0));
+ ErrCatch(dliteSearchError):  // suppress errors
+  break;
+ ErrEnd;
+
+ ErrTry:
+  // at least 2 of s,p,o must be given
+  mu_assert_string_eq(NULL, triplestore_value(ts, NULL, "is-a",
+                                              NULL, NULL, NULL, 0));
+  mu_assert_string_eq(NULL, triplestore_value(ts, "book", "is-a",
+                                              "thing", NULL, NULL, 0));
+ ErrCatch(dliteTypeError):  // suppress errors
+  break;
+ ErrEnd;
 }
 
 
 MU_TEST(test_remove)
 {
-  mu_check(6 == triplestore_length(ts));
+  mu_assert_int_eq(10, triplestore_length(ts));
+
+  mu_check(!triplestore_remove(ts, NULL, "is-something", NULL, NULL));
+  mu_assert_int_eq(10, triplestore_length(ts));
 
-  mu_check(!triplestore_remove(ts, NULL, "is-something", NULL));
-  mu_check(6 == triplestore_length(ts));
+  mu_assert_int_eq(4, triplestore_remove(ts, "book", NULL, NULL, NULL));
+  mu_assert_int_eq(6, triplestore_length(ts));
+}
 
-  mu_assert_int_eq(2, triplestore_remove(ts, "book", NULL, NULL));
-  mu_check(4 == triplestore_length(ts));
+
+MU_TEST(test_clear)
+{
+  mu_assert_int_eq(6, triplestore_length(ts));
+  triplestore_clear(ts);
+  mu_assert_int_eq(0, triplestore_length(ts));
 }
 
 
@@ -108,8 +202,7 @@ MU_TEST(test_free)
 {
   triplestore_free(ts);
 
-  Session *s = session_get_default();
-  session_free(s);
+  dlite_finalize();  // for testing memory leaks
 }
 
 
@@ -119,11 +212,13 @@ MU_TEST(test_free)
 MU_TEST_SUITE(test_suite)
 {
   MU_RUN_TEST(test_create);
-  MU_RUN_TEST(test_triple);
   MU_RUN_TEST(test_add);
   MU_RUN_TEST(test_next);
+  MU_RUN_TEST(test_poll);
   MU_RUN_TEST(test_find);
+  MU_RUN_TEST(test_value);
   MU_RUN_TEST(test_remove);
+  MU_RUN_TEST(test_clear);
   MU_RUN_TEST(test_free);
 }
 
diff --git a/src/tests/test_type.c b/src/tests/test_type.c
old mode 100755
new mode 100644
index 0e37a469f..b77983c3e
--- a/src/tests/test_type.c
+++ b/src/tests/test_type.c
@@ -209,7 +209,8 @@ MU_TEST(test_print)
   char *p=NULL, s[]="my source string", *q=s;
   DLiteInstance *inst = (DLiteInstance *)dlite_collection_create("myid");
   DLiteDimension d = {"name", "descr"};
-  DLiteRelation r = {"subject", "predicate", "object", "id"};
+  DLiteRelation r1 = {"subject", "predicate", "object", NULL,       NULL};
+  DLiteRelation r2 = {"subject", "predicate", "object", "datatype", NULL};
 
   mu_assert_int_eq(7, dlite_type_print(buf, sizeof(buf), &v, dliteFloat,
                                        sizeof(double), 0, -2, 0));
@@ -245,12 +246,17 @@ MU_TEST(test_print)
   mu_assert_string_eq("{\"name\": \"name\", \"description\": \"descr\"}", buf);
 
 
-  mu_assert_int_eq(34, dlite_type_print(buf, sizeof(buf), &r, dliteRelation,
+  mu_assert_int_eq(34, dlite_type_print(buf, sizeof(buf), &r1, dliteRelation,
                                        sizeof(DLiteRelation *), -1, -1, 0));
   mu_assert_string_eq("[\"subject\", \"predicate\", \"object\"]", buf);
 
+  mu_assert_int_eq(46, dlite_type_print(buf, sizeof(buf), &r2, dliteRelation,
+                                       sizeof(DLiteRelation *), -1, -1, 0));
+  mu_assert_string_eq("[\"subject\", \"predicate\", \"object\", \"datatype\"]",
+                      buf);
+
   buf[0] = '\0';
-  mu_assert_int_eq(34, dlite_type_print(buf, 0, &r, dliteRelation,
+  mu_assert_int_eq(34, dlite_type_print(buf, 0, &r1, dliteRelation,
                                        sizeof(DLiteRelation *), -1, -1, 0));
   mu_assert_int_eq(0, buf[0]);
 
@@ -470,19 +476,97 @@ MU_TEST(test_scan)
 
   /* dliteRelation */
   memset(&rel, 0, sizeof(DLiteRelation));
-
   s = "[\"subject\", \"predicate\", \"object\"]";
   n = dlite_type_scan(s, -1, &rel, dliteRelation, sizeof(DLiteRelation), 0);
   mu_assert_int_eq(34, n);
   mu_assert_string_eq("subject",   rel.s);
   mu_assert_string_eq("predicate", rel.p);
   mu_assert_string_eq("object",    rel.o);
+  mu_assert_string_eq(NULL,        rel.d);
+  mu_assert_string_eq(NULL,        rel.id);
+  triple_clean(&rel);
+
+  s = "[\"subject\", \"predicate\", \"object\", \"datatype\"]";
+  n = dlite_type_scan(s, -1, &rel, dliteRelation, sizeof(DLiteRelation), 0);
+  mu_assert_int_eq(46, n);
+  mu_assert_string_eq("subject",   rel.s);
+  mu_assert_string_eq("predicate", rel.p);
+  mu_assert_string_eq("object",    rel.o);
+  mu_assert_string_eq("datatype",  rel.d);
+  mu_assert_string_eq(NULL,        rel.id);
+  triple_clean(&rel);
+
+  s = "[\"subject\", \"predicate\", \"object\", \"datatype\", \"id\"]";
+  n = dlite_type_scan(s, -1, &rel, dliteRelation, sizeof(DLiteRelation), 0);
+  mu_assert_int_eq(52, n);
+  mu_assert_string_eq("subject",   rel.s);
+  mu_assert_string_eq("predicate", rel.p);
+  mu_assert_string_eq("object",    rel.o);
+  mu_assert_string_eq("datatype",  rel.d);
+  mu_assert_string_eq("id",        rel.id);
+  triple_clean(&rel);
+
+  s = "[\"subject\", \"predicate\", \"object\", \"\"]";
+  n = dlite_type_scan(s, -1, &rel, dliteRelation, sizeof(DLiteRelation), 0);
+  mu_assert_int_eq(38, n);
+  mu_assert_string_eq("subject",   rel.s);
+  mu_assert_string_eq("predicate", rel.p);
+  mu_assert_string_eq("object",    rel.o);
+  mu_assert_string_eq(NULL,        rel.d);
+  mu_assert_string_eq(NULL,        rel.id);
+  triple_clean(&rel);
+
+  s = "[\"subject\", \"predicate\", \"object\", \"\", \"\"]";
+  n = dlite_type_scan(s, -1, &rel, dliteRelation, sizeof(DLiteRelation), 0);
+  mu_assert_int_eq(42, n);
+  mu_assert_string_eq("subject",   rel.s);
+  mu_assert_string_eq("predicate", rel.p);
+  mu_assert_string_eq("object",    rel.o);
+  mu_assert_string_eq(NULL,        rel.d);
+  mu_assert_string_eq(NULL,        rel.id);
+  triple_clean(&rel);
+
+  s = "[\"subject\", \"predicate\", \"object\", \"\", \"id\"]";
+  n = dlite_type_scan(s, -1, &rel, dliteRelation, sizeof(DLiteRelation), 0);
+  mu_assert_int_eq(44, n);
+  mu_assert_string_eq("subject",   rel.s);
+  mu_assert_string_eq("predicate", rel.p);
+  mu_assert_string_eq("object",    rel.o);
+  mu_assert_string_eq(NULL,        rel.d);
+  mu_assert_string_eq("id",        rel.id);
+  triple_clean(&rel);
+
+  s = "{\"s\": \"subject\", \"p\": \"predicate\", \"o\": \"object\"}";
+  n = dlite_type_scan(s, -1, &rel, dliteRelation, sizeof(DLiteRelation), 0);
+  mu_assert_int_eq(49, n);
+  mu_assert_string_eq("subject",   rel.s);
+  mu_assert_string_eq("predicate", rel.p);
+  mu_assert_string_eq("object",    rel.o);
+  mu_assert_string_eq(NULL,        rel.d);
+  mu_assert_string_eq(NULL,        rel.id);
+  triple_clean(&rel);
+
+  s = "{\"s\": \"subject\", \"p\": \"predicate\", \"o\": \"object\", "
+    "\"d\": \"datatype\"}";
+  n = dlite_type_scan(s, -1, &rel, dliteRelation, sizeof(DLiteRelation), 0);
+  mu_assert_int_eq(66, n);
+  mu_assert_string_eq("subject",   rel.s);
+  mu_assert_string_eq("predicate", rel.p);
+  mu_assert_string_eq("object",    rel.o);
+  mu_assert_string_eq("datatype",  rel.d);
   mu_assert_string_eq(NULL,        rel.id);
+  triple_clean(&rel);
 
-  if (rel.s) free(rel.s);
-  if (rel.p) free(rel.p);
-  if (rel.o) free(rel.o);
-  if (rel.id) free(rel.id);
+  s = "{\"s\": \"subject\", \"p\": \"predicate\", \"o\": \"object\", "
+    "\"d\": \"datatype\", \"id\": \"id\"}";
+  n = dlite_type_scan(s, -1, &rel, dliteRelation, sizeof(DLiteRelation), 0);
+  mu_assert_int_eq(78, n);
+  mu_assert_string_eq("subject",   rel.s);
+  mu_assert_string_eq("predicate", rel.p);
+  mu_assert_string_eq("object",    rel.o);
+  mu_assert_string_eq("datatype",  rel.d);
+  mu_assert_string_eq("id",        rel.id);
+  triple_clean(&rel);
 }
 
 
diff --git a/src/utils/tests/.gitignore b/src/utils/tests/.gitignore
old mode 100755
new mode 100644
diff --git a/src/utils/tests/CMakeLists.txt b/src/utils/tests/CMakeLists.txt
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_bson.c b/src/utils/tests/test_bson.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_clp2.c b/src/utils/tests/test_clp2.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_compat.c b/src/utils/tests/test_compat.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_dsl.c b/src/utils/tests/test_dsl.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_dsl_lib.c b/src/utils/tests/test_dsl_lib.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_dsl_lib.def b/src/utils/tests/test_dsl_lib.def
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_err.c b/src/utils/tests/test_err.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_execprocess.c b/src/utils/tests/test_execprocess.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_fileinfo.c b/src/utils/tests/test_fileinfo.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_fileutils.c b/src/utils/tests/test_fileutils.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_globmatch.c b/src/utils/tests/test_globmatch.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_infixcalc.c b/src/utils/tests/test_infixcalc.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_jsmnx.c b/src/utils/tests/test_jsmnx.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_jstore.c b/src/utils/tests/test_jstore.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_macros.h b/src/utils/tests/test_macros.h
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_map.c b/src/utils/tests/test_map.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_plugin.c b/src/utils/tests/test_plugin.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_plugin.h b/src/utils/tests/test_plugin.h
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_plugin_lib.c b/src/utils/tests/test_plugin_lib.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_session.c b/src/utils/tests/test_session.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_sha3.c b/src/utils/tests/test_sha3.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_sha3_slow.c b/src/utils/tests/test_sha3_slow.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_snprintf.c b/src/utils/tests/test_snprintf.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_strtob.c b/src/utils/tests/test_strtob.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_strutils.c b/src/utils/tests/test_strutils.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_tgen.c b/src/utils/tests/test_tgen.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_tmpfileplus.c b/src/utils/tests/test_tmpfileplus.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_unicode.c b/src/utils/tests/test_unicode.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_urlsplit.c b/src/utils/tests/test_urlsplit.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_uuid.c b/src/utils/tests/test_uuid.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_uuid2.c b/src/utils/tests/test_uuid2.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/test_uuid4.c b/src/utils/tests/test_uuid4.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/tgen_example.c b/src/utils/tests/tgen_example.c
old mode 100755
new mode 100644
diff --git a/src/utils/tests/tgen_template.txt b/src/utils/tests/tgen_template.txt
old mode 100755
new mode 100644
diff --git a/storages/json/tests/CMakeLists.txt b/storages/json/tests/CMakeLists.txt
old mode 100755
new mode 100644
diff --git a/storages/json/tests/test_json_storage.c b/storages/json/tests/test_json_storage.c
old mode 100755
new mode 100644
diff --git a/storages/python/tests-c/.gitignore b/storages/python/tests-c/.gitignore
old mode 100755
new mode 100644
diff --git a/storages/python/tests-c/CMakeLists.txt b/storages/python/tests-c/CMakeLists.txt
old mode 100755
new mode 100644
diff --git a/storages/python/tests-c/Person.json b/storages/python/tests-c/Person.json
old mode 100755
new mode 100644
diff --git a/storages/python/tests-c/test_blob_storage.c b/storages/python/tests-c/test_blob_storage.c
old mode 100755
new mode 100644
diff --git a/storages/python/tests-c/test_bson_storage.c b/storages/python/tests-c/test_bson_storage.c
old mode 100755
new mode 100644
diff --git a/storages/python/tests-c/test_postgresql_storage.c b/storages/python/tests-c/test_postgresql_storage.c
old mode 100755
new mode 100644
diff --git a/storages/python/tests-c/test_postgresql_storage2.c b/storages/python/tests-c/test_postgresql_storage2.c
old mode 100755
new mode 100644
diff --git a/storages/python/tests-c/test_yaml_storage.c b/storages/python/tests-c/test_yaml_storage.c
old mode 100755
new mode 100644
diff --git a/storages/python/tests-python/CMakeLists.txt b/storages/python/tests-python/CMakeLists.txt
old mode 100755
new mode 100644
diff --git a/storages/python/tests-python/README.md b/storages/python/tests-python/README.md
old mode 100755
new mode 100644
diff --git a/storages/python/tests-python/input/postgresql_test_data_save.txt b/storages/python/tests-python/input/postgresql_test_data_save.txt
old mode 100755
new mode 100644
diff --git a/storages/python/tests-python/input/postgresql_test_meta_save.txt b/storages/python/tests-python/input/postgresql_test_meta_save.txt
old mode 100755
new mode 100644
diff --git a/storages/python/tests-python/input/test_data.bson b/storages/python/tests-python/input/test_data.bson
old mode 100755
new mode 100644
diff --git a/storages/python/tests-python/input/test_data.json b/storages/python/tests-python/input/test_data.json
old mode 100755
new mode 100644
diff --git a/storages/python/tests-python/input/test_data.pgsql b/storages/python/tests-python/input/test_data.pgsql
old mode 100755
new mode 100644
diff --git a/storages/python/tests-python/input/test_data.ttl b/storages/python/tests-python/input/test_data.ttl
old mode 100755
new mode 100644
diff --git a/storages/python/tests-python/input/test_data.yaml b/storages/python/tests-python/input/test_data.yaml
old mode 100755
new mode 100644
diff --git a/storages/python/tests-python/input/test_meta.bson b/storages/python/tests-python/input/test_meta.bson
old mode 100755
new mode 100644
diff --git a/storages/python/tests-python/input/test_meta.json b/storages/python/tests-python/input/test_meta.json
old mode 100755
new mode 100644
diff --git a/storages/python/tests-python/input/test_meta.pgsql b/storages/python/tests-python/input/test_meta.pgsql
old mode 100755
new mode 100644
diff --git a/storages/python/tests-python/input/test_meta.ttl b/storages/python/tests-python/input/test_meta.ttl
old mode 100755
new mode 100644
diff --git a/storages/python/tests-python/input/test_meta.yaml b/storages/python/tests-python/input/test_meta.yaml
old mode 100755
new mode 100644
diff --git a/storages/python/tests-python/input/test_meta_soft7.yaml b/storages/python/tests-python/input/test_meta_soft7.yaml
old mode 100755
new mode 100644
diff --git a/storages/python/tests-python/run_python_storage_tests.py b/storages/python/tests-python/run_python_storage_tests.py
old mode 100755
new mode 100644
diff --git a/storages/python/tests-python/test_bson_storage_python.py b/storages/python/tests-python/test_bson_storage_python.py
old mode 100755
new mode 100644
diff --git a/storages/python/tests-python/test_minio.py b/storages/python/tests-python/test_minio.py
old mode 100755
new mode 100644
diff --git a/storages/python/tests-python/test_mongodb-atlas_python.py b/storages/python/tests-python/test_mongodb-atlas_python.py
old mode 100755
new mode 100644
diff --git a/storages/python/tests-python/test_mongodb_python.py b/storages/python/tests-python/test_mongodb_python.py
old mode 100755
new mode 100644
index e6a97110a..c9cdce887
--- a/storages/python/tests-python/test_mongodb_python.py
+++ b/storages/python/tests-python/test_mongodb_python.py
@@ -1,8 +1,10 @@
 """Script to test the 'mongodb' DLite plugin from Python."""
+import os
 import sys
 from pathlib import Path
 
 import dlite
+from dlite.options import Options
 
 try:
     import pymongo
@@ -11,6 +13,30 @@
     sys.exit(44)  # skip test
 
 
+@mongomock.patch(servers=(('localhost', 27017),))
+def create_storage():
+    """ Create storage with mongomock """
+    storage = dlite.Storage(
+        "mongodb", "localhost", "user=testuser;password=testpw"
+    )
+    return storage
+
+
+if 'DLITETEST_MONGODB_AZURE' in os.environ:
+    print('Test MongoDB on Azure')
+    conn_str = os.environ['DLITETEST_MONGODB_AZURE']
+    options = Options("mode=w")
+    options.update(
+        database=os.environ['DLITETEST_MONGODB_AZURE_DATABASE'],
+        collection=os.environ['DLITETEST_MONGODB_AZURE_COLLECTION']
+    )
+    storage = dlite.Storage("mongodb", conn_str, str(options))
+    print(storage.options)
+    instances = list(storage.instances())
+    print('azure instances', len(instances))
+    assert len(instance) == int(os.environ['DLITETEST_MONGODB_AZURE_COUNT'])
+
+
 thisdir = Path(__file__).absolute().parent
 inputdir = thisdir / "input"
 
@@ -21,11 +47,8 @@
 inst2 = dlite.Instance.from_location("json", inputdir / "test_data.json",
                                      id="2f8ba28c-add6-5718-a03c-ea46961d6ca7")
 
-# Create storage
-storage = dlite.Storage(
-    "mongodb", "localhost", "user=testuser;password=testpw;mock=true"
-)
 
+storage = create_storage()
 # Store metadata and data
 meta.save(storage)
 inst1.save(storage)
diff --git a/storages/python/tests-python/test_postgresql_storage_python.py b/storages/python/tests-python/test_postgresql_storage_python.py
old mode 100755
new mode 100644
diff --git a/storages/python/tests-python/test_redis.py b/storages/python/tests-python/test_redis.py
old mode 100755
new mode 100644
diff --git a/storages/python/tests-python/test_yaml_storage_python.py b/storages/python/tests-python/test_yaml_storage_python.py
old mode 100755
new mode 100644
diff --git a/storages/rdf/tests/CMakeLists.txt b/storages/rdf/tests/CMakeLists.txt
old mode 100755
new mode 100644
diff --git a/storages/rdf/tests/data.xml b/storages/rdf/tests/data.xml
old mode 100755
new mode 100644
diff --git a/storages/rdf/tests/test_rdf.c b/storages/rdf/tests/test_rdf.c
old mode 100755
new mode 100644
index 40babac7a..847ddb07e
--- a/storages/rdf/tests/test_rdf.c
+++ b/storages/rdf/tests/test_rdf.c
@@ -99,13 +99,11 @@ MU_TEST(test_iter)
 
 MU_TEST(test_freedata)
 {
-  int nref;
-
-  nref = inst->_refcount;
-  while (nref--) dlite_instance_decref(inst);
-
-  nref = meta->_refcount;
-  while (nref--) dlite_meta_decref(meta);
+  if (meta) {
+    dlite_meta_decref(meta);
+    dlite_meta_decref(meta);
+  }
+  if (inst) dlite_instance_decref(inst);
 }
 
 
diff --git a/tools/tests/CMakeLists.txt b/tools/tests/CMakeLists.txt
old mode 100755
new mode 100644
diff --git a/tools/tests/Chemistry-0.1.json b/tools/tests/Chemistry-0.1.json
old mode 100755
new mode 100644
diff --git a/tools/tests/test_c_source.c b/tools/tests/test_c_source.c
old mode 100755
new mode 100644
diff --git a/tools/tests/test_codegen.c b/tools/tests/test_codegen.c
old mode 100755
new mode 100644
diff --git a/tools/tests/test_ext_header.c b/tools/tests/test_ext_header.c
old mode 100755
new mode 100644