Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add variant related models #352

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Changed
Added
-----
- Add ``restart`` method to the ``Data`` resource
- Add variants related models

Fixed
-----
Expand Down
43 changes: 41 additions & 2 deletions src/resdk/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,24 +162,35 @@ def _clone(self):

def _dehydrate_resources(self, obj):
"""Iterate through object and replace all objects with their ids."""
print("Dehydrating", obj, type(obj))
if isinstance(obj, BaseResource):
print("Base")
return obj.id
if isinstance(obj, dict):
print("Dict")
return {key: self._dehydrate_resources(value) for key, value in obj.items()}
if self._non_string_iterable(obj):
print("Non string iterable")
return [self._dehydrate_resources(element) for element in obj]

print("Returning unchanged", obj, type(obj))
return obj

def _add_filter(self, filter_):
"""Add filtering parameters."""
print("Filter for ", self.resource)
for key, value in filter_.items():
# 'sample' is called 'entity' in the backend.
key = key.replace("sample", "entity")
if not self.resource.__name__.startswith("Variant"):
print("Replacing sample with entity in", key)
key = key.replace("sample", "entity")
print("Adding filter", key, value)
value = self._dehydrate_resources(value)
print("Dehidrated value", value, type(value))
if self._non_string_iterable(value):
print("Iterable")
value = ",".join(map(str, value))
if self.resource.query_method == "GET":
print("Appending value", value)
self._filters[key].append(value)
elif self.resource.query_method == "POST":
self._filters[key] = value
Expand Down Expand Up @@ -211,6 +222,8 @@ def _fetch(self):

filters = self._compose_filters()
if self.resource.query_method == "GET":
print("Query with filters", filters)
print("My api", self.api)
items = self.api.get(**filters)
elif self.resource.query_method == "POST":
items = self.api.post(filters)
Expand Down Expand Up @@ -285,6 +298,8 @@ def get(self, *args, **kwargs):
kwargs["limit"] = kwargs.get("limit", 1)

new_query = self._clone()

print("Adding filters", kwargs)
new_query._add_filter(kwargs)

response = list(new_query)
Expand Down Expand Up @@ -400,6 +415,30 @@ def from_path(self, full_path: str) -> "AnnotationField":
return self.get(name=field_name, group__name=group_name)


class VariantCallQuery(ResolweQuery):
"""Do not translate 'sample' to 'entity'."""

def _add_filter(self, filter_):
"""Add filtering parameters."""
for key, value in filter_.items():
# 'sample' is called 'entity' in the backend.
print("Adding filter", key, value)
value = self._dehydrate_resources(value)
print("Dehidrated value", value, type(value))
if self._non_string_iterable(value):
print("Iterable")
value = ",".join(map(str, value))
if self.resource.query_method == "GET":
print("Appending value", value)
self._filters[key].append(value)
elif self.resource.query_method == "POST":
self._filters[key] = value
else:
raise NotImplementedError(
"Unsupported query_method: {}".format(self.resource.query_method)
)


class AnnotationValueQuery(ResolweQuery):
"""Populate Annotation fields with a single query."""

