Skip to content
This repository was archived by the owner on Sep 9, 2024. It is now read-only.

Commit 05e9f38

Browse files
authored
Merge pull request #35 from unity-sds/mliukis_hosted_workflow
mliukis hosted workflow updates
2 parents e517a38 + efb8bec commit 05e9f38

File tree

2 files changed

+83
-39
lines changed

2 files changed

+83
-39
lines changed

.github/workflows/python-app.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
python-version: [ "3.9", "3.10" ]
1919
poetry-version: [ "1.1.14" ]
2020
# os: [ ubuntu-18.04, macos-latest, windows-latest ]
21-
os: [ ubuntu-18.04, macos-latest ]
21+
os: [ ubuntu-22.04, macos-latest ]
2222
runs-on: ${{ matrix.os }}
2323
steps:
2424
- uses: actions/checkout@v2

unity_py/services/application_service.py

Lines changed: 82 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ class ApplicationPackage(object):
6767

6868
# Optional
6969
source_repository: str = None
70-
workflow_path: str = 'Dockstore.cwl' # Dockstore hard-codes the primary descriptor path
70+
# Dockstore hard-codes the primary descriptor path for the hosted workflow
71+
workflow_path: str = 'Dockstore.cwl'
7172
id: str = None # Not yet commited to catalog
7273
is_published: bool = False
7374
description: str = ""
@@ -209,7 +210,8 @@ def _patch(self, request_url, data):
209210

210211
response = requests.patch(f"{self.api_url}/{request_url}", headers=self._headers, data=json.dumps(data))
211212

212-
if response.status_code != 200:
213+
# 204 indicates that no action was taken
214+
if response.status_code != 200 and response.status_code != 204:
213215
raise ApplicationCatalogAccessError(f"PATCH operation to application catalog at {self.api_url}/{request_url} return unexpected status code: {response.status_code} with message: {response.content} using data: {data}")
214216

215217
return response
@@ -251,38 +253,45 @@ def _application_from_json(self, json_dict):
251253
# after registry path
252254
name = json_dict['full_workflow_path'].split("/")[-1]
253255

254-
return DockstoreApplicationPackage(id=str(json_dict['id']),
255-
name=name,
256-
source_repository=json_dict['gitUrl'],
257-
workflow_path=json_dict['workflow_path'],
258-
is_published=json_dict['is_published'],
259-
description=json_dict['description'],
260-
dockstore_info=json_dict)
256+
return DockstoreApplicationPackage(
257+
id=str(json_dict['id']),
258+
name=name,
259+
source_repository=json_dict['gitUrl'],
260+
workflow_path=json_dict['workflow_path'],
261+
is_published=json_dict['is_published'],
262+
description=json_dict['description'],
263+
dockstore_info=json_dict
264+
)
261265

262266
@staticmethod
263267
def _file_to_json(file_path: str, dockstore_path: str, file_format: str):
264268
"""
265269
Generate JSON format of the file representation for the Dockstore request.
266270
267271
file_path: Path to the file to create JSON format request representation for.
272+
If None or empty filepath is provided, then "dockstore_path" file will be
273+
removed from the hosted workflow.
268274
dockstore_path: Path to the file in the Dockstore.
269275
file_format: Dockstore file type for the file.
270276
"""
271-
# Read contents of the local file
272-
with open(file_path, 'r') as fhandle:
273-
data = fhandle.read()
277+
# Dockstore requires absolute path for the file to be uploaded
278+
dockstore_file_path = f'/{dockstore_path}' if dockstore_path[0] != '/' else dockstore_path
274279

275-
# Dockstore requires absolute path for the file to be uploaded
276-
dockstore_file_path = f'/{dockstore_path}' if dockstore_path[0] != '/' else dockstore_path
280+
# Content of the file: None means to delete the file from the hosted workflow
281+
data = None
282+
if file_path is not None and len(file_path):
283+
# Read contents of the local file
284+
with open(file_path, 'r') as fhandle:
285+
data = fhandle.read()
277286

278-
return {
279-
'path': dockstore_file_path,
280-
'absolutePath': dockstore_file_path,
281-
'content': data,
282-
'type': file_format
283-
}
287+
return {
288+
'path': dockstore_file_path,
289+
'absolutePath': dockstore_file_path,
290+
'content': data,
291+
'type': file_format
292+
}
284293

