Skip to content

Commit 8e24b67

Browse files
committed
[PLA-9255] Switching to v3 predictor endpoints.
This involves a change in some endpoint behavior, as well as a complete change in the payload shape. Backwards comptability is retained, and some fields/methods/usages have been deprecated in favor of new versions e.g. a "train" method separate from update.
1 parent 0eb4e58 commit 8e24b67

29 files changed

+892
-579
lines changed

src/citrine/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '1.30.3'
1+
__version__ = '1.31.0'
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
from deprecation import deprecated
2+
from typing import List, TypeVar
3+
4+
from citrine._rest.resource import Resource, ResourceTypeEnum
5+
from citrine._serialization import properties
6+
7+
8+
Self = TypeVar('Self', bound='Resource')
9+
10+
11+
# This class is the base type for the new module endpoints. Currently, it only covers predictors,
12+
# but should be applicable for others as they're written.
13+
class EngineResource(Resource[Self]):
14+
"""Base resource for predictors, including metadata representation."""
15+
16+
created_by = properties.Optional(properties.UUID, 'metadata.created.user', serializable=False)
17+
""":Optional[UUID]: id of the user who created the resource"""
18+
create_time = properties.Optional(properties.Datetime, 'metadata.created.time',
19+
serializable=False)
20+
""":Optional[datetime]: date and time at which the resource was created"""
21+
22+
updated_by = properties.Optional(properties.UUID, 'metadata.updated.user',
23+
serializable=False)
24+
""":Optional[UUID]: id of the user who most recently updated the resource,
25+
if it has been updated"""
26+
update_time = properties.Optional(properties.Datetime, 'metadata.updated.time',
27+
serializable=False)
28+
""":Optional[datetime]: date and time at which the resource was most recently updated,
29+
if it has been updated"""
30+
31+
archived_by = properties.Optional(properties.UUID, 'metadata.archived.user',
32+
serializable=False)
33+
""":Optional[UUID]: id of the user who archived the resource, if it has been archived"""
34+
archive_time = properties.Optional(properties.Datetime, 'metadata.archived.time',
35+
serializable=False)
36+
""":Optional[datetime]: date and time at which the resource was archived,
37+
if it has been archived"""
38+
39+
status = properties.Optional(properties.String(), 'metadata.status.name', serializable=False)
40+
""":Optional[str]: short description of the resource's status"""
41+
status_info = properties.Optional(properties.List(properties.String()), 'metadata.status.info',
42+
serializable=False)
43+
44+
# Due to the way object construction is done at present, __init__ is not executed on Resource
45+
# objects, so initializing _archived doesn't work.
46+
_archived = properties.Optional(properties.Boolean(), '', default=None, serializable=False,
47+
deserializable=False)
48+
49+
_resource_type = ResourceTypeEnum.MODULE
50+
51+
@property
52+
def is_archived(self):
53+
""":bool: whether the resource is archived (hidden but not deleted)."""
54+
return self.archived_by is not None
55+
56+
@property
57+
@deprecated(deprecated_in="1.31.0", removed_in="2.0.0",
58+
details="Please use the 'is_archived' property instead.'")
59+
def archived(self):
60+
"""[DEPRECATED] whether the resource is archived."""
61+
return self.is_archived
62+
63+
@archived.setter
64+
@deprecated(deprecated_in="1.31.0", removed_in="2.0.0",
65+
details="Please use archive() and restore() on PredictorCollection instead.")
66+
def archived(self, value):
67+
self._archived = value
68+
69+
@property
70+
@deprecated(deprecated_in="1.25.0", removed_in="2.0.0")
71+
def experimental(self) -> bool:
72+
"""[DEPRECATED] whether the execution is experimental (newer, less well-tested).""" # noqa - insisting this docstring is a signature
73+
return False
74+
75+
@property
76+
@deprecated(deprecated_in="1.25.0", removed_in="2.0.0")
77+
def experimental_reasons(self) -> List[str]:
78+
"""[DEPRECATED] human-readable reasons why the execution is experimental."""
79+
return []
80+
81+
def _post_dump(self, data: dict) -> dict:
82+
# Only the data portion of an entity is sent to the server.
83+
data = data["data"]
84+
85+
# Currently, name and description exists on both the data envelope and the config.
86+
data["instance"]["name"] = data["name"]
87+
data["instance"]["description"] = data["description"]
88+
return data

