Skip to content

Commit 0d3139a

Browse files
authored
feat!: update models to VRS 2.0.1 (#518)
close #515
1 parent 4956daa commit 0d3139a

9 files changed

Lines changed: 238 additions & 80 deletions

File tree

.gitmodules

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
[submodule "submodules/vrs"]
22
path = submodules/vrs
33
url = https://github.com/ga4gh/vrs.git
4-
branch = 2.0.0-snapshot.2025-02
4+
branch = 2.0

src/ga4gh/core/models.py

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -146,13 +146,17 @@ class Coding(Element, BaseModelForbidExtra):
146146
)
147147
system: str = Field(
148148
...,
149-
description="The terminology/code system that defined the code. May be reported as a free-text name (e.g. 'Sequence Ontology'), but it is preferable to provide a uri/url for the system. When the 'code' is reported as a CURIE, the 'system' should be reported as the uri that the CURIE's prefix expands to (e.g. 'http://purl.obofoundry.org/so.owl/' for the Sequence Ontology).",
149+
description="The terminology/code system that defined the code. May be reported as a free-text name (e.g. 'Sequence Ontology'), but it is preferable to provide a uri/url for the system.",
150150
)
151151
systemVersion: Optional[str] = Field( # noqa: N815
152152
None,
153153
description="Version of the terminology or code system that provided the code.",
154154
)
155155
code: code # Cannot use Field due to PydanticUserError: field name and type annotation must not clash.
156+
iris: Optional[list[iriReference]] = Field(
157+
None,
158+
description="A list of IRIs that are associated with the coding. This can be used to provide additional context or to link to additional information about the concept.",
159+
)
156160

157161

158162
class ConceptMapping(Element, BaseModelForbidExtra):
@@ -193,40 +197,30 @@ class Extension(Element, BaseModelForbidExtra):
193197

194198

195199
class MappableConcept(Element, BaseModelForbidExtra):
196-
"""A concept name that may be mapped to one or more `Codings`."""
200+
"""A concept based on a primaryCoding and/or name that may be mapped to one or more other `Codings`."""
197201

