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_dirto look atextra_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_contentto 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:
LocationOutOfBounds – if
self.pathrelative toself.root_diris not under that locationLocationDoesNotExist – if
must_existis True and the location doesn’t exist
- 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
srcintoroot_dirfrom 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
pathto copy frompath – The path relative to
srcto look at and relative toroot_dirto 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
srcexclude – an optional function that takes each file that is being copied. Returning
Falsefrom this function will result in the file not being copied over
- Raises:
LocationOutOfBounds – if
self.pathrelative toself.srcis outside ofself.srcLocationOutOfBounds – if
self.pathrelative toself.root_diris outside ofself.root_dirLocationDoesNotExist – if
self.pathrelative toself.srcdoes not existLocationIsNotDirectory – if
self.pathrelative toself.srcis not a directory
- protocol pytest_typing_runner.file_changers.PythonVariableChanger
Protocol representing a
changerused byPythonFileChangerIt takes in a node that is either
ast.Assignorast.AnnAssignand 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
pathinpath – The path relative to
root_dirto look atvariable_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.Assignorast.AnnAssignare 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_pathon the file. And will use aast.NodeTransformerto 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.pathrelative toself.root_diris outside ofself.root_dir
- class pytest_typing_runner.file_changers.VariableFinder(*, notify: Notifier)
An object that satisfies
PythonVariableChangerThis can be used with something like
BasicPythonAssignmentChangerto 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.
- class pytest_typing_runner.file_changers.ListVariableChanger(*, change: Changer)
An object that satisfies
PythonVariableChangerThis can be used with something like
BasicPythonAssignmentChangerto 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.exprobjects the function returns will be used as theeltsin aast.Listobject that replaces the value being assigned to that variable.