Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport bug fixes for a 4.2.1 release #2827

Merged
merged 7 commits into from Jan 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Expand Up @@ -24,6 +24,7 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install .[dev]
pip install "selenium<4.3.0"
pip install altair_saver
- name: Test with pytest
run: |
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/docbuild.yml
Expand Up @@ -19,10 +19,9 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install .[dev]
pip install "selenium<4.3.0"
pip install altair_saver
pip install -r doc/requirements.txt
- name: Run docbuild
run: |
cd doc && make ${{ matrix.build-type }}


2 changes: 1 addition & 1 deletion altair/__init__.py
@@ -1,5 +1,5 @@
# flake8: noqa
__version__ = "4.2.0"
__version__ = "4.2.1"

from .vegalite import *
from . import examples
Expand Down
2 changes: 1 addition & 1 deletion altair/utils/core.py
Expand Up @@ -314,7 +314,7 @@ def to_list_if_array(val):
else:
return val

for col_name, dtype in df.dtypes.iteritems():
for col_name, dtype in df.dtypes.items():
if str(dtype) == "category":
# XXXX: work around bug in to_json for categorical types
# https://github.com/pydata/pandas/issues/10778
Expand Down
5 changes: 2 additions & 3 deletions altair/utils/display.py
Expand Up @@ -4,10 +4,9 @@
from typing import Callable, Dict
import uuid

from jsonschema import validate

from .plugin_registry import PluginRegistry
from .mimebundle import spec_to_mimebundle
from .schemapi import validate_jsonschema


# ==============================================================================
Expand Down Expand Up @@ -126,7 +125,7 @@ def _validate(self):
# type: () -> None
"""Validate the spec against the schema."""
schema_dict = json.loads(pkgutil.get_data(*self.schema_path).decode("utf-8"))
validate(self.spec, schema_dict)
validate_jsonschema(self.spec, schema_dict)

def _repr_mimebundle_(self, include=None, exclude=None):
"""Return a MIME bundle for display in Jupyter frontends."""
Expand Down
31 changes: 24 additions & 7 deletions altair/utils/schemapi.py
Expand Up @@ -6,10 +6,11 @@
import json

import jsonschema
import jsonschema.exceptions
import numpy as np
import pandas as pd


JSONSCHEMA_VALIDATOR = jsonschema.Draft7Validator
# If DEBUG_MODE is True, then schema objects are converted to dict and
# validated at creation time. This slows things down, particularly for
# larger specs, but leads to much more useful tracebacks for the user.
Expand Down Expand Up @@ -39,6 +40,24 @@ def debug_mode(arg):
DEBUG_MODE = original


def validate_jsonschema(spec, schema, resolver=None):
# We don't use jsonschema.validate as this would validate the schema itself.
# Instead, we pass the schema directly to the validator class. This is done for
# two reasons: The schema comes from Vega-Lite and is not based on the user
# input, therefore there is no need to validate it in the first place. Furthermore,
# the "uri-reference" format checker fails for some of the references as URIs in
# "$ref" are not encoded,
# e.g. '#/definitions/ValueDefWithCondition<MarkPropFieldOrDatumDef,
# (Gradient|string|null)>' would be a valid $ref in a Vega-Lite schema but
# it is not a valid URI reference due to the characters such as '<'.
validator = JSONSCHEMA_VALIDATOR(
schema, format_checker=JSONSCHEMA_VALIDATOR.FORMAT_CHECKER, resolver=resolver
)
error = jsonschema.exceptions.best_match(validator.iter_errors(spec))
if error is not None:
raise error


def _subclasses(cls):
"""Breadth-first sequence of all classes which inherit from cls."""
seen = set()
Expand Down Expand Up @@ -150,7 +169,7 @@ class SchemaBase(object):
_schema = None
_rootschema = None
_class_is_valid_at_instantiation = True
_validator = jsonschema.Draft7Validator
_validator = JSONSCHEMA_VALIDATOR

def __init__(self, *args, **kwds):
# Two valid options for initialization, which should be handled by
Expand Down Expand Up @@ -440,9 +459,7 @@ def validate(cls, instance, schema=None):
if schema is None:
schema = cls._schema
resolver = jsonschema.RefResolver.from_schema(cls._rootschema or cls._schema)
return jsonschema.validate(
instance, schema, cls=cls._validator, resolver=resolver
)
return validate_jsonschema(instance, schema, resolver=resolver)

@classmethod
def resolve_references(cls, schema=None):
Expand All @@ -461,7 +478,7 @@ def validate_property(cls, name, value, schema=None):
value = _todict(value, validate=False, context={})
props = cls.resolve_references(schema or cls._schema).get("properties", {})
resolver = jsonschema.RefResolver.from_schema(cls._rootschema or cls._schema)
return jsonschema.validate(value, props.get(name, {}), resolver=resolver)
return validate_jsonschema(value, props.get(name, {}), resolver=resolver)

