Skip to content

BUG [units]: fix signature incompatibilities between helper and helped functions - #15710

Merged
mhvk merged 5 commits into
astropy:mainfrom
neutrinoceros:tst/detect_incompatible_helper_signature
Dec 14, 2023
Merged

mhvk merged 5 commits into
astropy:mainfrom
neutrinoceros:tst/detect_incompatible_helper_signature

Conversation

@neutrinoceros

Copy link
Copy Markdown
Contributor

Description

Will close #15703
Opening as a draft with only the new tests. I'll address failures next.

ping @hamogu @mhvk

  • By checking this box, the PR author has requested that maintainers do NOT use the "Squash and Merge" button. Maintainers should respect this when possible; however, the final decision is at the discretion of the maintainer that merges the PR.

@github-actions github-actions Bot added the units label Dec 11, 2023
@github-actions

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Astropy! 🌌 This checklist is meant to remind the package maintainers who will review this pull request of some common things to look for.

  • Do the proposed changes actually accomplish desired goals?
  • Do the proposed changes follow the Astropy coding guidelines?
  • Are tests added/updated as required? If so, do they follow the Astropy testing guidelines?
  • Are docs added/updated as required? If so, do they follow the Astropy documentation guidelines?
  • Is rebase and/or squash necessary? If so, please provide the author with appropriate instructions. Also see instructions for rebase and squash.
  • Did the CI pass? If no, are the failures related? If you need to run daily and weekly cron jobs as part of the PR, please apply the "Extra CI" label. Codestyle issues can be fixed by the bot.
  • Is a change log needed? If yes, did the change log check pass? If no, add the "no-changelog-entry-needed" label. If this is a manual backport, use the "skip-changelog-checks" label unless special changelog handling is necessary.
  • Is this a big PR that makes a "What's new?" entry worthwhile and if so, is (1) a "what's new" entry included in this PR and (2) the "whatsnew-needed" label applied?
  • Is a milestone set? Milestone must be set but we cannot check for it on Actions; do not let the green checkmark fool you.
  • At the time of adding the milestone, if the milestone set requires a backport to release branch(es), apply the appropriate "backport-X.Y.x" label(s) before merge.

@github-actions

Copy link
Copy Markdown
Contributor

👋 Thank you for your draft pull request! Do you know that you can use [ci skip] or [skip ci] in your commit messages to skip running continuous integration tests until you are ready?

@neutrinoceros

Copy link
Copy Markdown
Contributor Author

I've started fixing clear incompatibilities discovered with my test. The remaining failures are a mixed bag and will require more careful examination, as I suspect there are some false positives.

@neutrinoceros neutrinoceros changed the title TST: add meta tests for units.quantity_helper.function_helper to validate signature compatibility between helped and helper functions BUG [units]: fix signature incompatibilities between helper and helped functions Dec 11, 2023
@neutrinoceros
neutrinoceros force-pushed the tst/detect_incompatible_helper_signature branch from b8fc2b2 to 6047c74 Compare December 11, 2023 13:17
@neutrinoceros

Copy link
Copy Markdown
Contributor Author

I found that np.histogram, np.histogram2d and np.histogramdd underwent a unique change in numpy 1.24 as they lost their normed (positional-or-keyword) argument and the relative positions of density and weights (which are also positional-or-keyword) were swapped. This means that the simpler way to wrap them correctly in all supported versions is to use *args + **kwargs, but this would make their implementation more complicated. More importantly, the fact that density and weights are already present in the helper function's signature as positional-or-keyword, but with the pre-numpy 1.24 order makes it impossible to preserve 100% backward compatibility for astropy users and correctly replicate the current signature at the same time. Assuming that that usage of these function with 4 or 5 positional arguments is rare enough, maybe correctness should win over backward compatibility here ? I'm asking for @mhvk's guidance here.

@neutrinoceros
neutrinoceros force-pushed the tst/detect_incompatible_helper_signature branch from 6047c74 to 4a12859 Compare December 11, 2023 13:55
@neutrinoceros
neutrinoceros force-pushed the tst/detect_incompatible_helper_signature branch from 4a12859 to ad7be42 Compare December 11, 2023 14:23
@neutrinoceros

Copy link
Copy Markdown
Contributor Author

I'm hitting some failures that I don't immediately understand that apparently show that my parametrised test behaves funky when ran in parallel sessions ?
Any help with that would be very much appreciated. Meanwhile, I'll go to pytest to report a seemingly different problem I've encountered while writing this patch.

@hamogu

hamogu commented Dec 11, 2023

Copy link
Copy Markdown
Member

It is interesting that the error happens in the test collection, not when the test is actually run in parallel. Maybe that's a symptom of the same limitation that already led you to duplicate the parameterize fixture? You could probably bypass that (at the cost of some more duplicated lines of code) by breaking the class up into separate test functions?

@neutrinoceros

Copy link
Copy Markdown
Contributor Author

It is interesting that the error happens in the test collection, not when the test is actually run in parallel. Maybe that's a symptom of the same limitation that already led you to duplicate the parameterize fixture?

yes I think it may be due to the fact that I used a consumable iterator (itertools.chain) for params. I think I fixed it now, let's see.

@pllim pllim added this to the v6.1.0 milestone Dec 11, 2023
@pllim pllim added API change PRs and issues that change an existing API, possibly requiring a deprecation period Bug labels Dec 11, 2023
@neutrinoceros
neutrinoceros force-pushed the tst/detect_incompatible_helper_signature branch from 28fdac2 to 95f4625 Compare December 11, 2023 18:11

@mhvk mhvk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Really nice! I need to look through the test function logic with perhaps a bit more detail, but I like how it already found quite a few little buglets (and numpy illogicalities!).

