pysnap.report package¶
Reading and checking session reports recorded by the report utility.
Submodules¶
pysnap.report.archive module¶
Safe reading of report archives.
A report is an untrusted tar.gz file produced inside a student VM. The
reader never extracts anything to disk: it streams the archive once, keeps
only regular files with the names written by report and bounds both the
compressed and the decompressed amount of data it is willing to process.
- pysnap.report.archive.read_report_archive(path, limits=None)[source]¶
Read the known members of a report archive into memory.
Unknown names, links, devices, directories and path-traversal entries are skipped with a diagnostic; their content is never read.
- Parameters:
path (str | Path) – Path to the report file.
limits (ReportLimits | None) – Resource limits, defaults to
ReportLimits.
- Returns:
Raw archive members.
- Raises:
ReportFormatError – When the file is missing, too large, or is not a gzip-compressed tar archive.
- Return type:
pysnap.report.checkfile module¶
Check files: expected commands, output blocks and grading.
A check file is TOML:
[report] # optional expectations, reported as warnings
task = 1
host = "first"
[[command]]
id = "addr" # optional, referenced by ``of`` and ``after``
cmd = "ip a show <ETH-A>"
after = "other-id" # optional, commands only
points = 1 # optional weight, default 1
[[output]]
of = "addr" # optional: search only the output of that command
order = "any" # optional: lines in any order (default: sequence)
min_count = 3 # optional: the block must occur this many times
text = '''
inet <IP-A>/<MASK> scope global <ETH-A>
'''
[grading]
total = 10
scale = [[90, "5"], [75, "4"], [50, "3"], [0, "2"]]
Parameters: <IP>, <ETH> and <MASK> match any value of their
type; <IP-A>-style labels bind the first matched value and require it
later; any other name such as <X> binds one word. <*> matches any
text inside a line and a line holding only ... matches any number of
lines. Whitespace is normalized on both sides.
- exception pysnap.report.checkfile.CheckFileError[source]¶
Bases:
PySnapErrorReport a check file that cannot be used.
- class pysnap.report.checkfile.Parameter(kind, name)[source]¶
Bases:
objectDescribe one
<...>placeholder of a pattern.- Parameters:
kind (str) –
"IP","ETH","MASK"or"WORD"for free names.name (str | None) – Binding name,
Nonefor unlabeled typed parameters.
- kind: str¶
- name: str | None¶
- class pysnap.report.checkfile.Pattern(source, parts)[source]¶
Bases:
objectRepresent one line pattern split into literal text and parameters.
- Parameters:
- source: str¶
- class pysnap.report.checkfile.CommandCheck(number, id, pattern, after, points)[source]¶
Bases:
objectRepresent one
[[command]]item.- Parameters:
number (int)
id (str | None)
pattern (Pattern)
after (str | None)
points (float)
- number: int¶
- id: str | None¶
- after: str | None¶
- points: float¶
- class pysnap.report.checkfile.OutputCheck(number, of, lines, order, min_count, points)[source]¶
Bases:
objectRepresent one
[[output]]item.- Parameters:
lines (tuple[Pattern | None, ...]) – Line patterns;
Nonestands for a...gap.number (int)
of (str | None)
order (str)
min_count (int)
points (float)
- number: int¶
- of: str | None¶
- order: str¶
- min_count: int¶
- points: float¶
- class pysnap.report.checkfile.GradeStep(threshold, mark)[source]¶
Bases:
objectRepresent one step of the grading scale.
- Parameters:
threshold (float)
mark (str)
- threshold: float¶
- mark: str¶
- class pysnap.report.checkfile.CheckSpec(source_name, expected_task, expected_host, commands, outputs, total, scale)[source]¶
Bases:
objectRepresent a loaded check file.
- Parameters:
source_name (str)
expected_task (int | None)
expected_host (str | None)
commands (tuple[CommandCheck, ...])
outputs (tuple[OutputCheck, ...])
total (float)
scale (tuple[GradeStep, ...])
- source_name: str¶
- expected_task: int | None¶
- expected_host: str | None¶
- commands: tuple[CommandCheck, ...]¶
- outputs: tuple[OutputCheck, ...]¶
- total: float¶
- pysnap.report.checkfile.normalize_spacing(text)[source]¶
Trim a line and collapse whitespace runs to single spaces.
- Parameters:
text (str) – Raw line.
- Returns:
Normalized line.
- Return type:
str
- pysnap.report.checkfile.compile_pattern(text, where='pattern')[source]¶
Compile one line pattern.
- Parameters:
text (str) – Pattern line.
where (str) – Location used in error messages.
- Returns:
Compiled pattern.
- Raises:
CheckFileError – For an unknown
<NAME-label>form.- Return type:
- pysnap.report.checkfile.load_check(path)[source]¶
Load and validate a check file.
- Parameters:
path (str | Path) – Path to the TOML check file.
- Returns:
Validated check specification.
- Raises:
CheckFileError – When the file is missing, is not valid TOML or does not follow the check file format.
- Return type:
- pysnap.report.checkfile.parse_check(data, source_name)[source]¶
Validate parsed check file data.
- Parameters:
data (dict[str, Any]) – Parsed TOML document.
source_name (str) – File name used in messages.
- Returns:
Validated check specification.
- Raises:
CheckFileError – When the data does not follow the format.
- Return type:
pysnap.report.compare module¶
Pairwise comparison of session reports.
pysnap report compare looks for signs that reports of different students
share their origin. Every pair of the given reports is compared; deciding
which reports belong to the same student is left to the teacher. The
signals only point at pairs worth a look and never decide anything.
Strong signals:
S1the report files are identical (SHA-256 of the archives);S2the recorded output is identical (SHA-256 of the output body);S3both outputs show the same MAC address, except the all-zero and broadcast addresses and group (multicast) addresses;S4the sameSTART_TIME(to the second) andDURATION(to the microsecond).
Medium signals:
M1at least 20 identical consecutive keyboard input delays, compared to the microsecond: a copied or edited timing log;M2values that are random in practice: at least three consecutivepinground-trip times (time=... ms) in the same order, or at least three sharedtcpdumptimestamps with microseconds.pingprints only three significant digits, so single values often match by chance, while a run in the same order does not;M3byte-identicalCPU.txt(it includes the BogoMIPS value measured at boot);M4at least two shared erroneous commands, whose output reportscommand not foundorNo such file.
Weak signal:
W1the same CPU model.
Signals implied by a stronger one of the same pair are not repeated: an
identical file (S1) implies all others, and an identical CPU.txt
(M3) implies the same CPU model (W1).
- class pysnap.report.compare.CompareSignal(code, level, description)[source]¶
Bases:
objectRepresent one signal found for a pair of reports.
- Parameters:
code (str) – Signal code such as
"S3".level (str) –
"strong","medium"or"weak".description (str) – Human-readable description with the shared values.
- code: str¶
- level: str¶
- description: str¶
- class pysnap.report.compare.PairResult(first, second, signals)[source]¶
Bases:
objectRepresent the comparison of two reports.
- Parameters:
first (str) – Path of the first report as found.
second (str) – Path of the second report as found.
signals (tuple[CompareSignal, ...]) – Signals found, strongest first.
- first: str¶
- second: str¶
- signals: tuple[CompareSignal, ...]¶
- class pysnap.report.compare.CompareResult(reports, pairs, warnings)[source]¶
Bases:
objectRepresent the comparison of a set of reports.
- Parameters:
reports (tuple[str, ...]) – Paths of the compared reports.
pairs (tuple[PairResult, ...]) – Pairs with signals, strongest first, then pairs without.
warnings (tuple[str, ...]) – Paths that were skipped and why.
- reports: tuple[str, ...]¶
- pairs: tuple[PairResult, ...]¶
- warnings: tuple[str, ...]¶
- class pysnap.report.compare.ReportFingerprint(path, file_sha256, output_sha256, macs, start, delay_windows, ping_times, tcpdump_times, error_commands, cpu_info, cpu_model)[source]¶
Bases:
objectHold the values of one report that the signals compare.
- Parameters:
path (str) – Path of the report as found.
file_sha256 (str) – SHA-256 of the report file.
output_sha256 (str | None) – SHA-256 of the output body,
Nonewhen empty.macs (frozenset[str]) – Unicast MAC addresses seen in the output.
start (tuple[str, int] | None) –
START_TIMEto the second andDURATIONin microseconds, when both are known.delay_windows (dict[int, int]) – Hash of every run of
DELAY_RUNconsecutive input delays in microseconds, mapped to its first position. Runs of one repeated delay are left out.ping_times (tuple[str, ...]) –
pinground-trip times in output order.tcpdump_times (frozenset[str]) –
tcpdumptimestamps.error_commands (frozenset[str]) – Commands whose output reports an error.
cpu_info (str) – Content of
CPU.txt.cpu_model (str | None) – CPU model from
CPU.txt.
- path: str¶
- file_sha256: str¶
- output_sha256: str | None¶
- macs: frozenset[str]¶
- start: tuple[str, int] | None¶
- delay_windows: dict[int, int]¶
- ping_times: tuple[str, ...]¶
- tcpdump_times: frozenset[str]¶
- error_commands: frozenset[str]¶
- cpu_info: str¶
- cpu_model: str | None¶
- pysnap.report.compare.collect_report_paths(paths)[source]¶
Expand files and directories into the list of report files.
Directories are searched recursively for files named
report.NN.HOST; files given explicitly are used whatever their name. A file reached twice is compared once.- Parameters:
paths (Iterable[str | Path]) – Files and directories.
- Returns:
Report paths and warnings for paths that gave no reports.
- Return type:
tuple[list[Path], list[str]]
- pysnap.report.compare.fingerprint_report(path, recording, transcript)[source]¶
Collect the compared values of one report.
- Parameters:
path (str | Path) – Path of the report file.
recording (Recording) – Parsed report.
transcript (Transcript) – Rendered report.
- Returns:
Report fingerprint.
- Return type:
- pysnap.report.compare.compare_pair(first, second)[source]¶
Compare two reports.
- Parameters:
first (ReportFingerprint) – First report.
second (ReportFingerprint) – Second report.
- Returns:
The pair with its signals, strongest first.
- Return type:
- pysnap.report.compare.compare_fingerprints(fingerprints)[source]¶
Compare every pair of reports.
- Parameters:
fingerprints (Sequence[ReportFingerprint]) – Reports in input order.
- Returns:
Pairs with signals, strongest first, then pairs without.
- Return type:
tuple[PairResult, …]
- pysnap.report.compare.compare_reports(paths, limits=None)[source]¶
Load, fingerprint and compare reports.
- Parameters:
paths (Iterable[str | Path]) – Report files and directories.
limits (ReportLimits | None) – Resource limits for reading each report.
- Returns:
Comparison result;
reportsis empty when none could be read.- Return type:
pysnap.report.control module¶
Control channel between pysnap report extract and pysnap connect.
VirtualBox serves one client on a UART1 TCP port, so while
pysnap connect is attached only that process can talk to the console.
The connect process listens on 127.0.0.1 on a random port
and stores the port and a random secret in its session record, which only the
current user can read. pysnap report extract sends one JSON request line
with the secret and the quoted file path; the connect process runs the
transfer protocol of pysnap.report.extract on its own serial
connection and answers with one JSON line holding the file as base64 and its
checksum, or an error message.
- class pysnap.report.control.ControlServer(run_transfer)[source]¶
Bases:
objectServe transfer requests for one
pysnap connectsession.- Parameters:
run_transfer (TransferRunner)
- pysnap.report.control.request_transfer(port, token, remote_path, *, max_bytes=16777216, connect_timeout=5.0)[source]¶
Ask an attached
pysnap connectprocess to transfer a file.- Parameters:
port (int) – Control port from the session record.
token (str) – Control secret from the session record.
remote_path (str) – Quoted shell path of the file.
max_bytes (int) – Largest accepted file size.
connect_timeout (float) – Seconds to wait for the control port.
- Returns:
File content and its SHA-256 checksum.
- Raises:
ExtractError – When the session does not answer or the transfer fails.
- Return type:
tuple[bytes, str]
pysnap.report.extract module¶
Copy a report file from a running VM through its serial console or ssh.
A VM reached through ssh gets a separate ssh session that runs one command:
it prints the sha256sum of the file and then the file itself. The
student’s terminal is not touched, so this works while pysnap connect is
attached. Every other VM uses its serial console.
The serial transfer uses only the guest shell and coreutils:
The VM must be running and expose
UART1as a TCP server. VirtualBox serves one client, so whenpysnap connectis attached the transfer is delegated to that process through its control channel (pysnap.report.control), which runs the same protocol.PySnap wakes the console with Enter and refuses to continue when the prompt installed by
reportis visible: a recording is running and every further command would end up in the student’s report.echo PYSNAP_$((20+22))must answerPYSNAP_42. The echoed command line cannot contain that text, so the answer proves a working shell and not a login screen or a full-screen program.One command prints a begin marker, the file as one
base64line, itssha256sumand an end marker. The markers are split in the command so that its echo never looks like a marker.PySnap decodes the data, compares the checksum, writes the file atomically and clears the guest screen.
Service commands start with a space, so shells with HISTCONTROL set to
ignorespace or ignoreboth keep them out of the history.
- exception pysnap.report.extract.ExtractError[source]¶
Bases:
PySnapErrorReport a file transfer from a VM that could not be completed.
- class pysnap.report.extract.ExtractResult(vm_name, remote_path, destination, size, sha256)[source]¶
Bases:
objectDescribe a completed transfer.
- Parameters:
vm_name (str) – Source VM.
remote_path (str) – Shell path of the file inside the VM.
destination (Path) – File written on the host.
size (int) – Number of bytes transferred.
sha256 (str) – Checksum confirmed by the guest and the host.
- vm_name: str¶
- remote_path: str¶
- destination: Path¶
- size: int¶
- sha256: str¶
- pysnap.report.extract.extract_file(service, vm_name, name, *, destination=None, force=False, connector=None, connect_timeout=10.0, shell_timeout=5.0, idle_timeout=10.0, max_bytes=16777216)[source]¶
Copy one file from a running VM to the host.
- Parameters:
service – PySnap service used to inspect the VM and its sessions.
vm_name (str) – VM to copy from.
name (str) – File name in the root home directory, or a path with
/.destination (Path | None) – Host path; defaults to the file name in the current directory.
force (bool) – Whether an existing destination may be replaced.
connector (Callable[[str, int, float], socket] | None) – Opens the serial TCP socket; for tests.
connect_timeout (float) – Seconds to wait for the serial TCP port.
shell_timeout (float) – Seconds to wait for the shell check answer.
idle_timeout (float) – Seconds without data after which the transfer fails.
max_bytes (int) – Largest accepted file size.
- Returns:
Transfer details.
- Raises:
ExtractError – When any precondition or the transfer fails.
- Return type:
- pysnap.report.extract.run_transfer(console, vm_name, remote_path, *, shell_timeout=5.0, idle_timeout=10.0, max_bytes=16777216)[source]¶
Run the transfer protocol on a serial console.
- Parameters:
console (ConsoleIO) – Console transport.
vm_name (str) – VM name used in messages.
remote_path (str) – Quoted shell path of the file.
shell_timeout (float) – Seconds to wait for the shell check answer.
idle_timeout (float) – Seconds without data after which the transfer fails.
max_bytes (int) – Largest accepted file size.
- Returns:
File content and its SHA-256 checksum.
- Raises:
ExtractError – When the console is not usable or the transfer fails.
- Return type:
tuple[bytes, str]
- pysnap.report.extract.ssh_transfer(service, vm_name, remote_path, *, max_bytes=16777216, timeout=120.0)[source]¶
Copy a file through a separate ssh session with the PySnap key.
- Parameters:
service – PySnap service; gives the ssh target and the key store.
vm_name (str) – VM to copy from.
remote_path (str) – Quoted shell path of the file.
max_bytes (int) – Largest accepted file size.
timeout (float) – Seconds the whole transfer may take.
- Returns:
File content and its SHA-256 checksum.
- Raises:
ExtractError – When ssh fails or the file cannot be read.
- Return type:
tuple[bytes, str]
- pysnap.report.extract.remote_shell_path(name)[source]¶
Return the quoted shell path of a file in the VM.
- Parameters:
name (str) – File name, looked up in the home directory, or a path.
- Returns:
Shell expression for the path.
- Raises:
ExtractError – For empty names or names with control characters.
- Return type:
str
- class pysnap.report.extract.ConsoleIO(*args, **kwargs)[source]¶
Bases:
ProtocolConsole transport used by
run_transfer().- buffer: bytes¶
- send(data)[source]¶
Send bytes and start a new response buffer.
- Parameters:
data (bytes)
- Return type:
None
- class pysnap.report.extract.SocketConsole(connection)[source]¶
Bases:
_BufferedConsoleConsole on a blocking serial TCP socket.
- Parameters:
connection (socket.socket)
- class pysnap.report.extract.QueueConsole(write)[source]¶
Bases:
_BufferedConsoleConsole fed by another thread, such as the
pysnap connectloop.The owner calls
feed()with guest output andclose()when the serial connection ends;writemust be safe to call from the transfer thread.- Parameters:
write (Callable[[bytes], None])
pysnap.report.highlight module¶
Highlighting of prompts and entered commands in a transcript.
The report prompt is recorded without colors, so PySnap styles its parts
like a typical colored bash prompt and shows entered commands in bold. The
result is a list of styled runs per line that any front end can draw.
- pysnap.report.highlight.highlight_transcript(transcript)[source]¶
Return the transcript lines with highlighted prompts and commands.
Lines without a command keep the styles produced by the guest.
- Parameters:
transcript (Transcript) – Rendered session.
- Returns:
Styled runs for every transcript line.
- Return type:
list[tuple[StyledRun, …]]
pysnap.report.matcher module¶
Match a check file against a rendered report and grade the result.
Items are checked in file order: all [[command]] items, then all
[[output]] items, so labels bound by commands reach the output blocks.
A backtracking search chooses the matches: for every item it tries the
candidates in report order and also the possibility that the item fails, and
keeps the assignment with the most points; ties keep the earliest choices, so
the result is deterministic. A failing item therefore never takes the labels
of later items down with it.
- class pysnap.report.matcher.ItemResult(kind, number, label, pattern, passed, points, command=None, lines=(), count=0, reason=None, closest=None)[source]¶
Bases:
objectRepresent the outcome of one check item.
- Parameters:
kind (str) –
"command"or"output".number (int) – Position among the items of its kind, starting at 1.
label (str) – Item id or its first pattern line.
pattern (str) – Pattern text shown to the reader.
passed (bool) – Whether the item passed.
points (float) – Weight of the item.
command (int | None) – Index of the matched report command, for commands.
lines (tuple[int, ...]) – Transcript lines of the first match, for outputs.
count (int) – Number of block occurrences found, for outputs.
reason (str | None) – Explanation of a failure.
closest (str | None) – Closest entered command for a failed command item.
- kind: str¶
- number: int¶
- label: str¶
- pattern: str¶
- passed: bool¶
- points: float¶
- command: int | None = None¶
- lines: tuple[int, ...] = ()¶
- count: int = 0¶
- reason: str | None = None¶
- closest: str | None = None¶
- class pysnap.report.matcher.CheckResult(check_name, items, bindings, earned_weight, total_weight, percent, points, total_points, mark, warnings, search_exhausted=False)[source]¶
Bases:
objectRepresent the graded result of a check file.
- Parameters:
check_name (str)
items (tuple[ItemResult, ...])
bindings (dict[str, str])
earned_weight (float)
total_weight (float)
percent (float)
points (float)
total_points (float)
mark (str | None)
warnings (tuple[str, ...])
search_exhausted (bool)
- check_name: str¶
- items: tuple[ItemResult, ...]¶
- bindings: dict[str, str]¶
- earned_weight: float¶
- total_weight: float¶
- percent: float¶
- points: float¶
- total_points: float¶
- mark: str | None¶
- warnings: tuple[str, ...]¶
- search_exhausted: bool = False¶
- pysnap.report.matcher.run_check(spec, transcript, *, report_name=None, budget=200000)[source]¶
Check a transcript against a check file and grade it.
- Parameters:
spec (CheckSpec) – Loaded check file.
transcript (Transcript) – Rendered report.
report_name (ReportName | None) – Task and host from the report file name, used for the
[report]expectations.budget (int) – Largest number of search steps before the best assignment found so far is used.
- Returns:
Graded result.
- Return type:
pysnap.report.models module¶
Data contracts shared by the report reading modules.
- class pysnap.report.models.ReportLimits(max_archive_bytes=16777216, max_member_bytes=67108864, max_unpacked_bytes=134217728)[source]¶
Bases:
objectBound the resources spent on one untrusted report archive.
- Parameters:
max_archive_bytes (int) – Largest accepted compressed archive size.
max_member_bytes (int) – Largest accepted size of one archive member.
max_unpacked_bytes (int) – Largest amount of decompressed data read from the archive, including skipped members and tar headers.
- max_archive_bytes: int = 16777216¶
- max_member_bytes: int = 67108864¶
- max_unpacked_bytes: int = 134217728¶
- class pysnap.report.models.ReportDiagnostic(code, message)[source]¶
Bases:
objectDescribe one non-fatal problem found in a report.
- Parameters:
code (str) – Stable machine-readable problem identifier.
message (str) – Human-readable explanation.
- code: str¶
- message: str¶
- class pysnap.report.models.ReportName(task, host)[source]¶
Bases:
objectRepresent the task number and host encoded in a report file name.
- Parameters:
task (int)
host (str)
- task: int¶
- host: str¶
- classmethod parse(file_name)[source]¶
Parse a
report.<NN>.<host>file name.- Parameters:
file_name (str) – Base name of the report file.
- Returns:
Parsed name or
Nonewhen the name does not follow thereportconvention.- Return type:
ReportName | None
- class pysnap.report.models.ReportArchive(members, mtimes, diagnostics=())[source]¶
Bases:
objectHold the raw members read from a report archive.
- Parameters:
members (dict[str, bytes]) – Known member names mapped to their content.
mtimes (dict[str, int]) – Known member names mapped to their archive modification time in seconds since the epoch.
diagnostics (tuple[ReportDiagnostic, ...]) – Problems found while reading the archive.
- members: dict[str, bytes]¶
- mtimes: dict[str, int]¶
- diagnostics: tuple[ReportDiagnostic, ...] = ()¶
- class pysnap.report.models.TimingEvent(kind, delay, time, data)[source]¶
Bases:
objectRepresent one input or output chunk of the recorded session.
- Parameters:
kind (str) –
"I"for keyboard input or"O"for terminal output.delay (float) – Seconds elapsed since the previous chunk.
time (float) – Seconds elapsed since the start of the recording.
data (bytes) – Raw bytes of the chunk.
- kind: str¶
- delay: float¶
- time: float¶
- data: bytes¶
- class pysnap.report.models.Recording(source_name, name, headers, start_time, duration, exit_code, columns, lines, events, input_bytes, output_bytes, cpu_info='', mtimes=<factory>, diagnostics=())[source]¶
Bases:
objectRepresent a parsed session report.
- Parameters:
source_name (str) – Base name of the report file.
name (ReportName | None) – Task and host parsed from the file name, when available.
headers (dict[str, str]) – Raw
Hheader values from the timing log.start_time (datetime | None) – Recording start reported by
script.duration (float | None) – Recording duration in seconds reported by
script.exit_code (int | None) – Exit code of the recorded shell.
columns (int | None) – Terminal width at recording start.
lines (int | None) – Terminal height at recording start.
events (tuple[TimingEvent, ...]) – Input and output chunks in recording order.
input_bytes (bytes) – Complete recorded keyboard input.
output_bytes (bytes) – Complete recorded terminal output.
cpu_info (str) – Content of
CPU.txt(lscpuoutput).mtimes (dict[str, int]) – Archive modification times of the known members.
diagnostics (tuple[ReportDiagnostic, ...]) – Non-fatal problems found in the report.
- source_name: str¶
- name: ReportName | None¶
- headers: dict[str, str]¶
- start_time: datetime | None¶
- duration: float | None¶
- exit_code: int | None¶
- columns: int | None¶
- lines: int | None¶
- events: tuple[TimingEvent, ...]¶
- input_bytes: bytes¶
- output_bytes: bytes¶
- cpu_info: str = ''¶
- mtimes: dict[str, int]¶
- diagnostics: tuple[ReportDiagnostic, ...] = ()¶
- class pysnap.report.models.CellStyle(fg='default', bg='default', bold=False, italics=False, underscore=False, strikethrough=False, reverse=False)[source]¶
Bases:
objectRepresent the visual attributes of rendered terminal text.
Colors keep the
pytenotation:"default", an ANSI color name such as"red"or"brightred", or a six-digit hex value.- Parameters:
fg (str)
bg (str)
bold (bool)
italics (bool)
underscore (bool)
strikethrough (bool)
reverse (bool)
- fg: str = 'default'¶
- bg: str = 'default'¶
- bold: bool = False¶
- italics: bool = False¶
- underscore: bool = False¶
- strikethrough: bool = False¶
- reverse: bool = False¶
- class pysnap.report.models.StyledRun(text, style=CellStyle(fg='default', bg='default', bold=False, italics=False, underscore=False, strikethrough=False, reverse=False))[source]¶
Bases:
objectRepresent consecutive characters that share one style.
- Parameters:
text (str)
style (CellStyle)
- text: str¶
- class pysnap.report.models.TranscriptLine(runs=())[source]¶
Bases:
objectRepresent one rendered line of the session transcript.
- Parameters:
runs (tuple[StyledRun, ...])
- class pysnap.report.models.PromptInfo(user, task, host, directory)[source]¶
Bases:
objectRepresent the parts of the prompt installed by
report.reportsetsPS1="[\u@NN-HOST \W]# ".- Parameters:
user (str)
task (int)
host (str)
directory (str)
- user: str¶
- task: int¶
- host: str¶
- directory: str¶
- class pysnap.report.models.CommandRecord(index, time, line, column, prompt, text, prompt_info, first_event, enter_event, output_start, output_end)[source]¶
Bases:
objectRepresent one input line submitted with Enter.
- Parameters:
index (int) – Zero-based position among all recorded commands.
time (float) – Recording time of the Enter key press in seconds.
line (int) – Transcript line where the input starts.
column (int) – Column where the input starts, i.e. the prompt width.
prompt (str) – Text left of the input on its first line.
text (str) – Submitted input as displayed by the shell.
prompt_info (PromptInfo | None) – Parsed
reportprompt,Nonefor other prompts such asvtyshor a remote shell.first_event (int) – Index of the first input event of this line.
enter_event (int) – Index of the input event that contained Enter.
output_start (int) – First transcript line after the input.
output_end (int) – Transcript line after the last output line.
- index: int¶
- time: float¶
- line: int¶
- column: int¶
- prompt: str¶
- text: str¶
- prompt_info: PromptInfo | None¶
- first_event: int¶
- enter_event: int¶
- output_start: int¶
- output_end: int¶
- class pysnap.report.models.Transcript(lines, commands, diagnostics=())[source]¶
Bases:
objectRepresent the rendered session: final text and recognized commands.
- Parameters:
lines (tuple[TranscriptLine, ...])
commands (tuple[CommandRecord, ...])
diagnostics (tuple[ReportDiagnostic, ...])
- lines: tuple[TranscriptLine, ...]¶
- commands: tuple[CommandRecord, ...]¶
- diagnostics: tuple[ReportDiagnostic, ...] = ()¶
pysnap.report.player module¶
Timed replay of a recording for pysnap report show.
The player runs without any user interface: it owns a terminal emulator of
the recorded size and moves it to any moment of the recording. Playback uses a
compressed timeline in which every pause is limited to max_delay seconds,
like scriptreplay -m. Seeking backwards replays from the start, which is
fast because the output is only parsed, never drawn.
- class pysnap.report.player.ReplayPlayer(recording, transcript, *, max_delay=1.0, max_screen_size=1000, history=10000)[source]¶
Bases:
objectMove a terminal emulator along the timeline of a recording.
- Parameters:
recording (Recording)
transcript (Transcript)
max_delay (float | None)
max_screen_size (int)
history (int)
- property current_command: int[source]¶
Return how many commands have started up to the current moment.
- seek(time)[source]¶
Show the screen as it was at a moment of the compressed timeline.
- Parameters:
time (float) – Target moment in seconds, clamped to the recording.
- Return type:
None
- class pysnap.report.player.PlaybackController(player, speed=1.0)[source]¶
Bases:
objectHold the playback state driven by the
showkey bindings.- Parameters:
player (ReplayPlayer)
speed (float)
- tick(seconds)[source]¶
Advance playback by real elapsed time.
- Parameters:
seconds (float) – Real seconds since the previous tick.
- Return type:
None
pysnap.report.recording module¶
Parsing of the script recording stored in a report archive.
report runs script -I IN.txt -O OUT.txt -B BOTH.txt -T TIME.txt.
TIME.txt uses the multi-stream timing format of util-linux script:
one entry per line, <type> <delay> <value>, where H lines carry
headers, I/O lines carry the byte size of an input or output chunk
and S lines carry signals such as terminal resizes. The byte counts
address the stream files after their Script started on ... line and
before their Script done on ... trailer.
- pysnap.report.recording.load_report(path, limits=None)[source]¶
Load and parse a report file.
- Parameters:
path (str | Path) – Path to the report file.
limits (ReportLimits | None) – Resource limits, defaults to
ReportLimits.
- Returns:
Parsed recording.
- Raises:
ReportFormatError – When the file is not a usable report.
- Return type:
- pysnap.report.recording.parse_recording(archive, source_name)[source]¶
Parse the members of a report archive.
Inconsistencies are reported as diagnostics; only a report without a timing log or an output log is rejected.
- Parameters:
archive (ReportArchive) – Raw archive members.
source_name (str) – Base name of the report file.
- Returns:
Parsed recording.
- Raises:
ReportFormatError – When
TIME.txtorOUT.txtis missing or the timing log uses the unsupported single-stream format.- Return type:
pysnap.report.render module¶
Replay a recording through pyte to obtain text and typed commands.
The rendered transcript is what a reader would have seen: every line that
scrolled off the screen or was wiped by clear, followed by the final
screen. Command lines are recovered from the screen rather than from the raw
keystrokes, because the keystrokes contain line editing (Backspace, cursor
keys, history recall) that only the shell resolves.
A command is captured when Enter was pressed and the shell moves to the next
line: the output chunk after Enter is fed up to its first line feed, and the
logical input line is read from the screen at that moment. The input starts
where the cursor stood at the first keystroke of the line, so prompts of
other programs (vtysh, a remote shell) are handled the same way as the
prompt installed by report.
- pysnap.report.render.render_transcript(recording, *, max_lines=200000, max_screen_size=1000)[source]¶
Replay a recording and return its transcript and commands.
- Parameters:
recording (Recording) – Parsed recording.
max_lines (int) – Largest number of transcript lines to keep.
max_screen_size (int) – Largest accepted screen width and height.
- Returns:
Rendered transcript.
- Return type:
- pysnap.report.render.parse_resize(data)[source]¶
Parse a
SIGWINCH ROWS=<n> COLS=<n>signal entry.- Parameters:
data (bytes) – Signal description from an
Stiming entry.- Returns:
(rows, columns)orNonefor other or invalid signals.- Return type:
tuple[int, int] | None
- pysnap.report.render.parse_prompt(prompt)[source]¶
Parse the prompt installed by
report.- Parameters:
prompt (str) – Prompt text without trailing spaces.
- Returns:
Prompt parts or
Nonefor any other prompt.- Return type:
PromptInfo | None
pysnap.report.stats module¶
Report information and statistics for pysnap report check.
Every value comes directly from the report: the script headers, the
archive member times, CPU.txt, the timed keystrokes and the rendered
transcript.
- class pysnap.report.stats.CommandStats(index, text, pause, typing_time, keys, backspaces, from_history, interrupted, foreign_prompt)[source]¶
Bases:
objectRepresent the statistics of one entered command.
- Parameters:
index (int) – Zero-based command position.
text (str) – Entered command.
pause (float) – Seconds from the last output before the command to its first keystroke.
typing_time (float) – Seconds from the first keystroke to Enter.
keys (int) – Keystrokes from the first key through Enter.
backspaces (int) – Backspace keystrokes among
keys.from_history (bool) – Whether history keys (Up, Down, Ctrl-R) were used.
interrupted (bool) – Whether
Ctrl-Cwas typed while it ran.foreign_prompt (bool) – Whether it was typed at a prompt other than the one installed by
report.
- index: int¶
- text: str¶
- pause: float¶
- typing_time: float¶
- keys: int¶
- backspaces: int¶
- from_history: bool¶
- interrupted: bool¶
- foreign_prompt: bool¶
- class pysnap.report.stats.PasteEvent(time, text, command)[source]¶
Bases:
objectRepresent an input chunk that looks like pasted text.
- Parameters:
time (float) – Recording time of the chunk in seconds.
text (str) – Printable text of the chunk.
command (int | None) – Index of the command the chunk belongs to, if any.
- time: float¶
- text: str¶
- command: int | None¶
- class pysnap.report.stats.MemberTimeCheck(member, expected, difference, within_tolerance)[source]¶
Bases:
objectCompare the archive time of a member with the recording time.
- Parameters:
member (str) – Archive member name.
expected (str) –
"start"or"end"of the recording.difference (float) – Member time minus expected time in seconds.
within_tolerance (bool) – Whether
differenceis within the tolerance.
- member: str¶
- expected: str¶
- difference: float¶
- within_tolerance: bool¶
- class pysnap.report.stats.ReportStats(source_name, file_task, file_host, prompt_identities, start_time, end_time, duration, exit_code, tty, term, columns, lines, cpu_model, hypervisor, commands, unique_commands, pause_min, pause_median, pause_max, longest_pause_command, typing_speed, backspace_share, pastes, ip_addresses, mac_addresses, member_times, diagnostics, warnings)[source]¶
Bases:
objectRepresent the information block of
pysnap report check.- Parameters:
source_name (str)
file_task (int | None)
file_host (str | None)
prompt_identities (tuple[tuple[int, str], ...])
start_time (datetime | None)
end_time (datetime | None)
duration (float | None)
exit_code (int | None)
tty (str | None)
term (str | None)
columns (int | None)
lines (int | None)
cpu_model (str | None)
hypervisor (str | None)
commands (tuple[CommandStats, ...])
unique_commands (int)
pause_min (float | None)
pause_median (float | None)
pause_max (float | None)
longest_pause_command (str | None)
typing_speed (float | None)
backspace_share (float | None)
pastes (tuple[PasteEvent, ...])
ip_addresses (tuple[str, ...])
mac_addresses (tuple[str, ...])
member_times (tuple[MemberTimeCheck, ...])
diagnostics (tuple[ReportDiagnostic, ...])
warnings (tuple[str, ...])
- source_name: str¶
- file_task: int | None¶
- file_host: str | None¶
- prompt_identities: tuple[tuple[int, str], ...]¶
- start_time: datetime | None¶
- end_time: datetime | None¶
- duration: float | None¶
- exit_code: int | None¶
- tty: str | None¶
- term: str | None¶
- columns: int | None¶
- lines: int | None¶
- cpu_model: str | None¶
- hypervisor: str | None¶
- commands: tuple[CommandStats, ...]¶
- unique_commands: int¶
- pause_min: float | None¶
- pause_median: float | None¶
- pause_max: float | None¶
- longest_pause_command: str | None¶
- typing_speed: float | None¶
- pastes: tuple[PasteEvent, ...]¶
- ip_addresses: tuple[str, ...]¶
- mac_addresses: tuple[str, ...]¶
- member_times: tuple[MemberTimeCheck, ...]¶
- diagnostics: tuple[ReportDiagnostic, ...]¶
- warnings: tuple[str, ...]¶
- pysnap.report.stats.compute_stats(recording, transcript, *, paste_min_characters=5, mtime_tolerance=2.0)[source]¶
Compute the information block of a report.
- Parameters:
recording (Recording) – Parsed recording.
transcript (Transcript) – Rendered transcript.
paste_min_characters (int) – Printable characters in one input chunk from which the chunk counts as pasted.
mtime_tolerance (float) – Accepted difference in seconds between archive member times and the recording start or end.
- Returns:
Report statistics.
- Return type:
- pysnap.report.stats.split_keys(data)[source]¶
Split raw keyboard input into keystrokes.
Escape sequences such as cursor keys count as one keystroke; a chunk may hold several keystrokes because the serial console groups fast typing.
- Parameters:
data (bytes) – Raw input bytes.
- Returns:
One item per keystroke.
- Return type:
list[bytes]
- pysnap.report.stats.transcript_output_text(transcript)[source]¶
Return the transcript text without the lines of entered commands.
- Parameters:
transcript (Transcript) – Rendered report.
- Returns:
Output lines joined with newlines.
- Return type:
str
pysnap.report.viewer module¶
Interactive viewers for reports: the text pager and the show player.
Both viewers draw only styled text produced by PySnap itself (transcript runs
or the pyte screen of the player), so nothing from a report reaches the
reader’s terminal as a raw control sequence.
- class pysnap.report.viewer.PagerState(line_count, height=1, *, line_width=0, width=80)[source]¶
Bases:
objectTrack the scroll position of the
textpager.- Parameters:
line_count (int)
height (int)
line_width (int)
width (int)
- set_height(height)[source]¶
Adapt to a new window height.
- Parameters:
height (int) – Number of visible lines.
- Return type:
None
- set_width(width)[source]¶
Adapt to a new window width.
- Parameters:
width (int) – Number of visible columns.
- Return type:
None
- pysnap.report.viewer.clip_runs(runs, column, width)[source]¶
Cut a line to the visible columns, marking hidden text like
less -S.A
<in the first visible column means text continues to the left, a>in the last visible column means text continues to the right.
- pysnap.report.viewer.style_runs_to_fragments(runs)[source]¶
Convert styled runs into prompt_toolkit fragments.
- Parameters:
runs (Sequence[StyledRun]) – Styled runs of one line.
- Returns:
(style, text)fragments.- Return type:
list[tuple[str, str]]
- pysnap.report.viewer.cell_style_to_prompt_toolkit(style)[source]¶
Translate a
CellStyleinto a prompt_toolkit style string.- Parameters:
style (CellStyle) – Cell style.
- Returns:
prompt_toolkit style string.
- Return type:
str
- pysnap.report.viewer.run_pager(title, lines)[source]¶
Show styled lines in a full-screen scrollable pager.
Long lines are cut at the window edge like
less -S;<and>mark hidden text. Keys:Up/Downork/jscroll a line,PageUp/PageDown,bandSpacescroll a page,g/HomeandG/Endjump to the start or end,Left/Rightshift the view by half the window width,qorCtrl-Qquit; the mouse wheel scrolls on Linux.- Parameters:
title (str) – Name shown in the status line.
lines (Sequence[Sequence[StyledRun]]) – Styled runs of every line.
- Return type:
None
- pysnap.report.viewer.screen_viewport(rows, cursor_row, visible_rows)[source]¶
Choose the screen rows shown when the window is lower than the screen.
The viewport keeps the cursor row visible and prefers the bottom of the screen, where new output appears.
- Parameters:
rows (int) – Number of rows of the recorded screen.
cursor_row (int) – Row of the cursor.
visible_rows (int) – Number of rows that fit into the window.
- Returns:
Indices of the rows to show.
- Return type:
range
- pysnap.report.viewer.split_rows(fragments)[source]¶
Split screen fragments into rows at their
"\n"separators.- Parameters:
fragments (list[tuple[str, str]]) – Fragments of a whole screen.
- Returns:
Fragments per row.
- Return type:
list[list[tuple[str, str]]]
- pysnap.report.viewer.format_player_status(controller, title, clipped)[source]¶
Build the status line of the
showplayer.- Parameters:
controller (PlaybackController) – Playback state.
title (str) – Report name.
clipped (bool) – Whether the window is smaller than the recorded screen.
- Returns:
Status text.
- Return type:
str
- pysnap.report.viewer.run_player(controller, title)[source]¶
Play a recording in a full-screen window.
Keys follow the
showdecision:Spacepauses and resumes,+and-change the speed,n/pjump between commands,Home/Endjump to the start or end,qorCtrl-Qquit. While paused,Alt+Up/Alt+Downand the mouse wheel scroll the screen history.- Parameters:
controller (PlaybackController) – Playback state with its player.
title (str) – Report name shown in the status line.
- Return type:
None