dnastack-client-library 3.1.178__py3-none-any.whl → 3.1.204__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.
- dnastack/alpha/app/explorer.py +1 -1
- dnastack/alpha/app/workbench.py +4 -4
- dnastack/alpha/cli/wes.py +3 -3
- dnastack/alpha/client/collections/client.py +2 -3
- dnastack/alpha/client/wes/client.py +5 -8
- dnastack/cli/commands/collections/commands.py +3 -4
- dnastack/cli/commands/collections/tables.py +1 -1
- dnastack/cli/commands/collections/utils.py +1 -1
- dnastack/cli/commands/config/commands.py +1 -1
- dnastack/cli/commands/config/endpoints.py +39 -20
- dnastack/cli/commands/config/registries.py +2 -2
- dnastack/cli/commands/dataconnect/tables.py +1 -1
- dnastack/cli/commands/drs/commands.py +1 -1
- dnastack/cli/commands/explorer/questions/commands.py +12 -6
- dnastack/cli/commands/explorer/questions/utils.py +16 -7
- dnastack/cli/commands/publisher/collections/tables.py +1 -1
- dnastack/cli/commands/publisher/collections/utils.py +1 -1
- dnastack/cli/commands/publisher/datasources/commands.py +1 -1
- dnastack/cli/commands/workbench/runs/commands.py +11 -3
- dnastack/cli/commands/workbench/samples/commands.py +1 -2
- dnastack/cli/commands/workbench/workflows/versions/dependencies/commands.py +3 -3
- dnastack/cli/commands/workbench/workflows/versions/transformations.py +5 -9
- dnastack/cli/helpers/exporter.py +1 -1
- dnastack/client/base_exceptions.py +2 -2
- dnastack/client/collections/client.py +4 -4
- dnastack/client/collections/model.py +29 -29
- dnastack/client/data_connect.py +5 -9
- dnastack/client/datasources/model.py +3 -3
- dnastack/client/drs.py +4 -4
- dnastack/client/explorer/client.py +1 -1
- dnastack/client/explorer/models.py +3 -7
- dnastack/client/factory.py +1 -1
- dnastack/client/models.py +6 -6
- dnastack/client/service_registry/factory.py +3 -3
- dnastack/client/service_registry/helper.py +7 -14
- dnastack/client/service_registry/manager.py +4 -4
- dnastack/client/workbench/base_client.py +2 -2
- dnastack/client/workbench/ewes/client.py +3 -3
- dnastack/client/workbench/ewes/models.py +246 -181
- dnastack/client/workbench/models.py +7 -7
- dnastack/client/workbench/samples/models.py +41 -41
- dnastack/client/workbench/storage/client.py +2 -2
- dnastack/client/workbench/storage/models.py +24 -24
- dnastack/client/workbench/workflow/client.py +7 -7
- dnastack/client/workbench/workflow/models.py +64 -64
- dnastack/client/workbench/workflow/utils.py +5 -5
- dnastack/common/auth_manager.py +6 -13
- dnastack/common/class_decorator.py +3 -3
- dnastack/common/events.py +7 -7
- dnastack/common/json_argument_parser.py +4 -4
- dnastack/common/model_mixin.py +1 -1
- dnastack/common/parser.py +3 -3
- dnastack/common/simple_stream.py +1 -1
- dnastack/configuration/manager.py +8 -4
- dnastack/configuration/models.py +2 -2
- dnastack/constants.py +1 -1
- dnastack/context/manager.py +2 -2
- dnastack/context/models.py +2 -2
- dnastack/feature_flags.py +2 -2
- dnastack/http/authenticators/abstract.py +2 -2
- dnastack/http/authenticators/factory.py +2 -2
- dnastack/http/authenticators/oauth2.py +8 -8
- dnastack/http/authenticators/oauth2_adapter/client_credential.py +5 -14
- dnastack/http/authenticators/oauth2_adapter/models.py +15 -15
- dnastack/http/session.py +3 -3
- dnastack/http/session_info.py +3 -3
- {dnastack_client_library-3.1.178.dist-info → dnastack_client_library-3.1.204.dist-info}/METADATA +2 -2
- {dnastack_client_library-3.1.178.dist-info → dnastack_client_library-3.1.204.dist-info}/RECORD +72 -72
- {dnastack_client_library-3.1.178.dist-info → dnastack_client_library-3.1.204.dist-info}/WHEEL +0 -0
- {dnastack_client_library-3.1.178.dist-info → dnastack_client_library-3.1.204.dist-info}/entry_points.txt +0 -0
- {dnastack_client_library-3.1.178.dist-info → dnastack_client_library-3.1.204.dist-info}/licenses/LICENSE +0 -0
- {dnastack_client_library-3.1.178.dist-info → dnastack_client_library-3.1.204.dist-info}/top_level.txt +0 -0
dnastack/alpha/app/explorer.py
CHANGED
|
@@ -58,7 +58,7 @@ class Collection(PerCollectionApiMixin, BlobApiMixin):
|
|
|
58
58
|
url = urljoin(endpoint.url, f'collections/{self._collection.slugName}/tables/{table_name}/filter/query'
|
|
59
59
|
'?includeSharedQueryUrl=true')
|
|
60
60
|
headers = {'Content-Type': 'application/json'}
|
|
61
|
-
return FilterInfo(**session.post(url, data=json.dumps(filters), headers=headers).
|
|
61
|
+
return FilterInfo(**session.post(url, data=json.dumps(filters), headers=headers).model_dump_json())
|
|
62
62
|
|
|
63
63
|
def data_connect(self):
|
|
64
64
|
default_no_auth_properties = {'authentication': None, 'fallback_authentications': None}
|
dnastack/alpha/app/workbench.py
CHANGED
|
@@ -145,7 +145,7 @@ class Workbench:
|
|
|
145
145
|
return self._get_ewes_client().list_runs(list_options=ExtendedRunListOptions(tag=[f"batch_id:{batch_id}"]))
|
|
146
146
|
|
|
147
147
|
def submit_batch(self, batch: BatchRunRequest, batch_id:Optional[str]=None) -> str:
|
|
148
|
-
self._logger.debug("Submitting batch request: "+json.dumps(batch.
|
|
148
|
+
self._logger.debug("Submitting batch request: "+json.dumps(batch.model_dump()))
|
|
149
149
|
if batch_id is None:
|
|
150
150
|
batch_id = self._get_short_uuid()
|
|
151
151
|
|
|
@@ -164,7 +164,7 @@ class Workbench:
|
|
|
164
164
|
raise WorkbenchRunException("Could not submit run, unexpected run state '"+result.state)
|
|
165
165
|
return result.run_id
|
|
166
166
|
|
|
167
|
-
def cancel_batch(self, batch_id: str) ->
|
|
167
|
+
def cancel_batch(self, batch_id: str) -> None:
|
|
168
168
|
bad_run_ids=[]
|
|
169
169
|
for run_status in self.describe_batch(batch_id):
|
|
170
170
|
r=RunStatus(run_status.state)
|
|
@@ -173,7 +173,7 @@ class Workbench:
|
|
|
173
173
|
self.cancel_run(run_status.run_id)
|
|
174
174
|
except Exception:
|
|
175
175
|
bad_run_ids.append(run_status.run_id)
|
|
176
|
-
if
|
|
176
|
+
if len(bad_run_ids) > 0:
|
|
177
177
|
raise WorkbenchRunException("Could not cancel all runs in batch "+batch_id+". Run IDs: "+"\n".join(bad_run_ids), run_ids=bad_run_ids)
|
|
178
178
|
|
|
179
179
|
def cancel_run(self, run_id) -> RunStatus:
|
|
@@ -181,7 +181,7 @@ class Workbench:
|
|
|
181
181
|
if type(result) is RunId:
|
|
182
182
|
return RunStatus(RunId.state)
|
|
183
183
|
else:
|
|
184
|
-
raise WorkbenchRunException("Run "+run_id+" could not be canceled: "+json.dumps(result.
|
|
184
|
+
raise WorkbenchRunException("Run "+run_id+" could not be canceled: "+json.dumps(result.model_dump()))
|
|
185
185
|
|
|
186
186
|
# Returns the state that all of the runs are in unless:
|
|
187
187
|
# - If any run has a failed status, then that status is returned, otherwise
|
dnastack/alpha/cli/wes.py
CHANGED
|
@@ -115,7 +115,7 @@ def submit(context: Optional[str],
|
|
|
115
115
|
|
|
116
116
|
dnastack alpha wes submit --endpoint-id testing_wes -u hello_world.wdl -a samples/workflows/no_input/hello_world.wdl
|
|
117
117
|
"""
|
|
118
|
-
actual_params =
|
|
118
|
+
actual_params = {}
|
|
119
119
|
|
|
120
120
|
re_parameter = re.compile(r'^(?P<key>[a-zA-Z0-9_\.]+)(?P<op>:?=@?)(?P<value>.+)$')
|
|
121
121
|
|
|
@@ -138,7 +138,7 @@ def submit(context: Optional[str],
|
|
|
138
138
|
|
|
139
139
|
param_counter += 1
|
|
140
140
|
|
|
141
|
-
actual_tags =
|
|
141
|
+
actual_tags = {'agent': f'dnastack-client-library/{__version__}'}
|
|
142
142
|
if tags:
|
|
143
143
|
tag_counter = 0
|
|
144
144
|
for tag in tags:
|
|
@@ -192,7 +192,7 @@ def submit(context: Optional[str],
|
|
|
192
192
|
'this run request will not be submitted to the service endpoint.',
|
|
193
193
|
fg='yellow',
|
|
194
194
|
err=True)
|
|
195
|
-
click.secho(run_request.
|
|
195
|
+
click.secho(run_request.model_dump_json(indent=2), dim=True)
|
|
196
196
|
else:
|
|
197
197
|
_execute(context=context,
|
|
198
198
|
endpoint_id=endpoint_id,
|
|
@@ -37,9 +37,9 @@ class CollectionServiceClient(StandardCollectionServiceClient):
|
|
|
37
37
|
|
|
38
38
|
collection_id = collection.id if collection else id
|
|
39
39
|
|
|
40
|
-
given_overriding_properties = (collection.
|
|
40
|
+
given_overriding_properties = (collection.model_dump() if collection else (attrs or {}))
|
|
41
41
|
update_patches = [
|
|
42
|
-
JsonPatch(op='replace', path=f'/{k}', value=v).
|
|
42
|
+
JsonPatch(op='replace', path=f'/{k}', value=v).model_dump()
|
|
43
43
|
for k, v in given_overriding_properties.items()
|
|
44
44
|
if k not in COLLECTION_READ_ONLY_PROPERTIES and v is not None
|
|
45
45
|
]
|
|
@@ -52,7 +52,6 @@ class CollectionServiceClient(StandardCollectionServiceClient):
|
|
|
52
52
|
resource_url = self._get_single_collection_url(collection_id)
|
|
53
53
|
get_response = session.get(resource_url, trace_context=trace)
|
|
54
54
|
|
|
55
|
-
# trace_logger = trace.create_span_logger(self._logger)
|
|
56
55
|
assert get_response.status_code == 200, 'Unexpected Response'
|
|
57
56
|
|
|
58
57
|
etag = (get_response.headers.get('etag') or '').replace('"', '')
|
|
@@ -74,10 +74,7 @@ class RunRequest(BaseModel):
|
|
|
74
74
|
)
|
|
75
75
|
))
|
|
76
76
|
|
|
77
|
-
return
|
|
78
|
-
# data=form_data,
|
|
79
|
-
files=multipart_data
|
|
80
|
-
)
|
|
77
|
+
return {'files': multipart_data}
|
|
81
78
|
|
|
82
79
|
|
|
83
80
|
class _Id(BaseModel):
|
|
@@ -165,7 +162,7 @@ class RunListLoader(ResultLoader):
|
|
|
165
162
|
with self.__http_session as session:
|
|
166
163
|
current_url = self.__initial_url
|
|
167
164
|
|
|
168
|
-
params =
|
|
165
|
+
params = {}
|
|
169
166
|
if self.__page_size:
|
|
170
167
|
params['page_size'] = self.__page_size
|
|
171
168
|
if self.__page_token:
|
|
@@ -195,7 +192,7 @@ class RunListLoader(ResultLoader):
|
|
|
195
192
|
response_text = response.text
|
|
196
193
|
|
|
197
194
|
try:
|
|
198
|
-
response_body = response.json() if response_text else
|
|
195
|
+
response_body = response.json() if response_text else {}
|
|
199
196
|
except Exception:
|
|
200
197
|
self.logger.error(f'{self.__initial_url}: Unexpectedly non-JSON response body from {current_url}')
|
|
201
198
|
raise DataConnectError(
|
|
@@ -254,7 +251,7 @@ class Run:
|
|
|
254
251
|
|
|
255
252
|
def info(self) -> _Run:
|
|
256
253
|
# GET /runs/{id}
|
|
257
|
-
raw_data = self.__session.get(self.__base_url).
|
|
254
|
+
raw_data = self.__session.get(self.__base_url).model_dump_json()
|
|
258
255
|
try:
|
|
259
256
|
return _Run(**raw_data)
|
|
260
257
|
except ValidationError:
|
|
@@ -328,7 +325,7 @@ class WesClient(BaseServiceClient):
|
|
|
328
325
|
|
|
329
326
|
def submit(self, run: RunRequest) -> str:
|
|
330
327
|
workflow_url_is_external = run.workflow_url.startswith('http://') or run.workflow_url.startswith('https://')
|
|
331
|
-
workflow_url_is_in_attachments = run.workflow_url in [os.path.basename(p) for p in (run.attachments or
|
|
328
|
+
workflow_url_is_in_attachments = run.workflow_url in [os.path.basename(p) for p in (run.attachments or [])]
|
|
332
329
|
if not workflow_url_is_external and not workflow_url_is_in_attachments:
|
|
333
330
|
if not workflow_url_is_in_attachments:
|
|
334
331
|
raise RuntimeError('The workflow file from the local drive is defined but it is apparently not in the '
|
|
@@ -126,10 +126,9 @@ def init_collections_commands(group: Group):
|
|
|
126
126
|
|
|
127
127
|
logger.debug(f'Item Simplifier: given: {to_json(row)}')
|
|
128
128
|
|
|
129
|
-
item =
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
)
|
|
129
|
+
item = {'id': row['id'],
|
|
130
|
+
'name': row.get('qualified_table_name') or row.get('preferred_name') or row.get('display_name') or
|
|
131
|
+
row['name']}
|
|
133
132
|
|
|
134
133
|
if row['type'] == 'blob':
|
|
135
134
|
property_names.extend([
|
|
@@ -54,4 +54,4 @@ def get_table_info(context: Optional[str],
|
|
|
54
54
|
""" List all accessible tables """
|
|
55
55
|
client = _switch_to_data_connect(_get_context(context), _get(context, endpoint_id), collection, no_auth=no_auth)
|
|
56
56
|
obj = client.table(table_name, no_auth=no_auth).info
|
|
57
|
-
click.echo((to_json if output == 'json' else to_yaml)(obj.
|
|
57
|
+
click.echo((to_json if output == 'json' else to_yaml)(obj.model_dump()))
|
|
@@ -110,7 +110,7 @@ def _abort_with_collection_list(collection_service_client: CollectionServiceClie
|
|
|
110
110
|
def _transform_to_public_collection(collection: Collection) -> Dict[str, Any]:
|
|
111
111
|
return {
|
|
112
112
|
field_name: value
|
|
113
|
-
for field_name, value in (collection.
|
|
113
|
+
for field_name, value in (collection.model_dump() if isinstance(collection, Collection) else collection).items()
|
|
114
114
|
if field_name not in ['itemsQuery', 'accessTypeLabels']
|
|
115
115
|
}
|
|
116
116
|
|
|
@@ -17,7 +17,7 @@ def init_config_commands(group: Group):
|
|
|
17
17
|
)
|
|
18
18
|
def config_schema():
|
|
19
19
|
"""Show the schema of the configuration file"""
|
|
20
|
-
click.echo(json.dumps(Configuration.
|
|
20
|
+
click.echo(json.dumps(Configuration.model_json_schema(), indent=2, sort_keys=True))
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
@formatted_command(
|
|
@@ -83,10 +83,10 @@ def show_schema():
|
|
|
83
83
|
This is mainly for development.
|
|
84
84
|
"""
|
|
85
85
|
echo_header('Service Endpoint')
|
|
86
|
-
click.echo(to_json(ServiceEndpoint.
|
|
86
|
+
click.echo(to_json(ServiceEndpoint.model_json_schema()))
|
|
87
87
|
|
|
88
88
|
echo_header('OAuth2 Authentication Information')
|
|
89
|
-
click.echo(to_json(OAuth2Authentication.
|
|
89
|
+
click.echo(to_json(OAuth2Authentication.model_json_schema()))
|
|
90
90
|
|
|
91
91
|
|
|
92
92
|
@formatted_command(
|
|
@@ -109,7 +109,7 @@ def list_endpoints(context: Optional[str],
|
|
|
109
109
|
handler = EndpointCommandHandler(context_name=context)
|
|
110
110
|
full_type = handler.parse_given_service_type(short_or_full_type)[1] if short_or_full_type else None
|
|
111
111
|
show_iterator(output_format=output, iterator=[
|
|
112
|
-
endpoint.
|
|
112
|
+
endpoint.model_dump(exclude_none=True)
|
|
113
113
|
for endpoint in handler.list_endpoints()
|
|
114
114
|
if not full_type or endpoint.type == full_type
|
|
115
115
|
])
|
|
@@ -282,7 +282,7 @@ def unset_default(context: Optional[str],
|
|
|
282
282
|
class EndpointCommandHandler:
|
|
283
283
|
def __init__(self, context_name: Optional[str] = None):
|
|
284
284
|
self.__logger = get_logger(type(self).__name__)
|
|
285
|
-
self.__schema: Dict[str, Any] = self.__resolve_json_reference(ServiceEndpoint.
|
|
285
|
+
self.__schema: Dict[str, Any] = self.__resolve_json_reference(ServiceEndpoint.model_json_schema())
|
|
286
286
|
self.__config_manager: ConfigurationManager = container.get(ConfigurationManager)
|
|
287
287
|
self.__config = self.__config_manager.load()
|
|
288
288
|
self.__context_name = context_name
|
|
@@ -446,7 +446,7 @@ class EndpointCommandHandler:
|
|
|
446
446
|
raise None
|
|
447
447
|
|
|
448
448
|
def __repair_path(self, obj, path: str, overridden_path_defaults: Dict[str, Any] = None):
|
|
449
|
-
overridden_path_defaults = overridden_path_defaults or
|
|
449
|
+
overridden_path_defaults = overridden_path_defaults or {}
|
|
450
450
|
|
|
451
451
|
selectors = path.split(r'.')
|
|
452
452
|
visited = []
|
|
@@ -498,11 +498,11 @@ class EndpointCommandHandler:
|
|
|
498
498
|
if hasattr(node, property_name) and getattr(node, property_name) is not None:
|
|
499
499
|
return
|
|
500
500
|
elif str(annotation).startswith('typing.Dict['):
|
|
501
|
-
setattr(node, property_name,
|
|
501
|
+
setattr(node, property_name, {})
|
|
502
502
|
elif str(annotation).startswith('typing.List['):
|
|
503
|
-
setattr(node, property_name,
|
|
503
|
+
setattr(node, property_name, [])
|
|
504
504
|
elif issubclass(annotation, BaseModel):
|
|
505
|
-
required_properties = annotation.
|
|
505
|
+
required_properties = annotation.model_json_schema().get('required') or []
|
|
506
506
|
placeholders = {
|
|
507
507
|
p: self.__get_place_holder(annotation.__annotations__[p])
|
|
508
508
|
for p in required_properties
|
|
@@ -522,30 +522,49 @@ class EndpointCommandHandler:
|
|
|
522
522
|
raise NotImplementedError(cls)
|
|
523
523
|
|
|
524
524
|
def __list_all_json_path(self, obj: Dict[str, Any], prefix_path: List[str] = None) -> List[str]:
|
|
525
|
-
properties = obj.get('properties') or
|
|
525
|
+
properties = obj.get('properties') or {}
|
|
526
526
|
paths = []
|
|
527
527
|
|
|
528
|
-
prefix_path = prefix_path or
|
|
528
|
+
prefix_path = prefix_path or []
|
|
529
529
|
|
|
530
530
|
if len(prefix_path) == 1 and prefix_path[0] == 'authentication':
|
|
531
531
|
return [
|
|
532
532
|
f'{prefix_path[0]}.{oauth2_path}'
|
|
533
|
-
for oauth2_path in self.__list_all_json_path(OAuth2Authentication.
|
|
533
|
+
for oauth2_path in self.__list_all_json_path(OAuth2Authentication.model_json_schema())
|
|
534
534
|
]
|
|
535
535
|
else:
|
|
536
536
|
if obj['type'] == 'object':
|
|
537
537
|
for property_name, obj_property in properties.items():
|
|
538
538
|
if 'anyOf' in obj_property:
|
|
539
|
+
# In Pydantic v2, anyOf is used for Optional types
|
|
540
|
+
# Check if there's a non-null type in anyOf
|
|
541
|
+
has_ref_or_object = False
|
|
539
542
|
for property_to_resolve in obj_property['anyOf']:
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
self.
|
|
545
|
-
|
|
546
|
-
|
|
543
|
+
if '$ref' in property_to_resolve:
|
|
544
|
+
has_ref_or_object = True
|
|
545
|
+
paths.extend(
|
|
546
|
+
self.__list_all_json_path(
|
|
547
|
+
self.__fetch_json_reference(
|
|
548
|
+
property_to_resolve['$ref'],
|
|
549
|
+
self.__schema
|
|
550
|
+
),
|
|
551
|
+
prefix_path + [property_name]
|
|
552
|
+
)
|
|
547
553
|
)
|
|
548
|
-
)
|
|
554
|
+
elif property_to_resolve.get('type') == 'object':
|
|
555
|
+
has_ref_or_object = True
|
|
556
|
+
# Handle inlined object schemas
|
|
557
|
+
paths.extend(
|
|
558
|
+
self.__list_all_json_path(
|
|
559
|
+
property_to_resolve,
|
|
560
|
+
prefix_path + [property_name]
|
|
561
|
+
)
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
# If anyOf contains simple types (string, int, etc), treat as simple property
|
|
565
|
+
if not has_ref_or_object:
|
|
566
|
+
prefix_path_string = '.'.join(prefix_path)
|
|
567
|
+
paths.append(f'{prefix_path_string}{"." if prefix_path_string else ""}{property_name}')
|
|
549
568
|
elif obj_property['type'] == 'object':
|
|
550
569
|
paths.extend(
|
|
551
570
|
self.__list_all_json_path(
|
|
@@ -574,7 +593,7 @@ class EndpointCommandHandler:
|
|
|
574
593
|
|
|
575
594
|
def __resolve_json_reference(self, obj: Dict[str, Any], root: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
576
595
|
root = root or obj
|
|
577
|
-
properties = obj.get('properties') or
|
|
596
|
+
properties = obj.get('properties') or {}
|
|
578
597
|
for property_name, obj_property in properties.items():
|
|
579
598
|
if obj_property.get('$ref'):
|
|
580
599
|
properties[property_name] = self.__fetch_json_reference(obj_property.get('$ref'), root)
|
|
@@ -31,7 +31,7 @@ def registry_command_group():
|
|
|
31
31
|
def list_registries():
|
|
32
32
|
""" List registered service registries """
|
|
33
33
|
click.echo(to_json([
|
|
34
|
-
endpoint.
|
|
34
|
+
endpoint.model_dump(exclude_none=True)
|
|
35
35
|
for endpoint in ServiceRegistryCommandHandler().get_registry_endpoint_iterator()
|
|
36
36
|
]))
|
|
37
37
|
|
|
@@ -126,7 +126,7 @@ def sync(registry_endpoint_id: str):
|
|
|
126
126
|
def list_endpoints(registry_endpoint_id: str):
|
|
127
127
|
""" List all service endpoints imported from given registry """
|
|
128
128
|
click.echo(to_json([
|
|
129
|
-
endpoint.
|
|
129
|
+
endpoint.model_dump(exclude_none=True)
|
|
130
130
|
for endpoint in ServiceRegistryCommandHandler().list_endpoints_associated_to(registry_endpoint_id)
|
|
131
131
|
]))
|
|
132
132
|
|
|
@@ -70,5 +70,5 @@ def get_table_info(context: Optional[str],
|
|
|
70
70
|
no_auth: bool = False,
|
|
71
71
|
output: Optional[str] = None):
|
|
72
72
|
""" Get info from the given table """
|
|
73
|
-
obj = _get(context=context, id=endpoint_id).table(table_name, no_auth=no_auth).info.
|
|
73
|
+
obj = _get(context=context, id=endpoint_id).table(table_name, no_auth=no_auth).info.model_dump()
|
|
74
74
|
click.echo((to_json if output == 'json' else to_yaml)(obj))
|
|
@@ -119,7 +119,7 @@ def init_drs_commands(group: Group):
|
|
|
119
119
|
drs.events.on('download-ok', display_ok)
|
|
120
120
|
drs.events.on('download-failure', display_failure)
|
|
121
121
|
|
|
122
|
-
stats: Dict[str, DownloadProgressEvent] =
|
|
122
|
+
stats: Dict[str, DownloadProgressEvent] = {}
|
|
123
123
|
|
|
124
124
|
if not full_output:
|
|
125
125
|
drs._download_files(id_or_urls=download_urls,
|
|
@@ -43,7 +43,7 @@ def init_questions_commands(group: Group):
|
|
|
43
43
|
show_iterator(
|
|
44
44
|
output_format=output,
|
|
45
45
|
iterator=questions,
|
|
46
|
-
transform=lambda q: q.
|
|
46
|
+
transform=lambda q: q.model_dump()
|
|
47
47
|
)
|
|
48
48
|
|
|
49
49
|
@formatted_command(
|
|
@@ -71,7 +71,7 @@ def init_questions_commands(group: Group):
|
|
|
71
71
|
show_iterator(
|
|
72
72
|
output_format=output,
|
|
73
73
|
iterator=[question], # Single item as list
|
|
74
|
-
transform=lambda q: q.
|
|
74
|
+
transform=lambda q: q.model_dump()
|
|
75
75
|
)
|
|
76
76
|
|
|
77
77
|
@formatted_command(
|
|
@@ -94,7 +94,8 @@ def init_questions_commands(group: Group):
|
|
|
94
94
|
ArgumentSpec(
|
|
95
95
|
name='collections',
|
|
96
96
|
arg_names=['--collections'],
|
|
97
|
-
|
|
97
|
+
type=JsonLike,
|
|
98
|
+
help='Comma-separated list of collection IDs to query, or @filename to read from file (default: all collections for the question)'
|
|
98
99
|
),
|
|
99
100
|
ArgumentSpec(
|
|
100
101
|
name='output_file',
|
|
@@ -109,7 +110,7 @@ def init_questions_commands(group: Group):
|
|
|
109
110
|
def ask_question(
|
|
110
111
|
question_name: str,
|
|
111
112
|
args: tuple,
|
|
112
|
-
collections: Optional[
|
|
113
|
+
collections: Optional[JsonLike],
|
|
113
114
|
output_file: Optional[str],
|
|
114
115
|
output: str,
|
|
115
116
|
context: Optional[str],
|
|
@@ -118,9 +119,14 @@ def init_questions_commands(group: Group):
|
|
|
118
119
|
"""Ask a federated question with the provided parameters"""
|
|
119
120
|
trace = Span()
|
|
120
121
|
client = get_explorer_client(context=context, endpoint_id=endpoint_id, trace=trace)
|
|
121
|
-
|
|
122
|
+
|
|
122
123
|
# Parse collections if provided
|
|
123
|
-
|
|
124
|
+
if collections:
|
|
125
|
+
# Handle JsonLike object - get the actual value (handles @ file reading)
|
|
126
|
+
collections_str = collections.value()
|
|
127
|
+
collection_ids = parse_collections_argument(collections_str)
|
|
128
|
+
else:
|
|
129
|
+
collection_ids = None
|
|
124
130
|
|
|
125
131
|
# Parse arguments
|
|
126
132
|
inputs = {}
|
|
@@ -33,19 +33,28 @@ def get_explorer_client(context: Optional[str] = None,
|
|
|
33
33
|
|
|
34
34
|
def parse_collections_argument(collections_str: Optional[str]) -> Optional[List[str]]:
|
|
35
35
|
"""
|
|
36
|
-
Parse a
|
|
37
|
-
|
|
36
|
+
Parse a collections string into a list.
|
|
37
|
+
Handles both comma-separated and newline-separated formats.
|
|
38
|
+
|
|
38
39
|
Args:
|
|
39
|
-
collections_str:
|
|
40
|
-
|
|
40
|
+
collections_str: Collection IDs as either:
|
|
41
|
+
- Comma-separated (e.g., "id1,id2,id3")
|
|
42
|
+
- Newline-separated (one ID per line)
|
|
43
|
+
|
|
41
44
|
Returns:
|
|
42
45
|
List[str] or None: List of collection IDs or None if input is None/empty
|
|
43
46
|
"""
|
|
44
47
|
if not collections_str:
|
|
45
48
|
return None
|
|
46
|
-
|
|
47
|
-
#
|
|
48
|
-
|
|
49
|
+
|
|
50
|
+
# Check if it contains newlines (multiline file format)
|
|
51
|
+
if '\n' in collections_str:
|
|
52
|
+
# Split by newlines and strip whitespace
|
|
53
|
+
collections = [col.strip() for col in collections_str.split('\n')]
|
|
54
|
+
else:
|
|
55
|
+
# Split by comma and strip whitespace
|
|
56
|
+
collections = [col.strip() for col in collections_str.split(',')]
|
|
57
|
+
|
|
49
58
|
# Filter out empty strings
|
|
50
59
|
return [col for col in collections if col]
|
|
51
60
|
|
|
@@ -58,4 +58,4 @@ def get_table_info(context: Optional[str],
|
|
|
58
58
|
""" List all accessible tables """
|
|
59
59
|
client = _switch_to_data_connect(_get_context(context), _get_collection_service_client(context, endpoint_id), collection, no_auth=no_auth)
|
|
60
60
|
obj = client.table(table_name, no_auth=no_auth).info
|
|
61
|
-
click.echo((to_json if output == 'json' else to_yaml)(obj.
|
|
61
|
+
click.echo((to_json if output == 'json' else to_yaml)(obj.model_dump()))
|
|
@@ -111,7 +111,7 @@ def _abort_with_collection_list(collection_service_client: CollectionServiceClie
|
|
|
111
111
|
def _transform_to_public_collection(collection: Collection) -> Dict[str, Any]:
|
|
112
112
|
return {
|
|
113
113
|
field_name: value
|
|
114
|
-
for field_name, value in (collection.
|
|
114
|
+
for field_name, value in (collection.model_dump() if isinstance(collection, Collection) else collection).items()
|
|
115
115
|
if field_name not in ['itemsQuery', 'accessTypeLabels']
|
|
116
116
|
}
|
|
117
117
|
|
|
@@ -89,6 +89,12 @@ def init_runs_commands(group: Group):
|
|
|
89
89
|
arg_names=['--storage-account'],
|
|
90
90
|
help='Filter runs by the storage account ID. This will return runs that have outputs stored in the specified storage account.',
|
|
91
91
|
),
|
|
92
|
+
ArgumentSpec(
|
|
93
|
+
name='show_hidden',
|
|
94
|
+
arg_names=['--show-hidden'],
|
|
95
|
+
help='Include workflow runs with the visibility:hidden tag in the results. By default, hidden runs are excluded.',
|
|
96
|
+
type=bool,
|
|
97
|
+
),
|
|
92
98
|
NAMESPACE_ARG,
|
|
93
99
|
CONTEXT_ARG,
|
|
94
100
|
SINGLE_ENDPOINT_ID_ARG,
|
|
@@ -109,6 +115,7 @@ def init_runs_commands(group: Group):
|
|
|
109
115
|
tags: JsonLike,
|
|
110
116
|
samples: Optional[List[str]] = None,
|
|
111
117
|
storage_account_id: Optional[str] = None,
|
|
118
|
+
show_hidden: Optional[bool] = False,
|
|
112
119
|
states: Optional[List[State]] = None):
|
|
113
120
|
"""
|
|
114
121
|
List workflow runs
|
|
@@ -141,6 +148,7 @@ def init_runs_commands(group: Group):
|
|
|
141
148
|
search=search,
|
|
142
149
|
sample_ids=samples,
|
|
143
150
|
storage_account_id=storage_account_id,
|
|
151
|
+
show_hidden=show_hidden,
|
|
144
152
|
tag=tags
|
|
145
153
|
)
|
|
146
154
|
runs_list = client.list_runs(list_options, max_results)
|
|
@@ -608,7 +616,7 @@ def init_runs_commands(group: Group):
|
|
|
608
616
|
default_workflow_engine_parameters=default_workflow_engine_parameters,
|
|
609
617
|
default_workflow_params=default_workflow_params,
|
|
610
618
|
default_tags=tags.parsed_value() if tags else None,
|
|
611
|
-
run_requests=
|
|
619
|
+
run_requests=[],
|
|
612
620
|
samples=parse_samples()
|
|
613
621
|
)
|
|
614
622
|
|
|
@@ -626,12 +634,12 @@ def init_runs_commands(group: Group):
|
|
|
626
634
|
override_data = parse_and_merge_arguments(input_overrides)
|
|
627
635
|
if override_data:
|
|
628
636
|
if not batch_request.default_workflow_params:
|
|
629
|
-
batch_request.default_workflow_params =
|
|
637
|
+
batch_request.default_workflow_params = {}
|
|
630
638
|
merge(batch_request.default_workflow_params, override_data)
|
|
631
639
|
|
|
632
640
|
for run_request in batch_request.run_requests:
|
|
633
641
|
if not run_request.workflow_params:
|
|
634
|
-
run_request.workflow_params =
|
|
642
|
+
run_request.workflow_params = {}
|
|
635
643
|
merge(run_request.workflow_params, override_data)
|
|
636
644
|
|
|
637
645
|
if dry_run:
|
|
@@ -174,7 +174,6 @@ def init_samples_commands(group: Group):
|
|
|
174
174
|
|
|
175
175
|
if perspective == PerspectiveType.workflow and not workflow_id:
|
|
176
176
|
raise click.UsageError('When perspective is set to "WORKFLOW", the workflow-id is required.')
|
|
177
|
-
|
|
178
177
|
client = get_samples_client(context_name=context, endpoint_id=endpoint_id, namespace=namespace)
|
|
179
178
|
list_options: SampleListOptions = SampleListOptions(
|
|
180
179
|
page=page,
|
|
@@ -186,7 +185,7 @@ def init_samples_commands(group: Group):
|
|
|
186
185
|
workflow_version_id=workflow_version_id,
|
|
187
186
|
states=states,
|
|
188
187
|
family_id=family_ids,
|
|
189
|
-
|
|
188
|
+
id=sample_ids,
|
|
190
189
|
sexes=sexes,
|
|
191
190
|
perspective=perspective,
|
|
192
191
|
search=search,
|
|
@@ -151,7 +151,7 @@ def create(context: Optional[str] = None,
|
|
|
151
151
|
admin_only_action=global_action
|
|
152
152
|
)
|
|
153
153
|
|
|
154
|
-
click.echo(json.dumps(dependency.
|
|
154
|
+
click.echo(json.dumps(dependency.model_dump(), indent=2))
|
|
155
155
|
|
|
156
156
|
|
|
157
157
|
@formatted_command(
|
|
@@ -249,7 +249,7 @@ def describe(context: Optional[str] = None,
|
|
|
249
249
|
workflow_version_id=version_id,
|
|
250
250
|
dependency_id=dependency_id
|
|
251
251
|
)
|
|
252
|
-
dependencies.append(dependency.
|
|
252
|
+
dependencies.append(dependency.model_dump())
|
|
253
253
|
except Exception as e:
|
|
254
254
|
logger.error(f"Failed to get dependency {dependency_id}: {e}")
|
|
255
255
|
raise click.ClickException(f"Failed to get dependency {dependency_id}: {e}")
|
|
@@ -318,7 +318,7 @@ def update(context: Optional[str] = None,
|
|
|
318
318
|
admin_only_action=global_action
|
|
319
319
|
)
|
|
320
320
|
|
|
321
|
-
click.echo(json.dumps(dependency.
|
|
321
|
+
click.echo(json.dumps(dependency.model_dump(), indent=2))
|
|
322
322
|
|
|
323
323
|
except Exception as e:
|
|
324
324
|
logger.error(f"Failed to update workflow dependency: {e}")
|
|
@@ -4,7 +4,7 @@ import click
|
|
|
4
4
|
from click import style
|
|
5
5
|
|
|
6
6
|
from dnastack.cli.commands.workbench.utils import NAMESPACE_ARG
|
|
7
|
-
from dnastack.cli.commands.workbench.workflows.utils import get_workflow_client
|
|
7
|
+
from dnastack.cli.commands.workbench.workflows.utils import get_workflow_client
|
|
8
8
|
from dnastack.cli.core.command import formatted_command
|
|
9
9
|
from dnastack.cli.core.command_spec import ArgumentSpec, ArgumentType, CONTEXT_ARG, SINGLE_ENDPOINT_ID_ARG
|
|
10
10
|
from dnastack.cli.core.group import formatted_group
|
|
@@ -12,6 +12,7 @@ from dnastack.cli.helpers.exporter import to_json, normalize
|
|
|
12
12
|
from dnastack.cli.helpers.iterator_printer import show_iterator, OutputFormat
|
|
13
13
|
from dnastack.client.workbench.workflow.models import WorkflowTransformationListOptions, \
|
|
14
14
|
WorkflowTransformationCreate
|
|
15
|
+
from dnastack.common.json_argument_parser import FileOrValue
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
@formatted_group('transformations')
|
|
@@ -173,6 +174,7 @@ def delete_workflow_transformation(context: Optional[str],
|
|
|
173
174
|
' You do not need to return the entire context object, only the parts that you want to modify. '
|
|
174
175
|
'Results of transformations are merged into the final context object. '
|
|
175
176
|
'The context object is a JSON object that contains the workflow parameters and other metadata.',
|
|
177
|
+
type=FileOrValue,
|
|
176
178
|
required=True,
|
|
177
179
|
),
|
|
178
180
|
ArgumentSpec(
|
|
@@ -214,23 +216,17 @@ def add_workflow_transformation(context: Optional[str],
|
|
|
214
216
|
namespace: Optional[str],
|
|
215
217
|
workflow_id: str,
|
|
216
218
|
version_id: str,
|
|
217
|
-
script:
|
|
219
|
+
script: FileOrValue,
|
|
218
220
|
transformation_id: Optional[str],
|
|
219
221
|
next_transformation_id: Optional[str],
|
|
220
222
|
labels: List[str]):
|
|
221
223
|
"""Create a new workflow transformation"""
|
|
222
224
|
client = get_workflow_client(context_name=context, endpoint_id=endpoint_id, namespace=namespace)
|
|
223
225
|
|
|
224
|
-
if script.startswith("@"):
|
|
225
|
-
extractor = JavaScriptFunctionExtractor(script.split("@")[1])
|
|
226
|
-
script_content = extractor.extract_first_function()
|
|
227
|
-
else:
|
|
228
|
-
script_content = script
|
|
229
|
-
|
|
230
226
|
workflow_transformation = WorkflowTransformationCreate(
|
|
231
227
|
id=transformation_id,
|
|
232
228
|
next_transformation_id=next_transformation_id,
|
|
233
|
-
script=
|
|
229
|
+
script=script.value(),
|
|
234
230
|
labels=labels
|
|
235
231
|
)
|
|
236
232
|
|
dnastack/cli/helpers/exporter.py
CHANGED
|
@@ -24,7 +24,7 @@ def normalize(content: Any, map_decimal: Type = str, sort_keys: bool = True) ->
|
|
|
24
24
|
if isinstance(content, Decimal):
|
|
25
25
|
return map_decimal(content)
|
|
26
26
|
elif isinstance(content, BaseModel):
|
|
27
|
-
return normalize(content.
|
|
27
|
+
return normalize(content.model_dump(), map_decimal=map_decimal)
|
|
28
28
|
elif isinstance(content, dict):
|
|
29
29
|
# Handle a dictionary
|
|
30
30
|
DEFAULT_WEIGHT = 99999
|
|
@@ -60,7 +60,7 @@ class DataConnectError(RuntimeError):
|
|
|
60
60
|
details: Any = None,
|
|
61
61
|
urls: Optional[List[str]] = None,
|
|
62
62
|
url: Optional[str] = None):
|
|
63
|
-
self.__urls =
|
|
63
|
+
self.__urls = []
|
|
64
64
|
self.__status = response_status
|
|
65
65
|
self.__summary = summary or details
|
|
66
66
|
self.__details = details if self.__summary != details else None
|
|
@@ -99,4 +99,4 @@ class DataConnectError(RuntimeError):
|
|
|
99
99
|
for url in self.__urls:
|
|
100
100
|
blocks.append(f' → {url}')
|
|
101
101
|
|
|
102
|
-
return '\n'.join(blocks)
|
|
102
|
+
return '\n'.join(blocks)
|