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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

馃悜 Add seperate typing hints for humanize #150

Closed
wants to merge 7 commits into from

Conversation

coiax
Copy link

@coiax coiax commented Aug 26, 2020

Adds a collection of .pyi files that describe the expected type
signatures of the publicly available humanize functions.

A MANIFEST.in file has been added to include the new .pyi files and
the py.typed file to indicate to mypy that this package supports
typing.

mypy has been added to the pre-commit configuration to check the stubs.

Some of the signatures are slightly optimistic, since depending on error
conditions, different types are returned.

Adds a collection of `.pyi` files that describe the expected type
signatures of the publicly available `humanize` functions.

A `MANIFEST.in` file has been added to include the new `.pyi` files and
the `py.typed` file to indicate to mypy that this package supports
typing.

mypy has been added to the pre-commit configuration to check the stubs.

Some of the signatures are slightly optimistic, since depending on error
conditions, different types are returned.
@codecov-commenter
Copy link

codecov-commenter commented Aug 26, 2020

Codecov Report

Merging #150 into master will not change coverage.
The diff coverage is n/a.

Impacted file tree graph

@@            Coverage Diff            @@
##            master      #150   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            9         9           
  Lines          554       554           
=========================================
  Hits           554       554           
Flag Coverage 螖
#GHA_Ubuntu 100.00% <酶> (酶)
#GHA_Windows 100.00% <酶> (酶)
#GHA_macOS 100.00% <酶> (酶)

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage 螖
src/humanize/filesize.py 100.00% <酶> (酶)

Continue to review full report at Codecov.

Legend - Click here to learn more
螖 = absolute <relative> (impact), 酶 = not affected, ? = missing data
Powered by Codecov. Last update cca5a74...1cdd1d9. Read the comment docs.

src/humanize/filesize.py Show resolved Hide resolved
src/humanize/number.pyi Outdated Show resolved Hide resolved
@coiax
Copy link
Author

coiax commented Aug 26, 2020

In addition, I originally started these stub files thinking you couldn't use inline typing for Python 3.5, but it turns out that was incorrect.

It might be better in that case to make the typing inline, rather than a stub; depends on what people want.

@hugovk
Copy link
Collaborator

hugovk commented Aug 30, 2020

I haven't used typing hints much in Python yet, so thanks for the PR!

Support for Python 3.5 (EOL in two weeks) has now been dropped from master, I understand 3.6 has better support for typing?

I think inline typing makes more sense than stubs, that way it sits right next to the actual code. What do you think?

A MANIFEST.in file has been added to include the new .pyi files

This project uses setuptools_scm which means all files in source control are automatically included in the sdist, so a MANIFEST.in isn't needed (unless we wanted to include generated files, or exclude something).

@coiax
Copy link
Author

coiax commented Sep 30, 2020

Ah, this one dropped off my radar; apologies.

I'll move the stubs into the function definitions themselves.

@hugovk hugovk added the changelog: Added For new features label Sep 30, 2020
It is better to include typing hints directly in the functions that
they're describing; which allows mypy to actually check the functions,
both in their execution, and that they are actually returning the
appropriate values.

The major change here is that previously a number of the functions, if
they encountered a conversion error, would return the value unchanged.
However, that would require just declaring the return type as Any, which
although correct, isn't particularly useful.

So instead, any failed, unconvertable, inconsistent values will just
have `str` called on them, and the output will returned. The function
will always return a string, although sometimes it won't be in the
format you expect.

In addition, some internal variables inside the function have been
rearranged and moved around, to avoid changing the type of an already
defined variable.
Copy link
Collaborator

@hugovk hugovk left a comment

Choose a reason for hiding this comment

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

Thanks for this! I see it already found and you fixed some bugs it found!

Just a few small suggestions.

MANIFEST.in Outdated Show resolved Hide resolved
@@ -98,30 +105,29 @@ def naturaldelta(value, months=True, minimum_unit="seconds"):
tmp = Unit[minimum_unit.upper()]
if tmp not in (Unit.SECONDS, Unit.MILLISECONDS, Unit.MICROSECONDS):
raise ValueError(f"Minimum unit '{minimum_unit}' not supported")
minimum_unit = tmp
min_u = tmp
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's keep the original name here, it's more descriptive (or at least min_unit)

Copy link
Author

Choose a reason for hiding this comment

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

The problem is, the minimum_unit variable is defined in the function signature as a str, and mypy does not approve of reassigning variables to different types. I didn't want to change the name of the argument, because that could break other people calling the functions using keywords.

So the renaming is a bit awkward, but I'm not sure how else to do it.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah, I see.

Can we rename it to min_unit then?

Copy link
Author

Choose a reason for hiding this comment

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

