summaryrefslogtreecommitdiff
path: root/lib/thandy/checkJson.py
diff options
context:
space:
mode:
authorNick Mathewson <nickm@torproject.org>2008-11-22 17:39:57 +0000
committerNick Mathewson <nickm@torproject.org>2008-11-22 17:39:57 +0000
commit6c78fd8ff6b3d39fbae85103bda0651ca2f764d9 (patch)
treef392cef3d3e0b3810964d8f8a3f594229f8bf90d /lib/thandy/checkJson.py
parent06b83a522f6f05aed23b6dca4bfd4eeb6e6b057a (diff)
more docs and tests
git-svn-id: file:///home/or/svnrepo/updater/trunk@17364 55e972cd-5a19-0410-ae62-a4d7a52db4cd
Diffstat (limited to 'lib/thandy/checkJson.py')
-rw-r--r--lib/thandy/checkJson.py88
1 files changed, 87 insertions, 1 deletions
diff --git a/lib/thandy/checkJson.py b/lib/thandy/checkJson.py
index b5fef3d..84ea9f2 100644
--- a/lib/thandy/checkJson.py
+++ b/lib/thandy/checkJson.py
@@ -6,7 +6,10 @@ import sys
import thandy
class Schema:
+ """A schema matches a set of possible Python objects, of types
+ that are encodable in JSON."""
def matches(self, obj):
+ """Return True if 'obj' matches this schema, False if it doesn't."""
try:
self.checkMatch(obj)
except thandy.FormatException:
@@ -15,10 +18,14 @@ class Schema:
return True
def checkMatch(self, obj):
+ """Raise thandy.FormatException if 'obj' does not match this schema.
+ Abstract method."""
raise NotImplemented()
class Any(Schema):
"""
+ Matches any single object.
+
>>> s = Any()
>>> s.matches("A String")
True
@@ -30,6 +37,8 @@ class Any(Schema):
class RE(Schema):
"""
+ Matches any string that matches a given regular expression.
+
>>> s = RE("h.*d")
>>> s.matches("hello world")
True
@@ -40,12 +49,22 @@ class RE(Schema):
>>> s.matches([33, "Hello"])
False
"""
- def __init__(self, pat=None, modifiers=0, reObj=None, reName="pattern"):
+ def __init__(self, pat=None, modifiers=0, reObj=None, reName=None):
+ """Make a new RE schema
+ pat -- The pattern to match, or None if reObj is provided.
+ modifiers -- Flags to use when compiling the pattern.
+ reObj -- A compiled regular expression object.
+ """
if not reObj:
if not pat.endswith("$"):
pat += "$"
reObj = re.compile(pat, modifiers)
self._re = reObj
+ if reName == None:
+ if pat != None:
+ reName = "pattern /%s/"%pat
+ else:
+ reName = "pattern"
self._reName = reName
def checkMatch(self, obj):
if not isinstance(obj, basestring) or not self._re.match(obj):
@@ -54,6 +73,8 @@ class RE(Schema):
class Str(Schema):
"""
+ Matches a particular string.
+
>>> s = Str("Hi")
>>> s.matches("Hi")
True
@@ -68,6 +89,8 @@ class Str(Schema):
class AnyStr(Schema):
"""
+ Matches any string, but no non-string object.
+
>>> s = AnyStr()
>>> s.matches("")
True
@@ -88,8 +111,36 @@ class AnyStr(Schema):
if not isinstance(obj, basestring):
raise thandy.FormatException("Expected a string; got %r"%obj)
+class OneOf(Schema):
+ """
+ Matches an object that matches any one of several schemas.
+
+ >>> s = OneOf([ListOf(Int()), Str("Hello"), Str("bye")])
+ >>> s.matches(3)
+ False
+ >>> s.matches("bye")
+ True
+ >>> s.matches([])
+ True
+ >>> s.matches([1,2])
+ True
+ >>> s.matches(["Hi"])
+ False
+ """
+ def __init__(self, alternatives):
+ self._subschemas = alternatives
+
+ def checkMatch(self, obj):
+ for m in self._subschemas:
+ if m.matches(obj):
+ return
+
+ raise thandy.FormatException("Object matched no recognized alternative")
+
class ListOf(Schema):
"""
+ Matches a homogenous list of some subschema.
+
>>> s = ListOf(RE("(?:..)*"))
>>> s.matches("hi")
False
@@ -101,6 +152,16 @@ class ListOf(Schema):
True
>>> s.matches(["This", "one", "is not"])
False
+
+ >>> s = ListOf(Int(), minCount=3, maxCount=10)
+ >>> s.matches([3]*2)
+ False
+ >>> s.matches([3]*3)
+ True
+ >>> s.matches([3]*10)
+ True
+ >>> s.matches([3]*11)
+ False
"""
def __init__(self, schema, minCount=0, maxCount=sys.maxint,listName="list"):
self._schema = schema
@@ -123,6 +184,8 @@ class ListOf(Schema):
class Struct(Schema):
"""
+ Matches a non-homogenous list of items.
+
>>> s = Struct([ListOf(AnyStr()), AnyStr(), Str("X")])
>>> s.matches(False)
False
@@ -136,6 +199,18 @@ class Struct(Schema):
False
>>> s.matches([[], "Q", "X", "Y"])
False
+
+ >>> s = Struct([Str("X")], allowMore=True)
+ >>> s.matches([])
+ False
+ >>> s.matches(["X"])
+ True
+ >>> s.matches(["X", "Y"])
+ True
+ >>> s.matches(["X", ["Y", "Z"]])
+ True
+ >>> s.matches([["X"]])
+ False
"""
def __init__(self, subschemas, allowMore=False, structName="list"):
self._subschemas = subschemas[:]
@@ -156,6 +231,10 @@ class Struct(Schema):
class DictOf(Schema):
"""
+ Matches a mapping from items matching a particular key-schema
+ to items matching a value-schema. Note that in JSON, keys must
+ be strings.
+
>>> s = DictOf(RE(r'[aeiou]+'), Struct([AnyStr(), AnyStr()]))
>>> s.matches("")
False
@@ -199,6 +278,9 @@ class Opt:
class Obj(Schema):
"""
+ Matches a dict from specified keys to key-specific types. Unrecognized
+ keys are allowed.
+
>>> s = Obj(a=AnyStr(), bc=Struct([Int(), Int()]))
>>> s.matches({'a':"ZYYY", 'bc':[5,9]})
True
@@ -234,6 +316,8 @@ class Obj(Schema):
class Int(Schema):
"""
+ Matches an integer.
+
>>> s = Int()
>>> s.matches(99)
True
@@ -262,6 +346,8 @@ class Int(Schema):
class Bool(Schema):
"""
+ Matches a boolean.
+
>>> s = Bool()
>>> s.matches(True) and s.matches(False)
True