diff --git a/src/citrine/__version__.py b/src/citrine/__version__.py index f87b7d84c..659cac322 100644 --- a/src/citrine/__version__.py +++ b/src/citrine/__version__.py @@ -1 +1 @@ -__version__ = "3.21.0" +__version__ = "3.22.0" diff --git a/src/citrine/informatics/design_spaces/design_space.py b/src/citrine/informatics/design_spaces/design_space.py index 77c17dfb5..295d4094e 100644 --- a/src/citrine/informatics/design_spaces/design_space.py +++ b/src/citrine/informatics/design_spaces/design_space.py @@ -26,6 +26,15 @@ class DesignSpace(PolymorphicSerializable['DesignSpace'], AsynchronousObject): name = properties.String('data.name') description = properties.Optional(properties.String(), 'data.description') + locked_by = properties.Optional(properties.UUID, 'metadata.locked.user', + serializable=False) + """:Optional[UUID]: id of the user whose action cause the design space to + be locked, if it is locked""" + lock_time = properties.Optional(properties.Datetime, 'metadata.locked.time', + serializable=False) + """:Optional[datetime]: date and time at which the resource was locked, + if it is locked""" + @staticmethod def wrap_instance(subspace_data: dict) -> dict: """Insert a serialized embedded design space into an entity envelope. @@ -65,6 +74,11 @@ def get_type(cls, data) -> Type[Serializable]: 'HierarchicalDesignSpace': HierarchicalDesignSpace }[data['data']['instance']['type']] + @property + def is_locked(self) -> bool: + """If is_locked is true, edits to the design space will be rejected.""" + return self.locked_by is not None + @property def sample_design_space_executions(self): """Start a Sample Design Space Execution using the current Design Space.""" diff --git a/tests/resources/test_design_space.py b/tests/resources/test_design_space.py index 972b8b207..7b95746b0 100644 --- a/tests/resources/test_design_space.py +++ b/tests/resources/test_design_space.py @@ -1,6 +1,7 @@ +import random import uuid from copy import deepcopy -import random +from datetime import datetime, timezone import mock import pytest @@ -534,3 +535,31 @@ def test_carrying_settings_from_get(valid_product_design_space): assert session.num_calls == 3 assert session.calls[1] == expected_call + + +def test_locked(valid_product_design_space_data): + session = FakeSession() + collection = DesignSpaceCollection(project_id=uuid.uuid4(), session=session) + + session.set_response(deepcopy(valid_product_design_space_data)) + + ds = collection.get(uuid.uuid4()) + + assert not ds.is_locked + assert ds.locked_by is None + assert ds.lock_time is None + + lock_user = uuid.uuid4() + lock_time = datetime(2020, 4, 23, 15, 46, 23, tzinfo=timezone.utc) + lock_timestamp = int(lock_time.timestamp()) * 1000 + + response_data = deepcopy(valid_product_design_space_data) + response_data['metadata']['locked'] = {'user': str(lock_user), 'time': lock_timestamp} + + session.set_response(response_data) + + ds = collection.get(uuid.uuid4()) + + assert ds.is_locked + assert ds.locked_by == lock_user + assert ds.lock_time == lock_time