198202
conceptType: Optional[str] = Field( # noqa: N815
199203
None,
200204
description="A term indicating the type of concept being represented by the MappableConcept.",
201205
)
202206
name: Optional[str] = Field(None, description="A primary name for the concept.")
203-
primaryCode: Optional[code] = Field( # noqa: N815
207+
primaryCoding: Optional[Coding] = Field( # noqa: N815
204208
None,
205-
description="A primary code for the concept that is used to identify the concept in a terminology or code system. If there is a public code system for the primaryCode then it should also be specified in the mappings array with a relation of 'exactMatch'. This attribute is provided to both allow a more technical code to be used when a public Coding with a system is not available as well as when it is available but should be identified as the primary code.",
209+
description="A primary coding for the concept.",
206210
)
207211
mappings: Optional[list[ConceptMapping]] = Field(
208212
None,
209213
description="A list of mappings to concepts in terminologies or code systems. Each mapping should include a coding and a relation.",
210214
)
211215

212-
class ga4gh: # noqa: N801
213-
"""Contain properties used for computing digests"""
214-
215-
inherent: tuple[str] = ("primaryCode",)
216-
217216
@model_validator(mode="after")
218-
def require_name_or_primary_code(cls, v): # noqa: ANN001 N805 ANN201
219-
"""Ensure that ``name`` or ``primaryCode`` is provided"""
220-
if v.primaryCode is None and v.name is None:
221-
err_msg = "`One of name` or `primaryCode` must be provided."
217+
def require_name_or_primary_coding(cls, v): # noqa: ANN001 N805 ANN201
218+
"""Ensure that ``name`` or ``primaryCoding`` is provided"""
219+
if v.primaryCoding is None and v.name is None:
220+
err_msg = "One of `name` or `primaryCoding` must be provided."
222221
raise ValueError(err_msg)
223222
return v
224223

225-
def ga4gh_serialize(self) -> Optional[str]: # noqa: D102
226-
if self.primaryCode:
227-
return self.primaryCode.root
228-
return None
229-
230224

231225
Element.model_rebuild()
232226
Entity.model_rebuild()

src/ga4gh/vrs/extras/translator.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from abc import ABC
1111
from collections.abc import Mapping
1212

13-
from ga4gh.core import core_models, ga4gh_identify
13+
from ga4gh.core import ga4gh_identify
1414
from ga4gh.vrs import models, normalize
1515
from ga4gh.vrs.dataproxy import _DataProxy
1616
from ga4gh.vrs.extras.decorators import lazy_property
@@ -503,13 +503,13 @@ def _from_hgvs(
503503
copy_change = kwargs.get("copy_change")
504504
if not copy_change:
505505
copy_change = (
506-
models.CopyChange.EFO_0030067
506+
models.CopyChange.LOSS
507507
if sv_type == "del"
508-
else models.CopyChange.EFO_0030070
508+
else models.CopyChange.GAIN
509509
)
510510
cnv = models.CopyNumberChange(
511511
location=location,
512-
copyChange=core_models.MappableConcept(primaryCode=copy_change),
512+
copyChange=copy_change,
513513
)
514514

515515
return self._post_process_imported_cnv(cnv)

src/ga4gh/vrs/models.py

Lines changed: 15 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -196,16 +196,19 @@ class MoleculeType(str, Enum):
196196

197197

198198
class CopyChange(str, Enum):
199-
"""Define constraints for copy change"""
199+
"""MUST use one of the defined enumerations that are based on the corresponding EFO
200+
ontological terms for copy number variation. See Implementation Guidance for more
201+
details.
202+
"""
200203

201-
EFO_0030069 = "EFO:0030069"
202-
EFO_0020073 = "EFO:0020073"
203-
EFO_0030068 = "EFO:0030068"
204-
EFO_0030067 = "EFO:0030067"
205-
EFO_0030064 = "EFO:0030064"
206-
EFO_0030070 = "EFO:0030070"
207-
EFO_0030071 = "EFO:0030071"
208-
EFO_0030072 = "EFO:0030072"
204+
COMPLETE_GENOMIC_LOSS = "complete genomic loss"
205+
HIGH_LEVEL_LOSS = "high-level loss"
206+
LOW_LEVEL_LOSS = "low-level loss"
207+
LOSS = "loss"
208+
REGIONAL_BASE_PLOIDY = "regional base ploidy"
209+
GAIN = "gain"
210+
LOW_LEVEL_GAIN = "low-level gain"
211+
HIGH_LEVEL_GAIN = "high-level gain"
209212

210213

211214
class Syntax(str, Enum):
@@ -227,7 +230,7 @@ class Syntax(str, Enum):
227230
def _recurse_ga4gh_serialize(obj):
228231
if isinstance(obj, Ga4ghIdentifiableObject):
229232
return obj.get_or_create_digest()
230-
if isinstance(obj, (_ValueObject, MappableConcept)):
233+
if isinstance(obj, _ValueObject):
231234
return obj.ga4gh_serialize()
232235
if isinstance(obj, RootModel):
233236
return _recurse_ga4gh_serialize(obj.model_dump())
@@ -892,31 +895,11 @@ class CopyNumberChange(_VariationBase, BaseModelForbidExtra):
892895
...,
893896
description="The location of the subject of the copy change.",
894897
)
895-
copyChange: MappableConcept = Field(
898+
copyChange: CopyChange = Field(
896899
...,
897-
description='MUST use a `primaryCode` representing one of "EFO:0030069" (complete genomic loss), "EFO:0020073" (high-level loss), "EFO:0030068" (low-level loss), "EFO:0030067" (loss), "EFO:0030064" (regional base ploidy), "EFO:0030070" (gain), "EFO:0030071" (low-level gain), "EFO:0030072" (high-level gain).',
900+
description="MUST use one of the defined enumerations that are based on the corresponding EFO ontological terms for copy number variation. See Implementation Guidance for more details.",
898901
)
899902

900-
@field_validator("copyChange", mode="after")
901-
def validate_copy_change(cls, v) -> MappableConcept:
902-
"""Validate that copyChange.primaryCode is an EFO code
903-
904-
:raises ValueError: If `primaryCode` is not provided or if its not a valid
905-
EFO code
906-
:return: Copy change represented as mappable concept
907-
"""
908-
if v.primaryCode is None:
909-
err_msg = "`primaryCode` is required."
910-
raise ValueError(err_msg)
911-
912-
try:
913-
CopyChange(v.primaryCode.root)
914-
except ValueError:
915-
err_msg = f"`primaryCode` must be one of: {[v.value for v in CopyChange.__members__.values()]}."
916-
raise ValueError(err_msg)
917-
918-
return v
919-
920903
class ga4gh(Ga4ghIdentifiableObject.ga4gh):
921904
prefix = "CX"
922905
inherent = ["copyChange", "location", "type"]
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
interactions:
2+
- request:
3+
body: null
4+
headers:
5+
Accept:
6+
- '*/*'
7+
Accept-Encoding:
8+
- gzip, deflate
9+
Connection:
10+
- keep-alive
11+
User-Agent:
12+
- python-requests/2.32.3
13+
method: GET
14+
uri: http://localhost:5000/seqrepo/1/metadata/refseq:NC_000013.11
15+
response:
16+
body:
17+
string: "{\n \"added\": \"2016-08-27T23:50:14Z\",\n \"aliases\": [\n \"GRCh38:13\",\n
18+
\ \"GRCh38:chr13\",\n \"GRCh38.p1:13\",\n \"GRCh38.p1:chr13\",\n \"GRCh38.p10:13\",\n
19+
\ \"GRCh38.p10:chr13\",\n \"GRCh38.p11:13\",\n \"GRCh38.p11:chr13\",\n
20+
\ \"GRCh38.p12:13\",\n \"GRCh38.p12:chr13\",\n \"GRCh38.p2:13\",\n
21+
\ \"GRCh38.p2:chr13\",\n \"GRCh38.p3:13\",\n \"GRCh38.p3:chr13\",\n
22+
\ \"GRCh38.p4:13\",\n \"GRCh38.p4:chr13\",\n \"GRCh38.p5:13\",\n \"GRCh38.p5:chr13\",\n
23+
\ \"GRCh38.p6:13\",\n \"GRCh38.p6:chr13\",\n \"GRCh38.p7:13\",\n \"GRCh38.p7:chr13\",\n
24+
\ \"GRCh38.p8:13\",\n \"GRCh38.p8:chr13\",\n \"GRCh38.p9:13\",\n \"GRCh38.p9:chr13\",\n
25+
\ \"MD5:a5437debe2ef9c9ef8f3ea2874ae1d82\",\n \"NCBI:NC_000013.11\",\n
26+
\ \"refseq:NC_000013.11\",\n \"SEGUID:2oDBty0yKV9wHo7gg+Bt+fPgi5o\",\n
27+
\ \"SHA1:da80c1b72d32295f701e8ee083e06df9f3e08b9a\",\n \"VMC:GS__0wi-qoDrvram155UmcSC-zA5ZK4fpLT\",\n
28+
\ \"sha512t24u:_0wi-qoDrvram155UmcSC-zA5ZK4fpLT\",\n \"ga4gh:SQ._0wi-qoDrvram155UmcSC-zA5ZK4fpLT\"\n
29+
\ ],\n \"alphabet\": \"ACGKNTY\",\n \"length\": 114364328\n}\n"
30+
headers:
31+
Connection:
32+
- close
33+
Content-Length:
34+
- '1002'
35+
Content-Type:
36+
- application/json
37+
Date:
38+
- Wed, 19 Mar 2025 20:51:18 GMT
39+
Server:
40+
- Werkzeug/2.2.3 Python/3.10.12
41+
status:
42+
code: 200
43+
message: OK
44+
version: 1
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
interactions:
2+
- request:
3+
body: null
4+
headers:
5+
Accept:
6+
- '*/*'
7+
Accept-Encoding:
8+
- gzip, deflate
9+
Connection:
10+
- keep-alive
11+
User-Agent:
12+
- python-requests/2.32.3
13+
method: GET
14+
uri: http://localhost:5000/seqrepo/1/metadata/refseq:NC_000019.10
15+
response:
16+
body:
17+
string: "{\n \"added\": \"2016-08-24T08:19:02Z\",\n \"aliases\": [\n \"Ensembl:19\",\n
18+
\ \"ensembl:19\",\n \"GRCh38:19\",\n \"GRCh38:chr19\",\n \"GRCh38.p1:19\",\n
19+
\ \"GRCh38.p1:chr19\",\n \"GRCh38.p10:19\",\n \"GRCh38.p10:chr19\",\n
20+
\ \"GRCh38.p11:19\",\n \"GRCh38.p11:chr19\",\n \"GRCh38.p12:19\",\n
21+
\ \"GRCh38.p12:chr19\",\n \"GRCh38.p2:19\",\n \"GRCh38.p2:chr19\",\n
22+
\ \"GRCh38.p3:19\",\n \"GRCh38.p3:chr19\",\n \"GRCh38.p4:19\",\n \"GRCh38.p4:chr19\",\n
23+
\ \"GRCh38.p5:19\",\n \"GRCh38.p5:chr19\",\n \"GRCh38.p6:19\",\n \"GRCh38.p6:chr19\",\n
24+
\ \"GRCh38.p7:19\",\n \"GRCh38.p7:chr19\",\n \"GRCh38.p8:19\",\n \"GRCh38.p8:chr19\",\n
25+
\ \"GRCh38.p9:19\",\n \"GRCh38.p9:chr19\",\n \"MD5:b0eba2c7bb5c953d1e06a508b5e487de\",\n
26+
\ \"NCBI:NC_000019.10\",\n \"refseq:NC_000019.10\",\n \"SEGUID:AHxM5/L8jIX08UhBBkKXkiO5rhY\",\n
27+
\ \"SHA1:007c4ce7f2fc8c85f4f148410642979223b9ae16\",\n \"VMC:GS_IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl\",\n
28+
\ \"sha512t24u:IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl\",\n \"ga4gh:SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl\"\n
29+
\ ],\n \"alphabet\": \"ACGNT\",\n \"length\": 58617616\n}\n"
30+
headers:
31+
Connection:
32+
- close
33+
Content-Length:
34+
- '1035'
35+
Content-Type:
36+
- application/json
37+
Date:
38+
- Tue, 18 Mar 2025 14:14:55 GMT
39+
Server:
40+
- Werkzeug/2.2.3 Python/3.10.12
41+
status:
42+
code: 200
43+
message: OK
44+
- request:
45+
body: null
46+
headers:
47+
Accept:
48+
- '*/*'
49+
Accept-Encoding:
50+
- gzip, deflate
51+
Connection:
52+
- keep-alive
53+
User-Agent:
54+
- python-requests/2.32.3
55+
method: GET
56+
uri: http://localhost:5000/seqrepo/1/metadata/refseq:NC_012920.1
57+
response:
58+
body:
59+
string: "{\n \"added\": \"2016-08-24T06:13:07Z\",\n \"aliases\": [\n \"Ensembl:MT\",\n
60+
\ \"ensembl:MT\",\n \"GRCh37.p10:MT\",\n \"GRCh37.p10:chrM\",\n \"GRCh37.p11:MT\",\n
61+
\ \"GRCh37.p11:chrM\",\n \"GRCh37.p12:MT\",\n \"GRCh37.p12:chrM\",\n
62+
\ \"GRCh37.p13:MT\",\n \"GRCh37.p13:chrM\",\n \"GRCh37.p2:MT\",\n
63+
\ \"GRCh37.p2:chrM\",\n \"GRCh37.p5:MT\",\n \"GRCh37.p5:chrM\",\n
64+
\ \"GRCh37.p9:MT\",\n \"GRCh37.p9:chrM\",\n \"GRCh38:MT\",\n \"GRCh38:chrM\",\n
65+
\ \"GRCh38.p1:MT\",\n \"GRCh38.p1:chrM\",\n \"GRCh38.p10:MT\",\n \"GRCh38.p10:chrM\",\n
66+
\ \"GRCh38.p11:MT\",\n \"GRCh38.p11:chrM\",\n \"GRCh38.p12:MT\",\n
67+
\ \"GRCh38.p12:chrM\",\n \"GRCh38.p2:MT\",\n \"GRCh38.p2:chrM\",\n
68+
\ \"GRCh38.p3:MT\",\n \"GRCh38.p3:chrM\",\n \"GRCh38.p4:MT\",\n \"GRCh38.p4:chrM\",\n
69+
\ \"GRCh38.p5:MT\",\n \"GRCh38.p5:chrM\",\n \"GRCh38.p6:MT\",\n \"GRCh38.p6:chrM\",\n
70+
\ \"GRCh38.p7:MT\",\n \"GRCh38.p7:chrM\",\n \"GRCh38.p8:MT\",\n \"GRCh38.p8:chrM\",\n
71+
\ \"GRCh38.p9:MT\",\n \"GRCh38.p9:chrM\",\n \"MD5:c68f52674c9fb33aef52dcf399755519\",\n
72+
\ \"NCBI:NC_012920.1\",\n \"refseq:NC_012920.1\",\n \"SEGUID:eQNFYXnsCzhp/MkfBUBVnuFZzTA\",\n
73+
\ \"SHA1:7903456179ec0b3869fcc91f0540559ee159cd30\",\n \"VMC:GS_k3grVkjY-hoWcCUojHw6VU6GE3MZ8Sct\",\n
74+
\ \"sha512t24u:k3grVkjY-hoWcCUojHw6VU6GE3MZ8Sct\",\n \"ga4gh:SQ.k3grVkjY-hoWcCUojHw6VU6GE3MZ8Sct\",\n
75+
\ \"hs37-1kg:MT\",\n \"hs37d5:MT\"\n ],\n \"alphabet\": \"ACGNT\",\n
76+
\ \"length\": 16569\n}\n"
77+
headers:
78+
Connection:
79+
- close
80+
Content-Length:
81+
- '1355'
82+
Content-Type:
83+
- application/json
84+
Date:
85+
- Tue, 18 Mar 2025 14:14:55 GMT
86+
Server:
87+
- Werkzeug/2.2.3 Python/3.10.12
88+
status:
89+
code: 200
90+
message: OK
91+
- request:
92+
body: null
93+
headers:
94+
Accept:
95+
- '*/*'
96+
Accept-Encoding:
97+
- gzip, deflate
98+
Connection:
99+
- keep-alive
100+
User-Agent:
101+
- python-requests/2.32.3
102+
method: GET
103+
uri: http://localhost:5000/seqrepo/1/metadata/refseq:NC_000013.11
104+
response:
105+
body:
106+
string: "{\n \"added\": \"2016-08-27T23:50:14Z\",\n \"aliases\": [\n \"GRCh38:13\",\n
107+
\ \"GRCh38:chr13\",\n \"GRCh38.p1:13\",\n \"GRCh38.p1:chr13\",\n \"GRCh38.p10:13\",\n
108+
\ \"GRCh38.p10:chr13\",\n \"GRCh38.p11:13\",\n \"GRCh38.p11:chr13\",\n
109+
\ \"GRCh38.p12:13\",\n \"GRCh38.p12:chr13\",\n \"GRCh38.p2:13\",\n
110+
\ \"GRCh38.p2:chr13\",\n \"GRCh38.p3:13\",\n \"GRCh38.p3:chr13\",\n
111+
\ \"GRCh38.p4:13\",\n \"GRCh38.p4:chr13\",\n \"GRCh38.p5:13\",\n \"GRCh38.p5:chr13\",\n
112+
\ \"GRCh38.p6:13\",\n \"GRCh38.p6:chr13\",\n \"GRCh38.p7:13\",\n \"GRCh38.p7:chr13\",\n
113+
\ \"GRCh38.p8:13\",\n \"GRCh38.p8:chr13\",\n \"GRCh38.p9:13\",\n \"GRCh38.p9:chr13\",\n
114+
\ \"MD5:a5437debe2ef9c9ef8f3ea2874ae1d82\",\n \"NCBI:NC_000013.11\",\n
115+
\ \"refseq:NC_000013.11\",\n \"SEGUID:2oDBty0yKV9wHo7gg+Bt+fPgi5o\",\n
116+
\ \"SHA1:da80c1b72d32295f701e8ee083e06df9f3e08b9a\",\n \"VMC:GS__0wi-qoDrvram155UmcSC-zA5ZK4fpLT\",\n
117+
\ \"sha512t24u:_0wi-qoDrvram155UmcSC-zA5ZK4fpLT\",\n \"ga4gh:SQ._0wi-qoDrvram155UmcSC-zA5ZK4fpLT\"\n
118+
\ ],\n \"alphabet\": \"ACGKNTY\",\n \"length\": 114364328\n}\n"
119+
headers:
120+
Connection:
121+
- close
122+
Content-Length:
123+
- '1002'
124+
Content-Type:
125+
- application/json
126+
Date:
127+
- Tue, 18 Mar 2025 14:14:55 GMT
128+
Server:
129+
- Werkzeug/2.2.3 Python/3.10.12
130+
status:
131+
code: 200
132+
message: OK
133+
version: 1

0 commit comments

Comments
 (0)