Import a shadowed module from a package

Viewed 66

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.

1 Answers

I had to rename the module: run.py -> shoot.py:

init.py:

from .shoot import *

test.py:

import src.damastes.shoot as shoot
...
class TestNonPureHelpers:
    def new_args(self):
        return RestrictedDotDict(copy.deepcopy(CLEAN_CONTEXT_PARAMS))

    def test_path_compare(self, monkeypatch):
        args = self.new_args()
        monkeypatch.setattr(shoot, "_ARGS", args)
        args.sort_lex = True
        assert shoot._path_compare("10alfa", "2bravo") == -1
        args.sort_lex = False  # Sort naturally.
        assert shoot._path_compare("10alfa", "2bravo") == 1

The solution is kinda obvious, yet a bit uncomfortable. Not unthinkable for an ad hoc language like Python, but it is against the scope idea. Names inside the module should not conflict with the module name.

Is the renaming solution the only one possible?

Related