classification
Title: Can no longer patch flask.g
Type: behavior Stage: resolved
Components: Tests Versions: Python 3.9, Python 3.8
process
Status: closed Resolution: third party
Dependencies: Superseder:
Assigned To: Nosy List: Rob Taft, cjw296, rkm, terry.reedy, xtreak
Priority: normal Keywords:

Created on 2020-05-28 12:19 by Rob Taft, last changed 2020-06-08 13:38 by Rob Taft. This issue is now closed.

Messages (9)
msg370197 - (view) Author: Rob Taft (Rob Taft) Date: 2020-05-28 12:19
Whenever I try to patch flask.g, it appears to do nothing.  This happened when I upgraded mock from 3.x to 4.x.  I reported it on the mock github page https://github.com/testing-cabal/mock/issues/490 and was asked to report it here.  The folllowing code run with pytest works fine in mock 3.0.5, but fails to patch in 4.0.0 and up.

from mock import patch

import flask


def some_function():
    flask.g.somevariable = True
    return flask.g.somevariable


@patch('flask.g')
def test_some_function(mock_flask_global):
    assert some_function()
msg370356 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2020-05-30 03:01
Sorry, but I believe you were misdirected*.  mock, as opposed to unittest.mock, pytest, flask, and werkzeug  are 3rd party modules.  The error report seems to be missing part of the stacktrace at both ends.  What line is your file resulted in the error?  (This time we can guess == @patch. @What called _lookup_app_object in flask?  (I have no idea.)

However, once contextlib._GeneratorContextManager calls next(self.gen), the rest of the trace is outside the stdlib.  At the end, the RuntimeError is raised by flask, not by python, because the flask _app_ctx_stack.top does not exist.  If you do not understand their error message, ask flask people.

* I assume that cjw296 *glanced* at your report, saw 'RuntimeError', and too quickly assumed 'cpython error'.  flask could have defined, for instance, FlaskRuntimeError for its error reporting.
msg370368 - (view) Author: Rob Taft (Rob Taft) Date: 2020-05-30 10:42
The test was supposed to patch the flask component during the unit test,
the error indicates the patch did not work.  The actual error message is
not relevant to the actual issue. I don't know why I was directed to here.
When I replace it with unittest.mock, it appears to work fine, so my
solution might end up being to dump the 3rd party mock library.

On Fri, May 29, 2020 at 11:02 PM Terry J. Reedy <report@bugs.python.org>
wrote:

>
> Terry J. Reedy <tjreedy@udel.edu> added the comment:
>
> Sorry, but I believe you were misdirected*.  mock, as opposed to
> unittest.mock, pytest, flask, and werkzeug  are 3rd party modules.  The
> error report seems to be missing part of the stacktrace at both ends.  What
> line is your file resulted in the error?  (This time we can guess ==
> @patch. @What called _lookup_app_object in flask?  (I have no idea.)
>
> However, once contextlib._GeneratorContextManager calls next(self.gen),
> the rest of the trace is outside the stdlib.  At the end, the RuntimeError
> is raised by flask, not by python, because the flask _app_ctx_stack.top
> does not exist.  If you do not understand their error message, ask flask
> people.
>
> * I assume that cjw296 *glanced* at your report, saw 'RuntimeError', and
> too quickly assumed 'cpython error'.  flask could have defined, for
> instance, FlaskRuntimeError for its error reporting.
>
> ----------
> nosy: +terry.reedy
> resolution:  -> third party
> stage:  -> resolved
> status: open -> closed
>
> _______________________________________
> Python tracker <report@bugs.python.org>
> <https://bugs.python.org/issue40805>
> _______________________________________
>
msg370650 - (view) Author: Chris Withers (cjw296) * (Python committer) Date: 2020-06-03 10:09
Terry, mock is now a rolling backport of unittest.mock with all development taking place in cpython's repo. If issues are reported there, they need to be triaged here first, as it's most likely a bug in unittest.mock that needs fixing here.

Rob, please can you see if this is reproducible on Python 3.8 or 3.9, replacing `from mock import` with `from unittest.mock` import?
msg370651 - (view) Author: Chris Withers (cjw296) * (Python committer) Date: 2020-06-03 10:14
Rob, you're welcome to dump `mock` and use `unittest.mock`, and that might be best for now, but this will then likely come back to bite you when you end up on the version of Python, probably 3.9, where it is present ;-)
msg370673 - (view) Author: Rob Taft (Rob Taft) Date: 2020-06-03 13:33
I have confirmed that using unittest.mock instead of the 3rd party mock library in python 3.8.3 and 3.9-dev fails to patch flask.g.  3.7.7 works correctly.
msg370675 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2020-06-03 15:23
Chris, I see no evidence in the *incomplete* traceback not posted here that there is a bug in mock.  Mock calls "hasattr(obj, '__func__') and apparently flask, called via werkzeug, answers 'RuntimeError'.  The overt bug is that calling werkzeug.local.(type(obj)?).__getattr__ fails by doing something other than "return the (computed) attribute value or raise an AttributeError exception".
https://docs.python.org/3/reference/datamodel.html#object.__getattr__
msg370678 - (view) Author: Rob Taft (Rob Taft) Date: 2020-06-03 16:23
I updated the test with 2 cases and the traceback is different for each when I expected them to be the same if this was purely a mock issue, the line throwing the error is the same.  I can post this over with flask and see what they think.

from unittest.mock import patch

import flask

def some_function():
    flask.g.somevariable = True
    return flask.g.somevariable

@patch('flask.g')
def test_patch(mock_flask_global):
    assert some_function()

def test_no_patch():
    assert some_function()


$ pytest -vv temp_test.py 
============================================================================================================ test session starts =============================================================================================================
platform linux -- Python 3.8.3, pytest-5.4.3, py-1.8.1, pluggy-0.13.1 -- 
collected 2 items                                                                                                                                                                                                                            

temp_test.py::test_patch FAILED                                                                                                                                                                                                        [ 50%]
temp_test.py::test_no_patch FAILED                                                                                                                                                                                                     [100%]

================================================================================================================== FAILURES ==================================================================================================================
_________________________________________________________________________________________________________________ test_patch _________________________________________________________________________________________________________________

args = (), keywargs = {}

    @wraps(func)
    def patched(*args, **keywargs):
>       with self.decoration_helper(patched,
                                    args,
                                    keywargs) as (newargs, newkeywargs):

../../../.pyenv/versions/3.8.3/lib/python3.8/unittest/mock.py:1322: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.pyenv/versions/3.8.3/lib/python3.8/contextlib.py:113: in __enter__
    return next(self.gen)
../../../.pyenv/versions/3.8.3/lib/python3.8/unittest/mock.py:1304: in decoration_helper
    arg = exit_stack.enter_context(patching)
../../../.pyenv/versions/3.8.3/lib/python3.8/contextlib.py:425: in enter_context
    result = _cm_type.__enter__(cm)
../../../.pyenv/versions/3.8.3/lib/python3.8/unittest/mock.py:1416: in __enter__
    if spec is None and _is_async_obj(original):
../../../.pyenv/versions/3.8.3/lib/python3.8/unittest/mock.py:51: in _is_async_obj
    if hasattr(obj, '__func__'):
pyvenv/lib/python3.8/site-packages/werkzeug/local.py:347: in __getattr__
    return getattr(self._get_current_object(), name)
pyvenv/lib/python3.8/site-packages/werkzeug/local.py:306: in _get_current_object
    return self.__local()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

name = 'g'

    def _lookup_app_object(name):
        top = _app_ctx_stack.top
        if top is None:
>           raise RuntimeError(_app_ctx_err_msg)
E           RuntimeError: Working outside of application context.
E           
E           This typically means that you attempted to use functionality that needed
E           to interface with the current application object in some way. To solve
E           this, set up an application context with app.app_context().  See the
E           documentation for more information.

pyvenv/lib/python3.8/site-packages/flask/globals.py:45: RuntimeError
_______________________________________________________________________________________________________________ test_no_patch ________________________________________________________________________________________________________________

    def test_no_patch():
>       assert some_function()

temp_test.py:14: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
temp_test.py:6: in some_function
    flask.g.somevariable = True
pyvenv/lib/python3.8/site-packages/werkzeug/local.py:364: in <lambda>
    __setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
pyvenv/lib/python3.8/site-packages/werkzeug/local.py:306: in _get_current_object
    return self.__local()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

name = 'g'

    def _lookup_app_object(name):
        top = _app_ctx_stack.top
        if top is None:
>           raise RuntimeError(_app_ctx_err_msg)
E           RuntimeError: Working outside of application context.
E           
E           This typically means that you attempted to use functionality that needed
E           to interface with the current application object in some way. To solve
E           this, set up an application context with app.app_context().  See the
E           documentation for more information.

pyvenv/lib/python3.8/site-packages/flask/globals.py:45: RuntimeError
msg370981 - (view) Author: Rob Taft (Rob Taft) Date: 2020-06-08 13:38
https://github.com/pallets/flask/issues/3637

I've worked around the issue and accept that this will not work in the future.
History
Date User Action Args
2020-06-08 13:38:19Rob Taftsetstatus: open -> closed

messages: + msg370981
2020-06-03 16:23:15Rob Taftsetmessages: + msg370678
2020-06-03 15:23:32terry.reedysetmessages: + msg370675
2020-06-03 14:36:24Rob Taftsetversions: + Python 3.9, - Python 3.6
2020-06-03 13:33:01Rob Taftsetmessages: + msg370673
2020-06-03 10:14:10cjw296setmessages: + msg370651
2020-06-03 10:09:29cjw296setstatus: closed -> open
2020-06-03 10:09:23cjw296setnosy: + cjw296
messages: + msg370650
2020-05-30 10:42:18Rob Taftsetmessages: + msg370368
2020-05-30 03:01:56terry.reedysetstatus: open -> closed

nosy: + terry.reedy
messages: + msg370356

resolution: third party
stage: resolved
2020-05-29 12:15:58rkmsetnosy: + rkm
2020-05-28 12:52:45xtreaksetnosy: + xtreak
2020-05-28 12:19:57Rob Taftcreate