visualdynamics.core.run_log_files¶
run_log_files
¶
Controller files: what a controller writes while a test runs, proposed for the run log, and the log's files in a report's data index.
A controller writes its records (a run's stream, its spectra, its
settings) into an output folder. ControllerWatch watches one or more
such folders and proposes each file that appears: it never attaches
anything to the log itself. The log keeper accepts a proposal
(accept), which writes an entry naming the file by SHA-256, with its
format and what it holds, in the run that was current when the file
appeared (place_at). A proposal not accepted leaves no trace in the
log.
Only settled files are hashed. A file is settled when its size and
modification time have stayed the same over settle_s seconds, seen on
at least two looks at the folder; a file still being written would hash
to a value its finished bytes do not have. A file that changes while it
is hashed is not proposed and waits to settle again, and one removed
before it settles is forgotten. Hashing is throttled to rate bytes a
second, because the file sits on the controller's own disk or share and
the next run is writing there too, and a stream can be tens of
gigabytes. At a test's close everything seen and not yet hashed is
hashed at once (ControllerWatch.archive).
Watched and polled. The folders are listed every poll_s seconds,
whether or not anything says they changed, because a network share
often sends no notice of a change; a front end that is told of a change
(ControllerWatch.poke) only makes the next look come sooner. All of
it, looking and hashing, runs on a thread of its own
(ControllerWatch.start), which never holds up the program that writes
the log: proposals are handed to a callback or to a queue that is
safe to read from any thread.
Format and holdings. Each file is described as a data index
describes a file (data_index.IndexEntry): its name, where it is, its
size, its SHA-256, its format and what it holds, read through Visual
Dynamics' own importers. A Rattlesnake run is read from its dimensions
alone (rattlesnake.stream_summary), so a run of any size gives its
channels, its sample rate and its stream's length without reading the
stream; another format an importer recognizes is read whole when it is
no larger than HOLDINGS_LIMIT; and the run, axis and level its name
gives are added (runs.read_name). A format nothing recognizes is
recorded by name, size and hash with the format 'unknown': it is never
refused.
The stream and the counter. A Rattlesnake stream's length is
compared with the time the run's entries counted (check_stream). The
stream holds the ramps up and down, and any lower levels run inside the
same run, so it is longer than the counter by however long those take;
the comparison is within a stated tolerance, and a difference beyond it
is a finding for the log keeper, never a refusal.
One data index. The log's files go into the project's data index
through the index's own entry (DataIndex.add, the one the import and
Project.index_file use), with the checksum the log recorded, so
nothing is hashed twice and a report lists them beside every other file
it rests on (index_run_log). The log's own file goes in with each
test's head hash (RunLog.head) in what it holds, which anchors the
chain inside the file to a record outside it.
Classes:
| Name | Description |
|---|---|
ControllerFilesError |
A folder, a setting or a request the controller-file watch cannot |
Place |
Where in the log an entry goes: a test and a run by number, an |
Stream |
A Rattlesnake time stream's size, from its dimensions |
Description |
What a file is: its format ('unknown' when nothing recognizes |
Proposal |
A controller file proposed for the log. |
WatchProblem |
Something the watch could not do: the folder or file and why. |
StreamCheck |
A stream's length against the run's counter. |
IndexedLog |
What |
ControllerWatch |
A watch over one or more controller output folders. |
Functions:
| Name | Description |
|---|---|
describe |
What the file at |
place_at |
The place in |
counter_seconds |
A counter as the log keeper typed it, in seconds: '1:02:03' (hours, |
counted_seconds |
(seconds, '') the counter ran in run |
check_stream |
A Rattlesnake stream's length against the time its run counted. |
accept |
Write a proposed file into the log: an entry naming it by SHA-256, |
index_run_log |
Put a run log's files, and the log with each test's head hash, into |
Classes¶
ControllerFilesError
¶
Bases: ValueError
A folder, a setting or a request the controller-file watch cannot take; the message names the cause.
Place
¶
Bases: NamedTuple
Where in the log an entry goes: a test and a run by number, an axis by name, any of them None.
Stream
dataclass
¶
A Rattlesnake time stream's size, from its dimensions
(rattlesnake.stream_summary): its key, sample rate in hertz,
samples, length in seconds and channels.
Description
dataclass
¶
Description(format: str, holds: str, channels: tuple[str, ...] = (), run: dict[str, Any] = dict(), stream: Stream | None = None, notes: tuple[str, ...] = ())
What a file is: its format ('unknown' when nothing recognizes
it), what it holds in words, its channels, the run identity its name
gives (runs.read_name), a Rattlesnake time stream when it has one,
and why anything was not read.
Proposal
dataclass
¶
Proposal(file: IndexEntry, folder: str, seen: datetime, channels: tuple[str, ...] = (), run: dict[str, Any] = dict(), stream: Stream | None = None, notes: tuple[str, ...] = (), archived: bool = False)
A controller file proposed for the log.
file is the file as a data index records it (IndexEntry, its
source the run log), hashed once it settled. folder is the watched
folder it appeared in and seen when it was first seen there, with
its zone; the run current at seen is where it is proposed to go
(place_at). archived marks a file hashed at a test's close
rather than once it had settled. The rest is its Description.
Methods:
| Name | Description |
|---|---|
file_ref |
The file as a log entry names it: name, SHA-256, size, format, |
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The file's name. |
path |
str
|
Where the file was when it was hashed. |
sha256 |
str
|
The file's SHA-256, as 64 lower-case hex digits. |
WatchProblem
dataclass
¶
Something the watch could not do: the folder or file and why. The watch goes on; the problem is reported once until it clears.
StreamCheck
dataclass
¶
StreamCheck(ok: bool | None, message: str, tolerance_s: float, stream_s: float | None = None, sample_rate: float | None = None, counted_s: float | None = None)
A stream's length against the run's counter.
ok is True within the tolerance, False beyond it, and None when
nothing was compared (no time stream, no run, no counter), which is
never a pass. stream_s and sample_rate are the stream's,
counted_s the counter's, tolerance_s the difference allowed, and
message says what was found in words.
IndexedLog
dataclass
¶
IndexedLog(log: IndexEntry, files: tuple[IndexEntry, ...], missing: tuple[tuple[FileRef, str], ...])
What index_run_log put into a data index: the log's own entry,
each file's entry, and each file not found, with why.
ControllerWatch
¶
ControllerWatch(folders: str | PathLike | Sequence[str | PathLike], *, settle_s: float = SETTLE_S, poll_s: float = POLL_S, rate: float | None = RATE, limit: int = HOLDINGS_LIMIT, existing: bool = False, clock: Callable[[], float] = monotonic, now: Callable[[], datetime] = _local_now)
A watch over one or more controller output folders.
Each look (scan) lists the folders, notes each file that is new or
has changed since the watch began, and hashes each one that has
settled, returning a Proposal for it; what could not be done comes
back as a WatchProblem. start runs the looks on a thread of the
watch's own every poll_s seconds and hands what they find to
callbacks, or to a queue read with take; stop ends it.
The files already in a folder when the watch begins are the records
of earlier runs and are not proposed, unless existing is set; one
of them that changes afterwards is.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
folders
|
str, os.PathLike, or a sequence of them
|
The folders to watch. Each must exist when the watch begins. |
required |
settle_s
|
float
|
Seconds a file's size and modification time must stay the same before it is hashed. |
SETTLE_S
|
poll_s
|
float
|
Seconds between looks when the watch runs on its thread. |
POLL_S
|
rate
|
float or None
|
Bytes a second hashing reads at most; None for no limit. |
RATE
|
limit
|
int
|
The largest file read whole for its holdings ( |
HOLDINGS_LIMIT
|
existing
|
bool
|
Propose the files already in the folders as well. |
False
|
clock
|
callable
|
Seconds on a monotonic clock, which settling is timed by. |
monotonic
|
now
|
callable
|
The time with its zone, which a file is recorded as first seen at. |
_local_now
|
Raises:
| Type | Description |
|---|---|
ControllerFilesError
|
When no folder is named, a folder does not exist or is a file, or a setting is not a number it can be. |
Methods:
| Name | Description |
|---|---|
scan |
Look at the folders once, and hash and propose each file that |
archive |
Look at the folders once more and hash every file seen and not |
start |
Run the looks on a thread of the watch's own, every |
poke |
Look now rather than at the next poll: what a notice that a |
ask_archive |
Have the thread hash every file not yet proposed at its next |
stop |
End the thread: a hash in progress is abandoned at its next |
take |
Everything the thread has found since the last call, when it |
Attributes:
| Name | Type | Description |
|---|---|---|
pending |
tuple[str, ...]
|
The files seen and not yet proposed, by path. |
running |
bool
|
Whether the watch's thread is running. |
Source code in src/visualdynamics/core/run_log_files.py
Attributes¶
Methods:¶
scan
¶
scan() -> list[Proposal | WatchProblem]
Look at the folders once, and hash and propose each file that has settled.
Returns:
| Type | Description |
|---|---|
list of Proposal and WatchProblem
|
What this look found, in the order found. |
Source code in src/visualdynamics/core/run_log_files.py
archive
¶
archive() -> list[Proposal | WatchProblem]
Look at the folders once more and hash every file seen and not yet proposed, settled or not: what a test's close asks, so the files of its last run are named before it is archived. A file that changes while it is hashed is reported, not proposed.
Returns:
| Type | Description |
|---|---|
list of Proposal and WatchProblem
|
What was found, in the order found. |
Source code in src/visualdynamics/core/run_log_files.py
start
¶
start(on_proposal: Callable[[Proposal], Any] | None = None, on_problem: Callable[[WatchProblem], Any] | None = None) -> None
Run the looks on a thread of the watch's own, every poll_s
seconds, until stop.
The callbacks are called on that thread; a front end hands what
they get to its own thread (a queued signal). Without them, what
is found goes to a queue that take reads from any thread.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
on_proposal
|
callable
|
Called with each |
None
|
on_problem
|
callable
|
Called with each |
None
|
Returns:
| Type | Description |
|---|---|
None
|
|
Raises:
| Type | Description |
|---|---|
ControllerFilesError
|
When the watch is running already. |
Source code in src/visualdynamics/core/run_log_files.py
poke
¶
Look now rather than at the next poll: what a notice that a folder changed asks. Safe from any thread.
ask_archive
¶
Have the thread hash every file not yet proposed at its next
look, which comes at once (archive). Safe from any thread.
stop
¶
End the thread: a hash in progress is abandoned at its next chunk. Safe to call when it is not running.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timeout
|
float or None
|
Seconds to wait for the thread to end; None to wait as long as it takes. |
10.0
|
Returns:
| Type | Description |
|---|---|
bool
|
Whether the thread has ended. |
Source code in src/visualdynamics/core/run_log_files.py
take
¶
take() -> list[Proposal | WatchProblem]
Everything the thread has found since the last call, when it runs without callbacks. Safe from any thread.
Returns:
| Type | Description |
|---|---|
list of Proposal and WatchProblem
|
In the order found. |
Source code in src/visualdynamics/core/run_log_files.py
Functions:¶
describe
¶
describe(path: str | PathLike, *, limit: int = HOLDINGS_LIMIT) -> Description
What the file at path is and holds, read through Visual
Dynamics' importers.
A Rattlesnake run gives its kind, channels, sample rate and time
stream from its dimensions, whatever its size. Another format an
importer recognizes is read whole when it is at most limit bytes,
and its holdings are the data index's (project.holds). A file
nothing recognizes has the format 'unknown'. The run, axis, level
and start its name gives are added. Nothing here raises for a file
that cannot be read: what was not read, and why, is in notes and
in the holdings.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str or PathLike
|
The file. |
required |
limit
|
int
|
The largest file read whole for its holdings, in bytes. |
HOLDINGS_LIMIT
|
Returns:
| Type | Description |
|---|---|
Description
|
Its format, holdings, channels, run identity and stream. |
Source code in src/visualdynamics/core/run_log_files.py
place_at
¶
The place in log that was current at when: the run started
last at or before it (its test, axis and run), or the test opened
last at or before it when that came after, or no place.
A file that appears after a run stops still belongs to that run, since a controller writes its records as the run ends; one that appears after the next test opens belongs to that test.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
log
|
RunLog
|
The log, read in its corrected view. |
required |
when
|
datetime
|
The time, with its zone. |
required |
Returns:
| Type | Description |
|---|---|
Place
|
The test, axis and run, any of them None. |
Source code in src/visualdynamics/core/run_log_files.py
counter_seconds
¶
A counter as the log keeper typed it, in seconds: '1:02:03' (hours, minutes, seconds), '12:30' (minutes, seconds), '750', '750 s', '12.5 min' or '1 h'.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The counter. |
required |
Returns:
| Type | Description |
|---|---|
float
|
Seconds. |
Raises:
| Type | Description |
|---|---|
ControllerFilesError
|
When the text is none of those forms, or a clock's minutes or seconds are 60 or more; the message names the text. |
Source code in src/visualdynamics/core/run_log_files.py
counted_seconds
¶
counted_seconds(log: RunLog, test: int, run: int) -> tuple[float | None, str]
(seconds, '') the counter ran in run run of test test, from the
run's entries: the last counter recorded at a stop or abort less the
counter at its start (0 when its start has none). (None, why) when it
cannot be told.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
log
|
RunLog
|
The log, read in its corrected view. |
required |
test
|
int
|
The test and run, by number. |
required |
run
|
int
|
The test and run, by number. |
required |
Returns:
| Type | Description |
|---|---|
tuple of (float or None, str)
|
The seconds counted, or None and the reason. |
Source code in src/visualdynamics/core/run_log_files.py
check_stream
¶
check_stream(log: RunLog, proposal: Proposal, *, place: Place | None = None, tolerance_s: float = STREAM_TOLERANCE_S) -> StreamCheck
A Rattlesnake stream's length against the time its run counted.
The stream runs from the controller's start to its stop, ramps
included, so it is expected to be somewhat longer than the counter.
A difference of more than tolerance_s either way is a finding for
the log keeper: shorter, the stream may not hold the whole run;
longer, more ran than the counter and its ramps account for. Nothing
is refused, and when nothing can be compared the check says so and
is not a pass.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
log
|
RunLog
|
The log the run's counter is read from ( |
required |
proposal
|
Proposal
|
The file, with its stream ( |
required |
place
|
Place
|
The run it belongs to; the run current when it was first seen
( |
None
|
tolerance_s
|
float
|
Seconds the stream may differ from the counter. |
STREAM_TOLERANCE_S
|
Returns:
| Type | Description |
|---|---|
StreamCheck
|
What was compared and found. |
Raises:
| Type | Description |
|---|---|
ControllerFilesError
|
When the tolerance is not a number of seconds, 0 or more. |
Source code in src/visualdynamics/core/run_log_files.py
accept
¶
accept(log: RunLog, proposal: Proposal, *, author: str, place: Place | None = None, kind: str = 'note', text: str = '') -> Entry
Write a proposed file into the log: an entry naming it by SHA-256, with its size, format, holdings and path, at the time it was first seen, in the run current then.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
log
|
RunLog
|
The log, open for writing. |
required |
proposal
|
Proposal
|
The file. |
required |
author
|
str
|
The log keeper's initials. |
required |
place
|
Place
|
Where it goes; the run current when it was first seen
( |
None
|
kind
|
str
|
The kind of entry, a note unless given. |
'note'
|
text
|
str
|
Free text. |
''
|
Returns:
| Type | Description |
|---|---|
Entry
|
The entry written. |
Raises:
| Type | Description |
|---|---|
ControllerFilesError
|
When the log already names this file (the same name and SHA-256); the message names the entry. |
RunLogError
|
For anything the log refuses ( |
Source code in src/visualdynamics/core/run_log_files.py
index_run_log
¶
index_run_log(index: DataIndex, log: RunLog, *, tests: Iterable[int | None] | None = None, search: Sequence[str | PathLike] = ()) -> IndexedLog
Put a run log's files, and the log with each test's head hash, into
a data index (Project.data_index), so a report lists them.
Each file the log names is entered through the index's own entry
(DataIndex.add) with the size and SHA-256 the log recorded, so it
is not hashed again; the index's check before a report is issued
(DataIndex.verify) hashes it and says whether it still matches. A
file is looked for where it was when it was hashed, then by its name
in each folder of search, then beside the log; a file found
nowhere is listed as missing, since an index entry names a file
that is there. The log's own file is entered as a run log holding
each test's head (RunLog.head), replacing the run log's earlier
entry for the same file, since the log grows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
DataIndex
|
The index, the project's data index in a project. |
required |
log
|
RunLog
|
The log. |
required |
tests
|
iterable of int or None
|
The tests whose files and heads go in (None for the entries between tests); every test, and the entries between tests, unless given. |
None
|
search
|
sequence of str or os.PathLike
|
Folders a file is looked for in by its name. |
()
|
Returns:
| Type | Description |
|---|---|
IndexedLog
|
The log's entry, each file's entry, and each file not found. |
Raises:
| Type | Description |
|---|---|
ControllerFilesError
|
When a test named is not in the log. |
Source code in src/visualdynamics/core/run_log_files.py
1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 | |