From 710c9fe05798da3ccaf8965e64a77c017af67728 Mon Sep 17 00:00:00 2001 From: William FH <13333726+hinthornw@users.noreply.github.com> Date: Wed, 4 Sep 2024 12:41:21 -0700 Subject: [PATCH] [Python] Cache env var check (#971) --- python/langsmith/client.py | 2 +- python/langsmith/utils.py | 2 ++ python/pyproject.toml | 2 +- python/tests/integration_tests/test_client.py | 7 +++++- python/tests/unit_tests/test_client.py | 14 ++++++++++++ python/tests/unit_tests/test_run_helpers.py | 11 +++------- python/tests/unit_tests/test_utils.py | 22 ++++++++++++++----- 7 files changed, 44 insertions(+), 16 deletions(-) diff --git a/python/langsmith/client.py b/python/langsmith/client.py index c79c1fb56..888f16f60 100644 --- a/python/langsmith/client.py +++ b/python/langsmith/client.py @@ -1115,7 +1115,7 @@ def _run_transform( @staticmethod def _insert_runtime_env(runs: Sequence[dict]) -> None: - runtime_env = ls_env.get_runtime_and_metrics() + runtime_env = ls_env.get_runtime_environment() for run_create in runs: run_extra = cast(dict, run_create.setdefault("extra", {})) # update runtime diff --git a/python/langsmith/utils.py b/python/langsmith/utils.py index d5a4467c0..bf9d17b68 100644 --- a/python/langsmith/utils.py +++ b/python/langsmith/utils.py @@ -352,6 +352,7 @@ def is_base_message_like(obj: object) -> bool: ) +@functools.lru_cache(maxsize=100) def get_env_var( name: str, default: Optional[str] = None, @@ -379,6 +380,7 @@ def get_env_var( return default +@functools.lru_cache(maxsize=1) def get_tracer_project(return_default_value=True) -> Optional[str]: """Get the project name for a LangSmith tracer.""" return os.environ.get( diff --git a/python/pyproject.toml b/python/pyproject.toml index 710faef85..8f185f82e 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langsmith" -version = "0.1.111" +version = "0.1.112" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." authors = ["LangChain "] license = "MIT" diff --git a/python/tests/integration_tests/test_client.py b/python/tests/integration_tests/test_client.py index 57dffd963..5c007aeae 100644 --- a/python/tests/integration_tests/test_client.py +++ b/python/tests/integration_tests/test_client.py @@ -17,7 +17,11 @@ from langsmith.client import ID_TYPE, Client from langsmith.schemas import DataType -from langsmith.utils import LangSmithConnectionError, LangSmithError +from langsmith.utils import ( + LangSmithConnectionError, + LangSmithError, + get_env_var, +) def wait_for( @@ -351,6 +355,7 @@ def test_persist_update_run(langchain_client: Client) -> None: @pytest.mark.parametrize("uri", ["http://localhost:1981", "http://api.langchain.minus"]) def test_error_surfaced_invalid_uri(monkeypatch: pytest.MonkeyPatch, uri: str) -> None: + get_env_var.cache_clear() monkeypatch.setenv("LANGCHAIN_ENDPOINT", uri) monkeypatch.setenv("LANGCHAIN_API_KEY", "test") client = Client() diff --git a/python/tests/unit_tests/test_client.py b/python/tests/unit_tests/test_client.py index ace2e0f9f..0e648ffc4 100644 --- a/python/tests/unit_tests/test_client.py +++ b/python/tests/unit_tests/test_client.py @@ -54,9 +54,14 @@ def test__is_langchain_hosted() -> None: assert _is_langchain_hosted("https://dev.api.smith.langchain.com") +def _clear_env_cache(): + ls_utils.get_env_var.cache_clear() + + def test_validate_api_url(monkeypatch: pytest.MonkeyPatch) -> None: # Scenario 1: Both LANGCHAIN_ENDPOINT and LANGSMITH_ENDPOINT # are set, but api_url is not + _clear_env_cache() monkeypatch.setenv("LANGCHAIN_ENDPOINT", "https://api.smith.langchain-endpoint.com") monkeypatch.setenv("LANGSMITH_ENDPOINT", "https://api.smith.langsmith-endpoint.com") @@ -65,6 +70,7 @@ def test_validate_api_url(monkeypatch: pytest.MonkeyPatch) -> None: # Scenario 2: Both LANGCHAIN_ENDPOINT and LANGSMITH_ENDPOINT # are set, and api_url is set + _clear_env_cache() monkeypatch.setenv("LANGCHAIN_ENDPOINT", "https://api.smith.langchain-endpoint.com") monkeypatch.setenv("LANGSMITH_ENDPOINT", "https://api.smith.langsmith-endpoint.com") @@ -72,6 +78,7 @@ def test_validate_api_url(monkeypatch: pytest.MonkeyPatch) -> None: assert client.api_url == "https://api.smith.langchain.com" # Scenario 3: LANGCHAIN_ENDPOINT is set, but LANGSMITH_ENDPOINT is not + _clear_env_cache() monkeypatch.setenv("LANGCHAIN_ENDPOINT", "https://api.smith.langchain-endpoint.com") monkeypatch.delenv("LANGSMITH_ENDPOINT", raising=False) @@ -79,6 +86,7 @@ def test_validate_api_url(monkeypatch: pytest.MonkeyPatch) -> None: assert client.api_url == "https://api.smith.langchain-endpoint.com" # Scenario 4: LANGCHAIN_ENDPOINT is not set, but LANGSMITH_ENDPOINT is set + _clear_env_cache() monkeypatch.delenv("LANGCHAIN_ENDPOINT", raising=False) monkeypatch.setenv("LANGSMITH_ENDPOINT", "https://api.smith.langsmith-endpoint.com") @@ -89,6 +97,7 @@ def test_validate_api_url(monkeypatch: pytest.MonkeyPatch) -> None: def test_validate_api_key(monkeypatch: pytest.MonkeyPatch) -> None: # Scenario 1: Both LANGCHAIN_API_KEY and LANGSMITH_API_KEY are set, # but api_key is not + _clear_env_cache() monkeypatch.setenv("LANGCHAIN_API_KEY", "env_langchain_api_key") monkeypatch.setenv("LANGSMITH_API_KEY", "env_langsmith_api_key") @@ -97,6 +106,7 @@ def test_validate_api_key(monkeypatch: pytest.MonkeyPatch) -> None: # Scenario 2: Both LANGCHAIN_API_KEY and LANGSMITH_API_KEY are set, # and api_key is set + _clear_env_cache() monkeypatch.setenv("LANGCHAIN_API_KEY", "env_langchain_api_key") monkeypatch.setenv("LANGSMITH_API_KEY", "env_langsmith_api_key") @@ -111,6 +121,7 @@ def test_validate_api_key(monkeypatch: pytest.MonkeyPatch) -> None: assert client.api_key == "env_langchain_api_key" # Scenario 4: LANGCHAIN_API_KEY is not set, but LANGSMITH_API_KEY is set + _clear_env_cache() monkeypatch.delenv("LANGCHAIN_API_KEY", raising=False) monkeypatch.setenv("LANGSMITH_API_KEY", "env_langsmith_api_key") @@ -119,6 +130,7 @@ def test_validate_api_key(monkeypatch: pytest.MonkeyPatch) -> None: def test_validate_multiple_urls(monkeypatch: pytest.MonkeyPatch) -> None: + _clear_env_cache() monkeypatch.setenv("LANGCHAIN_ENDPOINT", "https://api.smith.langchain-endpoint.com") monkeypatch.setenv("LANGSMITH_ENDPOINT", "https://api.smith.langsmith-endpoint.com") monkeypatch.setenv("LANGSMITH_RUNS_ENDPOINTS", "{}") @@ -149,6 +161,7 @@ def test_validate_multiple_urls(monkeypatch: pytest.MonkeyPatch) -> None: def test_headers(monkeypatch: pytest.MonkeyPatch) -> None: + _clear_env_cache() monkeypatch.delenv("LANGCHAIN_API_KEY", raising=False) with patch.dict("os.environ", {}, clear=True): client = Client(api_url="http://localhost:1984", api_key="123") @@ -161,6 +174,7 @@ def test_headers(monkeypatch: pytest.MonkeyPatch) -> None: @mock.patch("langsmith.client.requests.Session") def test_upload_csv(mock_session_cls: mock.Mock) -> None: + _clear_env_cache() dataset_id = str(uuid.uuid4()) example_1 = ls_schemas.Example( id=str(uuid.uuid4()), diff --git a/python/tests/unit_tests/test_run_helpers.py b/python/tests/unit_tests/test_run_helpers.py index 72cd5d210..42b8fcc91 100644 --- a/python/tests/unit_tests/test_run_helpers.py +++ b/python/tests/unit_tests/test_run_helpers.py @@ -7,14 +7,7 @@ import time import uuid import warnings -from typing import ( - Any, - AsyncGenerator, - Generator, - Optional, - Set, - cast, -) +from typing import Any, AsyncGenerator, Generator, Optional, Set, cast from unittest.mock import MagicMock, patch import pytest @@ -22,6 +15,7 @@ import langsmith from langsmith import Client from langsmith import schemas as ls_schemas +from langsmith import utils as ls_utils from langsmith.run_helpers import ( _get_inputs, as_runnable, @@ -1333,6 +1327,7 @@ async def my_function(a: int) -> AsyncGenerator[int, None]: @pytest.mark.parametrize("env_var", [True, False]) @pytest.mark.parametrize("context", [True, False, None]) async def test_trace_respects_env_var(env_var: bool, context: Optional[bool]): + ls_utils.get_env_var.cache_clear() mock_client = _get_mock_client() with patch.dict(os.environ, {"LANGSMITH_TRACING": "true" if env_var else "false "}): with tracing_context(enabled=context): diff --git a/python/tests/unit_tests/test_utils.py b/python/tests/unit_tests/test_utils.py index 9db646aae..b32e6d8d5 100644 --- a/python/tests/unit_tests/test_utils.py +++ b/python/tests/unit_tests/test_utils.py @@ -31,6 +31,7 @@ def __init__( self.return_default_value = return_default_value def test_correct_get_tracer_project(self): + ls_utils.get_env_var.cache_clear() cases = [ self.GetTracerProjectTestCase( test_name="default to 'default' when no project provided", @@ -75,6 +76,8 @@ def test_correct_get_tracer_project(self): ] for case in cases: + ls_utils.get_env_var.cache_clear() + ls_utils.get_tracer_project.cache_clear() with self.subTest(msg=case.test_name): with pytest.MonkeyPatch.context() as mp: for k, v in case.envvars.items(): @@ -89,6 +92,7 @@ def test_correct_get_tracer_project(self): def test_tracing_enabled(): + ls_utils.get_env_var.cache_clear() with patch.dict( "os.environ", {"LANGCHAIN_TRACING_V2": "false", "LANGSMITH_TRACING": "false"} ): @@ -123,6 +127,7 @@ def parent_function(): assert not ls_utils.tracing_is_enabled() return untraced_child_function() + ls_utils.get_env_var.cache_clear() with patch.dict( "os.environ", {"LANGCHAIN_TRACING_V2": "true", "LANGSMITH_TRACING": "true"} ): @@ -131,6 +136,7 @@ def parent_function(): def test_tracing_disabled(): + ls_utils.get_env_var.cache_clear() with patch.dict( "os.environ", {"LANGCHAIN_TRACING_V2": "true", "LANGSMITH_TRACING": "true"} ): @@ -314,34 +320,40 @@ def test_parse_prompt_identifier(): def test_get_api_key() -> None: + ls_utils.get_env_var.cache_clear() assert ls_utils.get_api_key("provided_api_key") == "provided_api_key" assert ls_utils.get_api_key("'provided_api_key'") == "provided_api_key" assert ls_utils.get_api_key('"_provided_api_key"') == "_provided_api_key" with patch.dict("os.environ", {"LANGCHAIN_API_KEY": "env_api_key"}, clear=True): - assert ls_utils.get_api_key(None) == "env_api_key" + api_key_ = ls_utils.get_api_key(None) + assert api_key_ == "env_api_key" + + ls_utils.get_env_var.cache_clear() with patch.dict("os.environ", {}, clear=True): assert ls_utils.get_api_key(None) is None - + ls_utils.get_env_var.cache_clear() assert ls_utils.get_api_key("") is None assert ls_utils.get_api_key(" ") is None def test_get_api_url() -> None: + ls_utils.get_env_var.cache_clear() assert ls_utils.get_api_url("http://provided.url") == "http://provided.url" with patch.dict("os.environ", {"LANGCHAIN_ENDPOINT": "http://env.url"}): assert ls_utils.get_api_url(None) == "http://env.url" + ls_utils.get_env_var.cache_clear() with patch.dict("os.environ", {}, clear=True): assert ls_utils.get_api_url(None) == "https://api.smith.langchain.com" - + ls_utils.get_env_var.cache_clear() with patch.dict("os.environ", {}, clear=True): assert ls_utils.get_api_url(None) == "https://api.smith.langchain.com" - + ls_utils.get_env_var.cache_clear() with patch.dict("os.environ", {"LANGCHAIN_ENDPOINT": "http://env.url"}): assert ls_utils.get_api_url(None) == "http://env.url" - + ls_utils.get_env_var.cache_clear() with pytest.raises(ls_utils.LangSmithUserError): ls_utils.get_api_url(" ")