Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/citrine/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "4.0.3"
__version__ = "4.1.0"
8 changes: 7 additions & 1 deletion src/citrine/informatics/data_sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from abc import abstractmethod
from uuid import UUID

from deprecation import deprecated

from citrine._serialization import properties
from citrine._serialization.polymorphic_serializable import PolymorphicSerializable
from citrine._serialization.serializable import Serializable
Expand Down Expand Up @@ -115,7 +117,7 @@ def from_gemtable(cls, table: GemTable) -> "GemTableDataSource":


class ExperimentDataSourceRef(Serializable['ExperimentDataSourceRef'], DataSource):
"""A reference to a data source based on an experiment result hosted on the data platform.
"""[DEPRECATED] A reference to a data source based on an experiment result on the platform.

Parameters
----------
Expand All @@ -129,6 +131,10 @@ class ExperimentDataSourceRef(Serializable['ExperimentDataSourceRef'], DataSourc

_data_source_type = "experiments"

@deprecated(deprecated_in="4.1.0", removed_in="5.0.0",
details="Replaced by creating materials from candidates on the platform. "
"Alternatively, you may convert the candidate into a collection of GEMD "
"objects manually.")
def __init__(self, *, datasource_id: UUID):
self.datasource_id: UUID = datasource_id

Expand Down
35 changes: 28 additions & 7 deletions src/citrine/informatics/experiment_values.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from deprecation import deprecated

from citrine._serialization.serializable import Serializable
from citrine._serialization.polymorphic_serializable import PolymorphicSerializable
from citrine._serialization import properties
Expand All @@ -14,11 +16,18 @@


class ExperimentValue(PolymorphicSerializable['ExperimentValue']):
"""An container for experiment values.
"""[DEPRECATED] An container for experiment values.

Abstract type that returns the proper type given a serialized dict.
"""

@classmethod
@deprecated(deprecated_in="4.1.0", removed_in="5.0.0",
details="Replaced by creating materials from candidates.")
def build(cls, data: dict) -> 'ExperimentValue':
"""Build the underlying type."""
return super().build(data)

@classmethod
def get_type(cls, data) -> type[Serializable]:
"""Return the subtype."""
Expand Down Expand Up @@ -67,62 +76,74 @@ def _equals(self, other, attrs) -> bool:


class RealExperimentValue(Serializable['RealExperimentValue'], ExperimentValue):
"""A floating point experiment result."""
"""[DEPRECATED] A floating point experiment result."""

value = properties.Float('value')
typ = properties.String('type', default='RealValue', deserializable=False)

@deprecated(deprecated_in="4.1.0", removed_in="5.0.0",
details="Replaced by creating materials from candidates.")
def __init__(self, value: float):
self.value = value


class IntegerExperimentValue(Serializable['IntegerExperimentValue'], ExperimentValue):
"""An integer value experiment result."""
"""[DEPRECATED] An integer value experiment result."""

value = properties.Integer('value')
typ = properties.String('type', default='IntegerValue', deserializable=False)

@deprecated(deprecated_in="4.1.0", removed_in="5.0.0",
details="Replaced by creating materials from candidates.")
def __init__(self, value: int):
self.value = value


class CategoricalExperimentValue(Serializable['CategoricalExperimentValue'], ExperimentValue):
"""An experiment result with a categorical value."""
"""[DEPRECATED] An experiment result with a categorical value."""

value = properties.String('value')
typ = properties.String('type', default='CategoricalValue', deserializable=False)

@deprecated(deprecated_in="4.1.0", removed_in="5.0.0",
details="Replaced by creating materials from candidates.")
def __init__(self, value: str):
self.value = value


class MixtureExperimentValue(Serializable['MixtureExperimentValue'], ExperimentValue):
"""An experiment result mapping ingredients and labels to real values."""
"""[DEPRECATED] An experiment result mapping ingredients and labels to real values."""

value = properties.Mapping(properties.String, properties.Float, 'value')
typ = properties.String('type', default='MixtureValue', deserializable=False)

@deprecated(deprecated_in="4.1.0", removed_in="5.0.0",
details="Replaced by creating materials from candidates.")
def __init__(self, value: dict[str, float]):
self.value = value


class ChemicalFormulaExperimentValue(Serializable['ChemicalFormulaExperimentValue'],
ExperimentValue):
"""Experiment value for a chemical formula."""
"""[DEPRECATED] Experiment value for a chemical formula."""

value = properties.String('value')
typ = properties.String('type', default='InorganicValue', deserializable=False)

@deprecated(deprecated_in="4.1.0", removed_in="5.0.0",
details="Replaced by creating materials from candidates.")
def __init__(self, value: str):
self.value = value


class MolecularStructureExperimentValue(Serializable['MolecularStructureExperimentValue'],
ExperimentValue):
"""Experiment value for a molecular structure."""
"""[DEPRECATED] Experiment value for a molecular structure."""

value = properties.String('value')
typ = properties.String('type', default='OrganicValue', deserializable=False)

@deprecated(deprecated_in="4.1.0", removed_in="5.0.0",
details="Replaced by creating materials from candidates.")
def __init__(self, value: str):
self.value = value
2 changes: 0 additions & 2 deletions src/citrine/informatics/predictors/graph_predictor.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ class GraphPredictor(VersionedEngineResource['GraphPredictor'], AsynchronousObje
description = properties.Optional(properties.String(), 'data.description')
predictors = properties.List(properties.Object(PredictorNode), 'data.instance.predictors')

# the default seems to be defined in instances, not the class itself
# this is tested in test_graph_default_training_data
training_data = properties.List(
properties.Object(DataSource), 'data.instance.training_data', default=[]
)
Expand Down
12 changes: 11 additions & 1 deletion src/citrine/resources/experiment_datasource.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from io import StringIO
from uuid import UUID

from deprecation import deprecated

from citrine._rest.collection import Collection
from citrine._serialization import properties
from citrine._serialization.serializable import Serializable
Expand Down Expand Up @@ -32,6 +34,8 @@ class CandidateExperimentSnapshot(Serializable['CandidateExperimentSnapshot']):
'overrides')
""":dict[str, ExperimentValue]: dictionary of candidate material variable overrides"""

@deprecated(deprecated_in="4.1.0", removed_in="5.0.0",
details="Replaced by creating materials from candidates.")
def __init__(self, *args, **kwargs):
"""Candidate experiment snapshots are not directly instantiated by the user."""
pass # pragma: no cover
Expand All @@ -58,6 +62,8 @@ class ExperimentDataSource(Serializable['ExperimentDataSource']):
create_time = properties.Datetime('metadata.created.time', serializable=False)
""":datetime: date and time at which this data source was created"""

@deprecated(deprecated_in="4.1.0", removed_in="5.0.0",
details="Replaced by creating materials from candidates.")
def __init__(self, *args, **kwargs):
"""Experiment data sources are not directly instantiated by the user."""
pass # pragma: no cover
Expand Down Expand Up @@ -86,13 +92,17 @@ def read(self) -> str:


class ExperimentDataSourceCollection(Collection[ExperimentDataSource]):
"""Represents the collection of all experiment data sources associated with a project."""
"""[DEPRECATED] The collection of all experiment data sources associated with a project."""

_path_template = 'projects/{project_id}/candidate-experiment-datasources'
_individual_key = None
_resource = ExperimentDataSource
_collection_key = 'response'

@deprecated(deprecated_in="4.1.0", removed_in="5.0.0",
details="Replaced by creating materials from candidates on the platform. "
"Alternatively, you may convert the candidate into a collection of GEMD "
"objects manually.")
def __init__(self, project_id: UUID, session: Session):
self.project_id = project_id
self.session: Session = session
Expand Down
17 changes: 15 additions & 2 deletions src/citrine/resources/predictor.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Resources that represent collections of predictors."""
import warnings
from collections.abc import Iterable
from functools import partial
from typing import Any
Expand All @@ -11,7 +12,7 @@
from citrine._rest.paginator import Paginator
from citrine._serialization import properties
from citrine._session import Session
from citrine.informatics.data_sources import DataSource
from citrine.informatics.data_sources import DataSource, ExperimentDataSourceRef
from citrine.informatics.design_candidate import HierarchicalDesignMaterial
from citrine.informatics.predictors import GraphPredictor
from citrine.resources.status_detail import StatusDetail
Expand Down Expand Up @@ -107,6 +108,16 @@ def _page_fetcher(self, *, uid: UUID | str, **additional_params):
}
return partial(self._fetch_page, **fetcher_params)

