inspect-ai 0.3.74__py3-none-any.whl → 0.3.76__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.
Files changed (115) hide show
  1. inspect_ai/__init__.py +3 -2
  2. inspect_ai/_cli/cache.py +1 -1
  3. inspect_ai/_cli/common.py +15 -0
  4. inspect_ai/_cli/eval.py +4 -5
  5. inspect_ai/_cli/log.py +1 -1
  6. inspect_ai/_cli/sandbox.py +1 -1
  7. inspect_ai/_cli/trace.py +1 -1
  8. inspect_ai/_cli/view.py +1 -1
  9. inspect_ai/_display/core/config.py +3 -1
  10. inspect_ai/_eval/eval.py +55 -61
  11. inspect_ai/_eval/evalset.py +64 -154
  12. inspect_ai/_eval/loader.py +27 -54
  13. inspect_ai/_eval/registry.py +4 -15
  14. inspect_ai/_eval/run.py +7 -4
  15. inspect_ai/_eval/task/__init__.py +8 -2
  16. inspect_ai/_eval/task/log.py +9 -1
  17. inspect_ai/_eval/task/resolved.py +35 -0
  18. inspect_ai/_eval/task/run.py +4 -0
  19. inspect_ai/_eval/task/task.py +50 -69
  20. inspect_ai/_eval/task/tasks.py +30 -0
  21. inspect_ai/_util/constants.py +3 -0
  22. inspect_ai/_util/dotenv.py +17 -0
  23. inspect_ai/_util/logger.py +3 -0
  24. inspect_ai/_util/registry.py +43 -2
  25. inspect_ai/_view/server.py +28 -10
  26. inspect_ai/_view/www/dist/assets/index.css +32 -19
  27. inspect_ai/_view/www/dist/assets/index.js +17682 -29989
  28. inspect_ai/_view/www/log-schema.json +79 -9
  29. inspect_ai/_view/www/package.json +2 -2
  30. inspect_ai/_view/www/src/appearance/styles.ts +6 -5
  31. inspect_ai/_view/www/src/components/AnsiDisplay.tsx +2 -2
  32. inspect_ai/_view/www/src/constants.ts +3 -0
  33. inspect_ai/_view/www/src/logfile/remoteZipFile.ts +141 -20
  34. inspect_ai/_view/www/src/plan/PlanDetailView.tsx +2 -1
  35. inspect_ai/_view/www/src/samples/SampleSummaryView.tsx +1 -1
  36. inspect_ai/_view/www/src/samples/chat/tools/tool.ts +7 -5
  37. inspect_ai/_view/www/src/samples/descriptor/score/CategoricalScoreDescriptor.tsx +1 -1
  38. inspect_ai/_view/www/src/samples/descriptor/score/NumericScoreDescriptor.tsx +2 -2
  39. inspect_ai/_view/www/src/samples/error/FlatSampleErrorView.module.css +1 -0
  40. inspect_ai/_view/www/src/samples/error/FlatSampleErrorView.tsx +3 -1
  41. inspect_ai/_view/www/src/samples/sample-tools/SortFilter.tsx +1 -1
  42. inspect_ai/_view/www/src/samples/sample-tools/sample-filter/SampleFilter.tsx +5 -2
  43. inspect_ai/_view/www/src/samples/transcript/ModelEventView.module.css +2 -2
  44. inspect_ai/_view/www/src/samples/transcript/state/StateEventView.tsx +5 -1
  45. inspect_ai/_view/www/src/types/log.d.ts +11 -5
  46. inspect_ai/_view/www/src/workspace/navbar/PrimaryBar.tsx +17 -12
  47. inspect_ai/_view/www/src/workspace/sidebar/SidebarLogEntry.tsx +2 -1
  48. inspect_ai/_view/www/yarn.lock +12 -5
  49. inspect_ai/log/_log.py +10 -1
  50. inspect_ai/log/_recorders/eval.py +27 -8
  51. inspect_ai/log/_recorders/json.py +10 -2
  52. inspect_ai/log/_transcript.py +13 -4
  53. inspect_ai/model/_call_tools.py +13 -4
  54. inspect_ai/model/_chat_message.py +15 -1
  55. inspect_ai/model/_model.py +30 -12
  56. inspect_ai/model/_model_output.py +6 -1
  57. inspect_ai/model/_openai.py +11 -6
  58. inspect_ai/model/_providers/anthropic.py +167 -77
  59. inspect_ai/model/_providers/google.py +6 -2
  60. inspect_ai/model/_providers/none.py +31 -0
  61. inspect_ai/model/_providers/openai.py +11 -8
  62. inspect_ai/model/_providers/providers.py +7 -0
  63. inspect_ai/model/_providers/vertex.py +5 -2
  64. inspect_ai/solver/_bridge/bridge.py +1 -1
  65. inspect_ai/solver/_chain.py +7 -6
  66. inspect_ai/tool/__init__.py +4 -0
  67. inspect_ai/tool/_tool_call.py +5 -2
  68. inspect_ai/tool/_tool_support_helpers.py +200 -0
  69. inspect_ai/tool/_tools/_bash_session.py +119 -0
  70. inspect_ai/tool/_tools/_computer/_computer.py +1 -1
  71. inspect_ai/tool/_tools/_text_editor.py +121 -0
  72. inspect_ai/tool/_tools/_web_browser/_back_compat.py +150 -0
  73. inspect_ai/tool/_tools/_web_browser/_web_browser.py +75 -130
  74. inspect_ai/tool/_tools/_web_search.py +2 -2
  75. inspect_ai/util/_json.py +28 -0
  76. inspect_ai/util/_sandbox/context.py +18 -8
  77. inspect_ai/util/_sandbox/docker/config.py +1 -1
  78. inspect_ai/util/_sandbox/docker/internal.py +3 -3
  79. inspect_ai/util/_sandbox/environment.py +17 -2
  80. {inspect_ai-0.3.74.dist-info → inspect_ai-0.3.76.dist-info}/METADATA +8 -5
  81. {inspect_ai-0.3.74.dist-info → inspect_ai-0.3.76.dist-info}/RECORD +85 -108
  82. {inspect_ai-0.3.74.dist-info → inspect_ai-0.3.76.dist-info}/WHEEL +1 -1
  83. inspect_ai/tool/_tools/_web_browser/_resources/.pylintrc +0 -8
  84. inspect_ai/tool/_tools/_web_browser/_resources/.vscode/launch.json +0 -24
  85. inspect_ai/tool/_tools/_web_browser/_resources/.vscode/settings.json +0 -25
  86. inspect_ai/tool/_tools/_web_browser/_resources/Dockerfile +0 -22
  87. inspect_ai/tool/_tools/_web_browser/_resources/README.md +0 -63
  88. inspect_ai/tool/_tools/_web_browser/_resources/accessibility_tree.py +0 -71
  89. inspect_ai/tool/_tools/_web_browser/_resources/accessibility_tree_node.py +0 -323
  90. inspect_ai/tool/_tools/_web_browser/_resources/cdp/__init__.py +0 -5
  91. inspect_ai/tool/_tools/_web_browser/_resources/cdp/a11y.py +0 -279
  92. inspect_ai/tool/_tools/_web_browser/_resources/cdp/dom.py +0 -9
  93. inspect_ai/tool/_tools/_web_browser/_resources/cdp/dom_snapshot.py +0 -293
  94. inspect_ai/tool/_tools/_web_browser/_resources/cdp/page.py +0 -94
  95. inspect_ai/tool/_tools/_web_browser/_resources/constants.py +0 -2
  96. inspect_ai/tool/_tools/_web_browser/_resources/images/usage_diagram.svg +0 -2
  97. inspect_ai/tool/_tools/_web_browser/_resources/mock_environment.py +0 -45
  98. inspect_ai/tool/_tools/_web_browser/_resources/playwright_browser.py +0 -50
  99. inspect_ai/tool/_tools/_web_browser/_resources/playwright_crawler.py +0 -48
  100. inspect_ai/tool/_tools/_web_browser/_resources/playwright_page_crawler.py +0 -280
  101. inspect_ai/tool/_tools/_web_browser/_resources/pyproject.toml +0 -65
  102. inspect_ai/tool/_tools/_web_browser/_resources/rectangle.py +0 -64
  103. inspect_ai/tool/_tools/_web_browser/_resources/rpc_client_helpers.py +0 -146
  104. inspect_ai/tool/_tools/_web_browser/_resources/scale_factor.py +0 -64
  105. inspect_ai/tool/_tools/_web_browser/_resources/test_accessibility_tree_node.py +0 -180
  106. inspect_ai/tool/_tools/_web_browser/_resources/test_playwright_crawler.py +0 -99
  107. inspect_ai/tool/_tools/_web_browser/_resources/test_rectangle.py +0 -15
  108. inspect_ai/tool/_tools/_web_browser/_resources/test_web_client.py +0 -44
  109. inspect_ai/tool/_tools/_web_browser/_resources/web_browser_rpc_types.py +0 -39
  110. inspect_ai/tool/_tools/_web_browser/_resources/web_client.py +0 -214
  111. inspect_ai/tool/_tools/_web_browser/_resources/web_client_new_session.py +0 -35
  112. inspect_ai/tool/_tools/_web_browser/_resources/web_server.py +0 -192
  113. {inspect_ai-0.3.74.dist-info → inspect_ai-0.3.76.dist-info}/entry_points.txt +0 -0
  114. {inspect_ai-0.3.74.dist-info → inspect_ai-0.3.76.dist-info/licenses}/LICENSE +0 -0
  115. {inspect_ai-0.3.74.dist-info → inspect_ai-0.3.76.dist-info}/top_level.txt +0 -0
