Revert "Remove deprecated py.path (fspath) node constructor arguments"

This reverts commit 6c89f9261c.
This commit is contained in:
Bruno Oliveira
2024-03-07 19:19:14 -03:00
parent 86945f9a1f
commit 303cd0d48a
12 changed files with 156 additions and 67 deletions

View File

@@ -1,5 +1,6 @@
# mypy: allow-untyped-defs
"""Python version compatibility code."""
from __future__ import annotations
import dataclasses
@@ -16,6 +17,22 @@ from typing import Callable
from typing import Final
from typing import NoReturn
import py
#: constant to prepare valuing pylib path replacements/lazy proxies later on
# intended for removal in pytest 8.0 or 9.0
# fmt: off
# intentional space to create a fake difference for the verification
LEGACY_PATH = py.path. local
# fmt: on
def legacy_path(path: str | os.PathLike[str]) -> LEGACY_PATH:
"""Internal wrapper to prepare lazy proxies for legacy_path instances"""
return LEGACY_PATH(path)
# fmt: off
# Singleton type for NOTSET, as described in:

View File

@@ -0,0 +1,13 @@
from __future__ import annotations
from pathlib import Path
from ..compat import LEGACY_PATH
def _check_path(path: Path, fspath: LEGACY_PATH) -> None:
if Path(fspath) != path:
raise ValueError(
f"Path({fspath!r}) != {path!r}\n"
"if both path and fspath are given they need to be equal"
)

View File