src/citrine/informatics/modules.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@ def get_type(cls, data) -> Type['Module']:
3131
"""Return the subtype."""
3232
from citrine.informatics.design_spaces import DesignSpace
3333
from citrine.informatics.processors import Processor
34-
from citrine.informatics.predictors import Predictor
3534
return {
3635
'DESIGN_SPACE': DesignSpace,
37-
'PROCESSOR': Processor,
38-
'PREDICTOR': Predictor
36+
'PROCESSOR': Processor
3937
}[data['module_type']].get_type(data)

src/citrine/informatics/predictors/auto_ml_predictor.py

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
import warnings
22
from typing import List, Optional
33

4-
from citrine._rest.resource import Resource, ResourceTypeEnum
4+
from citrine._rest.engine_resource import EngineResource
55
from citrine._serialization import properties as _properties
66
from citrine.informatics.data_sources import DataSource
77
from citrine.informatics.descriptors import Descriptor
88
from citrine.informatics.predictors import Predictor
9-
from citrine._rest.ai_resource_metadata import AIResourceMetadata
109

1110
__all__ = ['AutoMLPredictor']
1211

1312

14-
class AutoMLPredictor(Resource['AutoMLPredictor'], Predictor, AIResourceMetadata):
13+
class AutoMLPredictor(EngineResource['AutoMLPredictor'], Predictor):
1514
"""A predictor interface that builds a single ML model.
1615
1716
The model uses the set of inputs to predict the output(s).
@@ -41,15 +40,12 @@ class AutoMLPredictor(Resource['AutoMLPredictor'], Predictor, AIResourceMetadata
4140
4241
"""
4342

44-
_resource_type = ResourceTypeEnum.MODULE
45-
46-
inputs = _properties.List(_properties.Object(Descriptor), 'config.inputs')
47-
outputs = _properties.List(_properties.Object(Descriptor), 'config.outputs')
43+
inputs = _properties.List(_properties.Object(Descriptor), 'data.instance.inputs')
44+
outputs = _properties.List(_properties.Object(Descriptor), 'data.instance.outputs')
4845
training_data = _properties.List(_properties.Object(DataSource),
49-
'config.training_data', default=[])
46+
'data.instance.training_data', default=[])
5047

51-
typ = _properties.String('config.type', default='AutoML', deserializable=False)
52-
module_type = _properties.String('module_type', default='PREDICTOR')
48+
typ = _properties.String('data.instance.type', default='AutoML', deserializable=False)
5349

