seqslab-cli 3.3.2.post1__tar.gz → 3.3.4__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 (108) hide show
  1. {seqslab-cli-3.3.2.post1/python/seqslab_cli.egg-info → seqslab-cli-3.3.4}/PKG-INFO +1 -1
  2. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/__init__.py +1 -1
  3. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/auth/azuread.py +2 -2
  4. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/auth/commands.py +4 -2
  5. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/drs/api/azure.py +12 -0
  6. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/drs/api/base.py +2 -2
  7. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/drs/commands.py +3 -3
  8. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/trs/template/base.py +1 -1
  9. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/user/resource/base.py +3 -1
  10. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/wes/commands.py +241 -28
  11. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/wes/internal/parameters.py +5 -2
  12. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/wes/resource/base.py +67 -0
  13. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/wes/template/base.py +14 -6
  14. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4/python/seqslab_cli.egg-info}/PKG-INFO +1 -1
  15. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab_cli.egg-info/SOURCES.txt +1 -1
  16. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab_cli.egg-info/requires.txt +7 -5
  17. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/requirements.txt +7 -5
  18. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/LICENSE +0 -0
  19. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/MANIFEST.in +0 -0
  20. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/README.md +0 -0
  21. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/auth/__init__.py +0 -0
  22. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/cli.py +0 -0
  23. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/context.py +0 -0
  24. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/drs/__init__.py +0 -0
  25. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/drs/api/__init__.py +0 -0
  26. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/drs/api/common.py +0 -0
  27. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/drs/api/template.py +0 -0
  28. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/drs/internal/__init__.py +0 -0
  29. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/drs/internal/aiocopy.py +0 -0
  30. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/drs/internal/common.py +0 -0
  31. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/drs/internal/utils.py +0 -0
  32. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/drs/storage/__init__.py +0 -0
  33. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/drs/storage/azure.py +0 -0
  34. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/drs/storage/base.py +0 -0
  35. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/drs/utils/__init__.py +0 -0
  36. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/drs/utils/atgxmetadata.py +0 -0
  37. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/drs/utils/biomimetype.py +0 -0
  38. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/drs/utils/progressbar.py +0 -0
  39. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/exceptions.py +0 -0
  40. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/organization/__init__.py +0 -0
  41. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/organization/commands.py +0 -0
  42. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/organization/resource/__init__.py +0 -0
  43. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/organization/resource/base.py +0 -0
  44. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/plugin.py +0 -0
  45. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/role/__init__.py +0 -0
  46. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/role/commands.py +0 -0
  47. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/role/internal/__init__.py +0 -0
  48. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/role/internal/common.py +0 -0
  49. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/role/resource/__init__.py +0 -0
  50. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/role/resource/azure.py +0 -0
  51. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/role/resource/base.py +0 -0
  52. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/runsheet/__init__.py +0 -0
  53. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/runsheet/runsheet.py +0 -0
  54. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/sample_sheet/__init__.py +0 -0
  55. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/sample_sheet/_version.py +0 -0
  56. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/sample_sheet/util.py +0 -0
  57. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/scr/__init__.py +0 -0
  58. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/scr/commands.py +0 -0
  59. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/scr/internal/__init__.py +0 -0
  60. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/scr/internal/common.py +0 -0
  61. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/scr/resource/__init__.py +0 -0
  62. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/scr/resource/azure.py +0 -0
  63. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/scr/resource/base.py +0 -0
  64. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/session_logger.py +0 -0
  65. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/settings.py +0 -0
  66. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/statusbar.py +0 -0
  67. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/trs/__init__.py +0 -0
  68. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/trs/commands.py +0 -0
  69. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/trs/internal/__init__.py +0 -0
  70. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/trs/internal/utils.py +0 -0
  71. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/trs/register/__init__.py +0 -0
  72. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/trs/register/azure.py +0 -0
  73. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/trs/register/base.py +0 -0
  74. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/trs/register/common.py +0 -0
  75. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/trs/register/template.py +0 -0
  76. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/trs/resource/__init__.py +0 -0
  77. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/trs/resource/azure.py +0 -0
  78. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/trs/resource/base.py +0 -0
  79. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/trs/resource/common.py +0 -0
  80. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/trs/template/__init__.py +0 -0
  81. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/trs/template/template.py +0 -0
  82. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/usage_logger.py +0 -0
  83. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/user/__init__.py +0 -0
  84. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/user/commands.py +0 -0
  85. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/user/internal/__init__.py +0 -0
  86. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/user/internal/common.py +0 -0
  87. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/user/resource/__init__.py +0 -0
  88. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/user/resource/azure.py +0 -0
  89. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/wes/__init__.py +0 -0
  90. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/wes/internal/__init__.py +0 -0
  91. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/wes/resource/__init__.py +0 -0
  92. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/wes/resource/azure.py +0 -0
  93. {seqslab-cli-3.3.2.post1/python/seqslab/wes/internal → seqslab-cli-3.3.4/python/seqslab/wes/resource}/common.py +0 -0
  94. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/wes/template/__init__.py +0 -0
  95. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/wes/template/template.py +0 -0
  96. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/workspace/__init__.py +0 -0
  97. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/workspace/commands.py +0 -0
  98. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/workspace/internal/__init__.py +0 -0
  99. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/workspace/internal/common.py +0 -0
  100. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/workspace/resource/__init__.py +0 -0
  101. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/workspace/resource/azure.py +0 -0
  102. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab/workspace/resource/base.py +0 -0
  103. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab_cli.egg-info/dependency_links.txt +0 -0
  104. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab_cli.egg-info/entry_points.txt +0 -0
  105. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab_cli.egg-info/top_level.txt +0 -0
  106. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/python/seqslab_cli.egg-info/zip-safe +0 -0
  107. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/setup.cfg +0 -0
  108. {seqslab-cli-3.3.2.post1 → seqslab-cli-3.3.4}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: seqslab-cli
