text: from .datapath import DataPath
  from .validationresults import ValidationResult
  from .. import syntax, util
  from .. import validators as val
  
  
  class Schema(object):
      """
      Makes a Schema object from a schema dict.
      Still acts like a dict.
      """
  
      def __init__(self, schema_dict, name='', validators=None, includes=None):
          self.validators = validators or val.DefaultValidators
          self.dict = schema_dict
          self.name = name
          self._schema = self._process_schema(DataPath(),
                                              schema_dict,
                                              self.validators)
          # if this schema is included it shares the includes with the top level
          # schema
          self.includes = {} if includes is None else includes
  
      def add_include(self, type_dict):
          for include_name, custom_type in type_dict.items():
              t = Schema(custom_type, name=include_name,
                         validators=self.validators, includes=self.includes)
              self.includes[include_name] = t
  
      def _process_schema(self, path, schema_data, validators):
          """
          Go through a schema and construct validators.
          """
          if util.is_map(schema_data) or util.is_list(schema_data):
              for key, data in util.get_iter(schema_data):
                  schema_data[key] = self._process_schema(path + DataPath(key),
                                                          data,
                                                          validators)
          else:
              schema_data = self._parse_schema_item(path,
                                                    schema_data,
                                                    validators)
          return schema_data
  
      def _parse_schema_item(self, path, expression, validators):
          try:
              return syntax.parse(expression, validators)
          except SyntaxError as e:
              # Tack on some more context and rethrow.
              error = str(e) + ' at node \'%s\'' % str(path)
              raise SyntaxError(error)
  
      def validate(self, data, data_name, strict):
          path = DataPath()
          errors = self._validate(self._schema, data, path, strict)
          return ValidationResult(data_name, self.name, errors)
  
      def _validate_item(self, validator, data, path, strict, key):
          """
          Fetch item from data at the postion key and validate with validator.
  
          Returns an array of errors.
          """
          errors = []
          path = path + DataPath(key)
          try:  # Pull value out of data. Data can be a map or a list/sequence
              data_item = data[key]
          except (KeyError, IndexError):  # Oops, that field didn't exist.
              # Optional? Who cares.
              if isinstance(validator, val.Validator) and validator.is_optional:
                  return errors
              # SHUT DOWN EVERTYHING
              errors.append('%s: Required field missing' % path)
              return errors
  
          return self._validate(validator, data_item, path, strict)
  
      def _validate(self, validator, data, path, strict):
          """
          Validate data with validator.
          Special handling of non-primitive validators.
  
          Returns an array of errors.
          """
  
          if util.is_list(validator) or util.is_map(validator):
              return self._validate_static_map_list(validator,
                                                    data,
                                                    path,
                                                    strict)
  
          errors = []
          # Optional field with optional value? Who cares.
          if (data is None and
                  validator.is_optional and
                  validator.can_be_none):
              return errors
  
          errors += self._validate_primitive(validator, data, path)
  
          if errors:
              return errors
  
          if isinstance(validator, val.Include):
              errors += self._validate_include(validator, data, path, strict)
  
          elif isinstance(validator, (val.Map, val.List)):
              errors += self._validate_map_list(validator, data, path, strict)
  
          elif isinstance(validator, val.Any):
              errors += self._validate_any(validator, data, path, strict)
  
          elif isinstance(validator, val.Subset):
              errors += self._validate_subset(validator, data, path, strict)
  
          return errors
  
      def _validate_static_map_list(self, validator, data, path, strict):
          if util.is_map(validator) and not util.is_map(data):
              return ["%s : '%s' is not a map" % (path, data)]
  
          if util.is_list(validator) and not util.is_list(data):
              return ["%s : '%s' is not a list" % (path, data)]
  
          errors = []
  
          if strict:
              data_keys = set(util.get_keys(data))
              validator_keys = set(util.get_keys(validator))
              for key in data_keys - validator_keys:
                  error_path = path + DataPath(key)
                  errors += ['%s: Unexpected element' % error_path]
  
          for key, sub_validator in util.get_iter(validator):
              errors += self._validate_item(sub_validator,
                                            data,
                                            path,
                                            strict,
                                            key)
          return errors
  
      def _validate_map_list(self, validator, data, path, strict):
          errors = []
  
          if not validator.validators:
              return errors  # No validators, user just wanted a map.
  
          for key in util.get_keys(data):
              sub_errors = []
              for v in validator.validators:
                  err = self._validate_item(v, data, path, strict, key)
                  if err:
                      sub_errors.append(err)
  
              if len(sub_errors) == len(validator.validators):
                  # All validators failed, add to errors
                  for err in sub_errors:
                      errors += err
  
          return errors
  
      def _validate_include(self, validator, data, path, strict):
          include_schema = self.includes.get(validator.include_name)
          if not include_schema:
              return [('Include \'%s\' has not been defined.'
                       % validator.include_name)]
          strict = strict if validator.strict is None else validator.strict
          return include_schema._validate(include_schema._schema,
                                          data,
                                          path,
                                          strict)
  
      def _validate_any(self, validator, data, path, strict):
          if not validator.validators:
              return []
  
          errors = []
  
          sub_errors = []
          for v in validator.validators:
              err = self._validate(v, data, path, strict)
              if err:
                  sub_errors.append(err)
  
          if len(sub_errors) == len(validator.validators):
              # All validators failed, add to errors
              for err in sub_errors:
                  errors += err
  
          return errors
  
      def _validate_subset(self, validator, data, path, strict):
          def _internal_validate(internal_data):
              sub_errors = []
              for val in validator.validators:
                  err = self._validate(val, internal_data, path, strict)
                  if not err:
                      break
                  sub_errors += err
              else:
                  return sub_errors
              return []
  
          if not validator.validators:
              return []
  
          errors = []
          if util.is_map(data):
              for k, v in data.items():
                  errors += _internal_validate({k: v})
          elif util.is_list(data):
              for k in data:
                  errors += _internal_validate(k)
          else:
              errors += _internal_validate(data)
          return errors
  
      def _validate_primitive(self, validator, data, path):
          errors = validator.validate(data)
  
          for i, error in enumerate(errors):
              errors[i] = ('%s: ' % path) + error
  
          return errors
  