Repository: cogent3/cogent3
Pull request #996 (2021-12-09 23:57:30)

API: app.result objects require source instance of str or pathlib.Path
[CHANGED] We now apply the get_data_source() function to provided source
    value and raise a ValueError if the returned value is not a str or Path.
    (Note that the data_store.DataMember is a subclass of string.)

NOTE: this affects all result types!


Patch:
diff --git a/src/cogent3/app/result.py b/src/cogent3/app/result.py
index 5de67b78f..89d2fcd34 100644
--- a/src/cogent3/app/result.py
+++ b/src/cogent3/app/result.py
@@ -3,9 +3,11 @@ import json
 from collections import OrderedDict
 from collections.abc import MutableMapping
 from functools import total_ordering
+from pathlib import Path
 
 import numpy
 
+from cogent3.app.data_store import get_data_source
 from cogent3.maths.stats import chisqprob
 from cogent3.util.misc import extend_docstring_from, get_object_provenance
 from cogent3.util.table import Table
@@ -28,6 +30,10 @@ class generic_result(MutableMapping):
     _item_types = ()
 
     def __init__(self, source):
+        source = get_data_source(source)
+        if not isinstance(source, (str, Path)):
+            raise ValueError(f"Cannot infer source from type {type(source)}")
+
         self._store = {}
         self._construction_kwargs = dict(source=source)
         self.source = source
@@ -142,12 +148,13 @@ class model_result(generic_result):
         if type(stat) == str:
             stat = eval(stat)
 
