Skip to content

Commit 7bd93cb

Browse files
[pycue/cuegui/cuebot] Add Department wrapper and fix TasksDialog (#2399) (#2427)
## Related Issues Resolves #2399 Originally reported as #1042 Related (not addressed here): #2400 ## Summarize your change. CueGUI's `TasksDialog` is written entirely against the pycue wrapper API, but the `Department` wrapper it depends on was never implemented — so opening the dialog raised `AttributeError` on `Show.getDepartments()` (`TasksDialog.py:94`). This adds the missing wrapper layer and fixes two related gaps found while wiring it up. **pycue** - New `opencue/wrappers/department.py`: `Department` wrapper following the existing `task.py` pattern (`self.data` + `self.stub`, one method per RPC), covering the `DepartmentInterface` RPCs that cuebot implements: `addTask`, `addTasks`, `clearTasks`, `clearTaskAdjustments`, `enableTiManaged`, `disableTiManaged`, `getTasks`, `replaceTasks`, `setManagedCores`. `DepartmentInterface.Delete` is intentionally **not** wrapped — cuebot has no implementation for it. - `Show.getDepartment()` / `Show.getDepartments()`, mirroring the existing `getGroups()`. - Module-level `api.addDepartmentName()` / `api.removeDepartmentName()`, alongside the existing `getDepartmentNames()`. - `Task.clearAdjustments()`, used by the Tasks dialog context menu. **cuegui** - `TaskActions.clearAdjustment` called `task.clearAdjustment()`, which never existed on the wrapper; the unit test mocked the attribute, so the gap was never caught. Both now call the real `Task.clearAdjustments()`. **cuebot / proto** - The proto declared `rpc SetMangedCores` (typo) while the servant implements `setManagedCores`. Because the names differed, the servant never overrode the generated base method and the RPC always returned `UNIMPLEMENTED` — so the "Minimum Cores" control in `TasksDialog` would fail even with the wrapper in place. Renamed the RPC to `SetManagedCores` and added `@Override` on the servant. The rename is safe: no client could have called the old RPC successfully, and nothing references the old name. `VERSION.in` is bumped 1.25 → 1.26 as required by `ci/check_version_bump.py` for proto changes. **Tests**: new `tests/wrappers/test_department.py`, plus `getDepartment`/`getDepartments` in `test_show.py`, `clearAdjustments` in `test_task.py`, and `getDepartmentNames`/`addDepartmentName`/`removeDepartmentName` in `test_api.py`. **Open question for the reviewer**: #2400 suggested naming these `createDepartment` / `deleteDepartment`. I chose `addDepartmentName` / `removeDepartmentName` instead, to match the underlying RPC names and the existing `getDepartmentNames()`, and because they manage the allowed-name whitelist rather than creating a show's department entity (entities are auto-created when a name is assigned to a group — see `department.proto`). Happy to rename if the `create`/`delete` spelling is preferred. ## LLM usage disclosure Claude (Opus) was used to build up testing environment across cuegui/pycue/cuebot, write the unit tests, and draft this PR description. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added department support across the Python API and wrappers, including listing/viewing departments from a show and managing department task settings and managed cores. * Added API actions to add or remove department names. * Added task-level support to clear all task adjustments. * **Bug Fixes** * Renamed the department managed-cores RPC to the corrected method name. * Updated task adjustment clearing behavior to consistently use the plural clear operation. * **Documentation** * Expanded Sphinx API docs to include the department wrapper. * **Tests** * Added/updated unit tests for department, show, task, and API behaviors. * **Chores** * Updated the project version to 1.28. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Hai Shun <o927416847@gmail.com> Signed-off-by: Diego Tavares <dtavares@imageworks.com> Co-authored-by: Diego Tavares <dtavares@imageworks.com>
1 parent 254013a commit 7bd93cb

14 files changed

Lines changed: 486 additions & 7 deletions

File tree

VERSION.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.27
1+
1.28

api_docs/modules/opencue.wrappers.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ opencue.wrappers.deed module
2828
.. automodule:: opencue.wrappers.deed
2929
:members:
3030

31+
opencue.wrappers.department module
32+
----------------------------------
33+
34+
.. automodule:: opencue.wrappers.department
35+
:members:
36+
3137
opencue.wrappers.depend module
3238
------------------------------
3339

cuebot/src/main/java/com/imageworks/spcue/servant/ManageDepartment.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ public void replaceTasks(DeptReplaceTaskRequest request,
182182
responseObserver.onCompleted();
183183
}
184184

185+
@Override
185186
public void setManagedCores(DeptSetManagedCoresRequest request,
186187
StreamObserver<DeptSetManagedCoresResponse> responseObserver) {
187188
PointDetail deptConfig =

cuegui/cuegui/MenuActions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2289,7 +2289,7 @@ def setMinCores(self, rpcObjects=None):
22892289
def clearAdjustment(self, rpcObjects=None):
22902290
tasks = self._getSelected(rpcObjects)
22912291
for task in tasks:
2292-
task.clearAdjustment()
2292+
task.clearAdjustments()
22932293
self._update()
22942294

22952295
delete_info = ["Delete Task", None, "configure"]

cuegui/tests/test_menu_actions.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1738,11 +1738,11 @@ def test_setMinCores(self, getDoubleMock):
17381738

17391739
def test_clearAdjustment(self):
17401740
task = opencue.wrappers.task.Task(opencue_proto.task_pb2.Task())
1741-
task.clearAdjustment = mock.MagicMock()
1741+
task.clearAdjustments = mock.MagicMock()
17421742

17431743
self.task_actions.clearAdjustment(rpcObjects=[task])
17441744

1745-
task.clearAdjustment.assert_called()
1745+
task.clearAdjustments.assert_called()
17461746

17471747
@mock.patch('cuegui.Utils.questionBoxYesNo', new=mock.Mock(return_value=True))
17481748
def test_delete(self):

proto/src/department.proto

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ service DepartmentInterface {
6969
rpc ReplaceTasks(DeptReplaceTaskRequest) returns (DeptReplaceTaskResponse);
7070

7171
// Sets the minimum number of cores for the department to manage between its tasks.
72-
rpc SetMangedCores(DeptSetManagedCoresRequest) returns (DeptSetManagedCoresResponse);
72+
rpc SetManagedCores(DeptSetManagedCoresRequest) returns (DeptSetManagedCoresResponse);
7373
}
7474

7575

pycue/opencue/api.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
# pylint: disable=cyclic-import
3939
from .wrappers.allocation import Allocation
4040
from .wrappers.comment import Comment
41+
from .wrappers.department import Department
4142
from .wrappers.depend import Depend
4243
from .wrappers.filter import Action
4344
from .wrappers.filter import Filter
@@ -62,8 +63,8 @@
6263
filter_pb2, host_pb2, job_pb2, renderPartition_pb2, report_pb2, service_pb2,
6364
show_pb2, subscription_pb2, task_pb2]
6465

65-
__wrappers = [Action, Allocation, Comment, Depend, Filter, Frame, Group, Host, Job, Layer, Matcher,
66-
NestedHost, Proc, Show, Subscription, Task]
66+
__wrappers = [Action, Allocation, Comment, Department, Depend, Filter, Frame, Group, Host, Job,
67+
Layer, Matcher, NestedHost, Proc, Show, Subscription, Task]
6768

6869

6970
#
@@ -187,6 +188,28 @@ def getDepartmentNames():
187188
department_pb2.DeptGetDepartmentNamesRequest(), timeout=Cuebot.Timeout).names)
188189

