Skip to content

Commit 8afe4c4

Browse files
committed
Improve typing of tests
1 parent feedab7 commit 8afe4c4

File tree

6 files changed

+105
-130
lines changed

6 files changed

+105
-130
lines changed

src/humanloop/sync/sync_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from humanloop.base_client import BaseHumanloop
1414

1515
# Set up logging
16-
logger = logging.getLogger(__name__)
16+
logger = logging.getLogger("humanloop.sdk.sync")
1717
logger.setLevel(logging.INFO)
1818
console_handler = logging.StreamHandler()
1919
formatter = logging.Formatter("%(message)s")

tests/custom/conftest.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
from typing import Generator
22
import typing
3+
import os
4+
from dotenv import load_dotenv
35
from unittest.mock import MagicMock
46

57
import pytest
6-
from humanloop.base_client import BaseHumanloop
78
from humanloop.otel.exporter import HumanloopSpanExporter
89
from humanloop.otel.processor import HumanloopSpanProcessor
910
from openai.types.chat.chat_completion_message_param import ChatCompletionMessageParam
@@ -18,9 +19,10 @@
1819
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
1920
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
2021
from opentelemetry.trace import Tracer
22+
from tests.custom.types import GetHumanloopClientFn
2123

2224
if typing.TYPE_CHECKING:
23-
from humanloop.client import BaseHumanloop
25+
from humanloop.client import Humanloop
2426

2527

