Skip to content

process events under authorization. #2540

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 14, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 20 additions & 22 deletions taipy/gui_core/_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,17 +116,15 @@ def __lazy_start(self):

def process_event(self, event: Event):
self.__lazy_start()
if event.entity_type is EventEntityType.SCENARIO:
with self.gui._get_authorization(system=True): # type: ignore
with self.gui._get_authorization(system=True): # type: ignore
if event.entity_type is EventEntityType.SCENARIO:
self.scenario_refresh(
event.entity_id
if event.operation is EventOperation.DELETION or is_readable(t.cast(ScenarioId, event.entity_id))
else None
)
elif event.entity_type is EventEntityType.SEQUENCE and event.entity_id:
sequence = None
try:
with self.gui._get_authorization(system=True): # type: ignore
elif event.entity_type is EventEntityType.SEQUENCE and event.entity_id:
try:
sequence = (
core_get(event.entity_id)
if event.operation is not EventOperation.DELETION
Expand All @@ -135,22 +133,22 @@ def process_event(self, event: Event):
)
if sequence and hasattr(sequence, "parent_ids") and sequence.parent_ids: # type: ignore
self.broadcast_core_changed({"scenario": list(sequence.parent_ids)}) # type: ignore
except Exception as e:
_warn(f"Access to sequence {event.entity_id} failed", e)
elif event.entity_type is EventEntityType.JOB:
with self.lock:
self.jobs_list = None
# no broadcast because the submission status will do the job
if event.operation is EventOperation.DELETION:
self.broadcast_core_changed({"jobs": True})
elif event.entity_type is EventEntityType.SUBMISSION:
self.submission_status_callback(event.entity_id, event)
elif event.entity_type is EventEntityType.DATA_NODE:
with self.lock:
self.data_nodes_by_owner = None
self.broadcast_core_changed(
{"datanode": event.entity_id if event.operation != EventOperation.DELETION else True}
)
except Exception as e:
_warn(f"Access to sequence '{event.entity_id}' failed", e)
elif event.entity_type is EventEntityType.JOB:
with self.lock:
self.jobs_list = None
# no broadcast because the submission status will do the job
if event.operation is EventOperation.DELETION:
self.broadcast_core_changed({"jobs": True})
elif event.entity_type is EventEntityType.SUBMISSION:
self.submission_status_callback(event.entity_id, event)
elif event.entity_type is EventEntityType.DATA_NODE:
with self.lock:
self.data_nodes_by_owner = None
self.broadcast_core_changed(
{"datanode": event.entity_id if event.operation != EventOperation.DELETION else True}
)

def broadcast_core_changed(self, payload: t.Dict[str, t.Any], client_id: t.Optional[str] = None):
self.gui._broadcast(_GuiCoreContext._CORE_CHANGED_NAME, payload, client_id) # type: ignore
Expand Down
56 changes: 56 additions & 0 deletions tests/gui_core/test_context_process_event_datanode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Copyright 2021-2025 Avaiga Private Limited
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
from collections import defaultdict
from unittest.mock import patch

import pytest

from taipy import Gui, Sequence, SequenceId, DataNode, Scope
from taipy.core.notification import Event, EventEntityType, EventOperation
from taipy.gui_core._context import _GuiCoreContext


class TestGuiCoreContextProcessDatanodeEvent:

@pytest.mark.parametrize("operation", [EventOperation.CREATION, EventOperation.UPDATE])
def test_datanode_event(self, operation):
event = Event(entity_type=EventEntityType.DATA_NODE,
operation=operation,
entity_id="whatever")
gui_core_context = _GuiCoreContext(Gui())
gui_core_context.data_nodes_by_owner = defaultdict(list)
gui_core_context.data_nodes_by_owner["owner_id"] = [DataNode(config_id="cfg_id", scope=Scope.SCENARIO)]
assert len(gui_core_context.data_nodes_by_owner) == 1

with (patch("taipy.gui.gui.Gui._broadcast") as mock_broadcast,
patch("taipy.gui.gui.Gui._get_authorization") as mock_get_auth):
gui_core_context.process_event(event=event)

mock_get_auth.assert_called_once_with(system=True)
assert gui_core_context.data_nodes_by_owner is None
mock_broadcast.assert_called_once_with("core_changed", {"datanode": "whatever"}, None)

