seqslab-cli 3.3.4__tar.gz → 3.3.5__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 (110) hide show
  1. {seqslab-cli-3.3.4/python/seqslab_cli.egg-info → seqslab-cli-3.3.5}/PKG-INFO +1 -1
  2. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/__init__.py +1 -1
  3. seqslab-cli-3.3.5/python/seqslab/color.py +8 -0
  4. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/drs/api/base.py +3 -3
  5. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/drs/commands.py +115 -108
  6. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/drs/internal/aiocopy.py +11 -6
  7. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/drs/storage/azure.py +7 -5
  8. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/drs/utils/atgxmetadata.py +1 -0
  9. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/trs/commands.py +15 -4
  10. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/trs/template/base.py +5 -3
  11. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/wes/commands.py +51 -61
  12. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/wes/internal/parameters.py +1 -1
  13. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/wes/resource/base.py +1 -2
  14. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/wes/resource/common.py +2 -2
  15. seqslab-cli-3.3.5/python/seqslab/workspace/resource/azure.py +26 -0
  16. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/workspace/resource/base.py +13 -1
  17. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5/python/seqslab_cli.egg-info}/PKG-INFO +1 -1
  18. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab_cli.egg-info/SOURCES.txt +1 -0
  19. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab_cli.egg-info/requires.txt +6 -6
  20. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/requirements.txt +6 -6
  21. seqslab-cli-3.3.4/python/seqslab/wes/resource/azure.py +0 -26
  22. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/LICENSE +0 -0
  23. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/MANIFEST.in +0 -0
  24. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/README.md +0 -0
  25. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/auth/__init__.py +0 -0
  26. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/auth/azuread.py +0 -0
  27. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/auth/commands.py +0 -0
  28. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/cli.py +0 -0
  29. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/context.py +0 -0
  30. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/drs/__init__.py +0 -0
  31. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/drs/api/__init__.py +0 -0
  32. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/drs/api/azure.py +0 -0
  33. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/drs/api/common.py +0 -0
  34. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/drs/api/template.py +0 -0
  35. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/drs/internal/__init__.py +0 -0
  36. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/drs/internal/common.py +0 -0
  37. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/drs/internal/utils.py +0 -0
  38. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/drs/storage/__init__.py +0 -0
  39. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/drs/storage/base.py +0 -0
  40. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/drs/utils/__init__.py +0 -0
  41. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/drs/utils/biomimetype.py +0 -0
  42. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/drs/utils/progressbar.py +0 -0
  43. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/exceptions.py +0 -0
  44. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/organization/__init__.py +0 -0
  45. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/organization/commands.py +0 -0
  46. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/organization/resource/__init__.py +0 -0
  47. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/organization/resource/base.py +0 -0
  48. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/plugin.py +0 -0
  49. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/role/__init__.py +0 -0
  50. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/role/commands.py +0 -0
  51. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/role/internal/__init__.py +0 -0
  52. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/role/internal/common.py +0 -0
  53. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/role/resource/__init__.py +0 -0
  54. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/role/resource/azure.py +0 -0
  55. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/role/resource/base.py +0 -0
  56. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/runsheet/__init__.py +0 -0
  57. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/runsheet/runsheet.py +0 -0
  58. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/sample_sheet/__init__.py +0 -0
  59. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/sample_sheet/_version.py +0 -0
  60. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/sample_sheet/util.py +0 -0
  61. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/scr/__init__.py +0 -0
  62. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/scr/commands.py +0 -0
  63. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/scr/internal/__init__.py +0 -0
  64. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/scr/internal/common.py +0 -0
  65. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/scr/resource/__init__.py +0 -0
  66. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/scr/resource/azure.py +0 -0
  67. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/scr/resource/base.py +0 -0
  68. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/session_logger.py +0 -0
  69. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/settings.py +0 -0
  70. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/statusbar.py +0 -0
  71. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/trs/__init__.py +0 -0
  72. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/trs/internal/__init__.py +0 -0
  73. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/trs/internal/utils.py +0 -0
  74. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/trs/register/__init__.py +0 -0
  75. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/trs/register/azure.py +0 -0
  76. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/trs/register/base.py +0 -0
  77. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/trs/register/common.py +0 -0
  78. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/trs/register/template.py +0 -0
  79. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/trs/resource/__init__.py +0 -0
  80. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/trs/resource/azure.py +0 -0
  81. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/trs/resource/base.py +0 -0
  82. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/trs/resource/common.py +0 -0
  83. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/trs/template/__init__.py +0 -0
  84. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/trs/template/template.py +0 -0
  85. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/usage_logger.py +0 -0
  86. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/user/__init__.py +0 -0
  87. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/user/commands.py +0 -0
  88. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/user/internal/__init__.py +0 -0
  89. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/user/internal/common.py +0 -0
  90. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/user/resource/__init__.py +0 -0
  91. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/user/resource/azure.py +0 -0
  92. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/user/resource/base.py +0 -0
  93. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/wes/__init__.py +0 -0
  94. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/wes/internal/__init__.py +0 -0
  95. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/wes/resource/__init__.py +0 -0
  96. {seqslab-cli-3.3.4/python/seqslab/workspace → seqslab-cli-3.3.5/python/seqslab/wes}/resource/azure.py +0 -0
  97. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/wes/template/__init__.py +0 -0
  98. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/wes/template/base.py +0 -0
  99. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/wes/template/template.py +0 -0
  100. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/workspace/__init__.py +0 -0
  101. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/workspace/commands.py +0 -0
  102. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/workspace/internal/__init__.py +0 -0
  103. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/workspace/internal/common.py +0 -0
  104. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab/workspace/resource/__init__.py +0 -0
  105. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab_cli.egg-info/dependency_links.txt +0 -0
  106. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab_cli.egg-info/entry_points.txt +0 -0
  107. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab_cli.egg-info/top_level.txt +0 -0
  108. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/python/seqslab_cli.egg-info/zip-safe +0 -0
  109. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/setup.cfg +0 -0
  110. {seqslab-cli-3.3.4 → seqslab-cli-3.3.5}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: seqslab-cli
