Skip to content

Commit 3208b07

Browse files
deeenesclaude
andcommitted
Add unit tests and update lint config
26 tests covering inventory parsing, query validation, and response conversion across all three DataFrame backends and sample Parquet files. Removes placeholder test. Adds ANN401 ignore (Any returns are intentional) and ANN ignore for tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2417171 commit 3208b07

6 files changed

Lines changed: 398 additions & 9 deletions

File tree

pyproject.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,9 @@ ignore = [
139139
"E303", # Name: too-many-blank-lines
140140
"E501", # Name: line too long
141141
"E731", # Name: lambda-assignment
142-
"E741" # allow I, O, l as variable names -> I is the identity matrix
142+
"E741", # allow I, O, l as variable names -> I is the identity matrix
143+
# flake8-annotations (ANN)
144+
"ANN401", # Any is needed for multi-backend DataFrame returns
143145
]
144146
select = [
145147
"B", # flake8-bugbear
@@ -185,7 +187,7 @@ section-order = [
185187
"*/__init__.py" = ["D104", "F401"]
186188
"docs/*" = ["I"]
187189
"docs/src/conf.py" = ["D100"]
188-
"tests/*" = ["D"]
190+
"tests/*" = ["D", "ANN"]
189191
"tests/conftest.py" = ["D101", "D102", "D103", "E402"]
190192

191193
[tool.ruff.lint.pydocstyle]

tests/conftest.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""
2+
Shared test fixtures for omnipath-client tests.
3+
"""
4+
5+
from pathlib import Path
6+
7+
import pytest
8+
9+
10+
SAMPLES_DIR = Path(__file__).parent.parent / 'parquet-samples'
11+
12+
13+
@pytest.fixture
14+
def entities_parquet():
15+
"""
16+
Path to the sample entities Parquet file.
17+
"""
18+
19+
return SAMPLES_DIR / 'search_entities.parquet'
20+
21+
22+
@pytest.fixture
23+
def interactions_parquet():
24+
"""
25+
Path to the sample interactions Parquet file.
26+
"""
27+
28+
return SAMPLES_DIR / 'search_interactions.parquet'
29+
30+
31+
@pytest.fixture
32+
def associations_parquet():
33+
"""
34+
Path to the sample associations Parquet file.
35+
"""
36+
37+
return SAMPLES_DIR / 'search_associations.parquet'

tests/test_inventory.py

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
"""
2+
Tests for API inventory loading and parsing.
3+
"""
4+
5+
import json
6+
from pathlib import Path
7+
8+
from omnipath_client._endpoints import ParamDef, EndpointDef
9+
from omnipath_client._inventory import (
10+
Inventory,
11+
parse_openapi,
12+
_build_static_fallback,
13+
)
14+
15+
16+
OPENAPI_PATH = Path(__file__).parent.parent / 'planning' / 'openapi.json'
17+
18+
19+
class TestStaticFallback:
20+
"""
21+
Tests for the static fallback inventory.
22+
"""
23+
24+
def test_builds_endpoints(self):
25+
26+
endpoints = _build_static_fallback()
27+
28+
assert len(endpoints) > 0
29+
assert 'exports/entities/parquet' in endpoints
30+
assert 'exports/interactions/parquet' in endpoints
31+
assert 'exports/associations/parquet' in endpoints
32+
33+
def test_entity_filters(self):
34+
35+
endpoints = _build_static_fallback()
36+
ep = endpoints['exports/entities/parquet']
37+
38+
assert isinstance(ep, EndpointDef)
39+
assert ep.method == 'POST'
40+
assert ep.response_format == 'parquet'
41+
assert 'taxonomy_ids' in ep.params
42+
assert 'entity_types' in ep.params
43+
44+
def test_interaction_filters(self):
45+
46+
endpoints = _build_static_fallback()
47+
ep = endpoints['exports/interactions/parquet']
48+
49+
assert 'direction' in ep.params
50+
pdef = ep.params['direction']
51+
assert pdef.allowed_values == [
52+
'any',
53+
'directed',
54+
'undirected',
55+
]
56+
57+
58+
class TestParseOpenapi:
59+
"""
60+
Tests for OpenAPI spec parsing.
61+
"""
62+
63+
def test_parse_local_spec(self):
64+
65+
if not OPENAPI_PATH.exists():
66+
return
67+
68+
with open(OPENAPI_PATH) as f:
69+
spec = json.load(f)
70+
71+
endpoints = parse_openapi(spec)
72+
73+
assert len(endpoints) > 0
74+
assert 'exports/entities/parquet' in endpoints
75+
assert 'exports/interactions/parquet' in endpoints
76+
assert 'exports/associations/parquet' in endpoints
77+
78+
def test_export_endpoints_have_filters(self):
79+
80+
if not OPENAPI_PATH.exists():
81+
return
82+
83+
with open(OPENAPI_PATH) as f:
84+
spec = json.load(f)
85+
86+
endpoints = parse_openapi(spec)
87+
ep = endpoints['exports/interactions/parquet']
88+
89+
assert ep.method == 'POST'
90+
assert ep.response_format == 'parquet'
91+
assert 'entity_ids' in ep.params
92+
assert 'direction' in ep.params
93+
94+
direction = ep.params['direction']
95+
assert direction.allowed_values == [
96+
'any',
97+
'directed',
98+
'undirected',
99+
]
100+
101+
def test_ontology_endpoints(self):
102+
103+
if not OPENAPI_PATH.exists():
104+
return
105+
106+
with open(OPENAPI_PATH) as f:
107+
spec = json.load(f)
108+
109+
endpoints = parse_openapi(spec)
110+
111+
assert 'terms' in endpoints
112+
assert 'tree' in endpoints
113+
assert 'ontologies' in endpoints
114+
115+
116+
class TestInventory:
117+
"""
118+
Tests for the Inventory class.
119+
"""
120+
121+
def test_fallback_on_bad_url(self):
122+
123+
inv = Inventory(base_url='http://localhost:99999')
124+
inv.load()
125+
126+
assert len(inv.endpoints) > 0
127+
assert 'exports/entities/parquet' in inv.endpoints
128+
129+
def test_params_method(self):
130+
131+
inv = Inventory(base_url='http://localhost:99999')
132+
inv.load()
133+
134+
params = inv.params('exports/interactions/parquet')
135+
136+
assert 'entity_ids' in params
137+
assert isinstance(params['entity_ids'], ParamDef)
138+
139+
def test_allowed_values_method(self):
140+
141+
inv = Inventory(base_url='http://localhost:99999')
142+
inv.load()
143+
144+
values = inv.allowed_values(
145+
'exports/interactions/parquet',
146+
'direction',
147+
)
148+
149+
assert values == ['any', 'directed', 'undirected']
150+
151+
def test_allowed_values_unconstrained(self):
152+
153+
inv = Inventory(base_url='http://localhost:99999')
154+
inv.load()
155+
156+
values = inv.allowed_values(
157+
'exports/entities/parquet',
158+
'taxonomy_ids',
159+
)
160+
161+
assert values is None

tests/test_placeholder.py

Lines changed: 0 additions & 7 deletions
This file was deleted.

tests/test_query.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
"""
2+
Tests for query building and validation.
3+
"""
4+
5+
import pytest
6+
7+
from omnipath_client._query import QueryBuilder
8+
from omnipath_client._errors import (
9+
UnknownEndpointError,
10+
UnknownParameterError,
11+
InvalidParameterValueError,
12+
)
13+
from omnipath_client._inventory import Inventory
14+
15+
16+
@pytest.fixture
17+
def query_builder():
18+
"""
19+
A QueryBuilder with static fallback inventory.
20+
"""
21+
22+
inv = Inventory(base_url='http://localhost:99999')
23+
inv.load()
24+
return QueryBuilder(inv)
25+
26+
27+
class TestQueryBuilder:
28+
"""
29+
Tests for QueryBuilder.build().
30+
"""
31+
32+
def test_valid_entity_query(self, query_builder):
33+
34+
query = query_builder.build(
35+
'exports/entities/parquet',
36+
taxonomy_ids=['9606'],
37+
)
38+
39+
assert query.endpoint.path == '/exports/entities/parquet'
40+
assert query.params == {'taxonomy_ids': ['9606']}
41+
42+
def test_valid_interaction_query(self, query_builder):
43+
44+
query = query_builder.build(
45+
'exports/interactions/parquet',
46+
entity_ids=['Q9Y6K9'],
47+
direction='directed',
48+
)
49+
50+
assert query.params['entity_ids'] == ['Q9Y6K9']
51+
assert query.params['direction'] == 'directed'
52+
53+
def test_unknown_endpoint(self, query_builder):
54+
55+
with pytest.raises(UnknownEndpointError):
56+
query_builder.build('nonexistent/endpoint')
57+
58+
def test_unknown_parameter(self, query_builder):
59+
60+
with pytest.raises(UnknownParameterError):
61+
query_builder.build(
62+
'exports/entities/parquet',
63+
bogus_param='value',
64+
)
65+
66+
def test_invalid_enum_value(self, query_builder):
67+
68+
with pytest.raises(InvalidParameterValueError):
69+
query_builder.build(
70+
'exports/interactions/parquet',
71+
direction='sideways',
72+
)
73+
74+
def test_valid_enum_value(self, query_builder):
75+
76+
query = query_builder.build(
77+
'exports/interactions/parquet',
78+
sign='positive',
79+
)
80+
81+
assert query.params['sign'] == 'positive'
82+
83+
def test_none_values_skipped(self, query_builder):
84+
85+
query = query_builder.build(
86+
'exports/entities/parquet',
87+
taxonomy_ids=['9606'],
88+
entity_types=None,
89+
)
90+
91+
assert 'entity_types' not in query.params
92+
93+
94+
class TestQueryJsonBody:
95+
"""
96+
Tests for Query.json_body construction.
97+
"""
98+
99+
def test_filters_nested(self, query_builder):
100+
101+
query = query_builder.build(
102+
'exports/entities/parquet',
103+
taxonomy_ids=['9606'],
104+
entity_types=['protein'],
105+
)
106+
107+
body = query.json_body
108+
109+
assert 'filters' in body
110+
assert body['filters']['taxonomy_ids'] == ['9606']
111+
assert body['filters']['entity_types'] == ['protein']
112+
113+
def test_get_endpoint_no_body(self, query_builder):
114+
115+
query = query_builder.build('health')
116+
117+
assert query.json_body is None

0 commit comments

Comments
 (0)