Skip to content

Conversation

@sgillies
Copy link
Contributor

@sgillies sgillies commented May 4, 2022

Resolves #489

@jreiberkyle @mkshah605 @kevinlacaille this is 6 commits, 9 files. It might look like a long review, but it's only a few lines per file. The TLDR: a few commits fix subtle bugs that our tests were not triggering (static analysis for the win!), the rest of the commits fix improper use of type hints or reconcile our code with mypy's stricter view of typing. I think we should merge this right away if we're going to continue to use type hints in the project. And I think we probably will, users increasingly expect them.

sgillies added 3 commits May 4, 2022 15:02
Fixes one mypy-detected error:

Type of __all__ must be "Sequence[str]", not "List[object]"

Towards resolving #489
Resolves the error

Incompatible types in assignment (expression has type "bool",
target has type "Collection[Any]")
request_json: typing.Dict[str, typing.Any] = {
'name': name, 'filter': search_filter, 'item_types': item_types
}
# QUESTION: can we just set the value to True or False above?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We have a test that doesn't expect __daily_email_enabled: False, but I wonder if the API would handle this?

Copy link
Contributor

Choose a reason for hiding this comment

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

The API reference claims that the type of this parameter is a boolean so I imagine it would handle True/False values. Is that the question here?


# TODO: validate item_types
request_json = {
request_json: typing.Dict[str, typing.Any] = {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Without this hint, mypy diagnoses request_json's type as Dict[str, Collection[Any]] because name, search_filter, and item_types are all collections, and then complains when we try to add a bool item, which is not a collection.

Copy link
Contributor

Choose a reason for hiding this comment

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

ahh interesting! good catch. I wonder if we want to make this a custom type, eg JSONType and move it over, along with AuthType to a new types module?

Added a new AuthType alias for httpx.Auth, added a missing
abstract method.

@abc.abstractmethod
def to_dict(self) -> dict:
pass
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Resolves

planet/auth.py:132: error: "Auth" has no attribute "to_dict"

BASE_URL = f'{PLANET_BASE_URL}/v0/auth'
ENV_API_KEY = 'PL_API_KEY'

AuthType = httpx.Auth
Copy link
Contributor Author

@sgillies sgillies May 4, 2022

Choose a reason for hiding this comment

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

Resolves

planet/http.py:123: error: Argument "auth" to "AsyncClient" has incompatible type "planet.auth.Auth"; expected "Union[Tuple[Union[str, bytes], Union[str, bytes]], Callable[[Request], Request], httpx._auth.Auth, None]"

'''
variable_name = variable_name or ENV_API_KEY
api_key = os.getenv(variable_name)
api_key = os.getenv(variable_name, '')
Copy link
Contributor Author

@sgillies sgillies May 4, 2022

Choose a reason for hiding this comment

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

Resolves

planet/auth.py:85: error: Argument 1 to "APIKeyAuth" has incompatible type "Optional[str]"; expected "str"

The APIKeyAuth constructor does not take None as an argument (key: str) but we might pass it None if there's nothing in the environment. Instead, we'll pass an empty string.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is a nice catch

def from_login(email: str, password: str, base_url: str = None) -> AuthType:
def from_login(email: str,
password: str,
base_url: str = None) -> AuthType:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Carried over formatting.

order type.
'''
details = {'name': name, 'products': products}
details: Dict[str, Any] = {'name': name, 'products': products}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

mypy requires us to be more careful about dict typing.

order_type = specs.validate_order_type(order_type)
details['order_type'] = order_type
validated_order_type = specs.validate_order_type(order_type)
details['order_type'] = validated_order_type
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In general, mypy discourages reassigning names, and not without reason because it can be a source of bugs.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh! yeah, I can see that. nice catch.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I reassign names a lot and it's a hard habit to break 😆

product_bundle = ','.join([product_bundle, fallback_bundle])
validated_fallback_bundle = specs.validate_bundle(fallback_bundle)
specs.validate_item_type(item_type, validated_fallback_bundle)
validated_product_bundle = ','.join([validated_product_bundle, validated_fallback_bundle])
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here, though, mypy's analyzer can't find that anything on the right side will be None, so it doesn't care if we reassign.

And ignore mypy warnings about modules with no typing or stubs.

self.bar.refresh()
if self.bar is not None:
self.bar.refresh()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

mypy noticed some cases where self.bar could be None and we would try to call its methods.

Copy link
Contributor

Choose a reason for hiding this comment

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

That is cool as heck. I should try mypy

Copy link
Contributor

Choose a reason for hiding this comment

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

awesome catch. this would probably have bubbled up as a bug eventually.

@sgillies sgillies marked this pull request as ready for review May 5, 2022 00:24
@sgillies
Copy link
Contributor Author

sgillies commented May 5, 2022

@jreiberkyle @mkshah605 @kevinlacaille this is 6 commits, 9 files. It might look like a long review, but it's only a few lines per file. The TLDR: a few commits fix subtle bugs that our tests were not triggering (static analysis for the win!), the rest of the commits fix improper use of type hints or reconcile our code with mypy's stricter view of typing. I think we should merge this right away if we're going to continue to use type hints in the project. And I think we probably will, users increasingly expect them.

@sgillies sgillies added this to the Data CLI MVP milestone May 5, 2022
@sgillies sgillies self-assigned this May 5, 2022
@sgillies sgillies added the fixup label May 5, 2022
'order_request',
'reporting',
'Session',
]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

__all__ requires strings, not objects.

Copy link
Contributor

@jreiberkyle jreiberkyle left a comment

Choose a reason for hiding this comment

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

its great to have static typing in the python library and turned out to be much easier than I had expected.

'''
variable_name = variable_name or ENV_API_KEY
api_key = os.getenv(variable_name)
api_key = os.getenv(variable_name, '')
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a nice catch

request_json: typing.Dict[str, typing.Any] = {
'name': name, 'filter': search_filter, 'item_types': item_types
}
# QUESTION: can we just set the value to True or False above?
Copy link
Contributor

Choose a reason for hiding this comment

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

The API reference claims that the type of this parameter is a boolean so I imagine it would handle True/False values. Is that the question here?


# TODO: validate item_types
request_json = {
request_json: typing.Dict[str, typing.Any] = {
Copy link
Contributor

Choose a reason for hiding this comment

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

ahh interesting! good catch. I wonder if we want to make this a custom type, eg JSONType and move it over, along with AuthType to a new types module?

order_type = specs.validate_order_type(order_type)
details['order_type'] = order_type
validated_order_type = specs.validate_order_type(order_type)
details['order_type'] = validated_order_type
Copy link
Contributor

Choose a reason for hiding this comment

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

Oh! yeah, I can see that. nice catch.


self.bar.refresh()
if self.bar is not None:
self.bar.refresh()
Copy link
Contributor

Choose a reason for hiding this comment

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

awesome catch. this would probably have bubbled up as a bug eventually.

Removes a conditional statement and eliminates one of the original
mypy errors.
@sgillies sgillies merged commit 72eb746 into main May 5, 2022
@sgillies sgillies deleted the issue489 branch May 5, 2022 14:44
@jreiberkyle
Copy link
Contributor

@sgillies can you please also add how to use mypy into CONTRIBUTING.md?

@sgillies
Copy link
Contributor Author

sgillies commented May 5, 2022

Done in ff57b41.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use mypy to check type hints

4 participants