Skip to content

Commit 81068f9

Browse files
authored
Add support for resolving indicators (#289)
1 parent 3f7e6d0 commit 81068f9

File tree

5 files changed

+123
-12
lines changed

5 files changed

+123
-12
lines changed

datacommons_client/endpoints/payloads.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,4 +133,7 @@ class ResolveRequestPayload(BaseDCModel):
133133
"""
134134

135135
node_dcids: ListOrStr = Field(..., serialization_alias="nodes")
136-
expression: str | list[str] = Field(..., serialization_alias="property")
136+
expression: str | list[str] | None = Field(default=None,
137+
serialization_alias="property")
138+
resolver: str | None = Field(default=None, serialization_alias="resolver")
139+
target: str | None = Field(default=None, serialization_alias="target")

datacommons_client/endpoints/resolve.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,20 @@ def __init__(self, api: API):
3737
"""Initializes the ResolveEndpoint instance."""
3838
super().__init__(endpoint="resolve", api=api)
3939

40-
def fetch(self, node_ids: str | list[str],
41-
expression: str | list[str]) -> ResolveResponse:
40+
def fetch(self,
41+
node_ids: str | list[str],
42+
expression: str | list[str] | None = None,
43+
resolver: str | None = None,
44+
target: str | None = None) -> ResolveResponse:
4245
"""
4346
Fetches resolved data for the given nodes and expressions, identified by name,
4447
coordinates, or wiki ID.
4548
4649
Args:
4750
node_ids (str | list[str]): One or more node IDs to resolve.
4851
expression (str): The relation expression to query.
52+
resolver (str | None): The resolver type to use (e.g., "indicator").
53+
target (str | None): The resolution target (e.g., "custom_only").
4954
5055
Returns:
5156
ResolveResponse: The response object containing the resolved data.
@@ -56,11 +61,28 @@ def fetch(self, node_ids: str | list[str],
5661

5762
# Construct the payload
5863
payload = ResolveRequestPayload(node_dcids=node_ids,
59-
expression=expression).to_dict()
64+
expression=expression,
65+
resolver=resolver,
66+
target=target).to_dict()
6067

6168
# Send the request and return the response
6269
return ResolveResponse.model_validate(self.post(payload))
6370

71+
def fetch_indicators(self,
72+
queries: str | list[str],
73+
target: str | None = None) -> ResolveResponse:
74+
"""
75+
Fetches resolved indicators (StatisticalVariables or Topics) for the given queries.
76+
77+
Args:
78+
queries (str | list[str]): One or more queries (e.g. "population", "gdp").
79+
target (str | None): Optional target for resolution (e.g., "base_only", "custom_only", "base_and_custom").
80+
81+
Returns:
82+
ResolveResponse: The response object containing the resolved indicators.
83+
"""
84+
return self.fetch(node_ids=queries, resolver="indicator", target=target)
85+
6486
def fetch_dcids_by_name(self,
6587
names: str | list[str],
6688
entity_type: Optional[str] = None) -> ResolveResponse:

datacommons_client/models/resolve.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ class Candidate(BaseDCModel):
2020

2121
dcid: NodeDCID = Field(default_factory=str)
2222
dominantType: Optional[DominantType] = None
23+
metadata: dict[str, str] | None = None
24+
typeOf: list[str] | None = None
2325

2426

2527
class Entity(BaseDCModel):

datacommons_client/tests/endpoints/test_resolve_endpoint.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,73 @@ def test_flatten_resolve_response():
155155

156156
# Assertions
157157
assert result == expected
158+
159+
160+
def test_fetch_indicators_calls_endpoints_correctly():
161+
"""Tests the fetch_indicators method."""
162+
api_mock = MagicMock()
163+
# Mock response data structure
164+
mock_response_data = {
165+
"entities": [{
166+
"node":
167+
"population",
168+
"candidates": [{
169+
"dcid": "Count_Person",
170+
"dominantType": "StatisticalVariable",
171+
"metadata": {
172+
"score": "0.9",
173+
"sentence": "population count"
174+
},
175+
"typeOf": ["StatisticalVariable"]
176+
}]
177+
}]
178+
}
179+
api_mock.post = MagicMock(return_value=mock_response_data)
180+
endpoint = ResolveEndpoint(api=api_mock)
181+
182+
# Call the method
183+
response = endpoint.fetch_indicators(queries=["population"],
184+
target="custom_only")
185+
186+
# Verify post was called with correct payload
187+
api_mock.post.assert_called_once_with(payload={
188+
"nodes": ["population"],
189+
"resolver": "indicator",
190+
"target": "custom_only"
191+
},
192+
endpoint="resolve",
193+
all_pages=True,
194+
next_token=None)
195+
196+
# Verify response parsing
197+
expected = ResolveResponse(entities=[
198+
Entity(node="population",
199+
candidates=[
200+
Candidate(dcid="Count_Person",
201+
dominantType="StatisticalVariable",
202+
metadata={
203+
"score": "0.9",
204+
"sentence": "population count"
205+
},
206+
typeOf=["StatisticalVariable"])
207+
])
208+
])
209+
assert response == expected
210+
211+
212+
def test_fetch_still_works_with_expression():
213+
"""Tests that fetch still works with expression (regression test)."""
214+
api_mock = MagicMock()
215+
mock_response_data = {"entities": []}
216+
api_mock.post = MagicMock(return_value=mock_response_data)
217+
endpoint = ResolveEndpoint(api=api_mock)
218+
219+
endpoint.fetch(node_ids=["geoId/06"], expression="<-containedInPlace")
220+
221+
api_mock.post.assert_called_once_with(payload={
222+
"nodes": ["geoId/06"],
223+
"property": "<-containedInPlace"
224+
},
225+
endpoint="resolve",
226+
all_pages=True,
227+
next_token=None)

datacommons_client/tests/endpoints/test_response.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -856,19 +856,26 @@ def test_resolve_response_dict():
856856
"candidates": [
857857
{
858858
"dcid": "dcid1",
859-
"dominantType": "Type1"
859+
"dominantType": "Type1",
860+
"metadata": None,
861+
"typeOf": None,
860862
},
861863
{
862864
"dcid": "dcid2",
863-
"dominantType": None
865+
"dominantType": None,
866+
"metadata": None,
867+
"typeOf": None,
864868
},
865869
],
866870
},
867871
{
868-
"node": "entity2",
872+
"node":
873+
"entity2",
869874
"candidates": [{
870875
"dcid": "dcid3",
871-
"dominantType": "Type2"
876+
"dominantType": "Type2",
877+
"metadata": None,
878+
"typeOf": None,
872879
},],
873880
},
874881
]
@@ -968,19 +975,26 @@ def test_resolve_response_json_string_exclude_none():
968975
"candidates": [
969976
{
970977
"dcid": "dcid1",
971-
"dominantType": "Type1"
978+
"dominantType": "Type1",
979+
"metadata": None,
980+
"typeOf": None,
972981
},
973982
{
974983
"dcid": "dcid2",
975-
"dominantType": None
984+
"dominantType": None,
985+
"metadata": None,
986+
"typeOf": None,
976987
},
977988
],
978989
},
979990
{
980-
"node": "entity2",
991+
"node":
992+
"entity2",
981993
"candidates": [{
982994
"dcid": "dcid3",
983-
"dominantType": "Type2"
995+
"dominantType": "Type2",
996+
"metadata": None,
997+
"typeOf": None,
984998
},],
985999
},
9861000
{

0 commit comments

Comments
 (0)