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

ENH: support pickle and copy in schemapi #1805

Merged
merged 5 commits into from
Nov 24, 2019
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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