nodes: 0, module; 1, function_definition; 2, function_name:_compile_dict; 3, parameters; 4, block; 5, identifier:self; 6, identifier:schema; 7, expression_statement; 8, expression_statement; 9, expression_statement; 10, expression_statement; 11, for_statement; 12, function_definition; 13, return_statement; 14, comment:"""Validate a dictionary.
  
          A dictionary schema can contain a set of values, or at most one
          validator function/type.
  
          A dictionary schema will only validate a dictionary:
  
              >>> validate = Schema({})
              >>> with raises(er.MultipleInvalid, 'expected a dictionary'):
              ...   validate([])
  
          An invalid dictionary value:
  
              >>> validate = Schema({'one': 'two', 'three': 'four'})
              >>> with raises(er.MultipleInvalid, "not a valid value for dictionary value @ data['one']"):
              ...   validate({'one': 'three'})
  
          An invalid key:
  
              >>> with raises(er.MultipleInvalid, "extra keys not allowed @ data['two']"):
              ...   validate({'two': 'three'})
  
  
          Validation function, in this case the "int" type:
  
              >>> validate = Schema({'one': 'two', 'three': 'four', int: str})
  
          Valid integer input:
  
              >>> validate({10: 'twenty'})
              {10: 'twenty'}
  
          By default, a "type" in the schema (in this case "int") will be used
          purely to validate that the corresponding value is of that type. It
          will not Coerce the value:
  
              >>> with raises(er.MultipleInvalid, "extra keys not allowed @ data['10']"):
              ...   validate({'10': 'twenty'})
  
          Wrap them in the Coerce() function to achieve this:
              >>> from voluptuous import Coerce
              >>> validate = Schema({'one': 'two', 'three': 'four',
              ...                    Coerce(int): str})
              >>> validate({'10': 'twenty'})
              {10: 'twenty'}
  
          Custom message for required key
  
              >>> validate = Schema({Required('one', 'required'): 'two'})
              >>> with raises(er.MultipleInvalid, "required @ data['one']"):
              ...   validate({})
  
          (This is to avoid unexpected surprises.)
  
          Multiple errors for nested field in a dict:
  
          >>> validate = Schema({
          ...     'adict': {
          ...         'strfield': str,
          ...         'intfield': int
          ...     }
          ... })
          >>> try:
          ...     validate({
          ...         'adict': {
          ...             'strfield': 123,
          ...             'intfield': 'one'
          ...         }
          ...     })
          ... except er.MultipleInvalid as e:
          ...     print(sorted(str(i) for i in e.errors)) # doctest: +NORMALIZE_WHITESPACE
          ["expected int for dictionary value @ data['adict']['intfield']",
           "expected str for dictionary value @ data['adict']['strfield']"]
  
          """; 15, assignment; 16, assignment; 17, assignment; 18, identifier:node; 19, identifier:schema; 20, block; 21, function_name:validate_dict; 22, parameters; 23, block; 24, identifier:validate_dict; 25, identifier:base_validate; 26, call; 27, identifier:groups_of_exclusion; 28, dictionary; 29, identifier:groups_of_inclusion; 30, dictionary; 31, if_statement; 32, identifier:path; 33, identifier:data; 34, if_statement; 35, expression_statement; 36, for_statement; 37, if_statement; 38, for_statement; 39, if_statement; 40, expression_statement; 41, return_statement; 42, attribute; 43, argument_list; 44, call; 45, block; 46, elif_clause; 47, not_operator; 48, block; 49, assignment; 50, pattern_list; 51, call; 52, block; 53, identifier:errors; 54, block; 55, pattern_list; 56, call; 57, block; 58, identifier:errors; 59, block; 60, assignment; 61, call; 62, identifier:self; 63, identifier:_compile_mapping; 64, identifier:schema; 65, keyword_argument; 66, identifier:isinstance; 67, argument_list; 68, expression_statement; 69, expression_statement; 70, call; 71, block; 72, call; 73, raise_statement; 74, identifier:errors; 75, list; 76, identifier:label; 77, identifier:group; 78, attribute; 79, argument_list; 80, expression_statement; 81, for_statement; 82, raise_statement; 83, identifier:label; 84, identifier:group; 85, attribute; 86, argument_list; 87, expression_statement; 88, if_statement; 89, raise_statement; 90, identifier:out; 91, call; 92, identifier:base_validate; 93, argument_list; 94, identifier:invalid_msg; 95, string; 96, identifier:node; 97, identifier:Exclusive; 98, assignment; 99, call; 100, identifier:isinstance; 101, argument_list; 102, expression_statement; 103, expression_statement; 104, identifier:isinstance; 105, argument_list; 106, call; 107, identifier:groups_of_exclusion; 108, identifier:items; 109, assignment; 110, identifier:exclusive; 111, identifier:group; 112, block; 113, call; 114, identifier:groups_of_inclusion; 115, identifier:items; 116, assignment; 117, boolean_operator; 118, block; 119, call; 120, attribute; 121, argument_list; 122, identifier:path; 123, call; 124, identifier:out; 125, string_content:dictionary value; 126, identifier:g; 127, call; 128, attribute; 129, argument_list; 130, identifier:node; 131, identifier:Inclusive; 132, assignment; 133, call; 134, identifier:data; 135, identifier:dict; 136, attribute; 137, argument_list; 138, identifier:exists; 139, False; 140, if_statement; 141, attribute; 142, argument_list; 143, identifier:included; 144, list_comprehension; 145, call; 146, not_operator; 147, expression_statement; 148, for_statement; 149, expression_statement; 150, expression_statement; 151, break_statement; 152, attribute; 153, argument_list; 154, identifier:data; 155, identifier:__class__; 156, identifier:iteritems; 157, argument_list; 158, attribute; 159, argument_list; 160, identifier:g; 161, identifier:append; 162, identifier:node; 163, identifier:g; 164, call; 165, attribute; 166, argument_list; 167, identifier:er; 168, identifier:DictInvalid; 169, string; 170, identifier:path; 171, comparison_operator:exclusive.schema in data; 172, block; 173, identifier:er; 174, identifier:MultipleInvalid; 175, identifier:errors; 176, comparison_operator:node.schema in data; 177, for_in_clause; 178, identifier:any; 179, argument_list; 180, call; 181, assignment; 182, identifier:g; 183, identifier:group; 184, block; 185, assignment; 186, call; 187, identifier:er; 188, identifier:MultipleInvalid; 189, identifier:errors; 190, identifier:data; 191, identifier:groups_of_exclusion; 192, identifier:setdefault; 193, attribute; 194, list; 195, attribute; 196, argument_list; 197, identifier:g; 198, identifier:append; 199, identifier:node; 200, string_content:expected a dictionary; 201, attribute; 202, identifier:data; 203, if_statement; 204, expression_statement; 205, attribute; 206, identifier:data; 207, identifier:node; 208, identifier:group; 209, identifier:included; 210, identifier:all; 211, argument_list; 212, identifier:msg; 213, binary_operator:"some but not all values in the same group of inclusion '%s'" % label; 214, if_statement; 215, identifier:next_path; 216, binary_operator:path + [VirtualPathComponent(label)]; 217, attribute; 218, argument_list; 219, identifier:node; 220, identifier:group_of_exclusion; 221, identifier:groups_of_inclusion; 222, identifier:setdefault; 223, attribute; 224, list; 225, identifier:exclusive; 226, identifier:schema; 227, identifier:exists; 228, block; 229, assignment; 230, identifier:node; 231, identifier:schema; 232, identifier:included; 233, string:"some but not all values in the same group of inclusion '%s'"; 234, identifier:label; 235, boolean_operator; 236, block; 237, identifier:path; 238, list; 239, identifier:errors; 240, identifier:append; 241, call; 242, identifier:node; 243, identifier:group_of_inclusion; 244, expression_statement; 245, expression_statement; 246, expression_statement; 247, break_statement; 248, identifier:exists; 249, True; 250, call; 251, attribute; 252, expression_statement; 253, break_statement; 254, call; 255, attribute; 256, argument_list; 257, assignment; 258, assignment; 259, call; 260, identifier:hasattr; 261, argument_list; 262, identifier:g; 263, identifier:msg; 264, assignment; 265, identifier:VirtualPathComponent; 266, argument_list; 267, identifier:er; 268, identifier:InclusiveInvalid; 269, identifier:msg; 270, identifier:next_path; 271, identifier:msg; 272, conditional_expression:exclusive.msg if hasattr(exclusive, 'msg') and exclusive.msg else \
                                  "two or more values in the same group of exclusion '%s'" % label; 273, identifier:next_path; 274, binary_operator:path + [VirtualPathComponent(label)]; 275, attribute; 276, argument_list; 277, identifier:g; 278, string; 279, identifier:msg; 280, attribute; 281, identifier:label; 282, attribute; 283, boolean_operator; 284, binary_operator:"two or more values in the same group of exclusion '%s'" % label; 285, identifier:path; 286, list; 287, identifier:errors; 288, identifier:append; 289, call; 290, string_content:msg; 291, identifier:g; 292, identifier:msg; 293, identifier:exclusive; 294, identifier:msg; 295, call; 296, attribute; 297, string:"two or more values in the same group of exclusion '%s'"; 298, identifier:label; 299, call; 300, attribute; 301, argument_list; 302, identifier:hasattr; 303, argument_list; 304, identifier:exclusive; 305, identifier:msg; 306, identifier:VirtualPathComponent; 307, argument_list; 308, identifier:er; 309, identifier:ExclusiveInvalid; 310, identifier:msg; 311, identifier:next_path; 312, identifier:exclusive; 313, string; 314, identifier:label; 315, string_content:msg
