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
add unit-aware quantity annotations. #10662
add unit-aware quantity annotations. #10662
Conversation
This comment has been minimized.
This comment has been minimized.
Maybe this is a little too ahead of its time right now. We should wait for |
This comment has been minimized.
This comment has been minimized.
What about the other parts of the PR? The |
@nstarman - my sense would indeed be to make the p.s. Barely looked at the actual code. General question: should our type be a sub-type of ndarray? (if there is such a thing). |
From what I've read, static typing with class annotations follows the expected MRO. I believe this means that I definitely need to do some checking on covariance / contravariance of subclasses, but I don't think this will be an issue. I think the relevant points are mentioned in https://docs.python.org/3/library/typing.html#typing.Type |
I'm trying to find a good place to put the utility function def _parse_allowed_type_hint(arg, detailed_exception=True):
try: # unit passed in as a string or unit or Quantity
unit = Unit(arg)
except ValueError: # physical type or mistake
from astropy.units.physical import _unit_physical_mapping # TODO move import
if arg in _unit_physical_mapping: # valid physical type
unit = arg # pass through unchanged
else: # Function argument arg is invalid
if detailed_exception:
raise ValueError(
(
f"Invalid unit or physical type '{arg}'.\n"
f"Did you mean: {'TODO!'}"
# TODO did_you_mean(arg, candidates)
)
)
else:
raise ValueError(f"Invalid unit or physical type '{arg}'.")
return unit |
Hello @nstarman 👋! It looks like you've made some changes in your pull request, so I've checked the code again for style. There are no PEP8 style issues with this pull request - thanks! 🎉 Comment last updated at 2021-10-29 20:38:52 UTC |
2665c20
to
dca1876
Compare
Just a note to say that I plan to take a look at this by the end of this week - thanks for the idea here, @nstarman ! |
The code now compiles and has unit testing. If we want to go that route, it currently implements a version check for py3.7+ to add the Also, Also, though this PR skirts around the actual introduction of static typing, it comes close. I've put the code for |
@taldcroft This might still be a WIP since #10689 would obviate the need for an |
Hello. I converted your PR to a draft PR due to #10706 . |
3935d9e
to
347b697
Compare
@mhvk I finished implementing how static typing can work with single annotations. If you have time, I want to consult on the convention for multiple annotations.
I'm strongly inclined towards the latter for 2 reasons:
What do you think? |
@nstarman - on your possibilities, I agree with your option 2, i.e., not allowing multiple units. Partially, this is because I could imagine that we'll need to merge some of the numpy static annotation in there as well, i.e., things like If we do allow multiple units, they would definitely need to be their own tuple - but I think we can always deal with that as an extension. |
That's a use case I hadn't considered. What if we wanted to annotate something as a complex Quantity without specifying a unit? Right now If we want to force the 1st-is-unit convention, then we'd need a null unit as the first argument. |
Signed-off-by: Nathaniel Starkman (@nstarman) <nstarkman@protonmail.com>
Signed-off-by: Nathaniel Starkman (@nstarman) <nstarkman@protonmail.com>
should be reactivated why py3.9+ Signed-off-by: Nathaniel Starkman (@nstarman) <nstarkman@protonmail.com>
Signed-off-by: Nathaniel Starkman (@nstarman) <nstarkman@protonmail.com>
4ecce7d
to
1698a3d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really nice! I'm happy with the simplifications. A few nitpicky remaining comments...
astropy/units/decorators.py
Outdated
return ptype | ||
else: | ||
if isinstance(unit, StructuredUnit): | ||
raise ValueError(f"unit {target!r} cannot be structured.") from None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not super important, but perhaps good to check this warning is actually raised.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this was important, but I don't remember why, so it's a little hard to design a test! 🤦
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might it then be best to just remove it? If no tests fail, it cannot have been that important!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
1698a3d
to
2da5229
Compare
Just wanted to drop in and say that I'm excited for this PR to be merged! Thank you for working on it! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really only nitpicks left, with the largest one a suggested different import for a numpy version check. I'll approve so you can merge once those are addressed.
This is very nice!! So, probably another little TODO is to add to What's new next week!
astropy/units/decorators.py
Outdated
return ptype | ||
else: | ||
if isinstance(unit, StructuredUnit): | ||
raise ValueError(f"unit {target!r} cannot be structured.") from None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might it then be best to just remove it? If no tests fail, it cannot have been that important!
astropy/units/quantity.py
Outdated
`typing.Annotated`, `typing_extensions.Annotated`, `astropy.units.Unit`, or `astropy.units.PhysicalType` | ||
Return type in this preference order: | ||
`typing.Annotated` if python v3.9+; `typing_extensions.Annotated` | ||
if :mod:`typing_extensions` is installed, a `astropy.units.Unit` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe simplify: "... is installed; or an ...
" and then delete "else-wise, depending on the input."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just made it a bullet point list.
def test_multiple_annotation(self): | ||
@u.quantity_input | ||
def multi_func(a: Quantity[u.km]) -> Quantity[u.m]: | ||
return a.to(u.m) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this just return a
? If I understood the code right, the return annotation will actually enforce the unit, so the test would still pass.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
About this... I personally prefer if the code just validated the value, not enforced units, but I didn't want to change the behavior.
Part of the reason I prefer just validation is consistency with the behavior for the inputs. Also the quantity_input
refactor is going to enable unit enforcement on inputs and outputs in a subclass decorator. Also fine-grained user control on a per-input / output basis.
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed that -> Quantity['length']
should be possible too, and that quantity_input
should allow more control:
- Check quantities of right physical type
- Convert input/output to specific units
- As 2, but drop to array, and attach unit on output (Decorator for converting quantity to arrays in specific units? #7368)
For now, fine to leave the test as is!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM. Something to consider for #10655
Signed-off-by: Nathaniel Starkman (@nstarman) <nstarkman@protonmail.com>
2da5229
to
e6d147f
Compare
@nstarman - any specific things I should look at? I had approved already, so am in principle quite happy to just leave it to you. |
Looks like this PR is breaking #11942 and #11634. See: https://github.com/astropy/astropy/runs/4051645176?check_suite_focus=true |
Yeah. I'm working on a fix now. Sigh. Should have rebased on your fix before merging this. Passed all my tests in py3.9 |
Odd. Made a new venv locally with all same versions. Not getting these errors. |
It's passing on 3.9 and 3.10, but failing with Python 3.8. |
Yes, the broken 3.10 test did cause most other tests to be cancelled before, including the 3.8 ones... |
What platform is that? Having the same failures with
Perhaps as a quick fix just mark them skip for 3.8? Keeps all open PRs from running the complete test suite otherwise. |
SGTM. I'll push two fixes. The skip and and actual fix. |
Signed-off-by: Nathaniel Starkman nstarkman@protonmail.com
Description
A
__class_getitem__
method will be added toQuantity
to shortcut creating unit aware annotations.Quantity[<unit>, other_annotations] == Annotated[Quantity, <unit>, other_annotation]
.As a class method, this will similarly work for all subclasses of
Quantity
.The annotations used by
quantity_input
, likeu.km
ordistance
, are not compliant with static typing since the inputs are of (sub)typeQuantity
, but the annotations are not. This problem can be solved by thetyping.Annotated
class, from this PEP which allows valid static type annotations to include metadata. Here we implement the machinery to allow for unit-aware type annotations.However, until astropy supports the official backport
typing_extensions
or the minimum python version 3.9+, metadata cannot be associated withtyping
. So to be compatible withquantity_input
in this case a Quantity[unit] just returns the unit.Part Of #10655