fix(client): honor page_size in get_all when page is not passed (#6900)
This commit is contained in:
@@ -404,10 +404,10 @@ export default class MemoryClient {
|
|||||||
...(filters && { filters }),
|
...(filters && { filters }),
|
||||||
};
|
};
|
||||||
|
|
||||||
let url = `${this.host}/v3/memories/`;
|
const queryParams: string[] = [];
|
||||||
if (page && pageSize) {
|
if (page !== undefined) queryParams.push(`page=${page}`);
|
||||||
url += `?page=${page}&page_size=${pageSize}`;
|
if (pageSize !== undefined) queryParams.push(`page_size=${pageSize}`);
|
||||||
}
|
const url = `${this.host}/v3/memories/${queryParams.length ? `?${queryParams.join("&")}` : ""}`;
|
||||||
|
|
||||||
const response = await this._fetchWithErrorHandling(url, {
|
const response = await this._fetchWithErrorHandling(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|||||||
@@ -373,3 +373,56 @@ describe("MemoryClient - getAll() entity param rejection", () => {
|
|||||||
expect(getFetchBody(call!).show_expired).toBe(true);
|
expect(getFetchBody(call!).show_expired).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("MemoryClient - getAll() page/pageSize query params", () => {
|
||||||
|
test("sends no query params when neither page nor pageSize is provided", async () => {
|
||||||
|
const extra = new Map<string, { status: number; body: unknown }>();
|
||||||
|
extra.set("/v3/memories/", { status: 200, body: { results: [] } });
|
||||||
|
const mock = setupMockFetch(extra);
|
||||||
|
|
||||||
|
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||||
|
await client.getAll({ filters: { user_id: "u1" } });
|
||||||
|
|
||||||
|
const call = findFetchCall(mock, "/v3/memories/", "POST");
|
||||||
|
expect(call![0]).not.toContain("?");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("sends page alone as a query param", async () => {
|
||||||
|
const extra = new Map<string, { status: number; body: unknown }>();
|
||||||
|
extra.set("/v3/memories/", { status: 200, body: { results: [] } });
|
||||||
|
const mock = setupMockFetch(extra);
|
||||||
|
|
||||||
|
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||||
|
await client.getAll({ filters: { user_id: "u1" }, page: 2 });
|
||||||
|
|
||||||
|
const call = findFetchCall(mock, "/v3/memories/", "POST");
|
||||||
|
expect(call![0]).toContain("?page=2");
|
||||||
|
expect(call![0]).not.toContain("page_size");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("sends pageSize alone as a query param", async () => {
|
||||||
|
const extra = new Map<string, { status: number; body: unknown }>();
|
||||||
|
extra.set("/v3/memories/", { status: 200, body: { results: [] } });
|
||||||
|
const mock = setupMockFetch(extra);
|
||||||
|
|
||||||
|
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||||
|
await client.getAll({ filters: { user_id: "u1" }, pageSize: 50 });
|
||||||
|
|
||||||
|
const call = findFetchCall(mock, "/v3/memories/", "POST");
|
||||||
|
expect(call![0]).toContain("?page_size=50");
|
||||||
|
expect(call![0]).not.toContain("?page=50");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("sends both page and pageSize when provided, including falsy 0 values", async () => {
|
||||||
|
const extra = new Map<string, { status: number; body: unknown }>();
|
||||||
|
extra.set("/v3/memories/", { status: 200, body: { results: [] } });
|
||||||
|
const mock = setupMockFetch(extra);
|
||||||
|
|
||||||
|
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||||
|
await client.getAll({ filters: { user_id: "u1" }, page: 0, pageSize: 0 });
|
||||||
|
|
||||||
|
const call = findFetchCall(mock, "/v3/memories/", "POST");
|
||||||
|
expect(call![0]).toContain("page=0");
|
||||||
|
expect(call![0]).toContain("page_size=0");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
+4
-10
@@ -275,11 +275,8 @@ class MemoryClient:
|
|||||||
kwargs = {**(options.model_dump(exclude_unset=True) if options else {}), **kwargs}
|
kwargs = {**(options.model_dump(exclude_unset=True) if options else {}), **kwargs}
|
||||||
params = self._prepare_params(kwargs)
|
params = self._prepare_params(kwargs)
|
||||||
|
|
||||||
if "page" in params and "page_size" in params:
|
query_params = {key: params.pop(key) for key in ("page", "page_size") if key in params}
|
||||||
query_params = {
|
if query_params:
|
||||||
"page": params.pop("page"),
|
|
||||||
"page_size": params.pop("page_size"),
|
|
||||||
}
|
|
||||||
response = self.client.post("/v3/memories/", json=params, params=query_params)
|
response = self.client.post("/v3/memories/", json=params, params=query_params)
|
||||||
else:
|
else:
|
||||||
response = self.client.post("/v3/memories/", json=params)
|
response = self.client.post("/v3/memories/", json=params)
|
||||||
@@ -1198,11 +1195,8 @@ class AsyncMemoryClient:
|
|||||||
kwargs = {**(options.model_dump(exclude_unset=True) if options else {}), **kwargs}
|
kwargs = {**(options.model_dump(exclude_unset=True) if options else {}), **kwargs}
|
||||||
params = self._prepare_params(kwargs)
|
params = self._prepare_params(kwargs)
|
||||||
|
|
||||||
if "page" in params and "page_size" in params:
|
query_params = {key: params.pop(key) for key in ("page", "page_size") if key in params}
|
||||||
query_params = {
|
if query_params:
|
||||||
"page": params.pop("page"),
|
|
||||||
"page_size": params.pop("page_size"),
|
|
||||||
}
|
|
||||||
response = await self.async_client.post("/v3/memories/", json=params, params=query_params)
|
response = await self.async_client.post("/v3/memories/", json=params, params=query_params)
|
||||||
else:
|
else:
|
||||||
response = await self.async_client.post("/v3/memories/", json=params)
|
response = await self.async_client.post("/v3/memories/", json=params)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import httpx
|
|||||||
import pytest
|
import pytest
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
from mem0.client.main import AsyncMemoryClient
|
||||||
from mem0.client.types import GetAllMemoryOptions, SearchMemoryOptions
|
from mem0.client.types import GetAllMemoryOptions, SearchMemoryOptions
|
||||||
|
|
||||||
|
|
||||||
@@ -181,6 +182,150 @@ class TestGetAllTypedOptionsParity:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestGetAllPageSizeQueryParams:
|
||||||
|
"""MEM-6227: page and page_size must reach the API as query params independently."""
|
||||||
|
|
||||||
|
def test_get_all_page_size_alone_lands_in_query_params(self, mock_memory_client):
|
||||||
|
"""get_all(page_size=...) without page should send page_size as a query param."""
|
||||||
|
mock_response = MagicMock()
|
||||||
|
mock_response.json.return_value = {"results": []}
|
||||||
|
mock_response.raise_for_status.return_value = None
|
||||||
|
mock_memory_client.client.post.return_value = mock_response
|
||||||
|
|
||||||
|
mock_memory_client.get_all(filters={"user_id": "u1"}, page_size=50)
|
||||||
|
|
||||||
|
mock_memory_client.client.post.assert_called_once_with(
|
||||||
|
"/v3/memories/",
|
||||||
|
json={"filters": {"user_id": "u1"}},
|
||||||
|
params={"page_size": 50},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_get_all_page_alone_lands_in_query_params(self, mock_memory_client):
|
||||||
|
"""get_all(page=...) without page_size should send page as a query param."""
|
||||||
|
mock_response = MagicMock()
|
||||||
|
mock_response.json.return_value = {"results": []}
|
||||||
|
mock_response.raise_for_status.return_value = None
|
||||||
|
mock_memory_client.client.post.return_value = mock_response
|
||||||
|
|
||||||
|
mock_memory_client.get_all(filters={"user_id": "u1"}, page=2)
|
||||||
|
|
||||||
|
mock_memory_client.client.post.assert_called_once_with(
|
||||||
|
"/v3/memories/",
|
||||||
|
json={"filters": {"user_id": "u1"}},
|
||||||
|
params={"page": 2},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_get_all_page_and_page_size_together(self, mock_memory_client):
|
||||||
|
"""get_all(page=..., page_size=...) should send both as query params."""
|
||||||
|
mock_response = MagicMock()
|
||||||
|
mock_response.json.return_value = {"results": []}
|
||||||
|
mock_response.raise_for_status.return_value = None
|
||||||
|
mock_memory_client.client.post.return_value = mock_response
|
||||||
|
|
||||||
|
mock_memory_client.get_all(filters={"user_id": "u1"}, page=2, page_size=50)
|
||||||
|
|
||||||
|
mock_memory_client.client.post.assert_called_once_with(
|
||||||
|
"/v3/memories/",
|
||||||
|
json={"filters": {"user_id": "u1"}},
|
||||||
|
params={"page": 2, "page_size": 50},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_get_all_neither_page_nor_page_size_sends_no_query_params(self, mock_memory_client):
|
||||||
|
"""get_all() without page or page_size should not pass a params kwarg."""
|
||||||
|
mock_response = MagicMock()
|
||||||
|
mock_response.json.return_value = {"results": []}
|
||||||
|
mock_response.raise_for_status.return_value = None
|
||||||
|
mock_memory_client.client.post.return_value = mock_response
|
||||||
|
|
||||||
|
mock_memory_client.get_all(filters={"user_id": "u1"})
|
||||||
|
|
||||||
|
mock_memory_client.client.post.assert_called_once_with(
|
||||||
|
"/v3/memories/",
|
||||||
|
json={"filters": {"user_id": "u1"}},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_async_get_all_page_size_alone_lands_in_query_params(self):
|
||||||
|
asyncio.run(self._assert_async_get_all_page_size_alone())
|
||||||
|
|
||||||
|
async def _assert_async_get_all_page_size_alone(self):
|
||||||
|
client = AsyncMemoryClient.__new__(AsyncMemoryClient)
|
||||||
|
client.async_client = MagicMock()
|
||||||
|
mock_response = MagicMock()
|
||||||
|
mock_response.json.return_value = {"results": []}
|
||||||
|
mock_response.raise_for_status.return_value = None
|
||||||
|
client.async_client.post = AsyncMock(return_value=mock_response)
|
||||||
|
|
||||||
|
with patch("mem0.client.main.capture_client_event"):
|
||||||
|
await client.get_all(filters={"user_id": "u1"}, page_size=50)
|
||||||
|
|
||||||
|
client.async_client.post.assert_called_once_with(
|
||||||
|
"/v3/memories/",
|
||||||
|
json={"filters": {"user_id": "u1"}},
|
||||||
|
params={"page_size": 50},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_async_get_all_page_alone_lands_in_query_params(self):
|
||||||
|
asyncio.run(self._assert_async_get_all_page_alone())
|
||||||
|
|
||||||
|
async def _assert_async_get_all_page_alone(self):
|
||||||
|
"""get_all(page=0) without page_size should still send page=0 as a query param."""
|
||||||
|
client = AsyncMemoryClient.__new__(AsyncMemoryClient)
|
||||||
|
client.async_client = MagicMock()
|
||||||
|
mock_response = MagicMock()
|
||||||
|
mock_response.json.return_value = {"results": []}
|
||||||
|
mock_response.raise_for_status.return_value = None
|
||||||
|
client.async_client.post = AsyncMock(return_value=mock_response)
|
||||||
|
|
||||||
|
with patch("mem0.client.main.capture_client_event"):
|
||||||
|
await client.get_all(filters={"user_id": "u1"}, page=0)
|
||||||
|
|
||||||
|
client.async_client.post.assert_called_once_with(
|
||||||
|
"/v3/memories/",
|
||||||
|
json={"filters": {"user_id": "u1"}},
|
||||||
|
params={"page": 0},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_async_get_all_page_and_page_size_together(self):
|
||||||
|
asyncio.run(self._assert_async_get_all_page_and_page_size_together())
|
||||||
|
|
||||||
|
async def _assert_async_get_all_page_and_page_size_together(self):
|
||||||
|
"""get_all(page=0, page_size=0) should send both as query params despite being falsy."""
|
||||||
|
client = AsyncMemoryClient.__new__(AsyncMemoryClient)
|
||||||
|
client.async_client = MagicMock()
|
||||||
|
mock_response = MagicMock()
|
||||||
|
mock_response.json.return_value = {"results": []}
|
||||||
|
mock_response.raise_for_status.return_value = None
|
||||||
|
client.async_client.post = AsyncMock(return_value=mock_response)
|
||||||
|
|
||||||
|
with patch("mem0.client.main.capture_client_event"):
|
||||||
|
await client.get_all(filters={"user_id": "u1"}, page=0, page_size=0)
|
||||||
|
|
||||||
|
client.async_client.post.assert_called_once_with(
|
||||||
|
"/v3/memories/",
|
||||||
|
json={"filters": {"user_id": "u1"}},
|
||||||
|
params={"page": 0, "page_size": 0},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_async_get_all_neither_page_nor_page_size_sends_no_query_params(self):
|
||||||
|
asyncio.run(self._assert_async_get_all_neither_page_nor_page_size())
|
||||||
|
|
||||||
|
async def _assert_async_get_all_neither_page_nor_page_size(self):
|
||||||
|
client = AsyncMemoryClient.__new__(AsyncMemoryClient)
|
||||||
|
client.async_client = MagicMock()
|
||||||
|
mock_response = MagicMock()
|
||||||
|
mock_response.json.return_value = {"results": []}
|
||||||
|
mock_response.raise_for_status.return_value = None
|
||||||
|
client.async_client.post = AsyncMock(return_value=mock_response)
|
||||||
|
|
||||||
|
with patch("mem0.client.main.capture_client_event"):
|
||||||
|
await client.get_all(filters={"user_id": "u1"})
|
||||||
|
|
||||||
|
client.async_client.post.assert_called_once_with(
|
||||||
|
"/v3/memories/",
|
||||||
|
json={"filters": {"user_id": "u1"}},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestUpdateExpirationDate:
|
class TestUpdateExpirationDate:
|
||||||
"""Tests for update expiration_date payload handling."""
|
"""Tests for update expiration_date payload handling."""
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user