fix(python_repl): create global REPL state lazily#516
Conversation
Instantiating ReplState at module import time created the persistence directory and loaded the persisted state file as a side effect of merely importing python_repl. Defer creation to first use via a get_repl_state() accessor so importing the module has no filesystem side effects. A module __getattr__ keeps python_repl.repl_state available for existing callers.
|
/strands-ts review |
Review —
|
Use a double-checked lock around the check-then-set in get_repl_state so concurrent first use cannot create two instances.
get_repl_state() loads the persisted state file on first use. Acquire it (and run the reset_state branch) only after the execution consent check, so the load does not happen before the user approves. Add a regression test asserting a declined run never triggers the load.
|
/strands-ts review |
Re-review (round 2) — HEAD
|
|
/strands-ts review |
Re-review (round 3) — HEAD
|
Description
python_replinstantiated its globalReplStateat module import time:ReplState.__init__creates the persistence directory and loads the persistedstate file (
repl_state.pkl). Doing this at import means simply importingpython_replhas filesystem side effects, before the tool is ever invoked. Thestate load also previously ran early inside
python_repl()— before the userwas prompted to confirm execution.
This change defers state creation to first use through a
get_repl_state()accessor, makes that accessor thread-safe, and moves the state acquisition to
after the execution consent prompt so an unapproved run never loads the state.
A module-level
__getattr__keepspython_repl.repl_stateworking for anyexisting callers that reference the attribute directly.
Changes
repl_state = ReplState()with a lazily-createdsingleton via
get_repl_state().__getattr__sopython_repl.repl_statestill resolves to the singleton.threading.Lock(double-checked) so concurrent first use cannot create two instances.
get_repl_state()(and thereset_statehandling) inpython_repl()until after the consent prompt, so declining consent does not trigger the
state-file load or persistence-directory creation. The code preview still
renders before the prompt without touching state.
get_repl_state().Testing
TestLazyStateasserts the module instantiates no state on import and thatget_repl_state()returns a reused singleton.test_state_not_loaded_before_consentasserts a declined run never callsget_repl_state().pytest tests/test_python_repl.pypasses (38 passed, 1 skipped).ruff format --checkandruff checkpass on the changed files.Notes
Switching persistence away from
dill(to a non-executable format) is aseparate, larger effort and is intentionally out of scope here; this PR removes
the import-time and pre-consent load paths.