microsoft-sharepoint-haystack 1.1.0__tar.gz → 2.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (22) hide show
  1. microsoft_sharepoint_haystack-2.0.0/CHANGELOG.md +32 -0
  2. {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-2.0.0}/PKG-INFO +17 -12
  3. microsoft_sharepoint_haystack-2.0.0/README.md +22 -0
  4. {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-2.0.0}/pyproject.toml +3 -2
  5. {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-2.0.0}/src/haystack_integrations/common/microsoft_sharepoint/utils.py +22 -0
  6. {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-2.0.0}/src/haystack_integrations/components/fetchers/microsoft_sharepoint/fetcher.py +3 -1
  7. {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-2.0.0}/src/haystack_integrations/components/retrievers/microsoft_sharepoint/retriever.py +19 -10
  8. {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-2.0.0}/tests/test_fetcher.py +55 -0
  9. {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-2.0.0}/tests/test_retriever.py +71 -0
  10. microsoft_sharepoint_haystack-1.1.0/CHANGELOG.md +0 -9
  11. microsoft_sharepoint_haystack-1.1.0/README.md +0 -17
  12. {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-2.0.0}/.gitignore +0 -0
  13. {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-2.0.0}/LICENSE.txt +0 -0
  14. {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-2.0.0}/pydoc/config_docusaurus.yml +0 -0
  15. {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-2.0.0}/src/haystack_integrations/common/microsoft_sharepoint/__init__.py +0 -0
  16. {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-2.0.0}/src/haystack_integrations/common/microsoft_sharepoint/errors.py +0 -0
  17. {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-2.0.0}/src/haystack_integrations/common/py.typed +0 -0
  18. {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-2.0.0}/src/haystack_integrations/components/fetchers/microsoft_sharepoint/__init__.py +0 -0
  19. {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-2.0.0}/src/haystack_integrations/components/fetchers/py.typed +0 -0
  20. {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-2.0.0}/src/haystack_integrations/components/retrievers/microsoft_sharepoint/__init__.py +0 -0
  21. {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-2.0.0}/src/haystack_integrations/components/retrievers/py.typed +0 -0
  22. {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-2.0.0}/tests/__init__.py +0 -0
