The file changer helpers

When a test fails pytest typing runner will add a pytest report section that lists what files where changed/created/removed and the different commands that were run to do static type checking, and their respective outputs.

To make that report know what files where changed requires all changes go through the file_modification method on the scenario runner.

This is a function represented by pytest_typing_runner.protocols.FileModifier and has a very simple interface that takes in the path to the file as a string relative to the root directory of the scenario, and the contents to use (or None if the file should be deleted).

To add extra functionality around what kind of changes we want to make to files there are some helpers in pytest_typing_runner.file_changers that are used to determine what new content a file should have after a change.

exception pytest_typing_runner.file_changers.FileChangerException

Parent exception for all file_changer exceptions

exception pytest_typing_runner.file_changers.LocationOutOfBounds(*, root_dir: Path, location: Path)

Risen when a location is outside of a specific parent directory

exception pytest_typing_runner.file_changers.LocationDoesNotExist(*, location: Path)

Risen when a location that should exist does not

exception pytest_typing_runner.file_changers.LocationIsNotDirectory(*, location: Path)

Risen when a location that should be a directory is not a directory

class pytest_typing_runner.file_changers.FileAppender(*, root_dir: Path, path: str, extra_content: str)

Used to determine what to change a file to if content were to be appended:

from pytest_typing_runner import file_changers


appender = file_changers.FileAppender(
    root_dir=root_dir,
    path="path/to/file.py",
    extra_content=textwrap.dedent(
        """
        class Other:
            pass
        """
    )
)

# Given a pytest_typing_runner.protocols.FileModifier function
file_modification(
    path="/path/to/file.py",
    content=appender.after_append()
)
Parameters:
  • root_dir – The base location to modify

  • path – The path relative to root_dir to look at

  • extra_content – The extra content to add to the existing content

after_append(divider: str = '\n', must_exist: bool = False) str

Return a string that would be the result of appending self.extra_content to the contents of the file that was found.

Parameters:
  • divider – The string that goes between the existing content (if there is existing content) and the extra content

  • must_exist – When set to True, an exception will be raised instead of pretending the file is empty

Raises:
Returns:

the new contents for the file

class pytest_typing_runner.file_changers.CopyDirectory(*, root_dir: Path, src: Path, path: str)

Used to copy a specific folder under src into root_dir

from pytest_typing_runner import file_changers


copier = file_changers.CopyDirectory(
    root_dir=root_dir,
    src=src,
    path="folder_in_src_to_copy",
)

# Given a pytest_typing_runner.protocols.FileModifier function
copier.do_copy(modify_file=file_modification)
Parameters:
  • root_dir – The base location to copy files into

  • src – The base location to look for the path to copy from

  • path – The path relative to src to look at and relative to root_dir to copy into

do_copy(*, modify_file: FileModifier, skip_if_destination_exists: bool, exclude: Callable[[Path], bool] | None = None) None

Use the file modifier to copy files into root_dir

Parameters:
  • modify_file – function that changes files

  • skip_if_destination_exists – when set to True this function doesn’t change anything if the root_dir already has the directory being looked for (without checking if the content in that directory match the src

  • exclude – an optional function that takes each file that is being copied. Returning False from this function will result in the file not being copied over

Raises:
protocol pytest_typing_runner.file_changers.PythonVariableChanger

Protocol representing a changer used by PythonFileChanger

It takes in a node that is either ast.Assign or ast.AnnAssign and must return a node of the same type.

When modifying the node it can be useful to use a match statement:

from typing import assert_never
from pytest_typing_runner import file_changers
from typing import TYPE_CHECKING
import ast

def changer(
    *,
    node: file_changers.T_Assign,
    variable_name: str,
    values: dict[str, object]
) -> file_changers.T_Assign:
    new_value = ... # some ast.expr object

    match node:
        case ast.AnnAssign(target=target, annotation=annotation, simple=simple):
            return ast.AnnAssign(
                target=target, annotation=annotation, simple=simple, value=new_value
            )
        case ast.Assign(targets=targets):
            return ast.Assign(targets=targets, value=new_value)
        case _:
            assert_never(node)

if TYPE_CHECKING:
    _c: file_changers.PythonVariableChanger = changer
Parameters:
  • node – The ast assignment node that is being looked at

  • variable_name – The name of the variable that was being assigned

  • values – A dictionary of all the values in the python file being changed

Classes that implement this protocol must have the following methods / attributes:

__call__(*, node: T_Assign, variable_name: str, values: dict[str, object]) T_Assign

Call self as a function.

class pytest_typing_runner.file_changers.BasicPythonAssignmentChanger(*, root_dir: Path, cwd: Path, path: str, variable_changers: Mapping[str, PythonVariableChanger])

Limited helper used to create a copy of a python file with changes to specific assignments.

from pytest_typing_runner import file_changers


changer = file_changers.BasicPythonAssignmentChanger(
    root_dir=root_dir,
    path="path/to/my_code.py",
    variable_changers={"var1": ..., "var2": ...}
)

# Given a pytest_typing_runner.protocols.FileModifier function
file_modification(
    path="path/to/my_code.py",
    content=changer.after_change(default_content="")
)
Parameters:
  • root_dir – The base location to look for the path in

  • path – The path relative to root_dir to look at

  • variable_changers

    A mapping where keys are the names of variables that are assigned to in the file. And variables are of the type PythonVariableChanger.

    When any ast.Assign or ast.AnnAssign are found in the python file those variable changes are used with the node that was found.

    This helper is useful for modifying files like a Django settings file where the file only has module level assignments.

  • cwd – The directory to be in and add to sys.path when determining the values in the file

after_change(*, default_content: str) str

Returns the contents of a file after changing specific assignments.

The method will determine what values are in the file using runpy.run_path on the file. And will use a ast.NodeTransformer to find and modify ast nodes in the file to determine what the file the would look like after changes.

Parameters:

default_content – If the file does not exist, then this is the string that will be used to determine values and ast nodes. A temporary file will be made for this purpose rather than changing the destination.

Raises:

LocationOutOfBounds – if self.path relative to self.root_dir is outside of self.root_dir

class pytest_typing_runner.file_changers.VariableFinder(*, notify: Notifier)

An object that satisfies PythonVariableChanger

This can be used with something like BasicPythonAssignmentChanger to perform some action if a specific variable is being assigned in the file.

Parameters:

notify – A function that is called with the name of the variable and the value for that variable that was found.

protocol Notifier

typing.Protocol.

Classes that implement this protocol must have the following methods / attributes:

__call__(*, variable_name: str, value: object) None

Call self as a function.

class pytest_typing_runner.file_changers.ListVariableChanger(*, change: Changer)

An object that satisfies PythonVariableChanger

This can be used with something like BasicPythonAssignmentChanger to change the value being assigned to a specific variable.

Parameters:

change – A function that is called with the name of the variable and the value for that variable that was found. The ast.expr objects the function returns will be used as the elts in a ast.List object that replaces the value being assigned to that variable.

protocol Changer

typing.Protocol.

Classes that implement this protocol must have the following methods / attributes:

__call__(*, variable_name: str, values: Sequence[object]) Sequence[expr]

Call self as a function.