Skip to content

Add definitions for boto/utils.py #1579

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

Merged
merged 12 commits into from
Oct 5, 2017
Merged

Add definitions for boto/utils.py #1579

merged 12 commits into from
Oct 5, 2017

Conversation

mxr
Copy link
Contributor

@mxr mxr commented Aug 28, 2017

No description provided.

@@ -0,0 +1,3 @@
from typing import Text

def pythonize_name(name: Text) -> Text: ...
Copy link
Member

Choose a reason for hiding this comment

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

There's a lot of other code in this module. We generally shouldn't create a partial stub, because then users who use other functions from this module will get false positive errors that are hard to ignore. (If the module doesn't exist in typeshed at all, mypy's --follow-imports flag lets you shut up mypy.) Would you mind adding stubs for the other public APIs in boto.utils?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure thing

@mxr mxr changed the title Add definitions for boto.utils.pythonize_name Add definitions for boto/utils.py Sep 5, 2017
@mxr
Copy link
Contributor Author

mxr commented Sep 5, 2017

@JelleZijlstra I'm almost done adding stubs here but I hit a roadblock. Any advice on the below errors?

  1. third_party/2and3/boto/utils.pyi:162: error: Invalid type "boto.utils.Password.str"

    The Password class has a variable called str which mypy hiccups on. Should I remove the str variable from the stub or do something else?
  2. third_party/2and3/boto/utils.pyi:1: error: Cannot find module named '_thread'

    I'm trying to annotate with LockType from _thread (which exists in python2 and python3) but mypy isn't picking it up. Should I split my stubs into separate folders for python2 and python3? If so would it be possible to share any stubs for boto/utils.py between multiple Python versions even if I have to split LockType into separate files? Any examples of this?

Other open questions, which I've worked around by using less specific stubs or removing stubs to get as far as I've gotten:

  1. Should I add stubs for boto/provider.pyi because boto.utils depends on a boto.provider.Provider existing? Just want to confirm if "We generally shouldn't create a partial stub, because then users who use other functions from this module will get false positive errors that are hard to ignore." (emphasis mine) applies to only annotating classes or not. I had the stubs ready to go in a3e45b5 and I can add them back to this PR if you think it's best to include them.
  2. How should I annotate the _Item.previous variable? Its type is _Item but mypy reports that error: Name '_Item' is not defined
  3. How should I annotate LazyLoadMetadata.values(), LazyLoadMetadata.items(), and LRUCache.__contains__? I got errors that my annotations for them did not match the parent's annotations so I loosened them.
    a. Should I use dict or Dict as the parent of these classes?
  4. find_class returns a ClassType - is there a better annotation than Type[object] that I could use instead?
  5. A lot of arguments in the ShellCommand class are straight passthroughs into functions in the subprocess module. To help ensure compatibility I borrowed the custom type annotations from the subprocess module, even though they are "private". Is there an alternative you would recommend?
  6. JSONDecodeError's value in the code depends on whether or not simplejson is installed. Is there a better way to annotate its true type than what I have?

Any guidance is appreciated. Thanks!

@JelleZijlstra
Copy link
Member

JelleZijlstra commented Sep 6, 2017 via email

@JelleZijlstra
Copy link
Member

Sorry for the mess in my emailed reply above; I hope it's still comprehensible.

@mxr mxr force-pushed the patch-1 branch 4 times, most recently from 214222a to f2191f6 Compare September 12, 2017 02:51
@mxr
Copy link
Contributor Author

mxr commented Sep 12, 2017

@JelleZijlstra thanks for the feedback! I was able to work around the above errors using your advice. I err'd on the side of using Any with a TODO though.

I have some more questions able to annotate the _Item class internals with _KT and _VT. Using them to annotate key, value, and __init__() leads to errors such as

typeshed/third_party/2and3/boto/utils.pyi:143: error: Invalid type "typing._KT"
typeshed/third_party/2and3/boto/utils.pyi:144: error: Invalid type "typing._VT"
typeshed/third_party/2and3/boto/utils.pyi:145: error: Type variable '_KT' is bound by an outer class
typeshed/third_party/2and3/boto/utils.pyi:145: error: Type variable '_VT' is bound by an outer class

Any advice on the above? I left off the types for those 3 for now.

