Skip to content

Commit 7dd6c5b

Browse files
committed
refactor: use callable function as fixture instead of passing in the client directly
1 parent 8afe4c4 commit 7dd6c5b

File tree

6 files changed

+157
-145
lines changed

6 files changed

+157
-145
lines changed

src/humanloop/overload.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ def _overload(self, function_name: str, **kwargs) -> PromptCallResponse:
206206
normalized_path = sync_client._normalize_path(kwargs["path"])
207207

208208
if use_remote:
209+
# Don't allow user to specify path + version_id/environment because it's ambiguous
209210
raise HumanloopRuntimeError(
210211
f"Cannot use local file for `{normalized_path}` as version_id or environment was specified. "
211212
"Please either remove version_id/environment to use local files, or set use_local_files=False to use remote files."

tests/custom/conftest.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
from typing import Generator
2-
import typing
32
import os
43
from dotenv import load_dotenv
54
from unittest.mock import MagicMock
65

76
import pytest
7+
from humanloop.client import Humanloop
88
from humanloop.otel.exporter import HumanloopSpanExporter
99
from humanloop.otel.processor import HumanloopSpanProcessor
1010
from openai.types.chat.chat_completion_message_param import ChatCompletionMessageParam
@@ -21,9 +21,6 @@
2121
from opentelemetry.trace import Tracer
2222
from tests.custom.types import GetHumanloopClientFn
2323

24-
if typing.TYPE_CHECKING:
25-
from humanloop.client import Humanloop
26-
2724

2825
@pytest.fixture(scope="function")
2926
def opentelemetry_test_provider() -> TracerProvider:
@@ -82,7 +79,6 @@ def opentelemetry_test_configuration(
8279
instrumentor.uninstrument()
8380

8481

85-
8682
@pytest.fixture(scope="session")
8783
def get_humanloop_client() -> GetHumanloopClientFn:
8884
load_dotenv()
@@ -92,6 +88,7 @@ def get_humanloop_client() -> GetHumanloopClientFn:
9288
def _get_humanloop_test_client(use_local_files: bool = False) -> Humanloop:
9389
return Humanloop(
9490
api_key=os.getenv("HUMANLOOP_API_KEY"),
91+
base_url="http://localhost:80/v5",
9592
use_local_files=use_local_files,
9693
)
9794

tests/custom/integration/conftest.py

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from typing import Callable
21
from contextlib import contextmanager, redirect_stdout
32
from dataclasses import dataclass
43
import os
@@ -10,6 +9,7 @@
109
import pytest
1110
import dotenv
1211
from humanloop.client import Humanloop
12+
from tests.custom.types import GetHumanloopClientFn
1313

1414

1515
@dataclass
@@ -18,7 +18,6 @@ class TestIdentifiers:
1818
file_path: str
1919

2020

21-
2221
@pytest.fixture()
2322
def capture_stdout() -> ContextManager[TextIO]:
2423
@contextmanager
@@ -39,31 +38,32 @@ def openai_key() -> str:
3938

4039

4140
@pytest.fixture(scope="function")
42-
def sdk_test_dir(humanloop_test_client: Humanloop) -> Generator[str, None, None]:
41+
def sdk_test_dir(get_humanloop_client: GetHumanloopClientFn) -> Generator[str, None, None]:
42+
humanloop_client = get_humanloop_client()
4343
def cleanup_directory(directory_id: str):
44-
directory_response = humanloop_test_client.directories.get(id=directory_id)
44+
directory_response = humanloop_client.directories.get(id=directory_id)
4545
for subdirectory in directory_response.subdirectories:
4646
cleanup_directory(subdirectory.id)
4747
for file in directory_response.files:
4848
match file.type:
4949
case "prompt":
50-
humanloop_test_client.prompts.delete(id=file.id)
50+
humanloop_client.prompts.delete(id=file.id)
5151
case "agent":
52-
humanloop_test_client.agents.delete(id=file.id)
52+
humanloop_client.agents.delete(id=file.id)
5353
case "dataset":
54-
humanloop_test_client.datasets.delete(id=file.id)
54+
humanloop_client.datasets.delete(id=file.id)
5555
case "evaluator":
56-
humanloop_test_client.evaluators.delete(id=file.id)
56+
humanloop_client.evaluators.delete(id=file.id)
5757
case "flow":
58-
humanloop_test_client.flows.delete(id=file.id)
58+
humanloop_client.flows.delete(id=file.id)
5959
case _:
6060
raise ValueError(f"Unknown file type: {file.type}")
61-
humanloop_test_client.directories.delete(id=directory_response.id)
61+
humanloop_client.directories.delete(id=directory_response.id)
6262

6363
path = f"SDK_INTEGRATION_TEST_{uuid.uuid4()}"
6464
response = None
6565
try:
66-
response = humanloop_test_client.directories.create(path=path)
66+
response = humanloop_client.directories.create(path=path)
6767
yield response.path
6868
except Exception as e:
6969
pytest.fail(f"Failed to create directory {path}: {e}")
@@ -93,10 +93,11 @@ def test_prompt_config() -> dict[str, Any]:
9393

9494

9595
@pytest.fixture(scope="function")
96-
def eval_dataset(humanloop_test_client: Humanloop, sdk_test_dir: str) -> Generator[TestIdentifiers, None, None]:
96+
def eval_dataset(get_humanloop_client: GetHumanloopClientFn, sdk_test_dir: str) -> Generator[TestIdentifiers, None, None]:
97+
humanloop_client = get_humanloop_client()
9798
dataset_path = f"{sdk_test_dir}/eval_dataset"
9899
try:
99-
response = humanloop_test_client.datasets.upsert(
100+
response = humanloop_client.datasets.upsert(
100101
path=dataset_path,
101102
datapoints=[
102103
{
@@ -117,34 +118,36 @@ def eval_dataset(humanloop_test_client: Humanloop, sdk_test_dir: str) -> Generat
117118
],
118119
)
119120
yield TestIdentifiers(file_id=response.id, file_path=response.path)
120-
humanloop_test_client.datasets.delete(id=response.id)
121+
humanloop_client.datasets.delete(id=response.id)
121122
except Exception as e:
122123
pytest.fail(f"Failed to create dataset {dataset_path}: {e}")
123124

124125

125126
@pytest.fixture(scope="function")
126127
def eval_prompt(
127-
humanloop_test_client: Humanloop, sdk_test_dir: str, openai_key: str, test_prompt_config: dict[str, Any]
128+
get_humanloop_client: GetHumanloopClientFn, sdk_test_dir: str, openai_key: str, test_prompt_config: dict[str, Any]
128129
) -> Generator[TestIdentifiers, None, None]:
130+
humanloop_client = get_humanloop_client()
129131
prompt_path = f"{sdk_test_dir}/eval_prompt"
130132
try:
131-
response = humanloop_test_client.prompts.upsert(
133+
response = humanloop_client.prompts.upsert(
132134
path=prompt_path,
133135
**test_prompt_config,
134136
)
135137
yield TestIdentifiers(file_id=response.id, file_path=response.path)
136-
humanloop_test_client.prompts.delete(id=response.id)
138+
humanloop_client.prompts.delete(id=response.id)
137139
except Exception as e:
138140
pytest.fail(f"Failed to create prompt {prompt_path}: {e}")
139141

140142

141143
@pytest.fixture(scope="function")
142144
def output_not_null_evaluator(
143-
humanloop_test_client: Humanloop, sdk_test_dir: str
145+
get_humanloop_client: GetHumanloopClientFn, sdk_test_dir: str
144146
) -> Generator[TestIdentifiers, None, None]:
147+
humanloop_client = get_humanloop_client()
145148
evaluator_path = f"{sdk_test_dir}/output_not_null_evaluator"
146149
try:
147-
response = humanloop_test_client.evaluators.upsert(
150+
response = humanloop_client.evaluators.upsert(
148151
path=evaluator_path,
149152
spec={
150153
"arguments_type": "target_required",
@@ -157,14 +160,15 @@ def output_not_null(log: dict) -> bool:
157160
},
158161
)
159162
yield TestIdentifiers(file_id=response.id, file_path=response.path)
160-
humanloop_test_client.evaluators.delete(id=response.id)
163+
humanloop_client.evaluators.delete(id=response.id)
161164
except Exception as e:
162165
pytest.fail(f"Failed to create evaluator {evaluator_path}: {e}")
163166

164167

165168
@pytest.fixture(scope="function")
166-
def id_for_staging_environment(humanloop_test_client: Humanloop, eval_prompt: TestIdentifiers) -> str:
167-
response = humanloop_test_client.prompts.list_environments(id=eval_prompt.file_id)
169+
def id_for_staging_environment(get_humanloop_client: GetHumanloopClientFn, eval_prompt: TestIdentifiers) -> str:
170+
humanloop_client = get_humanloop_client()
171+
response = humanloop_client.prompts.list_environments(id=eval_prompt.file_id)
168172
for environment in response:
169173
if environment.name == "staging":
170174
return environment.id

tests/custom/integration/test_decorators.py

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,27 @@
22
from typing import Any
33

44
from openai import OpenAI
5-
from humanloop.client import Humanloop
5+
from tests.custom.integration.conftest import GetHumanloopClientFn
66

77

88
def test_prompt_decorator(
9-
humanloop_test_client: Humanloop,
9+
get_humanloop_client: GetHumanloopClientFn,
1010
sdk_test_dir: str,
1111
test_prompt_config: dict[str, Any],
1212
openai_key: str,
1313
):
1414
try:
15+
humanloop_client = get_humanloop_client()
1516
prompt_path = f"{sdk_test_dir}/test_prompt"
16-
prompt_response = humanloop_test_client.prompts.upsert(
17+
prompt_response = humanloop_client.prompts.upsert(
1718
path=prompt_path,
1819
**test_prompt_config,
1920
)
2021

21-
prompt_versions_response = humanloop_test_client.prompts.list_versions(id=prompt_response.id)
22+
prompt_versions_response = humanloop_client.prompts.list_versions(id=prompt_response.id)
2223
assert len(prompt_versions_response.records) == 1
2324

24-
@humanloop_test_client.prompt(path=prompt_path)
25+
@humanloop_client.prompt(path=prompt_path)
2526
def my_prompt(question: str) -> str:
2627
openai_client = OpenAI(api_key=openai_key)
2728

@@ -36,26 +37,27 @@ def my_prompt(question: str) -> str:
3637
assert "paris" in my_prompt("What is the capital of the France?").lower()
3738

3839
time.sleep(5)
39-
prompt_versions_response = humanloop_test_client.prompts.list_versions(id=prompt_response.id)
40+
prompt_versions_response = humanloop_client.prompts.list_versions(id=prompt_response.id)
4041
assert len(prompt_versions_response.records) == 2
4142

42-
logs_response = humanloop_test_client.logs.list(file_id=prompt_response.id, page=1, size=50)
43+
logs_response = humanloop_client.logs.list(file_id=prompt_response.id, page=1, size=50)
4344

4445
assert logs_response.items is not None and len(logs_response.items) == 1
4546
finally:
46-
humanloop_test_client.prompts.delete(id=prompt_response.id)
47+
humanloop_client.prompts.delete(id=prompt_response.id)
4748

4849

4950
def test_call_prompt_in_flow_decorator(
50-
humanloop_test_client: Humanloop,
51+
get_humanloop_client: GetHumanloopClientFn,
5152
sdk_test_dir: str,
5253
openai_key: str,
5354
):
5455
try:
56+
humanloop_client = get_humanloop_client()
5557

56-
@humanloop_test_client.flow(path=f"{sdk_test_dir}/test_flow")
58+
@humanloop_client.flow(path=f"{sdk_test_dir}/test_flow")
5759
def my_flow(question: str) -> str:
58-
response = humanloop_test_client.prompts.call(
60+
response = humanloop_client.prompts.call(
5961
path=f"{sdk_test_dir}/test_prompt",
6062
prompt={
6163
"provider": "openai",
@@ -71,83 +73,81 @@ def my_flow(question: str) -> str:
7173

7274
assert "paris" in my_flow("What is the capital of the France?").lower()
7375
time.sleep(5)
74-
prompt_response = humanloop_test_client.files.retrieve_by_path(path=f"{sdk_test_dir}/test_prompt")
76+
prompt_response = humanloop_client.files.retrieve_by_path(path=f"{sdk_test_dir}/test_prompt")
7577
assert prompt_response is not None
76-
prompt_logs_response = humanloop_test_client.logs.list(file_id=prompt_response.id, page=1, size=50)
78+
prompt_logs_response = humanloop_client.logs.list(file_id=prompt_response.id, page=1, size=50)
7779
assert prompt_logs_response.items is not None and len(prompt_logs_response.items) == 1
7880
prompt_log = prompt_logs_response.items[0]
7981

80-
flow_response = humanloop_test_client.files.retrieve_by_path(path=f"{sdk_test_dir}/test_flow")
82+
flow_response = humanloop_client.files.retrieve_by_path(path=f"{sdk_test_dir}/test_flow")
8183
assert flow_response is not None
82-
flow_logs_response = humanloop_test_client.logs.list(file_id=flow_response.id, page=1, size=50)
84+
flow_logs_response = humanloop_client.logs.list(file_id=flow_response.id, page=1, size=50)
8385
assert flow_logs_response.items is not None and len(flow_logs_response.items) == 1
8486
flow_log = flow_logs_response.items[0]
8587
assert prompt_log.trace_parent_id == flow_log.id
8688
finally:
87-
flow_response = humanloop_test_client.files.retrieve_by_path(path=f"{sdk_test_dir}/test_flow")
89+
flow_response = humanloop_client.files.retrieve_by_path(path=f"{sdk_test_dir}/test_flow")
8890
if flow_response is not None:
89-
humanloop_test_client.flows.delete(id=flow_response.id)
90-
prompt_response = humanloop_test_client.files.retrieve_by_path(path=f"{sdk_test_dir}/test_prompt")
91+
humanloop_client.flows.delete(id=flow_response.id)
92+
prompt_response = humanloop_client.files.retrieve_by_path(path=f"{sdk_test_dir}/test_prompt")
9193
if prompt_response is not None:
92-
humanloop_test_client.prompts.delete(id=prompt_response.id)
94+
humanloop_client.prompts.delete(id=prompt_response.id)
9395

9496

9597
def test_flow_decorator_logs_exceptions(
96-
humanloop_test_client: Humanloop,
98+
get_humanloop_client: GetHumanloopClientFn,
9799
sdk_test_dir: str,
98100
):
99101
try:
102+
humanloop_client = get_humanloop_client()
100103

101-
@humanloop_test_client.flow(path=f"{sdk_test_dir}/test_flow_log_error")
104+
@humanloop_client.flow(path=f"{sdk_test_dir}/test_flow_log_error")
102105
def my_flow(question: str) -> str:
103106
raise ValueError("This is a test exception")
104107

105108
my_flow("test")
106109

107110
time.sleep(5)
108111

109-
flow_response = humanloop_test_client.files.retrieve_by_path(path=f"{sdk_test_dir}/test_flow_log_error")
112+
flow_response = humanloop_client.files.retrieve_by_path(path=f"{sdk_test_dir}/test_flow_log_error")
110113
assert flow_response is not None
111-
flow_logs_response = humanloop_test_client.logs.list(file_id=flow_response.id, page=1, size=50)
114+
flow_logs_response = humanloop_client.logs.list(file_id=flow_response.id, page=1, size=50)
112115
assert flow_logs_response.items is not None and len(flow_logs_response.items) == 1
113116
flow_log = flow_logs_response.items[0]
114117
assert flow_log.error is not None
115118
assert flow_log.output is None
116119

117120
finally:
118-
flow_response = humanloop_test_client.files.retrieve_by_path(path=f"{sdk_test_dir}/test_flow_log_error")
121+
flow_response = humanloop_client.files.retrieve_by_path(path=f"{sdk_test_dir}/test_flow_log_error")
119122
if flow_response is not None:
120-
humanloop_test_client.flows.delete(id=flow_response.id)
123+
humanloop_client.flows.delete(id=flow_response.id)
121124

122125

123126
def test_flow_decorator_populates_output_message(
124-
humanloop_test_client: Humanloop,
127+
get_humanloop_client: GetHumanloopClientFn,
125128
sdk_test_dir: str,
126129
):
127130
try:
131+
humanloop_client = get_humanloop_client()
128132

129-
@humanloop_test_client.flow(path=f"{sdk_test_dir}/test_flow_log_output_message")
133+
@humanloop_client.flow(path=f"{sdk_test_dir}/test_flow_log_output_message")
130134
def my_flow(question: str) -> dict[str, Any]:
131135
return {"role": "user", "content": question}
132136

133137
assert "france" in my_flow("What is the capital of the France?")["content"].lower()
134138

135139
time.sleep(5)
136140

137-
flow_response = humanloop_test_client.files.retrieve_by_path(
138-
path=f"{sdk_test_dir}/test_flow_log_output_message"
139-
)
141+
flow_response = humanloop_client.files.retrieve_by_path(path=f"{sdk_test_dir}/test_flow_log_output_message")
140142
assert flow_response is not None
141-
flow_logs_response = humanloop_test_client.logs.list(file_id=flow_response.id, page=1, size=50)
143+
flow_logs_response = humanloop_client.logs.list(file_id=flow_response.id, page=1, size=50)
142144
assert flow_logs_response.items is not None and len(flow_logs_response.items) == 1
143145
flow_log = flow_logs_response.items[0]
144146
assert flow_log.output_message is not None
145147
assert flow_log.output is None
146148
assert flow_log.error is None
147149

148150
finally:
149-
flow_response = humanloop_test_client.files.retrieve_by_path(
150-
path=f"{sdk_test_dir}/test_flow_log_output_message"
151-
)
151+
flow_response = humanloop_client.files.retrieve_by_path(path=f"{sdk_test_dir}/test_flow_log_output_message")
152152
if flow_response is not None:
153-
humanloop_test_client.flows.delete(id=flow_response.id)
153+
humanloop_client.flows.delete(id=flow_response.id)

0 commit comments

Comments
 (0)