Skip to content

Commit ebe0a52

Browse files
committed
Draft of Gene Ontology Controlled Keyword. Waiting for the final GO list.
The failed test is from test_hgvs_genes.py. Mypy errors are all from statistics.py.
1 parent 5153c34 commit ebe0a52

File tree

8 files changed

+138
-0
lines changed

8 files changed

+138
-0
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""Controlled keyword Gene Ontology
2+
3+
Revision ID: 18b6d81c045e
4+
Revises: c404b6719110
5+
Create Date: 2025-03-14 19:13:01.554046
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = '18b6d81c045e'
14+
down_revision = 'c404b6719110'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.add_column('controlled_keywords', sa.Column('accession', sa.String(), nullable=True))
22+
23+
# TODO: Will modify this part when we get the final GO terms.
24+
op.execute(
25+
"""INSERT INTO controlled_keywords (key, value, vocabulary, accession, special, description, creation_date, modification_date) VALUES ('Phenotypic Assay Mechanism', 'Other', NULL, NULL, False, 'The Gene Ontology (GO) is a structured, standardized representation of biological knowledge.', NOW(), NOW())"""
26+
)
27+
# ### end Alembic commands ###
28+
29+
30+
def downgrade():
31+
# ### commands auto generated by Alembic - please adjust! ###
32+
op.drop_column('controlled_keywords', 'accession')
33+
# ### end Alembic commands ###

src/mavedb/lib/validation/keywords.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
1+
import re
12
from typing import Optional
23

34
from mavedb.lib.validation.exceptions import ValidationError
45
from mavedb.lib.validation.utilities import is_null
56

67

8+
def validate_accession(key: str, value: str, accession: Optional[str]):
9+
if key.lower() == "phenotypic assay mechanism" and value.lower() != "other":
10+
# The Gene Ontology accession is a unique seven digit identifier prefixed by GO:.
11+
# e.g. GO:0005739, GO:1904659, or GO:0016597.
12+
if accession is None or not re.match(r"^GO:\d{7}$", accession):
13+
raise ValidationError("Invalid Gene Ontology accession.")
14+
15+
716
# TODO: value will not be Optional when we confirm the final controlled keyword list.
817
def validate_description(value: str, key: str, description: Optional[str]):
918
if value.lower() == "other" and (description is None or description.strip() == ""):

src/mavedb/models/controlled_keyword.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class ControlledKeyword(Base):
1212
key = Column(String, nullable=False)
1313
value = Column(String, nullable=False)
1414
vocabulary = Column(String, nullable=True)
15+
accession = Column(String, nullable=True)
1516
special = Column(Boolean, nullable=True)
1617
description = Column(String, nullable=True)
1718
creation_date = Column(Date, nullable=False, default=date.today)

src/mavedb/models/experiment.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ def keywords(self) -> list[dict]:
163163
"key": controlled_keyword.key,
164164
"value": controlled_keyword.value,
165165
"vocabulary": controlled_keyword.vocabulary,
166+
"accession": controlled_keyword.accession,
166167
"special": controlled_keyword.special,
167168
"description": controlled_keyword.description,
168169
},

src/mavedb/view_models/keyword.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class KeywordBase(BaseModel):
1717
key: str
1818
value: Optional[str]
1919
vocabulary: Optional[str]
20+
accession: Optional[str]
2021
special: Optional[bool]
2122
description: Optional[str]
2223

@@ -25,6 +26,13 @@ def validate_key(cls, v):
2526
keywords.validate_keyword(v)
2627
return v
2728

29+
@validator("accession")
30+
def validate_accession(cls, accession, values):
31+
key = values.get("key")
32+
value = values.get("value")
33+
keywords.validate_accession(key, value, accession)
34+
return accession
35+
2836
# validator("value") blocks creating a new experiment without controlled keywords so comment it first.
2937
# @validator("value")
3038
# def validate_value(cls, v):

tests/helpers/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@
162162
"description": "Description",
163163
},
164164
{"key": "Delivery method", "value": "Other", "special": False, "description": "Description"},
165+
{"key": "Phenotypic Assay Mechanism", "value": "Other", "accession": None, "special": False, "description": "Description"},
166+
{"key": "Phenotypic Assay Mechanism", "value": "Value", "accession": "GO:1234567", "special": False, "description": "Description"},
165167
]
166168

