Source code for tooluniverse.addgene_tool

"""
Addgene Developers API Tool

Provides programmatic access to Addgene's plasmid catalog via the official
Addgene Developers API (https://developers.addgene.org/).

Supports:
- Searching plasmids by name, gene, species, vector type, purpose, etc.
- Retrieving detailed plasmid information (cloning, inserts, resistance, growth)
- Browsing depositors (PIs) via plasmid catalog queries

API Base: https://api.developers.addgene.org
Authentication: Token-based via ADDGENE_API_KEY environment variable.
Register at https://developers.addgene.org/ to obtain a free token.
"""

import os
import requests
from typing import Dict, Any, Optional, List
from .base_tool import BaseTool
from .tool_registry import register_tool

ADDGENE_API_URL = "https://api.developers.addgene.org"


[docs] @register_tool("AddgeneTool") class AddgeneTool(BaseTool): """ Tool for querying the Addgene plasmid repository. Addgene is a nonprofit global plasmid repository that archives and distributes plasmids for the scientific community. This tool provides access to: - Plasmid search (by name, gene, species, vector type, purpose) - Plasmid detail retrieval (cloning info, inserts, resistance markers) - Depositor/PI search Requires API token via ADDGENE_API_KEY environment variable. Register at https://developers.addgene.org/ for access. """
[docs] def __init__(self, tool_config: Dict[str, Any]): super().__init__(tool_config) self.timeout = tool_config.get("timeout", 30) self.parameter = tool_config.get("parameter", {}) self.required = self.parameter.get("required", []) self.api_key = os.environ.get("ADDGENE_API_KEY", "")
[docs] def _get_headers(self): """Get request headers with auth token.""" headers = {"Accept": "application/json"} if self.api_key: headers["Authorization"] = "Token " + self.api_key return headers
[docs] def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]: operation = arguments.get("operation") if not operation: return {"status": "error", "error": "Missing required parameter: operation"} if not self.api_key: return { "status": "error", "error": ( "Addgene API key required. Set ADDGENE_API_KEY environment variable. " "Register at https://developers.addgene.org/ for access." ), } handlers = { "search_plasmids": self._search_plasmids, "get_plasmid": self._get_plasmid, "search_depositors": self._search_depositors, } handler = handlers.get(operation) if not handler: return { "status": "error", "error": "Unknown operation: {}. Available: {}".format( operation, ", ".join(handlers.keys()) ), } try: return handler(arguments) except requests.exceptions.Timeout: return {"status": "error", "error": "Addgene API request timed out"} except requests.exceptions.ConnectionError: return {"status": "error", "error": "Failed to connect to Addgene API"} except Exception as e: return {"status": "error", "error": "Operation failed: {}".format(str(e))}
[docs] def _search_plasmids(self, arguments: Dict[str, Any]) -> Dict[str, Any]: """ Search Addgene plasmid catalog. Supports filtering by name, genes, species, vector_types, purpose, experimental_use, expression, and more. """ query = arguments.get("query") organism = arguments.get("organism") vector_type = arguments.get("vector_type") limit = min(int(arguments.get("limit", 10)), 100) params = {"page_size": limit} if query: params["name"] = query if organism: params["species"] = organism if vector_type: params["vector_types"] = vector_type response = requests.get( ADDGENE_API_URL + "/catalog/plasmid/", params=params, headers=self._get_headers(), timeout=self.timeout, ) if response.status_code == 401: return { "status": "error", "error": "Authentication failed. Check your ADDGENE_API_KEY.", } response.raise_for_status() data = response.json() results = data.get("results", []) return { "status": "success", "data": { "plasmids": results, "total_count": data.get("count", len(results)), "next_page": data.get("next"), }, "metadata": { "source": "Addgene", "query": query, "organism": organism, "vector_type": vector_type, }, }
[docs] def _get_plasmid(self, arguments: Dict[str, Any]) -> Dict[str, Any]: """ Get detailed information about a specific plasmid. Returns full plasmid record including cloning info, inserts, resistance markers, growth conditions, article, depositor comments. """ plasmid_id = arguments.get("plasmid_id") if not plasmid_id: return { "status": "error", "error": "Missing required parameter: plasmid_id", } plasmid_id = str(plasmid_id).strip() response = requests.get( "{}/catalog/plasmid/{}/".format(ADDGENE_API_URL, plasmid_id), headers=self._get_headers(), timeout=self.timeout, ) if response.status_code == 401: return { "status": "error", "error": "Authentication failed. Check your ADDGENE_API_KEY.", } if response.status_code == 404: return { "status": "error", "error": "Plasmid ID {} not found in Addgene".format(plasmid_id), } response.raise_for_status() plasmid = response.json() return { "status": "success", "data": plasmid, "metadata": { "source": "Addgene", "plasmid_id": plasmid_id, "url": "https://www.addgene.org/{}/".format(plasmid_id), }, }
[docs] def _search_depositors(self, arguments: Dict[str, Any]) -> Dict[str, Any]: """ Search for depositors (PIs) by querying plasmids and extracting unique depositor information. The Addgene API does not have a dedicated depositor search endpoint, so this searches plasmids filtered by PI name or institution, then extracts and deduplicates depositor info from results. """ name = arguments.get("name") institution = arguments.get("institution") if not name and not institution: return { "status": "error", "error": "At least one of 'name' or 'institution' is required.", } params = {"page_size": 50} if name: params["pis"] = name if institution: # Institution is not a direct filter in the API; # we search by article_authors which may contain institution info params["article_authors"] = institution response = requests.get( ADDGENE_API_URL + "/catalog/plasmid/", params=params, headers=self._get_headers(), timeout=self.timeout, ) if response.status_code == 401: return { "status": "error", "error": "Authentication failed. Check your ADDGENE_API_KEY.", } response.raise_for_status() data = response.json() results = data.get("results", []) # Extract unique depositors from plasmid results depositors = {} for plasmid in results: depositor_list = plasmid.get("depositor", []) for dep in depositor_list: dep_str = str(dep) if dep_str not in depositors: depositors[dep_str] = { "name": dep_str, "plasmid_count": 0, "example_plasmids": [], } depositors[dep_str]["plasmid_count"] += 1 if len(depositors[dep_str]["example_plasmids"]) < 3: depositors[dep_str]["example_plasmids"].append( {"id": plasmid.get("id"), "name": plasmid.get("name")} ) depositor_list = sorted( depositors.values(), key=lambda x: x["plasmid_count"], reverse=True ) return { "status": "success", "data": { "depositors": depositor_list, "total_depositors": len(depositor_list), "total_plasmids_searched": data.get("count", len(results)), }, "metadata": { "source": "Addgene", "name_query": name, "institution_query": institution, }, }