Codebase list voluptuous / b4f8bd7
Import upstream version 0.13.1+git20221023.1.1105b31 Debian Janitor 1 year, 4 months ago
12 changed file(s) with 410 addition(s) and 466 deletion(s). Raw diff Collapse all Expand all
00 # Changelog
11
2 ## [0.13.1]
3
4 **Fixes**:
5
6 - [#439](https://github.com/alecthomas/voluptuous/pull/454): Ignore `Enum` if it is unavailable
7 - [#456](https://github.com/alecthomas/voluptuous/pull/456): Fix email regex match for Python 2.7
8
9 **New**:
10
11 - [#457](https://github.com/alecthomas/voluptuous/pull/457): Enable github actions
12 - [#462](https://github.com/alecthomas/voluptuous/pull/462): Convert codebase to adhere to `flake8` W504 (PEP 8)
13 - [#459](https://github.com/alecthomas/voluptuous/pull/459): Enable `flake8` in github actions
14 - [#464](https://github.com/alecthomas/voluptuous/pull/464): `pytest` migration + enable Python 3.10
15
216 ## [0.13.0]
317
418 **Changes**:
519
6 - [#450](https://github.com/alecthomas/voluptuous/pull/450): Display valid Enum values in Coerce
20 - [#450](https://github.com/alecthomas/voluptuous/pull/450): Display valid `Enum` values in `Coerce`
721
822 ## [0.12.2]
923
1024 **Fixes**:
11 - [#439](https://github.com/alecthomas/voluptuous/issues/439): Revert Breaking Maybe change in 0.12.1
25
26 - [#439](https://github.com/alecthomas/voluptuous/issues/439): Revert Breaking `Maybe` change in 0.12.1
1227 - [#447](https://github.com/alecthomas/voluptuous/issues/447): Fix Email Regex to not match on extra characters
1328
1429 ## [0.12.1]
00 Metadata-Version: 2.1
11 Name: voluptuous
2 Version: 0.13.0
3 Summary: UNKNOWN
2 Version: 0.13.1
43 Home-page: https://github.com/alecthomas/voluptuous
4 Download-URL: https://pypi.python.org/pypi/voluptuous
55 Author: Alec Thomas
66 Author-email: alec@swapoff.org
7 License: BSD
8 Download-URL: https://pypi.python.org/pypi/voluptuous
7 License: BSD-3-Clause
98 Platform: any
109 Classifier: Development Status :: 5 - Production/Stable
1110 Classifier: Intended Audience :: Developers
1817 Classifier: Programming Language :: Python :: 3.7
1918 Classifier: Programming Language :: Python :: 3.8
2019 Classifier: Programming Language :: Python :: 3.9
20 Classifier: Programming Language :: Python :: 3.10
2121 Description-Content-Type: text/markdown
2222 License-File: COPYING
2323
4444
4545 It has three goals:
4646
47 1. Simplicity.
48 2. Support for complex data structures.
49 3. Provide useful error messages.
47 1. Simplicity.
48 2. Support for complex data structures.
49 3. Provide useful error messages.
5050
5151 ## Contact
5252
9494 Twitter's [user search API](https://dev.twitter.com/rest/reference/get/users/search) accepts
9595 query URLs like:
9696
97 ```
97 ```bash
9898 $ curl 'https://api.twitter.com/1.1/users/search.json?q=python&per_page=20&page=1'
9999 ```
100100
107107 ... 'per_page': int,
108108 ... 'page': int,
109109 ... })
110
111110 ```
112111
113112 This schema very succinctly and roughly describes the data required by
124123 ... Required('per_page', default=5): All(int, Range(min=1, max=20)),
125124 ... 'page': All(int, Range(min=0)),
126125 ... })
127
128126 ```
129127
130128 This schema fully enforces the interface defined in Twitter's
141139 ... exc = e
142140 >>> str(exc) == "required key not provided @ data['q']"
143141 True
144
145142 ```
146143
147144 ...must be a string:
154151 ... exc = e
155152 >>> str(exc) == "expected str for dictionary value @ data['q']"
156153 True
157
158154 ```
159155
160156 ...and must be at least one character in length:
169165 True
170166 >>> schema({'q': '#topic'}) == {'q': '#topic', 'per_page': 5}
171167 True
172
173168 ```
174169
175170 "per\_page" is a positive integer no greater than 20:
189184 ... exc = e
190185 >>> str(exc) == "value must be at least 1 for dictionary value @ data['per_page']"
191186 True
192
193187 ```
194188
195189 "page" is an integer \>= 0:
204198 "expected int for dictionary value @ data['per_page']"
205199 >>> schema({'q': '#topic', 'page': 1}) == {'q': '#topic', 'page': 1, 'per_page': 5}
206200 True
207
208201 ```
209202
210203 ## Defining schemas
224217 >>> schema = Schema('a string')
225218 >>> schema('a string')
226219 'a string'
227
228220 ```
229221
230222 ### Types
243235 ... exc = e
244236 >>> str(exc) == "expected int"
245237 True
246
247238 ```
248239
249240 ### URLs
262253 ... exc = e
263254 >>> str(exc) == "expected a URL"
264255 True
265
266256 ```
267257
268258 ### Lists
278268 [1, 1, 1]
279269 >>> schema(['a', 1, 'string', 1, 'string'])
280270 ['a', 1, 'string', 1, 'string']
281
282271 ```
283272
284273 However, an empty list (`[]`) is treated as is. If you want to specify a list that can
300289 []
301290 >>> schema([1, 2])
302291 [1, 2]
303
304292 ```
305293
306294 ### Sets and frozensets
333321 ... exc = e
334322 >>> str(exc) == 'expected a frozenset'
335323 True
336
337324 ```
338325
339326 However, an empty set (`set()`) is treated as is. If you want to specify a set
353340 >>> schema = Schema(set)
354341 >>> schema({1, 2}) == {1, 2}
355342 True
356
357343 ```
358344
359345 ### Validation functions
373359 >>> from datetime import datetime
374360 >>> def Date(fmt='%Y-%m-%d'):
375361 ... return lambda v: datetime.strptime(v, fmt)
376
377362 ```
378363
379364 ```pycon
387372 ... exc = e
388373 >>> str(exc) == "not a valid value"
389374 True
390
391375 ```
392376
393377 In addition to simply determining if a value is valid, validators may
408392 except ValueError:
409393 raise Invalid(msg or ('expected %s' % type.__name__))
410394 return f
411
412395 ```
413396
414397 This example also shows a common idiom where an optional human-readable
424407 >>> schema = Schema({1: 'one', 2: 'two'})
425408 >>> schema({1: 'one'})
426409 {1: 'one'}
427
428410 ```
429411
430412 #### Extra dictionary keys
441423 ... exc = e
442424 >>> str(exc) == "extra keys not allowed @ data[1]"
443425 True
444
445426 ```
446427
447428 This behaviour can be altered on a per-schema basis. To allow
453434 >>> schema = Schema({2: 3}, extra=ALLOW_EXTRA)
454435 >>> schema({1: 2, 2: 3})
455436 {1: 2, 2: 3}
456
457437 ```
458438
459439 To remove additional keys use
464444 >>> schema = Schema({2: 3}, extra=REMOVE_EXTRA)
465445 >>> schema({1: 2, 2: 3})
466446 {2: 3}
467
468447 ```
469448
470449 It can also be overridden per-dictionary by using the catch-all marker
475454 >>> schema = Schema({1: {Extra: object}})
476455 >>> schema({1: {'foo': 'bar'}})
477456 {1: {'foo': 'bar'}}
478
479457 ```
480458
481459 #### Required dictionary keys
486464 >>> schema = Schema({1: 2, 3: 4})
487465 >>> schema({3: 4})
488466 {3: 4}
489
490467 ```
491468
492469 Similarly to how extra\_ keys work, this behaviour can be overridden
501478 ... exc = e
502479 >>> str(exc) == "required key not provided @ data[1]"
503480 True
504
505481 ```
506482
507483 And per-key, with the marker token `Required(key)`:
517493 True
518494 >>> schema({1: 2})
519495 {1: 2}
520
521496 ```
522497
523498 #### Optional dictionary keys
544519 ... exc = e
545520 >>> str(exc) == "extra keys not allowed @ data[4]"
546521 True
547
548522 ```
549523
550524 ```pycon
551525 >>> schema({1: 2, 3: 4})
552526 {1: 2, 3: 4}
553
554527 ```
555528
556529 ### Recursive / nested schema
562535 >>> recursive = Schema({"more": Self, "value": int})
563536 >>> recursive({"more": {"value": 42}, "value": 41}) == {'more': {'value': 42}, 'value': 41}
564537 True
565
566538 ```
567539
568540 ### Extending an existing Schema
577549 >>> person_with_age = person.extend({'age': int})
578550 >>> sorted(list(person_with_age.schema.keys()))
579551 ['age', 'name']
580
581552 ```
582553
583554 The original `Schema` remains unchanged.
598569 >>> schema = Schema(Object({'q': 'one'}, cls=Structure))
599570 >>> schema(Structure(q='one'))
600571 <Structure(q='one')>
601
602572 ```
603573
604574 ### Allow None values
612582 >>> schema(None)
613583 >>> schema(5)
614584 5
615
616585 ```
617586
618587 ## Error reporting
627596 exception. This is especially useful when you want to catch `Invalid`
628597 exceptions and give some feedback to the user, for instance in the context of
629598 an HTTP API.
630
631599
632600 ```pycon
633601 >>> def validate_email(email):
649617 'This email is invalid.'
650618 >>> exc.error_message
651619 'This email is invalid.'
652
653620 ```
654621
655622 The `path` attribute is used during error reporting, but also during matching
664631
665632 ```pycon
666633 >>> schema = Schema([[2, 3], 6])
667
668634 ```
669635
670636 Each value in the top-level list is matched depth-first in-order. Given
681647 ... exc = e
682648 >>> str(exc) == "not a valid value @ data[0][0]"
683649 True
684
685650 ```
686651
687652 If we pass the data `[6]`, the `6` is not a list type and so will not
691656 ```pycon
692657 >>> schema([6])
693658 [6]
694
695659 ```
696660
697661 ## Multi-field validation
708672 raise Invalid('passwords must match')
709673 return passwords
710674
711 s=Schema(All(
675 schema = Schema(All(
712676 # First "pass" for field types
713 {'password':str, 'password_again':str},
677 {'password': str, 'password_again': str},
714678 # Follow up the first "pass" with your multi-field rules
715679 passwords_must_match
716680 ))
717681
718682 # valid
719 s({'password':'123', 'password_again':'123'})
683 schema({'password': '123', 'password_again': '123'})
720684
721685 # raises MultipleInvalid: passwords must match
722 s({'password':'123', 'password_again':'and now for something completely different'})
686 schema({'password': '123', 'password_again': 'and now for something completely different'})
723687
724688 ```
725689
730694 The flipside is that if the first "pass" of validation fails, your
731695 cross-field validator will not run:
732696
733 ```
697 ```python
734698 # raises Invalid because password_again is not a string
735699 # passwords_must_match() will not run because first-pass validation already failed
736 s({'password':'123', 'password_again': 1337})
700 schema({'password': '123', 'password_again': 1337})
737701 ```
738702
739703 ## Running tests
740704
741 Voluptuous is using nosetests:
742
743 $ nosetests
744
705 Voluptuous is using `pytest`:
706
707 ```bash
708 $ pip install pytest
709 $ pytest
710 ```
711
712 To also include a coverage report:
713
714 ```bash
715 $ pip install pytest pytest-cov coverage>=3.0
716 $ pytest --cov=voluptuous voluptuous/tests/
717 ```
745718
746719 ## Other libraries and inspirations
747720
756729
757730 I greatly prefer the light-weight style promoted by these libraries to
758731 the complexity of libraries like FormEncode.
759
760
2020
2121 It has three goals:
2222
23 1. Simplicity.
24 2. Support for complex data structures.
25 3. Provide useful error messages.
23 1. Simplicity.
24 2. Support for complex data structures.
25 3. Provide useful error messages.
2626
2727 ## Contact
2828
7070 Twitter's [user search API](https://dev.twitter.com/rest/reference/get/users/search) accepts
7171 query URLs like:
7272
73 ```
73 ```bash
7474 $ curl 'https://api.twitter.com/1.1/users/search.json?q=python&per_page=20&page=1'
7575 ```
7676
8383 ... 'per_page': int,
8484 ... 'page': int,
8585 ... })
86
8786 ```
8887
8988 This schema very succinctly and roughly describes the data required by
10099 ... Required('per_page', default=5): All(int, Range(min=1, max=20)),
101100 ... 'page': All(int, Range(min=0)),
102101 ... })
103
104102 ```
105103
106104 This schema fully enforces the interface defined in Twitter's
117115 ... exc = e
118116 >>> str(exc) == "required key not provided @ data['q']"
119117 True
120
121118 ```
122119
123120 ...must be a string:
130127 ... exc = e
131128 >>> str(exc) == "expected str for dictionary value @ data['q']"
132129 True
133
134130 ```
135131
136132 ...and must be at least one character in length:
145141 True
146142 >>> schema({'q': '#topic'}) == {'q': '#topic', 'per_page': 5}
147143 True
148
149144 ```
150145
151146 "per\_page" is a positive integer no greater than 20:
165160 ... exc = e
166161 >>> str(exc) == "value must be at least 1 for dictionary value @ data['per_page']"
167162 True
168
169163 ```
170164
171165 "page" is an integer \>= 0:
180174 "expected int for dictionary value @ data['per_page']"
181175 >>> schema({'q': '#topic', 'page': 1}) == {'q': '#topic', 'page': 1, 'per_page': 5}
182176 True
183
184177 ```
185178
186179 ## Defining schemas
200193 >>> schema = Schema('a string')
201194 >>> schema('a string')
202195 'a string'
203
204196 ```
205197
206198 ### Types
219211 ... exc = e
220212 >>> str(exc) == "expected int"
221213 True
222
223214 ```
224215
225216 ### URLs
238229 ... exc = e
239230 >>> str(exc) == "expected a URL"
240231 True
241
242232 ```
243233
244234 ### Lists
254244 [1, 1, 1]
255245 >>> schema(['a', 1, 'string', 1, 'string'])
256246 ['a', 1, 'string', 1, 'string']
257
258247 ```
259248
260249 However, an empty list (`[]`) is treated as is. If you want to specify a list that can
276265 []
277266 >>> schema([1, 2])
278267 [1, 2]
279
280268 ```
281269
282270 ### Sets and frozensets
309297 ... exc = e
310298 >>> str(exc) == 'expected a frozenset'
311299 True
312
313300 ```
314301
315302 However, an empty set (`set()`) is treated as is. If you want to specify a set
329316 >>> schema = Schema(set)
330317 >>> schema({1, 2}) == {1, 2}
331318 True
332
333319 ```
334320
335321 ### Validation functions
349335 >>> from datetime import datetime
350336 >>> def Date(fmt='%Y-%m-%d'):
351337 ... return lambda v: datetime.strptime(v, fmt)
352
353338 ```
354339
355340 ```pycon
363348 ... exc = e
364349 >>> str(exc) == "not a valid value"
365350 True
366
367351 ```
368352
369353 In addition to simply determining if a value is valid, validators may
384368 except ValueError:
385369 raise Invalid(msg or ('expected %s' % type.__name__))
386370 return f
387
388371 ```
389372
390373 This example also shows a common idiom where an optional human-readable
400383 >>> schema = Schema({1: 'one', 2: 'two'})
401384 >>> schema({1: 'one'})
402385 {1: 'one'}
403
404386 ```
405387
406388 #### Extra dictionary keys
417399 ... exc = e
418400 >>> str(exc) == "extra keys not allowed @ data[1]"
419401 True
420
421402 ```
422403
423404 This behaviour can be altered on a per-schema basis. To allow
429410 >>> schema = Schema({2: 3}, extra=ALLOW_EXTRA)
430411 >>> schema({1: 2, 2: 3})
431412 {1: 2, 2: 3}
432
433413 ```
434414
435415 To remove additional keys use
440420 >>> schema = Schema({2: 3}, extra=REMOVE_EXTRA)
441421 >>> schema({1: 2, 2: 3})
442422 {2: 3}
443
444423 ```
445424
446425 It can also be overridden per-dictionary by using the catch-all marker
451430 >>> schema = Schema({1: {Extra: object}})
452431 >>> schema({1: {'foo': 'bar'}})
453432 {1: {'foo': 'bar'}}
454
455433 ```
456434
457435 #### Required dictionary keys
462440 >>> schema = Schema({1: 2, 3: 4})
463441 >>> schema({3: 4})
464442 {3: 4}
465
466443 ```
467444
468445 Similarly to how extra\_ keys work, this behaviour can be overridden
477454 ... exc = e
478455 >>> str(exc) == "required key not provided @ data[1]"
479456 True
480
481457 ```
482458
483459 And per-key, with the marker token `Required(key)`:
493469 True
494470 >>> schema({1: 2})
495471 {1: 2}
496
497472 ```
498473
499474 #### Optional dictionary keys
520495 ... exc = e
521496 >>> str(exc) == "extra keys not allowed @ data[4]"
522497 True
523
524498 ```
525499
526500 ```pycon
527501 >>> schema({1: 2, 3: 4})
528502 {1: 2, 3: 4}
529
530503 ```
531504
532505 ### Recursive / nested schema
538511 >>> recursive = Schema({"more": Self, "value": int})
539512 >>> recursive({"more": {"value": 42}, "value": 41}) == {'more': {'value': 42}, 'value': 41}
540513 True
541
542514 ```
543515
544516 ### Extending an existing Schema
553525 >>> person_with_age = person.extend({'age': int})
554526 >>> sorted(list(person_with_age.schema.keys()))
555527 ['age', 'name']
556
557528 ```
558529
559530 The original `Schema` remains unchanged.
574545 >>> schema = Schema(Object({'q': 'one'}, cls=Structure))
575546 >>> schema(Structure(q='one'))
576547 <Structure(q='one')>
577
578548 ```
579549
580550 ### Allow None values
588558 >>> schema(None)
589559 >>> schema(5)
590560 5
591
592561 ```
593562
594563 ## Error reporting
603572 exception. This is especially useful when you want to catch `Invalid`
604573 exceptions and give some feedback to the user, for instance in the context of
605574 an HTTP API.
606
607575
608576 ```pycon
609577 >>> def validate_email(email):
625593 'This email is invalid.'
626594 >>> exc.error_message
627595 'This email is invalid.'
628
629596 ```
630597
631598 The `path` attribute is used during error reporting, but also during matching
640607
641608 ```pycon
642609 >>> schema = Schema([[2, 3], 6])
643
644610 ```
645611
646612 Each value in the top-level list is matched depth-first in-order. Given
657623 ... exc = e
658624 >>> str(exc) == "not a valid value @ data[0][0]"
659625 True
660
661626 ```
662627
663628 If we pass the data `[6]`, the `6` is not a list type and so will not
667632 ```pycon
668633 >>> schema([6])
669634 [6]
670
671635 ```
672636
673637 ## Multi-field validation
684648 raise Invalid('passwords must match')
685649 return passwords
686650
687 s=Schema(All(
651 schema = Schema(All(
688652 # First "pass" for field types
689 {'password':str, 'password_again':str},
653 {'password': str, 'password_again': str},
690654 # Follow up the first "pass" with your multi-field rules
691655 passwords_must_match
692656 ))
693657
694658 # valid
695 s({'password':'123', 'password_again':'123'})
659 schema({'password': '123', 'password_again': '123'})
696660
697661 # raises MultipleInvalid: passwords must match
698 s({'password':'123', 'password_again':'and now for something completely different'})
662 schema({'password': '123', 'password_again': 'and now for something completely different'})
699663
700664 ```
701665
706670 The flipside is that if the first "pass" of validation fails, your
707671 cross-field validator will not run:
708672
709 ```
673 ```python
710674 # raises Invalid because password_again is not a string
711675 # passwords_must_match() will not run because first-pass validation already failed
712 s({'password':'123', 'password_again': 1337})
676 schema({'password': '123', 'password_again': 1337})
713677 ```
714678
715679 ## Running tests
716680
717 Voluptuous is using nosetests:
718
719 $ nosetests
720
681 Voluptuous is using `pytest`:
682
683 ```bash
684 $ pip install pytest
685 $ pytest
686 ```
687
688 To also include a coverage report:
689
690 ```bash
691 $ pip install pytest pytest-cov coverage>=3.0
692 $ pytest --cov=voluptuous voluptuous/tests/
693 ```
721694
722695 ## Other libraries and inspirations
723696
0 [nosetests]
1 doctest-extension = md
2 with-doctest = 1
3 where = .
4
50 [egg_info]
61 tag_build =
72 tag_date = 0
1818 description=description,
1919 long_description=long_description,
2020 long_description_content_type='text/markdown',
21 license='BSD',
21 license='BSD-3-Clause',
2222 platforms=['any'],
2323 packages=['voluptuous'],
2424 author='Alec Thomas',
3535 'Programming Language :: Python :: 3.7',
3636 'Programming Language :: Python :: 3.8',
3737 'Programming Language :: Python :: 3.9',
38 'Programming Language :: Python :: 3.10',
3839 ]
3940 )
44 from voluptuous.util import *
55 from voluptuous.error import *
66
7 __version__ = '0.13.0'
7 __version__ = '0.13.1'
88 __author__ = 'alecthomas'
307307
308308 # Keys that may be required
309309 all_required_keys = set(key for key in schema
310 if key is not Extra and
311 ((self.required and not isinstance(key, (Optional, Remove))) or
312 isinstance(key, Required)))
310 if key is not Extra
311 and ((self.required
312 and not isinstance(key, (Optional, Remove)))
313 or isinstance(key, Required)))
313314
314315 # Keys that may have defaults
315316 all_default_keys = set(key for key in schema
316 if isinstance(key, Required) or
317 isinstance(key, Optional))
317 if isinstance(key, Required)
318 or isinstance(key, Optional))
318319
319320 _compiled_schema = {}
320321 for skey, svalue in iteritems(schema):
864865 for i, check_ in priority:
865866 if check_(key_):
866867 return i
867 # values have hightest priorities
868 # values have highest priorities
868869 return 0
869870
870871 return item_priority
972973 return self.__str__()
973974
974975
975 # Markers.py
976
977
978976 class Marker(object):
979977 """Mark nodes for special treatment."""
980978
0 import collections
01 import copy
1 import collections
2 from enum import Enum
2
3 try:
4 from enum import Enum
5 except ImportError:
6 Enum = None
37 import os
48 import sys
59
6 from nose.tools import assert_equal, assert_false, assert_raises, assert_true
7
8 from voluptuous import (
9 Schema, Required, Exclusive, Inclusive, Optional, Extra, Invalid, In, Remove,
10 Literal, Url, MultipleInvalid, LiteralInvalid, TypeInvalid, NotIn, Match, Email,
11 Replace, Range, Coerce, All, Any, Length, FqdnUrl, ALLOW_EXTRA, PREVENT_EXTRA,
12 validate, ExactSequence, Equal, Unordered, Number, Maybe, Datetime, Date,
13 Contains, Marker, IsDir, IsFile, PathExists, SomeOf, TooManyValid, Self,
14 raises, Union, Clamp)
10 import pytest
11 from voluptuous import (ALLOW_EXTRA, PREVENT_EXTRA, All, Any, Clamp, Coerce,
12 Contains, Date, Datetime, Email, Equal, ExactSequence,
13 Exclusive, Extra, FqdnUrl, In, Inclusive, Invalid,
14 IsDir, IsFile, Length, Literal, LiteralInvalid, Marker,
15 Match, Maybe, MultipleInvalid, NotIn, Number, Object,
16 Optional, PathExists, Range, Remove, Replace, Required,
17 Schema, Self, SomeOf, TooManyValid, TypeInvalid, Union,
18 Unordered, Url, raises, validate)
1519 from voluptuous.humanize import humanize_error
16 from voluptuous.util import u, Capitalize, Lower, Strip, Title, Upper
20 from voluptuous.util import Capitalize, Lower, Strip, Title, Upper, u
1721
1822
1923 def test_new_required_test():
2024 schema = Schema({
2125 'my_key': All(int, Range(1, 20)),
2226 }, required=True)
23 assert_true(schema.required)
27 assert schema.required
2428
2529
2630 def test_exact_sequence():
3135 assert True
3236 else:
3337 assert False, "Did not raise Invalid"
34 assert_equal(schema([1, 2]), [1, 2])
38 assert schema([1, 2]) == [1, 2]
3539
3640
3741 def test_required():
4145 try:
4246 schema({})
4347 except Invalid as e:
44 assert_equal(str(e), "required key not provided @ data['q']")
48 assert str(e) == "required key not provided @ data['q']"
4549 else:
4650 assert False, "Did not raise Invalid"
4751
5054 """Verify that Required does not break Extra."""
5155 schema = Schema({Required('toaster'): str, Extra: object})
5256 r = schema({'toaster': 'blue', 'another_valid_key': 'another_valid_value'})
53 assert_equal(
54 r, {'toaster': 'blue', 'another_valid_key': 'another_valid_value'})
57 assert r == {'toaster': 'blue', 'another_valid_key': 'another_valid_value'}
5558
5659
5760 def test_iterate_candidates():
6265 }
6366 # toaster should be first.
6467 from voluptuous.schema_builder import _iterate_mapping_candidates
65 assert_equal(_iterate_mapping_candidates(schema)[0][0], 'toaster')
68 assert _iterate_mapping_candidates(schema)[0][0] == 'toaster'
6669
6770
6871 def test_in():
7275 try:
7376 schema({"color": "orange"})
7477 except Invalid as e:
75 assert_equal(str(e), "value must be one of ['blue', 'red', 'yellow'] for dictionary value @ data['color']")
78 assert str(e) == "value must be one of ['blue', 'red', 'yellow'] for dictionary value @ data['color']"
7679 else:
7780 assert False, "Did not raise InInvalid"
7881
8487 try:
8588 schema({"color": "blue"})
8689 except Invalid as e:
87 assert_equal(str(e), "value must not be one of ['blue', 'red', 'yellow'] for dictionary value @ data['color']")
90 assert str(e) == "value must not be one of ['blue', 'red', 'yellow'] for dictionary value @ data['color']"
8891 else:
8992 assert False, "Did not raise NotInInvalid"
9093
9699 try:
97100 schema({'color': ['blue', 'yellow']})
98101 except Invalid as e:
99 assert_equal(str(e),
100 "value is not allowed for dictionary value @ data['color']")
102 assert str(e) == "value is not allowed for dictionary value @ data['color']"
101103
102104
103105 def test_remove():
112114 # remove keys by type
113115 schema = Schema({"weight": float,
114116 "amount": int,
115 # remvove str keys with int values
117 # remove str keys with int values
116118 Remove(str): int,
117119 # keep str keys with str values
118120 str: str})
129131 # remove value from list
130132 schema = Schema([Remove(1), int])
131133 out_ = schema([1, 2, 3, 4, 1, 5, 6, 1, 1, 1])
132 assert_equal(out_, [2, 3, 4, 5, 6])
134 assert out_ == [2, 3, 4, 5, 6]
133135
134136 # remove values from list by type
135137 schema = Schema([1.0, Remove(float), int])
136138 out_ = schema([1, 2, 1.0, 2.0, 3.0, 4])
137 assert_equal(out_, [1, 2, 1.0, 4])
139 assert out_ == [1, 2, 1.0, 4]
138140
139141
140142 def test_extra_empty_errors():
153155 try:
154156 schema([{"c": 1}])
155157 except Invalid as e:
156 assert_equal(str(e), "{'c': 1} not match for {'b': 1} @ data[0]")
158 assert str(e) == "{'c': 1} not match for {'b': 1} @ data[0]"
157159 else:
158160 assert False, "Did not raise Invalid"
159161
161163 try:
162164 schema({"b": 1})
163165 except MultipleInvalid as e:
164 assert_equal(str(e), "{'b': 1} not match for {'a': 1}")
165 assert_equal(len(e.errors), 1)
166 assert_equal(type(e.errors[0]), LiteralInvalid)
166 assert str(e) == "{'b': 1} not match for {'a': 1}"
167 assert len(e.errors) == 1
168 assert type(e.errors[0]) == LiteralInvalid
167169 else:
168170 assert False, "Did not raise Invalid"
169171
178180 try:
179181 schema(None)
180182 except MultipleInvalid as e:
181 assert_equal(str(e), "expected C1")
182 assert_equal(len(e.errors), 1)
183 assert_equal(type(e.errors[0]), TypeInvalid)
183 assert str(e) == "expected C1"
184 assert len(e.errors) == 1
185 assert type(e.errors[0]) == TypeInvalid
184186 else:
185187 assert False, "Did not raise Invalid"
186188
194196 try:
195197 schema(None)
196198 except MultipleInvalid as e:
197 assert_equal(str(e), "expected C2")
198 assert_equal(len(e.errors), 1)
199 assert_equal(type(e.errors[0]), TypeInvalid)
199 assert str(e) == "expected C2"
200 assert len(e.errors) == 1
201 assert type(e.errors[0]) == TypeInvalid
200202 else:
201203 assert False, "Did not raise Invalid"
202204
215217 try:
216218 schema({"email": None})
217219 except MultipleInvalid as e:
218 assert_equal(str(e),
219 "expected an email address for dictionary value @ data['email']")
220 assert str(e) == "expected an email address for dictionary value @ data['email']"
220221 else:
221222 assert False, "Did not raise Invalid for None URL"
222223
227228 try:
228229 schema({"email": ''})
229230 except MultipleInvalid as e:
230 assert_equal(str(e),
231 "expected an email address for dictionary value @ data['email']")
231 assert str(e) == "expected an email address for dictionary value @ data['email']"
232232 else:
233233 assert False, "Did not raise Invalid for empty string URL"
234234
239239 try:
240240 schema({"email": 'a@.com'})
241241 except MultipleInvalid as e:
242 assert_equal(str(e),
243 "expected an email address for dictionary value @ data['email']")
242 assert str(e) == "expected an email address for dictionary value @ data['email']"
244243 else:
245244 assert False, "Did not raise Invalid for empty string URL"
245
246246
247247 def test_email_validation_with_bad_data():
248248 """ Test with bad data in email address """
251251 try:
252252 schema({"email": 'john@voluptuous.com>'})
253253 except MultipleInvalid as e:
254 assert_equal(str(e),
255 "expected an email address for dictionary value @ data['email']")
254 assert str(e) == "expected an email address for dictionary value @ data['email']"
256255 else:
257256 assert False, "Did not raise Invalid for bad email " + email
258257
271270 try:
272271 schema({"url": "http://localhost/"})
273272 except MultipleInvalid as e:
274 assert_equal(str(e),
275 "expected a fully qualified domain name URL for dictionary value @ data['url']")
273 assert str(e) == "expected a fully qualified domain name URL for dictionary value @ data['url']"
276274 else:
277275 assert False, "Did not raise Invalid for None URL"
278276
283281 try:
284282 schema({"url": None})
285283 except MultipleInvalid as e:
286 assert_equal(str(e),
287 "expected a fully qualified domain name URL for dictionary value @ data['url']")
284 assert str(e) == "expected a fully qualified domain name URL for dictionary value @ data['url']"
288285 else:
289286 assert False, "Did not raise Invalid for None URL"
290287
295292 try:
296293 schema({"url": ''})
297294 except MultipleInvalid as e:
298 assert_equal(str(e),
299 "expected a fully qualified domain name URL for dictionary value @ data['url']")
295 assert str(e) == "expected a fully qualified domain name URL for dictionary value @ data['url']"
300296 else:
301297 assert False, "Did not raise Invalid for empty string URL"
302298
307303 try:
308304 schema({"url": 'http://'})
309305 except MultipleInvalid as e:
310 assert_equal(str(e),
311 "expected a fully qualified domain name URL for dictionary value @ data['url']")
306 assert str(e) == "expected a fully qualified domain name URL for dictionary value @ data['url']"
312307 else:
313308 assert False, "Did not raise Invalid for empty string URL"
314309
327322 try:
328323 schema({"url": None})
329324 except MultipleInvalid as e:
330 assert_equal(str(e),
331 "expected a URL for dictionary value @ data['url']")
325 assert str(e) == "expected a URL for dictionary value @ data['url']"
332326 else:
333327 assert False, "Did not raise Invalid for None URL"
334328
339333 try:
340334 schema({"url": ''})
341335 except MultipleInvalid as e:
342 assert_equal(str(e),
343 "expected a URL for dictionary value @ data['url']")
336 assert str(e) == "expected a URL for dictionary value @ data['url']"
344337 else:
345338 assert False, "Did not raise Invalid for empty string URL"
346339
351344 try:
352345 schema({"url": 'http://'})
353346 except MultipleInvalid as e:
354 assert_equal(str(e),
355 "expected a URL for dictionary value @ data['url']")
347 assert str(e) == "expected a URL for dictionary value @ data['url']"
356348 else:
357349 assert False, "Did not raise Invalid for empty string URL"
358350
416408 extension = {Required('a'): int}
417409 extended = base.extend(extension)
418410
419 assert_equal(len(base.schema), 1)
420 assert_true(isinstance(list(base.schema)[0], Optional))
421 assert_equal(len(extended.schema), 1)
422 assert_true((list(extended.schema)[0], Required))
411 assert len(base.schema) == 1
412 assert isinstance(list(base.schema)[0], Optional)
413 assert len(extended.schema) == 1
414 assert list(extended.schema)[0]
423415
424416
425417 def test_subschema_extension():
428420 extension = {'d': str, 'a': {'b': str, 'e': int}}
429421 extended = base.extend(extension)
430422
431 assert_equal(base.schema, {'a': {'b': int, 'c': float}})
432 assert_equal(extension, {'d': str, 'a': {'b': str, 'e': int}})
433 assert_equal(extended.schema, {'a': {'b': str, 'c': float, 'e': int}, 'd': str})
423 assert base.schema == {'a': {'b': int, 'c': float}}
424 assert extension == {'d': str, 'a': {'b': str, 'e': int}}
425 assert extended.schema == {'a': {'b': str, 'c': float, 'e': int}, 'd': str}
434426
435427
436428 def test_schema_extend_handles_schema_subclass():
448440
449441
450442 def test_equality():
451 assert_equal(Schema('foo'), Schema('foo'))
452
453 assert_equal(Schema(['foo', 'bar', 'baz']),
454 Schema(['foo', 'bar', 'baz']))
443 assert Schema('foo') == Schema('foo')
444
445 assert Schema(['foo', 'bar', 'baz']) == Schema(['foo', 'bar', 'baz'])
455446
456447 # Ensure two Schemas w/ two equivalent dicts initialized in a different
457448 # order are considered equal.
465456 dict_b['bar'] = 2
466457 dict_b['foo'] = 1
467458
468 assert_equal(Schema(dict_a), Schema(dict_b))
459 assert Schema(dict_a) == Schema(dict_b)
469460
470461
471462 def test_equality_negative():
472463 """Verify that Schema objects are not equal to string representations"""
473 assert_false(Schema('foo') == 'foo')
474
475 assert_false(Schema(['foo', 'bar']) == "['foo', 'bar']")
476 assert_false(Schema(['foo', 'bar']) == Schema("['foo', 'bar']"))
477
478 assert_false(Schema({'foo': 1, 'bar': 2}) == "{'foo': 1, 'bar': 2}")
479 assert_false(Schema({'foo': 1, 'bar': 2}) == Schema("{'foo': 1, 'bar': 2}"))
464 assert not Schema('foo') == 'foo'
465
466 assert not Schema(['foo', 'bar']) == "['foo', 'bar']"
467 assert not Schema(['foo', 'bar']) == Schema("['foo', 'bar']")
468
469 assert not Schema({'foo': 1, 'bar': 2}) == "{'foo': 1, 'bar': 2}"
470 assert not Schema({'foo': 1, 'bar': 2}) == Schema("{'foo': 1, 'bar': 2}")
480471
481472
482473 def test_inequality():
483 assert_true(Schema('foo') != 'foo')
484
485 assert_true(Schema(['foo', 'bar']) != "['foo', 'bar']")
486 assert_true(Schema(['foo', 'bar']) != Schema("['foo', 'bar']"))
487
488 assert_true(Schema({'foo': 1, 'bar': 2}) != "{'foo': 1, 'bar': 2}")
489 assert_true(Schema({'foo': 1, 'bar': 2}) != Schema("{'foo': 1, 'bar': 2}"))
474 assert Schema('foo') != 'foo'
475
476 assert Schema(['foo', 'bar']) != "['foo', 'bar']"
477 assert Schema(['foo', 'bar']) != Schema("['foo', 'bar']")
478
479 assert Schema({'foo': 1, 'bar': 2}) != "{'foo': 1, 'bar': 2}"
480 assert Schema({'foo': 1, 'bar': 2}) != Schema("{'foo': 1, 'bar': 2}")
490481
491482
492483 def test_inequality_negative():
493 assert_false(Schema('foo') != Schema('foo'))
494
495 assert_false(Schema(['foo', 'bar', 'baz']) !=
496 Schema(['foo', 'bar', 'baz']))
484 assert not Schema('foo') != Schema('foo')
485
486 assert not Schema(['foo', 'bar', 'baz']) != Schema(['foo', 'bar', 'baz'])
497487
498488 # Ensure two Schemas w/ two equivalent dicts initialized in a different
499489 # order are considered equal.
507497 dict_b['bar'] = 2
508498 dict_b['foo'] = 1
509499
510 assert_false(Schema(dict_a) != Schema(dict_b))
500 assert not Schema(dict_a) != Schema(dict_b)
511501
512502
513503 def test_repr():
520510 all_ = All('10', Coerce(int), msg='all msg')
521511 maybe_int = Maybe(int)
522512
523 assert_equal(repr(match), "Match('a pattern', msg='message')")
524 assert_equal(repr(replace), "Replace('you', 'I', msg='you and I')")
525 assert_equal(
526 repr(range_),
527 "Range(min=0, max=42, min_included=False, max_included=False, msg='number not in range')"
528 )
529 assert_equal(repr(coerce_), "Coerce(int, msg='moo')")
530 assert_equal(repr(all_), "All('10', Coerce(int, msg=None), msg='all msg')")
531 assert_equal(repr(maybe_int), "Any(None, %s, msg=None)" % str(int))
513 assert repr(match) == "Match('a pattern', msg='message')"
514 assert repr(replace) == "Replace('you', 'I', msg='you and I')"
515 assert repr(range_) == "Range(min=0, max=42, min_included=False, max_included=False, msg='number not in range')"
516 assert repr(coerce_) == "Coerce(int, msg='moo')"
517 assert repr(all_) == "All('10', Coerce(int, msg=None), msg='all msg')"
518 assert repr(maybe_int) == "Any(None, %s, msg=None)" % str(int)
532519
533520
534521 def test_list_validation_messages():
544531 try:
545532 schema(dict(even_numbers=[3]))
546533 except Invalid as e:
547 assert_equal(len(e.errors), 1, e.errors)
548 assert_equal(str(e.errors[0]), "3 is not even @ data['even_numbers'][0]")
549 assert_equal(str(e), "3 is not even @ data['even_numbers'][0]")
534 assert len(e.errors) == 1
535 assert str(e.errors[0]) == "3 is not even @ data['even_numbers'][0]"
536 assert str(e) == "3 is not even @ data['even_numbers'][0]"
550537 else:
551538 assert False, "Did not raise Invalid"
552539
565552 try:
566553 schema(dict(even_numbers=[3]))
567554 except Invalid as e:
568 assert_equal(len(e.errors), 1, e.errors)
569 assert_equal(str(e.errors[0]), "3 is not even @ data['even_numbers'][0]")
570 assert_equal(str(e), "3 is not even @ data['even_numbers'][0]")
555 assert len(e.errors) == 1
556 assert str(e.errors[0]) == "3 is not even @ data['even_numbers'][0]"
557 assert str(e) == "3 is not even @ data['even_numbers'][0]"
571558 else:
572559 assert False, "Did not raise Invalid"
573560
584571 try:
585572 schema(data)
586573 except MultipleInvalid as e:
587 assert_equal(
588 humanize_error(data, e),
589 "expected int for dictionary value @ data['a']. Got 'not an int'\n"
590 "expected str @ data['b'][0]. Got 123"
591 )
574 assert humanize_error(data, e) == "expected int for dictionary value @ data['a']. Got 'not an int'\nexpected str @ data['b'][0]. Got 123"
592575 else:
593576 assert False, 'Did not raise MultipleInvalid'
594577
595578
596579 def test_fix_157():
597580 s = Schema(All([Any('one', 'two', 'three')]), Length(min=1))
598 assert_equal(['one'], s(['one']))
599 assert_raises(MultipleInvalid, s, ['four'])
581 assert ['one'] == s(['one'])
582 pytest.raises(MultipleInvalid, s, ['four'])
600583
601584
602585 def test_range_inside():
603586 s = Schema(Range(min=0, max=10))
604 assert_equal(5, s(5))
587 assert 5 == s(5)
605588
606589
607590 def test_range_outside():
608591 s = Schema(Range(min=0, max=10))
609 assert_raises(MultipleInvalid, s, 12)
610 assert_raises(MultipleInvalid, s, -1)
592 with pytest.raises(MultipleInvalid):
593 s(12)
594 with pytest.raises(MultipleInvalid):
595 s(-1)
611596
612597
613598 def test_range_no_upper_limit():
614599 s = Schema(Range(min=0))
615 assert_equal(123, s(123))
616 assert_raises(MultipleInvalid, s, -1)
600 assert 123 == s(123)
601 with pytest.raises(MultipleInvalid):
602 s(-1)
617603
618604
619605 def test_range_no_lower_limit():
620606 s = Schema(Range(max=10))
621 assert_equal(-1, s(-1))
622 assert_raises(MultipleInvalid, s, 123)
607 assert -1 == s(-1)
608 with pytest.raises(MultipleInvalid):
609 s(123)
623610
624611
625612 def test_range_excludes_nan():
626613 s = Schema(Range(min=0, max=10))
627 assert_raises(MultipleInvalid, s, float('nan'))
614 pytest.raises(MultipleInvalid, s, float('nan'))
628615
629616
630617 def test_range_excludes_none():
631618 s = Schema(Range(min=0, max=10))
632 assert_raises(MultipleInvalid, s, None)
619 pytest.raises(MultipleInvalid, s, None)
633620
634621
635622 def test_range_excludes_string():
636623 s = Schema(Range(min=0, max=10))
637 assert_raises(MultipleInvalid, s, "abc")
624 with pytest.raises(MultipleInvalid):
625 s("abc")
638626
639627
640628 def test_range_excludes_unordered_object():
642630 pass
643631
644632 s = Schema(Range(min=0, max=10))
645 assert_raises(MultipleInvalid, s, MyObject())
633 pytest.raises(MultipleInvalid, s, MyObject())
646634
647635
648636 def test_clamp_inside():
649637 s = Schema(Clamp(min=1, max=10))
650 assert_equal(5, s(5))
638 assert 5 == s(5)
651639
652640
653641 def test_clamp_above():
654642 s = Schema(Clamp(min=1, max=10))
655 assert_equal(10, s(12))
643 assert 10 == s(12)
656644
657645
658646 def test_clamp_below():
659647 s = Schema(Clamp(min=1, max=10))
660 assert_equal(1, s(-3))
648 assert 1 == s(-3)
661649
662650
663651 def test_clamp_invalid():
664652 s = Schema(Clamp(min=1, max=10))
665653 if sys.version_info.major >= 3:
666 assert_raises(MultipleInvalid, s, None)
667 assert_raises(MultipleInvalid, s, "abc")
668 else:
669 assert_equal(1, s(None))
654 with pytest.raises(MultipleInvalid):
655 s(None)
656 with pytest.raises(MultipleInvalid):
657 s("abc")
658 else:
659 assert 1 == s(None)
670660
671661
672662 def test_length_ok():
673663 v1 = ['a', 'b', 'c']
674664 s = Schema(Length(min=1, max=10))
675 assert_equal(v1, s(v1))
665 assert v1 == s(v1)
676666 v2 = "abcde"
677 assert_equal(v2, s(v2))
667 assert v2 == s(v2)
678668
679669
680670 def test_length_too_short():
681671 v1 = []
682672 s = Schema(Length(min=1, max=10))
683 assert_raises(MultipleInvalid, s, v1)
684 v2 = ''
685 assert_raises(MultipleInvalid, s, v2)
673 with pytest.raises(MultipleInvalid):
674 s(v1)
675 with pytest.raises(MultipleInvalid):
676 v2 = ''
677 s(v2)
686678
687679
688680 def test_length_too_long():
689681 v = ['a', 'b', 'c']
690682 s = Schema(Length(min=0, max=2))
691 assert_raises(MultipleInvalid, s, v)
683 with pytest.raises(MultipleInvalid):
684 s(v)
692685
693686
694687 def test_length_invalid():
695688 v = None
696689 s = Schema(Length(min=0, max=2))
697 assert_raises(MultipleInvalid, s, v)
690 with pytest.raises(MultipleInvalid):
691 s(v)
698692
699693
700694 def test_equal():
701695 s = Schema(Equal(1))
702696 s(1)
703 assert_raises(Invalid, s, 2)
697 pytest.raises(Invalid, s, 2)
704698 s = Schema(Equal('foo'))
705699 s('foo')
706 assert_raises(Invalid, s, 'bar')
700 pytest.raises(Invalid, s, 'bar')
707701 s = Schema(Equal([1, 2]))
708702 s([1, 2])
709 assert_raises(Invalid, s, [])
710 assert_raises(Invalid, s, [1, 2, 3])
703 pytest.raises(Invalid, s, [])
704 pytest.raises(Invalid, s, [1, 2, 3])
711705 # Evaluates exactly, not through validators
712706 s = Schema(Equal(str))
713 assert_raises(Invalid, s, 'foo')
707 pytest.raises(Invalid, s, 'foo')
714708
715709
716710 def test_unordered():
719713 s([2, 1])
720714 s([1, 2])
721715 # Amount of errors is OK
722 assert_raises(Invalid, s, [2, 0])
723 assert_raises(MultipleInvalid, s, [0, 0])
716 pytest.raises(Invalid, s, [2, 0])
717 pytest.raises(MultipleInvalid, s, [0, 0])
724718 # Different length is NOK
725 assert_raises(Invalid, s, [1])
726 assert_raises(Invalid, s, [1, 2, 0])
727 assert_raises(MultipleInvalid, s, [1, 2, 0, 0])
719 pytest.raises(Invalid, s, [1])
720 pytest.raises(Invalid, s, [1, 2, 0])
721 pytest.raises(MultipleInvalid, s, [1, 2, 0, 0])
728722 # Other type than list or tuple is NOK
729 assert_raises(Invalid, s, 'foo')
730 assert_raises(Invalid, s, 10)
723 pytest.raises(Invalid, s, 'foo')
724 pytest.raises(Invalid, s, 10)
731725 # Validators are evaluated through as schemas
732726 s = Schema(Unordered([int, str]))
733727 s([1, '2'])
736730 s([{'foo': 3}, []])
737731 # Most accurate validators must be positioned on left
738732 s = Schema(Unordered([int, 3]))
739 assert_raises(Invalid, s, [3, 2])
733 pytest.raises(Invalid, s, [3, 2])
740734 s = Schema(Unordered([3, int]))
741735 s([3, 2])
742736
745739 s = Schema(Maybe(int))
746740 assert s(1) == 1
747741 assert s(None) is None
748 assert_raises(Invalid, s, 'foo')
742 pytest.raises(Invalid, s, 'foo')
749743
750744 s = Schema(Maybe({str: Coerce(int)}))
751745 assert s({'foo': '100'}) == {'foo': 100}
752746 assert s(None) is None
753 assert_raises(Invalid, s, {'foo': 'bar'})
747 pytest.raises(Invalid, s, {'foo': 'bar'})
754748
755749
756750 def test_maybe_accepts_msg():
771765 # Should trigger a MultipleInvalid exception
772766 schema(3)
773767 except MultipleInvalid as e:
774 assert_equal(str(e), "not a valid value")
768 assert str(e) == "not a valid value"
775769 else:
776770 assert False, "Did not raise correct Invalid"
777771
783777 try:
784778 s([123])
785779 except MultipleInvalid as e:
786 assert_equal(str(e), "not a valid value @ data[123]")
780 assert str(e) == "not a valid value @ data[123]"
787781 else:
788782 assert False, "Did not raise correct Invalid"
789783
790784 try:
791785 s({'var': 123})
792786 except MultipleInvalid as e:
793 assert_equal(str(e), "expected a list")
794 else:
795 assert False, "Did not raise correct Invalid"
787 assert str(e) == "expected a list"
788 else:
789 assert False, "Did not raise correct Invalid"
796790
797791
798792 def test_schema_empty_dict():
802796 try:
803797 s({'var': 123})
804798 except MultipleInvalid as e:
805 assert_equal(str(e), "extra keys not allowed @ data['var']")
799 assert str(e) == "extra keys not allowed @ data['var']"
806800 else:
807801 assert False, "Did not raise correct Invalid"
808802
809803 try:
810804 s([123])
811805 except MultipleInvalid as e:
812 assert_equal(str(e), "expected a dictionary")
806 assert str(e) == "expected a dictionary"
813807 else:
814808 assert False, "Did not raise correct Invalid"
815809
822816 try:
823817 s({'var': [123]})
824818 except MultipleInvalid as e:
825 assert_equal(str(e), "not a valid value for dictionary value @ data['var']")
819 assert str(e) == "not a valid value for dictionary value @ data['var']"
826820 else:
827821 assert False, "Did not raise correct Invalid"
828822
840834 def fn(arg):
841835 return arg
842836
843 assert_raises(Invalid, fn, 1.0)
837 pytest.raises(Invalid, fn, 1.0)
844838
845839
846840 def test_schema_decorator_match_with_kwargs():
856850 def fn(arg):
857851 return arg
858852
859 assert_raises(Invalid, fn, 1.0)
853 pytest.raises(Invalid, fn, 1.0)
860854
861855
862856 def test_schema_decorator_match_return_with_args():
872866 def fn(arg):
873867 return "hello"
874868
875 assert_raises(Invalid, fn, 1)
869 pytest.raises(Invalid, fn, 1)
876870
877871
878872 def test_schema_decorator_match_return_with_kwargs():
888882 def fn(arg):
889883 return "hello"
890884
891 assert_raises(Invalid, fn, 1)
885 pytest.raises(Invalid, fn, 1)
892886
893887
894888 def test_schema_decorator_return_only_match():
904898 def fn(arg):
905899 return "hello"
906900
907 assert_raises(Invalid, fn, 1)
901 pytest.raises(Invalid, fn, 1)
908902
909903
910904 def test_schema_decorator_partial_match_called_with_args():
920914 def fn(arg1, arg2):
921915 return arg1
922916
923 assert_raises(Invalid, fn, "bar", "foo")
917 pytest.raises(Invalid, fn, "bar", "foo")
924918
925919
926920 def test_schema_decorator_partial_match_called_with_kwargs():
936930 def fn(arg1, arg2):
937931 return arg1
938932
939 assert_raises(Invalid, fn, arg1=1, arg2="foo")
933 pytest.raises(Invalid, fn, arg1=1, arg2="foo")
940934
941935
942936 def test_unicode_as_key():
943937 if sys.version_info >= (3,):
944938 text_type = str
945939 else:
946 text_type = unicode
940 text_type = unicode # noqa: F821
947941 schema = Schema({text_type: int})
948942 schema({u("foobar"): 1})
949943
954948 try:
955949 schema({"number": 'teststr'})
956950 except MultipleInvalid as e:
957 assert_equal(str(e),
958 "Value must be a number enclosed with string for dictionary value @ data['number']")
951 assert str(e) == "Value must be a number enclosed with string for dictionary value @ data['number']"
959952 else:
960953 assert False, "Did not raise Invalid for String"
961954
966959 try:
967960 schema({"number": '123456.712'})
968961 except MultipleInvalid as e:
969 assert_equal(str(e),
970 "Precision must be equal to 6, and Scale must be equal to 2 for dictionary value @ data['number']")
962 assert str(e) == "Precision must be equal to 6, and Scale must be equal to 2 for dictionary value @ data['number']"
971963 else:
972964 assert False, "Did not raise Invalid for String"
973965
976968 """ Test with Number with valid precision and scale"""
977969 schema = Schema({"number": Number(precision=6, scale=2, yield_decimal=True)})
978970 out_ = schema({"number": '1234.00'})
979 assert_equal(float(out_.get("number")), 1234.00)
971 assert float(out_.get("number")) == 1234.00
980972
981973
982974 def test_number_when_precision_scale_none_yield_decimal_true():
983975 """ Test with Number with no precision and scale"""
984976 schema = Schema({"number": Number(yield_decimal=True)})
985977 out_ = schema({"number": '12345678901234'})
986 assert_equal(out_.get("number"), 12345678901234)
978 assert out_.get("number") == 12345678901234
987979
988980
989981 def test_number_when_precision_none_n_valid_scale_case1_yield_decimal_true():
990982 """ Test with Number with no precision and valid scale case 1"""
991983 schema = Schema({"number": Number(scale=2, yield_decimal=True)})
992984 out_ = schema({"number": '123456789.34'})
993 assert_equal(float(out_.get("number")), 123456789.34)
985 assert float(out_.get("number")) == 123456789.34
994986
995987
996988 def test_number_when_precision_none_n_valid_scale_case2_yield_decimal_true():
997989 """ Test with Number with no precision and valid scale case 2 with zero in decimal part"""
998990 schema = Schema({"number": Number(scale=2, yield_decimal=True)})
999991 out_ = schema({"number": '123456789012.00'})
1000 assert_equal(float(out_.get("number")), 123456789012.00)
992 assert float(out_.get("number")) == 123456789012.00
1001993
1002994
1003995 def test_number_when_precision_none_n_invalid_scale_yield_decimal_true():
1006998 try:
1007999 schema({"number": '12345678901.234'})
10081000 except MultipleInvalid as e:
1009 assert_equal(str(e),
1010 "Scale must be equal to 2 for dictionary value @ data['number']")
1001 assert str(e) == "Scale must be equal to 2 for dictionary value @ data['number']"
10111002 else:
10121003 assert False, "Did not raise Invalid for String"
10131004
10161007 """ Test with Number with no precision and valid scale"""
10171008 schema = Schema({"number": Number(precision=14, yield_decimal=True)})
10181009 out_ = schema({"number": '1234567.8901234'})
1019 assert_equal(float(out_.get("number")), 1234567.8901234)
1010 assert float(out_.get("number")) == 1234567.8901234
10201011
10211012
10221013 def test_number_when_invalid_precision_n_scale_none_yield_decimal_true():
10251016 try:
10261017 schema({"number": '12345674.8901234'})
10271018 except MultipleInvalid as e:
1028 assert_equal(str(e),
1029 "Precision must be equal to 14 for dictionary value @ data['number']")
1019 assert str(e) == "Precision must be equal to 14 for dictionary value @ data['number']"
10301020 else:
10311021 assert False, "Did not raise Invalid for String"
10321022
10351025 """ Test with Number with valid precision, scale and no yield_decimal"""
10361026 schema = Schema({"number": Number(precision=6, scale=2, yield_decimal=False)})
10371027 out_ = schema({"number": '1234.00'})
1038 assert_equal(out_.get("number"), '1234.00')
1028 assert out_.get("number") == '1234.00'
10391029
10401030
10411031 def test_named_tuples_validate_as_tuples():
10521042 def test_datetime():
10531043 schema = Schema({"datetime": Datetime()})
10541044 schema({"datetime": "2016-10-24T14:01:57.102152Z"})
1055 assert_raises(MultipleInvalid, schema, {"datetime": "2016-10-24T14:01:57"})
1045 pytest.raises(MultipleInvalid, schema, {"datetime": "2016-10-24T14:01:57"})
10561046
10571047
10581048 def test_date():
10591049 schema = Schema({"date": Date()})
10601050 schema({"date": "2016-10-24"})
1061 assert_raises(MultipleInvalid, schema, {"date": "2016-10-24Z"})
1051 pytest.raises(MultipleInvalid, schema, {"date": "2016-10-24Z"})
10621052
10631053
10641054 def test_date_custom_format():
10651055 schema = Schema({"date": Date("%Y%m%d")})
10661056 schema({"date": "20161024"})
1067 assert_raises(MultipleInvalid, schema, {"date": "2016-10-24"})
1057 pytest.raises(MultipleInvalid, schema, {"date": "2016-10-24"})
10681058
10691059
10701060 def test_ordered_dict():
10861076 Required('x'): int, Optional('y'): float,
10871077 Remove('j'): int, Remove(int): str, int: int
10881078 }
1089 assert_equal(definition.get('x'), int)
1090 assert_equal(definition.get('y'), float)
1091 assert_true(Required('x') == Required('x'))
1092 assert_true(Required('x') != Required('y'))
1079 assert definition.get('x') == int
1080 assert definition.get('y') == float
1081 assert Required('x') == Required('x')
1082 assert Required('x') != Required('y')
10931083 # Remove markers are not hashable
1094 assert_equal(definition.get('j'), None)
1084 assert definition.get('j') is None
10951085
10961086
10971087 def test_schema_infer():
11011091 'int': 42,
11021092 'float': 3.14
11031093 })
1104 assert_equal(schema, Schema({
1094 assert schema == Schema({
11051095 Required('str'): str,
11061096 Required('bool'): bool,
11071097 Required('int'): int,
11081098 Required('float'): float
1109 }))
1099 })
11101100
11111101
11121102 def test_schema_infer_dict():
11181108 }
11191109 })
11201110
1121 assert_equal(schema, Schema({
1111 assert schema == Schema({
11221112 Required('a'): {
11231113 Required('b'): {
11241114 Required('c'): str
11251115 }
11261116 }
1127 }))
1117 })
11281118
11291119
11301120 def test_schema_infer_list():
11321122 'list': ['foo', True, 42, 3.14]
11331123 })
11341124
1135 assert_equal(schema, Schema({
1125 assert schema == Schema({
11361126 Required('list'): [str, bool, int, float]
1137 }))
1127 })
11381128
11391129
11401130 def test_schema_infer_scalar():
1141 assert_equal(Schema.infer('foo'), Schema(str))
1142 assert_equal(Schema.infer(True), Schema(bool))
1143 assert_equal(Schema.infer(42), Schema(int))
1144 assert_equal(Schema.infer(3.14), Schema(float))
1145 assert_equal(Schema.infer({}), Schema(dict))
1146 assert_equal(Schema.infer([]), Schema(list))
1131 assert Schema.infer('foo') == Schema(str)
1132 assert Schema.infer(True) == Schema(bool)
1133 assert Schema.infer(42) == Schema(int)
1134 assert Schema.infer(3.14) == Schema(float)
1135 assert Schema.infer({}) == Schema(dict)
1136 assert Schema.infer([]) == Schema(list)
11471137
11481138
11491139 def test_schema_infer_accepts_kwargs():
12061196
12071197 def test_IsDir():
12081198 schema = Schema(IsDir())
1209 assert_raises(MultipleInvalid, schema, 3)
1199 pytest.raises(MultipleInvalid, schema, 3)
12101200 schema(os.path.dirname(os.path.abspath(__file__)))
12111201
12121202
12131203 def test_IsFile():
12141204 schema = Schema(IsFile())
1215 assert_raises(MultipleInvalid, schema, 3)
1205 pytest.raises(MultipleInvalid, schema, 3)
12161206 schema(os.path.abspath(__file__))
12171207
12181208
12191209 def test_PathExists():
12201210 schema = Schema(PathExists())
1221 assert_raises(MultipleInvalid, schema, 3)
1211 pytest.raises(MultipleInvalid, schema, 3)
12221212 schema(os.path.abspath(__file__))
12231213
12241214
13001290 s({'q': 'str', 'q2': 'tata'})
13011291 except MultipleInvalid as exc:
13021292 assert (
1303 (exc.errors[0].path == ['q'] and exc.errors[1].path == ['q2']) or
1304 (exc.errors[1].path == ['q'] and exc.errors[0].path == ['q2'])
1293 (exc.errors[0].path == ['q'] and exc.errors[1].path == ['q2'])
1294 or (exc.errors[1].path == ['q'] and exc.errors[0].path == ['q2'])
13051295 )
13061296 else:
13071297 assert False, "Did not raise AnyInvalid"
13171307 s({'q': 'str', 'q2': 12})
13181308 except MultipleInvalid as exc:
13191309 assert (
1320 (exc.errors[0].path == ['q'] and exc.errors[1].path == ['q2']) or
1321 (exc.errors[1].path == ['q'] and exc.errors[0].path == ['q2'])
1310 (exc.errors[0].path == ['q'] and exc.errors[1].path == ['q2'])
1311 or (exc.errors[1].path == ['q'] and exc.errors[0].path == ['q2'])
13221312 )
13231313 else:
13241314 assert False, "Did not raise AllInvalid"
13921382
13931383
13941384 def test_comparing_voluptuous_object_to_str():
1395 assert_true(Optional('Classification') < 'Name')
1385 assert Optional('Classification') < 'Name'
13961386
13971387
13981388 def test_set_of_integers():
14081398 try:
14091399 schema(set(['abc']))
14101400 except MultipleInvalid as e:
1411 assert_equal(str(e), "invalid value in set")
1401 assert str(e) == "invalid value in set"
14121402 else:
14131403 assert False, "Did not raise Invalid"
14141404
14261416 try:
14271417 schema(frozenset(['abc']))
14281418 except MultipleInvalid as e:
1429 assert_equal(str(e), "invalid value in frozenset")
1419 assert str(e) == "invalid value in frozenset"
14301420 else:
14311421 assert False, "Did not raise Invalid"
14321422
14431433 try:
14441434 schema(set([None]))
14451435 except MultipleInvalid as e:
1446 assert_equal(str(e), "invalid value in set")
1436 assert str(e) == "invalid value in set"
14471437 else:
14481438 assert False, "Did not raise Invalid"
14491439
14601450 try:
14611451 schema(frozenset([None]))
14621452 except MultipleInvalid as e:
1463 assert_equal(str(e), "invalid value in frozenset")
1453 assert str(e) == "invalid value in frozenset"
14641454 else:
14651455 assert False, "Did not raise Invalid"
14661456
15061496 try:
15071497 schema({})
15081498 except MultipleInvalid as e:
1509 assert_equal(str(e),
1510 "required key not provided @ data['a']")
1499 assert str(e) == "required key not provided @ data['a']"
15111500 else:
15121501 assert False, "Did not raise Invalid for MultipleInvalid"
15131502
15211510 try:
15221511 schema({})
15231512 except MultipleInvalid as e:
1524 assert_equal(str(e),
1525 "required key not provided @ data['a']")
1513 assert str(e) == "required key not provided @ data['a']"
15261514 else:
15271515 assert False, "Did not raise Invalid for MultipleInvalid"
15281516
15291517
15301518 def test_inclusive():
15311519 schema = Schema({
1532 Inclusive('x', 'stuff'): int,
1533 Inclusive('y', 'stuff'): int,
1534 })
1520 Inclusive('x', 'stuff'): int,
1521 Inclusive('y', 'stuff'): int,
1522 })
15351523
15361524 r = schema({})
1537 assert_equal(r, {})
1525 assert r == {}
15381526
15391527 r = schema({'x': 1, 'y': 2})
1540 assert_equal(r, {'x': 1, 'y': 2})
1528 assert r == {'x': 1, 'y': 2}
15411529
15421530 try:
15431531 r = schema({'x': 1})
15441532 except MultipleInvalid as e:
1545 assert_equal(str(e),
1546 "some but not all values in the same group of inclusion 'stuff' @ data[<stuff>]")
1533 assert str(e) == "some but not all values in the same group of inclusion 'stuff' @ data[<stuff>]"
15471534 else:
15481535 assert False, "Did not raise Invalid for incomplete Inclusive group"
15491536
15501537
15511538 def test_inclusive_defaults():
15521539 schema = Schema({
1553 Inclusive('x', 'stuff', default=3): int,
1554 Inclusive('y', 'stuff', default=4): int,
1555 })
1540 Inclusive('x', 'stuff', default=3): int,
1541 Inclusive('y', 'stuff', default=4): int,
1542 })
15561543
15571544 r = schema({})
1558 assert_equal(r, {'x': 3, 'y': 4})
1545 assert r == {'x': 3, 'y': 4}
15591546
15601547 try:
15611548 r = schema({'x': 1})
15621549 except MultipleInvalid as e:
1563 assert_equal(str(e),
1564 "some but not all values in the same group of inclusion 'stuff' @ data[<stuff>]")
1550 assert str(e) == "some but not all values in the same group of inclusion 'stuff' @ data[<stuff>]"
15651551 else:
15661552 assert False, "Did not raise Invalid for incomplete Inclusive group with defaults"
15671553
15681554
15691555 def test_exclusive():
15701556 schema = Schema({
1571 Exclusive('x', 'stuff'): int,
1572 Exclusive('y', 'stuff'): int,
1573 })
1557 Exclusive('x', 'stuff'): int,
1558 Exclusive('y', 'stuff'): int,
1559 })
15741560
15751561 r = schema({})
1576 assert_equal(r, {})
1562 assert r == {}
15771563
15781564 r = schema({'x': 1})
1579 assert_equal(r, {'x': 1})
1565 assert r == {'x': 1}
15801566
15811567 try:
15821568 r = schema({'x': 1, 'y': 2})
15831569 except MultipleInvalid as e:
1584 assert_equal(str(e),
1585 "two or more values in the same group of exclusion 'stuff' @ data[<stuff>]")
1570 assert str(e) == "two or more values in the same group of exclusion 'stuff' @ data[<stuff>]"
15861571 else:
15871572 assert False, "Did not raise Invalid for multiple values in Exclusive group"
15881573
16081593 }
16091594 })
16101595 except MultipleInvalid as e:
1611 assert_equal(str(e), 'expected bool for dictionary value @ data[\'implementation\'][\'c-value\']')
1596 assert str(e) == 'expected bool for dictionary value @ data[\'implementation\'][\'c-value\']'
16121597 else:
16131598 assert False, "Did not raise correct Invalid"
16141599
1615 def test_coerce_enum():
1616 """Test Coerce Enum"""
1617 class Choice(Enum):
1618 Easy = 1
1619 Medium = 2
1620 Hard = 3
1621
1622 class StringChoice(str, Enum):
1623 Easy = "easy"
1624 Medium = "medium"
1625 Hard = "hard"
1626
1627 schema = Schema(Coerce(Choice))
1628 string_schema = Schema(Coerce(StringChoice))
1629
1630 # Valid value
1631 assert schema(1) == Choice.Easy
1632 assert string_schema("easy") == StringChoice.Easy
1633
1634 # Invalid value
1635 try:
1636 schema(4)
1637 except Invalid as e:
1638 assert_equal(str(e),
1639 "expected Choice or one of 1, 2, 3")
1640 else:
1641 assert False, "Did not raise Invalid for String"
1642
1643 try:
1644 string_schema("hello")
1645 except Invalid as e:
1646 assert_equal(str(e),
1647 "expected StringChoice or one of 'easy', 'medium', 'hard'")
1648 else:
1649 assert False, "Did not raise Invalid for String"
1600
1601 if Enum:
1602 def test_coerce_enum():
1603 """Test Coerce Enum"""
1604 class Choice(Enum):
1605 Easy = 1
1606 Medium = 2
1607 Hard = 3
1608
1609 class StringChoice(str, Enum):
1610 Easy = "easy"
1611 Medium = "medium"
1612 Hard = "hard"
1613
1614 schema = Schema(Coerce(Choice))
1615 string_schema = Schema(Coerce(StringChoice))
1616
1617 # Valid value
1618 assert schema(1) == Choice.Easy
1619 assert string_schema("easy") == StringChoice.Easy
1620
1621 # Invalid value
1622 try:
1623 schema(4)
1624 except Invalid as e:
1625 assert str(e) == "expected Choice or one of 1, 2, 3"
1626 else:
1627 assert False, "Did not raise Invalid for String"
1628
1629 try:
1630 string_schema("hello")
1631 except Invalid as e:
1632 assert str(e) == "expected StringChoice or one of 'easy', 'medium', 'hard'"
1633 else:
1634 assert False, "Did not raise Invalid for String"
1635
1636
1637 class MyValueClass(object):
1638 def __init__(self, value=None):
1639 self.value = value
1640
1641
1642 def test_object():
1643 s = Schema(Object({'value': 1}), required=True)
1644 s(MyValueClass(value=1))
1645 pytest.raises(MultipleInvalid, s, MyValueClass(value=2))
1646 pytest.raises(MultipleInvalid, s, 345)
1647
1648
1649 # Python 3.7 removed the trailing comma in repr() of BaseException
1650 # https://bugs.python.org/issue30399
1651 if sys.version_info >= (3, 7):
1652 invalid_scalar_excp_repr = "ScalarInvalid('not a valid value')"
1653 else:
1654 invalid_scalar_excp_repr = "ScalarInvalid('not a valid value',)"
1655
1656
1657 def test_exception():
1658 s = Schema(None)
1659 try:
1660 s(123)
1661 except MultipleInvalid as e:
1662 assert repr(e) == "MultipleInvalid([{}])".format(invalid_scalar_excp_repr)
1663 assert str(e.msg) == "not a valid value"
1664 assert str(e.error_message) == "not a valid value"
1665 assert str(e.errors) == "[{}]".format(invalid_scalar_excp_repr)
1666 e.add("Test Error")
1667 assert str(e.errors) == "[{}, 'Test Error']".format(invalid_scalar_excp_repr)
77
88
99 def _fix_str(v):
10 if sys.version_info[0] == 2 and isinstance(v, unicode):
10 if sys.version_info[0] == 2 and isinstance(v, unicode): # noqa: F821
1111 s = v
1212 else:
1313 s = str(v)
156156
157157 def u(x):
158158 if sys.version_info < (3,):
159 return unicode(x)
159 return unicode(x) # noqa: F821
160160 else:
161161 return x
33 import sys
44 from functools import wraps
55 from decimal import Decimal, InvalidOperation
6 from enum import Enum
6 try:
7 from enum import Enum
8 except ImportError:
9 Enum = None
710
811 from voluptuous.schema_builder import Schema, raises, message
912 from voluptuous.error import (MultipleInvalid, CoerceInvalid, TrueInvalid, FalseInvalid, BooleanInvalid, Invalid,
103106 return self.type(v)
104107 except (ValueError, TypeError, InvalidOperation):
105108 msg = self.msg or ('expected %s' % self.type_name)
106 if not self.msg and issubclass(self.type, Enum):
109 if not self.msg and Enum and issubclass(self.type, Enum):
107110 msg += " or one of %s" % str([e.value for e in self.type])[1:-1]
108111 raise CoerceInvalid(msg)
109112
448451 if not (USER_REGEX.match(user_part) and DOMAIN_REGEX.match(domain_part)):
449452 raise EmailInvalid("Invalid email address")
450453 return v
451 except:
454 except: # noqa: E722
452455 raise ValueError
453456
454457
467470 if "." not in parsed_url.netloc:
468471 raise UrlInvalid("must have a domain name in URL")
469472 return v
470 except:
473 except: # noqa: E722
471474 raise ValueError
472475
473476
484487 try:
485488 _url_validation(v)
486489 return v
487 except:
490 except: # noqa: E722
488491 raise ValueError
489492
490493
685688 self.msg or 'length of value must be at most %s' % self.max)
686689 return v
687690
688 # Objects that havbe no length e.g. None or strings will raise TypeError
691 # Objects that have no length e.g. None or strings will raise TypeError
689692 except TypeError:
690693 raise RangeInvalid(
691694 self.msg or 'invalid value or type')
747750 except TypeError:
748751 check = True
749752 if check:
750 raise InInvalid(self.msg or
751 'value must be one of {}'.format(sorted(self.container)))
753 raise InInvalid(self.msg
754 or 'value must be one of {}'.format(sorted(self.container)))
752755 return v
753756
754757 def __repr__(self):
768771 except TypeError:
769772 check = True
770773 if check:
771 raise NotInInvalid(self.msg or
772 'value must not be one of {}'.format(sorted(self.container)))
774 raise NotInInvalid(self.msg
775 or 'value must not be one of {}'.format(sorted(self.container)))
773776 return v
774777
775778 def __repr__(self):
841844 class Unique(object):
842845 """Ensure an iterable does not contain duplicate items.
843846
844 Only iterables convertable to a set are supported (native types and
847 Only iterables convertible to a set are supported (native types and
845848 objects with correct __eq__).
846849
847850 JSON does not support set, so they need to be presented as arrays.
00 Metadata-Version: 2.1
11 Name: voluptuous
2 Version: 0.13.0
3 Summary: UNKNOWN
2 Version: 0.13.1
43 Home-page: https://github.com/alecthomas/voluptuous
4 Download-URL: https://pypi.python.org/pypi/voluptuous
55 Author: Alec Thomas
66 Author-email: alec@swapoff.org
7 License: BSD
8 Download-URL: https://pypi.python.org/pypi/voluptuous
7 License: BSD-3-Clause
98 Platform: any
109 Classifier: Development Status :: 5 - Production/Stable
1110 Classifier: Intended Audience :: Developers
1817 Classifier: Programming Language :: Python :: 3.7
1918 Classifier: Programming Language :: Python :: 3.8
2019 Classifier: Programming Language :: Python :: 3.9
20 Classifier: Programming Language :: Python :: 3.10
2121 Description-Content-Type: text/markdown
2222 License-File: COPYING
2323
4444
4545 It has three goals:
4646
47 1. Simplicity.
48 2. Support for complex data structures.
49 3. Provide useful error messages.
47 1. Simplicity.
48 2. Support for complex data structures.
49 3. Provide useful error messages.
5050
5151 ## Contact
5252
9494 Twitter's [user search API](https://dev.twitter.com/rest/reference/get/users/search) accepts
9595 query URLs like:
9696
97 ```
97 ```bash
9898 $ curl 'https://api.twitter.com/1.1/users/search.json?q=python&per_page=20&page=1'
9999 ```
100100
107107 ... 'per_page': int,
108108 ... 'page': int,
109109 ... })
110
111110 ```
112111
113112 This schema very succinctly and roughly describes the data required by
124123 ... Required('per_page', default=5): All(int, Range(min=1, max=20)),
125124 ... 'page': All(int, Range(min=0)),
126125 ... })
127
128126 ```
129127
130128 This schema fully enforces the interface defined in Twitter's
141139 ... exc = e
142140 >>> str(exc) == "required key not provided @ data['q']"
143141 True
144
145142 ```
146143
147144 ...must be a string:
154151 ... exc = e
155152 >>> str(exc) == "expected str for dictionary value @ data['q']"
156153 True
157
158154 ```
159155
160156 ...and must be at least one character in length:
169165 True
170166 >>> schema({'q': '#topic'}) == {'q': '#topic', 'per_page': 5}
171167 True
172
173168 ```
174169
175170 "per\_page" is a positive integer no greater than 20:
189184 ... exc = e
190185 >>> str(exc) == "value must be at least 1 for dictionary value @ data['per_page']"
191186 True
192
193187 ```
194188
195189 "page" is an integer \>= 0:
204198 "expected int for dictionary value @ data['per_page']"
205199 >>> schema({'q': '#topic', 'page': 1}) == {'q': '#topic', 'page': 1, 'per_page': 5}
206200 True
207
208201 ```
209202
210203 ## Defining schemas
224217 >>> schema = Schema('a string')
225218 >>> schema('a string')
226219 'a string'
227
228220 ```
229221
230222 ### Types
243235 ... exc = e
244236 >>> str(exc) == "expected int"
245237 True
246
247238 ```
248239
249240 ### URLs
262253 ... exc = e
263254 >>> str(exc) == "expected a URL"
264255 True
265
266256 ```
267257
268258 ### Lists
278268 [1, 1, 1]
279269 >>> schema(['a', 1, 'string', 1, 'string'])
280270 ['a', 1, 'string', 1, 'string']
281
282271 ```
283272
284273 However, an empty list (`[]`) is treated as is. If you want to specify a list that can
300289 []
301290 >>> schema([1, 2])
302291 [1, 2]
303
304292 ```
305293
306294 ### Sets and frozensets
333321 ... exc = e
334322 >>> str(exc) == 'expected a frozenset'
335323 True
336
337324 ```
338325
339326 However, an empty set (`set()`) is treated as is. If you want to specify a set
353340 >>> schema = Schema(set)
354341 >>> schema({1, 2}) == {1, 2}
355342 True
356
357343 ```
358344
359345 ### Validation functions
373359 >>> from datetime import datetime
374360 >>> def Date(fmt='%Y-%m-%d'):
375361 ... return lambda v: datetime.strptime(v, fmt)
376
377362 ```
378363
379364 ```pycon
387372 ... exc = e
388373 >>> str(exc) == "not a valid value"
389374 True
390
391375 ```
392376
393377 In addition to simply determining if a value is valid, validators may
408392 except ValueError:
409393 raise Invalid(msg or ('expected %s' % type.__name__))
410394 return f
411
412395 ```
413396
414397 This example also shows a common idiom where an optional human-readable
424407 >>> schema = Schema({1: 'one', 2: 'two'})
425408 >>> schema({1: 'one'})
426409 {1: 'one'}
427
428410 ```
429411
430412 #### Extra dictionary keys
441423 ... exc = e
442424 >>> str(exc) == "extra keys not allowed @ data[1]"
443425 True
444
445426 ```
446427
447428 This behaviour can be altered on a per-schema basis. To allow
453434 >>> schema = Schema({2: 3}, extra=ALLOW_EXTRA)
454435 >>> schema({1: 2, 2: 3})
455436 {1: 2, 2: 3}
456
457437 ```
458438
459439 To remove additional keys use
464444 >>> schema = Schema({2: 3}, extra=REMOVE_EXTRA)
465445 >>> schema({1: 2, 2: 3})
466446 {2: 3}
467
468447 ```
469448
470449 It can also be overridden per-dictionary by using the catch-all marker
475454 >>> schema = Schema({1: {Extra: object}})
476455 >>> schema({1: {'foo': 'bar'}})
477456 {1: {'foo': 'bar'}}
478
479457 ```
480458
481459 #### Required dictionary keys
486464 >>> schema = Schema({1: 2, 3: 4})
487465 >>> schema({3: 4})
488466 {3: 4}
489
490467 ```
491468
492469 Similarly to how extra\_ keys work, this behaviour can be overridden
501478 ... exc = e
502479 >>> str(exc) == "required key not provided @ data[1]"
503480 True
504
505481 ```
506482
507483 And per-key, with the marker token `Required(key)`:
517493 True
518494 >>> schema({1: 2})
519495 {1: 2}
520
521496 ```
522497
523498 #### Optional dictionary keys
544519 ... exc = e
545520 >>> str(exc) == "extra keys not allowed @ data[4]"
546521 True
547
548522 ```
549523
550524 ```pycon
551525 >>> schema({1: 2, 3: 4})
552526 {1: 2, 3: 4}
553
554527 ```
555528
556529 ### Recursive / nested schema
562535 >>> recursive = Schema({"more": Self, "value": int})
563536 >>> recursive({"more": {"value": 42}, "value": 41}) == {'more': {'value': 42}, 'value': 41}
564537 True
565
566538 ```
567539
568540 ### Extending an existing Schema
577549 >>> person_with_age = person.extend({'age': int})
578550 >>> sorted(list(person_with_age.schema.keys()))
579551 ['age', 'name']
580
581552 ```
582553
583554 The original `Schema` remains unchanged.
598569 >>> schema = Schema(Object({'q': 'one'}, cls=Structure))
599570 >>> schema(Structure(q='one'))
600571 <Structure(q='one')>
601
602572 ```
603573
604574 ### Allow None values
612582 >>> schema(None)
613583 >>> schema(5)
614584 5
615
616585 ```
617586
618587 ## Error reporting
627596 exception. This is especially useful when you want to catch `Invalid`
628597 exceptions and give some feedback to the user, for instance in the context of
629598 an HTTP API.
630
631599
632600 ```pycon
633601 >>> def validate_email(email):
649617 'This email is invalid.'
650618 >>> exc.error_message
651619 'This email is invalid.'
652
653620 ```
654621
655622 The `path` attribute is used during error reporting, but also during matching
664631
665632 ```pycon
666633 >>> schema = Schema([[2, 3], 6])
667
668634 ```
669635
670636 Each value in the top-level list is matched depth-first in-order. Given
681647 ... exc = e
682648 >>> str(exc) == "not a valid value @ data[0][0]"
683649 True
684
685650 ```
686651
687652 If we pass the data `[6]`, the `6` is not a list type and so will not
691656 ```pycon
692657 >>> schema([6])
693658 [6]
694
695659 ```
696660
697661 ## Multi-field validation
708672 raise Invalid('passwords must match')
709673 return passwords
710674
711 s=Schema(All(
675 schema = Schema(All(
712676 # First "pass" for field types
713 {'password':str, 'password_again':str},
677 {'password': str, 'password_again': str},
714678 # Follow up the first "pass" with your multi-field rules
715679 passwords_must_match
716680 ))
717681
718682 # valid
719 s({'password':'123', 'password_again':'123'})
683 schema({'password': '123', 'password_again': '123'})
720684
721685 # raises MultipleInvalid: passwords must match
722 s({'password':'123', 'password_again':'and now for something completely different'})
686 schema({'password': '123', 'password_again': 'and now for something completely different'})
723687
724688 ```
725689
730694 The flipside is that if the first "pass" of validation fails, your
731695 cross-field validator will not run:
732696
733 ```
697 ```python
734698 # raises Invalid because password_again is not a string
735699 # passwords_must_match() will not run because first-pass validation already failed
736 s({'password':'123', 'password_again': 1337})
700 schema({'password': '123', 'password_again': 1337})
737701 ```
738702
739703 ## Running tests
740704
741 Voluptuous is using nosetests:
742
743 $ nosetests
744
705 Voluptuous is using `pytest`:
706
707 ```bash
708 $ pip install pytest
709 $ pytest
710 ```
711
712 To also include a coverage report:
713
714 ```bash
715 $ pip install pytest pytest-cov coverage>=3.0
716 $ pytest --cov=voluptuous voluptuous/tests/
717 ```
745718
746719 ## Other libraries and inspirations
747720
756729
757730 I greatly prefer the light-weight style promoted by these libraries to
758731 the complexity of libraries like FormEncode.
759
760
11 COPYING
22 MANIFEST.in
33 README.md
4 setup.cfg
54 setup.py
65 voluptuous/__init__.py
76 voluptuous/error.py