Skip to content

Commit 4879ee3

Browse files
authored
MRG: Merge pull request #48 from octue/fix/empty-analysis-attributes
Prevent imaginary/empty attributes being retrieved from Analysis
2 parents 52a72b3 + 79690b1 commit 4879ee3

File tree

6 files changed

+40
-55
lines changed

6 files changed

+40
-55
lines changed

octue/resources/analysis.py

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import logging
33

44
from octue.definitions import OUTPUT_STRANDS
5-
from octue.exceptions import ProtectedAttributeException
65
from octue.mixins import Identifiable, Loggable, Serialisable, Taggable
76
from octue.resources.manifest import Manifest
87
from octue.utils.encoders import OctueJSONEncoder
@@ -62,27 +61,11 @@ def __init__(self, twine, skip_checks=False, **kwargs):
6261
# Pop any possible strand data sources before init superclasses (and tie them to protected attributes)
6362
strand_kwargs = ((name, kwargs.pop(name, None)) for name in ALL_STRANDS)
6463
for strand_name, strand_data in strand_kwargs:
65-
self.__setattr__(f"_{strand_name}", strand_data)
64+
setattr(self, f"{strand_name}", strand_data)
6665

6766
# Init superclasses
6867
super().__init__(**kwargs)
6968

70-
def __setattr__(self, name, value):
71-
""" Override setters for protected attributes (the strand contents may change, but the strands themselves
72-
shouldn't be changed after instantiation)
73-
"""
74-
if name in ALL_STRANDS:
75-
raise ProtectedAttributeException(f"You cannot set {name} on an instantiated Analysis")
76-
77-
super().__setattr__(name, value)
78-
79-
def __getattr__(self, name):
80-
""" Override public getters to point to protected attributes (the strand contents may change, but the strands
81-
themselves shouldn't be changed after instantiation)
82-
"""
83-
if name in ALL_STRANDS:
84-
return getattr(self, f"_{name}", None)
85-
8669
def finalise(self, output_dir=None):
8770
""" Validates and serialises output_values and output_manifest, optionally writing them to files
8871

octue/runner.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,8 @@ def _update_manifest_path(manifest, pathname):
109109
if pathname.endswith(".json"):
110110
manifest.path = os.path.split(pathname)[0]
111111

112-
# Otherwise do nothing and rely on manifest having its path variable set already
113-
return manifest
112+
# Otherwise do nothing and rely on manifest having its path variable set already
113+
return manifest
114114

115115
def run(
116116
self,

octue/templates/template-using-manifests/app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def run(analysis, *args, **kwargs):
2020
"""
2121

2222
# You can use the attached logger to record debug statements, general information, warnings or errors
23-
analysis.logger.info(f"Starting clean up of files in {analysis.input_dir}")
23+
analysis.logger.info(f"Starting clean up of files in {analysis.input_manifest.absolute_path}")
2424

2525
# Get the configuration value for our time averaging window (or if not present, use the default specified in
2626
# the twine)

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
setup(
1919
name="octue",
20-
version="0.1.5",
20+
version="0.1.6",
2121
py_modules=["cli"],
2222
install_requires=["click>=7.1.2", "twined==0.0.13"], # Dev note: you also need to bump twined in tox.ini
2323
url="https://www.github.com/octue/octue-sdk-python",

tests/resources/test_analysis.py

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
import os
2-
3-
from octue import exceptions
41
from octue.resources import Analysis
52
from twined import Twine
63
from ..base import BaseTestCase
@@ -20,23 +17,11 @@ def test_instantiate_analysis_with_twine(self):
2017
analysis = Analysis(twine=Twine(source="{}"))
2118
self.assertEqual(analysis.__class__.__name__, "Analysis")
2219

23-
def test_protected_setter(self):
24-
""" Ensures that protected attributes can't be set
20+
def test_non_existent_attributes_cannot_be_retrieved(self):
21+
""" Ensure attributes that don't exist on Analysis aren't retrieved as None and instead raise an error. See
22+
https://github.com/octue/octue-sdk-python/issues/45 for reasoning behind adding this.
2523
"""
26-
analysis = Analysis(twine="{}")
27-
with self.assertRaises(exceptions.ProtectedAttributeException) as error:
28-
analysis.configuration_values = {}
29-
30-
self.assertIn("You cannot set configuration_values on an instantiated Analysis", error.exception.args[0])
24+
analysis = Analysis(twine=Twine(source="{}"))
3125

32-
def test_protected_getter(self):
33-
""" Ensures that protected attributes can't be set
34-
"""
35-
analysis = Analysis(
36-
twine=str(os.path.join(self.data_path, "twines", "valid_schema_twine.json")),
37-
configuration_values={"n_iterations": 5},
38-
input_values={"height": 5},
39-
output_values={},
40-
)
41-
cfg = analysis.configuration_values
42-
self.assertIn("n_iterations", cfg.keys())
26+
with self.assertRaises(AttributeError):
27+
analysis.furry_purry_cat

tests/test_runner.py

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
from .base import BaseTestCase
44

55

6+
def mock_app(analysis):
7+
pass
8+
9+
610
class RunnerTestCase(BaseTestCase):
711
def test_instantiate_runner(self):
812
""" Ensures that runner whose twine requires configuration can be instantiated
@@ -27,10 +31,7 @@ def test_run_with_configuration_passes(self):
2731
configuration_values="{}",
2832
)
2933

30-
def fcn(analysis):
31-
pass
32-
33-
runner.run(fcn)
34+
runner.run(mock_app)
3435

3536
def test_instantiation_without_configuration_fails(self):
3637
""" Ensures that runner can be instantiated with a string that points to a path
@@ -72,11 +73,8 @@ def test_run_output_values_validation(self):
7273
)
7374

7475
# Test for failure with an incorrect output
75-
def fcn(analysis):
76-
pass
77-
7876
with self.assertRaises(twined.exceptions.InvalidValuesContents) as error:
79-
runner.run(fcn).finalise()
77+
runner.run(mock_app).finalise()
8078

8179
self.assertIn("'n_iterations' is a required property", error.exception.args[0])
8280

@@ -112,13 +110,32 @@ def test_exception_raised_when_strand_data_missing(self):
112110
configuration_values={"n_iterations": 5},
113111
)
114112

115-
def fcn(analysis):
116-
pass
117-
118113
with self.assertRaises(twined.exceptions.TwineValueException) as error:
119-
runner.run(fcn)
114+
runner.run(mock_app)
120115

121116
self.assertIn(
122117
"The 'input_values' strand is defined in the twine, but no data is provided in sources",
123118
error.exception.args[0],
124119
)
120+
121+
def test_output_manifest_is_not_none(self):
122+
""" Ensure the output manifest of an analysis is not None if an output manifest is defined in the Twine. """
123+
runner = Runner(
124+
twine="""
125+
{
126+
"output_manifest": [
127+
{
128+
"key": "open_foam_result",
129+
"purpose": "A dataset containing solution fields of an openfoam case."
130+
},
131+
{
132+
"key": "airfoil_cp_values",
133+
"purpose": "A file containing cp values"
134+
}
135+
]
136+
}
137+
"""
138+
)
139+
140+
analysis = runner.run(mock_app)
141+
self.assertIsNotNone(analysis.output_manifest)

0 commit comments

Comments
 (0)