Skip to content

Commit 550e186

Browse files
committed
move Client-specific get_queryables implementation to Client class
1 parent 3e89be4 commit 550e186

File tree

3 files changed

+74
-29
lines changed

3 files changed

+74
-29
lines changed

pystac_client/client.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
QueryLike,
3939
SortbyLike,
4040
)
41-
from pystac_client.mixins import QueryablesMixin
41+
from pystac_client.mixins import QUERYABLES_ENDPOINT, QueryablesMixin
4242
from pystac_client.stac_api_io import StacApiIO, Timeout
4343
from pystac_client.warnings import DoesNotConformTo, FallbackToPystac, NoConformsTo
4444

@@ -335,6 +335,47 @@ def _warn_about_fallback(self, *args: str) -> None:
335335
warnings.warn(DoesNotConformTo(*args), stacklevel=2)
336336
warnings.warn(FallbackToPystac(), stacklevel=2)
337337

338+
def get_merged_queryables(self, collections: List[str]) -> Dict[str, Any]:
339+
"""Return the set of queryables in common to the specified collections.
340+
341+
Queryables from multiple collections are unioned together, except in the case
342+
when the same queryable key has a different definition, in which case that key
343+
is dropped.
344+
345+
Output is a dictionary that can be used in ``jsonshema.validate``
346+
347+
Args:
348+
collections List[str]: The IDs of the collections to inspect.
349+
350+
Return:
351+
Dict[str, Any]: Dictionary containing queryable fields
352+
"""
353+
if not collections:
354+
raise ValueError("cannot get_merged_queryables from empty Iterable")
355+
356+
if not self.conforms_to(ConformanceClasses.FILTER):
357+
raise DoesNotConformTo(ConformanceClasses.FILTER.name)
358+
response = self.get_queryables_from(
359+
self._get_collection_queryables_href(collections[0])
360+
)
361+
response.pop("$id")
362+
addl_props = response.get("additionalProperties", False)
363+
for collection in collections[1:]:
364+
resp = self.get_queryables_from(
365+
self._get_collection_queryables_href(collection)
366+
)
367+
368+
# additionalProperties is false if any collection doesn't support queryables
369+
addl_props &= resp.get("additionalProperties", False)
370+
371+
# drop queryables if their keys match, but the descriptions differ
372+
for k in set(resp["properties"]).intersection(response["properties"]):
373+
if resp["properties"][k] != response["properties"][k]:
374+
resp["properties"].pop(k)
375+
response["properties"].pop(k)
376+
response["properties"].update(resp["properties"])
377+
return response
378+
338379
@lru_cache()
339380
def get_collection(self, collection_id: str) -> Union[Collection, CollectionClient]:
340381
"""Get a single collection from this Catalog/API
@@ -598,3 +639,9 @@ def _collections_href(self, collection_id: Optional[str] = None) -> str:
598639
if collection_id is not None:
599640
return f"{href.rstrip('/')}/{collection_id}"
600641
return href
642+
643+
def _get_collection_queryables_href(
644+
self, collection_id: Optional[str] = None
645+
) -> str:
646+
href = self._collections_href(collection_id)
647+
return f"{href.rstrip('/')}/{QUERYABLES_ENDPOINT}"

pystac_client/mixins.py

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
from typing import Optional, Dict, Any, Union
21
import warnings
2+
from typing import Any, Dict, Optional, Union
33

44
import pystac
55

6-
from pystac_client.exceptions import APIError
76
from pystac_client.conformance import ConformanceClasses
7+
from pystac_client.exceptions import APIError
88
from pystac_client.stac_api_io import StacApiIO
99
from pystac_client.warnings import DoesNotConformTo, MissingLink
1010

@@ -32,34 +32,21 @@ def _get_href(self, rel: str, link: Optional[pystac.Link], endpoint: str) -> str
3232
class QueryablesMixin(BaseMixin):
3333
"""Mixin for adding support for /queryables endpoint"""
3434

35-
def get_queryables(self, *collections: Optional[str]) -> Dict[str, Any]:
36-
"""Return all queryables, or limit to those of specified collections.
37-
38-
Queryables from multiple collections are unioned together, except in the case when the same queryable key has a different definition, in which case that key is dropped.
35+
def get_queryables_from(self, url: str) -> Dict[str, Any]:
36+
"""Return all queryables.
3937
4038
Output is a dictionary that can be used in ``jsonshema.validate``
39+
4140
Args:
42-
*collections: The IDs of the items to include.
41+
url: a queryables url
4342
4443
Return:
4544
Dict[str, Any]: Dictionary containing queryable fields
4645
"""
47-
if collections and isinstance(self, pystac.Catalog):
48-
response = self.get_collection(collections[0]).get_queryables()
49-
response.pop("$id")
50-
for collection in collections[1:]:
51-
col_resp = self.get_collection(collection).get_queryables()
52-
response["properties"].update(col_resp["properties"])
53-
return response
5446