@@ -0,0 +1,32 @@
1
+ # Changelog
2
+
3
+ ## [integrations/microsoft_sharepoint-v1.2.0] - 2026-09-24
4
+
5
+ ### 🚀 Features
6
+
7
+ - Add region parameter to MSSharePointRetriever (#3722)
8
+
9
+ ### 🐛 Bug Fixes
10
+
11
+ - Fix new issues raised by ruff 0.16.0 (#3670)
12
+ - *(microsoft_sharepoint,google_drive)* Cancel and drain in-flight fetches when one fails (#3619)
13
+
14
+ ### 🧹 Chores
15
+
16
+ - Re-standardize READMEs (#3768)
17
+
18
+
19
+ ## [integrations/microsoft_sharepoint-v1.1.0] - 2026-06-25
20
+
21
+ ### 🚀 Features
22
+
23
+ - *(microsoft_sharepoint)* Add MSSharePointFetcher (#3485)
24
+
25
+
26
+ ## [integrations/microsoft_sharepoint-v1.0.0] - 2026-06-22
27
+
28
+ ### 🚀 Features
29
+
30
+ - *(microsoft_sharepoint)* Add Microsoft SharePoint retriever integration (#3429)
31
+
32
+ <!-- generated by git-cliff -->
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: microsoft-sharepoint-haystack
3
- Version: 1.1.0
3
+ Version: 2.0.0
4
4
  Summary: Haystack integration for microsoft_sharepoint
5
5
  Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/microsoft_sharepoint#readme
6
6
  Project-URL: Issues, https://github.com/deepset-ai/haystack-core-integrations/issues
@@ -12,14 +12,14 @@ Keywords: fetcher,haystack,microsoft,retriever,sharepoint
12
12
  Classifier: Development Status :: 4 - Beta
13
13
  Classifier: License :: OSI Approved :: Apache Software License
14
14
  Classifier: Programming Language :: Python
15
- Classifier: Programming Language :: Python :: 3.10
16
15
  Classifier: Programming Language :: Python :: 3.11
17
16
  Classifier: Programming Language :: Python :: 3.12
18
17
  Classifier: Programming Language :: Python :: 3.13
19
18
  Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Programming Language :: Python :: 3.15
20
20
  Classifier: Programming Language :: Python :: Implementation :: CPython
21
21
  Classifier: Programming Language :: Python :: Implementation :: PyPy
22
- Requires-Python: >=3.10
22
+ Requires-Python: >=3.11
23
23
  Requires-Dist: haystack-ai>=2.17.0
24
24
  Requires-Dist: httpx>=0.28.0
25
25
  Requires-Dist: tenacity>=8.2.3
@@ -30,15 +30,20 @@ Description-Content-Type: text/markdown
30
30
  [![PyPI - Version](https://img.shields.io/pypi/v/microsoft-sharepoint-haystack.svg)](https://pypi.org/project/microsoft-sharepoint-haystack)
31
31
  [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/microsoft-sharepoint-haystack.svg)](https://pypi.org/project/microsoft-sharepoint-haystack)
32
32
 
33
+ - [Integration page](https://haystack.deepset.ai/integrations/microsoft-sharepoint)
33
34
  - [Changelog](https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/microsoft_sharepoint/CHANGELOG.md)
34
35
 
35
- ## Running the tests
36
+ ---
36
37
 
37
- ```bash
38
- hatch run test:unit # offline, no credentials needed
39
- hatch run test:integration # hits live Microsoft Graph (see below)
40
- ```
38
+ ## Contributing
41
39
 
42
- The integration tests (the `TestLive` classes in `tests/`) exercise the retriever and the fetcher
43
- against live Microsoft Graph. They **skip themselves** when their inputs are not set, so the default
44
- local and CI runs stay offline. To run them you need a delegated Microsoft Graph access token.
40
+ Refer to the general [Contribution Guidelines](https://github.com/deepset-ai/haystack-core-integrations/blob/main/CONTRIBUTING.md).
41
+
42
+ The integration tests run against live Microsoft Graph and are skipped unless their
43
+ environment variables are set:
44
+
45
+ - `MS_SHAREPOINT_ACCESS_TOKEN` — a delegated Microsoft Graph access token; required by the
46
+ retriever and fetcher tests.
47
+ - `MS_SHAREPOINT_TEST_FILE_URL` — a SharePoint file URL; also required by the fetcher tests.
48
+ - `MS_GRAPH_TENANT_ID`, `MS_GRAPH_CLIENT_ID` and `MS_GRAPH_CLIENT_SECRET` — required by the
49
+ app-only retriever tests.
@@ -0,0 +1,22 @@
1
+ # microsoft-sharepoint-haystack
2
+
3
+ [![PyPI - Version](https://img.shields.io/pypi/v/microsoft-sharepoint-haystack.svg)](https://pypi.org/project/microsoft-sharepoint-haystack)
4
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/microsoft-sharepoint-haystack.svg)](https://pypi.org/project/microsoft-sharepoint-haystack)
5
+
6
+ - [Integration page](https://haystack.deepset.ai/integrations/microsoft-sharepoint)
7
+ - [Changelog](https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/microsoft_sharepoint/CHANGELOG.md)
8
+
9
+ ---
10
+
11
+ ## Contributing
12
+
13
+ Refer to the general [Contribution Guidelines](https://github.com/deepset-ai/haystack-core-integrations/blob/main/CONTRIBUTING.md).
14
+
15
+ The integration tests run against live Microsoft Graph and are skipped unless their
16
+ environment variables are set:
17
+
18
+ - `MS_SHAREPOINT_ACCESS_TOKEN` — a delegated Microsoft Graph access token; required by the
19
+ retriever and fetcher tests.
20
+ - `MS_SHAREPOINT_TEST_FILE_URL` — a SharePoint file URL; also required by the fetcher tests.
21
+ - `MS_GRAPH_TENANT_ID`, `MS_GRAPH_CLIENT_ID` and `MS_GRAPH_CLIENT_SECRET` — required by the
22
+ app-only retriever tests.
@@ -7,7 +7,7 @@ name = "microsoft-sharepoint-haystack"
7
7
  dynamic = ["version"]
8
8
  description = "Haystack integration for microsoft_sharepoint"
9
9
  readme = "README.md"
10
- requires-python = ">=3.10"
10
+ requires-python = ">=3.11"
11
11
  license = "Apache-2.0"
12
12
  keywords = [
13
13
  "haystack",
@@ -21,11 +21,11 @@ classifiers = [
21
21
  "License :: OSI Approved :: Apache Software License",
22
22
  "Development Status :: 4 - Beta",
23
23
  "Programming Language :: Python",
24
- "Programming Language :: Python :: 3.10",
25
24
  "Programming Language :: Python :: 3.11",
26
25
  "Programming Language :: Python :: 3.12",
27
26
  "Programming Language :: Python :: 3.13",
28
27
  "Programming Language :: Python :: 3.14",
28
+ "Programming Language :: Python :: 3.15",
29
29
  "Programming Language :: Python :: Implementation :: CPython",
30
30
  "Programming Language :: Python :: Implementation :: PyPy",
31
31
  ]
@@ -133,6 +133,7 @@ ignore = [
133
133
  "PLR0912",
134
134
  "PLR0913",
135
135
  "PLR0915",
136
+ "PLR0917",
136
137
  # Allow `Any` type - used legitimately for dynamic types and SDK boundaries
137
138
  "ANN401",
138
139
  ]
@@ -2,6 +2,9 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
+ import asyncio
6
+ from typing import TypeVar
7
+
5
8
  import httpx
6
9
  from haystack import logging
7
10
  from haystack.utils import Secret
@@ -18,12 +21,31 @@ from .errors import SharePointConfigError, SharePointRequestError
18
21
 
19
22
  logger = logging.getLogger(__name__)
20
23
 
24
+ T = TypeVar("T")
25
+
21
26
  DEFAULT_GRAPH_URL = "https://graph.microsoft.com/v1.0"
22
27
  _RETRYABLE_STATUS = frozenset({429, 500, 502, 503, 504})
23
28
  # Fallback backoff when a throttled response carries no `Retry-After` header: 1s, 2s, 4s, ...
24
29
  _EXPONENTIAL_BACKOFF = wait_exponential()
25
30
 
26
31
 
32
+ async def _gather_tasks_with_cancel(tasks: list[asyncio.Task[T]]) -> list[T]:
33
+ """
34
+ Wait for all tasks, cancelling and draining unfinished siblings if one fails.
35
+
36
+ :param tasks: Tasks to wait for.
37
+ :returns:
38
+ The task results in input order.
39
+ """
40
+ try:
41
+ return await asyncio.gather(*tasks)
42
+ except BaseException:
43
+ for task in tasks:
44
+ task.cancel()
45
+ await asyncio.gather(*tasks, return_exceptions=True)
46
+ raise
47
+
48
+
27
49
  def resolve_access_token(access_token: str | Secret) -> str:
28
50
  """Return the bearer token string, resolving it if a `Secret` was passed."""
29
51
  if not isinstance(access_token, Secret):
@@ -18,6 +18,7 @@ from haystack.utils import Secret
18
18
  from haystack_integrations.common.microsoft_sharepoint.errors import SharePointConfigError, SharePointRequestError
19
19
  from haystack_integrations.common.microsoft_sharepoint.utils import (
20
20
  DEFAULT_GRAPH_URL,
21
+ _gather_tasks_with_cancel,
21
22
  build_async_retrying,
22
23
  build_retrying,
23
24
  resolve_access_token,
@@ -193,7 +194,8 @@ class MSSharePointFetcher:
193
194
  return await self._process_async(client, token, url, hints)
194
195
 
195
196
  async with httpx.AsyncClient(timeout=self.timeout, follow_redirects=True) as client:
196
- results = await asyncio.gather(*(fetch_one(client, url, hints) for url, hints in resolved))
197
+ tasks = [asyncio.create_task(fetch_one(client, url, hints)) for url, hints in resolved]
198
+ results = await _gather_tasks_with_cancel(tasks)
197
199
 
198
200
  return {"streams": [stream for stream in results if stream is not None]}
199
201
 
@@ -44,10 +44,10 @@ class MSSharePointRetriever:
44
44
  download or convert the underlying files. Compose a downstream fetcher/converter (such as
45
45
  `MSSharePointFetcher`) when full content is needed.
46
46
 
47
- The retriever takes a per-user `access_token` as a run input, typically wired
48
- from an upstream `OAuthResolver`. The token must carry delegated Microsoft Graph permissions
49
- (for example `Files.Read.All` and, for site/list scoping, `Sites.Read.All`). The Search API supports
50
- delegated permissions only.
47
+ The retriever takes an `access_token` as a run input. For delegated (on-behalf-of) auth, pass a
48
+ per-user token wired from an upstream `OAuthResolver`; the token must carry delegated Microsoft Graph
49
+ permissions (for example `Files.Read.All` and, for site/list scoping, `Sites.Read.All`). For app-only
50
+ (client-credentials) auth, pass an application token and set the `region` parameter at init time.
51
51
 
52
52
  ### Usage example
53
53
  ```python
@@ -76,6 +76,7 @@ class MSSharePointRetriever:
76
76
  top_k: int = 10,
77
77
  fields: list[str] | None = None,
78
78
  query_template: str | None = None,
79
+ region: str | None = None,
79
80
  graph_url: str = DEFAULT_GRAPH_URL,
80
81
  timeout: float = 30.0,
81
82
  max_retries: int = 3,
@@ -96,6 +97,10 @@ class MSSharePointRetriever:
96
97
  `'{searchTerms} path:"https://contoso.sharepoint.com/sites/Team"'`. The literal `{searchTerms}`
97
98
  placeholder is replaced by the run-time query. The template uses
98
99
  [Keyword Query Language (KQL)](https://learn.microsoft.com/en-us/sharepoint/dev/general-development/keyword-query-language-kql-syntax-reference).
100
+ :param region: The region code for the Microsoft Search index, for example `"NAM"`, `"EUR"`, or `"APC"`.
101
+ Required when using application permissions (app-only / client-credentials auth); omit for delegated
102
+ (on-behalf-of) tokens. See the `region` property of the
103
+ [searchRequest resource](https://learn.microsoft.com/en-us/graph/api/resources/searchrequest).
99
104
  :param graph_url: The Microsoft Graph base URL. Defaults to `https://graph.microsoft.com/v1.0`.
100
105
  Override for sovereign clouds.
101
106
  :param timeout: The HTTP timeout in seconds for each request to Microsoft Graph.
@@ -121,6 +126,7 @@ class MSSharePointRetriever:
121
126
  self.top_k = top_k
122
127
  self.fields = fields
123
128
  self.query_template = query_template
129
+ self.region = region
124
130
  self.graph_url = graph_url.rstrip("/")
125
131
  self.timeout = timeout
126
132
  self.max_retries = max_retries
@@ -134,9 +140,9 @@ class MSSharePointRetriever:
134
140
  operators directly in the query, for example `filetype:docx`, `author:"Jane Doe"`, or
135
141
  `path:"https://contoso.sharepoint.com/sites/Team"`. See the
136
142
  [KQL syntax reference](https://learn.microsoft.com/en-us/sharepoint/dev/general-development/keyword-query-language-kql-syntax-reference).
137
- :param access_token: A delegated Microsoft Graph bearer token for the user whose content is searched,
138
- typically wired from an upstream `OAuthResolver` (which emits a plain `str`). A `Secret` is also
139
- accepted and resolved internally.
143
+ :param access_token: A Microsoft Graph bearer token. For delegated auth, pass a per-user token wired from
144
+ an upstream `OAuthResolver`. For app-only (client-credentials) auth, pass an application token and set
145
+ `region` at init time. A `Secret` is also accepted and resolved internally.
140
146
  :param top_k: Overrides the `top_k` configured at initialization for this run.
141
147
  :returns: A dictionary with a `documents` key holding the list of retrieved `Document` objects.
142
148
  :raises SharePointConfigError: If `access_token` is a `Secret` that does not resolve to a string.
@@ -171,9 +177,9 @@ class MSSharePointRetriever:
171
177
  operators directly in the query, for example `filetype:docx`, `author:"Jane Doe"`, or
172
178
  `path:"https://contoso.sharepoint.com/sites/Team"`. See the
173
179
  [KQL syntax reference](https://learn.microsoft.com/en-us/sharepoint/dev/general-development/keyword-query-language-kql-syntax-reference).
174
- :param access_token: A delegated Microsoft Graph bearer token for the user whose content is searched,
175
- typically wired from an upstream `OAuthResolver` (which emits a plain `str`). A `Secret` is also
176
- accepted and resolved internally.
180
+ :param access_token: A Microsoft Graph bearer token. For delegated auth, pass a per-user token wired from
181
+ an upstream `OAuthResolver`. For app-only (client-credentials) auth, pass an application token and set
182
+ `region` at init time. A `Secret` is also accepted and resolved internally.
177
183
  :param top_k: Overrides the `top_k` configured at initialization for this run.
178
184
  :returns: A dictionary with a `documents` key holding the list of retrieved `Document` objects.
179
185
  :raises SharePointConfigError: If `access_token` is a `Secret` that does not resolve to a string.
@@ -215,6 +221,8 @@ class MSSharePointRetriever:
215
221
  }
216
222
  if self.fields:
217
223
  request["fields"] = self.fields
224
+ if self.region:
225
+ request["region"] = self.region
218
226
 
219
227
  return {"requests": [request]}
220
228
 
@@ -328,6 +336,7 @@ class MSSharePointRetriever:
328
336
  top_k=self.top_k,
329
337
  fields=self.fields,
330
338
  query_template=self.query_template,
339
+ region=self.region,
331
340
  graph_url=self.graph_url,
332
341
  timeout=self.timeout,
333
342
  max_retries=self.max_retries,
@@ -1,6 +1,7 @@
1
1
  # SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+ import asyncio
4
5
  import base64
5
6
  import json
6
7
  import os
@@ -458,6 +459,60 @@ class TestPipeline:
458
459
 
459
460
  @pytest.mark.asyncio
460
461
  class TestRunAsync:
462
+ async def test_failure_cancels_and_drains_siblings(self):
463
+ fetcher = MSSharePointFetcher(max_retries=0)
464
+ slow_started = asyncio.Event()
465
+ slow_cancelled = False
466
+ current_task = asyncio.current_task()
467
+ tasks_before = asyncio.all_tasks() - {current_task}
468
+ slow_url = "https://host/slow.docx"
469
+ slow_marker = _encode_share_url(slow_url)
470
+
471
+ async def get(url, *_args, **_kwargs):
472
+ nonlocal slow_cancelled
473
+ if slow_marker in url:
474
+ slow_started.set()
475
+ try:
476
+ await asyncio.Event().wait()
477
+ except asyncio.CancelledError:
478
+ slow_cancelled = True
479
+ raise
480
+ await slow_started.wait()
481
+ msg = "failed"
482
+ raise httpx.ReadError(msg)
483
+
484
+ targets = [
485
+ _drive_item_document(url=slow_url),
486
+ _drive_item_document(url="https://host/failing.docx"),
487
+ ]
488
+ with patch.object(httpx.AsyncClient, "get", AsyncMock(side_effect=get)):
489
+ with pytest.raises(httpx.ReadError):
490
+ await fetcher.run_async(access_token="tok", targets=targets)
491
+
492
+ assert slow_cancelled is True
493
+ assert asyncio.all_tasks() - {current_task} == tasks_before
494
+
495
+ async def test_raise_on_failure_false_skips_failed_items(self):
496
+ fetcher = MSSharePointFetcher(max_retries=0, raise_on_failure=False)
497
+ missing_url = "https://host/missing.docx"
498
+ missing_marker = _encode_share_url(missing_url)
499
+
500
+ async def get(url, *_args, **_kwargs):
501
+ if missing_marker in url:
502
+ msg = "failed"
503
+ raise httpx.ReadError(msg)
504
+ return _binary_response(content=b"ok")
505
+
506
+ targets = [
507
+ _drive_item_document(url=missing_url),
508
+ _drive_item_document(url="https://host/healthy.docx"),
509
+ ]
510
+ with patch.object(httpx.AsyncClient, "get", AsyncMock(side_effect=get)):
511
+ streams = (await fetcher.run_async(access_token="tok", targets=targets))["streams"]
512
+
513
+ assert len(streams) == 1
514
+ assert streams[0].data == b"ok"
515
+
461
516
  async def test_downloads_drive_item(self):
462
517
  fetcher = MSSharePointFetcher()
463
518
  get = AsyncMock(return_value=_binary_response(content=b"async-bytes"))
@@ -7,6 +7,7 @@ from unittest.mock import AsyncMock, patch
7
7
 
8
8
  import httpx
9
9
  import pytest
10
+ import requests
10
11
  from haystack import Document, Pipeline
11
12
  from haystack.utils import Secret
12
13
 
@@ -100,6 +101,7 @@ class TestInit:
100
101
  assert retriever.top_k == 10
101
102
  assert retriever.fields is None
102
103
  assert retriever.query_template is None
104
+ assert retriever.region is None
103
105
  assert retriever.graph_url == "https://graph.microsoft.com/v1.0"
104
106
  assert retriever.timeout == 30.0
105
107
  assert retriever.max_retries == 3
@@ -140,12 +142,18 @@ class TestSerialization:
140
142
  "top_k": 5,
141
143
  "fields": ["title"],
142
144
  "query_template": '{searchTerms} path:"https://x"',
145
+ "region": None,
143
146
  "graph_url": "https://graph.microsoft.us/v1.0",
144
147
  "timeout": 10.0,
145
148
  "max_retries": 1,
146
149
  },
147
150
  }
148
151
 
152
+ def test_to_dict_with_region(self):
153
+ retriever = MSSharePointRetriever(region="NAM")
154
+ data = retriever.to_dict()
155
+ assert data["init_parameters"]["region"] == "NAM"
156
+
149
157
  def test_from_dict_round_trip(self):
150
158
  retriever = MSSharePointRetriever(entity_types=["site"], top_k=7, query_template="{searchTerms}")
151
159
  restored = MSSharePointRetriever.from_dict(retriever.to_dict())
@@ -241,6 +249,19 @@ class TestRun:
241
249
  assert "queryTemplate" not in mock_post.call_args.kwargs["json"]["requests"][0]["query"]
242
250
  assert "fields" not in mock_post.call_args.kwargs["json"]["requests"][0]
243
251
 
252
+ def test_region_included_in_request_body_when_set(self):
253
+ retriever = MSSharePointRetriever(region="NAM")
254
+ with patch.object(httpx.Client, "post", return_value=_make_response(json_body=EMPTY_RESPONSE)) as mock_post:
255
+ retriever.run(query="q", access_token="tok")
256
+ request = mock_post.call_args.kwargs["json"]["requests"][0]
257
+ assert request["region"] == "NAM"
258
+
259
+ def test_region_omitted_from_request_body_when_none(self):
260
+ retriever = MSSharePointRetriever()
261
+ with patch.object(httpx.Client, "post", return_value=_make_response(json_body=EMPTY_RESPONSE)) as mock_post:
262
+ retriever.run(query="q", access_token="tok")
263
+ assert "region" not in mock_post.call_args.kwargs["json"]["requests"][0]
264
+
244
265
  def test_empty_results(self):
245
266
  retriever = MSSharePointRetriever()
246
267
  with patch.object(httpx.Client, "post", return_value=_make_response(json_body=EMPTY_RESPONSE)):
@@ -410,3 +431,53 @@ class TestLive:
410
431
  for doc in documents:
411
432
  assert isinstance(doc, Document)
412
433
  assert "web_url" in doc.meta
434
+
435
+
436
+ @pytest.mark.integration
437
+ @pytest.mark.skipif(
438
+ not all(os.environ.get(v) for v in ("MS_GRAPH_TENANT_ID", "MS_GRAPH_CLIENT_ID", "MS_GRAPH_CLIENT_SECRET")),
439
+ reason="MS_GRAPH_TENANT_ID / MS_GRAPH_CLIENT_ID / MS_GRAPH_CLIENT_SECRET not set",
440
+ )
441
+ class TestLiveAppOnly:
442
+ """
443
+ End-to-end tests using app-only (client credentials) authentication.
444
+
445
+ Set these env vars before running:
446
+ MS_GRAPH_TENANT_ID — Azure AD tenant ID
447
+ MS_GRAPH_CLIENT_ID — App registration client ID
448
+ MS_GRAPH_CLIENT_SECRET — App registration client secret
449
+ MS_GRAPH_REGION — Search region, e.g. "NAM" (default: "DEU")
450
+
451
+ Run with:
452
+ hatch run test:integration
453
+ """
454
+
455
+ @staticmethod
456
+ def _get_app_token() -> str:
457
+ tenant_id = os.environ["MS_GRAPH_TENANT_ID"]
458
+ client_id = os.environ["MS_GRAPH_CLIENT_ID"]
459
+ client_secret = os.environ["MS_GRAPH_CLIENT_SECRET"]
460
+ response = requests.post(
461
+ f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token",
462
+ data={
463
+ "client_id": client_id,
464
+ "client_secret": client_secret,
465
+ "scope": "https://graph.microsoft.com/.default",
466
+ "grant_type": "client_credentials",
467
+ },
468
+ timeout=30,
469
+ )
470
+ response.raise_for_status()
471
+ return response.json()["access_token"]
472
+
473
+ def test_app_only_search_with_region_succeeds(self):
474
+ token = self._get_app_token()
475
+ region = os.environ.get("MS_GRAPH_REGION", "DEU")
476
+
477
+ # With region, the request should succeed.
478
+ retriever = MSSharePointRetriever(top_k=3, region=region, max_retries=0)
479
+ documents = retriever.run(query="test", access_token=token)["documents"]
480
+ assert isinstance(documents, list)
481
+ for doc in documents:
482
+ assert isinstance(doc, Document)
483
+ assert "web_url" in doc.meta
@@ -1,9 +0,0 @@
1
- # Changelog
2
-
3
- ## [integrations/microsoft_sharepoint-v1.0.0] - 2026-06-22
4
-
5
- ### 🚀 Features
6
-
7
- - *(microsoft_sharepoint)* Add Microsoft SharePoint retriever integration (#3429)
8
-
9
- <!-- generated by git-cliff -->
@@ -1,17 +0,0 @@
1
- # microsoft-sharepoint-haystack
2
-
3
- [![PyPI - Version](https://img.shields.io/pypi/v/microsoft-sharepoint-haystack.svg)](https://pypi.org/project/microsoft-sharepoint-haystack)
4
- [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/microsoft-sharepoint-haystack.svg)](https://pypi.org/project/microsoft-sharepoint-haystack)
5
-
6
- - [Changelog](https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/microsoft_sharepoint/CHANGELOG.md)
7
-
8
- ## Running the tests
9
-
10
- ```bash
11
- hatch run test:unit # offline, no credentials needed
12
- hatch run test:integration # hits live Microsoft Graph (see below)
13
- ```
14
-
15
- The integration tests (the `TestLive` classes in `tests/`) exercise the retriever and the fetcher
16
- against live Microsoft Graph. They **skip themselves** when their inputs are not set, so the default
17
- local and CI runs stay offline. To run them you need a delegated Microsoft Graph access token.