Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b85bdff
use 'BaseSettings' instead of 'BaseModel' as parent class for 'VlmRes…
jennifer-bowser Dec 17, 2025
2d8260c
updates comments + descriptions for vlm schema classes
jennifer-bowser Dec 17, 2025
45d2086
remove unnecessary fields 'setType' and 'exists' from instantiation o…
jennifer-bowser Dec 17, 2025
453eafc
simplify logic for using env vars for values + preventing overrides
jennifer-bowser Dec 18, 2025
bc3e3b3
add '.env.example' file with GREGoR-specific valules required for 'Vl…
jennifer-bowser Dec 18, 2025
8a80567
use 'default_factory' instead of 'default' for attributes configured …
jennifer-bowser Dec 18, 2025
8d32e34
add required env vars to GitHub workflow
jennifer-bowser Dec 18, 2025
cf62b44
modify 'ResultSet' to prevent inadvertently overriding static fields
jennifer-bowser Dec 18, 2025
4d6c1fc
update comments for clairity
jennifer-bowser Dec 18, 2025
bdb6e53
Merge branch 'main' into issue-27-enable-config-of-node-specific-fiel…
jennifer-bowser Dec 19, 2025
d39ef0e
update arg name for 'ResultSet.id' > 'ResultSet.resultset_id'
jennifer-bowser Dec 19, 2025
a8ad18c
use Pydantic 'SettingsConfigDict' instead of custom func to set attri…
jennifer-bowser Dec 19, 2025
37fb5e5
moves logic to pull info from env vars out of 'VlmResponse' classes a…
jennifer-bowser Dec 22, 2025
0fa0571
fix tests
jennifer-bowser Dec 22, 2025
913bb7b
reformat description string to avoid wonky whitespace formatting
jennifer-bowser Dec 23, 2025
4ffaf89
refactor test to move fixture into the test where it's used
jennifer-bowser Dec 23, 2025
734b61f
Merge branch 'main' into issue-27-enable-config-of-node-specific-fiel…
jsstevenson Dec 23, 2025
f576b09
resolve merge conflicts
jennifer-bowser Dec 29, 2025
4e9d85d
resolve mere conflicts
jennifer-bowser Dec 30, 2025
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
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
###########################
## VLM RESPONSE SETTINGS ##
###########################
HANDOVER_TYPE_ID="GREGoR-NCH"
HANDOVER_TYPE_LABEL="GREGoR AnyVLM Reference"
BEACON_HANDOVER_URL="https://variants.gregorconsortium.org/"
BEACON_NODE_ID="org.anyvlm.gregor"
4 changes: 4 additions & 0 deletions .github/workflows/python-package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ jobs:
run: uv run pytest
env:
ANYVLM_ANYVAR_TEST_STORAGE_URI: postgresql://postgres:postgres@localhost:5432/postgres
HANDOVER_TYPE_ID: GREGoR-NCH
HANDOVER_TYPE_LABEL: "GREGoR AnyVLM Reference"
BEACON_HANDOVER_URL: https://variants.gregorconsortium.org/
BEACON_NODE_ID: org.anyvlm.gregor
lint:
name: lint
runs-on: ubuntu-latest
Expand Down
54 changes: 44 additions & 10 deletions src/anyvlm/schemas/vlm.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,62 @@
"""Schemas relating to VLM API."""

import os
from typing import ClassVar, Literal, Self

from pydantic import BaseModel, ConfigDict, Field, model_validator

from anyvlm.utils.types import Zygosity

# ruff: noqa: N815 (allows camelCase vars instead of snake_case to align with expected VLM protocol response)
# ruff: noqa: N815, N803, D107 (allow camelCase instead of snake_case to align with expected VLM protocol response + don't require init docstrings)

RESULT_ENTITY_TYPE = "genomicVariant"


class MissingEnvironmentVariableError(Exception):
"""Raised when a required environment variable is not set."""


def _get_environment_var(key: str) -> str:
value: str | None = os.environ.get(key)
if not value:
message = f"Missing required environment variable: {key}"
raise MissingEnvironmentVariableError(message)
return value


class HandoverType(BaseModel):
"""The type of handover the parent `BeaconHandover` represents."""

id: str = Field(
default="gregor", description="Node-specific identifier"
) # TODO: enable configuration of this field. See Issue #27.
default_factory=lambda: _get_environment_var("HANDOVER_TYPE_ID"),
description="Node-specific identifier",
)
label: str = Field(
default="GREGoR AnVIL browser", description="Node-specific label"
) # TODO: enable configuration of this field. See Issue #27.
default_factory=lambda: _get_environment_var("HANDOVER_TYPE_LABEL"),
description="Node-specific label",
)

# custom __init__ to prevent overriding attributes that are set via environment variables
def __init__(self) -> None:
super().__init__()


