diff --git a/src/python_introspect/annotation_types.py b/src/python_introspect/annotation_types.py index 919b132..9af4c01 100644 --- a/src/python_introspect/annotation_types.py +++ b/src/python_introspect/annotation_types.py @@ -179,7 +179,7 @@ def enum_input_values(annotation: object) -> tuple[str, ...]: choices = declared_annotation_choices(annotation) if choices is not None: - return tuple(choices.label(choice) for choice in choices.choices()) + return tuple(label for label, _ in choices.labelled_choices()) enum_type = declared_enum_type(annotation) if enum_type is None: return () diff --git a/src/python_introspect/choices.py b/src/python_introspect/choices.py index e9d976a..fcfaa73 100644 --- a/src/python_introspect/choices.py +++ b/src/python_introspect/choices.py @@ -26,13 +26,30 @@ def label(self, choice: object) -> str: return str(choice) + def labelled_choices(self) -> tuple[tuple[str, object], ...]: + """Return each label with its value in one enumeration of the choice set.""" + return tuple((self.label(choice), choice) for choice in self.choices()) + def choice_for_label(self, label: str) -> object: """Decode one display label back to its admissible value.""" - - for choice in self.choices(): - if self.label(choice) == label: + options = self.labelled_choices() + for name, choice in options: + if name == label: return choice - raise ValueError(f"{label!r} is not one of {[self.label(c) for c in self.choices()]}.") + raise ValueError(f"{label!r} is not one of {[name for name, _ in options]}.") + +class LabelledChoices(AnnotationChoices): + """Choices whose source already supplies labels and values together.""" + + @abstractmethod + def labelled_choices(self) -> tuple[tuple[str, object], ...]: + """Return the source's ordered label/value pairs.""" + + def choices(self) -> tuple[object, ...]: + return tuple(value for _, value in self.labelled_choices()) + + def label(self, choice: object) -> str: + return next(label for label, value in self.labelled_choices() if value == choice) def declared_annotation_choices(annotation: object) -> AnnotationChoices | None: diff --git a/src/python_introspect/validation.py b/src/python_introspect/validation.py index bb3e816..788dedd 100644 --- a/src/python_introspect/validation.py +++ b/src/python_introspect/validation.py @@ -255,7 +255,7 @@ def _(metadata: AnnotationChoices, value: object, path: str) -> None: if item not in choices: raise AnnotationValidationError( f"{path} must hold values from " - f"{[metadata.label(choice) for choice in choices]}; got {item!r}." + f"{[label for label, _ in metadata.labelled_choices()]}; got {item!r}." )