compat.py
Overview
`compat.py` is a utility module focused on **Python version compatibility** and providing helper functions and classes that abstract away differences between Python versions and environments. It primarily supports introspection, function signature handling, path compatibility, and safe attribute access. This file is designed to be used internally by other parts of the system (notably pytest or similar testing frameworks) to ensure consistent behavior across Python versions, especially when dealing with asynchronous functions, inspection of callable signatures, and interaction with mock objects.
Key responsibilities include:
Wrapping legacy path objects.
Providing coroutine and async function detection without importing heavy dependencies.
Safely extracting function argument names and defaults.
Handling edge cases in attribute access and class detection.
Offering utilities for escaping ASCII and managing user IDs.
A utility
CallableBoolclass for backwards compatibility.Exhaustiveness checking via
assert_neverfor type safety.
Detailed Explanations
Constants and Types
LEGACY_PATH: py.path.local
A constant alias for `py.path.local` used as a placeholder for legacy path handling. Intended to be removed in future versions.
Functions
legacy_path(path: str | os.PathLike[str]) -> LEGACY_PATH
Wraps a string or path-like object into a legacy path object (`py.path.local`). **Usage Example:**
lp = legacy_path("/some/path")
print(lp)
iscoroutinefunction(func: object) -> bool
Returns `True` if `func` is a coroutine function (defined with `async def` and not a generator) or decorated with `@asyncio.coroutine`. **Notes:**
Avoids importing
asyncioto prevent side-effects (e.g., logging initialization).
Usage Example:
async def foo(): pass
print(iscoroutinefunction(foo)) # True
is_async_function(func: object) -> bool
Returns `True` if `func` is either a coroutine function or an async generator function. **Usage Example:**
async def foo(): pass
async def bar(): yield
print(is_async_function(foo)) # True
print(is_async_function(bar)) # True
signature(obj: Callable[..., Any]) -> Signature
Returns the function signature of `obj` without evaluating annotations.
For Python 3.14+, uses
annotation_format=Format.STRINGto avoid annotation evaluation.
Usage Example:
def foo(a: int, b: str = "x"): pass
sig = signature(foo)
print(sig) # (a: int, b: str = 'x')
getlocation(function, curdir: str | os.PathLike[str] | None = None) -> str
Returns a string representing the source location of the given `function` in the format `"filename:lineno"`.
If
curdiris provided and the function's path is relative tocurdir, returns a relative path.Adds 1 to the line number to match human-readable line counting.
Usage Example:
def foo(): pass
print(getlocation(foo)) # e.g. "/path/to/file.py:10"
num_mock_patch_args(function) -> int
Returns the count of mock patch arguments automatically injected by mock decorators on `function`.
Detects mock patchings that replace attributes with default mock sentinel objects.
Helps in adjusting argument handling when mocks are involved.
Usage Example:
@patch('module.Class')
def test_func(mock_class):
pass
print(num_mock_patch_args(test_func)) # 1
getfuncargnames(function: Callable[..., object], *, name: str = "", cls: type | None = None) -> tuple[str, ...]
Returns the names of **mandatory** (non-defaulted) arguments for the given function, excluding:
Bound instance or class method first parameters (if applicable).
Parameters replaced by mocks.
Parameters bound by
functools.partial.
Parameters:
function: The function to inspect.name: Optional original function name (used for introspection).cls: Optional class; if provided, treatsfunctionas a bound method unless it is static.
Raises pytest failure if arguments cannot be determined. **Usage Example:**
def foo(a, b, c=3): pass
print(getfuncargnames(foo)) # ('a', 'b')
get_default_arg_names(function: Callable[..., Any]) -> tuple[str, ...]
Returns the names of arguments of `function` that have default values. This complements `getfuncargnames` which returns mandatory args. **Usage Example:**
def foo(a, b=2, c=3): pass
print(get_default_arg_names(foo)) # ('b', 'c')
ascii_escaped(val: bytes | str) -> str
Returns a string where all non-printable ASCII characters in `val` are escaped using backslash notation.
For
bytes, returns a string with escaped hex sequences (e.g.,b'\xc3'→\\xc3).For
str, returns escaped unicode code points (e.g., newline →\\n).
Usage Example:
print(ascii_escaped(b'\xc3\xb4')) # '\xc3\xb4'
print(ascii_escaped("hello\nworld")) # 'hello\\nworld'
get_real_func(obj)
Unwraps `obj` to its underlying real function, removing `functools.wraps` wrappers and `functools.partial` layers. **Usage Example:**
def foo(): pass
wrapped = functools.wraps(foo)(lambda: None)
print(get_real_func(wrapped) is foo) # True
getimfunc(func)
Returns the underlying function of a method if `func` is a bound method, otherwise returns `func` itself. **Usage Example:**
class A:
def method(self): pass
print(getimfunc(A.method) is A.method) # True
print(getimfunc(A().method) is A.method) # True
safe_getattr(object: Any, name: str, default: Any) -> Any
Like `getattr`, but catches **any** exception (including pytest `OutcomeException`) and returns `default` if attribute access fails. Prevents tests from breaking due to attribute access errors on "evil" objects. **Usage Example:**
class Evil:
def __getattr__(self, name):
raise RuntimeError("bad!")
print(safe_getattr(Evil(), "foo", 42)) # 42
safe_isclass(obj: object) -> bool
Returns `True` if `obj` is a class, suppressing any exceptions raised by `inspect.isclass`. **Usage Example:**
print(safe_isclass(int)) # True
print(safe_isclass(123)) # False
get_user_id() -> int | None
Returns the current process's real user ID (`uid`) or `None` if not determinable (Windows, Emscripten, or error).
Returns
Noneon Windows and Emscripten due to lack ofos.getuid.On POSIX, returns
os.getuid()unless it returns-1(error).
Usage Example:
uid = get_user_id()
if uid is not None:
print(f"User ID is {uid}")
else:
print("Could not determine user ID")
assert_never(value: NoReturn) -> NoReturn
A utility for exhaustiveness checking in type-checked code.
Raises an assertion error with a message if called, indicating an unhandled value.
Useful to ensure all variants of unions or enums are handled in code.
**Usage Example:**
from typing import Union
def handle(x: Union[int, str]) -> int:
if isinstance(x, int):
return 1
elif isinstance(x, str):
return 2
else:
return assert_never(x) # type checker ensures this is unreachable
Classes
CallableBool
A backward compatibility class that behaves like a `bool` but is also callable, returning its boolean value.
Used to fix cases where an attribute was mistakenly implemented as a bool but expected to be a callable method.
Example use case:
TerminalReporter.isattyattribute in pytest.Do not use in new code.
**Attributes:**
_value: bool— The stored boolean value.
**Methods:**
__init__(self, value: bool): Initializes with a boolean value.__bool__(self) -> bool: Returns the stored boolean value.__call__(self) -> bool: Returns the stored boolean value when called.
**Usage Example:**
isatty = CallableBool(True)
print(bool(isatty)) # True
print(isatty()) # True
Important Implementation Details
Avoiding heavy imports: Functions like
iscoroutinefunctionavoid importingasyncioto prevent unwanted side-effects such as logging initialization.Signature retrieval: Uses Python 3.14's new
annotation_formatargument to get signatures without evaluating potentially expensive or problematic annotations.Mock patch awareness: Functions inspect mock patchings to correctly account for injected arguments.
Safe attribute access:
safe_getattrandsafe_isclassprotect against errant objects that may raise exceptions during introspection.Cross-platform user ID retrieval:
get_user_idaccounts for platform differences.Exhaustiveness checking:
assert_neverleverages type checking to ensure all cases are handled in union or enum dispatches.Legacy path handling:
legacy_pathandLEGACY_PATHsupport a transitional API for path handling, slated for removal.
Interaction with Other System Components
This module is intended to be used by pytest internals or other testing tools needing robust, version-agnostic introspection utilities.
It interacts with:
py.path.localfor legacy path compatibility.inspectmodule for function and class introspection.sysandosfor environment and user info.Mocking frameworks (
mock,unittest.mock) to handle patched functions._pytest.outcomesmodule for safe exception handling during attribute access.Possibly
annotationlibfor Python 3.14+ annotation formatting.
It forms a foundational utility layer that higher-level framework components depend on for reliable introspection and compatibility.
Visual Diagram
classDiagram
class CallableBool {
-_value: bool
+__init__(value: bool)
+__bool__() bool
+__call__() bool
}
class NotSetType {
<<enum>>
+token: int
}
class Functions {
+legacy_path(path: str | os.PathLike) LEGACY_PATH
+iscoroutinefunction(func: object) bool
+is_async_function(func: object) bool
+signature(obj: Callable) Signature
+getlocation(function, curdir: str | os.PathLike | None) str
+num_mock_patch_args(function) int
+getfuncargnames(function: Callable, name: str, cls: type | None) tuple[str, ...]
+get_default_arg_names(function: Callable) tuple[str, ...]
+ascii_escaped(val: bytes | str) str
+get_real_func(obj) object
+getimfunc(func) object
+safe_getattr(object: Any, name: str, default: Any) Any
+safe_isclass(obj: object) bool
+get_user_id() int | None
+assert_never(value: NoReturn) NoReturn
}
CallableBool --> Functions : uses
NotSetType <|-- Functions : defines NOTSET
Summary
`compat.py` is a critical utility module offering Python-version-agnostic helpers for function introspection, async detection, safe attribute access, and compatibility shims. It encapsulates low-level details and quirks of different Python versions and runtime environments, enabling other system components to operate reliably and consistently without dealing with these complexities directly. Its careful handling of edge cases, mocks, and platform differences makes it a foundational piece in testing or framework infrastructure.