google-drive-haystack 1.0.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.
- google_drive_haystack-2.0.0/CHANGELOG.md +16 -0
- {google_drive_haystack-1.0.0 → google_drive_haystack-2.0.0}/PKG-INFO +9 -6
- {google_drive_haystack-1.0.0 → google_drive_haystack-2.0.0}/README.md +3 -0
- {google_drive_haystack-1.0.0 → google_drive_haystack-2.0.0}/pydoc/config_docusaurus.yml +1 -0
- {google_drive_haystack-1.0.0 → google_drive_haystack-2.0.0}/pyproject.toml +6 -4
- {google_drive_haystack-1.0.0/src/haystack_integrations/components/retrievers → google_drive_haystack-2.0.0/src/haystack_integrations/common}/google_drive/__init__.py +0 -2
- {google_drive_haystack-1.0.0/src/haystack_integrations/components/retrievers → google_drive_haystack-2.0.0/src/haystack_integrations/common}/google_drive/errors.py +1 -1
- google_drive_haystack-2.0.0/src/haystack_integrations/common/google_drive/utils.py +129 -0
- google_drive_haystack-2.0.0/src/haystack_integrations/components/fetchers/google_drive/__init__.py +17 -0
- google_drive_haystack-2.0.0/src/haystack_integrations/components/fetchers/google_drive/fetcher.py +457 -0
- google_drive_haystack-2.0.0/src/haystack_integrations/components/fetchers/py.typed +0 -0
- google_drive_haystack-2.0.0/src/haystack_integrations/components/retrievers/google_drive/__init__.py +17 -0
- {google_drive_haystack-1.0.0 → google_drive_haystack-2.0.0}/src/haystack_integrations/components/retrievers/google_drive/retriever.py +19 -91
- google_drive_haystack-2.0.0/src/haystack_integrations/components/retrievers/py.typed +0 -0
- google_drive_haystack-2.0.0/tests/test_fetcher.py +450 -0
- {google_drive_haystack-1.0.0 → google_drive_haystack-2.0.0}/tests/test_retriever.py +4 -4
- {google_drive_haystack-1.0.0 → google_drive_haystack-2.0.0}/.gitignore +0 -0
- {google_drive_haystack-1.0.0 → google_drive_haystack-2.0.0}/LICENSE.txt +0 -0
- {google_drive_haystack-1.0.0/src/haystack_integrations/components/retrievers → google_drive_haystack-2.0.0/src/haystack_integrations/common}/py.typed +0 -0
- {google_drive_haystack-1.0.0 → google_drive_haystack-2.0.0}/tests/__init__.py +0 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [integrations/google_drive-v1.1.0] - 2026-06-25
|
|
4
|
+
|
|
5
|
+
### 🚀 Features
|
|
6
|
+
|
|
7
|
+
- *(google_drive)* Add Google Drive fetcher (#3488)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [integrations/google_drive-v1.0.0] - 2026-06-23
|
|
11
|
+
|
|
12
|
+
### 🚀 Features
|
|
13
|
+
|
|
14
|
+
- *(google_drive)* Add integration for Google Drive retriever (#3456)
|
|
15
|
+
|
|
16
|
+
<!-- generated by git-cliff -->
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: google-drive-haystack
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Haystack integration for google_drive
|
|
5
5
|
Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/google_drive#readme
|
|
6
6
|
Project-URL: Issues, https://github.com/deepset-ai/haystack-core-integrations/issues
|
|
@@ -8,19 +8,19 @@ Project-URL: Source, https://github.com/deepset-ai/haystack-core-integrations/tr
|
|
|
8
8
|
Author-email: deepset GmbH <info@deepset.ai>
|
|
9
9
|
License-Expression: Apache-2.0
|
|
10
10
|
License-File: LICENSE.txt
|
|
11
|
-
Keywords: drive,google,haystack,retriever
|
|
11
|
+
Keywords: drive,fetcher,google,haystack,retriever
|
|
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.
|
|
23
|
-
Requires-Dist: haystack-ai
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Requires-Dist: haystack-ai>=2.17.0
|
|
24
24
|
Requires-Dist: httpx>=0.28.0
|
|
25
25
|
Requires-Dist: tenacity>=8.2.3
|
|
26
26
|
Description-Content-Type: text/markdown
|
|
@@ -30,6 +30,7 @@ Description-Content-Type: text/markdown
|
|
|
30
30
|
[](https://pypi.org/project/google-drive-haystack)
|
|
31
31
|
[](https://pypi.org/project/google-drive-haystack)
|
|
32
32
|
|
|
33
|
+
- [Integration page](https://haystack.deepset.ai/integrations/google-drive)
|
|
33
34
|
- [Changelog](https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/google_drive/CHANGELOG.md)
|
|
34
35
|
|
|
35
36
|
---
|
|
@@ -37,3 +38,5 @@ Description-Content-Type: text/markdown
|
|
|
37
38
|
## Contributing
|
|
38
39
|
|
|
39
40
|
Refer to the general [Contribution Guidelines](https://github.com/deepset-ai/haystack-core-integrations/blob/main/CONTRIBUTING.md).
|
|
41
|
+
|
|
42
|
+
To run integration tests locally, you need to export the `GOOGLE_DRIVE_ACCESS_TOKEN` environment variable.
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
[](https://pypi.org/project/google-drive-haystack)
|
|
4
4
|
[](https://pypi.org/project/google-drive-haystack)
|
|
5
5
|
|
|
6
|
+
- [Integration page](https://haystack.deepset.ai/integrations/google-drive)
|
|
6
7
|
- [Changelog](https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/google_drive/CHANGELOG.md)
|
|
7
8
|
|
|
8
9
|
---
|
|
@@ -10,3 +11,5 @@
|
|
|
10
11
|
## Contributing
|
|
11
12
|
|
|
12
13
|
Refer to the general [Contribution Guidelines](https://github.com/deepset-ai/haystack-core-integrations/blob/main/CONTRIBUTING.md).
|
|
14
|
+
|
|
15
|
+
To run integration tests locally, you need to export the `GOOGLE_DRIVE_ACCESS_TOKEN` environment variable.
|
|
@@ -7,28 +7,29 @@ name = "google-drive-haystack"
|
|
|
7
7
|
dynamic = ["version"]
|
|
8
8
|
description = "Haystack integration for google_drive"
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
11
|
license = "Apache-2.0"
|
|
12
12
|
keywords = [
|
|
13
13
|
"haystack",
|
|
14
14
|
"google",
|
|
15
15
|
"drive",
|
|
16
16
|
"retriever",
|
|
17
|
+
"fetcher",
|
|
17
18
|
]
|
|
18
19
|
authors = [{ name = "deepset GmbH", email = "info@deepset.ai" }]
|
|
19
20
|
classifiers = [
|
|
20
21
|
"License :: OSI Approved :: Apache Software License",
|
|
21
22
|
"Development Status :: 4 - Beta",
|
|
22
23
|
"Programming Language :: Python",
|
|
23
|
-
"Programming Language :: Python :: 3.10",
|
|
24
24
|
"Programming Language :: Python :: 3.11",
|
|
25
25
|
"Programming Language :: Python :: 3.12",
|
|
26
26
|
"Programming Language :: Python :: 3.13",
|
|
27
27
|
"Programming Language :: Python :: 3.14",
|
|
28
|
+
"Programming Language :: Python :: 3.15",
|
|
28
29
|
"Programming Language :: Python :: Implementation :: CPython",
|
|
29
30
|
"Programming Language :: Python :: Implementation :: PyPy",
|
|
30
31
|
]
|
|
31
|
-
dependencies = ["haystack-ai", "httpx>=0.28.0", "tenacity>=8.2.3"]
|
|
32
|
+
dependencies = ["haystack-ai>=2.17.0", "httpx>=0.28.0", "tenacity>=8.2.3"]
|
|
32
33
|
|
|
33
34
|
[project.urls]
|
|
34
35
|
Documentation = "https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/google_drive#readme"
|
|
@@ -71,7 +72,7 @@ integration = 'pytest -m "integration" {args:tests}'
|
|
|
71
72
|
all = 'pytest {args:tests}'
|
|
72
73
|
unit-cov-retry = 'pytest --cov=haystack_integrations --reruns 3 --reruns-delay 30 -x -m "not integration" {args:tests}'
|
|
73
74
|
integration-cov-append-retry = 'pytest --cov=haystack_integrations --cov-append --reruns 3 --reruns-delay 30 -x -m "integration" {args:tests}'
|
|
74
|
-
types = "mypy -p haystack_integrations.components.retrievers.google_drive {args}"
|
|
75
|
+
types = "mypy -p haystack_integrations.common.google_drive -p haystack_integrations.components.retrievers.google_drive -p haystack_integrations.components.fetchers.google_drive {args}"
|
|
75
76
|
|
|
76
77
|
[tool.mypy]
|
|
77
78
|
install_types = true
|
|
@@ -132,6 +133,7 @@ ignore = [
|
|
|
132
133
|
"PLR0912",
|
|
133
134
|
"PLR0913",
|
|
134
135
|
"PLR0915",
|
|
136
|
+
"PLR0917",
|
|
135
137
|
# Allow `Any` type - used legitimately for dynamic types and SDK boundaries
|
|
136
138
|
"ANN401",
|
|
137
139
|
]
|
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
#
|
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
from .errors import GoogleDriveConfigError, GoogleDriveError, GoogleDriveRequestError
|
|
5
|
-
from .retriever import GoogleDriveRetriever
|
|
6
5
|
|
|
7
6
|
__all__ = [
|
|
8
7
|
"GoogleDriveConfigError",
|
|
9
8
|
"GoogleDriveError",
|
|
10
9
|
"GoogleDriveRequestError",
|
|
11
|
-
"GoogleDriveRetriever",
|
|
12
10
|
]
|
|
@@ -8,7 +8,7 @@ class GoogleDriveError(Exception):
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class GoogleDriveConfigError(GoogleDriveError):
|
|
11
|
-
"""Raised when
|
|
11
|
+
"""Raised when a Google Drive component is misconfigured."""
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class GoogleDriveRequestError(GoogleDriveError):
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from typing import TypeVar
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
from haystack import logging
|
|
10
|
+
from haystack.utils import Secret
|
|
11
|
+
from tenacity import (
|
|
12
|
+
AsyncRetrying,
|
|
13
|
+
RetryCallState,
|
|
14
|
+
Retrying,
|
|
15
|
+
retry_if_result,
|
|
16
|
+
stop_after_attempt,
|
|
17
|
+
wait_exponential,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from .errors import GoogleDriveConfigError, GoogleDriveRequestError
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
T = TypeVar("T")
|
|
25
|
+
|
|
26
|
+
DEFAULT_API_BASE_URL = "https://www.googleapis.com/drive/v3"
|
|
27
|
+
_RETRYABLE_STATUS = frozenset({429, 500, 502, 503, 504})
|
|
28
|
+
# Fallback backoff when a throttled response carries no `Retry-After` header: 1s, 2s, 4s, ...
|
|
29
|
+
_EXPONENTIAL_BACKOFF = wait_exponential()
|
|
30
|
+
|
|
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
|
+
|
|
49
|
+
def resolve_access_token(access_token: str | Secret) -> str:
|
|
50
|
+
"""Return the bearer token string, resolving it if a `Secret` was passed."""
|
|
51
|
+
if not isinstance(access_token, Secret):
|
|
52
|
+
return access_token
|
|
53
|
+
resolved = access_token.resolve_value()
|
|
54
|
+
if not isinstance(resolved, str):
|
|
55
|
+
msg = "The access_token Secret did not resolve to a string value."
|
|
56
|
+
raise GoogleDriveConfigError(msg)
|
|
57
|
+
return resolved
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _retry_response(retry_state: RetryCallState) -> httpx.Response | None:
|
|
61
|
+
"""Return the `httpx.Response` recorded for a retry attempt, or `None` if the attempt raised."""
|
|
62
|
+
outcome = retry_state.outcome
|
|
63
|
+
if outcome is None or outcome.failed:
|
|
64
|
+
return None
|
|
65
|
+
return outcome.result()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _is_retryable_response(response: httpx.Response) -> bool:
|
|
69
|
+
"""Return whether a Google Drive API response should trigger a retry."""
|
|
70
|
+
return response.status_code in _RETRYABLE_STATUS
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _wait_with_retry_after(retry_state: RetryCallState) -> float:
|
|
74
|
+
"""Wait strategy honoring a numeric `Retry-After` header, falling back to exponential backoff."""
|
|
75
|
+
response = _retry_response(retry_state)
|
|
76
|
+
if response is not None:
|
|
77
|
+
retry_after = response.headers.get("Retry-After")
|
|
78
|
+
if retry_after:
|
|
79
|
+
try:
|
|
80
|
+
return float(retry_after)
|
|
81
|
+
except ValueError:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
return _EXPONENTIAL_BACKOFF(retry_state)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _last_response(retry_state: RetryCallState) -> httpx.Response:
|
|
88
|
+
"""Return the final response once retries are exhausted, so the caller can raise a typed error."""
|
|
89
|
+
response = _retry_response(retry_state)
|
|
90
|
+
if response is None:
|
|
91
|
+
msg = "Google Drive API retries were exhausted without a recorded response."
|
|
92
|
+
raise GoogleDriveRequestError(msg)
|
|
93
|
+
return response
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _log_retry(retry_state: RetryCallState) -> None:
|
|
97
|
+
"""Log a warning before sleeping between Google Drive API retries."""
|
|
98
|
+
response = _retry_response(retry_state)
|
|
99
|
+
next_action = retry_state.next_action
|
|
100
|
+
if response is None or next_action is None:
|
|
101
|
+
return
|
|
102
|
+
logger.warning(
|
|
103
|
+
"Google Drive API returned status {status}; retrying in {delay}s (attempt {attempt}).",
|
|
104
|
+
status=response.status_code,
|
|
105
|
+
delay=next_action.sleep,
|
|
106
|
+
attempt=retry_state.attempt_number,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def build_retrying(max_retries: int) -> Retrying:
|
|
111
|
+
"""Build a tenacity `Retrying` that retries Google Drive API requests on throttling/transient errors."""
|
|
112
|
+
return Retrying(
|
|
113
|
+
stop=stop_after_attempt(max_retries + 1),
|
|
114
|
+
wait=_wait_with_retry_after,
|
|
115
|
+
retry=retry_if_result(_is_retryable_response),
|
|
116
|
+
before_sleep=_log_retry,
|
|
117
|
+
retry_error_callback=_last_response,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def build_async_retrying(max_retries: int) -> AsyncRetrying:
|
|
122
|
+
"""Async variant of `build_retrying`."""
|
|
123
|
+
return AsyncRetrying(
|
|
124
|
+
stop=stop_after_attempt(max_retries + 1),
|
|
125
|
+
wait=_wait_with_retry_after,
|
|
126
|
+
retry=retry_if_result(_is_retryable_response),
|
|
127
|
+
before_sleep=_log_retry,
|
|
128
|
+
retry_error_callback=_last_response,
|
|
129
|
+
)
|
google_drive_haystack-2.0.0/src/haystack_integrations/components/fetchers/google_drive/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
from haystack_integrations.common.google_drive.errors import (
|
|
5
|
+
GoogleDriveConfigError,
|
|
6
|
+
GoogleDriveError,
|
|
7
|
+
GoogleDriveRequestError,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from .fetcher import GoogleDriveFetcher
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"GoogleDriveConfigError",
|
|
14
|
+
"GoogleDriveError",
|
|
15
|
+
"GoogleDriveFetcher",
|
|
16
|
+
"GoogleDriveRequestError",
|
|
17
|
+
]
|