3
- Version: 3.3.2.post1
3
+ Version: 3.3.4
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.2.post1"
24
+ __version__ = "3.3.4"
25
25
 
26
26
  LOGGING = {"DIR_PATH": "/var/log/seqslab"}
@@ -72,14 +72,14 @@ class ClientApp(AuthBaseModel):
72
72
  public_app: msal.ClientApplication = None
73
73
  confidential_app: msal.ClientApplication = None
74
74
 
75
- def load_client(self, credential: str = None) -> Client:
75
+ def load_client(self, credential: str = None, tenant: str = None) -> Client:
76
76
  proxies = context.get_context().args.proxy
77
77
  if credential:
78
78
  if not self.confidential_app:
79
79
  self.confidential_app = msal.ConfidentialClientApplication(
80
80
  SOCIAL_AUTH_AZURE_KEY,
81
81
  client_credential=credential,
82
- authority=f"https://login.microsoftonline.com/{SOCIAL_AUTH_AZURE_TENANT_ID}",
82
+ authority=f"https://login.microsoftonline.com/{tenant}",
83
83
  app_name=seqslab.name,
84
84
  app_version=seqslab.__version__,
85
85
  proxies=proxies,
@@ -60,7 +60,9 @@ class BaseAuth:
60
60
  proxy: str = None,
61
61
  ) -> dict:
62
62
  scopes = azuread.SOCIAL_AUTH_AZURE_SCOPE_APP.get(scope)
63
- client = aad_app.load_client(credential=credential)
63
+ client = aad_app.load_client(
64
+ credential=credential, tenant=self._decode(assertion)["tid"]
65
+ )
64
66
  result = client.acquire_token_silent(scopes, account=None)
65
67
  if not result:
66
68
  logging.info(
@@ -92,7 +94,7 @@ class BaseAuth:
92
94
  result = None
93
95
  token_uri = self.ACCESS_TOKEN_URL.format(backend=backend)
94
96
  scopes = azuread.SOCIAL_AUTH_AZURE_SCOPE
95
- client = aad_app.load_client()
97
+ client = aad_app.load_client(tenant=azuread.SOCIAL_AUTH_AZURE_TENANT_ID)
96
98
  accounts = client.get_accounts()
97
99
  if accounts:
98
100
  logging.info(
@@ -8,6 +8,7 @@ from typing import List
8
8
  import requests
9
9
  from nubia import context
10
10
  from seqslab.auth.commands import BaseAuth
11
+ from seqslab.drs.storage.azure import BlobStorage
11
12
  from seqslab.drs.utils.biomimetype import get_mime_type
12
13
  from tenacity import retry, stop_after_attempt, wait_fixed
13
14
  from termcolor import cprint
@@ -396,3 +397,14 @@ class AzureDRSregister(DRSregister):
396
397
  )
397
398
 
398
399
  return dsts_list
400
+
401
+ def common_root(self, dsts: List[URL], **kwargs) -> str:
402
+ root_dst = super().common_root(dsts)
403
+ blob_root = BlobStorage(kwargs.get("workspace")).get_token(path=None)[
404
+ "register_url"
405
+ ]
406
+ if URL(root_dst).path == URL(blob_root).path:
407
+ raise ValueError(
408
+ "access_url pointing to cloud storage root path is not allowed"
409
+ )
410
+ return root_dst
@@ -335,7 +335,7 @@ class DRSregister:
335
335
  folder_stdin["access_methods"].append(access_method_infos[i])
336
336
  folder_stdin["access_methods"][i]["access_url"][
337
337
  "url"
338
- ] = self.common_root(dsts=dsts)
338
+ ] = self.common_root(dsts=dsts, workspace=kwargs.get("workspace"))
339
339
 
