unittest Mock - patch return value - python

I think I misunderstand how to use mocks for changing a function return value. Here is my test :
from path.to import programme_finder
#patch('path.to.programme_finder._calc_update_interval')
def test_refresh_interval(self,method_mock):
today = datetime.datetime.now()
dd = datetime.timedelta(millisecond=20)
earlier_date = today - dd
#The normal function returns a 5 day interval.
# For tests I want it down to 20ms
method_mock.return_value = earlier_date
#Here I would expect a date object, instead I get MagicMock
print("Calc returns %s " % programme_finder._calc_update_interval)
# rest of the test irrelevant
self.fail("need to time responce time")
What am I doing wrong ? How do I get the programme_finder._calc_update_interval to return my patched datetime?
Tried
assert programme_finder._calc_update_interval == earlier_date
as well and it fails.
#programme_finder.py
def _calc_update_interval():
today = datetime.datetime.now()
# The days we want to subtract for today.
update_interval = current_app.config.get("RESOURCE_UPDATE_INTERVAL")
dd = datetime.timedelta(days=update_interval)
# Find which date it was x days ago
earlier_date = today - dd
return earlier_date

It looks to me like you're not calling the function -- you're referencing it by name, so you get back the mocked function instead of your mock return value.
print("Calc returns %s " % programme_finder._calc_update_interval )
^reference
Should be
print("Calc returns %s " % programme_finder._calc_update_interval() )
^^call

