-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
01fe7ad
commit c19e229
Showing
4 changed files
with
269 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
## XData | ||
|
||
A simple but useful library for validating data. | ||
|
||
## Installation | ||
|
||
## Usage | ||
|
||
## License | ||
|
||
MIT |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
from setuptools import setup | ||
|
||
import xdata | ||
|
||
setup( | ||
name=xdata.__name__, | ||
version=xdata.__version__, | ||
author="gaojiuli", | ||
author_email="gaojiuli@gmail.com", | ||
description="Simple data validation library", | ||
license="MIT", | ||
keywords="schema json validation", | ||
url="https://github.com/gaojiuli/xdata", | ||
py_modules=['xdata'], | ||
platforms='any', | ||
classifiers=[ | ||
"Development Status :: 2 - Pre-Alpha", | ||
"Topic :: Utilities", | ||
"Programming Language :: Python :: 3.5", | ||
"Programming Language :: Python :: 3.6", | ||
"Programming Language :: Python :: Implementation :: PyPy", | ||
"License :: OSI Approved :: MIT License", | ||
], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
from xdata import * | ||
|
||
|
||
class UserSchema(Schema): | ||
telephone = Str(max_length=12, min_length=16, requred=True) | ||
password = Str(max_length=12, min_length=16, requred=True) | ||
code = Str(length=4, requred=True) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,227 @@ | ||
import re | ||
from datetime import datetime | ||
|
||
__name__ = 'xdata' | ||
__version__ = '0.0.1' | ||
|
||
|
||
class DataType: | ||
def __init__(self, *args, **kwargs): | ||
self.required = kwargs.get('required', False) | ||
self.default = kwargs.get('default', None) | ||
self.choices = kwargs.get('choices', []) | ||
self.fn = kwargs.get('fn', None) | ||
self.value = None | ||
self.name = None | ||
|
||
def check(self): | ||
if self.default is not None and not self.required: | ||
self.value = self.default | ||
|
||
if self.required and self.value is None: | ||
return '{} is required'.format(self.name) | ||
|
||
if self.choices is not None and self.value not in self.choices: | ||
return '{} should be in [{}]'.format(self.name, ','.join(self.choices)) | ||
|
||
if self.fn is not None and not self.fn(self.value): | ||
return '{} should be satisfied function {}'.format(self.name, self.fn) | ||
|
||
|
||
class Str(DataType): | ||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
self.max_length = kwargs.get('max_length', None) | ||
self.min_length = kwargs.get('max_length', None) | ||
self.length = kwargs.get('length', None) | ||
self.regex = kwargs.get('regex', None) | ||
|
||
def check(self): | ||
super().check() | ||
|
||
if not isinstance(self.value, str): | ||
return 'type of {} should string'.format(self.name) | ||
|
||
if self.min_length is not None and len(self.value) < self.min_length: | ||
return 'length of {} should be larger than {}'.format(self.name, self.min_length) | ||
|
||
if self.max_length is not None and len(self.value) > self.max_length: | ||
return 'length of {} should be less than {}'.format(self.name, self.max_length) | ||
|
||
if self.length is not None and len(self.value) != self.length: | ||
return 'length of {} should be equal to {}'.format(self.name, self.max_length) | ||
|
||
if self.regex is not None and re.compile(self.regex).match(self.value): | ||
return '{} should match with regex "{}"'.format(self.name, self.regex) | ||
|
||
|
||
class Int(DataType): | ||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
self.max = kwargs.get('max', None) | ||
self.min = kwargs.get('min', None) | ||
|
||
def check(self): | ||
super().check() | ||
|
||
if not isinstance(self.value, int): | ||
return 'type of {} should be integer'.format(self.name) | ||
|
||
if self.max is not None and self.value > self.max: | ||
return '{} should be less than {}'.format(self.name, self.max) | ||
|
||
if self.min is not None and self.value < self.min: | ||
return '{} should be larger than {}'.format(self.name, self.min) | ||
|
||
|
||
class Bool(DataType): | ||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
|
||
def check(self): | ||
super().check() | ||
if not isinstance(self.value, bool): | ||
return 'type of {} should be boolean'.format(self.name) | ||
|
||
|
||
class Decimal(DataType): | ||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
self.left = kwargs.get('left', 8) | ||
self.right = kwargs.get('right', 2) | ||
|
||
def check(self): | ||
super().check() | ||
if not isinstance(self.value, float): | ||
return 'type of {} should be decimal'.format(self.name) | ||
|
||
point_left, point_right = str(self.value).split('.') | ||
|
||
if len(point_left) > self.left: | ||
return 'length of the right of the decimal point should be less than {}'.format(self.left) | ||
if len(point_right) == self.right: | ||
return 'length of the left of the decimal point should be equal to {}'.format(self.right) | ||
|
||
|
||
class DateTime(DataType): | ||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
self.max_datetime = kwargs.get('max_datetime', None) | ||
self.min_datetime = kwargs.get('min_datetime', None) | ||
|
||
self.max_datetime = datetime.strptime(self.max_datetime, "%Y-%m-%d %H:%M:%S") | ||
self.min_datetime = datetime.strptime(self.min_datetime, "%Y-%m-%d %H:%M:%S") | ||
|
||
def check(self): | ||
super().check() | ||
|
||
try: | ||
self.value = datetime.strptime(self.value, "%Y-%m-%d %H:%M:%S") | ||
except ValueError: | ||
return '{} should be right datetime'.format(self.name) | ||
|
||
if self.value > self.max_datetime: | ||
return '{} should be before {}'.format(self.name, self.max_datetime) | ||
if self.value < self.min_datetime: | ||
return '{} should be after {}'.format(self.name, self.min_datetime) | ||
|
||
|
||
class Date(DataType): | ||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
self.max_date = kwargs.get('max_date', None) | ||
self.min_date = kwargs.get('min_date', None) | ||
|
||
self.max_date = datetime.strptime(self.max_date, "%Y-%m-%d") | ||
self.min_date = datetime.strptime(self.min_date, "%Y-%m-%d") | ||
|
||
def check(self): | ||
super().check() | ||
|
||
try: | ||
self.value = datetime.strptime(self.value, "%Y-%m-%d") | ||
except ValueError: | ||
return '{} should be right date'.format(self.name) | ||
|
||
if self.value > self.max_date: | ||
return '{} should be before {}'.format(self.name, self.max_date) | ||
if self.value < self.min_date: | ||
return '{} should be after {}'.format(self.name, self.min_date) | ||
|
||
|
||
class Time(DataType): | ||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
self.max_time = kwargs.get('max_time', None) | ||
self.min_time = kwargs.get('min_time', None) | ||
|
||
self.max_time = datetime.strptime(self.max_time, "%H:%M:%S") | ||
self.min_time = datetime.strptime(self.min_time, "%H:%M:%S") | ||
|
||
def check(self): | ||
super().check() | ||
|
||
try: | ||
self.value = datetime.strptime(self.value, "%H:%M:%S") | ||
except ValueError: | ||
return '{} should be right time'.format(self.name) | ||
if self.value > self.max_time: | ||
return '{} should be before {}'.format(self.name, self.max_time) | ||
if self.value < self.min_time: | ||
return '{} should be after {}'.format(self.name, self.min_time) | ||
|
||
|
||
class CheckException(Exception): | ||
"""check exception""" | ||
|
||
|
||
class SchemaMeta(type): | ||
def __new__(mcs, name, bases, attrs): | ||
checkers = {} | ||
for k, v in attrs.items: | ||
if isinstance(v, DataType): | ||
checkers[k] = v | ||
|
||
for k in checkers: | ||
attrs.pop(k) | ||
|
||
attrs['checkers'] = checkers | ||
return super().__new__(mcs, name, bases, attrs) | ||
|
||
|
||
class Schema(metaclass=SchemaMeta): | ||
def __init__(self): | ||
self._data = {} | ||
self._validated_data = {} | ||
self._errors = {} | ||
self._checked = False | ||
|
||
def validate(self, data): | ||
self._data = data | ||
|
||
for k, v in self._data.items(): | ||
if k in self.checkers: | ||
setattr(self.checkers[k], 'value', v) | ||
|
||
for k, checker in self.checkers: | ||
result = checker.check() | ||
if result is None: | ||
setattr(self._validated_data, k, self.checkers[k].value) | ||
else: | ||
setattr(self._errors, k, result) | ||
|
||
@property | ||
def errors(self): | ||
if not self._checked: | ||
raise CheckException('data should be validate before visit errors') | ||
return self._errors | ||
|
||
@property | ||
def validated_data(self): | ||
if not self._checked: | ||
raise CheckException('data should be validate before visit validated_data') | ||
return self._validated_data | ||
|
||
@property | ||
def data(self): | ||
return self._data |