From cab5a1d6781a0735f8322a3d1dd1347687ef223e Mon Sep 17 00:00:00 2001 From: Talley Lambert Date: Thu, 12 Dec 2024 15:19:53 -0500 Subject: [PATCH 1/3] fix: fix null to list --- .pre-commit-config.yaml | 4 ++-- pyproject.toml | 2 +- src/fpbase/models.py | 38 +++++++++++++++++++++++++++++--------- tests/test_fpbase.py | 5 +++++ 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fc1fa5f..b0f11e0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,13 +14,13 @@ repos: - id: validate-pyproject - repo: https://github.com/crate-ci/typos - rev: typos-dict-v0.11.35 + rev: typos-dict-v0.11.37 hooks: - id: typos args: [--force-exclude] # omitting --write-changes - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.7.4 + rev: v0.8.3 hooks: - id: ruff args: [--fix] # may also add '--unsafe-fixes' diff --git a/pyproject.toml b/pyproject.toml index e6a7331..e09f10f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ classifiers = [ "Programming Language :: Python :: 3.13", "Typing :: Typed", ] -dependencies = ['pydantic', 'requests'] +dependencies = ['pydantic', 'requests', 'typing_extensions'] # https://peps.python.org/pep-0621/#dependencies-optional-dependencies [project.optional-dependencies] diff --git a/src/fpbase/models.py b/src/fpbase/models.py index 27fbf6c..622df17 100644 --- a/src/fpbase/models.py +++ b/src/fpbase/models.py @@ -1,13 +1,33 @@ """Main fetching logic.""" +from collections.abc import Sequence from enum import Enum -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Annotated, Any, Optional, TypeVar -from pydantic import BaseModel, Field, computed_field, model_validator +from pydantic import ( + BaseModel, + BeforeValidator, + Field, + computed_field, + model_validator, +) if TYPE_CHECKING: from collections.abc import Iterable + +def _null_to_list(v: Any) -> list: + if v is None: + return [] + elif isinstance(v, Sequence): + return list(v) + raise ValueError(f"Expected a list or None, got {v!r}") + + +T = TypeVar("T") +# type that accepts null as json input and returns an empty list +SafeList = Annotated[list[T], BeforeValidator(_null_to_list)] + __all__ = [ "Filter", "FilterPlacement", @@ -135,7 +155,7 @@ class State(BaseModel): exhex: str = "" ext_coeff: Optional[float] = Field(None, alias="extCoeff") # M^-1 cm^-1 qy: Optional[float] = None - spectra: list[Spectrum] = Field(default_factory=list) + spectra: SafeList[Spectrum] = Field(default_factory=list) lifetime: Optional[float] = None # ns @property @@ -158,7 +178,7 @@ class Fluorophore(BaseModel): name: str id: str default_state: Optional[State] = Field(None, alias="defaultState") - states: list[State] = Field(default_factory=list) + states: SafeList[State] = Field(default_factory=list) @model_validator(mode="before") @classmethod @@ -193,14 +213,14 @@ def url(self) -> str: class Protein(Fluorophore): seq: Optional[str] = None - pdb: list[str] = Field(default_factory=list) + pdb: SafeList[str] = Field(default_factory=list) genbank: Optional[str] = None uniprot: Optional[str] = None agg: Optional[Olig] = None switch_type: Optional[SwitchType] = Field(None, alias="switchType") primary_reference: Optional[Reference] = Field(None, alias="primaryReference") - references: list[Reference] = Field(default_factory=list) - states: list[State] = Field(default_factory=list) + references: SafeList[Reference] = Field(default_factory=list) + states: SafeList[State] = Field(default_factory=list) # default_state: Optional[State] = Field(None, alias="defaultState") @@ -216,7 +236,7 @@ class OpticalConfig(BaseModel): """A collection of filters and light sources.""" name: str - filters: list[FilterPlacement] + filters: SafeList[FilterPlacement] camera: Optional["Camera"] light: Optional["LightSource"] laser: Optional[int] @@ -227,7 +247,7 @@ class Microscope(BaseModel): id: str name: str - opticalConfigs: list[OpticalConfig] + opticalConfigs: SafeList[OpticalConfig] class _MicroscopePayload(BaseModel): diff --git a/tests/test_fpbase.py b/tests/test_fpbase.py index c76e63e..fde2c5f 100644 --- a/tests/test_fpbase.py +++ b/tests/test_fpbase.py @@ -90,3 +90,8 @@ def test_generic_gql_query() -> None: q = "query getProtein($id: String!){ protein(id: $id){ name } }" data = fpbase.graphql_query(q, {"id": "R9NL8"}) assert data["data"]["protein"]["name"] == "EGFP" + + +@pytest.mark.parametrize("name", ["Clover1.5", "6C", "dClover2 A206K"]) +def test_fluors_with_no_pdb(name: str) -> None: + fpbase.get_fluorophore(name) From 17055e134e446349884adb2a6bfee6b75eed101c Mon Sep 17 00:00:00 2001 From: Talley Lambert Date: Thu, 12 Dec 2024 15:22:37 -0500 Subject: [PATCH 2/3] pragma --- src/fpbase/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fpbase/models.py b/src/fpbase/models.py index 622df17..7967a9b 100644 --- a/src/fpbase/models.py +++ b/src/fpbase/models.py @@ -21,7 +21,7 @@ def _null_to_list(v: Any) -> list: return [] elif isinstance(v, Sequence): return list(v) - raise ValueError(f"Expected a list or None, got {v!r}") + raise ValueError(f"Expected a list or None, got {v!r}") # pragma: no cover T = TypeVar("T") From c8fd703a3332ea7ba4d4df4680c8496050b1d1ce Mon Sep 17 00:00:00 2001 From: Talley Lambert Date: Thu, 12 Dec 2024 15:23:08 -0500 Subject: [PATCH 3/3] remove typing ext --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e09f10f..e6a7331 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ classifiers = [ "Programming Language :: Python :: 3.13", "Typing :: Typed", ] -dependencies = ['pydantic', 'requests', 'typing_extensions'] +dependencies = ['pydantic', 'requests'] # https://peps.python.org/pep-0621/#dependencies-optional-dependencies [project.optional-dependencies]