class Project(Resource['Project']): """ A Citrine Project. A project is a collection of datasets, some of which belong directly to the project and some of which have been shared with the project. Parameters ---------- name: str Name of the project. description: str Long-form description of the project. session: Session, optional The Citrine session used to connect to the database. Attributes ---------- uid: UUID Unique uuid4 identifier of this project. status: str Status of the project. created_at: int Time the project was created, in seconds since epoch. """ _response_key = 'project' name = properties.String('name') description = properties.Optional(properties.String(), 'description') uid = properties.Optional(properties.UUID(), 'id') status = properties.Optional(properties.String(), 'status') created_at = properties.Optional(properties.Datetime(), 'created_at') def __init__(self, name: str, description: Optional[str] = None, session: Optional[Session] = Session()): self.name: str = name self.description: Optional[str] = description self.session: Session = session def __str__(self): return '<Project {!r}>'.format(self.name) def _path(self): return '/projects/{project_id}'.format(**{"project_id": self.uid}) @property def design_spaces(self) -> DesignSpaceCollection: """Return a resource representing all visible design spaces.""" return DesignSpaceCollection(self.uid, self.session) @property def processors(self) -> ProcessorCollection: """Return a resource representing all visible processors.""" return ProcessorCollection(self.uid, self.session) @property def predictors(self) -> PredictorCollection: """Return a resource representing all visible predictors.""" return PredictorCollection(self.uid, self.session) @property def workflows(self) -> WorkflowCollection: """Return a resource representing all visible workflows.""" return WorkflowCollection(self.uid, self.session) @property def datasets(self) -> DatasetCollection: """Return a resource representing all visible datasets.""" return DatasetCollection(self.uid, self.session) @property def tables(self) -> TableCollection: """Return a resource representing all visible Tables.""" return TableCollection(self.uid, self.session) @property def property_templates(self) -> PropertyTemplateCollection: """Return a resource representing all property templates in this dataset.""" return PropertyTemplateCollection(self.uid, None, self.session) @property def condition_templates(self) -> ConditionTemplateCollection: """Return a resource representing all condition templates in this dataset.""" return ConditionTemplateCollection(self.uid, None, self.session) @property def parameter_templates(self) -> ParameterTemplateCollection: """Return a resource representing all parameter templates in this dataset.""" return ParameterTemplateCollection(self.uid, None, self.session) @property def material_templates(self) -> MaterialTemplateCollection: """Return a resource representing all material templates in this dataset.""" return MaterialTemplateCollection(self.uid, None, self.session) @property def measurement_templates(self) -> MeasurementTemplateCollection: """Return a resource representing all measurement templates in this dataset.""" return MeasurementTemplateCollection(self.uid, None, self.session) @property def process_templates(self) -> ProcessTemplateCollection: """Return a resource representing all process templates in this dataset.""" return ProcessTemplateCollection(self.uid, None, self.session) @property def process_runs(self) -> ProcessRunCollection: """Return a resource representing all process runs in this dataset.""" return ProcessRunCollection(self.uid, None, self.session) @property def measurement_runs(self) -> MeasurementRunCollection: """Return a resource representing all measurement runs in this dataset.""" return MeasurementRunCollection(self.uid, None, self.session) @property def material_runs(self) -> MaterialRunCollection: """Return a resource representing all material runs in this dataset.""" return MaterialRunCollection(self.uid, None, self.session) @property def ingredient_runs(self) -> IngredientRunCollection: """Return a resource representing all ingredient runs in this dataset.""" return IngredientRunCollection(self.uid, None, self.session) @property def process_specs(self) -> ProcessSpecCollection: """Return a resource representing all process specs in this dataset.""" return ProcessSpecCollection(self.uid, None, self.session) @property def measurement_specs(self) -> MeasurementSpecCollection: """Return a resource representing all measurement specs in this dataset.""" return MeasurementSpecCollection(self.uid, None, self.session) @property def material_specs(self) -> MaterialSpecCollection: """Return a resource representing all material specs in this dataset.""" return MaterialSpecCollection(self.uid, None, self.session) @property def ingredient_specs(self) -> IngredientSpecCollection: """Return a resource representing all ingredient specs in this dataset.""" return IngredientSpecCollection(self.uid, None, self.session) def share(self, project_id: str, resource_type: str, resource_id: str) -> Dict[str, str]: """Share a resource with another project.""" return self.session.post_resource(self._path() + "/share", { "project_id": project_id, "resource": {"type": resource_type, "id": resource_id} }) def make_public(self, resource: Resource) -> bool: """ Grant public access to a resource owned by this project. Parameters ---------- resource: Resource An instance of a resource owned by this project (e.g. a dataset). Returns ------- bool True if the action was performed successfully """ self.session.checked_post(self._path() + "/make-public", { "resource": resource.as_entity_dict() }) return True def make_private(self, resource: Resource) -> bool: """ Remove public access for a resource owned by this project. Parameters ---------- resource: Resource An instance of a resource owned by this project (e.g. a dataset). Returns ------- bool True if the action was performed successfully """ self.session.checked_post(self._path() + "/make-private", { "resource": resource.as_entity_dict() }) return True def list_members(self) -> List[ProjectMember]: """ List all of the members in the current project. Returns ------- List[ProjectMember] The members of the current project """ members = self.session.get_resource(self._path() + "/users")["users"] return [ProjectMember(user=User.build(m), project=self, role=m["role"]) for m in members] def update_user_role(self, user_uid: Union[str, UUID], role: ROLES, actions: ACTIONS = []): """ Update a User's role and action permissions in the Project Valid roles are MEMBER or LEAD. WRITE is the only action available for specification. Returns ------- bool Returns True if user role successfully updated """ self.session.checked_post(self._path() + "/users/{}".format(user_uid), {'role': role, 'actions': actions}) return True def add_user(self, user_uid: Union[str, UUID]): """ Add a User to a Project Adds User with MEMBER role to the Project. Use the update_user_rule method to change a User's role. Returns ------- bool Returns True if user successfully added """ self.session.checked_post(self._path() + "/users/{}".format(user_uid), {'role': MEMBER, 'actions': []}) return True def remove_user(self, user_uid: Union[str, UUID]) -> bool: """ Remove a User from a Project. Returns ------- bool Returns True if user successfully removed """ self.session.checked_delete( self._path() + "/users/{}".format(user_uid) ) return True
class Project(Resource['Project']): """ A Citrine Project. A project is a collection of datasets, some of which belong directly to the project and some of which have been shared with the project. Parameters ---------- name: str Name of the project. description: str Long-form description of the project. session: Session, optional The Citrine session used to connect to the database. Attributes ---------- uid: UUID Unique uuid4 identifier of this project. status: str Status of the project. created_at: int Time the project was created, in seconds since epoch. """ _response_key = 'project' _resource_type = ResourceTypeEnum.PROJECT name = properties.String('name') description = properties.Optional(properties.String(), 'description') uid = properties.Optional(properties.UUID(), 'id') status = properties.Optional(properties.String(), 'status') created_at = properties.Optional(properties.Datetime(), 'created_at') def __init__(self, name: str, description: Optional[str] = None, session: Optional[Session] = Session()): self.name: str = name self.description: Optional[str] = description self.session: Session = session def __str__(self): return '<Project {!r}>'.format(self.name) def _path(self): return '/projects/{project_id}'.format(**{"project_id": self.uid}) @property def modules(self) -> ModuleCollection: """Return a resource representing all visible design spaces.""" return ModuleCollection(self.uid, self.session) @property def design_spaces(self) -> DesignSpaceCollection: """Return a resource representing all visible design spaces.""" return DesignSpaceCollection(self.uid, self.session) @property def processors(self) -> ProcessorCollection: """Return a resource representing all visible processors.""" return ProcessorCollection(self.uid, self.session) @property def predictors(self) -> PredictorCollection: """Return a resource representing all visible predictors.""" return PredictorCollection(self.uid, self.session) @property def descriptors(self) -> DescriptorMethods: """Return a resource containing a set of methods returning descriptors.""" return DescriptorMethods(self.uid, self.session) @property @deprecated( deprecated_in="0.101.0", details="Use design_workflows or predictor_evaluation_workflows instead" ) def workflows(self) -> WorkflowCollection: """Return a resource representing all visible workflows.""" return WorkflowCollection(self.uid, self.session) @property def predictor_evaluation_workflows( self) -> PredictorEvaluationWorkflowCollection: """Return a collection representing all visible predictor evaluation workflows.""" return PredictorEvaluationWorkflowCollection(self.uid, self.session) @property def predictor_evaluation_executions( self) -> PredictorEvaluationExecutionCollection: """Return a collection representing all visible predictor evaluation executions.""" return PredictorEvaluationExecutionCollection(project_id=self.uid, session=self.session) @property def design_workflows(self) -> DesignWorkflowCollection: """Return a collection representing all visible design workflows.""" return DesignWorkflowCollection(self.uid, self.session) @property def datasets(self) -> DatasetCollection: """Return a resource representing all visible datasets.""" return DatasetCollection(self.uid, self.session) @property def tables(self) -> GemTableCollection: """Return a resource representing all visible Tables.""" return GemTableCollection(self.uid, self.session) @property def property_templates(self) -> PropertyTemplateCollection: """Return a resource representing all property templates in this dataset.""" return PropertyTemplateCollection(self.uid, None, self.session) @property def condition_templates(self) -> ConditionTemplateCollection: """Return a resource representing all condition templates in this dataset.""" return ConditionTemplateCollection(self.uid, None, self.session) @property def parameter_templates(self) -> ParameterTemplateCollection: """Return a resource representing all parameter templates in this dataset.""" return ParameterTemplateCollection(self.uid, None, self.session) @property def material_templates(self) -> MaterialTemplateCollection: """Return a resource representing all material templates in this dataset.""" return MaterialTemplateCollection(self.uid, None, self.session) @property def measurement_templates(self) -> MeasurementTemplateCollection: """Return a resource representing all measurement templates in this dataset.""" return MeasurementTemplateCollection(self.uid, None, self.session) @property def process_templates(self) -> ProcessTemplateCollection: """Return a resource representing all process templates in this dataset.""" return ProcessTemplateCollection(self.uid, None, self.session) @property def process_runs(self) -> ProcessRunCollection: """Return a resource representing all process runs in this dataset.""" return ProcessRunCollection(self.uid, None, self.session) @property def measurement_runs(self) -> MeasurementRunCollection: """Return a resource representing all measurement runs in this dataset.""" return MeasurementRunCollection(self.uid, None, self.session) @property def material_runs(self) -> MaterialRunCollection: """Return a resource representing all material runs in this dataset.""" return MaterialRunCollection(self.uid, None, self.session) @property def ingredient_runs(self) -> IngredientRunCollection: """Return a resource representing all ingredient runs in this dataset.""" return IngredientRunCollection(self.uid, None, self.session) @property def process_specs(self) -> ProcessSpecCollection: """Return a resource representing all process specs in this dataset.""" return ProcessSpecCollection(self.uid, None, self.session) @property def measurement_specs(self) -> MeasurementSpecCollection: """Return a resource representing all measurement specs in this dataset.""" return MeasurementSpecCollection(self.uid, None, self.session) @property def material_specs(self) -> MaterialSpecCollection: """Return a resource representing all material specs in this dataset.""" return MaterialSpecCollection(self.uid, None, self.session) @property def ingredient_specs(self) -> IngredientSpecCollection: """Return a resource representing all ingredient specs in this dataset.""" return IngredientSpecCollection(self.uid, None, self.session) @property def table_configs(self) -> TableConfigCollection: """Return a resource representing all Table Configs in the project.""" return TableConfigCollection(self.uid, self.session) @property @deprecated(deprecated_in="0.52.2", details="Use table_configs instead") def ara_definitions(self) -> TableConfigCollection: # pragma: no cover """[DEPRECATED] Use table_configs instead.""" from warnings import warn warn( "ara_definitions is deprecated and will soon be removed. " "Please call table_configs instead.", DeprecationWarning) return self.table_configs def share(self, project_id: str, resource_type: str, resource_id: str) -> Dict[str, str]: """Share a resource with another project.""" return self.session.post_resource( self._path() + "/share", { "project_id": project_id, "resource": { "type": resource_type, "id": resource_id } }) def transfer_resource(self, resource: Resource, receiving_project_uid: Union[str, UUID]) -> bool: """ Transfer ownership of a resource. The new owner of the the supplied resource becomes the project with ``uid == receiving_project_uid``. Parameters ---------- resource: Resource The resource owned by this project, which will get transferred to the project with ``uid == receiving_project_uid``. receiving_project_uid: Union[string, UUID] The uid of the project to which the resource will be transferred. Returns ------- bool Returns ``True`` upon successful resource transfer. """ try: self.session.checked_post( self._path() + "/transfer-resource", { "to_project_id": str(receiving_project_uid), "resource": resource.as_entity_dict() }) except AttributeError: # If _resource_type is not implemented raise RuntimeError( f"Resource of type {resource.__class__.__name__} " f"cannot be made transferred") return True def make_public(self, resource: Resource) -> bool: """ Grant public access to a resource owned by this project. Parameters ---------- resource: Resource An instance of a resource owned by this project (e.g., a dataset). Returns ------- bool ``True`` if the action was performed successfully """ try: self.session.checked_post(self._path() + "/make-public", {"resource": resource.as_entity_dict()}) except AttributeError: # If _resource_type is not implemented raise RuntimeError( f"Resource of type {resource.__class__.__name__} " f"cannot be made public") return True def make_private(self, resource: Resource) -> bool: """ Remove public access for a resource owned by this project. Parameters ---------- resource: Resource An instance of a resource owned by this project (e.g., a dataset). Returns ------- bool ``True`` if the action was performed successfully """ try: self.session.checked_post(self._path() + "/make-private", {"resource": resource.as_entity_dict()}) except AttributeError: # If _resource_type is not implemented raise RuntimeError( f"Resource of type {resource.__class__.__name__} " f"cannot be made private") return True def creator(self) -> str: """ Return the creator of this project. Returns ------- str The email of the creator of this resource. """ email = self.session.get_resource(self._path() + "/creator")["email"] return email def owned_dataset_ids(self) -> List[str]: """ List all the ids of the datasets owned by the current project. Returns ------- List[str] The ids of the modules owned by current project """ dataset_ids = self.session.get_resource(self._path() + "/dataset_ids")["dataset_ids"] return dataset_ids def owned_table_ids(self) -> List[str]: """ List all the ids of the tables owned by the current project. Returns ------- List[str] The ids of the tables owned by current project """ table_ids = self.session.get_resource(self._path() + "/table_ids")["table_ids"] return table_ids def owned_table_config_ids(self) -> List[str]: """ List all the ids of the table configs owned by the current project. Returns ------- List[str] The ids of the table configs owned by current project """ result = self.session.get_resource(self._path() + "/table_definition_ids") return result["table_definition_ids"] def list_members(self) -> List[ProjectMember]: """ List all of the members in the current project. Returns ------- List[ProjectMember] The members of the current project """ members = self.session.get_resource(self._path() + "/users")["users"] return [ ProjectMember(user=User.build(m), project=self, role=m["role"]) for m in members ] def update_user_role(self, user_uid: Union[str, UUID], role: ROLES, actions: ACTIONS = []): """ Update a User's role and action permissions in the Project. Valid roles are ``MEMBER`` or ``LEAD``. ``WRITE`` is the only action available for specification. Returns ------- bool Returns ``True`` if user role successfully updated """ self.session.checked_post(self._path() + "/users/{}".format(user_uid), { 'role': role, 'actions': actions }) return True def add_user(self, user_uid: Union[str, UUID]): """ Add a User to a Project. Adds User with ``MEMBER`` role to the Project. Use the ``update_user_rule`` method to change a User's role. Returns ------- bool Returns ``True`` if user successfully added """ self.session.checked_post(self._path() + "/users/{}".format(user_uid), { 'role': MEMBER, 'actions': [] }) return True def remove_user(self, user_uid: Union[str, UUID]) -> bool: """ Remove a User from a Project. Returns ------- bool Returns ``True`` if user successfully removed """ self.session.checked_delete(self._path() + "/users/{}".format(user_uid)) return True def gemd_batch_delete( self, id_list: List[Union[LinkByUID, UUID, str, BaseEntity]], *, timeout: float = 2 * 60, polling_delay: float = 1.0) -> List[Tuple[LinkByUID, ApiError]]: """ Remove a set of GEMD objects. You may provide GEMD objects that reference each other, and the objects will be removed in the appropriate order. A failure will be returned if the object cannot be deleted due to an external reference. You must have Write access on the associated datasets for each object. Parameters ---------- id_list: List[Union[LinkByUID, UUID, str, BaseEntity]] A list of the IDs of data objects to be removed. They can be passed as a LinkByUID tuple, a UUID, a string, or the object itself. A UUID or string is assumed to be a Citrine ID, whereas a LinkByUID or BaseEntity can also be used to provide an external ID. Returns ------- List[Tuple[LinkByUID, ApiError]] A list of (LinkByUID, api_error) for each failure to delete an object. Note that this method doesn't raise an exception if an object fails to be deleted. """ return _async_gemd_batch_delete(id_list, self.uid, self.session, None, timeout=timeout, polling_delay=polling_delay)
class Dataset(Resource['Dataset']): """ A collection of data objects. Datasets are the basic unit of access control. A user with read access to a dataset can view every object in that dataset. A user with write access to a dataset can create, update, and delete objects in the dataset. Parameters ---------- name: str Name of the dataset. Can be used for searching. summary: str A summary of this dataset. description: str Long-form description of the dataset. Attributes ---------- uid: UUID Unique uuid4 identifier of this dataset. deleted: bool Flag indicating whether or not this dataset has been deleted. created_by: UUID ID of the user who created the dataset. updated_by: UUID ID of the user who last updated the dataset. deleted_by: UUID ID of the user who deleted the dataset, if it is deleted. create_time: int Time the dataset was created, in seconds since epoch. update_time: int Time the dataset was most recently updated, in seconds since epoch. delete_time: int Time the dataset was deleted, in seconds since epoch, if it is deleted. public: bool Flag indicating whether the dataset is publicly readable. """ _response_key = 'dataset' uid = properties.Optional(properties.UUID(), 'id') name = properties.String('name') summary = properties.String('summary') description = properties.String('description') deleted = properties.Optional(properties.Boolean(), 'deleted') created_by = properties.Optional(properties.UUID(), 'created_by') updated_by = properties.Optional(properties.UUID(), 'updated_by') deleted_by = properties.Optional(properties.UUID(), 'deleted_by') create_time = properties.Optional(properties.Datetime(), 'create_time') update_time = properties.Optional(properties.Datetime(), 'update_time') delete_time = properties.Optional(properties.Datetime(), 'delete_time') public = properties.Optional(properties.Boolean(), 'public') def __init__(self, name: str, summary: str, description: str): self.name: str = name self.summary: str = summary self.description: str = description # The attributes below should not be set by the user. Instead they will be updated as the # dataset interacts with the backend data service self.uid = None self.deleted = None self.created_by = None self.updated_by = None self.deleted_by = None self.create_time = None self.update_time = None self.delete_time = None self.public = None def __str__(self): return '<Dataset {!r}>'.format(self.name) @property def property_templates(self) -> PropertyTemplateCollection: """Return a resource representing all property templates in this dataset.""" return PropertyTemplateCollection(self.project_id, self.uid, self.session) @property def condition_templates(self) -> ConditionTemplateCollection: """Return a resource representing all condition templates in this dataset.""" return ConditionTemplateCollection(self.project_id, self.uid, self.session) @property def parameter_templates(self) -> ParameterTemplateCollection: """Return a resource representing all parameter templates in this dataset.""" return ParameterTemplateCollection(self.project_id, self.uid, self.session) @property def material_templates(self) -> MaterialTemplateCollection: """Return a resource representing all material templates in this dataset.""" return MaterialTemplateCollection(self.project_id, self.uid, self.session) @property def measurement_templates(self) -> MeasurementTemplateCollection: """Return a resource representing all measurement templates in this dataset.""" return MeasurementTemplateCollection(self.project_id, self.uid, self.session) @property def process_templates(self) -> ProcessTemplateCollection: """Return a resource representing all process templates in this dataset.""" return ProcessTemplateCollection(self.project_id, self.uid, self.session) @property def process_runs(self) -> ProcessRunCollection: """Return a resource representing all process runs in this dataset.""" return ProcessRunCollection(self.project_id, self.uid, self.session) @property def measurement_runs(self) -> MeasurementRunCollection: """Return a resource representing all measurement runs in this dataset.""" return MeasurementRunCollection(self.project_id, self.uid, self.session) @property def material_runs(self) -> MaterialRunCollection: """Return a resource representing all material runs in this dataset.""" return MaterialRunCollection(self.project_id, self.uid, self.session) @property def ingredient_runs(self) -> IngredientRunCollection: """Return a resource representing all ingredient runs in this dataset.""" return IngredientRunCollection(self.project_id, self.uid, self.session) @property def process_specs(self) -> ProcessSpecCollection: """Return a resource representing all process specs in this dataset.""" return ProcessSpecCollection(self.project_id, self.uid, self.session) @property def measurement_specs(self) -> MeasurementSpecCollection: """Return a resource representing all measurement specs in this dataset.""" return MeasurementSpecCollection(self.project_id, self.uid, self.session) @property def material_specs(self) -> MaterialSpecCollection: """Return a resource representing all material specs in this dataset.""" return MaterialSpecCollection(self.project_id, self.uid, self.session) @property def ingredient_specs(self) -> IngredientSpecCollection: """Return a resource representing all ingredient specs in this dataset.""" return IngredientSpecCollection(self.project_id, self.uid, self.session) @property def files(self) -> FileCollection: """Return a resource representing all files in the dataset.""" return FileCollection(self.project_id, self.uid, self.session) def _collection_for(self, data_concepts_resource): if isinstance(data_concepts_resource, MeasurementTemplate): return self.measurement_templates if isinstance(data_concepts_resource, MeasurementSpec): return self.measurement_specs if isinstance(data_concepts_resource, MeasurementRun): return self.measurement_runs if isinstance(data_concepts_resource, MaterialTemplate): return self.material_templates if isinstance(data_concepts_resource, MaterialSpec): return self.material_specs if isinstance(data_concepts_resource, MaterialRun): return self.material_runs if isinstance(data_concepts_resource, ProcessTemplate): return self.process_templates if isinstance(data_concepts_resource, ProcessSpec): return self.process_specs if isinstance(data_concepts_resource, ProcessRun): return self.process_runs if isinstance(data_concepts_resource, IngredientSpec): return self.ingredient_specs if isinstance(data_concepts_resource, IngredientRun): return self.ingredient_runs if isinstance(data_concepts_resource, PropertyTemplate): return self.property_templates if isinstance(data_concepts_resource, ParameterTemplate): return self.parameter_templates if isinstance(data_concepts_resource, ConditionTemplate): return self.condition_templates def register(self, data_concepts_resource: ResourceType, dry_run=False) -> ResourceType: """Register a data concepts resource to the appropriate collection.""" return self._collection_for(data_concepts_resource)\ .register(data_concepts_resource, dry_run=dry_run) def register_all(self, data_concepts_resources: List[ResourceType], dry_run=False) -> List[ResourceType]: """ Register multiple data concepts resources to each of their appropriate collections. Does so in an order that is guaranteed to store all linked items before the item that references them. The uids of the input data concepts resources are updated with their on-platform uids. This supports storing an object that has a reference to an object that doesn't have a uid. Parameters ---------- data_concepts_resources: List[ResourceType] The resources to register. Can be different types. dry_run: bool Whether to actually register the item or run a dry run of the register operation. Dry run is intended to be used for validation. Default: false Returns ------- List[ResourceType] The registered versions """ resources = list() by_type = defaultdict(list) for obj in data_concepts_resources: by_type[obj.typ].append(obj) typ_groups = sorted(list(by_type.values()), key=lambda x: writable_sort_order(x[0])) batch_size = 50 for typ_group in typ_groups: num_batches = len(typ_group) // batch_size for batch_num in range(num_batches + 1): batch = typ_group[batch_num * batch_size: (batch_num + 1) * batch_size] if batch: # final batch is empty when batch_size divides len(typ_group) registered = self._collection_for(batch[0])\ .register_all(batch, dry_run=dry_run) for prewrite, postwrite in zip(batch, registered): if isinstance(postwrite, BaseEntity): prewrite.uids = postwrite.uids resources.extend(registered) return resources def delete(self, data_concepts_resource: ResourceType, dry_run=False) -> ResourceType: """Delete a data concepts resource to the appropriate collection.""" uid = next(iter(data_concepts_resource.uids.items()), None) if uid is None: raise ValueError("Only objects that contain identifiers can be deleted.") return self._collection_for(data_concepts_resource) \ .delete(uid[1], scope=uid[0], dry_run=dry_run)
class Dataset(Resource['Dataset']): """ A collection of data objects. Datasets are the basic unit of access control. A user with read access to a dataset can view every object in that dataset. A user with write access to a dataset can create, update, and delete objects in the dataset. Parameters ---------- name: str Name of the dataset. Can be used for searching. summary: str A summary of this dataset. description: str Long-form description of the dataset. unique_name: Optional[str] An optional, globally unique name that can be used to retrieve the dataset. Attributes ---------- uid: UUID Unique uuid4 identifier of this dataset. deleted: bool Flag indicating whether or not this dataset has been deleted. created_by: UUID ID of the user who created the dataset. updated_by: UUID ID of the user who last updated the dataset. deleted_by: UUID ID of the user who deleted the dataset, if it is deleted. create_time: int Time the dataset was created, in seconds since epoch. update_time: int Time the dataset was most recently updated, in seconds since epoch. delete_time: int Time the dataset was deleted, in seconds since epoch, if it is deleted. public: bool Flag indicating whether the dataset is publicly readable. """ _response_key = 'dataset' _resource_type = ResourceTypeEnum.DATASET uid = properties.Optional(properties.UUID(), 'id') name = properties.String('name') unique_name = properties.Optional(properties.String(), 'unique_name') summary = properties.String('summary') description = properties.String('description') deleted = properties.Optional(properties.Boolean(), 'deleted') created_by = properties.Optional(properties.UUID(), 'created_by') updated_by = properties.Optional(properties.UUID(), 'updated_by') deleted_by = properties.Optional(properties.UUID(), 'deleted_by') create_time = properties.Optional(properties.Datetime(), 'create_time') update_time = properties.Optional(properties.Datetime(), 'update_time') delete_time = properties.Optional(properties.Datetime(), 'delete_time') public = properties.Optional(properties.Boolean(), 'public') def __init__(self, name: str, summary: str, description: str, unique_name: Optional[str] = None): self.name: str = name self.summary: str = summary self.description: str = description self.unique_name = unique_name # The attributes below should not be set by the user. Instead they will be updated as the # dataset interacts with the backend data service self.uid = None self.deleted = None self.created_by = None self.updated_by = None self.deleted_by = None self.create_time = None self.update_time = None self.delete_time = None self.public = None def __str__(self): return '<Dataset {!r}>'.format(self.name) @property def property_templates(self) -> PropertyTemplateCollection: """Return a resource representing all property templates in this dataset.""" return PropertyTemplateCollection(self.project_id, self.uid, self.session) @property def condition_templates(self) -> ConditionTemplateCollection: """Return a resource representing all condition templates in this dataset.""" return ConditionTemplateCollection(self.project_id, self.uid, self.session) @property def parameter_templates(self) -> ParameterTemplateCollection: """Return a resource representing all parameter templates in this dataset.""" return ParameterTemplateCollection(self.project_id, self.uid, self.session) @property def material_templates(self) -> MaterialTemplateCollection: """Return a resource representing all material templates in this dataset.""" return MaterialTemplateCollection(self.project_id, self.uid, self.session) @property def measurement_templates(self) -> MeasurementTemplateCollection: """Return a resource representing all measurement templates in this dataset.""" return MeasurementTemplateCollection(self.project_id, self.uid, self.session) @property def process_templates(self) -> ProcessTemplateCollection: """Return a resource representing all process templates in this dataset.""" return ProcessTemplateCollection(self.project_id, self.uid, self.session) @property def process_runs(self) -> ProcessRunCollection: """Return a resource representing all process runs in this dataset.""" return ProcessRunCollection(self.project_id, self.uid, self.session) @property def measurement_runs(self) -> MeasurementRunCollection: """Return a resource representing all measurement runs in this dataset.""" return MeasurementRunCollection(self.project_id, self.uid, self.session) @property def material_runs(self) -> MaterialRunCollection: """Return a resource representing all material runs in this dataset.""" return MaterialRunCollection(self.project_id, self.uid, self.session) @property def ingredient_runs(self) -> IngredientRunCollection: """Return a resource representing all ingredient runs in this dataset.""" return IngredientRunCollection(self.project_id, self.uid, self.session) @property def process_specs(self) -> ProcessSpecCollection: """Return a resource representing all process specs in this dataset.""" return ProcessSpecCollection(self.project_id, self.uid, self.session) @property def measurement_specs(self) -> MeasurementSpecCollection: """Return a resource representing all measurement specs in this dataset.""" return MeasurementSpecCollection(self.project_id, self.uid, self.session) @property def material_specs(self) -> MaterialSpecCollection: """Return a resource representing all material specs in this dataset.""" return MaterialSpecCollection(self.project_id, self.uid, self.session) @property def ingredient_specs(self) -> IngredientSpecCollection: """Return a resource representing all ingredient specs in this dataset.""" return IngredientSpecCollection(self.project_id, self.uid, self.session) @property def files(self) -> FileCollection: """Return a resource representing all files in the dataset.""" return FileCollection(self.project_id, self.uid, self.session) def _collection_for(self, data_concepts_resource): if isinstance(data_concepts_resource, MeasurementTemplate): return self.measurement_templates if isinstance(data_concepts_resource, MeasurementSpec): return self.measurement_specs if isinstance(data_concepts_resource, MeasurementRun): return self.measurement_runs if isinstance(data_concepts_resource, MaterialTemplate): return self.material_templates if isinstance(data_concepts_resource, MaterialSpec): return self.material_specs if isinstance(data_concepts_resource, MaterialRun): return self.material_runs if isinstance(data_concepts_resource, ProcessTemplate): return self.process_templates if isinstance(data_concepts_resource, ProcessSpec): return self.process_specs if isinstance(data_concepts_resource, ProcessRun): return self.process_runs if isinstance(data_concepts_resource, IngredientSpec): return self.ingredient_specs if isinstance(data_concepts_resource, IngredientRun): return self.ingredient_runs if isinstance(data_concepts_resource, PropertyTemplate): return self.property_templates if isinstance(data_concepts_resource, ParameterTemplate): return self.parameter_templates if isinstance(data_concepts_resource, ConditionTemplate): return self.condition_templates def register(self, data_concepts_resource: ResourceType, dry_run=False) -> ResourceType: """Register a data concepts resource to the appropriate collection.""" return self._collection_for(data_concepts_resource)\ .register(data_concepts_resource, dry_run=dry_run) def register_all(self, data_concepts_resources: List[ResourceType], dry_run=False) -> List[ResourceType]: """ Register multiple data concepts resources to each of their appropriate collections. Does so in an order that is guaranteed to store all linked items before the item that references them. The uids of the input data concepts resources are updated with their on-platform uids. This supports storing an object that has a reference to an object that doesn't have a uid. Parameters ---------- data_concepts_resources: List[ResourceType] The resources to register. Can be different types. dry_run: bool Whether to actually register the item or run a dry run of the register operation. Dry run is intended to be used for validation. Default: false Returns ------- List[ResourceType] The registered versions """ resources = list() by_type = defaultdict(list) for obj in data_concepts_resources: by_type[obj.typ].append(obj) typ_groups = sorted(list(by_type.values()), key=lambda x: writable_sort_order(x[0])) batch_size = 50 for typ_group in typ_groups: num_batches = len(typ_group) // batch_size for batch_num in range(num_batches + 1): batch = typ_group[batch_num * batch_size:(batch_num + 1) * batch_size] if batch: # final batch is empty when batch_size divides len(typ_group) registered = self._collection_for(batch[0])\ .register_all(batch, dry_run=dry_run) for prewrite, postwrite in zip(batch, registered): if isinstance(postwrite, BaseEntity): prewrite.uids = postwrite.uids resources.extend(registered) return resources def update(self, model: ResourceType) -> ResourceType: """Update a data concepts resource using the appropriate collection.""" return self._collection_for(model).update(model) def delete(self, data_concepts_resource: ResourceType, dry_run=False) -> ResourceType: """Delete a data concepts resource to the appropriate collection.""" uid = next(iter(data_concepts_resource.uids.items()), None) if uid is None: raise ValueError( "Only objects that contain identifiers can be deleted.") return self._collection_for(data_concepts_resource) \ .delete(uid[1], scope=uid[0], dry_run=dry_run) def delete_contents(self, *, timeout: float = 2 * 60, polling_delay: float = 1.0): """ Delete all the GEMD objects from within a single Dataset. Parameters ---------- timeout: float Amount of time to wait on the job (in seconds) before giving up. Note that this number has no effect on the underlying job itself, which can also time out server-side. polling_delay: float How long to delay between each polling retry attempt. Returns ------- List[Tuple[LinkByUID, ApiError]] A list of (LinkByUID, api_error) for each failure to delete an object. Note that this method doesn't raise an exception if an object fails to be deleted. """ path = 'projects/{project_id}/datasets/{dataset_uid}/contents'.format( dataset_uid=self.uid, project_id=self.project_id) response = self.session.delete_resource(path) job_id = response["job_id"] return _poll_for_async_batch_delete_result(self.project_id, self.session, job_id, timeout, polling_delay) def gemd_batch_delete( self, id_list: List[Union[LinkByUID, UUID, str, BaseEntity]], *, timeout: float = 2 * 60, polling_delay: float = 1.0) -> List[Tuple[LinkByUID, ApiError]]: """ Remove a set of GEMD objects. You may provide GEMD objects that reference each other, and the objects will be removed in the appropriate order. A failure will be returned if the object cannot be deleted due to an external reference. All data objects must be associated with this dataset resource. You must also have write access on this dataset. If you wish to delete more than 50 objects, queuing of deletes requires that the types of objects be known, and thus you _must_ provide ids in the form of BaseEntities. Also note that Attribute Templates cannot be deleted at present. Parameters ---------- id_list: List[Union[LinkByUID, UUID, str, BaseEntity]] A list of the IDs of data objects to be removed. They can be passed as a LinkByUID tuple, a UUID, a string, or the object itself. A UUID or string is assumed to be a Citrine ID, whereas a LinkByUID or BaseEntity can also be used to provide an external ID. Returns ------- List[Tuple[LinkByUID, ApiError]] A list of (LinkByUID, api_error) for each failure to delete an object. Note that this method doesn't raise an exception if an object fails to be deleted. """ return _async_gemd_batch_delete(id_list, self.project_id, self.session, self.uid, timeout=timeout, polling_delay=polling_delay)
class Dataset(Resource['Dataset']): """ A collection of data objects. Datasets are the basic unit of access control. A user with read access to a dataset can view every object in that dataset. A user with write access to a dataset can create, update, and delete objects in the dataset. Parameters ---------- name: str Name of the dataset. Can be used for searching. summary: str A summary of this dataset. description: str Long-form description of the dataset. Attributes ---------- uid: UUID Unique uuid4 identifier of this dataset. deleted: bool Flag indicating whether or not this dataset has been deleted. created_by: UUID ID of the user who created the dataset. updated_by: UUID ID of the user who last updated the dataset. deleted_by: UUID ID of the user who deleted the dataset, if it is deleted. create_time: int Time the dataset was created, in seconds since epoch. update_time: int Time the dataset was most recently updated, in seconds since epoch. delete_time: int Time the dataset was deleted, in seconds since epoch, if it is deleted. """ _response_key = 'dataset' uid = properties.Optional(properties.UUID(), 'id') name = properties.String('name') summary = properties.String('summary') description = properties.String('description') deleted = properties.Optional(properties.Boolean(), 'deleted') created_by = properties.Optional(properties.UUID(), 'created_by') updated_by = properties.Optional(properties.UUID(), 'updated_by') deleted_by = properties.Optional(properties.UUID(), 'deleted_by') create_time = properties.Optional(properties.Datetime(), 'create_time') update_time = properties.Optional(properties.Datetime(), 'update_time') delete_time = properties.Optional(properties.Datetime(), 'delete_time') def __init__(self, name: str, summary: str, description: str): self.name: str = name self.summary: str = summary self.description: str = description # The attributes below should not be set by the user. Instead they will be updated as the # dataset interacts with the backend data service self.uid = None self.deleted = None self.created_by = None self.updated_by = None self.deleted_by = None self.create_time = None self.update_time = None self.delete_time = None def __str__(self): return '<Dataset {!r}>'.format(self.name) @property def property_templates(self) -> PropertyTemplateCollection: """Return a resource representing all property templates in this dataset.""" return PropertyTemplateCollection(self.project_id, self.uid, self.session) @property def condition_templates(self) -> ConditionTemplateCollection: """Return a resource representing all condition templates in this dataset.""" return ConditionTemplateCollection(self.project_id, self.uid, self.session) @property def parameter_templates(self) -> ParameterTemplateCollection: """Return a resource representing all parameter templates in this dataset.""" return ParameterTemplateCollection(self.project_id, self.uid, self.session) @property def material_templates(self) -> MaterialTemplateCollection: """Return a resource representing all material templates in this dataset.""" return MaterialTemplateCollection(self.project_id, self.uid, self.session) @property def measurement_templates(self) -> MeasurementTemplateCollection: """Return a resource representing all measurement templates in this dataset.""" return MeasurementTemplateCollection(self.project_id, self.uid, self.session) @property def process_templates(self) -> ProcessTemplateCollection: """Return a resource representing all process templates in this dataset.""" return ProcessTemplateCollection(self.project_id, self.uid, self.session) @property def process_runs(self) -> ProcessRunCollection: """Return a resource representing all process runs in this dataset.""" return ProcessRunCollection(self.project_id, self.uid, self.session) @property def measurement_runs(self) -> MeasurementRunCollection: """Return a resource representing all measurement runs in this dataset.""" return MeasurementRunCollection(self.project_id, self.uid, self.session) @property def material_runs(self) -> MaterialRunCollection: """Return a resource representing all material runs in this dataset.""" return MaterialRunCollection(self.project_id, self.uid, self.session) @property def ingredient_runs(self) -> IngredientRunCollection: """Return a resource representing all ingredient runs in this dataset.""" return IngredientRunCollection(self.project_id, self.uid, self.session) @property def process_specs(self) -> ProcessSpecCollection: """Return a resource representing all process specs in this dataset.""" return ProcessSpecCollection(self.project_id, self.uid, self.session) @property def measurement_specs(self) -> MeasurementSpecCollection: """Return a resource representing all measurement specs in this dataset.""" return MeasurementSpecCollection(self.project_id, self.uid, self.session) @property def material_specs(self) -> MaterialSpecCollection: """Return a resource representing all material specs in this dataset.""" return MaterialSpecCollection(self.project_id, self.uid, self.session) @property def ingredient_specs(self) -> IngredientSpecCollection: """Return a resource representing all ingredient specs in this dataset.""" return IngredientSpecCollection(self.project_id, self.uid, self.session) @property def files(self) -> FileCollection: """Return a resource representing all files in the dataset.""" return FileCollection(self.project_id, self.uid, self.session)