Force aware time in python - python

Is there any way to force all time objects in Python
to be aware, perhaps generating an error if I try to
generate a naïve object?
Is there a way to tell whether a time object is naïve?
Trusting code to be non-naïve is not working for me
and I'm getting bitten.

To tell if a datetime object is aware check its tzinfo attribute, as per the datetime doc says:
A datetime object d is aware if both of the following hold:
d.tzinfo is not None
d.tzinfo.utcoffset(d) does not return None
Otherwise, d is naive.
A time object t is aware if both of the following hold:
t.tzinfo is not None
t.tzinfo.utcoffset(None) does not return None.
As to how to force all datetimes to not be naive, there is a problem. Sometimes one simply does not have the timezone info.

Why not override datetime.datetime.__new__ in a subclass?
Notice how the method datetime.datetime.__new__ checks the timezone argument before creating new instances by calling the module level function:
# cpython/Lib/datetime.py
def _check_tzinfo_arg(tz):
if tz is not None and not isinstance(tz, tzinfo):
raise TypeError("tzinfo argument must be None or of a tzinfo subclass")
So we can invoke a similar check before calling datetime.datetime.__new__ in our subclass
import datetime
class AwareDate(datetime.datetime):
def __new__(cls, *args, **kwargs):
if not isinstance(kwargs.get('tzinfo', None), datetime.tzinfo):
raise ValueError('MUST BE TZ AWARE !')
return datetime.datetime.__new__(cls, *args, **kwargs)
Some quick tests
normaldate = datetime.datetime(2020, 12, 20, tzinfo=datetime.timezone.utc)
awaredate = AwareDate(2020, 12, 20, tzinfo=datetime.timezone.utc)
assert awaredate == normaldate
normaldate = datetime.datetime(2020, 12, 20)
awaredate = AwareDate(2020, 12, 20) # ValueError: MUST BE TZ AWARE!

Related

python - property() in a Class use problem, setter shows TypeError

I'm coding a pybite on codechalleng.es and totally run out of ideas.
Problem is as follows:
task description
I need to write a class that returns boolean True/False whether the promotion has expired or not.
It's tested by invoking a property getter promotion.expired (see tests at the very end)
from datetime import datetime, timedelta
NOW = datetime.now() # defined here for test compatilibity (time)
class Promo:
""" docstring hehe"""
def __init__(self, name, expires):
self.name = name
self.setpromo(expires)
def checkpromo(self):
return self.expired
def setpromo(self, value):
self.expired = value < datetime.now()
expired = property(checkpromo, setpromo)
test_time = NOW + timedelta(days=1)
promotion_10 = Promo('promotion 10%', test_time)
Now it throws an error where i can't compare value in setter vs. datetime object. In my opinion as we pass test_time (a datetime object) into setter, it should be able to compare datetime vs. datetime - instead the program says it's a bool. Not sure how to proceed. Thank you for any insight.
Traceback (most recent call last):
File "/tmp/simple_property.py", line 24, in <module>
promotion_10 = Promo('promotion 10%', test_time)
File "/tmp/simple_property.py", line 10, in __init__
self.setpromo(expires)
File "/tmp/simple_property.py", line 17, in setpromo
self.expired = value < datetime.now()
File "/tmp/simple_property.py", line 17, in setpromo
self.expired = value < datetime.now()
TypeError: '<' not supported between instances of 'bool' and 'datetime.datetime'
tests that needs to pass:
from datetime import timedelta
import inspect
from simple_property import Promo, NOW
def test_promo_expired():
past_time = NOW - timedelta(seconds=3)
twitter_promo = Promo('twitter', past_time)
assert twitter_promo.expired
def test_promo_not_expired():
future_date = NOW + timedelta(days=1)
newsletter_promo = Promo('newsletter', future_date)
assert not newsletter_promo.expired
def test_uses_property():
assert 'property' in inspect.getsource(Promo)
I've finally found the issue - it might be interesting to someone experiencing such setter/property() behaviour. (Please correct me if im not supposed to answer my own question/close the topic etc.)
In my example, the property expired has the same name as private attribute self.expired.
After changing the code to (just adding one underscore in self._expired - changing the name so it does not overlap with property() name):
def checkpromo(self):
return self._expired
def setpromo(self, value):
self._expired = value < datetime.now()
It's worked! thank you
EDIT - adding elegant decorator way of solving the task (provided as an answer once submitted my task to codechalleng.es)
from datetime import datetime
NOW = datetime.now()
class Promo:
def __init__(self, name, expires=NOW):
self.name = name
self.expires = expires
#property
def expired(self):
return datetime.now() > self.expires