3
- Version: 3.3.4
3
+ Version: 3.3.5
4
4
  Summary: Atgenomix SeqsLab Command Line Tool
5
5
  Home-page: https://github.com/AnomeGAP/seqslab-cli
6
6
  Author: Allen Chang
@@ -21,6 +21,6 @@ name = "seqslab"
21
21
  __all__ = []
22
22
 
23
23
 
24
- __version__ = "3.3.4"
24
+ __version__ = "3.3.5"
25
25
 
26
26
  LOGGING = {"DIR_PATH": "/var/log/seqslab"}
@@ -0,0 +1,8 @@
1
+ def color_handler(func):
2
+ ColorDict = {"yellow": "\033[01;33m", "green": "\033[01;32m", "red": "\033[01;31m"}
3
+
4
+ def inner_function(*args, **kwargs):
5
+ kwargs.update(ColorDict)
6
+ return func(*args, **kwargs)
7
+
8
+ return inner_function
@@ -333,9 +333,9 @@ class DRSregister:
333
333
  folder_stdin["access_methods"].clear()
334
334
  for i, dsts in enumerate(dsts_list):
335
335
  folder_stdin["access_methods"].append(access_method_infos[i])
336
- folder_stdin["access_methods"][i]["access_url"][
337
- "url"
338
- ] = self.common_root(dsts=dsts, workspace=kwargs.get("workspace"))
336
+ folder_stdin["access_methods"][i]["access_url"]["url"] = (
337
+ self.common_root(dsts=dsts, workspace=kwargs.get("workspace"))
338
+ )
339
339
 
