Skip to content
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

Feature/enterprise#362 - Add can_create() api #1390

Merged
merged 4 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions taipy/config/_serializer/_base_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ def _stringify(cls, as_dict):
return [cls._stringify(val) for val in as_dict]
if isinstance(as_dict, tuple):
return [cls._stringify(val) for val in as_dict]
if isinstance(as_dict, set):
return [cls._stringify(val) for val in as_dict]
jrobinAV marked this conversation as resolved.
Show resolved Hide resolved
return as_dict

@staticmethod
Expand Down
15 changes: 15 additions & 0 deletions taipy/core/data/_data_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
from ..cycle.cycle_id import CycleId
from ..exceptions.exceptions import InvalidDataNodeType
from ..notification import Event, EventEntityType, EventOperation, Notifier, _make_event
from ..reason._reason_factory import _build_not_global_scope_reason, _build_wrong_config_type_reason
from ..reason.reason import Reasons
from ..scenario.scenario_id import ScenarioId
from ..sequence.sequence_id import SequenceId
from ._data_fs_repository import _DataFSRepository
Expand Down Expand Up @@ -68,6 +70,19 @@ def _bulk_get_or_create(
for dn_config, owner_id in dn_configs_and_owner_id
}

@classmethod
def _can_create(cls, config: Optional[DataNodeConfig] = None) -> Reasons:
config_id = getattr(config, "id", None) or str(config)
jrobinAV marked this conversation as resolved.
Show resolved Hide resolved
reason = Reasons(config_id)

if config is not None:
if not isinstance(config, DataNodeConfig):
reason._add_reason(config_id, _build_wrong_config_type_reason(config_id, "DataNodeConfig"))
elif config.scope is not Scope.GLOBAL:
reason._add_reason(config_id, _build_not_global_scope_reason(config_id))

return reason

@classmethod
def _create_and_set(
cls, data_node_config: DataNodeConfig, owner_id: Optional[str], parent_ids: Optional[Set[str]]
Expand Down
13 changes: 13 additions & 0 deletions taipy/core/reason/_reason_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
# 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 typing import Optional

from ..data.data_node import DataNodeId


Expand All @@ -22,3 +24,14 @@ def _build_data_node_is_not_written(dn_id: DataNodeId) -> str:

def _build_not_submittable_entity_reason(entity_id: str) -> str:
return f"Entity {entity_id} is not a submittable entity"


def _build_wrong_config_type_reason(config_id: str, config_type: Optional[str]) -> str:
if config_type:
return f'Object "{config_id}" must be a valid {config_type}'

return f'Object "{config_id}" is not a valid config to be created'
trgiangdo marked this conversation as resolved.
Show resolved Hide resolved


def _build_not_global_scope_reason(config_id: str) -> str:
return f'Data node config "{config_id}" does not have GLOBAL scope'
13 changes: 12 additions & 1 deletion taipy/core/scenario/_scenario_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
from ..job._job_manager_factory import _JobManagerFactory
from ..job.job import Job
from ..notification import EventEntityType, EventOperation, Notifier, _make_event
from ..reason._reason_factory import _build_not_submittable_entity_reason
from ..reason._reason_factory import _build_not_submittable_entity_reason, _build_wrong_config_type_reason
from ..reason.reason import Reasons
from ..submission._submission_manager_factory import _SubmissionManagerFactory
from ..submission.submission import Submission
Expand Down Expand Up @@ -114,6 +114,17 @@ def __remove_subscriber(cls, callback, params, scenario: Scenario) -> None:
_make_event(scenario, EventOperation.UPDATE, attribute_name="subscribers", attribute_value=params)
)

@classmethod
def _can_create(cls, config: Optional[ScenarioConfig] = None) -> Reasons:
config_id = getattr(config, "id", None) or str(config)
reason = Reasons(config_id)

if config is not None:
if not isinstance(config, ScenarioConfig):
reason._add_reason(config_id, _build_wrong_config_type_reason(config_id, "ScenarioConfig"))

return reason

