Skip to content

Commit 39cfcf7

Browse files
feature(EXEC-892): add session schedule_at to the exec command (#196)
Summary: Use the exec command to test the session in display only mcx and absolute time constraints. Associated lab PR: strateos/lab#1237 If the user uses the exec command, upon success, they will receive a session id. Then can then reuse this session id, to interleave another run on the created worldstate. The user has also access to the flag --schedule-delay to provide a relative starting time in minutes, or --schedule-at to provide an absolute starting time ("14:13" meaning today at 2:13pm, "2021-02-20T12:34" meaning at that exact time). Test Plan: Add unit testing for the new flags. Manual local testing and on the test workcell in production
1 parent 526e532 commit 39cfcf7

4 files changed

Lines changed: 134 additions & 15 deletions

File tree

CHANGELOG.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
Changelog
22
=========
33

4+
Unreleased
5+
----------
6+
7+
Added
8+
~~~~~
9+
10+
Fixed
11+
~~~~~
12+
13+
Updated
14+
~~~~~~~
15+
16+
- Added support for sessions and absolute time constraint in `exec` CLI command.
17+
Added "--sessionId", "--schedule-at", and "--schedule-delay" flags.
18+
419

520
v9.2.0
621
----------

test/commands/exec_test.py

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99

1010

1111
# Structure of the response object from SCLE
12-
def bool_success_res():
13-
return {"success": True}
12+
def queue_test_success_res(sessionId="testSessionId"):
13+
return {"success": True, "sessionId": sessionId}
1414

1515

1616
def mock_api_endpoint():
@@ -34,15 +34,17 @@ def test_unspecified_api(cli_test_runner, ap_file):
3434

3535
def test_good_autoprotocol(cli_test_runner, monkeypatch, ap_file):
3636
def mockpost(*args, **kwargs):
37-
return MockResponse(0, bool_success_res(), json.dumps(bool_success_res()))
37+
return MockResponse(
38+
0, queue_test_success_res(), json.dumps(queue_test_success_res())
39+
)
3840

3941
monkeypatch.setattr(requests, "post", mockpost)
4042
result = cli_test_runner.invoke(
4143
cli, ["exec", str(ap_file), "-a", mock_api_endpoint()]
4244
)
4345
assert result.exit_code == 0
4446
assert (
45-
f"Success. View {mock_api_endpoint()} to see the scheduling outcome."
47+
f"Success. View {mock_api_endpoint()}/dashboard?sessionId=testSessionId to see the scheduling outcome."
4648
in result.output
4749
)
4850

@@ -88,15 +90,17 @@ def mockpost(*args, **kwargs):
8890

8991
def test_good_workcell(cli_test_runner, monkeypatch, ap_file):
9092
def mockpost(*args, **kwargs):
91-
return MockResponse(0, bool_success_res(), json.dumps(bool_success_res()))
93+
return MockResponse(
94+
0, queue_test_success_res(), json.dumps(queue_test_success_res())
95+
)
9296

9397
monkeypatch.setattr(requests, "post", mockpost)
9498
result = cli_test_runner.invoke(
9599
cli, ["exec", str(ap_file), "-a", mock_api_endpoint(), "-w", "wc3"]
96100
)
97101
assert result.exit_code == 0
98102
assert (
99-
f"Success. View {mock_api_endpoint()} to see the scheduling outcome."
103+
f"Success. View {mock_api_endpoint()}/dashboard?sessionId=testSessionId to see the scheduling outcome."
100104
in result.output
101105
)
102106

@@ -107,3 +111,42 @@ def test_bad_workcell(cli_test_runner, ap_file):
107111
)
108112
assert result.exit_code != 0
109113
assert "Workcell id must be like wcN but was bad-workcell-id" in result.stderr
114+
115+
116+
def test_session_id(cli_test_runner, monkeypatch, ap_file):
117+
sessionId = "hi_there"
118+
119+
def mockpost(*args, **kwargs):
120+
return MockResponse(
121+
0,
122+
queue_test_success_res(sessionId),
123+
json.dumps(queue_test_success_res(sessionId)),
124+
)
125+
126+
monkeypatch.setattr(requests, "post", mockpost)
127+
result = cli_test_runner.invoke(
128+
cli,
129+
[
130+
"exec",
131+
str(ap_file),
132+
"-a",
133+
mock_api_endpoint(),
134+
"-s",
135+
sessionId,
136+
],
137+
)
138+
assert result.exit_code == 0
139+
assert (
140+
f"Success. View {mock_api_endpoint()}/dashboard?sessionId={sessionId} to see the scheduling outcome."
141+
in result.output
142+
)
143+
144+
145+
def test_too_many_workcell_definition_arguments(cli_test_runner, monkeypatch, ap_file):
146+
147+
result = cli_test_runner.invoke(
148+
cli,
149+
["exec", str(ap_file), "-a", mock_api_endpoint(), "-s", "anthing", "-w", "wc0"],
150+
)
151+
assert result.exit_code == 0
152+
assert "Error: --workcell-id, --session-id are mutually exclusive." in result.stderr