340
340
  folder_stdin["name"] = kwargs.get(
341
341
  "name",
@@ -15,10 +15,12 @@ from urllib.parse import unquote
15
15
 
16
16
  from jsonpath_ng.ext import parse
17
17
  from nubia import argument, command, context
18
+ from seqslab.color import color_handler
18
19
  from seqslab.drs.utils.atgxmetadata import AtgxMetaData
19
20
  from seqslab.drs.utils.progressbar import ProgressBarObject
20
21
  from seqslab.exceptions import async_exception_handler, exception_handler
21
22
  from seqslab.runsheet.runsheet import RunSheet
23
+ from seqslab.workspace.internal.common import get_factory as get_workspace_factory
22
24
  from tabulate import tabulate
23
25
  from termcolor import cprint
24
26
  from yarl import URL
@@ -57,6 +59,7 @@ class BaseDatahub:
57
59
  return context.get_context().args.proxy
58
60
 
59
61
  @staticmethod
62
+ @color_handler
60
63
  @exception_handler
61
64
  def _upload(src: URL, dst: URL, recursive: bool, **kwargs) -> list:
62
65
  # copy from local to cloud
@@ -67,33 +70,40 @@ class BaseDatahub:
67
70
  if os.path.isdir(paths[0]):
68
71
  if not recursive:
69
72
  raise OSError("--recursive (-r) is Required.")
70
- if not str(dst).endswith("/") and not kwargs.get("non_interactive"):
71
- sys.stderr.write(
72
- f'f"Do you mean to rename the `{os.path.basename(str(src))}` to '
73
- f'`{os.path.basename(str(dst))}`?(Y/N): "'
74
- )
75
- user_input = input()
76
- if user_input.lower() != "y":
77
- raise InterruptedError("Interrupt all the process.")
73
+ sys.stderr.write(
74
+ f"{kwargs['green']}"
75
+ + "Destination Path: "
76
+ + f"{kwargs['red']}"
77
+ + f"{str(dst)}\n"
78
+ + f"{kwargs['yellow']}\0"
79
+ )
78
80
  coro = aiocopy.dir_to_blob(URL(*paths), dst, **kwargs)
79
81
  else:
80
- if not str(dst).endswith("/") and not kwargs.get("non_interactive"):
82
+ if str(dst).endswith("/"):
83
+ sys.stderr.write(
84
+ f"{kwargs['green']}"
85
+ + "Destination Path: "
86
+ + f"{kwargs['red']}"
87
+ + f"{os.path.join(str(dst), os.path.basename(str(src)))} \n"
88
+ + f"{kwargs['yellow']}\0"
89
+ )
90
+ else:
81
91
  sys.stderr.write(
82
- f'f"Do you mean to rename the `{os.path.basename(str(src))}` to '
83
- f'`{os.path.basename(str(dst))}`?(Y/N): "'
92
+ f"{kwargs['green']}"
93
+ + "Destination Path: "
94
+ + f"{kwargs['red']}"
95
+ + f"{str(dst)} \n"
96
+ + f"{kwargs['yellow']}\0"
84
97
  )
85
- user_input = input()
86
- if user_input.lower() != "y":
87
- raise InterruptedError("Interrupt all the process.")
88
98
  coro = aiocopy.file_to_blob([URL(*paths)], dst, **kwargs)
89
99
  else:
90
- if not str(dst).endswith("/") and not kwargs.get("non_interactive"):
91
- sys.stderr.write(
92
- f"Do you mean to upload all the files into {str(dst)}/?(Y/N): "
93
- )
94
- user_input = input()
95
- if user_input.lower() != "y":
96
- raise InterruptedError("Interrupt all the process.")
100
+ sys.stderr.write(
101
+ f"{kwargs['green']}"
102
+ + "Destination Path: "
103
+ + f"{kwargs['red']}"
104
+ + f"cloud://{str(dst)}. \n"
105
+ + f"{kwargs['yellow']}\0"
106
+ )
97
107
  files = []
98
108
  dirs = []
99
109
  for p in paths:
@@ -156,6 +166,13 @@ class BaseDatahub:
156
166
  "Stdin format only supports json. Please read the document and make sure the format is valid."
157
167
  )
158
168
 
169
+ @staticmethod
170
+ def _valide_workspace(workspace: str) -> bool:
171
+ ctx = context.get_context()
172
+ backend = ctx.args.backend
173
+ resource = get_workspace_factory().load_resource()
174
+ return resource.validate_workspace(workspace, backend)
175
+
159
176
  @command(aliases=["copy"])
160
177
  @argument(
161
178
  "workspace",
@@ -173,7 +190,9 @@ class BaseDatahub:
173
190
  "dst",
174
191
  type=str,
175
192
  positional=False,
176
- description="Specify the destination directory, file path, or DRS URL (optional, default ='MyDir/'). ",
193
+ description="Specify the destination directory, file path, or DRS URL. Paths with leading slashes, leading "
194
+ "backslashes, or dot-only segments are invalid. Additionally, paths should not contain characters other than "
195
+ "[0-9a-zA-Z-._:/]. If not provided, the default is the current Linux epoch timestamp (optional).",
177
196
  )
178
197
  @argument(
179
198
  "recursive",
@@ -194,15 +213,13 @@ class BaseDatahub:
194
213
  "multiprocessing",
195
214
  type=int,
196
215
  positional=False,
197
- description="Specify the number of files that you want to upload at any given time. "
198
- "Changing the default value is not recommended (default = 1).",
216
+ description="Specify the number of files that you want to upload at any given time. (default = 1).",
199
217
  )
200
218
  @argument(
201
219
  "chunk_size",
202
220
  type=str,
203
221
  positional=False,
204
- description="Specify the size of each file that you want to upload in megabytes (MB). "
205
- "Changing the default value is not recommended (default = 8MB).",
222
+ description="Specify the size of each file that you want to upload in mebibyte (MiB). (default = 8MiB).",
206
223
  choices=["8", "16"],
207
224
  )
208
225
  @argument(
@@ -212,28 +229,19 @@ class BaseDatahub:
212
229
  description="Specify the stdout format (default = json).",
213
230
  choices=["json", "table", "tsv"],
214
231
  )
215
- @argument(
216
- "non_interactive",
217
- type=bool,
218
- positional=False,
219
- description="Whether to run in non-interactive mode, i.e. without prompt (default = False).",
220
- aliases=["ni"],
221
- )
222
232
  def upload(
223
233
  self,
224
234
  workspace: str,
225
235
  src: str,
226
- dst: str = "MyDir/",
236
+ dst: str = f"{str(int(datetime.datetime.now().timestamp()))}/",
227
237
  recursive: bool = False,
228
238
  output: str = "json",
229
239
  concurrency: int = 0,
230
240
  multiprocessing: int = 1,
231
241
  chunk_size: str = "8",
232
- expiry_time: str = None,
233
- non_interactive: bool = False,
234
242
  ) -> int:
235
243
  """
236
- Upload data from the local file system to a SeqsLab Data Hub cloud service.
244
+ Upload data from the local file system to a SeqsLab Data Hub cloud service. This command behaves similarly to gsutil cp command.
237
245
  """
238
246
 
239
247
  def __log(results: List[dict], output: str):
@@ -249,12 +257,26 @@ class BaseDatahub:
249
257
  else:
250
258
  logging.info(msg)
251
259
 
252
- if not workspace:
253
- logging.error("Invalid workspace name")
254
- cprint("Enter a valid workspace name.", "red")
260
+ if not self._valide_workspace(workspace):
261
+ logging.error("Workspace not found")
262
+ cprint(f"Workspace {workspace} not found.", "red")
255
263
  return errno.EINVAL
256
264
 
257
- if dst.isspace() or len(dst) == 0:
265
+ if dst:
266
+ # Standard Library
267
+ import re
268
+
269
+ invalid_patterns = [
270
+ r"(^|/|\\)\.{1,}($|/|\\|$)|^\s*$|^\/$|^\.$|^/|//", # ex: ., .., ..., ./, ../, .../, ././. etc
271
+ r"[^0-9a-zA-Z\-\._:/]+",
272
+ # ex: Only alphanumeric characters, hyphen, period, colon, and underscore are allowed.
273
+ ]
274
+ for pattern in invalid_patterns:
275
+ if re.search(pattern, dst):
276
+ logging.error(f"Invalid dst path {dst}")
277
+ cprint(f"Invalid dst path {dst}.", "red")
278
+ return errno.EINVAL
279
+ else:
258
280
  logging.error("Invalid dst path")
259
281
  cprint("Enter a valid dst path.", "red")
260
282
  return errno.EINVAL
@@ -262,19 +284,7 @@ class BaseDatahub:
262
284
  if os.path.isdir(src):
263
285
  if src.endswith("/"):
264
286
  src = URL(f'{str(src).rstrip("/")}', encoded=True)
265
- if expiry_time:
266
- # Standard Library
267
- import re
268
-
269
- pattern = r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}"
270
- if not re.fullmatch(pattern, expiry_time):
271
- cprint(
272
- "Make sure expiry_time format is correct. e.g. 2023-12-31 00:00:00",
273
- "red",
274
- )
275
- return errno.EINVAL
276
287
 
