Skip to content
This repository was archived by the owner on Nov 6, 2023. It is now read-only.

Commit 301833c

Browse files
authored
feat: assuming roles
1 parent c167677 commit 301833c

13 files changed

Lines changed: 188 additions & 198 deletions

File tree

config_examples/s3.yaml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ plugins:
1111
aws_access_key_id: <aws_access_key_id> # Optional.
1212
aws_session_token: <aws_session_token> # Optional.
1313
aws_region: <aws_region> # Optional.
14+
filename_filter: # Optional. Default filter allows each file to be ingested to platform.
15+
include: [ '.*.parquet' ]
16+
exclude: [ 'dev_.*' ]
1417
datasets:
1518
# Recursive fetch for all objects in the bucket.
1619
- bucket: my_bucket
@@ -41,10 +44,9 @@ token: ""
4144
plugins:
4245
- type: s3
4346
name: s3_minio_adapter
44-
endpoint_url: <some_endpoint>
45-
aws_secret_access_key: <aws_secret_access_key>
46-
aws_access_key_id: <aws_access_key_id>
47-
aws_region: <aws_region>
47+
endpoint_url: http://localhost:9000
48+
aws_secret_access_key: minioadmin
49+
aws_access_key_id: minioadmin
4850
datasets:
4951
- bucket: my_bucket
5052
prefix: partitioned_data

odd_collector_aws/adapters/s3/clients/__init__.py

Whitespace-only changes.

odd_collector_aws/adapters/s3/clients/s3_client.py

Lines changed: 0 additions & 48 deletions
This file was deleted.

odd_collector_aws/adapters/s3/clients/s3_client_base.py

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 26 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,23 @@
11
from typing import Union
22

3-
import pyarrow.dataset as ds
4-
from funcy import iffy, lmap
5-
from pyarrow._fs import FileInfo, FileSelector
6-
from pyarrow.fs import S3FileSystem
7-
8-
from odd_collector_aws.domain.plugin import AwsPlugin
3+
from odd_collector_aws.domain.plugin import S3Plugin
94

105
from ...domain.dataset_config import DatasetConfig
6+
from ...filesystem.pyarrow_fs import FileSystem as PyarrowFs
7+
from ...utils.remove_s3_protocol import remove_protocol
118
from .domain.models import Bucket, File, Folder
129
from .logger import logger
1310
from .utils import file_format
1411

1512

16-
class FileSystem:
13+
class FileSystem(PyarrowFs):
1714
"""
1815
FileSystem hides pyarrow.fs implementation details.
1916
"""
2017

21-
def __init__(self, config: AwsPlugin):
22-
params = {}
23-
24-
if config.aws_access_key_id:
25-
params["access_key"] = config.aws_access_key_id
26-
if config.aws_secret_access_key:
27-
params["secret_key"] = config.aws_secret_access_key
28-
if config.aws_session_token:
29-
params["session_token"] = config.aws_session_token
30-
if config.aws_region:
31-
params["region"] = config.aws_region
32-
if config.endpoint_url:
33-
params["endpoint_override"] = config.endpoint_url
34-
35-
self.fs = S3FileSystem(**params)
36-
37-
def get_file_info(self, path: str) -> list[FileInfo]:
38-
"""
39-
Get file info from path.
40-
@param path: s3 path to file or folder
41-
@return: FileInfo
42-
"""
43-
return self.fs.get_file_info(FileSelector(base_dir=path))
44-
45-
def get_dataset(self, file_path: str, format: str) -> ds.Dataset:
46-
"""
47-
Get dataset from file path.
48-
@param file_path:
49-
@param format: Should be one of available formats: https://arrow.apache.org/docs/python/api/dataset.html#file-format
50-
@return: Dataset
51-
"""
52-
return ds.dataset(source=file_path, filesystem=self.fs, format=format)
18+
def __init__(self, config: S3Plugin):
19+
self.fs = PyarrowFs(config)
20+
self.filename_filter = config.filename_filter
5321

5422
def get_folder_as_file(self, dataset_config: DatasetConfig) -> File:
5523
"""
@@ -59,15 +27,13 @@ def get_folder_as_file(self, dataset_config: DatasetConfig) -> File:
5927
"""
6028
logger.debug(f"Getting folder dataset for {dataset_config=}")
6129

