r/learnpython • • 11h ago

dynamic import module path hack in pytest

In order to validate (using pytest) a python module which is not in my path or relative folders, I want to hack the import path.

I'm wondering what alternatives there are to doing a PYTHONPATH hack. I could use https://docs.pytest.org/en/7.1.x/reference/reference.html#confval-pythonpath, but that applies to the entire session as I understand it. And I only want to change one suite, so I am writing a quick fixture that will roughly look like (hard-coded for simplicity)

    @pytest.fixture(scope="function")
    def modules_path():
        old_path = sys.path
        extra_path= os.path.abspath("c:/otherpath/other") # replace constant with a func
        sys.path.insert(0, extra_path)
        yield extra_path
        sys.path = old_path

I've always felt there was a better way to do import path hacks that nobody had ever told me about. Or is this @fixture approach not as evil as I assumed it was, and was merely biased by painful memories whenever I did use it. Alternatives?

3 Upvotes

7 comments sorted by

6

u/stickycontentss 10h ago

A few alternatives that are cleaner than exporting PYTHONPATH, roughly in order of preference:

  1. Make the module installable. pip install -e . (or uv pip install -e .) with a minimal pyproject.toml - after that pytest imports it like any installed package and the problem disappears permanently, for you and anyone else who clones the repo.

  2. pytest's built-in pythonpath ini option (pytest 7+). In pyproject.toml:

    toml [tool.pytest.ini_options] pythonpath = ["src"]

    This is the same effect as the PYTHONPATH hack, but declarative, scoped to tests only, and applied before collection - unlike sys.path.insert() inside a test module, which runs too late and depends on import order.

  3. Root conftest.py that does the path insert once - it works because conftest is loaded before test imports, but the pytest docs now recommend option 2 instead.

  4. If the file genuinely isn't importable (not on the path at all), load it explicitly in a fixture with importlib.util.spec_from_file_location.

Also worth knowing: addopts = "--import-mode=importlib" avoids the duplicate-module-name clashes you can hit when the same filename exists in several folders.

1

u/zaphodikus 10h ago edited 10h ago
  1. It's a pytest, so I don't need to turn it into a module really, nobody else needs the code. but, I did not know about -e before though, so another cool learning. I'm stealing the file from another repo merely for this one test-case only.

  2. pyproject.toml feels most logical : I'm currently storing settings in a totally different file, which is why I figured I would just import that file in my example fixture. I originally thought I might want to test multiple iterations, but I probably only need one path change, so making it global is not a huge problem. But to be honest I could go ahead and create a pyproject.toml file and just change my tooling to always update the toml file before it starts the test suite. I'm not using a .toml yet and today might be a good day to start doing so. It's also a zero code change!

  3. conftest.py , technically a no-code way, but it's not a pure data file.

  4. Although, it's also just logical to use importlib.util.spec_from_file_location. I would still do that in a fixture and just use del to ensure teardown I guess. So many options!

1

u/stickycontentss 10h ago

Good place to land - for a single path change, one pyproject.toml with [tool.pytest.ini_options] pythonpath = [...] is honestly enough, and pytest picks it up automatically for everyone who runs the tests.

The reason I mentioned -e is mainly that your "settings in another file" trick keeps working forever without tooling changes once the project is installable, but if this stays a one-off test, don't bother. Keep it boring.

1

u/socal_nerdtastic 10h ago

Why can't you start the pytest program in the correct folder?

cd c:/otherpath/other
py c:/path/to/pytest/program/testymctestface.py

1

u/zaphodikus 10h ago

Because the correct folder may move about and I might have multiple correct folders. I am tempted to wrap the check in pytester which is something I only just learned pytester exists and might be a good learning investment.