The problem seems to be that _calc_update_interval is a property (I'm guessing created with the #property decorator) and not a method. The easiest approach is to simply use the PropertyMock class provided with mock (Documented here):
#patch('path.to.programme_finder._calc_update_interval', new_callable=PropertyMock):
def test_refresh_interval(self,method_mock):
#...

Related

freeze_time doesn't work inside invoked functions

# test.py
from freezegun import freeze_time
from actual import DummyClass
class DummyClassMock(DummyClass):
def some_function(self):
# does something
class TestClass():
def setUp(self):
self.dummy = DummyClassMock()
#freeze_time('2021-01-01')
def test_dummy_function(self):
self.assertTrue((self.dummy.dummy_function - datetime.utcnow()).total_seconds() >= 1)
# actual.py
from datetime import datetime, timedelta
class DummyClass():
def dummy_function(self):
return datetime.utcnow() + timedelta(5)
My code goes along the above structure. With this, if I am executing the test_dummy_function individually, dummy_function is returning 2021-01-01 and test case is a pass. However, when I running this along with all the other test cases in the project, it is failing. Content is not dependent either.
Not particularly a good solution but, the workaround I used was to define a function that would just return datetime.utcnow() and mock it. My test case will assign the date used in freeze_time as return value. It looks something like,
#mock.patch(actual.DummyClass.now_function)
#freeze_time('2021-01-01')
def test_dummy_function(self, mock_dt):
now = datetime.utcnow()
mock_dt.return_value = now
self.assertTrue((self.dummy.dummy_function - now).total_seconds() >= 1)
# actual.py
Class DummyClass():
def now_function():
return datetime.utcnow()
def dummy_function():
return now_function()+timedelta(days=5)

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

python list mysteriously getting set to something within my django/piston handler

Note: (I've updated this since the first two suggestions... you can view the old post in txt form here: http://bennyland.com/old-2554127.txt). The update I made was to better understand what was going wrong - and now I at least sort of know what's happening but I have no clue how to fix it.
Anyway, using Django and Piston, I've set up a new BaseHandler class named BaseApiHandler which does most of the work I was doing across all of my handlers. This worked great until I added the ability to limit the filters being applied to my results (for instance 'give me the first result only').
Examples (had to remove ":" because i can't submit more urls):
- http//localhost/api/hours_detail/empid/22 gives me all hours_detail rows from employee # 22
- http//localhost/api/hours_detail/empid/22/limit/first gives me the first hours_detail row from employee # 22
What's happening is that when I run /limit/first several times in succession, the first example is then broken, pretending it's a /limit/ url when it isn't.
Right now I'm storing whether or not it's a limit and what the limit is in a new class - prior to this stackoverflow edit, I was just using a list with two entries (limit = [] when initialized, limit = [0,1] when set). Prior to this stackoverflow edit, once you spammed /limit/first, when going to the first example 'limit' would be pre-set to [0,1] and the handler would then limit the query because of this. With the debug data I've added, I can say for certain that the list was pre-set and not getting set during the execution of the code.
I'm adding debug info into my response so I can see what's happening. Right now when you first ask for Example 1's url, you get this CORRECT statusmsg response:
"statusmsg": "2 hours_detail found with query: {'empid':'22','datestamp':'2009-03-02',}",
When you ask for Example 2's url, you get this CORRECT statusmsg response:
"statusmsg": "1 hours_detail found with query: {'empid':'22','datestamp':'2009-03-02','limit','first',with limit[0,1](limit,None... limit set 1 times),}",
However, if you refresh a bunch of times, the limit set value starts increasing (incrementing this value was something a friend of mine suggested to see if this variable was somehow getting kept around)
"statusmsg": "1 hours_detail found with query: {'empid':'22','datestamp':'2009-03-02','limit','first',with limit[0,1](limit,None... limit set 10 times),}",
Once that number goes above '1 times', you can start trying to get Example 1's url. Each time I now refresh example 1, i get odd results. Here are 3 different status messages from different refreshes (Notice that from each one, 'limit':'first' is CORRECTLY missing from the kwarg's debug output while the actual value of islimit is hovering between 8 and 10):
"statusmsg": "1 hours_detail found with query: {'empid':'22','datestamp':'2009-03-02',with limit[0,1](limit,None... limit set 10 times),}",
"statusmsg": "1 hours_detail found with query: {'empid':'22','datestamp':'2009-03-02',with limit[0,1](limit,None... limit set 8 times),}",
"statusmsg": "1 hours_detail found with query: {'empid':'22','datestamp':'2009-03-02',with limit[0,1](limit,None... limit set 9 times),}",
So it would appear that this object is getting cached. Prior to changing 'limit' form a list to a class, it also appeared that the list version of 'limit' was getting cached as after going to Example 2's url, i would sometimes have [0,1] as the limit.
Here are the updated snippets of the code (remember, you can view the first post here: bennyland.com/old-2554127.txt)
URLS.PY - inside 'urlpatterns = patterns('
#hours_detail/id/{id}/empid/{empid}/projid/{projid}/datestamp/{datestamp}/daterange/{fromdate}to{todate}
#empid is required
url(r'^api/hours_detail/(?:' + \
r'(?:[/]?id/(?P<id>\d+))?' + \
r'(?:[/]?empid/(?P<empid>\d+))?' + \
r'(?:[/]?projid/(?P<projid>\d+))?' + \
r'(?:[/]?datestamp/(?P<datestamp>\d{4,}[-/\.]\d{2,}[-/\.]\d{2,}))?' + \
r'(?:[/]?daterange/(?P<daterange>(?:\d{4,}[-/\.]\d{2,}[-/\.]\d{2,})(?:to|/-)(?:\d{4,}[-/\.]\d{2,}[-/\.]\d{2,})))?' + \
r')+' + \
r'(?:/limit/(?P<limit>(?:first|last)))?' + \
r'(?:/(?P<exact>exact))?$', hours_detail_resource),
HANDLERS.PY
class ResponseLimit(object):
def __init__(self):
self._min = 0
self._max = 0
self._islimit = 0
#property
def min(self):
if self.islimit == 0:
raise LookupError("trying to access min when no limit has been set")
return self._min
#property
def max(self):
if self.islimit == 0:
raise LookupError("trying to access max when no limit has been set")
return self._max
#property
def islimit(self):
return self._islimit
def setlimit(self, min, max):
self._min = min
self._max = max
# incrementing _islimit instead of using a bool so I can try and see why it's broken
self._islimit += 1
class BaseApiHandler(BaseHandler):
limit = ResponseLimit()
def __init__(self):
self._post_name = 'base'
#property
def post_name(self):
return self._post_name
#post_name.setter
def post_name(self, value):
self._post_name = value
def process_kwarg_read(self, key, value, d_post, b_exact):
"""
this should be overridden in the derived classes to process kwargs
"""
pass
# override 'read' so we can better handle our api's searching capabilities
def read(self, request, *args, **kwargs):
d_post = {'status':0,'statusmsg':'Nothing Happened'}
try:
# setup the named response object
# select all employees then filter - querysets are lazy in django
# the actual query is only done once data is needed, so this may
# seem like some memory hog slow beast, but it's actually not.
d_post[self.post_name] = self.queryset(request)
s_query = ''
b_exact = False
if 'exact' in kwargs and kwargs['exact'] <> None:
b_exact = True
s_query = '\'exact\':True,'
for key,value in kwargs.iteritems():
# the regex url possibilities will push None into the kwargs dictionary
# if not specified, so just continue looping through if that's the case
if value is None or key == 'exact':
continue
# write to the s_query string so we have a nice error message
s_query = '%s\'%s\':\'%s\',' % (s_query, key, value)
# now process this key/value kwarg
self.process_kwarg_read(key=key, value=value, d_post=d_post, b_exact=b_exact)
# end of the kwargs for loop
else:
if self.limit.islimit > 0:
s_query = '%swith limit[%s,%s](limit,%s... limit set %s times),' % (s_query, self.limit.min, self.limit.max, kwargs['limit'],self.limit.islimit)
d_post[self.post_name] = d_post[self.post_name][self.limit.min:self.limit.max]
if d_post[self.post_name].count() == 0:
d_post['status'] = 0
d_post['statusmsg'] = '%s not found with query: {%s}' % (self.post_name, s_query)
else:
d_post['status'] = 1
d_post['statusmsg'] = '%s %s found with query: {%s}' % (d_post[self.post_name].count(), self.post_name, s_query)
except:
e = sys.exc_info()[1]
d_post['status'] = 0
d_post['statusmsg'] = 'error: %s %s' % (e, traceback.format_exc())
d_post[self.post_name] = []
return d_post
class HoursDetailHandler(BaseApiHandler):
#allowed_methods = ('GET', 'PUT', 'POST', 'DELETE',)
model = HoursDetail
exclude = ()
def __init__(self):
BaseApiHandler.__init__(self)
self._post_name = 'hours_detail'
def process_kwarg_read(self, key, value, d_post, b_exact):
# each query is handled slightly differently... when keys are added
# handle them in here. python doesn't have switch statements, this
# could theoretically be performed using a dictionary with lambda
# expressions, however I was affraid it would mess with the way the
# filters on the queryset work so I went for the less exciting
# if/elif block instead
# querying on a specific row
if key == 'id':
d_post[self.post_name] = d_post[self.post_name].filter(pk=value)
# filter based on employee id - this is guaranteed to happen once
# per query (see read(...))
elif key == 'empid':
d_post[self.post_name] = d_post[self.post_name].filter(emp__id__exact=value)
# look for a specific project by id
elif key == 'projid':
d_post[self.post_name] = d_post[self.post_name].filter(proj__id__exact=value)
elif key == 'datestamp' or key == 'daterange':
d_from = None
d_to = None
# first, regex out the times in the case of range vs stamp
if key == 'daterange':
m = re.match('(?P<daterangefrom>\d{4,}[-/\.]\d{2,}[-/\.]\d{2,})(?:to|/-)(?P<daterangeto>\d{4,}[-/\.]\d{2,}[-/\.]\d{2,})', \
value)
d_from = datetime.strptime(m.group('daterangefrom'), '%Y-%m-%d')
d_to = datetime.strptime(m.group('daterangeto'), '%Y-%m-%d')
else:
d_from = datetime.strptime(value, '%Y-%m-%d')
d_to = datetime.strptime(value, '%Y-%m-%d')
# now min/max to get midnight on day1 through just before midnight on day2
# note: this is a hack because as of the writing of this app,
# __date doesn't yet exist as a queryable field thus any
# timestamps not at midnight were incorrectly left out
d_from = datetime.combine(d_from, time.min)
d_to = datetime.combine(d_to, time.max)
d_post[self.post_name] = d_post[self.post_name].filter(clock_time__gte=d_from)
d_post[self.post_name] = d_post[self.post_name].filter(clock_time__lte=d_to)
elif key == 'limit':
order_by = 'clock_time'
if value == 'last':
order_by = '-clock_time'
d_post[self.post_name] = d_post[self.post_name].order_by(order_by)
self.limit.setlimit(0, 1)
else:
raise NameError
def read(self, request, *args, **kwargs):
# empid is required, so make sure it exists before running BaseApiHandler's read method
if not('empid' in kwargs and kwargs['empid'] <> None and kwargs['empid'] >= 0):
return {'status':0,'statusmsg':'empid cannot be empty'}
else:
return BaseApiHandler.read(self, request, *args, **kwargs)
I would say that there is a basic flaw in your code, if has_limit() can return True when limit is a list of length 2, but this line will fail if limit is shorter than 3 elements long:
s_query = '%swith limit[%s,%s](limit,%s > traceback:%s),' %
(s_query, self.limit[0], self.limit[1], kwargs['limit'],
self.limit[2])
Why are you initializing self.limit to an invalid length list? You could also make this code a little more defensive:
if self.has_limit():
s_query += 'with limit[%s,%s]' % self.limit[0:1]
if 'limit' in kwargs and len(self.limit) > 2:
s_query += '(limit,%s > traceback:%s),' %
(kwargs['limit'], self.limit[2])
I think you may be creating an alias to your internal limit list, via the get_limit property accessor. Try removing (or at least adding a print statement) inside this accessor. If you have code externally that binds a local list to get_limit, then it can update the contents using append, del, or assignment to slices, such as [:]. Or try this:
def get_limit(self):
return self._limit[:]
Instead of binding your internal list to an external name, it will make a copy of your internal list.

How do I get the name of a function or method from within a Python function or method?

I feel like I should know this, but I haven't been able to figure it out...
I want to get the name of a method--which happens to be an integration test--from inside it so it can print out some diagnostic text. I can, of course, just hard-code the method's name in the string, but I'd like to make the test a little more DRY if possible.
This seems to be the simplest way using module inspect:
import inspect
def somefunc(a,b,c):
print "My name is: %s" % inspect.stack()[0][3]
You could generalise this with:
def funcname():
return inspect.stack()[1][3]
def somefunc(a,b,c):
print "My name is: %s" % funcname()
Credit to Stefaan Lippens which was found via google.
The answers involving introspection via inspect and the like are reasonable. But there may be another option, depending on your situation:
If your integration test is written with the unittest module, then you could use self.id() within your TestCase.
This decorator makes the name of the method available inside the function by passing it as a keyword argument.
from functools import wraps
def pass_func_name(func):
"Name of decorated function will be passed as keyword arg _func_name"
#wraps(func)
def _pass_name(*args, **kwds):
kwds['_func_name'] = func.func_name
return func(*args, **kwds)
return _pass_name
You would use it this way:
#pass_func_name
def sum(a, b, _func_name):
print "running function %s" % _func_name
return a + b
print sum(2, 4)
But maybe you'd want to write what you want directly inside the decorator itself. Then the code is an example of a way to get the function name in a decorator. If you give more details about what you want to do in the function, that requires the name, maybe I can suggest something else.
# file "foo.py"
import sys
import os
def LINE( back = 0 ):
return sys._getframe( back + 1 ).f_lineno
def FILE( back = 0 ):
return sys._getframe( back + 1 ).f_code.co_filename
def FUNC( back = 0):
return sys._getframe( back + 1 ).f_code.co_name
def WHERE( back = 0 ):
frame = sys._getframe( back + 1 )
return "%s/%s %s()" % ( os.path.basename( frame.f_code.co_filename ),
frame.f_lineno, frame.f_code.co_name )
def testit():
print "Here in %s, file %s, line %s" % ( FUNC(), FILE(), LINE() )
print "WHERE says '%s'" % WHERE()
testit()
Output:
$ python foo.py
Here in testit, file foo.py, line 17
WHERE says 'foo.py/18 testit()'
Use "back = 1" to find info regarding two levels back down the stack, etc.
I think the traceback module might have what you're looking for. In particular, the extract_stack function looks like it will do the job.
To elaborate on #mhawke's answer:
Rather than
def funcname():
return inspect.stack()[1][3]
You can use
def funcname():
frame = inspect.currentframe().f_back
return inspect.getframeinfo(frame).function
Which, on my machine, is about 5x faster than the original version according to timeit.

Categories