first commit

This commit is contained in:
Ayxan
2022-05-23 00:16:32 +04:00
commit d660f2a4ca
24786 changed files with 4428337 additions and 0 deletions

View File

@@ -0,0 +1,35 @@
from .between import between
from .btc_address import btc_address
from .card import (
amex,
card_number,
diners,
discover,
jcb,
mastercard,
unionpay,
visa
)
from .domain import domain
from .email import email
from .extremes import Max, Min
from .hashes import md5, sha1, sha224, sha256, sha512
from .i18n import fi_business_id, fi_ssn
from .iban import iban
from .ip_address import ipv4, ipv4_cidr, ipv6, ipv6_cidr
from .length import length
from .mac_address import mac_address
from .slug import slug
from .truthy import truthy
from .url import url
from .utils import ValidationFailure, validator
from .uuid import uuid
__all__ = ('between', 'domain', 'email', 'Max', 'Min', 'md5', 'sha1', 'sha224',
'sha256', 'sha512', 'fi_business_id', 'fi_ssn', 'iban', 'ipv4',
'ipv4_cidr', 'ipv6', 'ipv6_cidr', 'length', 'mac_address', 'slug',
'truthy', 'url', 'ValidationFailure', 'validator', 'uuid',
'card_number', 'visa', 'mastercard', 'amex', 'unionpay', 'diners',
'jcb', 'discover', 'btc_address')
__version__ = '0.19.0'

View File

@@ -0,0 +1,61 @@
from .extremes import Max, Min
from .utils import validator
@validator
def between(value, min=None, max=None):
"""
Validate that a number is between minimum and/or maximum value.
This will work with any comparable type, such as floats, decimals and dates
not just integers.
This validator is originally based on `WTForms NumberRange validator`_.
.. _WTForms NumberRange validator:
https://github.com/wtforms/wtforms/blob/master/wtforms/validators.py
Examples::
>>> from datetime import datetime
>>> between(5, min=2)
True
>>> between(13.2, min=13, max=14)
True
>>> between(500, max=400)
ValidationFailure(func=between, args=...)
>>> between(
... datetime(2000, 11, 11),
... min=datetime(1999, 11, 11)
... )
True
:param min:
The minimum required value of the number. If not provided, minimum
value will not be checked.
:param max:
The maximum value of the number. If not provided, maximum value
will not be checked.
.. versionadded:: 0.2
"""
if min is None and max is None:
raise AssertionError(
'At least one of `min` or `max` must be specified.'
)
if min is None:
min = Min
if max is None:
max = Max
try:
min_gt_max = min > max
except TypeError:
min_gt_max = max < min
if min_gt_max:
raise AssertionError('`min` cannot be more than `max`.')
return min <= value and max >= value

View File

@@ -0,0 +1,55 @@
import re
from hashlib import sha256
from .utils import validator
segwit_pattern = re.compile(
r'^(bc|tc)[0-3][02-9ac-hj-np-z]{14,74}$')
def validate_segwit_address(addr):
return segwit_pattern.match(addr)
def decode_base58(addr):
alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
return sum([
(58 ** e) * alphabet.index(i)
for e, i in enumerate(addr[::-1])
])
def validate_old_btc_address(addr):
"Validate P2PKH and P2SH type address"
if not len(addr) in range(25, 35):
return False
decoded_bytes = decode_base58(addr).to_bytes(25, "big")
header = decoded_bytes[:-4]
checksum = decoded_bytes[-4:]
return checksum == sha256(sha256(header).digest()).digest()[:4]
@validator
def btc_address(value):
"""
Return whether or not given value is a valid bitcoin address.
If the value is valid bitcoin address this function returns ``True``,
otherwise :class:`~validators.utils.ValidationFailure`.
Full validation is implemented for P2PKH and P2SH addresses.
For segwit addresses a regexp is used to provide a reasonable estimate
on whether the address is valid.
Examples::
>>> btc_address('3Cwgr2g7vsi1bXDUkpEnVoRLA9w4FZfC69')
True
:param value: Bitcoin address string to validate
"""
if not value or not isinstance(value, str):
return False
if value[:2] in ("bc", "tb"):
return validate_segwit_address(value)
return validate_old_btc_address(value)

View File

