Repository: microsoft/Qcodes

Issue:
Documentation error v0.1.11: load_or_create_experiment
Hello, thank you for this awesome library. I am using it for my master thesis at Universidad Autónoma de Madrid (Spain) and find it really useful. While learning I have found an error in the documentation.

I am using qcodes v0.1.11 (last version I have been able to install with pip using your instructions (http://qcodes.github.io/Qcodes/start/index.html#installing-the-latest-qcodes-release). When I check the documentation of this version I can see there is a function `load_or_create_experiment` (http://qcodes.github.io/Qcodes/api/generated/qcodes.dataset.experiment_container.load_or_create_experiment.html) However, I cannot import it in qcodes.

### Steps to reproduce
```python
sys.version

'3.6.7 |Anaconda, Inc.| (default, Dec 10 2018, 20:35:02) [MSC v.1915 64 bit (AMD64)]'

import qcodes
qcodes.__version__
'0.1.11'

from qcodes.dataset import experiment_container
experiment_container.load_or_create_experiment()
    ---------------------------------------------------------------------------

    AttributeError                            Traceback (most recent call last)

    <ipython-input-16-44f48f692e42> in <module>
    ----> 1 experiment_container.load_or_create_experiment()
    

    AttributeError: module 'qcodes.dataset.experiment_container' has no attribute 'load_or_create_experiment'
```

### Expected behaviour

I am still a novice with github and qcodes but I have been able to find that this function was introduced last summer while the last release of qcodes dates from April. Thus, I would expect not to see this function in the documentation of v0.1.11. This might be happening also for other new features, but I haven't explored further.

If I want to use these new features, would you recommend me to clone the github repo and install it from source or are you planning to make a new release soon?




Discussion:
Thanks for your feedback! :)

Yes, we haven't been doing `pip` releases because of a ton of work lately. So, for now, just follow http://qcodes.github.io/Qcodes/start/index.html#installing-qcodes-from-github to have the latest qcodes with all the latest updates.

Resolving patch:
diff --git a/qcodes/dataset/param_spec.py b/qcodes/dataset/param_spec.py
--- a/qcodes/dataset/param_spec.py
+++ b/qcodes/dataset/param_spec.py
@@ -43,10 +43,19 @@ def __init__(self, name: str,
         inferred_from = [] if inferred_from is None else inferred_from
         depends_on = [] if depends_on is None else depends_on
 
+        if isinstance(inferred_from, str):
+            raise ValueError(f"ParamSpec {self.name} got "
+                             f"string {inferred_from} as inferred_from. "
+                             f"It needs a "
+                             f"Sequence of ParamSpecs or strings")
         self._inferred_from.extend(
             p.name if isinstance(p, ParamSpec) else p
             for p in inferred_from)
 
+        if isinstance(depends_on, str):
+            raise ValueError(f"ParamSpec {self.name} got "
+                             f"string {depends_on} as depends_on. It needs a "
+                             f"Sequence of ParamSpecs or strings")
         self._depends_on.extend(
             p.name if isinstance(p, ParamSpec) else p
             for p in depends_on)
diff --git a/qcodes/dataset/sqlite_base.py b/qcodes/dataset/sqlite_base.py
--- a/qcodes/dataset/sqlite_base.py
+++ b/qcodes/dataset/sqlite_base.py
@@ -606,7 +606,7 @@ def _2to3_get_paramspecs(conn: ConnectionPlus,
     # depend, then the dependent ParamSpecs and finally the rest
 
     for layout_id in list(indeps) + list(deps) + list(the_rest):
-        (name, label, unit, inferred_from) = layouts[layout_id]
+        (name, label, unit, inferred_from_str) = layouts[layout_id]
         # get the data type
         sql = f'PRAGMA TABLE_INFO("{result_table_name}")'
         c = transaction(conn, sql)
@@ -615,34 +615,23 @@ def _2to3_get_paramspecs(conn: ConnectionPlus,
                 paramtype = row['type']
                 break
 
-        # first possibility: another parameter depends on this parameter
-        if layout_id in indeps:
-            paramspec = ParamSpec(name=name, paramtype=paramtype,
-                                  label=label, unit=unit,
-                                  inferred_from=inferred_from)
-            paramspecs[layout_id] = paramspec
-
-        # second possibility: this parameter depends on another parameter
-        elif layout_id in deps:
+        inferred_from: List[str] = []
+        depends_on: List[str] = []
 
+        # this parameter depends on another parameter
+        if layout_id in deps:
             setpoints = dependencies[layout_id]
             depends_on = [paramspecs[idp].name for idp in setpoints]
 
-            paramspec = ParamSpec(name=name,
-                                  paramtype=paramtype,
-                                  label=label, unit=unit,
-                                  depends_on=depends_on,
-                                  inferred_from=inferred_from)
-            paramspecs[layout_id] = paramspec
+        if inferred_from_str != '':
+            inferred_from = inferred_from_str.split(', ')
 
-        # third possibility: no dependencies
-        else:
-            paramspec = ParamSpec(name=name,
-                                  paramtype=paramtype,
-                                  label=label, unit=unit,
-                                  depends_on=[],
-                                  inferred_from=[])
-            paramspecs[layout_id] = paramspec
+        paramspec = ParamSpec(name=name,
+                              paramtype=paramtype,
+                              label=label, unit=unit,
+                              depends_on=depends_on,
+                              inferred_from=inferred_from)
+        paramspecs[layout_id] = paramspec
 
     return paramspecs
 
@@ -720,6 +709,77 @@ def perform_db_upgrade_2_to_3(conn: ConnectionPlus) -> None:
             log.debug(f"Upgrade in transition, run number {run_id}: OK")
 
 
+@upgrader
+def perform_db_upgrade_3_to_4(conn: ConnectionPlus) -> None:
+    """
+    Perform the upgrade from version 3 to version 4. This really
+    repeats the version 3 upgrade as it originally had two bugs in
+    the inferred annotation. inferred_from was passed incorrectly
+    resulting in the parameter being marked inferred_from for each char
+    in the inferred_from variable and inferred_from was not handled
+    correctly for parameters that were neither dependencies nor dependent on
+    other parameters. Both have since been fixed so rerun the upgrade.
+    """
+
+    no_of_runs_query = "SELECT max(run_id) FROM runs"
+    no_of_runs = one(atomic_transaction(conn, no_of_runs_query), 'max(run_id)')
+    no_of_runs = no_of_runs or 0
+
+    # If one run fails, we want the whole upgrade to roll back, hence the
+    # entire upgrade is one atomic transaction
+
+    with atomic(conn) as conn:
+
+        result_tables = _2to3_get_result_tables(conn)
+        layout_ids_all = _2to3_get_layout_ids(conn)
+        indeps_all = _2to3_get_indeps(conn)
+        deps_all = _2to3_get_deps(conn)
+        layouts = _2to3_get_layouts(conn)
+        dependencies = _2to3_get_dependencies(conn)
+
+        pbar = tqdm(range(1, no_of_runs+1))
+        pbar.set_description("Upgrading database")
+
+        for run_id in pbar:
+
+            if run_id in layout_ids_all:
+
+                result_table_name = result_tables[run_id]
+                layout_ids = list(layout_ids_all[run_id])
+                if run_id in indeps_all:
+                    independents = tuple(indeps_all[run_id])
+                else:
+                    independents = ()
+                if run_id in deps_all:
+                    dependents = tuple(deps_all[run_id])
+                else:
+                    dependents = ()
+
+                paramspecs = _2to3_get_paramspecs(conn,
+                                                  layout_ids,
+                                                  layouts,
+                                                  dependencies,
+                                                  dependents,
+                                                  independents,
+                                                  result_table_name)
+
+                interdeps = InterDependencies(*paramspecs.values())
+                desc = RunDescriber(interdeps=interdeps)
+                json_str = desc.to_json()
+
+            else:
+
+                json_str = RunDescriber(InterDependencies()).to_json()
+
+            sql = f"""
+                   UPDATE runs
+                   SET run_description = ?
+                   WHERE run_id == ?
+                   """
+            cur = conn.cursor()
+            cur.execute(sql, (json_str, run_id))
+            log.debug(f"Upgrade in transition, run number {run_id}: OK")
+
 def _latest_available_version() -> int:
     """Return latest available database schema version"""
     return len(_UPGRADE_ACTIONS)


Test patch:
diff --git a/qcodes/tests/dataset/legacy_DB_generation/README.md b/qcodes/tests/dataset/legacy_DB_generation/README.md
deleted file mode 100644
--- a/qcodes/tests/dataset/legacy_DB_generation/README.md
+++ /dev/null
@@ -1,26 +0,0 @@
-# Legacy database file generation
-
-## Why have old database files?
-
-When the database schema (the database user version number) changes, the API in the core QCoDeS repository also changes to produce output matching the new version. That, in turn, makes the official API incapable of generating databases of older versions. To the end of testing the upgrade functions, we would like to be able to generate old version databases. The purpose of the `legacy_DB_generation` folder is contain scripts that generate old-version databases for the tests to consume.
-
-When a new corner-case that we want to test for is discovered, we extend the scripts to produce a .db-file covering that case. When a new version upgrade is launched, we make a new script to generate .db-files for that version upgrade to test itself on.
-
-## How to run the scripts?
-
-The scripts use `gitpython` to roll QCoDeS back to a relevant old commit and create database files with the ancient source code.
-
-The scripts should *not* be run as a part of the QCoDeS test suite, but prior to test execution in a different process. The scripts have some dependencies and will run in the normal QCoDeS environment **PROVIDED** that QCoDeS was installed with the editable flag (i.e. `pip install -e <path-to-qcodes>`).
-
-## How do I write my own script?
-
-First, please check if there is already a script producing .db-files of your desired version. If so, simply extend that script to produce a .db-file covering your particular test case. If not, follow this checklist:
-
- * Identify the database version of which you would like to create one or more .db-files. Let's call that "your version".
- * Check the variable `GIT_HASHES` in `utils.py` to see if "your version" already has a recorded commit hash.
-   * If not, search through the `git log` of `master` to find the merge commit *just* before the merge commit that introduces the *next* version after "your version". Put that first commit into `GIT_HASHES` along with the version number of "your version".
- * Make a script called `generate_version_<your_version>.py`. Copy the general structure of `generate_version_0.py`. Make your generating functions take *ZERO* arguments and do all their imports inside their own scope.
-
-## Anything else?
-
-Remember to update the tests to use your newly generated fixtures. A test must **skip** (not fail) if the fixture is not present on disk. Also make sure that the CI runs your fixture-generating script.
diff --git a/qcodes/tests/dataset/legacy_DB_generation/__init__.py b/qcodes/tests/dataset/legacy_DB_generation/__init__.py
deleted file mode 100644
diff --git a/qcodes/tests/dataset/legacy_DB_generation/generate_version_0.py b/qcodes/tests/dataset/legacy_DB_generation/generate_version_0.py
deleted file mode 100644
--- a/qcodes/tests/dataset/legacy_DB_generation/generate_version_0.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# Generate version 0 database files for qcodes' test suite to consume
-
-import os
-
-# NB: it's important that we do not import anything from qcodes before we
-# do the git magic (which we do below), hence the relative import here
-import utils as utils
-
-
-fixturepath = os.sep.join(os.path.realpath(__file__).split(os.sep)[:-2])
-fixturepath = os.path.join(fixturepath, 'fixtures', 'db_files')
-
-
-def generate_empty_DB_file():
-    """
-    Generate the bare minimal DB file with no runs
-    """
-
-    import qcodes.dataset.sqlite_base as sqlite_base
-
-    v0fixturepath = os.path.join(fixturepath, 'version0')
-    os.makedirs(v0fixturepath, exist_ok=True)
-    path = os.path.join(v0fixturepath, 'empty.db')
-
-    if os.path.exists(path):
-        os.remove(path)
-
-    conn = sqlite_base.connect(path)
-    sqlite_base.init_db(conn)
-
-
-if __name__ == '__main__':
-
-    gens = (generate_empty_DB_file,)
-
-    # pylint: disable=E1101
-    utils.checkout_to_old_version_and_run_generators(version=0, gens=gens)
diff --git a/qcodes/tests/dataset/legacy_DB_generation/generate_version_1.py b/qcodes/tests/dataset/legacy_DB_generation/generate_version_1.py
deleted file mode 100644
--- a/qcodes/tests/dataset/legacy_DB_generation/generate_version_1.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# Generate version 1 database files for qcodes' test suite to consume
-
-import os
-
-# NB: it's important that we do not import anything from qcodes before we
-# do the git magic (which we do below), hence the relative import here
-import utils as utils
-
-
-fixturepath = os.sep.join(os.path.realpath(__file__).split(os.sep)[:-2])
-fixturepath = os.path.join(fixturepath, 'fixtures', 'db_files')
-
-
-def generate_empty_DB_file():
-    """
-    Generate the bare minimal DB file with no runs
-    """
-
-    import qcodes.dataset.sqlite_base as sqlite_base
-
-    v0fixturepath = os.path.join(fixturepath, 'version1')
-    os.makedirs(v0fixturepath, exist_ok=True)
-    path = os.path.join(v0fixturepath, 'empty.db')
-
-    if os.path.exists(path):
-        os.remove(path)
-
-    sqlite_base.connect(path)
-
-
-if __name__ == '__main__':
-
-    gens = (generate_empty_DB_file,)
-
-    # pylint: disable=E1101
-    utils.checkout_to_old_version_and_run_generators(version=1, gens=gens)
diff --git a/qcodes/tests/dataset/legacy_DB_generation/generate_version_2.py b/qcodes/tests/dataset/legacy_DB_generation/generate_version_2.py
deleted file mode 100644
--- a/qcodes/tests/dataset/legacy_DB_generation/generate_version_2.py
+++ /dev/null
@@ -1,146 +0,0 @@
-# Generate version 2 database files for qcodes' test suite to consume
-
-import os
-import numpy as np
-
-# NB: it's important that we do not import anything from qcodes before we
-# do the git magic (which we do below), hence the relative import here
-import utils as utils
-
-
-fixturepath = os.sep.join(os.path.realpath(__file__).split(os.sep)[:-2])
-fixturepath = os.path.join(fixturepath, 'fixtures', 'db_files')
-
-
-def generate_empty_DB_file():
-    """
-    Generate the bare minimal DB file with no runs
-    """
-
-    import qcodes.dataset.sqlite_base as sqlite_base
-
-    v2fixturepath = os.path.join(fixturepath, 'version2')
-    os.makedirs(v2fixturepath, exist_ok=True)
-    path = os.path.join(v2fixturepath, 'empty.db')
-
-    if os.path.exists(path):
-        os.remove(path)
-
-    sqlite_base.connect(path)
-
-
-def generate_DB_file_with_some_runs():
-    """
-    Generate a .db-file with a handful of runs with some interdependent
-    parameters
-    """
-
-    # This function will run often on CI and re-generate the .db-files
-    # That should ideally be a deterministic action
-    # (although this hopefully plays no role)
-    np.random.seed(0)
-
-    v2fixturepath = os.path.join(fixturepath, 'version2')
-    os.makedirs(v2fixturepath, exist_ok=True)
-    path = os.path.join(v2fixturepath, 'some_runs.db')
-
-    if os.path.exists(path):
-        os.remove(path)
-
-    from qcodes.dataset.sqlite_base import connect
-    from qcodes.dataset.measurements import Measurement
-    from qcodes.dataset.experiment_container import Experiment
-    from qcodes import Parameter
-
-    connect(path)
-    exp = Experiment(path)
-    exp._new(name='experiment_1', sample_name='no_sample_1')
-
-    # Now make some parameters to use in measurements
-    params = []
-    for n in range(5):
-        params.append(Parameter(f'p{n}', label=f'Parameter {n}',
-                                unit=f'unit {n}', set_cmd=None, get_cmd=None))
-
-    # Set up an experiment
-
-    meas = Measurement(exp)
-    meas.register_parameter(params[0])
-    meas.register_parameter(params[1])
-    meas.register_parameter(params[2], basis=(params[0],))
-    meas.register_parameter(params[3], basis=(params[1],))
-    meas.register_parameter(params[4], setpoints=(params[2], params[3]))
-
-    # Make a number of identical runs
-
-    for _ in range(10):
-
-        with meas.run() as datasaver:
-
-            for x in np.random.rand(10):
-                for y in np.random.rand(10):
-                    z = np.random.rand()
-                    datasaver.add_result((params[2], x),
-                                         (params[3], y),
-                                         (params[4], z))
-
-
-def generate_DB_file_with_empty_runs():
-    """
-    Generate a DB file that holds empty runs and runs with no interdependencies
-    """
-
-    v2fixturepath = os.path.join(fixturepath, 'version2')
-    os.makedirs(v2fixturepath, exist_ok=True)
-    path = os.path.join(v2fixturepath, 'empty_runs.db')
-
-    if os.path.exists(path):
-        os.remove(path)
-
-    from qcodes.dataset.sqlite_base import connect
-    from qcodes.dataset.measurements import Measurement
-    from qcodes.dataset.experiment_container import Experiment
-    from qcodes import Parameter
-    from qcodes.dataset.data_set import DataSet
-
-    conn = connect(path)
-    exp = Experiment(path)
-    exp._new(name='experiment_1', sample_name='no_sample_1')
-
-    # Now make some parameters to use in measurements
-    params = []
-    for n in range(5):
-        params.append(Parameter(f'p{n}', label=f'Parameter {n}',
-                                unit=f'unit {n}', set_cmd=None, get_cmd=None))
-
-    # truly empty run, no layouts table, no nothing
-    dataset = DataSet(path, conn=conn)
-    dataset._new('empty_dataset', exp_id=exp.exp_id)
-
-    # empty run
-    meas = Measurement(exp)
-    with meas.run() as datasaver:
-        pass
-
-    # run with no interdeps
-    meas = Measurement(exp)
-    for param in params:
-        meas.register_parameter(param)
-
-    with meas.run() as datasaver:
-        pass
-
-    with meas.run() as datasaver:
-        for _ in range(10):
-            res = tuple((p, 0.0) for p in params)
-            datasaver.add_result(*res)
-
-
-if __name__ == '__main__':
-
-    gens = (generate_empty_DB_file,
-            generate_DB_file_with_some_runs,
-            generate_DB_file_with_empty_runs)
-
-    # pylint: disable=E1101
-    utils.checkout_to_old_version_and_run_generators(version=2, gens=gens)
diff --git a/qcodes/tests/dataset/legacy_DB_generation/generate_version_3.py b/qcodes/tests/dataset/legacy_DB_generation/generate_version_3.py
deleted file mode 100644
--- a/qcodes/tests/dataset/legacy_DB_generation/generate_version_3.py
+++ /dev/null
@@ -1,139 +0,0 @@
-# Generate version 3 database files for qcodes' test suite to consume
-
-import os
-import numpy as np
-
-# NB: it's important that we do not import anything from qcodes before we
-# do the git magic (which we do below), hence the relative import here
-import utils as utils
-
-
-fixturepath = os.sep.join(os.path.realpath(__file__).split(os.sep)[:-2])
-fixturepath = os.path.join(fixturepath, 'fixtures', 'db_files')
-
-
-def generate_empty_DB_file():
-    """
-    Generate the bare minimal DB file with no runs
-    """
-
-    import qcodes.dataset.sqlite_base as sqlite_base
-
-    v3fixturepath = os.path.join(fixturepath, 'version3')
-    os.makedirs(v3fixturepath, exist_ok=True)
-    path = os.path.join(v3fixturepath, 'empty.db')
-
-    if os.path.exists(path):
-        os.remove(path)
-
-    sqlite_base.connect(path)
-
-
-def generate_DB_file_with_some_runs_having_not_run_descriptions():
-    """
-    Generate a .db-file with a handful of runs some of which lack run
-    description or have it as empty object (based on a real case).
-
-    Generated runs:
-        #1: run with parameters and correct run description
-        #2: run with parameters but run description is NULL
-        #3: run with parameters but run description is empty RunDescriber
-        #4: run without parameters but run description is NULL
-    """
-    v3fixturepath = os.path.join(fixturepath, 'version3')
-    os.makedirs(v3fixturepath, exist_ok=True)
-    path = os.path.join(v3fixturepath, 'some_runs_without_run_description.db')
-
-    if os.path.exists(path):
-        os.remove(path)
-
-    from qcodes.dataset.measurements import Measurement
-    from qcodes.dataset.experiment_container import Experiment
-    from qcodes import Parameter
-    from qcodes.dataset.descriptions import RunDescriber
-    from qcodes.dataset.dependencies import InterDependencies
-
-    exp = Experiment(path_to_db=path,
-                     name='experiment_1',
-                     sample_name='no_sample_1')
-    conn = exp.conn
-
-    # Now make some parameters to use in measurements
-    params = []
-    for n in range(5):
-        params.append(Parameter(f'p{n}', label=f'Parameter {n}',
-                                unit=f'unit {n}', set_cmd=None, get_cmd=None))
-
-    # Set up a measurement
-
-    meas = Measurement(exp)
-    meas.register_parameter(params[0])
-    meas.register_parameter(params[1])
-    meas.register_parameter(params[2], basis=(params[0],))
-    meas.register_parameter(params[3], basis=(params[1],))
-    meas.register_parameter(params[4], setpoints=(params[2], params[3]))
-
-    # Initially make 3 correct runs
-
-    run_ids = []
-
-    for _ in range(3):
-
-        with meas.run() as datasaver:
-
-            for x in np.random.rand(10):
-                for y in np.random.rand(10):
-                    z = np.random.rand()
-                    datasaver.add_result((params[2], x),
-                                         (params[3], y),
-                                         (params[4], z))
-
-        run_ids.append(datasaver.run_id)
-
-    assert [1, 2, 3] == run_ids, 'Run ids of generated runs are not as ' \
-                                 'expected after generating runs #1-3'
-
-    # Formulate SQL query for adjusting run_description column
-
-    set_run_description_sql = f"""
-               UPDATE runs
-               SET run_description = ?
-               WHERE run_id == ?
-               """
-
-    # Make run_description of run #2 NULL
-
-    conn.execute(set_run_description_sql, (None, run_ids[1]))
-    conn.commit()  # just to be sure
-
-    # Make run_description of run #3 equivalent to an empty RunDescriber
-
-    empty_run_description = RunDescriber(InterDependencies()).to_json()
-    conn.execute(set_run_description_sql, (empty_run_description, run_ids[2]))
-    conn.commit()  # just to be sure
-
-    # Set up a measurement without parameters, and create run #4 out of it
-
-    meas_no_params = Measurement(exp)
-
-    with meas_no_params.run() as datasaver:
-        pass
-
-    run_ids.append(datasaver.run_id)
-
-    assert [1, 2, 3, 4] == run_ids, 'Run ids of generated runs are not as ' \
-                                    'expected after generating run #4'
-
-    # Make run_description of run #4 NULL
-
-    conn.execute(set_run_description_sql, (None, run_ids[3]))
-    conn.commit()  # just to be sure
-
-
-if __name__ == '__main__':
-
-    gens = (generate_empty_DB_file,
-            generate_DB_file_with_some_runs_having_not_run_descriptions)
-
-    # pylint: disable=E1101
-    utils.checkout_to_old_version_and_run_generators(version=3, gens=gens)
diff --git a/qcodes/tests/dataset/legacy_DB_generation/utils.py b/qcodes/tests/dataset/legacy_DB_generation/utils.py
deleted file mode 100644
--- a/qcodes/tests/dataset/legacy_DB_generation/utils.py
+++ /dev/null
@@ -1,88 +0,0 @@
-# General utilities for the database generation and loading scheme
-from typing import Dict, List, Tuple
-from contextlib import contextmanager
-import os
-
-from git import Repo
-
-# A brief overview of what each version introduces:
-#
-# Version 0: the original table schema, runs, experiments, layouts,
-# dependencies, result-tables
-#
-# Version 1: a GUID column is added to the runs table
-#
-# Version 2: indices are added to runs; GUID and exp_id
-#
-# Version 3: run_description column is added to the runs table
-#
-
-# NOTE that each hash is supposed to be representing a commit JUST before the
-# "next" version is being introduced.
-#
-
-GIT_HASHES: Dict[int, str] = {0: '78d42620fc245a975b5a615ed5e33061baac7846',
-                              1: '056d59627e22fa3ca7aad4c265e9897c343f79cf',
-                              2: '5202255924542dad6841dfe3d941a7f80c43956c',
-                              # NOTE: commit hash associated with version 3
-                              # will need to be updated once we move to
-                              # version 4
-                              3: 'a5d8edae05898df0cbfb5d9048565df91efea692'}
-
-gitrepopath = os.sep.join(os.path.realpath(__file__).split(os.sep)[:-5])
-repo = Repo(gitrepopath)
-
-
-@contextmanager
-def leave_untouched(repo):
-    """
-    Leave a git repository untouched by whatever fiddling around we need to do
-    We support both the case of the repo being initially in a detached head
-    state (relevant for Travis) and the -hopefully- normal case for users of
-    being at the tip of a branch
-    """
-
-    if repo.is_dirty():
-        raise ValueError('Git repository is dirty. Can not proceed.')
-
-    was_detached = repo.head.is_detached
-
-    if not was_detached:
-        current_branch = repo.active_branch
-    current_commit = repo.head.commit
-
-    try:
-        yield
-
-    finally:
-        repo.git.reset('--hard', current_commit)
-        if not was_detached:
-            repo.git.checkout(current_branch)
-
-
-def checkout_to_old_version_and_run_generators(version: int,
-                                               gens: Tuple) -> None:
-    """
-    Check out the repo to an older version and run the generating functions
-    supplied.
-    """
-
-    with leave_untouched(repo):
-
-        repo.git.checkout(GIT_HASHES[version])
-
-        # If QCoDeS is not installed in editable mode, it makes no difference
-        # to do our git magic, since the import will be from site-packages in
-        # the environment folder, and not from the git-managed folder
-        import qcodes
-        qcpath = os.sep.join(qcodes.__file__.split(os.sep)[:-2])
-
-        # Windows and paths... There can be random un-capitalizations
-        if qcpath.lower() != gitrepopath.lower():
-            raise ValueError('QCoDeS does not seem to be installed in editable'
-                             ' mode, can not proceed. To use this script, '
-                             'uninstall QCoDeS and reinstall it with pip '
-                             'install -e <path-to-qcodes-folder>')
-
-        for generator in gens:
-            generator()
diff --git a/qcodes/tests/dataset/test_database_creation_and_upgrading.py b/qcodes/tests/dataset/test_database_creation_and_upgrading.py
--- a/qcodes/tests/dataset/test_database_creation_and_upgrading.py
+++ b/qcodes/tests/dataset/test_database_creation_and_upgrading.py
@@ -28,6 +28,7 @@
                                         perform_db_upgrade_0_to_1,
                                         perform_db_upgrade_1_to_2,
                                         perform_db_upgrade_2_to_3,
+                                        perform_db_upgrade_3_to_4,
                                         _latest_available_version)
 
 from qcodes.dataset.guids import parse_guid
@@ -124,7 +125,8 @@ def test_perform_actual_upgrade_0_to_1():
 
     if not os.path.exists(dbname_old):
         pytest.skip("No db-file fixtures found. You can generate test db-files"
-                    " using the scripts in the legacy_DB_generation folder")
+                    " using the scripts in the "
+                    "https://github.com/QCoDeS/qcodes_generate_test_db/ repo")
 
     with temporarily_copied_DB(dbname_old, debug=False, version=0) as conn:
 
@@ -182,7 +184,8 @@ def test_perform_actual_upgrade_2_to_3_empty():
 
     if not os.path.exists(dbname_old):
         pytest.skip("No db-file fixtures found. You can generate test db-files"
-                    " using the scripts in the legacy_DB_generation folder")
+                    " using the scripts in the "
+                    "https://github.com/QCoDeS/qcodes_generate_test_db/ repo")
 
     with temporarily_copied_DB(dbname_old, debug=False, version=2) as conn:
 
@@ -211,7 +214,8 @@ def test_perform_actual_upgrade_2_to_3_empty_runs():
 
     if not os.path.exists(dbname_old):
         pytest.skip("No db-file fixtures found. You can generate test db-files"
-                    " using the scripts in the legacy_DB_generation folder")
+                    " using the scripts in the "
+                    "https://github.com/QCoDeS/qcodes_generate_test_db/ repo")
 
     with temporarily_copied_DB(dbname_old, debug=False, version=2) as conn:
 