-        self._construction_kwargs = dict(
-            name=name,
-            stat=stat.__name__,
-            source=source,
-            elapsed_time=elapsed_time,
-            num_evaluations=num_evaluations,
+        self._construction_kwargs.update(
+            dict(
+                name=name,
+                stat=stat.__name__,
+                elapsed_time=elapsed_time,
+                num_evaluations=num_evaluations,
+            )
         )
         self._store = {}
         self._name = name
@@ -526,7 +533,7 @@ class hypothesis_result(model_collection_result):
             key for the null hypothesis
         """
         super(hypothesis_result, self).__init__(name=name, source=source)
-        self._construction_kwargs = dict(name_of_null=name_of_null, source=source)
+        self._construction_kwargs.update(dict(name_of_null=name_of_null))
 
         self._name_of_null = name_of_null
 
@@ -620,7 +627,6 @@ class bootstrap_result(generic_result):
 
     def __init__(self, source=None):
         super(bootstrap_result, self).__init__(source)
-        self._construction_kwargs = dict(source=source)
 
     @property
     def observed(self):


Test patch:
diff --git a/tests/test_app/test_data_store.py b/tests/test_app/test_data_store.py
index a91887291..16eea6d6e 100644
--- a/tests/test_app/test_data_store.py
+++ b/tests/test_app/test_data_store.py
@@ -868,7 +868,9 @@ class TestFunctions(TestCase):
 
         for val_klass in (str, pathlib.Path):
             value = val_klass("some/path.txt")
-            obj = make_unaligned_seqs(data=dict(seq1="ACGG"), info=dict(source=value))
+            obj = make_unaligned_seqs(
+                data=dict(seq1="ACGG"), info=dict(source=value, random_key=1234)
+            )
             got = get_data_source(obj)
             self.assertEqual(got, str(value))
 
diff --git a/tests/test_app/test_result.py b/tests/test_app/test_result.py
index 5423d52a4..dd2c2293e 100644
--- a/tests/test_app/test_result.py
+++ b/tests/test_app/test_result.py
@@ -1,7 +1,10 @@
+import pathlib
+
 from unittest import TestCase, main
 
 from cogent3 import make_aligned_seqs, make_table
 from cogent3.app import evo as evo_app
+from cogent3.app.data_store import DataStoreMember
 from cogent3.app.result import (
     generic_result,
     hypothesis_result,
@@ -10,6 +13,7 @@ from cogent3.app.result import (
     tabular_result,
 )
 from cogent3.util.deserialise import deserialise_object
+from cogent3.util.dict_array import DictArray
 
 
 __author__ = "Gavin Huttley"
@@ -21,8 +25,6 @@ __maintainer__ = "Gavin Huttley"
 __email__ = "Gavin.Huttley@anu.edu.au"
 __status__ = "Alpha"
 
-from cogent3.util.dict_array import DictArray
-
 
 class TestGenericResult(TestCase):
     def test_deserialised_values(self):
@@ -72,6 +74,30 @@ class TestGenericResult(TestCase):
         with self.assertRaises(TypeError):
             gr["null"] = {0, 23}
 
+    def test_infers_source(self):
+        """flexible handling of data source"""
+        # works for string
+        source = "path/blah.fasta"
+        aln = make_aligned_seqs(
+            {"A": "ACGT"}, info=dict(source=source, random_key=1234)
+        )
+        gr = generic_result(aln)
+        self.assertEqual(gr.source, "path/blah.fasta")
+
+        # or Path
+        aln.info.source = pathlib.Path(source)
+        gr = generic_result(aln)
+        self.assertEqual(str(gr.source), str(pathlib.Path("path/blah.fasta")))
+
+        # or DataStoreMember
+        aln.info.source = DataStoreMember(source)
+        gr = generic_result(aln)
+        self.assertEqual(str(gr.source), "path/blah.fasta")
+
+        aln.info = {}
+        with self.assertRaises(ValueError):
+            generic_result(aln)
+
 
 class TestModelResult(TestCase):
     def test_repr(self):
@@ -246,7 +272,7 @@ class TestModelResult(TestCase):
 
     def test_model_result_invalid_setitem(self):
         """model_result raise TypeError if trying to set incorrect item type"""
-        mr = model_result()
+        mr = model_result(source="blah")
         with self.assertRaises(TypeError):
             mr["null"] = 23
 
@@ -283,7 +309,7 @@ class TestModelCollectionResult(TestCase):
 
     def test_get_best_model(self):
         """should correctly identify the best model"""
-        coll = model_collection_result(None)
+        coll = model_collection_result(source="blah")
         coll.update(self._model_results)
         got = coll.get_best_model()
         # we ensure a model_result instance is returned from the possible set
@@ -292,7 +318,7 @@ class TestModelCollectionResult(TestCase):
     def test_select_model(self):
         """correctly select models"""
         # we ensure a series of model_result instances is returned
-        coll = model_collection_result(None)
+        coll = model_collection_result(source="blah")
         coll.update(self._model_results)
         got = coll.select_models()
         self.assertTrue(len(got) > 0)
@@ -302,8 +328,8 @@ class TestModelCollectionResult(TestCase):
 
     def test_model_collection_result_repr(self):
         """constructed result can do the different repr"""
-        result = model_collection_result(None)
-        coll = model_collection_result(None)
+        result = model_collection_result(source="blah")
+        coll = model_collection_result(source="blah")
         coll.update(self._model_results)
         got = result.__repr__()
         self.assertIsInstance(got, str)
@@ -342,7 +368,7 @@ class TestModelCollectionResult(TestCase):
 
     def test_model_collection_result_invalid_setitem(self):
         """model_collection_result raise TypeError if trying to set incorrect item type"""
-        mcr = model_collection_result()
+        mcr = model_collection_result(source="blah")
         with self.assertRaises(TypeError):
             mcr["null"] = 23
 
@@ -373,7 +399,7 @@ class TestHypothesisResult(TestCase):
 
     def test_invalid_setitem(self):
         """hypothesis_result raise TypeError if trying to set incorrect item type"""
-        hr = hypothesis_result("null")
+        hr = hypothesis_result(name_of_null="null", source="blah")
         with self.assertRaises(TypeError):
             hr["null"] = {0, 23}
 
diff --git a/tests/test_util/test_deserialise.py b/tests/test_util/test_deserialise.py
index e654e2325..3d6f222d4 100644
--- a/tests/test_util/test_deserialise.py
+++ b/tests/test_util/test_deserialise.py
@@ -233,7 +233,7 @@ class TestDeserialising(TestCase):
         edge_vals = zip(aln.names, (2, 3, 4))
         for edge, val in edge_vals:
             lf.set_param_rule("kappa", edge=edge, init=val)
-        result = model_result(name="test")
+        result = model_result(name="test", source="blah")
         result[1] = lf
         self.assertIs(result[1], lf)
         self.assertEqual(result.nfp, lf.nfp)