Adding an OverflowError side effect with patch - Python - python

I'd like to mock an OverflowError because I'd like to test the value of a variable is after that exception is raised. However, I do not know how to replicate an OverflowError with the libraries I am using. Libraries I am using in this specific test is pysolar.solar specifically the get_altitude, get_azimuth and radiation methods
After mindlessly trying out different numbers to try to simulate an OverflowError I decided to try out Mocking the function and introducing a side effect.
Code I am testing sunposition.py
import numpy as np
import pandas as pd
from pysolar.solar import get_altitude, get_azimuth, radiation as radiation_module
def sun_position(lat: float, lon: float, time: pd.Timestamp = None) -> List[float]:
if time is None:
time = pd.Timestamp.now(tz='UTC')
dt = time.to_pydatetime()
altitude = get_altitude(lat, lon, dt)
azimuth = get_azimuth(lat, lon, dt)
try:
radiation = radiation_module.get_radiation_direct(dt, altitude)
except OverflowError:
radiation = np.nan
return pd.Series([altitude, azimuth, radiation], index=['Alt', 'Azi', 'Rad'])
**What I started to do with patch **
"""Test sunposition module"""
import unittest
import numpy as np
import pandas as pd
from unittest.mock import MagicMock, patch, Mock
from bigfolder.sun import sunposition
class TestSunposition(unittest.TestCase):
"""Test functions in sunposition."""
def test_sun_position_overflow_error(self):
error_lat = 23
error_lon = 12
error_time = pd.Timestamp('2007-02-18 15:13:05', tz="UTC")
mock_args = {'side_effect': OverflowError}
with patch('bigfolder.sun.sunposition.sun_position', **mock_args):
# run the test
self.assertRaises(OverflowError, sunposition.sun_position(lat=error_lat, lon=error_lon, time=error_time))
if __name__ == '__main__':
unittest.main()
I was expecting it to give me and OverFlow error... and it did, however my assertion failed anyways with an OverflowError My guess is that I patched at the wrong place? I don't really understand why the test failed regardless when the error was still an OverFlow Error
This is what was printed out
_mock_self = <MagicMock name='sun_position' id='102333856'>, args = ()
kwargs = {'lat': 23, 'lon': 12, 'time': Timestamp('2007-02-18 15:13:05+0000', tz='UTC')}
self = <MagicMock name='sun_position' id='102333856'>, _new_name = ''
_new_parent = None
_call = call(lat=23, lon=12, time=Timestamp('2007-02-18 15:13:05+0000', tz='UTC'))
seen = set(), skip_next_dot = False, do_method_calls = False
name = 'sun_position'
def _mock_call(_mock_self, *args, **kwargs):
self = _mock_self
self.called = True
self.call_count += 1
_new_name = self._mock_new_name
_new_parent = self._mock_new_parent
_call = _Call((args, kwargs), two=True)
self.call_args = _call
self.call_args_list.append(_call)
self.mock_calls.append(_Call(('', args, kwargs)))
seen = set()
skip_next_dot = _new_name == '()'
do_method_calls = self._mock_parent is not None
name = self._mock_name
while _new_parent is not None:
this_mock_call = _Call((_new_name, args, kwargs))
if _new_parent._mock_new_name:
dot = '.'
if skip_next_dot:
dot = ''
skip_next_dot = False
if _new_parent._mock_new_name == '()':
skip_next_dot = True
_new_name = _new_parent._mock_new_name + dot + _new_name
if do_method_calls:
if _new_name == name:
this_method_call = this_mock_call
else:
this_method_call = _Call((name, args, kwargs))
_new_parent.method_calls.append(this_method_call)
do_method_calls = _new_parent._mock_parent is not None
if do_method_calls:
name = _new_parent._mock_name + '.' + name
_new_parent.mock_calls.append(this_mock_call)
_new_parent = _new_parent._mock_new_parent
# use ids here so as not to call __hash__ on the mocks
_new_parent_id = id(_new_parent)
if _new_parent_id in seen:
break
seen.add(_new_parent_id)
ret_val = DEFAULT
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E OverflowError
So then I thought I must have patched in the wrong place and introduced the side effect earlier than I should have? So I instead patched the method int the try block. This was my following code.
def test_sun_position_overflow_error(self):
error_lat = 23
error_lon = 12
error_time = pd.Timestamp('2007-02-18 15:13:05', tz="UTC")
mock_args = {'side_effect': OverflowError}
with patch('bigfolder.sun.sunposition.sun_position.radiation_module.get_radiation_direct', **mock_args):
# run the test
self.assertRaises(OverflowError, sunposition.sun_position(lat=error_lat, lon=error_lon, time=error_time))
And now my error is " ModuleNotFoundError: No module named 'bigfolder.sun.sunposition.sun_position'; 'bigfolder.sun.sunposition' is not a package"
I then just changed the path to 'sun_position.radiation_module.get_radiation_direct' but there's no module found.
So my questions are: How to replicate an OverflowError so that then I can check the value of a variable I set once that Exception is raised. Why is the first OverflowError I introduce still not pass my assertion?
Thanks
UPDATE TEST passes
After following #Gang 's suggestion, the OverFlowError was reproduced. I realized that in order to test the block for the exception, specifically that radiation was then np.nan I had to patch the method that I wanted to have the OverFlowError and not the whole method of sun_position. When I tried doing that I imported it incorrectly, as I thought the external library was part of the code. So I changed bigfolder.sun.sunposition.sun_position.radiation_module.get_radiation_direct to pysolar.solar.radiation.get_radiation_direct which is the external library that has the get_radiation_direct method I wanted to mock.
def test_sun_position_overflow_error(self):
lat = 23
lon = 12
time = pd.Timestamp('2007-02-18 15:13:05', tz="UTC")
# get_radiation_direct will now produce an OverFlowError(regardless of coordinates)
mock_args = {'side_effect': OverflowError}
# mock get_radiation_direct and produce OverFlowError
with patch('pysolar.solar.radiation.get_radiation_direct', **mock_args):
# Check radiation column is nan value
assert math.isnan(sunposition.sun_position(lat=lat, lon=lon, time=time)[2])