class BeaconHandover(BaseModel):
"""Describes how users can get more information about the results provided in the parent `VlmResponse`"""

handoverType: HandoverType = HandoverType()
handoverType: HandoverType = Field(default=HandoverType())
url: str = Field(
default="https://anvil.terra.bio/#workspaces?filter=GREGoR", # TODO: enable configuration of this field. See Issue #27.
description="A url which directs users to more detailed information about the results tabulated by the API (ideally human-readable)",
default_factory=lambda: _get_environment_var("BEACON_HANDOVER_URL"),
description="""
A url which directs users to more detailed information about the results tabulated by the API. Must be human-readable.
Ideally links directly to the variant specified in the query, but can be a generic search page if necessary.
""",
)

# custom __init__ to prevent overriding attributes that are static/set via environment variables
def __init__(self) -> None:
super().__init__()


class ReturnedSchema(BaseModel):
"""Fixed [Beacon Schema](https://github.com/ga4gh-beacon/beacon-v2/blob/c6558bf2e6494df3905f7b2df66e903dfe509500/framework/json/common/beaconCommonComponents.json#L241)"""
Expand All @@ -42,7 +68,7 @@ class ReturnedSchema(BaseModel):
schema_: str = Field(
default="ga4gh-beacon-variant-v2.0.0",
# Alias is required because 'schema' is reserved by Pydantic's BaseModel class,
# But VLM expects a field named 'schema'
# But VLM protocol expects a field named 'schema'
alias="schema",
)

Expand All @@ -57,7 +83,7 @@ class Meta(BaseModel):
description="The version of the VLM API that this response conforms to",
)
beaconId: str = Field(
default="org.gregor.beacon", # TODO: enable configuration of this field. See Issue #27.
default_factory=lambda: _get_environment_var("BEACON_NODE_ID"),
description="""
The Id of a Beacon. Usually a reversed domain string, but any URI is acceptable. The purpose of this attribute is,
in the context of a Beacon network, to disambiguate responses coming from different Beacons. See the beacon documentation
Expand All @@ -66,6 +92,10 @@ class Meta(BaseModel):
)
returnedSchemas: list[ReturnedSchema] = [ReturnedSchema()]

# custom __init__ to prevent overriding attributes that are static or set via environment variables
def __init__(self) -> None:
super().__init__()


class ResponseSummary(BaseModel):
"""A high-level summary of the results provided in the parent `VlmResponse"""
Expand Down Expand Up @@ -104,6 +134,10 @@ class ResultSet(BaseModel):
description=f"The type of entity relevant to these results. Must always be set to '{RESULT_ENTITY_TYPE}'",
)

# custom __init__ to prevent inadvertently overriding static fields
def __init__(self, resultset_id: str, resultsCount: int) -> None:
super().__init__(id=resultset_id, resultsCount=resultsCount)


class ResponseField(BaseModel):
"""A list of ResultSets"""
Expand Down
9 changes: 1 addition & 8 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,7 @@
from ga4gh.vrs import models
from pydantic import BaseModel


@pytest.fixture(scope="session", autouse=True)
def load_env():
"""Load `.env` file.

Must set `autouse=True` to run before other fixtures or test cases.
"""
load_dotenv()
load_dotenv()


@pytest.fixture(scope="session")
Expand Down
9 changes: 0 additions & 9 deletions tests/unit/test_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import pytest

from anyvlm.schemas.vlm import (
RESULT_ENTITY_TYPE,
HandoverType,
ResponseField,
ResponseSummary,
Expand All @@ -31,30 +30,24 @@ def responses_with_invalid_resultset_ids(valid_handover_id) -> list[ResponseFiel
ResponseField(
resultSets=[
ResultSet(
exists=True,
id=f"invalid_handover_id {Zygosity.HOMOZYGOUS}",
resultsCount=0,
setType=RESULT_ENTITY_TYPE,
)
]
),
ResponseField(
resultSets=[
ResultSet(
exists=True,
id=f"{valid_handover_id} invalid_zygosity",
resultsCount=0,
setType=RESULT_ENTITY_TYPE,
)
]
),
ResponseField(
resultSets=[
ResultSet(
exists=True,
id=f"{Zygosity.HOMOZYGOUS}-{valid_handover_id}", # incorrect order/formatting
resultsCount=0,
setType=RESULT_ENTITY_TYPE,
)
]
),
Expand All @@ -65,10 +58,8 @@ def test_valid_resultset_id(response_summary, valid_handover_id):
response = ResponseField(
resultSets=[
ResultSet(
exists=True,
id=f"{valid_handover_id} {Zygosity.HOMOZYGOUS}",
resultsCount=0,
setType=RESULT_ENTITY_TYPE,
)
]
)
Expand Down
Loading