Expand Down
9 changes: 9 additions & 0 deletions src/resdk/resolwe.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@
Relation,
Sample,
User,
Variant,
VariantAnnotation,
VariantCall,
VariantExperiment,
)
from .resources.base import BaseResource
from .resources.kb import Feature, Mapping
Expand Down Expand Up @@ -114,6 +118,10 @@ class Resolwe:
resource_query_mapping = {
AnnotationField: "annotation_field",
AnnotationValue: "annotation_value",
Variant: "variant",
VariantAnnotation: "variant_annotation",
VariantExperiment: "variant_experiment",
VariantCall: "variant_calls",
Data: "data",
Collection: "collection",
Sample: "sample",
Expand All @@ -126,6 +134,7 @@ class Resolwe:
Mapping: "mapping",
Geneset: "geneset",
Metadata: "metadata",
Variant: "variant",
}
# Map ResolweQuery name to it's slug_field
slug_field_mapping = {
Expand Down
9 changes: 9 additions & 0 deletions src/resdk/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@
:members:
:inherited-members:

.. autoclass:: resdk.resources.Variants
:members:
:inherited-members:

.. autoclass:: resdk.resources.User
:members:
:inherited-members:
Expand Down Expand Up @@ -102,6 +106,7 @@
from .relation import Relation
from .sample import Sample
from .user import Group, User
from .variants import Variant, VariantAnnotation, VariantCall, VariantExperiment

__all__ = (
"AnnotationField",
Expand All @@ -117,4 +122,8 @@
"Process",
"Relation",
"User",
"Variant",
"VariantAnnotation",
"VariantCall",
"VariantExperiment",
)
21 changes: 20 additions & 1 deletion src/resdk/resources/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,12 @@ def fetch_object(cls, resolwe, id=None, slug=None):
if (id is None and slug is None) or (id and slug):
raise ValueError("One and only one of id or slug must be given")

print("Getting fetch object query")
query = resolwe.get_query_by_resource(cls)
# print("Got query", query)
if id:
return query.get(id=id)
print("Getting from query with slug", slug)
return query.get(slug=slug)

def fields(self):
Expand All @@ -77,6 +80,10 @@ def update(self):
response = self.api(self.id).get()
self._update_fields(response)

def __hash__(self):
"""Return hash of the object."""
return hash(self.id)

def _dehydrate_resources(self, obj):
"""Iterate through object and replace all objects with their ids."""
# Prevent circular imports:
Expand Down Expand Up @@ -130,13 +137,16 @@ def assert_fields_unchanged(field_names):
payload = {}
for field_name in self.WRITABLE_FIELDS:
if field_changed(field_name):
print("Field changed", field_name)
print("Change", getattr(self, field_name))
payload[field_name] = self._dehydrate_resources(
getattr(self, field_name)
)
if "sample" in payload:
payload["entity"] = payload.pop("sample")

if payload:
print("Sending payload 1", payload)
response = self.api(self.id).patch(payload)
self._update_fields(response)

Expand Down Expand Up @@ -216,14 +226,23 @@ def __eq__(self, obj):

def _resource_setter(self, payload, resource, field):
"""Set ``resource`` with ``payload`` on ``field``."""
print("-" * 40)
print("Resource setter", payload, type(payload))
print("Resource", resource)
if isinstance(payload, resource):
print("resource")
setattr(self, field, payload)
elif isinstance(payload, dict):
print("Dict")
setattr(self, field, resource(resolwe=self.resolwe, **payload))
elif isinstance(payload, int):
print("Int")
setattr(self, field, resource.fetch_object(self.resolwe, id=payload))
elif isinstance(payload, str):
setattr(self, field, resource.fetch_object(self.resolwe, slug=payload))
print("Str")
res = resource.fetch_object(self.resolwe, slug=payload)
print("Got result for Str", res)
setattr(self, field, res)
else:
setattr(self, field, payload)

Expand Down
3 changes: 3 additions & 0 deletions src/resdk/resources/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,16 +158,19 @@ def descriptor_schema(self, payload):
def sample(self):
"""Get sample."""
if self._sample is None and self._original_values.get("entity", None):
print("Sample getter not set")
# The collection data is only serialized on the top level. Replace the
# data inside 'entity' with the actual collection data.
entity_values = self._original_values["entity"].copy()
entity_values["collection"] = self._original_values.get("collection", None)
self._sample = Sample(resolwe=self.resolwe, **entity_values)
print("Sample getter", self._sample)
return self._sample

@sample.setter
def sample(self, payload):
"""Set sample."""
print("Sample setter", payload)
self._resource_setter(payload, Sample, "_sample")

@property
Expand Down
36 changes: 35 additions & 1 deletion src/resdk/resources/sample.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
"""Sample resource."""

import logging
from typing import TYPE_CHECKING, Any, Dict, Optional
from typing import TYPE_CHECKING, Any, Dict, List, Optional

from resdk.exceptions import ResolweServerError
from resdk.shortcuts.sample import SampleUtilsMixin

from ..utils.decorators import assert_object_exists
from .background_task import BackgroundTask
from .collection import BaseCollection, Collection
from .variants import Variant

if TYPE_CHECKING:
from .annotations import AnnotationValue
Expand Down Expand Up @@ -39,6 +40,10 @@ def __init__(self, resolwe, **model_data):
self._background = None
#: is this sample background to any other sample?
self._is_background = None
#: list of ``Variant`` objects attached to the sample
self._variants = None
#: list of ``VariantExperiment`` objects attached to the sample
self._experiments = None

super().__init__(resolwe, **model_data)

Expand All @@ -48,6 +53,8 @@ def update(self):
self._relations = None
self._background = None
self._is_background = None
self._variants = None
self._experiments = None

super().update()

Expand All @@ -60,6 +67,33 @@ def data(self):

return self._data

@property
def experiments(self):
"""Get experiments."""
if self._experiments is None:
self._experiments = self.resolwe.variant_experiment.filter(
variant_calls__sample=self.id
)
return self._experiments

@property
def latest_experiment(self):
"""Get latest experiment."""
return self.experiments.filter(ordering="-timestamp", limit=1)[0]

@property
def variants(self):
"""Get variants."""
if self._variants is None:
self._variants = self.resolwe.variant.filter(variant_calls__sample=self.id)
return self._variants

def variants_by_experiment(self, experiment):
"""Get variants for sample detected by the given experiment."""
return self.resolwe.variant.filter(
variant_calls__sample=self.id, variant_calls__experiment=experiment.id
)

@property
def collection(self):
"""Get collection."""
Expand Down
Loading