back to Claude Sonnet 3.5 - Fill-in summary
Claude Sonnet 3.5 - Fill-in: voluptuous
Failed to run pytests for test tests
Pytest collection failure.
Patch diff
diff --git a/voluptuous/error.py b/voluptuous/error.py
index f72fbe7..135f37e 100644
--- a/voluptuous/error.py
+++ b/voluptuous/error.py
@@ -146,7 +146,7 @@ class LiteralInvalid(Invalid):
class LengthInvalid(Invalid):
- pass
+ """The value has an invalid length."""
class DatetimeInvalid(Invalid):
@@ -158,22 +158,40 @@ class DateInvalid(Invalid):
class InInvalid(Invalid):
- pass
+ """The value is not in the required collection."""
class NotInInvalid(Invalid):
- pass
+ """The value is in a collection it should not be in."""
class ExactSequenceInvalid(Invalid):
- pass
+ """The sequence does not match exactly."""
class NotEnoughValid(Invalid):
"""The value did not pass enough validations."""
- pass
+ def __init__(self, msg: str, path: typing.Optional[typing.List[typing.Hashable]] = None,
+ error_message: typing.Optional[str] = None,
+ error_type: typing.Optional[str] = None,
+ expected: int = 0, actual: int = 0) -> None:
+ super().__init__(msg, path, error_message, error_type)
+ self.expected = expected
+ self.actual = actual
+
+ def __str__(self) -> str:
+ return f"{super().__str__()} (expected at least {self.expected}, got {self.actual})"
class TooManyValid(Invalid):
"""The value passed more than expected validations."""
- pass
+ def __init__(self, msg: str, path: typing.Optional[typing.List[typing.Hashable]] = None,
+ error_message: typing.Optional[str] = None,
+ error_type: typing.Optional[str] = None,
+ expected: int = 0, actual: int = 0) -> None:
+ super().__init__(msg, path, error_message, error_type)
+ self.expected = expected
+ self.actual = actual
+
+ def __str__(self) -> str:
+ return f"{super().__str__()} (expected at most {self.expected}, got {self.actual})"
diff --git a/voluptuous/humanize.py b/voluptuous/humanize.py
index 2902871..fabb7d0 100644
--- a/voluptuous/humanize.py
+++ b/voluptuous/humanize.py
@@ -11,4 +11,29 @@ def humanize_error(data, validation_error: Invalid, max_sub_error_length:
Invalid and MultipleInvalid do not include the offending value in error messages,
and MultipleInvalid.__str__ only provides the first error.
"""
- pass
+ if isinstance(validation_error, MultipleInvalid):
+ errors = validation_error.errors
+ else:
+ errors = [validation_error]
+
+ error_messages = []
+ for error in errors:
+ path = ' -> '.join(str(p) for p in error.path) if error.path else 'root'
+ value = get_value_from_path(data, error.path)
+ value_str = str(value)[:max_sub_error_length]
+ if len(str(value)) > max_sub_error_length:
+ value_str += '...'
+ error_messages.append(f"Error at {path}: {error.error_message}. Got: {value_str}")
+
+ return '\n'.join(error_messages)
+
+def get_value_from_path(data, path):
+ """Helper function to get the value from the data using the error path."""
+ for key in path:
+ if isinstance(data, (list, tuple)) and isinstance(key, int):
+ data = data[key] if 0 <= key < len(data) else None
+ elif isinstance(data, dict):
+ data = data.get(key)
+ else:
+ return None
+ return data
diff --git a/voluptuous/schema_builder.py b/voluptuous/schema_builder.py
index de2b53c..df86a0a 100644
--- a/voluptuous/schema_builder.py
+++ b/voluptuous/schema_builder.py
@@ -113,7 +113,21 @@ class Schema(object):
Note: only very basic inference is supported.
"""
- pass
+ def infer_type(value):
+ if isinstance(value, dict):
+ return {k: infer_type(v) for k, v in value.items()}
+ elif isinstance(value, list):
+ if value:
+ return [infer_type(value[0])]
+ else:
+ return list
+ elif isinstance(value, (int, float, str, bool)):
+ return type(value)
+ else:
+ return object
+
+ inferred_schema = infer_type(data)
+ return cls(inferred_schema, **kwargs)
def __eq__(self, other):
if not isinstance(other, Schema):
@@ -142,7 +156,34 @@ class Schema(object):
def _compile_mapping(self, schema, invalid_msg=None):
"""Create validator for given mapping."""
- pass
+ def validate_mapping(path, data):
+ if not isinstance(data, dict):
+ raise er.Invalid(invalid_msg or 'expected a dictionary', path)
+
+ out = type(data)()
+ for key, value in data.items():
+ key_path = path + [key]
+ if key in schema:
+ out[key] = schema[key](key_path, value)
+ elif isinstance(key, str) and key.startswith('$'):
+ out[key] = value
+ elif Any in schema:
+ out[key] = schema[Any](key_path, value)
+ elif self.extra == ALLOW_EXTRA:
+ out[key] = value
+ elif self.extra != REMOVE_EXTRA:
+ raise er.Invalid('extra keys not allowed', key_path)
+
+ for key, validator in schema.items():
+ if isinstance(key, Required) and key.schema not in out:
+ if key.default is not UNDEFINED:
+ out[key.schema] = key.default()
+ else:
+ raise er.RequiredFieldInvalid('required key not provided', path + [key.schema])
+
+ return out
+
+ return validate_mapping
def _compile_object(self, schema):
"""Validate an object.
@@ -162,7 +203,27 @@ class Schema(object):
... validate(Structure(one='three'))
"""
- pass
+ def validate_object(path, data):
+ if not isinstance(data, schema.cls):
+ raise er.Invalid(f'expected {schema.cls.__name__}', path)
+
+ out = schema.cls()
+ for key, validator in schema.items():
+ if hasattr(data, key):
+ value = getattr(data, key)
+ try:
+ setattr(out, key, validator(path + [key], value))
+ except er.Invalid as e:
+ raise er.Invalid(f'not a valid value for object value', path + [key]) from e
+ elif isinstance(key, Required):
+ if key.default is not UNDEFINED:
+ setattr(out, key.schema, key.default())
+ else:
+ raise er.RequiredFieldInvalid('required attribute not provided', path + [key.schema])
+
+ return out
+
+ return validate_object
def _compile_dict(self, schema):
"""Validate a dictionary.
@@ -240,7 +301,48 @@ class Schema(object):
"expected str for dictionary value @ data['adict']['strfield']"]
"""
- pass
+ def validate_dict(path, data):
+ if not isinstance(data, dict):
+ raise er.Invalid('expected a dictionary', path)
+
+ out = type(data)()
+ errors = []
+ for key, value in data.items():
+ key_path = path + [key]
+ if key in schema:
+ try:
+ out[key] = schema[key](key_path, value)
+ except er.Invalid as e:
+ errors.append(e)
+ elif isinstance(key, type):
+ if any(isinstance(k, type) for k in schema):
+ continue
+ try:
+ valid_key = next(k for k in schema if isinstance(k, type) and isinstance(key, k))
+ out[key] = schema[valid_key](key_path, value)
+ except StopIteration:
+ if self.extra == ALLOW_EXTRA:
+ out[key] = value
+ elif self.extra != REMOVE_EXTRA:
+ errors.append(er.Invalid('extra keys not allowed', key_path))
+ elif self.extra == ALLOW_EXTRA:
+ out[key] = value
+ elif self.extra != REMOVE_EXTRA:
+ errors.append(er.Invalid('extra keys not allowed', key_path))
+
+ for key in schema:
+ if isinstance(key, Required) and key not in out:
+ if key.default is not UNDEFINED:
+ out[key.schema] = key.default()
+ else:
+ errors.append(er.RequiredFieldInvalid('required key not provided', path + [key.schema]))
+
+ if errors:
+ raise er.MultipleInvalid(errors)
+
+ return out
+
+ return validate_dict
def _compile_sequence(self, schema, seq_type):
"""Validate a sequence type.
@@ -255,7 +357,28 @@ class Schema(object):
>>> validator([1])
[1]
"""
- pass
+ def validate_sequence(path, data):
+ if not isinstance(data, seq_type):
+ raise er.Invalid(f'expected a {seq_type.__name__}', path)
+
+ out = []
+ errors = []
+ for i, value in enumerate(data):
+ item_path = path + [i]
+ for validator in schema:
+ try:
+ out.append(validator(item_path, value))
+ break
+ except er.Invalid as e:
+ if validator == schema[-1]:
+ errors.append(e)
+
+ if errors:
+ raise er.MultipleInvalid(errors)
+
+ return seq_type(out)
+
+ return validate_sequence
def _compile_tuple(self, schema):
"""Validate a tuple.
@@ -270,7 +393,7 @@ class Schema(object):
>>> validator((1,))
(1,)
"""
- pass
+ return self._compile_sequence(schema, tuple)
def _compile_list(self, schema):
"""Validate a list.
@@ -285,7 +408,7 @@ class Schema(object):
>>> validator([1])
[1]
"""
- pass
+ return self._compile_sequence(schema, list)
def _compile_set(self, schema):
"""Validate a set.
@@ -300,7 +423,28 @@ class Schema(object):
>>> with raises(er.MultipleInvalid, 'invalid value in set'):
... validator(set(['a']))
"""
- pass
+ def validate_set(path, data):
+ if not isinstance(data, set):
+ raise er.Invalid('expected a set', path)
+
+ out = set()
+ errors = []
+ for i, value in enumerate(data):
+ item_path = path + [i]
+ for validator in schema:
+ try:
+ out.add(validator(item_path, value))
+ break
+ except er.Invalid as e:
+ if validator == schema[-1]:
+ errors.append(e)
+
+ if errors:
+ raise er.MultipleInvalid(errors)
+
+ return out
+
+ return validate_set
def extend(self, schema: Schemable, required: typing.Optional[bool]=
None, extra: typing.Optional[int]=None) ->Schema:
@@ -316,7 +460,17 @@ class Schema(object):
:param required: if set, overrides `required` of this `Schema`
:param extra: if set, overrides `extra` of this `Schema`
"""
- pass
+ if not isinstance(self.schema, dict) or not isinstance(schema, dict):
+ raise ValueError("Both schemas must be dictionary-based")
+
+ new_schema = self.schema.copy()
+ new_schema.update(schema)
+
+ return Schema(
+ new_schema,
+ required=self.required if required is None else required,
+ extra=self.extra if extra is None else extra
+ )
def _compile_scalar(schema):
@@ -707,4 +861,16 @@ def validate(*a, **kw) ->typing.Callable:
... return arg1 * 2
"""
- pass
+ def decorator(func):
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ schema = Schema(dict(zip(func.__code__.co_varnames, a)), **kw)
+ bound_args = inspect.signature(func).bind(*args, **kwargs)
+ bound_args.apply_defaults()
+ validated_args = schema(dict(bound_args.arguments))
+ result = func(**validated_args)
+ if '__return__' in kw:
+ return Schema(kw['__return__'])(result)
+ return result
+ return wrapper
+ return decorator
diff --git a/voluptuous/util.py b/voluptuous/util.py
index fe15b1a..a2c57d8 100644
--- a/voluptuous/util.py
+++ b/voluptuous/util.py
@@ -13,7 +13,7 @@ def Lower(v: str) ->str:
>>> s('HI')
'hi'
"""
- pass
+ return v.lower()
def Upper(v: str) ->str:
@@ -23,7 +23,7 @@ def Upper(v: str) ->str:
>>> s('hi')
'HI'
"""
- pass
+ return v.upper()
def Capitalize(v: str) ->str:
@@ -33,7 +33,7 @@ def Capitalize(v: str) ->str:
>>> s('hello world')
'Hello world'
"""
- pass
+ return v.capitalize()
def Title(v: str) ->str:
@@ -43,7 +43,7 @@ def Title(v: str) ->str:
>>> s('hello world')
'Hello World'
"""
- pass
+ return v.title()
def Strip(v: str) ->str:
@@ -53,7 +53,7 @@ def Strip(v: str) ->str:
>>> s(' hello world ')
'hello world'
"""
- pass
+ return v.strip()
class DefaultTo(object):
diff --git a/voluptuous/validators.py b/voluptuous/validators.py
index 88b50f6..41ef694 100644
--- a/voluptuous/validators.py
+++ b/voluptuous/validators.py
@@ -41,7 +41,13 @@ def truth(f: typing.Callable) ->typing.Callable:
>>> with raises(MultipleInvalid, 'not a valid value'):
... validate('/notavaliddir')
"""
- pass
+ @wraps(f)
+ def wrapper(v):
+ t = f(v)
+ if not t:
+ raise ValueError
+ return v
+ return wrapper
class Coerce(object):
@@ -109,7 +115,7 @@ def IsTrue(v):
... except MultipleInvalid as e:
... assert isinstance(e.errors[0], TrueInvalid)
"""
- pass
+ return bool(v)
@message('value was not false', cls=FalseInvalid)
@@ -129,7 +135,9 @@ def IsFalse(v):
... except MultipleInvalid as e:
... assert isinstance(e.errors[0], FalseInvalid)
"""
- pass
+ if bool(v):
+ raise ValueError
+ return v
@message('expected boolean', cls=BooleanInvalid)
@@ -153,7 +161,18 @@ def Boolean(v):
... except MultipleInvalid as e:
... assert isinstance(e.errors[0], BooleanInvalid)
"""
- pass
+ if isinstance(v, bool):
+ return v
+ if isinstance(v, str):
+ v = v.lower()
+ if v in ('1', 'true', 'yes', 'on', 'enable'):
+ return True
+ if v in ('0', 'false', 'no', 'off', 'disable'):
+ return False
+ try:
+ return bool(v)
+ except ValueError:
+ raise BooleanInvalid('expected boolean')
class _WithSubValidators(object):
@@ -213,6 +232,15 @@ class Any(_WithSubValidators):
>>> with raises(MultipleInvalid, "Expected 1 2 or 3"):
... validate(4)
"""
+ def _run(self, v):
+ errors = []
+ for validator in self._compiled:
+ try:
+ return validator(v)
+ except Invalid as e:
+ errors.append(e)
+ else:
+ raise AnyInvalid(self.msg or errors)
Or = Any
@@ -255,6 +283,13 @@ class All(_WithSubValidators):
>>> validate('10')
10
"""
+ def _run(self, v):
+ try:
+ for validator in self._compiled:
+ v = validator(v)
+ except Invalid as e:
+ raise AllInvalid(self.msg or str(e))
+ return v
And = All
@@ -340,7 +375,17 @@ def Email(v):
>>> s('t@x.com')
't@x.com'
"""
- pass
+ try:
+ if not isinstance(v, str):
+ raise ValueError
+ user_part, domain_part = v.rsplit('@', 1)
+ if not USER_REGEX.match(user_part):
+ raise ValueError
+ if not DOMAIN_REGEX.match(domain_part):
+ raise ValueError
+ except ValueError:
+ raise EmailInvalid('expected an email address')
+ return v
@message('expected a fully qualified domain name URL', cls=UrlInvalid)
@@ -353,7 +398,17 @@ def FqdnUrl(v):
>>> s('http://w3.org')
'http://w3.org'
"""
- pass
+ try:
+ if not isinstance(v, str):
+ raise ValueError
+ parsed = urlparse.urlparse(v)
+ if not parsed.scheme or not parsed.netloc or parsed.netloc == 'localhost':
+ raise ValueError
+ if not DOMAIN_REGEX.match(parsed.netloc):
+ raise ValueError
+ except ValueError:
+ raise UrlInvalid('expected a fully qualified domain name URL')
+ return v
@message('expected a URL', cls=UrlInvalid)
@@ -366,7 +421,15 @@ def Url(v):
>>> s('http://w3.org')
'http://w3.org'
"""
- pass
+ try:
+ if not isinstance(v, str):
+ raise ValueError
+ parsed = urlparse.urlparse(v)
+ if not parsed.scheme or not parsed.netloc:
+ raise ValueError
+ except ValueError:
+ raise UrlInvalid('expected a URL')
+ return v
@message('Not a file', cls=FileInvalid)
@@ -425,7 +488,11 @@ def Maybe(validator: Schemable, msg: typing.Optional[str]=None):
... s("string")
"""
- pass
+ def maybe_validator(v):
+ if v is None:
+ return v
+ return Schema(validator)(v)
+ return maybe_validator
class Range(object):