Skip to content

Commit 3f4760f

Browse files
committed
feat(aws): add S3 endpoint and user-agent support
Signed-off-by: Gonzalo Peña-Castellanos <goanpeca@gmail.com>
1 parent 0882d7d commit 3f4760f

4 files changed

Lines changed: 40 additions & 3 deletions

File tree

great_expectations/compatibility/aws.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
from __future__ import annotations
22

3+
from importlib import metadata
4+
from typing import Any, Dict
5+
36
from great_expectations.compatibility.not_imported import NotImported
47

58
BOTO_NOT_IMPORTED = NotImported(
@@ -32,6 +35,40 @@
3235
except ImportError:
3336
exceptions = BOTO_NOT_IMPORTED
3437

38+
39+
def _get_distribution_version() -> str:
40+
try:
41+
return metadata.version("great_expectations")
42+
except metadata.PackageNotFoundError:
43+
return "dev"
44+
45+
46+
def get_s3_boto3_options(boto3_options: Dict[str, Any]) -> Dict[str, Any]:
47+
"""Return boto3 client options with a ``great-expectations`` agent suffix.
48+
49+
Works with Amazon S3 and any S3-compatible object store (for example
50+
Backblaze B2, Cloudflare R2, or MinIO). A caller-supplied ``config`` and
51+
``endpoint_url`` are preserved; the suffix is appended to an existing agent
52+
string rather than replacing it.
53+
"""
54+
suffix = f"great-expectations/{_get_distribution_version()}"
55+
options = dict(boto3_options)
56+
57+
if not Config:
58+
return options
59+
60+
config = options.get("config")
61+
existing = getattr(config, "user_agent_extra", None) if config else None
62+
user_agent_extra = f"{existing} {suffix}" if existing else suffix
63+
64+
if config:
65+
options["config"] = config.merge(Config(user_agent_extra=user_agent_extra))
66+
else:
67+
options["config"] = Config(user_agent_extra=user_agent_extra)
68+
69+
return options
70+
71+
3572
try:
3673
import sqlalchemy_redshift
3774
except ImportError:

great_expectations/datasource/fluent/pandas_s3_datasource.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def _get_s3_client(self) -> BaseClient:
6868
"boto3_options", {}
6969
)
7070
try:
71-
s3_client = aws.boto3.client("s3", **boto3_options)
71+
s3_client = aws.boto3.client("s3", **aws.get_s3_boto3_options(boto3_options))
7272
except Exception as e:
7373
# Failure to create "s3_client" is most likely due invalid "boto3_options" dictionary. # noqa: E501 # FIXME CoP
7474
raise PandasS3DatasourceError( # noqa: TRY003 # FIXME CoP

great_expectations/datasource/fluent/spark_s3_datasource.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def _get_s3_client(self) -> BaseClient:
7070
"boto3_options", {}
7171
)
7272
try:
73-
s3_client = aws.boto3.client("s3", **boto3_options)
73+
s3_client = aws.boto3.client("s3", **aws.get_s3_boto3_options(boto3_options))
7474
except Exception as e:
7575
# Failure to create "s3_client" is most likely due invalid "boto3_options" dictionary. # noqa: E501 # FIXME CoP
7676
raise SparkS3DatasourceError( # noqa: TRY003 # FIXME CoP

great_expectations/execution_engine/pandas_execution_engine.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ def _instantiate_azure_client(self) -> None:
167167
def _instantiate_s3_client(self) -> None:
168168
# If s3_client was passed in (from data source) use it, otherwise create our own
169169
self._s3 = self._config.get("s3_client") or aws.boto3.client(
170-
"s3", **self.config.get("boto3_options", {})
170+
"s3", **aws.get_s3_boto3_options(self.config.get("boto3_options", {}))
171171
)
172172

173173
def _instantiate_gcs_client(self) -> None:

0 commit comments

Comments
 (0)