visualdynamics.core.data_index¶
data_index
¶
The data index: every file a project rests on, by name, size and SHA-256.
A report names the evidence it was written from. The index is that
naming: for each file, its name, where it was, what it holds, its
format, its size in bytes and its SHA-256 checksum, the fingerprint
that changes if a single byte of the file changes, so that a copy can
be shown identical to the original years later. A project keeps one
index (Project.data_index), whatever adds to it: an import records
the file it read, the author attaches a document (a settings list, a
log, a scanned procedure), and any other source of files adds its rows
the same way (DataIndex.add).
Each entry is either referenced (named precisely, kept in the archive) or attached (small enough to travel with the report). A small text file that is attached keeps its contents, so the report can print it.
DataIndex.verify hashes every file again where it can be found and
marks each entry as matching, changed or not found; that is the check
a report is given before it is issued. Hashing reads files in chunks
and reports its progress, so a front end can run it off its event loop
and show a bar.
Classes:
| Name | Description |
|---|---|
IndexEntry |
One file of the index. |
DataIndex |
Every file a project rests on, in the order each was indexed. |
Functions:
| Name | Description |
|---|---|
now |
The time, in UTC, to the second, as an index records it. |
digest |
(size in bytes, SHA-256 as 64 lower-case hex digits) of a file. |
Classes¶
IndexEntry
dataclass
¶
IndexEntry(name: str, path: str, size: int, sha256: str, holds: str, format: str = '', role: str = 'referenced', source: str = 'import', objects: tuple[str, ...] = (), indexed: str = '', text: str = '', status: str = '', checked: str = '', found: str = '')
One file of the index.
name is the file's name as the report prints it, and path where
it was when it was indexed. size is in bytes and sha256 is the
checksum, as 64 lower-case hex digits. format names the file's
format and holds says what is in it: the records and channels of
a data file, or what a document is ("settings list"). role is
'referenced' or 'attached' (ROLES). source says what added the
entry: 'import' for a file read into the project, 'author' for one
added by hand, or the name of another source. objects are the
project objects read from the file. indexed is when the file was
hashed, in UTC. text is the contents of a small attached text
file, kept so the report can print it, and otherwise ''.
status is what the last check found (MATCHES, CHANGED,
NOT_FOUND, or '' before any check), checked when, and found
where the file was found when that was not path.
Methods:
| Name | Description |
|---|---|
record |
The entry as plain values, for the project file; an empty |
from_record |
An entry read back from |
Methods:¶
record
¶
The entry as plain values, for the project file; an empty field is left out.
Source code in src/visualdynamics/core/data_index.py
from_record
classmethod
¶
from_record(record: Mapping[str, Any]) -> IndexEntry
An entry read back from record. A key this version does
not know is ignored, so a file written by a later version still
opens; a malformed entry raises ValueError, and one that is
not a mapping TypeError.
Source code in src/visualdynamics/core/data_index.py
DataIndex
dataclass
¶
DataIndex(entries: list[IndexEntry] = list(), project_file: tuple[str, int, str, str] | None = None)
Every file a project rests on, in the order each was indexed.
Iterating gives the entries (IndexEntry). project_file is
(name, size, SHA-256, when) of the project's own file at the last
check, or None: kept in memory only, since a file cannot carry its
own checksum.
Methods:
| Name | Description |
|---|---|
add |
Index the file at |
remove |
Take the entry for the file named |
rename_object |
Follow a project object renamed from |
forget_objects |
Follow project objects removed: each entry loses |
adopt |
Take in the entries of another project's index, its objects |
verify |
Hash every indexed file again and record what was found. |
records |
Every entry as plain values, for the project file. |
from_records |
An index read back from |
Methods:¶
add
¶
add(path: str | PathLike, holds: str, *, format: str = '', role: str = 'referenced', source: str = 'import', objects: Iterable[str] = (), checksum: tuple[int, str] | None = None, progress: Callable[[int, int], None] | None = None) -> IndexEntry
Index the file at path and return its entry.
The file is hashed (digest, with progress) unless checksum
gives (size, SHA-256) already computed, for example on a worker
thread. An attached text file of at most TEXT_LIMIT bytes
keeps its contents. The same file with the same checksum
indexed again is one entry, which gains the new objects; the
same path with different contents is a second entry, since it
is a different file.
Refused, before anything changes: holds empty or not a
string, an unknown role, an empty source, no file at
path, a folder, and an attached text file whose contents
changed between the checksum and the read.
Raises:
| Type | Description |
|---|---|
ValueError
|
For a refusal; the message names the file and the reason. |
(FileNotFoundError, IsADirectoryError)
|
When |
TypeError
|
When |
Source code in src/visualdynamics/core/data_index.py
remove
¶
remove(name: str) -> IndexEntry
Take the entry for the file named name (its name, or its
path) out of the index, and return it. Raises ValueError when
no entry, or more than one, has that name.
Source code in src/visualdynamics/core/data_index.py
rename_object
¶
Follow a project object renamed from old to new.
Source code in src/visualdynamics/core/data_index.py
forget_objects
¶
forget_objects(names: Iterable[str], keep: Callable[[IndexEntry], bool] | None = None) -> list[IndexEntry]
Follow project objects removed: each entry loses names, and
an imported entry left with no object is dropped unless
keep(entry) says something still rests on it. Returns the
entries dropped.
Source code in src/visualdynamics/core/data_index.py
adopt
¶
adopt(other: DataIndex | None, mapping: Mapping[str, str] | None = None) -> None
Take in the entries of another project's index, its objects
renamed through mapping ({old: new}) the way the objects
were; an entry already here is merged, not repeated.
Source code in src/visualdynamics/core/data_index.py
verify
¶
verify(search: Sequence[str | PathLike] = (), progress: Callable[[int, int], None] | None = None) -> tuple[IndexEntry, ...]
Hash every indexed file again and record what was found.
Each file is looked for where it was indexed, then by its name
in each folder of search in turn (an archive copy, the folder
the project now sits in). Found with the same size and
checksum, it MATCHES; found otherwise, it has CHANGED; not
found anywhere, it is NOT_FOUND. progress is called as
(bytes read, bytes expected) across the whole index. The
entries are replaced only once every file has been read, so a
check stopped part way (a cancel raised from progress) leaves
the index as it was.
Returns:
| Type | Description |
|---|---|
tuple of IndexEntry
|
The entries afterwards, in order. |
Source code in src/visualdynamics/core/data_index.py
records
¶
Functions:¶
now
¶
digest
¶
(size in bytes, SHA-256 as 64 lower-case hex digits) of a file.
The file is read in chunks of CHUNK bytes, so a file of any size
is hashed in constant memory; progress, when given, is called as
progress(bytes read, bytes expected) before the first chunk and
after each one. The size returned is the bytes actually read, so it
always describes the bytes that were hashed.
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
When there is no file at |
IsADirectoryError
|
When |
TypeError
|
When |