Skip to content

Commit

Permalink
refactor code, add pyi typing, fix smoke test
Browse files Browse the repository at this point in the history
  • Loading branch information
guyskk committed Dec 13, 2023
1 parent a75439d commit c04f01f
Show file tree
Hide file tree
Showing 10 changed files with 316 additions and 198 deletions.
17 changes: 13 additions & 4 deletions src/validr/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
"""A simple, fast, extensible python library for data validation."""
from .exception import Invalid, ModelInvalid, SchemaError, ValidrError, mark_index, mark_key
from .model import ImmutableInstanceError, asdict, fields, modelclass
from .schema import Builder, Compiler, Schema, T
from .validator import (
create_re_validator, create_enum_validator, builtin_validators, validator)
from .schema import Schema, Compiler, T, Builder
from .model import modelclass, fields, asdict, ImmutableInstanceError
Invalid,
ModelInvalid,
SchemaError,
ValidrError,
builtin_validators,
create_enum_validator,
create_re_validator,
)
from .validator import py_mark_index as mark_index
from .validator import py_mark_key as mark_key
from .validator import validator

__all__ = (
'ValidrError', 'Invalid', 'ModelInvalid', 'SchemaError',
Expand Down
176 changes: 0 additions & 176 deletions src/validr/_exception_c.pyx

This file was deleted.

180 changes: 179 additions & 1 deletion src/validr/_validator_c.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,190 @@ from copy import copy
from functools import partial
from urllib.parse import urlparse, urlunparse

from .exception import Invalid, SchemaError, mark_key, mark_index
from ._vendor import durationpy
from ._vendor.email_validator import validate_email, EmailNotValidError
from ._vendor.fqdn import FQDN


_NOT_SET = object()


cdef _shorten(str text, int length):
if len(text) > length:
return text[:length] + '..'
return text


cdef _format_value(value):
if isinstance(value, str):
return repr(_shorten(value, 75))
else:
return _shorten(str(value), 75)


cdef _format_error(args, str position, str value_clause=None):
cdef str msg = str(args[0]) if args else 'invalid'
if position:
msg = '%s: %s' % (position, msg)
if value_clause:
msg = '%s, %s' % (msg, value_clause)
return msg


class ValidrError(ValueError):
"""Base exception of validr"""

def __init__(self, *args, value=_NOT_SET, **kwargs):
super().__init__(*args, **kwargs)
self._value = value
# marks item: (is_key, index_or_key)
self.marks = []

def mark_index(self, int index=-1):
self.marks.append((False, index))
return self

def mark_key(self, str key):
self.marks.append((True, key))
return self

@property
def has_value(self):
"""Check has value set"""
return self._value is not _NOT_SET

def set_value(self, value):
"""Set value if not set"""
if self._value is _NOT_SET:
self._value = value

@property
def value(self):
"""The invalid value"""
if self._value is _NOT_SET:
return None
return self._value

@property
def field(self):
"""First level index or key, usually it's the field"""
if not self.marks:
return None
__, index_or_key = self.marks[-1]
return index_or_key

@property
def position(self):
"""A string which represent the position of invalid.
For example:
{
"tags": ["ok", "invalid"], # tags[1]
"user": {
"name": "invalid", # user.name
"age": 500 # user.age
}
}
"""
cdef str text = ''
cdef bint is_key
for is_key, index_or_key in reversed(self.marks):
if is_key:
text = '%s.%s' % (text, index_or_key)
else:
if index_or_key == -1:
text = '%s[]' % text
else:
text = '%s[%d]' % (text, index_or_key)
if text and text[0] == '.':
text = text[1:]
return text

@property
def message(self):
"""Error message"""
if self.args:
return self.args[0]
else:
return None

def __str__(self):
return _format_error(self.args, self.position)


class Invalid(ValidrError):
"""Data invalid"""
def __str__(self):
cdef str value_clause = None
if self.has_value:
value_clause = 'value=%s' % _format_value(self.value)
return _format_error(self.args, self.position, value_clause)


class ModelInvalid(Invalid):
"""Model data invalid"""
def __init__(self, errors):
if not errors:
raise ValueError('errors is required')
self.errors = errors
message = errors[0].message or 'invalid'
message += ' ...total {} errors'.format(len(errors))
super().__init__(message)

def __str__(self):
error_line_s = []
for ex in self.errors:
error_line_s.append('{} is {}'.format(ex.position, ex.message))
return '; '.join(error_line_s)


class SchemaError(ValidrError):
"""Schema error"""
def __str__(self):
cdef str value_clause = None
if self.has_value:
value_clause = 'schema=%s' % self.value.repr(prefix=False, desc=False)
return _format_error(self.args, self.position, value_clause)


cdef class mark_index:
"""Add current index to Invalid/SchemaError"""

cdef int index

def __init__(self, index=-1):
"""index = -1 means the position is uncertainty"""
self.index = index

def __enter__(self):
return self

def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is not None and issubclass(exc_type, ValidrError):
exc_val.mark_index(self.index)


cdef class mark_key:
"""Add current key to Invalid/SchemaError"""

cdef str key

def __init__(self, key):
self.key = key

def __enter__(self):
return self

def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is not None and issubclass(exc_type, ValidrError):
exc_val.mark_key(self.key)


class py_mark_index(mark_index): pass
class py_mark_key(mark_key): pass


cdef bint is_dict(obj):
# use isinstance(obj, Mapping) is slow,
# hasattr check can speed up about 30%
Expand Down
4 changes: 0 additions & 4 deletions src/validr/exception.py

This file was deleted.

Loading

0 comments on commit c04f01f

Please sign in to comment.