From a93607dc3ebd38492a00ec1c590513d6a2cca5ce Mon Sep 17 00:00:00 2001
From: Florian Spreckelsen <f.spreckelsen@indiscale.com>
Date: Tue, 22 Jun 2021 07:46:07 +0000
Subject: [PATCH] DOC: Fix and extend documentation on inheritance

---
 CHANGELOG.md                         |  2 +
 README.md                            | 16 +++---
 README_SETUP.md                      |  4 --
 src/caosdb/__init__.py               |  6 ++-
 src/caosdb/common/models.py          | 76 +++++++++++++++++++++++++---
 src/caosdb/connection/connection.py  |  6 ++-
 src/caosdb/exceptions.py             |  8 +--
 src/doc/Makefile                     |  2 +-
 src/doc/conf.py                      | 32 ++++++++----
 src/doc/tutorials/Data-Insertion.rst | 68 ++++++++++++++-----------
 10 files changed, 155 insertions(+), 65 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 622d5678..38b470db 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Fixed ###
 
+* #53 Documentation of inheritance
+
 ### Security ###
 
 ## [0.5.2] - 2021-06-03 ##
diff --git a/README.md b/README.md
index f81202e0..04b34cbc 100644
--- a/README.md
+++ b/README.md
@@ -28,14 +28,18 @@ By participating, you are expected to uphold our [Code of Conduct](https://gitla
 
 * You found a bug, have a question, or want to request a feature? Please 
 [create an issue](https://gitlab.com/caosdb/caosdb-pylib/-/issues).
-* You want to contribute code? Please fork the repository and create a merge 
-request in GitLab and choose this repository as target. Make sure to select
-"Allow commits from members who can merge the target branch" under Contribution
-when creating the merge request. This allows our team to work with you on your request.
-- If you have a suggestion for the [documentation](https://docs.indiscale.com/caosdb-pylib/), 
+* You want to contribute code?
+    * **Forking:** Please fork the repository and create a merge request in GitLab and choose this repository as
+      target. Make sure to select "Allow commits from members who can merge the target branch" under
+      Contribution when creating the merge request. This allows our team to work with you on your
+      request.
+    * **Code style:** This project adhers to the PEP8 recommendations, you can test your code style
+      using the `autopep8` tool (`autopep8 -i -r ./`).  Please write your doc strings following the
+      [NumpyDoc](https://numpydoc.readthedocs.io/en/latest/format.html) conventions.
+* If you have a suggestion for the [documentation](https://docs.indiscale.com/caosdb-pylib/),
 the preferred way is also a merge request as describe above (the documentation resides in `src/doc`).
 However, you can also create an issue for it. 
-- You can also contact us at **info (AT) caosdb.de** and join the
+* You can also contact us at **info (AT) caosdb.de** and join the
   CaosDB community on
   [#caosdb:matrix.org](https://matrix.to/#/!unwwlTfOznjEnMMXxf:matrix.org).
 
diff --git a/README_SETUP.md b/README_SETUP.md
index 30e53ac1..9da54839 100644
--- a/README_SETUP.md
+++ b/README_SETUP.md
@@ -143,10 +143,6 @@ Now would be a good time to continue with the [tutorials](tutorials/index).
 ## Run Unit Tests
 tox
 
-## Code Formatting
-
-autopep8 -i -r ./
-
 ## Documentation ##
 
 Build documentation in `build/` with `make doc`.
diff --git a/src/caosdb/__init__.py b/src/caosdb/__init__.py
index 4043bbed..7e06885f 100644
--- a/src/caosdb/__init__.py
+++ b/src/caosdb/__init__.py
@@ -49,6 +49,10 @@ from caosdb.common.models import (ACL, ALL, FIX, NONE, OBLIGATORY, RECOMMENDED,
 from caosdb.configuration import _read_config_files, configure, get_config
 from caosdb.connection.connection import configure_connection, get_connection
 from caosdb.exceptions import *
-from caosdb.version import version as __version__
+try:
+    from caosdb.version import version as __version__
+except ModuleNotFoundError:
+    version = "uninstalled"
+    __version__ = version
 
 _read_config_files()
diff --git a/src/caosdb/common/models.py b/src/caosdb/common/models.py
index efd4a3e7..6ec49df2 100644
--- a/src/caosdb/common/models.py
+++ b/src/caosdb/common/models.py
@@ -431,14 +431,39 @@ class Entity(object):
     def add_parent(self, parent=None, **kwargs):  # @ReservedAssignment
         """Add a parent to this entity.
 
-        The first parameter is meant to identify the parent entity. So the method expects an instance of
-        Entity, an integer or a string here. Even though, by means of the **kwargs parameter you may pass
-        more parameters to this method. Accepted keywords are: id, name, inheritance. Any other keyword is
-        ignored right now but this may change in the future.
-
-        @param parent: An entity, an id or a name.
-        @param **kwargs: Accepted keywords: id, name, inheritance.
-        @raise UserWarning: If neither a 'parent' parameter, nor the 'id', nor 'name' parameter is passed to this method.
+        Parameters
+        ----------
+        parent : Entity or int or str or None
+            The parent entity, either specified by the Entity object
+            itself, or its id or its name. Default is None.
+        **kwargs : dict, optional
+            Additional keyword arguments for specifying the parent by
+            name or id, and for specifying the mode of inheritance.
+
+            id : int
+                Integer id of the parent entity. Ignored if `parent`
+                is not None.
+            name : str
+                Name of the parent entity. Ignored if `parent is not
+                none`.
+            inheritance : str
+                One of ``obligatory``, ``recommended``, ``suggested``, or ``fix``. Specifies the
+                minimum importance which parent properties need to have to be inherited by this
+                entity. If no `inheritance` is given, no properties will be inherited by the child.
+                This parameter is case-insensitive.
+
+                Note that the behaviour is currently not yet specified when assigning parents to
+                Records, it only works for inheritance of RecordTypes (and Properties).
+
+                For more information, it is recommended to look into the
+                :ref:`data insertion tutorial<tutorial-inheritance-properties>`.
+
+        Raises
+        ------
+        UserWarning
+            If neither a `parent` parameter, nor the `id`, nor `name`
+            parameter is passed to this method.
+
         """
         name = (kwargs['name'] if 'name' in kwargs else None)
         pid = (kwargs['id'] if 'id' in kwargs else None)
@@ -1435,6 +1460,23 @@ class Property(Entity):
             property=property, value=value, **copy_kwargs)
 
     def add_parent(self, parent=None, **kwargs):
+        """Add a parent Entity to this Property.
+
+        Parameters
+        ----------
+        parent : Entity or int or str or None, optional
+            The parent entity
+        **kwargs : dict, optional
+            Additional keyword arguments specifying the parent Entity
+            by id or name, and specifying the inheritance level. See
+            :py:meth:`Entity.add_parent` for more information. Note
+            that by default, `inheritance` is set to ``fix``.
+
+        See Also
+        --------
+        Entity.add_parent
+
+        """
         copy_kwargs = kwargs.copy()
 
         if 'inheritance' not in copy_kwargs:
@@ -1513,6 +1555,24 @@ class RecordType(Entity):
                                     **copy_kwargs)
 
     def add_parent(self, parent=None, **kwargs):
+        """Add a parent to this RecordType
+
+        Parameters
+        ----------
+        parent : Entity or int or str or None, optional
+            The parent entity, either specified by the Entity object
+            itself, or its id or its name. Default is None.
+        **kwargs : dict, optional
+            Additional keyword arguments specifying the parent Entity by id or
+            name, and specifying the inheritance level. See
+            :py:meth:`Entity.add_parent` for more information. Note
+            that by default, `inheritance` is set to ``obligatory``.
+
+        See Also
+        --------
+        Entity.add_parent
+
+        """
         copy_kwargs = kwargs.copy()
 
         if 'inheritance' not in copy_kwargs:
diff --git a/src/caosdb/connection/connection.py b/src/caosdb/connection/connection.py
index 2cb31f42..9e273a56 100644
--- a/src/caosdb/connection/connection.py
+++ b/src/caosdb/connection/connection.py
@@ -41,7 +41,11 @@ from caosdb.exceptions import (CaosDBException, HTTPClientError,
                                HTTPResourceNotFoundError,
                                HTTPServerError,
                                HTTPURITooLongError)
-from caosdb.version import version
+try:
+    from caosdb.version import version
+except ModuleNotFoundError:
+    version = "uninstalled"
+
 from pkg_resources import resource_filename
 
 from .interface import CaosDBHTTPResponse, CaosDBServerConnection
diff --git a/src/caosdb/exceptions.py b/src/caosdb/exceptions.py
index f02a4630..fdd2e11f 100644
--- a/src/caosdb/exceptions.py
+++ b/src/caosdb/exceptions.py
@@ -189,8 +189,8 @@ class TransactionError(CaosDBException):
         error_t. If direct_children_only is True, only direct children
         are checked.
 
-        Parameters:
-        -----------
+        Parameters
+        ----------
         error_t : EntityError
             error type to be checked
         direct_children_only: bool, optional
@@ -199,8 +199,8 @@ class TransactionError(CaosDBException):
             children, i.e., all errors in self.all_errors are
             used. Default is false.
 
-        Returns:
-        --------
+        Returns
+        -------
         has_error : bool
             True if at least one of the children is of type error_t,
             False otherwise.
diff --git a/src/doc/Makefile b/src/doc/Makefile
index 8b533626..64219c59 100644
--- a/src/doc/Makefile
+++ b/src/doc/Makefile
@@ -45,4 +45,4 @@ doc-help:
 	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
 
 apidoc:
-	@$(SPHINXAPIDOC) -o _apidoc $(PY_BASEDIR) --separate
+	@$(SPHINXAPIDOC) -o _apidoc --separate $(PY_BASEDIR)
diff --git a/src/doc/conf.py b/src/doc/conf.py
index 1865af12..b05fa1c7 100644
--- a/src/doc/conf.py
+++ b/src/doc/conf.py
@@ -8,16 +8,18 @@
 
 # -- Path setup --------------------------------------------------------------
 
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
+# If extensions (or modules to document with autodoc) are in another directory, add these
+# directories to sys.path here. This is particularly necessary if this package is installed at a
+# different version, for example via `pip install`.
 #
-# import os
-# import sys
-# sys.path.insert(0, os.path.abspath('../caosdb'))
-
+# If the directory is relative to the documentation root, use os.path.abspath to make it absolute,
+# like shown here.
+#
+import os
+import sys
+sys.path.insert(0, os.path.abspath('..'))
 
-import sphinx_rtd_theme
+import sphinx_rtd_theme  # noqa: E402
 
 
 # -- Project information -----------------------------------------------------
@@ -29,7 +31,7 @@ author = 'Daniel Hornung'
 # The short X.Y version
 version = '0.5.2'
 # The full version, including alpha/beta/rc tags
-#release = '0.5.2-rc2'
+# release = '0.5.2-rc2'
 release = '0.5.2'
 
 
@@ -184,14 +186,22 @@ epub_exclude_files = ['search.html']
 
 # -- Extension configuration -------------------------------------------------
 
+# True to prefix each section label with the name of the document it is in, followed by a colon. For
+# example, index:Introduction for a section called Introduction that appears in document
+# index.rst. Useful for avoiding ambiguity when the same section heading appears in different
+# documents.
+#
+# Note: This stops "normal" links from working, so it should be kept at False.
+# autosectionlabel_prefix_document = True
+
 # -- Options for intersphinx -------------------------------------------------
 
 # https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html#confval-intersphinx_mapping
 intersphinx_mapping = {
     "python": ("https://docs.python.org/", None),
-    "caosdb-mysqlbackend": ("https://caosdb.gitlab.io/caosdb-mysqlbackend/",
+    "caosdb-mysqlbackend": ("https://docs.indiscale.com/caosdb-mysqlbackend/",
                             None),
-    "caosdb-server": ("https://caosdb.gitlab.io/caosdb-server/", None),
+    "caosdb-server": ("https://docs.indiscale.com/caosdb-server/", None),
 }
 
 
diff --git a/src/doc/tutorials/Data-Insertion.rst b/src/doc/tutorials/Data-Insertion.rst
index 2ead1cc2..22fb9461 100644
--- a/src/doc/tutorials/Data-Insertion.rst
+++ b/src/doc/tutorials/Data-Insertion.rst
@@ -4,9 +4,9 @@ Data Insertion
 Data Models
 ~~~~~~~~~~~
 
-Data is stored and structured in CaosDB using a concept of RecordTypes,
-Properties, Records etc. If you do not know what these are, please look
-at the chapter :any:`caosdb-server:Data Model` .
+Data is stored and structured in CaosDB using a concept of RecordTypes, Properties, Records etc. If
+you do not know what these are, please look at the chapter :doc:`Data
+Model<caosdb-server:Data-Model>` in the CaosDB server documentation.
 
 In order to insert some actual data, we need to create a data model
 using RecordTypes and Properties (You may skip this if you use a CaosDB
@@ -35,6 +35,42 @@ two more Properties and a RecordType:
    container.extend([a, b, epsilon, recordtype])
    container.insert()
 
+.. _tutorial-inheritance-properties:
+
+Inheritance of Properties
+-------------------------
+
+Suppose you want to create a new RecordType “2D_BarkleySimulation”
+that denotes spatially extended Barkley simulations. This is a subtype
+of the “BarkleySimulation” RecordType above and should have all its
+parameters, i.e., properties. It may be assigned more, e.g., spatial
+resolution, but we'll omit this for the sake of brevity for now.
+
+.. code:: python
+
+   rt = db.RecordType(name="2D_BarkleySimulation",
+	              description="Spatially extended Barkley simulation")
+   # inherit all properties from the BarkleySimulation RecordType
+   rt.add_parent(name="BarkleySimulation", inheritance="all")
+   rt.insert()
+       
+   print(rt.get_property(name="epsilon").importance) ### rt has a "epsilon" property with the same importance as "BarkleySimulation"
+
+The parameter ``inheritance=(obligatory|recommended|fix|all|none)`` of
+:py:meth:`Entity.add_parent()<caosdb.common.models.Entity.add_parent>` tells the server to assign
+all properties of the parent RecordType with the chosen importance (and properties with a higher
+importance) to the child RecordType
+automatically upon insertion. See the chapter on `importance
+<https://docs.indiscale.com/caosdb-server/specification/RecordType.html#importance>`_ in the
+documentation of the CaosDB server for more information on the importance and inheritance of
+properties.
+
+.. note::
+
+   The inherited properties will only be visible after the insertion since they are set by the
+   CaosDB server, not by the Python client.
+
+
 Insert Actual Data
 ~~~~~~~~~~~~~~~~~~
 
@@ -108,32 +144,6 @@ Useful is also, that you can insert directly tabular data.
 With this example file
 `test.csv <uploads/4f2c8756a26a3984c0af09d206d583e5/test.csv>`__.
 
-Inheritance of Properties
--------------------------
-
-Given, you want to insert a new RecordType “Fridge temperatur
-experiment” as a child of the existing RecordType “Experiment”. The
-latter may have an obligatory Property “date” (since every experiment is
-conducted at some time). It is a natural way of thinking, that every sub
-type of “Experiment” also has this obligatory Property—in terms of
-object oriented programing the “Fridge temperatur experiment” *inherits*
-that Property.
-
-::
-
-       rt = h.RecordType(name="Fridge temperatur experiment", 
-                                 description="RecordType which inherits all obligatory properties from Experiment"
-                                 ).add_parent(name="Experiment", inheritance="obligatory").insert()
-       
-       print(rt.get_property(name="date").importance) ### rt now has a "date"-property -> this line prints "obligatory"
-
-The parameter *``inheritance=(obligatory|recommended|fix|all|none)``* of
-``add_parent`` tells the server to assign obligatory:: properties of the
-parent to the child automatically, recommended:: properties of the
-parent to the child automatically, fix:: properties of the parent to the
-child automatically, all:: properties of the parent to the child
-automatically, none:: of the properties of the parent to child
-automatically,
 
 File Update
 -----------
-- 
GitLab