0

Real goal: I have a module that is common between two packages (say, bar and bar2). I want to use exact same test files for both cases so I want to change the test imports to not name the package explicitly. (Why? This can be useful during the process of extracting modules from a mega-package into separate packages.)

My idea was to add another module that imports a particular package and provides an "alias" for it. It almost worked, but I got a problem.

Initially I had:

# test.py:
from bar import some_function

If I do nothing magical, there will be two versions of test.py: one with from bar import some_function and another with from new_bar import some_function. I want to avoid this and have the test code files remain the same.

After I added indirection:

#foo.py:
import bar as baz

#test.py:
from .foo import baz  # Works!
from .foo.baz import some_function  # ModuleNotFoundError: No module named 'cur_dir.foo.baz'; 'cur_dir.foo' is not a package

I can make foo a package:

#foo/__init__.py:
import bar as baz

#test.py:
from .foo import baz  # Works!
from .foo.baz import some_function  # ModuleNotFoundError: No module named 'cur_dir.foo.baz'

The error changes a bit, but still remains.

I know that I can work around the problem by writing

# test.py:
from .foo import baz
some_function = baz.some_function

Is there any other way? I want my imports to be "normal".

Is there a way to create an "alias" for a package that can be used with the standard import mechanism?

Ark-kun
  • 6,358
  • 2
  • 34
  • 70
  • Note that module imports (the part in the ``from ...``) work directly on the modules of your system. They do not inspect the *content* of parent modules. So making an alias of ``baz = bar`` (what ``import bar as baz`` does) is not visible to the import machinery. – MisterMiyagi Mar 23 '20 at 07:53
  • It doesn't sound like this would actually help with your goal of using the same test files. The test files for both packages can use `from bar import some_function` without your import fiddling. With your import fiddling, the two packages will have different aliases, and you'll have to have different test files if you want your test files to use the aliases. – user2357112 Mar 23 '20 at 07:54
  • @user2357112supportsMonica Without fiddling I will need `from bar1 import some_function` in one case and `from bar2 import some_function` in another case in all test files. I want to only have a single small file which is different. – Ark-kun Mar 23 '20 at 09:48
  • I'm introducing `foo.baz` as an alias which will, in different repos, point to different packages. – Ark-kun Mar 23 '20 at 09:55

1 Answers1

1

The import statement only looks at actual modules and their paths, not at aliases inside the loaded modules. An actual module alias in Python's module registry, sys.modules, is required.

import sys
import os

sys.modules["os_alias"] = os  # alias `os` to `os_alias`
import os_alias               # alias import works now
from os_alias import chdir    # even as from ... import ...

Once a module alias has been added to sys.modules, it is available for import in the entire application.


Note that module aliasing can lead to subtle bugs when submodules of aliased modules are loaded. In specific, if the submodules are not aliased explicitly, separate versions are created that are not identical. This means that any tests based on object identity, including isinstance(original.submodule.someclass(), alias.submodule.someclass), will fail if the versions are mixed.

To avoid this, you must alias all submodules of any aliased package.

MisterMiyagi
  • 44,374
  • 10
  • 104
  • 119
  • 1
    Adding aliases to `sys.modules` can lead to some [really weird bugs](https://stackoverflow.com/questions/46715586/isinstance-unexpectedly-returning-false). It has a lot more weird side effects than are apparent at first glance. – user2357112 Mar 23 '20 at 08:03
  • @user2357112supportsMonica Indeed. AFAIK that is common to all forms of module aliases. – MisterMiyagi Mar 23 '20 at 08:08
  • >"import os_alias" - Will the `from ... import ...` syntax work with this? // Will try tomorrow... – Ark-kun Mar 23 '20 at 09:57
  • @Ark-kun Yes, I've added it to the example now. – MisterMiyagi Mar 23 '20 at 09:59