transcriptic/cli.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -641,13 +641,18 @@ def format_cmd(manifest):
641641
@click.option(
642642
"--workcell-id",
643643
"-w",
644-
help="The workcell id to use for the device set. This is not permitted along with the `deviceSet` option.",
644+
help="The workcell id to use for the device set. This is not permitted along with the `device-set` or `session-id` option.",
645645
)
646646
@click.option(
647647
"--device-set",
648648
"-d",
649649
type=click.File("r"),
650-
help="A DeviceSet json file to use for scheduling. This is not permitted along with the `workcellId` option.",
650+
help="A DeviceSet json file to use for scheduling. This is not permitted along with the `workcell-id` or `session-id` options.",
651+
)
652+
@click.option(
653+
"--session-id",
654+
"-s",
655+
help="The session id of the session that should be used for scheduling this run. This is not permitted along with the `workcell-id` or `device-set` options.",
651656
)
652657
@click.option(
653658
"--time-limit",
@@ -656,6 +661,17 @@ def format_cmd(manifest):
656661
default=30,
657662
help="The maximum time in seconds to spend scheduling. The scheduler will use all the time until an optimal solution is found.",
658663
)
664+
@click.option(
665+
"--schedule-at",
666+
default=None,
667+
help="The absolute time at which the given protocol should start (at the earliest). Absolute time format YYYY-MM-DDThh:mm in the local time of the target workcell (if year, month, or day is missing, it will be auto filled with the current values).",
668+
)
669+
@click.option(
670+
"--schedule-delay",
671+
type=click.INT,
672+
default=None,
673+
help="Delay in minutes at which the given protocol should start (at the earliest).",
674+
)
659675
@click.option(
660676
"--partition-group-size",
661677
type=click.INT,
@@ -678,7 +694,10 @@ def execute(
678694
api,
679695
workcell_id,
680696
device_set,
697+
session_id,
681698
time_limit,
699+
schedule_at,
700+
schedule_delay,
682701
partition_group_size,
683702
partition_horizon,
684703
partitioning_swap_device_id,
@@ -689,7 +708,10 @@ def execute(
689708
api,
690709
workcell_id,
691710
device_set,
711+
session_id,
692712
time_limit,
713+
schedule_at,
714+
schedule_delay,
693715
partition_group_size,
694716
partition_horizon,
695717
partitioning_swap_device_id,

transcriptic/commands.py

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1481,22 +1481,41 @@ def execute(
14811481
api,
14821482
workcell_id,
14831483
device_set,
1484+
session_id,
14841485
time_limit,
1486+
schedule_at,
1487+
schedule_delay,
14851488
partition_group_size,
14861489
partition_horizon,
14871490
partitioning_swap_device_id,
14881491
):
1492+
# Define the initial payload
1493+
payload = {"timeLimit": f"{time_limit}:second"}
1494+
1495+
if schedule_delay is not None and schedule_at is not None:
1496+
click.echo(
1497+
"Error: '--schedule-delay' and '--schedule-at' are mutually exclusive.",
1498+
err=True,
1499+
)
1500+
return
1501+
1502+
# Get the requested scheduling time
1503+
if schedule_delay is not None:
1504+
# round up to the next minute!
1505+
payload["delay"] = schedule_delay
1506+
elif schedule_at is not None: # absolute time
1507+
payload["scheduleAt"] = schedule_at
1508+
14891509
# Get the autoprotocol
14901510
autoprotocol_str = autoprotocol.read()
14911511
try:
1492-
autoprotocol_dict = json.loads(autoprotocol_str)
1512+
payload["autoprotocol"] = json.loads(autoprotocol_str)
14931513
except json.decoder.JSONDecodeError as err:
14941514
click.echo(f"Error decoding autoprotocol json: {err}", err=True)
14951515
return
14961516

1497-
# Define the initial payload
1498-
payload = {"autoprotocol": autoprotocol_dict, "timeLimit": f"{time_limit}:second"}
1499-
1517+
# device set resolution
1518+
in_use = []
15001519
if device_set:
15011520
device_str = device_set.read()
15021521
try:
@@ -1505,13 +1524,26 @@ def execute(
15051524
except json.decoder.JSONDecodeError as err:
15061525
click.echo(f"Error decoding device set json: {err}", err=True)
15071526
return
1508-
elif workcell_id:
1527+
in_use.append("--device-set")
1528+
1529+
if workcell_id:
15091530
if not re.search("^wc[a-z,0-9]+$", workcell_id):
15101531
raise BadParameter(f"Workcell id must be like wcN but was {workcell_id}")
15111532
payload["workcellIdForDeviceSet"] = f"{workcell_id}-mcx1"
1512-
else:
1533+
in_use.append("--workcell-id")
1534+
1535+
if session_id is not None:
1536+
payload["sessionId"] = session_id
1537+
in_use.append("--session-id")
1538+
1539+
if len(in_use) > 1:
1540+
click.echo(f"Error: {', '.join(in_use)} are mutually exclusive.", err=True)
1541+
return
1542+
1543+
if len(in_use) == 0:
15131544
payload["workcellIdForDeviceSet"] = "wctest-mcx1"
15141545

1546+
# partition parameters
15151547
if partition_group_size is not None:
15161548
payload["partitionGroupSize"] = partition_group_size
15171549

@@ -1534,9 +1566,16 @@ def execute(
15341566
try:
15351567
res_json = json.loads(res.text)
15361568
if res_json["success"]:
1537-
click.echo(f"Success. View {clean_api} to see the scheduling outcome.")
1569+
click.echo(
1570+
f"Success. View {clean_api}/dashboard?sessionId={res_json['sessionId']} to see the scheduling outcome."
1571+
)
15381572
else:
15391573
click.echo(f"Error: {res_json['message']}", err=True)
1574+
if "sessionId" in res_json:
1575+
click.echo(
1576+
f"Dashboard can be seen at: {clean_api}/dashboard?sessionId={res_json['sessionId']}",
1577+
err=True,
1578+
)
15401579
except json.decoder.JSONDecodeError:
15411580
click.echo(f"Error: {res.text}", err=True)
15421581

0 commit comments

Comments
 (0)