arcade-mcp/toolkits/confluence/arcade_confluence/utils.py
Sam Partee b6b4cd0a4c
🏗️ Restructure: Multi-Package Architecture + uv Migration (#412)
### Overview
Major restructuring from monolithic `arcade-ai` package to modular
library architecture with standardized uv-based dependency management.

![arcade-ai Monorepo
(2)](https://github.com/user-attachments/assets/25f102b0-bb87-4a04-9701-d227d05664b1)

### New Package Structure
- **`arcade-tdk`** - Lightweight toolkit development kit (core
decorators, auth)
- **`arcade-core`** - Core execution engine and catalog functionality  
- **`arcade-serve`** - FastAPI/MCP server components
- **`arcade-ai`** - Meta package that includes CLI functionality.
Optionally include evals via the `evals` extra. Optionally include all
packages via the `all` extra.

### Key Benefits
- **Lighter Dependencies**: Toolkits now depend only on `arcade-tdk` (~2
deps) vs full `arcade-ai` (~30+ deps)
- **Faster Builds**: uv provides 10-100x faster dependency resolution
and installation
- **Better Modularity**: Clear separation of concerns, consumers import
only what they need
- **Standard Tooling**: Eliminates custom poetry scripts, uses standard
Python packaging

### Migration Impact
- All 20 toolkits converted from poetry → uv with `arcade-tdk`
dependencies plus `arcade-ai[evals]` and `arcade-serve` dev
dependencies. When developing locally, devs should install toolkits via
`make install-local`.
- Modern Python 3.10+ type hints throughout
- Standardized build system with hatchling backend
- Enhanced Makefile with robust toolkit management commands
- Removed `arcade dev` CLI command
- Reduce the number of files created by `arcade new` and add an option
to not generate a tests and evals folder.

This foundation enables faster development cycles and cleaner dependency
chains for the growing toolkit ecosystem.

### Todo After this PR is merged
- [ ] Post-merge workflow(s) (release & publish containers, etc)
- [ ] Release order plan. @EricGustin suggests releasing in the
following order:
    1. `arcade-core` version 0.1.0
    2. `arcade-serve` version 0.1.0 and `arcade-tdk` version 0.1.0
    3. `arcade-ai` version 2.0.0
4. Patch release for all toolkits (all changes in toolkits are internal
refactors)
- [ ] [Update docs](https://github.com/ArcadeAI/docs/pull/318)

---------

Co-authored-by: Eric Gustin <eric@arcade.dev>
Co-authored-by: Eric Gustin <34000337+EricGustin@users.noreply.github.com>
2025-06-11 16:48:17 -07:00

89 lines
3.1 KiB
Python

import re
from arcade_tdk.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