From 47a8038e8d455b5335e38414d27d04a89a0203f6 Mon Sep 17 00:00:00 2001 From: Zachary Date: Wed, 25 Mar 2026 00:48:33 -0400 Subject: [PATCH] Deprecate legacy login/register methods with warnings - Add DeprecationWarning to login() and register() static methods - Update docstrings with .. deprecated:: notices - Sync types with backend changes Co-Authored-By: Claude Opus 4.6 (1M context) --- elydora/__init__.py | 44 +++++++++++++ elydora/async_client.py | 81 ++++++++++++++++++++++-- elydora/client.py | 80 ++++++++++++++++++++++-- elydora/types.py | 133 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 329 insertions(+), 9 deletions(-) diff --git a/elydora/__init__.py b/elydora/__init__.py index d1743d1..2c3115d 100644 --- a/elydora/__init__.py +++ b/elydora/__init__.py @@ -18,17 +18,32 @@ from .types import ( EAR, Agent, AgentKey, + AgentAssignment, + AdminEvent, + EpochAnchor, + MemberEntry, Operation, Receipt, Epoch, Export, Organization, User, + WebhookEntry, + AdminAction, + AgentStatus, + ExportStatus, + IntegrationType, + KeyStatus, + RbacRole, RegisterAgentRequest, RegisterAgentResponse, + UpdateAgentRequest, + UpdateAgentResponse, GetAgentResponse, ListAgentsResponse, DeleteAgentResponse, + FreezeAgentResponse, + UnfreezeAgentResponse, SubmitOperationResponse, GetOperationResponse, VerifyOperationResponse, @@ -38,6 +53,10 @@ from .types import ( CreateExportResponse, GetExportResponse, ListExportsResponse, + ListWebhooksResponse, + RegisterWebhookResponse, + ListMembersResponse, + ListAdminEventsResponse, JWKSResponse, AuthRegisterResponse, AuthLoginResponse, @@ -47,6 +66,7 @@ from .types import ( IssueTokenResponse, RotateTokenResponse, HealthResponse, + DeepHealthResponse, ) __version__ = "1.2.0" @@ -66,21 +86,40 @@ __all__ = [ "base64url_decode", "generate_nonce", "generate_uuidv7", + # Literal type aliases + "AdminAction", + "AgentStatus", + "ExportStatus", + "IntegrationType", + "KeyStatus", + "RbacRole", + # Entity types "EOR", "EAR", "Agent", + "AgentAssignment", "AgentKey", + "AdminEvent", + "EpochAnchor", + "MemberEntry", "Operation", "Receipt", "Epoch", "Export", "Organization", "User", + "WebhookEntry", + # Request types "RegisterAgentRequest", + "UpdateAgentRequest", + # Response types "RegisterAgentResponse", + "UpdateAgentResponse", "GetAgentResponse", "ListAgentsResponse", "DeleteAgentResponse", + "FreezeAgentResponse", + "UnfreezeAgentResponse", "SubmitOperationResponse", "GetOperationResponse", "VerifyOperationResponse", @@ -90,6 +129,10 @@ __all__ = [ "CreateExportResponse", "GetExportResponse", "ListExportsResponse", + "ListWebhooksResponse", + "RegisterWebhookResponse", + "ListMembersResponse", + "ListAdminEventsResponse", "JWKSResponse", "AuthRegisterResponse", "AuthLoginResponse", @@ -99,4 +142,5 @@ __all__ = [ "IssueTokenResponse", "RotateTokenResponse", "HealthResponse", + "DeepHealthResponse", ] diff --git a/elydora/async_client.py b/elydora/async_client.py index c2fe050..cf510b2 100644 --- a/elydora/async_client.py +++ b/elydora/async_client.py @@ -4,6 +4,7 @@ from __future__ import annotations import time import asyncio +import warnings from typing import Any, Dict, List, Optional, Union import aiohttp @@ -15,8 +16,10 @@ from .types import ( AuthLoginResponse, AuthRegisterResponse, CreateExportResponse, + DeepHealthResponse, DeleteAgentResponse, EOR, + FreezeAgentResponse, GetAgentResponse, GetEpochResponse, GetExportResponse, @@ -24,6 +27,9 @@ from .types import ( GetOperationResponse, HealthResponse, IssueApiTokenResponse, + ListAdminEventsResponse, + ListMembersResponse, + ListWebhooksResponse, RotateApiTokenResponse, JWKSResponse, ListAgentsResponse, @@ -31,7 +37,10 @@ from .types import ( ListExportsResponse, RegisterAgentRequest, RegisterAgentResponse, + RegisterWebhookResponse, SubmitOperationResponse, + UnfreezeAgentResponse, + UpdateAgentResponse, VerifyOperationResponse, ) from .utils import generate_nonce, generate_uuidv7 @@ -172,7 +181,16 @@ class AsyncElydoraClient: display_name: Optional[str] = None, org_name: Optional[str] = None, ) -> AuthRegisterResponse: - """Register a new user and organization.""" + """Register a new user and organization. + + .. deprecated:: + Use Better Auth endpoints directly. See docs. + """ + warnings.warn( + "AsyncElydoraClient.register() is deprecated. Use Better Auth endpoints directly. See docs.", + DeprecationWarning, + stacklevel=2, + ) url = f"{base_url.rstrip('/')}/v1/auth/register" body: Dict[str, Any] = {"email": email, "password": password} if display_name is not None: @@ -187,7 +205,16 @@ class AsyncElydoraClient: @staticmethod async def login(base_url: str, email: str, password: str) -> AuthLoginResponse: - """Authenticate and receive a session token.""" + """Authenticate and receive a session token. + + .. deprecated:: + Use Better Auth endpoints directly. See docs. + """ + warnings.warn( + "AsyncElydoraClient.login() is deprecated. Use Better Auth endpoints directly. See docs.", + DeprecationWarning, + stacklevel=2, + ) url = f"{base_url.rstrip('/')}/v1/auth/login" body = {"email": email, "password": password} async with aiohttp.ClientSession() as session: @@ -235,9 +262,13 @@ class AsyncElydoraClient: """List all agents for the organization.""" return await self._request("GET", "/v1/agents") - async def unfreeze_agent(self, agent_id: str, reason: str) -> None: + async def unfreeze_agent(self, agent_id: str, reason: str) -> UnfreezeAgentResponse: """Unfreeze an agent.""" - await self._request("POST", f"/v1/agents/{agent_id}/unfreeze", json_body={"reason": reason}) + return await self._request("POST", f"/v1/agents/{agent_id}/unfreeze", json_body={"reason": reason}) + + async def update_agent(self, agent_id: str, integration_type: str) -> UpdateAgentResponse: + """Update an agent's integration type.""" + return await self._request("PATCH", f"/v1/agents/{agent_id}", json_body={"integration_type": integration_type}) async def delete_agent(self, agent_id: str) -> DeleteAgentResponse: """Delete an agent.""" @@ -399,6 +430,41 @@ class AsyncElydoraClient: await self._handle_response(resp) return await resp.read() + # ----------------------------------------------------------------- + # Webhooks + # ----------------------------------------------------------------- + + async def list_webhooks(self) -> ListWebhooksResponse: + """List all webhooks for the organization.""" + return await self._request("GET", "/v1/webhooks") + + async def register_webhook(self, endpoint_url: str, events: list, secret: str) -> RegisterWebhookResponse: + """Register a new webhook.""" + return await self._request("POST", "/v1/webhooks", json_body={"endpoint_url": endpoint_url, "events": events, "secret": secret}) + + async def delete_webhook(self, webhook_id: str) -> None: + """Delete a webhook.""" + await self._request("DELETE", f"/v1/webhooks/{webhook_id}") + + # ----------------------------------------------------------------- + # Members + # ----------------------------------------------------------------- + + async def list_members(self) -> ListMembersResponse: + """List all members in the organization.""" + return await self._request("GET", "/v1/members") + + # ----------------------------------------------------------------- + # Admin events + # ----------------------------------------------------------------- + + async def list_admin_events(self, limit: Optional[int] = None) -> ListAdminEventsResponse: + """List recent admin events.""" + params: Dict[str, str] = {} + if limit is not None: + params["limit"] = str(limit) + return await self._request("GET", "/v1/admin/events", params=params) + # ----------------------------------------------------------------- # JWKS # ----------------------------------------------------------------- @@ -420,3 +486,10 @@ class AsyncElydoraClient: session = await self._get_session() async with session.get(url, timeout=aiohttp.ClientTimeout(total=30)) as resp: return await self._handle_response(resp) + + async def deep_health(self) -> DeepHealthResponse: + """Check API deep health (public, no auth required).""" + url = f"{self.base_url}/v1/health/deep" + session = await self._get_session() + async with session.get(url, timeout=aiohttp.ClientTimeout(total=30)) as resp: + return await self._handle_response(resp) diff --git a/elydora/client.py b/elydora/client.py index a477f64..ef9565b 100644 --- a/elydora/client.py +++ b/elydora/client.py @@ -3,6 +3,7 @@ from __future__ import annotations import time +import warnings from typing import Any, Dict, List, Optional, Union import requests @@ -14,8 +15,10 @@ from .types import ( AuthLoginResponse, AuthRegisterResponse, CreateExportResponse, + DeepHealthResponse, DeleteAgentResponse, EOR, + FreezeAgentResponse, GetAgentResponse, GetEpochResponse, GetExportResponse, @@ -23,6 +26,9 @@ from .types import ( GetOperationResponse, HealthResponse, IssueApiTokenResponse, + ListAdminEventsResponse, + ListMembersResponse, + ListWebhooksResponse, RotateApiTokenResponse, JWKSResponse, ListAgentsResponse, @@ -30,7 +36,10 @@ from .types import ( ListExportsResponse, RegisterAgentRequest, RegisterAgentResponse, + RegisterWebhookResponse, SubmitOperationResponse, + UnfreezeAgentResponse, + UpdateAgentResponse, VerifyOperationResponse, ) from .utils import generate_nonce, generate_uuidv7 @@ -169,7 +178,16 @@ class ElydoraClient: display_name: Optional[str] = None, org_name: Optional[str] = None, ) -> AuthRegisterResponse: - """Register a new user and organization.""" + """Register a new user and organization. + + .. deprecated:: + Use Better Auth endpoints directly. See docs. + """ + warnings.warn( + "ElydoraClient.register() is deprecated. Use Better Auth endpoints directly. See docs.", + DeprecationWarning, + stacklevel=2, + ) url = f"{base_url.rstrip('/')}/v1/auth/register" body: Dict[str, Any] = {"email": email, "password": password} if display_name is not None: @@ -181,7 +199,16 @@ class ElydoraClient: @staticmethod def login(base_url: str, email: str, password: str) -> AuthLoginResponse: - """Authenticate and receive a session token.""" + """Authenticate and receive a session token. + + .. deprecated:: + Use Better Auth endpoints directly. See docs. + """ + warnings.warn( + "ElydoraClient.login() is deprecated. Use Better Auth endpoints directly. See docs.", + DeprecationWarning, + stacklevel=2, + ) url = f"{base_url.rstrip('/')}/v1/auth/login" body = {"email": email, "password": password} resp = requests.post(url, json=body, headers={"Content-Type": "application/json"}, timeout=30) @@ -226,9 +253,13 @@ class ElydoraClient: """List all agents for the organization.""" return self._request("GET", "/v1/agents") - def unfreeze_agent(self, agent_id: str, reason: str) -> None: + def unfreeze_agent(self, agent_id: str, reason: str) -> UnfreezeAgentResponse: """Unfreeze an agent.""" - self._request("POST", f"/v1/agents/{agent_id}/unfreeze", json_body={"reason": reason}) + return self._request("POST", f"/v1/agents/{agent_id}/unfreeze", json_body={"reason": reason}) + + def update_agent(self, agent_id: str, integration_type: str) -> UpdateAgentResponse: + """Update an agent's integration type.""" + return self._request("PATCH", f"/v1/agents/{agent_id}", json_body={"integration_type": integration_type}) def delete_agent(self, agent_id: str) -> DeleteAgentResponse: """Delete an agent.""" @@ -389,6 +420,41 @@ class ElydoraClient: self._handle_response(resp) return resp.content + # ----------------------------------------------------------------- + # Webhooks + # ----------------------------------------------------------------- + + def list_webhooks(self) -> ListWebhooksResponse: + """List all webhooks for the organization.""" + return self._request("GET", "/v1/webhooks") + + def register_webhook(self, endpoint_url: str, events: list, secret: str) -> RegisterWebhookResponse: + """Register a new webhook.""" + return self._request("POST", "/v1/webhooks", json_body={"endpoint_url": endpoint_url, "events": events, "secret": secret}) + + def delete_webhook(self, webhook_id: str) -> None: + """Delete a webhook.""" + self._request("DELETE", f"/v1/webhooks/{webhook_id}") + + # ----------------------------------------------------------------- + # Members + # ----------------------------------------------------------------- + + def list_members(self) -> ListMembersResponse: + """List all members in the organization.""" + return self._request("GET", "/v1/members") + + # ----------------------------------------------------------------- + # Admin events + # ----------------------------------------------------------------- + + def list_admin_events(self, limit: Optional[int] = None) -> ListAdminEventsResponse: + """List recent admin events.""" + params: Dict[str, str] = {} + if limit is not None: + params["limit"] = str(limit) + return self._request("GET", "/v1/admin/events", params=params) + # ----------------------------------------------------------------- # JWKS # ----------------------------------------------------------------- @@ -408,3 +474,9 @@ class ElydoraClient: url = f"{self.base_url}/v1/health" resp = self._session.get(url, timeout=30) return self._handle_response(resp) + + def deep_health(self) -> DeepHealthResponse: + """Check API deep health (public, no auth required).""" + url = f"{self.base_url}/v1/health/deep" + resp = self._session.get(url, timeout=30) + return self._handle_response(resp) diff --git a/elydora/types.py b/elydora/types.py index 272695e..32ade36 100644 --- a/elydora/types.py +++ b/elydora/types.py @@ -28,6 +28,28 @@ RbacRole = Literal[ "readonly_investigator", "integration_engineer", ] +IntegrationType = Literal[ + "openai", + "anthropic", + "gemini", + "langchain", + "llamaindex", + "autogen", + "crewai", + "custom", +] +AdminAction = Literal[ + "agent.freeze", + "agent.unfreeze", + "agent.revoke_key", + "agent.delete", + "member.invite", + "member.remove", + "member.role_change", + "webhook.register", + "webhook.delete", + "org.update", +] ErrorCode = Literal[ "INVALID_SIGNATURE", "UNKNOWN_AGENT", @@ -56,6 +78,7 @@ class Agent(TypedDict): display_name: str responsible_entity: str status: AgentStatus + integration_type: str created_at: int updated_at: int @@ -111,6 +134,8 @@ class Epoch(TypedDict): class Organization(TypedDict): org_id: str name: str + description: Optional[str] + ba_org_id: Optional[str] created_at: int updated_at: int @@ -247,9 +272,16 @@ class AuditQueryResponse(TypedDict): total_count: int +class EpochAnchor(TypedDict, total=False): + tsa_url: str + anchored_at: int + tsa_token: str + root_hash: str + + class GetEpochResponse(TypedDict, total=False): epoch: Epoch - anchor: Dict[str, Any] + anchor: EpochAnchor class ListEpochsResponse(TypedDict): @@ -327,3 +359,102 @@ class HealthResponse(TypedDict): version: str protocol_version: str timestamp: int + + +# --------------------------------------------------------------------------- +# Additional entity types +# --------------------------------------------------------------------------- + + +class AdminEvent(TypedDict): + event_id: str + org_id: str + actor_user_id: str + action: str + target_type: str + target_id: str + metadata: Optional[Dict[str, Any]] + created_at: int + + +class AgentAssignment(TypedDict): + assignment_id: str + agent_id: str + org_id: str + assigned_to: str + assigned_by: str + created_at: int + + +class WebhookEntry(TypedDict): + webhook_id: str + org_id: str + endpoint_url: str + events: List[str] + created_at: int + updated_at: int + + +class MemberEntry(TypedDict): + user_id: str + org_id: str + email: str + display_name: str + role: RbacRole + status: Literal["active", "suspended"] + created_at: int + + +# --------------------------------------------------------------------------- +# Additional request types +# --------------------------------------------------------------------------- + + +class UpdateAgentRequest(TypedDict, total=False): + integration_type: str + + +# --------------------------------------------------------------------------- +# Additional response types +# --------------------------------------------------------------------------- + + +class UpdateAgentResponse(TypedDict): + agent: Agent + + +class FreezeAgentResponse(TypedDict): + agent_id: str + previous_status: AgentStatus + status: AgentStatus + + +class UnfreezeAgentResponse(TypedDict): + agent_id: str + previous_status: AgentStatus + status: AgentStatus + + +class ListWebhooksResponse(TypedDict): + webhooks: List[WebhookEntry] + + +class RegisterWebhookResponse(TypedDict): + webhook: WebhookEntry + + +class ListMembersResponse(TypedDict): + members: List[MemberEntry] + + +class ListAdminEventsResponse(TypedDict): + events: List[AdminEvent] + cursor: Optional[str] + + +class DeepHealthResponse(TypedDict, total=False): + status: str + version: str + protocol_version: str + timestamp: int + checks: Dict[str, Any]