@@ -0,0 +1,183 @@
import re
from .utils import validator
@validator
def card_number(value):
"""
Return whether or not given value is a valid card number.
This validator is based on Luhn algorithm.
.. luhn:
https://github.com/mmcloughlin/luhn
Examples::
>>> card_number('4242424242424242')
True
>>> card_number('4242424242424241')
ValidationFailure(func=card_number, args={'value': '4242424242424241'})
.. versionadded:: 0.15.0
:param value: card number string to validate
"""
try:
digits = list(map(int, value))
odd_sum = sum(digits[-1::-2])
even_sum = sum([sum(divmod(2 * d, 10)) for d in digits[-2::-2]])
return (odd_sum + even_sum) % 10 == 0
except ValueError:
return False
@validator
def visa(value):
"""
Return whether or not given value is a valid Visa card number.
Examples::
>>> visa('4242424242424242')
True
>>> visa('2223003122003222')
ValidationFailure(func=visa, args={'value': '2223003122003222'})
.. versionadded:: 0.15.0
:param value: Visa card number string to validate
"""
pattern = re.compile(r'^4')
return card_number(value) and len(value) == 16 and pattern.match(value)
@validator
def mastercard(value):
"""
Return whether or not given value is a valid Mastercard card number.
Examples::
>>> mastercard('5555555555554444')
True
>>> mastercard('4242424242424242')
ValidationFailure(func=mastercard, args={'value': '4242424242424242'})
.. versionadded:: 0.15.0
:param value: Mastercard card number string to validate
"""
pattern = re.compile(r'^(51|52|53|54|55|22|23|24|25|26|27)')
return card_number(value) and len(value) == 16 and pattern.match(value)
@validator
def amex(value):
"""
Return whether or not given value is a valid American Express card number.
Examples::
>>> amex('378282246310005')
True
>>> amex('4242424242424242')
ValidationFailure(func=amex, args={'value': '4242424242424242'})
.. versionadded:: 0.15.0
:param value: American Express card number string to validate
"""
pattern = re.compile(r'^(34|37)')
return card_number(value) and len(value) == 15 and pattern.match(value)
@validator
def unionpay(value):
"""
Return whether or not given value is a valid UnionPay card number.
Examples::
>>> unionpay('6200000000000005')
True
>>> unionpay('4242424242424242')
ValidationFailure(func=unionpay, args={'value': '4242424242424242'})
.. versionadded:: 0.15.0
:param value: UnionPay card number string to validate
"""
pattern = re.compile(r'^62')
return card_number(value) and len(value) == 16 and pattern.match(value)
@validator
def diners(value):
"""
Return whether or not given value is a valid Diners Club card number.
Examples::
>>> diners('3056930009020004')
True
>>> diners('4242424242424242')
ValidationFailure(func=diners, args={'value': '4242424242424242'})
.. versionadded:: 0.15.0
:param value: Diners Club card number string to validate
"""
pattern = re.compile(r'^(30|36|38|39)')
return (
card_number(value) and len(value) in [14, 16] and pattern.match(value)
)
@validator
def jcb(value):
"""
Return whether or not given value is a valid JCB card number.
Examples::
>>> jcb('3566002020360505')
True
>>> jcb('4242424242424242')
ValidationFailure(func=jcb, args={'value': '4242424242424242'})
.. versionadded:: 0.15.0
:param value: JCB card number string to validate
"""
pattern = re.compile(r'^35')
return card_number(value) and len(value) == 16 and pattern.match(value)
@validator
def discover(value):
"""
Return whether or not given value is a valid Discover card number.
Examples::
>>> discover('6011111111111117')
True
>>> discover('4242424242424242')
ValidationFailure(func=discover, args={'value': '4242424242424242'})
.. versionadded:: 0.15.0
:param value: Discover card number string to validate
"""
pattern = re.compile(r'^(60|64|65)')
return card_number(value) and len(value) == 16 and pattern.match(value)

View File

@@ -0,0 +1,54 @@
import re
from .utils import validator
pattern = re.compile(
r'^(?:[a-zA-Z0-9]' # First character of the domain
r'(?:[a-zA-Z0-9-_]{0,61}[A-Za-z0-9])?\.)' # Sub domain + hostname
r'+[A-Za-z0-9][A-Za-z0-9-_]{0,61}' # First 61 characters of the gTLD
r'[A-Za-z]$' # Last character of the gTLD
)
def to_unicode(obj, charset='utf-8', errors='strict'):
if obj is None:
return None
if not isinstance(obj, bytes):
return str(obj)
return obj.decode(charset, errors)
@validator
def domain(value):
"""
Return whether or not given value is a valid domain.
If the value is valid domain name this function returns ``True``, otherwise
:class:`~validators.utils.ValidationFailure`.
Examples::
>>> domain('example.com')
True
>>> domain('example.com/')
ValidationFailure(func=domain, ...)
Supports IDN domains as well::
>>> domain('xn----gtbspbbmkef.xn--p1ai')
True
.. versionadded:: 0.9
.. versionchanged:: 0.10
Added support for internationalized domain name (IDN) validation.
:param value: domain string to validate
"""
try:
return pattern.match(to_unicode(value).encode('idna').decode('ascii'))
except (UnicodeError, AttributeError):
return False

View File

