-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Configuring discovery for paths with namespace packages #5147
Comments
I'd like to see pytest work better in this situation as well. I think once we can leave python2 behind this becomes even easier (and more common at the same time) as I'm not quite sure the best way to get to that state without breaking everything though 🤔 In my opinion ideally the "namespace package" situation would be the default, and the class of errors around test filename collision would disappear. Though I haven't put any thought or effort into seeing how this would work in code, @nicoddemus would know better |
I've done a test implementation based on hoefling's answer to the SO question above except instead of having an independently-specified list of namespace package dirs I simply use I've only given it brief testing, but it seems to be working pretty well. I will report back again after further testing. I don't know why I didn't think of this before, because it actually seems to be the most obvious and sensible solution (or at least partial solution) to the issue since, when the module is under My current thinking on the matter is that While using |
I just ran into the same thing. Something like a command line option that could be added to |
I don't think that using BTW, for those wondering where I'm at with all of this, my hack above seems to have worked well over the initial week since I implemented it, but the last ten days have been a long holiday in Japan (Golden Week) and so all work on the project using this has stopped. Assuming my change goes for another week or so without any issues, I will look at trying to find the time to code up a PR for this. (It's unfortunately not trivial in part because it requires changes to both |
I would love to help get a PR out if somebody pointed me in the right direction on what the recommended approach is. |
we have a concept of "collection roots" floating around, however its not yet formalized for me personally thats something i'd like to tackle sometime after sorting out the collection tree structure many of the details are currently unclear to me |
Hi! To circumvent the issue I've patched the package discovery to treat paths that are already included in In my opinion it would be favorable to have valid PEP420 namespaces working as expected by default. With my patch, this would (I think) always be the case unless the |
Using existing paths in |
@0cjs |
@blueyed That hadn't occurred to me! I wouldn't want to do that for Python ≥3.5, where we have better methods of dealing with that, but it seems to me a perfectly reasonable idea to consider when running earlier versions of Python, in the hope of maintaining more compatibility between pytest on ≤3.4 and ≥3.5. I suppose this would also involve removing the module from This would require a bit of thought, but possibly might work quite well. |
@0cjs diff --git a/src/_pytest/python.py b/src/_pytest/python.py
index 372631f10..265bf0689 100644
--- a/src/_pytest/python.py
+++ b/src/_pytest/python.py
@@ -515,6 +515,7 @@ def _importtestmodule(self) -> ModuleType:
# we assume we are only called once per module
importmode = self.config.getoption("--import-mode")
fspath = self.fspath
+ old_sys_path = sys.path[:] if importmode != "importlib" else None
try:
mod = fspath.pyimport(ensuresyspath=importmode)
except SyntaxError:
@@ -554,6 +555,9 @@ def _importtestmodule(self) -> ModuleType:
"or @pytest.mark.skipif decorators instead, and to skip a "
"module use `pytestmark = pytest.mark.{skip,skipif}."
)
+ finally:
+ if old_sys_path:
+ sys.path = old_sys_path
self.config.pluginmanager.consider_module(mod)
return mod Feel free to pick it up into a PR. |
As for pytest you do not have to consider anything before Python 3.5 btw. |
@blueyed Is the above code for pytest 4.x? Because, as you pointed out to me also above (thanks!), pytest 5.x no longer supports Python 3.4 or 2.7. For more modern versions of Python, wouldn't it make sense to use the more modern important mechanisms that neither require nor touch |
I'm fairly certain you need to touch |
@asottile writes:
Importing other tests as modules isn't a problem if you do it in the normal way. Say you have So with
This works fine currently, and will continue to work with all variants my proposed changes. EDIT: The following is not correctly expressing what I'm trying to say, nor is the code correct; see further discussion below for details. What will not work in my proposal is relying on
I (and I think most sensible people) would argue that that's absolutely fine. If you want to use it, import it explicitly, rather than relying on magic that breaks other things. (E.g., in the current code the above will—sometimes!—break (and often in mysterious ways) if you also have an |
please don't go after intelligence to try and make your point, it's not productive to conversation right, but it's a major breaking change to adjust the module hierarchy here that is currently importable as your proposal would break existing imports. large breaking changes need very good reason to move and a migration / deprecation period. your example where |
@asottile I'm not sure we have any real disagreement here; I think that perhaps taking a more charitable interpretation of my remarks when there's ambiguity or potential errors may help keep conversations working well. You can start with taking my "most sensible people" comment as, ""people who prefer well-defined behaviour that's in line with what the Python interpreter does." As I've mentioned several times previously, both here and in #6966, I'm well aware that what I'm proposing breaks certain kinds of changes that pytest makes to the module hierarchy, such that code is different when it runs under pytest as opposed to when it's not.
Exactly. I argue that this is behaviour that's worth changing. I believe that it's not well-defined (in particular, selecting different subsets of tests to collect can change what module is made available under a given name), and I see no indication that this was ever desired behaviour, rather than an unfortunate side-effect of the implementation (in large part due to what was available in earlier Python APIs).
Some existing imports, yes. I think that the long-term (and even short-term) benefits to the community outwigh the cost of the change.
Yes, we are in full agreement here. (I had hoped that would be clear from my comments about backward compatibility here and in #6966, but perhaps it was not.)
Sorry, I got my explanation wrong in the post above. That second example would require an |
Today I updated pytest to 6.0.1 and it broke my testing. After a small dig I've found the culprit. The new method Quick example: # These are the import paths initially setup by environment and IDE, with packages added in comments.
sys.path = [
"/myproj/component_a",
# myproj (PEP420)
# abc (normal package)
# tests (PEP420)
# myproj (PEP420)
# abc (normal package with conftest inside)
"/python_venv/site-packages",
# abc (installed dependency)
] I have imports in my code like
Now the same import As a workaround I've patched the mentioned function. It's very similar to how I've fixed PEP420 before pytest 6.0. #6966 (comment) |
I wonder if this issue is somewhat related to a problem I have when trying to test doctest modules (via the
where
the
or
though this actually results in certain tests failing—likely because of the import structure. |
<h2>Rationale</h2> <p>Implicit namespace packages are directories of Python files without an <code>__init__.py</code>. They’re valid and importable, but they break <em>many</em> tools, such as:</p> <ul> <li><p><a href="https://bugs.python.org/issue23882" rel="nofollow">unittest test discovery</a> (and by extension, Django’s test runner)</p></li> <li><p><a href="nedbat/coveragepy#1024" rel="nofollow">Coverage.py</a></p></li> <li><p>Mypy without its <a href="https://mypy.readthedocs.io/en/latest/command_line.html#import-discovery" rel="nofollow">–namespace-packages option</a></p></li> <li><p><a href="pytest-dev/pytest#5147" rel="nofollow">pytest</a></p></li> </ul> https://pypi.org/project/flake8-no-pep420/
<h2>Rationale</h2> <p>Implicit namespace packages are directories of Python files without an <code>__init__.py</code>. They’re valid and importable, but they break <em>many</em> tools, such as:</p> <ul> <li><p><a href="https://bugs.python.org/issue23882" rel="nofollow">unittest test discovery</a> (and by extension, Django’s test runner)</p></li> <li><p><a href="nedbat/coveragepy#1024" rel="nofollow">Coverage.py</a></p></li> <li><p>Mypy without its <a href="https://mypy.readthedocs.io/en/latest/command_line.html#import-discovery" rel="nofollow">–namespace-packages option</a></p></li> <li><p><a href="pytest-dev/pytest#5147" rel="nofollow">pytest</a></p></li> </ul> https://pypi.org/project/flake8-no-pep420/
I've also been having trouble with this, in particular that pytest does not run doctests for packages that use PEP-420 namespaces. My current workaround is to use the older pkgutils-style namespaces, despite their downsides. |
It should be supported in pytest/testing/test_pathlib.py Lines 1011 to 1040 in 2e5da5d
@dalleyg feel free to open a new issue with a MWE if this is not working for you yet as expected. I will close this issue for now, this use case should be supported in 8.1+. |
The structure of Python namespace packages cannot be determined just by looking at the filesystem. Consider the following directory tree:
If
src/
(but nothing underneath it) is insys.path
, we have the following modules:where
aaa
andbbb
are namespace modules. We might also have other parts of the namespace modules outside of this tree, e.g. by addingothersrc/
tosys.path
so thatimport bbb.other
will work ifothersrc/bbb/other.py
exists.)It's easy to configure pytest to have
src
in the path, but when discovering and loadingsrc/aaa/test_core.py
, as described here, it addssrc/aaa
tosys.path
and importssrc/aaa/test_core.py
using a different module structure than the programmer may have intended:import test_core
. Further, the same thing happens again withsrc/bbb/test_core.py
and now we get a conflict because there's already atest_core
module.It would be nice to be able to configure pytest so that we could tell it that
src/
may contain namespace packages and, if it does, they are all intended to be rooted atsrc/
. That is, when discoveringsrc/{aaa,bbb}/test_core.py
do not add anything tosys.path
but instead generate the fully qualified module name based on the file's position in the hierarchy relative tosrc/
.Another, more aggressive, option might be to see if the file is under any of the paths already in
sys.path
and determine the fully qualified module name by the hierarchy relative to the first path insys.path
under which the file falls.The Stack Overflow question How do I Pytest a project using PEP 420 namespace packages? also discusses the issue.
The way I put these here makes this sound pretty simple (at least as far as description, if not implementation), but there may be other subtleties I'm missing here, about which I welcome discussion.
The text was updated successfully, but these errors were encountered: