Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mock.patch.dict spoils order of items in collections.OrderedDict #69116

Closed
AlexanderOblovatniy mannequin opened this issue Aug 24, 2015 · 24 comments
Closed

mock.patch.dict spoils order of items in collections.OrderedDict #69116

AlexanderOblovatniy mannequin opened this issue Aug 24, 2015 · 24 comments
Assignees
Labels
3.9 only security fixes easy stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error

Comments

@AlexanderOblovatniy
Copy link
Mannequin

AlexanderOblovatniy mannequin commented Aug 24, 2015

BPO 24928
Nosy @rhettinger, @rbtcollins, @cjw296, @bitdancer, @voidspace, @berkerpeksag, @eamanu, @mariocj89, @tirkarthi, @dojutsu-user
PRs
  • bpo-24928: Add test case for patch.dict using OrderedDict #11437
  • Files
  • issue24928.patch: patch for issue 24928 with unittest
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = 'https://github.com/voidspace'
    closed_at = <Date 2020-01-25.21:07:23.297>
    created_at = <Date 2015-08-24.19:19:55.168>
    labels = ['easy', 'type-bug', 'library', '3.9']
    title = 'mock.patch.dict spoils order of items in collections.OrderedDict'
    updated_at = <Date 2020-01-25.21:07:23.295>
    user = 'https://bugs.python.org/AlexanderOblovatniy'

    bugs.python.org fields:

    activity = <Date 2020-01-25.21:07:23.295>
    actor = 'rhettinger'
    assignee = 'michael.foord'
    closed = True
    closed_date = <Date 2020-01-25.21:07:23.297>
    closer = 'rhettinger'
    components = ['Library (Lib)']
    creation = <Date 2015-08-24.19:19:55.168>
    creator = 'Alexander Oblovatniy'
    dependencies = []
    files = ['40488']
    hgrepos = []
    issue_num = 24928
    keywords = ['patch', 'easy', 'needs review']
    message_count = 24.0
    messages = ['249069', '249070', '249073', '249102', '250874', '254725', '331473', '331584', '332282', '332286', '332291', '332540', '332575', '332976', '332977', '333056', '333057', '333058', '333059', '333380', '333890', '334085', '360591', '360592']
    nosy_count = 13.0
    nosy_names = ['rhettinger', 'rbcollins', 'cjw296', 'r.david.murray', 'michael.foord', 'berker.peksag', 'nekobon', 'azsorkin', 'Alexander Oblovatniy', 'eamanu', 'mariocj89', 'xtreak', 'dojutsu-user']
    pr_nums = ['11437']
    priority = 'normal'
    resolution = 'fixed'
    stage = 'resolved'
    status = 'closed'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue24928'
    versions = ['Python 3.9']

    @AlexanderOblovatniy
    Copy link
    Mannequin Author

    AlexanderOblovatniy mannequin commented Aug 24, 2015

    Hi!

    Current implementation of patch.dict spoils order of items in collections.OrderedDict, because it explicitly converts passed values to dict (

    cpython/Lib/unittest/mock.py

    Lines 1559 to 1560 in 923527f

    # support any argument supported by dict(...) constructor
    self.values = dict(values)
    ):

    # support any argument supported by dict(...) constructor
    self.values = dict(values)

    Most obvious way is to check if values is an instance of dict. If it's not, then we need to convert it to dict:

    if not isinstance(values, dict):
        values = dict(values)
    
    self.values = values

    This will work for OrderedDict, because it's a subclass of dict. But this will not work for UserDict.UserDict, UserDict.DictMixin, collections.MutableMapping, etc., because they do not subclass dict. So, better way is to less strict check and look if values implements dict-like interface, e.g.:

    if not hasattr(values, 'update'):
        values = dict(values)
    
    self.values = values

    Here is a question existence of what attrs to check.

    Any ideas?

    Thanks!

    @AlexanderOblovatniy AlexanderOblovatniy mannequin added stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error labels Aug 24, 2015
    @AlexanderOblovatniy
    Copy link
    Mannequin Author

    AlexanderOblovatniy mannequin commented Aug 24, 2015

    Hi!

    Current implementation of patch.dict spoils order of items in collections.OrderedDict, because it explicitly converts passed values to dict (

    cpython/Lib/unittest/mock.py

    Lines 1559 to 1560 in 923527f

    # support any argument supported by dict(...) constructor
    self.values = dict(values)
    ):

        # support any argument supported by dict(...) constructor
        self.values = dict(values)

    Most obvious way is to check if values is an instance of dict. If it's not, then we need to convert it to dict:

        if not isinstance(values, dict):
            values = dict(values)
    
        self.values = values

    This will work for OrderedDict, because it's a subclass of dict. But this will not work for UserDict.UserDict, UserDict.DictMixin, collections.MutableMapping, etc., because they do not subclass dict. So, better way is to less strict check and look if values implements dict-like interface, e.g.:

        if not hasattr(values, 'update'):
            values = dict(values)
    
        self.values = values

    Here is a question existence of what attrs to check.

    Any ideas?

    Thanks!

    @bitdancer
    Copy link
    Member

    Based on reading the patch.dict doct, I'm guessing that that dict call is making a copy in order to do a restore later. Perhaps replacing the dict call with a copy call would be sufficient? (I haven't looked at the dict.patch code).

    @rhettinger
    Copy link
    Contributor

    Perhaps replacing the dict call with a copy call would be sufficient?

    I think that would do it.

    @bitdancer bitdancer added the easy label Aug 25, 2015
    @nekobon
    Copy link
    Mannequin

    nekobon mannequin commented Sep 17, 2015

    Submitting a patch.

    To support both iterable and mapping in the same way as with dict(...), values is updated to be a list of length-2 iterables instead of using copy call.

    Patch includes unittest which tests the reported problem.

    @voidspace
    Copy link
    Contributor

    Patch looks good to me.

    @tirkarthi
    Copy link
    Member

    Thanks @nekobon for the patch. I am triaging old mock related issues. I think dict insertion order is maintained from 3.6 and guaranteed with 3.7 and above. But it would be good to add the unit test in the patch as a PR. I ran the test on master and it passes.

    @tirkarthi tirkarthi added 3.7 (EOL) end of life 3.8 only security fixes labels Dec 10, 2018
    @cjw296
    Copy link
    Contributor

    cjw296 commented Dec 11, 2018

    More tests are generally a good thing, so go for it :-)

    @tirkarthi
    Copy link
    Member

    Thinking about it further the attached test is based on the ordering of dict and hence works only on 3.6 and above. But this test will be backported to mock on PyPI where this will fail on 2.7 and 3.4, 3.5. Is it okay to apply the fix that makes backporting easy though it has no effect on current CPython implementation or to only apply the test since it's fixed in CPython due to dict implementation detail and make sure the cabal backporting is done with this detail taken into account to run test only on 3.6+. But the latter makes the issue still prevalent in cabal's mock repo.

    I think it's better to apply the fix along with the test that will make it easy on both ends. I am not aware of the backport process for mock on PyPI so feedback will be helpful on this.

    @mariocj89
    Copy link
    Mannequin

    mariocj89 mannequin commented Dec 21, 2018

    I would suggest applying the fix with the latest version in mind to keep the codebase clean. If you want to backport it to previous versions of Python you can do it manually through a PR targetting the right branch.
    I think the process is to set up a label and then use https://github.com/python/core-workflow/tree/master/cherry_picker as I'd expect the tests to fail to apply the patch "manually.

    Alternative 1: Might be easier is to send a patch that works in all version and another one that "modernises" it to python3.7. Basically, write all tests with OrderedDict and then just shoot an extra PR that converts that to a plain dict.

    Alternative 2: This is only a problem on versions on versions < 3.6, isn't it? If so you might want to close this issue or just have a PR that targets the 3.5 if worth backporting and/or PyPI's. (This is my conservative mind as you know I usually push for changing as less as possible hehe).

    @tirkarthi
    Copy link
    Member

    Thanks Mario, I will convert the unit test as a PR before closing the issue since I feel the test is a good one for inclusion and can help if dict order guarantee is changed in future. I will raise a backport PR to cabal's mock repo where the fix with test can be merged with reference to this issue.

    @dojutsu-user
    Copy link
    Mannequin

    dojutsu-user mannequin commented Dec 26, 2018

    Hi.
    I would like to make a PR for this.
    Also, I am not very familiar with the process of backporting. Is something specific needs to be done for that which is related to this?

    @tirkarthi
    Copy link
    Member

    Hi Vaibhav,

    As noted in the thread the issue is fixed in 3.6 and above due to dict order being guaranteed. But it would be nice to have the test in the patch converted as a unit test. With respect to backport the fixes are backported to https://github.com/testing-cabal/mock to make mock library available for older versions of Python which would required the fix since dict order is not guaranteed in older versions. Once the test to CPython is merged you can make a PR to the mock repo with the fix and the test.

    I haven't started working on a PR for this so feel free to go ahead.

    @eamanu
    Copy link
    Mannequin

    eamanu mannequin commented Jan 4, 2019

    ping Vaibhav :-)

    I would like to make a PR for this.

    @dojutsu-user
    Copy link
    Mannequin

    dojutsu-user mannequin commented Jan 4, 2019

    Hi Emmanuel

    Please go ahead and make a PR. :)

    @eamanu
    Copy link
    Mannequin

    eamanu mannequin commented Jan 5, 2019

    Hi xtreak,

    Thanks @nekobon for the patch. I am triaging old mock related issues. I think dict insertion order is maintained from 3.6 and guaranteed with 3.7 and above. But it would be good to add the unit test in the patch as a PR. I ran the test on master and it passes.

    I am running the test on master and fail. I don't think that the orderdict on patch.dict is implement.

    Or maybe I am wronging somewhere

    @tirkarthi
    Copy link
    Member

    Can you please post the error and the command to run the test? On applying the patch on master I cannot see any errors with below commands :

    # Applying the patch with only test

    $ wget https://bugs.python.org/file40488/issue24928.patch
    $ git apply issue24928.patch
    $ git checkout Lib/unittest/mock.py # Only tests are needed

    # Running tests with no errors

    • ./python.exe Lib/unittest/test/testmock/
    • ./python.exe -m unittest -v unittest.test.testmock
    • ./python.exe -m unittest -v unittest.test.testmock.testpatch

    I can see an error running the file separately using ./python.exe [Lib/unittest/test/testmock/testpatch.py](https://github.com/python/cpython/blob/main/Lib/unittest/test/testmock/testpatch.py) but I don't think it's related to the patch :

    ./python.exe Lib/unittest/test/testmock/testpatch.py
    ............................................................................................F....
    ======================================================================
    FAIL: test_special_attrs (main.PatchTest)
    ----------------------------------------------------------------------

    Traceback (most recent call last):
      File "Lib/unittest/test/testmock/testpatch.py", line 1870, in test_special_attrs
        self.assertEqual(foo.__module__, 'unittest.test.testmock.testpatch')
    AssertionError: '__main__' != 'unittest.test.testmock.testpatch'
    - __main__
    + unittest.test.testmock.testpatch

    Ran 97 tests in 0.704s

    FAILED (failures=1)

    Build info

    $ ./python.exe
    Python 3.8.0a0 (heads/master:47a2fced84, Jan  4 2019, 10:36:15)

    @eamanu
    Copy link
    Mannequin

    eamanu mannequin commented Jan 5, 2019

    Sorry I was wrong.

    On

    foo = OrderedDict()
    foo['a'] = object()
    foo['b'] = 'something'

    I was write "first" and "second" like key and fail in

    @patch.dict(foo, OrderedDict(update_values))
    def test():
        self.assertEqual(list(foo), sorted(foo))
    
    test()

    Sorry.

    Now I am sending the PR

    @tirkarthi
    Copy link
    Member

    No problem :) I think the test can use a context manager instead of using test() and a decorator but that can be discussed on the PR.

    Thanks!

    @eamanu
    Copy link
    Mannequin

    eamanu mannequin commented Jan 10, 2019

    Ping :-)

    Thanks Karthikeyan for the PR review.

    Would someone else like review it, please?

    Thanks!
    Regards

    @berkerpeksag
    Copy link
    Member

    While I agree having more tests are a good thing, I'm not sure if the test in PR 11437 should be merged as it's not specifically testing a feature of the mock module.

    patch.dict() basically does the following operation (ignoring possible AttributeErrors):

       # Keep the original one to use later.
       d = original_dict.copy()
    
       original_dict.update(new_dict)

    I think the relationship between dict and OrderedDict (including any other dict subclasses and dict-like objects) and anything related to insertion order should be tested either in test_dict or in test_ordered_dict.

    Also, the test can be simplified, but I will let other core developers chime in with their thoughts before reviewing PR 11437 on GitHub.

    @eamanu
    Copy link
    Mannequin

    eamanu mannequin commented Jan 20, 2019

    Hi Berker,

    Thanks for you response.

    I agree when you say that patch.dict is a simply operation, but I think that this test can be necessary, if for some reason the implementation of patch.dict (or its parts) decide change.

    I think the relationship between dict and OrderedDict (including any other dict subclasses and dict-like objects) and anything related to insertion order should be tested either in test_dict or in test_ordered_dict.

    I am not sure if this has to be tested on test_dict or test_oredered_dict, because we are not testing specifically dict-like objects.

    This test, can be written on test_patch_dict and not create a new test_patch_dict_with_orderdict.

    @cjw296
    Copy link
    Contributor

    cjw296 commented Jan 24, 2020

    As I said before, I can't see an additional test like this hurting, especially if it highlights problems with earlier python versions when it's backported.

    @cjw296
    Copy link
    Contributor

    cjw296 commented Jan 24, 2020

    New changeset 1d0c5e1 by Chris Withers (Emmanuel Arias) in branch 'master':
    bpo-24928: Add test case for patch.dict using OrderedDict (GH -11437)
    1d0c5e1

    @rhettinger rhettinger added 3.9 only security fixes and removed 3.7 (EOL) end of life 3.8 only security fixes labels Jan 25, 2020
    @ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    3.9 only security fixes easy stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error
    Projects
    None yet
    Development

    No branches or pull requests

    6 participants