Sure.

src/humanize/time.py Show resolved Hide resolved
@hugovk
Copy link
Collaborator

hugovk commented Oct 2, 2020

And of course the CI is failing:

E   AttributeError: module 'typing' has no attribute 'Literal'

@@ -98,30 +105,29 @@ def naturaldelta(value, months=True, minimum_unit="seconds"):
tmp = Unit[minimum_unit.upper()]
if tmp not in (Unit.SECONDS, Unit.MILLISECONDS, Unit.MICROSECONDS):
raise ValueError(f"Minimum unit '{minimum_unit}' not supported")
minimum_unit = tmp
min_u = tmp
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah, I see.

Can we rename it to min_unit then?

src/humanize/time.py Show resolved Hide resolved
@coiax
Copy link
Author

coiax commented Oct 7, 2020

@hugovk Would I be able to add typing_extensions to the development requirements? It contains backports of typing features that were added in versions later than Python 3.5 (like Literal). They wouldn't be imported or required in production, only during type checking.

Originally only included this file to include the `.pyi` files in the
package, but it turns out it was redundant, and we don't have `.pyi`
files anymore.
Instead of using `typing.Literal`, which was only added in Python 3.7,
make the type annotations strings, preventing their execution in the
interpreter, and use `typing_extensions` to provide Literal.
In order to make the typing accurate, the failure case was changed in
multiple functions from "return the object unchanged", to "return the
string representation of the object".

As such, a number of tests all needed to be updated.
@coiax
Copy link
Author

coiax commented Oct 7, 2020

@hugovk Okay, we're passing CI with everything, except what appears to be a bit of an unrelated failure on windows pypy3.

Is it going to be okay changing the expected behavior of the functions from "when failing, return the original object", to "when failing, return the string representation of the object"?

If we didn't want to make the API change, we could revert the behavior, and declare the functions in question to return Any, which although correct, is less useful from a typing perspective.

@hugovk
Copy link
Collaborator

hugovk commented Oct 7, 2020

@hugovk Would I be able to add typing_extensions to the development requirements? It contains backports of typing features that were added in versions later than Python 3.5 (like Literal). They wouldn't be imported or required in production, only during type checking.

Sure, but Python 3.5 has been dropped, 3.6 is the minimum now. Is it still needed? Fine to add it if you need backports from 3.7+.

@coiax
Copy link
Author

coiax commented Oct 7, 2020

typing.Literal was new in Python 3.8, so we'll still need it.

@hugovk
Copy link
Collaborator

hugovk commented Oct 7, 2020

@hugovk Okay, we're passing CI with everything, except what appears to be a bit of an unrelated failure on windows pypy3.

Let's see how the next push goes 馃

Is it going to be okay changing the expected behavior of the functions from "when failing, return the original object", to "when failing, return the string representation of the object"?

If we didn't want to make the API change, we could revert the behavior, and declare the functions in question to return Any, which although correct, is less useful from a typing perspective.

I'm a bit cautious. On the one hand, for success cases, we return a string anyway.

On the other, this could be considered a breaking change, and I wonder if people are relying on this behaviour.

Breaking changes are allowed with a major bump. But we just had 3.0 last week (Python 3.5 dropped), 2.0 in March (2.7 dropped) and 1.0 in February (first release in 5 years, other old versions dropped). So (if this is a breaking change) we could release 4.0 after some time with deprecation warnings.

But then on the third hand 馃檭, we may end up returning odd looking strings with stuff like this:

    try:
        value = int(value)
    except (TypeError, ValueError):
        return value

And I think Django's humanize has similar behaviour. So I'm leaning towards retaining the API.

@coiax
Copy link
Author

coiax commented Oct 8, 2020

I mean, why not both? We could change the return type to Any for now, and then, for the next major release, change the API to always return a string, and update the typing definitions at the same time?

Even a small amount of typing hints will be better than no typing hints.

@hugovk
Copy link
Collaborator

hugovk commented Oct 8, 2020

Let's leave the API as it is for now, and use Any for the returns. Thank you!

@rorybyrne
Copy link

Any chance of getting this merged? I've got mypy complaining about humanize

@hugovk
Copy link
Collaborator

hugovk commented May 3, 2022

馃殌 Development has moved to https://github.com/python-humanize/humanize 馃殌

Please open new PRs at https://github.com/python-humanize/humanize/pulls

@hugovk hugovk closed this May 3, 2022
@hugovk hugovk mentioned this pull request May 3, 2022
1 task
@hugovk
Copy link
Collaborator

hugovk commented May 3, 2022

@coiax I've redone this PR at python-humanize/humanize#15, please could you have a look? Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
changelog: Added For new features
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants