Viewing file: datetime.py (24.42 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
from calendar import timegm from datetime import datetime, timedelta, date, tzinfo as TZInfo from typing import Optional, Callable, Type, Tuple, Dict, Any, cast, overload
class Queue(list):
delta: float delta_delta: float delta_type: str
def __init__(self, delta: Optional[float], delta_delta: float, delta_type: str): super().__init__() if delta is None: self.delta = 0 self.delta_delta = delta_delta else: self.delta = delta self.delta_delta = 0 self.delta_type = delta_type
def advance_next(self, delta: timedelta) -> None: self[-1] += delta
def next(self) -> 'MockedCurrent': instance = self.pop(0) if not self: self.delta += self.delta_delta n = instance + timedelta(**{self.delta_type: self.delta}) self.append(n) return instance
class MockedCurrent:
_mock_queue: Queue _mock_base_class: Type _mock_class: Type _mock_tzinfo: Optional[TZInfo] _mock_date_type: Type[date] _correct_mock_type: Callable = None
def __init_subclass__( cls, concrete: bool = False, queue: Queue = None, strict: bool = None, tzinfo: TZInfo = None, date_type: Type[date] = None ): if concrete: cls._mock_queue = queue cls._mock_base_class = cls.__bases__[0].__bases__[1] cls._mock_class = cls if strict else cls._mock_base_class cls._mock_tzinfo = tzinfo cls._mock_date_type = date_type
@classmethod def add(cls, *args, **kw): if 'tzinfo' in kw or len(args) > 7: raise TypeError('Cannot add using tzinfo on %s' % cls.__name__) if args and isinstance(args[0], cls._mock_base_class): instance = args[0] instance_tzinfo = getattr(instance, 'tzinfo', None) if instance_tzinfo: if instance_tzinfo != cls._mock_tzinfo: raise ValueError( 'Cannot add %s with tzinfo of %s as configured to use %s' % ( instance.__class__.__name__, instance_tzinfo, cls._mock_tzinfo )) instance = instance.replace(tzinfo=None) if cls._correct_mock_type: instance = cls._correct_mock_type(instance) else: instance = cls(*args, **kw) cls._mock_queue.append(instance)
@classmethod def set(cls, *args, **kw) -> None: cls._mock_queue.clear() cls.add(*args, **kw)
@classmethod def tick(cls, *args, **kw) -> None: if kw: delta = timedelta(**kw) else: delta, = args cls._mock_queue.advance_next(delta)
def __add__(self, other) -> 'MockedCurrent': instance = super().__add__(other) if self._correct_mock_type: instance = self._correct_mock_type(instance) return instance
def __new__(cls, *args, **kw): if cls is cls._mock_class: return super().__new__(cls, *args, **kw) else: return cls._mock_class(*args, **kw)
def mock_factory( type_name: str, mock_class: Type[MockedCurrent], default: Tuple[int, ...], args: tuple, kw: Dict[str, Any], delta: Optional[float], delta_type: str, delta_delta: float = 1, date_type: Type[date] = None, tzinfo: TZInfo = None, strict: bool = False ): cls = cast(Type[MockedCurrent], type( type_name, (mock_class,), {}, concrete=True, queue=Queue(delta, delta_delta, delta_type), strict=strict, tzinfo=tzinfo, date_type=date_type, ))
if args != (None,): if not (args or kw): args = default cls.add(*args, **kw)
return cls
class MockDateTime(MockedCurrent, datetime):
@overload @classmethod def add( cls, year: int, month: int, day: int, hour: int = ..., minute: int = ..., second: int = ..., microsecond: int = ..., tzinfo: TZInfo = ..., ) -> None: ...
@overload @classmethod def add( cls, instance: datetime, ) -> None: ...
@classmethod def add(cls, *args, **kw): """ This will add the :class:`datetime.datetime` created from the supplied parameters to the queue of datetimes to be returned by :meth:`~MockDateTime.now` or :meth:`~MockDateTime.utcnow`. An instance of :class:`~datetime.datetime` may also be passed as a single positional argument. """ return super().add(*args, **kw)
@overload @classmethod def set( cls, year: int, month: int, day: int, hour: int = ..., minute: int = ..., second: int = ..., microsecond: int = ..., tzinfo: TZInfo = ..., ) -> None: ...
@overload @classmethod def set( cls, instance: datetime, ) -> None: ...
@classmethod def set(cls, *args, **kw): """ This will set the :class:`datetime.datetime` created from the supplied parameters as the next datetime to be returned by :meth:`~MockDateTime.now` or :meth:`~MockDateTime.utcnow`, clearing out any datetimes in the queue. An instance of :class:`~datetime.datetime` may also be passed as a single positional argument. """ return super().set(*args, **kw)
@overload @classmethod def tick( cls, days: float = ..., seconds: float = ..., microseconds: float = ..., milliseconds: float = ..., minutes: float = ..., hours: float = ..., weeks: float = ..., ) -> None: ...
@overload @classmethod def tick( cls, delta: timedelta, # can become positional-only when Python 3.8 minimum ) -> None: ...
@classmethod def tick(cls, *args, **kw) -> None: """ This method should be called either with a :class:`~datetime.timedelta` as a positional argument, or with keyword parameters that will be used to construct a :class:`~datetime.timedelta`.
The :class:`~datetime.timedelta` will be used to advance the next datetime to be returned by :meth:`~MockDateTime.now` or :meth:`~MockDateTime.utcnow`. """ return super().tick(*args, **kw)
@classmethod def _correct_mock_type(cls, instance): return cls._mock_class( instance.year, instance.month, instance.day, instance.hour, instance.minute, instance.second, instance.microsecond, instance.tzinfo, )
@classmethod def now(cls, tz: TZInfo = None) -> datetime: """ :param tz: An optional timezone to apply to the returned time. If supplied, it must be an instance of a :class:`~datetime.tzinfo` subclass.
This will return the next supplied or calculated datetime from the internal queue, rather than the actual current datetime.
If `tz` is supplied, see :ref:`timezones`. """ instance = cast(datetime, cls._mock_queue.next()) if tz is not None: if cls._mock_tzinfo: instance = instance - cls._mock_tzinfo.utcoffset(instance) instance = tz.fromutc(instance.replace(tzinfo=tz)) return cls._correct_mock_type(instance)
@classmethod def utcnow(cls) -> datetime: """ This will return the next supplied or calculated datetime from the internal queue, rather than the actual current UTC datetime.
If you care about timezones, see :ref:`timezones`. """ instance = cast(datetime, cls._mock_queue.next()) if cls._mock_tzinfo is not None: instance = instance - cls._mock_tzinfo.utcoffset(instance) return instance
def date(self) -> date: """ This will return the date component of the current mock instance, but using the date type supplied when the mock class was created. """ return self._mock_date_type( self.year, self.month, self.day )
@overload def mock_datetime( tzinfo: TZInfo = None, delta: float = None, delta_type: str = 'seconds', date_type: Type[date] = date, strict: bool = False ) -> Type[MockDateTime]: ...
@overload def mock_datetime( year: int, month: int, day: int, hour: int = ..., minute: int = ..., second: int = ..., microsecond: int = ..., tzinfo: TZInfo = None, delta: float = None, delta_type: str = 'seconds', date_type: Type[date] = date, strict: bool = False ) -> Type[MockDateTime]: ...
@overload def mock_datetime( default: datetime, tzinfo: TZInfo = None, delta: float = None, delta_type: str = 'seconds', date_type: Type[date] = date, strict: bool = False ) -> Type[MockDateTime]: ...
@overload def mock_datetime( default: None, # explicit None positional tzinfo: TZInfo = None, delta: float = None, delta_type: str = 'seconds', date_type: Type[date] = date, strict: bool = False ) -> Type[MockDateTime]: ...
def mock_datetime( *args, tzinfo: TZInfo = None, delta: float = None, delta_type: str = 'seconds', date_type: Type[date] = date, strict: bool = False, **kw, ) -> Type[MockDateTime]: """ .. currentmodule:: testfixtures.datetime
A function that returns a mock object that can be used in place of the :class:`datetime.datetime` class but where the return value of :meth:`~MockDateTime.now` can be controlled.
If a single positional argument of ``None`` is passed, then the queue of datetimes to be returned will be empty and you will need to call :meth:`~MockDateTime.set` or :meth:`~MockDateTime.add` before calling :meth:`~MockDateTime.now` or :meth:`~MockDateTime.utcnow`.
If an instance of :class:`~datetime.datetime` is passed as a single positional argument, that will be used as the first date returned by :meth:`~MockDateTime.now`
:param year: An optional year used to create the first datetime returned by :meth:`~MockDateTime.now`.
:param month: An optional month used to create the first datetime returned by :meth:`~MockDateTime.now`.
:param day: An optional day used to create the first datetime returned by :meth:`~MockDateTime.now`.
:param hour: An optional hour used to create the first datetime returned by :meth:`~MockDateTime.now`.
:param minute: An optional minute used to create the first datetime returned by :meth:`~MockDateTime.now`.
:param second: An optional second used to create the first datetime returned by :meth:`~MockDateTime.now`.
:param microsecond: An optional microsecond used to create the first datetime returned by :meth:`~MockDateTime.now`.
:param tzinfo: An optional :class:`datetime.tzinfo`, see :ref:`timezones`.
:param delta: The size of the delta to use between values returned from mocked class methods. If not specified, it will increase by 1 with each call to :meth:`~MockDateTime.now`.
:param delta_type: The type of the delta to use between values returned from mocked class methods. This can be any keyword parameter accepted by the :class:`~datetime.timedelta` constructor.
:param date_type: The type to use for the return value of the mocked class methods. This can help with gotchas that occur when type checking is performed on values returned by the :meth:`~testfixtures.datetime.MockDateTime.date` method.
:param strict: If ``True``, calling the mock class and any of its methods will result in an instance of the mock being returned. If ``False``, the default, an instance of :class:`~datetime.datetime` will be returned instead.
The mock returned will behave exactly as the :class:`datetime.datetime` class as well as being a subclass of :class:`~testfixtures.datetime.MockDateTime`. """ if len(args) > 7: tzinfo = args[7] args = args[:7] else: tzinfo = tzinfo or (getattr(args[0], 'tzinfo', None) if args else None) return cast(Type[MockDateTime], mock_factory( 'MockDateTime', MockDateTime, (2001, 1, 1, 0, 0, 0), args, kw, tzinfo=tzinfo, delta=delta, delta_delta=10, delta_type=delta_type, date_type=date_type, strict=strict, ))
class MockDate(MockedCurrent, date):
@classmethod def _correct_mock_type(cls, instance): return cls._mock_class( instance.year, instance.month, instance.day, )
@overload @classmethod def add( cls, year: int, month: int, day: int, ) -> None: ...
@overload @classmethod def add( cls, instance: date, ) -> None: ...
@classmethod def add(cls, *args, **kw): """ This will add the :class:`datetime.date` created from the supplied parameters to the queue of dates to be returned by :meth:`~MockDate.today`. An instance of :class:`~datetime.date` may also be passed as a single positional argument. """ return super().add(*args, **kw)
@overload @classmethod def set( cls, year: int, month: int, day: int, ) -> None: ...
@overload @classmethod def set( cls, instance: date, ) -> None: ...
@classmethod def set(cls, *args, **kw) -> None: """ This will set the :class:`datetime.date` created from the supplied parameters as the next date to be returned by :meth:`~MockDate.today`, regardless of any dates in the queue. An instance of :class:`~datetime.date` may also be passed as a single positional argument. """ return super().set(*args, **kw)
@overload @classmethod def tick( cls, days: float = ..., weeks: float = ..., ) -> None: ...
@overload @classmethod def tick( cls, delta: timedelta, # can become positional-only when Python 3.8 minimum ) -> None: ...
@classmethod def tick(cls, *args, **kw) -> None: """ This method should be called either with a :class:`~datetime.timedelta` as a positional argument, or with keyword parameters that will be used to construct a :class:`~datetime.timedelta`.
The :class:`~datetime.timedelta` will be used to advance the next date to be returned by :meth:`~MockDate.today`. """ return super().tick(*args, **kw)
@classmethod def today(cls) -> date: """ This will return the next supplied or calculated date from the internal queue, rather than the actual current date.
""" return cast(date, cls._mock_queue.next())
@overload def mock_date( delta: float = None, delta_type: str = None, date_type: Type[date] = date, strict: bool = False ) -> Type[MockDate]: ...
@overload def mock_date( year: int, month: int, day: int, delta: float = None, delta_type: str = 'days', strict: bool = False, ) -> Type[MockDate]: ...
@overload def mock_date( default: date, delta: float = None, delta_type: str = 'days', strict: bool = False, ) -> Type[MockDate]: ...
@overload def mock_date( default: None, # explicit None positional delta: float = None, delta_type: str = 'days', strict: bool = False, ) -> Type[MockDate]: ...
def mock_date( *args, delta: float = None, delta_type: str = 'days', strict: bool = False, **kw ) -> Type[MockDate]: """ .. currentmodule:: testfixtures.datetime
A function that returns a mock object that can be used in place of the :class:`datetime.date` class but where the return value of :meth:`~datetime.date.today` can be controlled.
If a single positional argument of ``None`` is passed, then the queue of dates to be returned will be empty and you will need to call :meth:`~MockDate.set` or :meth:`~MockDate.add` before calling :meth:`~MockDate.today`.
If an instance of :class:`~datetime.date` is passed as a single positional argument, that will be used as the first date returned by :meth:`~datetime.date.today`
:param year: An optional year used to create the first date returned by :meth:`~datetime.date.today`.
:param month: An optional month used to create the first date returned by :meth:`~datetime.date.today`.
:param day: An optional day used to create the first date returned by :meth:`~datetime.date.today`.
:param delta: The size of the delta to use between values returned from :meth:`~datetime.date.today`. If not specified, it will increase by 1 with each call to :meth:`~datetime.date.today`.
:param delta_type: The type of the delta to use between values returned from :meth:`~datetime.date.today`. This can be any keyword parameter accepted by the :class:`~datetime.timedelta` constructor.
:param strict: If ``True``, calling the mock class and any of its methods will result in an instance of the mock being returned. If ``False``, the default, an instance of :class:`~datetime.date` will be returned instead.
The mock returned will behave exactly as the :class:`datetime.date` class as well as being a subclass of :class:`~testfixtures.datetime.MockDate`. """ return cast(Type[MockDate], mock_factory( 'MockDate', MockDate, (2001, 1, 1), args, kw, delta=delta, delta_type=delta_type, strict=strict, ))
ms = 10**6
class MockTime(MockedCurrent, datetime):
@overload @classmethod def add( cls, year: int, month: int, day: int, hour: int = ..., minute: int = ..., second: int = ..., microsecond: int = ..., ) -> None: ...
@overload @classmethod def add( cls, instance: datetime, ) -> None: ...
@classmethod def add(cls, *args, **kw): """ This will add the time specified by the supplied parameters to the queue of times to be returned by calls to the mock. The parameters are the same as the :class:`datetime.datetime` constructor. An instance of :class:`~datetime.datetime` may also be passed as a single positional argument. """ return super().add(*args, **kw)
@overload @classmethod def set( cls, year: int, month: int, day: int, hour: int = ..., minute: int = ..., second: int = ..., microsecond: int = ..., ) -> None: ...
@overload @classmethod def set( cls, instance: datetime, ) -> None: ...
@classmethod def set(cls, *args, **kw): """ This will set the time specified by the supplied parameters as the next time to be returned by a call to the mock, regardless of any times in the queue. The parameters are the same as the :class:`datetime.datetime` constructor. An instance of :class:`~datetime.datetime` may also be passed as a single positional argument. """ return super().set(*args, **kw)
@overload @classmethod def tick( cls, days: float = ..., seconds: float = ..., microseconds: float = ..., milliseconds: float = ..., minutes: float = ..., hours: float = ..., weeks: float = ..., ) -> None: ...
@overload @classmethod def tick( cls, delta: timedelta, # can become positional-only when Python 3.8 minimum ) -> None: ...
@classmethod def tick(cls, *args, **kw): """ This method should be called either with a :class:`~datetime.timedelta` as a positional argument, or with keyword parameters that will be used to construct a :class:`~datetime.timedelta`.
The :class:`~datetime.timedelta` will be used to advance the next time to be returned by a call to the mock. """ return super().tick(*args, **kw)
def __new__(cls, *args, **kw) -> float: """ Return a :class:`float` representing the mocked current time as would normally be returned by :func:`time.time`. """ if args or kw: # Used when adding stuff to the queue return super().__new__(cls, *args, **kw) else: instance = cast(datetime, cls._mock_queue.next()) time = timegm(instance.utctimetuple()) time += (float(instance.microsecond)/ms) return time
@overload def mock_time( delta: float = None, delta_type: str = 'seconds', ) -> Type[MockDateTime]: ...
@overload def mock_time( year: int, month: int, day: int, hour: int = ..., minute: int = ..., second: int = ..., microsecond: int = ..., delta: float = None, delta_type: str = 'seconds', ) -> Type[MockDateTime]: ...
@overload def mock_time( default: datetime, delta: float = None, delta_type: str = 'seconds', ) -> Type[MockDateTime]: ...
@overload def mock_time( default: None, # explicit None positional delta: float = None, delta_type: str = 'seconds', ) -> Type[MockDateTime]: ...
def mock_time(*args, delta: float = None, delta_type: str = 'seconds', **kw) -> Type[MockTime]: """ .. currentmodule:: testfixtures.datetime
A function that returns a :class:`mock object <testfixtures.datetime.MockTime>` that can be used in place of the :func:`time.time` function but where the return value can be controlled.
If a single positional argument of ``None`` is passed, then the queue of times to be returned will be empty and you will need to call :meth:`~MockTime.set` or :meth:`~MockTime.add` before calling the mock.
If an instance of :class:`~datetime.datetime` is passed as a single positional argument, that will be used to create the first time returned.
:param year: An optional year used to create the first time returned.
:param month: An optional month used to create the first time.
:param day: An optional day used to create the first time.
:param hour: An optional hour used to create the first time.
:param minute: An optional minute used to create the first time.
:param second: An optional second used to create the first time.
:param microsecond: An optional microsecond used to create the first time.
:param delta: The size of the delta to use between values returned. If not specified, it will increase by 1 with each call to the mock.
:param delta_type: The type of the delta to use between values returned. This can be any keyword parameter accepted by the :class:`~datetime.timedelta` constructor.
The :meth:`~testfixtures.datetime.MockTime.add`, :meth:`~testfixtures.datetime.MockTime.set` and :meth:`~testfixtures.datetime.MockTime.tick` methods on the mock can be used to control the return values. """ if 'tzinfo' in kw or len(args) > 7 or (args and getattr(args[0], 'tzinfo', None)): raise TypeError("You don't want to use tzinfo with test_time") return cast(Type[MockTime], mock_factory( 'MockTime', MockTime, (2001, 1, 1, 0, 0, 0), args, kw, delta=delta, delta_type=delta_type, ))
|