@classmethod
def _create(
cls,
Expand Down
14 changes: 14 additions & 0 deletions taipy/core/taipy.py
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,20 @@ def get_cycles() -> List[Cycle]:
return _CycleManagerFactory._build_manager()._get_all()


def can_create(config: Optional[Union[ScenarioConfig, DataNodeConfig]] = None) -> Reasons:
"""Indicate if a config can be created. The config should be a scenario or data node config.

If no config is provided, the function indicates if any scenario or data node config can be created.

Returns:
True if the given config can be created. False otherwise.
"""
if isinstance(config, DataNodeConfig):
return _DataManagerFactory._build_manager()._can_create(config)

return _ScenarioManagerFactory._build_manager()._can_create(config)


def create_scenario(
config: ScenarioConfig,
creation_date: Optional[datetime] = None,
Expand Down
22 changes: 22 additions & 0 deletions tests/core/data/test_data_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,28 @@ def test_create_data_node_and_modify_properties_does_not_modify_config(self):
assert dn.properties.get("foo") == "bar"
assert dn.properties.get("baz") == "qux"

def test_can_create(self):
dn_config = Config.configure_data_node("dn", 10, scope=Scope.SCENARIO)
global_dn_config = Config.configure_data_node(
id="global_dn", storage_type="in_memory", scope=Scope.GLOBAL, data=10
)

reasons = _DataManager._can_create()
assert bool(reasons) is True
assert reasons._reasons == {}

reasons = _DataManager._can_create(global_dn_config)
assert bool(reasons) is True
trgiangdo marked this conversation as resolved.
Show resolved Hide resolved
assert reasons._reasons == {}

reasons = _DataManager._can_create(dn_config)
assert bool(reasons) is False
assert reasons._reasons == {dn_config.id: {'Data node config "dn" does not have GLOBAL scope'}}

reasons = _DataManager._can_create(1)
assert bool(reasons) is False
assert reasons._reasons == {"1": {'Object "1" must be a valid DataNodeConfig'}}

def test_create_data_node_with_name_provided(self):
dn_config = Config.configure_data_node(id="dn", foo="bar", name="acb")
dn = _DataManager._create_and_set(dn_config, None, None)
Expand Down
27 changes: 27 additions & 0 deletions tests/core/scenario/test_scenario_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,33 @@ def test_create_and_delete_scenario():
assert len(_ScenarioManager._get_all()) == 0


def test_can_create():
dn_config = Config.configure_in_memory_data_node("dn", 10)
task_config = Config.configure_task("task", print, [dn_config])
scenario_config = Config.configure_scenario("sc", {task_config}, [], Frequency.DAILY)

reasons = _ScenarioManager._can_create()
assert bool(reasons) is True
assert reasons._reasons == {}

reasons = _ScenarioManager._can_create(scenario_config)
assert bool(reasons) is True
assert reasons._reasons == {}
_ScenarioManager._create(scenario_config)

reasons = _ScenarioManager._can_create(task_config)
assert bool(reasons) is False
assert reasons._reasons == {task_config.id: {'Object "task" must be a valid ScenarioConfig'}}
with pytest.raises(AttributeError):
_ScenarioManager._create(task_config)

reasons = _ScenarioManager._can_create(1)
assert bool(reasons) is False
assert reasons._reasons == {"1": {'Object "1" must be a valid ScenarioConfig'}}
with pytest.raises(AttributeError):
_ScenarioManager._create(1)


def test_is_deletable():
assert len(_ScenarioManager._get_all()) == 0
scenario_config = Config.configure_scenario("sc", None, None, Frequency.DAILY)
Expand Down
12 changes: 12 additions & 0 deletions tests/core/test_taipy.py
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,18 @@ def test_cycle_exists(self):
tp.exists(cycle_id)
mck.assert_called_once_with(cycle_id)

def test_can_create(self):
global_dn_config = Config.configure_in_memory_data_node("global_dn", 10, scope=Scope.GLOBAL)
dn_config = Config.configure_in_memory_data_node("dn", 10)
task_config = Config.configure_task("task", print, [dn_config])
scenario_config = Config.configure_scenario("sc", {task_config}, [], Frequency.DAILY)

assert tp.can_create()
assert tp.can_create(scenario_config)
assert tp.can_create(global_dn_config)
assert not tp.can_create(dn_config)
assert not tp.can_create("1")

def test_create_global_data_node(self):
dn_cfg_global = DataNodeConfig("id", "pickle", Scope.GLOBAL)
dn_cfg_scenario = DataNodeConfig("id", "pickle", Scope.SCENARIO)
Expand Down
Loading