feat: remove enum unions from use in ServiceInfo #1391
Closed
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Working towards issue #1287, this draft PR removes the use of
str
andenum
member unions connected to theops.pebble.ServiceInfo
class.In this PR I try to remove all such unions to simplify types. However, I wonder if it would be better to continue to allow the use of both
str
andenum
members as arguments in some cases (constructors) where removing the union might result in a breaking api change.Previously a
str
argument that anops
enum
doesn't know about would result in the use of thestr
, while a known argument would use the correspondingenum
member. Instead, we now use anUNKNOWN
enum member in this case.This is a behaviour change, but shouldn't be a breaking change, because the only time you should be using such values would be if you were developing for a more recent Pebble version than
ops.pebble
knows about. Sinceops.pebble
is presumable up to date currently, this will only be the case on olderops
versions, which this PR does not apply to. However, it will be worth mentioning this behaviour change in the appropriate release notes.An example of the core behaviour change, in the
ServiceInfo.from_dict
method:This leads us to the
__init__
method, where we also have unions to remove. I want to highlight this change because it's potentially breaking.This is a breaking change, as the
pebble
module is public (explicitly exported inops.__init__.__all__
), andServiceInfo
is a public member of that module. Previously a user could expect to be able to callServiceInfo("foo", startup="enabled", current="active")
if they wanted to. However:startup
andcurrent
are assigned in__init__
without any runtime validation. This leads to unexpected behaviour here:This may have been written with the assumption that
ServiceStatus.ACTIVE == 'active'
, the way it would with a normal class (is this how enums in other languages most commonly work?). If that were the case, then you'd expect self.current being a union ofstr
andenum
members to just work. Instead,ServiceStatus.ACTIVE
is an object oftype
<enum 'ServiceStatus'>
andServiceStatus.ACTIVE.value == "hello"
, so this will returnFalse
ifself.current == 'active'
.ServiceInfo
from arbitrary arguments isn't really the intended use case, as you want information about actually running services. You're more likely to callops.pebble.get_services
, or a bit lower level, callops.pebble.ServiceStatus.from_dict
with a dict representation of the services.This leads me to think this breaking change may be ok. Currently the change will only result in a type checking error, which if ignored will lead to the same (broken) runtime behaviour as previously.
We could avoid the breaking change and handle things better at runtime (fixing the issue with
is_running
) by continuing to accept unions for the__init__
args and turning them intoenum
members before assigning them. Thefrom_dict
logic could be moved to__init__
to handle this.The final instance of
str
andenum
member unions related to this class is_ServiceInfoDict
. Removing this union leads to another potentially breaking change._ServiceInfoDict
is part of the signature of the publicServiceInfo.from_dict
method, and also used to annotate a value returned from an external Pebble call, which could never actually contain Pythonenum
members. Without introducing more type variables, the correct change when removing these unions should therefore be:But again this is technically a breaking change, as previously a user could expect to validly call:
This still works at runtime, because
ServiceStartup(ServiceStartup.ENABLED)
andServiceStartup('enabled')
both produceServiceStartup.ENABLED
, but potentially introduces a new type checking error for users. Again though, using a hand crafted dictionary rather than one that ultimately comes from Pebble itself is less likely here (what about unit tests?).We could avoid a breaking change here by continuing to accept the original
_ServiceInfoDict
type infrom_dict
.Do we want to remove all the uses of
str
andenum
member unions as a goal in itself to simplify typing? If so, this may lead to some breaking changes.Alternatively, if the goal is just to switch to using an
UNKNOWN
enum member instead of passing along unknown string values, then I think we'll need to keepstr
andenum
member unions in some places.