-
Notifications
You must be signed in to change notification settings - Fork 128
fix: make testing.CheckInfo arg types match pebble.CheckInfo #2274
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
base: main
Are you sure you want to change the base?
fix: make testing.CheckInfo arg types match pebble.CheckInfo #2274
Conversation
| _: dataclasses.KW_ONLY | ||
|
|
||
| level: pebble.CheckLevel | None = None | ||
| level: pebble.CheckLevel | 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.
With init=False, these are now only attribute annotations, so the default values can go, as they're redundant with those defined in the __init__ method.
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.
There is also a documentation change, from:
To:
I do find it convenient to look at the field and see that the default is None, rather than having to scroll up and look at the __init__. However, I don't think we want to have the default in both places since then there's the risk that they get out of sync.
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.
| self, | ||
| name: str, | ||
| *, | ||
| level: pebble.CheckLevel | str | None = 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.
This is the key change from previously -- we now accept str as well.
| status: pebble.CheckStatus = pebble.CheckStatus.UP, | ||
| successes: int | None = 0, | ||
| failures: int = 0, | ||
| threshold: int | None = 3, |
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.
This is also a change from the previous behaviour, as noted in the PR description -- accepting None (and converting it to 0 below). Perhaps it should be dropped from this PR, or perhaps pebble.CheckInfo should be updated to match, or perhaps it's fine as-is, I'm not sure.
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.
Do we know that converting None (from a pebble.CheckInfo) to 0 is the correct move, from a Pebble point of view?
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.
threshold apparently being able to be None originates from pebble.Check (and pebble.CheckDict). I assumed that None and 0 should be treated equivalently because when we turn a Check into a dict we skip all fields with False-y values.
On the pebble side, threshold can't be nil. IIRC Pebble always uses 'omit empty' when sending JSON, so it would only be missing (and thus None on the Python side) if it was set to 0. However, the default threshold is 3, and it doesn't make any sense for the threshold to be 0 (health check fails if there are 0 failures in a row), so I'm guessing the zero-so-omit case never comes up in the real world?
This makes it seem like the right thing is to make pebble.Check.threshold not be None-able, which is a bit of a bigger question, so maybe it's best to leave changes to testing.CheckInfo.threshold out of this PR.
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.
threshold can be None in Check (the thing you use to build a layer), but it cannot be None in a CheckInfo (the thing you get from Pebble when you ask about the current status of a check).
It makes no sense for threshold to be None here, because it'll always be in the Pebble response, so we should definitely not make this change. successes can only be None because it's supporting old Pebble versions where we didn't have that field.
For a Check, None means "use the default value". That does make sense, and should also not be changed.
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.
thresholdcan beNoneinCheck(the thing you use to build a layer), but it cannot beNonein aCheckInfo(the thing you get from Pebble when you ask about the current status of a check)....
For a
Check,Nonemeans "use the default value". That does make sense, and should also not be changed.
I'm fine with dropping all the changes to threshold from this PR, and good to know that it's intended functionality for threshold to be None-able in Check objects (I guess when constructing them manually? Or does Pebble omit threshold from Check too?).
But I'm wondering, if testing.CheckInfo(threshold=my_check.threshold, ...) is a useful pattern, then should passing None to the CheckInfo constructor also mean "use the default value" (3)?
benhoyt
left a comment
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.
Thanks for this. I definitely think it'd be a good idea to add a test of this new behaviour / typing.
A thought on a possible way to simplify: could you define a _CheckInfo dataclass with most of the fields, and then inherit CheckInfo from that to add the special fields, calling the super()'s __init__ so you only need to special case the special fields?
| status: pebble.CheckStatus = pebble.CheckStatus.UP, | ||
| successes: int | None = 0, | ||
| failures: int = 0, | ||
| threshold: int | None = 3, |
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.
Do we know that converting None (from a pebble.CheckInfo) to 0 is the correct move, from a Pebble point of view?
| threshold: int | None = 3, | ||
| change_id: pebble.ChangeID | None = None, | ||
| ): | ||
| object.__setattr__(self, 'name', name) |
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'm having a thinko -- why do we need object.__setattr__ here, instead of just self.foo = foo?
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.
Because this dataclass has frozen=True -- the __init__ method that dataclasses would generate with init=True would also use object.__setattr__, and we do the same in __post_init__.
Will do.
I think that would work, but I'm not convinced it's truly simpler. TBH I think we should be moving in the direction of having a custom |
Fair enough. It's definitely more direct and flatter to do what you're suggesting. I don't love the |
I agree with this (we've talked about this before, I think). Perhaps worth creating a ticket? To record some history: we had a version with custom However, we don't need that any more (since we're using 3.10+), so some of the classes are perfectly fine with the dataclasses default |
tonyandrewmeyer
left a comment
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 haven't tested this out directly, but will do that after the fixes are done.
| _: dataclasses.KW_ONLY | ||
|
|
||
| level: pebble.CheckLevel | None = None | ||
| level: pebble.CheckLevel | 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.
There is also a documentation change, from:
To:
I do find it convenient to look at the field and see that the default is None, rather than having to scroll up and look at the __init__. However, I don't think we want to have the default in both places since then there's the risk that they get out of sync.
| status: pebble.CheckStatus = pebble.CheckStatus.UP, | ||
| successes: int | None = 0, | ||
| failures: int = 0, | ||
| threshold: int | None = 3, |
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.
threshold can be None in Check (the thing you use to build a layer), but it cannot be None in a CheckInfo (the thing you get from Pebble when you ask about the current status of a check).
It makes no sense for threshold to be None here, because it'll always be in the Pebble response, so we should definitely not make this change. successes can only be None because it's supporting old Pebble versions where we didn't have that field.
For a Check, None means "use the default value". That does make sense, and should also not be changed.
Co-authored-by: Tony Meyer <tony.meyer@gmail.com>
|
Thanks for the feedback. Still need to figure out exactly what to do for Tests are definitely needed here, I'll add them and re-request review at that point. |
The signature of
testing.CheckInfodoesn't quite match that ofpebble.CheckInfo. Whilepebble.CheckInfowill accept astrforlevel, thetestingversion only accepts apebble.CheckLevel | None. This is a nicer experience for users of thetesting.CheckInfo.levelattribute, but leads to a type error when passing apebble.Check.levelto thetesting.CheckInfoconstructor. This was reported in #1790.I believe the correct solution is to broaden the types accepted by the
testing.CheckInfoconstructor without broadening the correspondinglevelattribute. This PR does so by settinginit=Falseand adding a custom__init__method totesting.CheckInfo. This makes the argument types match and resolves this type checking error, while retaining the nicer attribute typing.While investigating this, I noticed that there is a similar issue with
pebble.Check.thresholdbeingNone-able, while thethresholdargument totesting.CheckInfois not. This differs from thelevelcase in thatpebble.CheckInfodoes not acceptNonefor thethresholdargument. Currently this PR 'fixes' this fortesting.CheckInfoby again broadening the types accepted by the constructor, while keeping the attribute the same, however this introduces a new mismatch between thepebbleandtestingversions of this class. Perhaps this enhancement should be dropped from this PR, or perhaps the PR should also include applying it topebble.CheckInfo.Fixes: #1790
The file inlined below (adapted from the example in the issue) can be used to validate these changes -- perhaps it's worth adding similar constructs to one of our tests?
On
mainwe get these errors: