# -*- coding: utf-8 -*-
# The lack of a module docstring for this module is **INTENTIONAL**.
# The module is imported into the documentation using Sphinx's autodoc
# extension, and its member function documentation is automatically incorporated
# there as needed.
import decimal as decimal_
import fractions
import io
import math
import os
import uuid as uuid_
import datetime as datetime_
from ast import parse
from validator_collection._compat import numeric_types, integer_types, datetime_types,\
date_types, time_types, timestamp_types, tzinfo_types, POSITIVE_INFINITY, \
NEGATIVE_INFINITY, TimeZone, json, is_py2, is_py3, dict_, float_, basestring, re
URL_REGEX = re.compile(
r"^"
# protocol identifier
r"(?:(?:https?|ftp)://)"
# user:pass authentication
r"(?:\S+(?::\S*)?@)?"
r"(?:"
# IP address exclusion
# private & local networks
r"(?!(?:10|127)(?:\.\d{1,3}){3})"
r"(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})"
r"(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})"
# 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"(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])"
r"(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}"
r"(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))"
r"|"
# host name
r"(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)"
# domain name
r"(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*"
# TLD identifier
r"(?:\.(?:[a-z\u00a1-\uffff]{2,}))"
r")"
# port number
r"(?::\d{2,5})?"
# resource path
r"(?:/\S*)?"
r"$"
, re.UNICODE)
EMAIL_REGEX = re.compile(
r"(?!localhost)[^@\s]+@[^@\s]+\.[a-zA-Z0-9]+$"
)
MAC_ADDRESS_REGEX = re.compile(r'^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$')
IPV6_REGEX = re.compile(
'^(?:(?:[0-9A-Fa-f]{1,4}:){6}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|::(?:[0-9A-Fa-f]{1,4}:){5}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,4}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){,6}[0-9A-Fa-f]{1,4})?::)(?:%25(?:[A-Za-z0-9\\-._~]|%[0-9A-Fa-f]{2})+)?$'
)
## CORE
[docs]def uuid(value, allow_empty = False):
"""Validate that ``value`` is a valid :class:`UUID <python:uuid.UUID>`.
:param value: The value to validate.
:param allow_empty: If ``True``, returns :class:`None <python:None>` if ``value`` is empty. If
``False``, raises a :class:`ValueError <python:ValueError>` if ``value`` is empty.
Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:returns: ``value`` coerced to a :class:`UUID <python:uuid.UUID>` object / :class:`None <python:None>`
:rtype: :class:`UUID <python:uuid.UUID>` / :class:`None <python:None>`
:raises ValueError: if ``value`` is empty and ``allow_empty`` is ``False``
:raises TypeError: if ``value`` cannot be coerced to a :class:`UUID <python:uuid.UUID>`
"""
if not value and not allow_empty:
raise ValueError('value cannot be empty')
elif not value:
return None
if isinstance(value, uuid_.UUID):
return value
try:
value = uuid_.UUID(value)
except ValueError:
raise TypeError('value must be a valid UUID')
return value
[docs]def string(value,
allow_empty = False,
coerce_value = False,
minimum_length = None,
maximum_length = None,
whitespace_padding = False):
"""Validate that ``value`` is a valid string.
:param value: The value to validate.
:type value: :class:`str <python:str>` / :class:`None <python:None>`
:param allow_empty: If ``True``, returns :class:`None <python:None>` if ``value`` is empty. If
``False``, raises a :class:`ValueError <python:ValueError>` if ``value`` is empty.
Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:param coerce_value: If ``True``, will attempt to coerce ``value`` to a string if
it is not already. If ``False``, will raise a :class:`ValueError` if ``value``
is not a string. Defaults to ``False``.
:type coerce_value: :class:`bool <python:bool>`
:param minimum_length: If supplied, indicates the minimum number of characters
needed to be valid.
:type minimum_length: :class:`int <python:int>`
:param maximum_length: If supplied, indicates the minimum number of characters
needed to be valid.
:type maximum_length: :class:`int <python:int>`
:param whitespace_padding: If ``True`` and the value is below the
``minimum_length``, pad the value with spaces. Defaults to ``False``.
:type whitespace_padding: :class:`bool <python:bool>`
:returns: ``value`` / :class:`None <python:None>`
:rtype: :class:`str <python:str>` / :class:`None <python:None>`
:raises ValueError: if ``value`` is empty and ``allow_empty`` is ``False``
:raises ValueError: if ``value`` is not a valid string and ``coerce_value``
is ``False``
:raises ValueError: if ``minimum_length`` is supplied and the length of
``value`` is less than ``minimum_length`` and ``whitespace_padding`` is
``False``
:raises ValueError: if ``maximum_length`` is supplied and the length of
``value`` is more than the ``maximum_length``
"""
if not value and not allow_empty:
raise ValueError('value cannot be empty')
elif not value:
return None
minimum_length = integer(minimum_length, allow_empty = True)
maximum_length = integer(maximum_length, allow_empty = True)
if coerce_value:
value = str(value)
elif not isinstance(value, basestring):
raise ValueError('value (%s) is not a string' % value)
if value and maximum_length and len(value) > maximum_length:
raise ValueError('value (%s) exceeds maximum length')
if value and minimum_length and len(value) < minimum_length:
if whitespace_padding:
value = value.ljust(minimum_length, ' ')
else:
raise ValueError('value (%s) is below the minimum length')
return value
[docs]def iterable(value,
allow_empty = False,
forbid_literals = (str, bytes),
minimum_length = None,
maximum_length = None):
"""Validate that ``value`` is a valid iterable.
:param value: The value to validate.
:param allow_empty: If ``True``, returns :class:`None <python:None>` if ``value``
is empty. If ``False``, raises a :class:`ValueError <python:ValueError>` if
``value`` is empty. Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:param forbid_literals: A collection of literals that will be considered invalid
even if they are (actually) iterable. Defaults to :class:`str <python:str>` and
:class:`bytes <python:bytes>`.
:type forbid_literals: iterable
:param minimum_length: If supplied, indicates the minimum number of members
needed to be valid.
:type minimum_length: :class:`int <python:int>`
:param maximum_length: If supplied, indicates the minimum number of members
needed to be valid.
:type maximum_length: :class:`int <python:int>`
:returns: ``value`` / :class:`None <python:None>`
:rtype: iterable / :class:`None <python:None>`
:raises ValueError: if ``value`` is empty and ``allow_empty`` is ``False``
:raises ValueError: if ``value`` is not a valid iterable or :class:`None <python:None>`
:raises ValueError: if ``minimum_length`` is supplied and the length of
``value`` is less than ``minimum_length`` and ``whitespace_padding`` is
``False``
:raises ValueError: if ``maximum_length`` is supplied and the length of
``value`` is more than the ``maximum_length``
"""
if not value and not allow_empty:
raise ValueError('value cannot be empty')
elif value is None:
return None
minimum_length = integer(minimum_length, allow_empty = True)
maximum_length = integer(maximum_length, allow_empty = True)
if isinstance(value, forbid_literals) or not hasattr(value, '__iter__'):
raise ValueError('value must be a valid iterable')
if value and minimum_length is not None and len(value) < minimum_length:
raise ValueError('value has fewer items than the minimum length')
if value and maximum_length is not None and len(value) > maximum_length:
raise ValueError('value has more items than the maximum length')
return value
[docs]def none(value,
allow_empty = False):
"""Validate that ``value`` is :class:`None <python:None>`.
:param value: The value to validate.
:param allow_empty: If ``True``, returns :class:`None <python:None>` if ``value``
is empty but **not** :class:`None <python:None>`. If ``False``, raises a
:class:`ValueError <python:ValueError>` if ``value`` is empty but **not**
:class:`None <python:None>`. Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:returns: :class:`None <python:None>`
:raises ValueError: if ``allow_empty`` is ``False`` and ``value`` is empty
but **not** :class:`None <python:None>` and
"""
if value is not None and not value and allow_empty:
pass
elif (value is not None and not value) or value:
raise ValueError('value must be None')
return None
[docs]def not_empty(value, allow_empty = False):
"""Validate that ``value`` is not empty.
:param value: The value to validate.
:param allow_empty: If ``True``, returns :class:`None <python:None>` if
``value`` is empty. If ``False``, raises a :class:`ValueError <python:ValueError>`
if ``value`` is empty. Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:returns: ``value`` / :class:`None <python:None>`
:raises ValueError: if ``value`` is empty and ``allow_empty`` is ``False``
"""
if not value and allow_empty:
return None
elif not value:
raise ValueError('value was empty')
return value
[docs]def variable_name(value,
allow_empty = False):
"""Validate that the value is a valid Python variable name.
.. caution::
This function does **NOT** check whether the variable exists. It only
checks that the ``value`` would work as a Python variable (or class, or
function, etc.) name.
:param value: The value to validate.
:param allow_empty: If ``True``, returns :class:`None <python:None>` if ``value`` is empty.
If ``False``, raises a :class:`ValueError <python:ValueError>` if ``value`` is empty.
Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:returns: ``value`` / :class:`None <python:None>`
:rtype: :class:`str <python:str>` or :class:`None <python:None>`
:raises ValueError: if ``allow_empty`` is ``False`` and ``value``
is empty
"""
if not value and not allow_empty:
raise ValueError('value cannot be empty')
elif not value:
return None
try:
parse('%s = None' % value)
except (SyntaxError, ValueError, TypeError):
raise ValueError('value (%s) is not a valid variable name' % value)
return value
[docs]def dict(value,
allow_empty = False,
json_serializer = None):
"""Validate that ``value`` is a :class:`dict <python:dict>`.
.. hint::
If ``value`` is a string, this validator will assume it is a JSON
object and try to convert it into a :class:`dict <python:dict>`
You can override the JSON serializer used by passing it to the
``json_serializer`` property. By default, will utilize the Python
:class:`json <json>` encoder/decoder.
:param value: The value to validate.
:param allow_empty: If ``True``, returns :class:`None <python:None>` if ``value``
is empty. If ``False``, raises a :class:`ValueError <python:ValueError>` if
``value`` is empty. Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:param json_serializer: The JSON encoder/decoder to use to deserialize a
string passed in ``value``. If not supplied, will default to the Python
:class:`json <python:json>` encoder/decoder.
:type json_serializer: callable
:returns: ``value`` / :class:`None <python:None>`
:rtype: :class:`dict <python:dict>` / :class:`None <python:None>`
:raises ValueError: if ``value`` is empty and ``allow_empty`` is ``False``
:raises ValueError: if ``value`` is not a :class:`dict <python:dict>`
"""
original_value = value
if not value and not allow_empty:
raise ValueError('value cannot be empty')
elif not value:
return None
if json_serializer is None:
json_serializer = json
if isinstance(value, str):
try:
value = json_serializer.loads(value)
except Exception:
raise ValueError('value (%s) cannot be coerced to a dict)' % original_value)
value = dict(value,
json_serializer = json_serializer)
if not isinstance(value, dict_):
raise ValueError('value (%s) is not a dict' % original_value)
return value
## DATE / TIME
[docs]def date(value,
allow_empty = False,
minimum = None,
maximum = None):
"""Validate that ``value`` is a valid date.
:param value: The value to validate.
:type value: :class:`str <python:str>` / :class:`datetime <python:datetime.datetime>`
/ :class:`date <python:datetime.date>` / :class:`None <python:None>`
:param allow_empty: If ``True``, returns :class:`None <python:None>` if
``value`` is empty. If ``False``, raises a :class:`ValueError <python:ValueError>`
if ``value`` is empty. Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:param minimum: If supplied, will make sure that ``value`` is on or after this value.
:type minimum: :class:`datetime <python:datetime.datetime>` /
:class:`date <python:datetime.date>` / compliant :class:`str <python:str>`
/ :class:`None <python:None>`
:param maximum: If supplied, will make sure that ``value`` is on or before this
value.
:type maximum: :class:`datetime <python:datetime.datetime>` /
:class:`date <python:datetime.date>` / compliant :class:`str <python:str>`
/ :class:`None <python:None>`
:returns: ``value`` / :class:`None <python:None>`
:rtype: :class:`date <python:datetime.date>` / :class:`None <python:None>`
:raises ValueError: if ``value`` is empty and ``allow_empty`` is ``False``
:raises ValueError: if ``value`` is not a valid value type or
:class:`None <python:None>`
:raises ValueError: if ``minimum`` is supplied but ``value`` occurs before ``minimum``
:raises ValueError: if ``maximum`` is supplied but ``value`` occurs after ``minimum``
"""
if not value and not allow_empty:
raise ValueError('value cannot be empty')
elif not value:
return None
minimum = date(minimum, allow_empty = True)
maximum = date(maximum, allow_empty = True)
if not isinstance(value, date_types):
raise ValueError('value must be a date object, datetime object, '
'ISO 8601-formatted string, '
'or POSIX timestamp')
elif isinstance(value, datetime_.datetime):
value = value.date()
elif isinstance(value, timestamp_types):
try:
value = datetime_.date.fromtimestamp(value)
except ValueError:
raise ValueError('value must be a date object, datetime object, '
'ISO 8601-formatted string, '
'or POSIX timestamp')
elif isinstance(value, str):
try:
value = datetime_.datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%f')
value = value.date()
except ValueError:
if ' ' in value:
value = value.split(' ')[0]
if 'T' in value:
value = value.split('T')[0]
if len(value) != 10:
raise ValueError('value must be a date object, datetime object, '
'ISO 8601-formatted string, '
'or POSIX timestamp')
try:
year = int(value[:4])
month = int(value[5:7])
day = int(value[-2:])
value = datetime_.date(year, month, day)
except (ValueError, TypeError):
raise ValueError('value must be a date object, datetime object, '
'ISO 8601-formatted string, '
'or POSIX timestamp')
if minimum and value and value < minimum:
raise ValueError('value (%s) is before the minimum given' % value.isoformat())
if maximum and value and value > maximum:
raise ValueError('value (%s) is after the maximum given' % value.isoformat())
return value
[docs]def datetime(value,
allow_empty = False,
minimum = None,
maximum = None):
"""Validate that ``value`` is a valid datetime.
.. caution::
If supplying a string, the string needs to be in an ISO 8601-format to pass
validation. If it is not in an ISO 8601-format, validation will fail.
:param value: The value to validate.
:type value: :class:`str <python:str>` / :class:`datetime <python:datetime.datetime>`
/ :class:`date <python:datetime.date>` / :class:`None <python:None>`
:param allow_empty: If ``True``, returns :class:`None <python:None>` if ``value``
is empty. If ``False``, raises a :class:`ValueError <python:ValueError>` if
``value`` is empty. Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:param minimum: If supplied, will make sure that ``value`` is on or after this value.
:type minimum: :class:`datetime <python:datetime.datetime>` /
:class:`date <python:datetime.date>` / compliant :class:`str <python:str>` /
:class:`None <python:None>`
:param maximum: If supplied, will make sure that ``value`` is on or before this
value.
:type maximum: :class:`datetime <python:datetime.datetime>` /
:class:`date <python:datetime.date>` / compliant :class:`str <python:str>` /
:class:`None <python:None>`
:returns: ``value`` / :class:`None <python:None>`
:rtype: :class:`datetime <python:datetime.datetime>` / :class:`None <python:None>`
:raises ValueError: if ``value`` is empty and ``allow_empty`` is ``False``
:raises ValueError: if ``minimum`` is supplied but ``value`` occurs before ``minimum``
:raises ValueError: if ``maximum`` is supplied but ``value`` occurs after ``minimum``
"""
# pylint: disable=too-many-branches
if not value and not allow_empty:
raise ValueError('value cannot be empty')
elif not value:
return None
minimum = datetime(minimum, allow_empty = True)
maximum = datetime(maximum, allow_empty = True)
if not isinstance(value, datetime_types):
raise ValueError('value must be a date object, datetime object, '
'ISO 8601-formatted string, '
'or POSIX timestamp')
elif isinstance(value, timestamp_types):
try:
value = datetime_.datetime.fromtimestamp(value)
except ValueError:
raise ValueError('value must be a date object, datetime object, '
'ISO 8601-formatted string, '
'or POSIX timestamp')
elif isinstance(value, str):
try:
if 'T' in value:
value = datetime_.datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%f')
else:
value = datetime_.datetime.strptime(value, '%Y-%m-%d %H:%M:%S.%f')
except ValueError:
try:
if 'T' in value:
value = datetime_.datetime.strptime(value, '%Y/%m/%dT%H:%M:%S')
else:
value = datetime_.datetime.strptime(value, '%Y/%m/%d %H:%M:%S')
except ValueError:
try:
if 'T' in value:
value = datetime_.datetime.strptime(value, '%Y-%m-%dT%H:%M:%S')
else:
value = datetime_.datetime.strptime(value, '%Y-%m-%d %H:%M:%S')
except ValueError:
try:
if 'T' in value:
value = datetime_.datetime.strptime(value,
'%Y/%m/%dT%H:%M:%S')
else:
value = datetime_.datetime.strptime(value,
'%Y/%m/%d %H:%M:%S')
except ValueError:
value = date(value)
if isinstance(value, datetime_.date) and not isinstance(value, datetime_.datetime):
value = datetime_.datetime(value.year, # pylint: disable=R0204
value.month,
value.day,
0,
0,
0,
0)
if minimum and value and value < minimum:
raise ValueError('value (%s) is before the minimum given' % value.isoformat())
if maximum and value and value > maximum:
raise ValueError('value (%s) is after the maximum given' % value.isoformat())
return value
[docs]def time(value,
allow_empty = False,
minimum = None,
maximum = None):
"""Validate that ``value`` is a valid :class:`time <python:datetime.time>`.
.. caution::
This validator will **always** return the time as timezone naive (effectively
UTC). If ``value`` has a timezone / UTC offset applied, the validator will
coerce the value returned back to UTC.
:param value: The value to validate.
:type value: :func:`datetime <validator_collection.validators.datetime>` or
:func:`time <validator_collection.validators.time>`-compliant
:class:`str <python:str>` / :class:`datetime <python:datetime.datetime>` /
:class:`time <python:datetime.time> / numeric / :class:`None <python:None>`
:param allow_empty: If ``True``, returns :class:`None <python:None>` if ``value``
is empty. If ``False``, raises a :class:`ValueError <python:ValueError>` if
``value`` is empty. Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:param minimum: If supplied, will make sure that ``value`` is on or after this value.
:type minimum: :func:`datetime <validator_collection.validators.datetime>` or
:func:`time <validator_collection.validators.time>`-compliant
:class:`str <python:str>` / :class:`datetime <python:datetime.datetime>` /
:class:`time <python:datetime.time> / numeric / :class:`None <python:None>`
:param maximum: If supplied, will make sure that ``value`` is on or before this
value.
:type maximum: :func:`datetime <validator_collection.validators.datetime>` or
:func:`time <validator_collection.validators.time>`-compliant
:class:`str <python:str>` / :class:`datetime <python:datetime.datetime>` /
:class:`time <python:datetime.time> / numeric / :class:`None <python:None>`
:returns: ``value`` in UTC time / :class:`None <python:None>`
:rtype: :class:`time <python:datetime.time>` / :class:`None <python:None>`
:raises ValueError: if ``value`` is empty and ``allow_empty`` is ``False``
:raises ValueError: if ``value`` is not a valid value type or
:class:`None <python:None>`
:raises ValueError: if ``minimum`` is supplied but ``value`` occurs before ``minimum``
:raises ValueError: if ``maximum`` is supplied but ``value`` occurs after ``minimum``
"""
# pylint: disable=too-many-branches
if not value and not allow_empty:
if isinstance(value, datetime_.time):
pass
else:
raise ValueError('value cannot be empty')
elif not value:
if not isinstance(value, datetime_.time):
return None
minimum = time(minimum, allow_empty = True)
maximum = time(maximum, allow_empty = True)
if not isinstance(value, time_types):
raise ValueError('value must be a datetime object, '
'ISO 8601-formatted string, '
'or POSIX timestamp')
elif isinstance(value, datetime_.datetime):
value = value.time()
elif isinstance(value, timestamp_types):
try:
datetime_value = datetime(value)
value = datetime_value.time()
except ValueError:
raise ValueError('value must be a datetime object, '
'ISO 8601-formatted string, '
'or POSIX timestamp')
elif isinstance(value, basestring):
is_value_calculated = False
if len(value) > 10:
try:
datetime_value = datetime(value)
value = datetime_value.time()
is_value_calculated = True
except ValueError:
pass
if not is_value_calculated:
try:
if '+' in value:
components = value.split('+')
is_offset_positive = True
elif '-' in value:
components = value.split('-')
is_offset_positive = False
else:
raise ValueError()
time_string = components[0]
if len(components) > 1:
utc_offset = components[1]
else:
utc_offset = None
time_components = time_string.split(':')
hour = int(time_components[0])
minutes = int(time_components[1])
seconds = time_components[2]
if '.' in seconds:
second_components = seconds.split('.')
seconds = int(second_components[0])
microseconds = int(second_components[1])
else:
microseconds = 0
utc_offset = timezone(utc_offset,
allow_empty = True,
positive = is_offset_positive)
value = datetime_.time(hour = hour,
minute = minutes,
second = seconds,
microsecond = microseconds,
tzinfo = utc_offset)
except (ValueError, TypeError, IndexError):
raise ValueError('value must be a date object, datetime object, '
'ISO 8601-formatted string, '
'or POSIX timestamp')
if value is not None:
value = value.replace(tzinfo = None)
if minimum is not None and value and value < minimum:
raise ValueError('value (%s) is before the minimum given' % value.isoformat())
if maximum is not None and value and value > maximum:
raise ValueError('value (%s) is after the maximum given' % value.isoformat())
return value
[docs]def timezone(value,
allow_empty = False,
positive = True):
"""Validate that ``value`` is a valid :class:`tzinfo <python:datetime.tzinfo>`.
.. caution::
This does **not** validate whether the value is a timezone that actually
exists, nor can it resolve timzone names (e.g. ``'Eastern'`` or ``'CET'``).
For that kind of functionality, we recommend you utilize:
`pytz <https://pypi.python.org/pypi/pytz>`_
:param value: The value to validate.
:type value: :class:`str <python:str>` / :class:`tzinfo <python:datetime.tzinfo>`
/ numeric / :class:`None <python:None>`
:param allow_empty: If ``True``, returns :class:`None <python:None>` if ``value``
is empty. If ``False``, raises a :class:`ValueError <python:ValueError>` if
``value`` is empty. Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:param positive: Indicates whether the ``value`` is positive or negative
(only has meaning if ``value`` is a string). Defaults to ``True``.
:type positive: :class:`bool <python:bool>`
:returns: ``value`` / :class:`None <python:None>`
:rtype: :class:`tzinfo <python:datetime.tzinfo>` / :class:`None <python:None>`
:raises ValueError: if ``value`` is empty and ``allow_empty`` is ``False``
:raises ValueError: if ``value`` is not a valid value type or
:class:`None <python:None>`
"""
# pylint: disable=too-many-branches
original_value = value
if not value and not allow_empty:
raise ValueError('value cannot be empty')
elif not value:
return None
if not isinstance(value, tzinfo_types):
raise ValueError('value must be a tzinfo, '
'UTC offset in seconds expressed as a number, '
'UTC offset expressed as string of form +HH:MM')
elif isinstance(value, datetime_.datetime):
value = value.tzinfo
elif isinstance(value, datetime_.date):
return None
elif isinstance(value, datetime_.time):
return value.tzinfo
elif isinstance(value, timestamp_types):
return None
elif isinstance(value, str):
if '+' not in value and '-' not in value:
try:
datetime_value = datetime(value)
return datetime_value.tzinfo
except ValueError:
raise ValueError('value must be a tzinfo, '
'UTC offset in seconds expressed as a number, '
'UTC offset expressed as string of form +HH:MM')
elif '-' in value:
try:
datetime_value = datetime(value)
return datetime_value.tzinfo
except ValueError:
pass
if '+' in value and not positive:
raise ValueError('expected a negative UTC offset but value is positive')
elif '-' in value and positive and len(value) == 6:
positive = False
elif '-' in value and positive:
raise ValueError('expected a positive UTC offset but value is negative')
if '+' in value:
value = value[value.find('+'):]
elif '-' in value:
value = value[value.rfind('-'):]
value = value[1:]
offset_components = value.split(':')
if len(offset_components) != 2:
raise ValueError('value must be a tzinfo, '
'UTC offset in seconds expressed as a number, '
'UTC offset expressed as string of form +HH:MM')
hour = int(offset_components[0])
minutes = int(offset_components[1])
value = (hour * 60 * 60) + (minutes * 60)
if not positive:
value = 0 - value
if isinstance(value, numeric_types):
if value > 0:
positive = True
elif value < 0:
positive = False
elif value == 0:
return None
offset = datetime_.timedelta(seconds = value)
if is_py2:
value = TimeZone(offset = offset)
elif is_py3:
try:
value = TimeZone(offset)
except ValueError:
raise ValueError('value (%s) cannot exceed +/- 24h' % original_value)
else:
raise NotImplementedError()
return value
## NUMBERS
[docs]def numeric(value,
allow_empty = False,
minimum = None,
maximum = None):
"""Validate that ``value`` is a numeric value.
:param value: The value to validate.
:param allow_empty: If ``True``, returns :class:`None <python:None>` if ``value``
is :class:`None <python:None>`. If ``False``, raises a
:class:`ValueError <python:ValueError>` if ``value`` is :class:`None <python:None>`.
Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:param minimum: If supplied, will make sure that ``value`` is greater than or
equal to this value.
:type minimum: numeric
:param maximum: If supplied, will make sure that ``value`` is less than or
equal to this value.
:type maximum: numeric
:returns: ``value`` / :class:`None <python:None>`
:raises ValueError: if ``value`` is :class:`None <python:None>` and
``allow_empty`` is ``False``
:raises ValueError: if ``minimum`` is supplied and ``value`` is less than the
``minimum``
:raises ValueError: if ``maximum`` is supplied and ``value`` is more than the
``maximum``
"""
if maximum is None:
maximum = POSITIVE_INFINITY
else:
maximum = numeric(maximum)
if minimum is None:
minimum = NEGATIVE_INFINITY
else:
minimum = numeric(minimum)
if value is None and not allow_empty:
raise ValueError('value cannot be empty')
elif value is not None:
if isinstance(value, str):
try:
value = float_(value)
except (ValueError, TypeError):
raise ValueError('value cannot be coerced to a numeric form')
elif not isinstance(value, numeric_types):
raise ValueError('value is not numeric')
if value is not None and value > maximum:
raise ValueError('value (%s) exceeds maximum (%s)' % (value, maximum))
if value is not None and value < minimum:
raise ValueError('value (%s) less than minimum (%s)' % (value, minimum))
return value
[docs]def integer(value,
allow_empty = False,
coerce_value = False,
minimum = None,
maximum = None,
base = 10):
"""Validate that ``value`` is an :class:`int <python:int>`.
:param value: The value to validate.
:param allow_empty: If ``True``, returns :class:`None <python:None>` if
``value`` is :class:`None <python:None>`. If ``False``, raises a
:class:`ValueError <python:ValueError>` if ``value`` is :class:`None <python:None>`.
Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:param coerce_value: If ``True``, will force any numeric ``value`` to an integer
(always rounding up). If ``False``, will raise an error if ``value`` is numeric
but not a whole number. Defaults to ``False``.
:type coerce_value: :class:`bool <python:bool>`
:param minimum: If supplied, will make sure that ``value`` is greater than or
equal to this value.
:type minimum: numeric
:param maximum: If supplied, will make sure that ``value`` is less than or
equal to this value.
:type maximum: numeric
:param base: Indicates the base that is used to determine the integer value.
The allowed values are 0 and 2–36. Base-2, -8, and -16 literals can be
optionally prefixed with ``0b/0B``, ``0o/0O/0``, or ``0x/0X``, as with
integer literals in code. Base 0 means to interpret the string exactly as
an integer literal, so that the actual base is 2, 8, 10, or 16. Defaults to
``10``.
:returns: ``value`` / :class:`None <python:None>`
:raises ValueError: if ``value`` is :class:`None <python:None>` and
``allow_empty`` is ``False``
:raises ValueError: if ``minimum`` is supplied and ``value`` is less than the
``minimum``
:raises ValueError: if ``maximum`` is supplied and ``value`` is more than the
``maximum``
"""
value = numeric(value,
allow_empty = allow_empty,
minimum = minimum,
maximum = maximum)
if value is not None and hasattr(value, 'is_integer'):
if value.is_integer():
return int(value)
if value is not None and coerce_value:
float_value = math.ceil(value)
if is_py2:
value = int(float_value) # pylint: disable=R0204
elif is_py3:
str_value = str(float_value)
value = int(str_value, base = base)
else:
raise NotImplementedError('Python %s not supported' % os.sys.version)
elif value is not None and not isinstance(value, integer_types):
raise ValueError('value (%s) is not an integer' % value)
return value
[docs]def float(value,
allow_empty = False,
minimum = None,
maximum = None):
"""Validate that ``value`` is a :class:`float <python:float>`.
:param value: The value to validate.
:param allow_empty: If ``True``, returns :class:`None <python:None>` if
``value`` is :class:`None <python:None>`. If ``False``, raises a
:class:`ValueError <python:ValueError>` if ``value`` is :class:`None <python:None>`.
Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:returns: ``value`` / :class:`None <python:None>`
:rtype: :class:`float <python:float>` / :class:`None <python:None>`
:raises ValueError: if ``value`` is :class:`None <python:None>` and ``allow_empty``
is ``False``
:raises ValueError: if ``minimum`` is supplied and ``value`` is less than the
``minimum``
:raises ValueError: if ``maximum`` is supplied and ``value`` is more than the
``maximum``
"""
value = _numeric_coercion(value,
coercion_function = float_,
allow_empty = allow_empty,
minimum = minimum,
maximum = maximum)
return value
[docs]def fraction(value,
allow_empty = False,
minimum = None,
maximum = None):
"""Validate that ``value`` is a :class:`Fraction <python:fractions.Fraction>`.
:param value: The value to validate.
:param allow_empty: If ``True``, returns :class:`None <python:None>` if ``value``
is :class:`None <python:None>`. If ``False``, raises a
:class:`ValueError <python:ValueError>` if ``value`` is :class:`None <python:None>`.
Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:returns: ``value`` / :class:`None <python:None>`
:rtype: :class:`Fraction <python:fractions.Fraction>` / :class:`None <python:None>`
:raises ValueError: if ``value`` is :class:`None <python:None>` and ``allow_empty``
is ``False``
:raises ValueError: if ``minimum`` is supplied and ``value`` is less than the
``minimum``
:raises ValueError: if ``maximum`` is supplied and ``value`` is more than the
``maximum``
"""
value = _numeric_coercion(value,
coercion_function = fractions.Fraction,
allow_empty = allow_empty,
minimum = minimum,
maximum = maximum)
return value
[docs]def decimal(value,
allow_empty = False,
minimum = None,
maximum = None):
"""Validate that ``value`` is a :class:`Decimal <python:decimal.Decimal>`.
:param value: The value to validate.
:param allow_empty: If ``True``, returns :class:`None <python:None>` if ``value``
is :class:`None <python:None>`. If ``False``, raises a
:class:`ValueError <python:ValueError>` if ``value`` is :class:`None <python:None>`.
Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:param minimum: If supplied, will make sure that ``value`` is greater than or
equal to this value.
:type minimum: numeric
:param maximum: If supplied, will make sure that ``value`` is less than or
equal to this value.
:type maximum: numeric
:returns: ``value`` / :class:`None <python:None>`
:rtype: :class:`Decimal <python:decimal.Decimal>` / :class:`None <python:None>`
:raises ValueError: if ``value`` is :class:`None <python:None>` and ``allow_empty``
is ``False``
:raises ValueError: if ``minimum`` is supplied and ``value`` is less than the
``minimum``
:raises ValueError: if ``maximum`` is supplied and ``value`` is more than the
``maximum``
"""
if value is None and allow_empty:
return None
elif value is None:
raise ValueError('value cannot be None')
if isinstance(value, str):
try:
value = decimal_.Decimal(value.strip())
except decimal_.InvalidOperation:
raise ValueError('value cannot be converted to a Decimal')
elif isinstance(value, fractions.Fraction):
try:
value = float(value) # pylint: disable=R0204
except ValueError:
raise ValueError('value cannot be converted to a Decimal')
value = numeric(value,
allow_empty = False,
maximum = maximum,
minimum = minimum)
if not isinstance(value, decimal_.Decimal):
value = decimal_.Decimal(value)
return value
def _numeric_coercion(value,
coercion_function = None,
allow_empty = False,
minimum = None,
maximum = None):
"""Validate that ``value`` is numeric and coerce using ``coercion_function``.
:param value: The value to validate.
:param coercion_function: The function to use to coerce ``value`` to the desired
type.
:type coercion_function: callable
:param allow_empty: If ``True``, returns :class:`None <python:None>` if
``value`` is :class:`None <python:None>`. If ``False``, raises a
:class:`ValueError <python:ValueError>` if ``value`` is :class:`None <python:None>`.
Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:returns: ``value`` / :class:`None <python:None>`
:rtype: the type returned by ``coercion_function``
:raises ValueError: if ``coercion_function`` is empty
:raises ValueError: if ``value`` is :class:`None <python:None>` and ``allow_empty``
is ``False``
:raises ValueError: if ``coercion_function`` raises an exception
"""
if coercion_function is None:
raise ValueError('coercion_function cannot be empty')
elif not hasattr(coercion_function, '__call__'):
raise ValueError('coercion_function must be callable')
value = numeric(value,
allow_empty = allow_empty,
minimum = minimum,
maximum = maximum)
if value is not None:
try:
value = coercion_function(value)
except (ValueError, TypeError, AttributeError, SyntaxError):
raise ValueError('cannot coerce value (%s) to desired type' % value)
return value
## FILE-RELATED
[docs]def bytesIO(value,
allow_empty = False):
"""Validate that ``value`` is a :class:`BytesIO <python:io.BytesIO>` object.
:param value: The value to validate.
:param allow_empty: If ``True``, returns :class:`None <python:None>` if
``value`` is empty. If ``False``, raises a
:class:`ValueError <python:ValueError>` if ``value`` is empty.
Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:returns: ``value`` / :class:`None <python:None>`
:rtype: :class:`StringIO <python:io.StringIO>` / :class:`None <python:None>`
:raises ValueError: if ``value`` is empty and ``allow_empty`` is ``False``
"""
if not value and not allow_empty:
raise ValueError('value cannot be empty')
elif not value:
return None
if not isinstance(value, io.BytesIO):
raise ValueError('value is not a BytesIO')
return value
[docs]def stringIO(value,
allow_empty = False):
"""Validate that ``value`` is a :class:`StringIO <python:io.StringIO>` object.
:param value: The value to validate.
:param allow_empty: If ``True``, returns :class:`None <python:None>` if
``value`` is empty. If ``False``, raises a
:class:`ValueError <python:ValueError>` if ``value`` is empty.
Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:returns: ``value`` / :class:`None <python:None>`
:rtype: :class:`StringIO <python:io.StringIO>` / :class:`None <python:None>`
:raises ValueError: if ``value`` is empty and ``allow_empty`` is ``False``
"""
if not value and not allow_empty:
raise ValueError('value cannot be empty')
elif not value:
return None
if not isinstance(value, io.StringIO):
raise ValueError('value is not an io.StringIO object')
return value
[docs]def path(value,
allow_empty = False):
"""Validate that ``value`` is a valid path-like object.
:param value: The value to validate.
:param allow_empty: If ``True``, returns :class:`None <python:None>` if
``value`` is empty. If ``False``, raises a
:class:`ValueError <python:ValueError>` if ``value`` is empty.
Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:returns: The path represented by ``value``.
:rtype: Path-like object / :class:`None <python:None>`
:raises ValueError: if ``allow_empty`` is ``False`` and ``value`` is empty
:raises ValueError: if ``value`` is not a valid path
"""
if not value and not allow_empty:
raise ValueError('value cannot be empty')
elif not value:
return None
if hasattr(os, 'PathLike'):
if not isinstance(value, (str, bytes, int, os.PathLike)): # pylint: disable=E1101
raise ValueError('value (%s) is not a valid path' % value)
else:
if not isinstance(value, int):
try:
os.path.exists(value)
except TypeError:
raise ValueError('value (%s) is not a valid path' % value)
return value
[docs]def path_exists(value,
allow_empty = False):
"""Validate that ``value`` is a path-like object that exists on the local
filesystem.
:param value: The value to validate.
:param allow_empty: If ``True``, returns :class:`None <python:None>` if
``value`` is empty. If ``False``, raises a
:class:`ValueError <python:ValueError>` if ``value`` is empty.
Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:returns: The file name represented by ``value``.
:rtype: Path-like object / :class:`None <python:None>`
:raises ValueError: if ``allow_empty`` is ``False`` and ``value``
is empty
:raises IOError: if ``value`` does not exist
"""
if not value and not allow_empty:
raise ValueError('value cannot be empty')
elif not value:
return None
value = path(value)
if not os.path.exists(value):
raise IOError('value (%s) not found' % value)
return value
[docs]def file_exists(value,
allow_empty = False):
"""Validate that ``value`` is a valid file that exists on the local filesystem.
:param value: The value to validate.
:param allow_empty: If ``True``, returns :class:`None <python:None>` if
``value`` is empty. If ``False``, raises a
:class:`ValueError <python:ValueError>` if ``value`` is empty.
Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:returns: The file name represented by ``value``.
:rtype: Path-like object / :class:`None <python:None>`
:raises ValueError: if ``allow_empty`` is ``False`` and ``value``
is empty
:raises IOError: if ``value`` does not exist on the local filesystem
:raises ValueError: if ``value`` is not a valid file
"""
if not value and not allow_empty:
raise ValueError('value cannot be empty')
elif not value:
return None
value = path_exists(value)
if not os.path.isfile(value):
raise ValueError('value (%s) is not a file')
return value
[docs]def directory_exists(value,
allow_empty = False):
"""Validate that ``value`` is a valid directory that exists on the local
filesystem.
:param value: The value to validate.
:param allow_empty: If ``True``, returns :class:`None <python:None>` if
``value`` is empty. If ``False``, raises a
:class:`ValueError <python:ValueError>` if ``value`` is empty.
Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:returns: The file name represented by ``value``.
:rtype: Path-like object / :class:`None <python:None>`
:raises ValueError: if ``allow_empty`` is ``False`` and ``value``
is empty
:raises IOError: if ``value`` does not exist on the local filesystem
:raises ValueError: if ``value`` is not a valid directory
"""
if not value and not allow_empty:
raise ValueError('value cannot be empty')
elif not value:
return None
value = path_exists(value)
if not os.path.isdir(value):
raise ValueError('value (%s) is not a directory')
return value
## INTERNET-RELATED
[docs]def email(value, allow_empty = False):
"""Validate that ``value`` is a valid email address.
:param value: The value to validate.
:type value: :class:`str <python:str>` / :class:`None <python:None>`
:param allow_empty: If ``True``, returns :class:`None <python:None>` if
``value`` is empty. If ``False``, raises a
:class:`ValueError <python:ValueError>` if ``value`` is empty.
Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:returns: ``value`` / :class:`None <python:None>`
:rtype: :class:`str <python:str>` / :class:`None <python:None>`
:raises ValueError: if ``value`` is empty and ``allow_empty`` is ``False``
:raises ValueError: if ``value`` is not a valid email address or
empty with ``allow_empty`` set to ``True``
"""
if not value and not allow_empty:
raise ValueError('value cannot be empty')
elif not value:
return None
if not isinstance(value, basestring):
raise ValueError('value must be a valid string')
value = value.lower()
is_valid = EMAIL_REGEX.match(value)
if not is_valid:
raise ValueError('value must be a valid email address')
return value
[docs]def url(value, allow_empty = False):
"""Validate that ``value`` is a valid URL.
:param value: The value to validate.
:type value: :class:`str <python:str>` / :class:`None <python:None>`
:param allow_empty: If ``True``, returns :class:`None <python:None>` if
``value`` is empty. If ``False``, raises a
:class:`ValueError <python:ValueError>` if ``value`` is empty.
Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:returns: ``value`` / :class:`None <python:None>`
:rtype: :class:`str <python:str>` / :class:`None <python:None>`
:raises ValueError: if ``value`` is empty and ``allow_empty`` is ``False``
:raises ValueError: if ``value`` is not a valid URL or
empty with ``allow_empty`` set to ``True``
"""
if not value and not allow_empty:
raise ValueError('value cannot be empty')
elif not value:
return None
if not isinstance(value, basestring):
raise ValueError('value must be a valid string')
value = value.lower()
is_valid = URL_REGEX.match(value)
if not is_valid:
raise ValueError('value must be a valid URL')
return value
[docs]def ip_address(value, allow_empty = False):
"""Validate that ``value`` is a valid IP address.
.. note::
First, the validator will check if the address is a valid IPv6 address.
If that doesn't work, the validator will check if the address is a valid
IPv4 address.
If neither works, the validator will raise an error (as always).
:param value: The value to validate.
:param allow_empty: If ``True``, returns :class:`None <python:None>` if
``value`` is empty. If ``False``, raises a
:class:`ValueError <python:ValueError>` if ``value`` is empty.
Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:returns: ``value`` / :class:`None <python:None>`
:raises ValueError: if ``value`` is empty and ``allow_empty`` is ``False``
:raises ValueError: if ``value`` is not a valid IP address or empty with
``allow_empty`` set to ``True``
"""
if not value and not allow_empty:
raise ValueError('value cannot be empty')
elif not value:
return None
try:
value = ipv6(value)
ipv6_failed = False
except ValueError:
ipv6_failed = True
if ipv6_failed:
try:
value = ipv4(value)
except ValueError:
raise ValueError('value (%s) is not a valid IPv6 or IPv4 address')
return value
[docs]def ipv4(value, allow_empty = False):
"""Validate that ``value`` is a valid IP version 4 address.
:param value: The value to validate.
:param allow_empty: If ``True``, returns :class:`None <python:None>` if
``value`` is empty. If ``False``, raises a
:class:`ValueError <python:ValueError>` if ``value`` is empty.
Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:returns: ``value`` / :class:`None <python:None>`
:raises ValueError: if ``value`` is empty and ``allow_empty`` is ``False``
:raises ValueError: if ``value`` is not a valid IP version 4 address or
empty with ``allow_empty`` set to ``True``
"""
if not value and allow_empty is False:
raise ValueError('value cannot be empty')
elif not value:
return None
try:
components = value.split('.')
except AttributeError:
raise ValueError('value (%s) is not a valid ipv4' % value)
if len(components) != 4 or not all(x.isdigit() for x in components):
raise ValueError('value (%s) is not a valid ipv4' % value)
for x in components:
try:
x = integer(x,
minimum = 0,
maximum = 255)
except ValueError:
raise ValueError('value (%s) is not a valid ipv4' % value)
return value
[docs]def ipv6(value, allow_empty = False):
"""Validate that ``value`` is a valid IP address version 6.
:param value: The value to validate.
:param allow_empty: If ``True``, returns :class:`None <python:None>` if
``value`` is empty. If ``False``, raises a
:class:`ValueError <python:ValueError>` if ``value`` is empty.
Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:returns: ``value`` / :class:`None <python:None>`
:raises ValueError: if ``value`` is empty and ``allow_empty`` is ``False``
:raises ValueError: if ``value`` is not a valid IP version 6 address or
empty with ``allow_empty`` is not set to ``True``
"""
if not value and allow_empty is False:
raise ValueError('value cannot be empty')
elif not value:
return None
if not isinstance(value, str):
raise ValueError('value (%s) is not a valid ipv6' % value)
value = value.lower()
is_valid = IPV6_REGEX.match(value)
if not is_valid:
raise ValueError('value (%s) is not a valid ipv6' % value)
return value
[docs]def mac_address(value, allow_empty = False):
"""Validate that ``value`` is a valid MAC address.
:param value: The value to validate.
:type value: :class:`str <python:str>` / :class:`None <python:None>`
:param allow_empty: If ``True``, returns :class:`None <python:None>` if
``value`` is empty. If ``False``, raises a
:class:`ValueError <python:ValueError>` if ``value`` is empty.
Defaults to ``False``.
:type allow_empty: :class:`bool <python:bool>`
:returns: ``value`` / :class:`None <python:None>`
:rtype: :class:`str <python:str>` / :class:`None <python:None>`
:raises ValueError: if ``value`` is empty and ``allow_empty`` is ``False``
:raises ValueError: if ``value`` is not a valid MAC address or empty with
``allow_empty`` set to ``True``
"""
if not value and not allow_empty:
raise ValueError('value cannot be empty')
elif not value:
return None
if not isinstance(value, basestring):
raise ValueError('value must be a valid string')
if '-' in value:
value = value.replace('-', ':')
is_valid = MAC_ADDRESS_REGEX.match(value)
if not is_valid:
raise ValueError('value (%s) is not a valid MAC address' % value)
return value