Skip to content

Commit 2a0278e

Browse files
authored
process events under authorization - Issue #662 - Backport #2540 (#2565)
process event under authorization (#2540)
1 parent 0c37a5a commit 2a0278e

5 files changed

+269
-20
lines changed

taipy/gui_core/_context.py

+18-20
Original file line numberDiff line numberDiff line change
@@ -104,17 +104,15 @@ def __init__(self, gui: Gui) -> None:
104104
self.start()
105105

106106
def process_event(self, event: Event):
107-
if event.entity_type == EventEntityType.SCENARIO:
108-
with self.gui._get_autorization(system=True):
107+
with self.gui._get_autorization(system=True):
108+
if event.entity_type == EventEntityType.SCENARIO:
109109
self.scenario_refresh(
110110
event.entity_id
111111
if event.operation != EventOperation.DELETION and is_readable(t.cast(ScenarioId, event.entity_id))
112112
else None
113113
)
114-
elif event.entity_type == EventEntityType.SEQUENCE and event.entity_id:
115-
sequence = None
116-
try:
117-
with self.gui._get_autorization(system=True):
114+
elif event.entity_type == EventEntityType.SEQUENCE and event.entity_id:
115+
try:
118116
sequence = (
119117
core_get(event.entity_id)
120118
if event.operation != EventOperation.DELETION
@@ -126,20 +124,20 @@ def process_event(self, event: Event):
126124
_GuiCoreContext._CORE_CHANGED_NAME,
127125
{"scenario": [x for x in sequence.parent_ids]}, # type: ignore
128126
)
129-
except Exception as e:
130-
_warn(f"Access to sequence {event.entity_id} failed", e)
131-
elif event.entity_type == EventEntityType.JOB:
132-
with self.lock:
133-
self.jobs_list = None
134-
elif event.entity_type == EventEntityType.SUBMISSION:
135-
self.submission_status_callback(event.entity_id)
136-
elif event.entity_type == EventEntityType.DATA_NODE:
137-
with self.lock:
138-
self.data_nodes_by_owner = None
139-
self.gui._broadcast(
140-
_GuiCoreContext._CORE_CHANGED_NAME,
141-
{"datanode": event.entity_id if event.operation != EventOperation.DELETION else True},
142-
)
127+
except Exception as e:
128+
_warn(f"Access to sequence {event.entity_id} failed", e)
129+
elif event.entity_type == EventEntityType.JOB:
130+
with self.lock:
131+
self.jobs_list = None
132+
elif event.entity_type == EventEntityType.SUBMISSION:
133+
self.submission_status_callback(event.entity_id)
134+
elif event.entity_type == EventEntityType.DATA_NODE:
135+
with self.lock:
136+
self.data_nodes_by_owner = None
137+
self.gui._broadcast(
138+
_GuiCoreContext._CORE_CHANGED_NAME,
139+
{"datanode": event.entity_id if event.operation != EventOperation.DELETION else True},
140+
)
143141

144142
def scenario_refresh(self, scenario_id: t.Optional[str]):
145143
with self.lock:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Copyright 2021-2025 Avaiga Private Limited
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
4+
# the License. You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
9+
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
10+
# specific language governing permissions and limitations under the License.
11+
from collections import defaultdict
12+
from unittest.mock import patch
13+
14+
import pytest
15+
16+
from taipy import DataNode, Gui, Scope
17+
from taipy.core.notification import Event, EventEntityType, EventOperation
18+
from taipy.gui_core._context import _GuiCoreContext
19+
20+
21+
class TestGuiCoreContextProcessDatanodeEvent:
22+
23+
@pytest.mark.parametrize("operation", [EventOperation.CREATION, EventOperation.UPDATE])
24+
def test_datanode_event(self, operation):
25+
event = Event(entity_type=EventEntityType.DATA_NODE,
26+
operation=operation,
27+
entity_id="whatever")
28+
gui_core_context = _GuiCoreContext(Gui())
29+
gui_core_context.data_nodes_by_owner = defaultdict(list)
30+
gui_core_context.data_nodes_by_owner["owner_id"] = [DataNode(config_id="cfg_id", scope=Scope.SCENARIO)]
31+
assert len(gui_core_context.data_nodes_by_owner) == 1
32+
33+
with patch("taipy.gui.gui.Gui._broadcast") as mock_broadcast:
34+
with patch("taipy.gui.gui.Gui._get_autorization") as mock_get_auth:
35+
gui_core_context.process_event(event=event)
36+
37+
mock_get_auth.assert_called_once_with(system=True)
38+
assert gui_core_context.data_nodes_by_owner is None
39+
mock_broadcast.assert_called_once_with("core_changed", {"datanode": "whatever"})
40+
41+
def test_datanode_deletion_event(self):
42+
event = Event(entity_type=EventEntityType.DATA_NODE,
43+
operation=EventOperation.DELETION,
44+
entity_id="whatever")
45+
gui_core_context = _GuiCoreContext(Gui())
46+
gui_core_context.data_nodes_by_owner = defaultdict(list)
47+
gui_core_context.data_nodes_by_owner["owner_id"] = [DataNode(config_id="cfg_id", scope=Scope.SCENARIO)]
48+
assert len(gui_core_context.data_nodes_by_owner) == 1
49+
50+
with patch("taipy.gui.gui.Gui._broadcast") as mock_broadcast:
51+
with patch("taipy.gui.gui.Gui._get_autorization") as mock_get_auth:
52+
gui_core_context.process_event(event=event)
53+
54+
mock_get_auth.assert_called_once_with(system=True)
55+
assert gui_core_context.data_nodes_by_owner is None
56+
mock_broadcast.assert_called_once_with("core_changed", {"datanode": True})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Copyright 2021-2025 Avaiga Private Limited
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
4+
# the License. You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
9+
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
10+
# specific language governing permissions and limitations under the License.
11+
from unittest.mock import patch
12+
13+
import pytest
14+
15+
from taipy import Gui
16+
from taipy.core.notification import Event, EventEntityType, EventOperation
17+
from taipy.gui_core._context import _GuiCoreContext
18+
19+
20+
class TestGuiCoreContextProcessJobEvent:
21+
22+
@pytest.mark.parametrize("operation", [EventOperation.CREATION, EventOperation.UPDATE])
23+
def test_job_event(self, operation):
24+
event = Event(entity_type=EventEntityType.JOB,
25+
operation=operation,
26+
entity_id="whatever")
27+
gui_core_context = _GuiCoreContext(Gui())
28+
gui_core_context.jobs_list = ["job_id"]
29+
assert gui_core_context.jobs_list == ["job_id"]
30+
with patch("taipy.gui.gui.Gui._get_autorization") as mock_get_auth:
31+
gui_core_context.process_event(event=event)
32+
33+
mock_get_auth.assert_called_once_with(system=True)
34+
assert gui_core_context.jobs_list is None
35+
36+
37+
def test_job_deletion(self):
38+
event = Event(entity_type=EventEntityType.JOB,
39+
operation=EventOperation.DELETION,
40+
entity_id="job_id")
41+
with patch("taipy.gui.gui.Gui._broadcast") as mock_broadcast:
42+
with patch("taipy.gui.gui.Gui._get_autorization") as mock_get_auth:
43+
gui_core_context = _GuiCoreContext(Gui())
44+
gui_core_context.process_event(event=event)
45+
46+
mock_get_auth.assert_called_once_with(system=True)
47+
mock_broadcast.assert_not_called()
48+
assert gui_core_context.jobs_list is None
49+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Copyright 2021-2025 Avaiga Private Limited
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
4+
# the License. You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
9+
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
10+
# specific language governing permissions and limitations under the License.
11+
from unittest.mock import patch
12+
13+
import pytest
14+
15+
from taipy import Gui
16+
from taipy.core.notification import Event, EventEntityType, EventOperation
17+
from taipy.gui_core._context import _GuiCoreContext
18+
19+
20+
class TestGuiCoreContextProcessScenarioEvent:
21+
22+
@pytest.mark.parametrize("operation, scenario_is_valid", [(EventOperation.CREATION, True),
23+
(EventOperation.CREATION, False),
24+
(EventOperation.UPDATE, True),
25+
(EventOperation.UPDATE, False),
26+
(EventOperation.SUBMISSION, True),
27+
(EventOperation.SUBMISSION, False),
28+
])
29+
def test_scenario_event(self, operation, scenario_is_valid):
30+
scenario_id = "scenario_id"
31+
event = Event(entity_type=EventEntityType.SCENARIO,
32+
operation=operation,
33+
entity_id=scenario_id)
34+
with patch("taipy.gui.gui.Gui._broadcast") as mock_broadcast:
35+
with patch("taipy.gui_core._context.is_readable") as mock_is_readable:
36+
with patch("taipy.gui.gui.Gui._get_autorization") as mock_get_auth:
37+
mock_is_readable.return_value = scenario_is_valid
38+
gui_core_context = _GuiCoreContext(Gui())
39+
gui_core_context.process_event(event=event)
40+
41+
mock_get_auth.assert_called_once_with(system=True)
42+
if scenario_is_valid:
43+
mock_broadcast.assert_called_once_with("core_changed", {"scenario": scenario_id})
44+
else:
45+
mock_broadcast.assert_called_once_with("core_changed", {"scenario": True})
46+
47+
@pytest.mark.parametrize("scenario_is_valid", [True, False])
48+
def test_scenario_deletion(self, scenario_is_valid):
49+
event = Event(entity_type=EventEntityType.SCENARIO,
50+
operation=EventOperation.DELETION,
51+
entity_id="scenario_id")
52+
with patch("taipy.gui.gui.Gui._broadcast") as mock_broadcast:
53+
with patch("taipy.gui_core._context.is_readable") as mock_is_readable:
54+
with patch("taipy.gui.gui.Gui._get_autorization") as mock_get_auth:
55+
mock_is_readable.return_value = scenario_is_valid
56+
gui_core_context = _GuiCoreContext(Gui())
57+
gui_core_context.process_event(event=event)
58+
59+
mock_get_auth.assert_called_once_with(system=True)
60+
mock_broadcast.assert_called_once_with("core_changed", {"scenario": True})
61+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Copyright 2021-2025 Avaiga Private Limited
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
4+
# the License. You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
9+
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
10+
# specific language governing permissions and limitations under the License.
11+
from unittest.mock import patch
12+
13+
import pytest
14+
15+
from taipy import Gui, Sequence, SequenceId
16+
from taipy.core.notification import Event, EventEntityType, EventOperation
17+
from taipy.gui_core._context import _GuiCoreContext
18+
19+
20+
class TestGuiCoreContextProcessSequenceEvent:
21+
22+
@pytest.mark.parametrize("operation, sequence_is_valid", [(EventOperation.CREATION, True),
23+
(EventOperation.CREATION, False),
24+
(EventOperation.UPDATE, True),
25+
(EventOperation.UPDATE, False),
26+
(EventOperation.SUBMISSION, True),
27+
(EventOperation.SUBMISSION, False),
28+
])
29+
def test_sequence_event(self, operation, sequence_is_valid):
30+
seq_id = "sequence_id"
31+
seq_parent_ids = {"a_scenario_id", "s_id"}
32+
sequence = Sequence({}, [], SequenceId(seq_id), parent_ids=seq_parent_ids)
33+
event = Event(entity_type=EventEntityType.SEQUENCE,
34+
operation=operation,
35+
entity_id=seq_id)
36+
with patch("taipy.gui.gui.Gui._broadcast") as mock_broadcast:
37+
with patch("taipy.gui_core._context.is_readable") as mock_is_readable:
38+
with patch("taipy.gui_core._context.core_get") as mock_core_get:
39+
with patch("taipy.gui.gui.Gui._get_autorization") as mock_get_auth:
40+
mock_core_get.return_value = sequence
41+
mock_is_readable.return_value = sequence_is_valid
42+
gui_core_context = _GuiCoreContext(Gui())
43+
gui_core_context.process_event(event=event)
44+
45+
mock_get_auth.assert_called_once_with(system=True)
46+
if sequence_is_valid:
47+
mock_broadcast.assert_called_once_with("core_changed", {"scenario": list(seq_parent_ids)})
48+
else:
49+
mock_broadcast.assert_not_called()
50+
51+
@pytest.mark.parametrize("sequence_is_valid", [True, False])
52+
def test_sequence_deletion(self, sequence_is_valid):
53+
event = Event(entity_type=EventEntityType.SEQUENCE,
54+
operation=EventOperation.DELETION,
55+
entity_id="sequence_id")
56+
57+
with patch("taipy.gui.gui.Gui._broadcast") as mock_broadcast:
58+
with patch("taipy.gui_core._context.is_readable") as mock_is_readable:
59+
with patch("taipy.gui.gui.Gui._get_autorization") as mock_get_auth:
60+
mock_is_readable.return_value = sequence_is_valid
61+
gui_core_context = _GuiCoreContext(Gui())
62+
gui_core_context.process_event(event=event)
63+
64+
mock_get_auth.assert_called_once_with(system=True)
65+
mock_broadcast.assert_not_called()
66+
67+
@pytest.mark.parametrize("operation", [EventOperation.CREATION, EventOperation.UPDATE, EventOperation.SUBMISSION])
68+
def test_sequence_without_parent_ids(self, operation):
69+
seq_id = "sequence_id"
70+
seq_parent_ids = set()
71+
sequence = Sequence({}, [], SequenceId(seq_id), parent_ids=seq_parent_ids)
72+
event = Event(entity_type=EventEntityType.SEQUENCE,
73+
operation=operation,
74+
entity_id=seq_id)
75+
with patch("taipy.gui.gui.Gui._broadcast") as mock_broadcast:
76+
with patch("taipy.gui_core._context.is_readable") as mock_is_readable:
77+
with patch("taipy.gui_core._context.core_get") as mock_core_get:
78+
with patch("taipy.gui.gui.Gui._get_autorization") as mock_get_auth:
79+
mock_core_get.return_value = sequence
80+
mock_is_readable.return_value = True
81+
gui_core_context = _GuiCoreContext(Gui())
82+
gui_core_context.process_event(event=event)
83+
84+
mock_get_auth.assert_called_once_with(system=True)
85+
mock_broadcast.assert_not_called()

0 commit comments

Comments
 (0)