diff --git a/src/citrine/__version__.py b/src/citrine/__version__.py index 87dca0296..c13e81a87 100644 --- a/src/citrine/__version__.py +++ b/src/citrine/__version__.py @@ -1 +1 @@ -__version__ = "3.28.0" +__version__ = "3.29.0" diff --git a/src/citrine/informatics/executions/design_execution.py b/src/citrine/informatics/executions/design_execution.py index b2f42134a..0511cd06f 100644 --- a/src/citrine/informatics/executions/design_execution.py +++ b/src/citrine/informatics/executions/design_execution.py @@ -6,7 +6,7 @@ from citrine._serialization import properties from citrine._utils.functions import format_escaped_url from citrine.informatics.descriptors import Descriptor -from citrine.informatics.design_candidate import DesignCandidate +from citrine.informatics.design_candidate import DesignCandidate, HierarchicalDesignCandidate from citrine.informatics.predict_request import PredictRequest from citrine.informatics.scores import Score from citrine.informatics.executions.execution import Execution @@ -56,6 +56,22 @@ def candidates(self, *, per_page: int = 100) -> Iterable[DesignCandidate]: collection_builder=self._build_candidates, per_page=per_page) + @classmethod + def _build_hierarchical_candidates( + cls, subset_collection: Iterable[dict]) -> Iterable[HierarchicalDesignCandidate]: + for candidate in subset_collection: + yield HierarchicalDesignCandidate.build(candidate) + + def hierarchical_candidates(self, *, per_page: int = 100) -> Iterable[DesignCandidate]: + """Fetch the Design Candidates for the particular execution, paginated.""" + path = self._path() + '/candidate-histories' + + fetcher = partial(self._fetch_page, path=path, fetch_func=self._session.get_resource) + + return self._paginator.paginate(page_fetcher=fetcher, + collection_builder=self._build_hierarchical_candidates, + per_page=per_page) + def predict(self, predict_request: PredictRequest) -> DesignCandidate: """Invoke a prediction on a design candidate.""" diff --git a/tests/conftest.py b/tests/conftest.py index b841139bc..39d2266af 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -839,6 +839,30 @@ def example_hierarchical_design_material(example_design_material): } +@pytest.fixture() +def example_hierarchical_candidates(example_hierarchical_design_material): + return { + "page": 2, + "per_page": 4, + "response": [{ + "id": str(uuid.uuid4()), + "primary_score": 0, + "rank": 1, + "material": example_hierarchical_design_material, + "name": "Example candidate", + "hidden": True, + "comments": [ + { + "message": "a message", + "created": { + "user": str(uuid.uuid4()), + "time": '2025-02-20T10:46:26Z' + } + } + ] + }] + } + @pytest.fixture() def example_candidates(example_design_material): return { diff --git a/tests/resources/test_design_executions.py b/tests/resources/test_design_executions.py index 48cf3c4c2..4dc786212 100644 --- a/tests/resources/test_design_executions.py +++ b/tests/resources/test_design_executions.py @@ -101,6 +101,22 @@ def test_workflow_execution_results(workflow_execution: DesignExecution, session assert session.last_call == FakeCall(method='GET', path=expected_path, params={"per_page": 4, 'page': 1}) +def test_workflow_execution_hierarchical_results(workflow_execution: DesignExecution, session, example_hierarchical_candidates): + # Given + session.set_response(example_hierarchical_candidates) + + # When + list(workflow_execution.hierarchical_candidates(per_page=4)) + + # Then + expected_path = '/projects/{}/design-workflows/{}/executions/{}/candidate-histories'.format( + workflow_execution.project_id, + workflow_execution.workflow_id, + workflow_execution.uid, + ) + assert session.last_call == FakeCall(method='GET', path=expected_path, params={"per_page": 4, 'page': 1}) + + def test_workflow_execution_results_pinned(workflow_execution: DesignExecution, session, example_candidates): # Given pinned_by = uuid.uuid4()