code: def _compile_dict(self, schema):
          """Validate a dictionary.
  
          A dictionary schema can contain a set of values, or at most one
          validator function/type.
  
          A dictionary schema will only validate a dictionary:
  
              >>> validate = Schema({})
              >>> with raises(er.MultipleInvalid, 'expected a dictionary'):
              ...   validate([])
  
          An invalid dictionary value:
  
              >>> validate = Schema({'one': 'two', 'three': 'four'})
              >>> with raises(er.MultipleInvalid, "not a valid value for dictionary value @ data['one']"):
              ...   validate({'one': 'three'})
  
          An invalid key:
  
              >>> with raises(er.MultipleInvalid, "extra keys not allowed @ data['two']"):
              ...   validate({'two': 'three'})
  
  
          Validation function, in this case the "int" type:
  
              >>> validate = Schema({'one': 'two', 'three': 'four', int: str})
  
          Valid integer input:
  
              >>> validate({10: 'twenty'})
              {10: 'twenty'}
  
          By default, a "type" in the schema (in this case "int") will be used
          purely to validate that the corresponding value is of that type. It
          will not Coerce the value:
  
              >>> with raises(er.MultipleInvalid, "extra keys not allowed @ data['10']"):
              ...   validate({'10': 'twenty'})
  
          Wrap them in the Coerce() function to achieve this:
              >>> from voluptuous import Coerce
              >>> validate = Schema({'one': 'two', 'three': 'four',
              ...                    Coerce(int): str})
              >>> validate({'10': 'twenty'})
              {10: 'twenty'}
  
          Custom message for required key
  
              >>> validate = Schema({Required('one', 'required'): 'two'})
              >>> with raises(er.MultipleInvalid, "required @ data['one']"):
              ...   validate({})
  
          (This is to avoid unexpected surprises.)
  
          Multiple errors for nested field in a dict:
  
          >>> validate = Schema({
          ...     'adict': {
          ...         'strfield': str,
          ...         'intfield': int
          ...     }
          ... })
          >>> try:
          ...     validate({
          ...         'adict': {
          ...             'strfield': 123,
          ...             'intfield': 'one'
          ...         }
          ...     })
          ... except er.MultipleInvalid as e:
          ...     print(sorted(str(i) for i in e.errors)) # doctest: +NORMALIZE_WHITESPACE
          ["expected int for dictionary value @ data['adict']['intfield']",
           "expected str for dictionary value @ data['adict']['strfield']"]
  
          """
          base_validate = self._compile_mapping(
              schema, invalid_msg='dictionary value')
  
          groups_of_exclusion = {}
          groups_of_inclusion = {}
          for node in schema:
              if isinstance(node, Exclusive):
                  g = groups_of_exclusion.setdefault(node.group_of_exclusion, [])
                  g.append(node)
              elif isinstance(node, Inclusive):
                  g = groups_of_inclusion.setdefault(node.group_of_inclusion, [])
                  g.append(node)
  
          def validate_dict(path, data):
              if not isinstance(data, dict):
                  raise er.DictInvalid('expected a dictionary', path)
  
              errors = []
              for label, group in groups_of_exclusion.items():
                  exists = False
                  for exclusive in group:
                      if exclusive.schema in data:
                          if exists:
                              msg = exclusive.msg if hasattr(exclusive, 'msg') and exclusive.msg else \
                                  "two or more values in the same group of exclusion '%s'" % label
                              next_path = path + [VirtualPathComponent(label)]
                              errors.append(er.ExclusiveInvalid(msg, next_path))
                              break
                          exists = True
  
              if errors:
                  raise er.MultipleInvalid(errors)
  
              for label, group in groups_of_inclusion.items():
                  included = [node.schema in data for node in group]
                  if any(included) and not all(included):
                      msg = "some but not all values in the same group of inclusion '%s'" % label
                      for g in group:
                          if hasattr(g, 'msg') and g.msg:
                              msg = g.msg
                              break
                      next_path = path + [VirtualPathComponent(label)]
                      errors.append(er.InclusiveInvalid(msg, next_path))
                      break
  
              if errors:
                  raise er.MultipleInvalid(errors)
  
              out = data.__class__()
              return base_validate(path, iteritems(data), out)
  
          return validate_dict