Repository: fair-workflows/fairworkflows

Issue:
Steps without URI are reffered to as <None> in workflow RDF.
```python
step1 = FairStep()
workflow = FairWorkflow()
workflow.add(step1)
print(workflow)
```
prints:
```
Workflow URI = None
@prefix pplan: <http://purl.org/net/p-plan#> .
@prefix pwo: <http://purl.org/spar/pwo/> .

_:Nc4cbc97adddf4b6396c0ff18497b6fae {
    <None> pplan:isStepOfPlan [ a pplan:Plan ;
                pwo:hasFirstStep <None> ] .
}
```
As you can see we refer to the step as <None> which does not make any sense. This is because we try to use the uri of the `FairStep` object to refer to it and this is None. 
When trying to publish this we get:
```
RuntimeError: Error in nanopub java application: org.eclipse.rdf4j.rio.RDFParseException: Not a valid (absolute) IRI: /None [line 19]
```
We should make use of the FairStep.self_ref. That likely breaks the code in different places though.

Requirements:
* [ ] Fix reference from workflows to steps that don't have a URI
* [ ] Fix publication of workflows with steps that don't have a URI

Discussion:

Patch:
diff --git a/examples/noodles_fw.ipynb b/examples/noodles_fw.ipynb
index b969ec4..139376d 100644
--- a/examples/noodles_fw.ipynb
+++ b/examples/noodles_fw.ipynb
@@ -2,7 +2,7 @@
  "cells": [
   {
    "cell_type": "markdown",
-   "id": "worth-borough",
+   "id": "regulated-intermediate",
    "metadata": {},
    "source": [
     "# FairWorkflows execution demo"
@@ -10,7 +10,7 @@
   },
   {
    "cell_type": "markdown",
-   "id": "suffering-lodging",
+   "id": "handed-swedish",
    "metadata": {},
    "source": [
     "## Define the steps of your workflow\n",
@@ -20,7 +20,7 @@
   {
    "cell_type": "code",
    "execution_count": 1,
-   "id": "rocky-insured",
+   "id": "unusual-speed",
    "metadata": {},
    "outputs": [],
    "source": [
@@ -30,7 +30,7 @@
   {
    "cell_type": "code",
    "execution_count": 2,
-   "id": "equipped-salem",
+   "id": "least-confusion",
    "metadata": {},
    "outputs": [],
    "source": [
@@ -43,7 +43,7 @@
   {
    "cell_type": "code",
    "execution_count": 3,
-   "id": "registered-assembly",
+   "id": "sized-desperate",
    "metadata": {},
    "outputs": [],
    "source": [
@@ -56,7 +56,7 @@
   {
    "cell_type": "code",
    "execution_count": 4,
-   "id": "stopped-camcorder",
+   "id": "closed-fortune",
    "metadata": {},
    "outputs": [],
    "source": [
@@ -69,7 +69,7 @@
   {
    "cell_type": "code",
    "execution_count": 5,
-   "id": "broad-indonesia",
+   "id": "growing-foster",
    "metadata": {},
    "outputs": [],
    "source": [
@@ -82,7 +82,7 @@
   },
   {
    "cell_type": "markdown",
-   "id": "searching-gospel",
+   "id": "palestinian-poverty",
    "metadata": {},
    "source": [
     "## Define your workflow using @fairworkflow\n",
@@ -92,7 +92,7 @@
   {
    "cell_type": "code",
    "execution_count": 6,
-   "id": "chronic-reggae",
+   "id": "retired-assignment",
    "metadata": {},
    "outputs": [],
    "source": [
@@ -109,7 +109,7 @@
   },
   {
    "cell_type": "markdown",
-   "id": "scenic-worker",
+   "id": "protecting-street",
    "metadata": {},
    "source": [
     "## Create an instance of your workflow and display it"
@@ -118,7 +118,7 @@
   {
    "cell_type": "code",
    "execution_count": 7,
-   "id": "heavy-proportion",
+   "id": "printable-storage",
    "metadata": {},
    "outputs": [
     {
@@ -140,7 +140,7 @@
   {
    "cell_type": "code",
    "execution_count": 8,
-   "id": "suffering-emission",
+   "id": "atomic-background",
    "metadata": {},
    "outputs": [
     {
@@ -164,7 +164,7 @@
   },
   {
    "cell_type": "markdown",
-   "id": "tribal-variation",
+   "id": "false-imagination",
    "metadata": {},
    "source": [
     "## Publish the (prospective) workflow\n",
@@ -174,16 +174,44 @@
   {
    "cell_type": "code",
    "execution_count": 9,
-   "id": "southern-philosophy",
+   "id": "statewide-search",
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Published to http://purl.org/np/RANKki6Fh-QYTHJ39yhJNOEcQLZOJld1VZFEvaLeILgCA\n",
+      "Published concept to http://purl.org/np/RANKki6Fh-QYTHJ39yhJNOEcQLZOJld1VZFEvaLeILgCA#step\n",
+      "Published to http://purl.org/np/RAsljALsS_3aL57HIU-8tdyxYl3ESlSkQu3kCfWBxvZV0\n",
+      "Published concept to http://purl.org/np/RAsljALsS_3aL57HIU-8tdyxYl3ESlSkQu3kCfWBxvZV0#step\n",
+      "Published to http://purl.org/np/RAUod2ToGWqyvBbFqKRPB4ilwjx78fYR1l37ORKDXLsk4\n",
+      "Published concept to http://purl.org/np/RAUod2ToGWqyvBbFqKRPB4ilwjx78fYR1l37ORKDXLsk4#step\n",
+      "Published to http://purl.org/np/RAuKOFZgd1RehE5vOclqdvJo2zchDUUF33VIh8A_8DXtM\n",
+      "Published concept to http://purl.org/np/RAuKOFZgd1RehE5vOclqdvJo2zchDUUF33VIh8A_8DXtM#step\n",
+      "Published to http://purl.org/np/RAFHW530LVNe6DwMV4uvA5cDLItEuvEbHJhfczfW92nMg\n",
+      "Published concept to http://purl.org/np/RAFHW530LVNe6DwMV4uvA5cDLItEuvEbHJhfczfW92nMg#plan\n"
+     ]
+    },
+    {
+     "data": {
+      "text/plain": [
+       "{'nanopub_uri': 'http://purl.org/np/RAFHW530LVNe6DwMV4uvA5cDLItEuvEbHJhfczfW92nMg',\n",
+       " 'concept_uri': 'http://purl.org/np/RAFHW530LVNe6DwMV4uvA5cDLItEuvEbHJhfczfW92nMg#plan'}"
+      ]
+     },
+     "execution_count": 9,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
    "source": [
-    "#fw.publish_as_nanopub()"
+    "fw.publish_as_nanopub()"
    ]
   },
   {
    "cell_type": "markdown",
-   "id": "extreme-participation",
+   "id": "fabulous-hydrogen",
    "metadata": {},
    "source": [
     "Be warned though - the above will keep publishing to the 'real' nanopub server network. For testing you may prefer to publish to the test servers as follows (note that this will refuse to publish a workflow you have already published :"
@@ -192,30 +220,21 @@
   {
    "cell_type": "code",
    "execution_count": 10,
-   "id": "ranging-andrews",
+   "id": "breeding-barrier",
    "metadata": {},
    "outputs": [
     {
-     "name": "stdout",
+     "name": "stderr",
      "output_type": "stream",
      "text": [
-      "Published to http://purl.org/np/RAiPTeYNAhFAfYft6htEHljFXxR8bCaRxH2Ais1b9prcA\n",
-      "Published concept to http://purl.org/np/RAiPTeYNAhFAfYft6htEHljFXxR8bCaRxH2Ais1b9prcA#step\n",
-      "Published to http://purl.org/np/RAGFccFuQRkxa5R5AjDUN6SwF3LQE8pAc3bIAdp9KD16I\n",
-      "Published concept to http://purl.org/np/RAGFccFuQRkxa5R5AjDUN6SwF3LQE8pAc3bIAdp9KD16I#step\n",
-      "Published to http://purl.org/np/RAoGKox_-enW_2DrM6qcny4wM6IYfYndsp8ABOaxLu754\n",
-      "Published concept to http://purl.org/np/RAoGKox_-enW_2DrM6qcny4wM6IYfYndsp8ABOaxLu754#step\n",
-      "Published to http://purl.org/np/RANnEexpRFhnlVx5Myv-ZVJx07dSFrhM_BP0RSqW39_Ag\n",
-      "Published concept to http://purl.org/np/RANnEexpRFhnlVx5Myv-ZVJx07dSFrhM_BP0RSqW39_Ag#step\n",
-      "Published to http://purl.org/np/RA2BTYCsFPkkdct55P2Am_l3fxHQZp1ariOPkQoKx_XaY\n",
-      "Published concept to http://purl.org/np/RA2BTYCsFPkkdct55P2Am_l3fxHQZp1ariOPkQoKx_XaY#plan\n"
+      "/Users/robinrichardson/miniconda3/envs/fairworkflows/lib/python3.8/site-packages/fairworkflows/rdf_wrapper.py:213: UserWarning: Cannot publish() this Fair object. This rdf is already published (at http://purl.org/np/RAFHW530LVNe6DwMV4uvA5cDLItEuvEbHJhfczfW92nMg#plan) and has not been modified locally.\n",
+      "  warnings.warn(f'Cannot publish() this Fair object. '\n"
      ]
     },
     {
      "data": {
       "text/plain": [
-       "{'nanopub_uri': 'http://purl.org/np/RA2BTYCsFPkkdct55P2Am_l3fxHQZp1ariOPkQoKx_XaY',\n",
-       " 'concept_uri': 'http://purl.org/np/RA2BTYCsFPkkdct55P2Am_l3fxHQZp1ariOPkQoKx_XaY#plan'}"
+       "{'nanopub_uri': None, 'concept_uri': None}"
       ]
      },
      "execution_count": 10,
@@ -229,7 +248,7 @@
   },
   {
    "cell_type": "markdown",
-   "id": "caring-excuse",
+   "id": "large-biography",
    "metadata": {},
    "source": [
     "You can then find your nanopublications by replacing the base of the URI with http://test-server.nanopubs.lod.labs.vu.nl/"
@@ -237,17 +256,17 @@
   },
   {
    "cell_type": "markdown",
-   "id": "warming-input",
+   "id": "balanced-filing",
    "metadata": {},
    "source": [
     "## Execute your workflow using .execute()\n",
-    "Set num_threads greater than 1 if you wish to exploit parallelisation in your workflow. The retrospective provenance is also returned as a (nano) Publication object, that can optionally be published."
+    "Set num_threads greater than 1 if you wish to exploit parallelisation in your workflow. A separate RetroProv object is also returned that can (optionally) be published as a nanopublication."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 13,
-   "id": "electoral-bangladesh",
+   "execution_count": 11,
+   "id": "scheduled-forest",
    "metadata": {},
    "outputs": [
     {
@@ -256,7 +275,7 @@
        "-66"
       ]
      },
-     "execution_count": 13,
+     "execution_count": 11,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -268,7 +287,7 @@
   },
   {
    "cell_type": "markdown",
-   "id": "simplified-twelve",
+   "id": "pressing-hartford",
    "metadata": {},
    "source": [
     " ## Retrospective prov\n",
@@ -277,70 +296,23 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 14,
-   "id": "alien-rental",
-   "metadata": {},
-   "outputs": [
-    {
-     "data": {
-      "text/plain": [
-       "nanopub.publication.Publication"
-      ]
-     },
-     "execution_count": 14,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "type(prov)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 16,
-   "id": "systematic-reference",
+   "execution_count": 12,
+   "id": "straight-watch",
    "metadata": {},
    "outputs": [
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "Original source URI = None\n",
-      "@prefix : <http://purl.org/nanopub/temp/mynanopub#> .\n",
-      "@prefix np: <http://www.nanopub.org/nschema#> .\n",
-      "@prefix prov: <http://www.w3.org/ns/prov#> .\n",
-      "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n",
-      "@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n",
-      "\n",
-      ":Head {\n",
-      "    : a np:Nanopublication ;\n",
-      "        np:hasAssertion :assertion ;\n",
-      "        np:hasProvenance :provenance ;\n",
-      "        np:hasPublicationInfo :pubInfo .\n",
-      "}\n",
-      "\n",
-      ":assertion {\n",
-      "    :retroprov a prov:Activity ;\n",
-      "        rdfs:label \"\"\"2021-01-28 12:01:55,933 - job             1: mul(22, -3) -> -66\n",
-      "2021-01-28 12:01:55,933 - job             2: weird(5, 3) -> 22\n",
-      "2021-01-28 12:01:55,933 - job             3: add(1, 4) -> 5\n",
-      "2021-01-28 12:01:55,933 - job             4: sub(1, 4) -> -3\n",
-      "2021-01-28 12:01:55,936 - result          1 [mul(22, -3) -> -66]: done -> -66\n",
-      "2021-01-28 12:01:55,936 - -end-of-queue-\n",
-      "\"\"\" ;\n",
-      "        prov:wasDerivedFrom <http://purl.org/np/RA2BTYCsFPkkdct55P2Am_l3fxHQZp1ariOPkQoKx_XaY#plan> .\n",
-      "}\n",
-      "\n",
-      ":pubInfo {\n",
-      "    : prov:generatedAtTime \"2021-01-28T12:01:55.939817\"^^xsd:dateTime ;\n",
-      "        prov:wasAttributedTo <https://orcid.org/0000-0000-0000-0000> .\n",
-      "}\n",
-      "\n",
-      ":provenance {\n",
-      "    :assertion prov:generatedAtTime \"2021-01-28T12:01:55.939817\"^^xsd:dateTime .\n",
-      "}\n",
-      "\n",
+      "2021-01-24 21:31:39,751 - job            13: add(1, 4)\n",
+      "2021-01-24 21:31:39,751 - job            14: sub(1, 4)\n",
+      "2021-01-24 21:31:39,753 - result         13 [add(1, 4)]: retrieved -> 5\n",
+      "2021-01-24 21:31:39,753 - job            15: weird(5, 3)\n",
+      "2021-01-24 21:31:39,754 - result         14 [sub(1, 4)]: retrieved -> -3\n",
+      "2021-01-24 21:31:39,754 - result         15 [weird(5, 3)]: retrieved -> 22\n",
+      "2021-01-24 21:31:39,755 - job            16: mul(22, -3)\n",
+      "2021-01-24 21:31:39,755 - result         16 [mul(22, -3)]: retrieved -> -66\n",
+      "2021-01-24 21:31:39,755 - -end-of-queue-\n",
       "\n"
      ]
     }
@@ -348,6 +320,102 @@
    "source": [
     "print(prov)"
    ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "iraqi-messaging",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "confident-emission",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "exterior-duplicate",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "administrative-complex",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "killing-investing",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "mathematical-departure",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "caroline-lending",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "ultimate-monte",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "greater-whole",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "minimal-wagon",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "administrative-nation",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "analyzed-belief",
+   "metadata": {},
+   "outputs": [],
+   "source": []
   }
  ],
  "metadata": {
diff --git a/fairworkflows/config.py b/fairworkflows/config.py
index fb68644..86dcbb4 100644
--- a/fairworkflows/config.py
+++ b/fairworkflows/config.py
@@ -8,3 +8,5 @@ TESTS_RESOURCES = TESTS_ROOT / 'resources'
 # Generated workflow directory names
 PYTHON_DIR = 'scripts'
 PLEX_DIR = 'plex'
+
+DUMMY_FAIRWORKFLOWS_URI = 'http://fairworkflows.org'
diff --git a/fairworkflows/fairstep.py b/fairworkflows/fairstep.py
index 0166e68..7158963 100644
--- a/fairworkflows/fairstep.py
+++ b/fairworkflows/fairstep.py
@@ -9,6 +9,7 @@ import rdflib
 from rdflib import RDF, RDFS, DCTERMS
 
 from fairworkflows import namespaces
+from fairworkflows.config import DUMMY_FAIRWORKFLOWS_URI
 from fairworkflows.rdf_wrapper import RdfWrapper, replace_in_rdf
 
 
@@ -93,6 +94,11 @@ class FairStep(RdfWrapper):
             self.inputs = inputs
         if outputs is not None:
             self.outputs = outputs
+        # Set temporary URI to refer to this step in workflows
+        # (i.e. 'http://fairworkflows.org#8769029329049')
+        if uri is None:
+            self._uri = DUMMY_FAIRWORKFLOWS_URI + '#step' + str(hash(self))
+
         self._is_modified = False
         self._workflows = set()
 
@@ -387,6 +393,48 @@ class FairStep(RdfWrapper):
         return s
 
 
+#def mark_as_fairstep(label: str = None, is_pplan_step: bool = True, is_manual_task: bool = None,
+#                     is_script_task: bool = None):
+#    """Mark a function as a FAIR step.
+#
+#    Use as decorator to mark a function as a FAIR step. Set properties of the fair step using
+#    arguments to the decorator.
+#
+#    The raw code of the function will be used to set the description of the fair step.
+#
+#    The type annotations of the input arguments and return statement will be used to
+#    automatically set inputs and outputs of the FAIR step.
+#
+#    Args:
+#        label (str): Label of the fair step (corresponds to rdfs:label predicate)
+#        is_pplan_step (str): Denotes whether this step is a pplan:Step
+#        is_manual_task (str): Denotes whether this step is a bpmn.ManualTask
+#        is_script_task (str): Denotes whether this step is a bpmn.ScriptTask
+#
+#    """
+#    def _modify_function(func):
+#        """
+#        Store FairStep object as _fairstep attribute of the function. Use inspection to get the
+#        description, inputs, and outputs of the step based on the function specification.
+#        """
+#        def _wrapper(*args, **kwargs):
+#            return func(*args, **kwargs)
+#        # Description of step is the raw function code
+#        description = inspect.getsource(func)
+#        inputs = _extract_inputs_from_function(func)
+#        outputs = _extract_outputs_from_function(func)
+#        _wrapper._fairstep = FairStep(label=label,
+#                                      description=description,
+#                                      is_pplan_step=is_pplan_step,
+#                                      is_manual_task=is_manual_task,
+#                                      is_script_task=is_script_task,
+#                                      inputs=inputs,
+#                                      outputs=outputs)
+#        _wrapper._fairstep.validate()
+#        return _wrapper
+#    return _modify_function
+
+
 def is_fairstep(label: str = None, is_pplan_step: bool = True, is_manual_task: bool = False,
                      is_script_task: bool = True):
     """Mark a function as a FAIR step to be used in a fair workflow.
diff --git a/fairworkflows/fairworkflow.py b/fairworkflows/fairworkflow.py
index 3d258ed..b170ecb 100644
--- a/fairworkflows/fairworkflow.py
+++ b/fairworkflows/fairworkflow.py
@@ -1,9 +1,10 @@
-import inspect
 import warnings
 from copy import deepcopy
 from pathlib import Path
 from tempfile import TemporaryDirectory
 from typing import Iterator, Optional
+import inspect
+from urllib.parse import urldefrag
 
 import networkx as nx
 import rdflib
@@ -11,10 +12,11 @@ from rdflib import RDF, RDFS, DCTERMS
 from rdflib.tools.rdf2dot import rdf2dot
 from requests import HTTPError
 
+
 from fairworkflows import namespaces
 from fairworkflows.fairstep import FairStep
-from fairworkflows.rdf_wrapper import RdfWrapper
-import nanopub
+from fairworkflows.rdf_wrapper import RdfWrapper, replace_in_rdf
+
 
 class FairWorkflow(RdfWrapper):
 
@@ -362,15 +364,6 @@ class FairWorkflow(RdfWrapper):
             return graphviz.Source.from_file(filename)
 
     def execute(self, num_threads=1):
-        """
-        Executes the workflow on the specified number of threads. Noodles is used as the execution
-        engine. If a noodles workflow has not been generated for this fairworkflow object, then
-        it cannot be executed and an exception will be raised.
-
-        Returns a tuple (result, retroprov), where result is the final output of the executed
-        workflow and retroprov is the retrospective provenance logged during execution.
-        """
-
         if not hasattr(self, 'noodles_promise'):
             raise ValueError('Cannot execute workflow as no noodles promise has been constructed.')
 
@@ -388,50 +381,14 @@ class FairWorkflow(RdfWrapper):
         logger.setLevel(logging.INFO)
         logger.handlers = [log_handler]
 
-        # A local function defining the serializers in the registry
-        # If we want to allow remote execution with noodles, we will
-        # need to define this function differently, but for multithreaded
-        # execution (most likely what we'll stick with) this is fine.
-        def registry():
-            return serial.pickle() + serial.base()
-
-        # TODO: Currently we are dumping a lot to an sqlite db, that will slow the
-        # execution down a bit. In future we will either write our own version of
-        # the runner, to grab that extra prov without any db writing, or we will
-        # read it from the database and convert it to RDF then.
-        with TemporaryDirectory() as td:
-            temp_db_fname = Path(td) / 'retro_prov.db'
-            result = run_parallel(self.noodles_promise, n_threads=num_threads,
-                                  registry=registry, db_file=temp_db_fname,
-                                  always_cache=True, echo_log=False)
+        result = run_parallel(
+            self.noodles_promise, n_threads=num_threads, registry=serial.base, db_file='temp_prov_fw.db',
+            always_cache=True, echo_log=False)
 
-        # Generate the retrospective provenance as a (nano-) Publication object
-        retroprov = self._generate_retrospective_prov_publication(log.getvalue())
+        retroprov = log.getvalue()
 
         return result, retroprov
 
-    def _generate_retrospective_prov_publication(self, log:str) -> nanopub.Publication:
-        """
-        Utility method for generating a Publication object for the retrospective
-        provenance of this workflow. Uses the given 'log' string as the actual
-        provenance for now.
-        """
-        log_message = rdflib.Literal(log)
-        this_retroprov = rdflib.BNode('retroprov')
-        if self.uri is None or self.uri == 'None': # TODO: This is horrific
-            this_workflow = rdflib.URIRef('http://www.example.org/unpublishedworkflow')
-        else:
-            this_workflow = rdflib.URIRef(self.uri)
-
-        retroprov_assertion = rdflib.Graph()
-        retroprov_assertion.add((this_retroprov, rdflib.RDF.type, namespaces.PROV.Activity))
-        retroprov_assertion.add((this_retroprov, namespaces.PROV.wasDerivedFrom, this_workflow))
-        retroprov_assertion.add((this_retroprov, RDFS.label, log_message))
-        retroprov = nanopub.Publication.from_assertion(assertion_rdf=retroprov_assertion)
-
-        return retroprov
-
-
     def draw(self, filepath):
         """Visualize workflow.
 
@@ -456,10 +413,8 @@ class FairWorkflow(RdfWrapper):
     def publish_as_nanopub(self, use_test_server=False, **kwargs):
         """Publish to nanopub server.
 
-        Publish the workflow as nanopublication to the nanopub server.
-
-        Raises:
-            RuntimeError: If one of the steps of the workflow was not published yet.
+        First publish the steps, use the URIs of the published steps in the workflow. Then
+        publish the workflow.
 
         Args:
             use_test_server (bool): Toggle using the test nanopub server.
diff --git a/fairworkflows/namespaces.py b/fairworkflows/namespaces.py
index 9c9f824..ddcfab0 100644
--- a/fairworkflows/namespaces.py
+++ b/fairworkflows/namespaces.py
@@ -5,9 +5,3 @@ PPLAN = rdflib.Namespace("http://purl.org/net/p-plan#")
 DUL = rdflib.Namespace("http://www.ontologydesignpatterns.org/ont/dul/DUL.owl#")
 BPMN = rdflib.Namespace("http://dkm.fbk.eu/index.php/BPMN2_Ontology#")
 PWO = rdflib.Namespace("http://purl.org/spar/pwo/")
-
-"""
-Namespace for
-`http://www.w3.org/ns/prov#`
-"""
-PROV = rdflib.Namespace("http://www.w3.org/ns/prov#")
diff --git a/fairworkflows/rdf_wrapper.py b/fairworkflows/rdf_wrapper.py
index e6ef131..cbf9728 100644
--- a/fairworkflows/rdf_wrapper.py
+++ b/fairworkflows/rdf_wrapper.py
@@ -1,4 +1,5 @@
 import warnings
+
 from typing import List
 from urllib.parse import urldefrag
 
@@ -7,8 +8,8 @@ import rdflib
 from nanopub import Publication, NanopubClient
 
 from fairworkflows import namespaces
-from fairworkflows.config import PACKAGE_DIR
 
+from fairworkflows.config import PACKAGE_DIR
 PLEX_SHAPES_SHACL_FILEPATH = str(PACKAGE_DIR / 'resources' / 'plex-shapes.ttl')
 
 
diff --git a/requirements.txt b/requirements.txt
index c83def8..d68fb45 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-nanopub==1.2.2
+nanopub==1.2.1
 networkx~=2.5
 pytest
 pyyaml


Test patch:
diff --git a/tests/test_fairworkflow.py b/tests/test_fairworkflow.py
index 4d77ad9..2ab9d78 100644
--- a/tests/test_fairworkflow.py
+++ b/tests/test_fairworkflow.py
@@ -3,12 +3,13 @@ from unittest import mock
 
 import pytest
 import rdflib
+from rdflib.compare import isomorphic
 from requests import HTTPError
 
 from conftest import skip_if_nanopub_server_unavailable, read_rdf_test_resource
-from fairworkflows import FairWorkflow, FairStep, namespaces, FairVariable, is_fairstep, is_fairworkflow
+from fairworkflows import FairWorkflow, FairStep, namespaces, FairVariable
 from fairworkflows.rdf_wrapper import replace_in_rdf
-from nanopub import Publication
+
 
 class TestFairWorkflow:
     test_description = 'This is a test workflow.'
@@ -68,6 +69,13 @@ class TestFairWorkflow:
         assert len(workflow.__str__()) > 0
         assert workflow.rdf is not None
 
+    def test_build_including_step_without_uri(self):
+        step1 = FairStep()
+        workflow = FairWorkflow()
+        workflow.add(step1)
+        assert (rdflib.URIRef('None'), None, None) not in workflow.rdf
+        assert (None, None, rdflib.URIRef('None')) not in workflow.rdf
+
     def test_construct_from_rdf_uri_not_in_subjects(self):
         rdf = read_rdf_test_resource('test_workflow.trig')
         # This URI is not in the subject of this RDF:
@@ -298,68 +306,3 @@ class TestFairWorkflow:
         pubinfo = test_workflow.publish_as_nanopub()
         assert mock_publish.call_count == 0
         assert pubinfo['nanopub_uri'] is None
-
-    def test_workflow_construction_and_execution(self):
-        """
-        Construct a workflow using the is_fairstep and is_fairworkflow decorators
-        and check that execution and returned provenance is as expected.
-        """
-        @is_fairstep(label='Addition')
-        def add(a:float, b:float) -> float:
-            """Adding up numbers."""
-            return a + b
-
-        @is_fairstep(label='Subtraction')
-        def sub(a: float, b: float) -> float:
-            """Subtracting numbers."""
-            return a - b
-
-        @is_fairstep(label='Multiplication')
-        def mul(a: float, b: float) -> float:
-            """Multiplying numbers."""
-            return a * b
-
-        @is_fairstep(label='A strange step with little use')
-        def weird(a: float, b:float) -> float:
-            """A weird function"""
-            return a * 2 + b * 4
-
-        @is_fairworkflow(label='My Workflow')
-        def my_workflow(in1, in2, in3):
-            """
-            A simple addition, subtraction, multiplication workflow
-            """
-            t1 = add(in1, in2)
-            t2 = sub(in1, in2)
-            t3 = mul(weird(t1, in3), t2)
-            return t3
-
-        fw = my_workflow(1, 4, 3)
-        assert isinstance(fw, FairWorkflow)
-
-        # TODO: Find out why execution hangs only when running in pytest. May be to do with the threading.
-        #result, prov = fw.execute(num_threads=1)
-        #assert result == -66
-        #assert isinstance(prov, Publication)
-
-    def test_workflow_complex_serialization(self):
-        class OtherType:
-            def __init__(self, message):
-                self.message = message
-
-        @is_fairstep(label='Returns the thing it recieves...')
-        def return_that_object(im:OtherType) -> OtherType:
-            return im
-
-        @is_fairworkflow(label='A workflow that passes an object reference around.')
-        def process_image(im: OtherType):
-            return return_that_object(im)
-
-        obj = OtherType('I could be e.g. a PIL Image')
-        fw = process_image(obj)
-
-        # TODO: Find out why execution hangs only when running in pytest. May be to do with the threading.
-        #result, prov = fw.execute()
-        #assert isinstance(result, type(obj))
-        #assert result.message == obj.message
-        #assert isinstance(prov, Publication)