Skip to content
Snippets Groups Projects
Commit 423017a6 authored by Nathan Hertz's avatar Nathan Hertz
Browse files

Refactored CapabilityStep.from_str() to allow for multiple step args of

the format [arg1, arg2, -a arg3]
parent 904c456a
No related branches found
No related tags found
No related merge requests found
Pipeline #442 passed
......@@ -14,6 +14,7 @@ class CapabilityStepType(Enum):
AwaitProduct = 3
AwaitParameter = 4
AwaitLargeAllocApproval = 5
Invalid = -1
@classmethod
def from_string(cls, string: str) -> CapabilityStepType:
......@@ -31,7 +32,7 @@ class CapabilityStepType(Enum):
"await-parameter": cls.AwaitParameter,
"await-large-alloc-approval": cls.AwaitLargeAllocApproval,
}
return strings[string]
return strings.get(string, cls.Invalid)
class CapabilityEventType(Enum):
......
from __future__ import annotations
import json
import re
from typing import Iterator, List, Optional
from workspaces.capability.enums import CapabilityStepType
......@@ -13,6 +14,10 @@ from workspaces.capability.schema_interfaces import CapabilityExecutionIF
from workspaces.capability.services.interfaces import CapabilityEngineIF
class MalformedCapabilityStep(ValueError):
pass
class CapabilityStep(CapabilityStepIF):
"""
Class that represents a step in a capability sequence. A step is of a certain type and
......@@ -31,25 +36,63 @@ class CapabilityStep(CapabilityStepIF):
self.step_value = step_value
self.step_args = step_args
@staticmethod
def parse_args(args_string: str) -> Optional[List[str]]:
"""
Parse a string of the form [arg-1, arg_2, -a 3], etc. into a list of those args
:param args_string: Args as a string
:return: List of given args or None if no args given
"""
args_string = args_string.replace("[", "").replace("]", "")
return args_string.split(", ") if args_string else None
@classmethod
def from_str(cls, step_string: str):
def from_str(cls, step_string: str) -> CapabilityStep:
"""
Create CapabilityStep from string containing space-separated type, value, and args
:param step_string: String of capability step, e.g. "PrepareAndRunWorkflow null"
:return: CapabilityStep of given string
"""
step_list = step_string.split(" ")
step_type = CapabilityStepType.from_string(step_list[0])
if step_type is CapabilityStepType.PrepareAndRunWorkflow:
step_value = step_list[1]
step_args = step_list[2] if len(step_list) == 3 else None
# === Regexes ===
# Matches a string with letters, digits, underscores, and hyphens, labeled steptype
# Ex: prepare-and-run-workflow, await-qa
r_step_type = r"(?P<steptype>[\w-]+)"
# Matches a string with letters, digits, underscores, and these specials: [./:], labeled stepval
# Ex: cal://alma/..., workflow_name, null
r_step_val = r"(?P<stepval>[\w\-./:]+)"
# Matches a string surrounded by [], with 0 or more of the same string as above (plus spaces), separated by
# ", " inside them
# Ex: [-f, -l path/to/location, arg_value], [arg], []
r_step_args = r"(?P<stepargs>\[(?:[\w\-./: ]+(?:, )?)*\])"
# Matches a string taking the form of a full capability step
# Ex: prepare-and-run-workflow null [-g], prepare-and-run-workflow download, await-qa
r_step = rf"^{r_step_type} ?{r_step_val}? ?{r_step_args}?$"
if match := re.search(r_step, step_string):
groups = match.groupdict()
step_type = groups.get("steptype", None)
step_value = groups.get("stepval", None)
step_args = groups.get("stepargs", None)
if step_type:
# Translate step_type from str to CapabilityStepType
step_type = CapabilityStepType.from_string(step_type)
if step_type is CapabilityStepType.Invalid:
raise MalformedCapabilityStep(f"Step type {step_type} invalid.")
else:
raise MalformedCapabilityStep(
f"Capability step {step_string} is malformed. Step type not found when it is required."
)
if step_args:
step_args = cls.parse_args(step_args)
return cls.TYPES[step_type](step_type, step_value, step_args)
else:
# Any other step type
step_value = step_list[1] if len(step_list) == 2 else None
step_args = None
return cls.TYPES[step_type](step_type, step_value, step_args)
# Step string not well-formatted
raise MalformedCapabilityStep(
f"Capability step {step_string} is malformed."
)
def __str__(self):
string = f"{self.step_type.name}"
......@@ -122,7 +165,9 @@ class PrepareAndRunWorkflow(CapabilityStep):
# DO NOT TAKE THIS OUT! Python will yell at you.
parameters = json.dumps(parameters)
workflow_args = json.loads(parameters)
engine.submit_workflow_request(execution.id, workflow_name, workflow_args, files)
engine.submit_workflow_request(
execution.id, workflow_name, workflow_args, files
)
class AwaitQa(CapabilityStep):
......
import pytest
from workspaces.capability.enums import CapabilityStepType
from workspaces.capability.helpers import CapabilityStep, MalformedCapabilityStep
def test_capability_step_from_str():
"""
Tests that a capability step can be correctly parsed from a string
"""
step_with_val_and_args = "prepare-and-run-workflow deliver [-r, -l ., shared/workspaces/test/test_data/spool/724126739/17A-109.sb33151331.eb33786546.57892.65940042824]"
step_with_val_and_one_arg = "prepare-and-run-workflow null [-g]"
step_with_val_and_empty_args = "prepare-and-run-workflow workflow []"
step_with_val = "prepare-and-run-workflow name_of_workflow"
step_only_type = "await-qa"
# Step of the form "step-type step-value [step_arg1, ...]"
step_with_val_and_args = CapabilityStep.from_str(step_with_val_and_args)
assert step_with_val_and_args.step_type is CapabilityStepType.PrepareAndRunWorkflow
assert step_with_val_and_args.step_value == "deliver"
assert step_with_val_and_args.step_args == [
"-r",
"-l .",
"shared/workspaces/test/test_data/spool/724126739/17A-109.sb33151331.eb33786546.57892.65940042824",
]
# Step of the form "step-type step-value [step_arg]"
step_with_val_and_one_arg = CapabilityStep.from_str(step_with_val_and_one_arg)
assert (
step_with_val_and_one_arg.step_type is CapabilityStepType.PrepareAndRunWorkflow
)
assert step_with_val_and_one_arg.step_value == "null"
assert step_with_val_and_one_arg.step_args == ["-g"]
# Step of the form "step-type step-value []"
step_with_val_and_empty_args = CapabilityStep.from_str(step_with_val_and_empty_args)
assert (
step_with_val_and_empty_args.step_type
is CapabilityStepType.PrepareAndRunWorkflow
)
assert step_with_val_and_empty_args.step_value == "workflow"
assert step_with_val_and_empty_args.step_args is None
# Step of the form "step-type step-value"
step_with_val = CapabilityStep.from_str(step_with_val)
assert step_with_val.step_type is CapabilityStepType.PrepareAndRunWorkflow
assert step_with_val.step_value == "name_of_workflow"
assert step_with_val.step_args is None
# Step of the form "step-type"
step_only_type = CapabilityStep.from_str(step_only_type)
assert step_only_type.step_type is CapabilityStepType.AwaitQA
assert step_only_type.step_value is None
assert step_only_type.step_args is None
def test_capability_step_from_str_error():
"""
Tests that an invalid capability step strings will correctly throw an error
"""
step_empty = ""
step_bad_format = "!step.type step$value arg1 arg2 arg3"
step_invalid_type = "invalid-step-type step-value [arg1, arg2]"
with pytest.raises(MalformedCapabilityStep):
CapabilityStep.from_str(step_empty)
CapabilityStep.from_str(step_bad_format)
CapabilityStep.from_str(step_invalid_type)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment