Skip to content

Commit bd1c75e

Browse files
committed
[PNE-7661] Deprecate most top-level design spaces.
As a simplification for the platform, all design spaces should be either a ProductDesignSpace or HierarchicalDesignSpace. The other types (namely, DataSourceDesignSpace and FormulationDesignSpace) should be subspaces of them. This is the only structure supported by the UI, so it will improve their experience of the platform, too. Additionally, EnumeratedDesignSpace is obsolete, and should be replaced by a DataSourceDesign space within a ProductDesignSpace. Note that we also emit a warning on retrieval. That's because when we go to v4.0, we will stop supporting such design spaces at all. This gives users a chance to update their design spaces as necessary, and ensures they will not be surprised when the type of design space they retrieve changes.
1 parent 80bcd5b commit bd1c75e

File tree

2 files changed

+108
-19
lines changed

2 files changed

+108
-19
lines changed

src/citrine/resources/design_space.py

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
"""Resources that represent collections of design spaces."""
2+
import warnings
23
from functools import partial
3-
from typing import Iterable, Optional, TypeVar, Union
4+
from typing import Iterable, Iterator, Optional, TypeVar, Union
45
from uuid import UUID
56

67

78
from citrine._utils.functions import format_escaped_url
8-
from citrine.informatics.design_spaces import DefaultDesignSpaceMode, DesignSpace, \
9-
DesignSpaceSettings, EnumeratedDesignSpace, HierarchicalDesignSpace
9+
from citrine.informatics.design_spaces import DataSourceDesignSpace, DefaultDesignSpaceMode, \
10+
DesignSpace, DesignSpaceSettings, EnumeratedDesignSpace, FormulationDesignSpace, \
11+
HierarchicalDesignSpace
1012
from citrine._rest.collection import Collection
1113
from citrine._session import Session
1214

