From ea4b5f0e676e8f490de770ac883ee2e1ec25d06d Mon Sep 17 00:00:00 2001 From: cjumel Date: Mon, 1 Jun 2026 12:47:54 +0200 Subject: [PATCH] fix: allow to use string dates directly --- AGENTS.md | 2 +- src/linkup/_client.py | 56 ++++++++++++++++++-------------- tests/unit/client_test.py | 67 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 25 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 47c143c..48adb63 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,7 +33,7 @@ When adding or changing a public API capability, update the relevant pieces toge - request/response typing and models, - sync and async behavior when applicable, - tests, -- README if the user-facing API changed. +- README if anything in it became out-dated. ## Validation diff --git a/src/linkup/_client.py b/src/linkup/_client.py index ead4b91..15d5152 100644 --- a/src/linkup/_client.py +++ b/src/linkup/_client.py @@ -104,8 +104,8 @@ def search( output_type: Literal["searchResults", "sourcedAnswer", "structured"], structured_output_schema: type[BaseModel] | dict[str, Any] | str | None = None, include_images: bool | None = None, - from_date: date | None = None, - to_date: date | None = None, + from_date: date | str | None = None, + to_date: date | str | None = None, exclude_domains: list[str] | None = None, include_domains: list[str] | None = None, max_results: int | None = None, @@ -133,10 +133,12 @@ def search( output. Supported formats are a pydantic.BaseModel, a Python dictionary containing a valid object JSON schema, or a string representing a valid object JSON schema. include_images: Indicate whether images should be included during the search. - from_date: The date from which the search results should be considered. If None, the - search results will not be filtered by date. - to_date: The date until which the search results should be considered. If None, the - search results will not be filtered by date. + from_date: The date from which the search results should be considered. Accepts a + `datetime.date`, `YYYY-MM-DD`, or full ISO datetime string. If None, the search + results will not be filtered by date. + to_date: The date until which the search results should be considered. Accepts a + `datetime.date`, `YYYY-MM-DD`, or full ISO datetime string. If None, the search + results will not be filtered by date. exclude_domains: If you want to exclude specific domains from your search. include_domains: If you want the search to only return results from certain domains. max_results: The maximum number of results to return. @@ -204,8 +206,8 @@ async def async_search( output_type: Literal["searchResults", "sourcedAnswer", "structured"], structured_output_schema: type[BaseModel] | dict[str, Any] | str | None = None, include_images: bool | None = None, - from_date: date | None = None, - to_date: date | None = None, + from_date: date | str | None = None, + to_date: date | str | None = None, exclude_domains: list[str] | None = None, include_domains: list[str] | None = None, max_results: int | None = None, @@ -233,10 +235,12 @@ async def async_search( output. Supported formats are a pydantic.BaseModel, a Python dictionary containing a valid object JSON schema, or a string representing a valid object JSON schema. include_images: Indicate whether images should be included during the search. - from_date: The date from which the search results should be considered. If None, the - search results will not be filtered by date. - to_date: The date until which the search results should be considered. If None, the - search results will not be filtered by date. + from_date: The date from which the search results should be considered. Accepts a + `datetime.date`, `YYYY-MM-DD`, or full ISO datetime string. If None, the search + results will not be filtered by date. + to_date: The date until which the search results should be considered. Accepts a + `datetime.date`, `YYYY-MM-DD`, or full ISO datetime string. If None, the search + results will not be filtered by date. exclude_domains: If you want to exclude specific domains from your search. include_domains: If you want the search to only return results from certain domains. max_results: The maximum number of results to return. @@ -304,8 +308,8 @@ def research( reasoning_depth: Literal["S", "M", "L", "XL"] | None = None, mode: Literal["answer", "auto", "investigate", "research"] | None = None, structured_output_schema: type[BaseModel] | dict[str, Any] | str | None = None, - from_date: date | None = None, - to_date: date | None = None, + from_date: date | str | None = None, + to_date: date | str | None = None, exclude_domains: list[str] | None = None, include_domains: list[str] | None = None, timeout: float | None = None, @@ -325,10 +329,12 @@ def research( structured_output_schema: If output_type is "structured", specify the output schema. Supported formats are a pydantic.BaseModel, a Python dictionary containing a valid object JSON schema, or a string representing a valid object JSON schema. - from_date: The date from which the research sources should be considered. If None, - sources will not be filtered by a start date. - to_date: The date until which the research sources should be considered. If None, - sources will not be filtered by an end date. + from_date: The date from which the research sources should be considered. Accepts a + `datetime.date`, `YYYY-MM-DD`, or full ISO datetime string. If None, sources will + not be filtered by a start date. + to_date: The date until which the research sources should be considered. Accepts a + `datetime.date`, `YYYY-MM-DD`, or full ISO datetime string. If None, sources will + not be filtered by an end date. exclude_domains: Domains to exclude from the research sources. include_domains: Domains to restrict the research sources to. timeout: The timeout for the HTTP request, in seconds. If None, the request will have @@ -373,8 +379,8 @@ async def async_research( reasoning_depth: Literal["S", "M", "L", "XL"] | None = None, mode: Literal["answer", "auto", "investigate", "research"] | None = None, structured_output_schema: type[BaseModel] | dict[str, Any] | str | None = None, - from_date: date | None = None, - to_date: date | None = None, + from_date: date | str | None = None, + to_date: date | str | None = None, exclude_domains: list[str] | None = None, include_domains: list[str] | None = None, timeout: float | None = None, @@ -394,10 +400,12 @@ async def async_research( structured_output_schema: If output_type is "structured", specify the output schema. Supported formats are a pydantic.BaseModel, a Python dictionary containing a valid object JSON schema, or a string representing a valid object JSON schema. - from_date: The date from which the research sources should be considered. If None, - sources will not be filtered by a start date. - to_date: The date until which the research sources should be considered. If None, - sources will not be filtered by an end date. + from_date: The date from which the research sources should be considered. Accepts a + `datetime.date`, `YYYY-MM-DD`, or full ISO datetime string. If None, sources will + not be filtered by a start date. + to_date: The date until which the research sources should be considered. Accepts a + `datetime.date`, `YYYY-MM-DD`, or full ISO datetime string. If None, sources will + not be filtered by an end date. exclude_domains: Domains to exclude from the research sources. include_domains: Domains to restrict the research sources to. timeout: The timeout for the HTTP request, in seconds. If None, the request will have diff --git a/tests/unit/client_test.py b/tests/unit/client_test.py index 840a727..bd782bd 100644 --- a/tests/unit/client_test.py +++ b/tests/unit/client_test.py @@ -90,6 +90,24 @@ class Company(BaseModel): b'{"results": []}', linkup.SearchResults(results=[]), ), + ( + { + "query": "query", + "depth": "standard", + "output_type": "searchResults", + "from_date": "2026-05-01T08:15:30.000Z", + "to_date": "2026-05-31T23:59:59.000Z", + }, + { + "q": "query", + "depth": "standard", + "outputType": "searchResults", + "fromDate": "2026-05-01T08:15:30.000Z", + "toDate": "2026-05-31T23:59:59.000Z", + }, + b'{"results": []}', + linkup.SearchResults(results=[]), + ), ( { "query": "query with timeout", @@ -688,6 +706,55 @@ async def test_async_research(mocker: MockerFixture, client: linkup.Client) -> N assert research_response.input.structured_output_schema == {"type": "object"} +def test_research_with_iso_datetime_string_dates( + mocker: MockerFixture, client: linkup.Client +) -> None: + request_mock = mocker.patch( + "httpx.Client.request", + return_value=Response( + status_code=200, + content=b""" + { + "createdAt": "2026-05-18T00:00:00.000Z", + "error": null, + "id": "f93f33c8-2688-4bd0-ab11-47c8ff89f7b7", + "input": { + "fromDate": "2026-05-01", + "outputType": "sourcedAnswer", + "q": "query", + "toDate": "2026-05-31" + }, + "output": null, + "status": "pending", + "type": "research", + "updatedAt": "2026-05-18T00:00:00.000Z" + } + """, + ), + ) + + research_response = client.research( + query="query", + output_type="sourcedAnswer", + from_date="2026-05-01T08:15:30.000Z", + to_date="2026-05-31T23:59:59.000Z", + ) + + request_mock.assert_called_once_with( + method="POST", + url="/research", + json={ + "q": "query", + "outputType": "sourcedAnswer", + "fromDate": "2026-05-01T08:15:30.000Z", + "toDate": "2026-05-31T23:59:59.000Z", + }, + timeout=None, + ) + assert research_response.input.from_date == "2026-05-01" + assert research_response.input.to_date == "2026-05-31" + + test_fetch_parameters = [ ( {"url": "https://example.com"},