2628
@pytest.fixture(scope="function")
@@ -80,10 +82,25 @@ def opentelemetry_test_configuration(
8082
instrumentor.uninstrument()
8183

8284

85+
86+
@pytest.fixture(scope="session")
87+
def get_humanloop_client() -> GetHumanloopClientFn:
88+
load_dotenv()
89+
if not os.getenv("HUMANLOOP_API_KEY"):
90+
pytest.fail("HUMANLOOP_API_KEY is not set for integration tests")
91+
92+
def _get_humanloop_test_client(use_local_files: bool = False) -> Humanloop:
93+
return Humanloop(
94+
api_key=os.getenv("HUMANLOOP_API_KEY"),
95+
use_local_files=use_local_files,
96+
)
97+
98+
return _get_humanloop_test_client
99+
100+
83101
@pytest.fixture(scope="function")
84102
def opentelemetry_hl_test_configuration(
85103
opentelemetry_test_provider: TracerProvider,
86-
humanloop_client: BaseHumanloop,
87104
) -> Generator[tuple[Tracer, InMemorySpanExporter], None, None]:
88105
"""Configure OTel backend with HumanloopSpanProcessor.
89106

tests/custom/integration/conftest.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from typing import Callable
12
from contextlib import contextmanager, redirect_stdout
23
from dataclasses import dataclass
34
import os
@@ -17,6 +18,7 @@ class TestIdentifiers:
1718
file_path: str
1819

1920

21+
2022
@pytest.fixture()
2123
def capture_stdout() -> ContextManager[TextIO]:
2224
@contextmanager
@@ -36,17 +38,6 @@ def openai_key() -> str:
3638
return os.getenv("OPENAI_API_KEY") # type: ignore [return-value]
3739

3840

39-
@pytest.fixture(scope="session")
40-
def humanloop_test_client() -> Humanloop:
41-
dotenv.load_dotenv()
42-
if not os.getenv("HUMANLOOP_API_KEY"):
43-
pytest.fail("HUMANLOOP_API_KEY is not set for integration tests")
44-
return Humanloop(
45-
api_key=os.getenv("HUMANLOOP_API_KEY"),
46-
base_url="http://0.0.0.0:80/v5",
47-
)
48-
49-
5041
@pytest.fixture(scope="function")
5142
def sdk_test_dir(humanloop_test_client: Humanloop) -> Generator[str, None, None]:
5243
def cleanup_directory(directory_id: str):

tests/custom/integration/test_sync.py

Lines changed: 66 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
from pathlib import Path
33
import pytest
44
from humanloop import Humanloop, FileType, AgentResponse, PromptResponse
5+
from humanloop.prompts.client import PromptsClient
6+
from humanloop.agents.client import AgentsClient
57
from humanloop.error import HumanloopRuntimeError
8+
from tests.custom.types import GetHumanloopClientFn
9+
import logging
610

711

812
class SyncableFile(NamedTuple):
@@ -15,7 +19,8 @@ class SyncableFile(NamedTuple):
1519

1620
@pytest.fixture
1721
def test_file_structure(
18-
humanloop_test_client: Humanloop, sdk_test_dir: str
22+
get_humanloop_client: GetHumanloopClientFn,
23+
sdk_test_dir: str,
1924
) -> Generator[list[SyncableFile], None, None]:
2025
"""Creates a predefined structure of files in Humanloop for testing sync"""
2126
files: List[SyncableFile] = [
@@ -46,18 +51,20 @@ def test_file_structure(
4651
),
4752
]
4853

54+
humanloop_client: Humanloop = get_humanloop_client()
55+
4956
# Create the files in Humanloop
5057
created_files = []
5158
for file in files:
5259
full_path = f"{sdk_test_dir}/{file.path}"
5360
response: Union[AgentResponse, PromptResponse]
5461
if file.type == "prompt":
55-
response = humanloop_test_client.prompts.upsert(
62+
response = humanloop_client.prompts.upsert(
5663
path=full_path,
5764
model=file.model,
5865
)
5966
elif file.type == "agent":
60-
response = humanloop_test_client.agents.upsert(
67+
response = humanloop_client.agents.upsert(
6168
path=full_path,
6269
model=file.model,
6370
)
@@ -82,10 +89,14 @@ def cleanup_local_files():
8289
shutil.rmtree(local_dir)
8390

8491

85-
def test_pull_basic(humanloop_test_client: Humanloop, test_file_structure: List[SyncableFile]):
92+
def test_pull_basic(
93+
get_humanloop_client: GetHumanloopClientFn,
94+
test_file_structure: List[SyncableFile],
95+
):
8696
"""Test that humanloop.sync() correctly syncs remote files to local filesystem"""
8797
# Run the sync
88-
successful_files = humanloop_test_client.pull()
98+
humanloop_client = get_humanloop_client()
99+
successful_files = humanloop_client.pull()
89100

90101
# Verify each file was synced correctly
91102
for file in test_file_structure:
@@ -105,7 +116,7 @@ def test_pull_basic(humanloop_test_client: Humanloop, test_file_structure: List[
105116

106117

107118
def test_overload_with_local_files(
108-
humanloop_test_client: Humanloop,
119+
get_humanloop_client: GetHumanloopClientFn,
109120
test_file_structure: List[SyncableFile],
110121
):
111122
"""Test that overload_with_local_files correctly handles local files.
@@ -116,7 +127,8 @@ def test_overload_with_local_files(
116127
3. Test using the pulled files
117128
"""
118129
# First pull the files locally
119-
humanloop_test_client.pull()
130+
humanloop_client = get_humanloop_client(use_local_files=True)
131+
humanloop_client.pull()
120132

121133
# Test using the pulled files
122134
test_file = test_file_structure[0] # Use the first test file
@@ -130,30 +142,31 @@ def test_overload_with_local_files(
130142
# Test call with pulled file
131143
response: Union[AgentResponse, PromptResponse]
132144
if test_file.type == "prompt":
133-
response = humanloop_test_client.prompts.call( # type: ignore [assignment]
145+
response = humanloop_client.prompts.call( # type: ignore [assignment]
134146
path=test_file.path, messages=[{"role": "user", "content": "Testing"}]
135147
)
136148
assert response is not None
137149
elif test_file.type == "agent":
138-
response = humanloop_test_client.agents.call( # type: ignore [assignment]
150+
response = humanloop_client.agents.call( # type: ignore [assignment]
139151
path=test_file.path, messages=[{"role": "user", "content": "Testing"}]
140152
)
141153
assert response is not None
142154

143155
# Test with invalid path
144156
with pytest.raises(HumanloopRuntimeError):
157+
sub_client: Union[PromptsClient, AgentsClient]
145158
match test_file.type:
146159
case "prompt":
147-
sub_agent = humanloop_test_client.agents
160+
sub_client = humanloop_client.prompts
148161
case "agent":
149-
sub_agent = humanloop_test_client.agents
162+
sub_client = humanloop_client.agents
150163
case _:
151164
raise ValueError(f"Invalid file type: {test_file.type}")
152-
sub_agent.call(path="invalid/path")
165+
sub_client.call(path="invalid/path")
153166

154167

155168
def test_overload_log_with_local_files(
156-
humanloop_test_client: Humanloop,
169+
get_humanloop_client: GetHumanloopClientFn,
157170
test_file_structure: List[SyncableFile],
158171
sdk_test_dir: str,
159172
):
@@ -169,7 +182,8 @@ def test_overload_log_with_local_files(
169182
:param cleanup_local_files: Fixture to clean up local files after test
170183
"""
171184
# First pull the files locally
172-
humanloop_test_client.pull()
185+
humanloop_client = get_humanloop_client(use_local_files=True)
186+
humanloop_client.pull()
173187

174188
# Test using the pulled files
175189
test_file = test_file_structure[0] # Use the first test file
@@ -182,34 +196,36 @@ def test_overload_log_with_local_files(
182196

183197
# Test log with pulled file
184198
if test_file.type == "prompt":
185-
response = humanloop_test_client.prompts.log( # type: ignore [assignment]
199+
response = humanloop_client.prompts.log( # type: ignore [assignment]
186200
path=test_file.path, messages=[{"role": "user", "content": "Testing"}], output="Test response"
187201
)
188202
assert response is not None
189203
elif test_file.type == "agent":
190-
response = humanloop_test_client.agents.log( # type: ignore [assignment]
204+
response = humanloop_client.agents.log( # type: ignore [assignment]
191205
path=test_file.path, messages=[{"role": "user", "content": "Testing"}], output="Test response"
192206
)
193207
assert response is not None
194208

195209
# Test with invalid path
196210
with pytest.raises(HumanloopRuntimeError):
197211
if test_file.type == "prompt":
198-
humanloop_test_client.prompts.log(
212+
humanloop_client.prompts.log(
199213
path=f"{sdk_test_dir}/invalid/path",
200214
messages=[{"role": "user", "content": "Testing"}],
201215
output="Test response",
202216
)
203217
elif test_file.type == "agent":
204-
humanloop_test_client.agents.log(
218+
humanloop_client.agents.log(
205219
path=f"{sdk_test_dir}/invalid/path",
206220
messages=[{"role": "user", "content": "Testing"}],
207221
output="Test response",
208222
)
209223

210224

211225
def test_overload_version_environment_handling(
212-
humanloop_test_client: Humanloop, test_file_structure: List[SyncableFile]
226+
caplog: pytest.LogCaptureFixture,
227+
get_humanloop_client: GetHumanloopClientFn,
228+
test_file_structure: List[SyncableFile],
213229
):
214230
"""Test that overload_with_local_files correctly handles version_id and environment parameters.
215231
@@ -219,7 +235,8 @@ def test_overload_version_environment_handling(
219235
3. Test that version_id/environment parameters cause remote usage with warning
220236
"""
221237
# First pull the files locally
222-
humanloop_test_client.pull()
238+
humanloop_client = get_humanloop_client(use_local_files=True)
239+
humanloop_client.pull()
223240

224241
# Test using the pulled files
225242
test_file = test_file_structure[0] # Use the first test file
@@ -231,47 +248,46 @@ def test_overload_version_environment_handling(
231248
assert local_path.parent.exists(), f"Expected directory at {local_path.parent}"
232249

233250
# Test with version_id - should use remote with warning
234-
with pytest.warns(UserWarning, match="Ignoring local file.*as version_id or environment was specified"):
251+
# Check that the warning was logged
252+
with caplog.at_level(level=logging.WARNING, logger="humanloop.sdk"):
235253
if test_file.type == "prompt":
236-
response = humanloop_test_client.prompts.call( # type: ignore [assignment]
254+
response = humanloop_client.prompts.call(
237255
path=test_file.path,
238256
version_id=test_file.version_id,
239257
messages=[{"role": "user", "content": "Testing"}],
240258
)
241259
elif test_file.type == "agent":
242-
response = humanloop_test_client.agents.call( # type: ignore [assignment]
260+
response = humanloop_client.agents.call( # type: ignore [assignment]
243261
path=test_file.path,
244262
version_id=test_file.version_id,
245263
messages=[{"role": "user", "content": "Testing"}],
246264
)
247265
assert response is not None
266+
assert any(["Ignoring local file" in record.message for record in caplog.records])
248267

249268
# Test with environment - should use remote with warning
250-
with pytest.warns(UserWarning, match="Ignoring local file.*as version_id or environment was specified"):
251-
if test_file.type == "prompt":
252-
response = humanloop_test_client.prompts.call( # type: ignore [assignment]
253-
path=test_file.path, environment="production", messages=[{"role": "user", "content": "Testing"}]
254-
)
255-
elif test_file.type == "agent":
256-
response = humanloop_test_client.agents.call( # type: ignore [assignment]
257-
path=test_file.path, environment="production", messages=[{"role": "user", "content": "Testing"}]
258-
)
259-
assert response is not None
269+
if test_file.type == "prompt":
270+
response = humanloop_client.prompts.call( # type: ignore [assignment]
271+
path=test_file.path, environment="production", messages=[{"role": "user", "content": "Testing"}]
272+
)
273+
elif test_file.type == "agent":
274+
response = humanloop_client.agents.call( # type: ignore [assignment]
275+
path=test_file.path, environment="production", messages=[{"role": "user", "content": "Testing"}]
276+
)
277+
assert response is not None
260278

261-
# Test with both version_id and environment - should use remote with warning
262-
with pytest.warns(UserWarning, match="Ignoring local file.*as version_id or environment was specified"):
263-
if test_file.type == "prompt":
264-
response = humanloop_test_client.prompts.call( # type: ignore [assignment]
265-
path=test_file.path,
266-
version_id=test_file.version_id,
267-
environment="staging",
268-
messages=[{"role": "user", "content": "Testing"}],
269-
)
270-
elif test_file.type == "agent":
271-
response = humanloop_test_client.agents.call( # type: ignore [assignment]
272-
path=test_file.path,
273-
version_id=test_file.version_id,
274-
environment="staging",
275-
messages=[{"role": "user", "content": "Testing"}],
276-
)
277-
assert response is not None
279+
if test_file.type == "prompt":
280+
response = humanloop_client.prompts.call( # type: ignore [assignment]
281+
path=test_file.path,
282+
version_id=test_file.version_id,
283+
environment="staging",
284+
messages=[{"role": "user", "content": "Testing"}],
285+
)
286+
elif test_file.type == "agent":
287+
response = humanloop_client.agents.call( # type: ignore [assignment]
288+
path=test_file.path,
289+
version_id=test_file.version_id,
290+
environment="staging",
291+
messages=[{"role": "user", "content": "Testing"}],
292+
)
293+
assert response is not None

0 commit comments

Comments
 (0)