277
- dst = "/" if dst == "." else dst
278
288
  result = self._upload(
279
289
  src=URL(src, encoded=True),
280
290
  dst=URL(dst, encoded=True),
@@ -284,8 +294,6 @@ class BaseDatahub:
284
294
  concurrency=concurrency,
285
295
  chunk_size=self.size[chunk_size],
286
296
  proxy=self.proxy,
287
- non_interactive=non_interactive,
288
- expiry_time=expiry_time,
289
297
  )
290
298
  if isinstance(result, int):
291
299
  return result
@@ -322,23 +330,20 @@ class BaseDatahub:
322
330
  type=int,
323
331
  positional=False,
324
332
  description="Specify the numbers of chunks that can be transferred concurrently. The download bandwidth "
325
- "can be estimated using the formula concurrency * chunk_size. "
326
- "Changing the default value is not recommended (optional, default = 120).",
333
+ "can be estimated using the formula concurrency * chunk_size. (optional, default = 120).",
327
334
  )
328
335
  @argument(
329
336
  "multiprocessing",
330
337
  type=int,
331
338
  positional=False,
332
- description="Specify the number of files that you want to upload at any given time. "
333
- "Changing the default value is not recommended (default = 1).",
339
+ description="Specify the number of files that you want to upload at any given time. (default = 1).",
334
340
  )
335
341
  @argument(
336
342
  "chunk_size",
337
343
  type=str,
338
344
  positional=False,
339
345
  description="Specify the size of chunk in the chunked downloading process. "
340
- "If the specified size is greater than 4MB, the md5 hashes are not checked. "
341
- "Changing the default value is not recommended (optional, default = 4MB).",
346
+ "If the specified size is greater than 4MiB, the md5 hashes are not checked. (optional, default = 4MiB).",
342
347
  choices=["4", "8", "16", "32"],
343
348
  )
