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

Adding trial type annotations to functions with correct return types. #202

Closed
wants to merge 3 commits into from

Conversation

mitchellkennedy
Copy link

I added a trial set of type annotations relating to #200 for the following functions:
parse
_extract_possible_number
_is_viable_phone_number
_normalize
normalize_digits_only
normalize_diallable_chars_only
convert_alpha_characters_in_number

This has been tested on 3.9, 3.8, 3.7, 3.6, 3.5, and 2.7. thus far.

Copy link
Owner

@daviddrysdale daviddrysdale left a comment

Choose a reason for hiding this comment

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

I've got some initial questions that I suspect just expose my ignorance of Python type annotations…

@@ -552,6 +552,7 @@ def _copy_number_format(other):


def _extract_possible_number(number):
# type: (str) -> None
Copy link
Owner

Choose a reason for hiding this comment

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

I'd naively expect this to be (str) -> str rather than None – maybe I need to learn more about Python type annotations…

Copy link
Author

@mitchellkennedy mitchellkennedy Aug 5, 2021

Choose a reason for hiding this comment

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

I believe you are correct. I was attempting to follow the convention laid out in examples provided in PEP 484 here. The code they include is as follows:

def embezzle(self, account, funds=1000000, *fake_receipts):
    # type: (str, int, *str) -> None
    """Embezzle funds from account using fake receipts."""
    <code goes here>

I was assuming they were using None for some sort of compatibility reason, but digging further into the documents, this is meant to specify the return type. I will correct this and submit another PR.

@@ -588,6 +589,7 @@ def _extract_possible_number(number):


def _is_viable_phone_number(number):
# type: (str) -> None
Copy link
Owner

Choose a reason for hiding this comment

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

Likewise, (str) -> bool?

Copy link
Author

Choose a reason for hiding this comment

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

As previously noted, you are correct.

@@ -2772,6 +2778,7 @@ def _set_italian_leading_zeros_for_phone_number(national_number, numobj):

def parse(number, region=None, keep_raw_input=False,
numobj=None, _check_region=True):
# type: (str, str, bool, **Any, bool) -> None
Copy link
Owner

Choose a reason for hiding this comment

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

Again, without knowing much about Python type annotations I'd expect the 4th parameter to have a type like Optional[PhoneNumber] – does that not work?

(BTW, the codebase has a general convention that a parameter called numobj is supposed to be a PhoneNumber object)

Copy link
Author

Choose a reason for hiding this comment

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

I have just tested this under Python 2 and 3, and it does appear to work. Pardon my confusion on the backwards compatible aspects. I will amend the next PR with this as well.

Copy link
Owner

Choose a reason for hiding this comment

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

Cool, sounds good – thanks for doing this.

By the way, how are you checking that the type annotations work? I'm thinking that I could/should add some type checking to the continuous integration GitHub actions.

Copy link
Author

Choose a reason for hiding this comment

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

No problem, happy to contribute!

To be specific, what I am implementing is type commenting rather than annotating. Both were introduced in Python 3, but annotating has not been backported to Python 2. Commenting is the suggested alternative to annotating if backwards compatibility needs to be maintained under the heading "Suggested syntax for Python 2.7 and straddling code" in PEP 484.
I've been validating by ensuring the examples I wrote run correctly and also that my IDE (PyCharm) correctly parses and code-completes the functions for which the type hints have been added.
Thinking about checking, I am sure there would be some way to implement that, but I'm not certain at this moment how. Since I have been digging into the documentation, I will give this some thought and raise a feature request if I can come up with something that works once this pull request is complete.

Copy link
Owner

Choose a reason for hiding this comment

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

I've got a hack to do mypy checking in #203; it will be interesting to see how it combines with this PR…

Copy link
Owner

Choose a reason for hiding this comment

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

I don't yet understand the failures in https://github.com/daviddrysdale/python-phonenumbers/runs/3363632239?check_suite_focus=true so I think I've got some more reading to do.

@mitchellkennedy mitchellkennedy changed the title Adding trial type annotations to functions Adding trial type annotations to functions with correct return types. Aug 5, 2021
@@ -2772,6 +2778,7 @@ def _set_italian_leading_zeros_for_phone_number(national_number, numobj):

def parse(number, region=None, keep_raw_input=False,
numobj=None, _check_region=True):
# type: (str, str, bool, PhoneNumber, bool) -> Optional[PhoneNumber]
Copy link
Owner

Choose a reason for hiding this comment

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

Can this ever return None (i.e. should the return type just be PhoneNumber)?

Copy link
Author

Choose a reason for hiding this comment

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

This line specifically is stating that it should always return PhoneNumber. None can be used just as any other type. It is possibly worth noting that for obvious reasons keeping hinting correct and accurate is the preferred method of operation, but any violation will not result in an exception.

Copy link
Owner

Choose a reason for hiding this comment

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

Doesn't the -> Optional[PhoneNumber] at the end imply that this function can return None as a normal return value (because Optional[T] is the same as Union[T, None])?

In other words I'm suspecting that the last part should be -> PhoneNumber.

Copy link
Author

Choose a reason for hiding this comment

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

I completely misunderstood what you were asking, sorry. Earlier in this thread, you had used Optional on that line as an example. I was under the impression that you were suggesting that should be the production code for a reason I had not looked hard enough to understand.
I have now removed that and pushed that change.

Copy link
Owner

Choose a reason for hiding this comment

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

Apologies for confusing things, I should have included my guesses at the full type signature.

… which I think should be:

# type: (str, str, bool, Optional[PhoneNumber], bool) -> PhoneNumber

(i.e. the function always returns a PhoneNumber object not None, but the 4th parameter can be either None or PhoneNumber)

WDYT?

@@ -2772,6 +2778,7 @@ def _set_italian_leading_zeros_for_phone_number(national_number, numobj):

def parse(number, region=None, keep_raw_input=False,
numobj=None, _check_region=True):
# type: (str, str, bool, **Any, bool) -> None
Copy link
Owner

Choose a reason for hiding this comment

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

I've got a hack to do mypy checking in #203; it will be interesting to see how it combines with this PR…

@daviddrysdale
Copy link
Owner

@mitchellkennedy thanks for getting the ball rolling with this – your initial type annotations look to match those in #207, so that's been helpful as a cross-check.

(Closing because I think this is now subsumed by #207)

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