def _check_data_sources(self, predictor: GraphPredictor):
for data_source in predictor.training_data:
print(data_source)
if isinstance(data_source, ExperimentDataSourceRef):
warnings.warn("This predictor contains an experiment result, which is being "
"replaced by creating materials from candidates on the platform. "
"Alternatively, you may convert the candidate into a collection of "
"GEMD objects manually.",
DeprecationWarning)

def build(self, data: dict) -> GraphPredictor:
"""Build an individual Predictor."""
predictor: GraphPredictor = GraphPredictor.build(data)
Expand All @@ -120,7 +131,9 @@ def get(self,
version: int | str = MOST_RECENT_VER) -> GraphPredictor:
path = self._construct_path(uid, version)
entity = self.session.get_resource(path, version=self._api_version)
return self.build(entity)
predictor = self.build(entity)
self._check_data_sources(predictor)
return predictor

def get_featurized_training_data(
self,
Expand Down
17 changes: 16 additions & 1 deletion tests/informatics/test_data_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@
@pytest.fixture(params=[
GemTableDataSource(table_id=uuid.uuid4(), table_version=1),
GemTableDataSource(table_id=uuid.uuid4(), table_version="2"),
ExperimentDataSourceRef(datasource_id=uuid.uuid4()),
SnapshotDataSource(snapshot_id=uuid.uuid4())
])
def data_source(request):
return request.param

@pytest.fixture
def deprecated_data_source():
with pytest.deprecated_call():
return ExperimentDataSourceRef(datasource_id=uuid.uuid4())


def test_deser_from_parent(data_source):
# Serialize and deserialize the descriptors, making sure they are round-trip serializable
Expand All @@ -29,6 +33,13 @@ def test_deser_from_parent(data_source):
assert data_source == data_source_deserialized


def test_deser_from_parent_deprecated(deprecated_data_source):
# Serialize and deserialize the descriptors, making sure they are round-trip serializable
data = deprecated_data_source.dump()
data_source_deserialized = DataSource.build(data)
assert deprecated_data_source == data_source_deserialized


def test_invalid_eq(data_source):
other = None
assert not data_source == other
Expand All @@ -42,6 +53,10 @@ def test_invalid_deser():
DataSource.build({"type": "foo"})


def test_deprecated_data_source_id(deprecated_data_source):
with pytest.deprecated_call():
assert deprecated_data_source == DataSource.from_data_source_id(deprecated_data_source.to_data_source_id())

def test_data_source_id(data_source):
assert data_source == DataSource.from_data_source_id(data_source.to_data_source_id())

Expand Down
19 changes: 11 additions & 8 deletions tests/informatics/test_experiment_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,24 @@


@pytest.fixture(params=[
CategoricalExperimentValue("categorical"),
ChemicalFormulaExperimentValue("(Ca)1(O)3(Si)1"),
IntegerExperimentValue(7),
MixtureExperimentValue({"ingredient1": 0.3, "ingredient2": 0.7}),
MolecularStructureExperimentValue("CC1(CC(CC(N1)(C)C)NCCCCCCNC2CC(NC(C2)(C)C)(C)C)C.C1COCCN1C2=NC(=NC(=N2)Cl)Cl"),
RealExperimentValue(3.5)
(CategoricalExperimentValue, ("categorical", )),
(ChemicalFormulaExperimentValue, ("(Ca)1(O)3(Si)1",)),
(IntegerExperimentValue, (7,)),
(MixtureExperimentValue, ({"ingredient1": 0.3, "ingredient2": 0.7},)),
(MolecularStructureExperimentValue, ("CC1(CC(CC(N1)(C)C)NCCCCCCNC2CC(NC(C2)(C)C)(C)C)C.C1COCCN1C2=NC(=NC(=N2)Cl)Cl",)),
(RealExperimentValue, (3.5,))
])
def experiment_value(request):
return request.param
cls, args = request.param
with pytest.deprecated_call():
return cls(*args)


def test_deser_from_parent(experiment_value):
# Serialize and deserialize the experiment values, making sure they are round-trip serializable
data = experiment_value.dump()
experiment_value_deserialized = ExperimentValue.build(data)
with pytest.deprecated_call():
experiment_value_deserialized = ExperimentValue.build(data)
assert experiment_value == experiment_value_deserialized


Expand Down
8 changes: 6 additions & 2 deletions tests/resources/test_branch.py
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,9 @@ def test_experiment_datasource(session, collection):
session.set_response({'response': [erds]})

# When / Then
assert branch.experiment_datasource is not None
with pytest.deprecated_call():
assert branch.experiment_datasource is not None

assert session.calls == [
FakeCall(method='GET', path=erds_path, params={'branch': str(branch.uid), 'version': LATEST_VER, 'per_page': 100, 'page': 1})
]
Expand All @@ -550,7 +552,9 @@ def test_no_experiment_datasource(session, collection):
session.set_response({'response': []})

# When / Then
assert branch.experiment_datasource is None
with pytest.deprecated_call():
assert branch.experiment_datasource is None

assert session.calls == [
FakeCall(method='GET', path=erds_path, params={'branch': str(branch.uid), 'version': LATEST_VER, 'per_page': 100, 'page': 1})
]
Expand Down
3 changes: 2 additions & 1 deletion tests/resources/test_experiment_datasource.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ def session():

@pytest.fixture
def collection(session) -> ExperimentDataSourceCollection:
return ExperimentDataSourceCollection(uuid.uuid4(), session)
with pytest.deprecated_call():
return ExperimentDataSourceCollection(uuid.uuid4(), session)


@pytest.fixture
Expand Down
19 changes: 18 additions & 1 deletion tests/resources/test_predictor.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from copy import deepcopy

from citrine.exceptions import BadRequest, Conflict, ModuleRegistrationFailedException, NotFound
from citrine.informatics.data_sources import GemTableDataSource
from citrine.informatics.data_sources import ExperimentDataSourceRef, GemTableDataSource
from citrine.informatics.descriptors import RealDescriptor
from citrine.informatics.predictors import (
AutoMLPredictor,
Expand Down Expand Up @@ -754,3 +754,20 @@ def test_rename_description_only(valid_graph_predictor_data):
versions_path = _PredictorVersionCollection._path_template.format(project_id=pc.project_id, uid=pred_id)
expected_payload = {"name": None, "description": new_description}
assert session.calls == [FakeCall(method="PUT", path=f"{versions_path}/{pred_version}/rename", json=expected_payload)]


def test_get_predictor_with_experiment_data_source_deprecated(valid_graph_predictor_data):
# Given
session = FakeSession()
pc = PredictorCollection(uuid.uuid4(), session)

with pytest.deprecated_call():
erds = ExperimentDataSourceRef(datasource_id=uuid.uuid4())
entity = deepcopy(valid_graph_predictor_data)
entity["data"]["instance"]["training_data"] = [erds.dump()]

session.set_responses(entity)

# When
with pytest.deprecated_call():
pc.get(uuid.uuid4())
Loading