@mxr
Copy link
Contributor Author

mxr commented Sep 17, 2017

@JelleZijlstra friendly ping on the above. Thanks!

Copy link
Member

@JelleZijlstra JelleZijlstra left a comment

Choose a reason for hiding this comment

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

Sorry for the delay! I just went over the code and over the implementation in https://github.com/boto/boto/blob/develop/boto/utils.py and noticed some small things.

def canonical_string(
method: str,
path: str,
headers: Dict[str, str],
Copy link
Member

Choose a reason for hiding this comment

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

Better to use Mapping since it doesn't actually need precisely a Dict.

provider: Optional[_Provider] = ...,
) -> str: ...
def merge_meta(
headers: Dict[str, str],
Copy link
Member

Choose a reason for hiding this comment

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

Same here (and probably for most other Dict arguments)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do

num_retries: int,
timeout: Optional[int] = ...,
) -> None: ...
def get(self, key: _KT, default: Optional[Any] = ...) -> Any: ...
Copy link
Member

Choose a reason for hiding this comment

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

Just remove this since it's the same as the base class.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure

RFC1123 = ... # type: str
LOCALE_LOCK = ... # type: _LockType

def setlocale(name: Union[str, Tuple[str, str]]) -> None: ...
Copy link
Member

Choose a reason for hiding this comment

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

This should return a ContextManager[None].

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nice catch, I missed the yield. It looks like setlocale returns a string so would I want to use ContextManager[str]

def setlocale(category: int,

file: Optional[IO] = ...,
username: Optional[str] = ...,
password: Optional[str] = ...,
) -> IO: ...
Copy link
Member

Choose a reason for hiding this comment

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

Should be Optional. Also, not sure if this is an IO[bytes] or IO[str] from reading the code, but we should pick one.

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 think str but not 100% sure...

body: Optional[str] = ...,
html_body: Optional[Union[Sequence[str], str]] = ...,
to_string: Optional[str] = ...,
attachments: Optional[Iterable] = ...,
Copy link
Member

Choose a reason for hiding this comment

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

What kind of Iterable?

Copy link
Contributor Author

@mxr mxr Sep 26, 2017

Choose a reason for hiding this comment

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

Each attachment is passed to email.message.Message.attach which is typed to be a Message

def attach(self, payload: 'Message') -> None: ...

attachments: Optional[Iterable] = ...,
append_instance_id: bool = ...,
) -> None: ...
def get_utf8_value(value: str) -> str: ...
Copy link
Member

Choose a reason for hiding this comment

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

Returns bytes

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will fix

) -> str: ...
def guess_mime_type(content: str, deftype: str) -> str: ...
def compute_md5(
fp: IO,
Copy link
Member

Choose a reason for hiding this comment

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

This supports both kinds of IO, but it's good to be explicit and say IO[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.

Sure

size: Optional[int] = ...,
hash_algorithm: Any = ...,
) -> Tuple[str, str, int]: ...
def find_matching_headers(name: str, headers: Dict[str, str]) -> List[str]: ...
Copy link
Member

Choose a reason for hiding this comment

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

Use Mapping. In fact, it accepts any Iterable[str], but it's apparently not intended to.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure

hash_algorithm: Any = ...,
) -> Tuple[str, str, int]: ...
def find_matching_headers(name: str, headers: Dict[str, str]) -> List[str]: ...
def merge_headers_by_name(name: str, headers: Dict[str, str]) -> str: ...
Copy link
Member

Choose a reason for hiding this comment

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

Mapping, and the values can be Optional[str].

Copy link
Contributor Author

@mxr mxr Sep 26, 2017

Choose a reason for hiding this comment

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

Perhaps I should change the value of all relevant headers variables to be Optional[str]?

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 traced through the code and believe it is safe to do it for merge_headers_by_name, find_matching_headers, and canonical_string but not the others so I will go forward with that change

Copy link
Member

Choose a reason for hiding this comment

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

Sounds good

@JelleZijlstra JelleZijlstra merged commit c68dcf1 into python:master Oct 5, 2017
@mxr mxr deleted the patch-1 branch October 5, 2017 06:09
@mxr
Copy link
Contributor Author

mxr commented Oct 5, 2017

Thanks for reviewing and merging this @JelleZijlstra !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants