You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
557 lines
19 KiB
Python
557 lines
19 KiB
Python
from __future__ import with_statement
|
|
from collections import defaultdict, namedtuple
|
|
from operator import methodcaller
|
|
import os
|
|
import re
|
|
import sys
|
|
import copy
|
|
|
|
from pytest import raises
|
|
|
|
from schema import (Schema, Use, And, Or, Regex, Optional,
|
|
SchemaError, SchemaWrongKeyError,
|
|
SchemaMissingKeyError, SchemaUnexpectedTypeError)
|
|
|
|
if sys.version_info[0] == 3:
|
|
basestring = str # Python 3 does not have basestring
|
|
unicode = str # Python 3 does not have unicode
|
|
|
|
|
|
SE = raises(SchemaError)
|
|
|
|
|
|
def ve(_):
|
|
raise ValueError()
|
|
|
|
|
|
def se(_):
|
|
raise SchemaError('first auto', 'first error')
|
|
|
|
|
|
def test_schema():
|
|
|
|
assert Schema(1).validate(1) == 1
|
|
with SE: Schema(1).validate(9)
|
|
|
|
assert Schema(int).validate(1) == 1
|
|
with SE: Schema(int).validate('1')
|
|
assert Schema(Use(int)).validate('1') == 1
|
|
with SE: Schema(int).validate(int)
|
|
|
|
assert Schema(str).validate('hai') == 'hai'
|
|
with SE: Schema(str).validate(1)
|
|
assert Schema(Use(str)).validate(1) == '1'
|
|
|
|
assert Schema(list).validate(['a', 1]) == ['a', 1]
|
|
assert Schema(dict).validate({'a': 1}) == {'a': 1}
|
|
with SE: Schema(dict).validate(['a', 1])
|
|
|
|
assert Schema(lambda n: 0 < n < 5).validate(3) == 3
|
|
with SE: Schema(lambda n: 0 < n < 5).validate(-1)
|
|
|
|
|
|
def test_validate_file():
|
|
assert Schema(
|
|
Use(open)).validate('LICENSE-MIT').read().startswith('Copyright')
|
|
with SE: Schema(Use(open)).validate('NON-EXISTENT')
|
|
assert Schema(os.path.exists).validate('.') == '.'
|
|
with SE: Schema(os.path.exists).validate('./non-existent/')
|
|
assert Schema(os.path.isfile).validate('LICENSE-MIT') == 'LICENSE-MIT'
|
|
with SE: Schema(os.path.isfile).validate('NON-EXISTENT')
|
|
|
|
|
|
def test_and():
|
|
assert And(int, lambda n: 0 < n < 5).validate(3) == 3
|
|
with SE: And(int, lambda n: 0 < n < 5).validate(3.33)
|
|
assert And(Use(int), lambda n: 0 < n < 5).validate(3.33) == 3
|
|
with SE: And(Use(int), lambda n: 0 < n < 5).validate('3.33')
|
|
|
|
|
|
def test_or():
|
|
assert Or(int, dict).validate(5) == 5
|
|
assert Or(int, dict).validate({}) == {}
|
|
with SE: Or(int, dict).validate('hai')
|
|
assert Or(int).validate(4)
|
|
with SE: Or().validate(2)
|
|
|
|
|
|
def test_regex():
|
|
# Simple case: validate string
|
|
assert Regex(r'foo').validate('afoot') == 'afoot'
|
|
with SE: Regex(r'bar').validate('afoot')
|
|
|
|
# More complex case: validate string
|
|
assert Regex(r'^[a-z]+$').validate('letters') == 'letters'
|
|
with SE:
|
|
Regex(r'^[a-z]+$').validate('letters + spaces') == 'letters + spaces'
|
|
|
|
# Validate dict key
|
|
assert (Schema({Regex(r'^foo'): str})
|
|
.validate({'fookey': 'value'}) == {'fookey': 'value'})
|
|
with SE: Schema({Regex(r'^foo'): str}).validate({'barkey': 'value'})
|
|
|
|
# Validate dict value
|
|
assert (Schema({str: Regex(r'^foo')}).validate({'key': 'foovalue'}) ==
|
|
{'key': 'foovalue'})
|
|
with SE: Schema({str: Regex(r'^foo')}).validate({'key': 'barvalue'})
|
|
|
|
# Error if the value does not have a buffer interface
|
|
with SE: Regex(r'bar').validate(1)
|
|
with SE: Regex(r'bar').validate({})
|
|
with SE: Regex(r'bar').validate([])
|
|
with SE: Regex(r'bar').validate(None)
|
|
|
|
# Validate that the pattern has a buffer interface
|
|
assert Regex(re.compile(r'foo')).validate('foo') == 'foo'
|
|
assert Regex(unicode('foo')).validate('foo') == 'foo'
|
|
with raises(TypeError): Regex(1).validate('bar')
|
|
with raises(TypeError): Regex({}).validate('bar')
|
|
with raises(TypeError): Regex([]).validate('bar')
|
|
with raises(TypeError): Regex(None).validate('bar')
|
|
|
|
|
|
def test_validate_list():
|
|
assert Schema([1, 0]).validate([1, 0, 1, 1]) == [1, 0, 1, 1]
|
|
assert Schema([1, 0]).validate([]) == []
|
|
with SE: Schema([1, 0]).validate(0)
|
|
with SE: Schema([1, 0]).validate([2])
|
|
assert And([1, 0], lambda l: len(l) > 2).validate([0, 1, 0]) == [0, 1, 0]
|
|
with SE: And([1, 0], lambda l: len(l) > 2).validate([0, 1])
|
|
|
|
|
|
def test_list_tuple_set_frozenset():
|
|
assert Schema([int]).validate([1, 2])
|
|
with SE: Schema([int]).validate(['1', 2])
|
|
assert Schema(set([int])).validate(set([1, 2])) == set([1, 2])
|
|
with SE: Schema(set([int])).validate([1, 2]) # not a set
|
|
with SE: Schema(set([int])).validate(['1', 2])
|
|
assert Schema(tuple([int])).validate(tuple([1, 2])) == tuple([1, 2])
|
|
with SE: Schema(tuple([int])).validate([1, 2]) # not a set
|
|
|
|
|
|
def test_strictly():
|
|
assert Schema(int).validate(1) == 1
|
|
with SE: Schema(int).validate('1')
|
|
|
|
|
|
def test_dict():
|
|
assert Schema({'key': 5}).validate({'key': 5}) == {'key': 5}
|
|
with SE: Schema({'key': 5}).validate({'key': 'x'})
|
|
with SE: Schema({'key': 5}).validate(['key', 5])
|
|
assert Schema({'key': int}).validate({'key': 5}) == {'key': 5}
|
|
assert Schema({'n': int, 'f': float}).validate(
|
|
{'n': 5, 'f': 3.14}) == {'n': 5, 'f': 3.14}
|
|
with SE: Schema({'n': int, 'f': float}).validate(
|
|
{'n': 3.14, 'f': 5})
|
|
with SE:
|
|
try:
|
|
Schema({}).validate({'abc': None, 1: None})
|
|
except SchemaWrongKeyError as e:
|
|
assert e.args[0].startswith("Wrong keys 'abc', 1 in")
|
|
raise
|
|
with SE:
|
|
try:
|
|
Schema({'key': 5}).validate({})
|
|
except SchemaMissingKeyError as e:
|
|
assert e.args[0] == "Missing keys: 'key'"
|
|
raise
|
|
with SE:
|
|
try:
|
|
Schema({'key': 5}).validate({'n': 5})
|
|
except SchemaMissingKeyError as e:
|
|
assert e.args[0] == "Missing keys: 'key'"
|
|
raise
|
|
with SE:
|
|
try:
|
|
Schema({}).validate({'n': 5})
|
|
except SchemaWrongKeyError as e:
|
|
assert e.args[0] == "Wrong keys 'n' in {'n': 5}"
|
|
raise
|
|
with SE:
|
|
try:
|
|
Schema({'key': 5}).validate({'key': 5, 'bad': 5})
|
|
except SchemaWrongKeyError as e:
|
|
assert e.args[0] in ["Wrong keys 'bad' in {'key': 5, 'bad': 5}",
|
|
"Wrong keys 'bad' in {'bad': 5, 'key': 5}"]
|
|
raise
|
|
with SE:
|
|
try:
|
|
Schema({}).validate({'a': 5, 'b': 5})
|
|
except SchemaError as e:
|
|
assert e.args[0] in ["Wrong keys 'a', 'b' in {'a': 5, 'b': 5}",
|
|
"Wrong keys 'a', 'b' in {'b': 5, 'a': 5}"]
|
|
raise
|
|
|
|
with SE:
|
|
try:
|
|
Schema({int: int}).validate({'': ''})
|
|
except SchemaUnexpectedTypeError as e:
|
|
assert e.args[0] in ["'' should be instance of 'int'"]
|
|
|
|
|
|
def test_dict_keys():
|
|
assert Schema({str: int}).validate(
|
|
{'a': 1, 'b': 2}) == {'a': 1, 'b': 2}
|
|
with SE: Schema({str: int}).validate({1: 1, 'b': 2})
|
|
assert Schema({Use(str): Use(int)}).validate(
|
|
{1: 3.14, 3.14: 1}) == {'1': 3, '3.14': 1}
|
|
|
|
|
|
def test_ignore_extra_keys():
|
|
assert Schema({'key': 5}, ignore_extra_keys=True).validate(
|
|
{'key': 5, 'bad': 4}) == {'key': 5}
|
|
assert Schema({'key': 5, 'dk': {'a': 'a'}}, ignore_extra_keys=True).validate(
|
|
{'key': 5, 'bad': 'b', 'dk': {'a': 'a', 'bad': 'b'}}) == \
|
|
{'key': 5, 'dk': {'a': 'a'}}
|
|
assert Schema([{'key': 'v'}], ignore_extra_keys=True).validate(
|
|
[{'key': 'v', 'bad': 'bad'}]) == [{'key': 'v'}]
|
|
assert Schema([{'key': 'v'}], ignore_extra_keys=True).validate(
|
|
[{'key': 'v', 'bad': 'bad'}]) == [{'key': 'v'}]
|
|
|
|
|
|
def test_ignore_extra_keys_validation_and_return_keys():
|
|
assert Schema({'key': 5, object: object}, ignore_extra_keys=True).validate(
|
|
{'key': 5, 'bad': 4}) == {'key': 5, 'bad': 4}
|
|
assert Schema({'key': 5, 'dk': {'a': 'a', object: object}},
|
|
ignore_extra_keys=True).validate(
|
|
{'key': 5, 'dk': {'a': 'a', 'bad': 'b'}}) == \
|
|
{'key': 5, 'dk': {'a': 'a', 'bad': 'b'}}
|
|
|
|
|
|
def test_dict_optional_keys():
|
|
with SE: Schema({'a': 1, 'b': 2}).validate({'a': 1})
|
|
assert Schema({'a': 1, Optional('b'): 2}).validate({'a': 1}) == {'a': 1}
|
|
assert Schema({'a': 1, Optional('b'): 2}).validate(
|
|
{'a': 1, 'b': 2}) == {'a': 1, 'b': 2}
|
|
# Make sure Optionals are favored over types:
|
|
assert Schema({basestring: 1,
|
|
Optional('b'): 2}).validate({'a': 1, 'b': 2}) == {'a': 1, 'b': 2}
|
|
|
|
|
|
def test_dict_optional_defaults():
|
|
# Optionals fill out their defaults:
|
|
assert Schema({Optional('a', default=1): 11,
|
|
Optional('b', default=2): 22}).validate({'a': 11}) == {'a': 11, 'b': 2}
|
|
|
|
# Optionals take precedence over types. Here, the "a" is served by the
|
|
# Optional:
|
|
assert Schema({Optional('a', default=1): 11,
|
|
basestring: 22}).validate({'b': 22}) == {'a': 1, 'b': 22}
|
|
|
|
with raises(TypeError):
|
|
Optional(And(str, Use(int)), default=7)
|
|
|
|
|
|
def test_dict_subtypes():
|
|
d = defaultdict(int, key=1)
|
|
v = Schema({'key': 1}).validate(d)
|
|
assert v == d
|
|
assert isinstance(v, defaultdict)
|
|
# Please add tests for Counter and OrderedDict once support for Python2.6
|
|
# is dropped!
|
|
|
|
|
|
def test_dict_key_error():
|
|
try:
|
|
Schema({'k': int}).validate({'k': 'x'})
|
|
except SchemaError as e:
|
|
assert e.code == "Key 'k' error:\n'x' should be instance of 'int'"
|
|
try:
|
|
Schema({'k': {'k2': int}}).validate({'k': {'k2': 'x'}})
|
|
except SchemaError as e:
|
|
code = "Key 'k' error:\nKey 'k2' error:\n'x' should be instance of 'int'"
|
|
assert e.code == code
|
|
try:
|
|
Schema({'k': {'k2': int}}, error='k2 should be int').validate({'k': {'k2': 'x'}})
|
|
except SchemaError as e:
|
|
assert e.code == 'k2 should be int'
|
|
|
|
|
|
def test_complex():
|
|
s = Schema({'<file>': And([Use(open)], lambda l: len(l)),
|
|
'<path>': os.path.exists,
|
|
Optional('--count'): And(int, lambda n: 0 <= n <= 5)})
|
|
data = s.validate({'<file>': ['./LICENSE-MIT'], '<path>': './'})
|
|
assert len(data) == 2
|
|
assert len(data['<file>']) == 1
|
|
assert data['<file>'][0].read().startswith('Copyright')
|
|
assert data['<path>'] == './'
|
|
|
|
|
|
def test_nice_errors():
|
|
try:
|
|
Schema(int, error='should be integer').validate('x')
|
|
except SchemaError as e:
|
|
assert e.errors == ['should be integer']
|
|
try:
|
|
Schema(Use(float), error='should be a number').validate('x')
|
|
except SchemaError as e:
|
|
assert e.code == 'should be a number'
|
|
try:
|
|
Schema({Optional('i'): Use(int, error='should be a number')}).validate({'i': 'x'})
|
|
except SchemaError as e:
|
|
assert e.code == 'should be a number'
|
|
|
|
|
|
def test_use_error_handling():
|
|
try:
|
|
Use(ve).validate('x')
|
|
except SchemaError as e:
|
|
assert e.autos == ["ve('x') raised ValueError()"]
|
|
assert e.errors == [None]
|
|
try:
|
|
Use(ve, error='should not raise').validate('x')
|
|
except SchemaError as e:
|
|
assert e.autos == ["ve('x') raised ValueError()"]
|
|
assert e.errors == ['should not raise']
|
|
try:
|
|
Use(se).validate('x')
|
|
except SchemaError as e:
|
|
assert e.autos == [None, 'first auto']
|
|
assert e.errors == [None, 'first error']
|
|
try:
|
|
Use(se, error='second error').validate('x')
|
|
except SchemaError as e:
|
|
assert e.autos == [None, 'first auto']
|
|
assert e.errors == ['second error', 'first error']
|
|
|
|
|
|
def test_or_error_handling():
|
|
try:
|
|
Or(ve).validate('x')
|
|
except SchemaError as e:
|
|
assert e.autos[0].startswith('Or(')
|
|
assert e.autos[0].endswith(") did not validate 'x'")
|
|
assert e.autos[1] == "ve('x') raised ValueError()"
|
|
assert len(e.autos) == 2
|
|
assert e.errors == [None, None]
|
|
try:
|
|
Or(ve, error='should not raise').validate('x')
|
|
except SchemaError as e:
|
|
assert e.autos[0].startswith('Or(')
|
|
assert e.autos[0].endswith(") did not validate 'x'")
|
|
assert e.autos[1] == "ve('x') raised ValueError()"
|
|
assert len(e.autos) == 2
|
|
assert e.errors == ['should not raise', 'should not raise']
|
|
try:
|
|
Or('o').validate('x')
|
|
except SchemaError as e:
|
|
assert e.autos == ["Or('o') did not validate 'x'",
|
|
"'o' does not match 'x'"]
|
|
assert e.errors == [None, None]
|
|
try:
|
|
Or('o', error='second error').validate('x')
|
|
except SchemaError as e:
|
|
assert e.autos == ["Or('o') did not validate 'x'",
|
|
"'o' does not match 'x'"]
|
|
assert e.errors == ['second error', 'second error']
|
|
|
|
|
|
def test_and_error_handling():
|
|
try:
|
|
And(ve).validate('x')
|
|
except SchemaError as e:
|
|
assert e.autos == ["ve('x') raised ValueError()"]
|
|
assert e.errors == [None]
|
|
try:
|
|
And(ve, error='should not raise').validate('x')
|
|
except SchemaError as e:
|
|
assert e.autos == ["ve('x') raised ValueError()"]
|
|
assert e.errors == ['should not raise']
|
|
try:
|
|
And(str, se).validate('x')
|
|
except SchemaError as e:
|
|
assert e.autos == [None, 'first auto']
|
|
assert e.errors == [None, 'first error']
|
|
try:
|
|
And(str, se, error='second error').validate('x')
|
|
except SchemaError as e:
|
|
assert e.autos == [None, 'first auto']
|
|
assert e.errors == ['second error', 'first error']
|
|
|
|
|
|
def test_schema_error_handling():
|
|
try:
|
|
Schema(Use(ve)).validate('x')
|
|
except SchemaError as e:
|
|
assert e.autos == [None, "ve('x') raised ValueError()"]
|
|
assert e.errors == [None, None]
|
|
try:
|
|
Schema(Use(ve), error='should not raise').validate('x')
|
|
except SchemaError as e:
|
|
assert e.autos == [None, "ve('x') raised ValueError()"]
|
|
assert e.errors == ['should not raise', None]
|
|
try:
|
|
Schema(Use(se)).validate('x')
|
|
except SchemaError as e:
|
|
assert e.autos == [None, None, 'first auto']
|
|
assert e.errors == [None, None, 'first error']
|
|
try:
|
|
Schema(Use(se), error='second error').validate('x')
|
|
except SchemaError as e:
|
|
assert e.autos == [None, None, 'first auto']
|
|
assert e.errors == ['second error', None, 'first error']
|
|
|
|
|
|
def test_use_json():
|
|
import json
|
|
gist_schema = Schema(And(Use(json.loads), # first convert from JSON
|
|
{Optional('description'): basestring,
|
|
'public': bool,
|
|
'files': {basestring: {'content': basestring}}}))
|
|
gist = '''{"description": "the description for this gist",
|
|
"public": true,
|
|
"files": {
|
|
"file1.txt": {"content": "String file contents"},
|
|
"other.txt": {"content": "Another file contents"}}}'''
|
|
assert gist_schema.validate(gist)
|
|
|
|
|
|
def test_error_reporting():
|
|
s = Schema({'<files>': [Use(open, error='<files> should be readable')],
|
|
'<path>': And(os.path.exists, error='<path> should exist'),
|
|
'--count': Or(None, And(Use(int), lambda n: 0 < n < 5),
|
|
error='--count should be integer 0 < n < 5')},
|
|
error='Error:')
|
|
s.validate({'<files>': [], '<path>': './', '--count': 3})
|
|
|
|
try:
|
|
s.validate({'<files>': [], '<path>': './', '--count': '10'})
|
|
except SchemaError as e:
|
|
assert e.code == 'Error:\n--count should be integer 0 < n < 5'
|
|
try:
|
|
s.validate({'<files>': [], '<path>': './hai', '--count': '2'})
|
|
except SchemaError as e:
|
|
assert e.code == 'Error:\n<path> should exist'
|
|
try:
|
|
s.validate({'<files>': ['hai'], '<path>': './', '--count': '2'})
|
|
except SchemaError as e:
|
|
assert e.code == 'Error:\n<files> should be readable'
|
|
|
|
|
|
def test_schema_repr(): # what about repr with `error`s?
|
|
schema = Schema([Or(None, And(str, Use(float)))])
|
|
repr_ = "Schema([Or(None, And(<type 'str'>, Use(<type 'float'>)))])"
|
|
# in Python 3 repr contains <class 'str'>, not <type 'str'>
|
|
assert repr(schema).replace('class', 'type') == repr_
|
|
|
|
|
|
def test_validate_object():
|
|
schema = Schema({object: str})
|
|
assert schema.validate({42: 'str'}) == {42: 'str'}
|
|
with SE: schema.validate({42: 777})
|
|
|
|
|
|
def test_issue_9_prioritized_key_comparison():
|
|
validate = Schema({'key': 42, object: 42}).validate
|
|
assert validate({'key': 42, 777: 42}) == {'key': 42, 777: 42}
|
|
|
|
|
|
def test_issue_9_prioritized_key_comparison_in_dicts():
|
|
# http://stackoverflow.com/questions/14588098/docopt-schema-validation
|
|
s = Schema({'ID': Use(int, error='ID should be an int'),
|
|
'FILE': Or(None, Use(open, error='FILE should be readable')),
|
|
Optional(str): object})
|
|
data = {'ID': 10, 'FILE': None, 'other': 'other', 'other2': 'other2'}
|
|
assert s.validate(data) == data
|
|
data = {'ID': 10, 'FILE': None}
|
|
assert s.validate(data) == data
|
|
|
|
|
|
def test_missing_keys_exception_with_non_str_dict_keys():
|
|
s = Schema({And(str, Use(str.lower), 'name'): And(str, len)})
|
|
with SE: s.validate(dict())
|
|
with SE:
|
|
try:
|
|
Schema({1: 'x'}).validate(dict())
|
|
except SchemaMissingKeyError as e:
|
|
assert e.args[0] == "Missing keys: 1"
|
|
raise
|
|
|
|
|
|
def test_issue_56_cant_rely_on_callables_to_have_name():
|
|
s = Schema(methodcaller('endswith', '.csv'))
|
|
assert s.validate('test.csv') == 'test.csv'
|
|
with SE:
|
|
try:
|
|
s.validate('test.py')
|
|
except SchemaError as e:
|
|
assert "operator.methodcaller" in e.args[0]
|
|
raise
|
|
|
|
|
|
def test_exception_handling_with_bad_validators():
|
|
BadValidator = namedtuple("BadValidator", ["validate"])
|
|
s = Schema(BadValidator("haha"))
|
|
with SE:
|
|
try:
|
|
s.validate("test")
|
|
except SchemaError as e:
|
|
assert "TypeError" in e.args[0]
|
|
raise
|
|
|
|
|
|
def test_issue_83_iterable_validation_return_type():
|
|
TestSetType = type("TestSetType", (set,), dict())
|
|
data = TestSetType(["test", "strings"])
|
|
s = Schema(set([str]))
|
|
assert isinstance(s.validate(data), TestSetType)
|
|
|
|
|
|
def test_optional_key_convert_failed_randomly_while_with_another_optional_object():
|
|
"""
|
|
In this test, created_at string "2015-10-10 00:00:00" is expected to be converted
|
|
to a datetime instance.
|
|
- it works when the schema is
|
|
|
|
s = Schema({
|
|
'created_at': _datetime_validator,
|
|
Optional(basestring): object,
|
|
})
|
|
|
|
- but when wrapping the key 'created_at' with Optional, it fails randomly
|
|
:return:
|
|
"""
|
|
import datetime
|
|
fmt = '%Y-%m-%d %H:%M:%S'
|
|
_datetime_validator = Or(None, Use(lambda i: datetime.datetime.strptime(i, fmt)))
|
|
# FIXME given tests enough
|
|
for i in range(1024):
|
|
s = Schema({
|
|
Optional('created_at'): _datetime_validator,
|
|
Optional('updated_at'): _datetime_validator,
|
|
Optional('birth'): _datetime_validator,
|
|
Optional(basestring): object,
|
|
})
|
|
data = {
|
|
'created_at': '2015-10-10 00:00:00'
|
|
}
|
|
validated_data = s.validate(data)
|
|
# is expected to be converted to a datetime instance, but fails randomly
|
|
# (most of the time)
|
|
assert isinstance(validated_data['created_at'], datetime.datetime)
|
|
# assert isinstance(validated_data['created_at'], basestring)
|
|
|
|
|
|
def test_copy():
|
|
s1 = SchemaError('a', None)
|
|
s2 = copy.deepcopy(s1)
|
|
assert s1 is not s2
|
|
assert type(s1) is type(s2)
|
|
|
|
|
|
def test_inheritance():
|
|
def convert(data):
|
|
if isinstance(data, int):
|
|
return data + 1
|
|
return data
|
|
|
|
class MySchema(Schema):
|
|
def validate(self, data):
|
|
return super(MySchema, self).validate(convert(data))
|
|
|
|
s = {'k': int, 'd': {'k': int, 'l': [{'l': [int]}]}}
|
|
v = {'k': 1, 'd': {'k': 2, 'l': [{'l': [3, 4, 5]}]}}
|
|
d = MySchema(s).validate(v)
|
|
assert d['k'] == 2 and d['d']['k'] == 3 and d['d']['l'][0]['l'] == [4, 5, 6]
|