Skip to content

Commit 1eac53a

Browse files
authored
Merge pull request #297 from VariantEffect/vrs-map-integration
VRS Map Integration
2 parents f0cf555 + d916711 commit 1eac53a

22 files changed

+1158
-51
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""merge 76e1e55bc5c1 and d7e6f8c3b9dc
2+
3+
Revision ID: 1d4933b4b6f7
4+
Revises: 76e1e55bc5c1, d7e6f8c3b9dc
5+
Create Date: 2024-09-04 16:17:20.875937
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = '1d4933b4b6f7'
14+
down_revision = ('76e1e55bc5c1', 'd7e6f8c3b9dc')
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
pass
21+
22+
23+
def downgrade():
24+
pass
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""scoreset_mapping_columns
2+
3+
Revision ID: d7e6f8c3b9dc
4+
Revises: f36cf612e029
5+
Create Date: 2024-08-28 09:54:08.249077
6+
7+
"""
8+
9+
from alembic import op
10+
from sqlalchemy.dialects import postgresql
11+
import sqlalchemy as sa
12+
13+
14+
# revision identifiers, used by Alembic.
15+
revision = "d7e6f8c3b9dc"
16+
down_revision = "f36cf612e029"
17+
branch_labels = None
18+
depends_on = None
19+
20+
21+
def upgrade():
22+
op.add_column(
23+
"scoresets",
24+
sa.Column(
25+
"mapping_state",
26+
sa.Enum(
27+
"incomplete",
28+
"processing",
29+
"failed",
30+
"complete",
31+
"pending_variant_processing",
32+
"not_attempted",
33+
"queued",
34+
name="mappingstate",
35+
native_enum=False,
36+
create_constraint=True,
37+
length=32
38+
),
39+
nullable=True,
40+
),
41+
)
42+
op.add_column("scoresets", sa.Column("mapping_errors", postgresql.JSONB, nullable=True))
43+
44+
45+
def downgrade():
46+
op.drop_constraint("mappingstate", table_name="scoresets")
47+
op.drop_column("scoresets", "mapping_state")
48+
op.drop_column("scoresets", "mapping_errors")
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
"""Additional mapping columns
2+
3+
Revision ID: f36cf612e029
4+
Revises: ec5d2787bec9
5+
Create Date: 2024-08-21 16:06:06.793541
6+
7+
"""
8+
9+
from alembic import op
10+
import sqlalchemy as sa
11+
from sqlalchemy.dialects import postgresql
12+
13+
from datetime import datetime
14+
15+
# revision identifiers, used by Alembic.
16+
revision = "f36cf612e029"
17+
down_revision = "ec5d2787bec9"
18+
branch_labels = None
19+
depends_on = None
20+
21+
22+
def upgrade():
23+
# ### commands auto generated by Alembic - please adjust! ###
24+
op.add_column(
25+
"mapped_variants",
26+
sa.Column("vrs_version", sa.String(), nullable=True, server_default="1.3"),
27+
)
28+
op.add_column("mapped_variants", sa.Column("error_message", sa.String(), nullable=True))
29+
op.add_column(
30+
"mapped_variants",
31+
sa.Column(
32+
"modification_date",
33+
sa.Date(),
34+
nullable=False,
35+
server_default = sa.func.current_date(),
36+
),
37+
)
38+
op.add_column(
39+
"mapped_variants",
40+
sa.Column(
41+
"mapped_date",
42+
sa.Date(),
43+
nullable=False,
44+
server_default=datetime.fromisocalendar(2024, 1, 1).date().strftime("%Y-%m-%d"),
45+
),
46+
)
47+
op.add_column(
48+
"mapped_variants",
49+
sa.Column("mapping_api_version", sa.String(), nullable=False, server_default="0.0.0"),
50+
)
51+
op.add_column(
52+
"mapped_variants",
53+
sa.Column("current", sa.Boolean(), nullable=False, server_default=sa.false()),
54+
)
55+
op.alter_column(
56+
"mapped_variants",
57+
"pre_mapped",
58+
existing_type=postgresql.JSONB(astext_type=sa.Text()),
59+
nullable=True,
60+
)
61+
op.alter_column(
62+
"mapped_variants",
63+
"post_mapped",
64+
existing_type=postgresql.JSONB(astext_type=sa.Text()),
65+
nullable=True,
66+
)
67+
68+
op.add_column(
69+
"target_genes",
70+
sa.Column("pre_mapped_metadata", postgresql.JSONB, nullable=True),
71+
)
72+
op.add_column(
73+
"target_genes",
74+
sa.Column("post_mapped_metadata", postgresql.JSONB, nullable=True),
75+
)
76+
# ### end Alembic commands ###
77+
78+
79+
def downgrade():
80+
# ### commands auto generated by Alembic - please adjust! ###
81+
op.alter_column(
82+
"mapped_variants",
83+
"post_mapped",
84+
existing_type=postgresql.JSONB(astext_type=sa.Text()),
85+
nullable=False,
86+
)
87+
op.alter_column(
88+
"mapped_variants",
89+
"pre_mapped",
90+
existing_type=postgresql.JSONB(astext_type=sa.Text()),
91+
nullable=False,
92+
)
93+
op.drop_column("mapped_variants", "current")
94+
op.drop_column("mapped_variants", "mapping_api_version")
95+
op.drop_column("mapped_variants", "mapped_date")
96+
op.drop_column("mapped_variants", "modification_date")
97+
op.drop_column("mapped_variants", "error_message")
98+
op.drop_column("mapped_variants", "vrs_version")
99+
100+
op.drop_column("target_genes", "pre_mapped_metadata")
101+
op.drop_column("target_genes", "post_mapped_metadata")
102+
# ### end Alembic commands ###

docker-compose-dev.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,24 @@ services:
6363
volumes:
6464
- mavedb-redis-dev:/data
6565

66+
dcd-mapping:
67+
image: dcd-mapping:dev
68+
command: bash -c "uvicorn api.server_main:app --host 0.0.0.0 --port 8000 --reload"
69+
depends_on:
70+
- db
71+
- seqrepo
72+
env_file:
73+
- settings/.env.dev
74+
ports:
75+
- "8004:8000"
76+
volumes:
77+
- mavedb-seqrepo-dev:/usr/local/share/seqrepo
78+
79+
seqrepo:
80+
image: biocommons/seqrepo:2021-01-29
81+
volumes:
82+
- mavedb-seqrepo-dev:/usr/local/share/seqrepo
83+
6684
# rabbitmq:
6785
# image: rabbitmq:3.8.3
6886
# ports:
@@ -71,3 +89,4 @@ services:
7189
volumes:
7290
mavedb-data-dev:
7391
mavedb-redis-dev:
92+
mavedb-seqrepo-dev:

settings/.env.template

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,30 @@ SLACK_WEBHOOK_URL=
3232
ORCID_CLIENT_ID=client-id
3333
ORCID_CLIENT_SECRET=secret
3434
ORCID_JWT_SIGNING_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\npublic key\n-----END PUBLIC KEY-----"
35+
36+
####################################################################################################
37+
# Environment variables for vrs-mapping
38+
####################################################################################################
39+
40+
GENE_NORM_DB_URL=postgres://postgres:postgres@db:5432/gene_normalizer
41+
MAVEDB_STORAGE_DIR=/root/.local/share/dcd-mapping
42+
43+
####################################################################################################
44+
# Environment variables for UTA connection via CoolSeqTool
45+
####################################################################################################
46+
47+
UTA_DB_URL=postgresql://anonymous:[email protected]:5432/uta/uta_20180821
48+
49+
####################################################################################################
50+
# Environment variables for seqrepo
51+
####################################################################################################
52+
53+
SEQREPO_ROOT_DIR=/usr/local/share/seqrepo/2021-01-29
54+
55+
####################################################################################################
56+
# Environment variables for mapping MaveDB connection
57+
####################################################################################################
58+
59+
MAVEDB_BASE_URL=http://app:8000
60+
MAVEDB_API_KEY=secret
61+
DCD_MAPPING_URL=http://dcd-mapping:8000
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,49 @@
1+
import os
2+
import requests
3+
from datetime import date
4+
from typing import Optional, TypedDict
5+
16
from cdot.hgvs.dataproviders import ChainedSeqFetcher, FastaSeqFetcher, RESTDataProvider
27

38
GENOMIC_FASTA_FILES = [
49
"/data/GCF_000001405.39_GRCh38.p13_genomic.fna.gz",
510
"/data/GCF_000001405.25_GRCh37.p13_genomic.fna.gz",
611
]
712

13+
DCD_MAP_URL = os.environ.get("DCD_MAPPING_URL", "http://dcd-mapping:8000")
14+
815

916
def seqfetcher() -> ChainedSeqFetcher:
1017
return ChainedSeqFetcher(*[FastaSeqFetcher(file) for file in GENOMIC_FASTA_FILES])
1118

1219

1320
def cdot_rest() -> RESTDataProvider:
1421
return RESTDataProvider(seqfetcher=seqfetcher())
22+
23+
24+
class VRSMap:
25+
url: str
26+
27+
class ScoreSetMappingResults(TypedDict):
28+
metadata: Optional[dict[str, str]]
29+
dcd_mapping_version: str
30+
mapped_date_utc: date
31+
computed_genomic_reference_sequence: Optional[dict[str, str]]
32+
mapped_genomic_reference_sequence: Optional[dict[str, str]]
33+
computed_protein_reference_sequence: Optional[dict[str, str]]
34+
mapped_protein_reference_sequence: Optional[dict[str, str]]
35+
mapped_scores: Optional[list[dict]]
36+
error_message: Optional[str]
37+
38+
def __init__(self, url: str) -> None:
39+
self.url = url
40+
41+
def map_score_set(self, score_set_urn: str) -> ScoreSetMappingResults:
42+
uri = f"{self.url}/api/v1/map/{score_set_urn}"
43+
response = requests.post(uri)
44+
response.raise_for_status()
45+
return response.json()
46+
47+
48+
def vrs_mapper(url: Optional[str] = None) -> VRSMap:
49+
return VRSMap(DCD_MAP_URL) if not url else VRSMap(url)

src/mavedb/lib/exceptions.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,19 @@ class NonexistentOrcidUserError(ValueError):
160160
"""Raised when a user tries to create a contributor with a non-existent ORCID ID"""
161161

162162
pass
163+
164+
165+
class NonexistentMappingResultsError(ValueError):
166+
"""Raised when score set mapping results do not contain mapping results"""
167+
168+
pass
169+
170+
171+
class NonexistentMappingReferenceError(ValueError):
172+
"""Raised when score set mapping results do not contain a valid reference sequence"""
173+
174+
pass
175+
176+
177+
class MappingEnqueueError(ValueError):
178+
"""Raised when a mapping job fails to be enqueued despite appearing as if it should have been"""

src/mavedb/lib/logging/canonical.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ async def log_job(ctx: dict) -> None:
2727

2828
if not result:
2929
logger.warning(msg=f"Job finished, but could not retrieve a job result for job {job_id}.", extra=log_context)
30-
log_context.pop("message")
3130
else:
3231
log_context = {
3332
**log_context,
@@ -55,10 +54,8 @@ async def log_job(ctx: dict) -> None:
5554

5655
if result is None:
5756
logger.error(msg="Job result could not be found.", extra=log_context)
58-
elif result.result == "success":
59-
logger.info(msg="Job completed successfully.", extra=log_context)
60-
elif result.result != "success":
61-
logger.warning(msg="Job completed with handled exception.", extra=log_context)
57+
elif result.result is not None:
58+
logger.info(msg="Job completed with result.", extra=log_context)
6259
else:
6360
logger.error(msg="Job completed with unhandled exception.", extra=log_context)
6461

src/mavedb/lib/permissions.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def has_permission(user_data: Optional[UserData], item: Base, action: Action) ->
9090
if user_may_edit or not private:
9191
return PermissionResponse(True)
9292
# Roles which may perform this operation.
93-
elif roles_permitted(active_roles, [UserRole.admin]):
93+
elif roles_permitted(active_roles, [UserRole.admin, UserRole.mapper]):
9494
return PermissionResponse(True)
9595
elif private:
9696
# Do not acknowledge the existence of a private entity.
@@ -142,7 +142,7 @@ def has_permission(user_data: Optional[UserData], item: Base, action: Action) ->
142142
if user_may_edit or not private:
143143
return PermissionResponse(True)
144144
# Roles which may perform this operation.
145-
elif roles_permitted(active_roles, [UserRole.admin]):
145+
elif roles_permitted(active_roles, [UserRole.admin, UserRole.mapper]):
146146
return PermissionResponse(True)
147147
elif private:
148148
# Do not acknowledge the existence of a private entity.
@@ -194,7 +194,7 @@ def has_permission(user_data: Optional[UserData], item: Base, action: Action) ->
194194
if user_may_edit or not private:
195195
return PermissionResponse(True)
196196
# Roles which may perform this operation.
197-
elif roles_permitted(active_roles, [UserRole.admin]):
197+
elif roles_permitted(active_roles, [UserRole.admin, UserRole.mapper]):
198198
return PermissionResponse(True)
199199
elif private:
200200
# Do not acknowledge the existence of a private entity.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import enum
2+
3+
4+
class MappingState(enum.Enum):
5+
incomplete = "incomplete"
6+
processing = "processing"
7+
failed = "failed"
8+
complete = "complete"
9+
pending_variant_processing = "pending_variant_processing"
10+
not_attempted = "not_attempted"
11+
queued = "queued"

0 commit comments

Comments
 (0)