Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions src/toad/preferences.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,26 +474,33 @@ def practice_opening(cls, files: FilesRequest, directory: Path) -> None:
if path.exists():
FieldCodec.decode(ToadSettings, FieldCodec.parse(path.read_text("utf-8")))

def saved_key(self, path: DottedFieldPath) -> str:
owner = DeclaredField(self.state, path).owner_type
return next(key for declaration, key in FieldCodec.record_fields(owner)
if declaration.name == path.field_name)
def saved_keys(self, path: str) -> list[tuple[DottedFieldPath, str]]:
"""Each setting or group directly below `path`, with its key in the file.

They share one owner record, so its class is resolved once for the level: resolving
it again per field cost every launch 0.04 s of value resolution for the keys alone.
"""
below = self.state.direct_parameter_paths(path)
if not below:
return []
owner = DeclaredField(self.state, below[0]).owner_type
keys = {declaration.name: key for declaration, key in FieldCodec.record_fields(owner)}
return [(field, keys[field.field_name]) for field in below]

def keys_held(self, path: str, raw: object) -> list[DottedFieldPath]:
"""The settings and groups a document read from the file has a key for."""
if not isinstance(raw, dict):
return []
held = []
for below in self.state.direct_parameter_paths(path):
if (key := self.saved_key(below)) in raw:
for below, key in self.saved_keys(path):
if key in raw:
held += [below, *self.keys_held(below.value, raw[key])]
return held

def kept(self, path: str) -> dict[str, JsonValue]:
"""The part of the whole encoded tree that belongs in the file."""
document: dict[str, JsonValue] = {}
for below in self.state.direct_parameter_paths(path):
key = self.saved_key(below)
for below, key in self.saved_keys(path):
if self.state.has_parameter_descendants(below.value):
part = self.kept(below.value)
if part or below in self.user_set:
Expand Down
4 changes: 4 additions & 0 deletions tests/support/roots.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,5 +108,9 @@ def environment(self, inherited: dict[str, str]) -> dict[str, str]:
"TERM": "xterm-256color",
"COLORTERM": "truecolor",
"FORCE_COLOR": "1",
# A test app publishes every 1 ms instead of 1/144 s. Each input still gets its
# own frame; a wheel walk that waited one 7 ms tick per event no longer does
# (the saved-history scroll test went from 36.5 s to 26.5 s).
"TEXTUAL_FPS": "1000",
})
return env
7 changes: 7 additions & 0 deletions tests/support/template_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@

from dataclasses import dataclass
import gc
import importlib
import json
import pkgutil
import os
import socket
import sys
Expand All @@ -17,6 +19,7 @@
import traceback

import pytest
import toad

import support.workspace # noqa: F401 (the app, its fixtures and the comms package)
from agent_comms.storage.locked_store import RootFile
Expand Down Expand Up @@ -85,6 +88,10 @@ def serve() -> None:
"""Warm what every app test pays for, then fork each test and its helper spawner."""
# Every agent_comms module once, so a forked helper imports and walks nothing.
RootFile.load_declarations()
# Every toad module once: the app imports its screens and widgets as it mounts them
# (51 modules, 0.21 s cold), and every test paid for the ones it reached.
for module in pkgutil.walk_packages(toad.__path__, "toad."):
importlib.import_module(module.name)
# The file-preview terminal probe: 0.15 s of every app's start. Neither this process nor
# a forked test has a terminal, so both detect the same answer.
FileKind.prepare_terminal()
Expand Down
Loading