Search code examples
pythonmockingpytestbiopythonmonkeypatching

monkeypatch function in module whose namespace was overwritten


I am trying to monkeypatch a function in an external module I use, but monkeypatch can't seem to access the function because the namespace of the module gets overwritten on import.

Concretely, I use a Bio.PDB.PDBList.PDBList object (biopython module) in my code, and I am trying to patch _urlretrieve in Bio.PDB.PDBList to prevent calls to the internet and instead get files from a local directory, without having to mock the instance methods of PDBList which would be substantially more work. But when I try the naïve:

m.setattr("Bio.PDB.PDBList._urlretrieve", mock_retrieve)

pytest complains:

AttributeError: 'type' object at Bio.PDB.PDBList has no attribute '_urlretrieve'

On further inspection of Bio.PDB, I can see that the module namespace .PDBList seems to be overwritten by the class .PDBList.PDBList:

# Download from the PDB
from .PDBList import PDBList

So that would explain why pytest sees Bio.PDB.PDBList as a type object with no attribute _urlretrieve. My question is, is there any way to get monkeypatch to patch this 'hidden' function?


Concrete example of usage of PDBList class:

from Bio.PDB.PDBList import PDBList

_pdblist = PDBList()

downloaded_file = _pdblist.retrieve_pdb_file('2O8B', pdir='./temp', file_format='pdb')

Solution

  • You are right - since the PDBList class has the same name as the module Bio.PDB.PDBList, after import Bio.PDB.PDBList you won't be able to access the module by its name (shadowing problem). However, you can still grab the imported module object from the loaded modules cache and monkeypatch that:

    import sys
    from unittest.mock import Mock
    import Bio.PDB.PDBList
    
    def test_spam(monkeypatch):
        assert isinstance(Bio.PDB.PDBList, type)
        with monkeypatch.context() as m:
            m.setattr(sys.modules['Bio.PDB.PDBList'], '_urlretrieve', Mock())
            ...