Skip to content

Commit 1671682

Browse files
Aryan SrivastavaAryan Srivastava
authored andcommitted
fix(client): skip pre-encoding for query params to prevent double url encoding
1 parent 50e8f91 commit 1671682

2 files changed

Lines changed: 95 additions & 1 deletion

File tree

langfuse/_client/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3744,7 +3744,7 @@ def _url_encode(self, url: str, *, is_url_param: Optional[bool] = False) -> str:
37443744
# “%”, “?”, “#”, “|”, … in query/path parts). Re-quoting here would
37453745
# double-encode, so we skip when the value is about to be sent straight
37463746
# to httpx (`is_url_param=True`) and the installed version is ≥ 0.28.
3747-
if is_url_param and Version(httpx.__version__) >= Version("0.28.0"):
3747+
if is_url_param:
37483748
return url
37493749

37503750
# urllib.parse.quote does not escape slashes "/" by default; we need to add safe="" to force escaping
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import pytest
2+
from unittest.mock import patch, MagicMock
3+
from langfuse import Langfuse
4+
import httpx
5+
6+
def test_dataset_url_encoding_in_requests():
7+
with patch("httpx.Client.send") as mock_send:
8+
# Mock response for get
9+
mock_response = MagicMock(spec=httpx.Response)
10+
mock_response.status_code = 200
11+
mock_response.json.return_value = {
12+
"id": "dataset-id",
13+
"name": "my/dataset",
14+
"description": "test",
15+
"metadata": {},
16+
"projectId": "project-id",
17+
"createdAt": "2026-01-01T00:00:00Z",
18+
"updatedAt": "2026-01-01T00:00:00Z"
19+
}
20+
mock_response.headers = httpx.Headers()
21+
22+
# Mock response for list
23+
mock_items_response = MagicMock(spec=httpx.Response)
24+
mock_items_response.status_code = 200
25+
mock_items_response.json.return_value = {
26+
"data": [],
27+
"meta": {"page": 1, "limit": 50, "totalItems": 0, "totalPages": 1}
28+
}
29+
30+
# Mock response for run
31+
mock_run_response = MagicMock(spec=httpx.Response)
32+
mock_run_response.status_code = 200
33+
mock_run_response.json.return_value = {
34+
"id": "run-id",
35+
"name": "my/run",
36+
"datasetName": "my/dataset",
37+
"datasetId": "dataset-id",
38+
"createdAt": "2026-01-01T00:00:00Z",
39+
"updatedAt": "2026-01-01T00:00:00Z",
40+
"metadata": {},
41+
"datasetRunItems": []
42+
}
43+
44+
# Mock response for runs
45+
mock_runs_response = MagicMock(spec=httpx.Response)
46+
mock_runs_response.status_code = 200
47+
mock_runs_response.json.return_value = {
48+
"data": [],
49+
"meta": {"page": 1, "limit": 50, "totalItems": 0, "totalPages": 1}
50+
}
51+
52+
# Mock response for delete
53+
mock_delete_response = MagicMock(spec=httpx.Response)
54+
mock_delete_response.status_code = 200
55+
mock_delete_response.json.return_value = {
56+
"message": "Dataset run deleted successfully"
57+
}
58+
59+
def side_effect(request, *args, **kwargs):
60+
url_str = str(request.url)
61+
if "dataset-items" in url_str:
62+
return mock_items_response
63+
elif "/runs/" in url_str:
64+
if request.method == "DELETE":
65+
return mock_delete_response
66+
return mock_run_response
67+
elif "/runs" in url_str:
68+
return mock_runs_response
69+
return mock_response
70+
71+
mock_send.side_effect = side_effect
72+
73+
langfuse = Langfuse(public_key="pk-test", secret_key="sk-test", base_url="http://localhost:3000")
74+
75+
# 1. get_dataset
76+
langfuse.get_dataset("my/dataset")
77+
langfuse.get_dataset("my dataset")
78+
79+
# 2. get_dataset_run
80+
langfuse.get_dataset_run(dataset_name="my/dataset", run_name="my/run")
81+
langfuse.get_dataset_run(dataset_name="my dataset", run_name="my run")
82+
83+
# 3. get_dataset_runs
84+
langfuse.get_dataset_runs(dataset_name="my/dataset")
85+
langfuse.get_dataset_runs(dataset_name="my dataset")
86+
87+
# 4. delete_dataset_run
88+
langfuse.delete_dataset_run(dataset_name="my/dataset", run_name="my/run")
89+
langfuse.delete_dataset_run(dataset_name="my dataset", run_name="my run")
90+
91+
# Collect all requested URLs
92+
requested_urls = [str(call[0][0].url) for call in mock_send.call_args_list]
93+
for url in requested_urls:
94+
print(url)

0 commit comments

Comments
 (0)