@@ -226,7 +230,8 @@ def test_perform_actual_upgrade_2_to_3_some_runs():
 
     if not os.path.exists(dbname_old):
         pytest.skip("No db-file fixtures found. You can generate test db-files"
-                    " using the scripts in the legacy_DB_generation folder")
+                    " using the scripts in the"
+                    "https://github.com/QCoDeS/qcodes_generate_test_db/ repo")
 
     with temporarily_copied_DB(dbname_old, debug=False, version=2) as conn:
 
@@ -259,16 +264,277 @@ def test_perform_actual_upgrade_2_to_3_some_runs():
 
         p0 = [p for p in idp.paramspecs if p.name == 'p0'][0]
         assert p0.depends_on == ''
+        assert p0.depends_on_ == []
+        assert p0.inferred_from == ''
+        assert p0.inferred_from_ == []
+        assert p0.label == "Parameter 0"
+        assert p0.unit == "unit 0"
+
+        p1 = [p for p in idp.paramspecs if p.name == 'p1'][0]
+        assert p1.depends_on == ''
+        assert p1.depends_on_ == []
+        assert p1.inferred_from == ''
+        assert p1.inferred_from_ == []
+        assert p1.label == "Parameter 1"
+        assert p1.unit == "unit 1"
+
+        p2 = [p for p in idp.paramspecs if p.name == 'p2'][0]
+        assert p2.depends_on == ''
+        assert p2.depends_on_ == []
+        assert p2.inferred_from == 'p0'
+        assert p2.inferred_from_ == ['p0']
+        assert p2.label == "Parameter 2"
+        assert p2.unit == "unit 2"
+
+        p3 = [p for p in idp.paramspecs if p.name == 'p3'][0]
+        assert p3.depends_on == ''
+        assert p3.depends_on_ == []
+        assert p3.inferred_from == 'p1, p0'
+        assert p3.inferred_from_ == ['p1', 'p0']
+        assert p3.label == "Parameter 3"
+        assert p3.unit == "unit 3"
+
+        p4 = [p for p in idp.paramspecs if p.name == 'p4'][0]
+        assert p4.depends_on == 'p2, p3'
+        assert p4.depends_on_ == ['p2', 'p3']
+        assert p4.inferred_from == ''
+        assert p4.inferred_from_ == []
+        assert p4.label == "Parameter 4"
+        assert p4.unit == "unit 4"
+
+        p5 = [p for p in idp.paramspecs if p.name == 'p5'][0]
+        assert p5.depends_on == ''
+        assert p5.depends_on_ == []
+        assert p5.inferred_from == 'p0'
+        assert p5.inferred_from_ == ['p0']
+        assert p5.label == "Parameter 5"
+        assert p5.unit == "unit 5"
+
+
+def test_perform_upgrade_v2_v3_to_v4_fixes():
+    """
+    Test that a db that was upgraded from v2 to v3 with a buggy
+    version will be corrected when upgraded to v4.
+    """
+
+    v3fixpath = os.path.join(fixturepath, 'db_files', 'version3')
+
+    dbname_old = os.path.join(v3fixpath, 'some_runs_upgraded_2.db')
+
+    if not os.path.exists(dbname_old):
+        pytest.skip("No db-file fixtures found. You can generate test db-files"
+                    " using the scripts in the"
+                    " https://github.com/QCoDeS/qcodes_generate_test_db/ repo")
+
+    with temporarily_copied_DB(dbname_old, debug=False, version=3) as conn:
+
+        assert get_user_version(conn) == 3
+
+        sql = f"""
+              SELECT run_description
+              FROM runs
+              WHERE run_id == 1
+              """
+        c = atomic_transaction(conn, sql)
+        json_str = one(c, 'run_description')
+
+        desc = RunDescriber.from_json(json_str)
+        idp = desc.interdeps
+        assert isinstance(idp, InterDependencies)
+
+        p0 = [p for p in idp.paramspecs if p.name == 'p0'][0]
+        assert p0.depends_on == ''
+        assert p0.depends_on_ == []
         assert p0.inferred_from == ''
