|
17 | 17 | TYPE_CHECKING, |
18 | 18 | Any, |
19 | 19 | Dict, |
| 20 | + Final, |
| 21 | + Generic, |
20 | 22 | Iterable, |
21 | 23 | List, |
| 24 | + Literal, |
22 | 25 | Mapping, |
23 | 26 | Sequence, |
24 | 27 | TypeVar, |
|
33 | 36 | from jsonschema import ValidationError |
34 | 37 | from packaging.version import Version |
35 | 38 |
|
| 39 | +if sys.version_info >= (3, 12): |
| 40 | + from typing import Protocol, TypeAliasType, runtime_checkable |
| 41 | +else: |
| 42 | + from typing_extensions import Protocol, TypeAliasType, runtime_checkable |
| 43 | + |
36 | 44 | if TYPE_CHECKING: |
37 | 45 | from types import ModuleType |
38 | | - from typing import Callable, ClassVar, Final, Iterator, KeysView, Literal |
| 46 | + from typing import Callable, ClassVar, Final, Iterator, KeysView |
39 | 47 |
|
40 | 48 | from jsonschema.protocols import Validator, _JsonParameter |
41 | 49 |
|
@@ -677,11 +685,7 @@ def _todict(obj: Any, context: dict[str, Any] | None, np_opt: Any, pd_opt: Any) |
677 | 685 | for k, v in obj.items() |
678 | 686 | if v is not Undefined |
679 | 687 | } |
680 | | - elif ( |
681 | | - hasattr(obj, "to_dict") |
682 | | - and (module_name := obj.__module__) |
683 | | - and module_name.startswith("altair") |
684 | | - ): |
| 688 | + elif isinstance(obj, SchemaLike): |
685 | 689 | return obj.to_dict() |
686 | 690 | elif pd_opt is not None and isinstance(obj, pd_opt.Timestamp): |
687 | 691 | return pd_opt.Timestamp(obj).isoformat() |
@@ -912,6 +916,95 @@ def _get_default_error_message( |
912 | 916 | return message.strip() |
913 | 917 |
|
914 | 918 |
|
| 919 | +_JSON_VT_co = TypeVar( |
| 920 | + "_JSON_VT_co", |
| 921 | + Literal["string"], |
| 922 | + Literal["object"], |
| 923 | + Literal["array"], |
| 924 | + covariant=True, |
| 925 | +) |
| 926 | +""" |
| 927 | +One of a subset of JSON Schema `primitive types`_: |
| 928 | +
|
| 929 | + ["string", "object", "array"] |
| 930 | +
|
| 931 | +.. _primitive types: |
| 932 | + https://json-schema.org/draft-07/json-schema-validation#rfc.section.6.1.1 |
| 933 | +""" |
| 934 | + |
| 935 | +_TypeMap = TypeAliasType( |
| 936 | + "_TypeMap", Mapping[Literal["type"], _JSON_VT_co], type_params=(_JSON_VT_co,) |
| 937 | +) |
| 938 | +""" |
| 939 | +A single item JSON Schema using the `type`_ keyword. |
| 940 | +
|
| 941 | +This may represent **one of**: |
| 942 | +
|
| 943 | + {"type": "string"} |
| 944 | + {"type": "object"} |
| 945 | + {"type": "array"} |
| 946 | +
|
| 947 | +.. _type: |
| 948 | + https://json-schema.org/understanding-json-schema/reference/type |
| 949 | +""" |
| 950 | + |
| 951 | +# NOTE: Type checkers want opposing things: |
| 952 | +# - `mypy` : Covariant type variable "_JSON_VT_co" used in protocol where invariant one is expected [misc] |
| 953 | +# - `pyright`: Type variable "_JSON_VT_co" used in generic protocol "SchemaLike" should be covariant [reportInvalidTypeVarUse] |
| 954 | +# Siding with `pyright` as this is consistent with https://github.com/python/typeshed/blob/9e506eb5e8fc2823db8c60ad561b1145ff114947/stdlib/typing.pyi#L690 |
| 955 | + |
| 956 | + |
| 957 | +@runtime_checkable |
| 958 | +class SchemaLike(Generic[_JSON_VT_co], Protocol): # type: ignore[misc] |
| 959 | + """ |
| 960 | + Represents ``altair`` classes which *may* not derive ``SchemaBase``. |
| 961 | +
|
| 962 | + Attributes |
| 963 | + ---------- |
| 964 | + _schema |
| 965 | + A single item JSON Schema using the `type`_ keyword. |
| 966 | +
|
| 967 | + Notes |
| 968 | + ----- |
| 969 | + Should be kept tightly defined to the **minimum** requirements for: |
| 970 | + - Converting into a form that can be validated by `jsonschema`_. |
| 971 | + - Avoiding calling ``.to_dict()`` on a class external to ``altair``. |
| 972 | + - ``_schema`` is more accurately described as a ``ClassVar`` |
| 973 | + - See `discussion`_ for blocking issue. |
| 974 | +
|
| 975 | + .. _jsonschema: |
| 976 | + https://github.com/python-jsonschema/jsonschema |
| 977 | + .. _type: |
| 978 | + https://json-schema.org/understanding-json-schema/reference/type |
| 979 | + .. _discussion: |
| 980 | + https://github.com/python/typing/discussions/1424 |
| 981 | + """ |
| 982 | + |
| 983 | + _schema: _TypeMap[_JSON_VT_co] |
| 984 | + |
| 985 | + def to_dict(self, *args, **kwds) -> Any: ... |
| 986 | + |
| 987 | + |
| 988 | +@runtime_checkable |
| 989 | +class ConditionLike(SchemaLike[Literal["object"]], Protocol): |
| 990 | + """ |
| 991 | + Represents the wrapped state of a conditional encoding or property. |
| 992 | +
|
| 993 | + Attributes |
| 994 | + ---------- |
| 995 | + condition |
| 996 | + One or more (predicate, statement) pairs which each form a condition. |
| 997 | +
|
| 998 | + Notes |
| 999 | + ----- |
| 1000 | + - Can be extended with additional conditions. |
| 1001 | + - *Does not* define a default value, but can be finalized with one. |
| 1002 | + """ |
| 1003 | + |
| 1004 | + condition: Any |
| 1005 | + _schema: _TypeMap[Literal["object"]] = {"type": "object"} |
| 1006 | + |
| 1007 | + |
915 | 1008 | class UndefinedType: |
916 | 1009 | """A singleton object for marking undefined parameters.""" |
917 | 1010 |
|
|
0 commit comments