@@ -0,0 +1,75 @@
import re
from .utils import validator
user_regex = re.compile(
# dot-atom
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+"
r"(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*$"
# quoted-string
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|'
r"""\\[\001-\011\013\014\016-\177])*"$)""",
re.IGNORECASE
)
domain_regex = re.compile(
# domain
r'(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+'
r'(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?$)'
# literal form, ipv4 address (SMTP 4.1.3)
r'|^\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)'
r'(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]$',
re.IGNORECASE)
domain_whitelist = ['localhost']
@validator
def email(value, whitelist=None):
"""
Validate an email address.
This validator is based on `Django's email validator`_. Returns
``True`` on success and :class:`~validators.utils.ValidationFailure`
when validation fails.
Examples::
>>> email('someone@example.com')
True
>>> email('bogus@@')
ValidationFailure(func=email, ...)
.. _Django's email validator:
https://github.com/django/django/blob/master/django/core/validators.py
.. versionadded:: 0.1
:param value: value to validate
:param whitelist: domain names to whitelist
:copyright: (c) Django Software Foundation and individual contributors.
:license: BSD
"""
if whitelist is None:
whitelist = domain_whitelist
if not value or '@' not in value:
return False
user_part, domain_part = value.rsplit('@', 1)
if not user_regex.match(user_part):
return False
if len(user_part.encode("utf-8")) > 64:
return False
if domain_part not in whitelist and not domain_regex.match(domain_part):
# Try for possible IDN domain-part
try:
domain_part = domain_part.encode('idna').decode('ascii')
return domain_regex.match(domain_part)
except UnicodeError:
return False
return True

View File

@@ -0,0 +1,61 @@
from functools import total_ordering
@total_ordering
class Min(object):
"""
An object that is less than any other object (except itself).
Inspired by https://pypi.python.org/pypi/Extremes
Examples::
>>> import sys
>>> Min < -sys.maxint
True
>>> Min < None
True
>>> Min < ''
True
.. versionadded:: 0.2
"""
def __lt__(self, other):
if other is Min:
return False
return True
@total_ordering
class Max(object):
"""
An object that is greater than any other object (except itself).
Inspired by https://pypi.python.org/pypi/Extremes
Examples::
>>> import sys
>>> Max > Min
True
>>> Max > sys.maxint
True
>>> Max > 99999999999999999
True
.. versionadded:: 0.2
"""
def __gt__(self, other):
if other is Max:
return False
return True
Min = Min()
Max = Max()

View File

@@ -0,0 +1,121 @@
import re
from .utils import validator
md5_regex = re.compile(
r"^[0-9a-f]{32}$",
re.IGNORECASE
)
sha1_regex = re.compile(
r"^[0-9a-f]{40}$",
re.IGNORECASE
)
sha224_regex = re.compile(
r"^[0-9a-f]{56}$",
re.IGNORECASE
)
sha256_regex = re.compile(
r"^[0-9a-f]{64}$",
re.IGNORECASE
)
sha512_regex = re.compile(
r"^[0-9a-f]{128}$",
re.IGNORECASE
)
@validator
def md5(value):
"""
Return whether or not given value is a valid MD5 hash.
Examples::
>>> md5('d41d8cd98f00b204e9800998ecf8427e')
True
>>> md5('900zz11')
ValidationFailure(func=md5, args={'value': '900zz11'})
:param value: MD5 string to validate
"""
return md5_regex.match(value)
@validator
def sha1(value):
"""
Return whether or not given value is a valid SHA1 hash.
Examples::
>>> sha1('da39a3ee5e6b4b0d3255bfef95601890afd80709')
True
>>> sha1('900zz11')
ValidationFailure(func=sha1, args={'value': '900zz11'})
:param value: SHA1 string to validate
"""
return sha1_regex.match(value)
@validator
def sha224(value):
"""
Return whether or not given value is a valid SHA224 hash.
Examples::
>>> sha224('d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f')
True
>>> sha224('900zz11')
ValidationFailure(func=sha224, args={'value': '900zz11'})
:param value: SHA224 string to validate
"""
return sha224_regex.match(value)
@validator
def sha256(value):
"""
Return whether or not given value is a valid SHA256 hash.
Examples::
>>> sha256(
... 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b'
... '855'
... )
True
>>> sha256('900zz11')
ValidationFailure(func=sha256, args={'value': '900zz11'})
:param value: SHA256 string to validate
"""
return sha256_regex.match(value)
@validator
def sha512(value):
"""
Return whether or not given value is a valid SHA512 hash.
Examples::
>>> sha512(
... 'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce'
... '9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af9'
... '27da3e'
... )
True
>>> sha512('900zz11')
ValidationFailure(func=sha512, args={'value': '900zz11'})
:param value: SHA512 string to validate
"""
return sha512_regex.match(value)

View File

