griptape-nodes 0.59.2__py3-none-any.whl → 0.60.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- griptape_nodes/common/macro_parser/__init__.py +28 -0
- griptape_nodes/common/macro_parser/core.py +230 -0
- griptape_nodes/common/macro_parser/exceptions.py +23 -0
- griptape_nodes/common/macro_parser/formats.py +170 -0
- griptape_nodes/common/macro_parser/matching.py +134 -0
- griptape_nodes/common/macro_parser/parsing.py +172 -0
- griptape_nodes/common/macro_parser/resolution.py +168 -0
- griptape_nodes/common/macro_parser/segments.py +42 -0
- griptape_nodes/exe_types/core_types.py +241 -4
- griptape_nodes/exe_types/node_types.py +7 -1
- griptape_nodes/exe_types/param_components/huggingface/__init__.py +1 -0
- griptape_nodes/exe_types/param_components/huggingface/huggingface_model_parameter.py +168 -0
- griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_file_parameter.py +38 -0
- griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_parameter.py +33 -0
- griptape_nodes/exe_types/param_components/huggingface/huggingface_utils.py +136 -0
- griptape_nodes/exe_types/param_components/log_parameter.py +136 -0
- griptape_nodes/exe_types/param_components/seed_parameter.py +59 -0
- griptape_nodes/exe_types/param_types/__init__.py +1 -0
- griptape_nodes/exe_types/param_types/parameter_bool.py +221 -0
- griptape_nodes/exe_types/param_types/parameter_float.py +179 -0
- griptape_nodes/exe_types/param_types/parameter_int.py +183 -0
- griptape_nodes/exe_types/param_types/parameter_number.py +380 -0
- griptape_nodes/exe_types/param_types/parameter_string.py +232 -0
- griptape_nodes/node_library/library_registry.py +2 -1
- griptape_nodes/retained_mode/events/app_events.py +21 -0
- griptape_nodes/retained_mode/events/os_events.py +142 -6
- griptape_nodes/retained_mode/events/parameter_events.py +2 -0
- griptape_nodes/retained_mode/griptape_nodes.py +14 -0
- griptape_nodes/retained_mode/managers/agent_manager.py +5 -3
- griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +19 -1
- griptape_nodes/retained_mode/managers/library_manager.py +27 -32
- griptape_nodes/retained_mode/managers/node_manager.py +14 -1
- griptape_nodes/retained_mode/managers/os_manager.py +403 -124
- griptape_nodes/retained_mode/managers/user_manager.py +120 -0
- griptape_nodes/retained_mode/managers/workflow_manager.py +44 -34
- griptape_nodes/traits/multi_options.py +26 -2
- griptape_nodes/utils/huggingface_utils.py +136 -0
- {griptape_nodes-0.59.2.dist-info → griptape_nodes-0.60.0.dist-info}/METADATA +1 -1
- {griptape_nodes-0.59.2.dist-info → griptape_nodes-0.60.0.dist-info}/RECORD +41 -18
- {griptape_nodes-0.59.2.dist-info → griptape_nodes-0.60.0.dist-info}/WHEEL +1 -1
- {griptape_nodes-0.59.2.dist-info → griptape_nodes-0.60.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from huggingface_hub import scan_cache_dir
|
|
6
|
+
from huggingface_hub.constants import HF_HUB_CACHE
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger("griptape_nodes")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def list_all_repo_revisions_in_cache() -> list[tuple[str, str]]:
|
|
12
|
+
"""Returns a list of (repo_id, revision) tuples for all repos in the huggingface cache."""
|
|
13
|
+
# Use quick scan for diffuser repos, fallback to scan_cache_dir only on errors
|
|
14
|
+
try:
|
|
15
|
+
repos = quick_scan_diffuser_repos(HF_HUB_CACHE)
|
|
16
|
+
results = [(repo["name"], repo["hash"]) for repo in repos]
|
|
17
|
+
except Exception:
|
|
18
|
+
logger.exception("Failed to quick scan diffuser repos, falling back to scan_cache_dir.")
|
|
19
|
+
else:
|
|
20
|
+
return results
|
|
21
|
+
|
|
22
|
+
# Fallback to original implementation
|
|
23
|
+
cache_info = scan_cache_dir()
|
|
24
|
+
results = []
|
|
25
|
+
for repo in cache_info.repos:
|
|
26
|
+
for revision in repo.revisions:
|
|
27
|
+
results.append((repo.repo_id, revision.commit_hash))
|
|
28
|
+
return results
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def list_repo_revisions_in_cache(repo_id: str) -> list[tuple[str, str]]:
|
|
32
|
+
"""Returns a list of (repo_id, revision) tuples matching repo_id in the huggingface cache."""
|
|
33
|
+
# Use quick scan for diffuser repos, fallback to scan_cache_dir only on errors
|
|
34
|
+
try:
|
|
35
|
+
repos = quick_scan_diffuser_repos(HF_HUB_CACHE)
|
|
36
|
+
results = [(repo["name"], repo["hash"]) for repo in repos if repo["name"] == repo_id]
|
|
37
|
+
except Exception:
|
|
38
|
+
logger.exception("Failed to quick scan diffuser repos, falling back to scan_cache_dir.")
|
|
39
|
+
else:
|
|
40
|
+
return results
|
|
41
|
+
|
|
42
|
+
# Fallback to original implementation
|
|
43
|
+
cache_info = scan_cache_dir()
|
|
44
|
+
results = []
|
|
45
|
+
for repo in cache_info.repos:
|
|
46
|
+
if repo.repo_id == repo_id:
|
|
47
|
+
for revision in repo.revisions:
|
|
48
|
+
results.append((repo.repo_id, revision.commit_hash))
|
|
49
|
+
return results
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def list_repo_revisions_with_file_in_cache(repo_id: str, file: str) -> list[tuple[str, str]]:
|
|
53
|
+
"""Returns a list of (repo_id, revision) tuples matching repo_id in the huggingface cache if it contains file."""
|
|
54
|
+
# Use quick scan for diffuser repos, check if file exists
|
|
55
|
+
try:
|
|
56
|
+
repos = quick_scan_diffuser_repos(HF_HUB_CACHE)
|
|
57
|
+
results = [
|
|
58
|
+
(repo["name"], repo["hash"])
|
|
59
|
+
for repo in repos
|
|
60
|
+
if repo["name"] == repo_id and (Path(repo["path"]) / file).exists()
|
|
61
|
+
]
|
|
62
|
+
except Exception:
|
|
63
|
+
logger.exception("Failed to quick scan diffuser repos, falling back to scan_cache_dir.")
|
|
64
|
+
else:
|
|
65
|
+
return results
|
|
66
|
+
|
|
67
|
+
# Fallback to original implementation
|
|
68
|
+
cache_info = scan_cache_dir()
|
|
69
|
+
results = []
|
|
70
|
+
for repo in cache_info.repos:
|
|
71
|
+
if repo.repo_id == repo_id:
|
|
72
|
+
for revision in repo.revisions:
|
|
73
|
+
if any(f.file_name == file for f in revision.files):
|
|
74
|
+
results.append((repo.repo_id, revision.commit_hash))
|
|
75
|
+
return results
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def quick_scan_diffuser_repos(cache_dir: str) -> list[dict[str, Any]]:
|
|
79
|
+
"""Quick scan of diffuser repositories in huggingface cache directory.
|
|
80
|
+
|
|
81
|
+
Adapted from https://github.com/huggingface/huggingface_hub/issues/1564#issuecomment-1710764361
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
cache_dir: Path to huggingface cache directory
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
List of dictionaries containing repo info with keys:
|
|
88
|
+
- name: Repository name
|
|
89
|
+
- filename: Repository name (alias for name)
|
|
90
|
+
- path: Path to the snapshot folder
|
|
91
|
+
- hash: Commit hash
|
|
92
|
+
- mtime: Modification time
|
|
93
|
+
- model_info: Path to model_info.json file
|
|
94
|
+
"""
|
|
95
|
+
diffuser_repos = []
|
|
96
|
+
cache_path = Path(cache_dir)
|
|
97
|
+
|
|
98
|
+
if not cache_path.exists():
|
|
99
|
+
return diffuser_repos
|
|
100
|
+
|
|
101
|
+
for folder_path in cache_path.iterdir():
|
|
102
|
+
folder = folder_path.name
|
|
103
|
+
if not folder_path.is_dir() or "--" not in folder:
|
|
104
|
+
continue
|
|
105
|
+
_, name = folder.split("--", maxsplit=1)
|
|
106
|
+
name = name.replace("--", "/")
|
|
107
|
+
|
|
108
|
+
snapshots_dir = folder_path / "snapshots"
|
|
109
|
+
if not snapshots_dir.exists():
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
snapshots = [p.name for p in snapshots_dir.iterdir() if p.is_dir()]
|
|
113
|
+
if len(snapshots) == 0:
|
|
114
|
+
continue
|
|
115
|
+
|
|
116
|
+
commit = snapshots[-1]
|
|
117
|
+
snapshot_path = snapshots_dir / commit
|
|
118
|
+
|
|
119
|
+
if (snapshot_path / "hidden").exists():
|
|
120
|
+
continue
|
|
121
|
+
|
|
122
|
+
mtime = snapshot_path.stat().st_mtime
|
|
123
|
+
info = snapshot_path / "model_info.json"
|
|
124
|
+
|
|
125
|
+
diffuser_repos.append(
|
|
126
|
+
{
|
|
127
|
+
"name": name,
|
|
128
|
+
"filename": name,
|
|
129
|
+
"path": str(snapshot_path),
|
|
130
|
+
"hash": commit,
|
|
131
|
+
"mtime": mtime,
|
|
132
|
+
"model_info": str(info),
|
|
133
|
+
}
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
return diffuser_repos
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
import time
|
|
5
|
+
from collections.abc import Callable, Iterator
|
|
6
|
+
from types import TracebackType
|
|
7
|
+
|
|
8
|
+
from griptape_nodes.exe_types.core_types import Parameter, ParameterMode
|
|
9
|
+
from griptape_nodes.exe_types.node_types import BaseNode
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LogParameter:
|
|
13
|
+
def __init__(self, node: BaseNode):
|
|
14
|
+
self._node = node
|
|
15
|
+
|
|
16
|
+
def add_output_parameters(self) -> None:
|
|
17
|
+
self._node.add_parameter(
|
|
18
|
+
Parameter(
|
|
19
|
+
name="logs",
|
|
20
|
+
output_type="str",
|
|
21
|
+
allowed_modes={ParameterMode.OUTPUT},
|
|
22
|
+
tooltip="logs",
|
|
23
|
+
ui_options={"multiline": True},
|
|
24
|
+
)
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
@contextlib.contextmanager
|
|
28
|
+
def append_stdout_to_logs(self) -> Iterator[None]:
|
|
29
|
+
def callback(data: str) -> None:
|
|
30
|
+
self.append_to_logs(data)
|
|
31
|
+
|
|
32
|
+
with StdoutCapture(callback):
|
|
33
|
+
yield
|
|
34
|
+
|
|
35
|
+
@contextlib.contextmanager
|
|
36
|
+
def append_logs_to_logs(self, logger: logging.Logger) -> Iterator[None]:
|
|
37
|
+
def callback(data: str) -> None:
|
|
38
|
+
self.append_to_logs(data)
|
|
39
|
+
|
|
40
|
+
with LoggerCapture(logger, callback):
|
|
41
|
+
yield
|
|
42
|
+
|
|
43
|
+
@contextlib.contextmanager
|
|
44
|
+
def append_profile_to_logs(self, label: str) -> Iterator[None]:
|
|
45
|
+
start = time.perf_counter()
|
|
46
|
+
yield
|
|
47
|
+
seconds = time.perf_counter() - start
|
|
48
|
+
human_readable_duration = seconds_to_human_readable(seconds)
|
|
49
|
+
self.append_to_logs(f"{label} took {human_readable_duration}\n")
|
|
50
|
+
|
|
51
|
+
def append_to_logs(self, text: str) -> None:
|
|
52
|
+
self._node.append_value_to_parameter("logs", text)
|
|
53
|
+
|
|
54
|
+
def clear_logs(self) -> None:
|
|
55
|
+
self._node.publish_update_to_parameter("logs", "")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class StdoutCapture:
|
|
59
|
+
def __init__(self, callback: Callable[[str], None]) -> None:
|
|
60
|
+
self.callback: Callable[[str], None] = callback
|
|
61
|
+
self._original_stdout = sys.stdout
|
|
62
|
+
|
|
63
|
+
def write(self, data: str) -> None:
|
|
64
|
+
self._original_stdout.write(data)
|
|
65
|
+
self._original_stdout.flush()
|
|
66
|
+
self.callback(data)
|
|
67
|
+
|
|
68
|
+
def flush(self) -> None:
|
|
69
|
+
self._original_stdout.flush()
|
|
70
|
+
|
|
71
|
+
def __enter__(self) -> "StdoutCapture":
|
|
72
|
+
sys.stdout = self
|
|
73
|
+
return self
|
|
74
|
+
|
|
75
|
+
def __exit__(
|
|
76
|
+
self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None
|
|
77
|
+
) -> None:
|
|
78
|
+
sys.stdout = self._original_stdout
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class CallbackHandler(logging.Handler):
|
|
82
|
+
def __init__(self, callback: Callable[[str], None]) -> None:
|
|
83
|
+
super().__init__(level=logging.INFO)
|
|
84
|
+
self.callback = callback
|
|
85
|
+
self.setFormatter(logging.Formatter("%(message)s\n"))
|
|
86
|
+
|
|
87
|
+
def emit(self, record: logging.LogRecord) -> None:
|
|
88
|
+
message = self.format(record)
|
|
89
|
+
self.callback(message)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class LoggerCapture:
|
|
93
|
+
def __init__(
|
|
94
|
+
self, logger: logging.Logger, callback: Callable[[str], None], level: int | str = logging.INFO
|
|
95
|
+
) -> None:
|
|
96
|
+
self.logger = logger
|
|
97
|
+
self.target_level = level
|
|
98
|
+
self._handler = CallbackHandler(callback)
|
|
99
|
+
|
|
100
|
+
def __enter__(self) -> "LoggerCapture":
|
|
101
|
+
self.original_level = self.logger.level
|
|
102
|
+
self.logger.setLevel(self.target_level)
|
|
103
|
+
self.logger.addHandler(self._handler)
|
|
104
|
+
return self
|
|
105
|
+
|
|
106
|
+
def __exit__(
|
|
107
|
+
self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None
|
|
108
|
+
) -> None:
|
|
109
|
+
self.logger.removeHandler(self._handler)
|
|
110
|
+
self.logger.setLevel(self.original_level)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def seconds_to_human_readable(seconds: float) -> str:
|
|
114
|
+
"""Convert seconds to a human-readable format.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
seconds (float): The number of seconds to convert.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
str: A human-readable string representing the time duration.
|
|
121
|
+
"""
|
|
122
|
+
intervals = (
|
|
123
|
+
("year", 31536000),
|
|
124
|
+
("month", 2592000),
|
|
125
|
+
("day", 86400),
|
|
126
|
+
("hour", 3600),
|
|
127
|
+
("minute", 60),
|
|
128
|
+
("second", 1),
|
|
129
|
+
("millisecond", 0.001),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
for name, count in intervals:
|
|
133
|
+
if seconds >= count:
|
|
134
|
+
value = seconds / count
|
|
135
|
+
return f"{value:.2f} {name}{'s' if value != 1 else ''}"
|
|
136
|
+
return "0.00 milliseconds"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import random
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from griptape_nodes.exe_types.core_types import Parameter, ParameterMode
|
|
5
|
+
from griptape_nodes.exe_types.node_types import BaseNode
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SeedParameter:
|
|
9
|
+
def __init__(self, node: BaseNode):
|
|
10
|
+
self._node = node
|
|
11
|
+
|
|
12
|
+
def add_input_parameters(self) -> None:
|
|
13
|
+
self._node.add_parameter(
|
|
14
|
+
Parameter(
|
|
15
|
+
name="randomize_seed",
|
|
16
|
+
type="bool",
|
|
17
|
+
output_type="bool",
|
|
18
|
+
tooltip="randomize the seed on each run",
|
|
19
|
+
default_value=False,
|
|
20
|
+
)
|
|
21
|
+
)
|
|
22
|
+
self._node.add_parameter(
|
|
23
|
+
Parameter(
|
|
24
|
+
name="seed",
|
|
25
|
+
type="int",
|
|
26
|
+
tooltip="seed",
|
|
27
|
+
default_value=42,
|
|
28
|
+
)
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def remove_input_parameters(self) -> None:
|
|
32
|
+
self._node.remove_parameter_element_by_name("randomize_seed")
|
|
33
|
+
self._node.remove_parameter_element_by_name("seed")
|
|
34
|
+
|
|
35
|
+
def after_value_set(self, parameter: Parameter, value: Any) -> None:
|
|
36
|
+
if parameter.name != "randomize_seed":
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
seed_parameter = self._node.get_parameter_by_name("seed")
|
|
40
|
+
if not seed_parameter:
|
|
41
|
+
msg = "Seed parameter not found"
|
|
42
|
+
raise RuntimeError(msg)
|
|
43
|
+
|
|
44
|
+
if value:
|
|
45
|
+
# Disable editing the seed if randomize_seed is True
|
|
46
|
+
seed_parameter.allowed_modes = {ParameterMode.OUTPUT}
|
|
47
|
+
else:
|
|
48
|
+
# Enable editing the seed if randomize_seed is False
|
|
49
|
+
seed_parameter.allowed_modes = {ParameterMode.PROPERTY, ParameterMode.INPUT, ParameterMode.OUTPUT}
|
|
50
|
+
|
|
51
|
+
def preprocess(self) -> None:
|
|
52
|
+
if self._node.get_parameter_value("randomize_seed"):
|
|
53
|
+
# Not using for cryptographic purposes
|
|
54
|
+
seed = random.randint(0, 2**32 - 1) # noqa: S311
|
|
55
|
+
self._node.set_parameter_value("seed", seed)
|
|
56
|
+
self._node.publish_update_to_parameter("seed", seed)
|
|
57
|
+
|
|
58
|
+
def get_seed(self) -> int:
|
|
59
|
+
return int(self._node.get_parameter_value("seed"))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Parameter types for simple parameter creation."""
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""ParameterBool component for boolean inputs with enhanced UI options."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from griptape_nodes.exe_types.core_types import Parameter, ParameterMode, Trait
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ParameterBool(Parameter):
|
|
10
|
+
"""A specialized Parameter class for boolean inputs with enhanced UI options.
|
|
11
|
+
|
|
12
|
+
This class provides a convenient way to create boolean parameters with common
|
|
13
|
+
UI customizations like custom on/off labels. It exposes these UI options as
|
|
14
|
+
direct properties for easy runtime modification.
|
|
15
|
+
|
|
16
|
+
Example:
|
|
17
|
+
param = ParameterBool(
|
|
18
|
+
name="enabled",
|
|
19
|
+
tooltip="Enable this feature",
|
|
20
|
+
on_label="Yes",
|
|
21
|
+
off_label="No",
|
|
22
|
+
default_value=True
|
|
23
|
+
)
|
|
24
|
+
param.on_label = "Enable" # Change labels at runtime
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__( # noqa: PLR0913
|
|
28
|
+
self,
|
|
29
|
+
name: str,
|
|
30
|
+
tooltip: str | None = None,
|
|
31
|
+
*,
|
|
32
|
+
type: str = "bool", # noqa: A002, ARG002
|
|
33
|
+
default_value: Any = None,
|
|
34
|
+
tooltip_as_input: str | None = None,
|
|
35
|
+
tooltip_as_property: str | None = None,
|
|
36
|
+
tooltip_as_output: str | None = None,
|
|
37
|
+
allowed_modes: set[ParameterMode] | None = None,
|
|
38
|
+
traits: set[type[Trait] | Trait] | None = None,
|
|
39
|
+
converters: list[Callable[[Any], Any]] | None = None,
|
|
40
|
+
validators: list[Callable[[Parameter, Any], None]] | None = None,
|
|
41
|
+
ui_options: dict | None = None,
|
|
42
|
+
on_label: str | None = None,
|
|
43
|
+
off_label: str | None = None,
|
|
44
|
+
accept_any: bool = True,
|
|
45
|
+
hide: bool = False,
|
|
46
|
+
hide_label: bool = False,
|
|
47
|
+
hide_property: bool = False,
|
|
48
|
+
allow_input: bool = True,
|
|
49
|
+
allow_property: bool = True,
|
|
50
|
+
allow_output: bool = True,
|
|
51
|
+
settable: bool = True,
|
|
52
|
+
serializable: bool = True,
|
|
53
|
+
user_defined: bool = False,
|
|
54
|
+
element_id: str | None = None,
|
|
55
|
+
element_type: str | None = None,
|
|
56
|
+
parent_container_name: str | None = None,
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Initialize a boolean parameter with enhanced UI options.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
name: Parameter name
|
|
62
|
+
tooltip: Parameter tooltip
|
|
63
|
+
type: Parameter type (ignored, always "bool" for ParameterBool)
|
|
64
|
+
default_value: Default parameter value
|
|
65
|
+
tooltip_as_input: Tooltip for input mode
|
|
66
|
+
tooltip_as_property: Tooltip for property mode
|
|
67
|
+
tooltip_as_output: Tooltip for output mode
|
|
68
|
+
allowed_modes: Allowed parameter modes
|
|
69
|
+
traits: Parameter traits
|
|
70
|
+
converters: Parameter converters
|
|
71
|
+
validators: Parameter validators
|
|
72
|
+
ui_options: Dictionary of UI options
|
|
73
|
+
on_label: Label for the "on" state
|
|
74
|
+
off_label: Label for the "off" state
|
|
75
|
+
accept_any: Whether to accept any input type and convert to boolean (default: True)
|
|
76
|
+
hide: Whether to hide the entire parameter
|
|
77
|
+
hide_label: Whether to hide the parameter label
|
|
78
|
+
hide_property: Whether to hide the parameter in property mode
|
|
79
|
+
allow_input: Whether to allow input mode
|
|
80
|
+
allow_property: Whether to allow property mode
|
|
81
|
+
allow_output: Whether to allow output mode
|
|
82
|
+
settable: Whether the parameter is settable
|
|
83
|
+
serializable: Whether the parameter is serializable
|
|
84
|
+
user_defined: Whether the parameter is user-defined
|
|
85
|
+
element_id: Element ID
|
|
86
|
+
element_type: Element type
|
|
87
|
+
parent_container_name: Name of parent container
|
|
88
|
+
"""
|
|
89
|
+
# Build ui_options dictionary from the provided UI-specific parameters
|
|
90
|
+
if ui_options is None:
|
|
91
|
+
ui_options = {}
|
|
92
|
+
else:
|
|
93
|
+
ui_options = ui_options.copy()
|
|
94
|
+
|
|
95
|
+
# Add boolean-specific UI options if they have values
|
|
96
|
+
if on_label is not None:
|
|
97
|
+
ui_options["on_label"] = on_label
|
|
98
|
+
if off_label is not None:
|
|
99
|
+
ui_options["off_label"] = off_label
|
|
100
|
+
|
|
101
|
+
# Set up boolean conversion based on accept_any setting
|
|
102
|
+
if converters is None:
|
|
103
|
+
existing_converters = []
|
|
104
|
+
else:
|
|
105
|
+
existing_converters = converters
|
|
106
|
+
|
|
107
|
+
if accept_any:
|
|
108
|
+
final_input_types = ["any"]
|
|
109
|
+
final_converters = [self._convert_to_bool, *existing_converters]
|
|
110
|
+
else:
|
|
111
|
+
final_input_types = ["bool"]
|
|
112
|
+
final_converters = existing_converters
|
|
113
|
+
|
|
114
|
+
# Call parent with explicit parameters, following ControlParameter pattern
|
|
115
|
+
super().__init__(
|
|
116
|
+
name=name,
|
|
117
|
+
tooltip=tooltip,
|
|
118
|
+
type="bool", # Always a boolean type for ParameterBool
|
|
119
|
+
input_types=final_input_types,
|
|
120
|
+
output_type="bool", # Always output as boolean
|
|
121
|
+
default_value=default_value,
|
|
122
|
+
tooltip_as_input=tooltip_as_input,
|
|
123
|
+
tooltip_as_property=tooltip_as_property,
|
|
124
|
+
tooltip_as_output=tooltip_as_output,
|
|
125
|
+
allowed_modes=allowed_modes,
|
|
126
|
+
traits=traits,
|
|
127
|
+
converters=final_converters,
|
|
128
|
+
validators=validators,
|
|
129
|
+
ui_options=ui_options,
|
|
130
|
+
hide=hide,
|
|
131
|
+
hide_label=hide_label,
|
|
132
|
+
hide_property=hide_property,
|
|
133
|
+
allow_input=allow_input,
|
|
134
|
+
allow_property=allow_property,
|
|
135
|
+
allow_output=allow_output,
|
|
136
|
+
settable=settable,
|
|
137
|
+
serializable=serializable,
|
|
138
|
+
user_defined=user_defined,
|
|
139
|
+
element_id=element_id,
|
|
140
|
+
element_type=element_type,
|
|
141
|
+
parent_container_name=parent_container_name,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def _convert_to_bool(self, value: Any) -> bool:
|
|
145
|
+
"""Safely convert any input value to a boolean.
|
|
146
|
+
|
|
147
|
+
Handles various input types including strings, numbers, and other objects.
|
|
148
|
+
Uses Python's built-in bool() conversion with proper handling of common
|
|
149
|
+
string representations.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
value: The value to convert to boolean
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Boolean representation of the value
|
|
156
|
+
"""
|
|
157
|
+
if value is None:
|
|
158
|
+
return False
|
|
159
|
+
|
|
160
|
+
# Handle boolean inputs
|
|
161
|
+
if isinstance(value, bool):
|
|
162
|
+
return value
|
|
163
|
+
|
|
164
|
+
# Handle string inputs with common boolean representations
|
|
165
|
+
if isinstance(value, str):
|
|
166
|
+
value_lower = value.lower().strip()
|
|
167
|
+
if value_lower in ("true", "yes", "on", "1", "enable", "enabled"):
|
|
168
|
+
return True
|
|
169
|
+
if value_lower in ("false", "no", "off", "0", "disable", "disabled"):
|
|
170
|
+
return False
|
|
171
|
+
# For other strings, use truthiness
|
|
172
|
+
return bool(value)
|
|
173
|
+
|
|
174
|
+
# For all other types (including numeric), use Python's built-in bool conversion
|
|
175
|
+
return bool(value)
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
def on_label(self) -> str | None:
|
|
179
|
+
"""Get the label for the "on" state.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
The on label if set, None otherwise
|
|
183
|
+
"""
|
|
184
|
+
return self.ui_options.get("on_label")
|
|
185
|
+
|
|
186
|
+
@on_label.setter
|
|
187
|
+
def on_label(self, value: str | None) -> None:
|
|
188
|
+
"""Set the label for the "on" state.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
value: The on label to use, or None to remove it
|
|
192
|
+
"""
|
|
193
|
+
if value is None:
|
|
194
|
+
ui_options = self.ui_options.copy()
|
|
195
|
+
ui_options.pop("on_label", None)
|
|
196
|
+
self.ui_options = ui_options
|
|
197
|
+
else:
|
|
198
|
+
self.update_ui_options_key("on_label", value)
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def off_label(self) -> str | None:
|
|
202
|
+
"""Get the label for the "off" state.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
The off label if set, None otherwise
|
|
206
|
+
"""
|
|
207
|
+
return self.ui_options.get("off_label")
|
|
208
|
+
|
|
209
|
+
@off_label.setter
|
|
210
|
+
def off_label(self, value: str | None) -> None:
|
|
211
|
+
"""Set the label for the "off" state.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
value: The off label to use, or None to remove it
|
|
215
|
+
"""
|
|
216
|
+
if value is None:
|
|
217
|
+
ui_options = self.ui_options.copy()
|
|
218
|
+
ui_options.pop("off_label", None)
|
|
219
|
+
self.ui_options = ui_options
|
|
220
|
+
else:
|
|
221
|
+
self.update_ui_options_key("off_label", value)
|