Session sandbox
axm_config.sandbox moves the six AXM state paths to a temporary root. It is
the primitive a test session uses so it never writes into the real ~/axm or
~/.axm state. The pytest plugin activates it for every
pytest session automatically.
from pathlib import Path
from axm_config.sandbox import activate_sandbox, deactivate_sandbox
sandbox = activate_sandbox(Path("/tmp/axm-session"))
try:
... # sessions_root(), tickets_db(), ... now resolve under /tmp/axm-session
finally:
deactivate_sandbox(sandbox)
API
| Symbol | Contract |
|---|---|
activate_sandbox(root, environ=None) -> SessionSandbox |
Checks the launch variables, then publishes the sandbox in environ (default os.environ) and removes AXM_PROFILE. It creates no directory: the caller creates and deletes root. |
deactivate_sandbox(sandbox, environ=None) -> None |
Restores the AXM_SANDBOX_* and AXM_PROFILE values recorded at activation. Nested sandboxes therefore unwind correctly. |
active_sandbox(environ=None) -> SessionSandbox \| None |
Rebuilds the sandbox from the environment. Returns None when AXM_SANDBOX_ROOT is absent. |
SessionSandbox |
Frozen dataclass with fields root, launch_home, launch_profile and launch_axm_home. |
SandboxEscapeError |
A ConfigError, raised at activation and naming the offending variable. |
Environment keys
The sandbox is stored only in the environment, never in module state. A child
process (a CLI, python -c, a warden) therefore rebuilds the same sandbox and
the same launch profile.
| Key | Holds |
|---|---|
AXM_SANDBOX_ROOT |
The sandbox root. Its presence is what makes a sandbox active. |
AXM_SANDBOX_LAUNCH_HOME |
HOME at activation. |
AXM_SANDBOX_LAUNCH_PROFILE |
AXM_PROFILE at activation (empty when unset). current_profile() returns it while AXM_PROFILE is absent. |
AXM_SANDBOX_LAUNCH_AXM_HOME |
AXM_HOME at activation (empty when unset). |
Sandboxed layout
| Helper | Namespace/key | Path under the root |
|---|---|---|
sessions_root() |
paths.sessions_root |
sessions |
quality_dir() |
paths.quality_dir |
quality |
protocols_dir() |
paths.protocols_dir |
protocols |
warden_socket() |
paths.warden_socket |
w.sock (short, to fit the 104-byte AF_UNIX limit) |
tickets_db() |
tickets.db_path |
tickets/tickets.db |
warden_log_path() |
warden.log_path |
warden.log |
Resolution precedence
While a sandbox is active, these six keys resolve in the following order. The first rule that applies wins:
AXM_PROFILEis set, orHOMEdiffers from the launchHOME. All six keys fall back to ordinary runtime-settings resolution. This includes theconfig.tomlunder thatHOMEand profile containment.- The key's own variable is set (for example
AXM_TICKETS_DB_PATH). Its value is used for that key only, resolved throughresolve_safe. No profile containment applies. AXM_HOMEdiffers from the launch value and itsconfig.tomlsets the key. That value is used.- Otherwise the sandbox path from the table above is returned.
For these keys, neither ~/.axm/config.toml nor the config.toml in the
launch AXM_HOME is ever read. Because AXM_PROFILE is removed at activation,
a test that sets it again is detected, even to the launch value.
Escape check
activate_sandbox resolves each launch variable that is set among the six
(AXM_PATHS_SESSIONS_ROOT, AXM_PATHS_QUALITY_DIR, AXM_PATHS_PROTOCOLS_DIR,
AXM_PATHS_WARDEN_SOCKET, AXM_TICKETS_DB_PATH, AXM_WARDEN_LOG_PATH). If
one resolves to <HOME>/axm or <HOME>/.axm, or below either, it raises
SandboxEscapeError. Symlinks are resolved first, so on macOS /var becomes
/private/var before the comparison.
pytest plugin
axm_config.pytest_plugin is registered under the pytest11 entry point as
axm_config. pytest therefore loads it in every session run in an environment
where axm-config is installed, including the suites of packages that only
depend on it.
| Hook | Contract |
|---|---|
pytest_configure (tryfirst) |
Creates a fresh root with tempfile.mkdtemp(prefix="axm-") under the system temp directory, then calls activate_sandbox(root). Each session gets its own root, even when a sandbox is already active: an inner pytester session or an xdist worker activates a new root over the inherited one. |
pytest_configure on SandboxEscapeError |
Removes the root and raises pytest.UsageError with the error message. The session exits non-zero before collection and the message names the offending variable. |
pytest_unconfigure |
Calls deactivate_sandbox, which restores the previous AXM_SANDBOX_* and AXM_PROFILE values, then deletes the root. |
The plugin never changes HOME or Path.home(). It leaves the
AXM_SANDBOX_* keys in os.environ for the whole session, so subprocesses
spawned by tests resolve under the same root. It imports axm_config modules
inside its hooks only, because pytest loads plugins before pytest-cov starts
measuring.
Bypass the plugin with -p no:axm_config or
PYTEST_DISABLE_PLUGIN_AUTOLOAD=1. See
Isolate test sessions.
Without AXM_SANDBOX_ROOT, every resolver behaves exactly as described in
runtime settings.