Source code for resdk.resources.sample

"""Sample resource."""
import logging

from resdk.shortcuts.sample import SampleUtilsMixin

from ..utils.decorators import assert_object_exists
from .collection import BaseCollection, Collection


[docs]class Sample(SampleUtilsMixin, BaseCollection): """Resolwe Sample resource. :param resolwe: Resolwe instance :type resolwe: Resolwe object :param model_data: Resource model data """ endpoint = "sample" WRITABLE_FIELDS = BaseCollection.WRITABLE_FIELDS + ("collection",) def __init__(self, resolwe, **model_data): """Initialize attributes.""" self.logger = logging.getLogger(__name__) #: ``Collection``s that contains the ``Sample`` (lazy loaded) self._collection = None #: list of ``Relation`` objects in ``Collection`` (lazy loaded) self._relations = None #: background ``Sample`` of the current ``Sample`` self._background = None #: is this sample background to any other sample? self._is_background = None super().__init__(resolwe, **model_data)
[docs] def update(self): """Clear cache and update resource fields from the server.""" self._collection = None self._relations = None self._background = None self._is_background = None super().update()
@property @assert_object_exists def data(self): """Get data.""" if self._data is None: self._data = self.resolwe.data.filter(entity=self.id) return self._data @property def collection(self): """Get collection.""" return self._collection @collection.setter def collection(self, payload): """Set collection.""" self._resource_setter(payload, Collection, "_collection") @property @assert_object_exists def relations(self): """Get ``Relation`` objects for this sample.""" if self._relations is None: self._relations = self.resolwe.relation.filter(entity=self.id) return self._relations
[docs] def update_descriptor(self, descriptor): """Update descriptor and descriptor_schema.""" self.api(self.id).patch({"descriptor": descriptor}) self.descriptor = descriptor
@property def background(self): """Get background sample of the current one.""" if self._background is None: background_relation = list( self.resolwe.relation.filter( type="background", entity=self.id, label="case", ) ) if len(background_relation) > 1: raise LookupError( "Multiple backgrounds defined for sample '{}'".format(self.name) ) elif not background_relation: raise LookupError( "No background is defined for sample '{}'".format(self.name) ) for partition in background_relation[0].partitions: if ( partition["label"] == "background" and partition["entity"] != self.id ): self._background = self.resolwe.sample.get(id=partition["entity"]) if self._background is None: # Cache the result = no background is found. self._background = False if self._background: return self._background @background.setter def background(self, bground): """Set sample background.""" def count_cases(entity, label): """Get a tuple (relation, number_of_cases) in a specified relation. Relation is specified by collection, type-background'entity and label. """ relation = list( self.resolwe.relation.filter( collection=collection.id, type="background", entity=entity.id, label=label, ) ) if len(relation) > 1: raise ValueError( 'Multiple relations of type "background" for sample {} in ' "collection {} " "with label {}.".format(entity, collection, label) ) elif len(relation) == 1: cases = len( [ prt for prt in relation[0].partitions if prt.get("label") == "case" ] ) else: cases = 0 return (relation[0] if relation else None, cases) if self.background == bground: return assert isinstance(bground, Sample) # Relations are always defined on collections: it is necessary # to check that both, background and case are defined in only # one common collection. Actions are done on this collection. if self.collection.id != bground.collection.id: raise ValueError( "Background and case sample are not in the same collection." ) collection = self.collection # One cannot simply assign a background to sample but needs to # account also for already existing background relations they # are part of. By this, 3 x 3 scenarios are possible. One # dimension of scenarios is determined by the relation in which # *sample* is. It can be in no background relation (0), it can # be in background relation where only one sample is the case # sample (1) or it can be in background relation where many # case samples are involved (2). Similarly, (future, to-be) # background relation can be without any existing background # relation (0), in background relation with one (1) or more (2) # case samples. # Get background relation for this sample and count cases in it. # If no relation is found set to 0. self_relation, self_cases = count_cases(self, "case") # Get background relation of to-be background sample and count # cases in it. If no relation is found set to 0. bground_relation, bground_cases = count_cases(bground, "background") # 3 x 3 options reduce to 5, since some of them can be treated equally: if self_cases == bground_cases == 0: # Neither case nor background is in background relation. # Make a new background relation. collection.create_background_relation("Background", bground, [self]) elif self_cases == 0 and bground_cases > 0: # Sample is not part of any existing background relation, # but background sample is. In this cae, just add sample to # alread existing background relation bground_relation.add_sample(self, label="case") elif self_cases == 1 and bground_cases == 0: # Sample si part od already existing background relation # where there is one sample and one background. New, # to-be-background sample is not part of any background # relation yet. Modify sample relation and replace background. for partition in self_relation.partitions: if partition["label"] == "background": partition["entity"] = bground.id break elif self_cases == 1 and bground_cases > 0: # Sample si part od already existing background relation # where there is one sample and one background. New, # to-be-background sample is is similar two-member relation. # Remove relaton of case sample and add it to existing # relation of the background smaple. self_relation.delete(force=True) bground_relation.add_sample(self, label="case") elif self_cases > 1: raise ValueError( "This sample is a case in a background relation with also other samples as cases. " "If you would like to change background sample for all of them please delete " "current relation and create new one with desired background." ) self.save() self._relations = None self._background = None bground._is_background = True @property def is_background(self): """Return ``True`` if given sample is background to any other and ``False`` otherwise.""" if self._is_background is None: background_relations = self.resolwe.relation.filter( type="background", entity=self.id, label="background", ) # we need to iterate ``background_relations`` (using len) to # evaluate ResolweQuery: self._is_background = len(background_relations) > 0 return self._is_background
[docs] @assert_object_exists def duplicate(self, inherit_collection=False): """Duplicate (make copy of) ``sample`` object. :param inherit_collection: If ``True`` then duplicated samples (and their data) will be added to collection of the original sample. :return: Duplicated sample """ duplicated = self.api().duplicate.post( {"ids": [self.id], "inherit_collection": inherit_collection} ) return self.__class__(resolwe=self.resolwe, **duplicated[0])