mirror of
https://github.com/langchain-ai/langsmith-fetch.git
synced 2026-07-01 20:54:19 -04:00
feat: sync config UUID with LANGSMITH_PROJECT env var
Store project name alongside UUID in config to detect staleness. When LANGSMITH_PROJECT changes, automatically re-fetch UUID and update config. Changes: - Add _update_project_config() helper for atomic config updates - Rewrite get_project_uuid() with sync detection logic - Clear in-memory cache when project_uuid manually set - Update priority: LANGSMITH_PROJECT_UUID > sync check > config fallback Benefits: - Automatic UUID sync when project name changes - No API call when project unchanged (string comparison only) - Backwards compatible with legacy configs - Explicit override via LANGSMITH_PROJECT_UUID still works Tests: - Add 10 comprehensive test cases for sync detection - Fix existing tests to clear env vars for config fallback - All 71 tests passing
This commit is contained in:
+78
-44
@@ -77,6 +77,26 @@ def set_config_value(key: str, value: str):
|
||||
config[key] = value
|
||||
save_config(config)
|
||||
|
||||
# If manually setting project_uuid, clear in-memory cache
|
||||
# to force re-validation on next lookup
|
||||
if key in ("project-uuid", "project_uuid"):
|
||||
_project_uuid_cache.clear()
|
||||
|
||||
|
||||
def _update_project_config(project_name: str, project_uuid: str):
|
||||
"""
|
||||
Update config file with both project name and UUID atomically.
|
||||
|
||||
Args:
|
||||
project_name: Project name to store
|
||||
project_uuid: Project UUID to store
|
||||
"""
|
||||
# Load, update both fields, and save atomically
|
||||
config = load_config()
|
||||
config["project-name"] = project_name
|
||||
config["project-uuid"] = project_uuid
|
||||
save_config(config)
|
||||
|
||||
|
||||
def _lookup_project_uuid_by_name(
|
||||
project_name: str,
|
||||
@@ -144,73 +164,87 @@ def get_base_url() -> str | None:
|
||||
|
||||
def get_project_uuid() -> str | None:
|
||||
"""
|
||||
Get project UUID from config, env var, or by looking up LANGSMITH_PROJECT.
|
||||
Get project UUID with automatic sync detection.
|
||||
|
||||
Priority order:
|
||||
1. Config file (project_uuid)
|
||||
2. LANGSMITH_PROJECT_UUID env var (explicit UUID)
|
||||
3. LANGSMITH_PROJECT env var → API lookup → cached
|
||||
1. LANGSMITH_PROJECT_UUID env var (explicit UUID override)
|
||||
2. LANGSMITH_PROJECT env var → check if config matches → fetch if stale
|
||||
3. Config file as fallback (when no env var set)
|
||||
|
||||
Returns:
|
||||
Project UUID from config file, env var, or looked up by name, or None
|
||||
Project UUID or None
|
||||
"""
|
||||
# Priority 1: Config file
|
||||
if config_uuid := get_config_value("project_uuid"):
|
||||
return config_uuid
|
||||
import sys
|
||||
|
||||
# Priority 2: Direct UUID from env var
|
||||
if env_uuid := os.environ.get("LANGSMITH_PROJECT_UUID"):
|
||||
# Priority 1: Explicit UUID override (bypasses all config logic)
|
||||
env_uuid = os.environ.get("LANGSMITH_PROJECT_UUID")
|
||||
if env_uuid:
|
||||
return env_uuid
|
||||
|
||||
# Priority 3: Project name from env var → lookup
|
||||
project_name = os.environ.get("LANGSMITH_PROJECT")
|
||||
if not project_name:
|
||||
# Get current project name from env var
|
||||
env_project_name = os.environ.get("LANGSMITH_PROJECT")
|
||||
|
||||
# Load config values
|
||||
config_project_uuid = get_config_value("project_uuid")
|
||||
config_project_name = get_config_value("project_name")
|
||||
|
||||
# Case 1: No env var set - use config as default
|
||||
if not env_project_name:
|
||||
if config_project_uuid:
|
||||
return config_project_uuid
|
||||
return None
|
||||
|
||||
# Check cache first
|
||||
if project_name in _project_uuid_cache:
|
||||
return _project_uuid_cache[project_name]
|
||||
# Case 2: Env var IS set - check if it matches config
|
||||
|
||||
# Lookup via API
|
||||
# Check in-memory cache first (keyed by project name)
|
||||
if env_project_name in _project_uuid_cache:
|
||||
cached_uuid = _project_uuid_cache[env_project_name]
|
||||
# If config is out of sync, update it
|
||||
if cached_uuid and config_project_name != env_project_name:
|
||||
_update_project_config(env_project_name, cached_uuid)
|
||||
return cached_uuid
|
||||
|
||||
# Config matches env var - use cached UUID
|
||||
if config_project_name == env_project_name and config_project_uuid:
|
||||
# Add to in-memory cache
|
||||
_project_uuid_cache[env_project_name] = config_project_uuid
|
||||
return config_project_uuid
|
||||
|
||||
# Config doesn't match (or doesn't exist) - need to fetch
|
||||
print(f"Project name changed to '{env_project_name}', fetching UUID...", file=sys.stderr)
|
||||
|
||||
# Validate we have API key before attempting lookup
|
||||
api_key = get_api_key()
|
||||
if not api_key:
|
||||
print(
|
||||
"Warning: LANGSMITH_PROJECT set but no API key found. "
|
||||
"Set LANGSMITH_API_KEY to enable project lookup.",
|
||||
file=sys.stderr
|
||||
)
|
||||
return None
|
||||
|
||||
base_url = get_base_url()
|
||||
|
||||
# Fetch UUID via API
|
||||
try:
|
||||
# Need API key for lookup
|
||||
api_key = get_api_key()
|
||||
if not api_key:
|
||||
import sys
|
||||
print(
|
||||
"Warning: LANGSMITH_PROJECT set but no API key found. "
|
||||
"Set LANGSMITH_API_KEY to enable project lookup.",
|
||||
file=sys.stderr
|
||||
)
|
||||
return None
|
||||
uuid = _lookup_project_uuid_by_name(env_project_name, api_key, base_url)
|
||||
|
||||
base_url = get_base_url()
|
||||
# Update in-memory cache
|
||||
_project_uuid_cache[env_project_name] = uuid
|
||||
|
||||
# Inform user about lookup
|
||||
import sys
|
||||
print(f"Looking up project '{project_name}'...", file=sys.stderr)
|
||||
# Update config with BOTH name and UUID
|
||||
_update_project_config(env_project_name, uuid)
|
||||
|
||||
uuid = _lookup_project_uuid_by_name(project_name, api_key, base_url)
|
||||
|
||||
# Cache result in-memory
|
||||
_project_uuid_cache[project_name] = uuid
|
||||
|
||||
# Persist to config file for future use
|
||||
set_config_value("project_uuid", uuid)
|
||||
|
||||
print(f"Found project '{project_name}' (UUID: {uuid})", file=sys.stderr)
|
||||
print(f"Saved project UUID to config file", file=sys.stderr)
|
||||
print(f"Found project '{env_project_name}' (UUID: {uuid})", file=sys.stderr)
|
||||
|
||||
return uuid
|
||||
|
||||
except ValueError as e:
|
||||
import sys
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
return None
|
||||
except Exception as e:
|
||||
import sys
|
||||
print(
|
||||
f"Warning: Failed to lookup project '{project_name}': {e}",
|
||||
f"Warning: Failed to lookup project '{env_project_name}': {e}",
|
||||
file=sys.stderr
|
||||
)
|
||||
return None
|
||||
|
||||
+35
-7
@@ -120,9 +120,13 @@ class TestThreadCommand:
|
||||
|
||||
@responses.activate
|
||||
def test_thread_default_format_with_config(
|
||||
self, sample_thread_response, mock_env_api_key, temp_config_dir
|
||||
self, sample_thread_response, mock_env_api_key, temp_config_dir, monkeypatch
|
||||
):
|
||||
"""Test thread command with default format and config."""
|
||||
# Clear env vars to test config fallback
|
||||
monkeypatch.delenv("LANGSMITH_PROJECT", raising=False)
|
||||
monkeypatch.delenv("LANGSMITH_PROJECT_UUID", raising=False)
|
||||
|
||||
# Set up config
|
||||
with patch("langsmith_cli.config.CONFIG_DIR", temp_config_dir):
|
||||
with patch(
|
||||
@@ -147,9 +151,13 @@ class TestThreadCommand:
|
||||
|
||||
@responses.activate
|
||||
def test_thread_pretty_format(
|
||||
self, sample_thread_response, mock_env_api_key, temp_config_dir
|
||||
self, sample_thread_response, mock_env_api_key, temp_config_dir, monkeypatch
|
||||
):
|
||||
"""Test thread command with explicit pretty format."""
|
||||
# Clear env vars to test config fallback
|
||||
monkeypatch.delenv("LANGSMITH_PROJECT", raising=False)
|
||||
monkeypatch.delenv("LANGSMITH_PROJECT_UUID", raising=False)
|
||||
|
||||
with patch("langsmith_cli.config.CONFIG_DIR", temp_config_dir):
|
||||
with patch(
|
||||
"langsmith_cli.config.CONFIG_FILE", temp_config_dir / "config.yaml"
|
||||
@@ -175,9 +183,13 @@ class TestThreadCommand:
|
||||
|
||||
@responses.activate
|
||||
def test_thread_json_format(
|
||||
self, sample_thread_response, mock_env_api_key, temp_config_dir
|
||||
self, sample_thread_response, mock_env_api_key, temp_config_dir, monkeypatch
|
||||
):
|
||||
"""Test thread command with json format."""
|
||||
# Clear env vars to test config fallback
|
||||
monkeypatch.delenv("LANGSMITH_PROJECT", raising=False)
|
||||
monkeypatch.delenv("LANGSMITH_PROJECT_UUID", raising=False)
|
||||
|
||||
with patch("langsmith_cli.config.CONFIG_DIR", temp_config_dir):
|
||||
with patch(
|
||||
"langsmith_cli.config.CONFIG_FILE", temp_config_dir / "config.yaml"
|
||||
@@ -203,9 +215,13 @@ class TestThreadCommand:
|
||||
|
||||
@responses.activate
|
||||
def test_thread_raw_format(
|
||||
self, sample_thread_response, mock_env_api_key, temp_config_dir
|
||||
self, sample_thread_response, mock_env_api_key, temp_config_dir, monkeypatch
|
||||
):
|
||||
"""Test thread command with raw format."""
|
||||
# Clear env vars to test config fallback
|
||||
monkeypatch.delenv("LANGSMITH_PROJECT", raising=False)
|
||||
monkeypatch.delenv("LANGSMITH_PROJECT_UUID", raising=False)
|
||||
|
||||
with patch("langsmith_cli.config.CONFIG_DIR", temp_config_dir):
|
||||
with patch(
|
||||
"langsmith_cli.config.CONFIG_FILE", temp_config_dir / "config.yaml"
|
||||
@@ -287,9 +303,13 @@ class TestThreadsCommand:
|
||||
|
||||
@responses.activate
|
||||
def test_threads_default_limit(
|
||||
self, sample_thread_response, mock_env_api_key, temp_config_dir, tmp_path
|
||||
self, sample_thread_response, mock_env_api_key, temp_config_dir, tmp_path, monkeypatch
|
||||
):
|
||||
"""Test threads command with default limit (1)."""
|
||||
# Clear env vars to test config fallback
|
||||
monkeypatch.delenv("LANGSMITH_PROJECT", raising=False)
|
||||
monkeypatch.delenv("LANGSMITH_PROJECT_UUID", raising=False)
|
||||
|
||||
with patch("langsmith_cli.config.CONFIG_DIR", temp_config_dir):
|
||||
with patch(
|
||||
"langsmith_cli.config.CONFIG_FILE", temp_config_dir / "config.yaml"
|
||||
@@ -341,9 +361,13 @@ class TestThreadsCommand:
|
||||
|
||||
@responses.activate
|
||||
def test_threads_custom_limit(
|
||||
self, sample_thread_response, mock_env_api_key, temp_config_dir, tmp_path
|
||||
self, sample_thread_response, mock_env_api_key, temp_config_dir, tmp_path, monkeypatch
|
||||
):
|
||||
"""Test threads command with custom limit."""
|
||||
# Clear env vars to test config fallback
|
||||
monkeypatch.delenv("LANGSMITH_PROJECT", raising=False)
|
||||
monkeypatch.delenv("LANGSMITH_PROJECT_UUID", raising=False)
|
||||
|
||||
with patch("langsmith_cli.config.CONFIG_DIR", temp_config_dir):
|
||||
with patch(
|
||||
"langsmith_cli.config.CONFIG_FILE", temp_config_dir / "config.yaml"
|
||||
@@ -400,9 +424,13 @@ class TestThreadsCommand:
|
||||
|
||||
@responses.activate
|
||||
def test_threads_custom_filename_pattern(
|
||||
self, sample_thread_response, mock_env_api_key, temp_config_dir, tmp_path
|
||||
self, sample_thread_response, mock_env_api_key, temp_config_dir, tmp_path, monkeypatch
|
||||
):
|
||||
"""Test threads command with custom filename pattern."""
|
||||
# Clear env vars to test config fallback
|
||||
monkeypatch.delenv("LANGSMITH_PROJECT", raising=False)
|
||||
monkeypatch.delenv("LANGSMITH_PROJECT_UUID", raising=False)
|
||||
|
||||
with patch("langsmith_cli.config.CONFIG_DIR", temp_config_dir):
|
||||
with patch(
|
||||
"langsmith_cli.config.CONFIG_FILE", temp_config_dir / "config.yaml"
|
||||
|
||||
+254
-8
@@ -206,8 +206,12 @@ class TestConfigFunctions:
|
||||
# Env var should take precedence over config
|
||||
assert get_api_key() == "env_api_key"
|
||||
|
||||
def test_get_project_uuid(self, temp_config_dir):
|
||||
"""Test getting project UUID from config."""
|
||||
def test_get_project_uuid(self, temp_config_dir, monkeypatch):
|
||||
"""Test getting project UUID from config when no env var set."""
|
||||
# Clear env vars to test config fallback behavior
|
||||
monkeypatch.delenv("LANGSMITH_PROJECT", raising=False)
|
||||
monkeypatch.delenv("LANGSMITH_PROJECT_UUID", raising=False)
|
||||
|
||||
with patch("langsmith_cli.config.CONFIG_DIR", temp_config_dir):
|
||||
with patch(
|
||||
"langsmith_cli.config.CONFIG_FILE", temp_config_dir / "config.yaml"
|
||||
@@ -253,8 +257,8 @@ class TestConfigFunctions:
|
||||
class TestProjectLookup:
|
||||
"""Tests for automatic project UUID lookup from LANGSMITH_PROJECT."""
|
||||
|
||||
def test_get_project_uuid_priority_config_first(self, temp_config_dir, monkeypatch):
|
||||
"""Test that config file takes priority over env var lookup."""
|
||||
def test_get_project_uuid_priority_explicit_uuid_wins(self, temp_config_dir, monkeypatch):
|
||||
"""Test that LANGSMITH_PROJECT_UUID env var takes highest priority."""
|
||||
monkeypatch.setenv("LANGSMITH_PROJECT", "my-project")
|
||||
monkeypatch.setenv("LANGSMITH_PROJECT_UUID", "env-uuid")
|
||||
|
||||
@@ -263,12 +267,13 @@ class TestProjectLookup:
|
||||
from langsmith_cli.config import get_project_uuid, set_config_value
|
||||
|
||||
set_config_value("project-uuid", "config-uuid")
|
||||
set_config_value("project-name", "old-project")
|
||||
|
||||
# Config file should win
|
||||
assert get_project_uuid() == "config-uuid"
|
||||
# LANGSMITH_PROJECT_UUID should always win (highest priority)
|
||||
assert get_project_uuid() == "env-uuid"
|
||||
|
||||
def test_get_project_uuid_priority_env_uuid_second(self, temp_config_dir, monkeypatch):
|
||||
"""Test that LANGSMITH_PROJECT_UUID env var is second priority."""
|
||||
def test_get_project_uuid_priority_env_uuid_no_lookup(self, temp_config_dir, monkeypatch):
|
||||
"""Test that LANGSMITH_PROJECT_UUID env var bypasses API lookup."""
|
||||
monkeypatch.setenv("LANGSMITH_PROJECT", "my-project")
|
||||
monkeypatch.setenv("LANGSMITH_PROJECT_UUID", "env-uuid")
|
||||
|
||||
@@ -366,3 +371,244 @@ class TestProjectLookup:
|
||||
# Should return None with warning
|
||||
result = get_project_uuid()
|
||||
assert result is None
|
||||
|
||||
def test_project_name_change_triggers_refetch(self, temp_config_dir, monkeypatch):
|
||||
"""Test that changing project name triggers UUID re-fetch."""
|
||||
from unittest.mock import Mock, MagicMock
|
||||
|
||||
monkeypatch.setenv("LANGSMITH_PROJECT", "new-project")
|
||||
monkeypatch.setenv("LANGSMITH_API_KEY", TEST_API_KEY)
|
||||
|
||||
mock_project = Mock()
|
||||
mock_project.id = "new-uuid"
|
||||
mock_project.name = "new-project"
|
||||
|
||||
mock_client = MagicMock()
|
||||
mock_client.read_project.return_value = mock_project
|
||||
|
||||
with patch("langsmith_cli.config.CONFIG_DIR", temp_config_dir):
|
||||
with patch("langsmith_cli.config.CONFIG_FILE", temp_config_dir / "config.yaml"):
|
||||
with patch("langsmith.Client", return_value=mock_client):
|
||||
from langsmith_cli.config import get_project_uuid, set_config_value, get_config_value, _project_uuid_cache
|
||||
|
||||
# Clear cache
|
||||
_project_uuid_cache.clear()
|
||||
|
||||
# Set old config
|
||||
set_config_value("project-name", "old-project")
|
||||
set_config_value("project-uuid", "old-uuid")
|
||||
|
||||
# Should detect mismatch and fetch new UUID
|
||||
result = get_project_uuid()
|
||||
assert result == "new-uuid"
|
||||
assert mock_client.read_project.call_count == 1
|
||||
|
||||
# Verify config was updated with both fields
|
||||
assert get_config_value("project-name") == "new-project"
|
||||
assert get_config_value("project-uuid") == "new-uuid"
|
||||
|
||||
def test_project_name_match_uses_cache(self, temp_config_dir, monkeypatch):
|
||||
"""Test that matching project name uses cached UUID without API call."""
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
monkeypatch.setenv("LANGSMITH_PROJECT", "test-project")
|
||||
monkeypatch.setenv("LANGSMITH_API_KEY", TEST_API_KEY)
|
||||
|
||||
mock_client = MagicMock()
|
||||
|
||||
with patch("langsmith_cli.config.CONFIG_DIR", temp_config_dir):
|
||||
with patch("langsmith_cli.config.CONFIG_FILE", temp_config_dir / "config.yaml"):
|
||||
with patch("langsmith.Client", return_value=mock_client):
|
||||
from langsmith_cli.config import get_project_uuid, set_config_value, _project_uuid_cache
|
||||
|
||||
# Clear cache
|
||||
_project_uuid_cache.clear()
|
||||
|
||||
# Set matching config
|
||||
set_config_value("project-name", "test-project")
|
||||
set_config_value("project-uuid", "test-uuid")
|
||||
|
||||
# Should use cached UUID without API call
|
||||
result = get_project_uuid()
|
||||
assert result == "test-uuid"
|
||||
assert mock_client.read_project.call_count == 0
|
||||
|
||||
def test_legacy_config_migration(self, temp_config_dir, monkeypatch):
|
||||
"""Test that legacy config (only project_uuid) triggers re-fetch and migration."""
|
||||
from unittest.mock import Mock, MagicMock
|
||||
|
||||
monkeypatch.setenv("LANGSMITH_PROJECT", "test-project")
|
||||
monkeypatch.setenv("LANGSMITH_API_KEY", TEST_API_KEY)
|
||||
|
||||
mock_project = Mock()
|
||||
mock_project.id = "fetched-uuid"
|
||||
mock_project.name = "test-project"
|
||||
|
||||
mock_client = MagicMock()
|
||||
mock_client.read_project.return_value = mock_project
|
||||
|
||||
with patch("langsmith_cli.config.CONFIG_DIR", temp_config_dir):
|
||||
with patch("langsmith_cli.config.CONFIG_FILE", temp_config_dir / "config.yaml"):
|
||||
with patch("langsmith.Client", return_value=mock_client):
|
||||
from langsmith_cli.config import get_project_uuid, set_config_value, get_config_value, _project_uuid_cache
|
||||
|
||||
# Clear cache
|
||||
_project_uuid_cache.clear()
|
||||
|
||||
# Set legacy config (only UUID, no name)
|
||||
set_config_value("project-uuid", "old-uuid")
|
||||
|
||||
# Should detect missing project_name and fetch new UUID
|
||||
result = get_project_uuid()
|
||||
assert result == "fetched-uuid"
|
||||
|
||||
# Verify config was updated with both fields
|
||||
assert get_config_value("project-name") == "test-project"
|
||||
assert get_config_value("project-uuid") == "fetched-uuid"
|
||||
|
||||
def test_no_env_var_uses_config_default(self, temp_config_dir, monkeypatch):
|
||||
"""Test that no env var uses config as default."""
|
||||
monkeypatch.delenv("LANGSMITH_PROJECT", raising=False)
|
||||
monkeypatch.delenv("LANGSMITH_PROJECT_UUID", raising=False)
|
||||
|
||||
with patch("langsmith_cli.config.CONFIG_DIR", temp_config_dir):
|
||||
with patch("langsmith_cli.config.CONFIG_FILE", temp_config_dir / "config.yaml"):
|
||||
from langsmith_cli.config import get_project_uuid, set_config_value
|
||||
|
||||
# Set config
|
||||
set_config_value("project-name", "default-project")
|
||||
set_config_value("project-uuid", "default-uuid")
|
||||
|
||||
# Should use config UUID without env var
|
||||
result = get_project_uuid()
|
||||
assert result == "default-uuid"
|
||||
|
||||
def test_explicit_uuid_override(self, temp_config_dir, monkeypatch):
|
||||
"""Test that LANGSMITH_PROJECT_UUID overrides everything."""
|
||||
monkeypatch.setenv("LANGSMITH_PROJECT", "test-project")
|
||||
monkeypatch.setenv("LANGSMITH_PROJECT_UUID", "override-uuid")
|
||||
|
||||
with patch("langsmith_cli.config.CONFIG_DIR", temp_config_dir):
|
||||
with patch("langsmith_cli.config.CONFIG_FILE", temp_config_dir / "config.yaml"):
|
||||
from langsmith_cli.config import get_project_uuid, set_config_value
|
||||
|
||||
# Set config
|
||||
set_config_value("project-name", "config-project")
|
||||
set_config_value("project-uuid", "config-uuid")
|
||||
|
||||
# LANGSMITH_PROJECT_UUID should override everything
|
||||
result = get_project_uuid()
|
||||
assert result == "override-uuid"
|
||||
|
||||
def test_api_failure_handling(self, temp_config_dir, monkeypatch):
|
||||
"""Test that API failure is handled gracefully."""
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
monkeypatch.setenv("LANGSMITH_PROJECT", "nonexistent")
|
||||
monkeypatch.setenv("LANGSMITH_API_KEY", TEST_API_KEY)
|
||||
|
||||
mock_client = MagicMock()
|
||||
mock_client.read_project.side_effect = Exception("Project not found")
|
||||
|
||||
with patch("langsmith_cli.config.CONFIG_DIR", temp_config_dir):
|
||||
with patch("langsmith_cli.config.CONFIG_FILE", temp_config_dir / "config.yaml"):
|
||||
with patch("langsmith.Client", return_value=mock_client):
|
||||
from langsmith_cli.config import get_project_uuid, get_config_value, set_config_value, _project_uuid_cache
|
||||
|
||||
# Clear cache
|
||||
_project_uuid_cache.clear()
|
||||
|
||||
# Set old config
|
||||
set_config_value("project-name", "old-project")
|
||||
set_config_value("project-uuid", "old-uuid")
|
||||
|
||||
# Should return None on API failure
|
||||
result = get_project_uuid()
|
||||
assert result is None
|
||||
|
||||
# Verify config was NOT updated (preserves last known good state)
|
||||
assert get_config_value("project-name") == "old-project"
|
||||
assert get_config_value("project-uuid") == "old-uuid"
|
||||
|
||||
def test_cache_clears_on_manual_update(self, temp_config_dir):
|
||||
"""Test that in-memory cache clears when project_uuid is manually set."""
|
||||
with patch("langsmith_cli.config.CONFIG_DIR", temp_config_dir):
|
||||
with patch("langsmith_cli.config.CONFIG_FILE", temp_config_dir / "config.yaml"):
|
||||
from langsmith_cli.config import set_config_value, _project_uuid_cache
|
||||
|
||||
# Populate cache
|
||||
_project_uuid_cache["test-project"] = "cached-uuid"
|
||||
|
||||
# Manually set project_uuid
|
||||
set_config_value("project-uuid", "new-uuid")
|
||||
|
||||
# Cache should be cleared
|
||||
assert len(_project_uuid_cache) == 0
|
||||
|
||||
def test_in_memory_cache_updates_config(self, temp_config_dir, monkeypatch):
|
||||
"""Test that in-memory cache updates config when out of sync."""
|
||||
monkeypatch.setenv("LANGSMITH_PROJECT", "cached-project")
|
||||
|
||||
with patch("langsmith_cli.config.CONFIG_DIR", temp_config_dir):
|
||||
with patch("langsmith_cli.config.CONFIG_FILE", temp_config_dir / "config.yaml"):
|
||||
from langsmith_cli.config import get_project_uuid, set_config_value, get_config_value, _project_uuid_cache
|
||||
|
||||
# Set old config
|
||||
set_config_value("project-name", "old-project")
|
||||
set_config_value("project-uuid", "old-uuid")
|
||||
|
||||
# Populate in-memory cache with different project
|
||||
_project_uuid_cache["cached-project"] = "cached-uuid"
|
||||
|
||||
# Should use cache and update config
|
||||
result = get_project_uuid()
|
||||
assert result == "cached-uuid"
|
||||
|
||||
# Verify config was updated
|
||||
assert get_config_value("project-name") == "cached-project"
|
||||
assert get_config_value("project-uuid") == "cached-uuid"
|
||||
|
||||
def test_empty_project_name_handling(self, temp_config_dir, monkeypatch):
|
||||
"""Test graceful handling of empty project name."""
|
||||
monkeypatch.setenv("LANGSMITH_PROJECT", "")
|
||||
|
||||
with patch("langsmith_cli.config.CONFIG_DIR", temp_config_dir):
|
||||
with patch("langsmith_cli.config.CONFIG_FILE", temp_config_dir / "config.yaml"):
|
||||
from langsmith_cli.config import get_project_uuid, set_config_value
|
||||
|
||||
# Set config
|
||||
set_config_value("project-uuid", "config-uuid")
|
||||
|
||||
# Empty string should be treated as no env var
|
||||
result = get_project_uuid()
|
||||
assert result == "config-uuid"
|
||||
|
||||
def test_project_uuid_persists_after_lookup(self, temp_config_dir, monkeypatch):
|
||||
"""Test that both project_name and project_uuid persist after lookup."""
|
||||
from unittest.mock import Mock, MagicMock
|
||||
|
||||
monkeypatch.setenv("LANGSMITH_PROJECT", "persist-project")
|
||||
monkeypatch.setenv("LANGSMITH_API_KEY", TEST_API_KEY)
|
||||
|
||||
mock_project = Mock()
|
||||
mock_project.id = "persist-uuid"
|
||||
mock_project.name = "persist-project"
|
||||
|
||||
mock_client = MagicMock()
|
||||
mock_client.read_project.return_value = mock_project
|
||||
|
||||
with patch("langsmith_cli.config.CONFIG_DIR", temp_config_dir):
|
||||
with patch("langsmith_cli.config.CONFIG_FILE", temp_config_dir / "config.yaml"):
|
||||
with patch("langsmith.Client", return_value=mock_client):
|
||||
from langsmith_cli.config import get_project_uuid, get_config_value, _project_uuid_cache
|
||||
|
||||
# Clear cache
|
||||
_project_uuid_cache.clear()
|
||||
|
||||
# First call should fetch and persist
|
||||
result = get_project_uuid()
|
||||
assert result == "persist-uuid"
|
||||
|
||||
# Verify both fields were persisted
|
||||
assert get_config_value("project-name") == "persist-project"
|
||||
assert get_config_value("project-uuid") == "persist-uuid"
|
||||
|
||||
Reference in New Issue
Block a user