Mocking method in production code, not in tests - python

I have a method in my code like this :
def save(items):
try:
for item in items():
do_something(item)
except Exception:
my_logging.warning("error happened")
def do_something(item):
pass
I would like to invoke this method from another location in the code, however, I would like to call a different method instead of do_something(item) :
#transaction.atomic
def do_with_transaction(item)
delete(item)
do_something(item)
Is it ok to use mock with side effect for production code? this way I can mock do_something() to use do_with_transaction(item).
It looks to me like a clean solution.

If what you want is to reuse save(items) but calling another function (instead of do_something() in the for loop, just pass the desired function as argument:
def save(items, callback=do_something):
for item in items():
try:
callback(item)
except Exception as e:
my_logging.exception("error %s happened on item %s", e, item)
and then:
save(items, do_something_with_transaction)

Related

How to handle exception raised at low levels of the package or function calls?

For example, my function call make look like the following:
def parse(self, text):
...
return self.parse_helper(text)
#staticmethod
def parser_helper(text):
...
return normalize(text)
#staticmethod
def normalize(text):
...
try:
...
except:
raise ValueError('normalize failed.')
If the 'parse' is the function to be provided to users to call, if an exception occurs in normalize(), the whole program terminates. To avoid this, to let users decide what to do when exception occurs, do I have to and try ... except blocks into both 'parser_helper' and 'parse', and let use to use try...except when 'parse' is called?
What's the normal practice of handling this? If there are a few more layers of function calls embedded other than 3 as shown below, do I have to use try ... except block in each layer of function, in order to transfer the handling of exception to the end users at the very top?
The practice to pass a parameter to decide wether to raise or be silent regarding an exception, is something that exists, for example in os.makedirs
You could do
def parse(self, text, error_silent=False):
...
try:
return self.parse_helper(text)
except ValueError as e:
if error_silent:
return None
raise e

What is the appropriete way to handling exceptions inside a python method?

Suppose I have a function and, depending on its inputs, it must "advise" the caller function that something went wrong:
def get_task(msg, chat):
task_id = int(msg)
query = db.SESSION.query(Task).filter_by(id=task_id, chat=chat)
try:
task = query.one()
except sqlalchemy.orm.exc.NoResultFound:
return "_404_ error"
return task
Notice at the except block I want to pass something that the caller function can handle and stop its execution if it's necessary, otherwise, it will return the right object.
def something_with_the_task(msg, chat):
task = get_task(msg, chat)
if task == "_404_ error":
return
#do some stuff with task
You already seem to know how exceptions work.
The best thing to do in case of an error is to raise an exception.
Returning some magic value is considered a bad practice, because it requires the caller to explicitly check for it, and a hundred of other reasons.
You can simply let the sqlalchemy.orm.exc.NoResultFound exception escape (by removing the try: and the except: block in get_task()), and let the caller handle it with a try: ... except: ... block, or, if you prefer to do some hiding, you can define a custom exception:
class YourException(Exception):
pass
and use it like this:
def get_task(msg, chat):
try:
task = ...
except sqlalchemy.orm.exc.NoResultFound:
raise YourException('explanation')
return task
def something_with_the_task(msg, chat):
try:
task = get_task(msg, chat)
# do some stuff with task
except YourException as e:
# do something with e
# e.args[0] will contain 'explanation'
Feel free to make the YourException class more informative by explicitly adding some attributes and a constructor to set those, if needed.
The default constructor makes a decent job however:
>>> e = YourException('Program made a boo boo', 42, 'FATAL')
>>> e
YourException('Program made a boo boo', 42, 'FATAL')
>>> e.args[0]
'Program made a boo boo'
>>> e.args[1]
42
>>> e.args[2]
'FATAL'

How to throw exception from mocked instance's method?

This demo function I want to test is pretty straight forward.
def is_email_deliverable(email):
try:
return external.verify(email)
except Exception:
logger.error("External failed failed")
return False
This function uses an external service which I want to mock out.
But I can't figure out how to throw an exception from external.verify(email) i.e. how to force the except clause to be executed.
My attempt:
#patch.object(other_module, 'external')
def test_is_email_deliverable(patched_external):
def my_side_effect(email):
raise Exception("Test")
patched_external.verify.side_effects = my_side_effect
# Or,
# patched_external.verify.side_effects = Exception("Test")
# Or,
# patched_external.verify.side_effects = Mock(side_effect=Exception("Test"))
assert is_email_deliverable("some_mail#domain.com") == False
This question claims to have the answer, but didn't work for me.
You have used side_effects instead of side_effect.
Its something like this
#patch.object(Class, "attribute")
def foo(attribute):
attribute.side_effect = Exception()
# Other things can go here
BTW, its not good approach to catch all the Exception and handle according to it.
You can set the side_effect value to None.

Separating except portion of a try/except into a function

I have a try/except where I repeat the except portion frequently in my code. This led me to believe that it would be better to separate the except portion into a function.
Below is my use case:
try:
...
except api.error.ReadError as e:
...
except api.error.APIConnectionError as e:
...
except Exception as e:
...
How would I go about separating this logic into a function so I can do something as simple as:
try:
...
except:
my_error_handling_function(e)
Just define the function:
def my_error_handling(e):
#Do stuff
...and pass in the exception object as the parameter:
try:
#some code
except Exception as e:
my_error_handling(e)
Using just a generic Exception type will allow you to have a single except clause and handle and test for different error types within your handling function.
In order to check for the name of the caught exception, you can get it by doing:
type(e).__name__
Which will print the name, such as ValueError, IOError, etc.
I would suggest refactoring your code so the try/except block is only present in a single location.
For instance, an API class with a send() method, seems like a reasonable candidate for containing the error handling logic you have described in your question.
Define your function:
def my_error_handling(e):
#Handle exception
And do what you're proposing:
try:
...
except Exception as e:
my_error_handling_function(e)
You can handle logic by getting the type of the exception 'e' within your function. See: python: How do I know what type of exception occurred?
If you don't like try-catch statement, you can use exception-decouple package and decorator.
from exception_decouple import redirect_exceptions
def my_error_handling(arg, e):
#Do stuff
#redirect_exceptions(my_error_handling, api.error.ReadError, api.error.APIConnectionError)
def foo(arg):
...

How to cover the except portion of a try-except in a python pytest unit test

I'm new to Python. I need to unit test the except part of a try-except statement in python. I'm using pytest. The problem is that I don't know how to force the try part to raise an exception. Here is my code:
try:
if master_bill_to is False:
master.update(
dbsession,
company_id=master.company_id,
)
except Exception as e:
dbsession.rollback()
raise Conflict(e.message)
The master.update method is called to make an update to the database. But how do I mock this code so that it somehow raises an exception in the try portion?
I'm trying to use monkeypatch with this code. The master object is an instance of the BillTo class so I'm thinking of putting that as the first parameter to monkeypatch.setattr.
def test_create_bill_to_fails_when_master_update_fails(dbsession, invoice_group1, company1,
monkeypatch):
def raise_flush_error():
raise FlushError
context = TestContext()
monkeypatch.setattr(BillTo, 'update', raise_flush_error)
with pytest.raises(FlushError):
create_bill_to(
context,
dbsession=dbsession,
invoice_group_id=invoice_group1.id,
company_id=company1.id,
)
But for some reason, the error is not raised.
Use mock library and side_effect to throw Exception during test case
Mock master and raise an exception in the update method.
Ok, I figured it out. I learned that you must pass parameters to the method called by monkeypatch. These parameters must match the signature of the method being replaced or mocked. Actually I also renamed the method with a prefix fake_ to denote the mocking. Here is what I did:
#staticmethod
def fake_update_flush_error(dbsession, company_id=None, address_id=None, proportion=None,
company_name=None, receiver_name=None, invoice_delivery_method=None,
invoice_delivery_text=None, master_bill_to=False):
raise FlushError
def test_create_bill_to_fails_when_master_update_fails(dbsession, invoice_group1, company1,
bill_to1, monkeypatch):
context = TestContext()
monkeypatch.setattr(BillTo, 'update', fake_update_flush_error)
with pytest.raises(Conflict):
create_bill_to(
context,
dbsession=dbsession,
invoice_group_id=invoice_group1.id,
company_id=company1.id,
address_id=None,
...
)
The BillTo.update method needed all those parameters.

Categories

Resources