diff options
Diffstat (limited to 'venv/lib/python3.9/site-packages/validators')
20 files changed, 1568 insertions, 0 deletions
diff --git a/venv/lib/python3.9/site-packages/validators/__init__.py b/venv/lib/python3.9/site-packages/validators/__init__.py new file mode 100644 index 00000000..f623e12f --- /dev/null +++ b/venv/lib/python3.9/site-packages/validators/__init__.py @@ -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.20.0' diff --git a/venv/lib/python3.9/site-packages/validators/between.py b/venv/lib/python3.9/site-packages/validators/between.py new file mode 100644 index 00000000..46f223c9 --- /dev/null +++ b/venv/lib/python3.9/site-packages/validators/between.py @@ -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 diff --git a/venv/lib/python3.9/site-packages/validators/btc_address.py b/venv/lib/python3.9/site-packages/validators/btc_address.py new file mode 100644 index 00000000..35ada853 --- /dev/null +++ b/venv/lib/python3.9/site-packages/validators/btc_address.py @@ -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) diff --git a/venv/lib/python3.9/site-packages/validators/card.py b/venv/lib/python3.9/site-packages/validators/card.py new file mode 100644 index 00000000..8b8cbeea --- /dev/null +++ b/venv/lib/python3.9/site-packages/validators/card.py @@ -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) diff --git a/venv/lib/python3.9/site-packages/validators/domain.py b/venv/lib/python3.9/site-packages/validators/domain.py new file mode 100644 index 00000000..d9bf44f0 --- /dev/null +++ b/venv/lib/python3.9/site-packages/validators/domain.py @@ -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 diff --git a/venv/lib/python3.9/site-packages/validators/email.py b/venv/lib/python3.9/site-packages/validators/email.py new file mode 100644 index 00000000..229c8e46 --- /dev/null +++ b/venv/lib/python3.9/site-packages/validators/email.py @@ -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 diff --git a/venv/lib/python3.9/site-packages/validators/extremes.py b/venv/lib/python3.9/site-packages/validators/extremes.py new file mode 100644 index 00000000..43d168a7 --- /dev/null +++ b/venv/lib/python3.9/site-packages/validators/extremes.py @@ -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() diff --git a/venv/lib/python3.9/site-packages/validators/hashes.py b/venv/lib/python3.9/site-packages/validators/hashes.py new file mode 100644 index 00000000..4db7f78e --- /dev/null +++ b/venv/lib/python3.9/site-packages/validators/hashes.py @@ -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) diff --git a/venv/lib/python3.9/site-packages/validators/i18n/__init__.py b/venv/lib/python3.9/site-packages/validators/i18n/__init__.py new file mode 100644 index 00000000..12775c6c --- /dev/null +++ b/venv/lib/python3.9/site-packages/validators/i18n/__init__.py @@ -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') diff --git a/venv/lib/python3.9/site-packages/validators/i18n/es.py b/venv/lib/python3.9/site-packages/validators/i18n/es.py new file mode 100644 index 00000000..ed2e2a63 --- /dev/null +++ b/venv/lib/python3.9/site-packages/validators/i18n/es.py @@ -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) diff --git a/venv/lib/python3.9/site-packages/validators/i18n/fi.py b/venv/lib/python3.9/site-packages/validators/i18n/fi.py new file mode 100644 index 00000000..2e5eb578 --- /dev/null +++ b/venv/lib/python3.9/site-packages/validators/i18n/fi.py @@ -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'] + ) diff --git a/venv/lib/python3.9/site-packages/validators/iban.py b/venv/lib/python3.9/site-packages/validators/iban.py new file mode 100644 index 00000000..7413d127 --- /dev/null +++ b/venv/lib/python3.9/site-packages/validators/iban.py @@ -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) diff --git a/venv/lib/python3.9/site-packages/validators/ip_address.py b/venv/lib/python3.9/site-packages/validators/ip_address.py new file mode 100644 index 00000000..e0c061db --- /dev/null +++ b/venv/lib/python3.9/site-packages/validators/ip_address.py @@ -0,0 +1,156 @@ +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) + or any(len(x) > 3 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 diff --git a/venv/lib/python3.9/site-packages/validators/length.py b/venv/lib/python3.9/site-packages/validators/length.py new file mode 100644 index 00000000..d0f91fd3 --- /dev/null +++ b/venv/lib/python3.9/site-packages/validators/length.py @@ -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) diff --git a/venv/lib/python3.9/site-packages/validators/mac_address.py b/venv/lib/python3.9/site-packages/validators/mac_address.py new file mode 100644 index 00000000..bdb19947 --- /dev/null +++ b/venv/lib/python3.9/site-packages/validators/mac_address.py @@ -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) diff --git a/venv/lib/python3.9/site-packages/validators/slug.py b/venv/lib/python3.9/site-packages/validators/slug.py new file mode 100644 index 00000000..83bfd4b1 --- /dev/null +++ b/venv/lib/python3.9/site-packages/validators/slug.py @@ -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) diff --git a/venv/lib/python3.9/site-packages/validators/truthy.py b/venv/lib/python3.9/site-packages/validators/truthy.py new file mode 100644 index 00000000..517149aa --- /dev/null +++ b/venv/lib/python3.9/site-packages/validators/truthy.py @@ -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()) + ) diff --git a/venv/lib/python3.9/site-packages/validators/url.py b/venv/lib/python3.9/site-packages/validators/url.py new file mode 100644 index 00000000..37d946cb --- /dev/null +++ b/venv/lib/python3.9/site-packages/validators/url.py @@ -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--[-]{0,2})|[a-z\u00a1-\uffff\U00010000-\U0010ffff0-9]-?)*" + r"[a-z\u00a1-\uffff\U00010000-\U0010ffff0-9]+)" + # domain name + r"(?:\.(?:(?:xn--[-]{0,2})|[a-z\u00a1-\uffff\U00010000-\U0010ffff0-9]-?)*" + r"[a-z\u00a1-\uffff\U00010000-\U0010ffff0-9]+)*" + # TLD identifier + r"(?:\.(?:(?:xn--[-]{0,2}[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')) + ) diff --git a/venv/lib/python3.9/site-packages/validators/utils.py b/venv/lib/python3.9/site-packages/validators/utils.py new file mode 100644 index 00000000..3044477b --- /dev/null +++ b/venv/lib/python3.9/site-packages/validators/utils.py @@ -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) diff --git a/venv/lib/python3.9/site-packages/validators/uuid.py b/venv/lib/python3.9/site-packages/validators/uuid.py new file mode 100644 index 00000000..20080088 --- /dev/null +++ b/venv/lib/python3.9/site-packages/validators/uuid.py @@ -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 |