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

fix: Retry constructors methods support None #592

Merged
merged 8 commits into from
Feb 5, 2024
Merged

Conversation

daniel-sanche
Copy link
Contributor

Fixes #591

v2.16.0 did some refactoring of the Retry class, including adding a _replace helper method to simplify the constructors with_deadline, with_timeout, with_predicate, and with_delay. But it was noticed there is a bug in the implementation, where falsy values are ignored, with the existing value silently used instead.

Note that None is technically not supported by the type annotations, and 0 is typically not a valid value for these fields, which is likely why there was a gap in the tests around this. But the annotations were added recently, and existing code use pass None in for a timeouts, and None is supported by the internal retry code down the stack. For this reason, I changed the type annotation for the timeout field to support None as part of this fix, and added tests to ensure that it will be supported in the future

@daniel-sanche daniel-sanche requested review from a team as code owners January 31, 2024 23:54
@product-auto-label product-auto-label bot added the size: m Pull request size is medium. label Jan 31, 2024
Copy link
Contributor

@vchudnov-g vchudnov-g left a comment

Choose a reason for hiding this comment

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

Some questions and test suggestions, but the core functionality LGTM.

Comment on lines +153 to +154
if not use_deadline
else retry_.with_deadline(value)
Copy link
Contributor

Choose a reason for hiding this comment

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

Under the hood, with_deadline is just calling with_timeout, so these two branches wind up testing the same code, module the trivial wrapping function. Right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I believe so. I think the intention is to make sure they stay in-line through any potential future refactors

@@ -318,26 +309,42 @@ def with_predicate(self, predicate: Callable[[Exception], bool]) -> Self:
Returns:
Retry: A new retry instance with the given predicate.
"""
return self._replace(predicate=predicate)
return type(self)(
predicate=predicate,
Copy link
Contributor

Choose a reason for hiding this comment

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

Are we allowing predicate to be None here? If so, we would need to tweak the type annotation, and to add a test for this edge case? (The test might be starting with something with a non-trivial predicate, and then verifying that calling .with_predicate(None) sets it to None?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It seems like predicate has always been a required variable

Copy link
Contributor

@vchudnov-g vchudnov-g Feb 5, 2024

Choose a reason for hiding this comment

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

Well, in _replace, we had predicate: Callable[[Exception], bool] | None = None,, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, I missed this comment

In the _replace helper, None represented a variable that isn't replaced (that is, it used the same value as the original Retry object).

But this doesn't work if users are passing in None for a with_timeout and expecting it to disable timeouts, which wasn't supported by the type annotations, but was used in practice. Which was the motivation for this change

return self._replace(initial=initial, maximum=maximum, multiplier=multiplier)
return type(self)(
predicate=self._predicate,
initial=initial if initial is not None else self._initial,
Copy link
Contributor

Choose a reason for hiding this comment

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

If these values are not None but still falsy, we still want to use them? If so, we should capture this in a test since this differs from the previous behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah it differs from the previous behaviour, but it is in line with the pre-2.16.0 behaviour

Good point on the test. I had added one for timeout, but not for this one. Fixed

Copy link
Collaborator

Choose a reason for hiding this comment

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

@daniel-sanche Please could you link the specific commit for the fix in #592 (comment)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sorry, forgot to push. The new test should be there now

@parthea parthea assigned daniel-sanche and unassigned parthea and vchudnov-g Feb 4, 2024
@daniel-sanche daniel-sanche enabled auto-merge (squash) February 5, 2024 22:07
@daniel-sanche daniel-sanche merged commit 416203c into main Feb 5, 2024
26 checks passed
@daniel-sanche daniel-sanche deleted the fix_replace branch February 5, 2024 22:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
size: m Pull request size is medium.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

API ignores user defined timeout and applies default timeout
3 participants