pythonimportpytestmonkeypatching

Import a shadowed module from a package


This is my package structure:

.
├── src
│   ├── damastes
│   │   ├── __init__.py
│   │   ├── main.py
│   │   └── run.py

__init__.py:

from .run import *

run.py:

...
_ARGS
...
def _path_compare(path_x: Path, path_y: Path) -> Ord:
    return (
        ...
        if _ARGS.sort_lex
        else ...
    )
...
def run()
...

I can import whatever I wish from the run module, but not the module run itself. It did not inconvenience me until I had to provide the module for monkey patching:

test.py:

from src.damastes.run import _path_compare
...
def test_path_compare(monkeypatch):
    args = RestrictedDotDict(copy.deepcopy(CLEAN_CONTEXT_PARAMS))
    args.sort_lex = True
    monkeypatch.setattr(src.damastes.run, "_ARGS", args)
    assert _path_compare("alfa", "alfa") == 0

My trouble is that monkeypatch.setattr() requires module as a first parameter, but I can't supply it. src.damastes.run is actually a function. As it should be. The "short-circuit" is intentional.

The error:

AttributeError: <function run at 0x7f3392b9f790> has no attribute '_ARGS'

Experimentally, I supply src.damastes:

AttributeError: <module 'src.damastes' from '/src/damastes/__init__.py'> has no attribute '_ARGS'

Of course it has not. Is there a way to supply the run module to monkeypatch.setattr() without restructuring the existing package/import solution?

from src.damastes.run import _path_compare part somehow works, by the way.


Solution

  • Given that from src.damastes.run import _path_compare works, it's clear that src.damastes.run is a valid module identifier.

    But it looks like the run function is shadowing the run module. So consider using one of these tricks to access the module specifically:

    from src.damastes.run import _path_compare
    
    # Consider using the sys.modules.
    # This is a dictionary that maps module names to modules which have already been loaded.
    # this should work because of the `from src.damastes.run import ...` statement above
    import sys
    run_module = sys.modules["src.damastes.run"]
    
    # or you could use the `importlib` module:
    import importlib
    run_module = importlib.import_module("src.damastes.run")
    
    # Once you have the module bound to a variable
    # you can patch it:
    monkeypatch.setattr(run_module, "_ARGS", "TESTING!")
    assert run_module._ARGS == "TESTING!"