compat.py 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. from __future__ import annotations
  2. from collections.abc import Mapping
  3. import functools
  4. from pathlib import Path
  5. from typing import Any
  6. import warnings
  7. import pluggy
  8. from ..compat import LEGACY_PATH
  9. from ..compat import legacy_path
  10. from ..deprecated import HOOK_LEGACY_PATH_ARG
  11. # hookname: (Path, LEGACY_PATH)
  12. imply_paths_hooks: Mapping[str, tuple[str, str]] = {
  13. "pytest_ignore_collect": ("collection_path", "path"),
  14. "pytest_collect_file": ("file_path", "path"),
  15. "pytest_pycollect_makemodule": ("module_path", "path"),
  16. "pytest_report_header": ("start_path", "startdir"),
  17. "pytest_report_collectionfinish": ("start_path", "startdir"),
  18. }
  19. def _check_path(path: Path, fspath: LEGACY_PATH) -> None:
  20. if Path(fspath) != path:
  21. raise ValueError(
  22. f"Path({fspath!r}) != {path!r}\n"
  23. "if both path and fspath are given they need to be equal"
  24. )
  25. class PathAwareHookProxy:
  26. """
  27. this helper wraps around hook callers
  28. until pluggy supports fixingcalls, this one will do
  29. it currently doesn't return full hook caller proxies for fixed hooks,
  30. this may have to be changed later depending on bugs
  31. """
  32. def __init__(self, hook_relay: pluggy.HookRelay) -> None:
  33. self._hook_relay = hook_relay
  34. def __dir__(self) -> list[str]:
  35. return dir(self._hook_relay)
  36. def __getattr__(self, key: str) -> pluggy.HookCaller:
  37. hook: pluggy.HookCaller = getattr(self._hook_relay, key)
  38. if key not in imply_paths_hooks:
  39. self.__dict__[key] = hook
  40. return hook
  41. else:
  42. path_var, fspath_var = imply_paths_hooks[key]
  43. @functools.wraps(hook)
  44. def fixed_hook(**kw: Any) -> Any:
  45. path_value: Path | None = kw.pop(path_var, None)
  46. fspath_value: LEGACY_PATH | None = kw.pop(fspath_var, None)
  47. if fspath_value is not None:
  48. warnings.warn(
  49. HOOK_LEGACY_PATH_ARG.format(
  50. pylib_path_arg=fspath_var, pathlib_path_arg=path_var
  51. ),
  52. stacklevel=2,
  53. )
  54. if path_value is not None:
  55. if fspath_value is not None:
  56. _check_path(path_value, fspath_value)
  57. else:
  58. fspath_value = legacy_path(path_value)
  59. else:
  60. assert fspath_value is not None
  61. path_value = Path(fspath_value)
  62. kw[path_var] = path_value
  63. kw[fspath_var] = fspath_value
  64. return hook(**kw)
  65. fixed_hook.name = hook.name # type: ignore[attr-defined]
  66. fixed_hook.spec = hook.spec # type: ignore[attr-defined]
  67. fixed_hook.__name__ = key
  68. self.__dict__[key] = fixed_hook
  69. return fixed_hook # type: ignore[return-value]