Export types of builtin fixture for type annotations
In order to allow users to type annotate fixtures they request, the types need to be imported from the `pytest` namespace. They are/were always available to import from the `_pytest` namespace, but that is not guaranteed to be stable. These types are only exported for the purpose of typing. Specifically, the following are *not* public: - Construction (`__init__`) - Subclassing - staticmethods and classmethods We try to combat them being used anyway by: - Marking the classes as `@final` when possible (already done). - Not documenting private stuff in the API Reference. - Using `_`-prefixed names or marking as `:meta private:` for private stuff. - Adding a keyword-only `_ispytest=False` to private constructors, warning if False, and changing pytest itself to pass True. In the future it will (hopefully) become a hard error. Hopefully that will be enough.
This commit is contained in:
@@ -123,3 +123,17 @@ def test_yield_fixture_is_deprecated() -> None:
|
||||
@pytest.yield_fixture
|
||||
def fix():
|
||||
assert False
|
||||
|
||||
|
||||
def test_private_is_deprecated() -> None:
|
||||
class PrivateInit:
|
||||
def __init__(self, foo: int, *, _ispytest: bool = False) -> None:
|
||||
deprecated.check_ispytest(_ispytest)
|
||||
|
||||
with pytest.warns(
|
||||
pytest.PytestDeprecationWarning, match="private pytest class or function"
|
||||
):
|
||||
PrivateInit(10)
|
||||
|
||||
# Doesn't warn.
|
||||
PrivateInit(10, _ispytest=True)
|
||||
|
||||
@@ -621,7 +621,7 @@ class TestRequestBasic:
|
||||
def test_func(something): pass
|
||||
"""
|
||||
)
|
||||
req = fixtures.FixtureRequest(item)
|
||||
req = fixtures.FixtureRequest(item, _ispytest=True)
|
||||
assert req.function == item.obj
|
||||
assert req.keywords == item.keywords
|
||||
assert hasattr(req.module, "test_func")
|
||||
@@ -661,7 +661,9 @@ class TestRequestBasic:
|
||||
)
|
||||
(item1,) = testdir.genitems([modcol])
|
||||
assert item1.name == "test_method"
|
||||
arg2fixturedefs = fixtures.FixtureRequest(item1)._arg2fixturedefs
|
||||
arg2fixturedefs = fixtures.FixtureRequest(
|
||||
item1, _ispytest=True
|
||||
)._arg2fixturedefs
|
||||
assert len(arg2fixturedefs) == 1
|
||||
assert arg2fixturedefs["something"][0].argname == "something"
|
||||
|
||||
@@ -910,7 +912,7 @@ class TestRequestBasic:
|
||||
def test_request_getmodulepath(self, testdir):
|
||||
modcol = testdir.getmodulecol("def test_somefunc(): pass")
|
||||
(item,) = testdir.genitems([modcol])
|
||||
req = fixtures.FixtureRequest(item)
|
||||
req = fixtures.FixtureRequest(item, _ispytest=True)
|
||||
assert req.fspath == modcol.fspath
|
||||
|
||||
def test_request_fixturenames(self, testdir):
|
||||
@@ -1052,7 +1054,7 @@ class TestRequestMarking:
|
||||
pass
|
||||
"""
|
||||
)
|
||||
req1 = fixtures.FixtureRequest(item1)
|
||||
req1 = fixtures.FixtureRequest(item1, _ispytest=True)
|
||||
assert "xfail" not in item1.keywords
|
||||
req1.applymarker(pytest.mark.xfail)
|
||||
assert "xfail" in item1.keywords
|
||||
@@ -3882,7 +3884,7 @@ class TestScopeOrdering:
|
||||
"""
|
||||
)
|
||||
items, _ = testdir.inline_genitems()
|
||||
request = FixtureRequest(items[0])
|
||||
request = FixtureRequest(items[0], _ispytest=True)
|
||||
assert request.fixturenames == "m1 f1".split()
|
||||
|
||||
def test_func_closure_with_native_fixtures(self, testdir, monkeypatch) -> None:
|
||||
@@ -3928,7 +3930,7 @@ class TestScopeOrdering:
|
||||
"""
|
||||
)
|
||||
items, _ = testdir.inline_genitems()
|
||||
request = FixtureRequest(items[0])
|
||||
request = FixtureRequest(items[0], _ispytest=True)
|
||||
# order of fixtures based on their scope and position in the parameter list
|
||||
assert (
|
||||
request.fixturenames == "s1 my_tmpdir_factory p1 m1 f1 f2 my_tmpdir".split()
|
||||
@@ -3954,7 +3956,7 @@ class TestScopeOrdering:
|
||||
"""
|
||||
)
|
||||
items, _ = testdir.inline_genitems()
|
||||
request = FixtureRequest(items[0])
|
||||
request = FixtureRequest(items[0], _ispytest=True)
|
||||
assert request.fixturenames == "m1 f1".split()
|
||||
|
||||
def test_func_closure_scopes_reordered(self, testdir):
|
||||
@@ -3987,7 +3989,7 @@ class TestScopeOrdering:
|
||||
"""
|
||||
)
|
||||
items, _ = testdir.inline_genitems()
|
||||
request = FixtureRequest(items[0])
|
||||
request = FixtureRequest(items[0], _ispytest=True)
|
||||
assert request.fixturenames == "s1 m1 c1 f2 f1".split()
|
||||
|
||||
def test_func_closure_same_scope_closer_root_first(self, testdir):
|
||||
@@ -4027,7 +4029,7 @@ class TestScopeOrdering:
|
||||
}
|
||||
)
|
||||
items, _ = testdir.inline_genitems()
|
||||
request = FixtureRequest(items[0])
|
||||
request = FixtureRequest(items[0], _ispytest=True)
|
||||
assert request.fixturenames == "p_sub m_conf m_sub m_test f1".split()
|
||||
|
||||
def test_func_closure_all_scopes_complex(self, testdir):
|
||||
@@ -4071,7 +4073,7 @@ class TestScopeOrdering:
|
||||
"""
|
||||
)
|
||||
items, _ = testdir.inline_genitems()
|
||||
request = FixtureRequest(items[0])
|
||||
request = FixtureRequest(items[0], _ispytest=True)
|
||||
assert request.fixturenames == "s1 p1 m1 m2 c1 f2 f1".split()
|
||||
|
||||
def test_multiple_packages(self, testdir):
|
||||
|
||||
@@ -1156,7 +1156,7 @@ def test_gitignore(testdir):
|
||||
from _pytest.cacheprovider import Cache
|
||||
|
||||
config = testdir.parseconfig()
|
||||
cache = Cache.for_config(config)
|
||||
cache = Cache.for_config(config, _ispytest=True)
|
||||
cache.set("foo", "bar")
|
||||
msg = "# Created by pytest automatically.\n*\n"
|
||||
gitignore_path = cache._cachedir.joinpath(".gitignore")
|
||||
@@ -1178,7 +1178,7 @@ def test_does_not_create_boilerplate_in_existing_dirs(testdir):
|
||||
"""
|
||||
)
|
||||
config = testdir.parseconfig()
|
||||
cache = Cache.for_config(config)
|
||||
cache = Cache.for_config(config, _ispytest=True)
|
||||
cache.set("foo", "bar")
|
||||
|
||||
assert os.path.isdir("v") # cache contents
|
||||
@@ -1192,7 +1192,7 @@ def test_cachedir_tag(testdir):
|
||||
from _pytest.cacheprovider import CACHEDIR_TAG_CONTENT
|
||||
|
||||
config = testdir.parseconfig()
|
||||
cache = Cache.for_config(config)
|
||||
cache = Cache.for_config(config, _ispytest=True)
|
||||
cache.set("foo", "bar")
|
||||
cachedir_tag_path = cache._cachedir.joinpath("CACHEDIR.TAG")
|
||||
assert cachedir_tag_path.read_bytes() == CACHEDIR_TAG_CONTENT
|
||||
|
||||
@@ -28,7 +28,7 @@ def test_recwarn_functional(testdir) -> None:
|
||||
|
||||
class TestWarningsRecorderChecker:
|
||||
def test_recording(self) -> None:
|
||||
rec = WarningsRecorder()
|
||||
rec = WarningsRecorder(_ispytest=True)
|
||||
with rec:
|
||||
assert not rec.list
|
||||
warnings.warn_explicit("hello", UserWarning, "xyz", 13)
|
||||
@@ -45,7 +45,7 @@ class TestWarningsRecorderChecker:
|
||||
|
||||
def test_warn_stacklevel(self) -> None:
|
||||
"""#4243"""
|
||||
rec = WarningsRecorder()
|
||||
rec = WarningsRecorder(_ispytest=True)
|
||||
with rec:
|
||||
warnings.warn("test", DeprecationWarning, 2)
|
||||
|
||||
@@ -53,21 +53,21 @@ class TestWarningsRecorderChecker:
|
||||
from _pytest.recwarn import WarningsChecker
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
WarningsChecker(5) # type: ignore
|
||||
WarningsChecker(5, _ispytest=True) # type: ignore[arg-type]
|
||||
with pytest.raises(TypeError):
|
||||
WarningsChecker(("hi", RuntimeWarning)) # type: ignore
|
||||
WarningsChecker(("hi", RuntimeWarning), _ispytest=True) # type: ignore[arg-type]
|
||||
with pytest.raises(TypeError):
|
||||
WarningsChecker([DeprecationWarning, RuntimeWarning]) # type: ignore
|
||||
WarningsChecker([DeprecationWarning, RuntimeWarning], _ispytest=True) # type: ignore[arg-type]
|
||||
|
||||
def test_invalid_enter_exit(self) -> None:
|
||||
# wrap this test in WarningsRecorder to ensure warning state gets reset
|
||||
with WarningsRecorder():
|
||||
with WarningsRecorder(_ispytest=True):
|
||||
with pytest.raises(RuntimeError):
|
||||
rec = WarningsRecorder()
|
||||
rec = WarningsRecorder(_ispytest=True)
|
||||
rec.__exit__(None, None, None) # can't exit before entering
|
||||
|
||||
with pytest.raises(RuntimeError):
|
||||
rec = WarningsRecorder()
|
||||
rec = WarningsRecorder(_ispytest=True)
|
||||
with rec:
|
||||
with rec:
|
||||
pass # can't enter twice
|
||||
|
||||
@@ -48,7 +48,9 @@ class FakeConfig:
|
||||
class TestTempdirHandler:
|
||||
def test_mktemp(self, tmp_path):
|
||||
config = cast(Config, FakeConfig(tmp_path))
|
||||
t = TempdirFactory(TempPathFactory.from_config(config))
|
||||
t = TempdirFactory(
|
||||
TempPathFactory.from_config(config, _ispytest=True), _ispytest=True
|
||||
)
|
||||
tmp = t.mktemp("world")
|
||||
assert tmp.relto(t.getbasetemp()) == "world0"
|
||||
tmp = t.mktemp("this")
|
||||
@@ -61,7 +63,7 @@ class TestTempdirHandler:
|
||||
"""#4425"""
|
||||
monkeypatch.chdir(tmp_path)
|
||||
config = cast(Config, FakeConfig("hello"))
|
||||
t = TempPathFactory.from_config(config)
|
||||
t = TempPathFactory.from_config(config, _ispytest=True)
|
||||
assert t.getbasetemp().resolve() == (tmp_path / "hello").resolve()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user