-
Notifications
You must be signed in to change notification settings - Fork 417
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
Enhancement: add type annotations to API surface #200
Comments
Thanks for the suggestion. I've been thinking about the problem and I'm not currently inclined to add manual type checking for this particular entrypoint, for a few reasons:
I think the right solution for modern Python would be to add type annotations across the whole library API surface. However, there's a couple of problems with this:
(I'll leave this issue open in case I get superfluous leisure to investigate further) |
I agree entirely with your assessment. Admittedly, I have not explored the library all that extensively and was assuming the parse function would be a singular- or one of a few functions that had a very high likelihood of a user accidentally passing an int. |
I've only had a quick skim of the Python typing stuff, but it seems like earlier Python versions (2.x, maybe also 3.y for y<5) would need comment based type annotations, whereas later Python versions could use So adding annotations now would require the comment variant, but at some point in the future the direct annotations would be more appropriate. (However, I may not have correctly understood what's available in what version.) |
I believe this to be correct. Using comment syntax should add no dependencies while being fully supported across all Python versions. While true that at some point in the future when older Python version support is dropped, it would be appropriate to migrate to function/variable annotations, refactoring should not be overly tedious. |
Assuming that comment-based type annotations work across all Python versions (2.x & 3.x), then it would be great if someone had the time to do a PR to add them! (If anyone does take this on, please drop a comment here to indicate interest and maybe start with a trial change that only updates a couple of API entrypoints.) |
I had a few hours and added some test annotations to 7 functions: parse, as it was the one being discussed, and the first 6 sequentially in the file. This works as expected in Pycharm for all tested versions. |
@daviddrysdale / @mitchellkennedy -- I went through and had a go at this for a personal project a few months ago, using I was only using https://github.com/AA-Turner/python-phonenumbers-stubs/tree/master/phonenumbers Info on pyi / stub files: https://mypy.readthedocs.io/en/stable/stubs.html#creating-a-stub A |
@AA-Turner , thanks for the suggestion – More generally, I'm struggling a bit with using % mypy-3.9 phonenumbers --exclude phonenumbers/pb2 --show-error-code
Success: no issues found in 595 source files but the checker doesn't seem to notice if I deliberately introduce a type error: % git diff
diff --git a/python/phonenumbers/phonenumberutil.py b/python/phonenumbers/phonenumberutil.py
index 6a73a7c47b26..73b4c175957b 100644
--- a/python/phonenumbers/phonenumberutil.py
+++ b/python/phonenumbers/phonenumberutil.py
@@ -730,7 +730,7 @@ def length_of_geographical_area_code(numobj):
if metadata.national_prefix is None and not numobj.italian_leading_zero:
return 0
- ntype = number_type(numobj)
+ ntype = number_type("string")
country_code = numobj.country_code
if (ntype == PhoneNumberType.MOBILE and
(country_code in _GEO_MOBILE_COUNTRIES_WITHOUT_MOBILE_AREA_CODES)): So I suspect I need to learn more about what Python type annotations are capable of. |
.pyi files work with pycharm (if you middle click a standard library function there's often an asterisk next to the line number that will switch between the Python and the type annotations) - almost certainly works with VS code etc but don't have first hand experience. Very odd on the deliberate error not being caught -- I don't have access to an IDE right now but should be able to check later in the week. This is more likely to be my misconfiguration than a problem in the type checker, if I had to guess. A |
It might well be user error… |
Having done some reading, it isn't user error! It turns out that mypy assumes
Therefore I would suggest for now just the "describe the API" part of static typing, with the "type check library code" bit to be deferred. This is mainly just to split up the work // for pragmatism purposes. Out of interest, I managed to use https://github.com/ambv/retype to do the "type check library code" work -- this led to some corrections in the stubs, and also raised some type errors -- for example in python-phonenumbers/python/phonenumbers/phonemetadata.py Lines 327 to 338 in bd23712
Around 160 "errors" were raised in total -- nine tenths of these are due to nullable values, which could be tightened up via A |
One minor, one more significant spotted at: #200 (comment)
Thanks for the information; I think the distinction between "describe the API types" and "type-check the library itself" is very helpful. In the near term, using (In the longer term, I imagine I'll eventually drop support for Python 2.x and then the annotations can be merged from the Thanks also for the find in |
How should we proceed RE adding Nothing else that I'd describe as bugs, though there are two things that the type checker throws a bit of a hissy fit over -- reusing variable names in the same scope with different types. This happens once in the function mentioned above ( python-phonenumbers/python/phonenumbers/phonemetadata.py Lines 331 to 338 in bd23712
python-phonenumbers/python/phonenumbers/phonemetadata.py Lines 543 to 557 in bd23712
|
One minor, one more significant spotted at: #200 (comment) Also rename reused `loader` variable to help avoid false positives.
A PR with your stubs would be great, thank you. There's a couple of things I'd like to investigate before merging, though:
I've also tried to refactor around the current false positives – does #205 look like it would fix things? |
I found python/typeshed#754 and python/mypy#5028, which pointed me to The test suite can't be easily used as it is untyped, so then you either get into a chicken and egg situation of factoring all of the test utilities into a distinct files that has a
No (ish) -- I've included the
Looks good! The other types of places are in e.g.
The same applies to mypy also found that A |
Oh, and Fix here would be to do as with e.g. def __eq__(self, other):
if not isinstance(other, _BlockRange):
return NotImplemented
return (self.start == other.start and self.end == other.end) Now, I'd hope consumers aren't doing comparisons of internal private unicode helper types, but good to be thorough! Edit: see PR #208 -- the issues above are more design decision level, so haven't done a PR for them (yet) A |
One minor, one more significant spotted at: #200 (comment) Also rename reused `loader` variable to help avoid false positives.
I think most of these cases can't end up with
Nice spot, fix in #213. |
Fixed in #207 – many thanks @AA-Turner ! |
Brilliant! Thanks for the reviews :) And if you move to Python versions that support inline annotations, the offer is very much open to move the stubs inline if wanted -- hopefully that would be a quicker review! A |
I believe exceptions due to type errors when trying to parse the wrong datatype should be more clear.
I recently ran into a situation whereby I was inadvertently passing a PhoneNumber object type to the parse function. This resulted in the following error:
AttributeError: 'PhoneNumber' object has no attribute 'find'
Despite the fact I was passing a PhoneNumber object, it was not obvious to me in the moment that I needed to be passing a string based on the exception, which sent me investigating why that object lacked the attribute being called instead of letting me know that I was experiencing a TypeError and that the function was expecting a string in the first place.
This becomes more ambiguous if a user feeds the function an int, (which seems a much more likely mistake) which returns the following exception:
TypeError: object of type 'int' has no len()
The following code produces these exceptions depending on what is printed:
I believe a simple check of the number variable that raises a TypeError if not a string type and lets the user know the function is expecting a string would be helpful, and I would be happy to raise a pull request with this change.
Thank you.
The text was updated successfully, but these errors were encountered: