bluer-objects 6.104.1__py3-none-any.whl → 6.464.1__py3-none-any.whl

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 (170) hide show
  1. bluer_objects/.abcli/abcli.sh +6 -0
  2. bluer_objects/.abcli/alias.sh +13 -6
  3. bluer_objects/.abcli/assets/cd.sh +20 -0
  4. bluer_objects/.abcli/assets/mv.sh +34 -0
  5. bluer_objects/.abcli/assets/publish.sh +40 -0
  6. bluer_objects/.abcli/assets.sh +15 -0
  7. bluer_objects/.abcli/create_test_asset.sh +10 -0
  8. bluer_objects/.abcli/download.sh +3 -1
  9. bluer_objects/.abcli/file.sh +15 -4
  10. bluer_objects/.abcli/gif.sh +18 -0
  11. bluer_objects/.abcli/host.sh +23 -7
  12. bluer_objects/.abcli/ls.sh +19 -8
  13. bluer_objects/.abcli/metadata/download.sh +9 -0
  14. bluer_objects/.abcli/metadata/edit.sh +15 -0
  15. bluer_objects/.abcli/metadata/upload.sh +9 -0
  16. bluer_objects/.abcli/mlflow/browse.sh +2 -0
  17. bluer_objects/.abcli/mlflow/deploy.sh +21 -5
  18. bluer_objects/.abcli/mlflow/lock/lock.sh +11 -0
  19. bluer_objects/.abcli/mlflow/lock/unlock.sh +12 -0
  20. bluer_objects/.abcli/mlflow/lock.sh +15 -0
  21. bluer_objects/.abcli/mlflow/tags/search.sh +1 -5
  22. bluer_objects/.abcli/mlflow.sh +0 -2
  23. bluer_objects/.abcli/pdf/convert.sh +92 -0
  24. bluer_objects/.abcli/pdf.sh +15 -0
  25. bluer_objects/.abcli/storage/clear.sh +2 -0
  26. bluer_objects/.abcli/tests/clone.sh +2 -3
  27. bluer_objects/.abcli/tests/create_test_asset.sh +16 -0
  28. bluer_objects/.abcli/tests/file.sh +64 -0
  29. bluer_objects/.abcli/tests/gif.sh +3 -3
  30. bluer_objects/.abcli/tests/help.sh +23 -7
  31. bluer_objects/.abcli/tests/ls.sh +11 -4
  32. bluer_objects/.abcli/tests/metadata.sh +35 -0
  33. bluer_objects/.abcli/tests/mlflow_lock.sh +30 -0
  34. bluer_objects/.abcli/tests/mlflow_tags.sh +1 -1
  35. bluer_objects/.abcli/tests/open.sh +11 -0
  36. bluer_objects/.abcli/tests/open_gif_open.sh +14 -0
  37. bluer_objects/.abcli/tests/pdf.sh +39 -0
  38. bluer_objects/.abcli/tests/storage_clear.sh +11 -0
  39. bluer_objects/.abcli/tests/storage_public_upload.sh +25 -0
  40. bluer_objects/.abcli/tests/storage_status.sh +12 -0
  41. bluer_objects/.abcli/tests/{storage.sh → storage_upload_download.sh} +26 -8
  42. bluer_objects/.abcli/upload.sh +26 -2
  43. bluer_objects/README/__init__.py +7 -22
  44. bluer_objects/README/alias.py +67 -0
  45. bluer_objects/README/build/__init__.py +0 -0
  46. bluer_objects/README/build/aliases.py +23 -0
  47. bluer_objects/README/build/docs.py +23 -0
  48. bluer_objects/README/build/modules.py +9 -0
  49. bluer_objects/README/consts.py +44 -0
  50. bluer_objects/README/functions.py +154 -204
  51. bluer_objects/README/items.py +78 -6
  52. bluer_objects/README/process/__init__.py +0 -0
  53. bluer_objects/README/process/assets.py +36 -0
  54. bluer_objects/README/process/details.py +20 -0
  55. bluer_objects/README/process/envs.py +23 -0
  56. bluer_objects/README/process/help.py +27 -0
  57. bluer_objects/README/process/include.py +40 -0
  58. bluer_objects/README/process/legacy.py +21 -0
  59. bluer_objects/README/process/mermaid.py +20 -0
  60. bluer_objects/README/process/national_internet.py +55 -0
  61. bluer_objects/README/process/objects.py +32 -0
  62. bluer_objects/README/process/signature.py +35 -0
  63. bluer_objects/README/process/title.py +44 -0
  64. bluer_objects/README/process/variables.py +12 -0
  65. bluer_objects/__init__.py +1 -1
  66. bluer_objects/assets/__init__.py +0 -0
  67. bluer_objects/assets/__main__.py +57 -0
  68. bluer_objects/assets/functions.py +62 -0
  69. bluer_objects/config.env +13 -1
  70. bluer_objects/env.py +27 -1
  71. bluer_objects/file/__main__.py +52 -7
  72. bluer_objects/file/functions.py +21 -4
  73. bluer_objects/file/load.py +2 -9
  74. bluer_objects/file/save.py +17 -24
  75. bluer_objects/graphics/__main__.py +7 -0
  76. bluer_objects/graphics/gif.py +11 -7
  77. bluer_objects/graphics/screen.py +9 -8
  78. bluer_objects/help/assets.py +93 -0
  79. bluer_objects/help/create_test_asset.py +22 -0
  80. bluer_objects/help/download.py +17 -3
  81. bluer_objects/help/file.py +59 -0
  82. bluer_objects/help/functions.py +9 -1
  83. bluer_objects/help/gif.py +25 -0
  84. bluer_objects/help/host.py +6 -4
  85. bluer_objects/help/ls.py +26 -3
  86. bluer_objects/help/metadata.py +51 -0
  87. bluer_objects/help/mlflow/__init__.py +23 -2
  88. bluer_objects/help/mlflow/cache.py +2 -4
  89. bluer_objects/help/mlflow/lock.py +52 -0
  90. bluer_objects/help/mlflow/tags.py +34 -23
  91. bluer_objects/help/pdf.py +67 -0
  92. bluer_objects/help/upload.py +10 -3
  93. bluer_objects/host/functions.py +4 -1
  94. bluer_objects/logger/confusion_matrix.py +76 -0
  95. bluer_objects/logger/image.py +110 -0
  96. bluer_objects/logger/stitch.py +107 -0
  97. bluer_objects/markdown.py +8 -6
  98. bluer_objects/metadata/__init__.py +1 -0
  99. bluer_objects/metadata/flatten.py +27 -0
  100. bluer_objects/mlflow/__init__.py +1 -1
  101. bluer_objects/mlflow/__main__.py +49 -31
  102. bluer_objects/mlflow/lock/__init__.py +1 -0
  103. bluer_objects/mlflow/lock/__main__.py +58 -0
  104. bluer_objects/mlflow/lock/functions.py +121 -0
  105. bluer_objects/mlflow/logging.py +53 -41
  106. bluer_objects/mlflow/models.py +7 -0
  107. bluer_objects/mlflow/objects.py +7 -0
  108. bluer_objects/mlflow/runs.py +10 -1
  109. bluer_objects/mlflow/serverless/__init__.py +3 -0
  110. bluer_objects/mlflow/serverless/api.py +88 -0
  111. bluer_objects/mlflow/serverless/read.py +19 -0
  112. bluer_objects/mlflow/serverless/search.py +35 -0
  113. bluer_objects/mlflow/serverless/write.py +42 -0
  114. bluer_objects/mlflow/tags.py +59 -9
  115. bluer_objects/objects.py +3 -1
  116. bluer_objects/pdf/__init__.py +1 -0
  117. bluer_objects/pdf/__main__.py +78 -0
  118. bluer_objects/pdf/convert/__init__.py +0 -0
  119. bluer_objects/pdf/convert/batch.py +54 -0
  120. bluer_objects/pdf/convert/combination.py +32 -0
  121. bluer_objects/pdf/convert/convert.py +110 -0
  122. bluer_objects/pdf/convert/image.py +53 -0
  123. bluer_objects/pdf/convert/md.py +97 -0
  124. bluer_objects/pdf/convert/missing.py +96 -0
  125. bluer_objects/pdf/convert/pdf.py +37 -0
  126. bluer_objects/sample.env +6 -0
  127. bluer_objects/storage/WebDAV.py +11 -7
  128. bluer_objects/storage/WebDAVrequest.py +360 -0
  129. bluer_objects/storage/WebDAVzip.py +26 -29
  130. bluer_objects/storage/__init__.py +28 -1
  131. bluer_objects/storage/__main__.py +40 -6
  132. bluer_objects/storage/base.py +84 -5
  133. bluer_objects/storage/policies.py +7 -0
  134. bluer_objects/storage/s3.py +367 -0
  135. bluer_objects/testing/__main__.py +6 -0
  136. bluer_objects/tests/test_README_consts.py +71 -0
  137. bluer_objects/tests/test_README_items.py +128 -0
  138. bluer_objects/tests/test_alias.py +33 -0
  139. bluer_objects/tests/test_env.py +42 -7
  140. bluer_objects/tests/test_file_download.py +30 -0
  141. bluer_objects/tests/test_file_load_save.py +1 -2
  142. bluer_objects/tests/test_file_load_save_text.py +46 -0
  143. bluer_objects/tests/test_graphics_gif.py +2 -0
  144. bluer_objects/tests/test_log_image_grid.py +29 -0
  145. bluer_objects/tests/test_logger_confusion_matrix.py +18 -0
  146. bluer_objects/tests/test_logger_matrix.py +2 -2
  147. bluer_objects/tests/test_logger_stitch_images.py +47 -0
  148. bluer_objects/tests/test_metadata.py +12 -6
  149. bluer_objects/tests/test_metadata_flatten.py +109 -0
  150. bluer_objects/tests/test_mlflow.py +114 -5
  151. bluer_objects/tests/test_mlflow_lock.py +26 -0
  152. bluer_objects/tests/test_objects.py +2 -0
  153. bluer_objects/tests/test_shell.py +34 -0
  154. bluer_objects/tests/test_storage.py +8 -21
  155. bluer_objects/tests/test_storage_base.py +39 -0
  156. bluer_objects/tests/test_storage_s3.py +67 -0
  157. bluer_objects/tests/test_storage_webdav_request.py +75 -0
  158. bluer_objects/tests/test_storage_webdav_zip.py +42 -0
  159. bluer_objects/tests/test_web_is_accessible.py +11 -0
  160. {bluer_objects-6.104.1.dist-info → bluer_objects-6.464.1.dist-info}/METADATA +20 -11
  161. bluer_objects-6.464.1.dist-info/RECORD +228 -0
  162. {bluer_objects-6.104.1.dist-info → bluer_objects-6.464.1.dist-info}/WHEEL +1 -1
  163. bluer_objects/.abcli/storage/download_file.sh +0 -9
  164. bluer_objects/.abcli/storage/exists.sh +0 -8
  165. bluer_objects/.abcli/storage/list.sh +0 -8
  166. bluer_objects/.abcli/storage/rm.sh +0 -11
  167. bluer_objects/.abcli/tests/mlflow_test.sh +0 -7
  168. bluer_objects-6.104.1.dist-info/RECORD +0 -143
  169. {bluer_objects-6.104.1.dist-info → bluer_objects-6.464.1.dist-info}/licenses/LICENSE +0 -0
  170. {bluer_objects-6.104.1.dist-info → bluer_objects-6.464.1.dist-info}/top_level.txt +0 -0