189190

191+
@util.grpcExceptionParser
192+
def addDepartmentName(name):
193+
"""Adds a department name to the list of allowed department names.
194+
195+
:type name: str
196+
:param name: a department name to allow
197+
"""
198+
Cuebot.getStub('department').AddDepartmentName(
199+
department_pb2.DeptAddDeptNameRequest(name=name), timeout=Cuebot.Timeout)
200+
201+
202+
@util.grpcExceptionParser
203+
def removeDepartmentName(name):
204+
"""Removes a department name from the list of allowed department names.
205+
206+
:type name: str
207+
:param name: the department name to remove
208+
"""
209+
Cuebot.getStub('department').RemoveDepartmentName(
210+
department_pb2.DeptRemoveDepartmentNameRequest(name=name), timeout=Cuebot.Timeout)
211+
212+
190213
#
191214
# Shows
192215
#
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# Copyright Contributors to the OpenCue Project
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Module for classes related to departments."""
16+
17+
from opencue_proto import department_pb2
18+
from opencue.cuebot import Cuebot
19+
import opencue.wrappers.task
20+
21+
22+
class Department(object):
23+
"""This class contains the grpc implementation related to a Department."""
24+
25+
def __init__(self, department=None):
26+
self.data = department
27+
self.stub = Cuebot.getStub('department')
28+
29+
def addTask(self, shot, minCores):
30+
"""Adds a task to the department and returns it.
31+
32+
:type shot: str
33+
:param shot: name of the shot the task is for
34+
:type minCores: float
35+
:param minCores: the minimum number of cores the task needs
36+
:rtype: opencue.wrappers.task.Task
37+
:return: the newly created task
38+
"""
39+
response = self.stub.AddTask(
40+
department_pb2.DeptAddTaskRequest(
41+
department=self.data, shot=shot, min_cores=minCores),
42+
timeout=Cuebot.Timeout)
43+
return opencue.wrappers.task.Task(response.task)
44+
45+
def addTasks(self, taskMap):
46+
"""Adds a map of tasks to the department and returns them as a list.
47+
48+
:type taskMap: dict<str, int>
49+
:param taskMap: map of shot names to minimum core units
50+
:rtype: list<opencue.wrappers.task.Task>
51+
:return: the newly created tasks
52+
"""
53+
response = self.stub.AddTasks(
54+
department_pb2.DeptAddTasksRequest(department=self.data, tmap=taskMap),
55+
timeout=Cuebot.Timeout)
56+
return [opencue.wrappers.task.Task(task) for task in response.tasks.tasks]
57+
58+
def clearTaskAdjustments(self):
59+
"""Clears all manual task adjustments to managed tasks.
60+
61+
This won't do anything unless the department is Track-It managed.
62+
"""
63+
self.stub.ClearTaskAdjustments(
64+
department_pb2.DeptClearTaskAdjustmentsRequest(department=self.data),
65+
timeout=Cuebot.Timeout)
66+
67+
def clearTasks(self):
68+
"""Clears all tasks from the department."""
69+
self.stub.ClearTasks(
70+
department_pb2.DeptClearTasksRequest(department=self.data),
71+
timeout=Cuebot.Timeout)
72+
73+
def disableTiManaged(self):
74+
"""Disables Track-It management; this also clears all tasks."""
75+
self.stub.DisableTiManaged(
76+
department_pb2.DeptDisableTiManagedRequest(department=self.data),
77+
timeout=Cuebot.Timeout)
78+
79+
def enableTiManaged(self, tiTask, managedCores):
80+
"""Enables Track-It management.
81+
82+
This will pull a task list from Track-It and keep it synced.
83+
84+
:type tiTask: str
85+
:param tiTask: name of the Track-It task to manage the department with
86+
:type managedCores: float
87+
:param managedCores: the number of cores to split up among the active tasks
88+
"""
89+
self.stub.EnableTiManaged(
90+
department_pb2.DeptEnableTiManagedRequest(
91+
department=self.data, ti_task=tiTask, managed_cores=managedCores),
92+
timeout=Cuebot.Timeout)
93+
94+
def getTasks(self):
95+
"""Returns the list of tasks for the department.
96+
97+
:rtype: list<opencue.wrappers.task.Task>
98+
:return: tasks of the department
99+
"""
100+
response = self.stub.GetTasks(
101+
department_pb2.DeptGetTasksRequest(department=self.data),
102+
timeout=Cuebot.Timeout)
103+
return [opencue.wrappers.task.Task(task) for task in response.tasks.tasks]
104+
105+
def replaceTasks(self, taskMap):
106+
"""Replaces a map of tasks; existing tasks are updated, new tasks are inserted.
107+
108+
:type taskMap: dict<str, int>
109+
:param taskMap: map of shot names to minimum core units
110+
:rtype: list<opencue.wrappers.task.Task>
111+
:return: the resulting tasks
112+
"""
113+
response = self.stub.ReplaceTasks(
114+
department_pb2.DeptReplaceTaskRequest(department=self.data, tmap=taskMap),
115+
timeout=Cuebot.Timeout)
116+
return [opencue.wrappers.task.Task(task) for task in response.tasks.tasks]
117+
118+
def setManagedCores(self, managedCores):
119+
"""Sets the minimum number of cores for the department to manage between its tasks.
120+
121+
:type managedCores: float
122+
:param managedCores: the number of cores to manage between the tasks
123+
"""
124+
self.stub.SetManagedCores(
125+
department_pb2.DeptSetManagedCoresRequest(
126+
department=self.data, managed_cores=managedCores),
127+
timeout=Cuebot.Timeout)
128+
129+
def id(self):
130+
"""Returns the unique id of the department.
131+
132+
:rtype: str
133+
:return: department id
134+
"""
135+
return self.data.id
136+
137+
def name(self):
138+
"""Returns the name of the department.
139+
140+
:rtype: str
141+
:return: department name
142+
"""
143+
return self.data.name

pycue/opencue/wrappers/show.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from opencue_proto import show_pb2
1818
from opencue.cuebot import Cuebot
19+
import opencue.wrappers.department
1920
import opencue.wrappers.filter
2021
import opencue.wrappers.group
2122
import opencue.wrappers.subscription
@@ -257,6 +258,31 @@ def createFilter(self, name):
257258
show=self.data, name=name), timeout=Cuebot.Timeout)
258259
return opencue.wrappers.filter.Filter(response.filter)
259260

261+
def getDepartment(self, department):
262+
"""Gets the department of the show with the matching name.
263+
264+
:type department: str
265+
:param department: name of the department to find
266+
:rtype: opencue.wrappers.department.Department
267+
:return: matching department of the show
268+
"""
269+
response = self.stub.GetDepartment(show_pb2.ShowGetDepartmentRequest(
270+
show=self.data, department=department),
271+
timeout=Cuebot.Timeout)
272+
return opencue.wrappers.department.Department(response.department)
273+
274+
def getDepartments(self):
275+
"""Gets the departments that belong to the show.
276+
277+
:rtype: list<opencue.wrappers.department.Department>
278+
:return: list of departments for this show
279+
"""
280+
response = self.stub.GetDepartments(show_pb2.ShowGetDepartmentsRequest(
281+
show=self.data),
282+
timeout=Cuebot.Timeout)
283+
return [opencue.wrappers.department.Department(dept)
284+
for dept in response.departments.departments]
285+
260286
def getGroups(self):
261287
"""Gets the groups for the show.
262288

pycue/opencue/wrappers/task.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ def setMinCores(self, minCores):
4343
task_pb2.TaskSetMinCoresRequest(task=self.data, new_min_cores=minCores),
4444
timeout=Cuebot.Timeout)
4545

46+
def clearAdjustments(self):
47+
"""Clears any manual adjustments made to the task."""
48+
self.stub.ClearAdjustments(
49+
task_pb2.TaskClearAdjustmentsRequest(task=self.data), timeout=Cuebot.Timeout)
50+
4651
def delete(self):
4752
"""Deletes the task."""
4853
self.stub.Delete(task_pb2.TaskDeleteRequest(task=self.data), timeout=Cuebot.Timeout)

0 commit comments

Comments
 (0)