+        assert p0.inferred_from_ == []
         assert p0.label == "Parameter 0"
         assert p0.unit == "unit 0"
 
+        p1 = [p for p in idp.paramspecs if p.name == 'p1'][0]
+        assert p1.depends_on == ''
+        assert p1.depends_on_ == []
+        assert p1.inferred_from == ''
+        assert p1.inferred_from_ == []
+        assert p1.label == "Parameter 1"
+        assert p1.unit == "unit 1"
+
+        p2 = [p for p in idp.paramspecs if p.name == 'p2'][0]
+        assert p2.depends_on == ''
+        assert p2.depends_on_ == []
+        # the 2 lines below are wrong due to the incorrect upgrade from
+        # db version 2 to 3
+        assert p2.inferred_from == 'p, 0'
+        assert p2.inferred_from_ == ['p', '0']
+        assert p2.label == "Parameter 2"
+        assert p2.unit == "unit 2"
+
+        p3 = [p for p in idp.paramspecs if p.name == 'p3'][0]
+        assert p3.depends_on == ''
+        assert p3.depends_on_ == []
+        # the 2 lines below are wrong due to the incorrect upgrade from
+        # db version 2 to 3
+        assert p3.inferred_from == 'p, 1, ,,  , p, 0'
+        assert p3.inferred_from_ == ['p', '1', ',', ' ', 'p', '0']
+        assert p3.label == "Parameter 3"
+        assert p3.unit == "unit 3"
+
         p4 = [p for p in idp.paramspecs if p.name == 'p4'][0]
         assert p4.depends_on == 'p2, p3'