@@ -0,0 +1,4 @@
# TODO: remove, let the user import it if they really want it
from .fi import fi_business_id, fi_ssn # noqa
__all__ = ('fi_business_id', 'fi_ssn')

View File

@@ -0,0 +1,200 @@
# -*- coding: utf-8 -*-
from validators.utils import validator
__all__ = ('es_cif', 'es_nif', 'es_nie', 'es_doi',)
def nif_nie_validation(doi, number_by_letter, special_cases):
"""
Validate if the doi is a NIF or a NIE.
:param doi: DOI to validate.
:return: boolean if it's valid.
"""
doi = doi.upper()
if doi in special_cases:
return False
table = 'TRWAGMYFPDXBNJZSQVHLCKE'
if len(doi) != 9:
return False
control = doi[8]
# If it is not a DNI, convert the first letter to the corresponding
# digit
numbers = number_by_letter.get(doi[0], doi[0]) + doi[1:8]
return numbers.isdigit() and control == table[int(numbers) % 23]
@validator
def es_cif(doi):
"""
Validate a Spanish CIF.
Each company in Spain prior to 2008 had a distinct CIF and has been
discontinued. For more information see `wikipedia.org/cif`_.
The new replacement is to use NIF for absolutely everything. The issue is
that there are "types" of NIFs now: company, person[citizen vs recident]
all distinguished by the first character of the DOI. For this reason we
will continue to call CIF NIFs that are used for companies.
This validator is based on `generadordni.es`_.
.. _generadordni.es:
https://generadordni.es/
.. _wikipedia.org/cif:
https://es.wikipedia.org/wiki/C%C3%B3digo_de_identificaci%C3%B3n_fiscal
Examples::
>>> es_cif('B25162520')
True
>>> es_cif('B25162529')
ValidationFailure(func=es_cif, args=...)
.. versionadded:: 0.13.0
:param doi: DOI to validate
"""
doi = doi.upper()
if len(doi) != 9:
return False
table = 'JABCDEFGHI'
first_chr = doi[0]
doi_body = doi[1:8]
control = doi[8]
if not doi_body.isdigit():
return False
odd_result = 0
even_result = 0
for index, char in enumerate(doi_body):
if index % 2 == 0:
# Multiply each each odd position doi digit by 2 and sum it all
# together
odd_result += sum(map(int, str(int(char) * 2)))
else:
even_result += int(char)
res = (10 - (even_result + odd_result) % 10) % 10
if first_chr in 'ABEH': # Number type
return str(res) == control
elif first_chr in 'PSQW': # Letter type
return table[res] == control
elif first_chr not in 'CDFGJNRUV':
return False
return control == str(res) or control == table[res]
@validator
def es_nif(doi):
"""
Validate a Spanish NIF.
Each entity, be it person or company in Spain has a distinct NIF. Since
we've designated CIF to be a company NIF, this NIF is only for person.
For more information see `wikipedia.org/nif`_.
This validator is based on `generadordni.es`_.
.. _generadordni.es:
https://generadordni.es/
.. _wikipedia.org/nif:
https://es.wikipedia.org/wiki/N%C3%BAmero_de_identificaci%C3%B3n_fiscal
Examples::
>>> es_nif('26643189N')
True
>>> es_nif('26643189X')
ValidationFailure(func=es_nif, args=...)
.. versionadded:: 0.13.0
:param doi: DOI to validate
"""
number_by_letter = {'L': '0', 'M': '0', 'K': '0'}
special_cases = ['X0000000T', '00000000T', '00000001R']
return nif_nie_validation(doi, number_by_letter, special_cases)
@validator
def es_nie(doi):
"""
Validate a Spanish NIE.
The NIE is a tax identification number in Spain, known in Spanish as the
NIE, or more formally the Número de identidad de extranjero. For more
information see `wikipedia.org/nie`_.
This validator is based on `generadordni.es`_.
.. _generadordni.es:
https://generadordni.es/
.. _wikipedia.org/nie:
https://es.wikipedia.org/wiki/N%C3%BAmero_de_identidad_de_extranjero
Examples::
>>> es_nie('X0095892M')
True
>>> es_nie('X0095892X')
ValidationFailure(func=es_nie, args=...)
.. versionadded:: 0.13.0
:param doi: DOI to validate
"""
number_by_letter = {'X': '0', 'Y': '1', 'Z': '2'}
special_cases = ['X0000000T']
# NIE must must start with X Y or Z
if not doi or doi[0] not in number_by_letter.keys():
return False
return nif_nie_validation(doi, number_by_letter, special_cases)
@validator
def es_doi(doi):
"""
Validate a Spanish DOI.
A DOI in spain is all NIF / CIF / NIE / DNI -- a digital ID. For more
information see `wikipedia.org/doi`_.
This validator is based on `generadordni.es`_.
.. _generadordni.es:
https://generadordni.es/
.. _wikipedia.org/doi:
https://es.wikipedia.org/wiki/Identificador_de_objeto_digital
Examples::
>>> es_doi('X0095892M')
True
>>> es_doi('X0095892X')
ValidationFailure(func=es_doi, args=...)
.. versionadded:: 0.13.0
:param doi: DOI to validate
"""
return es_nie(doi) or es_nif(doi) or es_cif(doi)

