diff --git a/changelog/3616.deprecation.rst b/changelog/3616.deprecation.rst new file mode 100644 index 00000000000..8ea1b4d3d14 --- /dev/null +++ b/changelog/3616.deprecation.rst @@ -0,0 +1,22 @@ +The following accesses have been documented as deprecated for years, but are now actually emitting deprecation warnings. + +* Access of ``Module``, ``Function``, ``Class``, ``Instance``, ``File`` and ``Item`` through ``Node`` instances. Now + users will this warning:: + + usage of Function.Module is deprecated, please use pytest.Module instead + + Users should just ``import pytest`` and access those objects using the ``pytest`` module. + +* ``request.cached_setup``, this was the precursor of the setup/teardown mechanism available to fixtures. You can + consult `funcarg comparision section in the docs `_. + +* Using objects named ``"Class"`` as a way to customize the type of nodes that are collected in ``Collector`` + subclasses has been deprecated. Users instead should use ``pytest_collect_make_item`` to customize node types during + collection. + + This issue should affect only advanced plugins who create new collection types, so if you see this warning + message please contact the authors so they can change the code. + +* The warning that produces the message below has changed to ``RemovedInPytest4Warning``:: + + getfuncargvalue is deprecated, use getfixturevalue diff --git a/changelog/README.rst b/changelog/README.rst index 47e21fb33fb..e471409b023 100644 --- a/changelog/README.rst +++ b/changelog/README.rst @@ -14,7 +14,8 @@ Each file should be named like ``..rst``, where * ``feature``: new user facing features, like new command-line options and new behavior. * ``bugfix``: fixes a reported bug. * ``doc``: documentation improvement, like rewording an entire session or adding missing docs. -* ``removal``: feature deprecation or removal. +* ``deprecation``: feature deprecation. +* ``removal``: feature removal. * ``vendor``: changes in packages vendored in pytest. * ``trivial``: fixing a small typo or internal change that might be noteworthy. diff --git a/doc/en/historical-notes.rst b/doc/en/historical-notes.rst index 028ceff9b17..9462d700f26 100644 --- a/doc/en/historical-notes.rst +++ b/doc/en/historical-notes.rst @@ -175,3 +175,13 @@ Previous to version 2.4 to set a break point in code one needed to use ``pytest. This is no longer needed and one can use the native ``import pdb;pdb.set_trace()`` call directly. For more details see :ref:`breakpoints`. + +"compat" properties +------------------- + +.. deprecated:: 3.9 + +Access of ``Module``, ``Function``, ``Class``, ``Instance``, ``File`` and ``Item`` through ``Node`` instances have long +been documented as deprecated, but started to emit warnings from pytest ``3.9`` and onward. + +Users should just ``import pytest`` and access those objects using the ``pytest`` module. diff --git a/pyproject.toml b/pyproject.toml index b1e85601ec5..e82f051e1d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,12 @@ template = "changelog/_template.rst" [[tool.towncrier.type]] directory = "removal" - name = "Deprecations and Removals" + name = "Removals" + showcontent = true + + [[tool.towncrier.type]] + directory = "deprecation" + name = "Deprecations" showcontent = true [[tool.towncrier.type]] diff --git a/src/_pytest/config/findpaths.py b/src/_pytest/config/findpaths.py index 7480603bece..f994301988f 100644 --- a/src/_pytest/config/findpaths.py +++ b/src/_pytest/config/findpaths.py @@ -103,21 +103,18 @@ def determine_setup(inifile, args, rootdir_cmd_arg=None, config=None): if inifile: iniconfig = py.iniconfig.IniConfig(inifile) is_cfg_file = str(inifile).endswith(".cfg") - # TODO: [pytest] section in *.cfg files is depricated. Need refactoring. sections = ["tool:pytest", "pytest"] if is_cfg_file else ["pytest"] for section in sections: try: inicfg = iniconfig[section] if is_cfg_file and section == "pytest" and config is not None: from _pytest.deprecated import CFG_PYTEST_SECTION - from _pytest.warning_types import RemovedInPytest4Warning from _pytest.warnings import _issue_config_warning + # TODO: [pytest] section in *.cfg files is deprecated. Need refactoring once + # the deprecation expires. _issue_config_warning( - RemovedInPytest4Warning( - CFG_PYTEST_SECTION.format(filename=str(inifile)) - ), - config, + CFG_PYTEST_SECTION.format(filename=str(inifile)), config ) break except KeyError: diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index dea8bbde867..69beeab5f91 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -4,10 +4,15 @@ Keeping it in a central location makes it easy to track what is deprecated and should be removed when the time comes. + +All constants defined in this module should be either PytestWarning instances or UnformattedWarning +in case of warnings which need to format their messages. """ from __future__ import absolute_import, division, print_function -from _pytest.warning_types import RemovedInPytest4Warning + +from _pytest.warning_types import UnformattedWarning, RemovedInPytest4Warning + MAIN_STR_ARGS = RemovedInPytest4Warning( "passing a string to pytest.main() is deprecated, " @@ -18,25 +23,48 @@ "yield tests are deprecated, and scheduled to be removed in pytest 4.0" ) -FUNCARG_PREFIX = ( +CACHED_SETUP = RemovedInPytest4Warning( + "cached_setup is deprecated and will be removed in a future release. " + "Use standard fixture functions instead." +) + +COMPAT_PROPERTY = UnformattedWarning( + RemovedInPytest4Warning, + "usage of {owner}.{name} is deprecated, please use pytest.{name} instead", +) + +CUSTOM_CLASS = UnformattedWarning( + RemovedInPytest4Warning, + 'use of special named "{name}" objects in collectors of type "{type_name}" to ' + "customize the created nodes is deprecated. " + "Use pytest_pycollect_makeitem(...) to create custom " + "collection nodes instead.", +) + +FUNCARG_PREFIX = UnformattedWarning( + RemovedInPytest4Warning, '{name}: declaring fixtures using "pytest_funcarg__" prefix is deprecated ' "and scheduled to be removed in pytest 4.0. " - "Please remove the prefix and use the @pytest.fixture decorator instead." + "Please remove the prefix and use the @pytest.fixture decorator instead.", ) -FIXTURE_FUNCTION_CALL = ( +FIXTURE_FUNCTION_CALL = UnformattedWarning( + RemovedInPytest4Warning, 'Fixture "{name}" called directly. Fixtures are not meant to be called directly, ' "are created automatically when test functions request them as parameters. " - "See https://docs.pytest.org/en/latest/fixture.html for more information." + "See https://docs.pytest.org/en/latest/fixture.html for more information.", ) -CFG_PYTEST_SECTION = ( - "[pytest] section in {filename} files is deprecated, use [tool:pytest] instead." +CFG_PYTEST_SECTION = UnformattedWarning( + RemovedInPytest4Warning, + "[pytest] section in {filename} files is deprecated, use [tool:pytest] instead.", ) -GETFUNCARGVALUE = "getfuncargvalue is deprecated, use getfixturevalue" +GETFUNCARGVALUE = RemovedInPytest4Warning( + "getfuncargvalue is deprecated, use getfixturevalue" +) -RESULT_LOG = ( +RESULT_LOG = RemovedInPytest4Warning( "--result-log is deprecated and scheduled for removal in pytest 4.0.\n" "See https://docs.pytest.org/en/latest/usage.html#creating-resultlog-format-files for more information." ) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 068e6814c1d..964b16e2997 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -32,7 +32,7 @@ get_real_method, _PytestWrapper, ) -from _pytest.deprecated import FIXTURE_FUNCTION_CALL, RemovedInPytest4Warning +from _pytest.deprecated import FIXTURE_FUNCTION_CALL from _pytest.outcomes import fail, TEST_OUTCOME FIXTURE_MSG = 'fixtures cannot have "pytest_funcarg__" prefix and be decorated with @pytest.fixture:\n{}' @@ -479,6 +479,9 @@ def cached_setup(self, setup, teardown=None, scope="module", extrakey=None): or ``session`` indicating the caching lifecycle of the resource. :arg extrakey: added to internal caching key of (funcargname, scope). """ + from _pytest.deprecated import CACHED_SETUP + + warnings.warn(CACHED_SETUP, stacklevel=2) if not hasattr(self.config, "_setupcache"): self.config._setupcache = {} # XXX weakref? cachekey = (self.fixturename, self._getscopeitem(scope), extrakey) @@ -512,7 +515,7 @@ def getfuncargvalue(self, argname): """ Deprecated, use getfixturevalue. """ from _pytest import deprecated - warnings.warn(deprecated.GETFUNCARGVALUE, DeprecationWarning, stacklevel=2) + warnings.warn(deprecated.GETFUNCARGVALUE, stacklevel=2) return self.getfixturevalue(argname) def _get_active_fixturedef(self, argname): @@ -956,8 +959,9 @@ def wrap_function_to_warning_if_called_directly(function, fixture_marker): used as an argument in a test function. """ is_yield_function = is_generator(function) - msg = FIXTURE_FUNCTION_CALL.format(name=fixture_marker.name or function.__name__) - warning = RemovedInPytest4Warning(msg) + warning = FIXTURE_FUNCTION_CALL.format( + name=fixture_marker.name or function.__name__ + ) if is_yield_function: @@ -1284,9 +1288,7 @@ def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False): filename, lineno = getfslineno(obj) warnings.warn_explicit( - RemovedInPytest4Warning( - deprecated.FUNCARG_PREFIX.format(name=name) - ), + deprecated.FUNCARG_PREFIX.format(name=name), category=None, filename=str(filename), lineno=lineno + 1, diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 29d1f0a871b..9cd7589415c 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -61,11 +61,11 @@ def __get__(self, obj, owner): if obj is None: return self - # TODO: reenable in the features branch - # warnings.warn( - # "usage of {owner!r}.{name} is deprecated, please use pytest.{name} instead".format( - # name=self.name, owner=type(owner).__name__), - # PendingDeprecationWarning, stacklevel=2) + from _pytest.deprecated import COMPAT_PROPERTY + + warnings.warn( + COMPAT_PROPERTY.format(name=self.name, owner=owner.__name__), stacklevel=2 + ) return getattr(__import__("pytest"), self.name) @@ -126,11 +126,10 @@ def _getcustomclass(self, name): if isinstance(maybe_compatprop, _CompatProperty): return getattr(__import__("pytest"), name) else: + from _pytest.deprecated import CUSTOM_CLASS + cls = getattr(self, name) - # TODO: reenable in the features branch - # warnings.warn("use of node.%s is deprecated, " - # "use pytest_pycollect_makeitem(...) to create custom " - # "collection nodes" % name, category=DeprecationWarning) + self.warn(CUSTOM_CLASS.format(name=name, type_name=type(self).__name__)) return cls def __repr__(self): diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 05165027231..a0a0655fd65 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -800,7 +800,10 @@ def collect(self): "%r generated tests with non-unique name %r" % (self, name) ) seen[name] = True - values.append(self.Function(name, self, args=args, callobj=call)) + with warnings.catch_warnings(): + # ignore our own deprecation warning + function_class = self.Function + values.append(function_class(name, self, args=args, callobj=call)) self.warn(deprecated.YIELD_TESTS) return values diff --git a/src/_pytest/resultlog.py b/src/_pytest/resultlog.py index 8a972eed736..9ae90e7704a 100644 --- a/src/_pytest/resultlog.py +++ b/src/_pytest/resultlog.py @@ -31,10 +31,9 @@ def pytest_configure(config): config.pluginmanager.register(config._resultlog) from _pytest.deprecated import RESULT_LOG - from _pytest.warning_types import RemovedInPytest4Warning from _pytest.warnings import _issue_config_warning - _issue_config_warning(RemovedInPytest4Warning(RESULT_LOG), config) + _issue_config_warning(RESULT_LOG, config) def pytest_unconfigure(config): diff --git a/src/_pytest/warning_types.py b/src/_pytest/warning_types.py index 8861f6f2b06..55e1f037ae5 100644 --- a/src/_pytest/warning_types.py +++ b/src/_pytest/warning_types.py @@ -1,3 +1,6 @@ +import attr + + class PytestWarning(UserWarning): """ Bases: :class:`UserWarning`. @@ -39,4 +42,19 @@ def simple(cls, apiname): ) +@attr.s +class UnformattedWarning(object): + """Used to hold warnings that need to format their message at runtime, as opposed to a direct message. + + Using this class avoids to keep all the warning types and messages in this module, avoiding misuse. + """ + + category = attr.ib() + template = attr.ib() + + def format(self, **kwargs): + """Returns an instance of the warning category, formatted with given kwargs""" + return self.category(self.template.format(**kwargs)) + + PYTESTER_COPY_EXAMPLE = PytestExperimentalApiWarning.simple("testdir.copy_example") diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index fbaca4e30fd..49b21ccb1b8 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -30,6 +30,74 @@ def test_gen2(): assert result.stdout.str().count("yield tests are deprecated") == 2 +def test_compat_properties_deprecation(testdir): + testdir.makepyfile( + """ + def test_foo(request): + print(request.node.Module) + """ + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines( + [ + "*test_compat_properties_deprecation.py:2:*usage of Function.Module is deprecated, " + "please use pytest.Module instead*", + "*1 passed, 1 warnings in*", + ] + ) + + +def test_cached_setup_deprecation(testdir): + testdir.makepyfile( + """ + import pytest + @pytest.fixture + def fix(request): + return request.cached_setup(lambda: 1) + + def test_foo(fix): + assert fix == 1 + """ + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines( + [ + "*test_cached_setup_deprecation.py:4:*cached_setup is deprecated*", + "*1 passed, 1 warnings in*", + ] + ) + + +def test_custom_class_deprecation(testdir): + testdir.makeconftest( + """ + import pytest + + class MyModule(pytest.Module): + + class Class(pytest.Class): + pass + + def pytest_pycollect_makemodule(path, parent): + return MyModule(path, parent) + """ + ) + testdir.makepyfile( + """ + class Test: + def test_foo(self): + pass + """ + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines( + [ + '*test_custom_class_deprecation.py:1:*"Class" objects in collectors of type "MyModule*', + "*1 passed, 1 warnings in*", + ] + ) + + @pytest.mark.filterwarnings("default") def test_funcarg_prefix_deprecation(testdir): testdir.makepyfile( diff --git a/testing/python/collect.py b/testing/python/collect.py index c92de12a09c..25d8a8e779d 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -240,6 +240,9 @@ def prop(self): assert result.ret == EXIT_NOTESTSCOLLECTED +@pytest.mark.filterwarnings( + "ignore:usage of Generator.Function is deprecated, please use pytest.Function instead" +) class TestGenerator(object): def test_generative_functions(self, testdir): modcol = testdir.getmodulecol( @@ -1255,6 +1258,9 @@ def test_hello(self): pass assert lineno == 1 assert msg == "TestClass" + @pytest.mark.filterwarnings( + "ignore:usage of Generator.Function is deprecated, please use pytest.Function instead" + ) def test_generator_reportinfo(self, testdir): modcol = testdir.getmodulecol( """ diff --git a/testing/python/fixture.py b/testing/python/fixture.py index fc3eee42b5f..4e44bf9611f 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -977,6 +977,7 @@ def test_func2b(self, something): ) reprec.assertoutcome(passed=4) + @pytest.mark.filterwarnings("ignore:cached_setup is deprecated") def test_request_cachedsetup_extrakey(self, testdir): item1 = testdir.getitem("def test_func(): pass") req1 = fixtures.FixtureRequest(item1) @@ -994,6 +995,7 @@ def setup(): assert ret1 == ret1b assert ret2 == ret2b + @pytest.mark.filterwarnings("ignore:cached_setup is deprecated") def test_request_cachedsetup_cache_deletion(self, testdir): item1 = testdir.getitem("def test_func(): pass") req1 = fixtures.FixtureRequest(item1)