Skip to content

Commit

Permalink
Merge pull request #1805 from jakevdp/bergtholdt-fix_pickle_copy
Browse files Browse the repository at this point in the history
ENH: support pickle and copy in schemapi
  • Loading branch information
jakevdp authored Nov 24, 2019
2 parents 76b8fde + fa787b2 commit dc1f333
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 18 deletions.
2 changes: 2 additions & 0 deletions altair/utils/schemapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,8 @@ def _get(self, attr, default=Undefined):

def __getattr__(self, attr):
# reminder: getattr is called after the normal lookups
if attr == '_kwds':
raise AttributeError()
if attr in self._kwds:
return self._kwds[attr]
else:
Expand Down
42 changes: 34 additions & 8 deletions altair/utils/tests/test_schemapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
#
# The contents of this file are automatically written by
# tools/generate_schema_wrapper.py. Do not modify directly.
import copy
import io
import jsonschema
import pickle
import pytest

from ..schemapi import (UndefinedType, SchemaBase, Undefined, _FromDict,
Expand Down Expand Up @@ -211,11 +214,16 @@ def test_undefined_singleton():
assert Undefined is UndefinedType()


def test_copy():
dct = {'a': {'foo': 'bar'}, 'a2': {'foo': 42},
'b': ['a', 'b', 'c'], 'b2': [1, 2, 3], 'c': 42,
'd': ['x', 'y', 'z']}
@pytest.fixture
def dct():
return {
'a': {'foo': 'bar'}, 'a2': {'foo': 42},
'b': ['a', 'b', 'c'], 'b2': [1, 2, 3], 'c': 42,
'd': ['x', 'y', 'z']
}


def test_copy_method(dct):
myschema = MySchema.from_dict(dct)

# Make sure copy is deep
Expand Down Expand Up @@ -246,6 +254,16 @@ def test_copy():
assert mydct['c'] == dct['c']


def test_copy_module(dct):
myschema = MySchema.from_dict(dct)

cp = copy.deepcopy(myschema)
cp['a']['foo'] = 'new value'
cp['b'] = ['A', 'B', 'C']
cp['c'] = 164
assert myschema.to_dict() == dct


def test_attribute_error():
m = MySchema()
with pytest.raises(AttributeError) as err:
Expand All @@ -254,16 +272,23 @@ def test_attribute_error():
"'invalid_attribute'")


def test_to_from_json():
dct = {'a': {'foo': 'bar'}, 'a2': {'foo': 42},
'b': ['a', 'b', 'c'], 'b2': [1, 2, 3], 'c': 42,
'd': ['x', 'y', 'z'], 'e': ['g', 'h']}
def test_to_from_json(dct):
json_str = MySchema.from_dict(dct).to_json()
new_dct = MySchema.from_json(json_str).to_dict()

assert new_dct == dct


def test_to_from_pickle(dct):
myschema = MySchema.from_dict(dct)
output = io.BytesIO()
pickle.dump(myschema, output)
output.seek(0)
myschema_new = pickle.load(output)

assert myschema_new.to_dict() == dct


def test_class_with_no_schema():
class BadSchema(SchemaBase):
pass
Expand Down Expand Up @@ -298,3 +323,4 @@ def test_schema_validation_error():
assert 'test_schemapi.MySchema->a' in message
assert "validating {!r}".format(the_err.validator) in message
assert the_err.message in message

8 changes: 6 additions & 2 deletions tools/schemapi/schemapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def debug_mode(arg):


def _subclasses(cls):
"""Breadth-first sequence of classes which inherit from cls."""
"""Breadth-first sequence of all classes which inherit from cls."""
seen = set()
current_set = {cls}
while current_set:
Expand Down Expand Up @@ -226,6 +226,8 @@ def _get(self, attr, default=Undefined):

def __getattr__(self, attr):
# reminder: getattr is called after the normal lookups
if attr == '_kwds':
raise AttributeError()
if attr in self._kwds:
return self._kwds[attr]
else:
Expand Down Expand Up @@ -494,7 +496,9 @@ def _passthrough(*args, **kwds):
return args[0] if args else kwds

if cls is None:
# TODO: do something more than simply selecting the last match?
# If there are multiple matches, we use the last one in the dict.
# Our class dict is constructed breadth-first from top to bottom,
# so the last class that matches is the most specific.
matches = self.class_dict[self.hash_schema(schema)]
cls = matches[-1] if matches else _passthrough
schema = _resolve_references(schema, rootschema)
Expand Down
42 changes: 34 additions & 8 deletions tools/schemapi/tests/test_schemapi.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import copy
import io
import jsonschema
import pickle
import pytest

from ..schemapi import (UndefinedType, SchemaBase, Undefined, _FromDict,
Expand Down Expand Up @@ -207,11 +210,16 @@ def test_undefined_singleton():
assert Undefined is UndefinedType()


def test_copy():
dct = {'a': {'foo': 'bar'}, 'a2': {'foo': 42},
'b': ['a', 'b', 'c'], 'b2': [1, 2, 3], 'c': 42,
'd': ['x', 'y', 'z']}
@pytest.fixture
def dct():
return {
'a': {'foo': 'bar'}, 'a2': {'foo': 42},
'b': ['a', 'b', 'c'], 'b2': [1, 2, 3], 'c': 42,
'd': ['x', 'y', 'z']
}


def test_copy_method(dct):
myschema = MySchema.from_dict(dct)

# Make sure copy is deep
Expand Down Expand Up @@ -242,6 +250,16 @@ def test_copy():
assert mydct['c'] == dct['c']


def test_copy_module(dct):
myschema = MySchema.from_dict(dct)

cp = copy.deepcopy(myschema)
cp['a']['foo'] = 'new value'
cp['b'] = ['A', 'B', 'C']
cp['c'] = 164
assert myschema.to_dict() == dct


def test_attribute_error():
m = MySchema()
with pytest.raises(AttributeError) as err:
Expand All @@ -250,16 +268,23 @@ def test_attribute_error():
"'invalid_attribute'")


def test_to_from_json():
dct = {'a': {'foo': 'bar'}, 'a2': {'foo': 42},
'b': ['a', 'b', 'c'], 'b2': [1, 2, 3], 'c': 42,
'd': ['x', 'y', 'z'], 'e': ['g', 'h']}
def test_to_from_json(dct):
json_str = MySchema.from_dict(dct).to_json()
new_dct = MySchema.from_json(json_str).to_dict()

assert new_dct == dct


def test_to_from_pickle(dct):
myschema = MySchema.from_dict(dct)
output = io.BytesIO()
pickle.dump(myschema, output)
output.seek(0)
myschema_new = pickle.load(output)

assert myschema_new.to_dict() == dct


def test_class_with_no_schema():
class BadSchema(SchemaBase):
pass
Expand Down Expand Up @@ -294,3 +319,4 @@ def test_schema_validation_error():
assert 'test_schemapi.MySchema->a' in message
assert "validating {!r}".format(the_err.validator) in message
assert the_err.message in message

0 comments on commit dc1f333

Please sign in to comment.