def __dir__(self):
return list(self._kwds.keys())
Expand Down Expand Up @@ -555,7 +572,7 @@ def from_dict(
for possible_schema in schemas:
resolver = jsonschema.RefResolver.from_schema(rootschema)
try:
jsonschema.validate(dct, possible_schema, resolver=resolver)
validate_jsonschema(dct, possible_schema, resolver=resolver)
except jsonschema.ValidationError:
continue
else:
Expand Down
6 changes: 3 additions & 3 deletions altair/utils/tests/test_plugin_registry.py
Expand Up @@ -26,7 +26,7 @@ def test_plugin_registry():
assert plugins.get() is None
assert repr(plugins) == "TypedCallableRegistry(active='', registered=[])"

plugins.register("new_plugin", lambda x: x ** 2)
plugins.register("new_plugin", lambda x: x**2)
assert plugins.names() == ["new_plugin"]
assert plugins.active == ""
assert plugins.get() is None
Expand All @@ -46,7 +46,7 @@ def test_plugin_registry():
def test_plugin_registry_extra_options():
plugins = GeneralCallableRegistry()

plugins.register("metadata_plugin", lambda x, p=2: x ** p)
plugins.register("metadata_plugin", lambda x, p=2: x**p)
plugins.enable("metadata_plugin")
assert plugins.get()(3) == 9

Expand Down Expand Up @@ -86,7 +86,7 @@ def test_plugin_registry_global_settings():
def test_plugin_registry_context():
plugins = GeneralCallableRegistry()

plugins.register("default", lambda x, p=2: x ** p)
plugins.register("default", lambda x, p=2: x**p)

# At first there is no plugin enabled
assert plugins.active == ""
Expand Down
12 changes: 6 additions & 6 deletions altair/utils/tests/test_utils.py
Expand Up @@ -129,9 +129,9 @@ def test_sanitize_nullable_integers():
)

df_clean = sanitize_dataframe(df)
assert {col.dtype.name for _, col in df_clean.iteritems()} == {"object"}
assert {col.dtype.name for _, col in df_clean.items()} == {"object"}

result_python = {col_name: list(col) for col_name, col in df_clean.iteritems()}
result_python = {col_name: list(col) for col_name, col in df_clean.items()}
assert result_python == {
"int_np": [1, 2, 3, 4, 5],
"int64": [1, 2, 3, None, 5],
Expand All @@ -157,9 +157,9 @@ def test_sanitize_string_dtype():
)

df_clean = sanitize_dataframe(df)
assert {col.dtype.name for _, col in df_clean.iteritems()} == {"object"}
assert {col.dtype.name for _, col in df_clean.items()} == {"object"}

result_python = {col_name: list(col) for col_name, col in df_clean.iteritems()}
result_python = {col_name: list(col) for col_name, col in df_clean.items()}
assert result_python == {
"string_object": ["a", "b", "c", "d"],
"string_string": ["a", "b", "c", "d"],
Expand All @@ -182,9 +182,9 @@ def test_sanitize_boolean_dtype():
)

df_clean = sanitize_dataframe(df)
assert {col.dtype.name for _, col in df_clean.iteritems()} == {"object"}
assert {col.dtype.name for _, col in df_clean.items()} == {"object"}

result_python = {col_name: list(col) for col_name, col in df_clean.iteritems()}
result_python = {col_name: list(col) for col_name, col in df_clean.items()}
assert result_python == {
"bool_none": [True, False, None],
"none": [None, None, None],
Expand Down
12 changes: 9 additions & 3 deletions doc/releases/changes.rst
Expand Up @@ -3,6 +3,15 @@
Altair Change Log
=================

Version 4.2.1 (released XXX XX, 2023)
-------------------------------------

Bug Fixes
~~~~~~~~~
- Disable uri-reference format check in jsonsschema (#2771)
- Replace ``iteritems`` with ```items``` due to pandas deprecation (#2683)


Version 4.2.0 (released Dec 29, 2021)
-------------------------------------
- Update Vega-Lite from version 4.8.1 to version 4.17.0;
Expand All @@ -26,9 +35,6 @@ Bug Fixes
- Fix ``to_dict()`` for nested selections (#2120).
- Fix item access for expressions (#2099).

Backward-Incompatible Changes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Version 4.1.0 (released April 1, 2020)
--------------------------------------
- Minimum Python version is now 3.6
Expand Down
149 changes: 0 additions & 149 deletions tools/schemapi/jsonschema-draft04.json

This file was deleted.