iker-python-common 1.0.58__tar.gz → 1.0.59__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 (83) hide show
  1. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/PKG-INFO +1 -3
  2. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/pyproject.toml +0 -4
  3. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/src/iker/common/utils/shutils.py +9 -5
  4. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/src/iker_python_common.egg-info/PKG-INFO +1 -3
  5. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/src/iker_python_common.egg-info/SOURCES.txt +0 -12
  6. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/src/iker_python_common.egg-info/requires.txt +0 -2
  7. iker_python_common-1.0.58/resources/unittest/shutils/dir.baz/file.bar.baz +0 -0
  8. iker_python_common-1.0.58/resources/unittest/shutils/dir.baz/file.foo.bar +0 -0
  9. iker_python_common-1.0.58/resources/unittest/shutils/dir.baz/file.foo.baz +0 -0
  10. iker_python_common-1.0.58/resources/unittest/shutils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz +0 -0
  11. iker_python_common-1.0.58/resources/unittest/shutils/dir.foo/dir.foo.bar/file.bar.baz +0 -0
  12. iker_python_common-1.0.58/resources/unittest/shutils/dir.foo/dir.foo.bar/file.foo.bar +0 -0
  13. iker_python_common-1.0.58/resources/unittest/shutils/dir.foo/dir.foo.bar/file.foo.baz +0 -0
  14. iker_python_common-1.0.58/resources/unittest/shutils/dir.foo/file.bar +0 -0
  15. iker_python_common-1.0.58/resources/unittest/shutils/dir.foo/file.baz +0 -0
  16. iker_python_common-1.0.58/resources/unittest/shutils/dir.foo/file.foo +0 -0
  17. iker_python_common-1.0.58/src/iker/common/utils/s3utils.py +0 -270
  18. iker_python_common-1.0.58/test/iker_tests/common/utils/s3utils_test.py +0 -422
  19. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/.editorconfig +0 -0
  20. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/.github/workflows/pr.yml +0 -0
  21. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/.github/workflows/push.yml +0 -0
  22. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/.gitignore +0 -0
  23. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/MANIFEST.in +0 -0
  24. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/README.md +0 -0
  25. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/VERSION +0 -0
  26. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/resources/unittest/config/config.cfg +0 -0
  27. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/resources/unittest/csv/data.csv +0 -0
  28. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/resources/unittest/csv/data.tsv +0 -0
  29. {iker_python_common-1.0.58/resources/unittest/s3utils → iker_python_common-1.0.59/resources/unittest/shutils}/dir.baz/file.bar.baz +0 -0
  30. {iker_python_common-1.0.58/resources/unittest/s3utils → iker_python_common-1.0.59/resources/unittest/shutils}/dir.baz/file.foo.bar +0 -0
  31. {iker_python_common-1.0.58/resources/unittest/s3utils → iker_python_common-1.0.59/resources/unittest/shutils}/dir.baz/file.foo.baz +0 -0
  32. {iker_python_common-1.0.58/resources/unittest/s3utils → iker_python_common-1.0.59/resources/unittest/shutils}/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz +0 -0
  33. {iker_python_common-1.0.58/resources/unittest/s3utils → iker_python_common-1.0.59/resources/unittest/shutils}/dir.foo/dir.foo.bar/file.bar.baz +0 -0
  34. {iker_python_common-1.0.58/resources/unittest/s3utils → iker_python_common-1.0.59/resources/unittest/shutils}/dir.foo/dir.foo.bar/file.foo.bar +0 -0
  35. {iker_python_common-1.0.58/resources/unittest/s3utils → iker_python_common-1.0.59/resources/unittest/shutils}/dir.foo/dir.foo.bar/file.foo.baz +0 -0
  36. {iker_python_common-1.0.58/resources/unittest/s3utils → iker_python_common-1.0.59/resources/unittest/shutils}/dir.foo/file.bar +0 -0
  37. {iker_python_common-1.0.58/resources/unittest/s3utils → iker_python_common-1.0.59/resources/unittest/shutils}/dir.foo/file.baz +0 -0
  38. {iker_python_common-1.0.58/resources/unittest/s3utils → iker_python_common-1.0.59/resources/unittest/shutils}/dir.foo/file.foo +0 -0
  39. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/setup.cfg +0 -0
  40. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/setup.py +0 -0
  41. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/src/iker/common/__init__.py +0 -0
  42. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/src/iker/common/utils/__init__.py +0 -0
  43. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/src/iker/common/utils/argutils.py +0 -0
  44. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/src/iker/common/utils/config.py +0 -0
  45. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/src/iker/common/utils/csv.py +0 -0
  46. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/src/iker/common/utils/dbutils.py +0 -0
  47. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/src/iker/common/utils/dockerutils.py +0 -0
  48. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/src/iker/common/utils/dtutils.py +0 -0
  49. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/src/iker/common/utils/funcutils.py +0 -0
  50. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/src/iker/common/utils/jsonutils.py +0 -0
  51. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/src/iker/common/utils/logger.py +0 -0
  52. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/src/iker/common/utils/numutils.py +0 -0
  53. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/src/iker/common/utils/randutils.py +0 -0
  54. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/src/iker/common/utils/retry.py +0 -0
  55. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/src/iker/common/utils/sequtils.py +0 -0
  56. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/src/iker/common/utils/span.py +0 -0
  57. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/src/iker/common/utils/strutils.py +0 -0
  58. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/src/iker/common/utils/testutils.py +0 -0
  59. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/src/iker/common/utils/typeutils.py +0 -0
  60. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/src/iker_python_common.egg-info/dependency_links.txt +0 -0
  61. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/src/iker_python_common.egg-info/not-zip-safe +0 -0
  62. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/src/iker_python_common.egg-info/top_level.txt +0 -0
  63. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/test/iker_test.py +0 -0
  64. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/test/iker_tests/__init__.py +0 -0
  65. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/test/iker_tests/common/utils/argutils_test.py +0 -0
  66. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/test/iker_tests/common/utils/config_test.py +0 -0
  67. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/test/iker_tests/common/utils/csv_test.py +0 -0
  68. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/test/iker_tests/common/utils/dbutils_test.py +0 -0
  69. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/test/iker_tests/common/utils/dockerutils_test.py +0 -0
  70. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/test/iker_tests/common/utils/dtutils_test.py +0 -0
  71. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/test/iker_tests/common/utils/funcutils_test.py +0 -0
  72. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/test/iker_tests/common/utils/jsonutils_test.py +0 -0
  73. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/test/iker_tests/common/utils/logger_test.py +0 -0
  74. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/test/iker_tests/common/utils/numutils_test.py +0 -0
  75. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/test/iker_tests/common/utils/randutils_test.py +0 -0
  76. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/test/iker_tests/common/utils/retry_test.py +0 -0
  77. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/test/iker_tests/common/utils/sequtils_test.py +0 -0
  78. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/test/iker_tests/common/utils/shutils_test.py +0 -0
  79. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/test/iker_tests/common/utils/span_test.py +0 -0
  80. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/test/iker_tests/common/utils/strutils_test.py +0 -0
  81. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/test/iker_tests/common/utils/testutils_test.py +0 -0
  82. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/test/iker_tests/common/utils/typeutils_test.py +0 -0
  83. {iker_python_common-1.0.58 → iker_python_common-1.0.59}/test/iker_tests/docker_fixtures.py +0 -0
