241 lines
9.7 KiB
Python
241 lines
9.7 KiB
Python
import json
|
|
import os
|
|
import shutil
|
|
from typing import List
|
|
|
|
from crc.api.common import ApiError
|
|
from crc.models.workflow import WorkflowSpecCategory, WorkflowSpecCategorySchema, WorkflowSpecInfo, \
|
|
WorkflowSpecInfoSchema
|
|
from crc.services.file_system_service import FileSystemService
|
|
|
|
|
|
class WorkflowSpecService(FileSystemService):
|
|
|
|
"""This is a way of persisting json files to the file system in a way that mimics the data
|
|
as it would have been stored in the database. This is specific to Workflow Specifications, and
|
|
Workflow Specification categories.
|
|
We do this, so we can easily drop in a new configuration on the file system, and change all
|
|
the workflow specs at once, or manage those file in a git repository. """
|
|
|
|
CAT_SCHEMA = WorkflowSpecCategorySchema()
|
|
WF_SCHEMA = WorkflowSpecInfoSchema()
|
|
|
|
def add_spec(self, spec: WorkflowSpecInfo):
|
|
display_order = self.next_display_order(spec)
|
|
spec.display_order = display_order
|
|
self.update_spec(spec)
|
|
|
|
def update_spec(self, spec:WorkflowSpecInfo):
|
|
spec_path = self.workflow_path(spec)
|
|
if spec.is_master_spec or spec.library or spec.standalone:
|
|
spec.category_id = ""
|
|
os.makedirs(spec_path, exist_ok=True)
|
|
json_path = os.path.join(spec_path, self.WF_JSON_FILE)
|
|
with open(json_path, "w") as wf_json:
|
|
json.dump(self.WF_SCHEMA.dump(spec), wf_json, indent=4)
|
|
|
|
def delete_spec(self, spec_id: str):
|
|
spec = self.get_spec(spec_id)
|
|
if not spec:
|
|
return
|
|
if spec.library:
|
|
self.__remove_library_references(spec.id)
|
|
path = self.workflow_path(spec)
|
|
shutil.rmtree(path)
|
|
|
|
def __remove_library_references(self, spec_id):
|
|
for spec in self.get_specs():
|
|
if spec_id in spec.libraries:
|
|
spec.libraries.remove(spec_id)
|
|
self.update_spec(spec)
|
|
|
|
@property
|
|
def master_spec(self):
|
|
return self.get_master_spec()
|
|
|
|
def get_master_spec(self):
|
|
path = os.path.join(FileSystemService.root_path(), FileSystemService.MASTER_SPECIFICATION)
|
|
if os.path.exists(path):
|
|
return self.__scan_spec(path, FileSystemService.MASTER_SPECIFICATION)
|
|
|
|
def get_spec(self, spec_id):
|
|
if not os.path.exists(FileSystemService.root_path()):
|
|
return # Nothing to scan yet. There are no files.
|
|
|
|
master_spec = self.get_master_spec()
|
|
if master_spec and master_spec.id == spec_id:
|
|
return master_spec
|
|
with os.scandir(FileSystemService.root_path()) as category_dirs:
|
|
for item in category_dirs:
|
|
category_dir = item
|
|
if item.is_dir():
|
|
with os.scandir(item.path) as spec_dirs:
|
|
for sd in spec_dirs:
|
|
if sd.name == spec_id:
|
|
# Now we have the category direcotry, and spec directory
|
|
category = self.__scan_category(category_dir)
|
|
return self.__scan_spec(sd.path, sd.name, category)
|
|
|
|
def get_specs(self):
|
|
categories = self.get_categories()
|
|
specs = []
|
|
for cat in categories:
|
|
specs.extend(cat.specs)
|
|
return specs
|
|
|
|
def reorder_spec(self, spec:WorkflowSpecInfo, direction):
|
|
specs = spec.category.specs
|
|
specs.sort(key=lambda w: w.display_order)
|
|
index = specs.index(spec)
|
|
if direction == 'up' and index > 0:
|
|
specs[index-1], specs[index] = specs[index], specs[index-1]
|
|
if direction == 'down' and index < len(specs)-1:
|
|
specs[index+1], specs[index] = specs[index], specs[index+1]
|
|
return self.cleanup_workflow_spec_display_order(spec.category)
|
|
|
|
def cleanup_workflow_spec_display_order(self, category):
|
|
index = 0
|
|
if not category:
|
|
return []
|
|
for workflow in category.specs:
|
|
workflow.display_order = index
|
|
self.update_spec(workflow)
|
|
index += 1
|
|
return category.specs
|
|
|
|
def get_categories(self) -> List[WorkflowSpecCategory]:
|
|
"""Returns the categories as a list in display order"""
|
|
cat_list = self.__scan_categories()
|
|
cat_list.sort(key=lambda w: w.display_order)
|
|
return cat_list
|
|
|
|
def get_libraries(self) -> List[WorkflowSpecInfo]:
|
|
cat = self.get_category(self.LIBRARY_SPECS)
|
|
if not cat:
|
|
return []
|
|
return cat.specs
|
|
|
|
def get_standalones(self) -> List[WorkflowSpecInfo]:
|
|
cat = self.get_category(self.STAND_ALONE_SPECS)
|
|
if not cat:
|
|
return []
|
|
return cat.specs
|
|
|
|
def get_category(self, category_id):
|
|
"""Look for a given category, and return it."""
|
|
if not os.path.exists(FileSystemService.root_path()):
|
|
return # Nothing to scan yet. There are no files.
|
|
with os.scandir(FileSystemService.root_path()) as directory_items:
|
|
for item in directory_items:
|
|
if item.is_dir() and item.name == category_id:
|
|
return self.__scan_category(item)
|
|
|
|
def add_category(self, category: WorkflowSpecCategory):
|
|
display_order = len(self.get_categories())
|
|
category.display_order = display_order
|
|
return self.update_category(category)
|
|
|
|
def update_category(self, category: WorkflowSpecCategory):
|
|
cat_path = self.category_path(category.id)
|
|
os.makedirs(cat_path, exist_ok=True)
|
|
json_path = os.path.join(cat_path, self.CAT_JSON_FILE)
|
|
with open(json_path, "w") as cat_json:
|
|
json.dump(self.CAT_SCHEMA.dump(category), cat_json, indent=4)
|
|
return category
|
|
|
|
def delete_category(self, category_id: str):
|
|
path = self.category_path(category_id)
|
|
if os.path.exists(path):
|
|
shutil.rmtree(path)
|
|
self.cleanup_category_display_order()
|
|
|
|
def reorder_workflow_spec_category(self, cat: WorkflowSpecCategory, direction):
|
|
cats = self.get_categories() # Returns an ordered list
|
|
index = cats.index(cat)
|
|
if direction == 'up' and index > 0:
|
|
cats[index-1], cats[index] = cats[index], cats[index-1]
|
|
if direction == 'down' and index < len(cats)-1:
|
|
cats[index+1], cats[index] = cats[index], cats[index+1]
|
|
index = 0
|
|
for category in cats:
|
|
category.display_order = index
|
|
self.update_category(category)
|
|
index += 1
|
|
return cats
|
|
|
|
def cleanup_category_display_order(self):
|
|
cats = self.get_categories() # Returns an ordered list
|
|
index = 0
|
|
for category in cats:
|
|
category.display_order = index
|
|
self.update_category(category)
|
|
index += 1
|
|
return cats
|
|
|
|
def __scan_categories(self):
|
|
if not os.path.exists(FileSystemService.root_path()):
|
|
return [] # Nothing to scan yet. There are no files.
|
|
|
|
with os.scandir(FileSystemService.root_path()) as directory_items:
|
|
categories = []
|
|
for item in directory_items:
|
|
if item.is_dir() and not item.name[0] == '.':
|
|
if item.name == self.REFERENCE_FILES:
|
|
continue
|
|
elif item.name == self.MASTER_SPECIFICATION:
|
|
continue
|
|
elif item.name == self.LIBRARY_SPECS:
|
|
continue
|
|
elif item.name == self.STAND_ALONE_SPECS:
|
|
continue
|
|
categories.append(self.__scan_category(item))
|
|
return categories
|
|
|
|
def __scan_category(self, dir_item: os.DirEntry):
|
|
"""Reads the category.json file, and any workflow directories """
|
|
cat_path = os.path.join(dir_item.path, self.CAT_JSON_FILE)
|
|
if os.path.exists(cat_path):
|
|
with open(cat_path) as cat_json:
|
|
data = json.load(cat_json)
|
|
cat = self.CAT_SCHEMA.load(data)
|
|
else:
|
|
cat = WorkflowSpecCategory(id=dir_item.name, display_name=dir_item.name, display_order=10000, admin=False)
|
|
with open(cat_path, "w") as wf_json:
|
|
json.dump(self.CAT_SCHEMA.dump(cat), wf_json, indent=4)
|
|
with os.scandir(dir_item.path) as workflow_dirs:
|
|
cat.specs = []
|
|
for item in workflow_dirs:
|
|
if item.is_dir():
|
|
cat.specs.append(self.__scan_spec(item.path, item.name, category=cat))
|
|
cat.specs.sort(key=lambda w: w.display_order)
|
|
return cat
|
|
|
|
@staticmethod
|
|
def _get_workflow_metas(study_id):
|
|
# Add in the Workflows for each category
|
|
# Fixme: moved fro the Study Service
|
|
workflow_metas = []
|
|
# for workflow in workflow_models:
|
|
# workflow_metas.append(WorkflowMetadata.from_workflow(workflow))
|
|
return workflow_metas
|
|
|
|
def __scan_spec(self, path, name, category=None):
|
|
spec_path = os.path.join(path, self.WF_JSON_FILE)
|
|
is_master = FileSystemService.MASTER_SPECIFICATION in spec_path
|
|
|
|
if os.path.exists(spec_path):
|
|
with open(spec_path) as wf_json:
|
|
data = json.load(wf_json)
|
|
spec = self.WF_SCHEMA.load(data)
|
|
else:
|
|
spec = WorkflowSpecInfo(id=name, library=False, standalone=False, is_master_spec=is_master,
|
|
display_name=name, description="", primary_process_id="",
|
|
primary_file_name="", display_order=0, is_review=False,
|
|
libraries=[])
|
|
with open(spec_path, "w") as wf_json:
|
|
json.dump(self.WF_SCHEMA.dump(spec), wf_json, indent=4)
|
|
if category:
|
|
spec.category = category
|
|
spec.category_id = category.id
|
|
return spec
|