Skip to content

Commit

Permalink
Merge pull request #18 from TheJacksonLaboratory/task/add-api-respons…
Browse files Browse the repository at this point in the history
…es-schemas

Minor updates to some scehmas, adding api responses, adding sex enum
  • Loading branch information
bergsalex authored Apr 19, 2024
2 parents 8197301 + 9a35ccc commit 6095a82
Show file tree
Hide file tree
Showing 8 changed files with 256 additions and 2 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "geneweaver-core"
version = "0.9.0a8"
version = "0.9.0a9"
description = "The core of the Jax-Geneweaver Python library"
authors = ["Jax Computational Sciences <[email protected]>"]
readme = "README.md"
Expand Down
12 changes: 12 additions & 0 deletions src/geneweaver/core/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,18 @@ def _str_class() -> Enum:
return CurationAssignment


class Sex(Enum):
"""The Sex of an Animal."""

FEMALE = "Female"
MALE = "Male"
BOTH = "Both"

def __str__(self) -> str:
"""Render as a string."""
return self.value


class ScoreType(_StrToIntMixin, Enum):
"""Enum for the different types of geneset scores."""

Expand Down
1 change: 1 addition & 0 deletions src/geneweaver/core/schema/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Schema module for GeneWeaver."""

# ruff: noqa: F401
from .api_response import CollectionResponse, Paging, PagingLinks
from .gene import Gene, GeneValue
from .geneset import Geneset, GenesetGenes, GenesetUpload
from .group import Group, UserAdminGroup
Expand Down
31 changes: 31 additions & 0 deletions src/geneweaver/core/schema/api_response.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Schemas for API Responses."""

from typing import List, Optional

from pydantic import AnyUrl, BaseModel


class PagingLinks(BaseModel):
"""Schema for holding paging links."""

first: Optional[AnyUrl] = None
previous: Optional[AnyUrl] = None
next: Optional[AnyUrl] = None
last: Optional[AnyUrl] = None


class Paging(BaseModel):
"""Schema for paging information."""

page: Optional[int] = None
items: Optional[int] = None
total_pages: Optional[int] = None
total_items: Optional[int] = None
links: Optional[PagingLinks] = None


class CollectionResponse(BaseModel):
"""Schema for API responses with collections."""

data: List
paging: Optional[Paging] = None
7 changes: 7 additions & 0 deletions src/geneweaver/core/schema/score.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,10 @@ def __str__(self: "GenesetScoreType") -> str:
if self.threshold_low:
return f"{self.threshold_low} < {name} < {self.threshold}"
return f"{name} < {self.threshold}"

def threshold_as_db_string(self) -> str:
"""Return a string representation of the score type for the database."""
if self.threshold_low is not None:
return f"{self.threshold_low},{self.threshold}"
else:
return str(self.threshold)
10 changes: 9 additions & 1 deletion src/geneweaver/core/types.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
"""A module for common complex types used by Geneweaver."""

from pathlib import Path
from typing import Dict, Union
from typing import Dict, List, Union

from geneweaver.core.schema.gene import GeneValue

StringOrPath = Union[str, Path]

DictRow = Dict[str, Union[str, int]]

GeneIds = List[str]

GeneIdValues = List[GeneValue]

StrainIds = List[str]
146 changes: 146 additions & 0 deletions tests/unit/schema/test_api_response_schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
"""Test the api_response schemas."""

from typing import List

import pytest
from geneweaver.core.schema.api_response import CollectionResponse, Paging, PagingLinks
from geneweaver.core.schema.gene import Gene, GeneValue
from geneweaver.core.schema.geneset import BatchUpload, Geneset, GenesetTier
from geneweaver.core.schema.publication import Publication, PublicationInfo


def test_paging_links():
"""Test the PagingLinks schema."""
links = PagingLinks(
first="http://example.com/first",
previous="http://example.com/previous",
next="http://example.com/next",
last="http://example.com/last",
)
assert links.first == "http://example.com/first"
assert links.previous == "http://example.com/previous"
assert links.next == "http://example.com/next"
assert links.last == "http://example.com/last"