View File

@@ -0,0 +1,94 @@
import re
from validators.utils import validator
business_id_pattern = re.compile(r'^[0-9]{7}-[0-9]$')
ssn_checkmarks = '0123456789ABCDEFHJKLMNPRSTUVWXY'
ssn_pattern = re.compile(
r"""^
(?P<date>(0[1-9]|[1-2]\d|3[01])
(0[1-9]|1[012])
(\d{{2}}))
[A+-]
(?P<serial>(\d{{3}}))
(?P<checksum>[{checkmarks}])$""".format(checkmarks=ssn_checkmarks),
re.VERBOSE
)
@validator
def fi_business_id(business_id):
"""
Validate a Finnish Business ID.
Each company in Finland has a distinct business id. For more
information see `Finnish Trade Register`_
.. _Finnish Trade Register:
http://en.wikipedia.org/wiki/Finnish_Trade_Register
Examples::
>>> fi_business_id('0112038-9') # Fast Monkeys Ltd
True
>>> fi_business_id('1234567-8') # Bogus ID
ValidationFailure(func=fi_business_id, ...)
.. versionadded:: 0.4
.. versionchanged:: 0.5
Method renamed from ``finnish_business_id`` to ``fi_business_id``
:param business_id: business_id to validate
"""
if not business_id or not re.match(business_id_pattern, business_id):
return False
factors = [7, 9, 10, 5, 8, 4, 2]
numbers = map(int, business_id[:7])
checksum = int(business_id[8])
sum_ = sum(f * n for f, n in zip(factors, numbers))
modulo = sum_ % 11
return (11 - modulo == checksum) or (modulo == 0 and checksum == 0)
@validator
def fi_ssn(ssn, allow_temporal_ssn=True):
"""
Validate a Finnish Social Security Number.
This validator is based on `django-localflavor-fi`_.
.. _django-localflavor-fi:
https://github.com/django/django-localflavor-fi/
Examples::
>>> fi_ssn('010101-0101')
True
>>> fi_ssn('101010-0102')
ValidationFailure(func=fi_ssn, args=...)
.. versionadded:: 0.5
:param ssn: Social Security Number to validate
:param allow_temporal_ssn:
Whether to accept temporal SSN numbers. Temporal SSN numbers are the
ones where the serial is in the range [900-999]. By default temporal
SSN numbers are valid.
"""
if not ssn:
return False
result = re.match(ssn_pattern, ssn)
if not result:
return False
gd = result.groupdict()
checksum = int(gd['date'] + gd['serial'])
return (
int(gd['serial']) >= 2 and
(allow_temporal_ssn or int(gd['serial']) <= 899) and
ssn_checkmarks[checksum % len(ssn_checkmarks)] ==
gd['checksum']
)

View File

@@ -0,0 +1,52 @@
import re
from .utils import validator
regex = (
r'^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$'
)
pattern = re.compile(regex)
def char_value(char):
"""A=10, B=11, ..., Z=35
"""
if char.isdigit():
return int(char)
else:
return 10 + ord(char) - ord('A')
def modcheck(value):
"""Check if the value string passes the mod97-test.
"""
# move country code and check numbers to end
rearranged = value[4:] + value[:4]
# convert letters to numbers
converted = [char_value(char) for char in rearranged]
# interpret as integer
integerized = int(''.join([str(i) for i in converted]))
return (integerized % 97 == 1)
@validator
def iban(value):
"""
Return whether or not given value is a valid IBAN code.
If the value is a valid IBAN this function returns ``True``, otherwise
:class:`~validators.utils.ValidationFailure`.
Examples::
>>> iban('DE29100500001061045672')
True
>>> iban('123456')
ValidationFailure(func=iban, ...)
.. versionadded:: 0.8
:param value: IBAN string to validate
"""
return pattern.match(value) and modcheck(value)

View File

