StringEnum
StringEnum is a StrEnum that converts a string to the member of that
value, and says so when it can’t.
Every enumeration whose members come from the outside - a command line, a configuration file, a REST reply -
needs the same three answers: what a missing value means, what a value of the wrong type is, and what a value no
member carries is. Written per enumeration, those answers drift; written once here, an enumeration adds its
members and inherits Parse().
The default is declared as an alias
Default is an alias of the member that stands for “nothing was given”. An alias, because that keeps it
out of the enumeration’s own list: it isn’t iterated, and it is not a second member to compare against - it is
the member it aliases.
Example:
from pyTooling.Common import StringEnum
class GanttFormat(StringEnum):
MatplotlibPNG = "matplotlib-png"
MatplotlibSVG = "matplotlib-svg"
Default = MatplotlibPNG
GanttFormat.Parse("matplotlib-svg") # GanttFormat.MatplotlibSVG
GanttFormat.Parse(None) # GanttFormat.MatplotlibPNG
list(GanttFormat) # [MatplotlibPNG, MatplotlibSVG] - no third entry
An enumeration declaring no Default answers None instead, which is what a field that may legitimately be
absent wants - a workflow run has no conclusion while it is still running:
class Conclusion(StringEnum):
Success = "success"
Failure = "failure"
Conclusion.Parse(None) # None
Conclusion.Parse("") # None
What Parse rejects
Argument |
Answer |
|---|---|
|
|
A value a member carries |
That member. |
A value no member carries |
|
Anything that isn’t a |
|
Example:
GanttFormat.Parse("matplotlib-gif")
# ValueError: 'matplotlib-gif' is not a valid GanttFormat.
# Allowed values: matplotlib-png, matplotlib-svg.
GanttFormat.Parse(5)
# TypeError: Parameter 'value' is not of type 'str'.
# Got type 'int'.
An enumeration with its own exception
Parse() raises a ValueError, which is what an unknown value is. An
enumeration belonging to a domain that has its own exception overrides Parse, catches that ValueError
and chains it as the cause.
The difference is visible to a user: pyTooling.CLI.main() prints a
ToolingException as a message, while an unhandled ValueError reaches
PrintException(), which prints a traceback and invites the user
to open an issue. A value a service sent that pyTooling doesn’t know is that service’s problem, not a bug in
pyTooling, so it wants the first.
Example:
class Status(StringEnum):
Queued = "queued"
InProgress = "in_progress"
Completed = "completed"
@classmethod
def Parse(cls, value: Nullable[str]) -> Nullable[Self]:
try:
return super().Parse(value)
except ValueError as ex:
error = GitHubError(f"'{value}' is not a GitHub status.")
error.add_note(f"Known: {', '.join(member.value for member in cls)}.")
raise error from ex
The TypeError is deliberately not caught: a value of the wrong type is a defect at the call site, not
a value the service chose, and it reads better as itself.
Status, Conclusion and
Event are written that way.
A member is its value
Deriving from StrEnum rather than Enum means a member is a string: it goes into a
message, an HTTP header or a filename without being unwrapped, and the whole enumeration joins into the list of
values an option accepts.
Example:
f"Drawn as {GanttFormat.MatplotlibPNG}." # 'Drawn as matplotlib-png.'
", ".join(GanttFormat) # 'matplotlib-png, matplotlib-svg'