Metadata-Version: 2.4
Name: danube
Version: 0.3.0
Summary: Official Python SDK for Danube
Project-URL: Homepage, https://danubeai.com
Project-URL: Documentation, https://docs.danubeai.com/sdk/python
Author-email: Danube <support@danubeai.com>
License: MIT License
        
        Copyright (c) 2026 Danube AI, Inc.
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: ai,api,danube,mcp,sdk,tools
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Typing :: Typed
Requires-Python: >=3.8
Requires-Dist: httpx>=0.25.0
Requires-Dist: pydantic>=2.0.0
Provides-Extra: dev
Requires-Dist: black>=23.0.0; extra == 'dev'
Requires-Dist: mypy>=1.0.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Requires-Dist: respx>=0.20.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Description-Content-Type: text/markdown

# Danube Python SDK

Official Python SDK for [Danube AI](https://danubeai.com) - access services, tools, skills, and user identity through a clean, Pythonic interface.

## Installation

```bash
pip install danube
```

Or install from source:

```bash
pip install -e .
```

## Quick Start

```python
from danube import DanubeClient

# Initialize with API key (or set DANUBE_API_KEY environment variable)
client = DanubeClient(api_key="dk_...")

# List available services
services = client.services.list(limit=5)
for service in services:
    print(f"{service.name}: {service.tool_count} tools")

# Search for tools
tools = client.tools.search("send email")
for tool in tools:
    print(f"{tool.name}: {tool.description}")

# Execute a tool
result = client.tools.execute(
    tool_name="Gmail - Send Email",
    parameters={
        "to": "user@example.com",
        "subject": "Hello from Danube!",
        "body": "This email was sent using the Danube SDK."
    }
)

if result.success:
    print(f"Success: {result.content}")
else:
    print(f"Error: {result.error}")

# Clean up
client.close()
```

## Usage

### Context Manager (Recommended)

```python
from danube import DanubeClient

with DanubeClient(api_key="dk_...") as client:
    services = client.services.list()
    # Client automatically closes when exiting the context
```

### Async Usage

For better performance in async applications:

```python
import asyncio
from danube import AsyncDanubeClient

async def main():
    async with AsyncDanubeClient(api_key="dk_...") as client:
        # Parallel requests
        services, tools = await asyncio.gather(
            client.services.list(limit=10),
            client.tools.search("weather"),
        )

        # Execute a tool
        result = await client.tools.execute(
            tool_name="Weather - Get Current",
            parameters={"city": "San Francisco"}
        )
        print(result.content)

asyncio.run(main())
```

## API Reference

### Services

```python
# List/search services
services = client.services.list(query="github", limit=10)

# Get a specific service
service = client.services.get("service-uuid")

# Get tools for a service
result = client.services.get_tools("service-uuid")
if result.needs_configuration:
    print(f"Configure at: {result.configuration_url}")
else:
    for tool in result.tools:
        print(tool.name)
```

### Tools

```python
# Search for tools
tools = client.tools.search("send email", service_id="optional-filter")

# Get a specific tool
tool = client.tools.get("tool-uuid")

# Execute by ID (faster)
result = client.tools.execute(tool_id="tool-uuid", parameters={"key": "value"})

# Execute by name (searches first)
result = client.tools.execute(tool_name="Gmail - Send Email", parameters={...})

# Check result
if result.success:
    print(result.content)
    print(f"Took {result.duration_ms}ms")
else:
    print(f"Error: {result.error}")

# Batch execute (up to 10 calls)
results = client.tools.batch_execute([
    {"tool_id": "tool-uuid-1", "parameters": {"city": "San Francisco"}},
    {"tool_id": "tool-uuid-2", "parameters": {"city": "New York"}},
])
for r in results:
    print(f"{r.tool_id}: {'ok' if r.success else r.error}")
```

### Skills

```python
# Search for skills
skills = client.skills.search("pdf processing")

# Get full skill content
skill = client.skills.get(skill_id="skill-uuid")
# or
skill = client.skills.get(skill_name="pdf-processing")

print(f"Instructions:\n{skill.skill_md}")

for script in skill.scripts:
    print(f"Script: {script.name}")
    print(script.content)
```

### Identity

```python
# Get user identity
identity = client.identity.get()
print(f"Name: {identity.name}")
print(f"Email: {identity.email}")

```

### Workflows

```python
# List public workflows
workflows = client.workflows.list(query="email automation", limit=10)

# Get workflow details
workflow = client.workflows.get("workflow-uuid")
for step in workflow.steps:
    print(f"Step {step.step_number}: {step.tool_name}")

# Execute a workflow
execution = client.workflows.execute(
    "workflow-uuid",
    inputs={"query": "test", "recipient": "user@example.com"}
)

# Check execution result
result = client.workflows.get_execution(execution.id)
print(f"Status: {result.status}")
for step in result.step_results:
    print(f"  Step {step.step_number}: {step.status}")

# Create a workflow
workflow = client.workflows.create(
    name="My Pipeline",
    steps=[
        {
            "step_number": 1,
            "tool_id": "tool-uuid",
            "tool_name": "Weather - Get Forecast",
            "description": "Fetch forecast",
            "input_mapping": {"city": "{{inputs.city}}"},
        }
    ],
    visibility="private",
    tags=["weather"],
)

# Update a workflow
updated = client.workflows.update("workflow-uuid", name="Renamed Pipeline")

# Delete a workflow
client.workflows.delete("workflow-uuid")
```

### Sites

```python
# Search agent-friendly site directory
sites = client.sites.search("stripe", category="finance", limit=10)

# Get site by ID
site = client.sites.get("site-uuid")
print(f"Domain: {site.domain}")
print(f"Components: {list(site.components.__dict__.keys())}")

# Get site by domain
site = client.sites.get_by_domain("stripe.com")
if site.components.pricing:
    print(f"Pricing: {site.components.pricing}")
```

### Wallet

```python
# Check balance
balance = client.wallet.get_balance()
print(f"Balance: ${balance.balance_dollars:.2f}")

# Get transaction history
transactions = client.wallet.get_transactions()
for tx in transactions:
    print(f"{tx.type}: {tx.amount_cents}c ({tx.created_at})")
```

### Ratings

```python
# Submit a rating
client.ratings.submit(tool_id="tool-uuid", rating=5, comment="Works great!")

# Get your rating for a tool
my_rating = client.ratings.get_mine("tool-uuid")

# Get aggregate ratings
aggregate = client.ratings.get_tool_ratings("tool-uuid")
print(f"Average: {aggregate.average_rating} ({aggregate.total_ratings} ratings)")
```

### Credentials

```python
# Store a credential for a service
result = client.credentials.store(
    service_id="service-uuid",
    credential_type="bearer",
    credential_value="sk-..."
)
print(f"Stored for: {result.service_name}")
```

### Device Auth

```python
# Initiate device auth flow (for CLI onboarding)
code = client.device_auth.request_code()
print(f"Go to {code.verification_url} and enter: {code.user_code}")

# Poll for token
import time
while True:
    token = client.device_auth.poll_token(code.device_code)
    if token:
        print(f"API key: {token.api_key}")
        break
    time.sleep(code.interval)
```

## Configuration

### Environment Variables

| Variable | Description | Default |
|----------|-------------|---------|
| `DANUBE_API_KEY` | Your Danube API key | (required) |
| `DANUBE_API_URL` | API base URL | `https://api.danubeai.com` |
| `DANUBE_TIMEOUT` | Request timeout (seconds) | `30` |
| `DANUBE_MAX_RETRIES` | Max retry attempts | `3` |

### Programmatic Configuration

```python
from danube import DanubeClient

client = DanubeClient(
    api_key="dk_...",
    base_url="https://api.danubeai.com",
    timeout=60.0,
    max_retries=5,
)
```

## Error Handling

```python
from danube import DanubeClient
from danube.exceptions import (
    NotFoundError,
    ExecutionError,
    ConfigurationRequiredError,
    RateLimitError,
    AuthenticationError,
)

with DanubeClient() as client:
    try:
        result = client.tools.execute(tool_id="invalid-id")
    except AuthenticationError:
        print("Invalid API key")
    except NotFoundError as e:
        print(f"Tool not found: {e}")
    except ExecutionError as e:
        print(f"Execution failed: {e}")
    except ConfigurationRequiredError as e:
        print(f"Configure credentials at: {e.configuration_url}")
    except RateLimitError as e:
        if e.retry_after:
            print(f"Rate limited. Retry in {e.retry_after}s")
```

## Models

### Service

| Field | Type | Description |
|-------|------|-------------|
| `id` | str | Service UUID |
| `name` | str | Service name |
| `description` | str | Service description |
| `service_type` | str | "mcp_server", "api", "internal", or "website" |
| `tool_count` | int | Number of available tools |
| `is_connected` | bool | Whether MCP service is connected |

### Tool

| Field | Type | Description |
|-------|------|-------------|
| `id` | str | Tool UUID |
| `name` | str | Tool name |
| `description` | str | Tool description |
| `service_id` | str | Parent service ID |
| `parameters` | dict | Parameter definitions |

### ToolResult

| Field | Type | Description |
|-------|------|-------------|
| `success` | bool | Whether execution succeeded |
| `result` | Any | Execution result |
| `error` | str | Error message (if failed) |
| `content` | str | Result as text (property) |
| `duration_ms` | float | Execution time |

### Skill / SkillContent

| Field | Type | Description |
|-------|------|-------------|
| `id` | str | Skill UUID |
| `name` | str | Skill name |
| `description` | str | Skill description |
| `skill_md` | str | Main SKILL.md content |
| `scripts` | list | Script files |
| `references` | list | Reference files |
| `assets` | list | Asset files |

### Identity / Contact

| Field | Type | Description |
|-------|------|-------------|
| `profile` | dict | User profile data |
| `key_people` | list | Important contacts |
| `contacts` | list | General contacts |
| `name` | str | Contact name |
| `email` | str | Contact email |

### WorkflowDetail / WorkflowStep

| Field | Type | Description |
|-------|------|-------------|
| `id` | str | Workflow UUID |
| `name` | str | Workflow name |
| `description` | str | Workflow description |
| `steps` | list[WorkflowStep] | Ordered execution steps |
| `visibility` | str | "private" or "public" |
| `tags` | list[str] | Workflow tags |
| `step_number` | int | Step order (on WorkflowStep) |
| `tool_id` | str | Tool UUID (on WorkflowStep) |
| `tool_name` | str | Tool name (on WorkflowStep) |
| `input_mapping` | dict | Template mapping (on WorkflowStep) |

### WorkflowExecution / WorkflowStepResult

| Field | Type | Description |
|-------|------|-------------|
| `id` | str | Execution UUID |
| `status` | str | "pending", "running", "success", or "failed" |
| `inputs` | dict | Input values used |
| `step_results` | list[WorkflowStepResult] | Per-step results |
| `error` | str? | Error message if failed |
| `execution_time_ms` | int? | Total execution time |

### AgentSite / AgentSiteListItem

| Field | Type | Description |
|-------|------|-------------|
| `id` | str | Site UUID |
| `domain` | str | Site domain |
| `url` | str | Full URL |
| `status` | str | "pending", "crawling", "analyzing", "review", "live", "error" |
| `components` | SiteComponents | Extracted structured components |
| `discovered_tools` | list[dict] | Auto-generated tool schemas |
| `category` | str? | Site category |
| `tags` | list[str] | Tags |

### SiteComponents

| Field | Type | Description |
|-------|------|-------------|
| `contact` | dict? | Emails, phones, forms |
| `about` | dict? | Company info |
| `services` | list[dict]? | Services offered |
| `docs` | dict? | Documentation links |
| `pricing` | dict? | Pricing plans |
| `faq` | list[dict]? | FAQ items |
| `legal` | dict? | Privacy policy, ToS |
| `navigation` | list[dict]? | Site navigation |

### WalletBalance / WalletTransaction

| Field | Type | Description |
|-------|------|-------------|
| `balance_cents` | int | Balance in cents |
| `balance_dollars` | float | Balance in dollars |
| `type` | str | Transaction type (on WalletTransaction) |
| `amount_cents` | int | Transaction amount (on WalletTransaction) |

### ToolRating / RatingAggregate

| Field | Type | Description |
|-------|------|-------------|
| `tool_id` | str | Tool UUID |
| `rating` | int | Star rating (1-5) |
| `comment` | str? | Optional comment |
| `average_rating` | float | Average stars (on RatingAggregate) |
| `total_ratings` | int | Total rating count (on RatingAggregate) |

### CredentialStoreResult

| Field | Type | Description |
|-------|------|-------------|
| `success` | bool | Whether storage succeeded |
| `service_id` | str | Service UUID |
| `service_name` | str | Service name |
| `credential_type` | str | "bearer" or "api_key" |

### DeviceCode / DeviceToken

| Field | Type | Description |
|-------|------|-------------|
| `device_code` | str | Device code for polling |
| `user_code` | str | User-facing code (XXXX-XXXX) |
| `verification_url` | str | URL to enter user_code |
| `expires_in` | int | Seconds until expiry |
| `api_key` | str | Returned API key (on DeviceToken) |

## Development

```bash
# Install dev dependencies
pip install -e ".[dev]"

# Run tests
pytest

# Type checking
mypy src/danube

# Format code
black src/danube tests
ruff check src/danube tests
```

## License

MIT License - see [LICENSE](LICENSE) for details.
