-
Notifications
You must be signed in to change notification settings - Fork 107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Partially typecheck APIformatting
module
#598
Changes from 7 commits
67f3a9c
f0c0d88
7c01510
2948bea
05d5abd
ef5bdbe
3da94ef
1b46d85
ea4a0c5
b90a10e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
"""Generate and format information for submitting to API (CMR and NSIDC).""" | ||
|
||
import datetime as dt | ||
from typing import Any, Generic, Literal, TypeVar, Union, overload | ||
from typing import Any, Generic, Literal, Optional, TypeVar, Union, overload | ||
|
||
from icepyx.core.types import ( | ||
CMRParams, | ||
|
@@ -36,10 +36,6 @@ | |
|
||
assert isinstance(start, dt.datetime) | ||
assert isinstance(end, dt.datetime) | ||
assert key in [ | ||
"time", | ||
"temporal", | ||
], "An invalid time key was submitted for formatting." | ||
|
||
if key == "temporal": | ||
fmt_timerange = ( | ||
|
@@ -53,6 +49,8 @@ | |
+ "," | ||
+ end.strftime("%Y-%m-%dT%H:%M:%S") | ||
) | ||
else: | ||
raise RuntimeError("An invalid time key was submitted for formatting.") | ||
|
||
return {key: fmt_timerange} | ||
|
||
|
@@ -231,7 +229,7 @@ | |
Returns the dictionary of formatted keys associated with the | ||
parameter object. | ||
""" | ||
return instance._fmted_keys | ||
return instance._fmted_keys # pyright: ignore[reportReturnType] | ||
|
||
|
||
# ---------------------------------------------------------------------- | ||
|
@@ -257,13 +255,16 @@ | |
on the type of query. Must be one of ['search','download'] | ||
""" | ||
|
||
partype: T | ||
_reqtype: Optional[Literal["search", "download"]] | ||
fmted_keys = _FmtedKeysDescriptor() | ||
# _fmted_keys: Union[CMRParams, EGISpecificRequiredParams, EGIParamsSubset] | ||
|
||
def __init__( | ||
self, | ||
partype: T, | ||
values=None, | ||
reqtype=None, | ||
values: Optional[dict] = None, | ||
reqtype: Optional[Literal["search", "download"]] = None, | ||
): | ||
assert partype in [ | ||
"CMR", | ||
|
@@ -282,31 +283,14 @@ | |
self._fmted_keys = values if values is not None else {} | ||
|
||
@property | ||
def poss_keys(self): | ||
def poss_keys(self) -> dict[str, list[str]]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This property returns static data, from what I can tell, so I simplified it down, and removed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I had it this way because (1) I'd learned about properties and was probably overusing them; (2) I wanted it to be private enough the user understood they could look but shouldn't touch (so an invalid parameter wasn't submitted). Overall a fan of the simplification though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Who can blame you, properties are awesome!
Do you mean "can't set it" by "shouldn't touch"? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I suppose so. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gotcha. That's already covered by the >>> class Foo:
... @property
... def foo(self):
... return "foo"
...
>>> Foo().foo
'foo'
>>> Foo().foo = "bar"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute 'foo' |
||
""" | ||
Returns a list of possible input keys for the given parameter object. | ||
Possible input keys depend on the parameter type (partype). | ||
""" | ||
|
||
if not hasattr(self, "_poss_keys"): | ||
self._get_possible_keys() | ||
|
||
return self._poss_keys | ||
|
||
# @property | ||
# def wanted_keys(self): | ||
# if not hasattr(_wanted): | ||
# self._wanted = [] | ||
|
||
# return self._wanted | ||
|
||
def _get_possible_keys(self) -> dict[str, list[str]]: | ||
""" | ||
Use the parameter type to get a list of possible parameter keys. | ||
""" | ||
|
||
if self.partype == "CMR": | ||
self._poss_keys = { | ||
return { | ||
"spatial": ["bounding_box", "polygon"], | ||
"optional": [ | ||
"temporal", | ||
|
@@ -316,7 +300,7 @@ | |
], | ||
} | ||
elif self.partype == "required": | ||
self._poss_keys = { | ||
return { | ||
"search": ["short_name", "version", "page_size"], | ||
"download": [ | ||
"short_name", | ||
|
@@ -331,7 +315,7 @@ | |
], | ||
} | ||
elif self.partype == "subset": | ||
self._poss_keys = { | ||
return { | ||
"spatial": ["bbox", "Boundingshape"], | ||
"optional": [ | ||
"time", | ||
|
@@ -341,8 +325,17 @@ | |
"Coverage", | ||
], | ||
} | ||
else: | ||
raise RuntimeError("Programmer error!") | ||
JessicaS11 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
# @property | ||
# def wanted_keys(self): | ||
# if not hasattr(_wanted): | ||
# self._wanted = [] | ||
|
||
def _check_valid_keys(self): | ||
# return self._wanted | ||
|
||
def _check_valid_keys(self) -> None: | ||
""" | ||
Checks that any keys passed in with values are valid keys. | ||
""" | ||
|
@@ -352,13 +345,13 @@ | |
|
||
val_list = list({val for lis in self.poss_keys.values() for val in lis}) | ||
|
||
for key in self.fmted_keys: | ||
for key in self.fmted_keys: # pyright: ignore[reportAttributeAccessIssue] | ||
assert key in val_list, ( | ||
"An invalid key (" + key + ") was passed. Please remove it using `del`" | ||
) | ||
|
||
# DevNote: can check_req_values and check_values be combined? | ||
def check_req_values(self): | ||
def check_req_values(self) -> bool: | ||
""" | ||
Check that all of the required keys have values, if the key was passed in with | ||
the values parameter. | ||
|
@@ -367,17 +360,22 @@ | |
assert ( | ||
self.partype == "required" | ||
), "You cannot call this function for your parameter type" | ||
|
||
if not self._reqtype: | ||
raise RuntimeError("Programmer error!") | ||
|
||
reqkeys = self.poss_keys[self._reqtype] | ||
|
||
if all(keys in self.fmted_keys for keys in reqkeys): | ||
if all(keys in self.fmted_keys for keys in reqkeys): # pyright: ignore[reportAttributeAccessIssue] | ||
assert all( | ||
self.fmted_keys.get(key, -9999) != -9999 for key in reqkeys | ||
self.fmted_keys.get(key, -9999) != -9999 # pyright: ignore[reportAttributeAccessIssue] | ||
for key in reqkeys | ||
), "One of your formatted parameters is missing a value" | ||
return True | ||
else: | ||
return False | ||
|
||
def check_values(self): | ||
def check_values(self) -> bool: | ||
""" | ||
Check that the non-required keys have values, if the key was | ||
passed in with the values parameter. | ||
|
@@ -391,7 +389,8 @@ | |
# not the most robust check, but better than nothing... | ||
if any(keys in self._fmted_keys for keys in spatial_keys): | ||
assert any( | ||
self.fmted_keys.get(key, -9999) != -9999 for key in spatial_keys | ||
self.fmted_keys.get(key, -9999) != -9999 # pyright: ignore[reportAttributeAccessIssue] | ||
for key in spatial_keys | ||
), "One of your formatted parameters is missing a value" | ||
return True | ||
else: | ||
|
@@ -427,6 +426,9 @@ | |
self._check_valid_keys() | ||
|
||
if self.partype == "required": | ||
if not self._reqtype: | ||
raise RuntimeError("Programmer error!") | ||
|
||
if self.check_req_values() and kwargs == {}: | ||
pass | ||
else: | ||
|
@@ -484,6 +486,7 @@ | |
if any(keys in self._fmted_keys for keys in spatial_keys): | ||
pass | ||
else: | ||
k = None | ||
if self.partype == "CMR": | ||
k = kwargs["extent_type"] | ||
elif self.partype == "subset": | ||
|
@@ -492,6 +495,9 @@ | |
elif kwargs["extent_type"] == "polygon": | ||
k = "Boundingshape" | ||
|
||
if not k: | ||
raise RuntimeError("Programmer error!") | ||
|
||
self._fmted_keys.update({k: kwargs["spatial_extent"]}) | ||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic (is
key
"time"
or"temporal"
?) is repeated just below, so I moved the error statement into that conditional.In general, assertions should not be used in "the code" (use only in the tests) unless you specifically want the assertions to be controllable at runtime.
python -O
will disable all assertions like-W
disables warnings. So when I moved this logic I also changed it to raise aRuntimeError
.