@@ -0,0 +1,152 @@
from .utils import validator
@validator
def ipv4(value):
"""
Return whether a given value is a valid IP version 4 address.
This validator is based on `WTForms IPAddress validator`_
.. _WTForms IPAddress validator:
https://github.com/wtforms/wtforms/blob/master/wtforms/validators.py
Examples::
>>> ipv4('123.0.0.7')
True
>>> ipv4('900.80.70.11')
ValidationFailure(func=ipv4, args={'value': '900.80.70.11'})
.. versionadded:: 0.2
:param value: IP address string to validate
"""
groups = value.split('.')
if len(groups) != 4 or any(not x.isdigit() for x in groups):
return False
return all(0 <= int(part) < 256 for part in groups)
@validator
def ipv4_cidr(value):
"""
Return whether a given value is a valid CIDR-notated IP version 4
address range.
This validator is based on RFC4632 3.1.
Examples::
>>> ipv4_cidr('1.1.1.1/8')
True
>>> ipv4_cidr('1.1.1.1')
ValidationFailure(func=ipv4_cidr, args={'value': '1.1.1.1'})
"""
try:
prefix, suffix = value.split('/', 2)
except ValueError:
return False
if not ipv4(prefix) or not suffix.isdigit():
return False
return 0 <= int(suffix) <= 32
@validator
def ipv6(value):
"""
Return whether a given value is a valid IP version 6 address
(including IPv4-mapped IPv6 addresses).
This validator is based on `WTForms IPAddress validator`_.
.. _WTForms IPAddress validator:
https://github.com/wtforms/wtforms/blob/master/wtforms/validators.py
Examples::
>>> ipv6('abcd:ef::42:1')
True
>>> ipv6('::ffff:192.0.2.128')
True
>>> ipv6('::192.0.2.128')
True
>>> ipv6('abc.0.0.1')
ValidationFailure(func=ipv6, args={'value': 'abc.0.0.1'})
.. versionadded:: 0.2
:param value: IP address string to validate
"""
ipv6_groups = value.split(':')
if len(ipv6_groups) == 1:
return False
ipv4_groups = ipv6_groups[-1].split('.')
if len(ipv4_groups) > 1:
if not ipv4(ipv6_groups[-1]):
return False
ipv6_groups = ipv6_groups[:-1]
else:
ipv4_groups = []
count_blank = 0
for part in ipv6_groups:
if not part:
count_blank += 1
continue
try:
num = int(part, 16)
except ValueError:
return False
else:
if not 0 <= num <= 65536 or len(part) > 4:
return False
max_groups = 6 if ipv4_groups else 8
part_count = len(ipv6_groups) - count_blank
if count_blank == 0 and part_count == max_groups:
# no :: -> must have size of max_groups
return True
elif count_blank == 1 and ipv6_groups[-1] and ipv6_groups[0] and part_count < max_groups:
# one :: inside the address or prefix or suffix : -> filter least two cases
return True
elif count_blank == 2 and part_count < max_groups and (
((ipv6_groups[0] and not ipv6_groups[-1]) or (not ipv6_groups[0] and ipv6_groups[-1])) or ipv4_groups):
# leading or trailing :: or : at end and begin -> filter last case
# Check if it has ipv4 groups because they get removed from the ipv6_groups
return True
elif count_blank == 3 and part_count == 0:
# :: is the address -> filter everything else
return True
return False
@validator
def ipv6_cidr(value):
"""
Returns whether a given value is a valid CIDR-notated IP version 6
address range.
This validator is based on RFC4632 3.1.
Examples::
>>> ipv6_cidr('::1/128')
True
>>> ipv6_cidr('::1')
ValidationFailure(func=ipv6_cidr, args={'value': '::1'})
"""
try:
prefix, suffix = value.split('/', 2)
except ValueError:
return False
if not ipv6(prefix) or not suffix.isdigit():
return False
return 0 <= int(suffix) <= 128

View File

@@ -0,0 +1,37 @@
from .between import between
from .utils import validator
@validator
def length(value, min=None, max=None):
"""
Return whether or not the length of given string is within a specified
range.
Examples::
>>> length('something', min=2)
True
>>> length('something', min=9, max=9)
True
>>> length('something', max=5)
ValidationFailure(func=length, ...)
:param value:
The string to validate.
:param min:
The minimum required length of the string. If not provided, minimum
length will not be checked.
:param max:
The maximum length of the string. If not provided, maximum length
will not be checked.
.. versionadded:: 0.2
"""
if (min is not None and min < 0) or (max is not None and max < 0):
raise AssertionError(
'`min` and `max` need to be greater than zero.'
)
return between(len(value), min=min, max=max)

View File

@@ -0,0 +1,33 @@
import re
from .utils import validator
pattern = re.compile(r'^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$')
@validator
def mac_address(value):
"""
Return whether or not given value is a valid MAC address.
If the value is valid MAC address this function returns ``True``,
otherwise :class:`~validators.utils.ValidationFailure`.
This validator is based on `WTForms MacAddress validator`_.
.. _WTForms MacAddress validator:
https://github.com/wtforms/wtforms/blob/master/wtforms/validators.py
Examples::
>>> mac_address('01:23:45:67:ab:CD')
True
>>> mac_address('00:00:00:00:00')
ValidationFailure(func=mac_address, args={'value': '00:00:00:00:00'})
.. versionadded:: 0.2
:param value: Mac address string to validate
"""
return pattern.match(value)

