Skip to content

Commit 453eafc

Browse files
simplify logic for using env vars for values + preventing overrides
1 parent 45d2086 commit 453eafc

File tree

2 files changed

+32
-54
lines changed

2 files changed

+32
-54
lines changed

src/anyvlm/schemas/vlm.py

Lines changed: 25 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
"""Schemas relating to VLM API."""
22

3-
from collections.abc import Callable
4-
from typing import Any, ClassVar, Literal, Self
3+
import os
4+
from typing import ClassVar, Literal, Self
55

6-
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
7-
from pydantic_settings import BaseSettings, SettingsConfigDict
6+
from pydantic import BaseModel, ConfigDict, Field, model_validator
87

98
from anyvlm.utils.types import Zygosity
109

@@ -13,59 +12,46 @@
1312
RESULT_ENTITY_TYPE = "genomicVariant"
1413

1514

16-
def forbid_env_override(field_name: str) -> Callable[..., Any]:
17-
"""Returns a Pydantic field validator that forbids explicitly
18-
passing a value for `field_name`. The value must come from env.
19-
"""
15+
class MissingEnvironmentVariableError(Exception):
16+
"""Raised when a required environment variable is not set."""
2017

21-
@field_validator(field_name, mode="before")
22-
@classmethod
23-
def _forbid_override(cls, value: Any) -> Any: # noqa: ARG001, ANN401, ANN001
24-
if value is not None:
25-
raise TypeError(f"{field_name} must be set via environment variable only")
26-
return value
2718

28-
return _forbid_override
19+
def _get_environment_var(key: str) -> str:
20+
value: str | None = os.environ.get(key)
21+
if not value:
22+
message = f"Missing required environment variable: {key}"
23+
raise MissingEnvironmentVariableError(message)
24+
return value
2925

3026

31-
class HandoverType(BaseSettings):
27+
class HandoverType(BaseModel):
3228
"""The type of handover the parent `BeaconHandover` represents."""
3329

34-
id: str = Field(..., description="Node-specific identifier")
35-
label: str = Field(..., description="Node-specific label")
36-
37-
model_config = SettingsConfigDict(env_prefix="HANDOVER_TYPE_", extra="forbid")
38-
39-
# These validators prevent instantiation of this class with values that would override `id` or `label`
40-
_forbid_id_override = forbid_env_override("id")
41-
_forbid_label_override = forbid_env_override("label")
30+
id: str = Field(
31+
_get_environment_var("HANDOVER_TYPE_ID"), description="Node-specific identifier"
32+
)
33+
label: str = Field(
34+
_get_environment_var("HANDOVER_TYPE_LABEL"), description="Node-specific label"
35+
)
4236

43-
# Allows `HandoverType` to be instantiated without providing values for the
44-
# any required fields, since both are pulled from environment variables instead
37+
# override __init__ to prevent the ability to override attributes that are set via environment variables
4538
def __init__(self) -> None:
4639
super().__init__()
4740

4841

49-
class BeaconHandover(BaseSettings):
42+
class BeaconHandover(BaseModel):
5043
"""Describes how users can get more information about the results provided in the parent `VlmResponse`"""
5144

5245
handoverType: HandoverType = Field(default=HandoverType())
5346
url: str = Field(
54-
...,
47+
_get_environment_var("BEACON_HANDOVER_URL"),
5548
description="""
5649
A url which directs users to more detailed information about the results tabulated by the API. Must be human-readable.
5750
Ideally links directly to the variant specified in the query, but can be a generic search page if necessary.
5851
""",
5952
)
6053

61-
model_config = SettingsConfigDict(env_prefix="BEACON_HANDOVER_", extra="forbid")
62-
63-
# These validators prevent instantiation of this class with values that would override `handoverType` or `url`
64-
_forbid_handoverType_override = forbid_env_override("handoverType")
65-
_forbid_url_override = forbid_env_override("url")
66-
67-
# Allows `BeaconHandover` to be instantiated without providing values
68-
# for any required fields, since both are generated statically
54+
# override __init__ to prevent the ability to override attributes that are set via environment variables
6955
def __init__(self) -> None:
7056
super().__init__()
7157

@@ -87,16 +73,15 @@ class ReturnedSchema(BaseModel):
8773
model_config = ConfigDict(populate_by_name=True)
8874

8975

90-
class Meta(BaseSettings):
76+
class Meta(BaseModel):
9177
"""Relevant metadata about the results provided in the parent `VlmResponse`"""
9278

9379
apiVersion: str = Field(
9480
default="v1.0",
9581
description="The version of the VLM API that this response conforms to",
9682
)
9783
beaconId: str = Field(
98-
...,
99-
alias="BEACON_NODE_ID",
84+
_get_environment_var("BEACON_NODE_ID"),
10085
description="""
10186
The Id of a Beacon. Usually a reversed domain string, but any URI is acceptable. The purpose of this attribute is,
10287
in the context of a Beacon network, to disambiguate responses coming from different Beacons. See the beacon documentation
@@ -105,15 +90,7 @@ class Meta(BaseSettings):
10590
)
10691
returnedSchemas: list[ReturnedSchema] = [ReturnedSchema()]
10792

108-
model_config = SettingsConfigDict(
109-
env_prefix="", populate_by_name=False, extra="forbid"
110-
)
111-
112-
# This validator prevents instantiation of this class with values that would override `handoverType` or `url`
113-
_forbid_beaconId_override = forbid_env_override("beaconId")
114-
115-
# Allows `Meta` to be instantiated without providing values
116-
# for any required fields, since all are generated statically
93+
# override __init__ to prevent the ability to override attributes that are set via environment variables
11794
def __init__(self) -> None:
11895
super().__init__()
11996

tests/conftest.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@
66
from ga4gh.vrs import models
77
from pydantic import BaseModel
88

9+
load_dotenv()
910

10-
@pytest.fixture(scope="session", autouse=True)
11-
def load_env():
12-
"""Load `.env` file.
11+
# @pytest.fixture(scope="session", autouse=True)
12+
# def load_env():
13+
# """Load `.env` file.
1314

14-
Must set `autouse=True` to run before other fixtures or test cases.
15-
"""
16-
load_dotenv()
15+
# Must set `autouse=True` to run before other fixtures or test cases.
16+
# """
17+
# print("LOADING DOTENV")
1718

1819

1920
@pytest.fixture(scope="session")

0 commit comments

Comments
 (0)