|
18 | 18 | Any, |
19 | 19 | Dict, |
20 | 20 | Final, |
| 21 | + Generic, |
21 | 22 | Iterable, |
22 | 23 | Iterator, |
23 | 24 | List, |
24 | 25 | Literal, |
| 26 | + Mapping, |
25 | 27 | Sequence, |
26 | 28 | TypeVar, |
27 | 29 | Union, |
|
41 | 43 | # not yet be fully instantiated in case your code is being executed during import time |
42 | 44 | from altair import vegalite |
43 | 45 |
|
| 46 | +if sys.version_info >= (3, 12): |
| 47 | + from typing import Protocol, TypeAliasType, runtime_checkable |
| 48 | +else: |
| 49 | + from typing_extensions import Protocol, TypeAliasType, runtime_checkable |
| 50 | + |
44 | 51 | if TYPE_CHECKING: |
45 | 52 | from types import ModuleType |
46 | 53 | from typing import ClassVar |
@@ -524,11 +531,7 @@ def _todict(obj: Any, context: dict[str, Any] | None, np_opt: Any, pd_opt: Any) |
524 | 531 | for k, v in obj.items() |
525 | 532 | if v is not Undefined |
526 | 533 | } |
527 | | - elif ( |
528 | | - hasattr(obj, "to_dict") |
529 | | - and (module_name := obj.__module__) |
530 | | - and module_name.startswith("altair") |
531 | | - ): |
| 534 | + elif isinstance(obj, SchemaLike): |
532 | 535 | return obj.to_dict() |
533 | 536 | elif pd_opt is not None and isinstance(obj, pd_opt.Timestamp): |
534 | 537 | return pd_opt.Timestamp(obj).isoformat() |
@@ -789,6 +792,95 @@ def _get_default_error_message( |
789 | 792 | return message |
790 | 793 |
|
791 | 794 |
|
| 795 | +_JSON_VT_co = TypeVar( |
| 796 | + "_JSON_VT_co", |
| 797 | + Literal["string"], |
| 798 | + Literal["object"], |
| 799 | + Literal["array"], |
| 800 | + covariant=True, |
| 801 | +) |
| 802 | +""" |
| 803 | +One of a subset of JSON Schema `primitive types`_: |
| 804 | +
|
| 805 | + ["string", "object", "array"] |
| 806 | +
|
| 807 | +.. _primitive types: |
| 808 | + https://json-schema.org/draft-07/json-schema-validation#rfc.section.6.1.1 |
| 809 | +""" |
| 810 | + |
| 811 | +_TypeMap = TypeAliasType( |
| 812 | + "_TypeMap", Mapping[Literal["type"], _JSON_VT_co], type_params=(_JSON_VT_co,) |
| 813 | +) |
| 814 | +""" |
| 815 | +A single item JSON Schema using the `type`_ keyword. |
| 816 | +
|
| 817 | +This may represent **one of**: |
| 818 | +
|
| 819 | + {"type": "string"} |
| 820 | + {"type": "object"} |
| 821 | + {"type": "array"} |
| 822 | +
|
| 823 | +.. _type: |
| 824 | + https://json-schema.org/understanding-json-schema/reference/type |
| 825 | +""" |
| 826 | + |
| 827 | +# NOTE: Type checkers want opposing things: |
| 828 | +# - `mypy` : Covariant type variable "_JSON_VT_co" used in protocol where invariant one is expected [misc] |
| 829 | +# - `pyright`: Type variable "_JSON_VT_co" used in generic protocol "SchemaLike" should be covariant [reportInvalidTypeVarUse] |
| 830 | +# Siding with `pyright` as this is consistent with https://github.com/python/typeshed/blob/9e506eb5e8fc2823db8c60ad561b1145ff114947/stdlib/typing.pyi#L690 |
| 831 | + |
| 832 | + |
| 833 | +@runtime_checkable |
| 834 | +class SchemaLike(Generic[_JSON_VT_co], Protocol): # type: ignore[misc] |
| 835 | + """ |
| 836 | + Represents ``altair`` classes which *may* not derive ``SchemaBase``. |
| 837 | +
|
| 838 | + Attributes |
| 839 | + ---------- |
| 840 | + _schema |
| 841 | + A single item JSON Schema using the `type`_ keyword. |
| 842 | +
|
| 843 | + Notes |
| 844 | + ----- |
| 845 | + Should be kept tightly defined to the **minimum** requirements for: |
| 846 | + - Converting into a form that can be validated by `jsonschema`_. |
| 847 | + - Avoiding calling ``.to_dict()`` on a class external to ``altair``. |
| 848 | + - ``_schema`` is more accurately described as a ``ClassVar`` |
| 849 | + - See `discussion`_ for blocking issue. |
| 850 | +
|
| 851 | + .. _jsonschema: |
| 852 | + https://github.com/python-jsonschema/jsonschema |
| 853 | + .. _type: |
| 854 | + https://json-schema.org/understanding-json-schema/reference/type |
| 855 | + .. _discussion: |
| 856 | + https://github.com/python/typing/discussions/1424 |
| 857 | + """ |
| 858 | + |
| 859 | + _schema: _TypeMap[_JSON_VT_co] |
| 860 | + |
| 861 | + def to_dict(self, *args, **kwds) -> Any: ... |
| 862 | + |
| 863 | + |
| 864 | +@runtime_checkable |
| 865 | +class ConditionLike(SchemaLike[Literal["object"]], Protocol): |
| 866 | + """ |
| 867 | + Represents the wrapped state of a conditional encoding or property. |
| 868 | +
|
| 869 | + Attributes |
| 870 | + ---------- |
| 871 | + condition |
| 872 | + One or more (predicate, statement) pairs which each form a condition. |
| 873 | +
|
| 874 | + Notes |
| 875 | + ----- |
| 876 | + - Can be extended with additional conditions. |
| 877 | + - *Does not* define a default value, but can be finalized with one. |
| 878 | + """ |
| 879 | + |
| 880 | + condition: Any |
| 881 | + _schema: _TypeMap[Literal["object"]] = {"type": "object"} |
| 882 | + |
| 883 | + |
792 | 884 | class UndefinedType: |
793 | 885 | """A singleton object for marking undefined parameters.""" |
794 | 886 |
|
|
0 commit comments