285-
def application(self, app_id):
294+
def application(self, app_id: int):
286295
"""
287296
Get application information from the Dockstore based on the application ID.
288297
"""
@@ -296,12 +305,11 @@ def application_list(self, for_user: bool = False, published: bool = None):
296305
297306
Unpublished applications can only be seen when using for_user=True
298307
"""
308+
request_url = "/workflows/published"
309+
299310
if for_user or (published is not None and not published):
300311
request_url = f"/users/{self._user_id}/workflows"
301312

302-
else:
303-
request_url = "/workflows/published"
304-
305313
app_list = []
306314
for app_info in self._get(request_url).json():
307315
app_obj = None
@@ -365,8 +373,52 @@ def register(
365373

366374
response = self._post(request_url, params)
367375

376+
new_app = self._application_from_json(response.json())
377+
368378
# Dockstore ID of newly registered application
369-
new_app_id = response.json()['id']
379+
# new_app_id = response.json()['id']
380+
new_app_id = new_app.id
381+
382+
self.upload_files(new_app, cwl_files, json_files, filename_map)
383+
384+
# Optionally publish workflow: Dockstore allows to publish only workflows that have parameter files uploaded
385+
if publish:
386+
if len(cwl_files) or len(json_files):
387+
self._publish(new_app_id, publish)
388+
389+
else:
390+
raise HostedWorkflowError('Can not publish hosted workflow (id={new_app_id}) as no parameter files have been uploaded')
391+
392+
# Reload application information from the Dockstore
393+
return self.application(new_app_id)
394+
395+
def upload_files(
396+
self,
397+
application,
398+
cwl_files: list = [],
399+
json_files: list = [],
400+
filename_map: dict = {}
401+
):
402+
"""
403+
Upload workflow parameter files for the workflow.
404+
405+
Basename of each parameter file will be used as absolute path of the file within the Dockstore. If other
406+
than basename path is preferred to store the file in the Dockstore, "filename_map" input argument should
407+
be used to provide mapping of local file vs. preferred path of the file in the Dockstore. For example:
408+
{
409+
'local_path/step_one.cwl': 'l1/step_one.cwl',
410+
'local_path/params_one.json': 'l1_params/params_one.json'
411+
}
412+
413+
Inputs:
414+
application: DockstoreApplicationPackage to upload the files for.
415+
cwl_files: List of CWL format parameter file paths to upload to the Dockstore. Default is an empty list.
416+
json_files: List of JSON format parameter file paths to upload to the Dockstore. Default is an empty list.
417+
filename_map: Mapping of parameter filenames on local file system vs. filename path as to appear in
418+
the Dockstore once the file is uploaded. Default is an empty map meaning that each file will be uploaded into
419+
the Dockstore using its basename.
420+
"""
421+
app_type = application.workflow_type
370422

371423
if len(cwl_files) or len(json_files):
372424
# Format contents of the parameter files for the request to upload all CWL and JSON files if any
@@ -394,20 +446,12 @@ def register(
394446
)
395447
)
396448

397-
request_url = f"/workflows/hostedEntry/{new_app_id}"
449+
request_url = f"/workflows/hostedEntry/{application.id}"
398450

399451
# Upload the files
400452
self._patch(request_url, params)
401453

402-
# Optionally publish workflow: Dockstore allows to publish only workflows that have parameter files uploaded
403-
if publish:
404-
if len(cwl_files) or len(json_files):
405-
self._publish(new_app_id, publish)
406-
407-
else:
408-
raise HostedWorkflowError('Can not publish hosted workflow (id={new_app_id}) as no parameter files have been uploaded')
409-
410-
return self.application(new_app_id)
454+
return
411455

412456
def upload_parameter_file(self, application, param_filename: str, dockstore_filename: str = ''):
413457
"""
@@ -436,7 +480,7 @@ def upload_json_file(self, application, param_filename: str, dockstore_filename:
436480
"""
437481
Upload local JSON file "param_filename" to the hosted by Dockstore workflow.
438482
439-
If "dockstore_filename" is an empty string than "param_filename" is uploaded into
483+
If "dockstore_filename" is an empty string then "param_filename" is uploaded into
440484
the Dockstore using its basename.
441485
442486
To remove the file from the registered application just upload file of an empy content to the Dockstore.
@@ -457,13 +501,13 @@ def upload_json_file(self, application, param_filename: str, dockstore_filename:
457501

458502
def publish(self, application):
459503
"""
460-
Publish the worksflow.
504+
Publish the workflow.
461505
"""
462506
self._publish(application.id, publish=True)
463507

464508
def unpublish(self, application):
465509
"""
466-
Unpublish the worksflow.
510+
Unpublish the workflow.
467511
468512
Dockstore does not allow to delete a hosted workflow, so we can only remove/add parameter files and
469513
publish/unpublish hosted workflows within Dockstore.

0 commit comments

Comments
 (0)