5450
def __init__(self,
5551
name: str,
@@ -78,10 +74,6 @@ def __init__(self,
7874
elif output is not None:
7975
self.outputs = [output]
8076

81-
def _post_dump(self, data: dict) -> dict:
82-
data['display_name'] = data['config']['name']
83-
return data
84-
8577
@property
8678
def output(self) -> Optional[Descriptor]:
8779
"""[DEPRECATED] Get the first output descriptor."""

src/citrine/informatics/predictors/chemical_formula_featurizer.py

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
from typing import List, Optional
22

3-
from citrine._rest.resource import Resource, ResourceTypeEnum
3+
from citrine._rest.engine_resource import EngineResource
44
from citrine._serialization import properties as _properties
55
from citrine.informatics.descriptors import ChemicalFormulaDescriptor, Descriptor
66
from citrine.informatics.predictors import Predictor
7-
from citrine._rest.ai_resource_metadata import AIResourceMetadata
87

98
__all__ = ['ChemicalFormulaFeaturizer']
109

1110

12-
class ChemicalFormulaFeaturizer(Resource['ChemicalFormulaFeaturizer'],
13-
Predictor, AIResourceMetadata):
11+
class ChemicalFormulaFeaturizer(EngineResource['ChemicalFormulaFeaturizer'], Predictor):
1412
"""
1513
A featurizer for chemical formulae. Inspired by Magpie.
1614
@@ -132,16 +130,13 @@ class ChemicalFormulaFeaturizer(Resource['ChemicalFormulaFeaturizer'],
132130
133131
"""
134132

135-
_resource_type = ResourceTypeEnum.MODULE
133+
input_descriptor = _properties.Object(Descriptor, 'data.instance.input')
134+
features = _properties.List(_properties.String, 'data.instance.features')
135+
excludes = _properties.List(_properties.String, 'data.instance.excludes')
136+
powers = _properties.List(_properties.Integer, 'data.instance.powers')
136137

137-
input_descriptor = _properties.Object(Descriptor, 'config.input')
138-
features = _properties.List(_properties.String, 'config.features')
139-
excludes = _properties.List(_properties.String, 'config.excludes')
140-
powers = _properties.List(_properties.Integer, 'config.powers')
141-
142-
typ = _properties.String('config.type', default='ChemicalFormulaFeaturizer',
138+
typ = _properties.String('data.instance.type', default='ChemicalFormulaFeaturizer',
143139
deserializable=False)
144-
module_type = _properties.String('module_type', default='PREDICTOR')
145140

146141
def __init__(self,
147142
name: str,
@@ -158,9 +153,5 @@ def __init__(self,
158153
self.excludes = excludes if excludes is not None else []
159154
self.powers = powers if powers is not None else [1]
160155

161-
def _post_dump(self, data: dict) -> dict:
162-
data['display_name'] = data['config']['name']
163-
return data
164-
165156
def __str__(self):
166157
return '<ChemicalFormulaFeaturizer {!r}>'.format(self.name)

src/citrine/informatics/predictors/expression_predictor.py

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
from typing import Mapping
22

3-
from citrine._rest.resource import Resource
3+
from citrine._rest.engine_resource import EngineResource
44
from citrine._serialization import properties as _properties
55
from citrine.informatics.descriptors import RealDescriptor
66
from citrine.informatics.predictors import Predictor
7-
from citrine._rest.ai_resource_metadata import AIResourceMetadata
87

98
__all__ = ['ExpressionPredictor']
109

1110

12-
class ExpressionPredictor(Resource['ExpressionPredictor'], Predictor, AIResourceMetadata):
11+
class ExpressionPredictor(EngineResource['ExpressionPredictor'], Predictor):
1312
"""A predictor that computes an output from an expression and set of bounded inputs.
1413
1514
For a discussion of expression syntax and a list of allowed symbols,
@@ -31,13 +30,13 @@ class ExpressionPredictor(Resource['ExpressionPredictor'], Predictor, AIResource
3130
3231
"""
3332

34-
expression = _properties.String('config.expression')
35-
output = _properties.Object(RealDescriptor, 'config.output')
33+
expression = _properties.String('data.instance.expression')
34+
output = _properties.Object(RealDescriptor, 'data.instance.output')
3635
aliases = _properties.Mapping(_properties.String, _properties.Object(RealDescriptor),
37-
'config.aliases')
36+
'data.instance.aliases')
3837

39-
typ = _properties.String('config.type', default='AnalyticExpression', deserializable=False)
40-
module_type = _properties.String('module_type', default='PREDICTOR')
38+
typ = _properties.String('data.instance.type',
39+
default='AnalyticExpression', deserializable=False)
4140

4241
def __init__(self,
4342
name: str,
@@ -52,9 +51,5 @@ def __init__(self,
5251
self.output: RealDescriptor = output
5352
self.aliases: Mapping[str, RealDescriptor] = aliases
5453

55-
def _post_dump(self, data: dict) -> dict:
56-
data['display_name'] = data['config']['name']
57-
return data
58-
5954
def __str__(self):
6055
return '<ExpressionPredictor {!r}>'.format(self.name)
Lines changed: 11 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
from typing import List, Optional, Union
22
from uuid import UUID
33

4-
from citrine._rest.resource import Resource, ResourceTypeEnum
4+
from citrine._rest.engine_resource import EngineResource
55
from citrine._serialization import properties as _properties
66
from citrine.informatics.data_sources import DataSource
77
from citrine.informatics.predictors import Predictor
8-
from citrine._rest.ai_resource_metadata import AIResourceMetadata
98

109
__all__ = ['GraphPredictor']
1110

1211

13-
class GraphPredictor(Resource['GraphPredictor'], Predictor, AIResourceMetadata):
12+
class GraphPredictor(EngineResource['GraphPredictor'], Predictor):
1413
"""A predictor interface that stitches other predictors together.
1514
1615
Parameters
@@ -32,17 +31,14 @@ class GraphPredictor(Resource['GraphPredictor'], Predictor, AIResourceMetadata):
3231
3332
"""
3433

35-
_resource_type = ResourceTypeEnum.MODULE
36-
3734
predictors = _properties.List(_properties.Union(
38-
[_properties.UUID, _properties.Object(Predictor)]), 'config.predictors')
35+
[_properties.UUID, _properties.Object(Predictor)]), 'data.instance.predictors')
3936
# the default seems to be defined in instances, not the class itself
4037
# this is tested in test_graph_default_training_data
4138
training_data = _properties.List(_properties.Object(DataSource),
42-
'config.training_data', default=[])
39+
'data.instance.training_data', default=[])
4340

44-
typ = _properties.String('config.type', default='Graph', deserializable=False)
45-
module_type = _properties.String('module_type', default='PREDICTOR')
41+
typ = _properties.String('data.instance.type', default='Graph', deserializable=False)
4642

4743
def __init__(self,
4844
name: str,
@@ -56,32 +52,19 @@ def __init__(self,
5652
self.training_data: List[DataSource] = training_data or []
5753

5854
def _post_dump(self, data: dict) -> dict:
59-
data['display_name'] = data['config']['name']
60-
for i, predictor in enumerate(data['config']['predictors']):
55+
data = super()._post_dump(data)
56+
for i, predictor in enumerate(data['instance']['predictors']):
6157
if isinstance(predictor, dict):
6258
# embedded predictors are not modules, so only serialize their config
63-
data['config']['predictors'][i] = predictor['config']
59+
data['instance']['predictors'][i] = predictor['instance']
6460
return data
6561

6662
@classmethod
6763
def _pre_build(cls, data: dict) -> dict:
68-
for i, predictor in enumerate(data['config']['predictors']):
69-
if isinstance(predictor, dict):
70-
data['config']['predictors'][i] = \
71-
GraphPredictor.stuff_predictor_into_envelope(predictor)
64+
for i, predictor_data in enumerate(data['data']['instance']['predictors']):
65+
if isinstance(predictor_data, dict):
66+
data['data']['instance']['predictors'][i] = Predictor.wrap_instance(predictor_data)
7267
return data
7368

74-
@staticmethod
75-
def stuff_predictor_into_envelope(predictor: dict) -> dict:
76-
"""Insert a serialized embedded predictor into a module envelope.
77-
78-
This facilitates deserialization.
79-
"""
80-
return dict(
81-
module_type='PREDICTOR',
82-
config=predictor,
83-
archived=False
84-
)
85-
8669
def __str__(self):
8770
return '<GraphPredictor {!r}>'.format(self.name)

src/citrine/informatics/predictors/ingredient_fractions_predictor.py

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
from typing import Set
22

3-
from citrine._rest.resource import Resource, ResourceTypeEnum
3+
from citrine._rest.engine_resource import EngineResource
44
from citrine._serialization import properties as _properties
55
from citrine.informatics.descriptors import FormulationDescriptor
66
from citrine.informatics.predictors import Predictor
7-
from citrine._rest.ai_resource_metadata import AIResourceMetadata
87

98
__all__ = ['IngredientFractionsPredictor']
109

1110

12-
class IngredientFractionsPredictor(Resource["IngredientFractionsPredictor"],
13-
Predictor, AIResourceMetadata):
11+
class IngredientFractionsPredictor(EngineResource["IngredientFractionsPredictor"], Predictor):
1412
"""A predictor interface that computes ingredient fractions.
1513
1614
Parameters
@@ -27,13 +25,10 @@ class IngredientFractionsPredictor(Resource["IngredientFractionsPredictor"],
2725
2826
"""
2927

30-
_resource_type = ResourceTypeEnum.MODULE
28+
input_descriptor = _properties.Object(FormulationDescriptor, 'data.instance.input')
29+
ingredients = _properties.Set(_properties.String, 'data.instance.ingredients')
3130

32-
input_descriptor = _properties.Object(FormulationDescriptor, 'config.input')
33-
ingredients = _properties.Set(_properties.String, 'config.ingredients')
34-
35-
module_type = _properties.String('module_type', default='PREDICTOR')
36-
typ = _properties.String('config.type', default='IngredientFractions',
31+
typ = _properties.String('data.instance.type', default='IngredientFractions',
3732
deserializable=False)
3833

3934
def __init__(self,
@@ -47,9 +42,5 @@ def __init__(self,
4742
self.input_descriptor: FormulationDescriptor = input_descriptor
4843
self.ingredients: Set[str] = ingredients
4944

50-
def _post_dump(self, data: dict) -> dict:
51-
data['display_name'] = data['config']['name']
52-
return data
53-
5445
def __str__(self):
5546
return '<IngredientFractionsPredictor {!r}>'.format(self.name)

0 commit comments

Comments
 (0)