Why is the first OverflowError I introduce still not pass my assertion?
Almost there. The right way to assertRaises:
def test_sun_position_overflow_error(self):
# This has to be here first and without func call
with self.assertRaises(OverflowError):
# patch the function to have an exception no matter what
mock_args = {'side_effect': OverflowError}
with patch('bigfolder.sun.sunposition.sun_position', **mock_args):
# call this func to trigger an exception
sunposition.sun_position(lat=error_lat, lon=error_lon, time=error_time)
After review the document, it is how to call a func inside assertRaises
assertRaises(exception, callable, *args, **kwds)
fun(*args, **kwds) raises exc
This usage is wrong:
self.assertRaises(OverflowError, sunposition.sun_position(lat=error_lat, lon=error_lon, time=error_time))
It should be a func name with kwargs:
self.assertRaises(OverflowError, sunposition.sun_position, lat=error_lat, lon=error_lon, time=error_time)

Related

Pythonic way of not printing a error message up

I am trying to suppress a error/warning in my log while calling a library. Assume i have this code
try:
kazoo_client.start()
except:
pass
This is calling a zookeeper client which throws some exception which bubble up, now i don't want the warn/error in my logs when i call kazoo_client.start() is there a way to get this suppressed when you call the client
Assuming python 2.7.17
Try this approach:
import sys, StringIO
def funky() :
"1" + 1 # This should raise an error
sys.stderr = StringIO.StringIO()
funky() # this should call the funky function
And your code should look something like this:
import sys, StringIO
# import kazoo somehere around here
sys.stderr = StringIO.StringIO()
kazoo_client.start()
And lastly the Python 3 example:
import sys
from io import StringIO
# import kazoo somehere around here
sys.stderr = StringIO()
kazoo_client.start()
If you know the exception, try contextlib.suppress:
>>> from contextlib import suppress
>>> x = (i for i in range(10))
>>> with suppress(StopIteration):
... for i in range(11):
... print(next(x))
0
1
2
3
4
5
6
7
8
9
Without suppress it throws StopIteration error at last iteration.
>>> x = (i for i in range(10))
>>> for i in range(11):
... print(next(x))
0
1
2
3
4
5
6
7
8
9
Traceback (most recent call last):
File "<ipython-input-10-562798e05ad5>", line 2, in <module>
print(next(x))
StopIteration
Suppress is Pythonic, safe and explicit.
So in your case:
with suppress(SomeError):
kazoo_client.start()
EDIT:
To suppress all exceptions:
with suppress(Exception):
kazoo_client.start()
I would like to suggest a more generic approach, which can be used in general.
I leave you an example of how to create an decorator who ignore errors.
import functools
# Use the Decorator Design Pattern
def ignore_error_decorator(function_reference):
#functools.wraps(function_reference) # the decorator inherits the original function signature
def wrapper(*args):
try:
result = function_reference(*args) # execute the function
return result # If the function executed correctly, return
except Exception as e:
pass # Default ignore; You can also log the error or do what ever you want
return wrapper # Return the wrapper reference
if __name__ == '__main__':
# First alternative to use. Compose the decorator with another function
def my_first_function(a, b):
return a + b
rez_valid = ignore_error_decorator(my_first_function)(1, 3)
rez_invalid = ignore_error_decorator(my_first_function)(1, 'a')
print("Alternative_1 valid: {result}".format(result=rez_valid))
print("Alternative_1 invalid: {result}".format(result=rez_invalid)) # None is return by the exception bloc
# Second alternative. Decorating a function
#ignore_error_decorator
def my_second_function(a, b):
return a + b
rez_valid = my_second_function(1, 5)
rez_invalid = my_second_function(1, 'a')
print("Alternative_2 valid: {result}".format(result=rez_valid))
print("Alternative_2 invalid: {result}".format(result=rez_invalid)) # None is return by the exception bloc
Getting back to your problem, using my alternative you have to run
ignore_error_decorator(kazoo_client.start)()

