test_normalization.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. # -*- coding: utf-8 -*-
  2. from tempfile import NamedTemporaryFile
  3. from cerberus import Validator, errors
  4. from cerberus.tests import (assert_fail, assert_has_error, assert_normalized,
  5. assert_success)
  6. def test_coerce():
  7. schema = {'amount': {'coerce': int}}
  8. document = {'amount': '1'}
  9. expected = {'amount': 1}
  10. assert_normalized(document, expected, schema)
  11. def test_coerce_in_dictschema():
  12. schema = {'thing': {'type': 'dict',
  13. 'schema': {'amount': {'coerce': int}}}}
  14. document = {'thing': {'amount': '2'}}
  15. expected = {'thing': {'amount': 2}}
  16. assert_normalized(document, expected, schema)
  17. def test_coerce_in_listschema():
  18. schema = {'things': {'type': 'list',
  19. 'schema': {'coerce': int}}}
  20. document = {'things': ['1', '2', '3']}
  21. expected = {'things': [1, 2, 3]}
  22. assert_normalized(document, expected, schema)
  23. def test_coerce_in_dictschema_in_listschema():
  24. item_schema = {'type': 'dict', 'schema': {'amount': {'coerce': int}}}
  25. schema = {'things': {'type': 'list', 'schema': item_schema}}
  26. document = {'things': [{'amount': '2'}]}
  27. expected = {'things': [{'amount': 2}]}
  28. assert_normalized(document, expected, schema)
  29. def test_coerce_not_destructive():
  30. schema = {
  31. 'amount': {'coerce': int}
  32. }
  33. v = Validator(schema)
  34. doc = {'amount': '1'}
  35. v.validate(doc)
  36. assert v.document is not doc
  37. def test_coerce_catches_ValueError():
  38. schema = {'amount': {'coerce': int}}
  39. _errors = assert_fail({'amount': 'not_a_number'}, schema)
  40. _errors[0].info = () # ignore exception message here
  41. assert_has_error(_errors, 'amount', ('amount', 'coerce'),
  42. errors.COERCION_FAILED, int)
  43. def test_coerce_catches_TypeError():
  44. schema = {'name': {'coerce': str.lower}}
  45. _errors = assert_fail({'name': 1234}, schema)
  46. _errors[0].info = () # ignore exception message here
  47. assert_has_error(_errors, 'name', ('name', 'coerce'),
  48. errors.COERCION_FAILED, str.lower)
  49. def test_coerce_unknown():
  50. schema = {'foo': {'schema': {}, 'allow_unknown': {'coerce': int}}}
  51. document = {'foo': {'bar': '0'}}
  52. expected = {'foo': {'bar': 0}}
  53. assert_normalized(document, expected, schema)
  54. def test_custom_coerce_and_rename():
  55. class MyNormalizer(Validator):
  56. def __init__(self, multiplier, *args, **kwargs):
  57. super(MyNormalizer, self).__init__(*args, **kwargs)
  58. self.multiplier = multiplier
  59. def _normalize_coerce_multiply(self, value):
  60. return value * self.multiplier
  61. v = MyNormalizer(2, {'foo': {'coerce': 'multiply'}})
  62. assert v.normalized({'foo': 2})['foo'] == 4
  63. v = MyNormalizer(3, allow_unknown={'rename_handler': 'multiply'})
  64. assert v.normalized({3: None}) == {9: None}
  65. def test_coerce_chain():
  66. drop_prefix = lambda x: x[2:]
  67. upper = lambda x: x.upper()
  68. schema = {'foo': {'coerce': [hex, drop_prefix, upper]}}
  69. assert_normalized({'foo': 15}, {'foo': 'F'}, schema)
  70. def test_coerce_chain_aborts(validator):
  71. def dont_do_me(value):
  72. raise AssertionError('The coercion chain did not abort after an '
  73. 'error.')
  74. schema = {'foo': {'coerce': [hex, dont_do_me]}}
  75. validator({'foo': '0'}, schema)
  76. assert errors.COERCION_FAILED in validator._errors
  77. def test_coerce_non_digit_in_sequence(validator):
  78. # https://github.com/pyeve/cerberus/issues/211
  79. schema = {'data': {'type': 'list',
  80. 'schema': {'type': 'integer', 'coerce': int}}}
  81. document = {'data': ['q']}
  82. assert validator.validated(document, schema) is None
  83. assert (validator.validated(document, schema, always_return_document=True)
  84. == document) # noqa: W503
  85. def test_nullables_dont_fail_coerce():
  86. schema = {'foo': {'coerce': int, 'nullable': True, 'type': 'integer'}}
  87. document = {'foo': None}
  88. assert_normalized(document, document, schema)
  89. def test_normalized():
  90. schema = {'amount': {'coerce': int}}
  91. document = {'amount': '2'}
  92. expected = {'amount': 2}
  93. assert_normalized(document, expected, schema)
  94. def test_rename(validator):
  95. schema = {'foo': {'rename': 'bar'}}
  96. document = {'foo': 0}
  97. expected = {'bar': 0}
  98. # We cannot use assertNormalized here since there is bug where
  99. # Cerberus says that the renamed field is an unknown field:
  100. # {'bar': 'unknown field'}
  101. validator(document, schema, False)
  102. assert validator.document == expected
  103. def test_rename_handler():
  104. validator = Validator(allow_unknown={'rename_handler': int})
  105. schema = {}
  106. document = {'0': 'foo'}
  107. expected = {0: 'foo'}
  108. assert_normalized(document, expected, schema, validator)
  109. def test_purge_unknown():
  110. validator = Validator(purge_unknown=True)
  111. schema = {'foo': {'type': 'string'}}
  112. document = {'bar': 'foo'}
  113. expected = {}
  114. assert_normalized(document, expected, schema, validator)
  115. def test_purge_unknown_in_subschema():
  116. schema = {'foo': {'type': 'dict',
  117. 'schema': {'foo': {'type': 'string'}},
  118. 'purge_unknown': True}}
  119. document = {'foo': {'bar': ''}}
  120. expected = {'foo': {}}
  121. assert_normalized(document, expected, schema)
  122. def test_issue_147_complex():
  123. schema = {'revision': {'coerce': int}}
  124. document = {'revision': '5', 'file': NamedTemporaryFile(mode='w+')}
  125. document['file'].write(r'foobar')
  126. document['file'].seek(0)
  127. normalized = Validator(schema, allow_unknown=True).normalized(document)
  128. assert normalized['revision'] == 5
  129. assert normalized['file'].read() == 'foobar'
  130. document['file'].close()
  131. normalized['file'].close()
  132. def test_issue_147_nested_dict():
  133. schema = {'thing': {'type': 'dict',
  134. 'schema': {'amount': {'coerce': int}}}}
  135. ref_obj = '2'
  136. document = {'thing': {'amount': ref_obj}}
  137. normalized = Validator(schema).normalized(document)
  138. assert document is not normalized
  139. assert normalized['thing']['amount'] == 2
  140. assert ref_obj == '2'
  141. assert document['thing']['amount'] is ref_obj
  142. def test_coerce_in_valueschema():
  143. # https://github.com/pyeve/cerberus/issues/155
  144. schema = {'thing': {'type': 'dict',
  145. 'valueschema': {'coerce': int,
  146. 'type': 'integer'}}}
  147. document = {'thing': {'amount': '2'}}
  148. expected = {'thing': {'amount': 2}}
  149. assert_normalized(document, expected, schema)
  150. def test_coerce_in_keyschema():
  151. # https://github.com/pyeve/cerberus/issues/155
  152. schema = {'thing': {'type': 'dict',
  153. 'keyschema': {'coerce': int, 'type': 'integer'}}}
  154. document = {'thing': {'5': 'foo'}}
  155. expected = {'thing': {5: 'foo'}}
  156. assert_normalized(document, expected, schema)
  157. def test_coercion_of_sequence_items(validator):
  158. # https://github.com/pyeve/cerberus/issues/161
  159. schema = {'a_list': {'type': 'list', 'schema': {'type': 'float',
  160. 'coerce': float}}}
  161. document = {'a_list': [3, 4, 5]}
  162. expected = {'a_list': [3.0, 4.0, 5.0]}
  163. assert_normalized(document, expected, schema, validator)
  164. for x in validator.document['a_list']:
  165. assert isinstance(x, float)
  166. def test_default_missing():
  167. _test_default_missing({'default': 'bar_value'})
  168. def test_default_setter_missing():
  169. _test_default_missing({'default_setter': lambda doc: 'bar_value'})
  170. def _test_default_missing(default):
  171. bar_schema = {'type': 'string'}
  172. bar_schema.update(default)
  173. schema = {'foo': {'type': 'string'},
  174. 'bar': bar_schema}
  175. document = {'foo': 'foo_value'}
  176. expected = {'foo': 'foo_value', 'bar': 'bar_value'}
  177. assert_normalized(document, expected, schema)
  178. def test_default_existent():
  179. _test_default_existent({'default': 'bar_value'})
  180. def test_default_setter_existent():
  181. def raise_error(doc):
  182. raise RuntimeError('should not be called')
  183. _test_default_existent({'default_setter': raise_error})
  184. def _test_default_existent(default):
  185. bar_schema = {'type': 'string'}
  186. bar_schema.update(default)
  187. schema = {'foo': {'type': 'string'},
  188. 'bar': bar_schema}
  189. document = {'foo': 'foo_value', 'bar': 'non_default'}
  190. assert_normalized(document, document.copy(), schema)
  191. def test_default_none_nullable():
  192. _test_default_none_nullable({'default': 'bar_value'})
  193. def test_default_setter_none_nullable():
  194. def raise_error(doc):
  195. raise RuntimeError('should not be called')
  196. _test_default_none_nullable({'default_setter': raise_error})
  197. def _test_default_none_nullable(default):
  198. bar_schema = {'type': 'string',
  199. 'nullable': True}
  200. bar_schema.update(default)
  201. schema = {'foo': {'type': 'string'},
  202. 'bar': bar_schema}
  203. document = {'foo': 'foo_value', 'bar': None}
  204. assert_normalized(document, document.copy(), schema)
  205. def test_default_none_nonnullable():
  206. _test_default_none_nullable({'default': 'bar_value'})
  207. def test_default_setter_none_nonnullable():
  208. _test_default_none_nullable(
  209. {'default_setter': lambda doc: 'bar_value'})
  210. def _test_default_none_nonnullable(default):
  211. bar_schema = {'type': 'string',
  212. 'nullable': False}
  213. bar_schema.update(default)
  214. schema = {'foo': {'type': 'string'},
  215. 'bar': bar_schema}
  216. document = {'foo': 'foo_value', 'bar': 'bar_value'}
  217. assert_normalized(document, document.copy(), schema)
  218. def test_default_none_default_value():
  219. schema = {'foo': {'type': 'string'},
  220. 'bar': {'type': 'string',
  221. 'nullable': True,
  222. 'default': None}}
  223. document = {'foo': 'foo_value'}
  224. expected = {'foo': 'foo_value', 'bar': None}
  225. assert_normalized(document, expected, schema)
  226. def test_default_missing_in_subschema():
  227. _test_default_missing_in_subschema({'default': 'bar_value'})
  228. def test_default_setter_missing_in_subschema():
  229. _test_default_missing_in_subschema(
  230. {'default_setter': lambda doc: 'bar_value'})
  231. def _test_default_missing_in_subschema(default):
  232. bar_schema = {'type': 'string'}
  233. bar_schema.update(default)
  234. schema = {'thing': {'type': 'dict',
  235. 'schema': {'foo': {'type': 'string'},
  236. 'bar': bar_schema}}}
  237. document = {'thing': {'foo': 'foo_value'}}
  238. expected = {'thing': {'foo': 'foo_value',
  239. 'bar': 'bar_value'}}
  240. assert_normalized(document, expected, schema)
  241. def test_depending_default_setters():
  242. schema = {
  243. 'a': {'type': 'integer'},
  244. 'b': {'type': 'integer', 'default_setter': lambda d: d['a'] + 1},
  245. 'c': {'type': 'integer', 'default_setter': lambda d: d['b'] * 2},
  246. 'd': {'type': 'integer',
  247. 'default_setter': lambda d: d['b'] + d['c']}
  248. }
  249. document = {'a': 1}
  250. expected = {'a': 1, 'b': 2, 'c': 4, 'd': 6}
  251. assert_normalized(document, expected, schema)
  252. def test_circular_depending_default_setters(validator):
  253. schema = {
  254. 'a': {'type': 'integer', 'default_setter': lambda d: d['b'] + 1},
  255. 'b': {'type': 'integer', 'default_setter': lambda d: d['a'] + 1}
  256. }
  257. validator({}, schema)
  258. assert errors.SETTING_DEFAULT_FAILED in validator._errors
  259. def test_issue_250():
  260. # https://github.com/pyeve/cerberus/issues/250
  261. schema = {
  262. 'list': {
  263. 'type': 'list',
  264. 'schema': {
  265. 'type': 'dict',
  266. 'allow_unknown': True,
  267. 'schema': {'a': {'type': 'string'}}
  268. }
  269. }
  270. }
  271. document = {'list': {'is_a': 'mapping'}}
  272. assert_fail(document, schema,
  273. error=('list', ('list', 'type'), errors.BAD_TYPE,
  274. schema['list']['type']))
  275. def test_issue_250_no_type_pass_on_list():
  276. # https://github.com/pyeve/cerberus/issues/250
  277. schema = {
  278. 'list': {
  279. 'schema': {
  280. 'allow_unknown': True,
  281. 'type': 'dict',
  282. 'schema': {'a': {'type': 'string'}}
  283. }
  284. }
  285. }
  286. document = {'list': [{'a': 'known', 'b': 'unknown'}]}
  287. assert_normalized(document, document, schema)
  288. def test_issue_250_no_type_fail_on_dict():
  289. # https://github.com/pyeve/cerberus/issues/250
  290. schema = {
  291. 'list': {
  292. 'schema': {
  293. 'allow_unknown': True,
  294. 'schema': {'a': {'type': 'string'}}
  295. }
  296. }
  297. }
  298. document = {'list': {'a': {'a': 'known'}}}
  299. assert_fail(document, schema,
  300. error=('list', ('list', 'schema'), errors.BAD_TYPE_FOR_SCHEMA,
  301. schema['list']['schema']))
  302. def test_issue_250_no_type_fail_pass_on_other():
  303. # https://github.com/pyeve/cerberus/issues/250
  304. schema = {
  305. 'list': {
  306. 'schema': {
  307. 'allow_unknown': True,
  308. 'schema': {'a': {'type': 'string'}}
  309. }
  310. }
  311. }
  312. document = {'list': 1}
  313. assert_normalized(document, document, schema)
  314. def test_allow_unknown_with_of_rules():
  315. # https://github.com/pyeve/cerberus/issues/251
  316. schema = {
  317. 'test': {
  318. 'oneof': [
  319. {
  320. 'type': 'dict',
  321. 'allow_unknown': True,
  322. 'schema': {'known': {'type': 'string'}}
  323. },
  324. {
  325. 'type': 'dict',
  326. 'schema': {'known': {'type': 'string'}}
  327. },
  328. ]
  329. }
  330. }
  331. # check regression and that allow unknown does not cause any different
  332. # than expected behaviour for one-of.
  333. document = {'test': {'known': 's'}}
  334. assert_fail(document, schema,
  335. error=('test', ('test', 'oneof'),
  336. errors.ONEOF, schema['test']['oneof']))
  337. # check that allow_unknown is actually applied
  338. document = {'test': {'known': 's', 'unknown': 'asd'}}
  339. assert_success(document, schema)
  340. def test_271_normalising_tuples():
  341. # https://github.com/pyeve/cerberus/issues/271
  342. schema = {
  343. 'my_field': {
  344. 'type': 'list',
  345. 'schema': {'type': ('string', 'number', 'dict')}
  346. }
  347. }
  348. document = {'my_field': ('foo', 'bar', 42, 'albert',
  349. 'kandinsky', {'items': 23})}
  350. assert_success(document, schema)
  351. normalized = Validator(schema).normalized(document)
  352. assert normalized['my_field'] == ('foo', 'bar', 42, 'albert',
  353. 'kandinsky', {'items': 23})
  354. def test_allow_unknown_wo_schema():
  355. # https://github.com/pyeve/cerberus/issues/302
  356. v = Validator({'a': {'type': 'dict', 'allow_unknown': True}})
  357. v({'a': {}})
  358. def test_allow_unknown_with_purge_unknown():
  359. validator = Validator(purge_unknown=True)
  360. schema = {'foo': {'type': 'dict', 'allow_unknown': True}}
  361. document = {'foo': {'bar': True}, 'bar': 'foo'}
  362. expected = {'foo': {'bar': True}}
  363. assert_normalized(document, expected, schema, validator)
  364. def test_allow_unknown_with_purge_unknown_subdocument():
  365. validator = Validator(purge_unknown=True)
  366. schema = {
  367. 'foo': {
  368. 'type': 'dict',
  369. 'schema': {
  370. 'bar': {
  371. 'type': 'string'
  372. }
  373. },
  374. 'allow_unknown': True
  375. }
  376. }
  377. document = {'foo': {'bar': 'baz', 'corge': False}, 'thud': 'xyzzy'}
  378. expected = {'foo': {'bar': 'baz', 'corge': False}}
  379. assert_normalized(document, expected, schema, validator)