5547
if self._stac_io is None:
5648
raise APIError("API access is not properly configured")
5749

58-
if not self.conforms_to(ConformanceClasses.FILTER):
59-
raise DoesNotConformTo(ConformanceClasses.FILTER.name)
60-
61-
url = self._get_queryables_href()
62-
6350
result = self._stac_io.read_json(url)
6451
if "properties" not in result:
6552
raise APIError(
@@ -69,7 +56,14 @@ def get_queryables(self, *collections: Optional[str]) -> Dict[str, Any]:
6956

7057
return result
7158

59+
def get_queryables(self) -> Dict[str, Any]:
60+
url = self._get_queryables_href()
61+
return self.get_queryables_from(url)
62+
7263
def _get_queryables_href(self) -> str:
64+
if not self.conforms_to(ConformanceClasses.FILTER):
65+
raise DoesNotConformTo(ConformanceClasses.FILTER.name)
66+
7367
link = self.get_single_link(QUERYABLES_REL)
7468
href = self._get_href(QUERYABLES_REL, link, QUERYABLES_ENDPOINT)
7569
return href

tests/test_client.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
import pytest
1111
from dateutil.tz import tzutc
1212
from pystac import MediaType
13-
from requests_mock import Mocker
14-
1513
from pystac_client import Client, CollectionClient
1614
from pystac_client._utils import Modifiable
1715
from pystac_client.conformance import ConformanceClasses
@@ -25,6 +23,7 @@
2523
NoConformsTo,
2624
strict,
2725
)
26+
from requests_mock import Mocker
2827

2928
from .helpers import STAC_URLS, TEST_DATA, read_data_file
3029

@@ -587,11 +586,13 @@ def test_get_queryables(self) -> None:
587586
def test_get_queryables_collections(self) -> None:
588587
api = Client.open(STAC_URLS["PLANETARY-COMPUTER"])
589588
with pytest.warns(MissingLink, match="queryables"):
590-
tdep_seamless_props = api.get_collection("3dep-seamless").get_queryables()[
591-
"properties"
592-
]
593-
fia_props = api.get_collection("fia").get_queryables()["properties"]
594-
result = api.get_queryables("fia", "3dep-seamless")
589+
col = api.get_collection("3dep-seamless")
590+
assert isinstance(col, CollectionClient)
591+
tdep_seamless_props = col.get_queryables()["properties"]
592+
col = api.get_collection("fia")
593+
assert isinstance(col, CollectionClient)
594+
fia_props = col.get_queryables()["properties"]
595+
result = api.get_merged_queryables(["fia", "3dep-seamless"])
595596
assert "properties" in result
596597
assert "id" in result["properties"]
597598
assert set(fia_props.keys()).issubset(result["properties"])
@@ -607,14 +608,17 @@ def test_get_queryables_errors(self, requests_mock: Mocker) -> None:
607608

608609
assert api._stac_io is not None
609610
api.add_conforms_to("FILTER")
611+
self_href = api.get_self_href()
610612
api.set_self_href(None)
611613
with pytest.warns(MissingLink, match="queryables"):
612614
with pytest.raises(ValueError, match="does not have a self_href set"):
613615
api.get_queryables()
614616

617+
api.set_self_href(self_href)
615618
api._stac_io = None
616-
with pytest.raises(APIError, match="API access is not properly configured"):
617-
api.get_queryables()
619+
with pytest.warns(MissingLink, match="queryables"):
620+
with pytest.raises(APIError, match="API access is not properly configured"):
621+
api.get_queryables()
618622

619623

620624
class TestConformsTo:

0 commit comments

Comments
 (0)