Skip to content

Commit 17f6a70

Browse files
joshuafontanyclaude
andcommitted
feat(config): honor MEMPALACE_CONFIG_DIR, mirroring the palace-path override
`MempalaceConfig.__init__` took `config_dir` as a constructor argument documented "useful for testing", with no environment or CLI lever — so the config directory hardwires to `~/.mempalace` for every spawned process, while `palace_path` already honors `MEMPALACE_PALACE_PATH`. A host that embeds mempalace as a sidecar spawns it as a process: it can pass env and argv, and nothing else. The config file supplies `backend`, `collection_name`, `embedding_model`, `write_routing`, `palace_path`, the milvus/qdrant/pgvector sets and ~20 more, so an embedding host inherits every one of them from a user-level file it does not own. Countering key-by-key with env vars does not scale to that surface; one symmetric lever closes it. Resolution order matches `palace_path`: explicit argument > environment > default. `~` expands, a blank value reads as unset, and an unset variable leaves `~/.mempalace` exactly where it stood — the change adds a lever and moves no existing behaviour. Six tests cover the promised order: env redirects both config-dir-derived files; a config.json under the env-named directory actually feeds resolved values; an explicit argument outranks the env; unset and blank both keep the home default; `~` expands. Revert-verified: with the change reverted, 3 of the 6 fail — the guard bites rather than passing vacuously. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4NJGuNb4oxv4C8RJH8TJx
1 parent 72bbb0a commit 17f6a70

2 files changed

Lines changed: 81 additions & 5 deletions

File tree

mempalace/config.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -354,15 +354,28 @@ def __init__(self, config_dir=None, palace_path=None):
354354
"""Initialize config.
355355
356356
Args:
357-
config_dir: Override config directory (useful for testing).
358-
Defaults to ~/.mempalace.
357+
config_dir: Explicit config directory. Takes precedence over the
358+
``MEMPALACE_CONFIG_DIR`` environment variable, which in
359+
turn takes precedence over the ~/.mempalace default.
359360
palace_path: Explicit palace data directory. This is primarily
360361
used by CLI operations that received ``--palace``;
361362
it takes precedence over environment and file config.
362363
"""
363-
self._config_dir = (
364-
Path(config_dir) if config_dir else Path(os.path.expanduser("~/.mempalace"))
365-
)
364+
# Explicit arg > MEMPALACE_CONFIG_DIR > ~/.mempalace, mirroring how
365+
# ``palace_path`` resolves. A host that embeds mempalace as a library
366+
# needs to point a spawned process at its own config root: without an
367+
# env lever the directory hardwires to ~/.mempalace, so every spawned
368+
# process reads a user-level config it does not own. The config file
369+
# supplies backend, collection_name, embedding_model, write_routing and
370+
# ~20 more, so an embedding host inherits all of them by default.
371+
env_config_dir = os.environ.get("MEMPALACE_CONFIG_DIR")
372+
if config_dir:
373+
resolved_config_dir = str(config_dir)
374+
elif env_config_dir and env_config_dir.strip():
375+
resolved_config_dir = env_config_dir.strip()
376+
else:
377+
resolved_config_dir = "~/.mempalace"
378+
self._config_dir = Path(os.path.expanduser(resolved_config_dir))
366379
self._config_file = self._config_dir / "config.json"
367380
self._people_map_file = self._config_dir / "people_map.json"
368381
self._palace_path_override = (

tests/test_config_dir_env.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""MEMPALACE_CONFIG_DIR — the config-directory lever, mirroring MEMPALACE_PALACE_PATH.
2+
3+
A host that embeds mempalace as a sidecar spawns it as a process, so it can pass env and argv and
4+
nothing else. Without an env lever the config directory hardwires to ~/.mempalace and every spawned
5+
process reads a user-level config it does not own — carrying backend, collection_name,
6+
embedding_model, write_routing and ~20 more keys across a boundary the embedding was meant to hold.
7+
8+
These cover the resolution order the docstring promises: explicit arg > env > default.
9+
"""
10+
11+
import json
12+
import os
13+
14+
from mempalace.config import MempalaceConfig
15+
16+
17+
def test_env_var_sets_the_config_dir(tmp_path, monkeypatch):
18+
"""MEMPALACE_CONFIG_DIR redirects both config-dir-derived files."""
19+
monkeypatch.setenv("MEMPALACE_CONFIG_DIR", str(tmp_path))
20+
cfg = MempalaceConfig()
21+
assert cfg._config_dir == tmp_path
22+
assert cfg._config_file == tmp_path / "config.json"
23+
assert cfg._people_map_file == tmp_path / "people_map.json"
24+
25+
26+
def test_env_var_config_file_actually_loads(tmp_path, monkeypatch):
27+
"""A config.json under the env-named dir feeds the resolved values — the point of the lever."""
28+
(tmp_path / "config.json").write_text(json.dumps({"collection_name": "sidecar_only"}))
29+
monkeypatch.setenv("MEMPALACE_CONFIG_DIR", str(tmp_path))
30+
cfg = MempalaceConfig()
31+
assert cfg._file_config.get("collection_name") == "sidecar_only"
32+
33+
34+
def test_explicit_arg_outranks_the_env_var(tmp_path, monkeypatch):
35+
"""Explicit arg > env, matching how palace_path resolves an explicit --palace over its env."""
36+
env_dir = tmp_path / "from_env"
37+
arg_dir = tmp_path / "from_arg"
38+
env_dir.mkdir()
39+
arg_dir.mkdir()
40+
monkeypatch.setenv("MEMPALACE_CONFIG_DIR", str(env_dir))
41+
cfg = MempalaceConfig(config_dir=str(arg_dir))
42+
assert cfg._config_dir == arg_dir
43+
44+
45+
def test_unset_env_keeps_the_home_default(monkeypatch):
46+
"""Absent the env var the directory stands where it always stood — behaviour-preserving."""
47+
monkeypatch.delenv("MEMPALACE_CONFIG_DIR", raising=False)
48+
cfg = MempalaceConfig()
49+
assert cfg._config_dir == type(cfg._config_dir)(os.path.expanduser("~/.mempalace"))
50+
51+
52+
def test_blank_env_falls_through_to_the_default(monkeypatch):
53+
"""An exported-but-empty value reads as unset rather than as the current directory."""
54+
monkeypatch.setenv("MEMPALACE_CONFIG_DIR", " ")
55+
cfg = MempalaceConfig()
56+
assert cfg._config_dir == type(cfg._config_dir)(os.path.expanduser("~/.mempalace"))
57+
58+
59+
def test_env_var_expands_a_tilde(monkeypatch):
60+
"""`~` expands, so an operator may export a home-relative path."""
61+
monkeypatch.setenv("MEMPALACE_CONFIG_DIR", "~/some-sidecar-root")
62+
cfg = MempalaceConfig()
63+
assert str(cfg._config_dir) == os.path.expanduser("~/some-sidecar-root")

0 commit comments

Comments
 (0)