View File

@@ -0,0 +1,28 @@
import re
from .utils import validator
slug_regex = re.compile(r'^[-a-zA-Z0-9_]+$')
@validator
def slug(value):
"""
Validate whether or not given value is valid slug.
Valid slug can contain only alphanumeric characters, hyphens and
underscores.
Examples::
>>> slug('my.slug')
ValidationFailure(func=slug, args={'value': 'my.slug'})
>>> slug('my-slug-2134')
True
.. versionadded:: 0.6
:param value: value to validate
"""
return slug_regex.match(value)

View File

@@ -0,0 +1,39 @@
from .utils import validator
@validator
def truthy(value):
"""
Validate that given value is not a falsey value.
This validator is based on `WTForms DataRequired validator`_.
.. _WTForms DataRequired validator:
https://github.com/wtforms/wtforms/blob/master/wtforms/validators.py
Examples::
>>> truthy(1)
True
>>> truthy('someone')
True
>>> truthy(0)
ValidationFailure(func=truthy, args={'value': 0})
>>> truthy(' ')
ValidationFailure(func=truthy, args={'value': ' '})
>>> truthy(False)
ValidationFailure(func=truthy, args={'value': False})
>>> truthy(None)
ValidationFailure(func=truthy, args={'value': None})
.. versionadded:: 0.2
"""
return (
value and
(not isinstance(value, str) or value.strip())
)

View File

@@ -0,0 +1,154 @@
import re
from .utils import validator
ip_middle_octet = r"(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5]))"
ip_last_octet = r"(?:\.(?:0|[1-9]\d?|1\d\d|2[0-4]\d|25[0-5]))"
regex = re.compile( # noqa: W605
r"^"
# protocol identifier
r"(?:(?:https?|ftp)://)"
# user:pass authentication
r"(?:[-a-z\u00a1-\uffff0-9._~%!$&'()*+,;=:]+"
r"(?::[-a-z0-9._~%!$&'()*+,;=:]*)?@)?"
r"(?:"
r"(?P<private_ip>"
# IP address exclusion
# private & local networks
r"(?:(?:10|127)" + ip_middle_octet + r"{2}" + ip_last_octet + r")|"
r"(?:(?:169\.254|192\.168)" + ip_middle_octet + ip_last_octet + r")|"
r"(?:172\.(?:1[6-9]|2\d|3[0-1])" + ip_middle_octet + ip_last_octet + r"))"
r"|"
# private & local hosts
r"(?P<private_host>"
r"(?:localhost))"
r"|"
# IP address dotted notation octets
# excludes loopback network 0.0.0.0
# excludes reserved space >= 224.0.0.0
# excludes network & broadcast addresses
# (first & last IP address of each class)
r"(?P<public_ip>"
r"(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])"
r"" + ip_middle_octet + r"{2}"
r"" + ip_last_octet + r")"
r"|"
# IPv6 RegEx from https://stackoverflow.com/a/17871737
r"\[("
# 1:2:3:4:5:6:7:8
r"([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|"
# 1:: 1:2:3:4:5:6:7::
r"([0-9a-fA-F]{1,4}:){1,7}:|"
# 1::8 1:2:3:4:5:6::8 1:2:3:4:5:6::8
r"([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|"
# 1::7:8 1:2:3:4:5::7:8 1:2:3:4:5::8
r"([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|"
# 1::6:7:8 1:2:3:4::6:7:8 1:2:3:4::8
r"([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|"
# 1::5:6:7:8 1:2:3::5:6:7:8 1:2:3::8
r"([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|"
# 1::4:5:6:7:8 1:2::4:5:6:7:8 1:2::8
r"([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|"
# 1::3:4:5:6:7:8 1::3:4:5:6:7:8 1::8
r"[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|"
# ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 ::
r":((:[0-9a-fA-F]{1,4}){1,7}|:)|"
# fe80::7:8%eth0 fe80::7:8%1
# (link-local IPv6 addresses with zone index)
r"fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|"
r"::(ffff(:0{1,4}){0,1}:){0,1}"
r"((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}"
# ::255.255.255.255 ::ffff:255.255.255.255 ::ffff:0:255.255.255.255
# (IPv4-mapped IPv6 addresses and IPv4-translated addresses)
r"(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|"
r"([0-9a-fA-F]{1,4}:){1,4}:"
r"((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}"
# 2001:db8:3:4::192.0.2.33 64:ff9b::192.0.2.33
# (IPv4-Embedded IPv6 Address)
r"(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])"
r")\]|"
# host name
r"(?:(?:(?:xn--)|[a-z\u00a1-\uffff\U00010000-\U0010ffff0-9]-?)*"
r"[a-z\u00a1-\uffff\U00010000-\U0010ffff0-9]+)"
# domain name
r"(?:\.(?:(?:xn--)|[a-z\u00a1-\uffff\U00010000-\U0010ffff0-9]-?)*"
r"[a-z\u00a1-\uffff\U00010000-\U0010ffff0-9]+)*"
# TLD identifier
r"(?:\.(?:(?:xn--[a-z\u00a1-\uffff\U00010000-\U0010ffff0-9]{2,})|"
r"[a-z\u00a1-\uffff\U00010000-\U0010ffff]{2,}))"
r")"
# port number
r"(?::\d{2,5})?"
# resource path
r"(?:/[-a-z\u00a1-\uffff\U00010000-\U0010ffff0-9._~%!$&'()*+,;=:@/]*)?"
# query string
r"(?:\?\S*)?"
# fragment
r"(?:#\S*)?"
r"$",
re.UNICODE | re.IGNORECASE
)
pattern = re.compile(regex)
@validator
def url(value, public=False):
"""
Return whether or not given value is a valid URL.
If the value is valid URL this function returns ``True``, otherwise
:class:`~validators.utils.ValidationFailure`.
This validator is based on the wonderful `URL validator of dperini`_.
.. _URL validator of dperini:
https://gist.github.com/dperini/729294
Examples::
>>> url('http://foobar.dk')
True
>>> url('ftp://foobar.dk')
True
>>> url('http://10.0.0.1')
True
>>> url('http://foobar.d')
ValidationFailure(func=url, ...)
>>> url('http://10.0.0.1', public=True)
ValidationFailure(func=url, ...)
.. versionadded:: 0.2
.. versionchanged:: 0.10.2
Added support for various exotic URLs and fixed various false
positives.
.. versionchanged:: 0.10.3
Added ``public`` parameter.
.. versionchanged:: 0.11.0
Made the regular expression this function uses case insensitive.
.. versionchanged:: 0.11.3
Added support for URLs containing localhost
:param value: URL address string to validate
:param public: (default=False) Set True to only allow a public IP address
"""
result = pattern.match(value)
if not public:
return result
return result and not any(
(result.groupdict().get(key) for key in ('private_ip', 'private_host'))
)