62-
dataset = ds.dataset(
63-
source=dataset_config.full_path,
30+
dataset = self.get_dataset(
31+
path=dataset_config.full_path,
6432
format=dataset_config.folder_as_dataset.file_format,
65-
partitioning=ds.partitioning(
66-
flavor=dataset_config.folder_as_dataset.flavor,
67-
field_names=dataset_config.folder_as_dataset.field_names,
68-
),
69-
filesystem=self.fs,
33+
partitioning_flavor=dataset_config.folder_as_dataset.flavor,
34+
field_names=dataset_config.folder_as_dataset.field_names,
7035
)
36+
7137
return File(
7238
path=dataset_config.full_path,
7339
base_name=dataset_config.full_path,
@@ -103,14 +69,18 @@ def list_objects(self, path: str) -> list[Union[File, Folder]]:
10369
@return: list of either File or Folder
10470
"""
10571
logger.debug(f"Getting objects for {path=}")
106-
return lmap(
107-
iffy(
108-
lambda x: x.is_file,
109-
lambda x: self.get_file(x.path, x.base_name),
110-
lambda x: self.get_folder(x.path),
111-
),
112-
self.get_file_info(path),
113-
)
72+
objects = []
73+
74+
for obj in self.fs.get_file_info(path):
75+
if obj.is_file:
76+
if not self.filename_filter.is_allowed(obj.base_name):
77+
continue
78+
79+
objects.append(self.get_file(obj.path, obj.base_name))
80+
else:
81+
objects.append(self.get_folder(obj.path))
82+
83+
return objects
11484

11585
def get_file(self, path: str, file_name: str = None) -> File:
11686
"""
@@ -125,7 +95,7 @@ def get_file(self, path: str, file_name: str = None) -> File:
12595

12696
try:
12797
file_fmt = file_format(file_name)
128-
dataset = self.get_dataset(path, file_fmt)
98+
dataset = self.fs.get_dataset(path, file_fmt)
12999

130100
return File.dataset(
131101
path=path,
@@ -146,17 +116,9 @@ def get_folder(self, path: str, recursive: bool = True) -> Folder:
146116
"""
147117
Get Folder with objects recursively.
148118
@param path: s3 path to
119+
@param recursive: Flag to recursively search nested objects
149120
@return: Folder class with objects and path
150121
"""
151122
path = remove_protocol(path)
152123
objects = self.list_objects(path) if recursive else []
153124
return Folder(path, objects)
154-
155-
156-
def remove_protocol(path: str) -> str:
157-
if path.startswith("s3://"):
158-
return path.removeprefix("s3://")
159-
elif path.startswith(("s3a://", "s3n://")):
160-
return path[6:]
161-
else:
162-
return path

odd_collector_aws/adapters/s3_delta/client.py

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import datetime
21
import traceback as tb
32
from dataclasses import asdict, dataclass
43
from typing import Any, Iterable, Optional
@@ -9,19 +8,15 @@
98
from odd_collector_aws.domain.plugin import DeltaTableConfig, S3DeltaPlugin
109
from odd_collector_aws.filesystem.pyarrow_fs import FileSystem
1110

11+
from ...utils.dates import add_utc_timezone, from_ms
12+
from ...utils.remove_s3_protocol import remove_protocol
1213
from .logger import logger
1314
from .models.table import DTable
1415

1516

16-
def from_ms(ms) -> datetime.datetime:
17-
return datetime.datetime.fromtimestamp(ms / 1000, tz=datetime.timezone.utc)
18-
19-
20-
def add_utc_timezone(dt: datetime.datetime) -> datetime.datetime:
21-
return dt.replace(tzinfo=datetime.timezone.utc)
22-
23-
24-
def handle_values(obj: dict, handler: tuple[str, callable]) -> dict[str, Optional[any]]:
17+
def handle_values(
18+
obj: dict, handler: tuple[str, callable]
19+
) -> tuple[str, Optional[any]]:
2520
key, callback = handler
2621
return key, silent(callback)(obj.get(key))
2722

@@ -34,8 +29,10 @@ class StorageOptions:
3429
aws_secret_access_key: str = None
3530
aws_region: str = None
3631
aws_session_token: str = None
37-
aws_storage_allow_http: bool = None
32+
aws_storage_allow_http: str = None
3833
endpoint_url: str = None
34+
aws_profile: str = None
35+
aws_role_session_name: str = None
3936

4037
@classmethod
4138
def from_config(cls, config: S3DeltaPlugin) -> "StorageOptions":
@@ -44,8 +41,10 @@ def from_config(cls, config: S3DeltaPlugin) -> "StorageOptions":
4441
aws_secret_access_key=config.aws_secret_access_key,
4542
aws_region=config.aws_region or cls.DEFAULT_REGION,
4643
aws_session_token=config.aws_session_token,
47-
aws_storage_allow_http="true" if config.aws_storage_allow_http else None,
4844
endpoint_url=config.endpoint_url,
45+
aws_storage_allow_http="true" if config.aws_storage_allow_http else None,
46+
aws_profile=config.profile_name,
47+
aws_role_session_name=config.aws_role_session_name,
4948
)
5049

5150
def to_dict(self) -> dict[str, str]:
@@ -75,10 +74,8 @@ def handle_folder(self, config: DeltaTableConfig) -> Iterable[DTable]:
7574

7675
objects = self.fs.get_file_info(remove_protocol(config.path))
7776

78-
allowed = filter(
79-
lambda obj: not obj.is_file and config.allow(obj.base_name),
80-
objects,
81-
)
77+
folders = filter(lambda obj: not obj.is_file, objects)
78+
allowed = filter(lambda folder: folder.base_name, folders)
8279

8380
for obj in allowed:
8481
new_config = config.append_prefix(obj.base_name)
@@ -142,12 +139,3 @@ def get_metadata(table: DeltaTable) -> dict[str, Any]:
142139
logger.error(f"Failed to get metadata for {table.table_uri}. {e}")
143140

144141
return metadata
145-
146-
147-
def remove_protocol(path: str) -> str:
148-
if path.startswith("s3://"):
149-
return path.removeprefix("s3://")
150-
elif path.startswith(("s3a://", "s3n://")):
151-
return path[6:]
152-
else:
153-
return path

0 commit comments

Comments
 (0)