import re from arcade.sdk.errors import RetryableToolError, ToolExecutionError def remove_none_values(data: dict) -> dict: """Remove all keys with None values from the dictionary.""" return {k: v for k, v in data.items() if v is not None} def validate_ids(ids: list[str] | None, max_length: int) -> None: """Validate a list of IDs. The ids can be page ids, space ids, etc. A valid id is a string that is a number. Args: ids: A list of IDs to validate. Returns: None Raises: ToolExecutionError: If any of the IDs are not valid. RetryableToolError: If the number of IDs is greater than the max length. """ if not ids: return if len(ids) > max_length: raise RetryableToolError( message=f"The 'ids' parameter must have less than {max_length} items. Got {len(ids)}" ) if any(not id_.isdigit() for id_ in ids): raise ToolExecutionError(message="Invalid ID provided. IDs are numeric") def build_child_url(base_url: str, child: dict) -> str | None: """Build URL for a child node based on its type and status. Args: base_url: The base URL for the Confluence space child: A dictionary representing a Confluence content item Returns: The URL for the child, or None if it can't be determined """ if child["type"] in ("whiteboard", "database", "embed"): return f"{base_url}/{child['type']}/{child['id']}" elif child["type"] == "folder": return None elif child["type"] == "page": parsed_title = re.sub(r"[ '\s]+", "+", child["title"].strip()) if child.get("status") == "draft": return f"{base_url}/{child['type']}s/edit-v2/{child['id']}" else: return f"{base_url}/{child['type']}s/{child['id']}/{parsed_title}" return None def build_hierarchy(transformed_children: list, parent_id: str, parent_node: dict) -> None: """Build parent-child hierarchy from a flat list of descendants. This function takes a flat list of items that have parent_id references and builds a hierarchical tree structure. It modifies the parent_node in place. Args: transformed_children: List of child nodes with parent_id fields parent_id: The ID of the parent node parent_node: The parent node to attach direct children to Returns: None (modifies parent_node in place) """ # Create a map of children by their ID for efficient lookups child_map = {child["id"]: child for child in transformed_children} # Find all direct children of the given parent_id direct_children = [] for child in transformed_children: if child.get("parent_id") == parent_id: direct_children.append(child) elif child.get("parent_id") in child_map: # Add child to its parent's children list parent = child_map[child.get("parent_id")] if "children" not in parent: parent["children"] = [] parent["children"].append(child) # Set the direct children on the parent node parent_node["children"] = direct_children