|
6 | 6 | `zarr_metadata.v3.data_type`. |
7 | 7 | """ |
8 | 8 |
|
9 | | -from collections.abc import Mapping, Sequence |
| 9 | +from collections.abc import Mapping |
10 | 10 | from typing import NotRequired |
11 | 11 |
|
12 | | -from typing_extensions import TypeAliasType, TypedDict |
| 12 | +from typing_extensions import ReadOnly, TypeAliasType, TypedDict |
13 | 13 |
|
14 | 14 | JSONValue = TypeAliasType( |
15 | 15 | "JSONValue", |
16 | | - int | float | bool | str | Sequence["JSONValue"] | Mapping[str, "JSONValue"] | None, |
| 16 | + int |
| 17 | + | float |
| 18 | + | bool |
| 19 | + | str |
| 20 | + | list["JSONValue"] |
| 21 | + | tuple["JSONValue", ...] |
| 22 | + | Mapping[str, "JSONValue"] |
| 23 | + | None, |
17 | 24 | ) |
18 | 25 | """A recursive type alias for JSON-encodable values. |
19 | 26 |
|
20 | 27 | Defined via `TypeAliasType` (rather than a plain `TypeAlias`) so the |
21 | 28 | self-reference is a named recursion point that pydantic can resolve when |
22 | 29 | building a `TypeAdapter`; a bare recursive `TypeAlias` raises |
23 | 30 | `PydanticUserError`/`RecursionError` at validation time. |
24 | | -
|
25 | | -The array arm is the covariant `Sequence` rather than the invariant |
26 | | -`list["JSONValue"] | tuple["JSONValue", ...]`, so values typed with a |
27 | | -*narrower* element type still count as JSON values: a `list[str]` field on a |
28 | | -TypedDict is assignable to `JSONValue` under `Sequence` but not under |
29 | | -`list[JSONValue]` (`list` is invariant in its element type, and pyright's |
30 | | -diagnostic for that failure suggests exactly this change). This is what lets |
31 | | -downstream TypedDicts give their fields precise types (`Sequence[str]`, |
32 | | -`list[int]`, ...) while remaining assignable to `Mapping[str, JSONValue]`. |
33 | | -The type-level cost, accepted deliberately: `Sequence` says nothing about the |
34 | | -concrete container, and it admits `str`/`bytes` (`str` was already a union |
35 | | -arm); runtime code narrowing a JSON array must exclude `str`/`bytes`/ |
36 | | -`bytearray` regardless of how this alias is spelled. |
37 | 31 | """ |
38 | 32 |
|
39 | 33 |
|
40 | | -class ZarrV3NamedConfigJSON(TypedDict): |
| 34 | +class ZarrV3NamedConfigJSON(TypedDict, closed=True): |
41 | 35 | """ |
42 | 36 | Externally-tagged union member for a metadata field. |
43 | 37 |
|
44 | 38 | The optional `configuration` mapping holds arbitrary JSON-encodable |
45 | 39 | values. `must_understand` is implicitly true when absent. |
| 40 | +
|
| 41 | + `name` and `configuration` are `ReadOnly` (PEP 705) so that concrete |
| 42 | + entity types — `BloscCodecObject`, `RegularChunkGridObject`, and the |
| 43 | + rest — are assignable to this type, and therefore to |
| 44 | + `ZarrV3MetadataFieldJSON`. Without `ReadOnly` both items are invariant, |
| 45 | + so a concrete `name: Literal["blosc"]` does not satisfy `name: str`, and |
| 46 | + a required `configuration` does not satisfy a `NotRequired` one. That |
| 47 | + made the package's own codec types unusable in the very fields they |
| 48 | + describe (`codecs`, `data_type`, `chunk_grid`, ...), and made |
| 49 | + `TypeIs`-based codec classification impossible to declare, since `TypeIs` |
| 50 | + requires the narrowed type to be assignable to the input type. |
| 51 | +
|
| 52 | + `must_understand` stays writable: nothing needs to narrow it, and |
| 53 | + keeping it mutable lets writers set it on an already-constructed field. |
| 54 | +
|
| 55 | + The type is `closed` (PEP 728): the spec's named-configuration envelope |
| 56 | + has exactly these three members, and closing it is also what makes this |
| 57 | + type — and every concrete entity type embedding it, e.g. the |
| 58 | + `sharding_indexed` configuration's inner `codecs` list — assignable to |
| 59 | + `Mapping[str, JSONValue]` (i.e. usable as a `JSONValue`). |
46 | 60 | """ |
47 | 61 |
|
48 | | - name: str |
49 | | - configuration: NotRequired[Mapping[str, JSONValue]] |
| 62 | + name: ReadOnly[str] |
| 63 | + configuration: NotRequired[ReadOnly[Mapping[str, JSONValue]]] |
50 | 64 | must_understand: NotRequired[bool] |
0 commit comments