+        assert p4.depends_on_ == ['p2', 'p3']
         assert p4.inferred_from == ''
+        assert p4.inferred_from_ == []
         assert p4.label == "Parameter 4"
         assert p4.unit == "unit 4"
 
+        p5 = [p for p in idp.paramspecs if p.name == 'p5'][0]
+        assert p5.depends_on == ''
+        assert p5.depends_on_ == []
+        # the 2 lines below are wrong due to the incorrect upgrade from
+        # db version 2 to 3. Here the interdep is missing
+        assert p5.inferred_from == ''
+        assert p5.inferred_from_ == []
+        assert p5.label == "Parameter 5"
+        assert p5.unit == "unit 5"
+
+        perform_db_upgrade_3_to_4(conn)
+
+        c = atomic_transaction(conn, sql)
+        json_str = one(c, 'run_description')
+
+        desc = RunDescriber.from_json(json_str)
+        idp = desc.interdeps
+        assert isinstance(idp, InterDependencies)
+
+        p0 = [p for p in idp.paramspecs if p.name == 'p0'][0]
+        assert p0.depends_on == ''
+        assert p0.depends_on_ == []
+        assert p0.inferred_from == ''
+        assert p0.inferred_from_ == []
+        assert p0.label == "Parameter 0"
+        assert p0.unit == "unit 0"
+
+        p1 = [p for p in idp.paramspecs if p.name == 'p1'][0]
+        assert p1.depends_on == ''
+        assert p1.depends_on_ == []
+        assert p1.inferred_from == ''
+        assert p1.inferred_from_ == []
+        assert p1.label == "Parameter 1"
+        assert p1.unit == "unit 1"
+
+        p2 = [p for p in idp.paramspecs if p.name == 'p2'][0]
+        assert p2.depends_on == ''
+        assert p2.depends_on_ == []
+        assert p2.inferred_from == 'p0'
+        assert p2.inferred_from_ == ['p0']
+        assert p2.label == "Parameter 2"
+        assert p2.unit == "unit 2"
+
+        p3 = [p for p in idp.paramspecs if p.name == 'p3'][0]
+        assert p3.depends_on == ''
+        assert p3.depends_on_ == []
+        assert p3.inferred_from == 'p1, p0'
+        assert p3.inferred_from_ == ['p1', 'p0']
+        assert p3.label == "Parameter 3"
+        assert p3.unit == "unit 3"
+
+        p4 = [p for p in idp.paramspecs if p.name == 'p4'][0]
+        assert p4.depends_on == 'p2, p3'
+        assert p4.depends_on_ == ['p2', 'p3']
+        assert p4.inferred_from == ''
+        assert p4.inferred_from_ == []
+        assert p4.label == "Parameter 4"
+        assert p4.unit == "unit 4"
+
+        p5 = [p for p in idp.paramspecs if p.name == 'p5'][0]
+        assert p5.depends_on == ''
+        assert p5.depends_on_ == []
+        assert p5.inferred_from == 'p0'
+        assert p5.inferred_from_ == ['p0']
+        assert p5.label == "Parameter 5"
+        assert p5.unit == "unit 5"
+
+
+def test_perform_upgrade_v3_to_v4():
+    """
+    Test that a db upgrade from v2 to v4 works correctly.
+    """
+
+    v3fixpath = os.path.join(fixturepath, 'db_files', 'version3')
+
+    dbname_old = os.path.join(v3fixpath, 'some_runs_upgraded_2.db')
+
+    if not os.path.exists(dbname_old):
+        pytest.skip("No db-file fixtures found. You can generate test db-files"
+                    " using the scripts in the "
+                    "https://github.com/QCoDeS/qcodes_generate_test_db/ repo")
+
+    with temporarily_copied_DB(dbname_old, debug=False, version=3) as conn:
+
+        assert get_user_version(conn) == 3
+
+        sql = f"""
+              SELECT run_description
+              FROM runs
+              WHERE run_id == 1
+              """
+
+        perform_db_upgrade_3_to_4(conn)
+
+        c = atomic_transaction(conn, sql)
+        json_str = one(c, 'run_description')
+
+        desc = RunDescriber.from_json(json_str)
+        idp = desc.interdeps
+        assert isinstance(idp, InterDependencies)
+
+        p0 = [p for p in idp.paramspecs if p.name == 'p0'][0]
+        assert p0.depends_on == ''
+        assert p0.depends_on_ == []
+        assert p0.inferred_from == ''
+        assert p0.inferred_from_ == []
+        assert p0.label == "Parameter 0"
+        assert p0.unit == "unit 0"
+
+        p1 = [p for p in idp.paramspecs if p.name == 'p1'][0]
+        assert p1.depends_on == ''
+        assert p1.depends_on_ == []
+        assert p1.inferred_from == ''
+        assert p1.inferred_from_ == []
+        assert p1.label == "Parameter 1"
+        assert p1.unit == "unit 1"
+
+        p2 = [p for p in idp.paramspecs if p.name == 'p2'][0]
+        assert p2.depends_on == ''
+        assert p2.depends_on_ == []
+        assert p2.inferred_from == 'p0'
+        assert p2.inferred_from_ == ['p0']
+        assert p2.label == "Parameter 2"
+        assert p2.unit == "unit 2"
+
+        p3 = [p for p in idp.paramspecs if p.name == 'p3'][0]
+        assert p3.depends_on == ''
+        assert p3.depends_on_ == []
+        assert p3.inferred_from == 'p1, p0'
+        assert p3.inferred_from_ == ['p1', 'p0']
+        assert p3.label == "Parameter 3"
+        assert p3.unit == "unit 3"
+
+        p4 = [p for p in idp.paramspecs if p.name == 'p4'][0]
+        assert p4.depends_on == 'p2, p3'
+        assert p4.depends_on_ == ['p2', 'p3']
+        assert p4.inferred_from == ''
+        assert p4.inferred_from_ == []
+        assert p4.label == "Parameter 4"
+        assert p4.unit == "unit 4"
+
+        p5 = [p for p in idp.paramspecs if p.name == 'p5'][0]
+        assert p5.depends_on == ''
+        assert p5.depends_on_ == []
+        assert p5.inferred_from == 'p0'
+        assert p5.inferred_from_ == ['p0']
+        assert p5.label == "Parameter 5"
+        assert p5.unit == "unit 5"
+
 
 @pytest.mark.usefixtures("empty_temp_db")
 def test_update_existing_guids(caplog):