pytest mock my own module imported in tested function

I want to test a function which import another module.
So i want to mock module.
So my test function :
def test_validate_transaction():
dataset_id = "idx"
transaction_id = "idy"
transaction = {
"transaction_rid" : transaction_id,
"is_successful" : True
}
with mock.patch('table_management.get_last_transaction_id', return_value=transaction) :
assert validate_transaction(dataset_rid,transaction_rid) == False
and my function that i want to test is
import json
import os
from table_management import get_last_transaction_id
def validate_transaction(dataset_id,transaction_id):
try:
transaction = get_last_transaction_id(dataset_rid)
return True if transaction['transaction_id'] != transaction_rid or transaction['is_successful']==False else False
except Exception as e:
print("An exception occurred " + str(e))
return {}
But by doing this, i receive the error :
..\..\..\env\lib\site-packages\mock\mock.py:1378: in __enter__
self.target = self.getter() ..\..\..\env\lib\site-packages\mock\mock.py:1548: in <lambda>
getter = lambda: _importer(target)
target = 'table_management'
def _importer(target):
components = target.split('.')
import_path = components.pop(0)
thing = __import__(import_path)
E ImportError: No module named table_management
..\..\..\env\lib\site-packages\mock\mock.py:1231: ImportError
do you have any idea what is missing ?
I'm not sure that you actually don't have some other issues related to import but you are trying to mock a wrong object. You need to mock get_last_transaction_id imported in the module where validate_transaction is. You didn't mention its name so let's assume it's called xyz:
from full_path.to.xyz import validate_transaction
def test_validate_transaction():
dataset_id = "idx"
transaction_id = "idy"
transaction = {
"transaction_rid" : transaction_id,
"is_successful" : True
}
with mock.patch('xyz.get_last_transaction_id', return_value=transaction):
assert validate_transaction(dataset_rid,transaction_rid) == False

Using functools.partial to make custom filters for pdfquery getting attribute error

