GildedRose-Refactoring-Kata/.venv/lib/python3.12/site-packages/testfixtures/datetime.py
2025-06-22 13:36:01 +05:30

859 lines
24 KiB
Python

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[MockTime]:
...
@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[MockTime]:
...
@overload
def mock_time(
default: datetime,
delta: float = None,
delta_type: str = 'seconds',
) -> Type[MockTime]:
...
@overload
def mock_time(
default: None, # explicit None positional
delta: float = None,
delta_type: str = 'seconds',
) -> Type[MockTime]:
...
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,
))