@@ -371,7 +637,7 @@ def test_cannot_connect_to_newer_db():
 
 
 def test_latest_available_version():
-    assert 3 == _latest_available_version()
+    assert 4 == _latest_available_version()
 
 
 @pytest.mark.parametrize('version', VERSIONS)
@@ -383,7 +649,8 @@ def test_getting_db_version(version):
 
     if not os.path.exists(dbname):
         pytest.skip("No db-file fixtures found. You can generate test db-files"
-                    " using the scripts in the legacy_DB_generation folder")
+                    " using the scripts in the "
+                    "https://github.com/QCoDeS/qcodes_generate_test_db/ repo")
 
     (db_v, new_v) = get_db_version_and_newest_available_version(dbname)
 
diff --git a/qcodes/tests/dataset/test_database_extract_runs.py b/qcodes/tests/dataset/test_database_extract_runs.py
--- a/qcodes/tests/dataset/test_database_extract_runs.py
+++ b/qcodes/tests/dataset/test_database_extract_runs.py
@@ -443,7 +443,7 @@ def test_old_versions_not_touched(two_empty_temp_db_connections,
         with pytest.warns(UserWarning) as warning:
             extract_runs_into_db(fixturepath, target_path, 1)
             expected_mssg = ('Source DB version is 2, but this '
-                             'function needs it to be in version 3. '
+                             'function needs it to be in version 4. '
                              'Run this function again with '
                              'upgrade_source_db=True to auto-upgrade '
                              'the source DB file.')
@@ -465,7 +465,7 @@ def test_old_versions_not_touched(two_empty_temp_db_connections,
         with pytest.warns(UserWarning) as warning:
             extract_runs_into_db(source_path, fixturepath, 1)
             expected_mssg = ('Target DB version is 2, but this '
-                             'function needs it to be in version 3. '
+                             'function needs it to be in version 4. '
                              'Run this function again with '
                              'upgrade_target_db=True to auto-upgrade '
                              'the target DB file.')
diff --git a/qcodes/tests/dataset/test_paramspec.py b/qcodes/tests/dataset/test_paramspec.py
--- a/qcodes/tests/dataset/test_paramspec.py
+++ b/qcodes/tests/dataset/test_paramspec.py
@@ -47,7 +47,7 @@ def version_0_serializations():
                  'paramtype': 'array',
                  'label': 'My Array ParamSpec',
                  'unit': 'Ars',
-                 'inferred_from': ['p1', 'p2' ],
+                 'inferred_from': ['p1', 'p2'],
                  'depends_on': []})
     return sers
 
@@ -141,6 +141,11 @@ def test_depends_on(name1, name2, name3):
     assert ps1.depends_on == f"{ps2.name}, {ps3.name}, foo"
     assert ps1.depends_on_ == [ps2.name, ps3.name, "foo"]
 
+    with pytest.raises(ValueError,
+                       match=f"ParamSpec {name1} got string foo as depends_on. "
+                       "It needs a Sequence of ParamSpecs or strings"):
+        ParamSpec(name1, "numeric", depends_on='foo')
+
 
 @given(
     name1=hst.text(min_size=4, alphabet=alphabet),
@@ -156,6 +161,12 @@ def test_inferred_from(name1, name2, name3):
     assert ps1.inferred_from == f"{ps2.name}, {ps3.name}, bar"
     assert ps1.inferred_from_ == [ps2.name, ps3.name, "bar"]
 
+    with pytest.raises(ValueError,
+                       match=f"ParamSpec {name1} got string foo as "
+                       f"inferred_from. "
+                       "It needs a Sequence of ParamSpecs or strings"):
+        ParamSpec(name1, "numeric", inferred_from='foo')
+
 
 @given(
     name1=hst.text(min_size=4, alphabet=alphabet),