click-extended 0.0.2__tar.gz → 0.0.3__tar.gz
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.
- {click_extended-0.0.2/click_extended.egg-info → click_extended-0.0.3}/PKG-INFO +16 -5
- click_extended-0.0.3/README.md +38 -0
- {click_extended-0.0.2 → click_extended-0.0.3/click_extended.egg-info}/PKG-INFO +16 -5
- {click_extended-0.0.2 → click_extended-0.0.3}/click_extended.egg-info/SOURCES.txt +1 -0
- {click_extended-0.0.2 → click_extended-0.0.3}/pyproject.toml +1 -1
- click_extended-0.0.3/tests/test_global_node.py +749 -0
- {click_extended-0.0.2 → click_extended-0.0.3}/tests/test_tree.py +135 -0
- click_extended-0.0.2/README.md +0 -27
- {click_extended-0.0.2 → click_extended-0.0.3}/AUTHORS.md +0 -0
- {click_extended-0.0.2 → click_extended-0.0.3}/LICENSE +0 -0
- {click_extended-0.0.2 → click_extended-0.0.3}/click_extended/__init__.py +0 -0
- {click_extended-0.0.2 → click_extended-0.0.3}/click_extended/errors.py +0 -0
- {click_extended-0.0.2 → click_extended-0.0.3}/click_extended/types.py +0 -0
- {click_extended-0.0.2 → click_extended-0.0.3}/click_extended.egg-info/dependency_links.txt +0 -0
- {click_extended-0.0.2 → click_extended-0.0.3}/click_extended.egg-info/requires.txt +0 -0
- {click_extended-0.0.2 → click_extended-0.0.3}/click_extended.egg-info/top_level.txt +0 -0
- {click_extended-0.0.2 → click_extended-0.0.3}/setup.cfg +0 -0
- {click_extended-0.0.2 → click_extended-0.0.3}/tests/test_argument.py +0 -0
- {click_extended-0.0.2 → click_extended-0.0.3}/tests/test_child_node.py +0 -0
- {click_extended-0.0.2 → click_extended-0.0.3}/tests/test_command.py +0 -0
- {click_extended-0.0.2 → click_extended-0.0.3}/tests/test_env.py +0 -0
- {click_extended-0.0.2 → click_extended-0.0.3}/tests/test_group.py +0 -0
- {click_extended-0.0.2 → click_extended-0.0.3}/tests/test_option.py +0 -0
- {click_extended-0.0.2 → click_extended-0.0.3}/tests/test_parent_node.py +0 -0
- {click_extended-0.0.2 → click_extended-0.0.3}/tests/test_root_node.py +0 -0
- {click_extended-0.0.2 → click_extended-0.0.3}/tests/test_tag.py +0 -0
- {click_extended-0.0.2 → click_extended-0.0.3}/tests/test_transform.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: click_extended
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: An extension to Click with additional features like automatic async support, aliasing and a modular decorator system.
|
|
5
5
|
Author-email: Marcus Fredriksson <marcus@marcusfredriksson.com>
|
|
6
6
|
License: MIT License
|
|
@@ -65,7 +65,14 @@ Dynamic: license-file
|
|
|
65
65
|
|
|
66
66
|
# Click Extended
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
An extension of the [Click](https://github.com/pallets/click) library with additional features like aliasing, asynchronous support, an extended decorator system and more.
|
|
69
|
+
|
|
70
|
+
## Features
|
|
71
|
+
|
|
72
|
+
- **Aliasing**: Add multiple aliases to a group or command.
|
|
73
|
+
- **Async supprt**: Automatically run both synchronous and asynchronous functions.
|
|
74
|
+
- **Extended decorator system**: Create or use pre-made validation and transformation decorators, inject values into the context and more.
|
|
75
|
+
- **Environment variables**: Automatically validate and inject environment variables into the function.
|
|
69
76
|
|
|
70
77
|
## Installation
|
|
71
78
|
|
|
@@ -75,16 +82,20 @@ pip install click-extended
|
|
|
75
82
|
|
|
76
83
|
## Requirements
|
|
77
84
|
|
|
78
|
-
|
|
85
|
+
- **Python**: 3.10 or higher
|
|
79
86
|
|
|
80
|
-
##
|
|
87
|
+
## Usage
|
|
81
88
|
|
|
82
|
-
|
|
89
|
+
TBD
|
|
83
90
|
|
|
84
91
|
## Contributing
|
|
85
92
|
|
|
86
93
|
TBD
|
|
87
94
|
|
|
95
|
+
## License
|
|
96
|
+
|
|
97
|
+
This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.
|
|
98
|
+
|
|
88
99
|
## Acknowledgements
|
|
89
100
|
|
|
90
101
|
This project is built on top of the [Click](https://github.com/pallets/click) library.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# Click Extended
|
|
4
|
+
|
|
5
|
+
An extension of the [Click](https://github.com/pallets/click) library with additional features like aliasing, asynchronous support, an extended decorator system and more.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Aliasing**: Add multiple aliases to a group or command.
|
|
10
|
+
- **Async supprt**: Automatically run both synchronous and asynchronous functions.
|
|
11
|
+
- **Extended decorator system**: Create or use pre-made validation and transformation decorators, inject values into the context and more.
|
|
12
|
+
- **Environment variables**: Automatically validate and inject environment variables into the function.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install click-extended
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
- **Python**: 3.10 or higher
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
TBD
|
|
27
|
+
|
|
28
|
+
## Contributing
|
|
29
|
+
|
|
30
|
+
TBD
|
|
31
|
+
|
|
32
|
+
## License
|
|
33
|
+
|
|
34
|
+
This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.
|
|
35
|
+
|
|
36
|
+
## Acknowledgements
|
|
37
|
+
|
|
38
|
+
This project is built on top of the [Click](https://github.com/pallets/click) library.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: click_extended
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: An extension to Click with additional features like automatic async support, aliasing and a modular decorator system.
|
|
5
5
|
Author-email: Marcus Fredriksson <marcus@marcusfredriksson.com>
|
|
6
6
|
License: MIT License
|
|
@@ -65,7 +65,14 @@ Dynamic: license-file
|
|
|
65
65
|
|
|
66
66
|
# Click Extended
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
An extension of the [Click](https://github.com/pallets/click) library with additional features like aliasing, asynchronous support, an extended decorator system and more.
|
|
69
|
+
|
|
70
|
+
## Features
|
|
71
|
+
|
|
72
|
+
- **Aliasing**: Add multiple aliases to a group or command.
|
|
73
|
+
- **Async supprt**: Automatically run both synchronous and asynchronous functions.
|
|
74
|
+
- **Extended decorator system**: Create or use pre-made validation and transformation decorators, inject values into the context and more.
|
|
75
|
+
- **Environment variables**: Automatically validate and inject environment variables into the function.
|
|
69
76
|
|
|
70
77
|
## Installation
|
|
71
78
|
|
|
@@ -75,16 +82,20 @@ pip install click-extended
|
|
|
75
82
|
|
|
76
83
|
## Requirements
|
|
77
84
|
|
|
78
|
-
|
|
85
|
+
- **Python**: 3.10 or higher
|
|
79
86
|
|
|
80
|
-
##
|
|
87
|
+
## Usage
|
|
81
88
|
|
|
82
|
-
|
|
89
|
+
TBD
|
|
83
90
|
|
|
84
91
|
## Contributing
|
|
85
92
|
|
|
86
93
|
TBD
|
|
87
94
|
|
|
95
|
+
## License
|
|
96
|
+
|
|
97
|
+
This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.
|
|
98
|
+
|
|
88
99
|
## Acknowledgements
|
|
89
100
|
|
|
90
101
|
This project is built on top of the [Click](https://github.com/pallets/click) library.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "click_extended"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.3"
|
|
4
4
|
description = "An extension to Click with additional features like automatic async support, aliasing and a modular decorator system."
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Marcus Fredriksson", email = "marcus@marcusfredriksson.com" },
|
|
@@ -0,0 +1,749 @@
|
|
|
1
|
+
"""Tests for the GlobalNode class and global node registration."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from click_extended.core._global_node import GlobalNode
|
|
8
|
+
from click_extended.core._parent_node import ParentNode
|
|
9
|
+
from click_extended.core._root_node import RootNode
|
|
10
|
+
from click_extended.core._tree import Tree, get_pending_nodes, queue_global
|
|
11
|
+
from click_extended.core.tag import Tag
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DummyRootNode(RootNode):
|
|
15
|
+
"""Dummy RootNode for testing."""
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def _get_click_decorator(cls) -> Any:
|
|
19
|
+
"""Return a dummy decorator."""
|
|
20
|
+
|
|
21
|
+
def outer(**kwargs: Any) -> Any:
|
|
22
|
+
def inner(f: Any) -> Any:
|
|
23
|
+
return f
|
|
24
|
+
|
|
25
|
+
return inner
|
|
26
|
+
|
|
27
|
+
return outer
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def _get_click_cls(cls) -> Any:
|
|
31
|
+
"""Return a dummy class."""
|
|
32
|
+
return object
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class DummyParentNode(ParentNode):
|
|
36
|
+
"""Dummy ParentNode for testing."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, name: str, **kwargs: Any) -> None:
|
|
39
|
+
super().__init__(name=name, **kwargs)
|
|
40
|
+
|
|
41
|
+
def get_raw_value(self) -> Any:
|
|
42
|
+
"""Return a test value."""
|
|
43
|
+
return "test_value"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ConcreteGlobalNode(GlobalNode):
|
|
47
|
+
"""Concrete GlobalNode implementation for testing."""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self, name: str | None = None, delay: bool = False, **kwargs: Any
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Initialize with optional return value."""
|
|
53
|
+
super().__init__(name=name, delay=delay)
|
|
54
|
+
self.return_value = kwargs.get("return_value", None)
|
|
55
|
+
self.call_count = 0
|
|
56
|
+
self.last_call_args: dict[str, Any] = {}
|
|
57
|
+
|
|
58
|
+
def process(
|
|
59
|
+
self,
|
|
60
|
+
tree: Tree,
|
|
61
|
+
root: RootNode,
|
|
62
|
+
parents: list[ParentNode],
|
|
63
|
+
tags: dict[str, Tag],
|
|
64
|
+
globals: list[GlobalNode],
|
|
65
|
+
call_args: tuple[Any, ...],
|
|
66
|
+
call_kwargs: dict[str, Any],
|
|
67
|
+
*args: Any,
|
|
68
|
+
**kwargs: Any,
|
|
69
|
+
) -> Any:
|
|
70
|
+
"""Record the call and return configured value."""
|
|
71
|
+
self.call_count += 1
|
|
72
|
+
self.last_call_args = {
|
|
73
|
+
"tree": tree,
|
|
74
|
+
"root": root,
|
|
75
|
+
"parents": parents,
|
|
76
|
+
"tags": tags,
|
|
77
|
+
"globals": globals,
|
|
78
|
+
"call_args": call_args,
|
|
79
|
+
"call_kwargs": call_kwargs,
|
|
80
|
+
"args": args,
|
|
81
|
+
"kwargs": kwargs,
|
|
82
|
+
}
|
|
83
|
+
return self.return_value
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class TestGlobalNodeInitialization:
|
|
87
|
+
"""Tests for GlobalNode initialization."""
|
|
88
|
+
|
|
89
|
+
def test_init_with_name(self) -> None:
|
|
90
|
+
"""Test GlobalNode initialization with injection name."""
|
|
91
|
+
node = ConcreteGlobalNode(name="test_var")
|
|
92
|
+
|
|
93
|
+
assert node.inject_name == "test_var"
|
|
94
|
+
assert node.delay is False
|
|
95
|
+
assert node.call_count == 0
|
|
96
|
+
|
|
97
|
+
def test_init_without_name(self) -> None:
|
|
98
|
+
"""Test GlobalNode initialization in observer mode."""
|
|
99
|
+
node = ConcreteGlobalNode()
|
|
100
|
+
|
|
101
|
+
assert node.inject_name is None
|
|
102
|
+
assert node.delay is False
|
|
103
|
+
|
|
104
|
+
def test_init_with_delay(self) -> None:
|
|
105
|
+
"""Test GlobalNode initialization with delay parameter."""
|
|
106
|
+
node = ConcreteGlobalNode(delay=True)
|
|
107
|
+
|
|
108
|
+
assert node.inject_name is None
|
|
109
|
+
assert node.delay is True
|
|
110
|
+
|
|
111
|
+
def test_init_with_name_and_delay(self) -> None:
|
|
112
|
+
"""Test GlobalNode initialization with both name and delay."""
|
|
113
|
+
node = ConcreteGlobalNode(name="var", delay=True)
|
|
114
|
+
|
|
115
|
+
assert node.inject_name == "var"
|
|
116
|
+
assert node.delay is True
|
|
117
|
+
|
|
118
|
+
def test_init_creates_unique_internal_name(self) -> None:
|
|
119
|
+
"""Test that GlobalNode creates unique internal names."""
|
|
120
|
+
node1 = ConcreteGlobalNode()
|
|
121
|
+
node2 = ConcreteGlobalNode()
|
|
122
|
+
|
|
123
|
+
assert node1.name != node2.name
|
|
124
|
+
assert "_global_" in node1.name
|
|
125
|
+
assert "_global_" in node2.name
|
|
126
|
+
|
|
127
|
+
def test_init_uses_inject_name_as_internal_name(self) -> None:
|
|
128
|
+
"""Test that inject_name is used as internal name when provided."""
|
|
129
|
+
node = ConcreteGlobalNode(name="test_var")
|
|
130
|
+
|
|
131
|
+
assert node.name == "test_var"
|
|
132
|
+
assert node.inject_name == "test_var"
|
|
133
|
+
|
|
134
|
+
def test_init_stores_process_kwargs(self) -> None:
|
|
135
|
+
"""Test that initialization stores process_kwargs."""
|
|
136
|
+
node = ConcreteGlobalNode(return_value="test")
|
|
137
|
+
|
|
138
|
+
assert node.process_kwargs == {}
|
|
139
|
+
assert node.return_value == "test"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class TestGlobalNodeProcess:
|
|
143
|
+
"""Tests for GlobalNode.process method."""
|
|
144
|
+
|
|
145
|
+
def test_process_is_abstract(self) -> None:
|
|
146
|
+
"""Test that GlobalNode.process is abstract."""
|
|
147
|
+
|
|
148
|
+
with pytest.raises(TypeError):
|
|
149
|
+
GlobalNode() # type: ignore
|
|
150
|
+
|
|
151
|
+
def test_process_receives_tree(self) -> None:
|
|
152
|
+
"""Test that process receives the tree structure."""
|
|
153
|
+
tree = Tree()
|
|
154
|
+
root = DummyRootNode(name="root")
|
|
155
|
+
tree.root = root
|
|
156
|
+
|
|
157
|
+
node = ConcreteGlobalNode()
|
|
158
|
+
node.process(tree, root, [], {}, [], (), {})
|
|
159
|
+
|
|
160
|
+
assert node.last_call_args["tree"] is tree
|
|
161
|
+
|
|
162
|
+
def test_process_receives_root(self) -> None:
|
|
163
|
+
"""Test that process receives the root node."""
|
|
164
|
+
tree = Tree()
|
|
165
|
+
root = DummyRootNode(name="root")
|
|
166
|
+
|
|
167
|
+
node = ConcreteGlobalNode()
|
|
168
|
+
node.process(tree, root, [], {}, [], (), {})
|
|
169
|
+
|
|
170
|
+
assert node.last_call_args["root"] is root
|
|
171
|
+
|
|
172
|
+
def test_process_receives_parents(self) -> None:
|
|
173
|
+
"""Test that process receives parent nodes list."""
|
|
174
|
+
tree = Tree()
|
|
175
|
+
root = DummyRootNode(name="root")
|
|
176
|
+
parent1 = DummyParentNode(name="parent1")
|
|
177
|
+
parent2 = DummyParentNode(name="parent2")
|
|
178
|
+
|
|
179
|
+
node = ConcreteGlobalNode()
|
|
180
|
+
node.process(tree, root, [parent1, parent2], {}, [], (), {})
|
|
181
|
+
|
|
182
|
+
assert len(node.last_call_args["parents"]) == 2
|
|
183
|
+
assert node.last_call_args["parents"][0] is parent1
|
|
184
|
+
assert node.last_call_args["parents"][1] is parent2
|
|
185
|
+
|
|
186
|
+
def test_process_receives_tags(self) -> None:
|
|
187
|
+
"""Test that process receives tags dictionary."""
|
|
188
|
+
tree = Tree()
|
|
189
|
+
root = DummyRootNode(name="root")
|
|
190
|
+
tag = Tag(name="test_tag")
|
|
191
|
+
|
|
192
|
+
node = ConcreteGlobalNode()
|
|
193
|
+
node.process(tree, root, [], {"test_tag": tag}, [], (), {})
|
|
194
|
+
|
|
195
|
+
assert "test_tag" in node.last_call_args["tags"]
|
|
196
|
+
assert node.last_call_args["tags"]["test_tag"] is tag
|
|
197
|
+
|
|
198
|
+
def test_process_receives_globals_list(self) -> None:
|
|
199
|
+
"""Test that process receives globals list including itself."""
|
|
200
|
+
tree = Tree()
|
|
201
|
+
root = DummyRootNode(name="root")
|
|
202
|
+
node1 = ConcreteGlobalNode(name="node1")
|
|
203
|
+
node2 = ConcreteGlobalNode(name="node2")
|
|
204
|
+
|
|
205
|
+
node1.process(tree, root, [], {}, [node1, node2], (), {})
|
|
206
|
+
|
|
207
|
+
assert len(node1.last_call_args["globals"]) == 2
|
|
208
|
+
|
|
209
|
+
def test_process_receives_call_args(self) -> None:
|
|
210
|
+
"""Test that process receives call arguments."""
|
|
211
|
+
tree = Tree()
|
|
212
|
+
root = DummyRootNode(name="root")
|
|
213
|
+
call_args = ("arg1", "arg2")
|
|
214
|
+
|
|
215
|
+
node = ConcreteGlobalNode()
|
|
216
|
+
node.process(tree, root, [], {}, [], call_args, {})
|
|
217
|
+
|
|
218
|
+
assert node.last_call_args["call_args"] == call_args
|
|
219
|
+
|
|
220
|
+
def test_process_receives_call_kwargs(self) -> None:
|
|
221
|
+
"""Test that process receives call keyword arguments."""
|
|
222
|
+
tree = Tree()
|
|
223
|
+
root = DummyRootNode(name="root")
|
|
224
|
+
call_kwargs = {"key1": "value1", "key2": "value2"}
|
|
225
|
+
|
|
226
|
+
node = ConcreteGlobalNode()
|
|
227
|
+
node.process(tree, root, [], {}, [], (), call_kwargs)
|
|
228
|
+
|
|
229
|
+
assert node.last_call_args["call_kwargs"] == call_kwargs
|
|
230
|
+
|
|
231
|
+
def test_process_receives_additional_args(self) -> None:
|
|
232
|
+
"""Test that process receives additional positional arguments."""
|
|
233
|
+
tree = Tree()
|
|
234
|
+
root = DummyRootNode(name="root")
|
|
235
|
+
|
|
236
|
+
node = ConcreteGlobalNode()
|
|
237
|
+
node.process(tree, root, [], {}, [], (), {}, "extra1", "extra2")
|
|
238
|
+
|
|
239
|
+
assert node.last_call_args["args"] == ("extra1", "extra2")
|
|
240
|
+
|
|
241
|
+
def test_process_receives_additional_kwargs(self) -> None:
|
|
242
|
+
"""Test that process receives additional keyword arguments."""
|
|
243
|
+
tree = Tree()
|
|
244
|
+
root = DummyRootNode(name="root")
|
|
245
|
+
|
|
246
|
+
node = ConcreteGlobalNode()
|
|
247
|
+
node.process(tree, root, [], {}, [], (), {}, extra_key="extra_value")
|
|
248
|
+
|
|
249
|
+
assert node.last_call_args["kwargs"]["extra_key"] == "extra_value"
|
|
250
|
+
|
|
251
|
+
def test_process_return_value(self) -> None:
|
|
252
|
+
"""Test that process returns configured value."""
|
|
253
|
+
tree = Tree()
|
|
254
|
+
root = DummyRootNode(name="root")
|
|
255
|
+
|
|
256
|
+
node = ConcreteGlobalNode(return_value={"test": "data"})
|
|
257
|
+
result = node.process(tree, root, [], {}, [], (), {})
|
|
258
|
+
|
|
259
|
+
assert result == {"test": "data"}
|
|
260
|
+
|
|
261
|
+
def test_process_increments_call_count(self) -> None:
|
|
262
|
+
"""Test that process increments call count."""
|
|
263
|
+
tree = Tree()
|
|
264
|
+
root = DummyRootNode(name="root")
|
|
265
|
+
|
|
266
|
+
node = ConcreteGlobalNode()
|
|
267
|
+
|
|
268
|
+
assert node.call_count == 0
|
|
269
|
+
node.process(tree, root, [], {}, [], (), {})
|
|
270
|
+
assert node.call_count == 1
|
|
271
|
+
node.process(tree, root, [], {}, [], (), {})
|
|
272
|
+
assert node.call_count == 2
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class TestGlobalNodeAsDecorator:
|
|
276
|
+
"""Tests for GlobalNode.as_decorator class method."""
|
|
277
|
+
|
|
278
|
+
def setup_method(self) -> None:
|
|
279
|
+
"""Clear pending nodes before each test."""
|
|
280
|
+
get_pending_nodes()
|
|
281
|
+
|
|
282
|
+
def test_as_decorator_returns_callable(self) -> None:
|
|
283
|
+
"""Test that as_decorator returns a callable."""
|
|
284
|
+
decorator = ConcreteGlobalNode.as_decorator()
|
|
285
|
+
|
|
286
|
+
assert callable(decorator)
|
|
287
|
+
|
|
288
|
+
def test_as_decorator_with_name(self) -> None:
|
|
289
|
+
"""Test as_decorator with injection name."""
|
|
290
|
+
decorator = ConcreteGlobalNode.as_decorator("test_var")
|
|
291
|
+
|
|
292
|
+
def test_func() -> None:
|
|
293
|
+
pass
|
|
294
|
+
|
|
295
|
+
result = decorator(test_func)
|
|
296
|
+
assert callable(result)
|
|
297
|
+
|
|
298
|
+
def test_as_decorator_without_name(self) -> None:
|
|
299
|
+
"""Test as_decorator in observer mode."""
|
|
300
|
+
decorator = ConcreteGlobalNode.as_decorator()
|
|
301
|
+
|
|
302
|
+
def test_func() -> None:
|
|
303
|
+
pass
|
|
304
|
+
|
|
305
|
+
result = decorator(test_func)
|
|
306
|
+
assert callable(result)
|
|
307
|
+
|
|
308
|
+
def test_as_decorator_with_delay(self) -> None:
|
|
309
|
+
"""Test as_decorator with delay parameter."""
|
|
310
|
+
decorator = ConcreteGlobalNode.as_decorator(delay=True)
|
|
311
|
+
|
|
312
|
+
def test_func() -> None:
|
|
313
|
+
pass
|
|
314
|
+
|
|
315
|
+
result = decorator(test_func)
|
|
316
|
+
assert callable(result)
|
|
317
|
+
|
|
318
|
+
def test_as_decorator_with_name_and_delay(self) -> None:
|
|
319
|
+
"""Test as_decorator with both name and delay."""
|
|
320
|
+
decorator = ConcreteGlobalNode.as_decorator("var", delay=True)
|
|
321
|
+
|
|
322
|
+
def test_func() -> None:
|
|
323
|
+
pass
|
|
324
|
+
|
|
325
|
+
result = decorator(test_func)
|
|
326
|
+
assert callable(result)
|
|
327
|
+
|
|
328
|
+
def test_as_decorator_queues_node(self) -> None:
|
|
329
|
+
"""Test that as_decorator queues the global node."""
|
|
330
|
+
decorator = ConcreteGlobalNode.as_decorator("test")
|
|
331
|
+
|
|
332
|
+
def test_func() -> None:
|
|
333
|
+
pass
|
|
334
|
+
|
|
335
|
+
decorator(test_func)
|
|
336
|
+
pending = get_pending_nodes()
|
|
337
|
+
|
|
338
|
+
assert len(pending) == 1
|
|
339
|
+
assert pending[0][0] == "global"
|
|
340
|
+
assert isinstance(pending[0][1], ConcreteGlobalNode)
|
|
341
|
+
|
|
342
|
+
def test_as_decorator_stores_kwargs(self) -> None:
|
|
343
|
+
"""Test that as_decorator stores additional kwargs."""
|
|
344
|
+
decorator = ConcreteGlobalNode.as_decorator(
|
|
345
|
+
"test", custom_param="value"
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
def test_func() -> None:
|
|
349
|
+
pass
|
|
350
|
+
|
|
351
|
+
decorator(test_func)
|
|
352
|
+
pending = get_pending_nodes()
|
|
353
|
+
|
|
354
|
+
node = pending[0][1]
|
|
355
|
+
assert isinstance(node, ConcreteGlobalNode)
|
|
356
|
+
assert node.process_kwargs["custom_param"] == "value"
|
|
357
|
+
|
|
358
|
+
def test_as_decorator_preserves_function(self) -> None:
|
|
359
|
+
"""Test that decorated function remains callable."""
|
|
360
|
+
|
|
361
|
+
@ConcreteGlobalNode.as_decorator("test")
|
|
362
|
+
def test_func(x: int) -> int:
|
|
363
|
+
return x * 2
|
|
364
|
+
|
|
365
|
+
assert test_func(5) == 10
|
|
366
|
+
|
|
367
|
+
def test_as_decorator_preserves_function_metadata(self) -> None:
|
|
368
|
+
"""Test that decorator preserves function metadata."""
|
|
369
|
+
|
|
370
|
+
@ConcreteGlobalNode.as_decorator("test")
|
|
371
|
+
def test_func() -> None:
|
|
372
|
+
"""Test function docstring."""
|
|
373
|
+
pass
|
|
374
|
+
|
|
375
|
+
assert test_func.__name__ == "test_func"
|
|
376
|
+
assert test_func.__doc__ == "Test function docstring."
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
class TestQueueGlobal:
|
|
380
|
+
"""Tests for queue_global function."""
|
|
381
|
+
|
|
382
|
+
def setup_method(self) -> None:
|
|
383
|
+
"""Clear pending nodes before each test."""
|
|
384
|
+
get_pending_nodes()
|
|
385
|
+
|
|
386
|
+
def test_queue_global_adds_to_pending(self) -> None:
|
|
387
|
+
"""Test that queue_global adds a global node to pending queue."""
|
|
388
|
+
node = ConcreteGlobalNode(name="test")
|
|
389
|
+
queue_global(node)
|
|
390
|
+
|
|
391
|
+
pending = get_pending_nodes()
|
|
392
|
+
assert len(pending) == 1
|
|
393
|
+
assert pending[0] == ("global", node)
|
|
394
|
+
|
|
395
|
+
def test_queue_global_preserves_node_reference(self) -> None:
|
|
396
|
+
"""Test that queued global node maintains its identity."""
|
|
397
|
+
node = ConcreteGlobalNode(name="test")
|
|
398
|
+
queue_global(node)
|
|
399
|
+
|
|
400
|
+
pending = get_pending_nodes()
|
|
401
|
+
retrieved_node = pending[0][1]
|
|
402
|
+
|
|
403
|
+
assert retrieved_node is node
|
|
404
|
+
|
|
405
|
+
def test_queue_global_multiple_nodes(self) -> None:
|
|
406
|
+
"""Test that multiple global nodes can be queued."""
|
|
407
|
+
node1 = ConcreteGlobalNode(name="node1")
|
|
408
|
+
node2 = ConcreteGlobalNode(name="node2")
|
|
409
|
+
|
|
410
|
+
queue_global(node1)
|
|
411
|
+
queue_global(node2)
|
|
412
|
+
|
|
413
|
+
pending = get_pending_nodes()
|
|
414
|
+
assert len(pending) == 2
|
|
415
|
+
assert pending[0][1] is node1
|
|
416
|
+
assert pending[1][1] is node2
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
class TestTreeGlobalsList:
|
|
420
|
+
"""Tests for Tree.globals list."""
|
|
421
|
+
|
|
422
|
+
def test_tree_has_globals_list(self) -> None:
|
|
423
|
+
"""Test that Tree initializes with globals list."""
|
|
424
|
+
tree = Tree()
|
|
425
|
+
|
|
426
|
+
assert hasattr(tree, "globals")
|
|
427
|
+
assert isinstance(tree.globals, list)
|
|
428
|
+
assert len(tree.globals) == 0
|
|
429
|
+
|
|
430
|
+
def test_tree_globals_list_is_mutable(self) -> None:
|
|
431
|
+
"""Test that Tree.globals list can be modified."""
|
|
432
|
+
tree = Tree()
|
|
433
|
+
node = ConcreteGlobalNode(name="test")
|
|
434
|
+
|
|
435
|
+
tree.globals.append(node)
|
|
436
|
+
|
|
437
|
+
assert len(tree.globals) == 1
|
|
438
|
+
assert tree.globals[0] is node
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
class TestTreeRegisterRootWithGlobals:
|
|
442
|
+
"""Tests for Tree.register_root with global nodes."""
|
|
443
|
+
|
|
444
|
+
def setup_method(self) -> None:
|
|
445
|
+
"""Clear pending nodes before each test."""
|
|
446
|
+
get_pending_nodes()
|
|
447
|
+
|
|
448
|
+
def test_register_root_processes_global_nodes(self) -> None:
|
|
449
|
+
"""Test that register_root processes pending global nodes."""
|
|
450
|
+
tree = Tree()
|
|
451
|
+
root = DummyRootNode(name="root")
|
|
452
|
+
node = ConcreteGlobalNode(name="test")
|
|
453
|
+
|
|
454
|
+
queue_global(node)
|
|
455
|
+
tree.register_root(root)
|
|
456
|
+
|
|
457
|
+
assert len(tree.globals) == 1
|
|
458
|
+
assert tree.globals[0] is node
|
|
459
|
+
|
|
460
|
+
def test_register_root_multiple_globals(self) -> None:
|
|
461
|
+
"""Test that register_root processes multiple global nodes."""
|
|
462
|
+
tree = Tree()
|
|
463
|
+
root = DummyRootNode(name="root")
|
|
464
|
+
node1 = ConcreteGlobalNode(name="node1")
|
|
465
|
+
node2 = ConcreteGlobalNode(name="node2")
|
|
466
|
+
node3 = ConcreteGlobalNode(name="node3")
|
|
467
|
+
|
|
468
|
+
queue_global(node1)
|
|
469
|
+
queue_global(node2)
|
|
470
|
+
queue_global(node3)
|
|
471
|
+
tree.register_root(root)
|
|
472
|
+
|
|
473
|
+
assert len(tree.globals) == 3
|
|
474
|
+
assert tree.globals[0] is node1
|
|
475
|
+
assert tree.globals[1] is node2
|
|
476
|
+
assert tree.globals[2] is node3
|
|
477
|
+
|
|
478
|
+
def test_register_root_with_mixed_node_types(self) -> None:
|
|
479
|
+
"""Test register_root with global and parent nodes."""
|
|
480
|
+
tree = Tree()
|
|
481
|
+
root = DummyRootNode(name="root")
|
|
482
|
+
parent = DummyParentNode(name="parent")
|
|
483
|
+
global_node = ConcreteGlobalNode(name="global")
|
|
484
|
+
|
|
485
|
+
from click_extended.core._tree import queue_parent
|
|
486
|
+
|
|
487
|
+
queue_global(global_node)
|
|
488
|
+
queue_parent(parent)
|
|
489
|
+
tree.register_root(root)
|
|
490
|
+
|
|
491
|
+
assert tree.root is root
|
|
492
|
+
assert len(tree.globals) == 1
|
|
493
|
+
assert tree.globals[0] is global_node
|
|
494
|
+
assert root.children is not None
|
|
495
|
+
assert "parent" in root.children
|
|
496
|
+
|
|
497
|
+
def test_register_root_globals_order_preserved(self) -> None:
|
|
498
|
+
"""Test that global nodes maintain decorator order."""
|
|
499
|
+
tree = Tree()
|
|
500
|
+
root = DummyRootNode(name="root")
|
|
501
|
+
|
|
502
|
+
nodes = [ConcreteGlobalNode(name=f"node{i}") for i in range(5)]
|
|
503
|
+
for node in nodes:
|
|
504
|
+
queue_global(node)
|
|
505
|
+
|
|
506
|
+
tree.register_root(root)
|
|
507
|
+
|
|
508
|
+
for i, node in enumerate(nodes):
|
|
509
|
+
assert tree.globals[i] is node
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
class TestGlobalNodeObserverMode:
|
|
513
|
+
"""Tests for GlobalNode observer mode (name=None)."""
|
|
514
|
+
|
|
515
|
+
def test_observer_mode_inject_name_is_none(self) -> None:
|
|
516
|
+
"""Test that observer mode has inject_name=None."""
|
|
517
|
+
node = ConcreteGlobalNode()
|
|
518
|
+
|
|
519
|
+
assert node.inject_name is None
|
|
520
|
+
|
|
521
|
+
def test_observer_mode_can_return_value(self) -> None:
|
|
522
|
+
"""Test that observer mode can return values."""
|
|
523
|
+
tree = Tree()
|
|
524
|
+
root = DummyRootNode(name="root")
|
|
525
|
+
|
|
526
|
+
node = ConcreteGlobalNode(return_value="observed")
|
|
527
|
+
result = node.process(tree, root, [], {}, [], (), {})
|
|
528
|
+
|
|
529
|
+
assert result == "observed"
|
|
530
|
+
|
|
531
|
+
def test_observer_mode_return_value_ignored(self) -> None:
|
|
532
|
+
"""Test that observer mode return value is not injected."""
|
|
533
|
+
node = ConcreteGlobalNode(return_value="data")
|
|
534
|
+
|
|
535
|
+
assert node.inject_name is None
|
|
536
|
+
assert node.return_value == "data"
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
class TestGlobalNodeInjectionMode:
|
|
540
|
+
"""Tests for GlobalNode injection mode (name="var")."""
|
|
541
|
+
|
|
542
|
+
def test_injection_mode_has_inject_name(self) -> None:
|
|
543
|
+
"""Test that injection mode stores inject_name."""
|
|
544
|
+
node = ConcreteGlobalNode(name="my_var")
|
|
545
|
+
|
|
546
|
+
assert node.inject_name == "my_var"
|
|
547
|
+
|
|
548
|
+
def test_injection_mode_with_delay(self) -> None:
|
|
549
|
+
"""Test injection mode with delayed execution."""
|
|
550
|
+
node = ConcreteGlobalNode(name="my_var", delay=True)
|
|
551
|
+
|
|
552
|
+
assert node.inject_name == "my_var"
|
|
553
|
+
assert node.delay is True
|
|
554
|
+
|
|
555
|
+
def test_injection_mode_return_value_available(self) -> None:
|
|
556
|
+
"""Test that injection mode return value is available."""
|
|
557
|
+
tree = Tree()
|
|
558
|
+
root = DummyRootNode(name="root")
|
|
559
|
+
|
|
560
|
+
node = ConcreteGlobalNode(name="data_var", return_value={"key": "val"})
|
|
561
|
+
result = node.process(tree, root, [], {}, [], (), {})
|
|
562
|
+
|
|
563
|
+
assert result == {"key": "val"}
|
|
564
|
+
assert node.inject_name == "data_var"
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
class TestGlobalNodeDelayParameter:
|
|
568
|
+
"""Tests for GlobalNode delay parameter."""
|
|
569
|
+
|
|
570
|
+
def test_delay_false_by_default(self) -> None:
|
|
571
|
+
"""Test that delay defaults to False."""
|
|
572
|
+
node = ConcreteGlobalNode()
|
|
573
|
+
|
|
574
|
+
assert node.delay is False
|
|
575
|
+
|
|
576
|
+
def test_delay_true_when_specified(self) -> None:
|
|
577
|
+
"""Test that delay can be set to True."""
|
|
578
|
+
node = ConcreteGlobalNode(delay=True)
|
|
579
|
+
|
|
580
|
+
assert node.delay is True
|
|
581
|
+
|
|
582
|
+
def test_delay_with_name(self) -> None:
|
|
583
|
+
"""Test delay parameter with injection name."""
|
|
584
|
+
node = ConcreteGlobalNode(name="var", delay=True)
|
|
585
|
+
|
|
586
|
+
assert node.inject_name == "var"
|
|
587
|
+
assert node.delay is True
|
|
588
|
+
|
|
589
|
+
def test_delay_affects_timing(self) -> None:
|
|
590
|
+
"""Test that delay value is accessible for timing logic."""
|
|
591
|
+
early_node = ConcreteGlobalNode(delay=False)
|
|
592
|
+
late_node = ConcreteGlobalNode(delay=True)
|
|
593
|
+
|
|
594
|
+
assert early_node.delay is False
|
|
595
|
+
assert late_node.delay is True
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
class TestGlobalNodeEdgeCases:
|
|
599
|
+
"""Tests for GlobalNode edge cases."""
|
|
600
|
+
|
|
601
|
+
def test_empty_parents_list(self) -> None:
|
|
602
|
+
"""Test process with empty parents list."""
|
|
603
|
+
tree = Tree()
|
|
604
|
+
root = DummyRootNode(name="root")
|
|
605
|
+
|
|
606
|
+
node = ConcreteGlobalNode()
|
|
607
|
+
node.process(tree, root, [], {}, [], (), {})
|
|
608
|
+
|
|
609
|
+
assert node.last_call_args["parents"] == []
|
|
610
|
+
|
|
611
|
+
def test_empty_tags_dict(self) -> None:
|
|
612
|
+
"""Test process with empty tags dictionary."""
|
|
613
|
+
tree = Tree()
|
|
614
|
+
root = DummyRootNode(name="root")
|
|
615
|
+
|
|
616
|
+
node = ConcreteGlobalNode()
|
|
617
|
+
node.process(tree, root, [], {}, [], (), {})
|
|
618
|
+
|
|
619
|
+
assert node.last_call_args["tags"] == {}
|
|
620
|
+
|
|
621
|
+
def test_empty_globals_list(self) -> None:
|
|
622
|
+
"""Test process with empty globals list."""
|
|
623
|
+
tree = Tree()
|
|
624
|
+
root = DummyRootNode(name="root")
|
|
625
|
+
|
|
626
|
+
node = ConcreteGlobalNode()
|
|
627
|
+
node.process(tree, root, [], {}, [], (), {})
|
|
628
|
+
|
|
629
|
+
assert node.last_call_args["globals"] == []
|
|
630
|
+
|
|
631
|
+
def test_none_return_value(self) -> None:
|
|
632
|
+
"""Test that None can be returned from process."""
|
|
633
|
+
tree = Tree()
|
|
634
|
+
root = DummyRootNode(name="root")
|
|
635
|
+
|
|
636
|
+
node = ConcreteGlobalNode(return_value=None)
|
|
637
|
+
result = node.process(tree, root, [], {}, [], (), {})
|
|
638
|
+
|
|
639
|
+
assert result is None
|
|
640
|
+
|
|
641
|
+
def test_complex_return_value(self) -> None:
|
|
642
|
+
"""Test that complex objects can be returned."""
|
|
643
|
+
tree = Tree()
|
|
644
|
+
root = DummyRootNode(name="root")
|
|
645
|
+
|
|
646
|
+
complex_data: dict[str, Any] = {
|
|
647
|
+
"tree": tree,
|
|
648
|
+
"list": [1, 2, 3],
|
|
649
|
+
"nested": {"key": "value"},
|
|
650
|
+
}
|
|
651
|
+
node = ConcreteGlobalNode(return_value=complex_data)
|
|
652
|
+
result = node.process(tree, root, [], {}, [], (), {})
|
|
653
|
+
|
|
654
|
+
assert result is complex_data
|
|
655
|
+
assert result["tree"] is tree
|
|
656
|
+
|
|
657
|
+
def test_multiple_process_calls(self) -> None:
|
|
658
|
+
"""Test that process can be called multiple times."""
|
|
659
|
+
tree = Tree()
|
|
660
|
+
root = DummyRootNode(name="root")
|
|
661
|
+
|
|
662
|
+
node = ConcreteGlobalNode(return_value=1)
|
|
663
|
+
|
|
664
|
+
result1 = node.process(tree, root, [], {}, [], (), {})
|
|
665
|
+
assert result1 == 1
|
|
666
|
+
assert node.call_count == 1
|
|
667
|
+
|
|
668
|
+
node.return_value = 2
|
|
669
|
+
result2 = node.process(tree, root, [], {}, [], (), {})
|
|
670
|
+
assert result2 == 2
|
|
671
|
+
assert node.call_count == 2
|
|
672
|
+
|
|
673
|
+
def test_inject_name_special_characters(self) -> None:
|
|
674
|
+
"""Test inject_name with underscores and numbers."""
|
|
675
|
+
node = ConcreteGlobalNode(name="_test_var_123")
|
|
676
|
+
|
|
677
|
+
assert node.inject_name == "_test_var_123"
|
|
678
|
+
assert node.name == "_test_var_123"
|
|
679
|
+
|
|
680
|
+
def test_process_with_large_parents_list(self) -> None:
|
|
681
|
+
"""Test process with many parent nodes."""
|
|
682
|
+
tree = Tree()
|
|
683
|
+
root = DummyRootNode(name="root")
|
|
684
|
+
parents: list[ParentNode] = [
|
|
685
|
+
DummyParentNode(name=f"p{i}") for i in range(100)
|
|
686
|
+
]
|
|
687
|
+
|
|
688
|
+
node = ConcreteGlobalNode()
|
|
689
|
+
node.process(tree, root, parents, {}, [], (), {})
|
|
690
|
+
|
|
691
|
+
assert len(node.last_call_args["parents"]) == 100
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
class TestGlobalNodeIntegration:
|
|
695
|
+
"""Integration tests for GlobalNode with Tree."""
|
|
696
|
+
|
|
697
|
+
def setup_method(self) -> None:
|
|
698
|
+
"""Clear pending nodes before each test."""
|
|
699
|
+
get_pending_nodes()
|
|
700
|
+
|
|
701
|
+
def test_full_registration_flow(self) -> None:
|
|
702
|
+
"""Test complete flow of global node registration."""
|
|
703
|
+
tree = Tree()
|
|
704
|
+
root = DummyRootNode(name="root")
|
|
705
|
+
|
|
706
|
+
@ConcreteGlobalNode.as_decorator("test_var")
|
|
707
|
+
def dummy_func() -> None: # type: ignore
|
|
708
|
+
pass
|
|
709
|
+
|
|
710
|
+
tree.register_root(root)
|
|
711
|
+
|
|
712
|
+
assert len(tree.globals) == 1
|
|
713
|
+
assert isinstance(tree.globals[0], ConcreteGlobalNode)
|
|
714
|
+
assert tree.globals[0].inject_name == "test_var"
|
|
715
|
+
|
|
716
|
+
def test_multiple_globals_registration(self) -> None:
|
|
717
|
+
"""Test registration of multiple global nodes."""
|
|
718
|
+
tree = Tree()
|
|
719
|
+
root = DummyRootNode(name="root")
|
|
720
|
+
|
|
721
|
+
@ConcreteGlobalNode.as_decorator("var1")
|
|
722
|
+
@ConcreteGlobalNode.as_decorator("var2", delay=True)
|
|
723
|
+
@ConcreteGlobalNode.as_decorator()
|
|
724
|
+
def dummy_func() -> None: # type: ignore
|
|
725
|
+
pass
|
|
726
|
+
|
|
727
|
+
tree.register_root(root)
|
|
728
|
+
|
|
729
|
+
assert len(tree.globals) == 3
|
|
730
|
+
assert tree.globals[0].inject_name is None
|
|
731
|
+
assert tree.globals[1].inject_name == "var2"
|
|
732
|
+
assert tree.globals[2].inject_name == "var1"
|
|
733
|
+
|
|
734
|
+
def test_globals_with_parents(self) -> None:
|
|
735
|
+
"""Test global nodes registered alongside parent nodes."""
|
|
736
|
+
from click_extended.core._tree import queue_parent
|
|
737
|
+
|
|
738
|
+
tree = Tree()
|
|
739
|
+
root = DummyRootNode(name="root")
|
|
740
|
+
parent = DummyParentNode(name="option")
|
|
741
|
+
|
|
742
|
+
queue_global(ConcreteGlobalNode(name="global1"))
|
|
743
|
+
queue_parent(parent)
|
|
744
|
+
queue_global(ConcreteGlobalNode(name="global2"))
|
|
745
|
+
|
|
746
|
+
tree.register_root(root)
|
|
747
|
+
|
|
748
|
+
assert len(tree.globals) == 2
|
|
749
|
+
assert "option" in root.children
|
|
@@ -5,12 +5,14 @@ from typing import Any
|
|
|
5
5
|
import pytest
|
|
6
6
|
|
|
7
7
|
from click_extended.core._child_node import ChildNode
|
|
8
|
+
from click_extended.core._global_node import GlobalNode
|
|
8
9
|
from click_extended.core._parent_node import ParentNode
|
|
9
10
|
from click_extended.core._root_node import RootNode
|
|
10
11
|
from click_extended.core._tree import (
|
|
11
12
|
Tree,
|
|
12
13
|
get_pending_nodes,
|
|
13
14
|
queue_child,
|
|
15
|
+
queue_global,
|
|
14
16
|
queue_parent,
|
|
15
17
|
)
|
|
16
18
|
from click_extended.errors import (
|
|
@@ -61,6 +63,32 @@ class DummyChildNode(ChildNode):
|
|
|
61
63
|
return value
|
|
62
64
|
|
|
63
65
|
|
|
66
|
+
class DummyGlobalNode(GlobalNode):
|
|
67
|
+
"""Dummy GlobalNode for testing."""
|
|
68
|
+
|
|
69
|
+
def __init__(
|
|
70
|
+
self, name: str | None = None, delay: bool = False, **kwargs: Any
|
|
71
|
+
) -> None:
|
|
72
|
+
"""Initialize with optional return value."""
|
|
73
|
+
super().__init__(name=name, delay=delay)
|
|
74
|
+
self.return_value = kwargs.get("return_value", None)
|
|
75
|
+
|
|
76
|
+
def process(
|
|
77
|
+
self,
|
|
78
|
+
tree: Tree,
|
|
79
|
+
root: "RootNode",
|
|
80
|
+
parents: list["ParentNode"],
|
|
81
|
+
tags: dict[str, Any],
|
|
82
|
+
globals: list["GlobalNode"],
|
|
83
|
+
call_args: tuple[Any, ...],
|
|
84
|
+
call_kwargs: dict[str, Any],
|
|
85
|
+
*args: Any,
|
|
86
|
+
**kwargs: Any,
|
|
87
|
+
) -> Any:
|
|
88
|
+
"""Return configured value."""
|
|
89
|
+
return self.return_value
|
|
90
|
+
|
|
91
|
+
|
|
64
92
|
class TestTreeInitialization:
|
|
65
93
|
"""Tests for Tree initialization."""
|
|
66
94
|
|
|
@@ -70,6 +98,12 @@ class TestTreeInitialization:
|
|
|
70
98
|
assert tree.root is None
|
|
71
99
|
assert tree.recent is None
|
|
72
100
|
|
|
101
|
+
def test_init_creates_empty_globals_list(self) -> None:
|
|
102
|
+
"""Test that Tree initializes with empty globals list."""
|
|
103
|
+
tree = Tree()
|
|
104
|
+
assert isinstance(tree.globals, list)
|
|
105
|
+
assert len(tree.globals) == 0
|
|
106
|
+
|
|
73
107
|
def test_multiple_trees_are_independent(self) -> None:
|
|
74
108
|
"""Test that multiple Tree instances are independent."""
|
|
75
109
|
tree1 = Tree()
|
|
@@ -108,6 +142,15 @@ class TestPendingNodesQueue:
|
|
|
108
142
|
assert len(pending) == 1
|
|
109
143
|
assert pending[0] == ("child", child)
|
|
110
144
|
|
|
145
|
+
def test_queue_global_adds_to_pending(self) -> None:
|
|
146
|
+
"""Test that queue_global adds a global node to pending queue."""
|
|
147
|
+
global_node = DummyGlobalNode(name="test_global")
|
|
148
|
+
queue_global(global_node)
|
|
149
|
+
|
|
150
|
+
pending = get_pending_nodes()
|
|
151
|
+
assert len(pending) == 1
|
|
152
|
+
assert pending[0] == ("global", global_node)
|
|
153
|
+
|
|
111
154
|
def test_get_pending_nodes_clears_queue(self) -> None:
|
|
112
155
|
"""Test that get_pending_nodes clears the queue after retrieval."""
|
|
113
156
|
parent = DummyParentNode(name="test_parent")
|
|
@@ -135,6 +178,22 @@ class TestPendingNodesQueue:
|
|
|
135
178
|
assert pending[1] == ("child", child1)
|
|
136
179
|
assert pending[2] == ("child", child2)
|
|
137
180
|
|
|
181
|
+
def test_mixed_node_types_queued_in_order(self) -> None:
|
|
182
|
+
"""Test that mixed node types are queued in order."""
|
|
183
|
+
parent = DummyParentNode(name="parent")
|
|
184
|
+
child = DummyChildNode(name="child")
|
|
185
|
+
global_node = DummyGlobalNode(name="global")
|
|
186
|
+
|
|
187
|
+
queue_global(global_node)
|
|
188
|
+
queue_parent(parent)
|
|
189
|
+
queue_child(child)
|
|
190
|
+
|
|
191
|
+
pending = get_pending_nodes()
|
|
192
|
+
assert len(pending) == 3
|
|
193
|
+
assert pending[0] == ("global", global_node)
|
|
194
|
+
assert pending[1] == ("parent", parent)
|
|
195
|
+
assert pending[2] == ("child", child)
|
|
196
|
+
|
|
138
197
|
def test_queue_preserves_node_references(self) -> None:
|
|
139
198
|
"""Test that queued nodes maintain their identity."""
|
|
140
199
|
parent = DummyParentNode(name="test")
|
|
@@ -203,6 +262,36 @@ class TestTreeRegisterRoot:
|
|
|
203
262
|
assert 0 in parent.children
|
|
204
263
|
assert parent.children[0] is child
|
|
205
264
|
|
|
265
|
+
def test_register_root_processes_pending_globals(self) -> None:
|
|
266
|
+
"""Test that register_root processes pending global nodes."""
|
|
267
|
+
tree = Tree()
|
|
268
|
+
root = DummyRootNode(name="root")
|
|
269
|
+
global_node = DummyGlobalNode(name="test_global")
|
|
270
|
+
|
|
271
|
+
queue_global(global_node)
|
|
272
|
+
tree.register_root(root)
|
|
273
|
+
|
|
274
|
+
assert len(tree.globals) == 1
|
|
275
|
+
assert tree.globals[0] is global_node
|
|
276
|
+
|
|
277
|
+
def test_register_root_processes_multiple_globals(self) -> None:
|
|
278
|
+
"""Test that register_root processes multiple global nodes."""
|
|
279
|
+
tree = Tree()
|
|
280
|
+
root = DummyRootNode(name="root")
|
|
281
|
+
global1 = DummyGlobalNode(name="global1")
|
|
282
|
+
global2 = DummyGlobalNode(name="global2")
|
|
283
|
+
global3 = DummyGlobalNode(name="global3")
|
|
284
|
+
|
|
285
|
+
queue_global(global1)
|
|
286
|
+
queue_global(global2)
|
|
287
|
+
queue_global(global3)
|
|
288
|
+
tree.register_root(root)
|
|
289
|
+
|
|
290
|
+
assert len(tree.globals) == 3
|
|
291
|
+
assert tree.globals[0] is global1
|
|
292
|
+
assert tree.globals[1] is global2
|
|
293
|
+
assert tree.globals[2] is global3
|
|
294
|
+
|
|
206
295
|
def test_register_root_reverses_pending_order(self) -> None:
|
|
207
296
|
"""Test that register_root processes nodes in reverse order."""
|
|
208
297
|
tree = Tree()
|
|
@@ -242,6 +331,28 @@ class TestTreeRegisterRoot:
|
|
|
242
331
|
with pytest.raises(NoParentError):
|
|
243
332
|
tree.register_root(root)
|
|
244
333
|
|
|
334
|
+
def test_register_root_with_mixed_node_types(self) -> None:
|
|
335
|
+
"""Test register_root with parent, child, and global nodes."""
|
|
336
|
+
tree = Tree()
|
|
337
|
+
root = DummyRootNode(name="root")
|
|
338
|
+
parent = DummyParentNode(name="parent")
|
|
339
|
+
child = DummyChildNode(name="child")
|
|
340
|
+
global_node = DummyGlobalNode(name="global")
|
|
341
|
+
|
|
342
|
+
queue_global(global_node)
|
|
343
|
+
queue_child(child)
|
|
344
|
+
queue_parent(parent)
|
|
345
|
+
tree.register_root(root)
|
|
346
|
+
|
|
347
|
+
assert tree.root is root
|
|
348
|
+
assert len(tree.globals) == 1
|
|
349
|
+
assert tree.globals[0] is global_node
|
|
350
|
+
assert root.children is not None
|
|
351
|
+
assert "parent" in root.children
|
|
352
|
+
assert parent.children is not None
|
|
353
|
+
assert 0 in parent.children
|
|
354
|
+
assert parent.children[0] is child
|
|
355
|
+
|
|
245
356
|
def test_register_root_sets_recent_to_last_parent(self) -> None:
|
|
246
357
|
"""Test that register_root sets recent to the most recent parent."""
|
|
247
358
|
tree = Tree()
|
|
@@ -641,3 +752,27 @@ class TestTreeEdgeCases:
|
|
|
641
752
|
|
|
642
753
|
assert tree.root is None
|
|
643
754
|
assert tree.recent is None
|
|
755
|
+
|
|
756
|
+
def test_globals_list_independence(self) -> None:
|
|
757
|
+
"""Test that globals list is independent between trees."""
|
|
758
|
+
tree1 = Tree()
|
|
759
|
+
tree2 = Tree()
|
|
760
|
+
|
|
761
|
+
global1 = DummyGlobalNode(name="global1")
|
|
762
|
+
global2 = DummyGlobalNode(name="global2")
|
|
763
|
+
|
|
764
|
+
tree1.globals.append(global1)
|
|
765
|
+
tree2.globals.append(global2)
|
|
766
|
+
|
|
767
|
+
assert len(tree1.globals) == 1
|
|
768
|
+
assert len(tree2.globals) == 1
|
|
769
|
+
assert tree1.globals[0] is global1
|
|
770
|
+
assert tree2.globals[0] is global2
|
|
771
|
+
|
|
772
|
+
def test_empty_globals_list_preserved(self) -> None:
|
|
773
|
+
"""Test that empty globals list remains empty."""
|
|
774
|
+
tree = Tree()
|
|
775
|
+
|
|
776
|
+
assert len(tree.globals) == 0
|
|
777
|
+
_ = tree.globals
|
|
778
|
+
assert len(tree.globals) == 0
|
click_extended-0.0.2/README.md
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-

|
|
2
|
-
|
|
3
|
-
# Click Extended
|
|
4
|
-
|
|
5
|
-
TBD
|
|
6
|
-
|
|
7
|
-
## Installation
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
pip install click-extended
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## Requirements
|
|
14
|
-
|
|
15
|
-
TBD
|
|
16
|
-
|
|
17
|
-
## License
|
|
18
|
-
|
|
19
|
-
This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.
|
|
20
|
-
|
|
21
|
-
## Contributing
|
|
22
|
-
|
|
23
|
-
TBD
|
|
24
|
-
|
|
25
|
-
## Acknowledgements
|
|
26
|
-
|
|
27
|
-
This project is built on top of the [Click](https://github.com/pallets/click) library.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|