340
340
  folder_stdin["name"] = kwargs.get(
341
341
  "name",
@@ -360,7 +360,7 @@ class DRSregister:
360
360
  raise ValueError("Make sure all the sizes are filled.")
361
361
  return sum(sizes)
362
362
 
363
- def common_root(self, dsts: List[URL]) -> str:
363
+ def common_root(self, dsts: List[URL], **kwargs) -> str:
364
364
  """
365
365
  e.g. http://user:password@example.com:8042/over/there?name=ferret#nose
366
366
  URL.scheme: http
@@ -173,7 +173,7 @@ class BaseDatahub:
173
173
  "dst",
174
174
  type=str,
175
175
  positional=False,
176
- description="Specify the destination directory, file path, or DRS URL (optional, default =' /'). ",
176
+ description="Specify the destination directory, file path, or DRS URL (optional, default ='MyDir/'). ",
177
177
  )
178
178
  @argument(
179
179
  "recursive",
@@ -223,7 +223,7 @@ class BaseDatahub:
223
223
  self,
224
224
  workspace: str,
225
225
  src: str,
226
- dst: str = "/",
226
+ dst: str = "MyDir/",
227
227
  recursive: bool = False,
228
228
  output: str = "json",
229
229
  concurrency: int = 0,
@@ -706,7 +706,7 @@ class BaseDatahub:
706
706
  backend = drs_register().load_register(workspace)
707
707
  if stdin:
708
708
  payloads = backend.create_payload(
709
- stdin=BaseDatahub._stdin(), type=drs_type, **kwargs
709
+ stdin=BaseDatahub._stdin(), type=drs_type, workspace=workspace, **kwargs
710
710
  )
711
711
  else:
712
712
  if not kwargs["is_integrity"]:
@@ -1,4 +1,5 @@
1
1
  # Standard Library
2
+ import re
2
3
  from typing import List
3
4
 
4
5
  import pydot
@@ -40,7 +41,6 @@ class TrsCreateTemplate:
40
41
  @staticmethod
41
42
  def dot_cleaning(gs: str) -> str:
42
43
  # Standard Library
43
- import re
44
44
 
45
45
  rx = r';[\w;="]+'
46
46
  return re.sub(rx, "", gs)
@@ -112,7 +112,9 @@ class BaseResource:
112
112
  def is_global_admin(self, **kwargs) -> bool:
113
113
  token = BaseAuth.get_token()
114
114
  response = self.get_user(token.get("attrs").get("user_id"))
115
- if "Global administrator" in response.get("roles"):
115
+ if "Global administrator" in [
116
+ item.get("name") for item in response.get("roles")
117
+ ]:
116
118
  return True
117
119
 
118
120
  return False
@@ -11,6 +11,7 @@ from datetime import datetime
11
11
  from functools import lru_cache
12
12
  from typing import List
13
13
 
14
+ import pytz
14
15
  import requests
15
16
  from nubia import argument, command, context
16
17
  from requests_toolbelt.multipart.encoder import MultipartEncoder
@@ -22,8 +23,9 @@ from seqslab.wes import API_HOSTNAME, __version__
22
23
  from seqslab.wes.internal import parameters
23
24
  from tenacity import retry, stop_after_attempt, wait_fixed
24
25
  from termcolor import cprint
26
+ from tzlocal import get_localzone
25
27
 
26
- from .internal.common import get_factory
28
+ from .resource.common import get_factory
27
29
 
28
30
  """
29
31
  Copyright (C) 2022, Atgenomix Incorporated.
@@ -186,40 +188,122 @@ class BaseJobs:
186
188
 
187
189
  @command
188
190
  @argument(
189
- "run_request_id",
191
+ "request_path",
190
192
  type=str,
191
193
  positional=False,
192
- description="Specify a previously scheduled run request ID (required). ",
194
+ description="Specify the request.json file to run (required).",
195
+ )
196
+ @argument(
197
+ "workspace",
198
+ type=str,
199
+ description="Specify the workspace based on the signed in account (required).",
193
200
  )
194
201
  @argument(
195
- "schedule_tag",
202
+ "date",
196
203
  type=str,
197
204
  positional=False,
198
- description="Specify a tag marked on previously uploaded samples for a scheduled WES run (required).",
205
+ aliases=["d"],
206
+ description="Specify the schedule date, in the format of YYYY-MM-DD (required).",
199
207
  )
200
208
  @argument(
201
- "workspace",
209
+ "time",
202
210
  type=str,
203
- description="Specify the workspace based on the signed in account (required).",
211
+ positional=False,
212
+ aliases=["t"],
213
+ description="Specify the schedule time, in the format of HH-MM (required).",
214
+ )
215
+ @argument(
216
+ "time_zone",
217
+ type=str,
218
+ choices=["UTC", "LOCAL"],
219
+ positional=False,
220
+ aliases=["z"],
221
+ description="Specify the time zone for the provided date and time. If 'LOCAL' is selected, the date and time "
222
+ "will be interpreted according to the operating system's time zone. (optional, default = UTC).",
204
223
  )
205
- def schedule(self, run_request_id: str, schedule_tag: str, workspace: str) -> int:
224
+ @argument(
225
+ "recurrence",
226
+ type=str,
227
+ positional=False,
228
+ aliases=["r"],
229
+ choices=["Once", "Hourly", "Daily", "Weekly", "Monthly"],
230
+ description="Specify the schedule recurrence (optional, default = Once).",
231
+ )
232
+ def schedule(
233
+ self,
234
+ request_path: str,
235
+ workspace: str,
236
+ date: str,
237
+ time: str,
238
+ time_zone: str = "UTC",
239
+ recurrence: str = "Once",
240
+ ) -> int:
206
241
  """
207
- Run a WES run based on a previously registered run request. Typically, the run request
208
- is designed for a scheduled WES run, where the FQN-DRS connection of sequencing samples
209
- are left blank for future runtime sample-resolving. Thus, by specifying a sample-resolving rule,
210
- the run request can be used to serve a scheduled WES run use case.
242
+ Schedule a WES run with given date, time, and recurrence.
211
243
  """
212
- mp = MultipartEncoder(fields={})
244
+ if not os.path.isfile(request_path):
245
+ cprint("request_path is not a file", "red")
246
+ return errno.EINVAL
247
+ else:
248
+ try:
249
+ with open(request_path, "r") as f:
250
+ req = json.load(f)
251
+ except json.decoder.JSONDecodeError as e:
252
+ cprint(f"given request not in json format - {e}", "red")
253
+ return errno.EINVAL
254
+
255
+ if not self._is_valid_time(date, "%Y-%m-%d"):
256
+ cprint(f"date {date} not in YYYY-MM-DD format", "red")
257
+ return errno.EINVAL
258
+
259
+ if not self._is_valid_time(time, "%H:%M"):
260
+ cprint(f"time {time} not in HH-MM format", "red")
261
+ return errno.EINVAL
262
+
263
+ time_f = f"{date} {time}"
264
+
265
+ if time_zone == "LOCAL":
266
+ time_f, msg = self._to_utc(time_f)
267
+ if not time_f:
268
+ cprint(f"timezone transform failed {msg}", "red")
269
+ return errno.EINVAL
270
+
271
+ payloads = {
272
+ "schedule": {"schedule_type": recurrence[0], "next_run": time_f},
273
+ "request": req,
274
+ }
275
+
213
276
  resource = get_factory().load_resource(workspace)
214
- ret = resource.sync_run_jobs(
215
- data=mp,
216
- headers={"Content-Type": mp.content_type},
217
- run_request_id=run_request_id,
218
- run_name=schedule_tag,
277
+ ret = resource.schedule_run(
278
+ data=payloads,
279
+ ).json()
280
+ cprint(
281
+ f"wes_schedule_id: {ret['id']}; schedule_details: {str(ret['schedule'])}",
282
+ "yellow",
219
283
  )
220
- cprint(f"{ret.content.decode('utf-8')}", "yellow")
221
284
  return 0
222
285
 
286
+ @staticmethod
287
+ def _is_valid_time(time_str: str, time_format: str):
288
+ try:
289
+ datetime.strptime(time_str, time_format)
290
+ return True
291
+ except ValueError:
292
+ return False
293
+
294
+ @staticmethod
295
+ def _to_utc(local_t: str):
296
+ try:
297
+ local_timezone = pytz.timezone(get_localzone().key)
298
+ utc_t = (
299
+ local_timezone.localize(datetime.strptime(local_t, "%Y-%m-%d %H:%M"))
300
+ .astimezone(pytz.utc)
301
+ .strftime("%Y-%m-%d %H:%M")
302
+ )
303
+ return utc_t, None
304
+ except Exception as e:
305
+ return None, str(e)
306
+
223
307
  @command(aliases=["state"])
224
308
  @argument(
225
309
  "run_id",
@@ -400,8 +484,21 @@ class BaseJobs:
400
484
  else:
401
485
  execs_path = f"{working_dir}/{execs}"
402
486
 
487
+ resource = get_factory().load_resource(workspace)
488
+ ops = resource.list_operator_pipelines(page=1, page_size=1000)["results"]
489
+ opp_w_args = [
490
+ op["id"]
491
+ for op in ops
492
+ for operator in op["operators"]
493
+ if isinstance(operator, dict) and operator.get("arguments")
494
+ ]
403
495
  params = parameters.workflow_params(
404
- execs_path, run, is_runsheet_template, is_single_end, fq_signature
496
+ execs_path,
497
+ run,
498
+ is_runsheet_template,
499
+ is_single_end,
500
+ fq_signature,
501
+ opp_w_args,
405
502
  )
406
503
  if not isinstance(params, dict):
407
504
  raise Exception(
@@ -458,11 +555,11 @@ class BaseJobs:
458
555
  @argument(
459
556
  "runtimes",
460
557
  type=str,
461
- description="Key:value pairs indicating the workflow name -> SeqsLab supported runtime_options names. "
462
- "Multiple configuration pairs can be provided using ':' as separator, "
463
- "e.g. main=m4-cluster:subworkflow=m4-8xcluster "
464
- "(optional, default = None, which indicates running the whole workflow.wdl using m4-cluster for "
465
- "a single node cluster on the Azure backend).",
558
+ description="String of key-value pairs using : as a separator, indicating the execution runtimes for each task "
559
+ "or workflow defined in workflow-url. For example, Main=m4-cluster:Main>Fq2Bam=m4-8xcluster. To "
560
+ "list tasks/workflows for a given workflow, use the execs command. To find available runtime "
561
+ "options, use the request runtimes_options command. (Optional, defaults to None, which runs the "
562
+ "entire workflow using m4-cluster.)",
466
563
  )
467
564
  @argument(
468
565
  "integrity",
@@ -620,10 +717,9 @@ class BaseJobs:
620
717
  try:
621
718
  result = get_factory().load_resource(workspace).cancel_run(run_id)
622
719
  cprint(json.dumps(result, indent=4), "yellow")
623
- except requests.HTTPError:
624
- cprint(f"given run_id {run_id} is not valid.", "red")
720
+ except requests.HTTPError as e:
721
+ cprint(f"Fail to cancel Job {run_id} - '{str(e)}'.", "red")
625
722
  return -1
626
-
627
723
  return 0
628
724
 
629
725
  @command
@@ -661,6 +757,123 @@ class BaseJobs:
661
757
 
662
758
  return 0
663
759
 
760
+ @command(aliases=["rt"])
761
+ @argument(
762
+ "page",
763
+ type=int,
764
+ positional=False,
765
+ description="Specify the page number in the set of paginated records (optional, default = 1).",
766
+ )
767
+ @argument(
768
+ "page_size",
769
+ type=int,
770
+ positional=False,
771
+ description="Specify the number of records to return in each page (optional, default = 10).",
772
+ )
773
+ @command
774
+ @argument(
775
+ "output",
776
+ type=str,
777
+ positional=False,
778
+ description="Specify the output format of the stdout file (optional, default = json).",
779
+ choices=["json", "table"],
780
+ )
781
+ def runtime_options(
782
+ self, workspace: str, page: int = 1, page_size: int = 10, output="json"
783
+ ) -> int:
784
+ """
785
+ List registered cluster runtimes settings.
786
+ """
787
+ resource = get_factory().load_resource(workspace)
788
+ r = resource.list_runtime_options(page=page, page_size=page_size)
789
+
790
+ if isinstance(r, int):
791
+ return r
792
+
793
+ self._stdout(r["results"], output)
794
+ return 0
795
+
796
+ @command(aliases=["op"])
797
+ @argument(
798
+ "page",
799
+ type=int,
800
+ positional=False,
801
+ description="Specify the page number in the set of paginated records (optional, default = 1).",
802
+ )
803
+ @argument(
804
+ "page_size",
805
+ type=int,
806
+ positional=False,
807
+ description="Specify the number of records to return in each page (optional, default = 10).",
808
+ )
809
+ @command
810
+ @argument(
811
+ "output",
812
+ type=str,
813
+ positional=False,
814
+ description="Specify the output format of the stdout file (optional, default = json).",
815
+ choices=["json", "table"],
816
+ )
817
+ def operator_pipelines(
818
+ self, workspace: str, page: int = 1, page_size: int = 10, output="json"
819
+ ) -> int:
820
+ """
821
+ List registered operator pipelines.
822
+ """
823
+ resource = get_factory().load_resource(workspace)
824
+ r = resource.list_operator_pipelines(page=page, page_size=page_size)
825
+
826
+ if isinstance(r, int):
827
+ return r
828
+
829
+ self._stdout(r["results"], output)
830
+ return 0
831
+
832
+ @staticmethod
833
+ def _stdout(results, output: str) -> int:
834
+ # Standard Library
835
+
836
+ from tabulate import tabulate
837
+
838
+ """
839
+ stdout:: TODO: support different format ex: json, tsv, table
840
+ """
841
+ if output == "json":
842
+ cprint(json.dumps(results, indent=4))
843
+ elif output == "table":
844
+ table_header = list(results[0].keys())
845
+ table_datas = [result.values() for result in results]
846
+ cprint(
847
+ tabulate(
848
+ tabular_data=table_datas, headers=table_header, tablefmt="pipe"
849
+ )
850
+ )
851
+ return 0
852
+
853
+ @command
854
+ @argument(
855
+ "run_id",
856
+ type=str,
857
+ positional=False,
858
+ description="Specify the run_id that is going to be delete (required).",
859
+ )
860
+ @argument(
861
+ "workspace",
862
+ type=str,
863
+ description="Specify the workspace based on the signed in account (required).",
864
+ )
865
+ def delete(self, run_id: str, workspace: str) -> int:
866
+ """
867
+ Delete WES run as well as all the generated output files based on run ID.
868
+ """
869
+ try:
870
+ get_factory().load_resource(workspace).delete_run(run_id)
871
+ except requests.HTTPError as e:
872
+ cprint(f"Fail to delete Job {run_id} - '{str(e)}'.", "red")
873
+ return -1
874
+
875
+ return 0
876
+
664
877
 
665
878
  @command
666
879
  class Jobs(BaseJobs):
@@ -6,7 +6,7 @@ import re
6
6
  import zipfile
7
7
 
8
8
  from seqslab.runsheet.runsheet import Run
9
- from seqslab.wes.internal.common import get_factory
9
+ from seqslab.wes.resource.common import get_factory
10
10
  from seqslab.wes.template.base import (
11
11
  WorkflowBackendParamsClusterTemplate,
12
12
  WorkflowBackendParamsTemplate,
@@ -119,6 +119,7 @@ def workflow_params(
119
119
  is_runsheet_template: bool,
120
120
  is_single_end: bool,
121
121
  fq_sig: str,
122
+ opp_w_args: dict,
122
123
  ) -> dict:
123
124
  """
124
125
  Create workflow_params.json.
@@ -132,7 +133,9 @@ def workflow_params(
132
133
  with open(execs_json, "r") as f:
133
134
  t_content = json.loads(f.read())
134
135
 
135
- params = WorkflowParamsTemplate().create(ex_template=t_content)
136
+ params = WorkflowParamsTemplate().create(
137
+ ex_template=t_content, opp_w_args=opp_w_args
138
+ )
136
139
 
137
140
  if is_runsheet_template:
138
141
  r1fqn, r2fqn = validate_label_column(run)
@@ -50,9 +50,12 @@ class BaseResource(ABC):
50
50
  WES_RUNS_DRY_URL = f"{WES_BASE_URL}runs/dryrun/?backend={{backend}}"
51
51
  WES_RUNS_FILE_URL = f"{WES_BASE_URL}runs/{{id}}/files/?backend={{backend}}"
52
52
  WES_RUNS_STATUS_URL = f"{WES_BASE_URL}runs/{{id}}/status/?backend={{backend}}"
53
+ WES_RUNTIME_OPTIONS_BASE_URL = f"{WES_BASE_URL}runtime-options/"
54
+ WES_OPERATOR_PIPELINES_BASE_URL = f"{WES_BASE_URL}operator-pipelines/"
53
55
  WES_RUNTIME_OPTIONS_URL = (
54
56
  f"{WES_BASE_URL}runtime-options/{{name}}?backend={{backend}}"
55
57
  )
58
+ WES_SCHEDULES_URL = f"{WES_BASE_URL}schedules/?backend={{backend}}"
56
59
 
57
60
  class Response(NamedTuple):
58
61
  status: int
@@ -310,3 +313,67 @@ class BaseResource(ABC):
310
313
  if response.status_code not in [requests.codes.ok]:
311
314
  raise requests.HTTPError(response.text)
312
315
  return json.loads(response.content)
316
+
317
+ def delete_run(self, run_id) -> int:
318
+ token = BaseAuth.get_token().get("tokens").get("access")
319
+ with requests.delete(
320
+ url=f"{self.WES_BASE_URL}runs/{run_id}",
321
+ headers={"Authorization": f"Bearer {token}"},
322
+ ) as response:
323
+ if response.status_code not in [204]:
324
+ raise requests.HTTPError(response.text)
325
+ return 0
326
+
327
+ def list_runtime_options(self, page=1, page_size=10):
328
+ try:
329
+ token = BaseAuth.get_token().get("tokens").get("access")
330
+ with requests.get(
331
+ url=f"{self.WES_RUNTIME_OPTIONS_BASE_URL}?page={page}&page_size={page_size}",
332
+ headers={"Authorization": f"Bearer {token}"},
333
+ ) as response:
334
+ if response.status_code not in [requests.codes.ok]:
335
+ raise requests.HTTPError(
336
+ f"{response.status_code}: {repr(response.text)}"
337
+ )
338
+ except Exception as err:
339
+ print(err)
340
+ raise err
341
+ return response.json()
342
+
343
+ def list_operator_pipelines(self, page=1, page_size=10):
344
+ try:
345
+ token = BaseAuth.get_token().get("tokens").get("access")
346
+ with requests.get(
347
+ url=f"{self.WES_OPERATOR_PIPELINES_BASE_URL}?page={page}&page_size={page_size}",
348
+ headers={"Authorization": f"Bearer {token}"},
349
+ ) as response:
350
+ if response.status_code not in [requests.codes.ok]:
351
+ raise requests.HTTPError(
352
+ f"{response.status_code}: {repr(response.text)}"
353
+ )
354
+ except Exception as err:
355
+ print(err)
356
+ raise err
357
+ return response.json()
358
+
359
+ def schedule_run(self, data) -> requests.Response:
360
+ try:
361
+ ctx = context.get_context()
362
+ backend = ctx.args.backend
363
+ token = BaseAuth.get_token().get("tokens").get("access")
364
+
365
+ response = requests.post(
366
+ url=self.WES_SCHEDULES_URL.format(backend=backend),
367
+ headers={"Authorization": f"Bearer {token}"},
368
+ json=data,
369
+ )
370
+
371
+ if response.status_code not in [requests.codes.created]:
372
+ raise requests.HTTPError(
373
+ f"{response.status_code}: {repr(response.text)}"
374
+ )
375
+ except Exception as err:
376
+ print(err)
377
+ raise err
378
+
379
+ return response
@@ -1,6 +1,6 @@
1
1
  class WorkflowParamsTemplate:
2
- def create(self, ex_template: dict) -> dict:
3
- operator_pipelines = self.operator_pipelines(ex_template)
2
+ def create(self, ex_template: dict, opp_w_args: dict) -> dict:
3
+ operator_pipelines = self.operator_pipelines(ex_template, opp_w_args)
4
4
  return {
5
5
  "inputs": ex_template.get("inputs"),
6
6
  "datasets": ex_template.get("connections", None),
@@ -19,8 +19,7 @@ class WorkflowParamsTemplate:
19
19
  return self._flat_list(sub_v, r, layer)
20
20
  return {"list": r, "layer": layer}
21
21
 
22
- @staticmethod
23
- def operator_pipelines(ex_template: dict) -> dict:
22
+ def operator_pipelines(self, ex_template: dict, opp_w_args: dict) -> dict:
24
23
  """
25
24
  :param: parameter: parameter API response
26
25
  :return:
@@ -34,19 +33,28 @@ class WorkflowParamsTemplate:
34
33
  tasks = {}
35
34
  pl_keys = [pipeline["id"] for pipeline in ex_template["operator_pipelines"]]
36
35
  for k, v in dict(ex_template["i_configs"], **ex_template["o_configs"]).items():
36
+ if not v:
37
+ continue
37
38
  assert (
38
39
  v in pl_keys
39
40
  ), f"given operator pipeline ID {v} for FQN {k} not in operator pipeline list from execs: {pl_keys}"
40
41
  for pipeline in ex_template["operator_pipelines"]:
41
42
  if pipeline["id"] == v:
42
43
  tasks[k] = {
43
- "id": v,
44
+ "id": self.norm_pl_key(v, opp_w_args),
44
45
  "operators": pipeline["operators"],
45
46
  "description": pipeline["description"],
46
47
  }
47
48
  continue
48
49
  return tasks
49
50
 
51
+ @staticmethod
52
+ def norm_pl_key(pl_key: str, opp_w_args: dict) -> str:
53
+ for opp in opp_w_args:
54
+ if pl_key.startswith(opp):
55
+ return opp
56
+ return pl_key
57
+
50
58
  @staticmethod
51
59
  def inputs_connections(inputs_connection: list = None) -> dict:
52
60
  """
@@ -113,7 +121,7 @@ def WorkflowBackendParamsTemplate(
113
121
 
114
122
 
115
123
  def WorkflowBackendParamsClusterTemplate(
116
- run_time: dict, workflow_name: str, kernel_version: str
124
+ run_time: dict, kernel_version: str, workflow_name: str = ""
117
125
  ) -> dict:
118
126
  if kernel_version:
119
127
  opts = run_time["options"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: seqslab-cli
3
- Version: 3.3.2.post1
3
+ Version: 3.3.4
4
4
  Summary: Atgenomix SeqsLab Command Line Tool
5
5
  Home-page: https://github.com/AnomeGAP/seqslab-cli
6
6
  Author: Allen Chang
@@ -83,11 +83,11 @@ python/seqslab/user/resource/base.py
83
83
  python/seqslab/wes/__init__.py
84
84
  python/seqslab/wes/commands.py
85
85
  python/seqslab/wes/internal/__init__.py
86
- python/seqslab/wes/internal/common.py
87
86
  python/seqslab/wes/internal/parameters.py
88
87
  python/seqslab/wes/resource/__init__.py
89
88
  python/seqslab/wes/resource/azure.py
90
89
  python/seqslab/wes/resource/base.py
90
+ python/seqslab/wes/resource/common.py
91
91
  python/seqslab/wes/template/__init__.py
92
92
  python/seqslab/wes/template/base.py
93
93
  python/seqslab/wes/template/template.py
@@ -2,7 +2,7 @@ pip>=22.0.4
2
2
  click==8.1.7
3
3
  tabulate==0.9.0
4
4
  terminaltables==3.1.10
5
- cryptography==41.0.7
5
+ cryptography==42.0.6
6
6
  jeepney==0.8.0
7
7
  secretstorage==3.3.3
8
8
  dbus-python==1.2.16
@@ -12,7 +12,7 @@ python-nubia==0.2b5
12
12
  msal==1.26.0
13
13
  tenacity==8.2.3
14
14
  aiofiles==23.2.1
15
- aiohttp[speedups]==3.9.1
15
+ aiohttp[speedups]==3.9.5
16
16
  aioretry==5.0.2
17
17
  uvloop==0.19.0
18
18
  arrow==1.3.0
@@ -22,9 +22,11 @@ PyJWT==2.8.0
22
22
  django-environ==0.11.2
23
23
  validators==0.22.0
24
24
  pydot==2.0.0
25
- orjson==3.9.10
26
- pydantic~=2.5.3
25
+ orjson==3.10.3
26
+ pydantic~=2.7.1
27
27
  aiohttp_retry==2.8.3
28
28
  requests~=2.31.0
29
- setuptools~=69.0.3
29
+ setuptools~=69.5.1
30
30
  jsonpath-ng==1.6.0
31
+ tzlocal==5.2
32
+ pytz==2021.3
@@ -1,4 +1,4 @@
1
- cryptography==41.0.7
1
+ cryptography==42.0.6
2
2
  jeepney==0.8.0
3
3
  secretstorage==3.3.3
4
4
  dbus-python==1.2.16
@@ -8,7 +8,7 @@ python-nubia==0.2b5
8
8
  msal==1.26.0
9
9
  tenacity==8.2.3
10
10
  aiofiles==23.2.1
11
- aiohttp[speedups]==3.9.1
11
+ aiohttp[speedups]==3.9.5
12
12
  aioretry==5.0.2
13
13
  uvloop==0.19.0
14
14
  arrow==1.3.0
@@ -18,9 +18,11 @@ PyJWT==2.8.0
18
18
  django-environ==0.11.2
19
19
  validators==0.22.0
20
20
  pydot==2.0.0
21
- orjson==3.9.10
22
- pydantic~=2.5.3
21
+ orjson==3.10.3
22
+ pydantic~=2.7.1
23
23
  aiohttp_retry==2.8.3
24
24
  requests~=2.31.0
25
- setuptools~=69.0.3
25
+ setuptools~=69.5.1
26
26
  jsonpath-ng==1.6.0
27
+ tzlocal==5.2
28
+ pytz==2021.3
File without changes
File without changes
File without changes
File without changes