@@ -1,12 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iker-python-common
3
- Version: 1.0.58
3
+ Version: 1.0.59
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.12
6
6
  Classifier: Programming Language :: Python :: 3.13
7
7
  Classifier: Programming Language :: Python :: 3.14
8
8
  Requires-Python: <3.15,>=3.12
9
- Requires-Dist: boto3>=1.35
10
9
  Requires-Dist: docker>=7.1
11
10
  Requires-Dist: numpy>=2.3
12
11
  Requires-Dist: psycopg>=3.2
@@ -16,7 +15,6 @@ Provides-Extra: all
16
15
  Requires-Dist: iker-python-common; extra == "all"
17
16
  Provides-Extra: test
18
17
  Requires-Dist: ddt>=1.7; extra == "test"
19
- Requires-Dist: moto[all,ec2,s3]>=5.0; extra == "test"
20
18
  Requires-Dist: pytest-cov>=5.0; extra == "test"
21
19
  Requires-Dist: pytest-mysql>=3.0; extra == "test"
22
20
  Requires-Dist: pytest-order>=1.3; extra == "test"
@@ -8,7 +8,6 @@ build-backend = "setuptools.build_meta"
8
8
 
9
9
  [dependency-groups]
10
10
  dev = [
11
- "boto3>=1.35",
12
11
  "docker>=7.1",
13
12
  "numpy>=2.3",
14
13
  "psycopg>=3.2",
@@ -17,7 +16,6 @@ dev = [
17
16
  ]
18
17
  test = [
19
18
  "ddt>=1.7",
20
- "moto[ec2,s3,all]>=5.0",
21
19
  "pytest-cov>=5.0",
22
20
  "pytest-mysql>=3.0",
23
21
  "pytest-order>=1.3",
@@ -36,7 +34,6 @@ classifiers = [
36
34
  "Programming Language :: Python :: 3.14",
37
35
  ]
38
36
  dependencies = [
39
- "boto3>=1.35",
40
37
  "docker>=7.1",
41
38
  "numpy>=2.3",
42
39
  "psycopg>=3.2",
@@ -50,7 +47,6 @@ all = [
50
47
  ]
51
48
  test = [
52
49
  "ddt>=1.7",
53
- "moto[ec2,s3,all]>=5.0",
54
50
  "pytest-cov>=5.0",
55
51
  "pytest-mysql>=3.0",
56
52
  "pytest-order>=1.3",
@@ -95,7 +95,11 @@ def path_depth(root: str, child: str) -> int:
95
95
  return child_expanded[len(root_expanded):].count(os.sep)
96
96
 
97
97
 
98
- def glob_match(names: list[str], include_patterns: list[str] = None, exclude_patterns: list[str] = None) -> list[str]:
98
+ def glob_match(
99
+ names: list[str],
100
+ include_patterns: list[str] | None = None,
101
+ exclude_patterns: list[str] | None = None,
102
+ ) -> list[str]:
99
103
  """
100
104
  Applies the given inclusive and exclusive glob patterns to the given ``names`` and returns the filtered result.
101
105
 
@@ -121,8 +125,8 @@ class CopyFuncProtocol(Protocol):
121
125
  def listfile(
122
126
  path: str,
123
127
  *,
124
- include_patterns: list[str] = None,
125
- exclude_patterns: list[str] = None,
128
+ include_patterns: list[str] | None = None,
129
+ exclude_patterns: list[str] | None = None,
126
130
  depth: int = 0,
127
131
  ) -> list[str]:
128
132
  """
@@ -153,8 +157,8 @@ def copy(
153
157
  src: str,
154
158
  dst: str,
155
159
  *,
156
- include_patterns: list[str] = None,
157
- exclude_patterns: list[str] = None,
160
+ include_patterns: list[str] | None = None,
161
+ exclude_patterns: list[str] | None = None,
158
162
  depth: int = 0,
159
163
  follow_symlinks: bool = False,
160
164
  ignore_dangling_symlinks: bool = False,
@@ -1,12 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iker-python-common
3
- Version: 1.0.58
3
+ Version: 1.0.59
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.12
6
6
  Classifier: Programming Language :: Python :: 3.13
7
7
  Classifier: Programming Language :: Python :: 3.14
8
8
  Requires-Python: <3.15,>=3.12
9
- Requires-Dist: boto3>=1.35
10
9
  Requires-Dist: docker>=7.1
11
10
  Requires-Dist: numpy>=2.3
12
11
  Requires-Dist: psycopg>=3.2
@@ -16,7 +15,6 @@ Provides-Extra: all
16
15
  Requires-Dist: iker-python-common; extra == "all"
17
16
  Provides-Extra: test
18
17
  Requires-Dist: ddt>=1.7; extra == "test"
19
- Requires-Dist: moto[all,ec2,s3]>=5.0; extra == "test"
20
18
  Requires-Dist: pytest-cov>=5.0; extra == "test"
21
19
  Requires-Dist: pytest-mysql>=3.0; extra == "test"
22
20
  Requires-Dist: pytest-order>=1.3; extra == "test"
@@ -10,16 +10,6 @@ setup.py
10
10
  resources/unittest/config/config.cfg
11
11
  resources/unittest/csv/data.csv
12
12
  resources/unittest/csv/data.tsv
13
- resources/unittest/s3utils/dir.baz/file.bar.baz
14
- resources/unittest/s3utils/dir.baz/file.foo.bar
15
- resources/unittest/s3utils/dir.baz/file.foo.baz
16
- resources/unittest/s3utils/dir.foo/file.bar
17
- resources/unittest/s3utils/dir.foo/file.baz
18
- resources/unittest/s3utils/dir.foo/file.foo
19
- resources/unittest/s3utils/dir.foo/dir.foo.bar/file.bar.baz
20
- resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.bar
21
- resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.baz
22
- resources/unittest/s3utils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz
23
13
  resources/unittest/shutils/dir.baz/file.bar.baz
24
14
  resources/unittest/shutils/dir.baz/file.foo.bar
25
15
  resources/unittest/shutils/dir.baz/file.foo.baz
@@ -44,7 +34,6 @@ src/iker/common/utils/logger.py
44
34
  src/iker/common/utils/numutils.py
45
35
  src/iker/common/utils/randutils.py
46
36
  src/iker/common/utils/retry.py
47
- src/iker/common/utils/s3utils.py
48
37
  src/iker/common/utils/sequtils.py
49
38
  src/iker/common/utils/shutils.py
50
39
  src/iker/common/utils/span.py
@@ -72,7 +61,6 @@ test/iker_tests/common/utils/logger_test.py
72
61
  test/iker_tests/common/utils/numutils_test.py
73
62
  test/iker_tests/common/utils/randutils_test.py
74
63
  test/iker_tests/common/utils/retry_test.py
75
- test/iker_tests/common/utils/s3utils_test.py
76
64
  test/iker_tests/common/utils/sequtils_test.py
77
65
  test/iker_tests/common/utils/shutils_test.py
78
66
  test/iker_tests/common/utils/span_test.py
@@ -1,4 +1,3 @@
1
- boto3>=1.35
2
1
  docker>=7.1
3
2
  numpy>=2.3
4
3
  psycopg>=3.2
@@ -10,7 +9,6 @@ iker-python-common
10
9
 
11
10
  [test]
12
11
  ddt>=1.7
13
- moto[all,ec2,s3]>=5.0
14
12
  pytest-cov>=5.0
15
13
  pytest-mysql>=3.0
16
14
  pytest-order>=1.3
@@ -1,270 +0,0 @@
1
- import concurrent.futures
2
- import contextlib
3
- import dataclasses
4
- import datetime
5
- import mimetypes
6
- import os
7
- import tempfile
8
-
9
- import boto3
10
- from botocore.client import BaseClient
11
-
12
- from iker.common.utils.shutils import glob_match, listfile, path_depth
13
- from iker.common.utils.strutils import is_empty, trim_to_none
14
-
15
- __all__ = [
16
- "S3ObjectMeta",
17
- "s3_make_client",
18
- "s3_list_objects",
19
- "s3_listfile",
20
- "s3_cp_download",
21
- "s3_cp_upload",
22
- "s3_sync_download",
23
- "s3_sync_upload",
24
- "s3_pull_text",
25
- "s3_push_text",
26
- ]
27
-
28
-
29
- @dataclasses.dataclass
30
- class S3ObjectMeta(object):
31
- key: str
32
- last_modified: datetime.datetime
33
- size: int
34
-
35
-
36
- def s3_make_client(
37
- access_key_id: str = None,
38
- secret_access_key: str = None,
39
- region_name: str = None,
40
- endpoint_url: str = None,
41
- ) -> contextlib.AbstractContextManager[BaseClient]:
42
- """
43
- Creates an AWS S3 client as a context manager for safe resource handling.
44
-
45
- :param access_key_id: AWS access key ID.
46
- :param secret_access_key: AWS secret access key.
47
- :param region_name: AWS service region name.
48
- :param endpoint_url: AWS service endpoint URL.
49
- :return: A context manager yielding an S3 client instance.
50
- """
51
- client = boto3.client("s3",
52
- region_name=trim_to_none(region_name),
53
- endpoint_url=trim_to_none(endpoint_url),
54
- aws_access_key_id=trim_to_none(access_key_id),
55
- aws_secret_access_key=trim_to_none(secret_access_key))
56
- return contextlib.closing(client)
57
-
58
-
59
- def s3_list_objects(client: BaseClient, bucket: str, prefix: str, limit: int = None) -> list[S3ObjectMeta]:
60
- """
61
- Lists all objects from the given S3 ``bucket`` and ``prefix``.
62
-
63
- :param client: AWS S3 client instance.
64
- :param bucket: Bucket name.
65
- :param prefix: Object keys prefix.
66
- :param limit: Maximum number of objects to return (``None`` for all).
67
- :return: List of ``S3ObjectMeta`` items.
68
- """
69
- entries = []
70
-
71
- next_marker = None
72
- while True:
73
- if is_empty(next_marker):
74
- response = client.list_objects(MaxKeys=1000, Bucket=bucket, Prefix=prefix)
75
- else:
76
- response = client.list_objects(MaxKeys=1000, Bucket=bucket, Prefix=prefix, Marker=next_marker)
77
-
78
- entries.extend(response.get("Contents", []))
79
-
80
- if limit is not None and len(entries) >= limit:
81
- entries = entries[:limit]
82
-
83
- if not response.get("IsTruncated"):
84
- break
85
-
86
- next_marker = response.get("NextMarker")
87
- if is_empty(next_marker):
88
- next_marker = entries[-1]["Key"]
89
-
90
- return [S3ObjectMeta(key=e["Key"], last_modified=e["LastModified"], size=e["Size"]) for e in entries]
91
-
92
-
93
- def s3_listfile(
94
- client: BaseClient,
95
- bucket: str,
96
- prefix: str,
97
- *,
98
- include_patterns: list[str] = None,
99
- exclude_patterns: list[str] = None,
100
- depth: int = 0,
101
- ) -> list[S3ObjectMeta]:
102
- """
103
- Lists all objects from the given S3 ``bucket`` and ``prefix``, filtered by patterns and directory depth.
104
-
105
- :param client: AWS S3 client instance.
106
- :param bucket: Bucket name.
107
- :param prefix: Object keys prefix.
108
- :param include_patterns: Inclusive glob patterns applied to filenames.
109
- :param exclude_patterns: Exclusive glob patterns applied to filenames.
110
- :param depth: Maximum depth of subdirectories to include in the scan.
111
- :return: List of ``S3ObjectMeta`` items.
112
- """
113
-
114
- # We add trailing slash "/" to the prefix if it is absent
115
- if not prefix.endswith("/"):
116
- prefix = prefix + "/"
117
-
118
- objects = s3_list_objects(client, bucket, prefix)
119
-
120
- def filter_object_meta(object_meta: S3ObjectMeta) -> bool:
121
- if 0 < depth <= path_depth(prefix, os.path.dirname(object_meta.key)):
122
- return False
123
- if len(glob_match([os.path.basename(object_meta.key)], include_patterns, exclude_patterns)) == 0:
124
- return False
125
- return True
126
-
127
- return list(filter(filter_object_meta, objects))
128
-
129
-
130
- def s3_cp_download(client: BaseClient, bucket: str, key: str, file_path: str):
131
- """
132
- Downloads an object from the given S3 ``bucket`` and ``key`` to a local file path.
133
-
134
- :param client: AWS S3 client instance.
135
- :param bucket: Bucket name.
136
- :param key: Object key.
137
- :param file_path: Local file path to save the object.
138
- """
139
- client.download_file(bucket, key, file_path)
140
-
141
-
142
- def s3_cp_upload(client: BaseClient, file_path: str, bucket: str, key: str):
143
- """
144
- Uploads a local file to the given S3 ``bucket`` and ``key``.
145
-
146
- :param client: AWS S3 client instance.
147
- :param file_path: Local file path to upload.
148
- :param bucket: Bucket name.
149
- :param key: Object key for the uploaded file.
150
- """
151
- t, _ = mimetypes.MimeTypes().guess_type(file_path)
152
- client.upload_file(file_path, bucket, key, ExtraArgs={"ContentType": "binary/octet-stream" if t is None else t})
153
-
154
-
155
- def s3_sync_download(
156
- client: BaseClient,
157
- bucket: str,
158
- prefix: str,
159
- dir_path: str,
160
- *,
161
- max_workers: int = None,
162
- include_patterns: list[str] = None,
163
- exclude_patterns: list[str] = None,
164
- depth: int = 0,
165
- ):
166
- """
167
- Recursively downloads all objects from the given S3 ``bucket`` and ``prefix`` to a local directory path, using a thread pool.
168
-
169
- :param client: AWS S3 client instance.
170
- :param bucket: Bucket name.
171
- :param prefix: Object keys prefix.
172
- :param dir_path: Local directory path to save objects.
173
- :param max_workers: Maximum number of worker threads.
174
- :param include_patterns: Inclusive glob patterns applied to filenames.
175
- :param exclude_patterns: Exclusive glob patterns applied to filenames.
176
- :param depth: Maximum depth of subdirectories to include in the scan.
177
- """
178
-
179
- # We add trailing slash "/" to the prefix if it is absent
180
- if not prefix.endswith("/"):
181
- prefix = prefix + "/"
182
-
183
- objects = s3_listfile(client,
184
- bucket,
185
- prefix,
186
- include_patterns=include_patterns,
187
- exclude_patterns=exclude_patterns,
188
- depth=depth)
189
-
190
- def download_file(key: str):
191
- file_path = os.path.join(dir_path, key[len(prefix):])
192
- os.makedirs(os.path.dirname(file_path), exist_ok=True)
193
- s3_cp_download(client, bucket, key, file_path)
194
-
195
- with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
196
- concurrent.futures.wait([executor.submit(download_file, obj.key) for obj in objects],
197
- return_when=concurrent.futures.FIRST_EXCEPTION)
198
-
199
-
200
- def s3_sync_upload(
201
- client: BaseClient,
202
- dir_path: str,
203
- bucket: str,
204
- prefix: str,
205
- *,
206
- max_workers: int = None,
207
- include_patterns: list[str] = None,
208
- exclude_patterns: list[str] = None,
209
- depth: int = 0,
210
- ):
211
- """
212
- Recursively uploads all files from a local directory to the given S3 ``bucket`` and ``prefix``, using a thread pool.
213
-
214
- :param client: AWS S3 client instance.
215
- :param dir_path: Local directory path to upload from.
216
- :param bucket: Bucket name.
217
- :param prefix: Object keys prefix for uploaded files.
218
- :param max_workers: Maximum number of worker threads.
219
- :param include_patterns: Inclusive glob patterns applied to filenames.
220
- :param exclude_patterns: Exclusive glob patterns applied to filenames.
221
- :param depth: Maximum depth of subdirectories to include in the scan.
222
- """
223
-
224
- # We add trailing slash "/" to the prefix if it is absent
225
- if not prefix.endswith("/"):
226
- prefix = prefix + "/"
227
-
228
- file_paths = listfile(dir_path,
229
- include_patterns=include_patterns,
230
- exclude_patterns=exclude_patterns,
231
- depth=depth)
232
-
233
- def upload_file(file_path: str):
234
- s3_cp_upload(client, file_path, bucket, prefix + os.path.relpath(file_path, dir_path))
235
-
236
- with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
237
- concurrent.futures.wait([executor.submit(upload_file, file_path) for file_path in file_paths],
238
- return_when=concurrent.futures.FIRST_EXCEPTION)
239
-
240
-
241
- def s3_pull_text(client: BaseClient, bucket: str, key: str, encoding: str = None) -> str:
242
- """
243
- Downloads and decodes text content stored as an object in the given S3 ``bucket`` and ``key``.
244
-
245
- :param client: AWS S3 client instance.
246
- :param bucket: Bucket name.
247
- :param key: Object key storing the text.
248
- :param encoding: String encoding to use (defaults to UTF-8).
249
- :return: The decoded text content.
250
- """
251
- with tempfile.TemporaryFile() as fp:
252
- client.download_fileobj(bucket, key, fp)
253
- fp.seek(0)
254
- return fp.read().decode(encoding or "utf-8")
255
-
256
-
257
- def s3_push_text(client: BaseClient, text: str, bucket: str, key: str, encoding: str = None):
258
- """
259
- Uploads the given text as an object to the specified S3 ``bucket`` and ``key``.
260
-
261
- :param client: AWS S3 client instance.
262
- :param text: Text content to upload.
263
- :param bucket: Bucket name.
264
- :param key: Object key to store the text.
265
- :param encoding: String encoding to use (defaults to UTF-8).
266
- """
267
- with tempfile.TemporaryFile() as fp:
268
- fp.write(text.encode(encoding or "utf-8"))
269
- fp.seek(0)
270
- client.upload_fileobj(fp, bucket, key)
@@ -1,422 +0,0 @@
1
- import tempfile
2
- import unittest
3
-
4
- import ddt
5
- import moto
6
-
7
- from iker.common.utils.randutils import randomizer
8
- from iker.common.utils.s3utils import *
9
- from iker.common.utils.shutils import listfile
10
- from iker.common.utils.testutils import norm_path
11
- from iker_tests import resources_directory
12
-
13
-
14
- @ddt.ddt
15
- class S3UtilsTest(unittest.TestCase):
16
-
17
- def test_s3_list_object__random_text_files(self):
18
- with moto.mock_aws(), s3_make_client(region_name="us-east-1") as client:
19
- client.create_bucket(Bucket="dummy-bucket")
20
-
21
- data = []
22
- for i in range(0, 1000):
23
- text = randomizer().random_alphanumeric(randomizer().next_int(1000, 2000))
24
- key = "dummy_prefix/%03d" % i
25
- data.append((key, text))
26
-
27
- for key, text in data:
28
- s3_push_text(client, text, "dummy-bucket", key)
29
-
30
- result = s3_list_objects(client, "dummy-bucket", "dummy_prefix/")
31
-
32
- for key, text in data:
33
- self.assertTrue(any(o.key == key for o in result))
34
- self.assertEqual(s3_pull_text(client, "dummy-bucket", key), text)
35
-
36
- def test_s3_listfile__random_text_files(self):
37
- with moto.mock_aws(), s3_make_client(region_name="us-east-1") as client:
38
- client.create_bucket(Bucket="dummy-bucket")
39
-
40
- data = []
41
- for i in range(0, 1000):
42
- text = randomizer().random_alphanumeric(randomizer().next_int(1000, 2000))
43
- key = "dummy_prefix/%03d" % i
44
- data.append((key, text))
45
-
46
- for key, text in data:
47
- s3_push_text(client, text, "dummy-bucket", key)
48
-
49
- result = s3_listfile(client, "dummy-bucket", "dummy_prefix/")
50
-
51
- for key, text in data:
52
- self.assertTrue(any(o.key == key for o in result))
53
- self.assertEqual(s3_pull_text(client, "dummy-bucket", key), text)
54
-
55
- data_s3_listfile = [
56
- (
57
- "{res_dir}/unittest/s3utils/",
58
- "dummy-bucket",
59
- "unittest/s3utils/",
60
- [],
61
- [],
62
- 0,
63
- [
64
- "unittest/s3utils/dir.baz/file.foo.bar",
65
- "unittest/s3utils/dir.baz/file.foo.baz",
66
- "unittest/s3utils/dir.baz/file.bar.baz",
67
- "unittest/s3utils/dir.foo/file.bar",
68
- "unittest/s3utils/dir.foo/file.baz",
69
- "unittest/s3utils/dir.foo/file.foo",
70
- "unittest/s3utils/dir.foo/dir.foo.bar/file.foo.bar",
71
- "unittest/s3utils/dir.foo/dir.foo.bar/file.foo.baz",
72
- "unittest/s3utils/dir.foo/dir.foo.bar/file.bar.baz",
73
- "unittest/s3utils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz",
74
- ],
75
- ),
76
- (
77
- "{res_dir}/unittest/s3utils",
78
- "dummy-bucket",
79
- "unittest/s3utils/",
80
- [],
81
- [],
82
- 0,
83
- [
84
- "unittest/s3utils/dir.baz/file.foo.bar",
85
- "unittest/s3utils/dir.baz/file.foo.baz",
86
- "unittest/s3utils/dir.baz/file.bar.baz",
87
- "unittest/s3utils/dir.foo/file.bar",
88
- "unittest/s3utils/dir.foo/file.baz",
89
- "unittest/s3utils/dir.foo/file.foo",
90
- "unittest/s3utils/dir.foo/dir.foo.bar/file.foo.bar",
91
- "unittest/s3utils/dir.foo/dir.foo.bar/file.foo.baz",
92
- "unittest/s3utils/dir.foo/dir.foo.bar/file.bar.baz",
93
- "unittest/s3utils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz",
94
- ],
95
- ),
96
- (
97
- "{res_dir}/unittest/s3utils/dir.foo",
98
- "dummy-bucket",
99
- "unittest/s3utils/dir.foo",
100
- [],
101
- [],
102
- 0,
103
- [
104
- "unittest/s3utils/dir.foo/file.bar",
105
- "unittest/s3utils/dir.foo/file.baz",
106
- "unittest/s3utils/dir.foo/file.foo",
107
- "unittest/s3utils/dir.foo/dir.foo.bar/file.foo.bar",
108
- "unittest/s3utils/dir.foo/dir.foo.bar/file.foo.baz",
109
- "unittest/s3utils/dir.foo/dir.foo.bar/file.bar.baz",
110
- "unittest/s3utils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz",
111
- ],
112
- ),
113
- (
114
- "{res_dir}/unittest/s3utils/dir.baz",
115
- "dummy-bucket",
116
- "unittest/s3utils/dir.baz",
117
- [],
118
- [],
119
- 0,
120
- [
121
- "unittest/s3utils/dir.baz/file.foo.bar",
122
- "unittest/s3utils/dir.baz/file.foo.baz",
123
- "unittest/s3utils/dir.baz/file.bar.baz",
124
- ],
125
- ),
126
- (
127
- "{res_dir}/unittest/s3utils",
128
- "dummy-bucket",
129
- "unittest/s3utils/",
130
- ["*.foo", "*.bar"],
131
- ["*.foo.bar"],
132
- 0,
133
- [
134
- "unittest/s3utils/dir.foo/file.bar",
135
- "unittest/s3utils/dir.foo/file.foo",
136
- ],
137
- ),
138
- (
139
- "{res_dir}/unittest/s3utils",
140
- "dummy-bucket",
141
- "unittest/s3utils/",
142
- ["*.foo", "*.bar"],
143
- [],
144
- 0,
145
- [
146
- "unittest/s3utils/dir.baz/file.foo.bar",
147
- "unittest/s3utils/dir.foo/file.bar",
148
- "unittest/s3utils/dir.foo/file.foo",
149
- "unittest/s3utils/dir.foo/dir.foo.bar/file.foo.bar",
150
- ],
151
- ),
152
- (
153
- "{res_dir}/unittest/s3utils",
154
- "dummy-bucket",
155
- "unittest/s3utils/",
156
- [],
157
- ["*.baz"],
158
- 0,
159
- [
160
- "unittest/s3utils/dir.baz/file.foo.bar",
161
- "unittest/s3utils/dir.foo/file.bar",
162
- "unittest/s3utils/dir.foo/file.foo",
163
- "unittest/s3utils/dir.foo/dir.foo.bar/file.foo.bar",
164
- ],
165
- ),
166
- (
167
- "{res_dir}/unittest/s3utils",
168
- "dummy-bucket",
169
- "unittest/s3utils",
170
- [],
171
- [],
172
- 2,
173
- [
174
- "unittest/s3utils/dir.baz/file.foo.bar",
175
- "unittest/s3utils/dir.baz/file.foo.baz",
176
- "unittest/s3utils/dir.baz/file.bar.baz",
177
- "unittest/s3utils/dir.foo/file.bar",
178
- "unittest/s3utils/dir.foo/file.baz",
179
- "unittest/s3utils/dir.foo/file.foo",
180
- ],
181
- ),
182
- ]
183
-
184
- @ddt.idata(data_s3_listfile)
185
- @ddt.unpack
186
- def test_s3_listfile(self, src, bucket, prefix, include_patterns, exclude_patterns, depth, expect):
187
- with moto.mock_aws(), s3_make_client(region_name="us-east-1") as client:
188
- client.create_bucket(Bucket=bucket)
189
-
190
- s3_sync_upload(client,
191
- src.format(res_dir=resources_directory),
192
- bucket,
193
- prefix)
194
-
195
- object_metas = s3_listfile(client,
196
- bucket,
197
- prefix,
198
- include_patterns=include_patterns,
199
- exclude_patterns=exclude_patterns,
200
- depth=depth)
201
-
202
- self.assertSetEqual(set(map(lambda x: norm_path(x.key), object_metas)),
203
- set(map(lambda x: norm_path(x), expect)))
204
-
205
- data_s3_sync = [
206
- (
207
- "{res_dir}/unittest/s3utils/",
208
- "{tmp_dir}/unittest/s3utils/",
209
- "dummy-bucket",
210
- "unittest/s3utils/",
211
- [],
212
- [],
213
- 0,
214
- [
215
- "{tmp_dir}/unittest/s3utils/dir.baz/file.foo.bar",
216
- "{tmp_dir}/unittest/s3utils/dir.baz/file.foo.baz",
217
- "{tmp_dir}/unittest/s3utils/dir.baz/file.bar.baz",
218
- "{tmp_dir}/unittest/s3utils/dir.foo/file.bar",
219
- "{tmp_dir}/unittest/s3utils/dir.foo/file.baz",
220
- "{tmp_dir}/unittest/s3utils/dir.foo/file.foo",
221
- "{tmp_dir}/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.bar",
222
- "{tmp_dir}/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.baz",
223
- "{tmp_dir}/unittest/s3utils/dir.foo/dir.foo.bar/file.bar.baz",
224
- "{tmp_dir}/unittest/s3utils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz",
225
- ],
226
- ),
227
- (
228
- "{res_dir}/unittest/s3utils",
229
- "{tmp_dir}/unittest/s3utils",
230
- "dummy-bucket",
231
- "unittest/s3utils/",
232
- [],
233
- [],
234
- 0,
235
- [
236
- "{tmp_dir}/unittest/s3utils/dir.baz/file.foo.bar",
237
- "{tmp_dir}/unittest/s3utils/dir.baz/file.foo.baz",
238
- "{tmp_dir}/unittest/s3utils/dir.baz/file.bar.baz",
239
- "{tmp_dir}/unittest/s3utils/dir.foo/file.bar",
240
- "{tmp_dir}/unittest/s3utils/dir.foo/file.baz",
241
- "{tmp_dir}/unittest/s3utils/dir.foo/file.foo",
242
- "{tmp_dir}/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.bar",
243
- "{tmp_dir}/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.baz",
244
- "{tmp_dir}/unittest/s3utils/dir.foo/dir.foo.bar/file.bar.baz",
245
- "{tmp_dir}/unittest/s3utils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz",
246
- ],
247
- ),
248
- (
249
- "{res_dir}/unittest/s3utils/dir.foo",
250
- "{tmp_dir}/unittest/s3utils/dir.foo",
251
- "dummy-bucket",
252
- "unittest/s3utils/dir.foo",
253
- [],
254
- [],
255
- 0,
256
- [
257
- "{tmp_dir}/unittest/s3utils/dir.foo/file.bar",
258
- "{tmp_dir}/unittest/s3utils/dir.foo/file.baz",
259
- "{tmp_dir}/unittest/s3utils/dir.foo/file.foo",
260
- "{tmp_dir}/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.bar",
261
- "{tmp_dir}/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.baz",
262
- "{tmp_dir}/unittest/s3utils/dir.foo/dir.foo.bar/file.bar.baz",
263
- "{tmp_dir}/unittest/s3utils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz",
264
- ],
265
- ),
266
- (
267
- "{res_dir}/unittest/s3utils/dir.baz",
268
- "{tmp_dir}/unittest/s3utils/dir.baz",
269
- "dummy-bucket",
270
- "unittest/s3utils/dir.baz",
271
- [],
272
- [],
273
- 0,
274
- [
275
- "{tmp_dir}/unittest/s3utils/dir.baz/file.foo.bar",
276
- "{tmp_dir}/unittest/s3utils/dir.baz/file.foo.baz",
277
- "{tmp_dir}/unittest/s3utils/dir.baz/file.bar.baz",
278
- ],
279
- ),
280
- (
281
- "{res_dir}/unittest/s3utils",
282
- "{tmp_dir}/unittest/s3utils",
283
- "dummy-bucket",
284
- "unittest/s3utils/",
285
- ["*.foo", "*.bar"],
286
- ["*.foo.bar"],
287
- 0,
288
- [
289
- "{tmp_dir}/unittest/s3utils/dir.foo/file.bar",
290
- "{tmp_dir}/unittest/s3utils/dir.foo/file.foo",
291
- ],
292
- ),
293
- (
294
- "{res_dir}/unittest/s3utils",
295
- "{tmp_dir}/unittest/s3utils",
296
- "dummy-bucket",
297
- "unittest/s3utils/",
298
- ["*.foo", "*.bar"],
299
- [],
300
- 0,
301
- [
302
- "{tmp_dir}/unittest/s3utils/dir.baz/file.foo.bar",
303
- "{tmp_dir}/unittest/s3utils/dir.foo/file.bar",
304
- "{tmp_dir}/unittest/s3utils/dir.foo/file.foo",
305
- "{tmp_dir}/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.bar",
306
- ],
307
- ),
308
- (
309
- "{res_dir}/unittest/s3utils",
310
- "{tmp_dir}/unittest/s3utils",
311
- "dummy-bucket",
312
- "unittest/s3utils/",
313
- [],
314
- ["*.baz"],
315
- 0,
316
- [
317
- "{tmp_dir}/unittest/s3utils/dir.baz/file.foo.bar",
318
- "{tmp_dir}/unittest/s3utils/dir.foo/file.bar",
319
- "{tmp_dir}/unittest/s3utils/dir.foo/file.foo",
320
- "{tmp_dir}/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.bar",
321
- ],
322
- ),
323
- (
324
- "{res_dir}/unittest/s3utils",
325
- "{tmp_dir}/unittest/s3utils",
326
- "dummy-bucket",
327
- "unittest/s3utils",
328
- [],
329
- [],
330
- 2,
331
- [
332
- "{tmp_dir}/unittest/s3utils/dir.baz/file.foo.bar",
333
- "{tmp_dir}/unittest/s3utils/dir.baz/file.foo.baz",
334
- "{tmp_dir}/unittest/s3utils/dir.baz/file.bar.baz",
335
- "{tmp_dir}/unittest/s3utils/dir.foo/file.bar",
336
- "{tmp_dir}/unittest/s3utils/dir.foo/file.baz",
337
- "{tmp_dir}/unittest/s3utils/dir.foo/file.foo",
338
- ],
339
- ),
340
- ]
341
-
342
- @ddt.idata(data_s3_sync)
343
- @ddt.unpack
344
- def test_s3_sync(self, src, dst, bucket, prefix, include_patterns, exclude_patterns, depth, expect):
345
- with moto.mock_aws(), s3_make_client(region_name="us-east-1") as client:
346
- client.create_bucket(Bucket=bucket)
347
-
348
- s3_sync_upload(client,
349
- src.format(res_dir=resources_directory),
350
- bucket,
351
- prefix,
352
- include_patterns=include_patterns,
353
- exclude_patterns=exclude_patterns,
354
- depth=depth)
355
-
356
- with tempfile.TemporaryDirectory() as temp_directory:
357
- s3_sync_download(client,
358
- bucket,
359
- prefix,
360
- dst.format(tmp_dir=temp_directory))
361
-
362
- self.assertSetEqual(set(map(lambda x: norm_path(x), listfile(dst.format(tmp_dir=temp_directory)))),
363
- set(map(lambda x: norm_path(x.format(tmp_dir=temp_directory)), expect)))
364
-
365
- with moto.mock_aws(), s3_make_client(region_name="us-east-1") as client:
366
- client.create_bucket(Bucket=bucket)
367
-
368
- s3_sync_upload(client,
369
- src.format(res_dir=resources_directory),
370
- bucket,
371
- prefix)
372
-
373
- with tempfile.TemporaryDirectory() as temp_directory:
374
- s3_sync_download(client,
375
- bucket,
376
- prefix,
377
- dst.format(tmp_dir=temp_directory),
378
- include_patterns=include_patterns,
379
- exclude_patterns=exclude_patterns,
380
- depth=depth)
381
-
382
- self.assertSetEqual(set(map(lambda x: norm_path(x), listfile(dst.format(tmp_dir=temp_directory)))),
383
- set(map(lambda x: norm_path(x.format(tmp_dir=temp_directory)), expect)))
384
-
385
- with moto.mock_aws(), s3_make_client(region_name="us-east-1") as client:
386
- client.create_bucket(Bucket=bucket)
387
-
388
- s3_sync_upload(client,
389
- src.format(res_dir=resources_directory),
390
- bucket,
391
- prefix,
392
- include_patterns=include_patterns,
393
- depth=depth)
394
-
395
- with tempfile.TemporaryDirectory() as temp_directory:
396
- s3_sync_download(client,
397
- bucket,
398
- prefix,
399
- dst.format(tmp_dir=temp_directory),
400
- exclude_patterns=exclude_patterns,
401
- depth=depth)
402
-
403
- self.assertSetEqual(set(map(lambda x: norm_path(x), listfile(dst.format(tmp_dir=temp_directory)))),
404
- set(map(lambda x: norm_path(x.format(tmp_dir=temp_directory)), expect)))
405
-
406
- data_s3_text = [
407
- ("dummy-bucket", "dummy/key", "dummy content", None),
408
- ("dummy-bucket", "dummy/key.alpha", "Old MacDonald had a farm", None),
409
- ("dummy-bucket", "dummy/key.beta", "Ee-i-ee-i-o", None),
410
- ("dummy-bucket", "dummy/key", "dummy content", "ascii"),
411
- ("dummy-bucket", "dummy/key.alpha", "Old MacDonald had a farm", "ascii"),
412
- ("dummy-bucket", "dummy/key.beta", "Ee-i-ee-i-o", "ascii"),
413
- ]
414
-
415
- @ddt.idata(data_s3_text)
416
- @ddt.unpack
417
- def test_s3_text(self, bucket, key, text, encoding):
418
- with moto.mock_aws(), s3_make_client(region_name="us-east-1") as client:
419
- client.create_bucket(Bucket=bucket)
420
-
421
- s3_push_text(client, text, bucket, key, encoding=encoding)
422
- self.assertEqual(s3_pull_text(client, bucket, key, encoding=encoding), text)