from datetime import date, time, datetime, timedelta, tzinfo
from .. import exc
from .. import contracts
from ..compat.datetime import UTC
from ..compat.types import basestr
from . import abstract
[docs]class Date(abstract.Validator):
"""
Date Validator
:param bool nullable:
accept ``None`` as a valid value.
:param bool unixts:
convert Unix timestamp (``int`` or ``float``) to ``date``.
:param str format:
try to parse ``date`` from ``str`` (Python 3.x)
or ``basestring`` (Python 2.x),
using ``datetime.strptime(value, self.format).date()``.
:param callable parser:
try to parse ``date`` from ``str`` (Python 3.x)
or ``basestring`` (Python 2.x),
using ``self.parser(value).date()``.
:param date min:
absolute lower limit.
:param date max:
absolute upper limit.
:param timedelta relmin:
relative lower limit.
:param timedelta relmax:
relative upper limit.
:param tzinfo tz:
timezone, see notes below.
:raises InvalidTypeError:
* if ``value is None`` and ``not self.nullable``;
* if ``isinstance(value, (int, float))`` and ``not self.unixts``;
* if ``not isinstance(value, date)``.
:raises DatetimeParseError:
* if ``datetime.strptime(value, self.format)`` raises ``ValueError``;
* if ``self.parser(value)`` raises ``ValueError``.
:raises MinValueError:
* if ``value < self.min``;
* if ``value < date.today() + self.relmin``.
:raises MaxValueError:
* if ``value > self.max``;
* if ``value > date.today() + self.relmax``.
:note:
Relative limits are calculated adding deltas to current date,
use negative ``relmin/relmax`` to specify date in the past.
:note:
It implicitly converts ``datetime`` to ``date``.
If timezone is specified and ``datetime`` object is timezone-aware,
it will be arranged to specified timezone first.
:note:
If timezone is specified,
it will be used in conversion from Unix timestamp.
In fact,
it will create ``datetime`` object in UTC,
using ``datetime.fromtimestamp(value, UTC)``.
And then arrange it to specified timezone,
and extract date part.
"""
__slots__ = (
"nullable",
"unixts",
"format",
"parser",
"min",
"max",
"relmin",
"relmax",
"tz",
)
def __init__(
self,
nullable=False,
unixts=False,
format=None,
parser=None,
min=None,
max=None,
relmin=None,
relmax=None,
tz=None,
alias=None,
replace=False,
):
nullable = contracts.expect_flag(self, "nullable", nullable)
unixts = contracts.expect_flag(self, "unixts", unixts)
format = contracts.expect_basestr(self, "format", format, nullable=True)
parser = contracts.expect_callable(self, "parser", parser, nullable=True)
min = contracts.expect(self, "min", min, types=date, nullable=True)
max = contracts.expect(self, "max", max, types=date, nullable=True)
relmin = contracts.expect(
self, "relmin", relmin, types=timedelta, nullable=True
)
relmax = contracts.expect(
self, "relmax", relmax, types=timedelta, nullable=True
)
tz = contracts.expect(self, "tz", tz, types=tzinfo, nullable=True)
setattr = object.__setattr__
setattr(self, "nullable", nullable)
setattr(self, "unixts", unixts)
setattr(self, "format", format)
setattr(self, "parser", parser)
setattr(self, "min", min)
setattr(self, "max", max)
setattr(self, "relmin", relmin)
setattr(self, "relmax", relmax)
setattr(self, "tz", tz)
self._register(alias, replace)
def __call__(self, value, __context=None):
if value is None and self.nullable:
return value
if not isinstance(value, (date, datetime)):
if isinstance(value, (int, float)) and self.unixts:
# Value will be arranged to ``self.tz`` below.
tz = None if self.tz is None else UTC
value = datetime.fromtimestamp(value, tz)
elif isinstance(value, basestr) and self.format is not None:
try:
value = datetime.strptime(value, self.format)
except ValueError:
raise exc.DatetimeParseError(expected=self.format, actual=value)
elif isinstance(value, basestr) and self.parser is not None:
try:
value = self.parser(value)
except ValueError:
raise exc.DatetimeParseError(expected=self.parser, actual=value)
else:
raise exc.InvalidTypeError(expected=date, actual=type(value))
if isinstance(value, datetime):
# Implicitly convert ``datetime`` to ``date``,
# but localize it first, if timezone info provided
if value.tzinfo is not None and self.tz is not None:
value = value.astimezone(self.tz)
value = value.date()
if self.min is not None and value < self.min:
raise exc.MinValueError(expected=self.min, actual=value)
if self.max is not None and value > self.max:
raise exc.MaxValueError(expected=self.max, actual=value)
if self.relmin is not None or self.relmax is not None:
today = date.today()
if self.relmin is not None and value < today + self.relmin:
raise exc.MinValueError(expected=today + self.relmin, actual=value)
if self.relmax is not None and value > today + self.relmax:
raise exc.MaxValueError(expected=today + self.relmax, actual=value)
return value
[docs]class Time(abstract.Validator):
"""
Time Validator
:param bool nullable:
accept ``None`` as a valid value.
:param str format:
try to parse ``time`` from ``str`` (Python 3.x)
or ``basestring`` (Python 2.x),
using ``datetime.strptime(value, self.format).time()``.
:param callable parser:
try to parse ``time`` from ``str`` (Python 3.x)
or ``basestring`` (Python 2.x),
using ``self.parser(value).time()``.
:param time min:
lower limit.
:param time max:
upper limit.
:raises InvalidTypeError:
* if ``value is None`` and ``not self.nullable``;
* if ``not isinstance(value, time)``.
:raises DatetimeParseError:
* if ``datetime.strptime(value, self.format)`` raises ``ValueError``;
* if ``self.parser(value)`` raises ``ValueError``.
:raises MinValueError:
if ``value < self.min``.
:raises MaxValueError:
if ``value > self.max``.
"""
__slots__ = ("nullable", "format", "parser", "min", "max")
def __init__(
self,
nullable=False,
format=None,
parser=None,
min=None,
max=None,
alias=None,
replace=False,
):
nullable = contracts.expect_flag(self, "nullable", nullable)
format = contracts.expect_basestr(self, "format", format, nullable=True)
parser = contracts.expect_callable(self, "parser", parser, nullable=True)
min = contracts.expect(self, "min", min, types=time, nullable=True)
max = contracts.expect(self, "max", max, types=time, nullable=True)
setattr = object.__setattr__
setattr(self, "nullable", nullable)
setattr(self, "format", format)
setattr(self, "parser", parser)
setattr(self, "min", min)
setattr(self, "max", max)
self._register(alias, replace)
def __call__(self, value, __context=None):
if value is None and self.nullable:
return value
if not isinstance(value, time):
if isinstance(value, basestr) and self.format is not None:
try:
value = datetime.strptime(value, self.format).time()
except ValueError:
raise exc.DatetimeParseError(expected=self.format, actual=value)
elif isinstance(value, basestr) and self.parser is not None:
try:
value = self.parser(value).time()
except ValueError:
raise exc.DatetimeParseError(expected=self.parser, actual=value)
else:
raise exc.InvalidTypeError(expected=time, actual=type(value))
if self.min is not None and value < self.min:
raise exc.MinValueError(expected=self.min, actual=value)
if self.max is not None and value > self.max:
raise exc.MaxValueError(expected=self.max, actual=value)
return value
[docs]class Datetime(abstract.Validator):
"""
Date & Time Validator
:param bool nullable:
accept ``None`` as a valid value.
:param bool unixts:
convert Unix timestamp (``int`` or ``float``) to ``datetime``.
:param str format:
try to parse ``datetime`` from ``str`` (Python 3.x)
or ``basestring`` (Python 2.x),
using ``datetime.strptime(value, self.format)``.
:param callable parser:
try to parse ``datetime`` from ``str`` (Python 3.x)
or ``basestring`` (Python 2.x),
using ``self.parser(value)``.
:param datetime min:
absolute lower limit.
:param datetime max:
absolute upper limit.
:param timedelta relmin:
relative lower limit.
:param timedelta relmax:
relative upper limit.
:param tzinfo tz:
timezone.
:raises InvalidTypeError:
* if ``value is None`` and ``not self.nullable``;
* if ``isinstance(value, (int, float))`` and ``not self.unixts``;
* if ``not isinstance(value, datetime)``.
:raises DatetimeParseError:
* if ``datetime.strptime(value, self.format)`` raises ``ValueError``;
* if ``self.parser(value)`` raises ``ValueError``.
:raises DatetimeTypeError:
* if ``self.tz is None and value.tzinfo is not None``;
* if ``self.tz is not None and value.tzinfo is None``;
:raises MinValueError:
* if ``value < self.min``;
* if ``self.tz is None and value < datetime.now() + self.relmin``.
* if ``self.tz is not None and value < datetime.now(UTC).astimezone(self.tz) + self.relmin``.
:raises MaxValueError:
* if ``value > self.max``;
* if ``self.tz is None and value > datetime.now() + self.relmax``.
* if ``self.tz is not None and value > datetime.now(UTC).astimezone(self.tz) + self.relmax``.
:note:
Relative limits are calculated adding deltas to midnight of current date,
use negative ``relmin/relmax`` to specify date and time in the past.
"""
__slots__ = (
"nullable",
"unixts",
"format",
"parser",
"min",
"max",
"relmin",
"relmax",
"tz",
)
def __init__(
self,
nullable=False,
unixts=False,
format=None,
parser=None,
min=None,
max=None,
relmin=None,
relmax=None,
tz=None,
alias=None,
replace=False,
):
nullable = contracts.expect_flag(self, "nullable", nullable)
unixts = contracts.expect_flag(self, "unixts", unixts)
format = contracts.expect_basestr(self, "format", format, nullable=True)
parser = contracts.expect_callable(self, "parser", parser, nullable=True)
min = contracts.expect(self, "min", min, types=datetime, nullable=True)
max = contracts.expect(self, "max", max, types=datetime, nullable=True)
relmin = contracts.expect(
self, "relmin", relmin, types=timedelta, nullable=True
)
relmax = contracts.expect(
self, "relmax", relmax, types=timedelta, nullable=True
)
tz = contracts.expect(self, "tz", tz, types=tzinfo, nullable=True)
if tz is not None:
if min is not None:
if min.tzinfo is None:
raise ValueError(
"%s.%s.min should be timezone-aware datetime object"
% (self.__class__.__module__, self.__class__.__name__)
)
min = min.astimezone(tz)
if max is not None:
if max.tzinfo is None:
raise ValueError(
"%s.%s.max should be timezone-aware datetime object"
% (self.__class__.__module__, self.__class__.__name__)
)
max = max.astimezone(tz)
else:
if min is not None and min.tzinfo is not None:
raise ValueError(
"%s.%s.min should be naive datetime object"
% (self.__class__.__module__, self.__class__.__name__)
)
if max is not None and max.tzinfo is not None:
raise ValueError(
"%s.%s.max should be naive datetime object"
% (self.__class__.__module__, self.__class__.__name__)
)
setattr = object.__setattr__
setattr(self, "nullable", nullable)
setattr(self, "unixts", unixts)
setattr(self, "format", format)
setattr(self, "parser", parser)
setattr(self, "min", min)
setattr(self, "max", max)
setattr(self, "relmin", relmin)
setattr(self, "relmax", relmax)
setattr(self, "tz", tz)
self._register(alias, replace)
def __call__(self, value, __context=None):
if value is None and self.nullable:
return value
if not isinstance(value, datetime):
if isinstance(value, date):
# Implicitly convert ``date`` to ``datetime``
value = datetime.combine(value, time(tzinfo=self.tz))
elif isinstance(value, (int, float)) and self.unixts:
# Value will be arranged to ``self.tz`` below.
tz = None if self.tz is None else UTC
value = datetime.fromtimestamp(value, tz)
elif isinstance(value, basestr) and self.format is not None:
try:
value = datetime.strptime(value, self.format)
except ValueError:
raise exc.DatetimeParseError(expected=self.format, actual=value)
elif isinstance(value, basestr) and self.parser is not None:
try:
value = self.parser(value)
except ValueError:
raise exc.DatetimeParseError(expected=self.parser, actual=value)
else:
raise exc.InvalidTypeError(expected=datetime, actual=type(value))
if self.tz is not None:
if value.tzinfo is None:
raise exc.DatetimeTypeError(expected="tzaware", actual=value)
value = value.astimezone(self.tz)
else:
if value.tzinfo is not None:
raise exc.DatetimeTypeError(expected="naive", actual=value)
if self.min is not None and value < self.min:
raise exc.MinValueError(expected=self.min, actual=value)
if self.max is not None and value > self.max:
raise exc.MaxValueError(expected=self.max, actual=value)
if self.relmin is not None or self.relmax is not None:
if self.tz is not None:
now = datetime.now(UTC).astimezone(self.tz)
else:
now = datetime.now()
if self.relmin is not None and value < now + self.relmin:
raise exc.MinValueError(expected=now + self.relmin, actual=value)
if self.relmax is not None and value > now + self.relmax:
raise exc.MaxValueError(expected=now + self.relmax, actual=value)
return value