344
349
  @argument(
@@ -384,9 +389,9 @@ class BaseDatahub:
384
389
  else:
385
390
  logging.info(msg)
386
391
 
387
- if not workspace:
388
- logging.error("Invalid workspace name")
389
- cprint("Invalid workspace name", "red")
392
+ if not self._valide_workspace(workspace):
393
+ logging.error("Workspace not found")
394
+ cprint(f"Workspace {workspace} not found.", "red")
390
395
  return errno.EINVAL
391
396
 
392
397
  tasks = []
@@ -461,9 +466,9 @@ class BaseDatahub:
461
466
  browsers. The share link expires either after the first successful download or after a period of three months.
462
467
  """
463
468
 
464
- if not workspace:
465
- logging.error("Invalid workspace name")
466
- cprint("Invalid workspace name", "red")
469
+ if not self._valide_workspace(workspace):
470
+ logging.error("Workspace not found")
471
+ cprint("Workspace not found.", "red")
467
472
  return errno.EINVAL
468
473
 
469
474
  api_backend = drs_register().load_register(workspace)
@@ -663,6 +668,10 @@ class BaseDatahub:
663
668
  """
664
669
  Register a DRS object as a blob.
665
670
  """
671
+ if not self._valide_workspace(workspace):
672
+ logging.error("Workspace not found")
673
+ cprint("Workspace not found.", "red")
674
+ return errno.EINVAL
666
675
 
667
676
  def __log(results: List[dict], output: str):
668
677
  self._stdout(results=results, output=output)
@@ -725,17 +734,21 @@ class BaseDatahub:
725
734
  "mime_type": kwargs.get("mime_type"),
726
735
  "file_type": kwargs.get("file_type"),
727
736
  "created_time": kwargs.get("created_time"),
728
- "updated_time": kwargs.get("updated_time")
729
- if kwargs.get("updated_time")
730
- else kwargs.get("created_time"),
737
+ "updated_time": (
738
+ kwargs.get("updated_time")
739
+ if kwargs.get("updated_time")
740
+ else kwargs.get("created_time")
741
+ ),
731
742
  "size": kwargs.get("size"),
732
743
  "access_methods": json.loads(kwargs.get("access_methods")),
733
744
  "checksums": checksums,
734
745
  "description": kwargs.get("description"),
735
746
  "aliases": kwargs.get("aliases"),
736
- "metadata": json.loads(kwargs.get("metadata"))
737
- if kwargs.get("metadata")
738
- else None,
747
+ "metadata": (
748
+ json.loads(kwargs.get("metadata"))
749
+ if kwargs.get("metadata")
750
+ else None
751
+ ),
739
752
  "tags": kwargs.get("tags"),
740
753
  "id": kwargs.get("id"),
741
754
  "deleted_time": kwargs.get("deleted_time"),
@@ -828,8 +841,9 @@ class BaseDatahub:
828
841
  msg = f"{r['id']} has been registered."
829
842
  logging.info(msg)
830
843
 
831
- if not workspace:
832
- cprint("Enter a valid workspace", "red")
844
+ if not self._valide_workspace(workspace):
845
+ logging.error("Workspace not found")
846
+ cprint("Workspace not found.", "red")
833
847
  return errno.EINVAL
834
848
 
835
849
  results = self._register_bundle(
@@ -856,9 +870,11 @@ class BaseDatahub:
856
870
  "file_type": "bundle",
857
871
  "description": kwargs.get("description"),
858
872
  "aliases": kwargs.get("aliases"),
859
- "metadata": json.loads(kwargs.get("metadata"))
860
- if kwargs.get("metadata")
861
- else None,
873
+ "metadata": (
874
+ json.loads(kwargs.get("metadata"))
875
+ if kwargs.get("metadata")
876
+ else None
877
+ ),
862
878
  "tags": kwargs.get("tags"),
863
879
  "id": kwargs.get("id"),
864
880
  "contents": drs_ids,
@@ -872,7 +888,7 @@ class BaseDatahub:
872
888
 
873
889
  @command(aliases=["deregister"])
874
890
  @argument(
875
- "ids",
891
+ "id",
876
892
  type=List[str],
877
893
  positional=False,
878
894
  description="Specify the IDs of the DRS objects that you want to delete "
@@ -894,14 +910,14 @@ class BaseDatahub:
894
910
  )
895
911
  def deregister(
896
912
  self,
897
- ids: List[str] = [],
913
+ id: List[str] = [],
898
914
  tags: List[str] = [],
899
915
  names: List[str] = [],
900
916
  ) -> int:
901
917
  """
902
918
  Deregister DRS objects by DRS ID, DRS name, or DRS tag.
903
919
  """
904
- if not ids and not names and not tags:
920
+ if not id and not names and not tags:
905
921
  cprint(
906
922
  "Must specify one of the IDs, names or tags to identify DRS objects for deletion",
907
923
  "red",
@@ -909,7 +925,7 @@ class BaseDatahub:
909
925
  return errno.ENOENT
910
926
 
911
927
  resps = asyncio.run(
912
- utils.drs_delete(ids, names, tags, query_opts="?backend_content=false")
928
+ utils.drs_delete(id, names, tags, query_opts="?backend_content=false")
913
929
  )
914
930
  for r in resps:
915
931
  cprint(r, "yellow")
@@ -918,7 +934,7 @@ class BaseDatahub:
918
934
 
919
935
  @command(aliases=["clean"])
920
936
  @argument(
921
- "ids",
937
+ "id",
922
938
  type=List[str],
923
939
  positional=False,
924
940
  description="Specify the IDs of the DRS objects that you want to delete "
@@ -940,14 +956,14 @@ class BaseDatahub:
940
956
  )
941
957
  def delete(
942
958
  self,
943
- ids: List[str] = [],
959
+ id: List[str] = [],
944
960
  tags: List[str] = [],
945
961
  names: List[str] = [],
946
962
  ) -> int:
947
963
  """
948
964
  Delete DRS objects and the associated files in cloud storage by DRS ID, DRS name, or DRS tag.
949
965
  """
950
- if not ids and not names and not tags:
966
+ if not id and not names and not tags:
951
967
  cprint(
952
968
  "Must specify one of the IDs, names or tags to identify "
953
969
  "the associated files in cloud storage of DRS objects for deletion",
@@ -956,7 +972,7 @@ class BaseDatahub:
956
972
  return errno.ENOENT
957
973
 
958
974
  resps = asyncio.run(
959
- utils.drs_delete(ids, names, tags, query_opts="?backend_content=true")
975
+ utils.drs_delete(id, names, tags, query_opts="?backend_content=true")
960
976
  )
961
977
  for r in resps:
962
978
  cprint(r, "yellow")
@@ -1202,24 +1218,15 @@ class BaseDatahub:
1202
1218
  "multiprocessing",
1203
1219
  type=int,
1204
1220
  positional=False,
1205
- description="Specify the number of files that you want to upload at any given time. "
1206
- "Changing the default value is not recommended (optional, default = 1).",
1221
+ description="Specify the number of files that you want to upload at any given time. (optional, default = 1).",
1207
1222
  )
1208
1223
  @argument(
1209
1224
  "chunk_size",
1210
1225
  type=str,
1211
1226
  positional=False,
1212
- description="Specify the size of each file that you want to upload in megabytes (MB). "
1213
- "Changing the default value is not recommended (default = 8MB).",
1227
+ description="Specify the size of each file that you want to upload in mebibyte (MiB). (default = 8MiB).",
1214
1228
  choices=["8", "16"],
1215
1229
  )
1216
- @argument(
1217
- "non_interactive",
1218
- type=bool,
1219
- positional=False,
1220
- description="Whether to run in non-interactive mode, i.e. without prompt (default = False).",
1221
- aliases=["ni"],
1222
- )
1223
1230
  @argument(
1224
1231
  "seq_run_id",
1225
1232
  type=str,
@@ -1237,17 +1244,16 @@ class BaseDatahub:
1237
1244
  concurrency: int = 0,
1238
1245
  multiprocessing: int = 1,
1239
1246
  chunk_size: str = "8",
1240
- expiry_time: str = None,
1241
- non_interactive: bool = False,
1242
1247
  seq_run_id: str = "",
1243
1248
  ) -> int:
1244
1249
  """
1245
1250
  Upload samples based on the Run Sheet and FASTQ path information.
1246
1251
  """
1247
- if not workspace:
1248
- logging.error("Invalid workspace name")
1249
- cprint("Invalid workspace name", "red")
1252
+ if not self._valide_workspace(workspace):
1253
+ logging.error("Workspace not found")
1254
+ cprint("Workspace not found.", "red")
1250
1255
  return errno.EINVAL
1256
+
1251
1257
  failed = False
1252
1258
  if not upload_dst:
1253
1259
  upload_dst = int(datetime.datetime.timestamp(datetime.datetime.now()))
@@ -1282,8 +1288,7 @@ class BaseDatahub:
1282
1288
  concurrency=concurrency,
1283
1289
  chunk_size=self.size[chunk_size],
1284
1290
  proxy=self.proxy,
1285
- non_interactive=non_interactive,
1286
- expiry_time=expiry_time,
1291
+ non_interactive=False,
1287
1292
  )
1288
1293
  if isinstance(result, list):
1289
1294
  r = result[0]
@@ -1375,8 +1380,9 @@ class BaseDatahub:
1375
1380
  """
1376
1381
  Update a DRS object.
1377
1382
  """
1378
- if not workspace:
1379
- cprint("Enter a valid workspace", "red")
1383
+ if not self._valide_workspace(workspace):
1384
+ logging.error("Workspace not found")
1385
+ cprint("Workspace not found.", "red")
1380
1386
  return errno.EINVAL
1381
1387
 
1382
1388
  if stdin:
@@ -1427,8 +1433,9 @@ class BaseDatahub:
1427
1433
  """
1428
1434
  Get a DRS object.
1429
1435
  """
1430
- if not workspace:
1431
- cprint("Enter a valid workspace", "red")
1436
+ if not self._valide_workspace(workspace):
1437
+ logging.error("Workspace not found")
1438
+ cprint("Workspace not found.", "red")
1432
1439
  return errno.EINVAL
1433
1440
 
1434
1441
  def __log(results: dict):
@@ -193,9 +193,11 @@ async def result_setting(status: list, files: List[URL], resp_list: list) -> Lis
193
193
  name = os.path.basename(resp["dst"][0]).replace(f".{file_extension}", "")
194
194
  access_methods = [
195
195
  CopyResult.access_method(
196
- access_methods_type=resp["access_methods_type"][i]
197
- if resp.get("access_methods_type")
198
- else None,
196
+ access_methods_type=(
197
+ resp["access_methods_type"][i]
198
+ if resp.get("access_methods_type")
199
+ else None
200
+ ),
199
201
  access_tier="hot",
200
202
  dst=dst,
201
203
  region=resp["region"],
@@ -208,7 +210,7 @@ async def result_setting(status: list, files: List[URL], resp_list: list) -> Lis
208
210
  file_type=file_extension,
209
211
  created_time=resp["created_time"],
210
212
  size=sent,
211
- aliases=[os.path.basename(resp["dst"][0])],
213
+ aliases=[],
212
214
  access_methods=access_methods if status == "complete" else None,
213
215
  checksums=checksums if status == "complete" else None,
214
216
  status=status,
@@ -381,14 +383,17 @@ async def dir_to_blob(dir: URL, dst: URL, **kwargs) -> List[dict]:
381
383
  """
382
384
  files = []
383
385
  relpath = []
384
- root_path = os.path.basename(dir.human_repr())
385
386
  for root, dirlist, filelist in os.walk(dir.human_repr()):
386
387
  if filelist:
387
388
  for file in filelist:
389
+ if file.startswith("."):
390
+ # skip file start with '.'
391
+ print(f"skip {file}")
392
+ continue
388
393
  absolute_path = os.path.join(root, file)
389
394
  relative_path = os.path.relpath(absolute_path, dir.human_repr())
390
395
  files.append(URL(absolute_path))
391
- relpath.append(os.path.join(root_path, relative_path))
396
+ relpath.append(relative_path)
392
397
 
393
398
  async with get_factory().load_storage(kwargs.get("workspace")) as store:
394
399
  progress: int = 0
@@ -517,11 +517,13 @@ class BlobStorage(BaseBlob):
517
517
  ),
