Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into perf-validation
Browse files Browse the repository at this point in the history
  • Loading branch information
dangotbanned committed Sep 22, 2024
2 parents 0c829fb + eb6febd commit edd34fd
Show file tree
Hide file tree
Showing 10 changed files with 578 additions and 249 deletions.
3 changes: 2 additions & 1 deletion altair/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@
from .deprecation import AltairDeprecationWarning, deprecated, deprecated_warn
from .html import spec_to_html
from .plugin_registry import PluginRegistry
from .schemapi import Optional, SchemaBase, Undefined, is_undefined
from .schemapi import Optional, SchemaBase, SchemaLike, Undefined, is_undefined

__all__ = (
"SHORTHAND_KEYS",
"AltairDeprecationWarning",
"Optional",
"PluginRegistry",
"SchemaBase",
"SchemaLike",
"Undefined",
"deprecated",
"deprecated_warn",
Expand Down
4 changes: 3 additions & 1 deletion altair/utils/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from narwhals.dependencies import get_polars, is_pandas_dataframe
from narwhals.typing import IntoDataFrame

from altair.utils.schemapi import SchemaBase, Undefined
from altair.utils.schemapi import SchemaBase, SchemaLike, Undefined

if sys.version_info >= (3, 12):
from typing import Protocol, TypeAliasType, runtime_checkable
Expand Down Expand Up @@ -868,6 +868,8 @@ def _wrap_in_channel(self, obj: Any, encoding: str, /):
obj = {"shorthand": obj}
elif isinstance(obj, (list, tuple)):
return [self._wrap_in_channel(el, encoding) for el in obj]
elif isinstance(obj, SchemaLike):
obj = obj.to_dict()
if channel := self.name_to_channel.get(encoding):
tp = channel["value" if "value" in obj else "field"]
# Don't force validation here; some objects won't be valid until
Expand Down
105 changes: 99 additions & 6 deletions altair/utils/schemapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@
TYPE_CHECKING,
Any,
Dict,
Final,
Generic,
Iterable,
List,
Literal,
Mapping,
Sequence,
TypeVar,
Expand All @@ -33,9 +36,14 @@
from jsonschema import ValidationError
from packaging.version import Version

if sys.version_info >= (3, 12):
from typing import Protocol, TypeAliasType, runtime_checkable
else:
from typing_extensions import Protocol, TypeAliasType, runtime_checkable

if TYPE_CHECKING:
from types import ModuleType
from typing import Callable, ClassVar, Final, Iterator, KeysView, Literal
from typing import Callable, ClassVar, Final, Iterator, KeysView

from jsonschema.protocols import Validator, _JsonParameter

Expand Down Expand Up @@ -677,11 +685,7 @@ def _todict(obj: Any, context: dict[str, Any] | None, np_opt: Any, pd_opt: Any)
for k, v in obj.items()
if v is not Undefined
}
elif (
hasattr(obj, "to_dict")
and (module_name := obj.__module__)
and module_name.startswith("altair")
):
elif isinstance(obj, SchemaLike):
return obj.to_dict()
elif pd_opt is not None and isinstance(obj, pd_opt.Timestamp):
return pd_opt.Timestamp(obj).isoformat()
Expand Down Expand Up @@ -912,6 +916,95 @@ def _get_default_error_message(
return message.strip()


_JSON_VT_co = TypeVar(
"_JSON_VT_co",
Literal["string"],
Literal["object"],
Literal["array"],
covariant=True,
)
"""
One of a subset of JSON Schema `primitive types`_:
["string", "object", "array"]
.. _primitive types:
https://json-schema.org/draft-07/json-schema-validation#rfc.section.6.1.1
"""

_TypeMap = TypeAliasType(
"_TypeMap", Mapping[Literal["type"], _JSON_VT_co], type_params=(_JSON_VT_co,)
)
"""
A single item JSON Schema using the `type`_ keyword.
This may represent **one of**:
{"type": "string"}
{"type": "object"}
{"type": "array"}
.. _type:
https://json-schema.org/understanding-json-schema/reference/type
"""

# NOTE: Type checkers want opposing things:
# - `mypy` : Covariant type variable "_JSON_VT_co" used in protocol where invariant one is expected [misc]
# - `pyright`: Type variable "_JSON_VT_co" used in generic protocol "SchemaLike" should be covariant [reportInvalidTypeVarUse]
# Siding with `pyright` as this is consistent with https://github.com/python/typeshed/blob/9e506eb5e8fc2823db8c60ad561b1145ff114947/stdlib/typing.pyi#L690


@runtime_checkable
class SchemaLike(Generic[_JSON_VT_co], Protocol): # type: ignore[misc]
"""
Represents ``altair`` classes which *may* not derive ``SchemaBase``.
Attributes
----------
_schema
A single item JSON Schema using the `type`_ keyword.
Notes
-----
Should be kept tightly defined to the **minimum** requirements for:
- Converting into a form that can be validated by `jsonschema`_.
- Avoiding calling ``.to_dict()`` on a class external to ``altair``.
- ``_schema`` is more accurately described as a ``ClassVar``
- See `discussion`_ for blocking issue.
.. _jsonschema:
https://github.com/python-jsonschema/jsonschema
.. _type:
https://json-schema.org/understanding-json-schema/reference/type
.. _discussion:
https://github.com/python/typing/discussions/1424
"""

_schema: _TypeMap[_JSON_VT_co]

def to_dict(self, *args, **kwds) -> Any: ...


@runtime_checkable
class ConditionLike(SchemaLike[Literal["object"]], Protocol):
"""
Represents the wrapped state of a conditional encoding or property.
Attributes
----------
condition
One or more (predicate, statement) pairs which each form a condition.
Notes
-----
- Can be extended with additional conditions.
- *Does not* define a default value, but can be finalized with one.
"""

condition: Any
_schema: _TypeMap[Literal["object"]] = {"type": "object"}


class UndefinedType:
"""A singleton object for marking undefined parameters."""

Expand Down
Loading

0 comments on commit edd34fd

Please sign in to comment.