pytestarch icon indicating copy to clipboard operation
pytestarch copied to clipboard

bug or feature request: dealing with non-unique root for architecture

Open Lenormju opened this issue 3 months ago • 0 comments

I have a strange behavior when using pytestarch on one of my projects, which is partly due of its unusual code layout, so I wanted to show it to you so that you can decide if is a bug or not, and if it should be fixed or not.

Given this file structure :

.
├── src
│   ├── package1
│   │   ├── __init__.py
│   │   └── module1.py
│   └── package2
│       ├── __init__.py
│       └── module2.py
└── tests
    └── test_arch.py

and my modules contents :

# file: module1.py

import sys

from package2 import module2

if __name__ == "__main__":
    print(sys.path)
    module2.hello_world()

and

# file: module2.py

def hello_world():
    print("hello world")

I can run the code fine with :
$ PYTHONPATH=src uvx python -m package1.module1

['/workspace/pytestarch_multiple_packages', '/workspace/pytestarch_multiple_packages/src', .....................]
hello world

Because I'm running python in python -m mode, it appends the current dir instead of the script dir in the PYTHONPATH. And before that, I have already added the src dir (sources root).

So for me, everything works fine (although forcing the PYTHONPATH in such a way is not recommended IMO).

But then comes pytestarch :

# file: test_arch.py

from pprint import pprint
from pathlib import Path

import pytestarch

ROOT_DIR = Path(__file__).parent.parent  # pytestarch_multiple_packages/
SRC_DIR = ROOT_DIR / "src"

arch = pytestarch.get_evaluable_architecture(
        root_path=str(SRC_DIR),
        module_path=str(SRC_DIR),
        # exclude_external_libraries=False,
    )
pprint(sorted(arch.modules))

And when I run it : $ uvx --with pytestarch python tests/test_arch.py

['src',
 'src.package1',
 'src.package1.__init__',
 'src.package1.module1',
 'src.package2',
 'src.package2.__init__',
 'src.package2.module2']

Here, all of my packages are prefixed by src.. It is my first problem. I was (wrongly ?) expecting them without the src module prefix, like so :

-['src',
+[
- 'src.package1',
+ 'package1',
- 'src.package1.__init__',
+ 'package1.__init__',
- 'src.package1.module1',
+ 'package1.module1',
- 'src.package2',
+ 'package2',
- 'src.package2.__init__',
+ 'package2.__init__',
- 'src.package2.module2']
+ 'package2.module2']

My second problem comes when I add the exclude_external_libraries=False, then I get :

['package2',
 'src',
 'src.__init__',
 'src.package1',
 'src.package1.__init__',
 'src.package1.module1',
 'src.package2',
 'src.package2.__init__',
 'src.package2.module2',
 'sys']

The sys was expected, but not that there is now a package2, which is sort-of a duplicate but without the src prefix.

So I don't know what exactly I'm doing wrong. I'm not sure to really understand what the distinction is between root_path and module_path, and how it relates to the Python import system (which I know well). Is this library intended to work only for repositories where the code is all located in a single top-level package ? It seems like it.

If I create a single top-level package, put the 2 packages inside it :

.
├── src
│   └── toplevel_package
│       ├── __init__.py
│       ├── package1
│       │   ├── ...
│       └── package2
│           ├── ...
 # file: module1.py
-from package2 import module2
+from toplevel_package.package2 import module2
 # file: test_arch.py
-ROOT_DIR = Path(__file__).parent.parent
-SRC_DIR = ROOT_DIR / "src"
+SRC_DIR = Path(__file__).parent.parent / "src"
+TOPLEVEL_PACKAGE_DIR = SRC_DIR / "toplevel_package"

 arch = pytestarch.get_evaluable_architecture(
-        root_path=str(SRC_DIR),
-        module_path=str(SRC_DIR),
+        root_path=str(TOPLEVEL_PACKAGE_DIR),
+        module_path=str(TOPLEVEL_PACKAGE_DIR),

Then all is working fine :

  • $ PYTHONPATH=src uvx python -m toplevel_package.package1.module1 lists the correct sys.paths and its import works
  • uvx --with pytestarch python tests/test_arch.py gives the expected modules.

Lenormju avatar Sep 24 '25 19:09 Lenormju