167169
TEST_KEYWORDS = [

tests/routers/test_experiments.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,70 @@ def test_cannot_create_experiment_that_keywords_has_wrong_combination4(client, s
270270
)
271271

272272

273+
def test_create_experiment_that_keyword_gene_ontology_has_valid_accession(client, setup_router_db):
274+
valid_keyword = {
275+
"keywords": [
276+
{
277+
"keyword": {
278+
"key": "Phenotypic Assay Mechanism",
279+
"value": "Value",
280+
"accession": "GO:1234567",
281+
"special": False,
282+
"description": "Description"},
283+
},
284+
],
285+
}
286+
experiment = {**TEST_MINIMAL_EXPERIMENT, **valid_keyword}
287+
response = client.post("/api/v1/experiments/", json=experiment)
288+
assert response.status_code == 200
289+
response_data = response.json()
290+
assert response_data["keywords"][0]["keyword"]["key"] == "Phenotypic Assay Mechanism"
291+
assert response_data["keywords"][0]["keyword"]["value"] == "Value"
292+
assert response_data["keywords"][0]["keyword"]["accession"] == "GO:1234567"
293+
294+
295+
def test_create_experiment_that_keyword_gene_ontology_is_other_without_accession(client, setup_router_db):
296+
valid_keyword = {
297+
"keywords": [
298+
{
299+
"keyword": {
300+
"key": "Phenotypic Assay Mechanism",
301+
"value": "Other",
302+
"accession": None,
303+
"description": "Description"
304+
},
305+
"description": "Description",
306+
},
307+
]
308+
}
309+
experiment = {**TEST_MINIMAL_EXPERIMENT, **valid_keyword}
310+
response = client.post("/api/v1/experiments/", json=experiment)
311+
assert response.status_code == 200
312+
response_data = response.json()
313+
assert response_data["keywords"][0]["keyword"]["key"] == "Phenotypic Assay Mechanism"
314+
assert response_data["keywords"][0]["keyword"]["value"] == "Other"
315+
316+
317+
def test_cannot_create_experiment_that_keyword_has_an_invalid_accession(client, setup_router_db):
318+
invalid_keyword = {
319+
"keywords": [
320+
{
321+
"keyword": {
322+
"key": "Phenotypic Assay Mechanism",
323+
"value": "Value",
324+
"accession": "invalid",
325+
"description": "Description"
326+
},
327+
},
328+
]
329+
}
330+
experiment = {**TEST_MINIMAL_EXPERIMENT, **invalid_keyword}
331+
response = client.post("/api/v1/experiments/", json=experiment)
332+
assert response.status_code == 422
333+
response_data = response.json()
334+
assert response_data["detail"][0]["msg"] == "Invalid Gene Ontology accession."
335+
336+
273337
def test_cannot_create_experiment_that_keyword_value_is_other_without_description(client, setup_router_db):
274338
"""
275339
Test src/mavedb/lib/validation/keywords.validate_description function

tests/validation/test_keywords.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from mavedb.lib.validation.exceptions import ValidationError
44
from mavedb.lib.validation.keywords import (
5+
validate_accession,
56
validate_description,
67
validate_duplicates,
78
validate_keyword,
@@ -39,6 +40,25 @@ def test_other_without_extra_description(self):
3940
with self.assertRaises(ValidationError):
4041
validate_description(value, key, description)
4142

43+
def test_Gene_Ontology_valid_accession(self):
44+
key = "Phenotypic Assay Mechanism"
45+
value = "value"
46+
accession = "GO:1234567"
47+
validate_accession(key, value, accession)
48+
49+
def test_Gene_Ontology_invalid_accession(self):
50+
key = "Phenotypic Assay Mechanism"
51+
value = "value"
52+
accession = "GO:123"
53+
with self.assertRaises(ValidationError):
54+
validate_accession(key, value, accession)
55+
56+
def test_Gene_Ontoloty_term_is_other(self):
57+
key = "Phenotypic Assay Mechanism"
58+
value = "Other"
59+
accession = None
60+
validate_accession(key, value, accession)
61+
4262
def test_duplicate_keys(self):
4363
# Invalid keywords list.
4464
keyword1 = {

0 commit comments

Comments
 (0)