Skip to content

Commit

Permalink
Allow to use nested schema
Browse files Browse the repository at this point in the history
This allows to refer to the current schema using voluptuous.Self and have
nested definitions.

Fixes #128
  • Loading branch information
jd committed Dec 21, 2017
1 parent 1666a68 commit f53780e
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 3 deletions.
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -443,9 +443,21 @@ True

```

### Recursive schema
### Recursive / nested schema

There is no syntax to have a recursive schema. The best way to do it is to have a wrapper like this:
You can use `voluptuous.Self` to define a nested schema:

```pycon
>>> from voluptuous import Schema, Self
>>> recursive = Schema({"more": Self, "value": int})
>>> recursive({"more": {"value": 42}, "value": 41}) == {'more': {'value': 42}, 'value': 41}
True

```

This only works if `Self` is used in the `Schema` directly. If you use `Any`,
`All` or `SomeOf`, this won't work as they compile their arguments down to a
new `Schema`. In that case, you can use an external reference:

```pycon
>>> from voluptuous import Schema, Any
Expand Down
6 changes: 6 additions & 0 deletions voluptuous/schema_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ def __repr__(self):
UNDEFINED = Undefined()


def Self():
raise er.SchemaError('"Self" should never be called')


def default_factory(value):
if value is UNDEFINED or callable(value):
return value
Expand Down Expand Up @@ -270,6 +274,8 @@ def __call__(self, data):
def _compile(self, schema):
if schema is Extra:
return lambda _, v: v
if schema is Self:
return lambda p, v: self._compiled(p, v)
if isinstance(schema, Object):
return self._compile_object(schema)
if isinstance(schema, collections.Mapping):
Expand Down
22 changes: 21 additions & 1 deletion voluptuous/tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
Url, MultipleInvalid, LiteralInvalid, TypeInvalid, NotIn, Match, Email,
Replace, Range, Coerce, All, Any, Length, FqdnUrl, ALLOW_EXTRA, PREVENT_EXTRA,
validate, ExactSequence, Equal, Unordered, Number, Maybe, Datetime, Date,
Contains, Marker, IsDir, IsFile, PathExists, SomeOf, TooManyValid, raises)
Contains, Marker, IsDir, IsFile, PathExists, SomeOf, TooManyValid, Self,
raises)
from voluptuous.humanize import humanize_error
from voluptuous.util import u

Expand Down Expand Up @@ -1065,6 +1066,25 @@ def test_SomeOf_max_validation():
validator('Aa1')


def test_self_validation():
schema = Schema({"number": int,
"follow": Self})
try:
schema({"number": "abc"})
except MultipleInvalid:
pass
else:
assert False, "Did not raise Invalid"
try:
schema({"follow": {"number": '123456.712'}})
except MultipleInvalid:
pass
else:
assert False, "Did not raise Invalid"
schema({"follow": {"number": 123456}})
schema({"follow": {"follow": {"number": 123456}}})


def test_SomeOf_on_bounds_assertion():
with raises(AssertionError, 'when using "SomeOf" you should specify at least one of min_valid and max_valid'):
SomeOf(validators=[])

0 comments on commit f53780e

Please sign in to comment.