@pytest.mark.parametrize(
"paging_links_kwargs",
[
{"first": "not a link", "previous": "http://example.com/previous"},
{"first": "http://alink.com", "previous": "not a link"},
{
"first": "http://alink.com",
"previous": "http://example.com/previous",
"next": "not a link",
},
{
"first": "http://alink.com",
"previous": "http://alink.com",
"next": "http://example.com/next",
"last": "not a link",
},
],
)
def test_paging_links_error(paging_links_kwargs):
"""Test the PagingLinks schema in error cases."""
with pytest.raises(ValueError, match="validation error"):
PagingLinks(**paging_links_kwargs)


def test_paging():
"""Test the Paging schema in non-error cases."""
paging = Paging(
page=1,
items=10,
total_pages=5,
total_items=50,
links=PagingLinks(
first="http://example.com/first",
previous="http://example.com/previous",
next="http://example.com/next",
last="http://example.com/last",
),
)
assert paging.page == 1
assert paging.items == 10
assert paging.total_pages == 5
assert paging.total_items == 50
assert paging.links.first == "http://example.com/first"
assert paging.links.previous == "http://example.com/previous"
assert paging.links.next == "http://example.com/next"
assert paging.links.last == "http://example.com/last"


@pytest.mark.parametrize(
"paging_kwargs",
[
{"page": "not a number", "items": 10},
{"page": 1, "items": "not a number"},
{"page": 1, "items": 10, "total_pages": "not a number"},
{"page": 1, "items": 10, "total_pages": 5, "total_items": "not a number"},
{
"page": 1,
"items": 10,
"total_pages": 5,
"total_items": 50,
"links": "not a link",
},
],
)
def test_paging_errors(paging_kwargs):
"""Test the Paging schema in error cases."""
with pytest.raises(ValueError, match="validation error"):
Paging(**paging_kwargs)


def test_collection_response():
"""Test the CollectionResponse schema in non-error cases."""
collection_response = CollectionResponse(
data=[1, 2, 3],
paging=Paging(
page=1,
items=10,
total_pages=5,
total_items=50,
links=PagingLinks(
first="http://example.com/first",
previous="http://example.com/previous",
next="http://example.com/next",
last="http://example.com/last",
),
),
)

assert collection_response.data == [1, 2, 3]
assert collection_response.paging.page == 1
assert collection_response.paging.items == 10
assert collection_response.paging.total_pages == 5
assert collection_response.paging.total_items == 50
assert collection_response.paging.links.first == "http://example.com/first"
assert collection_response.paging.links.previous == "http://example.com/previous"
assert collection_response.paging.links.next == "http://example.com/next"
assert collection_response.paging.links.last == "http://example.com/last"


@pytest.mark.parametrize(
"data_class",
[GeneValue, Gene, BatchUpload, Geneset, GenesetTier, Publication, PublicationInfo],
)
def test_inherit_from_collection_response(data_class):
"""Test that we can inherit from the CollectionResponse class."""

class CollectionResponseSubclass(CollectionResponse):
data: List[data_class]

collection_response = CollectionResponseSubclass(
data=[],
paging=None,
)

assert collection_response is not None


def test_collection_response_error():
"""Test the CollectionResponse class in error cases."""
with pytest.raises(ValueError, match="validation error"):
CollectionResponse(data="not a list")
49 changes: 49 additions & 0 deletions tests/unit/test_sex_enum.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""Test the enum.Sex enum."""

import pytest
from geneweaver.core.enum import Sex


def test_sex_enum_as_string():
"""Test that the Sex enum renders to string as expected."""
assert str(Sex.FEMALE) == "Female"
assert str(Sex.MALE) == "Male"
assert str(Sex.BOTH) == "Both"


def test_initialize_sex_enum_from_string():
"""Test that we can initialize the Sex enum as expected."""
assert Sex("Female") == Sex.FEMALE
assert Sex("Male") == Sex.MALE
assert Sex("Both") == Sex.BOTH


@pytest.mark.parametrize(
"invalid_sex_value",
[
"test",
"another",
"CAPS",
"MALE",
"male",
"FEMALE",
"female",
"BOTH",
"both",
"neither",
"either",
"none",
"all",
"unknown",
"other",
"12345",
"1.2345",
"0",
"None",
],
)
def test_sex_enum_raises_error_when_initialized_with_invalid_value(invalid_sex_value):
"""Test that initializing the Sex enum with an invalid value raises an error."""
match_text = f"'{invalid_sex_value}' is not a valid Sex"
with pytest.raises(ValueError, match=match_text):
Sex(invalid_sex_value)

0 comments on commit 6095a82

Please sign in to comment.