Background
I'm using pdfquery to parse multiple files like this one.
Problem
I'm trying to write a generalized filer function, building off of the custom selectors mentioned in pdfquery's docs, that can take a specific range as an argument. Because this is referenced I thought I could get around this by supplying a partial function using functools.partial (as seen below)
Input
import pdfquery
import functools
def load_file(PDF_FILE):
pdf = pdfquery.PDFQuery(PDF_FILE)
pdf.load()
return pdf
file_with_table = 'Path to the file mentioned above'
pdf = load_file(file_with_table)
def elements_in_range(x1_range):
return in_range(x1_range[0], x1_range[1], float(this.get('x1',0)))
x1_part = functools.partial(elements_in_range, (95,350))
pdf.pq('LTPage[page_index="0"] *').filter(x1_part)
But when I do that I get the following attribute error;
Output
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
C:\Anaconda3\lib\site-packages\pyquery\pyquery.py in filter(self, selector)
597 if len(args) == 1:
--> 598 func_globals(selector)['this'] = this
599 if callback(selector, i, this):
C:\Anaconda3\lib\site-packages\pyquery\pyquery.py in func_globals(f)
28 def func_globals(f):
---> 29 return f.__globals__ if PY3k else f.func_globals
30
AttributeError: 'functools.partial' object has no attribute '__globals__'
During handling of the above exception, another exception occurred:
AttributeError Traceback (most recent call last)
<ipython-input-74-d75c2c19f74b> in <module>()
15 x1_part = functools.partial(elements_in_range, (95,350))
16
---> 17 pdf.pq('LTPage[page_index="0"] *').filter(x1_part)
C:\Anaconda3\lib\site-packages\pyquery\pyquery.py in filter(self, selector)
600 elements.append(this)
601 finally:
--> 602 f_globals = func_globals(selector)
603 if 'this' in f_globals:
604 del f_globals['this']
C:\Anaconda3\lib\site-packages\pyquery\pyquery.py in func_globals(f)
27
28 def func_globals(f):
---> 29 return f.__globals__ if PY3k else f.func_globals
30
31
AttributeError: 'functools.partial' object has no attribute '__globals__'
Is there any way to get around this? Or possibly some other way to write a custom selector for pdfquery that can take arguments?
What about just using a function to return a new function (similar to a functools.partial in a way), but using a closure instead?
import pdfquery
def load_file(PDF_FILE):
pdf = pdfquery.PDFQuery(PDF_FILE)
pdf.load()
return pdf
file_with_table = './RG234621_90110.pdf'
pdf = load_file(file_with_table)
def in_range(x1, x2, sample):
return x1 <= sample <= x2
def in_x_range(bounds):
def wrapped(*args, **kwargs):
x = float(this.get('x1', 0))
return in_range(bounds[0], bounds[1], x)
return wrapped
def in_y_range(bounds):
def wrapped(*args, **kwargs):
y = float(this.get('y1', 0))
return in_range(bounds[0], bounds[1], y)
return wrapped
print(len(pdf.pq('LTPage[page_index="0"] *').filter(in_x_range((95, 350))).filter(in_y_range((60, 100)))))
# Or, perhaps easier to read
x_check = in_x_range((95, 350))
y_check = in_y_range((60, 100))
print(len(pdf.pq('LTPage[page_index="0"] *').filter(x_check).filter(y_check)))
OUTPUT
1
16 # <-- bounds check is larger for y in this test
You could event parameterize the property you are comparing
import pdfquery
def load_file(PDF_FILE):
pdf = pdfquery.PDFQuery(PDF_FILE)
pdf.load()
return pdf
file_with_table = './RG234621_90110.pdf'
pdf = load_file(file_with_table)
def in_range(prop, bounds):
def wrapped(*args, **kwargs):
n = float(this.get(prop, 0))
return bounds[0] <= n <= bounds[1]
return wrapped
print(len(pdf.pq('LTPage[page_index="0"] *').filter(in_range('x1', (95, 350))).filter(in_range('y1', (60, 100)))))
x_check = in_range('x1', (95, 350))
y_check = in_range('y1', (40, 100))
print(len(pdf.pq('LTPage[page_index="0"] *').filter(x_check).filter(y_check)))
I would also suggest the use of the parse_tree_cacher argument as that sped up the time for me to find an appropriate solution (though you may not need to reprocess frequently as I did while figuring this out).
import pdfquery
from pdfquery.cache import FileCache
def load_file(PDF_FILE):
pdf = pdfquery.PDFQuery(PDF_FILE, parse_tree_cacher=FileCache("/tmp/"))
pdf.load()
return pdf
Although, I like the closure approach, I really should mention that you can copy attributes from your wrapped function to your wrapper.
from functools import update_wrapper
custom_filter = update_wrapper(
partial(
elements_in_range, (95, 20)
),
wrapped=elements_in_range,
assigned=('__globals__', '__code__')
)

Python ctypes-wrapped function taking int fails to reject argument larger than INT_MAX

