Skip to content

Commit 628b9b6

Browse files
authored
Merge branch 'main' into add-pr-priority-label-job
2 parents 60b1795 + 0ae941d commit 628b9b6

28 files changed

+4763
-298
lines changed

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ classifiers = [
1818
]
1919
dependencies = [
2020
"ga4gh.vrs>=2.2.0,<3.0",
21-
"ga4gh.va_spec~=0.4.2",
21+
"ga4gh.va_spec~=0.4.3",
2222
"biocommons.anyvar@git+https://github.com/biocommons/anyvar.git@0d3ab56fe936b27235a1ce136da4641ea81c0bbf",
2323
"fastapi>=0.95.0",
2424
"python-multipart", # required for fastapi file uploads
@@ -40,6 +40,7 @@ test = [
4040
"jsonschema",
4141
"pyyaml",
4242
"pytest-recording",
43+
"deepdiff",
4344
]
4445
dev = [
4546
"ruff==0.12.8",

src/anyvlm/anyvar/base_client.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from collections.abc import Iterable, Sequence
55

66
from anyvar.utils.liftover_utils import ReferenceAssembly
7-
from anyvar.utils.types import VrsVariation
7+
from ga4gh.vrs.models import Allele
88

99

1010
class AnyVarClientError(Exception):
@@ -21,6 +21,21 @@ class AnyVarClientConnectionError(AnyVarClientError):
2121
class BaseAnyVarClient(abc.ABC):
2222
"""Interface elements for an AnyVar client"""
2323

24+
@abc.abstractmethod
25+
def get_registered_allele(
26+
self, expression: str, assembly: ReferenceAssembly = ReferenceAssembly.GRCH38
27+
) -> Allele | None:
28+
"""Retrieve registered VRS Allele for given allele expression
29+
30+
Currently, only expressions supported by the VRS-Python translator are supported.
31+
This could change depending on the AnyVar implementation, though, and probably
32+
can't be validated on the AnyVLM side.
33+
34+
:param expression: variation expression to get VRS Allele for
35+
:param assembly: reference assembly used in expression
36+
:return: VRS Allele if translation succeeds and VRS Allele has already been registered, else `None`
37+
"""
38+
2439
@abc.abstractmethod
2540
def put_allele_expressions(
2641
self,
@@ -39,19 +54,6 @@ def put_allele_expressions(
3954
else `None`, for the i'th expression
4055
"""
4156

42-
@abc.abstractmethod
43-
def search_by_interval(
44-
self, accession: str, start: int, end: int
45-
) -> list[VrsVariation]:
46-
"""Get all variation IDs located within the specified range
47-
48-
:param accession: sequence accession
49-
:param start: start position for genomic region
50-
:param end: end position for genomic region
51-
:return: list of matching variant objects
52-
:raise AnyVarClientConnectionError: if connection is unsuccessful during search query
53-
"""
54-
5557
@abc.abstractmethod
5658
def close(self) -> None:
5759
"""Clean up AnyVar connection."""

src/anyvlm/anyvar/http_client.py

Lines changed: 92 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22

33
import logging
44
from collections.abc import Iterable, Sequence
5-
from http import HTTPStatus
5+
from http import HTTPMethod, HTTPStatus
66

77
import requests
88
from anyvar.utils.liftover_utils import ReferenceAssembly
9-
from anyvar.utils.types import VrsVariation
109
from ga4gh.vrs import VrsType, models
1110

1211
from anyvlm.anyvar.base_client import (
@@ -33,6 +32,91 @@ def __init__(
3332
self.hostname = hostname
3433
self.request_timeout = request_timeout
3534

35+
def _make_allele_expression_request(
36+
self, expression: str, assembly: ReferenceAssembly, method: HTTPMethod
37+
) -> requests.Response | None:
38+
"""Make a request to the AnyVar variation endpoint for an allele expression
39+
40+
:param expression: variation expression to translate
41+
:param assembly: reference assembly used in expression
42+
:param method: HTTP method to use for the request. Only POST and PUT are
43+
supported.
44+
POST is used for retrieving a registered variation, while PUT is used for
45+
registering a new variation.
46+
:raises ValueError: if unsupported HTTP method is provided
47+
:raise AnyVarClientConnectionError: if connection to AnyVar service is
48+
unsuccessful
49+
:raise AnyVarClientError: if HTTP request fails for other reasons
50+
:return: HTTP response object if request is successful, else `None`
51+
"""
52+
if method not in {HTTPMethod.POST, HTTPMethod.PUT}:
53+
msg = (
54+
f"HTTP method {method} is not supported for allele expression requests"
55+
)
56+
raise ValueError(msg)
57+
58+
url = f"{self.hostname}/variation"
59+
payload = {
60+
"definition": expression,
61+
"assembly_name": assembly.value,
62+
"input_type": VrsType.ALLELE.value,
63+
}
64+
try:
65+
response = requests.request(
66+
method=method,
67+
url=url,
68+
json=payload,
69+
timeout=self.request_timeout,
70+
)
71+
except requests.ConnectionError as e:
72+
_logger.exception(
73+
"Unable to establish connection using AnyVar configured at %s",
74+
self.hostname,
75+
)
76+
raise AnyVarClientConnectionError from e
77+
78+
try:
79+
response.raise_for_status()
80+
except requests.HTTPError as e:
81+
_logger.exception(
82+
"Encountered HTTP exception submitting payload %s to %s",
83+
payload,
84+
url,
85+
)
86+
if response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY:
87+
_logger.debug(
88+
"Translation failed for variant expression '%s'", expression
89+
)
90+
return None
91+
92+
if response.status_code == HTTPStatus.NOT_FOUND:
93+
_logger.debug("No variation found for expression '%s'", expression)
94+
return None
95+
96+
raise AnyVarClientError from e
97+
return response
98+
99+
def get_registered_allele(
100+
self, expression: str, assembly: ReferenceAssembly = ReferenceAssembly.GRCH38
101+
) -> models.Allele | None:
102+
"""Retrieve registered VRS Allele for given allele expression
103+
104+
Currently, only expressions supported by the VRS-Python translator are supported.
105+
This could change depending on the AnyVar implementation, though, and probably
106+
can't be validated on the AnyVLM side.
107+
108+
:param expression: variation expression to get VRS Allele for
109+
:param assembly: reference assembly used in expression
110+
:return: VRS Allele if translation succeeds and VRS Allele has already been registered, else `None`
111+
"""
112+
response = self._make_allele_expression_request(
113+
expression, assembly, HTTPMethod.POST
114+
)
115+
if not response:
116+
return None
117+
118+
return models.Allele(**response.json()["data"])
119+
36120
def put_allele_expressions(
37121
self,
38122
expressions: Iterable[str],
@@ -50,70 +134,17 @@ def put_allele_expressions(
50134
else `None`, for the i'th expression
51135
:raise AnyVarClientError: for unexpected errors relating to specifics of client interface
52136
"""
53-
results = []
54-
url = f"{self.hostname}/variation"
137+
results: list[str | None] = []
55138
for expression in expressions:
56-
payload = {
57-
"definition": expression,
58-
"assembly_name": assembly.value,
59-
"input_type": VrsType.ALLELE.value,
60-
}
61-
try:
62-
response = requests.put(
63-
url,
64-
json=payload,
65-
timeout=self.request_timeout,
66-
)
67-
except requests.ConnectionError as e:
68-
_logger.exception(
69-
"Unable to establish connection using AnyVar configured at %s",
70-
self.hostname,
71-
)
72-
raise AnyVarClientConnectionError from e
73-
try:
74-
response.raise_for_status()
75-
except requests.HTTPError as e:
76-
_logger.exception(
77-
"Encountered HTTP exception submitting payload %s to %s",
78-
payload,
79-
url,
80-
)
81-
if response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY:
82-
_logger.debug(
83-
"Translation failed for variant expression '%s'", expression
84-
)
85-
results.append(None)
86-
else:
87-
raise AnyVarClientError from e
139+
response = self._make_allele_expression_request(
140+
expression, assembly, HTTPMethod.PUT
141+
)
142+
if not response:
143+
results.append(None)
88144
else:
89145
results.append(response.json()["object_id"])
90146
return results
91147

92-
def search_by_interval(
93-
self, accession: str, start: int, end: int
94-
) -> list[VrsVariation]:
95-
"""Get all variation IDs located within the specified range
96-
97-
:param accession: sequence accession
98-
:param start: start position for genomic region
99-
:param end: end position for genomic region
100-
:return: list of matching variant objects
101-
:raise AnyVarClientError: if connection is unsuccessful during search query
102-
"""
103-
response = requests.get(
104-
f"{self.hostname}/search?accession={accession}&start={start}&end={end}",
105-
timeout=self.request_timeout,
106-
)
107-
try:
108-
response.raise_for_status()
109-
except requests.HTTPError as e:
110-
if response.json() == {
111-
"detail": "Unable to dereference provided accession ID"
112-
}:
113-
return []
114-
raise AnyVarClientError from e
115-
return [models.Allele(**v) for v in response.json()["variations"]]
116-
117148
def close(self) -> None:
118149
"""Clean up AnyVar connection.
119150

src/anyvlm/anyvar/python_client.py

Lines changed: 57 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
from anyvar.storage.base_storage import Storage
88
from anyvar.translate.translate import TranslationError, Translator
99
from anyvar.utils.liftover_utils import ReferenceAssembly
10-
from anyvar.utils.types import SupportedVariationType, VrsVariation
10+
from anyvar.utils.types import SupportedVariationType
1111
from ga4gh.vrs.dataproxy import DataProxyValidationError
12+
from ga4gh.vrs.models import Allele
1213

1314
from anyvlm.anyvar.base_client import BaseAnyVarClient
1415

@@ -26,6 +27,56 @@ def __init__(self, translator: Translator, storage: Storage) -> None:
2627
"""
2728
self.av = AnyVar(translator, storage)
2829

30+
def _translate_allele_expression(
31+
self, expression: str, assembly: ReferenceAssembly = ReferenceAssembly.GRCH38
32+
) -> Allele | None:
33+
"""Translate a single allele expression to a VRS Allele
34+
35+
Currently, only expressions supported by the VRS-Python translator are supported.
36+
This could change depending on the AnyVar implementation, though, and probably
37+
can't be validated on the AnyVLM side.
38+
39+
:param expression: variation expression to translate
40+
:param assembly: reference assembly used in expression
41+
:return: VRS Allele if translation succeeds, else `None`
42+
"""
43+
translated_variation = None
44+
try:
45+
translated_variation = self.av.translator.translate_variation(
46+
expression,
47+
assembly=assembly.value,
48+
input_type=SupportedVariationType.ALLELE, # type: ignore
49+
)
50+
except DataProxyValidationError:
51+
_logger.exception("Found invalid base in expression %s", expression)
52+
except TranslationError:
53+
_logger.exception("Failed to translate expression: %s", expression)
54+
return translated_variation # type: ignore
55+
56+
def get_registered_allele(
57+
self, expression: str, assembly: ReferenceAssembly = ReferenceAssembly.GRCH38
58+
) -> Allele | None:
59+
"""Retrieve registered VRS Allele for given allele expression
60+
61+
Currently, only expressions supported by the VRS-Python translator are supported.
62+
This could change depending on the AnyVar implementation, though, and probably
63+
can't be validated on the AnyVLM side.
64+
65+
:param expression: variation expression to get VRS Allele for
66+
:param assembly: reference assembly used in expression
67+
:return: VRS Allele if translation succeeds and VRS Allele has already been registered, else `None`
68+
"""
69+
translated_variation = self._translate_allele_expression(expression, assembly)
70+
if not translated_variation:
71+
return None
72+
73+
try:
74+
return self.av.get_object(translated_variation.id, Allele) # type: ignore
75+
except KeyError:
76+
_logger.exception(
77+
"VRS Allele with ID %s not found", translated_variation.id
78+
)
79+
2980
def put_allele_expressions(
3081
self,
3182
expressions: Iterable[str],
@@ -44,49 +95,16 @@ def put_allele_expressions(
4495
"""
4596
results = []
4697
for expression in expressions:
47-
translated_variation = None
48-
try:
49-
translated_variation = self.av.translator.translate_variation(
50-
expression,
51-
assembly=assembly.value,
52-
input_type=SupportedVariationType.ALLELE,
53-
)
54-
except DataProxyValidationError:
55-
_logger.exception("Found invalid base in expression %s", expression)
56-
except TranslationError:
57-
_logger.exception("Failed to translate expression: %s", expression)
98+
translated_variation = self._translate_allele_expression(
99+
expression, assembly
100+
)
58101
if translated_variation:
59-
self.av.put_objects([translated_variation]) # type: ignore
60-
results.append(translated_variation.id) # type: ignore
102+
self.av.put_objects([translated_variation])
103+
results.append(translated_variation.id)
61104
else:
62105
results.append(None)
63106
return results
64107

65-
def search_by_interval(
66-
self, accession: str, start: int, end: int
67-
) -> list[VrsVariation]:
68-
"""Get all variation IDs located within the specified range
69-
70-
:param accession: sequence accession
71-
:param start: start position for genomic region
72-
:param end: end position for genomic region
73-
:return: list of matching variant objects
74-
"""
75-
try:
76-
if accession.startswith("ga4gh:"):
77-
ga4gh_id = accession
78-
else:
79-
ga4gh_id = self.av.translator.get_sequence_id(accession)
80-
except KeyError:
81-
return []
82-
83-
alleles = []
84-
if ga4gh_id:
85-
refget_accession = ga4gh_id.split("ga4gh:")[-1]
86-
alleles = self.av.object_store.search_alleles(refget_accession, start, end)
87-
88-
return alleles # type: ignore[reportReturnType]
89-
90108
def close(self) -> None:
91109
"""Clean up AnyVar instance."""
92110
_logger.info("Closing AnyVar client.")
Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
"""Craft a VlmResponse object from a list of CohortAlleleFrequencyStudyResults"""
22

3-
from ga4gh.va_spec.base.core import CohortAlleleFrequencyStudyResult
4-
53
from anyvlm.schemas.vlm import (
64
VlmResponse,
75
)
6+
from anyvlm.utils.types import AnyVlmCohortAlleleFrequencyResult
87

98

109
def build_vlm_response_from_caf_data(
11-
caf_data: list[CohortAlleleFrequencyStudyResult],
10+
caf_data: list[AnyVlmCohortAlleleFrequencyResult],
1211
) -> VlmResponse:
1312
"""Craft a VlmResponse object from a list of CohortAlleleFrequencyStudyResults.
1413
15-
:param caf_data: A list of `CohortAlleleFrequencyStudyResult` objects that will be used to build the VlmResponse
14+
:param caf_data: A list of `AnyVlmCohortAlleleFrequencyResult` objects that will be used to build the VlmResponse
1615
:return: A `VlmResponse` object.
1716
"""
1817
raise NotImplementedError # TODO: Implement this during/after Issue #16

0 commit comments

Comments
 (0)