Mock datetime.datetime.now() while unit testing

#pytest.mark.parametrize("test_input,expected_output", data)
def test_send_email(test_input, expected_output):
emails = SendEmails(email_client=MagicMock())
emails.send_email = MagicMock()
emails.send_new_email(*test_input)
emails.send_email.assert_called_with(*expected_output)
I am looking to mock datetime.datetime.now() which is called in the send_new_email method. I am unsure how to do it however.
I tried creating a new datetime object
datetime_object = datetime.datetime.strptime('Jun 1 2017 1:33PM',
'%b %d %Y %I:%M%p')
and then overriding datetime.datetime.now
datetime.datetime.now = MagicMock(return_value=datetime_object)
However, I get the error
TypeError: can't set attributes of built-in/extension type 'datetime.datetime'
This question was marked as duplicate of Python: Trying to mock datetime.date.today() but not working
Python: Trying to mock datetime.date.today() but not working
I had already tried this solution but I could not get it to work. I cannot install freezegun due to the project requirements.
I have created a new class in the test file
class NewDate(datetime.date):
#classmethod
def today(cls):
return cls(2010, 1, 1)
datetime.date = NewDate
But I have no idea how to get the SendEmails class to use this.
You could replace the whole class:
_FAKE_TIME = 0
class _FakeDateTime(datetime.datetime):
#staticmethod
def now():
return _FAKE_TIME
then to use it:
_FAKE_TIME = whatever
datetime.datetime = _FakeDateTime
The class needs some refinements like comparison operators to make a FakeDateTime comparable to a datetime, but it should work.

How to create tzinfo when I have UTC offset?