@@ -2,9 +2,11 @@ import argparse
2
2
 
3
3
  from blueness import module
4
4
  from blueness.argparse.generic import sys_exit
5
+ from bluer_options.logger.config import log_list
5
6
 
6
7
  from bluer_objects import NAME
7
8
  from bluer_objects import storage
9
+ from bluer_objects.storage.policies import DownloadPolicy
8
10
  from bluer_objects.logger import logger
9
11
 
10
12
  NAME = module.name(__file__, NAME)
@@ -13,7 +15,7 @@ parser = argparse.ArgumentParser(NAME)
13
15
  parser.add_argument(
14
16
  "task",
15
17
  type=str,
16
- help="clear | download | ls | upload",
18
+ help="clear | download | ls | ls_objects | upload",
17
19
  )
18
20
  parser.add_argument(
19
21
  "--object_name",
@@ -24,6 +26,18 @@ parser.add_argument(
24
26
  type=str,
25
27
  default="",
26
28
  )
29
+ parser.add_argument(
30
+ "--public",
31
+ type=int,
32
+ default=0,
33
+ help="0 | 1",
34
+ )
35
+ parser.add_argument(
36
+ "--zip",
37
+ type=int,
38
+ default=0,
39
+ help="0 | 1",
40
+ )
27
41
  parser.add_argument(
28
42
  "--where",
29
43
  type=str,
@@ -47,6 +61,17 @@ parser.add_argument(
47
61
  default=1,
48
62
  help="0 | 1",
49
63
  )
64
+ parser.add_argument(
65
+ "--policy",
66
+ type=str,
67
+ default="none",
68
+ help=" | ".join(sorted([policy.name.lower() for policy in DownloadPolicy])),
69
+ )
70
+ parser.add_argument(
71
+ "--prefix",
72
+ type=str,
73
+ default="",
74
+ )
50
75
  args = parser.parse_args()
51
76
 
52
77
  delim = " " if args.delim == "space" else args.delim
@@ -55,11 +80,13 @@ success = False
55
80
  if args.task == "clear":
56
81
  success = storage.clear(
57
82
  do_dryrun=args.do_dryrun == 1,
83
+ public=args.public == 1,
58
84
  )
59
85
  elif args.task == "download":
60
86
  success = storage.download(
61
87
  object_name=args.object_name,
62
88
  filename=args.filename,
89
+ policy=DownloadPolicy[args.policy.upper()],
63
90
  )
64
91
  elif args.task == "ls":
65
92
  success, list_of_files = storage.ls(
@@ -68,18 +95,25 @@ elif args.task == "ls":
68
95
  )
69
96
 
70
97
  if args.log:
71
- logger.info(
72
- "{:,} file(s).".format(len(list_of_files)),
73
- )
74
- for index, filename in enumerate(list_of_files):
75
- logger.info(f"#{index+1: 4d} - {filename}")
98
+ log_list(logger, "", list_of_files, "file(s)", 999)
76
99
  else:
77
100
  print(delim.join(list_of_files))
101
+ elif args.task == "ls_objects":
102
+ success, list_of_objects = storage.ls_objects(
103
+ prefix=args.prefix,
104
+ where=args.where,
105
+ )
78
106
 
107
+ if args.log:
108
+ log_list(logger, "", list_of_objects, "objects(s)", 999)
109
+ else:
110
+ print(delim.join(list_of_objects))
79
111
  elif args.task == "upload":
80
112
  success = storage.upload(
81
113
  object_name=args.object_name,
82
114
  filename=args.filename,
115
+ zip=args.zip == 1,
116
+ public=args.public == 1,
83
117
  )
84
118
  else:
85
119
  success = None
@@ -1,27 +1,50 @@
1
+ import os
2
+ import glob
1
3
  from typing import Tuple, List
2
4
 
5
+ from bluer_objects import objects
6
+ from bluer_objects import path
7
+ from bluer_objects.storage.policies import DownloadPolicy
3
8
  from bluer_objects.logger import logger
9
+ from bluer_objects.env import ABCLI_OBJECT_ROOT
4
10
 
5
11
 
6
12
  class StorageInterface:
7
13
  def clear(
8
14
  self,
9
15
  do_dryrun: bool = True,
16
+ log: bool = True,
17
+ public: bool = False,
10
18
  ) -> bool:
11
- return False
19
+ logger.info(
20
+ "{}.clear({})".format(
21
+ self.__class__.__name__,
22
+ ",".join(
23
+ (["dryrun"] if do_dryrun else []) + (["public"] if public else [])
24
+ ),
25
+ )
26
+ )
27
+
28
+ return True
12
29
 
13
30
  def download(
14
31
  self,
15
32
  object_name: str,
16
33
  filename: str = "",
17
34
  log: bool = True,
35
+ policy: DownloadPolicy = DownloadPolicy.NONE,
18
36
  ) -> bool:
19
37
  if log:
20
38
  logger.info(
21
- "{}.download {}{}".format(
39
+ "{}.download {}{}{}".format(
22
40
  self.__class__.__name__,
23
41
  object_name,
24
42
  f"/{filename}" if filename else "",
43
+ (
44
+ ""
45
+ if policy == DownloadPolicy.NONE
46
+ else " - policy:{}".format(policy.name.lower())
47
+ ),
25
48
  )
26
49
  )
27
50
 
@@ -32,20 +55,76 @@ class StorageInterface:
32
55
  object_name: str,
33
56
  where: str = "local",
34
57
  ) -> Tuple[bool, List[str]]:
35
- return True, []
58
+ if where == "local":
59
+ object_path = objects.object_path(
60
+ object_name=object_name,
61
+ )
62
+
63
+ return True, sorted(
64
+ [
65
+ os.path.relpath(filename, start=object_path)
66
+ for filename in glob.glob(
67
+ os.path.join(
68
+ object_path,
69
+ "**",
70
+ "*",
71
+ ),
72
+ recursive=True,
73
+ )
74
+ if os.path.isfile(filename)
75
+ ]
76
+ )
77
+
78
+ if where == "cloud":
79
+ logger.error("not implemented")
80
+ return False, []
81
+
82
+ logger.error(f"Unknown 'where': {where}")
83
+ return False, []
84
+
85
+ def ls_objects(
86
+ self,
87
+ prefix: str,
88
+ where: str = "local",
89
+ ) -> Tuple[bool, List[str]]:
90
+ if where == "local":
91
+ return True, sorted(
92
+ [
93
+ os.path.relpath(dirname, start=ABCLI_OBJECT_ROOT)
94
+ for dirname in glob.glob(
95
+ os.path.join(
96
+ ABCLI_OBJECT_ROOT,
97
+ "*",
98
+ ),
99
+ recursive=False,
100
+ )
101
+ if not os.path.isfile(dirname)
102
+ and path.name(dirname).startswith(prefix)
103
+ ]
104
+ )
105
+
106
+ if where == "cloud":
107
+ logger.error("not implemented")
108
+ return False, []
109
+
110
+ logger.error(f"Unknown 'where': {where}")
111
+ return False, []
36
112
 
37
113
  def upload(
38
114
  self,
39
115
  object_name: str,
40
116
  filename: str = "",
117
+ public: bool = False,
118
+ zip: bool = False,
41
119
  log: bool = True,
42
120
  ) -> bool:
43
121
  if log:
44
122
  logger.info(
45
- "{}.upload {}{}".format(
123
+ "{}.upload {}{}{}".format(
46
124
  self.__class__.__name__,
47
125
  object_name,
48
- f"/{filename}" if filename else "",
126
+ ".tar.gz" if zip else f"/{filename}" if filename else "",
127
+ " [public]" if public else "",
49
128
  )
50
129
  )
51
130
 
@@ -0,0 +1,7 @@
1
+ from enum import Enum, auto
2
+
3
+
4
+ class DownloadPolicy(Enum):
5
+ NONE = auto()
6
+ DOESNT_EXIST = auto()
7
+ DIFFERENT = auto()
@@ -0,0 +1,367 @@
1
+ import boto3
2
+ import os
3
+ from botocore.exceptions import ClientError
4
+ import glob
5
+ from typing import Tuple, List
6
+ from xml.etree import ElementTree as ET
7
+ from tqdm import tqdm
8
+ from functools import reduce
9
+
10
+ from bluer_objects.storage.base import StorageInterface
11
+ from bluer_objects.env import ABCLI_OBJECT_ROOT
12
+ from bluer_objects import env, file, path
13
+ from bluer_objects import objects
14
+ from bluer_objects.storage.policies import DownloadPolicy
15
+ from bluer_objects.logger import logger
16
+
17
+
18
+ # https://docs.arvancloud.ir/fa/developer-tools/sdk/object-storage/
19
+ class S3Interface(StorageInterface):
20
+ name = "s3"
21
+
22
+ def clear(
23
+ self,
24
+ do_dryrun: bool = True,
25
+ log: bool = True,
26
+ public: bool = False,
27
+ ) -> bool:
28
+ if not super().clear(
29
+ do_dryrun=do_dryrun,
30
+ log=log,
31
+ public=public,
32
+ ):
33
+ return False
34
+
35
+ bucket_name = env.S3_PUBLIC_STORAGE_BUCKET if public else env.S3_STORAGE_BUCKET
36
+
37
+ success, list_of_objects = self.list_of_objects(
38
+ prefix="test",
39
+ bucket_name=bucket_name,
40
+ )
41
+ if not success:
42
+ return success
43
+ logger.info(f"{len(list_of_objects)} object(s) to delete.")
44
+
45
+ for object_name in tqdm(list_of_objects):
46
+ if not self.delete(
47
+ object_name=object_name,
48
+ do_dryrun=do_dryrun,
49
+ bucket_name=bucket_name,
50
+ ):
51
+ return False
52
+
53
+ return True
54
+
55
+ def delete(
56
+ self,
57
+ object_name: str,
58
+ do_dryrun: bool = True,
59
+ log: bool = True,
60
+ bucket_name: str = env.S3_STORAGE_BUCKET,
61
+ ) -> bool:
62
+ if log:
63
+ logger.info(
64
+ "{}.delete({}){}".format(
65
+ self.__class__.__name__,
66
+ object_name,
67
+ " dryrun" if do_dryrun else "",
68
+ )
69
+ )
70
+ if do_dryrun:
71
+ return True
72
+
73
+ try:
74
+ s3 = boto3.resource(
75
+ "s3",
76
+ endpoint_url=env.S3_STORAGE_ENDPOINT_URL,
77
+ aws_access_key_id=env.S3_STORAGE_AWS_ACCESS_KEY_ID,
78
+ aws_secret_access_key=env.S3_STORAGE_AWS_SECRET_ACCESS_KEY,
79
+ )
80
+ bucket = s3.Bucket(bucket_name)
81
+
82
+ if object_name.endswith(".tar.gz"):
83
+ delete_requests = [{"Key": object_name}]
84
+ else:
85
+ objects_to_delete = bucket.objects.filter(Prefix=f"{object_name}/")
86
+ delete_requests = [{"Key": obj.key} for obj in objects_to_delete]
87
+
88
+ if not delete_requests:
89
+ logger.warning(f"no files found under {object_name}.")
90
+ return True
91
+
92
+ bucket.delete_objects(Delete={"Objects": delete_requests})
93
+ except Exception as e:
94
+ logger.error(e)
95
+ return False
96
+
97
+ return True
98
+
99
+ def download(
100
+ self,
101
+ object_name: str,
102
+ filename: str = "",
103
+ log: bool = True,
104
+ policy: DownloadPolicy = DownloadPolicy.NONE,
105
+ ) -> bool:
106
+ if filename:
107
+ local_path = objects.path_of(
108
+ object_name=object_name,
109
+ filename=filename,
110
+ create=True,
111
+ )
112
+
113
+ if policy == DownloadPolicy.DOESNT_EXIST and file.exists(local_path):
114
+ if log:
115
+ logger.info(f"✅ {filename}")
116
+ return True
117
+
118
+ if not path.create(file.path(local_path)):
119
+ return False
120
+
121
+ try:
122
+ s3_resource = boto3.resource(
123
+ "s3",
124
+ endpoint_url=env.S3_STORAGE_ENDPOINT_URL,
125
+ aws_access_key_id=env.S3_STORAGE_AWS_ACCESS_KEY_ID,
126
+ aws_secret_access_key=env.S3_STORAGE_AWS_SECRET_ACCESS_KEY,
127
+ )
128
+ except Exception as e:
129
+ logger.error(e)
130
+ return False
131
+
132
+ try:
133
+ bucket = s3_resource.Bucket(env.S3_STORAGE_BUCKET)
134
+
135
+ bucket.download_file(
136
+ f"{object_name}/{filename}",
137
+ local_path,
138
+ )
139
+ except ClientError as e:
140
+ if int(e.response["Error"]["Code"]) == 404: # Not found
141
+ return True
142
+ logger.error(e)
143
+ return False
144
+ except Exception as e:
145
+ logger.error(e)
146
+ return False
147
+
148
+ return super().download(
149
+ object_name=object_name,
150
+ filename=filename,
151
+ log=log,
152
+ policy=policy,
153
+ )
154
+
155
+ success, list_of_files = self.ls(
156
+ object_name=object_name,
157
+ where="cloud",
158
+ )
159
+ if not success:
160
+ return False
161
+
162
+ for filename_ in tqdm(list_of_files):
163
+ if not self.download(
164
+ object_name=object_name,
165
+ filename=filename_,
166
+ log=log,
167
+ policy=policy,
168
+ ):
169
+ return False
170
+
171
+ return True
172
+
173
+ def list_of_objects(
174
+ self,
175
+ prefix: str = "",
176
+ bucket_name: str = env.S3_STORAGE_BUCKET,
177
+ ) -> Tuple[bool, List[str]]:
178
+ try:
179
+ s3 = boto3.client(
180
+ "s3",
181
+ endpoint_url=env.S3_STORAGE_ENDPOINT_URL,
182
+ aws_access_key_id=env.S3_STORAGE_AWS_ACCESS_KEY_ID,
183
+ aws_secret_access_key=env.S3_STORAGE_AWS_SECRET_ACCESS_KEY,
184
+ )
185
+
186
+ paginator = s3.get_paginator("list_objects_v2")
187
+ pages = paginator.paginate(
188
+ Bucket=bucket_name,
189
+ Prefix=prefix,
190
+ )
191
+ except Exception as e:
192
+ logger.error(e)
193
+ return False, []
194
+
195
+ list_of_objects = sorted(
196
+ list(
197
+ set(
198
+ reduce(
199
+ lambda x, y: x + y,
200
+ [
201
+ [
202
+ obj["Key"].split("/", 1)[0]
203
+ for obj in page.get("Contents", [])
204
+ ]
205
+ for page in pages
206
+ ],
207
+ [],
208
+ )
209
+ )
210
+ )
211
+ )
212
+
213
+ return True, list_of_objects
214
+
215
+ def ls(
216
+ self,
217
+ object_name: str,
218
+ where: str = "local",
219
+ ) -> Tuple[bool, List[str]]:
220
+ if where == "cloud":
221
+ try:
222
+ s3 = boto3.client(
223
+ "s3",
224
+ endpoint_url=env.S3_STORAGE_ENDPOINT_URL,
225
+ aws_access_key_id=env.S3_STORAGE_AWS_ACCESS_KEY_ID,
226
+ aws_secret_access_key=env.S3_STORAGE_AWS_SECRET_ACCESS_KEY,
227
+ )
228
+
229
+ prefix = f"{object_name}/"
230
+
231
+ paginator = s3.get_paginator("list_objects_v2")
232
+ pages = paginator.paginate(
233
+ Bucket=env.S3_STORAGE_BUCKET,
234
+ Prefix=prefix,
235
+ )
236
+ except Exception as e:
237
+ logger.error(e)
238
+ return False, []
239
+
240
+ try:
241
+ list_of_files = sorted(
242
+ reduce(
243
+ lambda x, y: x + y,
244
+ [
245
+ [
246
+ obj["Key"].split(prefix, 1)[1]
247
+ for obj in page.get("Contents", [])
248
+ ]
249
+ for page in pages
250
+ ],
251
+ [],
252
+ )
253
+ )
254
+ except Exception as e:
255
+ logger.error(e)
256
+ return False, []
257
+
258
+ return True, list_of_files
259
+
260
+ return super().ls(
261
+ object_name=object_name,
262
+ where=where,
263
+ )
264
+
265
+ def ls_objects(
266
+ self,
267
+ prefix: str,
268
+ where: str = "local",
269
+ ) -> Tuple[bool, List[str]]:
270
+ if where == "cloud":
271
+ return self.list_of_objects(prefix)
272
+
273
+ return super().ls_objects(
274
+ prefix=prefix,
275
+ where=where,
276
+ )
277
+
278
+ def upload(
279
+ self,
280
+ object_name: str,
281
+ filename: str = "",
282
+ public: bool = False,
283
+ zip: bool = False,
284
+ log: bool = True,
285
+ ) -> bool:
286
+ if filename or zip:
287
+ local_path = (
288
+ os.path.join(
289
+ ABCLI_OBJECT_ROOT,
290
+ f"{object_name}.tar.gz",
291
+ )
292
+ if zip
293
+ else objects.path_of(
294
+ object_name=object_name,
295
+ filename=filename,
296
+ )
297
+ )
298
+
299
+ bucket_name = (
300
+ env.S3_PUBLIC_STORAGE_BUCKET if public else env.S3_STORAGE_BUCKET
301
+ )
302
+
303
+ try:
304
+ s3_resource = boto3.resource(
305
+ "s3",
306
+ endpoint_url=env.S3_STORAGE_ENDPOINT_URL,
307
+ aws_access_key_id=env.S3_STORAGE_AWS_ACCESS_KEY_ID,
308
+ aws_secret_access_key=env.S3_STORAGE_AWS_SECRET_ACCESS_KEY,
309
+ )
310
+
311
+ bucket = s3_resource.Bucket(bucket_name)
312
+
313
+ with open(local_path, "rb") as fp:
314
+ bucket.put_object(
315
+ ACL="public-read" if public else "private",
316
+ Body=fp,
317
+ Key=(
318
+ f"{object_name}.tar.gz"
319
+ if zip
320
+ else f"{object_name}/{filename}"
321
+ ),
322
+ )
323
+ except ClientError as e:
324
+ logger.error(e)
325
+ return False
326
+
327
+ if public:
328
+ logger.info(
329
+ "🔗 https://{}.{}/{}".format(
330
+ bucket_name,
331
+ env.S3_STORAGE_ENDPOINT_URL.split("https://", 1)[1],
332
+ f"{object_name}.tar.gz" if zip else f"{object_name}/{filename}",
333
+ )
334
+ )
335
+
336
+ return super().upload(
337
+ object_name=object_name,
338
+ filename=filename,
339
+ public=public,
340
+ zip=zip,
341
+ log=log,
342
+ )
343
+
344
+ object_path = "{}/".format(objects.object_path(object_name=object_name))
345
+ for filename_ in tqdm(
346
+ sorted(
347
+ glob.glob(
348
+ objects.path_of(
349
+ object_name=object_name,
350
+ filename="**",
351
+ ),
352
+ recursive=True,
353
+ )
354
+ )
355
+ ):
356
+ if not file.exists(filename_):
357
+ continue
358
+
359
+ if not self.upload(
360
+ object_name=object_name,
361
+ filename=filename_.split(object_path, 1)[1],
362
+ public=public,
363
+ log=log,
364
+ ):
365
+ return False
366
+
367
+ return True
@@ -19,12 +19,18 @@ parser.add_argument(
19
19
  "--object_name",
20
20
  type=str,
21
21
  )
22
+ parser.add_argument(
23
+ "--depth",
24
+ type=int,
25
+ default=10,
26
+ )
22
27
  args = parser.parse_args()
23
28
 
24
29
  success = False
25
30
  if args.task == "create_test_asset":
26
31
  success = create_test_asset(
27
32
  object_name=args.object_name,
33
+ depth=args.depth,
28
34
  )
29
35
  else:
30
36
  success = None
@@ -0,0 +1,71 @@
1
+ import pytest
2
+
3
+ from bluer_objects.env import abcli_path_git
4
+ from bluer_objects.README.consts import (
5
+ assets_path,
6
+ assets_url,
7
+ designs_repo,
8
+ designs_url,
9
+ github_kamangir,
10
+ )
11
+
12
+
13
+ @pytest.mark.parametrize(
14
+ ["suffix"],
15
+ [
16
+ ["this"],
17
+ ["that/which"],
18
+ ],
19
+ )
20
+ @pytest.mark.parametrize(
21
+ ["volume"],
22
+ [
23
+ [""],
24
+ ["2"],
25
+ [2],
26
+ ],
27
+ )
28
+ def test_README_assets(
29
+ suffix: str,
30
+ volume: str,
31
+ ):
32
+ volume_url = assets_url(volume=volume)
33
+ assert isinstance(volume_url, str)
34
+ assert volume_url.startswith(github_kamangir)
35
+
36
+ # ---
37
+
38
+ suffix_url = assets_url(
39
+ suffix=suffix,
40
+ volume=volume,
41
+ )
42
+
43
+ assert isinstance(suffix_url, str)
44
+ assert suffix_url.endswith(suffix)
45
+ assert volume_url in suffix_url
46
+
47
+ # ---
48
+
49
+ suffix_path = assets_path(
50
+ suffix=suffix,
51
+ volume=volume,
52
+ )
53
+
54
+ assert isinstance(suffix_url, str)
55
+ assert suffix_path.endswith(suffix)
56
+ assert abcli_path_git in suffix_path
57
+
58
+
59
+ @pytest.mark.parametrize(
60
+ ["suffix"],
61
+ [
62
+ ["this"],
63
+ ["that/which"],
64
+ ],
65
+ )
66
+ def test_README_designs_url(suffix):
67
+ url = designs_url(suffix=suffix)
68
+
69
+ assert isinstance(url, str)
70
+ assert url.startswith(designs_repo)
71
+ assert url.endswith(suffix)