From c657c299b3659bb2f9db0af5de894c53ad956921 Mon Sep 17 00:00:00 2001 From: Vadim Grem Date: Thu, 18 Jun 2026 12:09:58 +0000 Subject: [PATCH 1/2] fix: handle JSON file download from OneDrive /content endpoint The OData response processor relies solely on Content-Type header to decide whether to parse the response body as OData JSON. For OneDrive's /content endpoint, a .json file returns application/json, causing the client to incorrectly parse the file contents as an OData response instead of returning raw bytes. Fix: add _is_content_download() check that skips JSON parsing for FunctionQuery operations named 'content' (OneDrive) or '$value' (SharePoint), which always represent raw file content downloads. Fixes #944, #598 --- office365/runtime/odata/request.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/office365/runtime/odata/request.py b/office365/runtime/odata/request.py index 6f3e4980e..93223d970 100644 --- a/office365/runtime/odata/request.py +++ b/office365/runtime/odata/request.py @@ -77,7 +77,9 @@ def process_response(self, response: Response, query: ClientQuery) -> None: if isinstance(return_type, ClientObject): return_type.clear_state() - if response.headers.get("Content-Type", "").lower().split(";")[0] != "application/json": + content_type = response.headers.get("Content-Type", "").lower().split(";")[0] + + if content_type != "application/json" or self._is_content_download(query): if isinstance(return_type, ClientResult): return_type.set_property("__value", response.content) else: @@ -87,6 +89,14 @@ def process_response(self, response: Response, query: ClientQuery) -> None: self.map_json(response.json(), return_type, json_format) + @staticmethod + def _is_content_download(query: ClientQuery) -> bool: + """Check if the query is a raw content download (file content, not OData metadata).""" + if isinstance(query, FunctionQuery): + name = query.name + return name in ("content", "$value") + return False + def map_json( self, json: Any, From 811ca9055b7e32c906e537522530e92f62f4c708 Mon Sep 17 00:00:00 2001 From: Vadim Grem Date: Thu, 18 Jun 2026 14:47:45 +0000 Subject: [PATCH 2/2] feat: add search suggestions example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a practical example for the suggest() API — query suggestions, people names, and popular results — matching the pattern PnP Modern Search uses for its autocomplete/suggestion provider. --- examples/sharepoint/search/README.md | 3 +- .../sharepoint/search/query_suggestions.py | 35 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 examples/sharepoint/search/query_suggestions.py diff --git a/examples/sharepoint/search/README.md b/examples/sharepoint/search/README.md index 10115a459..2b8c45cb1 100644 --- a/examples/sharepoint/search/README.md +++ b/examples/sharepoint/search/README.md @@ -58,7 +58,8 @@ Results respect the requesting user's permissions. | **7** | Sort by managed property | [`query_with_sort.py`](./query_with_sort.py) | Read access to content | [Search REST API](https://learn.microsoft.com/en-us/sharepoint/dev/general-development/sharepoint-search-rest-api-overview) | | **8** | Refinement / faceted drill-down | [`query_with_refinement.py`](./query_with_refinement.py) | Read access to content | [Search REST API](https://learn.microsoft.com/en-us/sharepoint/dev/general-development/sharepoint-search-rest-api-overview) | | **9** | Paginate through results | [`query_paged.py`](./query_paged.py) | Read access to content | [Search REST API](https://learn.microsoft.com/en-us/sharepoint/dev/general-development/sharepoint-search-rest-api-overview) | -| **10** | Export search reports | [`export_reports.py`](./export_reports.py) | Search admin | [Search REST API](https://learn.microsoft.com/en-us/sharepoint/dev/general-development/sharepoint-search-rest-api-overview) | +| **10** | Search suggestions (type-ahead) | [`query_suggestions.py`](./query_suggestions.py) | Read access to content | [Search REST API](https://learn.microsoft.com/en-us/sharepoint/dev/general-development/sharepoint-search-rest-api-overview) | +| **11** | Export search reports | [`export_reports.py`](./export_reports.py) | Search admin | [Search REST API](https://learn.microsoft.com/en-us/sharepoint/dev/general-development/sharepoint-search-rest-api-overview) | --- diff --git a/examples/sharepoint/search/query_suggestions.py b/examples/sharepoint/search/query_suggestions.py new file mode 100644 index 000000000..47bc85fbe --- /dev/null +++ b/examples/sharepoint/search/query_suggestions.py @@ -0,0 +1,35 @@ +""" +Search-as-you-type suggestions: query keywords, people names, and popular results. + +Uses the SharePoint Search suggest API to power autocomplete/search-as-you-type +scenarios — the same API PnP Modern Search calls for its suggestion provider. + +https://learn.microsoft.com/en-us/sharepoint/dev/general-development/sharepoint-search-rest-api-overview +""" + +from office365.sharepoint.client_context import ClientContext +from tests import test_client_id, test_password, test_site_url, test_tenant, test_username + +ctx = ClientContext(test_site_url).with_username_and_password( + tenant=test_tenant, + client_id=test_client_id, + username=test_username, + password=test_password, +) + +# Partial query — as a user types "guide" the suggest API returns completions +result = ctx.search.suggest("guide").execute_query() +suggestions = result.value + +print("=== Query Suggestions ===") +for q in suggestions.Queries: + source = "personal" if q.IsPersonal else "global" + print(f" [{source}] {q.Query}") + +print("\n=== People Name Suggestions ===") +for name in suggestions.PeopleNames: + print(f" {name}") + +print("\n=== Popular Results ===") +for item in suggestions.PopularResults: + print(f" {item.Title} — {item.Url}")