def test_datanode_deletion_event(self):
event = Event(entity_type=EventEntityType.DATA_NODE,
operation=EventOperation.DELETION,
entity_id="whatever")
gui_core_context = _GuiCoreContext(Gui())
gui_core_context.data_nodes_by_owner = defaultdict(list)
gui_core_context.data_nodes_by_owner["owner_id"] = [DataNode(config_id="cfg_id", scope=Scope.SCENARIO)]
assert len(gui_core_context.data_nodes_by_owner) == 1

with (patch("taipy.gui.gui.Gui._broadcast") as mock_broadcast,
patch("taipy.gui.gui.Gui._get_authorization") as mock_get_auth):
gui_core_context.process_event(event=event)

mock_get_auth.assert_called_once_with(system=True)
assert gui_core_context.data_nodes_by_owner is None
mock_broadcast.assert_called_once_with("core_changed", {"datanode": True}, None)
49 changes: 49 additions & 0 deletions tests/gui_core/test_context_process_event_job.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Copyright 2021-2025 Avaiga Private Limited
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
from unittest.mock import patch

import pytest

from taipy import Gui
from taipy.core.notification import Event, EventEntityType, EventOperation
from taipy.gui_core._context import _GuiCoreContext


class TestGuiCoreContextProcessJobEvent:

@pytest.mark.parametrize("operation", [EventOperation.CREATION, EventOperation.UPDATE])
def test_job_event(self, operation):
event = Event(entity_type=EventEntityType.JOB,
operation=operation,
entity_id="whatever")
gui_core_context = _GuiCoreContext(Gui())
gui_core_context.jobs_list = ["job_id"]
assert gui_core_context.jobs_list == ["job_id"]
with patch("taipy.gui.gui.Gui._get_authorization") as mock_get_auth:
gui_core_context.process_event(event=event)

mock_get_auth.assert_called_once_with(system=True)
assert gui_core_context.jobs_list is None


def test_job_deletion(self):
event = Event(entity_type=EventEntityType.JOB,
operation=EventOperation.DELETION,
entity_id="job_id")
with (patch("taipy.gui.gui.Gui._broadcast") as mock_broadcast,
patch("taipy.gui.gui.Gui._get_authorization") as mock_get_auth):
gui_core_context = _GuiCoreContext(Gui())
gui_core_context.process_event(event=event)

mock_get_auth.assert_called_once_with(system=True)
mock_broadcast.assert_called_once_with("core_changed", {"jobs": True}, None)
assert gui_core_context.jobs_list is None

65 changes: 65 additions & 0 deletions tests/gui_core/test_context_process_event_scenario.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Copyright 2021-2025 Avaiga Private Limited
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
from unittest.mock import call, patch

import pytest

from taipy import Gui
from taipy.core.notification import Event, EventEntityType, EventOperation
from taipy.gui_core._context import _GuiCoreContext


class TestGuiCoreContextProcessScenarioEvent:

@pytest.mark.parametrize("operation, scenario_is_valid", [(EventOperation.CREATION, True),
(EventOperation.CREATION, False),
(EventOperation.UPDATE, True),
(EventOperation.UPDATE, False),
(EventOperation.SUBMISSION, True),
(EventOperation.SUBMISSION, False),
])
def test_scenario_event(self, operation, scenario_is_valid):
scenario_id = "scenario_id"
event = Event(entity_type=EventEntityType.SCENARIO,
operation=operation,
entity_id=scenario_id)
with (
patch("taipy.gui.gui.Gui._broadcast") as mock_broadcast,
patch("taipy.gui_core._context.is_readable") as mock_is_readable,
patch("taipy.gui.gui.Gui._get_authorization") as mock_get_auth
):
mock_is_readable.return_value = scenario_is_valid
gui_core_context = _GuiCoreContext(Gui())
gui_core_context.process_event(event=event)

mock_get_auth.assert_called_once_with(system=True)
if scenario_is_valid:
mock_broadcast.assert_called_once_with("core_changed", {"scenario": scenario_id}, None)
else:
mock_broadcast.assert_called_once_with("core_changed", {"scenario": True}, None)