@@ -1,63 +0,0 @@
1
- ## Headless Browser Tool
2
-
3
- This directory contains an implementation for the Headless Browser Tool which can be used to test web browsing agents.
4
-
5
- ### Usage
6
-
7
- #### 1. Start the Docker container
8
-
9
- A web server with the headless browser will be launched automatically on starting of the docker container and will be ready to receive client requests.
10
-
11
- #### 2. Send the command
12
-
13
- Use the following format:
14
-
15
- ```
16
- # Inside the Docker container
17
- $ python web_client.py [COMMAND] [args]
18
- ```
19
-
20
- ###### Commands
21
-
22
- The following commands are available at the moment:
23
-
24
- * **web_go \<URL\>** - goes to the specified url.
25
- * **web_click \<ELEMENT_ID\>** - clicks on a given element.
26
- * **web_scroll \<up/down\>** - scrolls up or down one page.
27
- * **web_forward** - navigates forward a page.
28
- * **web_back** - navigates back a page.
29
- * **web_refresh** - reloads current page (F5).
30
- * **web_type \<ELEMENT_ID\> \<TEXT\>** - types the specified text into the input with the specified id.
31
- * **web_type_submit \<ELEMENT_ID\> \<TEXT\>** - types the specified text into the input with the specified id and presses ENTER to submit the form.
32
-
33
- #### 3. Read the resulting observations
34
-
35
- The result will be printed out in _stdout_ in the following format:
36
-
37
- ```
38
- # Inside the Docker container
39
- error: <an ERROR message if one occurred>
40
- info: <general info about the container>
41
- web_url: <the URL of the page the browser is currently at>
42
- web_at: <accessibility tree of the visible elements of the page>
43
- ```
44
-
45
- ### Design
46
-
47
- The following diagram describes the design and the intended usage of the tool:
48
-
49
- ![diagram](images/usage_diagram.svg)
50
-
51
- The tool consists of the following components:
52
-
53
- - [WebServer](web_server.py) - a server which launches a stateful session with the headless chromium browser and interacts with it through the [Playwright API](https://playwright.dev/python/docs/intro) upon receiving client commands. The server components are:
54
-
55
- - _dm_env_servicer.py_ - an implementation for the gRPC Service based on [dm_env_rpc protocol](https://github.com/google-deepmind/dm_env_rpc).
56
- - _web_environment.py_ - an environment which gets instantiated by the servicer and which launches the browser, stores its state and maps client commands to Playwright API.
57
- - _playwright_crawler.py_ - a wrapper over the sync Playwright API.
58
-
59
- - [WebClient](web_client.py) - a simple stateless client to interact with the server. When launched, the client:
60
- 1. creates a connection with the server;
61
- 2. sends user command to the server;
62
- 3. receives the response in the form of observations and prints them to stdout;
63
- 4. Destroys the connection.
@@ -1,71 +0,0 @@
1
- from functools import reduce
2
- from typing import Iterable, TypedDict
3
-
4
- from accessibility_tree_node import AccessibilityTreeNode
5
- from cdp.a11y import AXNode, AXNodeId
6
- from cdp.dom_snapshot import DOMSnapshot, create_snapshot_context
7
- from rectangle import Rectangle
8
-
9
- _AccType = tuple[
10
- AXNode | None,
11
- dict[AXNodeId, AXNode],
12
- ]
13
-
14
-
15
- class AccessibilityTree(TypedDict):
16
- root: AccessibilityTreeNode
17
- nodes: dict[AXNodeId, AccessibilityTreeNode]
18
-
19
-
20
- def create_accessibility_tree(
21
- *,
22
- ax_nodes: Iterable[AXNode],
23
- dom_snapshot: DOMSnapshot,
24
- device_scale_factor: float,
25
- window_bounds: Rectangle,
26
- ) -> AccessibilityTree | None:
27
- """
28
- Creates an accessibility tree from the given Chrome DevTools Protocol AX nodes and DOM snapshot.
29
-
30
- Args:
31
- ax_nodes (Iterable[AXNode]): An iterable of AXNode objects representing the accessibility nodes.
32
- dom_snapshot (DOMSnapshot): A snapshot of the DOM at the time of accessibility tree creation.
33
- device_scale_factor (float): The scale factor of the device.
34
- window_bounds (Bounds): The bounds of the window.
35
-
36
- Returns:
37
- AccessibilityTree: The accessibility tree.
38
- """
39
-
40
- # first make a dict of AXNodeId's to AXNode's and find the root on the way
41
- def reducer(acc: _AccType, ax_node: AXNode) -> _AccType:
42
- root_node, nodes = acc
43
- nodes[ax_node.nodeId] = ax_node
44
- return (
45
- # TODO: What do we want for multiple roots?
46
- root_node or (ax_node if ax_node.parentId is None else None),
47
- nodes,
48
- )
49
-
50
- initial_acc: _AccType = (None, {}) # The inference engine is weak
51
- root_node, nodes = reduce(reducer, ax_nodes, initial_acc)
52
-
53
- if not root_node:
54
- return None
55
-
56
- # Now create the AccessibilityTreeNode hierarchy
57
- snapshot_context = create_snapshot_context(dom_snapshot)
58
- all_accessibility_tree_nodes: dict[AXNodeId, AccessibilityTreeNode] = {}
59
-
60
- return AccessibilityTree(
61
- root=AccessibilityTreeNode(
62
- ax_node=root_node,
63
- ax_nodes=nodes,
64
- parent=None,
65
- all_accessibility_tree_nodes=all_accessibility_tree_nodes,
66
- snapshot_context=snapshot_context,
67
- device_scale_factor=device_scale_factor,
68
- window_bounds=window_bounds,
69
- ),
70
- nodes=all_accessibility_tree_nodes,
71
- )
@@ -1,323 +0,0 @@
1
- from functools import reduce
2
- from typing import Optional
3
-
4
- from cdp.a11y import (
5
- AXNode,
6
- AXNodeId,
7
- AXProperty,
8
- AXPropertyName,
9
- node_has_property,
10
- string_from_ax_value,
11
- )
12
- from cdp.dom_snapshot import (
13
- DOMSnapshotContext,
14
- bounds_for_node_index,
15
- current_url_src_for_node_index,
16
- index_for_node_id,
17
- text_value_for_node_index,
18
- )
19
- from rectangle import Rectangle
20
-
21
- # Properties to ignore when printing out the accessibility tree.
22
- _IGNORED_AT_PROPERTIES: set[AXPropertyName] = {
23
- "focusable",
24
- "readonly",
25
- "level",
26
- "settable",
27
- "multiline",
28
- "invalid",
29
- }
30
-
31
- # The AXRole enum is here (https://chromium.googlesource.com/chromium/chromium/+/HEAD/ui/accessibility/ax_enums.idl#62)
32
- _ROLES_IGNORED = {"horizontal_rule", "ignored"}
33
-
34
- # These are noisy and provide no real benefit unless there's a name associated with them
35
- _ROLES_REQUIRING_A_NAME = {"generic", "paragraph", "div"}
36
-
37
- # Used for debugging, include the elements bounding rect during output.
38
- _INCLUDE_BOUNDS_IN_OUTPUT = False
39
-
40
-
41
- class AccessibilityTreeNode:
42
- def __init__(
43
- self,
44
- *,
45
- ax_node: AXNode,
46
- ax_nodes: dict[AXNodeId, AXNode],
47
- parent: Optional["AccessibilityTreeNode"],
48
- all_accessibility_tree_nodes: dict[AXNodeId, "AccessibilityTreeNode"],
49
- snapshot_context: DOMSnapshotContext,
50
- device_scale_factor: float,
51
- window_bounds: Rectangle,
52
- ):
53
- all_accessibility_tree_nodes[ax_node.nodeId] = self
54
-
55
- self._is_expanded = node_has_property(ax_node, "expanded")
56
- self._ax_node = ax_node
57
- self._parent = parent
58
- self._is_ignored: bool | None = None
59
- self._bounds: Rectangle | None = None
60
- self._is_visible: bool | None = None
61
- self._current_src_url: str | None = None
62
- self._input: str | None = None
63
- self.__closest_non_ignored_parent: AccessibilityTreeNode | None = None
64
-
65
- dom_node_index = (
66
- index_for_node_id(ax_node.backendDOMNodeId, snapshot_context)
67
- if ax_node.backendDOMNodeId
68
- else None
69
- )
70
-
71
- # The following three variables (_bounds, _is_visible, and _current_src_url) depend on
72
- # info from the DOM element associated with this AXNode
73
- if dom_node_index:
74
- self._bounds = (
75
- Rectangle(*layout_bounds).scale(1 / device_scale_factor)
76
- if (
77
- layout_bounds := bounds_for_node_index(
78
- dom_node_index, snapshot_context
79
- )
80
- )
81
- is not None
82
- else None
83
- )
84
- self._is_visible = (
85
- (self._bounds.has_area and self._bounds.overlaps(window_bounds))
86
- if self._bounds
87
- else None
88
- )
89
- self._current_src_url = (
90
- current_url_src_for_node_index(dom_node_index, snapshot_context)
91
- if self.role == "image"
92
- else None
93
- )
94
-
95
- if node_has_property(ax_node, "editable"):
96
- # Sometimes a node will have it's input stored as 'value' other times we
97
- # need to go looking through the DOM tree to find its matching string.
98
- self._input = (
99
- string_from_ax_value(ax_node.value)
100
- if ax_node.value
101
- else text_value_for_node_index(dom_node_index, snapshot_context)
102
- if dom_node_index is not None
103
- else None
104
- )
105
-
106
- # this is a little awkward, but the old code ensured that menuitems were visible in given the following condition
107
- if self.role == "menuitem" and parent is not None and parent.is_expanded:
108
- self._is_visible = True
109
-
110
- # It's important to set all other properties before creating children in case
111
- # the children sample parent.xxx
112
- self.children = (
113
- [
114
- AccessibilityTreeNode(
115
- ax_node=ax_nodes[child_id],
116
- ax_nodes=ax_nodes,
117
- parent=self,
118
- all_accessibility_tree_nodes=all_accessibility_tree_nodes,
119
- snapshot_context=snapshot_context,
120
- device_scale_factor=device_scale_factor,
121
- window_bounds=window_bounds,
122
- )
123
- for child_id in ax_node.childIds
124
- ]
125
- if ax_node.childIds
126
- else None
127
- )
128
-
129
- def __str__(self) -> str:
130
- bounds_string = str(self.bounds) if _INCLUDE_BOUNDS_IN_OUTPUT else None
131
-
132
- input_string = f'Current input: "{self._input}"' if self._input else None
133
-
134
- name_string = f'"{self.name}"' if self.name else None
135
- # Without bounds we can't click on the element, and therefore the ID
136
- # can not be used. Still useful to show these elements though (e.g.)
137
- # to let user know about options.
138
- id_string = f"[{'*' if not self._bounds or not self._bounds.has_area else self._ax_node.nodeId}]"
139
- url_string = self._current_src_url if self._current_src_url else None
140
-
141
- url_string = _maybe_redact_base64_url(self._current_src_url)
142
-
143
- return " ".join(
144
- [
145
- item
146
- for item in [
147
- id_string,
148
- self.role,
149
- name_string,
150
- url_string,
151
- input_string,
152
- self._property_string(),
153
- bounds_string,
154
- ]
155
- if item is not None
156
- ]
157
- )
158
-
159
- @property
160
- def node_id(self) -> AXNodeId:
161
- return self._ax_node.nodeId
162
-
163
- @property
164
- def name(self) -> str:
165
- return string_from_ax_value(self._ax_node.name)
166
-
167
- @property
168
- def is_expanded(self) -> bool:
169
- return self._is_expanded
170
-
171
- @property
172
- def is_visible(self) -> bool:
173
- return self._is_visible or False
174
-
175
- @property
176
- def bounds(self) -> Rectangle | None:
177
- return self._bounds
178
-
179
- @property
180
- def current_src_url(self) -> str | None:
181
- return self._current_src_url
182
-
183
- @property
184
- def input(self) -> str | None:
185
- return self._input
186
-
187
- @property
188
- def is_ignored(self) -> bool:
189
- # computing _is_ignored is very expensive, do it on demand and remember the result
190
- if self._is_ignored is None:
191
- self._is_ignored = self._compute_is_ignored()
192
- return self._is_ignored
193
-
194
- @property
195
- def role(self) -> str:
196
- return string_from_ax_value(self._ax_node.role)
197
-
198
- @property
199
- def _closest_non_ignored_parent(self) -> Optional["AccessibilityTreeNode"]:
200
- if self._parent and self.__closest_non_ignored_parent is None:
201
- self.__closest_non_ignored_parent = (
202
- None
203
- if not self._parent
204
- else self._parent
205
- if not self._parent.is_ignored
206
- else self._parent._closest_non_ignored_parent # pylint: disable=protected-access
207
- )
208
- return self.__closest_non_ignored_parent
209
-
210
- def render_accessibility_tree(self, indent: int = 0) -> str:
211
- """Returns the string representation of the node and its children."""
212
- result = ""
213
- if not self.is_ignored:
214
- result = " " * indent + str(self) + "\n"
215
- indent += 1
216
- children_strings = [
217
- child.render_accessibility_tree(indent) for child in self.children or []
218
- ]
219
- result = result + "".join([s for s in children_strings if s])
220
- return result
221
-
222
- def render_main_content(self) -> str | None:
223
- return (
224
- main_node._extract_text() # pylint: disable=protected-access
225
- if (main_node := self._get_main_content_node())
226
- else None
227
- )
228
-
229
- def _compute_is_ignored(self) -> bool:
230
- if self._ax_node.ignored or not self.is_visible or self.role in _ROLES_IGNORED:
231
- return True
232
-
233
- # if the parent is a link/button with a name, and this node is within the parent's bounds, we can ignore it.
234
- if (
235
- (closest_parent := self._closest_non_ignored_parent)
236
- and closest_parent.bounds
237
- and self.bounds
238
- and (closest_parent.role == "link" or closest_parent.role == "button")
239
- and closest_parent.name
240
- and self.bounds.within(closest_parent.bounds)
241
- ):
242
- return True
243
-
244
- if bool(self.name): # excludes None or ""
245
- # This simplifies things like links/buttons whose contents are sub-elements
246
- # e.g.
247
- # [158] button "Menu"
248
- # [1166] StaticText "Menu"
249
- # becomes
250
- # [158] button "Menu"
251
- return self._parent is not None and self._parent.name == self.name
252
-
253
- return self.role in _ROLES_REQUIRING_A_NAME
254
-
255
- def _get_main_content_node(self) -> Optional["AccessibilityTreeNode"]:
256
- if self.role == "main":
257
- return self
258
-
259
- return next(
260
- (
261
- result
262
- for child in self.children or []
263
- if (result := child._get_main_content_node()) # pylint: disable=protected-access
264
- ),
265
- None,
266
- )
267
-
268
- def _extract_text(self):
269
- """
270
- Recursively extracts and concatenates text from the accessibility tree nodes.
271
-
272
- This method traverses the accessibility tree starting from the current node,
273
- concatenating the text of each node.
274
-
275
- Returns:
276
- str: The concatenated text from the accessibility tree nodes, with
277
- appropriate spacing based on node roles.
278
- """
279
- if not self.is_visible:
280
- return ""
281
-
282
- def reducer(acc, child):
283
- child_text = child._extract_text() # pylint: disable=protected-access
284
- return (
285
- acc
286
- + (
287
- "\n\n"
288
- if child.role
289
- in {"paragraph", "note", "row", "div", "heading", "region"}
290
- else " "
291
- )
292
- + child_text
293
- )
294
-
295
- return reduce(reducer, self.children or [], self.name).strip()
296
-
297
- def _property_string(self) -> str:
298
- properties = [
299
- _prop_name_value_str(node_property)
300
- if node_property.value.value
301
- else f"{node_property.name}"
302
- for node_property in self._ax_node.properties or ()
303
- if node_property.name not in _IGNORED_AT_PROPERTIES
304
- ]
305
- return " [" + ", ".join(properties) + "]" if properties else ""
306
-
307
-
308
- def _prop_name_value_str(node_property: AXProperty) -> str:
309
- # pre-commit hook (debug-statements) crashes if value is inlined below
310
- value = (
311
- _maybe_redact_base64_url(node_property.value.value)
312
- if node_property.name == "url" and isinstance(node_property.value.value, str)
313
- else node_property.value.value
314
- )
315
- return f"{node_property.name}: {value}"
316
-
317
-
318
- def _maybe_redact_base64_url(url: str | None) -> str | None:
319
- return (
320
- "data:<base64-data-removed>"
321
- if url and url.startswith("data:") and "base64" in url
322
- else url
323
- )
@@ -1,5 +0,0 @@
1
- """
2
- Types and pure functional helpers associated with Chrome DevTools Protocol
3
-
4
- https://chromedevtools.github.io/devtools-protocol/
5
- """