518
518
  "region": workspace["location"],
519
519
  "access_methods_type": workspace["resources"][0]["type"],
520
- "exception": ValueError(
521
- "Make sure the the file needs to be overwritten or rename the dst name."
522
- )
523
- if exist_size != file_size
524
- else None,
520
+ "exception": (
521
+ ValueError(
522
+ "Make sure the the file needs to be overwritten or rename the dst name."
523
+ )
524
+ if exist_size != file_size
525
+ else None
526
+ ),
525
527
  }
526
528
  elif os.path.exists(temp_file):
527
529
  # start with temp
@@ -10,6 +10,7 @@ preparation of derivative works based upon the program or distribution of
10
10
  copies by sale, rental, lease or lending are violations of federal copyright
11
11
  laws and state trade secret laws, punishable by civil and criminal penalties.
12
12
  """
13
+
13
14
  # Standard Library
14
15
  import re
15
16
 
@@ -8,8 +8,9 @@ from pathlib import Path
8
8
  from typing import List
9
9
  from zipfile import ZipFile
10
10
 
11
- from nubia import argument, command
11
+ from nubia import argument, command, context
12
12
  from seqslab.exceptions import exception_handler
13
+ from seqslab.workspace.internal.common import get_factory as get_workspace_factory
13
14
  from termcolor import cprint
14
15
  from yarl import URL
15
16
 
@@ -138,6 +139,13 @@ class BaseTools:
138
139
  ret.append(res)
139
140
  return ret
140
141
 
142
+ @staticmethod
143
+ def _valide_workspace(workspace: str) -> bool:
144
+ ctx = context.get_context()
145
+ backend = ctx.args.backend
146
+ resource = get_workspace_factory().load_resource()
147
+ return resource.validate_workspace(workspace, backend)
148
+
141
149
  @command
142
150
  @argument(
143
151
  "name",
@@ -285,9 +293,12 @@ class BaseTools:
285
293
  """
286
294
  Register TRS version object.
287
295
  """
288
- if not workspace:
289
- cprint("Enter a valid workspace", "red")
290
- return errno.EIO
296
+
297
+ if not self._valide_workspace(workspace):
298
+ logging.error("Workspace not found")
299
+ cprint(f"Workspace {workspace} not found.", "red")
300
+ return errno.EINVAL
301
+
291
302
  kwargs["workspace"] = workspace
292
303
  id = self._version(
293
304
  tool_id=tool_id,
@@ -204,9 +204,11 @@ class TrsInfoTemplate:
204
204
  )
205
205
  return list(
206
206
  map(
207
- lambda x: {**x, **{"image_name": ""}}
208
- if x.get("type").find("DESCRIPTOR") != -1
209
- else x,
207
+ lambda x: (
208
+ {**x, **{"image_name": ""}}
209
+ if x.get("type").find("DESCRIPTOR") != -1
210
+ else x
211
+ ),
210
212
  parameter["workflow"],
211
213
  )
212
214
  )