@pytest.mark.parametrize("scenario_is_valid", [True, False])
def test_scenario_deletion(self, scenario_is_valid):
event = Event(entity_type=EventEntityType.SCENARIO,
operation=EventOperation.DELETION,
entity_id="scenario_id")
with (
patch("taipy.gui.gui.Gui._broadcast") as mock_broadcast,
patch("taipy.gui_core._context.is_readable") as mock_is_readable,
patch("taipy.gui.gui.Gui._get_authorization") as mock_get_auth
):
mock_is_readable.return_value = scenario_is_valid
gui_core_context = _GuiCoreContext(Gui())
gui_core_context.process_event(event=event)

mock_get_auth.assert_called_once_with(system=True)
mock_broadcast.assert_called_once_with("core_changed", {"scenario": "scenario_id"}, None)

90 changes: 90 additions & 0 deletions tests/gui_core/test_context_process_event_sequence.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Copyright 2021-2025 Avaiga Private Limited
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
from unittest.mock import patch

import pytest

from taipy import Gui, Sequence, SequenceId
from taipy.core.notification import Event, EventEntityType, EventOperation
from taipy.gui_core._context import _GuiCoreContext


class TestGuiCoreContextProcessSequenceEvent:

@pytest.mark.parametrize("operation, sequence_is_valid", [(EventOperation.CREATION, True),
(EventOperation.CREATION, False),
(EventOperation.UPDATE, True),
(EventOperation.UPDATE, False),
(EventOperation.SUBMISSION, True),
(EventOperation.SUBMISSION, False),
])
def test_sequence_event(self, operation, sequence_is_valid):
seq_id = "sequence_id"
seq_parent_ids = {"a_scenario_id", "s_id"}
sequence = Sequence({}, [], SequenceId(seq_id), parent_ids=seq_parent_ids)
event = Event(entity_type=EventEntityType.SEQUENCE,
operation=operation,
entity_id=seq_id)
with (
patch("taipy.gui.gui.Gui._broadcast") as mock_broadcast,
patch("taipy.gui_core._context.is_readable") as mock_is_readable,
patch("taipy.gui_core._context.core_get") as mock_core_get,
patch("taipy.gui.gui.Gui._get_authorization") as mock_get_auth
):
mock_core_get.return_value = sequence
mock_is_readable.return_value = sequence_is_valid
gui_core_context = _GuiCoreContext(Gui())
gui_core_context.process_event(event=event)

mock_get_auth.assert_called_once_with(system=True)
if sequence_is_valid:
mock_broadcast.assert_called_once_with("core_changed", {"scenario": list(seq_parent_ids)}, None)
else:
mock_broadcast.assert_not_called()

@pytest.mark.parametrize("sequence_is_valid", [True, False])
def test_sequence_deletion(self, sequence_is_valid):
event = Event(entity_type=EventEntityType.SEQUENCE,
operation=EventOperation.DELETION,
entity_id="sequence_id")
with (
patch("taipy.gui.gui.Gui._broadcast") as mock_broadcast,
patch("taipy.gui_core._context.is_readable") as mock_is_readable,
patch("taipy.gui.gui.Gui._get_authorization") as mock_get_auth
):
mock_is_readable.return_value = sequence_is_valid
gui_core_context = _GuiCoreContext(Gui())
gui_core_context.process_event(event=event)

mock_get_auth.assert_called_once_with(system=True)
mock_broadcast.assert_not_called()

@pytest.mark.parametrize("operation", [EventOperation.CREATION, EventOperation.UPDATE, EventOperation.SUBMISSION])
def test_sequence_without_parent_ids(self, operation):
seq_id = "sequence_id"
seq_parent_ids = set()
sequence = Sequence({}, [], SequenceId(seq_id), parent_ids=seq_parent_ids)
event = Event(entity_type=EventEntityType.SEQUENCE,
operation=operation,
entity_id=seq_id)
with (
patch("taipy.gui.gui.Gui._broadcast") as mock_broadcast,
patch("taipy.gui_core._context.is_readable") as mock_is_readable,
patch("taipy.gui_core._context.core_get") as mock_core_get,
patch("taipy.gui.gui.Gui._get_authorization") as mock_get_auth
):
mock_core_get.return_value = sequence
mock_is_readable.return_value = True
gui_core_context = _GuiCoreContext(Gui())
gui_core_context.process_event(event=event)

mock_get_auth.assert_called_once_with(system=True)
mock_broadcast.assert_not_called()
Loading