validate many, docstrings, validator register/get
added docstrings, validate_many, register_validator, get_validator, removed VALIDATORS from public, the cython version will break things when trying to import.
MODIFY
benchmark/benchmark.py
benchmark/benchmark.py
@@ -9,7 +9,7 @@ except ImportError:
from fastavro._timezone import utc
try:
- from fastavro.validate import validate
+ from fastavro.validate import validate, validate_many
except ImportError:
try:
from fastavro._write import validate
@@ -17,6 +17,10 @@ except ImportError:
from fastavro._write_py import validate
+ def validate_many(records, schema):
+ return all([validate(record, schema) for record in records])
+
+
def write(schema, records, runs=1):
times = []
for _ in range(runs):
@@ -34,11 +38,11 @@ def validater(schema, records, runs=1):
valid = []
for _ in range(runs):
start = time.time()
- valid = [validate(record, schema) for record in records]
+ valid = validate_many(records, schema)
end = time.time()
times.append(end - start)
print('... {0} runs averaged {1} seconds'.format(runs, (sum(times) / runs)))
- return all(valid)
+ return valid
def read(iostream, runs=1):

MODIFY
fastavro/__init__.py
fastavro/__init__.py
@@ -47,6 +47,7 @@ __version__ = '%s.%s.%s' % __version_info__
import fastavro.read
import fastavro.write
import fastavro.schema
+import fastavro.validate
def _acquaint_schema(schema):
@@ -70,6 +71,7 @@ dump = fastavro.write.dump
acquaint_schema = _acquaint_schema
fastavro.schema.acquaint_schema = _acquaint_schema
is_avro = fastavro.read.is_avro
+validator = fastavro.validate.validate
__all__ = [
n for n in locals().keys() if not n.startswith('_')

MODIFY
fastavro/_validate.pyx
fastavro/_validate.pyx
@@ -19,24 +19,75 @@ cdef int32 INT_MAX_VALUE = const.INT_MAX_VALUE
cdef long64 LONG_MIN_VALUE = const.LONG_MIN_VALUE
cdef long64 LONG_MAX_VALUE = const.LONG_MAX_VALUE
-cpdef inline bint validate_null(datum, schema=None,
+cpdef bint validate_null(datum, schema=None,
str parent_ns='', bint raise_errors=False):
+ """
+ Checks that the data value is None.
+
+ :param datum: data to validate as None
+ :param raise_errors: not used
+ :param parent_ns: not used
+ :param schema: not used
+ :return: bool
+ """
return datum is None
-cpdef inline bint validate_boolean(datum, schema=None,
+cpdef bint validate_boolean(datum, schema=None,
str parent_ns='', bint raise_errors=False):
+ """Check that the data value is bool instance
+
+ :param datum: data to validate as boolean
+ :param raise_errors: not used
+ :param parent_ns: not used
+ :param schema: not used
+ :return: bool
+ """
return isinstance(datum, bool)
-cpdef inline bint validate_string(datum, schema=None,
+cpdef bint validate_string(datum, schema=None,
str parent_ns='', bint raise_errors=False):
+ """Check that the data value is string type, uses
+ six for Python version compatibility.
+
+ :param datum: data to validate as string
+ :param raise_errors: not used
+ :param parent_ns: not used
+ :param schema: not used
+ :return: bool
+ """
return is_str(datum)
-cpdef inline bint validate_bytes(datum, schema=None,
+cpdef bint validate_bytes(datum, schema=None,
str parent_ns='', bint raise_errors=False):
+ """
+ Check that the data value is
+ (bytes or decimal.Decimal)
+
+ :param datum: data to validate as bytes
+ :param raise_errors: not used
+ :param parent_ns: not used
+ :param schema: not used
+ :return: bool
+ """
return isinstance(datum, (bytes, decimal.Decimal))
-cpdef inline bint validate_int(datum, schema=None,
+cpdef bint validate_int(datum, schema=None,
str parent_ns='', bint raise_errors=False):
+ """
+ Check that the data value is a non floating
+ point number with size less that Int32.
+
+ Also support for logicalType timestamp validation with datetime.
+
+ Int32 = -2147483648<=datum<=2147483647
+
+ :param datum: (int, long, numbers.Integral,
+ datetime.time, datetime.datetime, datetime.date)
+ :param raise_errors: not used
+ :param parent_ns: not used
+ :param schema: not used
+ :return: bool
+ """
return (
(isinstance(datum, (int, long, numbers.Integral)) and
INT_MIN_VALUE <= datum <= INT_MAX_VALUE) or
@@ -44,8 +95,22 @@ cpdef inline bint validate_int(datum, schema=None,
datetime.time, datetime.datetime, datetime.date))
)
-cpdef inline bint validate_long(datum, schema=None,
+cpdef bint validate_long(datum, schema=None,
str parent_ns='', bint raise_errors=False):
+ """
+ Check that the data value is a non floating
+ point number with size less that Int32.
+
+ Also support for logicalType timestamp validation with datetime.
+
+ Int64 = -9223372036854775808 <= datum <= 9223372036854775807
+
+ :param datum: number: data to validate as long64
+ :param raise_errors: not used
+ :param parent_ns: not used
+ :param schema: not used
+ :return: bool
+ """
return (
(isinstance(datum, (int, long, numbers.Integral)) and
LONG_MIN_VALUE <= datum <= LONG_MAX_VALUE) or
@@ -53,22 +118,63 @@ cpdef inline bint validate_long(datum, schema=None,
datetime.time, datetime.datetime, datetime.date))
)
-cpdef inline bint validate_float(datum, schema=None,
+cpdef bint validate_float(datum, schema=None,
str parent_ns='', bint raise_errors=False):
+ """
+ Check that the data value is a floating
+ point number or double precision.
+
+ :param datum: number: data to validate as float
+ :param raise_errors: not used
+ :param parent_ns: not used
+ :param schema: not used
+ :return: bool
+ """
return isinstance(datum, (int, long, float, numbers.Real))
-cpdef inline bint validate_fixed(datum, dict schema,
+cpdef bint validate_fixed(datum, dict schema,
str parent_ns='', bint raise_errors=False):
+ """
+ Check that the data value is fixed width bytes,
+ matching the schema['size'] exactly!
+
+ :param datum: (bytes, decimal.Decimal)
+ :param schema: avro schema of 'fixed' type
+ :param parent_ns: not used
+ :param raise_errors: not used
+ :return: bool
+ """
return (isinstance(datum, bytes) and
len(datum) == schema['size']) or \
(isinstance(datum, decimal.Decimal))
-cpdef inline bint validate_enum(datum, dict schema,
+cpdef bint validate_enum(datum, dict schema,
str parent_ns='', bint raise_errors=False):
+ """
+ Check that the data value matches one of the enum symbols.
+
+ i.e "blue" in ["red", green", "blue"]
+
+ :param datum: str: data to validate in enum symbols
+ :param schema: avro schema of 'enum' type
+ :param parent_ns: not used
+ :param raise_errors: not used
+ :return: bool
+ """
return datum in schema['symbols']
-cpdef inline bint validate_array(datum, dict schema,
+cpdef bint validate_array(datum, dict schema,
str parent_ns='', bint raise_errors=False):
+ """
+ Check that the data list values all match schema['items'].
+
+ :param datum: list: data to validate as specified "items" type
+ :param schema: avro schema of 'array' type
+ :param parent_ns: str: parent namespace
+ :param raise_errors: bool: should raise ValidationError
+ :return: bool
+ :except: ValidationError
+ """
if not isinstance(datum, Iterable) or is_str(datum):
return False
@@ -83,17 +189,30 @@ cpdef inline bint validate_array(datum, dict schema,
return False
return True
-cpdef inline bint validate_map(object datum, dict schema, str parent_ns='',
+cpdef bint validate_map(object datum, dict schema, str parent_ns='',
bint raise_errors=False):
- if raise_errors:
- namespace, name = schema_name(schema, parent_ns)
- else:
- name = parent_ns
+ """
+ Check that the data is a Map(k,v)
+ matching values to schema['values'] type.
+
+ :param datum: Mapping: data to validate as specified "items" type
+ :param schema: avro schema of 'map' type
+ :param parent_ns: str: parent namespace
+ :param raise_errors: bool: should raise ValidationError
+ :return: bool
+ :except: ValidationError
+ """
+ # initial checks for map type
if not isinstance(datum, Mapping):
return False
for k in iterkeys(datum):
if not is_str(k):
return False
+
+ if raise_errors:
+ namespace, name = schema_name(schema, parent_ns)
+ else:
+ name = parent_ns
for v in itervalues(datum):
if not validate(datum=v, schema=schema['values'],
field=name,
@@ -101,8 +220,19 @@ cpdef inline bint validate_map(object datum, dict schema, str parent_ns='',
return False
return True
-cpdef inline bint validate_record(object datum, dict schema, str parent_ns='',
+cpdef bint validate_record(object datum, dict schema, str parent_ns='',
bint raise_errors=False):
+ """
+ Check that the data is a Mapping type with all schema defined fields
+ validated as True.
+
+ :param datum: Mapping: data to validate schema fields
+ :param schema: avro schema of 'record' type
+ :param parent_ns: str: parent namespace
+ :param raise_errors: bool: should raise ValidationError
+ :return: bool
+ :except: ValidationError
+ """
if not isinstance(datum, Mapping):
return False
if raise_errors:
@@ -117,8 +247,20 @@ cpdef inline bint validate_record(object datum, dict schema, str parent_ns='',
return False
return True
-cpdef inline bint validate_union(object datum, list schema, str parent_ns=None,
+cpdef bint validate_union(object datum, list schema, str parent_ns=None,
bint raise_errors=False):
+ """
+ Check that the data is a list type with possible options to
+ validate as True.
+
+ :param datum: (Iterable, tuple(name, Iterable)): data to validate
+ as multiple data types
+ :param schema: avro schema of 'union' type
+ :param parent_ns: str: parent namespace
+ :param raise_errors: bool: should raise ValidationError
+ :return: bool
+ :except: ValidationError
+ """
if isinstance(datum, tuple):
(name, datum) = datum
for candidate in schema:
@@ -130,7 +272,7 @@ cpdef inline bint validate_union(object datum, list schema, str parent_ns=None,
else:
return False
- errors = []
+ cdef list errors = []
for s in schema:
try:
ret = validate(datum, schema=s,
@@ -145,7 +287,7 @@ cpdef inline bint validate_union(object datum, list schema, str parent_ns=None,
raise ValidationError(*errors)
return False
-BASE_VALIDATORS = {
+cpdef BASE_VALIDATORS = {
'null': validate_null,
'boolean': validate_boolean,
'string': validate_string,
@@ -165,15 +307,15 @@ BASE_VALIDATORS = {
'request': validate_record
}
-VALIDATORS = BASE_VALIDATORS.copy()
+cpdef VALIDATORS = BASE_VALIDATORS.copy()
-cpdef void register_validator(avro_type, validator):
- if avro_type in BASE_VALIDATORS:
+cpdef void register_validator(record_type, validator):
+ if record_type in BASE_VALIDATORS:
raise ValueError("Not allowed to override Base Validators.")
- VALIDATORS[avro_type] = validator
+ VALIDATORS[record_type] = validator
-cpdef get_validator(avro_type):
- return VALIDATORS.get(avro_type)
+cpdef get_validator(record_type):
+ return VALIDATORS.get(record_type)
cpdef validate(object datum, object schema, str field='',
bint raise_errors=False):
@@ -187,6 +329,47 @@ cpdef validate(object datum, object schema, str field='',
elif field:
ns_field = field
+ # explicit, so that cython is faster, but only for Base Validators
+ if record_type == 'null':
+ result = validate_null(datum, schema=schema, parent_ns=ns_field,
+ raise_errors=raise_errors)
+ elif record_type == 'boolean':
+ result = validate_boolean(datum, schema=schema, parent_ns=ns_field,
+ raise_errors=raise_errors)
+ elif record_type == 'string':
+ result = validate_string(datum, schema=schema, parent_ns=ns_field,
+ raise_errors=raise_errors)
+ elif record_type == 'int':
+ result = validate_int(datum, schema=schema, parent_ns=ns_field,
+ raise_errors=raise_errors)
+ elif record_type == 'long':
+ result = validate_long(datum, schema=schema, parent_ns=ns_field,
+ raise_errors=raise_errors)
+ elif record_type in ('float', 'double'):
+ result = validate_float(datum, schema=schema, parent_ns=ns_field,
+ raise_errors=raise_errors)
+ elif record_type == 'bytes':
+ result = validate_bytes(datum, schema=schema, parent_ns=ns_field,
+ raise_errors=raise_errors)
+ elif record_type == 'fixed':
+ result = validate_fixed(datum, schema=schema, parent_ns=ns_field,
+ raise_errors=raise_errors)
+ elif record_type == 'enum':
+ result = validate_enum(datum, schema=schema, parent_ns=ns_field,
+ raise_errors=raise_errors)
+ elif record_type == 'array':
+ result = validate_array(datum, schema=schema, parent_ns=ns_field,
+ raise_errors=raise_errors)
+ elif record_type == 'map':
+ result = validate_map(datum, schema=schema, parent_ns=ns_field,
+ raise_errors=raise_errors)
+ elif record_type in ('union', 'error_union'):
+ result = validate_union(datum, schema=schema, parent_ns=ns_field,
+ raise_errors=raise_errors)
+ elif record_type in ('record', 'error', 'request'):
+ result = validate_record(datum, schema=schema, parent_ns=ns_field,
+ raise_errors=raise_errors)
+ else:
validator = get_validator(record_type)
if validator:
result = validator(datum, schema=schema, parent_ns=ns_field,
@@ -205,3 +388,32 @@ cpdef validate(object datum, object schema, str field='',
raise UnknownType(record_type)
return bool(result)
+
+
+cpdef validate_many(records, schema, bint raise_errors=False, int stop_count=-1):
+ """
+ Validate a list of data!
+
+ :param records: Iterable: list of records to validate
+ :param schema: Avro schema
+ :param raise_errors: bool: should raise ValidationError
+ :param stop_count: int: stop early for error count
+ :return: bool
+ :except: ValidationError
+ """
+ cdef int error_count = 0
+ cdef bint result
+ cdef list errors = []
+ cdef list results = []
+ for record in records:
+ try:
+ result = validate(record, schema, raise_errors=raise_errors)
+ results.append(result)
+ except ValidationError as e:
+ error_count += 1
+ errors.extend(e.errors)
+ if error_count >= stop_count:
+ break
+ if raise_errors:
+ raise ValidationError(*errors)
+ return all(results)

MODIFY
fastavro/_validate_py.py
fastavro/_validate_py.py
@@ -12,22 +12,66 @@ from ._schema_common import SCHEMA_DEFS
def validate_null(datum, **kwargs):
+ """
+ Checks that the data value is None.
+
+ :param datum: None : data to validate as None
+ :param kwargs: black-hole args
+ :return: bool
+ """
return datum is None
def validate_boolean(datum, **kwargs):
+ """
+ Check that the data value is bool instance
+
+ :param datum: (bool) : data to validate as boolean
+ :param kwargs: black-hole args
+ :return: bool
+ """
return isinstance(datum, bool)
def validate_string(datum, **kwargs):
+ """
+ Check that the data value is string type, uses
+ six for Python version compatibility.
+
+ :param datum: (str, basestring, unicode) : data to validate as string
+ :param kwargs: black-hole args
+ :return: bool
+ """
return is_str(datum)
def validate_bytes(datum, **kwargs):
+ """
+ Check that the data value is (python bytes type or decimal.Decimal type
+
+ :param datum: (bytes, decimal.Decimal): data to validate as bytes
+ :param kwargs: black-hole args
+ :return: bool
+ """
return isinstance(datum, (bytes, decimal.Decimal))
def validate_int(datum, **kwargs):
+ """
+ Check that the data value is a non floating
+ point number with size less that Int32.
+ Also support for logicalType timestamp validation with datetime.
+
+ Int32 = -2147483648<=datum<=2147483647
+
+ conditional python types
+ (int, long, numbers.Integral,
+ datetime.time, datetime.datetime, datetime.date)
+
+ :param datum: number: data to validate as int32
+ :param kwargs: black-hole args
+ :return: bool
+ """
return (
(isinstance(datum, (int, long, numbers.Integral)) and
INT_MIN_VALUE <= datum <= INT_MAX_VALUE) or
@@ -37,6 +81,21 @@ def validate_int(datum, **kwargs):
def validate_long(datum, **kwargs):
+ """
+ Check that the data value is a non floating
+ point number with size less that long64.
+ * Also support for logicalType timestamp validation with datetime.
+
+ Int64 = -9223372036854775808 <= datum <= 9223372036854775807
+
+ conditional python types
+ (int, long, numbers.Integral,
+ datetime.time, datetime.datetime, datetime.date)
+
+ :param datum: number: data to validate as long64
+ :param kwargs: black-hole args
+ :return: bool
+ """
return (
(isinstance(datum, (int, long, numbers.Integral)) and
LONG_MIN_VALUE <= datum <= LONG_MAX_VALUE) or
@@ -46,10 +105,30 @@ def validate_long(datum, **kwargs):
def validate_float(datum, **kwargs):
+ """
+ Check that the data value is a floating
+ point number or double precision.
+
+ conditional python types
+ (int, long, float, numbers.Real)
+
+ :param datum: number: data to validate as float
+ :param kwargs: black-hole args
+ :return: bool
+ """
return isinstance(datum, (int, long, float, numbers.Real))
def validate_fixed(datum, schema, **kwargs):
+ """
+ Check that the data value is fixed width bytes,
+ matching the schema['size'] exactly!
+
+ :param datum: (bytes, decimal.Decimal): data to validate as fixed bytes
+ :param schema: avro schema of 'fixed' type
+ :param kwargs: black-hole args
+ :return: bool
+ """
return (
(isinstance(datum, bytes) and len(datum) == schema['size'])
or (isinstance(datum, decimal.Decimal))
@@ -57,10 +136,30 @@ def validate_fixed(datum, schema, **kwargs):
def validate_enum(datum, schema, **kwargs):
+ """
+ Check that the data value matches one of the enum symbols.
+
+ i.e "blue" in ["red", green", "blue"]
+
+ :param datum: str: data to validate in enum symbols
+ :param schema: avro schema of 'enum' type
+ :param kwargs: black-hole args
+ :return: bool
+ """
return datum in schema['symbols']
def validate_array(datum, schema, parent_ns=None, raise_errors=False):
+ """
+ Check that the data list values all match schema['items'].
+
+ :param datum: list: data to validate as specified "items" type
+ :param schema: avro schema of 'array' type
+ :param parent_ns: str: parent namespace
+ :param raise_errors: bool: should raise ValidationError
+ :return: bool
+ :except: ValidationError
+ """
if raise_errors:
namespace, name = schema_name(schema, parent_ns)
else:
@@ -75,6 +174,17 @@ def validate_array(datum, schema, parent_ns=None, raise_errors=False):
def validate_map(datum, schema, parent_ns=None, raise_errors=False):
+ """
+ Check that the data is a Map(k,v)
+ matching values to schema['values'] type.
+
+ :param datum: Mapping: data to validate as specified "items" type
+ :param schema: avro schema of 'map' type
+ :param parent_ns: str: parent namespace
+ :param raise_errors: bool: should raise ValidationError
+ :return: bool
+ :except: ValidationError
+ """
if raise_errors:
namespace, name = schema_name(schema, parent_ns)
else:
@@ -89,6 +199,17 @@ def validate_map(datum, schema, parent_ns=None, raise_errors=False):
def validate_record(datum, schema, parent_ns=None, raise_errors=False):
+ """
+ Check that the data is a Mapping type with all schema defined fields
+ validated as True.
+
+ :param datum: Mapping: data to validate schema fields
+ :param schema: avro schema of 'record' type
+ :param parent_ns: str: parent namespace
+ :param raise_errors: bool: should raise ValidationError
+ :return: bool
+ :except: ValidationError
+ """
if raise_errors:
namespace, name = schema_name(schema, parent_ns)
else:
@@ -105,6 +226,18 @@ def validate_record(datum, schema, parent_ns=None, raise_errors=False):
def validate_union(datum, schema, parent_ns=None, raise_errors=False):
+ """
+ Check that the data is a list type with possible options to
+ validate as True.
+
+ :param datum: (Iterable, tuple(name, Iterable)): data to validate
+ as multiple data types
+ :param schema: avro schema of 'union' type
+ :param parent_ns: str: parent namespace
+ :param raise_errors: bool: should raise ValidationError
+ :return: bool
+ :except: ValidationError
+ """
if isinstance(datum, tuple):
(name, datum) = datum
for candidate in schema:
@@ -132,7 +265,7 @@ def validate_union(datum, schema, parent_ns=None, raise_errors=False):
return False
-VALIDATORS = {
+BASE_VALIDATORS = {
'null': validate_null,
'boolean': validate_boolean,
'string': validate_string,
@@ -152,6 +285,18 @@ VALIDATORS = {
'request': validate_record
}
+VALIDATORS = BASE_VALIDATORS.copy()
+
+
+def register_validator(record_type, validator):
+ if record_type in BASE_VALIDATORS:
+ raise ValueError("Not allowed to override Base Validators.")
+ VALIDATORS[record_type] = validator
+
+
+def get_validator(record_type):
+ return VALIDATORS.get(record_type)
+
def validate(datum, schema, field=None, raise_errors=False):
"""Determine if a python datum is an instance of a schema."""
@@ -164,15 +309,7 @@ def validate(datum, schema, field=None, raise_errors=False):
elif field:
ns_field = field
- if record_type in ('union', 'null'):
- # test_string_not_treated_as_array
- validator = VALIDATORS.get(record_type)
- result = validator(datum, schema=schema, parent_ns=ns_field,
- raise_errors=raise_errors)
- elif datum is None:
- result = False
- else:
- validator = VALIDATORS.get(record_type)
+ validator = get_validator(record_type)
if validator:
result = validator(datum, schema=schema,
parent_ns=ns_field,
@@ -191,3 +328,30 @@ def validate(datum, schema, field=None, raise_errors=False):
raise UnknownType(record_type)
return result
+
+
+def validate_many(records, schema, raise_errors=False, stop_count=-1):
+ """
+ Validate a list of data!
+
+ :param records: Iterable: list of records to validate
+ :param schema: Avro schema
+ :param raise_errors: bool: should raise ValidationError
+ :param stop_count: int: -1 never stop validation loop
+ :return: bool
+ :except: ValidationError
+ """
+ error_count = 0
+ errors = []
+ results = []
+ for record in records:
+ try:
+ results.append(validate(record, schema, raise_errors=raise_errors))
+ except ValidationError as e:
+ error_count += 1
+ errors.extend(e.errors)
+ if error_count == stop_count:
+ break
+ if raise_errors:
+ raise ValidationError(*errors)
+ return all(results)

MODIFY
fastavro/validate.py
fastavro/validate.py
+'''Fast Avro file iteration.
+
+Example usage::
+
+ # Validating
+ from fastavro import validator
+
+ schema = {
+ 'doc': 'A weather reading.',
+ 'name': 'Weather',
+ 'namespace': 'test',
+ 'type': 'record',
+ 'fields': [
+ {'name': 'station', 'type': 'string'},
+ {'name': 'time', 'type': 'long'},
+ {'name': 'temp', 'type': 'int'},
+ ],
+ }
+
+ # 'records' can be an iterable (including generator)
+ records = [
+ {u'station': u'011990-99999', u'temp': 0, u'time': 1433269388},
+ {u'station': u'011990-99999', u'temp': 22, u'time': 1433270389},
+ {u'station': u'011990-99999', u'temp': -11, u'time': 1433273379},
+ {u'station': u'012650-99999', u'temp': 111, u'time': 1433275478},
+ ]
+
+ writer(out, schema, records)
+'''
try:
from . import _validate
except ImportError:
@@ -5,6 +34,9 @@ except ImportError:
from ._validate_common import ValidationErrorData, ValidationError
validate = _validate.validate
-VALIDATORS = _validate.VALIDATORS
+registry_validator = _validate.register_validator
+get_validator = _validate.get_validator
+validate_many = _validate.validate_many
-__all__ = ['ValidationError', 'ValidationErrorData', 'validate', 'VALIDATORS']
+__all__ = ['ValidationError', 'ValidationErrorData', 'validate',
+ 'validate_many', 'registry_validator', 'get_validator']

MODIFY
tests/test_validation.py
tests/test_validation.py
-from fastavro.validate import validate, ValidationError
+from fastavro.validate import ValidationError, validate_many
import pytest
schema = {
@@ -24,20 +24,14 @@ schema = {
"name": "missingerror",
"type": "record"
}
-
+# TODO: Add more test for all types and combinations
def validation_boolean(schema, *records):
- return all(
- validate(record, schema, raise_errors=False)
- for record in records
- )
+ return validate_many(records, schema, raise_errors=False)
def validation_raise(schema, *records):
- return all(
- validate(record, schema, raise_errors=True)
- for record in records
- )
+ return validate_many(records, schema, raise_errors=True)
def test_validate_string_in_int_raises():

validate many, docstrings, validator register/get

added docstrings, validate_many, register_validator, get_validator, removed VALIDATORS from public, the cython version will break things when trying to import.