Source code for tooluniverse.datacite_tool
"""
DataCite REST tool with proper parameter mapping for the DataCite API.
The DataCite API uses hyphenated query parameter names (e.g., resource-type-id,
page[size]) which need mapping from the snake_case tool parameter names.
"""
import requests
from typing import Any, Dict
from .base_rest_tool import BaseRESTTool
from .tool_registry import register_tool
[docs]
@register_tool("DataCiteRESTTool")
class DataCiteRESTTool(BaseRESTTool):
"""REST tool for DataCite API endpoints with custom parameter mapping."""
[docs]
def _get_param_mapping(self) -> Dict[str, str]:
"""Map tool parameter names to DataCite API query parameter names."""
return {
"resource_type_general": "resource-type-id",
"resource_type": "resource-type-id",
"page_size": "page[size]",
"page_number": "page[number]",
"publisher": "publisher",
"year": "published",
}
[docs]
def _process_response(
self, response: requests.Response, url: str
) -> Dict[str, Any]:
"""Process DataCite API response.
For search endpoints: data is the list of items, meta has total.
For single-item endpoints: data is the single record object.
"""
raw = response.json()
# Search endpoint returns {"data": [...], "meta": {...}}
# Single-item endpoint returns {"data": {...}}
items = raw.get("data", raw)
meta = raw.get("meta", {})
result = {
"status": "success",
"data": items,
"url": url,
}
if meta:
result["meta"] = meta
result["total"] = meta.get("total", 0)
if isinstance(items, list):
result["count"] = len(items)
return result
[docs]
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
"""Validate DataCite pagination and expose its 10k search window."""
is_search = self.tool_config.get("name") in {
"DataCite_search_dois",
"DataCite_search_datasets",
}
if not is_search:
return super().run(arguments)
props = self.tool_config.get("parameter", {}).get("properties", {})
page_size = arguments.get(
"page_size", props.get("page_size", {}).get("default", 10)
)
page_number = arguments.get(
"page_number", props.get("page_number", {}).get("default", 1)
)
if (
isinstance(page_size, bool)
or not isinstance(page_size, int)
or not 1 <= page_size <= 1000
):
return {
"status": "error",
"error": "page_size must be an integer between 1 and 1000",
"parameter": "page_size",
}
if (
isinstance(page_number, bool)
or not isinstance(page_number, int)
or page_number < 1
):
return {
"status": "error",
"error": "page_number must be an integer greater than or equal to 1",
"parameter": "page_number",
}
normalized = dict(arguments)
normalized["page_size"] = page_size
normalized["page_number"] = page_number
result = super().run(normalized)
if result.get("status") != "success":
return result
records = result.get("data")
if not isinstance(records, list):
records = []
meta = result.get("meta")
if not isinstance(meta, dict):
meta = {}
total = meta.get("total")
total_is_exact = isinstance(total, int) and not isinstance(total, bool)
upstream_page = meta.get("page")
page = (
upstream_page
if isinstance(upstream_page, int) and not isinstance(upstream_page, bool)
else page_number
)
upstream_pages = meta.get("totalPages")
pages_known = isinstance(upstream_pages, int) and not isinstance(
upstream_pages, bool
)
accessible = (
min(total, upstream_pages * page_size)
if total_is_exact and pages_known
else None
)
returned = len(records)
has_more = bool(pages_known and returned and page < upstream_pages)
upstream_truncated = bool(
total_is_exact and accessible is not None and accessible < total
)
result["metadata"] = {
"returned": returned,
"total_results": total if total_is_exact else None,
"total_results_is_exact": total_is_exact,
"accessible_results": accessible,
"accessible_pages": upstream_pages if pages_known else None,
"upstream_truncated": upstream_truncated,
"page": page,
"page_size": page_size,
"has_more": has_more,
"next_page": page + 1 if has_more else None,
"truncated": page > 1 or has_more or upstream_truncated,
"result_set_complete": bool(
total_is_exact and page == 1 and returned == total
),
"pagination_scope": "datacite_jsonapi_10000_result_window",
"warning": (
"DataCite matched more records than its approximately 10,000-record "
"deep-pagination window exposes. Refine the query or add filters to "
"reach records outside accessible_results."
if upstream_truncated
else None
),
}
return result