Tested using Python 2.7.13 on 64-bit Linux.
I have a C function inside a shared object. This function takes two ints and returns an int. When I try to pass an integer from the Python side that is larger than INT_MAX but still representable as an unsigned int, I don't get a Python-side exception like I would expect.
// add.c
int add(int a, int b)
{
return a + b;
}
This file is compiled into a shared objects using the following options. (snippet from makefile)
gcc -shared -fPIC add.c -o add.so
I'm using the following python script test_add.c to test the code. I'm probably doing more work than necessary, manually supplying argument types of ctypes.c_int and manually coercing the integers generated by hypothesis, but it doesn't seem like any of those operations should be causing the behavior I'm seeing.
Here's the main test that I'm trying with different values of x and y. One example of a failing test case is x=0 and y=2147483648, a number one larger than INT_MAX.
try:
s = add(ctypes.c_int(x), ctypes.c_int(y))
except Exception:
return
# if no exception was thrown, python and C should agree
# about the result of the addition
assert s == (x + y)
And here's the script in full for the sake of completeness:
import ctypes
import os.path
import unittest
from hypothesis import given
import hypothesis.strategies as st
# import shared object from this directory
addso_path = os.path.dirname(os.path.abspath(__file__)) + os.path.sep + "add.so"
addso = ctypes.cdll.LoadLibrary(addso_path)
# extract add symbol, explicitly force its argument types to be
# (int, int)
# and return type to be
# int
add = addso.add
add.argtypes = [ctypes.c_int, ctypes.c_int]
add.restype = ctypes.c_int
class TestAddition(unittest.TestCase):
#given(x=st.integers(), y=st.integers())
def test_add(self, x, y):
# if we catch any error on the python side,
# then that counts as successfully preventing an
# out-of-bounds integer before it is passed to C
try:
s = add(ctypes.c_int(x), ctypes.c_int(y))
except Exception:
return
# if no exception was thrown, python and C should
# agree about the result of the addition
assert s == (x + y)
if __name__ == "__main__":
unittest.main()
Here's the output of the test suite:
Falsifying example: test_add(self=<__main__.TestAddition testMethod=test_add>, x=0, y=2147483648)
F
======================================================================
FAIL: test_add (__main__.TestAddition)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_add.py", line 22, in test_add
def test_add(self, x, y):
File "/.../opt/c-add/venv/lib/python2.7/site-packages/hypothesis/core.py", line 525, in wrapped_test
print_example=True, is_final=True
File "/...opt/c-add/venv/lib/python2.7/site-packages/hypothesis/executors.py", line 58, in default_new_style_executor
return function(data)
File "/.../opt/c-add/venv/lib/python2.7/site-packages/hypothesis/core.py", line 112, in run
return test(*args, **kwargs)
File "test_add.py", line 33, in test_add
assert s == (x + y)
AssertionError

Python unittesting initiate values

Sorry if this question is stupid. I created an unittest class which needs to take given inputs and outputs from outside. Thus, I guess these values should be initiated. However, I met some errors in the following code:
CODE:
import unittest
from StringIO import StringIO
##########Inputs and outputs from outside#######
a=[1,2]
b=[2,3]
out=[3,4]
####################################
def func1(a,b):
return a+b
class MyTestCase(unittest.TestCase):
def __init__(self,a,b,out):
self.a=a
self.b=b
self.out=out
def testMsed(self):
for i in range(self.tot_iter):
print i
fun = func1(self.a[i],self.b[i])
value = self.out[i]
testFailureMessage = "Test of function name: %s iteration: %i expected: %i != calculated: %i" % ("func1",i,value,fun)
self.assertEqual(round(fun,3),round(value,3),testFailureMessage)
if __name__ == '__main__':
f = MyTestCase(a,b,out)
from pprint import pprint
stream = StringIO()
runner = unittest.TextTestRunner(stream=stream, verbosity=2)
result = runner.run(unittest.makeSuite(MyTestCase(a,b,out)))
print 'Tests run', result.testsRun
However, I got the following error
Traceback (most recent call last):
File "C:testing.py", line 33, in <module>
result = runner.run(unittest.makeSuite(MyTestCase(a,b,out)))
File "C:\Python27\lib\unittest\loader.py", line 310, in makeSuite
return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase(testCaseClass)
File "C:\Python27\lib\unittest\loader.py", line 50, in loadTestsFromTestCase
if issubclass(testCaseClass, suite.TestSuite):
TypeError: issubclass() arg 1 must be a class
Can anyone give me some suggestions? Thanks!
The root of the problem is this line,
result = runner.run(unittest.makeSuite(MyTestCase(a,b,out)))
unittest.makeSuite expects a class, not an instance of a class. So just MyTestCase, not MyTestCase(a, b, out). This means that you can't pass parameters to your test case in the manner you are attempting to. You should probably move the code from init to a setUp function. Either access a, b, and out as globals inside setUp or take a look at this link for information regarding passing parameters to a unit test.
By the way, here is the source file within python where the problem originated. Might be informative to read.

Categories

Resources