View File

@@ -0,0 +1,85 @@
import inspect
import itertools
from collections import OrderedDict
from decorator import decorator
class ValidationFailure(Exception):
def __init__(self, func, args):
self.func = func
self.__dict__.update(args)
def __repr__(self):
return u'ValidationFailure(func={func}, args={args})'.format(
func=self.func.__name__,
args=dict(
[(k, v) for (k, v) in self.__dict__.items() if k != 'func']
)
)
def __str__(self):
return repr(self)
def __unicode__(self):
return repr(self)
def __bool__(self):
return False
def __nonzero__(self):
return False
def func_args_as_dict(func, args, kwargs):
"""
Return given function's positional and key value arguments as an ordered
dictionary.
"""
_getargspec = inspect.getfullargspec
arg_names = list(
OrderedDict.fromkeys(
itertools.chain(
_getargspec(func)[0],
kwargs.keys()
)
)
)
return OrderedDict(
list(zip(arg_names, args)) +
list(kwargs.items())
)
def validator(func, *args, **kwargs):
"""
A decorator that makes given function validator.
Whenever the given function is called and returns ``False`` value
this decorator returns :class:`ValidationFailure` object.
Example::
>>> @validator
... def even(value):
... return not (value % 2)
>>> even(4)
True
>>> even(5)
ValidationFailure(func=even, args={'value': 5})
:param func: function to decorate
:param args: positional function arguments
:param kwargs: key value function arguments
"""
def wrapper(func, *args, **kwargs):
value = func(*args, **kwargs)
if not value:
return ValidationFailure(
func, func_args_as_dict(func, args, kwargs)
)
return True
return decorator(wrapper, func)

View File

@@ -0,0 +1,41 @@
from __future__ import absolute_import
import re
from uuid import UUID
from .utils import validator
pattern = re.compile(r'^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$')
@validator
def uuid(value):
"""
Return whether or not given value is a valid UUID.
If the value is valid UUID this function returns ``True``, otherwise
:class:`~validators.utils.ValidationFailure`.
This validator is based on `WTForms UUID validator`_.
.. _WTForms UUID validator:
https://github.com/wtforms/wtforms/blob/master/wtforms/validators.py
Examples::
>>> uuid('2bc1c94f-0deb-43e9-92a1-4775189ec9f8')
True
>>> uuid('2bc1c94f 0deb-43e9-92a1-4775189ec9f8')
ValidationFailure(func=uuid, ...)
.. versionadded:: 0.2
:param value: UUID value to validate
"""
if isinstance(value, UUID):
return True
try:
return pattern.match(value)
except TypeError:
return False