@@ -48,15 +50,48 @@ def _verify_write_request(self, design_space: DesignSpace):
4850
rather than let the POST or PUT call fail because the request body is too big. This
4951
validation is performed when the design space is sent to the platform in case a user
5052
creates a large intermediate design space but then filters it down before registering it.
53+
54+
Additionally, checks for deprecated top-level design space types, and emits deprecation
55+
warnings as appropriate.
5156
"""
5257
if isinstance(design_space, EnumeratedDesignSpace):
58+
warnings.warn("As of 3.27.0, EnumeratedDesignSpace is deprecated in favor of a "
59+
"ProductDesignSpace containing a DataSourceDesignSpace subspace. "
60+
"Support for EnumeratedDesignSpace will be dropped in 4.0.",
61+
DeprecationWarning)
62+
5363
width = len(design_space.descriptors)
5464
length = len(design_space.data)
5565
if width * length > self._enumerated_cell_limit:
5666
msg = "EnumeratedDesignSpace only supports up to {} descriptor-values, " \
5767
"but {} were given. Please reduce the number of descriptors or candidates " \
5868
"in this EnumeratedDesignSpace"
5969
raise ValueError(msg.format(self._enumerated_cell_limit, width * length))
70+
elif isinstance(design_space, (DataSourceDesignSpace, FormulationDesignSpace)):
71+
typ = type(design_space).__name__
72+
warnings.warn(f"As of 3.27.0, saving a top-level {typ} is deprecated. Support "
73+
"will be removed in 4.0. Wrap it in a ProductDesignSpace instead: "
74+
f"ProductDesignSpace('name', 'description', subspaces=[{typ}(...)])",
75+
DeprecationWarning)
76+
77+
def _verify_read_request(self, design_space: DesignSpace):
78+
"""Perform read-time validations of the design space.
79+
80+
Checks for deprecated top-level design space types, and emits deprecation warnings as
81+
appropriate.
82+
"""
83+
if isinstance(design_space, EnumeratedDesignSpace):
84+
warnings.warn("As of 3.27.0, EnumeratedDesignSpace is deprecated in favor of a "
85+
"ProductDesignSpace containing a DataSourceDesignSpace subspace. "
86+
"Support for EnumeratedDesignSpace will be dropped in 4.0.",
87+
DeprecationWarning)
88+
elif isinstance(design_space, (DataSourceDesignSpace, FormulationDesignSpace)):
89+
typ = type(design_space).__name__
90+
warnings.warn(f"As of 3.27.0, top-level {typ}s are deprecated. Any that remain when "
91+
"SDK 4.0 are released will be wrapped in a ProductDesignSpace. You "
92+
"can wrap it yourself to get rid of this warning now: "
93+
f"ProductDesignSpace('name', 'description', subspaces=[{typ}(...)])",
94+
DeprecationWarning)
6095

6196
def register(self, design_space: DesignSpace) -> DesignSpace:
6297
"""Create a new design space."""
@@ -116,6 +151,32 @@ def restore(self, uid: Union[UUID, str]) -> DesignSpace:
116151
entity = self.session.put_resource(url, {}, version=self._api_version)
117152
return self.build(entity)
118153

154+
def get(self, uid: Union[UUID, str]) -> DesignSpace:
155+
"""Get a particular element of the collection."""
156+
design_space = super().get(uid)
157+
self._verify_read_request(design_space)
158+
return design_space
159+
160+
def _build_collection_elements(self, collection: Iterable[dict]) -> Iterator[DesignSpace]:
161+
"""
162+
For each element in the collection, build the appropriate resource type.
163+
164+
Parameters
165+
---------
166+
collection: Iterable[dict]
167+
collection containing the elements to be built
168+
169+
Returns
170+
-------
171+
Iterator[DesignSpace]
172+
Resources in this collection.
173+
174+
"""
175+
for element in collection:
176+
design_space = self.build(element)
177+
self._verify_read_request(design_space)
178+
yield design_space
179+
119180
def _list_base(self, *, per_page: int = 100, archived: Optional[bool] = None):
120181
filters = {}
121182
if archived is not None:

tests/resources/test_design_space.py

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -169,18 +169,21 @@ def test_design_space_limits():
169169
session.responses.append(mock_response)
170170

171171
# Then
172-
with pytest.raises(ValueError) as excinfo:
173-
collection.register(too_big)
172+
with pytest.deprecated_call():
173+
with pytest.raises(ValueError) as excinfo:
174+
collection.register(too_big)
174175
assert "only supports" in str(excinfo.value)
175176

176177
# test register
177-
collection.register(just_right)
178+
with pytest.deprecated_call():
179+
collection.register(just_right)
178180

179181
# add back the response for the next test
180182
session.responses.append(mock_response)
181183

182184
# test update
183-
collection.update(just_right)
185+
with pytest.deprecated_call():
186+
collection.update(just_right)
184187

185188

186189
@pytest.mark.parametrize("predictor_version", (2, "1", "latest", None))
@@ -312,12 +315,12 @@ def test_create_default_with_config(valid_product_design_space, ingredient_fract
312315
assert default_design_space.dump() == expected_response
313316

314317

315-
def test_list_design_spaces(valid_formulation_design_space_data, valid_enumerated_design_space_data):
318+
def test_list_design_spaces(valid_product_design_space_data, valid_hierarchical_design_space_data):
316319
# Given
317320
session = FakeSession()
318321
collection = DesignSpaceCollection(uuid.uuid4(), session)
319322
session.set_response({
320-
'response': [valid_formulation_design_space_data, valid_enumerated_design_space_data]
323+
'response': [valid_product_design_space_data, valid_hierarchical_design_space_data]
321324
})
322325

323326
# When
@@ -331,12 +334,12 @@ def test_list_design_spaces(valid_formulation_design_space_data, valid_enumerate
331334
assert len(design_spaces) == 2
332335

333336

334-
def test_list_all_design_spaces(valid_formulation_design_space_data, valid_enumerated_design_space_data):
337+
def test_list_all_design_spaces(valid_product_design_space_data, valid_hierarchical_design_space_data):
335338
# Given
336339
session = FakeSession()
337340
collection = DesignSpaceCollection(uuid.uuid4(), session)
338341
session.set_response({
339-
'response': [valid_formulation_design_space_data, valid_enumerated_design_space_data]
342+
'response': [valid_product_design_space_data, valid_hierarchical_design_space_data]
340343
})
341344

342345
# When
@@ -350,12 +353,12 @@ def test_list_all_design_spaces(valid_formulation_design_space_data, valid_enume
350353
assert len(design_spaces) == 2
351354

352355

353-
def test_list_archived_design_spaces(valid_formulation_design_space_data, valid_enumerated_design_space_data):
356+
def test_list_archived_design_spaces(valid_product_design_space_data, valid_hierarchical_design_space_data):
354357
# Given
355358
session = FakeSession()
356359
collection = DesignSpaceCollection(uuid.uuid4(), session)
357360
session.set_response({
358-
'response': [valid_formulation_design_space_data, valid_enumerated_design_space_data]
361+
'response': [valid_product_design_space_data, valid_hierarchical_design_space_data]
359362
})
360363

361364
# When
@@ -369,13 +372,13 @@ def test_list_archived_design_spaces(valid_formulation_design_space_data, valid_
369372
assert len(design_spaces) == 2
370373

371374

372-
def test_archive(valid_formulation_design_space_data):
375+
def test_archive(valid_product_design_space_data):
373376
session = FakeSession()
374377
dsc = DesignSpaceCollection(uuid.uuid4(), session)
375378
base_path = DesignSpaceCollection._path_template.format(project_id=dsc.project_id)
376-
ds_id = valid_formulation_design_space_data["id"]
379+
ds_id = valid_product_design_space_data["id"]
377380

378-
response = deepcopy(valid_formulation_design_space_data)
381+
response = deepcopy(valid_product_design_space_data)
379382
response["metadata"]["archived"] = response["metadata"]["created"]
380383
session.set_response(response)
381384

@@ -387,13 +390,13 @@ def test_archive(valid_formulation_design_space_data):
387390
]
388391

389392

390-
def test_restore(valid_formulation_design_space_data):
393+
def test_restore(valid_product_design_space_data):
391394
session = FakeSession()
392395
dsc = DesignSpaceCollection(uuid.uuid4(), session)
393396
base_path = DesignSpaceCollection._path_template.format(project_id=dsc.project_id)
394-
ds_id = valid_formulation_design_space_data["id"]
397+
ds_id = valid_product_design_space_data["id"]
395398

396-
response = deepcopy(valid_formulation_design_space_data)
399+
response = deepcopy(valid_product_design_space_data)
397400
if "archived" in response["metadata"]:
398401
del response["metadata"]["archived"]
399402
session.set_response(deepcopy(response))
@@ -563,3 +566,28 @@ def test_locked(valid_product_design_space_data):
563566
assert ds.is_locked
564567
assert ds.locked_by == lock_user
565568
assert ds.lock_time == lock_time
569+
570+
571+
@pytest.mark.parametrize("ds_data_fixture_name", ("valid_formulation_design_space_data",
572+
"valid_enumerated_design_space_data",
573+
"valid_data_source_design_space_dict"))
574+
def test_deprecated_top_level_design_spaces(request, ds_data_fixture_name):
575+
ds_data = request.getfixturevalue(ds_data_fixture_name)
576+
577+
session = FakeSession()
578+
session.set_response(ds_data)
579+
dc = DesignSpaceCollection(uuid.uuid4(), session)
580+
581+
with pytest.deprecated_call():
582+
ds = dc.get(uuid.uuid4())
583+
584+
with pytest.deprecated_call():
585+
dc.register(ds)
586+
587+
with pytest.deprecated_call():
588+
dc.update(ds)
589+
590+
session.set_response({"response": [ds_data]})
591+
592+
with pytest.deprecated_call():
593+
next(dc.list())

0 commit comments

Comments
 (0)