dobby-ai-sdk 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dobby_ai_sdk-0.1.0/.gitignore +82 -0
- dobby_ai_sdk-0.1.0/PKG-INFO +150 -0
- dobby_ai_sdk-0.1.0/README.md +118 -0
- dobby_ai_sdk-0.1.0/pyproject.toml +64 -0
- dobby_ai_sdk-0.1.0/src/dobby_sdk/__init__.py +50 -0
- dobby_ai_sdk-0.1.0/src/dobby_sdk/_base.py +83 -0
- dobby_ai_sdk-0.1.0/src/dobby_sdk/_config.py +50 -0
- dobby_ai_sdk-0.1.0/src/dobby_sdk/_exceptions.py +84 -0
- dobby_ai_sdk-0.1.0/src/dobby_sdk/client.py +148 -0
- dobby_ai_sdk-0.1.0/src/dobby_sdk/resources/__init__.py +1 -0
- dobby_ai_sdk-0.1.0/src/dobby_sdk/resources/agents.py +416 -0
- dobby_ai_sdk-0.1.0/src/dobby_sdk/resources/approvals.py +81 -0
- dobby_ai_sdk-0.1.0/src/dobby_sdk/resources/chat.py +73 -0
- dobby_ai_sdk-0.1.0/src/dobby_sdk/resources/costs.py +106 -0
- dobby_ai_sdk-0.1.0/src/dobby_sdk/resources/keys.py +78 -0
- dobby_ai_sdk-0.1.0/src/dobby_sdk/resources/tasks.py +141 -0
- dobby_ai_sdk-0.1.0/tests/test_client.py +141 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# ============================================
|
|
2
|
+
# Dobby Platform - Git Ignore
|
|
3
|
+
# ============================================
|
|
4
|
+
|
|
5
|
+
# Environment Variables (CRITICAL - Never commit!)
|
|
6
|
+
.env
|
|
7
|
+
.env.local
|
|
8
|
+
.env.dev
|
|
9
|
+
.env.prod
|
|
10
|
+
.env.*.local
|
|
11
|
+
.env.*.backup
|
|
12
|
+
*.backup
|
|
13
|
+
|
|
14
|
+
# Credentials & Secrets
|
|
15
|
+
/credentials/
|
|
16
|
+
*.pem
|
|
17
|
+
*.key
|
|
18
|
+
service-account.json
|
|
19
|
+
|
|
20
|
+
# Python
|
|
21
|
+
__pycache__/
|
|
22
|
+
*.py[cod]
|
|
23
|
+
*$py.class
|
|
24
|
+
*.so
|
|
25
|
+
.Python
|
|
26
|
+
*.egg-info/
|
|
27
|
+
.eggs/
|
|
28
|
+
|
|
29
|
+
# Node
|
|
30
|
+
node_modules/
|
|
31
|
+
npm-debug.log*
|
|
32
|
+
yarn-debug.log*
|
|
33
|
+
yarn-error.log*
|
|
34
|
+
.pnpm-debug.log*
|
|
35
|
+
|
|
36
|
+
# Next.js
|
|
37
|
+
.next/
|
|
38
|
+
out/
|
|
39
|
+
build/
|
|
40
|
+
dist/
|
|
41
|
+
|
|
42
|
+
# Debug
|
|
43
|
+
*.log
|
|
44
|
+
|
|
45
|
+
# IDE
|
|
46
|
+
.vscode/
|
|
47
|
+
.idea/
|
|
48
|
+
*.swp
|
|
49
|
+
*.swo
|
|
50
|
+
*~
|
|
51
|
+
.DS_Store
|
|
52
|
+
|
|
53
|
+
# Testing
|
|
54
|
+
coverage/
|
|
55
|
+
.nyc_output/
|
|
56
|
+
test-results-*.json
|
|
57
|
+
testing_sessions/
|
|
58
|
+
|
|
59
|
+
# Build output artifacts
|
|
60
|
+
tsc-*.txt
|
|
61
|
+
test_write.txt
|
|
62
|
+
|
|
63
|
+
# Docker
|
|
64
|
+
docker-compose.override.yml
|
|
65
|
+
|
|
66
|
+
# OS
|
|
67
|
+
Thumbs.db
|
|
68
|
+
|
|
69
|
+
# Backups
|
|
70
|
+
backups/
|
|
71
|
+
|
|
72
|
+
# Claude settings (local)
|
|
73
|
+
.claude/settings.local.json
|
|
74
|
+
|
|
75
|
+
# ============================================
|
|
76
|
+
# Render Monitor
|
|
77
|
+
# ============================================
|
|
78
|
+
render-monitor/.env
|
|
79
|
+
render-monitor/logs/
|
|
80
|
+
render-monitor/*.log
|
|
81
|
+
render-monitor/*.jsonl
|
|
82
|
+
.venv/
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dobby-ai-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the Dobby AI Platform — Home for your AI agents
|
|
5
|
+
Project-URL: Homepage, https://dobby-ai.com
|
|
6
|
+
Project-URL: Documentation, https://dobby-ai.com/docs/sdk
|
|
7
|
+
Project-URL: Repository, https://github.com/gil-dobby/dobby-sdk-python
|
|
8
|
+
Author-email: Dobby AI <dev@dobby-ai.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Keywords: a2a,agents,ai,dobby,gateway,llm,mcp
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Requires-Dist: httpx>=0.25.0
|
|
23
|
+
Requires-Dist: openai>=1.0.0
|
|
24
|
+
Requires-Dist: pydantic>=2.0.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: mypy>=1.8.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-httpx>=0.30.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff>=0.3.0; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# Dobby SDK for Python
|
|
34
|
+
|
|
35
|
+
Official Python SDK for the [Dobby AI Platform](https://dobby-ai.com) — Home for your AI agents.
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install dobby-ai-sdk
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from dobby_sdk import DobbyClient
|
|
47
|
+
|
|
48
|
+
client = DobbyClient(api_key="gk_user_...")
|
|
49
|
+
|
|
50
|
+
# LLM calls (OpenAI-compatible)
|
|
51
|
+
response = client.chat.completions.create(
|
|
52
|
+
model="claude-sonnet-4-20250514",
|
|
53
|
+
messages=[{"role": "user", "content": "Hello from Dobby!"}],
|
|
54
|
+
)
|
|
55
|
+
print(response.choices[0].message.content)
|
|
56
|
+
|
|
57
|
+
# Streaming
|
|
58
|
+
stream = client.chat.completions.create(
|
|
59
|
+
model="gpt-4o-mini",
|
|
60
|
+
messages=[{"role": "user", "content": "Explain AI agents"}],
|
|
61
|
+
stream=True,
|
|
62
|
+
)
|
|
63
|
+
for chunk in stream:
|
|
64
|
+
print(chunk.choices[0].delta.content or "", end="")
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Task Management
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
# Create a task
|
|
71
|
+
task = client.tasks.create(
|
|
72
|
+
title="Review PR #42",
|
|
73
|
+
priority="high",
|
|
74
|
+
agent_name="dobby-code-reviewer-agent",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# List pending tasks
|
|
78
|
+
tasks = client.tasks.list(status="pending")
|
|
79
|
+
|
|
80
|
+
# Approve a task (HITL)
|
|
81
|
+
client.approvals.approve(task["id"], comment="Looks good!")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Agent Fleet
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
# List all agents
|
|
88
|
+
agents = client.agents.list()
|
|
89
|
+
|
|
90
|
+
# Register an external agent
|
|
91
|
+
agent = client.agents.register(
|
|
92
|
+
display_name="Research Agent",
|
|
93
|
+
framework="crewai",
|
|
94
|
+
protocol="a2a",
|
|
95
|
+
endpoint_url="https://my-agent.example.com",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Pause/resume
|
|
99
|
+
client.agents.pause("agent_abc123")
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Cost Tracking
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
# Organization cost summary
|
|
106
|
+
costs = client.costs.summary(period="30d")
|
|
107
|
+
|
|
108
|
+
# Per-agent breakdown
|
|
109
|
+
agent_costs = client.costs.by_agent(period="7d")
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Async Support
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from dobby_sdk import AsyncDobbyClient
|
|
116
|
+
|
|
117
|
+
async with AsyncDobbyClient(api_key="gk_user_...") as client:
|
|
118
|
+
response = await client.chat.completions.create(
|
|
119
|
+
model="claude-sonnet-4-20250514",
|
|
120
|
+
messages=[{"role": "user", "content": "Hello async!"}],
|
|
121
|
+
)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Configuration
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
client = DobbyClient(
|
|
128
|
+
api_key="gk_user_...", # or DOBBY_API_KEY env var
|
|
129
|
+
base_url="https://dobby-ai.com", # or DOBBY_BASE_URL
|
|
130
|
+
org_id="org_...", # or DOBBY_ORG_ID
|
|
131
|
+
tenant_id="tenant_...", # or DOBBY_TENANT_ID
|
|
132
|
+
timeout=120.0,
|
|
133
|
+
max_retries=2,
|
|
134
|
+
)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Error Handling
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
from dobby_sdk import DobbyAuthError, DobbyRateLimitError, DobbyBudgetExceededError
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
response = client.chat.completions.create(...)
|
|
144
|
+
except DobbyAuthError:
|
|
145
|
+
print("Invalid or expired API key")
|
|
146
|
+
except DobbyRateLimitError as e:
|
|
147
|
+
print(f"Rate limited. Retry after: {e.retry_after}s")
|
|
148
|
+
except DobbyBudgetExceededError:
|
|
149
|
+
print("Organization budget limit reached")
|
|
150
|
+
```
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Dobby SDK for Python
|
|
2
|
+
|
|
3
|
+
Official Python SDK for the [Dobby AI Platform](https://dobby-ai.com) — Home for your AI agents.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install dobby-ai-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from dobby_sdk import DobbyClient
|
|
15
|
+
|
|
16
|
+
client = DobbyClient(api_key="gk_user_...")
|
|
17
|
+
|
|
18
|
+
# LLM calls (OpenAI-compatible)
|
|
19
|
+
response = client.chat.completions.create(
|
|
20
|
+
model="claude-sonnet-4-20250514",
|
|
21
|
+
messages=[{"role": "user", "content": "Hello from Dobby!"}],
|
|
22
|
+
)
|
|
23
|
+
print(response.choices[0].message.content)
|
|
24
|
+
|
|
25
|
+
# Streaming
|
|
26
|
+
stream = client.chat.completions.create(
|
|
27
|
+
model="gpt-4o-mini",
|
|
28
|
+
messages=[{"role": "user", "content": "Explain AI agents"}],
|
|
29
|
+
stream=True,
|
|
30
|
+
)
|
|
31
|
+
for chunk in stream:
|
|
32
|
+
print(chunk.choices[0].delta.content or "", end="")
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Task Management
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
# Create a task
|
|
39
|
+
task = client.tasks.create(
|
|
40
|
+
title="Review PR #42",
|
|
41
|
+
priority="high",
|
|
42
|
+
agent_name="dobby-code-reviewer-agent",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# List pending tasks
|
|
46
|
+
tasks = client.tasks.list(status="pending")
|
|
47
|
+
|
|
48
|
+
# Approve a task (HITL)
|
|
49
|
+
client.approvals.approve(task["id"], comment="Looks good!")
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Agent Fleet
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
# List all agents
|
|
56
|
+
agents = client.agents.list()
|
|
57
|
+
|
|
58
|
+
# Register an external agent
|
|
59
|
+
agent = client.agents.register(
|
|
60
|
+
display_name="Research Agent",
|
|
61
|
+
framework="crewai",
|
|
62
|
+
protocol="a2a",
|
|
63
|
+
endpoint_url="https://my-agent.example.com",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Pause/resume
|
|
67
|
+
client.agents.pause("agent_abc123")
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Cost Tracking
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
# Organization cost summary
|
|
74
|
+
costs = client.costs.summary(period="30d")
|
|
75
|
+
|
|
76
|
+
# Per-agent breakdown
|
|
77
|
+
agent_costs = client.costs.by_agent(period="7d")
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Async Support
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from dobby_sdk import AsyncDobbyClient
|
|
84
|
+
|
|
85
|
+
async with AsyncDobbyClient(api_key="gk_user_...") as client:
|
|
86
|
+
response = await client.chat.completions.create(
|
|
87
|
+
model="claude-sonnet-4-20250514",
|
|
88
|
+
messages=[{"role": "user", "content": "Hello async!"}],
|
|
89
|
+
)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Configuration
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
client = DobbyClient(
|
|
96
|
+
api_key="gk_user_...", # or DOBBY_API_KEY env var
|
|
97
|
+
base_url="https://dobby-ai.com", # or DOBBY_BASE_URL
|
|
98
|
+
org_id="org_...", # or DOBBY_ORG_ID
|
|
99
|
+
tenant_id="tenant_...", # or DOBBY_TENANT_ID
|
|
100
|
+
timeout=120.0,
|
|
101
|
+
max_retries=2,
|
|
102
|
+
)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Error Handling
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from dobby_sdk import DobbyAuthError, DobbyRateLimitError, DobbyBudgetExceededError
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
response = client.chat.completions.create(...)
|
|
112
|
+
except DobbyAuthError:
|
|
113
|
+
print("Invalid or expired API key")
|
|
114
|
+
except DobbyRateLimitError as e:
|
|
115
|
+
print(f"Rate limited. Retry after: {e.retry_after}s")
|
|
116
|
+
except DobbyBudgetExceededError:
|
|
117
|
+
print("Organization budget limit reached")
|
|
118
|
+
```
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "dobby-ai-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Python SDK for the Dobby AI Platform — Home for your AI agents"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Dobby AI", email = "dev@dobby-ai.com" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["ai", "agents", "llm", "gateway", "dobby", "a2a", "mcp"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.9",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
26
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"openai>=1.0.0",
|
|
30
|
+
"httpx>=0.25.0",
|
|
31
|
+
"pydantic>=2.0.0",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.optional-dependencies]
|
|
35
|
+
dev = [
|
|
36
|
+
"pytest>=7.0.0",
|
|
37
|
+
"pytest-asyncio>=0.23.0",
|
|
38
|
+
"pytest-httpx>=0.30.0",
|
|
39
|
+
"ruff>=0.3.0",
|
|
40
|
+
"mypy>=1.8.0",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[project.urls]
|
|
44
|
+
Homepage = "https://dobby-ai.com"
|
|
45
|
+
Documentation = "https://dobby-ai.com/docs/sdk"
|
|
46
|
+
Repository = "https://github.com/gil-dobby/dobby-sdk-python"
|
|
47
|
+
|
|
48
|
+
[tool.hatch.build.targets.wheel]
|
|
49
|
+
packages = ["src/dobby_sdk"]
|
|
50
|
+
|
|
51
|
+
[tool.ruff]
|
|
52
|
+
line-length = 100
|
|
53
|
+
target-version = "py39"
|
|
54
|
+
|
|
55
|
+
[tool.ruff.lint]
|
|
56
|
+
select = ["E", "F", "I", "N", "W", "UP"]
|
|
57
|
+
|
|
58
|
+
[tool.mypy]
|
|
59
|
+
strict = true
|
|
60
|
+
python_version = "3.9"
|
|
61
|
+
|
|
62
|
+
[tool.pytest.ini_options]
|
|
63
|
+
asyncio_mode = "auto"
|
|
64
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dobby AI SDK — Official Python client for the Dobby AI Platform.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
from dobby_sdk import DobbyClient
|
|
6
|
+
|
|
7
|
+
client = DobbyClient(api_key="gk_user_...")
|
|
8
|
+
|
|
9
|
+
# LLM calls (OpenAI-compatible)
|
|
10
|
+
response = client.chat.completions.create(
|
|
11
|
+
model="claude-sonnet-4-20250514",
|
|
12
|
+
messages=[{"role": "user", "content": "Hello"}]
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
# Task management
|
|
16
|
+
task = client.tasks.create(title="Review PR #42", priority="high")
|
|
17
|
+
|
|
18
|
+
# Agent operations
|
|
19
|
+
agents = client.agents.list()
|
|
20
|
+
|
|
21
|
+
# Approvals
|
|
22
|
+
pending = client.approvals.list(status="pending")
|
|
23
|
+
|
|
24
|
+
# Cost tracking
|
|
25
|
+
costs = client.costs.summary(period="30d")
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from dobby_sdk._config import DobbyConfig
|
|
29
|
+
from dobby_sdk._exceptions import (
|
|
30
|
+
DobbyAuthError,
|
|
31
|
+
DobbyBudgetExceededError,
|
|
32
|
+
DobbyError,
|
|
33
|
+
DobbyNotFoundError,
|
|
34
|
+
DobbyRateLimitError,
|
|
35
|
+
DobbyServerError,
|
|
36
|
+
)
|
|
37
|
+
from dobby_sdk.client import AsyncDobbyClient, DobbyClient
|
|
38
|
+
|
|
39
|
+
__version__ = "0.1.0"
|
|
40
|
+
__all__ = [
|
|
41
|
+
"DobbyClient",
|
|
42
|
+
"AsyncDobbyClient",
|
|
43
|
+
"DobbyConfig",
|
|
44
|
+
"DobbyError",
|
|
45
|
+
"DobbyAuthError",
|
|
46
|
+
"DobbyRateLimitError",
|
|
47
|
+
"DobbyBudgetExceededError",
|
|
48
|
+
"DobbyNotFoundError",
|
|
49
|
+
"DobbyServerError",
|
|
50
|
+
]
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Base HTTP client for Dobby API calls."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from dobby_sdk._config import DobbyConfig
|
|
11
|
+
from dobby_sdk._exceptions import raise_for_status
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BaseResource:
|
|
15
|
+
"""Base class for all API resource namespaces."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, config: DobbyConfig, http_client: httpx.Client) -> None:
|
|
18
|
+
self._config = config
|
|
19
|
+
self._http = http_client
|
|
20
|
+
|
|
21
|
+
def _request(
|
|
22
|
+
self,
|
|
23
|
+
method: str,
|
|
24
|
+
path: str,
|
|
25
|
+
*,
|
|
26
|
+
json: dict[str, Any] | None = None,
|
|
27
|
+
params: dict[str, Any] | None = None,
|
|
28
|
+
) -> dict[str, Any]:
|
|
29
|
+
"""Make an authenticated API request."""
|
|
30
|
+
url = f"{self._config.api_url}{path}"
|
|
31
|
+
headers = {
|
|
32
|
+
**self._config.auth_headers,
|
|
33
|
+
"X-Request-Id": str(uuid.uuid4()),
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
response = self._http.request(
|
|
37
|
+
method=method,
|
|
38
|
+
url=url,
|
|
39
|
+
json=json,
|
|
40
|
+
params=params,
|
|
41
|
+
headers=headers,
|
|
42
|
+
timeout=self._config.timeout,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
body = response.json() if response.content else {}
|
|
46
|
+
raise_for_status(response.status_code, body)
|
|
47
|
+
return body
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class AsyncBaseResource:
|
|
51
|
+
"""Async version of BaseResource."""
|
|
52
|
+
|
|
53
|
+
def __init__(self, config: DobbyConfig, http_client: httpx.AsyncClient) -> None:
|
|
54
|
+
self._config = config
|
|
55
|
+
self._http = http_client
|
|
56
|
+
|
|
57
|
+
async def _request(
|
|
58
|
+
self,
|
|
59
|
+
method: str,
|
|
60
|
+
path: str,
|
|
61
|
+
*,
|
|
62
|
+
json: dict[str, Any] | None = None,
|
|
63
|
+
params: dict[str, Any] | None = None,
|
|
64
|
+
) -> dict[str, Any]:
|
|
65
|
+
"""Make an authenticated async API request."""
|
|
66
|
+
url = f"{self._config.api_url}{path}"
|
|
67
|
+
headers = {
|
|
68
|
+
**self._config.auth_headers,
|
|
69
|
+
"X-Request-Id": str(uuid.uuid4()),
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
response = await self._http.request(
|
|
73
|
+
method=method,
|
|
74
|
+
url=url,
|
|
75
|
+
json=json,
|
|
76
|
+
params=params,
|
|
77
|
+
headers=headers,
|
|
78
|
+
timeout=self._config.timeout,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
body = response.json() if response.content else {}
|
|
82
|
+
raise_for_status(response.status_code, body)
|
|
83
|
+
return body
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""SDK configuration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class DobbyConfig:
|
|
11
|
+
"""Configuration for the Dobby SDK client."""
|
|
12
|
+
|
|
13
|
+
api_key: str = field(default_factory=lambda: os.environ.get("DOBBY_API_KEY", ""))
|
|
14
|
+
base_url: str = field(
|
|
15
|
+
default_factory=lambda: os.environ.get("DOBBY_BASE_URL", "https://dobby-ai.com")
|
|
16
|
+
)
|
|
17
|
+
org_id: str | None = field(default_factory=lambda: os.environ.get("DOBBY_ORG_ID"))
|
|
18
|
+
tenant_id: str | None = field(default_factory=lambda: os.environ.get("DOBBY_TENANT_ID"))
|
|
19
|
+
timeout: float = 120.0
|
|
20
|
+
max_retries: int = 2
|
|
21
|
+
|
|
22
|
+
def __post_init__(self) -> None:
|
|
23
|
+
if not self.api_key:
|
|
24
|
+
raise ValueError(
|
|
25
|
+
"API key is required. Pass api_key= or set DOBBY_API_KEY environment variable."
|
|
26
|
+
)
|
|
27
|
+
self.base_url = self.base_url.rstrip("/")
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def gateway_url(self) -> str:
|
|
31
|
+
"""Base URL for gateway API calls."""
|
|
32
|
+
return f"{self.base_url}/api/v1/gateway"
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def api_url(self) -> str:
|
|
36
|
+
"""Base URL for platform API calls."""
|
|
37
|
+
return f"{self.base_url}/api/v1"
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def auth_headers(self) -> dict[str, str]:
|
|
41
|
+
"""Headers required for all API calls."""
|
|
42
|
+
headers: dict[str, str] = {
|
|
43
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
44
|
+
"User-Agent": "dobby-sdk-python/0.1.0",
|
|
45
|
+
}
|
|
46
|
+
if self.org_id:
|
|
47
|
+
headers["X-Org-Id"] = self.org_id
|
|
48
|
+
if self.tenant_id:
|
|
49
|
+
headers["X-Tenant-Id"] = self.tenant_id
|
|
50
|
+
return headers
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""SDK exception hierarchy."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DobbyError(Exception):
|
|
9
|
+
"""Base exception for all Dobby SDK errors."""
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
message: str,
|
|
14
|
+
status_code: int | None = None,
|
|
15
|
+
body: dict[str, Any] | None = None,
|
|
16
|
+
) -> None:
|
|
17
|
+
super().__init__(message)
|
|
18
|
+
self.status_code = status_code
|
|
19
|
+
self.body = body or {}
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def error_code(self) -> str | None:
|
|
23
|
+
return self.body.get("error")
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def error_message(self) -> str | None:
|
|
27
|
+
return self.body.get("message")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class DobbyAuthError(DobbyError):
|
|
31
|
+
"""Authentication failed — invalid, expired, or revoked API key."""
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class DobbyRateLimitError(DobbyError):
|
|
36
|
+
"""Rate limit exceeded for this API key tier."""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
message: str,
|
|
41
|
+
retry_after: float | None = None,
|
|
42
|
+
**kwargs: object,
|
|
43
|
+
):
|
|
44
|
+
super().__init__(message, **kwargs) # type: ignore[arg-type]
|
|
45
|
+
self.retry_after = retry_after
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class DobbyBudgetExceededError(DobbyError):
|
|
49
|
+
"""Organization or tenant budget limit reached."""
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class DobbyNotFoundError(DobbyError):
|
|
54
|
+
"""Requested resource not found."""
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class DobbyServerError(DobbyError):
|
|
59
|
+
"""Server-side error (5xx)."""
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def raise_for_status(status_code: int, body: dict[str, Any]) -> None:
|
|
64
|
+
"""Raise appropriate exception based on HTTP status code."""
|
|
65
|
+
if status_code < 400:
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
message = body.get("message") or body.get("error") or f"HTTP {status_code}"
|
|
69
|
+
|
|
70
|
+
if status_code == 401:
|
|
71
|
+
raise DobbyAuthError(message, status_code=status_code, body=body)
|
|
72
|
+
elif status_code == 403:
|
|
73
|
+
error_code = body.get("error", "")
|
|
74
|
+
if "budget" in error_code.lower() or "budget" in message.lower():
|
|
75
|
+
raise DobbyBudgetExceededError(message, status_code=status_code, body=body)
|
|
76
|
+
raise DobbyAuthError(message, status_code=status_code, body=body)
|
|
77
|
+
elif status_code == 404:
|
|
78
|
+
raise DobbyNotFoundError(message, status_code=status_code, body=body)
|
|
79
|
+
elif status_code == 429:
|
|
80
|
+
raise DobbyRateLimitError(message, status_code=status_code, body=body)
|
|
81
|
+
elif status_code >= 500:
|
|
82
|
+
raise DobbyServerError(message, status_code=status_code, body=body)
|
|
83
|
+
else:
|
|
84
|
+
raise DobbyError(message, status_code=status_code, body=body)
|