Skip to content

Commit

Permalink
Add a declarative mode.
Browse files Browse the repository at this point in the history
  • Loading branch information
mcdonc committed Mar 12, 2010
1 parent 82bbd4e commit 86f39e7
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 59 deletions.
55 changes: 53 additions & 2 deletions cereal/__init__.py
@@ -1,4 +1,5 @@
import pkg_resources
import itertools

def resolve_dotted(dottedname, package=None):
if dottedname.startswith('.') or dottedname.startswith(':'):
Expand Down Expand Up @@ -310,6 +311,8 @@ def serialize(self, struct, value):
def deserialize(self, struct, value):
return self._validate(struct, value)

Int = Integer

class GlobalObject(object):
""" A type representing an importable Python object """
def __init__(self, package):
Expand All @@ -331,12 +334,19 @@ def deserialize(self, struct, value):
'The dotted name %r cannot be imported' % value)

class Structure(object):
def __init__(self, name, typ, *structs, **kw):
self.name = name
_counter = itertools.count()

def __new__(cls, *arg, **kw):
inst = object.__new__(cls)
inst._order = cls._counter.next()
return inst

def __init__(self, typ, *structs, **kw):
self.typ = typ
self.validator = kw.get('validator', None)
self.default = kw.get('default', None)
self.required = kw.get('required', True)
self.name = kw.get('name', '')
self.structs = list(structs)

def serialize(self, value):
Expand All @@ -351,3 +361,44 @@ def deserialize(self, value):
def add(self, struct):
self.structs.append(struct)

class _SchemaMeta(type):
def __init__(cls, name, bases, clsattrs):
structs = []
for name, value in clsattrs.items():
if isinstance(value, Structure):
value.name = name
structs.append((value._order, value))
cls.__schema_structures__ = structs
# Combine all attrs from this class and its subclasses.
extended = []
for c in cls.__mro__:
extended.extend(getattr(c, '__schema_structures__', []))
# Sort the attrs to maintain the order as defined, and assign to the
# class.
extended.sort()
cls.structs = [x[1] for x in extended]

class Schema(object):
struct_type = Mapping
__metaclass__ = _SchemaMeta

def __new__(cls, *args, **kw):
inst = object.__new__(Structure)
inst.name = None
inst._order = Structure._counter.next()
struct = cls.struct_type(*args, **kw)
inst.__init__(struct)
for s in cls.structs:
inst.add(s)
return inst

MappingSchema = Schema

class SequenceSchema(Schema):
struct_type = Sequence

class TupleSchema(Schema):
struct_type = Tuple



140 changes: 83 additions & 57 deletions cereal/tests.py
@@ -1,58 +1,106 @@
import unittest

class TestFunctional(unittest.TestCase):
class TestFunctional(object):
def test_deserialize_ok(self):
import cereal.tests
data = {
'int':'10',
'ob':'cereal.tests',
'seq':[('1', 's'),('2', 's'), ('3', 's'), ('4', 's')],
'seq2':[{'key':'1', 'key2':'2'}, {'key':'3', 'key2':'4'}],
'tup':('1', 's'),
}
schema = self._makeSchema()
result = schema.deserialize(data)
self.assertEqual(result['int'], 10)
self.assertEqual(result['ob'], cereal.tests)
self.assertEqual(result['seq'],
[(1, 's'), (2, 's'), (3, 's'), (4, 's')])
self.assertEqual(result['seq2'],
[{'key':1, 'key2':2}, {'key':3, 'key2':4}])
self.assertEqual(result['tup'], (1, 's'))

def test_invalid_asdict(self):
expected = {
'int': '20 is greater than maximum value 10',
'ob': "The dotted name 'no.way.this.exists' cannot be imported",
'seq.0.0': "'q' is not a number",
'seq.1.0': "'w' is not a number",
'seq.2.0': "'e' is not a number",
'seq.3.0': "'r' is not a number",
'seq2.0.key': "'t' is not a number",
'seq2.0.key2': "'y' is not a number",
'seq2.1.key': "'u' is not a number",
'seq2.1.key2': "'i' is not a number",
'tup.0': "'s' is not a number"}
import cereal
data = {
'int':'20',
'ob':'no.way.this.exists',
'seq':[('q', 's'),('w', 's'), ('e', 's'), ('r', 's')],
'seq2':[{'key':'t', 'key2':'y'}, {'key':'u', 'key2':'i'}],
'tup':('s', 's'),
}
schema = self._makeSchema()
try:
schema.deserialize(data)
except cereal.Invalid, e:
errors = e.asdict()
self.assertEqual(errors, expected)

