microsoft-sharepoint-haystack 1.1.0__tar.gz → 1.2.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.
- {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-1.2.0}/CHANGELOG.md +7 -0
- {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-1.2.0}/PKG-INFO +15 -10
- microsoft_sharepoint_haystack-1.2.0/README.md +22 -0
- {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-1.2.0}/pyproject.toml +1 -0
- {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-1.2.0}/src/haystack_integrations/common/microsoft_sharepoint/utils.py +22 -0
- {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-1.2.0}/src/haystack_integrations/components/fetchers/microsoft_sharepoint/fetcher.py +3 -1
- {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-1.2.0}/src/haystack_integrations/components/retrievers/microsoft_sharepoint/retriever.py +19 -10
- {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-1.2.0}/tests/test_fetcher.py +55 -0
- {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-1.2.0}/tests/test_retriever.py +71 -0
- microsoft_sharepoint_haystack-1.1.0/README.md +0 -17
- {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-1.2.0}/.gitignore +0 -0
- {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-1.2.0}/LICENSE.txt +0 -0
- {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-1.2.0}/pydoc/config_docusaurus.yml +0 -0
- {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-1.2.0}/src/haystack_integrations/common/microsoft_sharepoint/__init__.py +0 -0
- {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-1.2.0}/src/haystack_integrations/common/microsoft_sharepoint/errors.py +0 -0
- {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-1.2.0}/src/haystack_integrations/common/py.typed +0 -0
- {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-1.2.0}/src/haystack_integrations/components/fetchers/microsoft_sharepoint/__init__.py +0 -0
- {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-1.2.0}/src/haystack_integrations/components/fetchers/py.typed +0 -0
- {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-1.2.0}/src/haystack_integrations/components/retrievers/microsoft_sharepoint/__init__.py +0 -0
- {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-1.2.0}/src/haystack_integrations/components/retrievers/py.typed +0 -0
- {microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-1.2.0}/tests/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: microsoft-sharepoint-haystack
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.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
|
|
@@ -30,15 +30,20 @@ Description-Content-Type: text/markdown
|
|
|
30
30
|
[](https://pypi.org/project/microsoft-sharepoint-haystack)
|
|
31
31
|
[](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
|
-
|
|
36
|
+
---
|
|
36
37
|
|
|
37
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
+
[](https://pypi.org/project/microsoft-sharepoint-haystack)
|
|
4
|
+
[](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.
|
|
@@ -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
|
-
|
|
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
|
|
48
|
-
from an upstream `OAuthResolver
|
|
49
|
-
(for example `Files.Read.All` and, for site/list scoping, `Sites.Read.All`).
|
|
50
|
-
|
|
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
|
|
138
|
-
|
|
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
|
|
175
|
-
|
|
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,
|
{microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-1.2.0}/tests/test_fetcher.py
RENAMED
|
@@ -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"))
|
{microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-1.2.0}/tests/test_retriever.py
RENAMED
|
@@ -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,17 +0,0 @@
|
|
|
1
|
-
# microsoft-sharepoint-haystack
|
|
2
|
-
|
|
3
|
-
[](https://pypi.org/project/microsoft-sharepoint-haystack)
|
|
4
|
-
[](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.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{microsoft_sharepoint_haystack-1.1.0 → microsoft_sharepoint_haystack-1.2.0}/tests/__init__.py
RENAMED
|
File without changes
|