Skip to content
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

Merged
merged 10 commits into from
Sep 25, 2024
78 changes: 42 additions & 36 deletions icepyx/core/APIformatting.py
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,
Expand Down Expand Up @@ -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."
Copy link
Member Author

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 a RuntimeError.


if key == "temporal":
fmt_timerange = (
Expand All @@ -53,6 +49,8 @@
+ ","
+ end.strftime("%Y-%m-%dT%H:%M:%S")
)
else:
raise RuntimeError("An invalid time key was submitted for formatting.")

Check warning on line 53 in icepyx/core/APIformatting.py

View check run for this annotation

Codecov / codecov/patch

icepyx/core/APIformatting.py#L53

Added line #L53 was not covered by tests

return {key: fmt_timerange}

Expand Down Expand Up @@ -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]


# ----------------------------------------------------------------------
Expand All @@ -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",
Expand All @@ -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]]:
Copy link
Member Author

Choose a reason for hiding this comment

The 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 _poss_keys attribute.

Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(1) I'd learned about properties and was probably overusing them;

Who can blame you, properties are awesome!

(2) I wanted it to be private enough the user understood they could look but shouldn't touch

Do you mean "can't set it" by "shouldn't touch"?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean "can't set it" by "shouldn't touch"?

I suppose so.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha. That's already covered by the @property decorator :)

>>> 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",
Expand All @@ -316,7 +300,7 @@
],
}
elif self.partype == "required":
self._poss_keys = {
return {
"search": ["short_name", "version", "page_size"],
"download": [
"short_name",
Expand All @@ -331,7 +315,7 @@
],
}
elif self.partype == "subset":
self._poss_keys = {
return {
"spatial": ["bbox", "Boundingshape"],
"optional": [
"time",
Expand All @@ -341,8 +325,17 @@
"Coverage",
],
}
else:
raise RuntimeError("Programmer error!")

Check warning on line 329 in icepyx/core/APIformatting.py

View check run for this annotation

Codecov / codecov/patch

icepyx/core/APIformatting.py#L329

Added line #L329 was not covered by tests
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.
"""
Expand All @@ -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.
Expand All @@ -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!")

Check warning on line 365 in icepyx/core/APIformatting.py

View check run for this annotation

Codecov / codecov/patch

icepyx/core/APIformatting.py#L365

Added line #L365 was not covered by tests

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.
Expand All @@ -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:
Expand Down Expand Up @@ -427,6 +426,9 @@
self._check_valid_keys()

if self.partype == "required":
if not self._reqtype:
raise RuntimeError("Programmer error!")

Check warning on line 430 in icepyx/core/APIformatting.py

View check run for this annotation

Codecov / codecov/patch

icepyx/core/APIformatting.py#L430

Added line #L430 was not covered by tests

if self.check_req_values() and kwargs == {}:
pass
else:
Expand Down Expand Up @@ -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":
Expand All @@ -492,6 +495,9 @@
elif kwargs["extent_type"] == "polygon":
k = "Boundingshape"

if not k:
raise RuntimeError("Programmer error!")

Check warning on line 499 in icepyx/core/APIformatting.py

View check run for this annotation

Codecov / codecov/patch

icepyx/core/APIformatting.py#L499

Added line #L499 was not covered by tests

self._fmted_keys.update({k: kwargs["spatial_extent"]})


Expand Down
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,6 @@ exclude = [
# DevGoal: Remove all ignores
ignore = [
"icepyx/quest/*",
"icepyx/core/APIformatting.py",
"icepyx/core/auth.py",
"icepyx/core/is2ref.py",
"icepyx/core/read.py",
Expand Down