class TestImperative(unittest.TestCase, TestFunctional):

def _makeSchema(self):
import cereal

integer = cereal.Structure(
'int',
cereal.Integer(),
name='int',
validator=cereal.Range(0, 10)
)

ob = cereal.Structure(
'ob',
cereal.GlobalObject(package=cereal),
name='ob',
)

tup = cereal.Structure(
'tup',
cereal.Tuple(),
cereal.Structure(
'tupint',
cereal.Integer(),
name='tupint',
),
cereal.Structure(
'tupstring',
cereal.String(),
name='tupstring',
),
name='tup',
)

seq = cereal.Structure(
'seq',
cereal.Sequence(tup),
name='seq',
)

seq2 = cereal.Structure(
'seq2',
cereal.Sequence(
cereal.Structure(
'mapping',
cereal.Mapping(),
cereal.Structure(
'key',
cereal.Integer(),
name='key',
),
cereal.Structure(
'key2',
cereal.Integer(),
name='key2',
),
name='mapping',
)
),
name='seq2',
)

schema = cereal.Structure(
None,
cereal.Mapping(),
integer,
ob,
Expand All @@ -62,49 +110,27 @@ def _makeSchema(self):

return schema

def test_deserialize_ok(self):
import cereal.tests
data = {
'int':'10',
'ob':'cereal.tests',
'seq':[('1', 's'),('2', 's'), ('3', 's'), ('4', 's')],
'seq2':[{'key':'1', 'key2':'2'}, {'key':'3', 'key2':'4'}],
'tup':('1', 's'),
}
schema = self._makeSchema()
result = schema.deserialize(data)
self.assertEqual(result['int'], 10)
self.assertEqual(result['ob'], cereal.tests)
self.assertEqual(result['seq'],
[(1, 's'), (2, 's'), (3, 's'), (4, 's')])
self.assertEqual(result['seq2'],
[{'key':1, 'key2':2}, {'key':3, 'key2':4}])
self.assertEqual(result['tup'], (1, 's'))

def test_invalid_asdict(self):
expected = {
'int': '20 is greater than maximum value 10',
'ob': "The dotted name 'no.way.this.exists' cannot be imported",
'seq.0.0': "'q' is not a number",
'seq.1.0': "'w' is not a number",
'seq.2.0': "'e' is not a number",
'seq.3.0': "'r' is not a number",
'seq2.0.key': "'t' is not a number",
'seq2.0.key2': "'y' is not a number",
'seq2.1.key': "'u' is not a number",
'seq2.1.key2': "'i' is not a number",
'tup.0': "'s' is not a number"}
class TestDeclarative(unittest.TestCase, TestFunctional):

def _makeSchema(self):

import cereal
data = {
'int':'20',
'ob':'no.way.this.exists',
'seq':[('q', 's'),('w', 's'), ('e', 's'), ('r', 's')],
'seq2':[{'key':'t', 'key2':'y'}, {'key':'u', 'key2':'i'}],
'tup':('s', 's'),
}
schema = self._makeSchema()
try:
schema.deserialize(data)
except cereal.Invalid, e:
errors = e.asdict()
self.assertEqual(errors, expected)

class TupleSchema(cereal.TupleSchema):
tupint = cereal.Structure(cereal.Int())
tupstring = cereal.Structure(cereal.String())

class MappingSchema(cereal.MappingSchema):
key = cereal.Structure(cereal.Int())
key2 = cereal.Structure(cereal.Int())

class MainSchema(cereal.MappingSchema):
int = cereal.Structure(cereal.Int(), validator=cereal.Range(0, 10))
ob = cereal.Structure(cereal.GlobalObject(package=cereal))
seq = cereal.Structure(cereal.Sequence(TupleSchema()))
tup = TupleSchema()
seq2 = cereal.SequenceSchema(MappingSchema())

schema = MainSchema()
return schema

0 comments on commit 86f39e7

Please sign in to comment.