Notice Changers
This plugin providers some helpers for modifying notices so that the api of the notices themselves remains small.
These helpers are split into high level helpers available in
pytest_typing_runner.notices and low level helpers available in
pytest_typing_runner.notice_changers.
High level changers
- class pytest_typing_runner.notices.AddRevealedTypes(*, name: str, revealed: Sequence[str], replace: bool = False)
Used to add revealed type notices to a specific line
from pytest_typing_runner import notices, protocols file_notices: protocols.FileNotices = ... assert [n.display() for n in file_notices.notices_at_line(1)] == [ 'severity=note:: Revealed type is "one"', 'severity=error[arg-type]:: an error', 'severity=note:: a note', ] assert file_notices.get_line_number("line_name") == 1 changed = notices.AddRevealedTypes( name="line_name", revealed=["two", "three"], )(file_notices) assert [n.display() for n in file_notices.notices_at_line(1)] == [ 'severity=note:: Revealed type is "one"', 'severity=error[arg-type]:: an error', 'severity=note:: a note', 'severity=note:: Revealed type is "two"', 'severity=note:: Revealed type is "three"', ]
Where existing revealed notes can be removed. For example, continuing the code example:
changed = notices.AddRevealedTypes( name="line_name", revealed=["two", "three"], replace=True )(file_notices) assert [n.display() for n in file_notices.notices_at_line(1)] == [ 'severity=error[arg-type]:: an error', 'severity=note:: a note', 'severity=note:: Revealed type is "two"', 'severity=note:: Revealed type is "three"', ]
Note
When there are multiple items in revealed, only one notice is appended where the different notes are in a single multiline string for the msg of that one notice. The default implementation for comparing notices already knows how to split these into multiple notices.
- Parameters:
name – The name of the line to change. The name must already be registered
revealed – A sequence of strings to add reveal messages for. Each message is wrapped such that if the string is ‘X’ the result is ‘Revealed type is “X”’
replace – Defaults to
False. WhenTrueany existing reveal notes will be removed before new ones are added.
- __call__(notices: FileNotices) FileNotices
Peforms the transformation
- Parameters:
notices – The file notices to change
- Raises:
MissingNotices – When the specified
nameisn’t registered with the file notices.- Returns:
Copy of the file notices with additional reveal notes, where existing reveal notes have been removed if
replaceisTrue
- class pytest_typing_runner.notices.AddErrors(*, name: str, errors: Sequence[tuple[str, str | NoticeMsg]], replace: bool = False)
Used to add error notices to a specific line
from pytest_typing_runner import notices, protocols file_notices: protocols.FileNotices = ... assert [n.display() for n in file_notices.notices_at_line(1)] == [ 'severity=note:: Revealed type is "one"', 'severity=error[arg-type]:: an error', 'severity=note:: a note', ] assert file_notices.get_line_number("line_name") == 1 changed = notices.AddErrors( name="line_name", errors=[("misc", "error two"), ("assignment", "error three")], )(file_notices) assert [n.display() for n in file_notices.notices_at_line(1)] == [ 'severity=note:: Revealed type is "one"', 'severity=error[arg-type]:: an error', 'severity=note:: a note', 'severity=error[misc]:: error two', 'severity=error[assignment]:: error three', ]
Where existing errors can be removed. For example, continuing the code example:
changed = notices.AddErrors( name="line_name", errors=[("misc", "error two"), ("assignment", "error three")], replace=True, )(file_notices) assert [n.display() for n in file_notices.notices_at_line(1)] == [ 'severity=note:: Revealed type is "one"', 'severity=note:: a note', 'severity=error[misc]:: error two', 'severity=error[assignment]:: error three', ]
Note
Unlike
AddRevealedTypesevery entry inerrorsbecomes it’s own notice.- Parameters:
name – The name of the line to change. The name must already be registered
errors – A sequence of two string tuples where the first string is the error type and the second string is the error message.
replace – Defaults to
False. WhenTrueany existing error notices will be removed before new ones are added.
- __call__(notices: FileNotices) FileNotices
Peforms the transformation
- Parameters:
notices – The file notices to change
- Raises:
MissingNotices – When the specified
nameisn’t registered with the file notices.- Returns:
Copy of the file notices with additional error notices, where existing error notices have been removed if
replaceisTrue
- class pytest_typing_runner.notices.AddNotes(*, name: str, notes: Sequence[str | NoticeMsg], replace: bool = False, keep_reveals: bool = True)
Used to add note notices to a specific line
from pytest_typing_runner import notices, protocols file_notices: protocols.FileNotices = ... assert [n.display() for n in file_notices.notices_at_line(1)] == [ 'severity=note:: Revealed type is "one"', 'severity=error[arg-type]:: an error', 'severity=note:: a note', ] assert file_notices.get_line_number("line_name") == 1 changed = notices.AddNotes( name="line_name", notes=["two", "three"], )(file_notices) assert [n.display() for n in file_notices.notices_at_line(1)] == [ 'severity=note:: Revealed type is "one"', 'severity=error[arg-type]:: an error', 'severity=note:: a note', 'severity=note:: two', 'severity=note:: three', ]
Where existing notes can be removed. For example, continuing the code example:
changed = notices.AddNotes( name="line_name", notes=["two", "three"], replace=True, )(file_notices) assert [n.display() for n in file_notices.notices_at_line(1)] == [ 'severity=error[arg-type]:: an error', 'severity=note:: two', 'severity=note:: three', ]
And existing reveal notes can be left alone, continuing the code example:
changed = notices.AddNotes( name="line_name", notes=["two", "three"], replace=True, keep_reveals=True, )(file_notices) assert [n.display() for n in file_notices.notices_at_line(1)] == [ 'severity=note:: Revealed type is "one"', 'severity=error[arg-type]:: an error', 'severity=note:: two', 'severity=note:: three', ]
Note
Like :class:AddRevealedTypes: only one notice is append to the end where the message is a multiline string with each note on it’s own line
- Parameters:
name – The name of the line to change. The name must already be registered
notes – A sequence of strings representing each note to add
replace – Defaults to
False. WhenTrueany existing notes removed before new ones are added.keep_reveals – Defaults to
True. WhenreplaceisTrueand this isTruethen notes that are type reveals will not be removed.
- __call__(notices: FileNotices) FileNotices
Peforms the transformation
- Parameters:
notices – The file notices to change
- Raises:
MissingNotices – When the specified
nameisn’t registered with the file notices.- Returns:
Copy of the file notices with additional notes, where existing notes are removed depending on the values of
replaceandkeep_reveals.
- class pytest_typing_runner.notices.RemoveFromRevealedType(*, name: str, remove: str, must_exist: bool = True)
Used to remove some specific string from existing reveal notes.
from pytest_typing_runner import notices, protocols file_notices: protocols.FileNotices = ... assert [n.display() for n in file_notices.notices_at_line(1)] == [ 'severity=note:: Revealed type is "some specific message"', 'severity=error[arg-type]:: an error', 'severity=note:: a specific note', ] assert file_notices.get_line_number("line_name") == 1 changed = notices.RemoveFromRevealedType( name="line_name", remove="specific", )(file_notices) assert [n.display() for n in file_notices.notices_at_line(1)] == [ 'severity=note:: Revealed type is "some message"', 'severity=error[arg-type]:: an error', 'severity=note:: a specific note', ]
- Parameters:
name – The name of the line to change. The name must already be registered
remove – The string to remove from all reveal notes that are found.
must_exist – Defaults to
True. WhenTrueand no reveal notes are found with the specifiedreplacestring, thenMissingNoticeswill be raised.
- __call__(notices: FileNotices) FileNotices
Peforms the transformation
- Parameters:
notices – The file notices to change
- Raises:
MissingNotices – When the specified
nameisn’t registered with the file notices.MissingNotices – When
must_existisTrueand no reveal notes with thereplacestring are found
- Returns:
Copy of the file notices where all revealed notes at the specified line have the
replacestring removed from theirmsg.
Low level changers
- exception pytest_typing_runner.notice_changers.MissingNotices(*, location: Path, name: str | int | None = None, line_number: int | None = None)
Raised when notices are expected to be somewhere they are not
- Parameters:
location – The file the notices were expected to be in
name – Optional name of the line notices expected to be on
line_number – Optional line number the notices were expected to be on
- class pytest_typing_runner.notice_changers.FirstMatchOnly(*, change: ProgramNoticeChanger)
Used to change a ProgramNotice such that only the first time this is used a change will be made:
from pytest_typing_runner import notice_changers, protocols changer = notice_changers.FirstMatchOnly( change = lambda notice: notice.clone(msg="changed!") ) existing_notice: protocols.ProgramNotice = ... changed_notice = changer(existing_notice) assert changed_notice is not existing_notice other_existing_notice: protocols.ProgramNotice = ... # subsequent changes will pass through existing notice changed_other_notice = changer(other_existing_notice) assert changed_other_notice is other_existing_notice
- Parameters:
change – A function that takes in a program notice and returns either
Noneif the notice should be removed, or a program notice to replace it.
- __call__(notice: ProgramNotice, /) ProgramNotice | None
Perform the transformation
- Parameters:
notice – The notice to change
- Returns:
Clone of the program notice with changes, or None if the notice should be deleted
- class pytest_typing_runner.notice_changers.AppendToLine(*, notices_maker: Callable[[LineNotices], Sequence[ProgramNotice | None]])
Used to append notices to a line notices.
from pytest_typing_runner import notice_changer, protocols existing_line_notices: protocols.LineNotices = ... assert list(existing_line_notices) == [_existing_notice1, _existing_notice2] changer = notice_changer.AppendToLine( notices_maker = lambda ln: [_new_notice1, new_notice2] ) changed = changer(existing_line_notices) assert list(existing_line_notices) == [_existing_notice1, _existing_notice2, _new_notice1, _new_notice2]
- Parameters:
notices_maker – Callable that takes the line notices being changed and returns a sequence of program notice objects to add to that line notices. Any
Nonevalues in the sequence will be ignored.
- __call__(notices: LineNotices, /) LineNotices
Perform the transformation
- Parameters:
notices – The line notices to change
- Returns:
A copy of the passed in notices with the additional notices appended. This changer never returns
None.
- class pytest_typing_runner.notice_changers.ModifyLatestMatch(*, must_exist: bool = False, allow_empty: bool = False, change: ProgramNoticeChanger, matcher: Callable[[ProgramNotice], bool])
Used to match against a particular notice and change the first one that matches where the matching happens from the end of the notices back.
So if the line notices has
[n1, n2, n3]then it will try to matchn3thenn2, thenn1. Once a notice has been changed, the rest will not be changed.By default if no notice matches then a new notice is added to the end and run through the change argument.
from pytest_typing_runner import protocols, notice_changers line_notices: protocols.LineNotices = ... assert [n.msg for n in list(line_notices)] == ["n1", "s1", "n2", "s2", "n3"] changer = notice_changers.ModifyLatestMatch( change = lambda notice: notice.clone(msg="changed!"), matcher = lambda notice: notice.msg.startswith("s") ) changed = changer(line_notices) assert [n.msg for n in (list(changed) or [])] == ["n1", "s1", "n2", "changed!", "n3"]
- Parameters:
must_exist – Defaults to
False, if passed in asTruethen an exception will be raised if no notice matchesallow_empty – Defaults to
False, if passed in asTruethen this changer will never returnNone. Otherwise if there are no notices in the line notices after the change, thenNonewill be returned to indicate the notices should be deleted.change – A function to be given the notice to change. Note that if
must_existisFalseand no notice matches, then it will be given a notice with a default “note” severity and empty message.matcher – A function given each notice from the back to the front till
Trueis returned to indicate a notice that should be changed
- __call__(notices: LineNotices) LineNotices | None
Perform the transformation
- Parameters:
notices – The line notices to change
- Raises:
MissingNotices – if no notice matches and
must_existis True- Returns:
A copy of the line notices with the changed notice, or
Noneifallow_emptyisFalseand no notices are left after the change.
- class pytest_typing_runner.notice_changers.ModifyLine(*, name_or_line: str | int, name_must_exist: bool = True, line_must_exist: bool = True, change: LineNoticesChanger)
Used to modify a line notices at a specific line in a file notices.
from pytest_typing_runner import notice_changers, protocols file_notices: protocols.FileNotices = ... assert file_notices.notices_at_line(5) is None changer = notice_changers.ModifyLine( name_or_line=5, change=lambda line_notices: line_notices.set_notices([_new_notice1, _new_notice2]) ) changed = changer(file_notices) assert list(changed.notices_at_line(5) or []) == [_new_notice1, _new_notice2]
This also works with named lines:
from pytest_typing_runner import notice_changers, protocols file_notices: protocols.FileNotices = ... file_notices = file_notices.set_name("one", 5) assert file_notices.notices_at_line(5) is None changer = notice_changers.ModifyLine( name_or_line="one", change=lambda line_notices: line_notices.set_notices([_new_notice1, _new_notice2]) ) changed = changer(file_notices) assert list(changed.notices_at_line(5) or []) == [_new_notice1, _new_notice2]
- Parameters:
name_or_line – Either a string used as a name, or an integer representing the specific line.
name_must_exist – Defaults to
True. Will raiseMissingNoticesifTrueandname_or_lineis a string that isn’t registered on the file notices.line_must_exist – Defaults to
True. Will raiseMissingNoticesifTrueand the resolved line number fromname_or_linedoesn’t have an existing line notices.change – A function given a line notices to return a new line notices to give to the file notices. If this function returns None then the file notices will be told to not have notices for the resolved line number. If the
name_must_existandline_must_existflags areFalseand the resolved line number doesn’t have an existing line notices, a new one will be generated to pass intochange
- __call__(notices: FileNotices) FileNotices
Perform the transformation
- Parameters:
notices – The file notices to change
- Raises:
MissingNotices – if
name_must_existisTrueandname_or_lineis a string and not a registered nameMissingNotices – if
line_must_existisTrueand the resolved line number doesn’t already have line notices on the file notices.
- Returns:
A copy of the file notices with changed line notices for the resolved line number.
- class pytest_typing_runner.notice_changers.ModifyFile(*, location: Path, must_exist: bool = True, change: FileNoticesChanger)
Used to modify the notices for a particular location on a program notices.
from pytest_typing_runner import notice_changers, protocols import pathlib location = pathlib.Path(...) program_notices: protocols.ProgramNotices = ... assert list(program_notices.notices_at_location(location) or []) == [ _existing_line_1_notice, _existing_line_2_notice, ] changer = notice_changers.ModifyFile( location=location, change=lambda file_notices: file_notices.set_lines( { 5: file_notices.generate_notices_for_line(5).set_notices([_new_line_5_notice]) } ) ) changed = changer(program_notices) assert list(program_notices.notices_at_location(location) or []) == [ _existing_line_1_notice, _existing_line_2_notice, _new_line_5_notice, ]
- Parameters:
location – The location to modify
must_exist – Defaults to
True. WhenTrueand thelocationisn’t already in the program notices aMissingNoticeswill be raised.change – A function that takes a file notices to change. One will be generated if the location isn’t already in the program notices and
must_existisFalse.
- __call__(notices: ProgramNotices) ProgramNotices
Perform the transformation
- Parameters:
notices – The program notices to change
- Raises:
MissingNotices – if
must_existisTrueand thelocationisn’t already in the program notices.- Returns:
A copy of the program notices with changed file notices for the specified location.
- class pytest_typing_runner.notice_changers.BulkAdd(*, root_dir: Path, add: Mapping[str, Mapping[int | tuple[int, str], Sequence[str | NoticeMsg | tuple[Severity, str]]]])
Used to bulk add notices to a program notices.
from pytest_typing_runner import notice_changers, protocols import pathlib root_dir = pathlib.Path(...) program_notices: protocols.ProgramNotices = ... changer = notice_changers.BulkAdd( root_dir=root_dir, add={ "one": { 1: [ "a note", (notices.ErrorSeverity("arg-type"), "an error"), ], (20, "name"): [...], }, "two/three": {...}, }, ) program_notices = changer(program_notices) # and now we have program notices for: # location=(root_dir / "one") | line_number=1 | severity=note | msg="a note" # location=(root_dir / "one") | line_number=1 | severity=error(arg-type) | msg="an error" # location=(root_dir / "one") | line_number=20 | ... # location=(root_dir / "two" / "three") | ... # # And line 20 will have the name "name"
- Parameters:
root_dir – The path all locations are relative to
add –
A Mapping of path to Mapping of line number to sequence of notices.
Where the key for the line number is either an integer or a tuple of
(line_number, line_name)for registering that name to that line number in that location.Where the notices are either a string indicating a note severity notice or a tuple of
(severity, msg).
- __call__(notices: ProgramNotices) ProgramNotices
Add the specified notices
- Parameters:
notices – The program notices to change
- Returns:
A copy of the program notices with additional notices.