@@ -36,6 +36,14 @@ YIELD_FIXTURE = PytestDeprecationWarning(
PRIVATE = PytestDeprecationWarning("A private pytest class or function was used.")
NODE_CTOR_FSPATH_ARG = UnformattedWarning(
PytestRemovedIn9Warning,
"The (fspath: py.path.local) argument to {node_type_name} is deprecated. "
"Please use the (path: pathlib.Path) argument instead.\n"
"See https://docs.pytest.org/en/latest/deprecations.html"
"#fspath-argument-for-node-constructors-replaced-with-pathlib-path",
)
HOOK_LEGACY_MARKING = UnformattedWarning(
PytestDeprecationWarning,
"The hook{type} {fullname} uses old-style configuration options (marks or attributes).\n"

View File

@@ -1,7 +1,7 @@
# mypy: allow-untyped-defs
"""Add backward compatibility support for the legacy py path type."""
import dataclasses
import os
from pathlib import Path
import shlex
import subprocess
@@ -14,9 +14,9 @@ from typing import Union
from iniconfig import SectionWrapper
import py
from _pytest.cacheprovider import Cache
from _pytest.compat import LEGACY_PATH
from _pytest.compat import legacy_path
from _pytest.config import Config
from _pytest.config import hookimpl
from _pytest.config import PytestPluginManager
@@ -39,20 +39,6 @@ if TYPE_CHECKING:
import pexpect
#: constant to prepare valuing pylib path replacements/lazy proxies later on
# intended for removal in pytest 8.0 or 9.0
# fmt: off
# intentional space to create a fake difference for the verification
LEGACY_PATH = py.path. local
# fmt: on
def legacy_path(path: Union[str, "os.PathLike[str]"]) -> LEGACY_PATH:
"""Internal wrapper to prepare lazy proxies for legacy_path instances"""
return LEGACY_PATH(path)
@final
class Testdir:
"""

View File

@@ -557,6 +557,7 @@ class Session(nodes.Collector):
super().__init__(
name="",
path=config.rootpath,
fspath=None,
parent=None,
config=config,
session=self,

View File

@@ -3,6 +3,7 @@ import abc
from functools import cached_property
from inspect import signature
import os
import pathlib
from pathlib import Path
from typing import Any
from typing import Callable
@@ -29,8 +30,11 @@ from _pytest._code import getfslineno
from _pytest._code.code import ExceptionInfo
from _pytest._code.code import TerminalRepr
from _pytest._code.code import Traceback
from _pytest.compat import LEGACY_PATH
from _pytest.config import Config
from _pytest.config import ConftestImportFailure
from _pytest.config.compat import _check_path
from _pytest.deprecated import NODE_CTOR_FSPATH_ARG
from _pytest.mark.structures import Mark
from _pytest.mark.structures import MarkDecorator
from _pytest.mark.structures import NodeKeywords
@@ -55,6 +59,29 @@ tracebackcutdir = Path(_pytest.__file__).parent
_T = TypeVar("_T")
def _imply_path(
node_type: Type["Node"],
path: Optional[Path],
fspath: Optional[LEGACY_PATH],
) -> Path:
if fspath is not None:
warnings.warn(
NODE_CTOR_FSPATH_ARG.format(
node_type_name=node_type.__name__,
),
stacklevel=6,
)
if path is not None:
if fspath is not None:
_check_path(path, fspath)
return path
else:
assert fspath is not None
return Path(fspath)
_NodeType = TypeVar("_NodeType", bound="Node")
@@ -110,6 +137,13 @@ class Node(abc.ABC, metaclass=NodeMeta):
leaf nodes.
"""
# Implemented in the legacypath plugin.
#: A ``LEGACY_PATH`` copy of the :attr:`path` attribute. Intended for usage
#: for methods not migrated to ``pathlib.Path`` yet, such as
#: :meth:`Item.reportinfo <pytest.Item.reportinfo>`. Will be deprecated in
#: a future release, prefer using :attr:`path` instead.
fspath: LEGACY_PATH
# Use __slots__ to make attribute access faster.
# Note that __dict__ is still available.
__slots__ = (
@@ -129,6 +163,7 @@ class Node(abc.ABC, metaclass=NodeMeta):
parent: "Optional[Node]" = None,
config: Optional[Config] = None,
session: "Optional[Session]" = None,
fspath: Optional[LEGACY_PATH] = None,
path: Optional[Path] = None,
nodeid: Optional[str] = None,
) -> None:
@@ -154,11 +189,10 @@ class Node(abc.ABC, metaclass=NodeMeta):
raise TypeError("session or parent must be provided")
self.session = parent.session
if path is None:
if path is None and fspath is None:
path = getattr(parent, "path", None)
assert path is not None
#: Filesystem path where this node was collected from (can be None).
self.path = path
self.path: pathlib.Path = _imply_path(type(self), path, fspath=fspath)
# The explicit annotation is to avoid publicly exposing NodeKeywords.
#: Keywords/markers collected from all scopes.
@@ -529,6 +563,7 @@ class FSCollector(Collector, abc.ABC):
def __init__(
self,
fspath: Optional[LEGACY_PATH] = None,
path_or_parent: Optional[Union[Path, Node]] = None,
path: Optional[Path] = None,
name: Optional[str] = None,
@@ -544,8 +579,8 @@ class FSCollector(Collector, abc.ABC):
elif isinstance(path_or_parent, Path):
assert path is None
path = path_or_parent
assert path is not None
path = _imply_path(type(self), path, fspath=fspath)
if name is None:
name = path.name
if parent is not None and parent.path != path:
@@ -585,11 +620,12 @@ class FSCollector(Collector, abc.ABC):
cls,
parent,
*,
fspath: Optional[LEGACY_PATH] = None,
path: Optional[Path] = None,
**kw,
) -> "Self":
"""The public constructor."""
return super().from_parent(parent=parent, path=path, **kw)
return super().from_parent(parent=parent, fspath=fspath, path=path, **kw)
class File(FSCollector, abc.ABC):

View File

@@ -48,6 +48,7 @@ from _pytest.compat import getimfunc
from _pytest.compat import getlocation
from _pytest.compat import is_async_function
from _pytest.compat import is_generator
from _pytest.compat import LEGACY_PATH
from _pytest.compat import NOTSET
from _pytest.compat import safe_getattr
from _pytest.compat import safe_isclass
@@ -665,6 +666,7 @@ class Package(nodes.Directory):
def __init__(
self,
fspath: Optional[LEGACY_PATH],
parent: nodes.Collector,
# NOTE: following args are unused:
config=None,
@@ -676,6 +678,7 @@ class Package(nodes.Directory):
# super().__init__(self, fspath, parent=parent)
session = parent.session
super().__init__(
fspath=fspath,
path=path,
parent=parent,
config=config,