Comment thread docs/changes/units/15710.bugfix.rst Resolved
@neutrinoceros

Copy link
Copy Markdown
Contributor Author

Thanks @mhvk for your review, I think I've addressed all your points !
Now the one thing we need to discuss is what to do about histogram functions (see my comment #15710 (comment)), since it blocks tests with oldest supported dependencies. Let me know what you think !

@neutrinoceros
neutrinoceros force-pushed the tst/detect_incompatible_helper_signature branch 3 times, most recently from 60e160f to 89fbf28 Compare December 12, 2023 11:38
@neutrinoceros

Copy link
Copy Markdown
Contributor Author

Actually I think I found a simple-ish solution for np.histogram and friends, and now CI is back to all-green !

@neutrinoceros
neutrinoceros marked this pull request as ready for review December 12, 2023 11:39

@mhvk mhvk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just a few things; still have to go properly through the class you added (though it looks good!).

@mhvk mhvk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

OK, it is looking very good. Some comments on the actual test class, mostly to make it clearer (for me at least...)

Also, ideally the same tests are run for Masked, though I think it is fine to do that as follow-up...

assert (
have_args_helper and have_kwargs_helper
), f"argument {nt!r} is not re-exposed as positional-or-keyword"
if pt.kind is VAR_POSITIONAL:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

replace pt.kind with kt here too (or use pt.kind everywhere -- may be a bit clearer)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Also, this can be an elif

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

elif statements seem redundant with continue (which I just added following another of your suggestions !), and having them all be if makes it slightly more readable (to me ?) because it forces alignement and makes it easier to spot differences.

), f"argument {nt!r} is not re-exposed as positional-or-keyword"
if pt.kind is VAR_POSITIONAL:
assert have_args_helper, "helper is missing a catch-all *args argument"
if pt.kind is VAR_KEYWORD:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Another elif

…ignatures

TST: add meta tests for units.quantity_helper.function_helper to validate signature compatibility between helped and helper functions
@neutrinoceros
neutrinoceros force-pushed the tst/detect_incompatible_helper_signature branch from 473300d to d8e7942 Compare December 13, 2023 14:05
@neutrinoceros

Copy link
Copy Markdown
Contributor Author

@mhvk I just integrated all your recent suggestions in a rebase. To help you reviewing again, I kept all changes more recent than your review in a separate commit, but everything else was squashed !

Also, ideally the same tests are run for Masked, though I think it is fine to do that as follow-up...

I'll add it to my todo list, and will work on it after this one is dealt with !

@mhvk mhvk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One little buglet, otherwise all nitpicks - should be ready to go in with these addressed.


@function_helper(helps=(np.isin, np.in1d)) # np.in1d deprecated in not NUMPY_LT_2_0.
def setcheckop(ar1, ar2, *args, **kwargs):
@function_helper # np.in1d deprecated in not NUMPY_LT_2_0.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Here, the comment is misleading, since this is for isin

def setcheckop(ar1, ar2, *args, **kwargs):
@function_helper # np.in1d deprecated in not NUMPY_LT_2_0.
def isin(element, test_elements, *args, **kwargs):
# This tests whether ar1 is in ar2, so we should change the unit of

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I guess here and below we might as well adjust the names to the new ones.

args_helper = list(params_helper.items())

pos_helper = 0
for nt, pt in args_target:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

With the for loop, you can just replace with params_target.items() and remove the above definition.

params_helper = sig_helper.parameters

for kind in (POSITIONAL_ONLY, POSITIONAL_OR_KEYWORD):
args_target = self.get_param_group(params_helper, [POSITIONAL_ONLY])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Here, you forgot to replace [POSITIONAL_ONLY] with [kind]!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

good catch !

if (nhelper := len(args_helper)) > (ntarget := len(args_target)):
unknown: list[str] = args_helper[ntarget:]
raise AssertionError(
f"Found unknown {str(kind).lower().replace('_', '-')} parameter(s) "

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For an error message, I'd not string-massage something that may change, i.e., just do unknown {kind} parameter(s)

I guess here the assert statement became just too unwieldy, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I guess here the assert statement became just too unwieldy, right?

Correct ! that's exactly why I used raise AssertionError 😉

Comment thread docs/changes/units/15710.bugfix.rst Resolved
@neutrinoceros

Copy link
Copy Markdown
Contributor Author

@mhvk Thank you so much for your careful review. I think I've addressed or answered all your comments now.

@mhvk mhvk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Super, looks all good. Thanks very much for creating this -- it should be a big help to keep things in sync.

@mhvk
mhvk merged commit f87e75d into astropy:main Dec 14, 2023
@pllim

pllim commented Dec 14, 2023

Copy link
Copy Markdown
Member

Hmm do we have to backport this to make numpy-dev compat backports easier for 6.0.x?

@mhvk

mhvk commented Dec 14, 2023

Copy link
Copy Markdown
Contributor

Hmm do we have to backport this to make numpy-dev compat backports easier for 6.0.x?

Good point. I think this is reasonably safe to backport, and it does fix one bug (new period argument for unwrap). From that respect, backporting seems sensible. But people have been a bit wary of anything labeled "API change"... (which in this case is extremely minor, but still). I guess the alternative would be to just backport the actual change. @neutrinoceros - what do you think?

@neutrinoceros

Copy link
Copy Markdown
Contributor Author

To me it would make sense to backport, but it's also fine not to. If you guys think there's a chance that backporting would be controversial, I don't think it's worth it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

API change PRs and issues that change an existing API, possibly requiring a deprecation period Bug Refactoring units

Projects

None yet

Development

Successfully merging this pull request may close these issues.

API [units]: how should we handle forward compatibility against numpy's evolving interfaces ?

4 participants