|
4 | 4 | dataclass projects through its declared fields, and the decoder rebuilds it |
5 | 5 | from the same declarations. Further value types join through |
6 | 6 | ``to_jsonable.register``. |
| 7 | +
|
| 8 | +A class registered in an ``AutoRegisterMeta`` family travels as its registry |
| 9 | +key; ``registered_class_from_key`` is the inverse the decoder uses for fields |
| 10 | +annotated ``type[Family]``. A family whose boundary spelling is not its |
| 11 | +registry (for example one resolved through an active subset) registers its own |
| 12 | +decoder on its metaclass. |
7 | 13 | """ |
8 | 14 |
|
9 | 15 | from __future__ import annotations |
10 | 16 |
|
11 | 17 | import inspect |
12 | 18 | from collections.abc import Callable, Mapping |
13 | | -from dataclasses import fields, is_dataclass |
| 19 | +from dataclasses import dataclass, fields, is_dataclass |
14 | 20 | from enum import Enum |
15 | 21 | from functools import singledispatch |
16 | 22 | from pathlib import Path |
17 | 23 | from typing import ForwardRef, TypeAlias |
18 | 24 |
|
19 | 25 | from metaclass_registry import AutoRegisterMeta |
20 | 26 |
|
| 27 | +from .choices import AnnotationChoices |
21 | 28 | from .signature_analyzer import signature_analysis_target |
22 | 29 |
|
23 | 30 | JsonScalar: TypeAlias = str | int | float | bool | None |
@@ -101,6 +108,57 @@ def _jsonable_registered_type(value: AutoRegisterMeta) -> JsonValue: |
101 | 108 | return to_jsonable(key) |
102 | 109 |
|
103 | 110 |
|
| 111 | +@singledispatch |
| 112 | +def registered_class_from_key(family: type, key: JsonValue) -> type: |
| 113 | + """Return the member of ``family`` that ``to_jsonable`` spells as ``key``. |
| 114 | +
|
| 115 | + Dispatches on the family's metaclass, mirroring how ``to_jsonable`` |
| 116 | + dispatches a registered class on its metaclass. |
| 117 | + """ |
| 118 | + |
| 119 | + raise TypeError( |
| 120 | + f"{family.__qualname__} is not a registered family; " |
| 121 | + f"cannot decode {key!r} into one of its classes." |
| 122 | + ) |
| 123 | + |
| 124 | + |
| 125 | +@registered_class_from_key.register(AutoRegisterMeta) |
| 126 | +def _registered_class_from_registry(family: AutoRegisterMeta, key: JsonValue) -> type: |
| 127 | + keys_by_member = { |
| 128 | + member: to_jsonable(registered_key) |
| 129 | + for registered_key, member in family.__registry__.items() |
| 130 | + if issubclass(member, family) |
| 131 | + } |
| 132 | + matches = [member for member, member_key in keys_by_member.items() if member_key == key] |
| 133 | + if len(matches) != 1: |
| 134 | + raise ValueError( |
| 135 | + f"{key!r} is not a registered {family.__qualname__}; " |
| 136 | + f"registered: {sorted(map(str, keys_by_member.values()))}." |
| 137 | + ) |
| 138 | + return matches[0] |
| 139 | + |
| 140 | + |
| 141 | +@dataclass(frozen=True) |
| 142 | +class RegisteredClassChoices(AnnotationChoices): |
| 143 | + """A field holding one class of a registered family. |
| 144 | +
|
| 145 | + The choices are the family's registered members in registration order, each |
| 146 | + labelled by its JSON spelling (its registry key). |
| 147 | + """ |
| 148 | + |
| 149 | + family: AutoRegisterMeta |
| 150 | + |
| 151 | + def choices(self) -> tuple[object, ...]: |
| 152 | + return tuple( |
| 153 | + member |
| 154 | + for member in self.family.__registry__.values() |
| 155 | + if issubclass(member, self.family) |
| 156 | + ) |
| 157 | + |
| 158 | + def label(self, choice: object) -> str: |
| 159 | + return str(to_jsonable(choice)) |
| 160 | + |
| 161 | + |
104 | 162 | @to_jsonable.register(Enum) |
105 | 163 | def _jsonable_enum(value: Enum) -> JsonValue: |
106 | 164 | return to_jsonable(value.value) |
|
0 commit comments