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:


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:**

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.

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"`.

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`.

@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:

Parameters:

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.

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).

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.

**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.

**Attributes:**

**Methods:**

**Usage Example:**

isatty = CallableBool(True)
print(bool(isatty))  # True
print(isatty())      # True

Important Implementation Details


Interaction with Other System Components


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.