I have one timezone's offset from UTC in seconds (19800) and also have it in string format - +0530.
How do I use them to create a tzinfo instance? I looked into pytz, but there I could only find APIs that take timezone name as input.
With Python 3.2 or higher, you can do this using the builtin datetime library:
import datetime
datetime.timezone(-datetime.timedelta(hours=5, minutes=30)
To solve your specific problem, you could use regex:
sign, hours, minutes = re.match('([+\-]?)(\d{2})(\d{2})', '+0530').groups()
sign = -1 if sign == '-' else 1
hours, minutes = int(hours), int(minute)
tzinfo = datetime.timezone(sign * datetime.timedelta(hours=hours, minutes=minutes))
datetime.datetime(2013, 2, 3, 9, 45, tzinfo=tzinfo)
If you can, take a look at the excellent dateutil package instead of implementing this yourself.
Specifically, tzoffset. It's a fixed offset tzinfo instance initialized with offset, given in seconds, which is what you're looking for.
Update
Here's an example. Be sure to run pip install python-dateutil first.
from datetime import datetime
from dateutil import tz
# First create the tzinfo object
tzlocal = tz.tzoffset('IST', 19800)
# Now add it to a naive datetime
local = naive.replace(tzinfo=tzlocal)
# Or convert another timezone to it
utcnow = datetime.utcnow().replace(tzinfo=tz.tzutc())
now = utcnow.astimezone(tzlocal)
I looked up the name IST from here. The name can really be anything. Just be careful if you deviate, since if you use code that relies on the name, it could lead to bugs later on.
By the way, if you have the timezone name upfront, and your operating system supports it, you can use gettz instead:
# Replace the above with this
tzlocal = tz.gettz('IST')
Python Standard Library (8.1.6) says that :
tzinfo is an abstract base class
the datetime module does not supply any concrete subclasses of tzinfo
you need to derive a concrete subclass, and (at least) supply implementations of the standard tzinfo methods needed by the datetime methods you use
a concrete subclass of tzinfo may need to implement the following methods ... If in doubt, simply implement all of them
tzinfo.utcoffset(self, dt) : return offset of local time from UTC, in minutes east of UTC
tzinfo.dst(self, dt) : return the daylight saving time (DST) adjustment, in minutes east of UTC, or None if DST information isn’t known
tzinfo.tzname(self, dt) : return the time zone name corresponding to the datetime object dt, as a string
All that means that you will have to provide your own implementation for the tzinfo. For example :
class UTC0530(datetime.tzinfo):
"""tzinfo derived concrete class named "+0530" with offset of 19800"""
# can be configured here
_offset = datetime.timedelta(seconds = 19800)
_dst = datetime.timedelta(0)
_name = "+0530"
def utcoffset(self, dt):
return self.__class__._offset
def dst(self, dt):
return self.__class__._dst
def tzname(self, dt):
return self.__class__._name
Usage :
tz = UTC0530()
d = datetime.datetime.now(tz)
d.isoformat()
output :
2015-01-27T20:19:41.257000+05:30
You have to implement subclass of datetime.tzinfo class. General guide is described here, where you also can find excellent examples of custom tzinfo implementations.
Here is example (given that there is no daylight saving time) :
from datetime import tzinfo, timedelta, datetime
from pytz import UTC
class MyUTCOffsetTimezone (tzinfo):
def __init__(self, offset=19800, name=None):
self.offset = timedelta(seconds=offset)
self.name = name or self.__class__.__name__
def utcoffset(self, dt):
return self.offset
def tzname(self, dt):
return self.name
def dst(self, dt):
return timedelta(0)
now = datetime.now(tz=UTC)
print now
# -> 2015-01-28 10:46:42.256841+00:00
print now.astimezone(MyUTCOffsetTimezone())
# -> 2015-01-28 16:16:42.256841+05:30
print datetime.now(MyUTCOffsetTimezone())
# -> 2015-01-28 16:16:42.256915+05:30
If you have pytz:
tz = pytz.FixedOffset(180)
now = timezone.now()
local_now = tz.normalize(now.astimezone(tz))
It's simple, only import datetime
>>> tz = datetime.timezone(datetime.timedelta(seconds=19800))
Next, you can, for example
>>> datetime.datetime.now(tz).isoformat(timespec='minutes')
'2021-08-03T18:07+05:30'
Based on the excellent answer from #Joe here, I wrote a function which monkey-patches pytz to support named timezones such as 'UTC-06:00' or 'UTC+11:30'. I can construct one of these names based on an offset sent to me from a browser, which only has an integer given by Javascript new Date().getTimezoneOffset() as described here and referenced here, and then I can post the name as a normal timezone name usable by the rest of my application which uses pytz.
This mechanism would also work for the op in this question who has an offset in seconds.
Example construct tzname using the offset the op has in this question:
minutes = offset // 60
tzname = 'UTC%s%02d:%02d' % (
'-' if minutes < 0 else '+',
abs(minutes) // 60, abs(minutes) % 60))
Example construct tzname using a browser timezone offset returned by
JavaScript new Date().getTimezoneOffset(), which of note has a reversed sign:
tzname = (
'UTC%s%02d:%02d' % (
'-' if browser_tz_offset > 0 else '+', # reverse sign
abs(browser_tz_offset) // 60, abs(browser_tz_offset) % 60))
Use the named zone to construct a tzinfo object:
from datetime import datetime
import pytz
tz = pytz.timezone(tzname) # tzname = e.g. 'UTC-06:00' or 'Europe/Madrid'
localized_now = datetime.now(tz)
I call this function during application startup.
import re
import pytz
from dateutil import tz as dateutil_tz
def post_load_pytz_offset_timezones_server_wide():
pristine_pytz_timezone = pytz.timezone
def extended_pytz_timezone(zone):
matches = re.match('^UTC([+-])([0-9][0-9]):([0-9][0-9])$', zone) if zone else None
if matches:
sign = -1 if matches.group(1) == '-' else 1
minutes = int(matches.group(2)) * 60 + int(matches.group(3))
tzinfo = dateutil_tz.tzoffset(zone, sign*minutes*60)
else:
tzinfo = pristine_pytz_timezone(zone)
return tzinfo
pytz.timezone = extended_pytz_timezone

Appengine datastore raising badValueErro on datetime, might be caused by monkeypatch

I'm getting the following error message:
BadValueError: Expected datetime, got datetime.datetime(2013, 4, 19, 19, 48, 2, 566558)
The error is raised in ndb's model.py line 1190, here's a snippet of code in there.
def _validate(self, value):
if not isinstance(value, datetime.datetime):
raise datastore_errors.BadValueError('Expected datetime, got %r' % (value,))
This DateTime is Passed into a model like this:
#user is a parameter that's passed into the function this code is inside
#the same goes for lifetime, which is a number of seconds
token = os.urandom(16).encode("hex")
expires=datetime.datetime.now() + datetime.timedelta(seconds=lifetime)
instance = cls(token=token, user=user, expires=expires)
However, I'm monkeypatching datetime.datetime using pytest's monkeypatch argument.
newnow = datetime.datetime.now()
later = newnow + datetime.timedelta(seconds=10)
class fake_dt(datetime.datetime):
#classmethod
def now(cls):
return newnow
monkeypatch.setattr(datetime,"datetime", fake_dt)
#after that, I call my model creation function for the test.
Could the fact that I'm monkeypatching datetime.datetime be the problem? and how do I get around that? I need to make sure that datetime.datetime.now() returns the date I captured at the start of my test.

Trying to mock datetime.date.today(), but not working

Can anyone tell me why this isn't working?
>>> import mock
>>> #mock.patch('datetime.date.today')
... def today(cls):
... return date(2010, 1, 1)
...
>>> from datetime import date
>>> date.today()
datetime.date(2010, 12, 19)
Perhaps someone could suggest a better way?
Another option is to use
https://github.com/spulec/freezegun/
Install it:
pip install freezegun
And use it:
from freezegun import freeze_time
#freeze_time("2012-01-01")
def test_something():
from datetime import datetime
print(datetime.now()) # 2012-01-01 00:00:00
from datetime import date
print(date.today()) # 2012-01-01
It also affects other datetime calls in method calls from other modules:
other_module.py:
from datetime import datetime
def other_method():
print(datetime.now())
main.py:
from freezegun import freeze_time
#freeze_time("2012-01-01")
def test_something():
import other_module
other_module.other_method()
And finally:
$ python main.py
# 2012-01-01
For what it's worth, the Mock docs talk about datetime.date.today specifically, and it's possible to do this without having to create a dummy class:
https://docs.python.org/3/library/unittest.mock-examples.html#partial-mocking
>>> from datetime import date
>>> with patch('mymodule.date') as mock_date:
... mock_date.today.return_value = date(2010, 10, 8)
... mock_date.side_effect = lambda *args, **kw: date(*args, **kw)
...
... assert mymodule.date.today() == date(2010, 10, 8)
... assert mymodule.date(2009, 6, 8) == date(2009, 6, 8)
...
There are a few problems.
First of all, the way you're using mock.patch isn't quite right. When used as a decorator, it replaces the given function/class (in this case, datetime.date.today) with a Mock object only within the decorated function. So, only within your today() will datetime.date.today be a different function, which doesn't appear to be what you want.
What you really want seems to be more like this:
#mock.patch('datetime.date.today')
def test():
datetime.date.today.return_value = date(2010, 1, 1)
print datetime.date.today()
Unfortunately, this won't work:
>>> test()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "build/bdist.macosx-10.6-universal/egg/mock.py", line 557, in patched
File "build/bdist.macosx-10.6-universal/egg/mock.py", line 620, in __enter__
TypeError: can't set attributes of built-in/extension type 'datetime.date'
This fails because Python built-in types are immutable - see this answer for more details.
In this case, I would subclass datetime.date myself and create the right function:
import datetime
class NewDate(datetime.date):
#classmethod
def today(cls):
return cls(2010, 1, 1)
datetime.date = NewDate
And now you could do:
>>> datetime.date.today()
NewDate(2010, 1, 1)
I guess I came a little late for this but I think the main problem here is that you're patching datetime.date.today directly and, according to the documentation, this is wrong.
You should patch the reference imported in the file where the tested function is, for example.
Let's say you have a functions.py file where you have the following:
import datetime
def get_today():
return datetime.date.today()
then, in your test, you should have something like this
import datetime
import unittest
from functions import get_today
from mock import patch, Mock
class GetTodayTest(unittest.TestCase):
#patch('functions.datetime')
def test_get_today(self, datetime_mock):
datetime_mock.date.today = Mock(return_value=datetime.strptime('Jun 1 2005', '%b %d %Y'))
value = get_today()
# then assert your thing...
Hope this helps a little bit.
Here's another way to mock datetime.date.today() with an added bonus that the rest of datetime functions continue to work, as the mock object is configured to wrap the original datetime module:
from unittest import mock, TestCase
import foo_module
class FooTest(TestCase):
#mock.patch(f'{foo_module.__name__}.datetime', wraps=datetime)
def test_something(self, mock_datetime):
# mock only datetime.date.today()
mock_datetime.date.today.return_value = datetime.date(2019, 3, 15)
# other calls to datetime functions will be forwarded to original datetime
Note the wraps=datetime argument to mock.patch() – when the foo_module uses other datetime functions besides date.today() they will be forwarded to the original wrapped datetime module.
To add to Daniel G's solution:
from datetime import date
class FakeDate(date):
"A manipulable date replacement"
def __new__(cls, *args, **kwargs):
return date.__new__(date, *args, **kwargs)
This creates a class which, when instantiated, will return a normal datetime.date object, but which is also able to be changed.
#mock.patch('datetime.date', FakeDate)
def test():
from datetime import date
FakeDate.today = classmethod(lambda cls: date(2010, 1, 1))
return date.today()
test() # datetime.date(2010, 1, 1)
The easiest way for me is doing this:
import datetime
from unittest.mock import Mock, patch
def test():
datetime_mock = Mock(wraps=datetime.datetime)
datetime_mock.now.return_value = datetime.datetime(1999, 1, 1)
with patch('datetime.datetime', new=datetime_mock):
assert datetime.datetime.now() == datetime.datetime(1999, 1, 1)
CAUTION for this solution: all functionality from datetime module from the target_module will stop working.
I faced the same situation a couple of days ago, and my solution was to define a function in the module to test and just mock that:
def get_date_now():
return datetime.datetime.now()
Today I found out about FreezeGun, and it seems to cover this case beautifully
from freezegun import freeze_time
import datetime
import unittest
#freeze_time("2012-01-14")
def test():
assert datetime.datetime.now() == datetime.datetime(2012, 1, 14)
You can use the following approach, based on Daniel G solution. This one has advantage of not breaking type checking with isinstance(d, datetime.date).
import mock
def fixed_today(today):
from datetime import date
class FakeDateType(type):
def __instancecheck__(self, instance):
return isinstance(instance, date)
class FakeDate(date):
__metaclass__ = FakeDateType
def __new__(cls, *args, **kwargs):
return date.__new__(date, *args, **kwargs)
#staticmethod
def today():
return today
return mock.patch("datetime.date", FakeDate)
Basically, we replace C-based datetime.date class with our own python subclass, that produces original datetime.date instances and responds to isinstance() queries exactly as native datetime.date.
Use it as context manager in your tests:
with fixed_today(datetime.date(2013, 11, 22)):
# run the code under test
# note, that these type checks will not break when patch is active:
assert isinstance(datetime.date.today(), datetime.date)
Similar approach can be used to mock datetime.datetime.now() function.
CPython actually implements the datetime module using both a pure-Python Lib/datetime.py and a C-optimized Modules/_datetimemodule.c. The C-optimized version cannot be patched but the pure-Python version can.
At the bottom of the pure-Python implementation in Lib/datetime.py is this code:
try:
from _datetime import * # <-- Import from C-optimized module.
except ImportError:
pass
This code imports all the C-optimized definitions and effectively replaces all the pure-Python definitions. We can force CPython to use the pure-Python implementation of the datetime module by doing:
import datetime
import importlib
import sys
sys.modules["_datetime"] = None
importlib.reload(datetime)
By setting sys.modules["_datetime"] = None, we tell Python to ignore the C-optimized module. Then we reload the module which causes the import from _datetime to fail. Now the pure-Python definitions remain and can be patched normally.
If you're using Pytest then include the snippet above in conftest.py and you can patch datetime objects normally.
We can use pytest-mock (https://pypi.org/project/pytest-mock/) mocker object to mock datetime behaviour in a particular module
Let's say you want to mock date time in the following file
# File path - source_dir/x/a.py
import datetime
def name_function():
name = datetime.now()
return f"name_{name}"
In the test function, mocker will be added to the function when test runs
def test_name_function(mocker):
mocker.patch('x.a.datetime')
x.a.datetime.now.return_value = datetime(2019, 1, 1)
actual = name_function()
assert actual == "name_2019-01-01"
Generally speaking, you would have datetime or perhaps datetime.date imported into a module somewhere. A more effective way of mocking the method would be to patch it on the module that is importing it. Example:
a.py
from datetime import date
def my_method():
return date.today()
Then for your test, the mock object itself would be passed as an argument to the test method. You would set up the mock with the result value you want, and then call your method under test. Then you would assert that your method did what you want.
>>> import mock
>>> import a
>>> #mock.patch('a.date')
... def test_my_method(date_mock):
... date_mock.today.return_value = mock.sentinel.today
... result = a.my_method()
... print result
... date_mock.today.assert_called_once_with()
... assert mock.sentinel.today == result
...
>>> test_my_method()
sentinel.today
A word of warning. It is most certainly possible to go overboard with mocking. When you do, it makes your tests longer, harder to understand, and impossible to maintain. Before you mock a method as simple as datetime.date.today, ask yourself if you really need to mock it. If your test is short and to the point and works fine without mocking the function, you may just be looking at an internal detail of the code you're testing rather than an object you need to mock.
It's possible to mock functions from datetime module without adding side_effects
import mock
from datetime import datetime
from where_datetime_used import do
initial_date = datetime.strptime('2018-09-27', "%Y-%m-%d")
with mock.patch('where_datetime_used.datetime') as mocked_dt:
mocked_dt.now.return_value = initial_date
do()
The best approach for me is a combination of #Daniel G and #frx08 solutions:
class Test_mock_date:
class NewDate(datetime.datetime):
#classmethod
def now(cls, tz=None):
return cls(2021, 5, 12)
def test_mock_date(self):
with patch('datetime.datetime', new = self.NewDate):
assert datetime.datetime.now() == datetime.datetime(2021, 5, 12, 0, 0)
You can take a look at the following medium article I wrote with different examples about how to use MagicMock https://medium.com/#camposer/d2113513b365
For those of you using pytest with pytest-mock (more info on pytest-mock at the end) here is how I mocked datetime.datetime.now() which is very similar to the original question.
test_get_now(mocker):
datetime_mock = mocker.patch("blackline_accounts_import.datetime",)
datetime_mock.datetime.now.return_value=datetime.datetime(2019,3,11,6,2,0,0)
now == function_being_tested() # run function
assert now == datetime.datetime(2019,3,11,6,2,0,0)
Essentially the mock has to be set to return the specified date. You aren't able to patch over datetime's object directly.
Pytest-mock is a library that makes a mock object a fixture. More detail can be found here
Maybe you could use your own "today()" method that you will patch where needed. Example with mocking utcnow() can be found here: https://bitbucket.org/k_bx/blog/src/tip/source/en_posts/2012-07-13-double-call-hack.rst?at=default
I implemented #user3016183 method using a custom decorator:
def changeNow(func, newNow = datetime(2015, 11, 23, 12, 00, 00)):
"""decorator used to change datetime.datetime.now() in the tested function."""
def retfunc(self):
with mock.patch('mymodule.datetime') as mock_date:
mock_date.now.return_value = newNow
mock_date.side_effect = lambda *args, **kw: datetime(*args, **kw)
func(self)
return retfunc
I thought that might help someone one day...
Several solutions are discussed in http://blog.xelnor.net/python-mocking-datetime/. In summary:
Mock object - Simple and efficient but breaks isinstance() checks:
target = datetime.datetime(2009, 1, 1)
with mock.patch.object(datetime, 'datetime', mock.Mock(wraps=datetime.datetime)) as patched:
patched.now.return_value = target
print(datetime.datetime.now())
Mock class
import datetime
import mock
real_datetime_class = datetime.datetime
def mock_datetime_now(target, dt):
class DatetimeSubclassMeta(type):
#classmethod
def __instancecheck__(mcs, obj):
return isinstance(obj, real_datetime_class)
class BaseMockedDatetime(real_datetime_class):
#classmethod
def now(cls, tz=None):
return target.replace(tzinfo=tz)
#classmethod
def utcnow(cls):
return target
# Python2 & Python3 compatible metaclass
MockedDatetime = DatetimeSubclassMeta('datetime', (BaseMockedDatetime,), {})
return mock.patch.object(dt, 'datetime', MockedDatetime)
Use as:
with mock_datetime_now(target, datetime):
....
I made this work by importing datetime as realdatetime and replacing the methods I needed in the mock with the real methods:
import datetime as realdatetime
#mock.patch('datetime')
def test_method(self, mock_datetime):
mock_datetime.today = realdatetime.today
mock_datetime.now.return_value = realdatetime.datetime(2019, 8, 23, 14, 34, 8, 0)
You can mock datetime using this:
In the module sources.py:
import datetime
class ShowTime:
def current_date():
return datetime.date.today().strftime('%Y-%m-%d')
In your tests.py:
from unittest import TestCase, mock
import datetime
class TestShowTime(TestCase):
def setUp(self) -> None:
self.st = sources.ShowTime()
super().setUp()
#mock.patch('sources.datetime.date')
def test_current_date(self, date_mock):
date_mock.today.return_value = datetime.datetime(year=2019, month=10, day=1)
current_date = self.st.current_date()
self.assertEqual(current_date, '2019-10-01')
For those using patchers in a test class, here's how I'm successfully patching datetime functionality:
from datetime import datetime
import unittest
from unittest.mock import Mock, patch
# Replace with the proper path to the module you would
# like datetime to be mocked
from path.to.my_module
class MyTestCases(unittest.TestCase):
def setUp(self):
"""execute on class instantiation"""
# Record both times at the same moment
self.dt_now, self.dt_utcnow = datetime.now(), datetime.utcnow()
# After retrieving real (or hardcoded datetime values),
# proceed to mock them in desired module
self.patch_datetime_functions()
def patch_datetime_functions(self) -> None:
"""
Patch datetime.now() and datetime.utcnow() to prevent issues when
comparing expected dates
"""
# Create a patcher
self.patcher_dt = patch(
'path.to.my_module'
)
# Start but make sure cleanup always occurs
self.patcher_dt.start()
self.addCleanup(self.patcher_dt.stop)
# Perform the actual patch – use lambdas as mock functions
datetime_mock = Mock(wraps=datetime)
datetime_mock.now.return_value = self.dt_now
datetime_mock.utcnow.return_value = self.dt_utcnow
my_module.datetime = datetime_mock
# Here's what it will look like when testing:
def some_test(self):
curr_dt = self.dt_now
returned_dt = my_module.datetime.utcnow()
# Compare the dates
self.assertEqual(curr_dt, returned_dt,
'Datetime values should be equal'
)
Minimal working example with monkeypatch
This solution uses monkeypatch from the https://pypi.org/project/pytest-mock/ package.
Features:
mock only datetime.today(), but datetime.now() still works as expected
mock only in a specific scope (i.e. block)
import sys
from datetime import datetime
MOCKED_DATETIME_TODAY = datetime(1900, 1, 1, 0, 0, 0)
class MockedDatetime(datetime):
#classmethod
def today(cls):
return MOCKED_DATETIME_TODAY
def test_mock_datetime_today(monkeypatch):
"""Only datetime.today() is mocked and returns some date in 1900. datetime.now() returns still the current date."""
with monkeypatch.context() as mpc:
mpc.setattr(sys.modules[__name__], 'datetime', MockedDatetime)
assert datetime.today() == MOCKED_DATETIME_TODAY # datetime.today() mocked
assert datetime.now() > MOCKED_DATETIME_TODAY # datetime.now() not mocked
assert datetime.today() > MOCKED_DATETIME_TODAY # not mocked anymore

Categories