- Fix Python formatting (sync no longer strips blank lines from .py files) - Remove test_community_build.py (tests private sync module) - Fix ruff warnings in test files - Add CODE_OF_CONDUCT.md - Add .github/PULL_REQUEST_TEMPLATE.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
692 lines
24 KiB
Python
692 lines
24 KiB
Python
"""Taxonomy Handler - manages WordPress categories, tags, and custom taxonomies"""
|
|
|
|
import json
|
|
from typing import Any
|
|
|
|
from plugins.wordpress.client import WordPressClient
|
|
|
|
|
|
def get_tool_specifications() -> list[dict[str, Any]]:
|
|
"""Return tool specifications for ToolGenerator"""
|
|
return [
|
|
# === CATEGORIES ===
|
|
{
|
|
"name": "list_categories",
|
|
"method_name": "list_categories",
|
|
"description": "List WordPress post categories. Returns hierarchical list of categories with post counts.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"per_page": {
|
|
"type": "integer",
|
|
"description": "Number of categories per page (1-100)",
|
|
"default": 100,
|
|
"minimum": 1,
|
|
"maximum": 100,
|
|
},
|
|
"page": {
|
|
"type": "integer",
|
|
"description": "Page number",
|
|
"default": 1,
|
|
"minimum": 1,
|
|
},
|
|
"hide_empty": {
|
|
"type": "boolean",
|
|
"description": "Hide categories with no posts",
|
|
"default": False,
|
|
},
|
|
},
|
|
},
|
|
"scope": "read",
|
|
},
|
|
{
|
|
"name": "create_category",
|
|
"method_name": "create_category",
|
|
"description": "Create a new WordPress category. Supports hierarchical categories with parent relationships.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"name": {"type": "string", "description": "Category name", "minLength": 1},
|
|
"description": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"description": "Category description",
|
|
},
|
|
"parent": {
|
|
"anyOf": [{"type": "integer"}, {"type": "null"}],
|
|
"description": "Parent category ID for hierarchy",
|
|
},
|
|
},
|
|
"required": ["name"],
|
|
},
|
|
"scope": "write",
|
|
},
|
|
{
|
|
"name": "update_category",
|
|
"method_name": "update_category",
|
|
"description": "Update an existing WordPress category. Can modify name, description, and parent category.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"category_id": {
|
|
"type": "integer",
|
|
"description": "Category ID to update",
|
|
"minimum": 1,
|
|
},
|
|
"name": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"description": "New category name",
|
|
},
|
|
"description": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"description": "New category description",
|
|
},
|
|
"parent": {
|
|
"anyOf": [{"type": "integer"}, {"type": "null"}],
|
|
"description": "New parent category ID",
|
|
},
|
|
},
|
|
"required": ["category_id"],
|
|
},
|
|
"scope": "write",
|
|
},
|
|
{
|
|
"name": "delete_category",
|
|
"method_name": "delete_category",
|
|
"description": "Delete a WordPress category. Can permanently delete or reassign posts to another category.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"category_id": {
|
|
"type": "integer",
|
|
"description": "Category ID to delete",
|
|
"minimum": 1,
|
|
},
|
|
"force": {
|
|
"type": "boolean",
|
|
"description": "Force permanent deletion",
|
|
"default": False,
|
|
},
|
|
},
|
|
"required": ["category_id"],
|
|
},
|
|
"scope": "write",
|
|
},
|
|
# === TAGS ===
|
|
{
|
|
"name": "list_tags",
|
|
"method_name": "list_tags",
|
|
"description": "List WordPress post tags. Returns all tags with usage counts and metadata.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"per_page": {
|
|
"type": "integer",
|
|
"description": "Number of tags per page (1-100)",
|
|
"default": 100,
|
|
"minimum": 1,
|
|
"maximum": 100,
|
|
},
|
|
"page": {
|
|
"type": "integer",
|
|
"description": "Page number",
|
|
"default": 1,
|
|
"minimum": 1,
|
|
},
|
|
"hide_empty": {
|
|
"type": "boolean",
|
|
"description": "Hide tags with no posts",
|
|
"default": False,
|
|
},
|
|
},
|
|
},
|
|
"scope": "read",
|
|
},
|
|
{
|
|
"name": "create_tag",
|
|
"method_name": "create_tag",
|
|
"description": "Create a new WordPress tag. Tag slug is auto-generated from name if not provided.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"name": {"type": "string", "description": "Tag name", "minLength": 1},
|
|
"description": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"description": "Tag description",
|
|
},
|
|
},
|
|
"required": ["name"],
|
|
},
|
|
"scope": "write",
|
|
},
|
|
{
|
|
"name": "update_tag",
|
|
"method_name": "update_tag",
|
|
"description": "Update an existing WordPress tag. Can modify name and description.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"tag_id": {"type": "integer", "description": "Tag ID to update", "minimum": 1},
|
|
"name": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"description": "New tag name",
|
|
},
|
|
"description": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"description": "New tag description",
|
|
},
|
|
},
|
|
"required": ["tag_id"],
|
|
},
|
|
"scope": "write",
|
|
},
|
|
{
|
|
"name": "delete_tag",
|
|
"method_name": "delete_tag",
|
|
"description": "Delete a WordPress tag. Permanently removes the tag from all posts.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"tag_id": {"type": "integer", "description": "Tag ID to delete", "minimum": 1},
|
|
"force": {
|
|
"type": "boolean",
|
|
"description": "Force permanent deletion",
|
|
"default": False,
|
|
},
|
|
},
|
|
"required": ["tag_id"],
|
|
},
|
|
"scope": "write",
|
|
},
|
|
# === CUSTOM TAXONOMIES ===
|
|
{
|
|
"name": "list_taxonomies",
|
|
"method_name": "list_taxonomies",
|
|
"description": "List all registered taxonomies including built-in (category, post_tag) and custom taxonomies. Returns configuration and post type associations.",
|
|
"schema": {"type": "object", "properties": {}},
|
|
"scope": "read",
|
|
},
|
|
{
|
|
"name": "list_taxonomy_terms",
|
|
"method_name": "list_taxonomy_terms",
|
|
"description": "List terms of a specific taxonomy. Works with any registered taxonomy including categories, tags, and custom taxonomies.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"taxonomy": {
|
|
"type": "string",
|
|
"description": "Taxonomy slug (e.g., 'category', 'post_tag', 'product_cat')",
|
|
},
|
|
"per_page": {
|
|
"type": "integer",
|
|
"description": "Number of terms per page",
|
|
"default": 100,
|
|
"minimum": 1,
|
|
"maximum": 100,
|
|
},
|
|
"page": {
|
|
"type": "integer",
|
|
"description": "Page number",
|
|
"default": 1,
|
|
"minimum": 1,
|
|
},
|
|
"hide_empty": {
|
|
"type": "boolean",
|
|
"description": "Hide terms with no posts",
|
|
"default": False,
|
|
},
|
|
"parent": {
|
|
"anyOf": [{"type": "integer"}, {"type": "null"}],
|
|
"description": "Filter by parent term ID (for hierarchical taxonomies)",
|
|
},
|
|
},
|
|
"required": ["taxonomy"],
|
|
},
|
|
"scope": "read",
|
|
},
|
|
{
|
|
"name": "create_taxonomy_term",
|
|
"method_name": "create_taxonomy_term",
|
|
"description": "Create a new term in a taxonomy. Supports hierarchical taxonomies with parent terms. Works with any registered taxonomy.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"taxonomy": {
|
|
"type": "string",
|
|
"description": "Taxonomy slug (e.g., 'category', 'product_cat')",
|
|
},
|
|
"name": {"type": "string", "description": "Term name", "minLength": 1},
|
|
"description": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"description": "Term description",
|
|
},
|
|
"parent": {
|
|
"anyOf": [{"type": "integer"}, {"type": "null"}],
|
|
"description": "Parent term ID for hierarchical taxonomies (e.g., subcategories)",
|
|
},
|
|
},
|
|
"required": ["taxonomy", "name"],
|
|
},
|
|
"scope": "write",
|
|
},
|
|
]
|
|
|
|
|
|
class TaxonomyHandler:
|
|
"""Handle taxonomy-related operations for WordPress"""
|
|
|
|
def __init__(self, client: WordPressClient):
|
|
"""
|
|
Initialize taxonomy handler.
|
|
|
|
Args:
|
|
client: WordPress API client instance
|
|
"""
|
|
self.client = client
|
|
|
|
# === CATEGORIES ===
|
|
|
|
async def list_categories(
|
|
self, per_page: int = 100, page: int = 1, hide_empty: bool = False
|
|
) -> str:
|
|
"""
|
|
List WordPress post categories.
|
|
|
|
Args:
|
|
per_page: Number of categories per page (1-100)
|
|
page: Page number
|
|
hide_empty: Hide categories with no posts
|
|
|
|
Returns:
|
|
JSON string with categories list
|
|
"""
|
|
try:
|
|
params = {
|
|
"per_page": per_page,
|
|
"page": page,
|
|
"hide_empty": "true" if hide_empty else "false",
|
|
}
|
|
|
|
categories = await self.client.get("categories", params=params)
|
|
|
|
result = {
|
|
"total": len(categories),
|
|
"categories": [
|
|
{
|
|
"id": cat["id"],
|
|
"name": cat["name"],
|
|
"slug": cat["slug"],
|
|
"description": cat.get("description", ""),
|
|
"count": cat.get("count", 0),
|
|
"parent": cat.get("parent", 0),
|
|
}
|
|
for cat in categories
|
|
],
|
|
}
|
|
|
|
return json.dumps(result, indent=2)
|
|
except Exception as e:
|
|
return json.dumps(
|
|
{"error": str(e), "message": f"Failed to list categories: {str(e)}"}, indent=2
|
|
)
|
|
|
|
async def create_category(
|
|
self, name: str, description: str | None = None, parent: int | None = None
|
|
) -> str:
|
|
"""
|
|
Create a new WordPress category.
|
|
|
|
Args:
|
|
name: Category name
|
|
description: Category description
|
|
parent: Parent category ID for hierarchy
|
|
|
|
Returns:
|
|
JSON string with created category data
|
|
"""
|
|
try:
|
|
data = {"name": name}
|
|
if description:
|
|
data["description"] = description
|
|
if parent:
|
|
data["parent"] = parent
|
|
|
|
category = await self.client.post("categories", json_data=data)
|
|
|
|
result = {
|
|
"id": category["id"],
|
|
"name": category["name"],
|
|
"slug": category["slug"],
|
|
"message": f"Category '{name}' created successfully with ID {category['id']}",
|
|
}
|
|
|
|
return json.dumps(result, indent=2)
|
|
except Exception as e:
|
|
return json.dumps(
|
|
{"error": str(e), "message": f"Failed to create category: {str(e)}"}, indent=2
|
|
)
|
|
|
|
async def update_category(
|
|
self,
|
|
category_id: int,
|
|
name: str | None = None,
|
|
description: str | None = None,
|
|
parent: int | None = None,
|
|
) -> str:
|
|
"""
|
|
Update an existing WordPress category.
|
|
|
|
Args:
|
|
category_id: Category ID to update
|
|
name: New category name
|
|
description: New category description
|
|
parent: New parent category ID
|
|
|
|
Returns:
|
|
JSON string with updated category data
|
|
"""
|
|
try:
|
|
# Build data dict with only provided values
|
|
data = {}
|
|
if name is not None:
|
|
data["name"] = name
|
|
if description is not None:
|
|
data["description"] = description
|
|
if parent is not None:
|
|
data["parent"] = parent
|
|
|
|
category = await self.client.post(f"categories/{category_id}", json_data=data)
|
|
|
|
result = {
|
|
"id": category["id"],
|
|
"name": category["name"],
|
|
"slug": category["slug"],
|
|
"message": f"Category {category_id} updated successfully",
|
|
}
|
|
|
|
return json.dumps(result, indent=2)
|
|
except Exception as e:
|
|
return json.dumps(
|
|
{"error": str(e), "message": f"Failed to update category {category_id}: {str(e)}"},
|
|
indent=2,
|
|
)
|
|
|
|
async def delete_category(self, category_id: int, force: bool = False) -> str:
|
|
"""
|
|
Delete a WordPress category.
|
|
|
|
Args:
|
|
category_id: Category ID to delete
|
|
force: Force permanent deletion
|
|
|
|
Returns:
|
|
JSON string with deletion result
|
|
"""
|
|
try:
|
|
params = {"force": "true" if force else "false"}
|
|
result = await self.client.delete(f"categories/{category_id}", params=params)
|
|
|
|
message = f"Category {category_id} deleted successfully"
|
|
return json.dumps({"success": True, "message": message, "result": result}, indent=2)
|
|
except Exception as e:
|
|
return json.dumps(
|
|
{"error": str(e), "message": f"Failed to delete category {category_id}: {str(e)}"},
|
|
indent=2,
|
|
)
|
|
|
|
# === TAGS ===
|
|
|
|
async def list_tags(self, per_page: int = 100, page: int = 1, hide_empty: bool = False) -> str:
|
|
"""
|
|
List WordPress post tags.
|
|
|
|
Args:
|
|
per_page: Number of tags per page (1-100)
|
|
page: Page number
|
|
hide_empty: Hide tags with no posts
|
|
|
|
Returns:
|
|
JSON string with tags list
|
|
"""
|
|
try:
|
|
params = {
|
|
"per_page": per_page,
|
|
"page": page,
|
|
"hide_empty": "true" if hide_empty else "false",
|
|
}
|
|
|
|
tags = await self.client.get("tags", params=params)
|
|
|
|
result = {
|
|
"total": len(tags),
|
|
"tags": [
|
|
{
|
|
"id": tag["id"],
|
|
"name": tag["name"],
|
|
"slug": tag["slug"],
|
|
"description": tag.get("description", ""),
|
|
"count": tag.get("count", 0),
|
|
}
|
|
for tag in tags
|
|
],
|
|
}
|
|
|
|
return json.dumps(result, indent=2)
|
|
except Exception as e:
|
|
return json.dumps(
|
|
{"error": str(e), "message": f"Failed to list tags: {str(e)}"}, indent=2
|
|
)
|
|
|
|
async def create_tag(self, name: str, description: str | None = None) -> str:
|
|
"""
|
|
Create a new WordPress tag.
|
|
|
|
Args:
|
|
name: Tag name
|
|
description: Tag description
|
|
|
|
Returns:
|
|
JSON string with created tag data
|
|
"""
|
|
try:
|
|
data = {"name": name}
|
|
if description:
|
|
data["description"] = description
|
|
|
|
tag = await self.client.post("tags", json_data=data)
|
|
|
|
result = {
|
|
"id": tag["id"],
|
|
"name": tag["name"],
|
|
"slug": tag["slug"],
|
|
"message": f"Tag '{name}' created successfully with ID {tag['id']}",
|
|
}
|
|
|
|
return json.dumps(result, indent=2)
|
|
except Exception as e:
|
|
return json.dumps(
|
|
{"error": str(e), "message": f"Failed to create tag: {str(e)}"}, indent=2
|
|
)
|
|
|
|
async def update_tag(
|
|
self, tag_id: int, name: str | None = None, description: str | None = None
|
|
) -> str:
|
|
"""
|
|
Update an existing WordPress tag.
|
|
|
|
Args:
|
|
tag_id: Tag ID to update
|
|
name: New tag name
|
|
description: New tag description
|
|
|
|
Returns:
|
|
JSON string with updated tag data
|
|
"""
|
|
try:
|
|
# Build data dict with only provided values
|
|
data = {}
|
|
if name is not None:
|
|
data["name"] = name
|
|
if description is not None:
|
|
data["description"] = description
|
|
|
|
tag = await self.client.post(f"tags/{tag_id}", json_data=data)
|
|
|
|
result = {
|
|
"id": tag["id"],
|
|
"name": tag["name"],
|
|
"slug": tag["slug"],
|
|
"message": f"Tag {tag_id} updated successfully",
|
|
}
|
|
|
|
return json.dumps(result, indent=2)
|
|
except Exception as e:
|
|
return json.dumps(
|
|
{"error": str(e), "message": f"Failed to update tag {tag_id}: {str(e)}"}, indent=2
|
|
)
|
|
|
|
async def delete_tag(self, tag_id: int, force: bool = False) -> str:
|
|
"""
|
|
Delete a WordPress tag.
|
|
|
|
Args:
|
|
tag_id: Tag ID to delete
|
|
force: Force permanent deletion
|
|
|
|
Returns:
|
|
JSON string with deletion result
|
|
"""
|
|
try:
|
|
params = {"force": "true" if force else "false"}
|
|
result = await self.client.delete(f"tags/{tag_id}", params=params)
|
|
|
|
message = f"Tag {tag_id} deleted successfully"
|
|
return json.dumps({"success": True, "message": message, "result": result}, indent=2)
|
|
except Exception as e:
|
|
return json.dumps(
|
|
{"error": str(e), "message": f"Failed to delete tag {tag_id}: {str(e)}"}, indent=2
|
|
)
|
|
|
|
# === CUSTOM TAXONOMIES ===
|
|
|
|
async def list_taxonomies(self) -> str:
|
|
"""
|
|
List all registered taxonomies.
|
|
|
|
Returns list of all taxonomies including built-in (category, post_tag)
|
|
and custom taxonomies (portfolio_category, etc.).
|
|
|
|
Returns:
|
|
JSON string with total count and taxonomies list
|
|
"""
|
|
try:
|
|
taxonomies_data = await self.client.get("taxonomies")
|
|
|
|
# Convert dict to list
|
|
taxonomies = []
|
|
if isinstance(taxonomies_data, dict):
|
|
for slug, data in taxonomies_data.items():
|
|
taxonomies.append(
|
|
{
|
|
"slug": slug,
|
|
"name": data.get("name", slug),
|
|
"description": data.get("description", ""),
|
|
"types": data.get("types", []),
|
|
"hierarchical": data.get("hierarchical", False),
|
|
"rest_base": data.get("rest_base", slug),
|
|
}
|
|
)
|
|
|
|
result = {"total": len(taxonomies), "taxonomies": taxonomies}
|
|
|
|
return json.dumps(result, indent=2)
|
|
except Exception as e:
|
|
return json.dumps(
|
|
{"error": str(e), "message": f"Failed to list taxonomies: {str(e)}"}, indent=2
|
|
)
|
|
|
|
async def list_taxonomy_terms(
|
|
self,
|
|
taxonomy: str,
|
|
per_page: int = 100,
|
|
page: int = 1,
|
|
hide_empty: bool = False,
|
|
parent: int | None = None,
|
|
) -> str:
|
|
"""
|
|
List terms of a specific taxonomy.
|
|
|
|
Args:
|
|
taxonomy: Taxonomy slug (e.g., 'category', 'product_category')
|
|
per_page: Number of terms per page
|
|
page: Page number
|
|
hide_empty: Hide terms with no posts
|
|
parent: Filter by parent term ID
|
|
|
|
Returns:
|
|
JSON string with terms list
|
|
"""
|
|
try:
|
|
params = {
|
|
"per_page": per_page,
|
|
"page": page,
|
|
"hide_empty": "true" if hide_empty else "false",
|
|
}
|
|
|
|
if parent is not None:
|
|
params["parent"] = parent
|
|
|
|
terms = await self.client.get(taxonomy, params=params)
|
|
|
|
result = {
|
|
"total": len(terms) if isinstance(terms, list) else 0,
|
|
"taxonomy": taxonomy,
|
|
"page": page,
|
|
"per_page": per_page,
|
|
"terms": terms,
|
|
}
|
|
|
|
return json.dumps(result, indent=2)
|
|
except Exception as e:
|
|
return json.dumps(
|
|
{
|
|
"error": str(e),
|
|
"message": f"Failed to list taxonomy terms for '{taxonomy}': {str(e)}",
|
|
},
|
|
indent=2,
|
|
)
|
|
|
|
async def create_taxonomy_term(
|
|
self, taxonomy: str, name: str, description: str | None = None, parent: int | None = None
|
|
) -> str:
|
|
"""
|
|
Create a new term in a taxonomy.
|
|
|
|
Args:
|
|
taxonomy: Taxonomy slug (e.g., 'category', 'product_category')
|
|
name: Term name
|
|
description: Term description
|
|
parent: Parent term ID for hierarchical taxonomies
|
|
|
|
Returns:
|
|
JSON string with created term details
|
|
"""
|
|
try:
|
|
data = {"name": name}
|
|
|
|
if description:
|
|
data["description"] = description
|
|
if parent:
|
|
data["parent"] = parent
|
|
|
|
term = await self.client.post(taxonomy, json_data=data)
|
|
|
|
return json.dumps(term, indent=2)
|
|
except Exception as e:
|
|
return json.dumps(
|
|
{
|
|
"error": str(e),
|
|
"message": f"Failed to create taxonomy term in '{taxonomy}': {str(e)}",
|
|
},
|
|
indent=2,
|
|
)
|