Skip to content

Commit ed858fd

Browse files
authored
Support "unchecked" query parameters. (#107)
Fixes #106
1 parent 59f86f3 commit ed858fd

4 files changed

Lines changed: 54 additions & 16 deletions

File tree

CHANGELOG.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,22 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
1414

1515
### Changed
1616

17-
- Deprecate methods `Query.get` and `Query.get_all` in favor of the new `Query.results` method. These deprecated methods will likely be removed for the 1.0.0 release. ([#37](https://github.com/nasa/python_cmr/issues/37))
17+
- Deprecate methods `Query.get` and `Query.get_all` in favor of the new
18+
`Query.results` method. These deprecated methods will likely be removed for
19+
the 1.0.0 release. ([#37](https://github.com/nasa/python_cmr/issues/37))
20+
- `Query.parameters` accepts "unchecked" keywords, meaning that it accepts
21+
keywords that do not have a corresponding method by the same name in the
22+
`Query` class (or specific subclass being used).
23+
24+
This allows the caller to supply a parameter that does not have a
25+
corresponding method without raising a `ValueError`. Instead, such a parameter
26+
is passed directly through to the CMR, where it will be checked. If the
27+
parameter is not supported or its value is invalid, the CMR response will
28+
indicate as such.
29+
30+
This avoids the need to wait for the corresponding method to be added, or
31+
having to write cumbersome code to get around the limitation.
32+
([#106](https://github.com/nasa/python_cmr/issues/106))
1833

1934
## [0.13.0]
2035

cmr/queries.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from datetime import date, datetime, timezone
88
from inspect import getmembers, ismethod
99
from re import search
10-
from typing import Iterator
10+
from typing import Iterable, Iterator
1111

1212
from typing_extensions import (
1313
Any,
@@ -127,7 +127,7 @@ def get_all(self) -> Sequence[Any]:
127127
128128
:returns: query results as a list
129129
"""
130-
130+
131131
return list(self.get(self.hits()))
132132

133133
def results(self, page_size: int = 2000) -> Iterator[Any]:
@@ -196,12 +196,19 @@ def parameters(self, **kwargs: Any) -> Self:
196196
methods = dict(getmembers(self, predicate=ismethod))
197197

198198
for key, val in kwargs.items():
199-
# verify the key matches one of our methods
199+
# If the key does not match one of the methods defined in the Query
200+
# class or subclass, simply set the parameter "unchecked" (i.e.,
201+
# set the parameter, but without a method that can do some value
202+
# checking. If the value is invalid, the CMR response will indicate
203+
# the problem).
200204
if key not in methods:
201-
raise ValueError(f"Unknown key {key}")
202-
203-
# call the method
204-
if isinstance(val, tuple):
205+
if isinstance(val, str) or not isinstance(val, Iterable):
206+
# Set single-valued parameter
207+
self.params[key] = val
208+
else:
209+
# Set multi-valued parameter adding `[]` suffix to key
210+
self.params[f"{key}[]"] = tuple(val)
211+
elif isinstance(val, tuple):
205212
methods[key](*val)
206213
else:
207214
methods[key](val)

tests/test_queries.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,56 +2,72 @@
22

33

44
class MockQuery(Query):
5+
6+
def __init__(self) -> None:
7+
super().__init__("/foo")
8+
59
def _valid_state(self) -> bool:
610
return True
711

812

913
def test_query_headers_initially_empty():
10-
query = MockQuery("/foo")
14+
query = MockQuery()
1115
assert query.headers == {}
1216

1317

1418
def test_bearer_token_adds_header():
15-
query = MockQuery("/foo")
19+
query = MockQuery()
1620
query.headers["foo"] = "bar"
1721
query.bearer_token("bearertoken")
1822

1923
assert query.headers["foo"] == "bar"
2024

2125

2226
def test_bearer_token_does_not_clobber_other_headers():
23-
query = MockQuery("/foo")
27+
query = MockQuery()
2428
query.bearer_token("bearertoken")
2529

2630
assert query.headers["Authorization"] == "Bearer bearertoken"
2731

2832

2933
def test_bearer_token_replaces_existing_auth_header():
30-
query = MockQuery("/foo")
34+
query = MockQuery()
3135
query.token("token")
3236
query.bearer_token("bearertoken")
3337

3438
assert query.headers["Authorization"] == "Bearer bearertoken"
3539

3640

3741
def test_token_adds_header():
38-
query = MockQuery("/foo")
42+
query = MockQuery()
3943
query.token("token")
4044

4145
assert query.headers["Authorization"] == "token"
4246

4347

4448
def test_token_does_not_clobber_other_headers():
45-
query = MockQuery("/foo")
49+
query = MockQuery()
4650
query.headers["foo"] = "bar"
4751
query.token("token")
4852

4953
assert query.headers["foo"] == "bar"
5054

5155

5256
def test_token_replaces_existing_auth_header():
53-
query = MockQuery("/foo")
57+
query = MockQuery()
5458
query.bearer_token("bearertoken")
5559
query.token("token")
5660

5761
assert query.headers["Authorization"] == "token"
62+
63+
64+
def test_singular_unknown_parameter():
65+
query = MockQuery().parameters(unknown_parameter="foo")
66+
67+
assert query.params["unknown_parameter"] == "foo"
68+
69+
70+
def test_plural_unknown_parameter():
71+
query = MockQuery().parameters(unknown_parameter=["foo", "bar"])
72+
73+
assert query.params["unknown_parameter[]"] == ("foo", "bar")

tests/test_service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def test_token(self):
7878
self.assertIn("Authorization", query.headers)
7979
self.assertEqual(query.headers["Authorization"], "123TOKEN")
8080

81-
def bearer_test_token(self):
81+
def test_bearer_token(self):
8282
query = ServiceQuery()
8383

8484
query.bearer_token("123TOKEN")

0 commit comments

Comments
 (0)